From 656e00d1823c28a24f9704f1a2f13b6e8e7647a4 Mon Sep 17 00:00:00 2001 From: Andrey Kolkov Date: Wed, 24 Dec 2025 17:37:34 +0400 Subject: [PATCH 001/889] ci: add used gemini action for changelog generations Signed-off-by: Andrey Kolkov --- .github/workflows/tags.yaml | 156 ++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) diff --git a/.github/workflows/tags.yaml b/.github/workflows/tags.yaml index 80100f23..89e14c3d 100644 --- a/.github/workflows/tags.yaml +++ b/.github/workflows/tags.yaml @@ -239,3 +239,159 @@ jobs: } else { console.log(`PR already exists from ${head} to ${base}`); } + + generate-changelog: + name: Generate Changelog + runs-on: [self-hosted] + needs: [prepare-release] + permissions: + contents: write + pull-requests: write + if: needs.prepare-release.result == 'success' + steps: + - name: Parse tag and get base branch + id: tag + uses: actions/github-script@v7 + with: + script: | + const ref = context.ref.replace('refs/tags/', ''); + const m = ref.match(/^v(\d+\.\d+\.\d+)(-(?:alpha|beta|rc)\.\d+)?$/); + if (!m) { + core.setFailed(`❌ tag '${ref}' must match 'vX.Y.Z' or 'vX.Y.Z-(alpha|beta|rc).N'`); + return; + } + const version = m[1] + (m[2] ?? ''); + const [maj, min] = m[1].split('.'); + + // Determine base branch: if patch release (Z > 0), use release-X.Y, else use main + const patch = parseInt(m[1].split('.')[2]); + const baseBranch = patch > 0 ? `release-${maj}.${min}` : 'main'; + + core.setOutput('version', version); + core.setOutput('tag', ref); + core.setOutput('base_branch', baseBranch); + + - name: Checkout main branch + uses: actions/checkout@v4 + with: + ref: main + fetch-depth: 0 + fetch-tags: true + token: ${{ secrets.GH_PAT }} + + - name: Check if changelog already exists + id: check_changelog + run: | + CHANGELOG_FILE="docs/changelogs/v${{ steps.tag.outputs.version }}.md" + if [ -f "$CHANGELOG_FILE" ]; then + echo "exists=true" >> $GITHUB_OUTPUT + echo "Changelog file $CHANGELOG_FILE already exists" + else + echo "exists=false" >> $GITHUB_OUTPUT + echo "Changelog file $CHANGELOG_FILE does not exist" + fi + + - name: Generate changelog using AI + if: steps.check_changelog.outputs.exists == 'false' + # Uses official run-gemini-cli action from GitHub Marketplace + # Requires GEMINI_API_KEY secret to be set in repository settings + # See: https://github.com/marketplace/actions/run-gemini-cli + uses: google-github-actions/run-gemini-cli@v0.1.18 + with: + prompt: "prepare changelog file for tagged release v${{ steps.tag.outputs.version }}, use @docs/agents/changelog.md for it. Create the changelog file at docs/changelogs/v${{ steps.tag.outputs.version }}.md" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} + + - name: Create changelog branch and commit + if: steps.check_changelog.outputs.exists == 'false' + env: + GH_PAT: ${{ secrets.GH_PAT }} + run: | + git config user.name "cozystack-bot" + git config user.email "217169706+cozystack-bot@users.noreply.github.com" + git remote set-url origin https://cozystack-bot:${GH_PAT}@github.com/${GITHUB_REPOSITORY} + + CHANGELOG_FILE="docs/changelogs/v${{ steps.tag.outputs.version }}.md" + CHANGELOG_BRANCH="changelog-v${{ steps.tag.outputs.version }}" + + if [ -f "$CHANGELOG_FILE" ]; then + # Fetch latest main branch + git fetch origin main + + # Delete local branch if it exists + git branch -D "$CHANGELOG_BRANCH" 2>/dev/null || true + + # Create and checkout new branch from main + git checkout -b "$CHANGELOG_BRANCH" origin/main + + # Add and commit changelog + git add "$CHANGELOG_FILE" + if git diff --staged --quiet; then + echo "⚠️ No changes to commit (file may already be committed)" + else + git commit -m "docs: add changelog for v${{ steps.tag.outputs.version }}" -s + echo "✅ Changelog committed to branch $CHANGELOG_BRANCH" + fi + + # Push the branch (force push to update if it exists) + git push -f origin "$CHANGELOG_BRANCH" + else + echo "⚠️ Changelog file was not generated" + exit 1 + fi + + - name: Create PR for changelog + if: steps.check_changelog.outputs.exists == 'false' + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GH_PAT }} + script: | + const version = '${{ steps.tag.outputs.version }}'; + const changelogBranch = `changelog-v${version}`; + const baseBranch = 'main'; + + // Check if PR already exists + const prs = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + head: `${context.repo.owner}:${changelogBranch}`, + base: baseBranch, + state: 'open' + }); + + if (prs.data.length > 0) { + const pr = prs.data[0]; + console.log(`PR #${pr.number} already exists for changelog branch ${changelogBranch}`); + + // Update PR body with latest info + const body = `This PR adds the changelog for release \`v${version}\`.\n\n✅ Changelog has been automatically generated in \`docs/changelogs/v${version}.md\`.`; + await github.rest.pulls.update({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: pr.number, + body: body + }); + console.log(`Updated existing PR #${pr.number}`); + } else { + // Create new PR + const pr = await github.rest.pulls.create({ + owner: context.repo.owner, + repo: context.repo.repo, + head: changelogBranch, + base: baseBranch, + title: `docs: add changelog for v${version}`, + body: `This PR adds the changelog for release \`v${version}\`.\n\n✅ Changelog has been automatically generated in \`docs/changelogs/v${version}.md\`.`, + draft: false + }); + + // Add label if needed + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.data.number, + labels: ['documentation', 'automated'] + }); + + console.log(`Created PR #${pr.data.number} for changelog`); + } From aa66b8c0d3a71e60905cf1637d992650e94b88ab 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 002/889] [lineage-webhook] Tolerate all taints Signed-off-by: nbykov0 <166552198+nbykov0@users.noreply.github.com> --- .../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 4e602fd55d9f50c1c9d9d7c89175f362eafb7bd2 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 003/889] [cozystack-api] Tolerate all taints Signed-off-by: nbykov0 <166552198+nbykov0@users.noreply.github.com> --- 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 d0bad07beef4b1b11e14451e14073dabd3e23335 Mon Sep 17 00:00:00 2001 From: majiayu000 <1835304752@qq.com> Date: Fri, 2 Jan 2026 04:17:56 +0800 Subject: [PATCH 004/889] docs: add Hubble network observability documentation and dashboards Add documentation explaining how to enable Hubble for network observability in Grafana. Include four pre-built Hubble dashboards (overview, dns-namespace, l7-http-metrics, network-overview) and register them in the monitoring hub's dashboard list. Closes #749 Signed-off-by: majiayu000 <1835304752@qq.com> --- dashboards/hubble/dns-namespace.json | 602 ++++ dashboards/hubble/l7-http-metrics.json | 1394 +++++++++ dashboards/hubble/network-overview.json | 1001 ++++++ dashboards/hubble/overview.json | 3357 +++++++++++++++++++++ docs/hubble-observability.md | 99 + packages/extra/monitoring/dashboards.list | 4 + 6 files changed, 6457 insertions(+) create mode 100644 dashboards/hubble/dns-namespace.json create mode 100644 dashboards/hubble/l7-http-metrics.json create mode 100644 dashboards/hubble/network-overview.json create mode 100644 dashboards/hubble/overview.json create mode 100644 docs/hubble-observability.md diff --git a/dashboards/hubble/dns-namespace.json b/dashboards/hubble/dns-namespace.json new file mode 100644 index 00000000..57f804cf --- /dev/null +++ b/dashboards/hubble/dns-namespace.json @@ -0,0 +1,602 @@ +{ + "__inputs": [ + { + "name": "DS_PROMETHEUS", + "label": "Prometheus", + "description": "", + "type": "datasource", + "pluginId": "prometheus", + "pluginName": "Prometheus" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "panel", + "id": "bargauge", + "name": "Bar gauge", + "version": "" + }, + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "9.4.7" + }, + { + "type": "datasource", + "id": "prometheus", + "name": "Prometheus", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "description": "", + "editable": true, + "fiscalYearStartMonth": 0, + "gnetId": 16612, + "graphTooltip": 0, + "id": null, + "links": [ + { + "asDropdown": true, + "icon": "external link", + "includeVars": true, + "keepTime": true, + "tags": [ + "cilium-overview" + ], + "targetBlank": false, + "title": "Cilium Overviews", + "tooltip": "", + "type": "dashboards", + "url": "" + }, + { + "asDropdown": true, + "icon": "external link", + "includeVars": false, + "keepTime": true, + "tags": [ + "hubble" + ], + "targetBlank": false, + "title": "Hubble", + "tooltip": "", + "type": "dashboards", + "url": "" + } + ], + "liveNow": false, + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 2, + "panels": [], + "title": "DNS", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 1 + }, + "id": 37, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(rate(hubble_dns_queries_total{cluster=~\"$cluster\", source_namespace=~\"$source_namespace\", destination_namespace=~\"$destination_namespace\"}[$__rate_interval])) by (source) > 0", + "legendFormat": "{{source}}", + "range": true, + "refId": "A" + } + ], + "title": "DNS queries", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 1 + }, + "id": 41, + "options": { + "displayMode": "gradient", + "minVizHeight": 10, + "minVizWidth": 0, + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showUnfilled": true + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "topk(10, sum(rate(hubble_dns_queries_total{cluster=~\"$cluster\", source_namespace=~\"$source_namespace\", destination_namespace=~\"$destination_namespace\"}[$__rate_interval])*60) by (query))", + "legendFormat": "{{query}}", + "range": true, + "refId": "A" + } + ], + "title": "Top 10 DNS queries", + "type": "bargauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 10 + }, + "id": 39, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "round(sum(rate(hubble_dns_queries_total{cluster=~\"$cluster\", source_namespace=~\"$source_namespace\", destination_namespace=~\"$destination_namespace\"}[$__rate_interval])) by (source) - sum(label_replace(sum(rate(hubble_dns_responses_total{cluster=~\"$cluster\", source_namespace=~\"$destination_namespace\", destination_namespace=~\"$source_namespace\"}[$__rate_interval])) by (destination), \"source\", \"$1\", \"destination\", \"(.*)\")) without (destination), 0.001) > 0", + "legendFormat": "{{source}}", + "range": true, + "refId": "A" + } + ], + "title": "Missing DNS responses", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 10 + }, + "id": 43, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(rate(hubble_dns_responses_total{cluster=~\"$cluster\", source_namespace=~\"$destination_namespace\", destination_namespace=~\"$source_namespace\", rcode!=\"No Error\"}[$__rate_interval])) by (destination, rcode) > 0", + "legendFormat": "{{destination}}: {{rcode}}", + "range": true, + "refId": "A" + } + ], + "title": "DNS errors", + "type": "timeseries" + } + ], + "refresh": "", + "revision": 1, + "schemaVersion": 38, + "style": "dark", + "tags": [ + "kubecon-demo" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "default", + "value": "default" + }, + "hide": 0, + "includeAll": false, + "label": "Data Source", + "multi": false, + "name": "DS_PROMETHEUS", + "options": [], + "query": "prometheus", + "queryValue": "", + "refresh": 1, + "regex": "(?!grafanacloud-usage|grafanacloud-ml-metrics).+", + "skipUrlSync": false, + "type": "datasource" + }, + { + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "definition": "label_values(cilium_version, cluster)", + "hide": 0, + "includeAll": true, + "multi": true, + "name": "cluster", + "options": [], + "query": { + "query": "label_values(cilium_version, cluster)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allValue": ".*", + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "definition": "label_values(source_namespace)", + "hide": 0, + "includeAll": true, + "label": "Source Namespace", + "multi": true, + "name": "source_namespace", + "options": [], + "query": { + "query": "label_values(source_namespace)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allValue": ".*", + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "definition": "label_values(destination_namespace)", + "hide": 0, + "includeAll": true, + "label": "Destination Namespace", + "multi": true, + "name": "destination_namespace", + "options": [], + "query": { + "query": "label_values(destination_namespace)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "", + "title": "Hubble / DNS Overview (Namespace)", + "uid": "_f0DUpY4k", + "version": 26, + "weekStart": "" + } + \ No newline at end of file diff --git a/dashboards/hubble/l7-http-metrics.json b/dashboards/hubble/l7-http-metrics.json new file mode 100644 index 00000000..b21004a6 --- /dev/null +++ b/dashboards/hubble/l7-http-metrics.json @@ -0,0 +1,1394 @@ +{ + "__inputs": [ + { + "name": "DS_PROMETHEUS", + "label": "prometheus", + "description": "", + "type": "datasource", + "pluginId": "prometheus", + "pluginName": "Prometheus" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "9.4.7" + }, + { + "type": "datasource", + "id": "prometheus", + "name": "Prometheus", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "stat", + "name": "Stat", + "version": "" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 14, + "panels": [], + "title": "General", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 8, + "x": 0, + "y": 1 + }, + "id": 16, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "round(sum(rate(hubble_http_requests_total{reporter=~\"${reporter}\", destination_namespace=~\"${destination_namespace}\", destination_workload=~\"${destination_workload}\"}[$__rate_interval])), 0.001)", + "refId": "A" + } + ], + "title": "Incoming Request Volume", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 8, + "x": 8, + "y": 1 + }, + "id": 17, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(rate(hubble_http_requests_total{cluster=~\"${cluster}\", destination_namespace=~\"${destination_namespace}\", destination_workload=~\"${destination_workload}\", reporter=\"${reporter}\", status!~\"5.*\"}[$__rate_interval]))\n/\nsum(rate(hubble_http_requests_total{cluster=~\"${cluster}\", destination_namespace=~\"${destination_namespace}\", destination_workload=~\"${destination_workload}\", reporter=\"${reporter}\"}[$__rate_interval]))", + "interval": "", + "legendFormat": "{{ cluster }} {{ method }} {{ source_namespace }}/{{ source_workload }}", + "range": true, + "refId": "A" + } + ], + "title": "Incoming Request Success Rate (non-5xx responses)", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "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": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 8, + "x": 16, + "y": 1 + }, + "id": 18, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "9.0.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "histogram_quantile(0.50, sum(rate(hubble_http_request_duration_seconds_bucket{cluster=~\"${cluster}\", destination_namespace=~\"${destination_namespace}\", destination_workload=~\"${destination_workload}\", reporter=\"${reporter}\"}[$__rate_interval])) by (le))", + "interval": "", + "legendFormat": "P50", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "histogram_quantile(0.95, sum(rate(hubble_http_request_duration_seconds_bucket{cluster=~\"${cluster}\", destination_namespace=~\"${destination_namespace}\", destination_workload=~\"${destination_workload}\", reporter=\"${reporter}\"}[$__rate_interval])) by (le))", + "hide": false, + "interval": "", + "legendFormat": "P95", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "histogram_quantile(0.99, sum(rate(hubble_http_request_duration_seconds_bucket{cluster=~\"${cluster}\", destination_namespace=~\"${destination_namespace}\", destination_workload=~\"${destination_workload}\", reporter=\"${reporter}\"}[$__rate_interval])) by (le))", + "hide": false, + "interval": "", + "legendFormat": "P99", + "range": true, + "refId": "C" + } + ], + "title": "Request Duration", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 6 + }, + "id": 6, + "panels": [], + "title": "Requests by Source", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "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" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 7 + }, + "id": 3, + "options": { + "legend": { + "calcs": [ + "max", + "mean", + "sum", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "round(sum(rate(hubble_http_requests_total{cluster=~\"${cluster}\", destination_namespace=~\"${destination_namespace}\", destination_workload=~\"${destination_workload}\", reporter=\"${reporter}\", source_namespace=~\"${source_namespace}\", source_workload=~\"${source_workload}\"}[$__rate_interval])) by (cluster, source_namespace, source_workload, status), 0.001)", + "interval": "", + "legendFormat": "{{ cluster }} {{ method }} {{ source_namespace }}/{{ source_workload }}: {{ status }}", + "range": true, + "refId": "A" + } + ], + "title": "Incoming Requests by Source and Response Code", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "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": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 7 + }, + "id": 7, + "options": { + "legend": { + "calcs": [ + "mean", + "min", + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(rate(hubble_http_requests_total{cluster=~\"${cluster}\", destination_namespace=~\"${destination_namespace}\", destination_workload=~\"${destination_workload}\", reporter=\"${reporter}\", source_namespace=~\"${source_namespace}\", source_workload=~\"${source_workload}\",status!~\"5.*\"}[$__rate_interval])) by (cluster, source_namespace, source_workload)\n/\nsum(rate(hubble_http_requests_total{cluster=~\"${cluster}\", destination_namespace=~\"${destination_namespace}\", destination_workload=~\"${destination_workload}\", reporter=\"${reporter}\", source_namespace=~\"${source_namespace}\", source_workload=~\"${source_workload}\"}[$__rate_interval])) by (cluster, source_namespace, source_workload)", + "interval": "", + "legendFormat": "{{ cluster }} {{ method }} {{ source_namespace }}/{{ source_workload }}", + "range": true, + "refId": "A" + } + ], + "title": "Incoming Request Success Rate (non-5xx responses) By Source", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "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": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 17 + }, + "id": 4, + "options": { + "legend": { + "calcs": [ + "min", + "max", + "mean", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": true, + "expr": "histogram_quantile(0.50, sum(rate(hubble_http_request_duration_seconds_bucket{cluster=~\"${cluster}\", destination_namespace=~\"${destination_namespace}\", destination_workload=~\"${destination_workload}\", reporter=\"${reporter}\", source_namespace=~\"${source_namespace}\", source_workload=~\"${source_workload}\"}[$__rate_interval])) by (cluster, source_namespace, source_workload, le))", + "interval": "", + "legendFormat": "{{ cluster }} {{ source_namespace }}/{{ source_workload }} P50", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": true, + "expr": "histogram_quantile(0.95, sum(rate(hubble_http_request_duration_seconds_bucket{cluster=~\"${cluster}\", destination_namespace=~\"${destination_namespace}\", destination_workload=~\"${destination_workload}\", reporter=\"${reporter}\", source_namespace=~\"${source_namespace}\", source_workload=~\"${source_workload}\"}[$__rate_interval])) by (cluster, source_namespace, source_workload, le))", + "hide": false, + "interval": "", + "legendFormat": "{{ cluster }} {{ source_namespace }}/{{ source_workload }} P95", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": true, + "expr": "histogram_quantile(0.99, sum(rate(hubble_http_request_duration_seconds_bucket{cluster=~\"${cluster}\", destination_namespace=~\"${destination_namespace}\", destination_workload=~\"${destination_workload}\", reporter=\"${reporter}\", source_namespace=~\"${source_namespace}\", source_workload=~\"${source_workload}\"}[$__rate_interval])) by (cluster, source_namespace, source_workload, le))", + "hide": false, + "interval": "", + "legendFormat": "{{ cluster }} {{ source_namespace }}/{{ source_workload }} P99", + "range": true, + "refId": "C" + } + ], + "title": "HTTP Request Duration by Source", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 17 + }, + "id": 20, + "options": { + "legend": { + "calcs": [ + "min", + "max", + "mean", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(\n node_namespace_pod_container:container_cpu_usage_seconds_total:sum_irate{cluster=~\"${cluster}\", namespace=~\"${destination_namespace}\"}\n * on(namespace,pod)\n group_left(workload, workload_type) namespace_workload_pod:kube_pod_owner:relabel{cluster=~\"${cluster}\", namespace=~\"${destination_namespace}\", workload=~\"${source_workload}\"}\n) by (namespace, workload)", + "interval": "", + "legendFormat": "{{ cluster }} {{ namespace }}/{{ workload }}", + "range": true, + "refId": "A" + } + ], + "title": "CPU Usage by Source", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 27 + }, + "id": 9, + "panels": [], + "title": "Requests by Destination", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "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" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 28 + }, + "id": 10, + "options": { + "legend": { + "calcs": [ + "max", + "mean", + "sum", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "round(sum(rate(hubble_http_requests_total{cluster=~\"${cluster}\", destination_namespace=~\"${destination_namespace}\", destination_workload=~\"${destination_workload}\", reporter=\"${reporter}\", source_namespace=~\"${source_namespace}\", source_workload=~\"${source_workload}\"}[$__rate_interval])) by (cluster, destination_namespace, destination_workload, status), 0.001)", + "interval": "", + "legendFormat": "{{ cluster }} {{ method }} {{ destination_namespace }}/{{ destination_workload }}: {{ status }}", + "range": true, + "refId": "A" + } + ], + "title": "Incoming Requests by Destination and Response Code", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "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": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 28 + }, + "id": 11, + "options": { + "legend": { + "calcs": [ + "mean", + "min", + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(rate(hubble_http_requests_total{cluster=~\"${cluster}\", destination_namespace=~\"${destination_namespace}\", destination_workload=~\"${destination_workload}\", reporter=\"${reporter}\", source_namespace=~\"${source_namespace}\", source_workload=~\"${source_workload}\",status!~\"5.*\"}[$__rate_interval])) by (cluster, destination_namespace, destination_workload)\n/\nsum(rate(hubble_http_requests_total{cluster=~\"${cluster}\", destination_namespace=~\"${destination_namespace}\", destination_workload=~\"${destination_workload}\", reporter=\"${reporter}\", source_namespace=~\"${source_namespace}\", source_workload=~\"${source_workload}\"}[$__rate_interval])) by (cluster, destination_namespace, destination_workload)", + "interval": "", + "legendFormat": "{{ cluster }} {{ method }} {{ destination_namespace }}/{{ destination_workload }}", + "range": true, + "refId": "A" + } + ], + "title": "Incoming Request Success Rate (non-5xx responses) By Destination", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 38 + }, + "id": 12, + "options": { + "legend": { + "calcs": [ + "min", + "max", + "mean", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": true, + "expr": "histogram_quantile(0.50, sum(rate(hubble_http_request_duration_seconds_bucket{cluster=~\"${cluster}\", destination_namespace=~\"${destination_namespace}\", destination_workload=~\"${destination_workload}\", reporter=\"${reporter}\", source_namespace=~\"${source_namespace}\", source_workload=~\"${source_workload}\"}[$__rate_interval])) by (cluster, destination_namespace, destination_workload, le))", + "interval": "", + "legendFormat": "{{ cluster }} {{ destination_namespace }}/{{ destination_workload }} P50", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": true, + "expr": "histogram_quantile(0.95, sum(rate(hubble_http_request_duration_seconds_bucket{cluster=~\"${cluster}\", destination_namespace=~\"${destination_namespace}\", destination_workload=~\"${destination_workload}\", reporter=\"${reporter}\", source_namespace=~\"${source_namespace}\", source_workload=~\"${source_workload}\"}[$__rate_interval])) by (cluster, destination_namespace, destination_workload, le))", + "hide": false, + "interval": "", + "legendFormat": "{{ cluster }} {{ destination_namespace }}/{{ destination_workload }} P95", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": true, + "expr": "histogram_quantile(0.99, sum(rate(hubble_http_request_duration_seconds_bucket{cluster=~\"${cluster}\", destination_namespace=~\"${destination_namespace}\", destination_workload=~\"${destination_workload}\", reporter=\"${reporter}\", source_namespace=~\"${source_namespace}\", source_workload=~\"${source_workload}\"}[$__rate_interval])) by (cluster, destination_namespace, destination_workload, le))", + "hide": false, + "interval": "", + "legendFormat": "{{ cluster }} {{ destination_namespace }}/{{ destination_workload }} P99", + "range": true, + "refId": "C" + } + ], + "title": "HTTP Request Duration by Destination", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 38 + }, + "id": 19, + "options": { + "legend": { + "calcs": [ + "min", + "max", + "mean", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(\n node_namespace_pod_container:container_cpu_usage_seconds_total:sum_irate{cluster=~\"${cluster}\", namespace=~\"${destination_namespace}\"}\n * on(namespace,pod)\n group_left(workload, workload_type) namespace_workload_pod:kube_pod_owner:relabel{cluster=~\"${cluster}\", namespace=~\"${destination_namespace}\", workload=\"${destination_workload}\"}\n) by (namespace, workload)", + "interval": "", + "legendFormat": "{{ cluster }} {{ namespace }}/{{ workload }}", + "range": true, + "refId": "A" + } + ], + "title": "CPU Usage by Destination", + "type": "timeseries" + } + ], + "refresh": "30s", + "revision": 1, + "schemaVersion": 38, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": {}, + "hide": 0, + "includeAll": false, + "label": "Prometheus", + "multi": false, + "name": "DS_PROMETHEUS", + "options": [], + "query": "prometheus", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "definition": "label_values(hubble_http_requests_total, cluster)", + "hide": 0, + "includeAll": false, + "label": "Cluster", + "multi": false, + "name": "cluster", + "options": [], + "query": { + "query": "label_values(hubble_http_requests_total, cluster)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 2, + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "definition": "label_values(hubble_http_requests_total{cluster=~\"${cluster}\"}, destination_namespace)", + "description": "", + "hide": 0, + "includeAll": false, + "label": "Destination Namespace", + "multi": false, + "name": "destination_namespace", + "options": [], + "query": { + "query": "label_values(hubble_http_requests_total{cluster=~\"${cluster}\"}, destination_namespace)", + "refId": "StandardVariableQuery" + }, + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 2, + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "definition": "label_values(hubble_http_requests_total{cluster=~\"${cluster}\", destination_namespace=~\"${destination_namespace}\"}, destination_workload)", + "hide": 0, + "includeAll": false, + "label": "Destination Workload", + "multi": false, + "name": "destination_workload", + "options": [], + "query": { + "query": "label_values(hubble_http_requests_total{cluster=~\"${cluster}\", destination_namespace=~\"${destination_namespace}\"}, destination_workload)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 2, + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "definition": "label_values(hubble_http_requests_total, reporter)", + "hide": 0, + "includeAll": false, + "label": "Reporter", + "multi": false, + "name": "reporter", + "options": [], + "query": { + "query": "label_values(hubble_http_requests_total, reporter)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "definition": "label_values(hubble_http_requests_total{cluster=~\"${cluster}\", destination_namespace=~\"${destination_namespace}\", destination_workload=~\"${destination_workload}\"}, source_namespace)", + "hide": 0, + "includeAll": true, + "label": "Source Namespace", + "multi": true, + "name": "source_namespace", + "options": [], + "query": { + "query": "label_values(hubble_http_requests_total{cluster=~\"${cluster}\", destination_namespace=~\"${destination_namespace}\", destination_workload=~\"${destination_workload}\"}, source_namespace)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "definition": "label_values(hubble_http_requests_total{cluster=~\"${cluster}\", destination_namespace=~\"${destination_namespace}\", destination_workload=~\"${destination_workload}\", source_namespace=~\"${source_namespace}\"}, source_workload)", + "hide": 0, + "includeAll": true, + "label": "Source Workload", + "multi": true, + "name": "source_workload", + "options": [], + "query": { + "query": "label_values(hubble_http_requests_total{cluster=~\"${cluster}\", destination_namespace=~\"${destination_namespace}\", destination_workload=~\"${destination_workload}\", source_namespace=~\"${source_namespace}\"}, source_workload)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Hubble L7 HTTP Metrics by Workload", + "uid": "3g264CZVz", + "version": 3, + "weekStart": "" +} diff --git a/dashboards/hubble/network-overview.json b/dashboards/hubble/network-overview.json new file mode 100644 index 00000000..cddb473d --- /dev/null +++ b/dashboards/hubble/network-overview.json @@ -0,0 +1,1001 @@ +{ + "__inputs": [ + { + "name": "DS_PROMETHEUS", + "label": "Prometheus", + "description": "", + "type": "datasource", + "pluginId": "prometheus", + "pluginName": "Prometheus" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "panel", + "id": "bargauge", + "name": "Bar gauge", + "version": "" + }, + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "9.4.7" + }, + { + "type": "datasource", + "id": "prometheus", + "name": "Prometheus", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "description": "", + "editable": true, + "fiscalYearStartMonth": 0, + "gnetId": 16612, + "graphTooltip": 0, + "id": null, + "links": [ + { + "asDropdown": true, + "icon": "external link", + "includeVars": true, + "keepTime": true, + "tags": [ + "cilium-overview" + ], + "targetBlank": false, + "title": "Cilium Overviews", + "tooltip": "", + "type": "dashboards", + "url": "" + }, + { + "asDropdown": true, + "icon": "external link", + "includeVars": false, + "keepTime": true, + "tags": [ + "hubble" + ], + "targetBlank": false, + "title": "Hubble", + "tooltip": "", + "type": "dashboards", + "url": "" + } + ], + "liveNow": false, + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 8, + "panels": [], + "title": "Flows processed", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 1 + }, + "id": 12, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(rate(hubble_flows_processed_total{cluster=~\"$cluster\", source_namespace=~\"$source_namespace\", destination_namespace=~\"$destination_namespace\"}[$__rate_interval])) by (type, subtype)", + "legendFormat": "{{type}}/{{subtype}}", + "range": true, + "refId": "A" + } + ], + "title": "Flows processed by type", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ops" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 1 + }, + "id": 35, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(rate(hubble_flows_processed_total{cluster=~\"$cluster\", source_namespace=~\"$source_namespace\", destination_namespace=~\"$destination_namespace\"}[$__rate_interval])) by (verdict)", + "legendFormat": "{{verdict}}", + "range": true, + "refId": "A" + } + ], + "title": "Flows processed by verdict", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "pps" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 10 + }, + "id": 36, + "options": { + "displayMode": "gradient", + "minVizHeight": 10, + "minVizWidth": 0, + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showUnfilled": true + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "topk(10, sum(rate(hubble_flows_processed_total{cluster=~\"$cluster\", source_namespace=~\"$source_namespace\", destination_namespace=~\"$destination_namespace\"}[$__rate_interval])) by (source))", + "legendFormat": "{{source}}", + "range": true, + "refId": "A" + } + ], + "title": "Top 10 sources", + "type": "bargauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "pps" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 10 + }, + "id": 37, + "options": { + "displayMode": "gradient", + "minVizHeight": 10, + "minVizWidth": 0, + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showUnfilled": true + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "topk(10, sum(rate(hubble_flows_processed_total{cluster=~\"$cluster\", source_namespace=~\"$source_namespace\", destination_namespace=~\"$destination_namespace\"}[$__rate_interval])) by (destination))", + "legendFormat": "{{source}}", + "range": true, + "refId": "A" + } + ], + "title": "Top 10 destinations", + "type": "bargauge" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 19 + }, + "id": 10, + "panels": [], + "title": "Connection drops", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "pps" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 20 + }, + "id": 23, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(rate(hubble_tcp_flags_total{cluster=~\"$cluster\", source_namespace=~\"$source_namespace\", destination_namespace=~\"$destination_namespace\", flag=\"SYN\"}[$__rate_interval])) by (source) - sum(label_replace(sum(rate(hubble_tcp_flags_total{cluster=~\"$cluster\", source_namespace=~\"$destination_namespace\", destination_namespace=~\"$source_namespace\", flag=\"SYN-ACK\"}[$__rate_interval])) by (destination), \"source\", \"$1\", \"destination\", \"(.*)\")) without (destination) > 0", + "hide": false, + "legendFormat": "{{source}}", + "range": true, + "refId": "A" + } + ], + "title": "Missing TCP SYN-ACKs", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "pps" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 20 + }, + "id": 34, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(rate(hubble_icmp_total{cluster=~\"$cluster\", source_namespace=~\"$source_namespace\", destination_namespace=~\"$destination_namespace\", type=\"EchoRequest\"}[$__rate_interval])) by (source) - sum(label_replace(sum(rate(hubble_icmp_total{cluster=~\"$cluster\", source_namespace=~\"$destination_namespace\", destination_namespace=~\"$source_namespace\", type=\"EchoReply\"}[$__rate_interval])) by (destination), \"source\", \"$1\", \"destination\", \"(.*)\")) without (destination) > 0", + "legendFormat": "{{source}}", + "range": true, + "refId": "A" + } + ], + "title": "Missing ICMP Echo Replys", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 29 + }, + "id": 6, + "panels": [], + "title": "Network Policy drops", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "pps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 30 + }, + "id": 29, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(rate(hubble_drop_total{cluster=~\"$cluster\", source_namespace=~\"$source_namespace\", destination_namespace=~\"$destination_namespace\"}[$__rate_interval])) by (source, reason) > 0", + "legendFormat": "{{source}}: {{reason}}", + "range": true, + "refId": "A" + } + ], + "title": "Network Policy drops by source", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "pps" + }, + "overrides": [ + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": [ + "kube-dns-7d44cdb5d5-g85vg: UNSUPPORTED_PROTOCOL_FOR_NAT_MASQUERADE" + ], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": false, + "viz": true + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 30 + }, + "id": 30, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(rate(hubble_drop_total{cluster=~\"$cluster\", source_namespace=~\"$source_namespace\", destination_namespace=~\"$destination_namespace\"}[$__rate_interval])) by (destination, reason) > 0", + "legendFormat": "{{destination}}: {{reason}}", + "range": true, + "refId": "A" + } + ], + "title": "Network Policy drops by destination", + "type": "timeseries" + } + ], + "refresh": "", + "revision": 1, + "schemaVersion": 38, + "style": "dark", + "tags": [ + "kubecon-demo" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "default", + "value": "default" + }, + "hide": 0, + "includeAll": false, + "label": "Data Source", + "multi": false, + "name": "DS_PROMETHEUS", + "options": [], + "query": "prometheus", + "queryValue": "", + "refresh": 1, + "regex": "(?!grafanacloud-usage|grafanacloud-ml-metrics).+", + "skipUrlSync": false, + "type": "datasource" + }, + { + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "definition": "label_values(cilium_version, cluster)", + "hide": 0, + "includeAll": true, + "multi": true, + "name": "cluster", + "options": [], + "query": { + "query": "label_values(cilium_version, cluster)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allValue": ".*", + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "definition": "label_values(source_namespace)", + "hide": 0, + "includeAll": true, + "label": "Source Namespace", + "multi": true, + "name": "source_namespace", + "options": [], + "query": { + "query": "label_values(source_namespace)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allValue": ".*", + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "definition": "label_values(destination_namespace)", + "hide": 0, + "includeAll": true, + "label": "Destination Namespace", + "multi": true, + "name": "destination_namespace", + "options": [], + "query": { + "query": "label_values(destination_namespace)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "", + "title": "Hubble / Network Overview (Namespace)", + "uid": "nlsO8tYVz", + "version": 18, + "weekStart": "" + } + \ No newline at end of file diff --git a/dashboards/hubble/overview.json b/dashboards/hubble/overview.json new file mode 100644 index 00000000..783aa131 --- /dev/null +++ b/dashboards/hubble/overview.json @@ -0,0 +1,3357 @@ +{ + "__inputs": [ + { + "name": "DS_PROMETHEUS", + "label": "prometheus", + "description": "", + "type": "datasource", + "pluginId": "prometheus", + "pluginName": "Prometheus" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 3, + "links": [], + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 14, + "panels": [], + "title": "General Processing", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fill": 1, + "gridPos": { + "h": 5, + "w": 12, + "x": 0, + "y": 1 + }, + "id": 12, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": {}, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "max", + "fillBelowTo": "avg", + "lines": false + }, + { + "alias": "avg", + "fill": 0, + "fillBelowTo": "min" + }, + { + "alias": "min", + "lines": false + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "avg(sum(rate(hubble_flows_processed_total[1m])) by (pod))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "avg", + "refId": "A" + }, + { + "expr": "min(sum(rate(hubble_flows_processed_total[1m])) by (pod))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "min", + "refId": "B" + }, + { + "expr": "max(sum(rate(hubble_flows_processed_total[1m])) by (pod))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "max", + "refId": "C" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Flows processed Per Node", + "tooltip": { + "shared": true, + "sort": 1, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ops", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fill": 1, + "gridPos": { + "h": 5, + "w": 12, + "x": 12, + "y": 1 + }, + "id": 32, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": {}, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(hubble_flows_processed_total[1m])) by (pod, type)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{type}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Flows Types", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ops", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fill": 1, + "gridPos": { + "h": 5, + "w": 12, + "x": 0, + "y": 6 + }, + "id": 59, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": {}, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(hubble_flows_processed_total{type=\"L7\"}[1m])) by (pod, subtype)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{subtype}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "L7 Flow Distribution", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ops", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fill": 1, + "gridPos": { + "h": 5, + "w": 12, + "x": 12, + "y": 6 + }, + "id": 60, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": {}, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(hubble_flows_processed_total{type=\"Trace\"}[1m])) by (pod, subtype)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{subtype}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Trace Flow Distribution", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ops", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 11 + }, + "id": 16, + "panels": [], + "title": "Network", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fill": 1, + "gridPos": { + "h": 5, + "w": 12, + "x": 0, + "y": 12 + }, + "id": 33, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": {}, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(hubble_flows_processed_total[1m])) by (pod, verdict)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{verdict}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Forwarded vs Dropped", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ops", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fill": 1, + "gridPos": { + "h": 5, + "w": 12, + "x": 12, + "y": 12 + }, + "id": 8, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": {}, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(hubble_drop_total[1m])) by (pod, reason)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{reason}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Drop Reason", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "pps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fill": 1, + "gridPos": { + "h": 5, + "w": 12, + "x": 0, + "y": 17 + }, + "id": 34, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": true, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": {}, + "percentage": true, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "sum (rate(hubble_port_distribution_total[1m])) by (pod, protocol)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{protocol}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Protocol Usage", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "pps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fill": 1, + "gridPos": { + "h": 5, + "w": 12, + "x": 12, + "y": 17 + }, + "id": 18, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": false, + "min": false, + "rightSide": true, + "show": true, + "sort": "current", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": {}, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "topk(10, sum (rate(hubble_port_distribution_total{port!=\"0\"}[1m])) by (pod, port, protocol))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{port}}/{{protocol}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Top 10 Port Distribution", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "pps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fill": 1, + "gridPos": { + "h": 5, + "w": 12, + "x": 0, + "y": 22 + }, + "id": 10, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": {}, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "fin", + "yaxis": 1 + }, + { + "alias": "FIN", + "yaxis": 2 + }, + { + "alias": "RST", + "yaxis": 2 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(hubble_tcp_flags_total{family=\"IPv4\"}[1m])) by (pod, flag)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{flag}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "TCPv4", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "pps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "alert": { + "conditions": [ + { + "evaluator": { + "params": [ + 0.2 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "B", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "avg" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "5m", + "frequency": "1m", + "handler": 1, + "name": "Missing TCP SYN-ACK", + "noDataState": "no_data", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fill": 1, + "gridPos": { + "h": 5, + "w": 12, + "x": 12, + "y": 22 + }, + "id": 62, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": {}, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "fin", + "yaxis": 1 + }, + { + "alias": "FIN", + "yaxis": 2 + }, + { + "alias": "RST", + "yaxis": 2 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(hubble_tcp_flags_total{family=\"IPv4\", flag=\"SYN\"}[1m])) by (pod) - sum(rate(hubble_tcp_flags_total{family=\"IPv4\", flag=\"SYN-ACK\"}[1m])) by (pod)", + "format": "time_series", + "hide": false, + "intervalFactor": 1, + "legendFormat": "Missing SYN-ACK", + "refId": "B" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "gt", + "value": 0.2 + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Missing TCPv4 SYN-ACKs", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "pps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fill": 1, + "gridPos": { + "h": 5, + "w": 12, + "x": 0, + "y": 27 + }, + "id": 35, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": {}, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "fin", + "yaxis": 1 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(hubble_tcp_flags_total{family=\"IPv6\"}[1m])) by (pod, flag)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{flag}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "TCPv6", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "pps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "alert": { + "conditions": [ + { + "evaluator": { + "params": [ + 0.2 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "B", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "avg" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "5m", + "frequency": "1m", + "handler": 1, + "name": "Missing TCPv6 SYN-ACKs alert", + "noDataState": "no_data", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fill": 1, + "gridPos": { + "h": 5, + "w": 12, + "x": 12, + "y": 27 + }, + "id": 63, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": {}, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "fin", + "yaxis": 1 + }, + { + "alias": "FIN", + "yaxis": 2 + }, + { + "alias": "RST", + "yaxis": 2 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(hubble_tcp_flags_total{family=\"IPv6\", flag=\"SYN\"}[1m])) by (pod) - sum(rate(hubble_tcp_flags_total{family=\"IPv6\", flag=\"SYN-ACK\"}[1m])) by (pod)", + "format": "time_series", + "hide": false, + "intervalFactor": 1, + "legendFormat": "Missing SYN-ACK", + "refId": "B" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "gt", + "value": 0.2 + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Missing TCPv6 SYN-ACKs", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "pps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fill": 1, + "gridPos": { + "h": 5, + "w": 12, + "x": 0, + "y": 32 + }, + "id": 31, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": {}, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(hubble_icmp_total{family=\"IPv4\"}[1m])) by (pod, type)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{type}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "ICMPv4", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "alert": { + "conditions": [ + { + "evaluator": { + "params": [ + 0.1 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "B", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "avg" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "5m", + "frequency": "1m", + "handler": 1, + "name": "Missing ICMPv4 Echo-Reply alert", + "noDataState": "no_data", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fill": 1, + "gridPos": { + "h": 5, + "w": 12, + "x": 12, + "y": 32 + }, + "id": 64, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": {}, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(hubble_icmp_total{family=\"IPv4\", type=\"EchoRequest\"}[1m])) by (pod) - sum(rate(hubble_icmp_total{family=\"IPv4\", type=\"EchoReply\"}[1m])) by (pod)", + "format": "time_series", + "hide": false, + "intervalFactor": 1, + "legendFormat": "Missing ICMP Echo-Reply", + "refId": "B" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "gt", + "value": 0.1 + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Missing ICMPv4 Echo-Reply", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "pps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fill": 1, + "gridPos": { + "h": 5, + "w": 12, + "x": 0, + "y": 37 + }, + "id": 36, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": {}, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(hubble_icmp_total{family=\"IPv6\"}[1m])) by (pod, type)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{type}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "ICMPv6", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fill": 1, + "gridPos": { + "h": 5, + "w": 12, + "x": 12, + "y": 37 + }, + "id": 65, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": {}, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(hubble_icmp_total{family=\"IPv6\", type=\"EchoRequest\"}[1m])) by (pod) - sum(rate(hubble_icmp_total{family=\"IPv6\", type=\"EchoReply\"}[1m])) by (pod)", + "format": "time_series", + "hide": false, + "intervalFactor": 1, + "legendFormat": "Missing ICMP Echo-Reply", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Missing ICMPv6 Echo-Reply", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "pps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 42 + }, + "id": 42, + "panels": [], + "title": "Network Policy", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fill": 1, + "gridPos": { + "h": 4, + "w": 12, + "x": 0, + "y": 43 + }, + "id": 43, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": {}, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(hubble_drop_total{reason=\"POLICY_DENIED\"}[1m])) by (pod, reason)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{reason}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Denies by Reason", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "pps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fill": 1, + "gridPos": { + "h": 4, + "w": 12, + "x": 12, + "y": 43 + }, + "id": 61, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": {}, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(hubble_drop_total{reason=\"POLICY_DENIED\"}[1m])) by (pod, protocol)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{protocol}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Denied Packets by Protocol", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "pps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fill": 1, + "gridPos": { + "h": 5, + "w": 12, + "x": 0, + "y": 47 + }, + "id": 55, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": {}, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "topk(10, sum(rate(hubble_drop_total{reason=\"POLICY_DENIED\"}[1m])) by (pod, source))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{source}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Top 10 Source Pods with Denied Packets", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "pps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fill": 1, + "gridPos": { + "h": 5, + "w": 12, + "x": 12, + "y": 47 + }, + "id": 54, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": {}, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "topk(10, sum(rate(hubble_drop_total{reason=\"POLICY_DENIED\"}[1m])) by (pod, destination))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{destination}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Top 10 Destination Pods with Denied Packets", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "pps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 52 + }, + "id": 47, + "panels": [], + "title": "HTTP", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fill": 1, + "gridPos": { + "h": 6, + "w": 12, + "x": 0, + "y": 53 + }, + "id": 45, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": {}, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(hubble_http_requests_total[1m])) by (pod, method)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{method}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "HTTP Requests", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": 1, + "format": "reqps", + "label": "", + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fill": 1, + "gridPos": { + "h": 6, + "w": 12, + "x": 12, + "y": 53 + }, + "id": 49, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": {}, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(hubble_http_responses_total[1m])) by (pod, status)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{status}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "HTTP responses", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": 1, + "format": "reqps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fill": 1, + "gridPos": { + "h": 5, + "w": 12, + "x": 0, + "y": 59 + }, + "id": 51, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": {}, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "histogram_quantile(0.5, rate(hubble_http_request_duration_seconds_bucket[1m]))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{method}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "HTTP Request/Response Latency (p50)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fill": 1, + "gridPos": { + "h": 5, + "w": 12, + "x": 12, + "y": 59 + }, + "id": 58, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": {}, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "histogram_quantile(0.99, rate(hubble_http_request_duration_seconds_bucket[1m]))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{method}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "HTTP Request/Response Latency (p99)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fill": 1, + "gridPos": { + "h": 5, + "w": 12, + "x": 0, + "y": 64 + }, + "id": 53, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": {}, + "percentage": true, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(hubble_http_requests_total[5m])) by (pod, protocol)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{protocol}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "HTTP Protocol Usage", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": 0, + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 69 + }, + "id": 6, + "panels": [], + "title": "DNS", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fill": 1, + "gridPos": { + "h": 5, + "w": 8, + "x": 0, + "y": 70 + }, + "id": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": {}, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(hubble_dns_queries_total[1m])) by (pod, qtypes)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{qtypes}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "DNS Requests", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "reqps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fill": 1, + "gridPos": { + "h": 5, + "w": 8, + "x": 8, + "y": 70 + }, + "id": 4, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": {}, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(hubble_dns_responses_total{rcode=\"No Error\"}[1m])) by (pod, qtypes)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{qtypes}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "DNS responses", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": 1, + "format": "reqps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "alert": { + "conditions": [ + { + "evaluator": { + "params": [ + 0.5 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "A", + "5m", + "now" + ] + }, + "reducer": { + "params": [], + "type": "avg" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "5m", + "frequency": "1m", + "handler": 1, + "name": "DNS Request/Response Symmetry alert", + "noDataState": "no_data", + "notifications": [] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fill": 1, + "gridPos": { + "h": 5, + "w": 8, + "x": 16, + "y": 70 + }, + "id": 66, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": {}, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(hubble_dns_queries_total[1m])) by (pod, qtypes) - sum(rate(hubble_dns_responses_total[1m])) by (pod, qtypes)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{qtypes}}", + "refId": "A" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "gt", + "value": 0.5 + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Missing DNS Responses", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "reqps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fill": 1, + "gridPos": { + "h": 5, + "w": 12, + "x": 0, + "y": 75 + }, + "id": 40, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": {}, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(hubble_dns_response_types_total[1m])) by (pod, type)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{type}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "DNS Response Record Type", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": 1, + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fill": 1, + "gridPos": { + "h": 5, + "w": 12, + "x": 12, + "y": 75 + }, + "id": 57, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": {}, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(hubble_dns_responses_total{rcode=\"No Error\"}[1m])) by (pod,ips_returned)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{ips_returned}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "DNS Response IPs Returned", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": 1, + "format": "reqps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fill": 1, + "gridPos": { + "h": 5, + "w": 12, + "x": 0, + "y": 80 + }, + "id": 28, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": {}, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(hubble_dns_responses_total{rcode!=\"No Error\"}[1m])) by (pod, qtypes, rcode)", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{rcode}} ({{qtypes}})", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "DNS Errors", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": 4, + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fill": 1, + "gridPos": { + "h": 5, + "w": 12, + "x": 12, + "y": 80 + }, + "id": 56, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "sideWidth": null, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": {}, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "topk(10,sum(rate(hubble_dns_responses_total{rcode!=\"No Error\"}[1m])) by (pod, destination))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{destination}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Pods with DNS errors", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": 4, + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fill": 1, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 85 + }, + "id": 20, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "max": false, + "min": false, + "rightSide": true, + "show": true, + "sort": "current", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": {}, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "topk(10, sum(rate(hubble_dns_queries_total[10m])*60) by (query, qtypes))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "{{query}} ({{qtypes}})", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Top 10 DNS Queries per minute", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": "30s", + "schemaVersion": 18, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": {}, + "hide": 0, + "includeAll": false, + "label": "Prometheus", + "multi": false, + "name": "DS_PROMETHEUS", + "options": [], + "query": "prometheus", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "", + "title": "Hubble Metrics and Monitoring", + "uid": "5HftnJAWz", + "version": 24 +} diff --git a/docs/hubble-observability.md b/docs/hubble-observability.md new file mode 100644 index 00000000..33597eec --- /dev/null +++ b/docs/hubble-observability.md @@ -0,0 +1,99 @@ +# Enabling Hubble for Network Observability + +Hubble is a network and security observability platform built on top of Cilium. It provides deep visibility into the communication and behavior of services in your Kubernetes cluster. + +## Prerequisites + +- Cozystack platform running with Cilium as the CNI +- Monitoring hub enabled for Grafana access + +## Configuration + +Hubble is disabled by default in Cozystack. To enable it, update the Cilium configuration. + +### Enable Hubble + +Edit the Cilium values in your platform configuration to enable Hubble: + +```yaml +cilium: + hubble: + enabled: true + relay: + enabled: true + ui: + enabled: true + metrics: + enabled: + - dns + - drop + - tcp + - flow + - port-distribution + - icmp + - httpV2:exemplars=true;labelsContext=source_ip,source_namespace,source_workload,destination_ip,destination_namespace,destination_workload,traffic_direction +``` + +### Components + +When Hubble is enabled, the following components become available: + +- **Hubble Relay**: Aggregates flow data from all Cilium agents +- **Hubble UI**: Web-based interface for exploring network flows +- **Hubble Metrics**: Prometheus metrics for network observability + +## Grafana Dashboards + +Once Hubble is enabled and the monitoring hub is deployed, the following dashboards become available in Grafana under the `hubble` folder: + +| Dashboard | Description | +|-----------|-------------| +| **Overview** | General Hubble metrics including processing statistics | +| **DNS Namespace** | DNS query and response metrics by namespace | +| **L7 HTTP Metrics** | HTTP layer 7 metrics by workload | +| **Network Overview** | Network flow overview by namespace | + +### Accessing Dashboards + +1. Navigate to Grafana via the monitoring hub +2. Browse to the `hubble` folder in the dashboard browser +3. Select a dashboard to view network observability data + +## Metrics Available + +Hubble exposes various metrics that can be queried in Grafana: + +- `hubble_flows_processed_total`: Total number of flows processed +- `hubble_dns_queries_total`: DNS queries by type +- `hubble_dns_responses_total`: DNS responses by status +- `hubble_drop_total`: Dropped packets by reason +- `hubble_tcp_flags_total`: TCP connections by flag +- `hubble_http_requests_total`: HTTP requests by method and status + +## Troubleshooting + +### Verify Hubble Status + +Check if Hubble is running: + +```bash +kubectl get pods -n cozy-cilium -l k8s-app=hubble-relay +kubectl get pods -n cozy-cilium -l k8s-app=hubble-ui +``` + +### Check Metrics Endpoint + +Verify Hubble metrics are being scraped: + +```bash +kubectl port-forward -n cozy-cilium svc/hubble-metrics 9965:9965 +curl http://localhost:9965/metrics +``` + +### Verify ServiceMonitor + +Ensure the ServiceMonitor is created for Prometheus scraping: + +```bash +kubectl get servicemonitor -n cozy-cilium +``` diff --git a/packages/extra/monitoring/dashboards.list b/packages/extra/monitoring/dashboards.list index 21ecb974..1f4b2eea 100644 --- a/packages/extra/monitoring/dashboards.list +++ b/packages/extra/monitoring/dashboards.list @@ -39,3 +39,7 @@ goldpinger/goldpinger clickhouse/altinity-clickhouse-operator-dashboard storage/linstor seaweedfs/seaweedfs +hubble/overview +hubble/dns-namespace +hubble/l7-http-metrics +hubble/network-overview From 4494b6a111caf973a24f85ac88342332119542ff Mon Sep 17 00:00:00 2001 From: IvanHunters Date: Fri, 2 Jan 2026 17:45:23 +0300 Subject: [PATCH 005/889] change of cilium replicas to 1 Signed-off-by: IvanHunters --- packages/system/cilium/charts/cilium/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/system/cilium/charts/cilium/values.yaml b/packages/system/cilium/charts/cilium/values.yaml index 5fac5be5..a4b17186 100644 --- a/packages/system/cilium/charts/cilium/values.yaml +++ b/packages/system/cilium/charts/cilium/values.yaml @@ -2854,7 +2854,7 @@ operator: pullPolicy: "IfNotPresent" suffix: "" # -- Number of replicas to run for the cilium-operator deployment - replicas: 2 + replicas: 1 # -- The priority class to use for cilium-operator priorityClassName: "" # -- DNS policy for Cilium operator pods. From 01d01cf351d9f47771339b402c7960016ab6d40f Mon Sep 17 00:00:00 2001 From: IvanHunters Date: Fri, 2 Jan 2026 17:49:16 +0300 Subject: [PATCH 006/889] change of cilium replicas to 1 Signed-off-by: IvanHunters --- packages/system/cilium/charts/cilium/values.yaml | 2 +- packages/system/cilium/values.yaml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/system/cilium/charts/cilium/values.yaml b/packages/system/cilium/charts/cilium/values.yaml index a4b17186..5fac5be5 100644 --- a/packages/system/cilium/charts/cilium/values.yaml +++ b/packages/system/cilium/charts/cilium/values.yaml @@ -2854,7 +2854,7 @@ operator: pullPolicy: "IfNotPresent" suffix: "" # -- Number of replicas to run for the cilium-operator deployment - replicas: 1 + replicas: 2 # -- The priority class to use for cilium-operator priorityClassName: "" # -- DNS policy for Cilium operator pods. diff --git a/packages/system/cilium/values.yaml b/packages/system/cilium/values.yaml index 95159b1f..5493cc6d 100644 --- a/packages/system/cilium/values.yaml +++ b/packages/system/cilium/values.yaml @@ -22,3 +22,4 @@ cilium: rollOutCiliumPods: true operator: rollOutPods: true + replicas: 1 From a5d3757c3620a229614d25fa492567e20cf2f327 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Fri, 2 Jan 2026 20:44:09 +0100 Subject: [PATCH 007/889] refactor: replace Helm lookup with valuesFrom mechanism Replace Helm lookup functions with FluxCD valuesFrom for passing configuration to HelmReleases. This eliminates the need for force reconcile controllers and provides cleaner config propagation. Changes: - Add Secret cozystack-values creation in platform and tenant charts - Modify cozystack-api to add valuesFrom, filter _ prefixed keys - Add valuesFrom validation in cozystack-controller - Add helper templates in cozy-lib for _cluster/_namespace access - Replace ConfigMap/Namespace lookups in 40+ Helm charts - Remove CozystackConfigReconciler and TenantHelmReconciler The Secret cozystack-values contains: - _cluster: data from cozystack, cozystack-branding, cozystack-scheduling ConfigMaps - _namespace: service references (etcd, host, ingress, monitoring, seaweedfs) Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- cmd/cozystack-controller/main.go | 16 -- ...ystackresourcedefinition_helmreconciler.go | 47 +++++- internal/controller/system_helm_reconciler.go | 140 --------------- internal/controller/tenant_helm_reconciler.go | 159 ------------------ .../apps/bucket/templates/bucketclaim.yaml | 3 +- .../apps/bucket/templates/helmrelease.yaml | 9 + .../apps/clickhouse/templates/chkeeper.yaml | 3 +- .../apps/clickhouse/templates/clickhouse.yaml | 3 +- .../apps/ferretdb/templates/postgres.yaml | 5 +- .../apps/foundationdb/templates/cluster.yaml | 3 +- .../apps/kubernetes/templates/cluster.yaml | 12 +- .../helmreleases/monitoring-agents.yaml | 3 +- .../helmreleases/vertical-pod-autoscaler.yaml | 6 +- .../apps/kubernetes/templates/ingress.yaml | 3 +- packages/apps/nats/templates/nats.yaml | 12 +- packages/apps/postgres/templates/db.yaml | 5 +- packages/apps/tenant/templates/etcd.yaml | 9 + packages/apps/tenant/templates/info.yaml | 9 + packages/apps/tenant/templates/ingress.yaml | 9 + .../apps/tenant/templates/keycloakgroups.yaml | 3 +- .../apps/tenant/templates/monitoring.yaml | 9 + packages/apps/tenant/templates/namespace.yaml | 85 +++++++--- packages/apps/tenant/templates/seaweedfs.yaml | 9 + .../virtual-machine/templates/_helpers.tpl | 5 +- .../apps/vm-instance/templates/_helpers.tpl | 5 +- packages/apps/vpn/templates/secret.yaml | 3 +- packages/core/platform/templates/apps.yaml | 41 +++++ .../core/platform/templates/helmreleases.yaml | 5 + .../core/platform/templates/namespaces.yaml | 22 +++ .../bootbox/templates/matchbox/ingress.yaml | 9 +- .../bootbox/templates/matchbox/machines.yaml | 7 +- .../extra/etcd/templates/etcd-cluster.yaml | 5 +- .../info/templates/dashboard-resourcemap.yaml | 3 +- packages/extra/info/templates/kubeconfig.yaml | 14 +- .../ingress/templates/nginx-ingress.yaml | 14 +- .../templates/alerta/alerta-db.yaml | 5 +- .../monitoring/templates/alerta/alerta.yaml | 9 +- .../monitoring/templates/grafana/db.yaml | 5 +- .../monitoring/templates/grafana/grafana.yaml | 9 +- .../templates/client/cosi-deployment.yaml | 4 +- .../extra/seaweedfs/templates/ingress.yaml | 8 +- .../extra/seaweedfs/templates/seaweedfs.yaml | 14 +- .../cozy-lib/templates/_cozyconfig.tpl | 127 +++++++++++++- packages/system/bucket/templates/ingress.yaml | 8 +- .../templates/cluster-issuers.yaml | 3 +- .../cozystack-api/templates/api-ingress.yaml | 7 +- .../system/dashboard/templates/configmap.yaml | 14 +- .../dashboard/templates/gatekeeper.yaml | 5 +- .../system/dashboard/templates/ingress.yaml | 9 +- .../dashboard/templates/keycloakclient.yaml | 5 +- .../dashboard/templates/nginx-config.yaml | 3 +- .../templates/configure-kk.yaml | 11 +- packages/system/keycloak/templates/db.yaml | 5 +- .../system/keycloak/templates/ingress.yaml | 7 +- packages/system/keycloak/templates/sts.yaml | 5 +- .../templates/cdi-uploadproxy-ingress.yaml | 7 +- .../templates/vm-exportproxy-ingress.yaml | 7 +- .../cozy-lib-tests/tests/quota_values.yaml | 3 + pkg/registry/apps/application/rest.go | 69 +++++++- 59 files changed, 543 insertions(+), 501 deletions(-) delete mode 100644 internal/controller/system_helm_reconciler.go delete mode 100644 internal/controller/tenant_helm_reconciler.go diff --git a/cmd/cozystack-controller/main.go b/cmd/cozystack-controller/main.go index 91889224..0e7199d3 100644 --- a/cmd/cozystack-controller/main.go +++ b/cmd/cozystack-controller/main.go @@ -200,22 +200,6 @@ func main() { os.Exit(1) } - if err = (&controller.TenantHelmReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "TenantHelmReconciler") - os.Exit(1) - } - - if err = (&controller.CozystackConfigReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "CozystackConfigReconciler") - os.Exit(1) - } - cozyAPIKind := "DaemonSet" if reconcileDeployment { cozyAPIKind = "Deployment" diff --git a/internal/controller/cozystackresourcedefinition_helmreconciler.go b/internal/controller/cozystackresourcedefinition_helmreconciler.go index c086b56f..57c8141f 100644 --- a/internal/controller/cozystackresourcedefinition_helmreconciler.go +++ b/internal/controller/cozystackresourcedefinition_helmreconciler.go @@ -97,7 +97,44 @@ func (r *CozystackResourceDefinitionHelmReconciler) updateHelmReleasesForCRD(ctx return nil } -// updateHelmReleaseChart updates the chart in HelmRelease based on CozystackResourceDefinition +// expectedValuesFrom returns the expected valuesFrom configuration for HelmReleases +func expectedValuesFrom() []helmv2.ValuesReference { + return []helmv2.ValuesReference{ + { + Kind: "Secret", + Name: "cozystack-values", + ValuesKey: "_namespace", + TargetPath: "_namespace", + Optional: false, + }, + { + Kind: "Secret", + Name: "cozystack-values", + ValuesKey: "_cluster", + TargetPath: "_cluster", + Optional: false, + }, + } +} + +// valuesFromEqual compares two ValuesReference slices +func valuesFromEqual(a, b []helmv2.ValuesReference) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i].Kind != b[i].Kind || + a[i].Name != b[i].Name || + a[i].ValuesKey != b[i].ValuesKey || + a[i].TargetPath != b[i].TargetPath || + a[i].Optional != b[i].Optional { + return false + } + } + return true +} + +// updateHelmReleaseChart updates the chart and valuesFrom in HelmRelease based on CozystackResourceDefinition func (r *CozystackResourceDefinitionHelmReconciler) updateHelmReleaseChart(ctx context.Context, hr *helmv2.HelmRelease, crd *cozyv1alpha1.CozystackResourceDefinition) error { logger := log.FromContext(ctx) hrCopy := hr.DeepCopy() @@ -154,6 +191,14 @@ func (r *CozystackResourceDefinitionHelmReconciler) updateHelmReleaseChart(ctx c } } + // Check and update valuesFrom configuration + expected := expectedValuesFrom() + if !valuesFromEqual(hrCopy.Spec.ValuesFrom, expected) { + logger.V(4).Info("Updating HelmRelease valuesFrom", "name", hr.Name, "namespace", hr.Namespace) + hrCopy.Spec.ValuesFrom = expected + updated = true + } + if updated { logger.V(4).Info("Updating HelmRelease chart", "name", hr.Name, "namespace", hr.Namespace) if err := r.Update(ctx, hrCopy); err != nil { diff --git a/internal/controller/system_helm_reconciler.go b/internal/controller/system_helm_reconciler.go deleted file mode 100644 index 6e40027c..00000000 --- a/internal/controller/system_helm_reconciler.go +++ /dev/null @@ -1,140 +0,0 @@ -package controller - -import ( - "context" - "crypto/sha256" - "encoding/hex" - "fmt" - "sort" - "time" - - helmv2 "github.com/fluxcd/helm-controller/api/v2" - corev1 "k8s.io/api/core/v1" - kerrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/event" - "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/predicate" -) - -type CozystackConfigReconciler struct { - client.Client - Scheme *runtime.Scheme -} - -var configMapNames = []string{"cozystack", "cozystack-branding", "cozystack-scheduling"} - -const configMapNamespace = "cozy-system" -const digestAnnotation = "cozystack.io/cozy-config-digest" -const forceReconcileKey = "reconcile.fluxcd.io/forceAt" -const requestedAt = "reconcile.fluxcd.io/requestedAt" - -func (r *CozystackConfigReconciler) Reconcile(ctx context.Context, _ ctrl.Request) (ctrl.Result, error) { - log := log.FromContext(ctx) - time.Sleep(2 * time.Second) - - digest, err := r.computeDigest(ctx) - if err != nil { - log.Error(err, "failed to compute config digest") - return ctrl.Result{}, nil - } - - var helmList helmv2.HelmReleaseList - if err := r.List(ctx, &helmList); err != nil { - return ctrl.Result{}, fmt.Errorf("failed to list HelmReleases: %w", err) - } - - now := time.Now().Format(time.RFC3339Nano) - updated := 0 - - for _, hr := range helmList.Items { - isSystemApp := hr.Labels["cozystack.io/system-app"] == "true" - isTenantRoot := hr.Namespace == "tenant-root" && hr.Name == "tenant-root" - if !isSystemApp && !isTenantRoot { - continue - } - patchTarget := hr.DeepCopy() - - if hr.Annotations == nil { - hr.Annotations = map[string]string{} - } - - if hr.Annotations[digestAnnotation] == digest { - continue - } - patchTarget.Annotations[digestAnnotation] = digest - patchTarget.Annotations[forceReconcileKey] = now - patchTarget.Annotations[requestedAt] = now - - patch := client.MergeFrom(hr.DeepCopy()) - if err := r.Patch(ctx, patchTarget, patch); err != nil { - log.Error(err, "failed to patch HelmRelease", "name", hr.Name, "namespace", hr.Namespace) - continue - } - updated++ - log.Info("patched HelmRelease with new config digest", "name", hr.Name, "namespace", hr.Namespace) - } - - log.Info("finished reconciliation", "updatedHelmReleases", updated) - return ctrl.Result{}, nil -} - -func (r *CozystackConfigReconciler) computeDigest(ctx context.Context) (string, error) { - hash := sha256.New() - - for _, name := range configMapNames { - var cm corev1.ConfigMap - err := r.Get(ctx, client.ObjectKey{Namespace: configMapNamespace, Name: name}, &cm) - if err != nil { - if kerrors.IsNotFound(err) { - continue // ignore missing - } - return "", err - } - - // Sort keys for consistent hashing - var keys []string - for k := range cm.Data { - keys = append(keys, k) - } - sort.Strings(keys) - - for _, k := range keys { - v := cm.Data[k] - fmt.Fprintf(hash, "%s:%s=%s\n", name, k, v) - } - } - - return hex.EncodeToString(hash.Sum(nil)), nil -} - -func (r *CozystackConfigReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - WithEventFilter(predicate.Funcs{ - UpdateFunc: func(e event.UpdateEvent) bool { - cm, ok := e.ObjectNew.(*corev1.ConfigMap) - return ok && cm.Namespace == configMapNamespace && contains(configMapNames, cm.Name) - }, - CreateFunc: func(e event.CreateEvent) bool { - cm, ok := e.Object.(*corev1.ConfigMap) - return ok && cm.Namespace == configMapNamespace && contains(configMapNames, cm.Name) - }, - DeleteFunc: func(e event.DeleteEvent) bool { - cm, ok := e.Object.(*corev1.ConfigMap) - return ok && cm.Namespace == configMapNamespace && contains(configMapNames, cm.Name) - }, - }). - For(&corev1.ConfigMap{}). - Complete(r) -} - -func contains(slice []string, val string) bool { - for _, s := range slice { - if s == val { - return true - } - } - return false -} diff --git a/internal/controller/tenant_helm_reconciler.go b/internal/controller/tenant_helm_reconciler.go deleted file mode 100644 index 28b4ad16..00000000 --- a/internal/controller/tenant_helm_reconciler.go +++ /dev/null @@ -1,159 +0,0 @@ -package controller - -import ( - "context" - "fmt" - "strings" - "time" - - e "errors" - - helmv2 "github.com/fluxcd/helm-controller/api/v2" - "gopkg.in/yaml.v2" - corev1 "k8s.io/api/core/v1" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - "k8s.io/apimachinery/pkg/api/errors" - "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" -) - -type TenantHelmReconciler struct { - client.Client - Scheme *runtime.Scheme -} - -func (r *TenantHelmReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - logger := log.FromContext(ctx) - time.Sleep(2 * time.Second) - - hr := &helmv2.HelmRelease{} - if err := r.Get(ctx, req.NamespacedName, hr); err != nil { - if errors.IsNotFound(err) { - return ctrl.Result{}, nil - } - logger.Error(err, "unable to fetch HelmRelease") - return ctrl.Result{}, err - } - - if !strings.HasPrefix(hr.Name, "tenant-") { - return ctrl.Result{}, nil - } - - if len(hr.Status.Conditions) == 0 || hr.Status.Conditions[0].Type != "Ready" { - return ctrl.Result{}, nil - } - - if len(hr.Status.History) == 0 { - logger.Info("no history in HelmRelease status", "name", hr.Name) - return ctrl.Result{}, nil - } - - if hr.Status.History[0].Status != "deployed" { - return ctrl.Result{}, nil - } - - newDigest := hr.Status.History[0].Digest - var hrList helmv2.HelmReleaseList - childNamespace := getChildNamespace(hr.Namespace, hr.Name) - if childNamespace == "tenant-root" && hr.Name == "tenant-root" { - if hr.Spec.Values == nil { - logger.Error(e.New("hr.Spec.Values is nil"), "cant annotate tenant-root ns") - return ctrl.Result{}, nil - } - err := annotateTenantRootNs(*hr.Spec.Values, r.Client) - if err != nil { - logger.Error(err, "cant annotate tenant-root ns") - return ctrl.Result{}, nil - } - logger.Info("namespace 'tenant-root' annotated") - } - - if err := r.List(ctx, &hrList, client.InNamespace(childNamespace)); err != nil { - logger.Error(err, "unable to list HelmReleases in namespace", "namespace", hr.Name) - return ctrl.Result{}, err - } - - for _, item := range hrList.Items { - if item.Name == hr.Name { - continue - } - oldDigest := item.GetAnnotations()["cozystack.io/tenant-config-digest"] - if oldDigest == newDigest { - continue - } - patchTarget := item.DeepCopy() - - if patchTarget.Annotations == nil { - patchTarget.Annotations = map[string]string{} - } - ts := time.Now().Format(time.RFC3339Nano) - - patchTarget.Annotations["cozystack.io/tenant-config-digest"] = newDigest - patchTarget.Annotations["reconcile.fluxcd.io/forceAt"] = ts - patchTarget.Annotations["reconcile.fluxcd.io/requestedAt"] = ts - - patch := client.MergeFrom(item.DeepCopy()) - if err := r.Patch(ctx, patchTarget, patch); err != nil { - logger.Error(err, "failed to patch HelmRelease", "name", patchTarget.Name) - continue - } - - logger.Info("patched HelmRelease with new digest", "name", patchTarget.Name, "digest", newDigest, "version", hr.Status.History[0].Version) - } - - return ctrl.Result{}, nil -} - -func (r *TenantHelmReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&helmv2.HelmRelease{}). - Complete(r) -} - -func getChildNamespace(currentNamespace, hrName string) string { - tenantName := strings.TrimPrefix(hrName, "tenant-") - - switch { - case currentNamespace == "tenant-root" && hrName == "tenant-root": - // 1) root tenant inside root namespace - return "tenant-root" - - case currentNamespace == "tenant-root": - // 2) any other tenant in root namespace - return fmt.Sprintf("tenant-%s", tenantName) - - default: - // 3) tenant in a dedicated namespace - return fmt.Sprintf("%s-%s", currentNamespace, tenantName) - } -} - -func annotateTenantRootNs(values apiextensionsv1.JSON, c client.Client) error { - var data map[string]interface{} - if err := yaml.Unmarshal(values.Raw, &data); err != nil { - return fmt.Errorf("failed to parse HelmRelease values: %w", err) - } - - host, ok := data["host"].(string) - if !ok || host == "" { - return fmt.Errorf("host field not found or not a string") - } - - var ns corev1.Namespace - if err := c.Get(context.TODO(), client.ObjectKey{Name: "tenant-root"}, &ns); err != nil { - return fmt.Errorf("failed to get namespace tenant-root: %w", err) - } - - if ns.Annotations == nil { - ns.Annotations = map[string]string{} - } - ns.Annotations["namespace.cozystack.io/host"] = host - - if err := c.Update(context.TODO(), &ns); err != nil { - return fmt.Errorf("failed to update namespace: %w", err) - } - - return nil -} diff --git a/packages/apps/bucket/templates/bucketclaim.yaml b/packages/apps/bucket/templates/bucketclaim.yaml index cebf95b4..5cfdc1c1 100644 --- a/packages/apps/bucket/templates/bucketclaim.yaml +++ b/packages/apps/bucket/templates/bucketclaim.yaml @@ -1,5 +1,4 @@ -{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }} -{{- $seaweedfs := index $myNS.metadata.annotations "namespace.cozystack.io/seaweedfs" }} +{{- $seaweedfs := .Values._namespace.seaweedfs }} apiVersion: objectstorage.k8s.io/v1alpha1 kind: BucketClaim metadata: diff --git a/packages/apps/bucket/templates/helmrelease.yaml b/packages/apps/bucket/templates/helmrelease.yaml index 45959572..8a57760e 100644 --- a/packages/apps/bucket/templates/helmrelease.yaml +++ b/packages/apps/bucket/templates/helmrelease.yaml @@ -21,5 +21,14 @@ spec: force: true remediation: retries: -1 + valuesFrom: + - kind: Secret + name: cozystack-values + valuesKey: _cluster + targetPath: _cluster + - kind: Secret + name: cozystack-values + valuesKey: _namespace + targetPath: _namespace values: bucketName: {{ .Release.Name }} diff --git a/packages/apps/clickhouse/templates/chkeeper.yaml b/packages/apps/clickhouse/templates/chkeeper.yaml index 54824a44..42d88af4 100644 --- a/packages/apps/clickhouse/templates/chkeeper.yaml +++ b/packages/apps/clickhouse/templates/chkeeper.yaml @@ -1,5 +1,4 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $clusterDomain := (index $cozyConfig.data "cluster-domain") | default "cozy.local" }} +{{- $clusterDomain := (index .Values._cluster "cluster-domain") | default "cozy.local" }} {{- if .Values.clickhouseKeeper.enabled }} apiVersion: "clickhouse-keeper.altinity.com/v1" diff --git a/packages/apps/clickhouse/templates/clickhouse.yaml b/packages/apps/clickhouse/templates/clickhouse.yaml index 47e5b56a..b645260b 100644 --- a/packages/apps/clickhouse/templates/clickhouse.yaml +++ b/packages/apps/clickhouse/templates/clickhouse.yaml @@ -1,5 +1,4 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $clusterDomain := (index $cozyConfig.data "cluster-domain") | default "cozy.local" }} +{{- $clusterDomain := (index .Values._cluster "cluster-domain") | default "cozy.local" }} {{- $existingSecret := lookup "v1" "Secret" .Release.Namespace (printf "%s-credentials" .Release.Name) }} {{- $passwords := dict }} {{- $users := .Values.users }} diff --git a/packages/apps/ferretdb/templates/postgres.yaml b/packages/apps/ferretdb/templates/postgres.yaml index 4d1d8e29..f1b1ce2c 100644 --- a/packages/apps/ferretdb/templates/postgres.yaml +++ b/packages/apps/ferretdb/templates/postgres.yaml @@ -50,9 +50,8 @@ spec: postgresUID: 999 postgresGID: 999 enableSuperuserAccess: true - {{- $configMap := lookup "v1" "ConfigMap" "cozy-system" "cozystack-scheduling" }} - {{- if $configMap }} - {{- $rawConstraints := get $configMap.data "globalAppTopologySpreadConstraints" }} + {{- if .Values._cluster.scheduling }} + {{- $rawConstraints := get .Values._cluster.scheduling "globalAppTopologySpreadConstraints" }} {{- if $rawConstraints }} {{- $rawConstraints | fromYaml | toYaml | nindent 2 }} labelSelector: diff --git a/packages/apps/foundationdb/templates/cluster.yaml b/packages/apps/foundationdb/templates/cluster.yaml index 06992cb5..be804233 100644 --- a/packages/apps/foundationdb/templates/cluster.yaml +++ b/packages/apps/foundationdb/templates/cluster.yaml @@ -1,5 +1,4 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" | default (dict "data" (dict)) }} -{{- $clusterDomain := index $cozyConfig.data "cluster-domain" | default "cozy.local" }} +{{- $clusterDomain := index .Values._cluster "cluster-domain" | default "cozy.local" }} --- apiVersion: apps.foundationdb.org/v1beta2 kind: FoundationDBCluster diff --git a/packages/apps/kubernetes/templates/cluster.yaml b/packages/apps/kubernetes/templates/cluster.yaml index 7edd07f5..6acfb107 100644 --- a/packages/apps/kubernetes/templates/cluster.yaml +++ b/packages/apps/kubernetes/templates/cluster.yaml @@ -1,7 +1,6 @@ -{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }} -{{- $etcd := index $myNS.metadata.annotations "namespace.cozystack.io/etcd" }} -{{- $ingress := index $myNS.metadata.annotations "namespace.cozystack.io/ingress" }} -{{- $host := index $myNS.metadata.annotations "namespace.cozystack.io/host" }} +{{- $etcd := .Values._namespace.etcd }} +{{- $ingress := .Values._namespace.ingress }} +{{- $host := .Values._namespace.host }} {{- $kubevirtmachinetemplateNames := list }} {{- define "kubevirtmachinetemplate" -}} spec: @@ -31,9 +30,8 @@ spec: {{- end }} cluster.x-k8s.io/deployment-name: {{ $.Release.Name }}-{{ .groupName }} spec: - {{- $configMap := lookup "v1" "ConfigMap" "cozy-system" "cozystack-scheduling" }} - {{- if $configMap }} - {{- $rawConstraints := get $configMap.data "globalAppTopologySpreadConstraints" }} + {{- if .Values._cluster.scheduling }} + {{- $rawConstraints := get .Values._cluster.scheduling "globalAppTopologySpreadConstraints" }} {{- if $rawConstraints }} {{- $rawConstraints | fromYaml | toYaml | nindent 10 }} labelSelector: diff --git a/packages/apps/kubernetes/templates/helmreleases/monitoring-agents.yaml b/packages/apps/kubernetes/templates/helmreleases/monitoring-agents.yaml index cf93f233..73c3a368 100644 --- a/packages/apps/kubernetes/templates/helmreleases/monitoring-agents.yaml +++ b/packages/apps/kubernetes/templates/helmreleases/monitoring-agents.yaml @@ -1,5 +1,4 @@ -{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }} -{{- $targetTenant := index $myNS.metadata.annotations "namespace.cozystack.io/monitoring" }} +{{- $targetTenant := .Values._namespace.monitoring }} {{- if .Values.addons.monitoringAgents.enabled }} apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease diff --git a/packages/apps/kubernetes/templates/helmreleases/vertical-pod-autoscaler.yaml b/packages/apps/kubernetes/templates/helmreleases/vertical-pod-autoscaler.yaml index 8b615c9c..8a62288d 100644 --- a/packages/apps/kubernetes/templates/helmreleases/vertical-pod-autoscaler.yaml +++ b/packages/apps/kubernetes/templates/helmreleases/vertical-pod-autoscaler.yaml @@ -1,8 +1,6 @@ {{- define "cozystack.defaultVPAValues" -}} -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $clusterDomain := (index $cozyConfig.data "cluster-domain") | default "cozy.local" }} -{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }} -{{- $targetTenant := index $myNS.metadata.annotations "namespace.cozystack.io/monitoring" }} +{{- $clusterDomain := (index .Values._cluster "cluster-domain") | default "cozy.local" }} +{{- $targetTenant := .Values._namespace.monitoring }} vpaForVPA: false vertical-pod-autoscaler: recommender: diff --git a/packages/apps/kubernetes/templates/ingress.yaml b/packages/apps/kubernetes/templates/ingress.yaml index 8dd244cb..7993dba8 100644 --- a/packages/apps/kubernetes/templates/ingress.yaml +++ b/packages/apps/kubernetes/templates/ingress.yaml @@ -1,5 +1,4 @@ -{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }} -{{- $ingress := index $myNS.metadata.annotations "namespace.cozystack.io/ingress" }} +{{- $ingress := .Values._namespace.ingress }} {{- if and (eq .Values.addons.ingressNginx.exposeMethod "Proxied") .Values.addons.ingressNginx.hosts }} --- apiVersion: networking.k8s.io/v1 diff --git a/packages/apps/nats/templates/nats.yaml b/packages/apps/nats/templates/nats.yaml index b05c87b5..b41c1574 100644 --- a/packages/apps/nats/templates/nats.yaml +++ b/packages/apps/nats/templates/nats.yaml @@ -1,5 +1,4 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $clusterDomain := (index $cozyConfig.data "cluster-domain") | default "cozy.local" }} +{{- $clusterDomain := (index .Values._cluster "cluster-domain") | default "cozy.local" }} {{- $existingSecret := lookup "v1" "Secret" .Release.Namespace (printf "%s-credentials" .Release.Name) }} {{- $passwords := dict }} @@ -53,6 +52,15 @@ spec: force: true remediation: retries: -1 + valuesFrom: + - kind: Secret + name: cozystack-values + valuesKey: _cluster + targetPath: _cluster + - kind: Secret + name: cozystack-values + valuesKey: _namespace + targetPath: _namespace values: nats: container: diff --git a/packages/apps/postgres/templates/db.yaml b/packages/apps/postgres/templates/db.yaml index 92fb34c6..7557c436 100644 --- a/packages/apps/postgres/templates/db.yaml +++ b/packages/apps/postgres/templates/db.yaml @@ -46,9 +46,8 @@ spec: imageName: ghcr.io/cloudnative-pg/postgresql:{{ include "postgres.versionMap" $ | trimPrefix "v" }} enableSuperuserAccess: true - {{- $configMap := lookup "v1" "ConfigMap" "cozy-system" "cozystack-scheduling" }} - {{- if $configMap }} - {{- $rawConstraints := get $configMap.data "globalAppTopologySpreadConstraints" }} + {{- if .Values._cluster.scheduling }} + {{- $rawConstraints := get .Values._cluster.scheduling "globalAppTopologySpreadConstraints" }} {{- if $rawConstraints }} {{- $rawConstraints | fromYaml | toYaml | nindent 2 }} labelSelector: diff --git a/packages/apps/tenant/templates/etcd.yaml b/packages/apps/tenant/templates/etcd.yaml index 8cd720cc..4da22fb7 100644 --- a/packages/apps/tenant/templates/etcd.yaml +++ b/packages/apps/tenant/templates/etcd.yaml @@ -31,4 +31,13 @@ spec: force: true remediation: retries: -1 + valuesFrom: + - kind: Secret + name: cozystack-values + valuesKey: _cluster + targetPath: _cluster + - kind: Secret + name: cozystack-values + valuesKey: _namespace + targetPath: _namespace {{- end }} diff --git a/packages/apps/tenant/templates/info.yaml b/packages/apps/tenant/templates/info.yaml index 4a66e422..e1281ebb 100644 --- a/packages/apps/tenant/templates/info.yaml +++ b/packages/apps/tenant/templates/info.yaml @@ -30,3 +30,12 @@ spec: force: true remediation: retries: -1 + valuesFrom: + - kind: Secret + name: cozystack-values + valuesKey: _cluster + targetPath: _cluster + - kind: Secret + name: cozystack-values + valuesKey: _namespace + targetPath: _namespace diff --git a/packages/apps/tenant/templates/ingress.yaml b/packages/apps/tenant/templates/ingress.yaml index beb07342..c9c791f1 100644 --- a/packages/apps/tenant/templates/ingress.yaml +++ b/packages/apps/tenant/templates/ingress.yaml @@ -31,4 +31,13 @@ spec: force: true remediation: retries: -1 + valuesFrom: + - kind: Secret + name: cozystack-values + valuesKey: _cluster + targetPath: _cluster + - kind: Secret + name: cozystack-values + valuesKey: _namespace + targetPath: _namespace {{- end }} diff --git a/packages/apps/tenant/templates/keycloakgroups.yaml b/packages/apps/tenant/templates/keycloakgroups.yaml index 59288ca4..9e25e60d 100644 --- a/packages/apps/tenant/templates/keycloakgroups.yaml +++ b/packages/apps/tenant/templates/keycloakgroups.yaml @@ -1,5 +1,4 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $oidcEnabled := index $cozyConfig.data "oidc-enabled" }} +{{- $oidcEnabled := index .Values._cluster "oidc-enabled" }} {{- if eq $oidcEnabled "true" }} {{- if .Capabilities.APIVersions.Has "v1.edp.epam.com/v1" }} apiVersion: v1.edp.epam.com/v1 diff --git a/packages/apps/tenant/templates/monitoring.yaml b/packages/apps/tenant/templates/monitoring.yaml index 4986fc21..3b0536de 100644 --- a/packages/apps/tenant/templates/monitoring.yaml +++ b/packages/apps/tenant/templates/monitoring.yaml @@ -31,4 +31,13 @@ spec: force: true remediation: retries: -1 + valuesFrom: + - kind: Secret + name: cozystack-values + valuesKey: _cluster + targetPath: _cluster + - kind: Secret + name: cozystack-values + valuesKey: _namespace + targetPath: _namespace {{- end }} diff --git a/packages/apps/tenant/templates/namespace.yaml b/packages/apps/tenant/templates/namespace.yaml index d97ebf42..6c94a87f 100644 --- a/packages/apps/tenant/templates/namespace.yaml +++ b/packages/apps/tenant/templates/namespace.yaml @@ -1,46 +1,63 @@ -{{- define "cozystack.namespace-anotations" }} -{{- $context := index . 0 }} -{{- $existingNS := index . 1 }} -{{- range $x := list "etcd" "monitoring" "ingress" "seaweedfs" }} -{{- if (index $context.Values $x) }} -namespace.cozystack.io/{{ $x }}: "{{ include "tenant.name" $context }}" -{{- else }} -namespace.cozystack.io/{{ $x }}: "{{ index $existingNS.metadata.annotations (printf "namespace.cozystack.io/%s" $x) | required (printf "namespace %s has no namespace.cozystack.io/%s annotation" $context.Release.Namespace $x) }}" -{{- end }} -{{- end }} -{{- end }} - +{{/* Lookup for namespace uid (needed for ownerReferences) */}} {{- $existingNS := lookup "v1" "Namespace" "" .Release.Namespace }} {{- if not $existingNS }} {{- fail (printf "error lookup existing namespace: %s" .Release.Namespace) }} {{- end }} {{- if ne (include "tenant.name" .) "tenant-root" }} +{{/* Compute namespace values once for use in both Secret and labels */}} +{{- $tenantName := include "tenant.name" . }} +{{- $parentNamespace := .Values._namespace | default dict }} +{{- $parentHost := $parentNamespace.host | default "" }} + +{{/* Compute host */}} +{{- $computedHost := "" }} +{{- if .Values.host }} +{{- $computedHost = .Values.host }} +{{- else if $parentHost }} +{{- $computedHost = printf "%s.%s" (splitList "-" $tenantName | last) $parentHost }} +{{- end }} + +{{/* Compute service references */}} +{{- $etcd := $parentNamespace.etcd | default "" }} +{{- if .Values.etcd }} +{{- $etcd = $tenantName }} +{{- end }} + +{{- $ingress := $parentNamespace.ingress | default "" }} +{{- if .Values.ingress }} +{{- $ingress = $tenantName }} +{{- end }} + +{{- $monitoring := $parentNamespace.monitoring | default "" }} +{{- if .Values.monitoring }} +{{- $monitoring = $tenantName }} +{{- end }} + +{{- $seaweedfs := $parentNamespace.seaweedfs | default "" }} +{{- if .Values.seaweedfs }} +{{- $seaweedfs = $tenantName }} +{{- end }} --- apiVersion: v1 kind: Namespace metadata: - name: {{ include "tenant.name" . }} + name: {{ $tenantName }} {{- if hasPrefix "tenant-" .Release.Namespace }} - annotations: - {{- if .Values.host }} - namespace.cozystack.io/host: "{{ .Values.host }}" - {{- else }} - {{ $parentHost := index $existingNS.metadata.annotations "namespace.cozystack.io/host" | required (printf "namespace %s has no namespace.cozystack.io/host annotation" .Release.Namespace) }} - namespace.cozystack.io/host: "{{ splitList "-" (include "tenant.name" .) | last }}.{{ $parentHost }}" - {{- end }} - {{- include "cozystack.namespace-anotations" (list . $existingNS) | nindent 4 }} labels: - tenant.cozystack.io/{{ include "tenant.name" $ }}: "" - {{- if hasPrefix "tenant-" .Release.Namespace }} + tenant.cozystack.io/{{ $tenantName }}: "" {{- $parts := splitList "-" .Release.Namespace }} {{- range $i, $v := $parts }} {{- if ne $i 0 }} tenant.cozystack.io/{{ join "-" (slice $parts 0 (add $i 1)) }}: "" {{- end }} {{- end }} - {{- end }} - {{- include "cozystack.namespace-anotations" (list $ $existingNS) | nindent 4 }} + {{/* Labels for network policies */}} + namespace.cozystack.io/etcd: {{ $etcd | quote }} + namespace.cozystack.io/ingress: {{ $ingress | quote }} + namespace.cozystack.io/monitoring: {{ $monitoring | quote }} + namespace.cozystack.io/seaweedfs: {{ $seaweedfs | quote }} + namespace.cozystack.io/host: {{ $computedHost | quote }} alpha.kubevirt.io/auto-memory-limits-ratio: "1.0" ownerReferences: - apiVersion: v1 @@ -50,4 +67,22 @@ metadata: name: {{ .Release.Namespace }} uid: {{ $existingNS.metadata.uid }} {{- end }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: cozystack-values + namespace: {{ $tenantName }} + labels: + reconcile.fluxcd.io/watch: Enabled +type: Opaque +stringData: + _cluster: | + {{- .Values._cluster | toYaml | nindent 4 }} + _namespace: | + etcd: {{ $etcd | quote }} + ingress: {{ $ingress | quote }} + monitoring: {{ $monitoring | quote }} + seaweedfs: {{ $seaweedfs | quote }} + host: {{ $computedHost | quote }} {{- end }} diff --git a/packages/apps/tenant/templates/seaweedfs.yaml b/packages/apps/tenant/templates/seaweedfs.yaml index 9a714b79..be768571 100644 --- a/packages/apps/tenant/templates/seaweedfs.yaml +++ b/packages/apps/tenant/templates/seaweedfs.yaml @@ -31,4 +31,13 @@ spec: force: true remediation: retries: -1 + valuesFrom: + - kind: Secret + name: cozystack-values + valuesKey: _cluster + targetPath: _cluster + - kind: Secret + name: cozystack-values + valuesKey: _namespace + targetPath: _namespace {{- end }} diff --git a/packages/apps/virtual-machine/templates/_helpers.tpl b/packages/apps/virtual-machine/templates/_helpers.tpl index 7b6929ac..e65cad9d 100644 --- a/packages/apps/virtual-machine/templates/_helpers.tpl +++ b/packages/apps/virtual-machine/templates/_helpers.tpl @@ -74,9 +74,8 @@ Generate a stable UUID for cloud-init re-initialization upon upgrade. 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 .Values._cluster.scheduling -}} +{{- $dedicatedNodesForWindowsVMs := get .Values._cluster.scheduling "dedicatedNodesForWindowsVMs" -}} {{- if eq $dedicatedNodesForWindowsVMs "true" -}} {{- $isWindows := hasPrefix "windows" (toString .Values.instanceProfile) -}} affinity: diff --git a/packages/apps/vm-instance/templates/_helpers.tpl b/packages/apps/vm-instance/templates/_helpers.tpl index 7b6929ac..e65cad9d 100644 --- a/packages/apps/vm-instance/templates/_helpers.tpl +++ b/packages/apps/vm-instance/templates/_helpers.tpl @@ -74,9 +74,8 @@ Generate a stable UUID for cloud-init re-initialization upon upgrade. 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 .Values._cluster.scheduling -}} +{{- $dedicatedNodesForWindowsVMs := get .Values._cluster.scheduling "dedicatedNodesForWindowsVMs" -}} {{- if eq $dedicatedNodesForWindowsVMs "true" -}} {{- $isWindows := hasPrefix "windows" (toString .Values.instanceProfile) -}} affinity: diff --git a/packages/apps/vpn/templates/secret.yaml b/packages/apps/vpn/templates/secret.yaml index 79960096..3ef4ac4b 100644 --- a/packages/apps/vpn/templates/secret.yaml +++ b/packages/apps/vpn/templates/secret.yaml @@ -1,5 +1,4 @@ -{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }} -{{- $host := index $myNS.metadata.annotations "namespace.cozystack.io/host" }} +{{- $host := .Values._namespace.host }} {{- $existingSecret := lookup "v1" "Secret" .Release.Namespace (printf "%s-vpn" .Release.Name) }} {{- $accessKeys := list }} {{- $passwords := dict }} diff --git a/packages/core/platform/templates/apps.yaml b/packages/core/platform/templates/apps.yaml index 514dcffb..2477fc17 100644 --- a/packages/core/platform/templates/apps.yaml +++ b/packages/core/platform/templates/apps.yaml @@ -1,4 +1,6 @@ {{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} +{{- $cozystackBranding := lookup "v1" "ConfigMap" "cozy-system" "cozystack-branding" }} +{{- $cozystackScheduling := lookup "v1" "ConfigMap" "cozy-system" "cozystack-scheduling" }} {{- $bundleName := index $cozyConfig.data "bundle-name" }} {{- $bundle := tpl (.Files.Get (printf "bundles/%s.yaml" $bundleName)) . | fromYaml }} {{- $host := "example.org" }} @@ -22,6 +24,8 @@ kind: Namespace metadata: annotations: helm.sh/resource-policy: keep + labels: + tenant.cozystack.io/tenant-root: "" namespace.cozystack.io/etcd: tenant-root namespace.cozystack.io/monitoring: tenant-root namespace.cozystack.io/ingress: tenant-root @@ -29,6 +33,32 @@ metadata: namespace.cozystack.io/host: "{{ $host }}" name: tenant-root --- +apiVersion: v1 +kind: Secret +metadata: + name: cozystack-values + namespace: tenant-root + labels: + reconcile.fluxcd.io/watch: Enabled +type: Opaque +stringData: + _cluster: | + {{- $cozyConfig.data | toYaml | nindent 4 }} + {{- with $cozystackBranding.data }} + branding: + {{- . | toYaml | nindent 6 }} + {{- end }} + {{- with $cozystackScheduling.data }} + scheduling: + {{- . | toYaml | nindent 6 }} + {{- end }} + _namespace: | + etcd: tenant-root + monitoring: tenant-root + ingress: tenant-root + seaweedfs: tenant-root + host: {{ $host | quote }} +--- apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease metadata: @@ -56,6 +86,17 @@ spec: kind: HelmRepository name: cozystack-apps namespace: cozy-public + valuesFrom: + - kind: Secret + name: cozystack-values + valuesKey: _namespace + targetPath: _namespace + optional: false + - kind: Secret + name: cozystack-values + valuesKey: _cluster + targetPath: _cluster + optional: false values: host: "{{ $host }}" dependsOn: diff --git a/packages/core/platform/templates/helmreleases.yaml b/packages/core/platform/templates/helmreleases.yaml index 6ed61ed8..83ab18ea 100644 --- a/packages/core/platform/templates/helmreleases.yaml +++ b/packages/core/platform/templates/helmreleases.yaml @@ -83,6 +83,11 @@ spec: values: {{- toYaml . | nindent 4}} {{- end }} + valuesFrom: + - kind: Secret + name: cozystack-values + valuesKey: _cluster + targetPath: _cluster {{- with $x.dependsOn }} dependsOn: diff --git a/packages/core/platform/templates/namespaces.yaml b/packages/core/platform/templates/namespaces.yaml index 11d00553..a4ed5135 100644 --- a/packages/core/platform/templates/namespaces.yaml +++ b/packages/core/platform/templates/namespaces.yaml @@ -1,4 +1,6 @@ {{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} +{{- $cozystackBranding := lookup "v1" "ConfigMap" "cozy-system" "cozystack-branding" }} +{{- $cozystackScheduling := lookup "v1" "ConfigMap" "cozy-system" "cozystack-scheduling" }} {{- $bundleName := index $cozyConfig.data "bundle-name" }} {{- $bundle := tpl (.Files.Get (printf "bundles/%s.yaml" $bundleName)) . | fromYaml }} {{- $disabledComponents := splitList "," ((index $cozyConfig.data "bundle-disable") | default "") }} @@ -37,4 +39,24 @@ metadata: pod-security.kubernetes.io/enforce: privileged {{- end }} name: {{ $namespace }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: cozystack-values + namespace: {{ $namespace }} + labels: + reconcile.fluxcd.io/watch: Enabled +type: Opaque +stringData: + _cluster: | + {{- $cozyConfig.data | toYaml | nindent 4 }} + {{- with $cozystackBranding.data }} + branding: + {{- . | toYaml | nindent 6 }} + {{- end }} + {{- with $cozystackScheduling.data }} + scheduling: + {{- . | toYaml | nindent 6 }} + {{- end }} {{- end }} diff --git a/packages/extra/bootbox/templates/matchbox/ingress.yaml b/packages/extra/bootbox/templates/matchbox/ingress.yaml index 31de7716..fa62165b 100644 --- a/packages/extra/bootbox/templates/matchbox/ingress.yaml +++ b/packages/extra/bootbox/templates/matchbox/ingress.yaml @@ -1,9 +1,6 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $issuerType := (index $cozyConfig.data "clusterissuer") | default "http01" }} - -{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }} -{{- $ingress := index $myNS.metadata.annotations "namespace.cozystack.io/ingress" }} -{{- $host := index $myNS.metadata.annotations "namespace.cozystack.io/host" }} +{{- $issuerType := (index .Values._cluster "clusterissuer") | default "http01" }} +{{- $ingress := .Values._namespace.ingress }} +{{- $host := .Values._namespace.host }} apiVersion: networking.k8s.io/v1 kind: Ingress metadata: diff --git a/packages/extra/bootbox/templates/matchbox/machines.yaml b/packages/extra/bootbox/templates/matchbox/machines.yaml index e2733b89..a52c4b40 100644 --- a/packages/extra/bootbox/templates/matchbox/machines.yaml +++ b/packages/extra/bootbox/templates/matchbox/machines.yaml @@ -1,9 +1,4 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $issuerType := (index $cozyConfig.data "clusterissuer") | default "http01" }} - -{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }} -{{- $ingress := index $myNS.metadata.annotations "namespace.cozystack.io/ingress" }} -{{- $host := index $myNS.metadata.annotations "namespace.cozystack.io/host" }} +{{- $host := .Values._namespace.host }} {{ range $m := .Values.machines }} --- diff --git a/packages/extra/etcd/templates/etcd-cluster.yaml b/packages/extra/etcd/templates/etcd-cluster.yaml index 017a3178..a26ae173 100644 --- a/packages/extra/etcd/templates/etcd-cluster.yaml +++ b/packages/extra/etcd/templates/etcd-cluster.yaml @@ -49,10 +49,9 @@ spec: {{- with .Values.resources }} resources: {{- include "cozy-lib.resources.sanitize" (list . $) | nindent 10 }} {{- end }} - {{- $configMap := lookup "v1" "ConfigMap" "cozy-system" "cozystack-scheduling" }} {{- $rawConstraints := "" }} - {{- if $configMap }} - {{- $rawConstraints = get $configMap.data "globalAppTopologySpreadConstraints" }} + {{- if .Values._cluster.scheduling }} + {{- $rawConstraints = get .Values._cluster.scheduling "globalAppTopologySpreadConstraints" }} {{- end }} {{- if $rawConstraints }} {{- $rawConstraints | fromYaml | toYaml | nindent 6 }} diff --git a/packages/extra/info/templates/dashboard-resourcemap.yaml b/packages/extra/info/templates/dashboard-resourcemap.yaml index 39da1b37..aa8b7641 100644 --- a/packages/extra/info/templates/dashboard-resourcemap.yaml +++ b/packages/extra/info/templates/dashboard-resourcemap.yaml @@ -1,5 +1,4 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $oidcEnabled := index $cozyConfig.data "oidc-enabled" }} +{{- $oidcEnabled := index .Values._cluster "oidc-enabled" }} apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: diff --git a/packages/extra/info/templates/kubeconfig.yaml b/packages/extra/info/templates/kubeconfig.yaml index d960a587..8a5c150d 100644 --- a/packages/extra/info/templates/kubeconfig.yaml +++ b/packages/extra/info/templates/kubeconfig.yaml @@ -1,23 +1,15 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $host := index $cozyConfig.data "root-host" }} +{{- $host := .Values._namespace.host | default (index .Values._cluster "root-host") }} {{- $k8sClientSecret := lookup "v1" "Secret" "cozy-keycloak" "k8s-client" }} {{- if $k8sClientSecret }} -{{- $apiServerEndpoint := index $cozyConfig.data "api-server-endpoint" }} -{{- $managementKubeconfigEndpoint := default "" (get $cozyConfig.data "management-kubeconfig-endpoint") }} +{{- $apiServerEndpoint := index .Values._cluster "api-server-endpoint" }} +{{- $managementKubeconfigEndpoint := default "" (index .Values._cluster "management-kubeconfig-endpoint") }} {{- if and $managementKubeconfigEndpoint (ne $managementKubeconfigEndpoint "") }} {{- $apiServerEndpoint = $managementKubeconfigEndpoint }} {{- end }} {{- $k8sClient := index $k8sClientSecret.data "client-secret-key" | b64dec }} {{- $rootSaConfigMap := lookup "v1" "ConfigMap" "kube-system" "kube-root-ca.crt" }} {{- $k8sCa := index $rootSaConfigMap.data "ca.crt" | b64enc }} - -{{- if .Capabilities.APIVersions.Has "helm.toolkit.fluxcd.io/v2" }} -{{- $tenantRoot := lookup "helm.toolkit.fluxcd.io/v2" "HelmRelease" "tenant-root" "tenant-root" }} -{{- if and $tenantRoot $tenantRoot.spec $tenantRoot.spec.values $tenantRoot.spec.values.host }} -{{- $host = $tenantRoot.spec.values.host }} -{{- end }} -{{- end }} --- apiVersion: v1 kind: Secret diff --git a/packages/extra/ingress/templates/nginx-ingress.yaml b/packages/extra/ingress/templates/nginx-ingress.yaml index e28918e4..13ad9a6b 100644 --- a/packages/extra/ingress/templates/nginx-ingress.yaml +++ b/packages/extra/ingress/templates/nginx-ingress.yaml @@ -1,6 +1,5 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $exposeIngress := index $cozyConfig.data "expose-ingress" | default "tenant-root" }} -{{- $exposeExternalIPs := (index $cozyConfig.data "expose-external-ips") | default "" | nospace }} +{{- $exposeIngress := (index .Values._cluster "expose-ingress") | default "tenant-root" }} +{{- $exposeExternalIPs := (index .Values._cluster "expose-external-ips") | default "" | nospace }} apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease metadata: @@ -24,6 +23,15 @@ spec: force: true remediation: retries: -1 + valuesFrom: + - kind: Secret + name: cozystack-values + valuesKey: _cluster + targetPath: _cluster + - kind: Secret + name: cozystack-values + valuesKey: _namespace + targetPath: _namespace values: ingress-nginx: fullnameOverride: {{ trimPrefix "tenant-" .Release.Namespace }}-ingress diff --git a/packages/extra/monitoring/templates/alerta/alerta-db.yaml b/packages/extra/monitoring/templates/alerta/alerta-db.yaml index a2cca187..845cd8ba 100644 --- a/packages/extra/monitoring/templates/alerta/alerta-db.yaml +++ b/packages/extra/monitoring/templates/alerta/alerta-db.yaml @@ -5,9 +5,8 @@ metadata: name: alerta-db spec: instances: 2 - {{- $configMap := lookup "v1" "ConfigMap" "cozy-system" "cozystack-scheduling" }} - {{- if $configMap }} - {{- $rawConstraints := get $configMap.data "globalAppTopologySpreadConstraints" }} + {{- if .Values._cluster.scheduling }} + {{- $rawConstraints := get .Values._cluster.scheduling "globalAppTopologySpreadConstraints" }} {{- if $rawConstraints }} {{- $rawConstraints | fromYaml | toYaml | nindent 2 }} labelSelector: diff --git a/packages/extra/monitoring/templates/alerta/alerta.yaml b/packages/extra/monitoring/templates/alerta/alerta.yaml index 72500948..76b7a02b 100644 --- a/packages/extra/monitoring/templates/alerta/alerta.yaml +++ b/packages/extra/monitoring/templates/alerta/alerta.yaml @@ -1,9 +1,6 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $issuerType := (index $cozyConfig.data "clusterissuer") | default "http01" }} - -{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }} -{{- $ingress := index $myNS.metadata.annotations "namespace.cozystack.io/ingress" }} -{{- $host := index $myNS.metadata.annotations "namespace.cozystack.io/host" }} +{{- $issuerType := (index .Values._cluster "clusterissuer") | default "http01" }} +{{- $ingress := .Values._namespace.ingress }} +{{- $host := .Values._namespace.host }} {{- $apiKey := randAlphaNum 32 }} {{- $existingSecret := lookup "v1" "Secret" .Release.Namespace "alerta" }} diff --git a/packages/extra/monitoring/templates/grafana/db.yaml b/packages/extra/monitoring/templates/grafana/db.yaml index f1781cff..662f5cf4 100644 --- a/packages/extra/monitoring/templates/grafana/db.yaml +++ b/packages/extra/monitoring/templates/grafana/db.yaml @@ -6,9 +6,8 @@ spec: instances: 2 storage: size: {{ .Values.grafana.db.size }} - {{- $configMap := lookup "v1" "ConfigMap" "cozy-system" "cozystack-scheduling" }} - {{- if $configMap }} - {{- $rawConstraints := get $configMap.data "globalAppTopologySpreadConstraints" }} + {{- if .Values._cluster.scheduling }} + {{- $rawConstraints := get .Values._cluster.scheduling "globalAppTopologySpreadConstraints" }} {{- if $rawConstraints }} {{- $rawConstraints | fromYaml | toYaml | nindent 2 }} labelSelector: diff --git a/packages/extra/monitoring/templates/grafana/grafana.yaml b/packages/extra/monitoring/templates/grafana/grafana.yaml index 2397fd10..1eadda9e 100644 --- a/packages/extra/monitoring/templates/grafana/grafana.yaml +++ b/packages/extra/monitoring/templates/grafana/grafana.yaml @@ -1,9 +1,6 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $issuerType := (index $cozyConfig.data "clusterissuer") | default "http01" }} - -{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }} -{{- $ingress := index $myNS.metadata.annotations "namespace.cozystack.io/ingress" }} -{{- $host := index $myNS.metadata.annotations "namespace.cozystack.io/host" }} +{{- $issuerType := (index .Values._cluster "clusterissuer") | default "http01" }} +{{- $ingress := .Values._namespace.ingress }} +{{- $host := .Values._namespace.host }} --- apiVersion: grafana.integreatly.org/v1beta1 kind: Grafana diff --git a/packages/extra/seaweedfs/templates/client/cosi-deployment.yaml b/packages/extra/seaweedfs/templates/client/cosi-deployment.yaml index 5307a67f..f73a61bf 100644 --- a/packages/extra/seaweedfs/templates/client/cosi-deployment.yaml +++ b/packages/extra/seaweedfs/templates/client/cosi-deployment.yaml @@ -1,7 +1,5 @@ {{- if eq .Values.topology "Client" }} -{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }} -{{- $ingress := index $myNS.metadata.annotations "namespace.cozystack.io/ingress" }} -{{- $host := index $myNS.metadata.annotations "namespace.cozystack.io/host" }} +{{- $host := .Values._namespace.host }} --- apiVersion: apps/v1 kind: Deployment diff --git a/packages/extra/seaweedfs/templates/ingress.yaml b/packages/extra/seaweedfs/templates/ingress.yaml index 05bf201d..cf4e837e 100644 --- a/packages/extra/seaweedfs/templates/ingress.yaml +++ b/packages/extra/seaweedfs/templates/ingress.yaml @@ -1,9 +1,5 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $issuerType := (index $cozyConfig.data "clusterissuer") | default "http01" }} - -{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }} -{{- $ingress := index $myNS.metadata.annotations "namespace.cozystack.io/ingress" }} -{{- $host := index $myNS.metadata.annotations "namespace.cozystack.io/host" }} +{{- $ingress := .Values._namespace.ingress }} +{{- $host := .Values._namespace.host }} {{- if and (not (eq .Values.topology "Client")) (.Values.filer.grpcHost) }} --- apiVersion: networking.k8s.io/v1 diff --git a/packages/extra/seaweedfs/templates/seaweedfs.yaml b/packages/extra/seaweedfs/templates/seaweedfs.yaml index 02bac375..4ef17dec 100644 --- a/packages/extra/seaweedfs/templates/seaweedfs.yaml +++ b/packages/extra/seaweedfs/templates/seaweedfs.yaml @@ -34,9 +34,8 @@ {{- end }} {{- if not (eq .Values.topology "Client") }} -{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }} -{{- $ingress := index $myNS.metadata.annotations "namespace.cozystack.io/ingress" }} -{{- $host := index $myNS.metadata.annotations "namespace.cozystack.io/host" }} +{{- $ingress := .Values._namespace.ingress }} +{{- $host := .Values._namespace.host }} apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease metadata: @@ -60,6 +59,15 @@ spec: force: true remediation: retries: -1 + valuesFrom: + - kind: Secret + name: cozystack-values + valuesKey: _cluster + targetPath: _cluster + - kind: Secret + name: cozystack-values + valuesKey: _namespace + targetPath: _namespace values: global: serviceAccountName: "{{ .Release.Namespace }}-seaweedfs" diff --git a/packages/library/cozy-lib/templates/_cozyconfig.tpl b/packages/library/cozy-lib/templates/_cozyconfig.tpl index 559f7415..648b2617 100644 --- a/packages/library/cozy-lib/templates/_cozyconfig.tpl +++ b/packages/library/cozy-lib/templates/_cozyconfig.tpl @@ -1,7 +1,130 @@ +{{/* +Cluster-wide configuration helpers. +These helpers read from .Values._cluster which is populated via valuesFrom from Secret cozystack-values. +*/}} + +{{/* +Get the root host for the cluster. +Usage: {{ include "cozy-lib.root-host" . }} +*/}} +{{- define "cozy-lib.root-host" -}} +{{- (index .Values._cluster "root-host") | default "" }} +{{- end }} + +{{/* +Get the bundle name for the cluster. +Usage: {{ include "cozy-lib.bundle-name" . }} +*/}} +{{- define "cozy-lib.bundle-name" -}} +{{- (index .Values._cluster "bundle-name") | default "" }} +{{- end }} + +{{/* +Get the images registry. +Usage: {{ include "cozy-lib.images-registry" . }} +*/}} +{{- define "cozy-lib.images-registry" -}} +{{- (index .Values._cluster "images-registry") | default "" }} +{{- end }} + +{{/* +Get the ipv4 cluster CIDR. +Usage: {{ include "cozy-lib.ipv4-cluster-cidr" . }} +*/}} +{{- define "cozy-lib.ipv4-cluster-cidr" -}} +{{- (index .Values._cluster "ipv4-cluster-cidr") | default "" }} +{{- end }} + +{{/* +Get the ipv4 service CIDR. +Usage: {{ include "cozy-lib.ipv4-service-cidr" . }} +*/}} +{{- define "cozy-lib.ipv4-service-cidr" -}} +{{- (index .Values._cluster "ipv4-service-cidr") | default "" }} +{{- end }} + +{{/* +Get the ipv4 join CIDR. +Usage: {{ include "cozy-lib.ipv4-join-cidr" . }} +*/}} +{{- define "cozy-lib.ipv4-join-cidr" -}} +{{- (index .Values._cluster "ipv4-join-cidr") | default "" }} +{{- end }} + +{{/* +Get scheduling configuration. +Usage: {{ include "cozy-lib.scheduling" . }} +Returns: YAML string of scheduling configuration +*/}} +{{- define "cozy-lib.scheduling" -}} +{{- if .Values._cluster.scheduling }} +{{- .Values._cluster.scheduling | toYaml }} +{{- end }} +{{- end }} + +{{/* +Get branding configuration. +Usage: {{ include "cozy-lib.branding" . }} +Returns: YAML string of branding configuration +*/}} +{{- define "cozy-lib.branding" -}} +{{- if .Values._cluster.branding }} +{{- .Values._cluster.branding | toYaml }} +{{- end }} +{{- end }} + +{{/* +Namespace-specific configuration helpers. +These helpers read from .Values._namespace which is populated via valuesFrom from Secret cozystack-values. +*/}} + +{{/* +Get the host for this namespace. +Usage: {{ include "cozy-lib.ns-host" . }} +*/}} +{{- define "cozy-lib.ns-host" -}} +{{- .Values._namespace.host | default "" }} +{{- end }} + +{{/* +Get the etcd namespace reference. +Usage: {{ include "cozy-lib.ns-etcd" . }} +*/}} +{{- define "cozy-lib.ns-etcd" -}} +{{- .Values._namespace.etcd | default "" }} +{{- end }} + +{{/* +Get the ingress namespace reference. +Usage: {{ include "cozy-lib.ns-ingress" . }} +*/}} +{{- define "cozy-lib.ns-ingress" -}} +{{- .Values._namespace.ingress | default "" }} +{{- end }} + +{{/* +Get the monitoring namespace reference. +Usage: {{ include "cozy-lib.ns-monitoring" . }} +*/}} +{{- define "cozy-lib.ns-monitoring" -}} +{{- .Values._namespace.monitoring | default "" }} +{{- end }} + +{{/* +Get the seaweedfs namespace reference. +Usage: {{ include "cozy-lib.ns-seaweedfs" . }} +*/}} +{{- define "cozy-lib.ns-seaweedfs" -}} +{{- .Values._namespace.seaweedfs | default "" }} +{{- end }} + +{{/* +Legacy helper - kept for backward compatibility during migration. +Loads config into context. Deprecated: use direct .Values._cluster access instead. +*/}} {{- define "cozy-lib.loadCozyConfig" }} {{- include "cozy-lib.checkInput" . }} {{- if not (hasKey (index . 1) "cozyConfig") }} -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $_ := set (index . 1) "cozyConfig" $cozyConfig }} +{{- $_ := set (index . 1) "cozyConfig" (dict "data" ((index . 1).Values._cluster | default dict)) }} {{- end }} {{- end }} diff --git a/packages/system/bucket/templates/ingress.yaml b/packages/system/bucket/templates/ingress.yaml index d8d659c7..f7b4eed4 100644 --- a/packages/system/bucket/templates/ingress.yaml +++ b/packages/system/bucket/templates/ingress.yaml @@ -1,8 +1,6 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }} -{{- $host := index $myNS.metadata.annotations "namespace.cozystack.io/host" }} -{{- $ingress := index $myNS.metadata.annotations "namespace.cozystack.io/ingress" }} -{{- $issuerType := (index $cozyConfig.data "clusterissuer") | default "http01" }} +{{- $host := .Values._namespace.host }} +{{- $ingress := .Values._namespace.ingress }} +{{- $issuerType := (index .Values._cluster "clusterissuer") | default "http01" }} apiVersion: networking.k8s.io/v1 kind: Ingress diff --git a/packages/system/cert-manager-issuers/templates/cluster-issuers.yaml b/packages/system/cert-manager-issuers/templates/cluster-issuers.yaml index 6a70eef0..e93f3f67 100644 --- a/packages/system/cert-manager-issuers/templates/cluster-issuers.yaml +++ b/packages/system/cert-manager-issuers/templates/cluster-issuers.yaml @@ -1,5 +1,4 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $issuerType := (index $cozyConfig.data "clusterissuer") | default "http01" }} +{{- $issuerType := (index .Values._cluster "clusterissuer") | default "http01" }} apiVersion: cert-manager.io/v1 kind: ClusterIssuer diff --git a/packages/system/cozystack-api/templates/api-ingress.yaml b/packages/system/cozystack-api/templates/api-ingress.yaml index 54fdf54b..9226d887 100644 --- a/packages/system/cozystack-api/templates/api-ingress.yaml +++ b/packages/system/cozystack-api/templates/api-ingress.yaml @@ -1,7 +1,6 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $host := index $cozyConfig.data "root-host" }} -{{- $exposeServices := splitList "," ((index $cozyConfig.data "expose-services") | default "") }} -{{- $exposeIngress := index $cozyConfig.data "expose-ingress" | default "tenant-root" }} +{{- $host := index .Values._cluster "root-host" }} +{{- $exposeServices := splitList "," ((index .Values._cluster "expose-services") | default "") }} +{{- $exposeIngress := (index .Values._cluster "expose-ingress") | default "tenant-root" }} {{- if and (has "api" $exposeServices) }} apiVersion: networking.k8s.io/v1 diff --git a/packages/system/dashboard/templates/configmap.yaml b/packages/system/dashboard/templates/configmap.yaml index bbf71610..f148109f 100644 --- a/packages/system/dashboard/templates/configmap.yaml +++ b/packages/system/dashboard/templates/configmap.yaml @@ -1,4 +1,4 @@ -{{- $brandingConfig:= lookup "v1" "ConfigMap" "cozy-system" "cozystack-branding" }} +{{- $brandingConfig := .Values._cluster.branding | default dict }} {{- $tenantText := "v0.38.2" }} {{- $footerText := "Cozystack" }} @@ -16,9 +16,9 @@ metadata: app.kubernetes.io/instance: incloud-web app.kubernetes.io/name: web data: - CUSTOM_TENANT_TEXT: {{ $brandingConfig | dig "data" "tenantText" $tenantText | quote }} - FOOTER_TEXT: {{ $brandingConfig | dig "data" "footerText" $footerText | quote }} - TITLE_TEXT: {{ $brandingConfig | dig "data" "titleText" $titleText | quote }} - LOGO_TEXT: {{ $brandingConfig | dig "data" "logoText" $logoText | quote }} - CUSTOM_LOGO_SVG: {{ $brandingConfig | dig "data" "logoSvg" $logoSvg | quote }} - ICON_SVG: {{ $brandingConfig | dig "data" "iconSvg" $iconSvg | quote }} + CUSTOM_TENANT_TEXT: {{ $brandingConfig.tenantText | default $tenantText | quote }} + FOOTER_TEXT: {{ $brandingConfig.footerText | default $footerText | quote }} + TITLE_TEXT: {{ $brandingConfig.titleText | default $titleText | quote }} + LOGO_TEXT: {{ $brandingConfig.logoText | default $logoText | quote }} + CUSTOM_LOGO_SVG: {{ $brandingConfig.logoSvg | default $logoSvg | quote }} + ICON_SVG: {{ $brandingConfig.iconSvg | default $iconSvg | quote }} diff --git a/packages/system/dashboard/templates/gatekeeper.yaml b/packages/system/dashboard/templates/gatekeeper.yaml index b4503f4f..40f2565f 100644 --- a/packages/system/dashboard/templates/gatekeeper.yaml +++ b/packages/system/dashboard/templates/gatekeeper.yaml @@ -1,6 +1,5 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $host := index $cozyConfig.data "root-host" }} -{{- $oidcEnabled := index $cozyConfig.data "oidc-enabled" }} +{{- $host := index .Values._cluster "root-host" }} +{{- $oidcEnabled := index .Values._cluster "oidc-enabled" }} apiVersion: apps/v1 kind: Deployment diff --git a/packages/system/dashboard/templates/ingress.yaml b/packages/system/dashboard/templates/ingress.yaml index 5a634e2c..aacc1bc1 100644 --- a/packages/system/dashboard/templates/ingress.yaml +++ b/packages/system/dashboard/templates/ingress.yaml @@ -1,8 +1,7 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $issuerType := (index $cozyConfig.data "clusterissuer") | default "http01" }} -{{- $host := index $cozyConfig.data "root-host" }} -{{- $exposeServices := splitList "," ((index $cozyConfig.data "expose-services") | default "") }} -{{- $exposeIngress := index $cozyConfig.data "expose-ingress" | default "tenant-root" }} +{{- $issuerType := (index .Values._cluster "clusterissuer") | default "http01" }} +{{- $host := index .Values._cluster "root-host" }} +{{- $exposeServices := splitList "," ((index .Values._cluster "expose-services") | default "") }} +{{- $exposeIngress := (index .Values._cluster "expose-ingress") | default "tenant-root" }} {{- if and (has "dashboard" $exposeServices) }} apiVersion: networking.k8s.io/v1 diff --git a/packages/system/dashboard/templates/keycloakclient.yaml b/packages/system/dashboard/templates/keycloakclient.yaml index 55ebc5d5..80f7332e 100644 --- a/packages/system/dashboard/templates/keycloakclient.yaml +++ b/packages/system/dashboard/templates/keycloakclient.yaml @@ -1,6 +1,5 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $host := index $cozyConfig.data "root-host" }} -{{- $extraRedirectUris := splitList "," ((index $cozyConfig.data "extra-keycloak-redirect-uri-for-dashboard") | default "") }} +{{- $host := index .Values._cluster "root-host" }} +{{- $extraRedirectUris := splitList "," ((index .Values._cluster "extra-keycloak-redirect-uri-for-dashboard") | default "") }} {{- $existingK8sSecret := lookup "v1" "Secret" .Release.Namespace "k8s-client" }} {{- $existingDashboardSecret := lookup "v1" "Secret" .Release.Namespace "dashboard-client" }} diff --git a/packages/system/dashboard/templates/nginx-config.yaml b/packages/system/dashboard/templates/nginx-config.yaml index c2d6f624..696e9ce9 100644 --- a/packages/system/dashboard/templates/nginx-config.yaml +++ b/packages/system/dashboard/templates/nginx-config.yaml @@ -1,5 +1,4 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $host := index $cozyConfig.data "root-host" }} +{{- $host := index .Values._cluster "root-host" }} apiVersion: v1 data: diff --git a/packages/system/keycloak-configure/templates/configure-kk.yaml b/packages/system/keycloak-configure/templates/configure-kk.yaml index 3fc92c86..7e77ac4c 100644 --- a/packages/system/keycloak-configure/templates/configure-kk.yaml +++ b/packages/system/keycloak-configure/templates/configure-kk.yaml @@ -1,13 +1,12 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $host := index $cozyConfig.data "root-host" }} -{{- $extraRedirectUris := splitList "," ((index $cozyConfig.data "extra-keycloak-redirect-uri-for-dashboard") | default "") }} +{{- $host := index .Values._cluster "root-host" }} +{{- $extraRedirectUris := splitList "," ((index .Values._cluster "extra-keycloak-redirect-uri-for-dashboard") | default "") }} {{- $rootSaConfigMap := lookup "v1" "ConfigMap" "kube-system" "kube-root-ca.crt" }} {{- $k8sCa := index $rootSaConfigMap.data "ca.crt" | b64enc }} {{- $existingK8sSecret := lookup "v1" "Secret" .Release.Namespace "k8s-client" }} {{- $existingKubeappsSecret := lookup "v1" "Secret" .Release.Namespace "kubeapps-client" }} {{- $existingAuthConfig := lookup "v1" "Secret" "cozy-dashboard" "kubeapps-auth-config" }} -{{- $cozystackBranding:= lookup "v1" "ConfigMap" "cozy-system" "cozystack-branding" }} +{{- $brandingConfig := .Values._cluster.branding | default dict }} {{ $k8sClient := "" }} {{- if $existingK8sSecret }} @@ -17,8 +16,8 @@ {{- end }} {{ $branding := "" }} -{{- if $cozystackBranding }} - {{- $branding = index $cozystackBranding.data "branding" }} +{{- if $brandingConfig }} + {{- $branding = $brandingConfig.branding }} {{- end }} --- diff --git a/packages/system/keycloak/templates/db.yaml b/packages/system/keycloak/templates/db.yaml index 70666d7b..1cd98ffc 100644 --- a/packages/system/keycloak/templates/db.yaml +++ b/packages/system/keycloak/templates/db.yaml @@ -6,9 +6,8 @@ spec: instances: 2 storage: size: 20Gi - {{- $configMap := lookup "v1" "ConfigMap" "cozy-system" "cozystack-scheduling" }} - {{- if $configMap }} - {{- $rawConstraints := get $configMap.data "globalAppTopologySpreadConstraints" }} + {{- if .Values._cluster.scheduling }} + {{- $rawConstraints := get .Values._cluster.scheduling "globalAppTopologySpreadConstraints" }} {{- if $rawConstraints }} {{- $rawConstraints | fromYaml | toYaml | nindent 2 }} labelSelector: diff --git a/packages/system/keycloak/templates/ingress.yaml b/packages/system/keycloak/templates/ingress.yaml index 30120619..0e0f56cb 100644 --- a/packages/system/keycloak/templates/ingress.yaml +++ b/packages/system/keycloak/templates/ingress.yaml @@ -1,7 +1,6 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $host := index $cozyConfig.data "root-host" }} -{{- $issuerType := (index $cozyConfig.data "clusterissuer") | default "http01" }} -{{- $exposeIngress := index $cozyConfig.data "expose-ingress" | default "tenant-root" }} +{{- $host := index .Values._cluster "root-host" }} +{{- $issuerType := (index .Values._cluster "clusterissuer") | default "http01" }} +{{- $exposeIngress := (index .Values._cluster "expose-ingress") | default "tenant-root" }} apiVersion: networking.k8s.io/v1 kind: Ingress diff --git a/packages/system/keycloak/templates/sts.yaml b/packages/system/keycloak/templates/sts.yaml index 4705f77c..96d30601 100644 --- a/packages/system/keycloak/templates/sts.yaml +++ b/packages/system/keycloak/templates/sts.yaml @@ -1,6 +1,5 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $host := index $cozyConfig.data "root-host" }} -{{- $clusterDomain := (index $cozyConfig.data "cluster-domain") | default "cozy.local" }} +{{- $host := index .Values._cluster "root-host" }} +{{- $clusterDomain := (index .Values._cluster "cluster-domain") | default "cozy.local" }} {{- $existingPassword := lookup "v1" "Secret" "cozy-keycloak" (printf "%s-credentials" .Release.Name) }} {{- $password := randAlphaNum 16 -}} diff --git a/packages/system/kubevirt-cdi/templates/cdi-uploadproxy-ingress.yaml b/packages/system/kubevirt-cdi/templates/cdi-uploadproxy-ingress.yaml index 58eef4fa..ee89953f 100644 --- a/packages/system/kubevirt-cdi/templates/cdi-uploadproxy-ingress.yaml +++ b/packages/system/kubevirt-cdi/templates/cdi-uploadproxy-ingress.yaml @@ -1,7 +1,6 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $host := index $cozyConfig.data "root-host" }} -{{- $exposeServices := splitList "," ((index $cozyConfig.data "expose-services") | default "") }} -{{- $exposeIngress := index $cozyConfig.data "expose-ingress" | default "tenant-root" }} +{{- $host := index .Values._cluster "root-host" }} +{{- $exposeServices := splitList "," ((index .Values._cluster "expose-services") | default "") }} +{{- $exposeIngress := (index .Values._cluster "expose-ingress") | default "tenant-root" }} {{- if and (has "cdi-uploadproxy" $exposeServices) }} diff --git a/packages/system/kubevirt/templates/vm-exportproxy-ingress.yaml b/packages/system/kubevirt/templates/vm-exportproxy-ingress.yaml index b77743d0..a089f5ef 100644 --- a/packages/system/kubevirt/templates/vm-exportproxy-ingress.yaml +++ b/packages/system/kubevirt/templates/vm-exportproxy-ingress.yaml @@ -1,7 +1,6 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $host := index $cozyConfig.data "root-host" }} -{{- $exposeServices := splitList "," ((index $cozyConfig.data "expose-services") | default "") }} -{{- $exposeIngress := index $cozyConfig.data "expose-ingress" | default "tenant-root" }} +{{- $host := index .Values._cluster "root-host" }} +{{- $exposeServices := splitList "," ((index .Values._cluster "expose-services") | default "") }} +{{- $exposeIngress := (index .Values._cluster "expose-ingress") | default "tenant-root" }} {{- if and (has "vm-exportproxy" $exposeServices) }} apiVersion: networking.k8s.io/v1 diff --git a/packages/tests/cozy-lib-tests/tests/quota_values.yaml b/packages/tests/cozy-lib-tests/tests/quota_values.yaml index bcf6a21e..87e5cf17 100644 --- a/packages/tests/cozy-lib-tests/tests/quota_values.yaml +++ b/packages/tests/cozy-lib-tests/tests/quota_values.yaml @@ -3,3 +3,6 @@ quota: cpu: "20" storage: "5Gi" foobar: "3" + +_cluster: {} +_namespace: {} diff --git a/pkg/registry/apps/application/rest.go b/pkg/registry/apps/application/rest.go index ee189e26..dd211cf3 100644 --- a/pkg/registry/apps/application/rest.go +++ b/pkg/registry/apps/application/rest.go @@ -148,6 +148,11 @@ func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation return nil, fmt.Errorf("expected *appsv1alpha1.Application object, got %T", obj) } + // Validate that values don't contain reserved keys (starting with "_") + if err := validateNoInternalKeys(app.Spec); err != nil { + return nil, apierrors.NewBadRequest(err.Error()) + } + // Convert Application to HelmRelease helmRelease, err := r.ConvertApplicationToHelmRelease(app) if err != nil { @@ -442,6 +447,11 @@ func (r *REST) Update(ctx context.Context, name string, objInfo rest.UpdatedObje return nil, false, fmt.Errorf("expected *appsv1alpha1.Application object, got %T", newObj) } + // Validate that values don't contain reserved keys (starting with "_") + if err := validateNoInternalKeys(app.Spec); err != nil { + return nil, false, apierrors.NewBadRequest(err.Error()) + } + // Convert Application to HelmRelease helmRelease, err := r.ConvertApplicationToHelmRelease(app) if err != nil { @@ -851,8 +861,49 @@ func (r *REST) ConvertApplicationToHelmRelease(app *appsv1alpha1.Application) (* return r.convertApplicationToHelmRelease(app) } +// filterInternalKeys removes keys starting with "_" from the JSON values +func filterInternalKeys(values *apiextv1.JSON) *apiextv1.JSON { + if values == nil || len(values.Raw) == 0 { + return values + } + var data map[string]interface{} + if err := json.Unmarshal(values.Raw, &data); err != nil { + return values + } + for key := range data { + if strings.HasPrefix(key, "_") { + delete(data, key) + } + } + filtered, err := json.Marshal(data) + if err != nil { + return values + } + return &apiextv1.JSON{Raw: filtered} +} + +// validateNoInternalKeys checks that values don't contain keys starting with "_" +func validateNoInternalKeys(values *apiextv1.JSON) error { + if values == nil || len(values.Raw) == 0 { + return nil + } + var data map[string]interface{} + if err := json.Unmarshal(values.Raw, &data); err != nil { + return err + } + for key := range data { + if strings.HasPrefix(key, "_") { + return fmt.Errorf("values key %q is reserved (keys starting with '_' are not allowed)", key) + } + } + return nil +} + // convertHelmReleaseToApplication implements the actual conversion logic func (r *REST) convertHelmReleaseToApplication(hr *helmv2.HelmRelease) (appsv1alpha1.Application, error) { + // Filter out internal keys (starting with "_") from spec + filteredSpec := filterInternalKeys(hr.Spec.Values) + app := appsv1alpha1.Application{ TypeMeta: metav1.TypeMeta{ APIVersion: "apps.cozystack.io/v1alpha1", @@ -868,7 +919,7 @@ func (r *REST) convertHelmReleaseToApplication(hr *helmv2.HelmRelease) (appsv1al Labels: filterPrefixedMap(hr.Labels, LabelPrefix), Annotations: filterPrefixedMap(hr.Annotations, AnnotationPrefix), }, - Spec: hr.Spec.Values, + Spec: filteredSpec, Status: appsv1alpha1.ApplicationStatus{ Version: hr.Status.LastAttemptedRevision, }, @@ -935,6 +986,22 @@ func (r *REST) convertApplicationToHelmRelease(app *appsv1alpha1.Application) (* Retries: -1, }, }, + ValuesFrom: []helmv2.ValuesReference{ + { + Kind: "Secret", + Name: "cozystack-values", + ValuesKey: "_namespace", + TargetPath: "_namespace", + Optional: false, + }, + { + Kind: "Secret", + Name: "cozystack-values", + ValuesKey: "_cluster", + TargetPath: "_cluster", + Optional: false, + }, + }, Values: app.Spec, }, } From a8d32a4bc3cf17751ce7007b634f47bcbdc77686 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Sun, 4 Jan 2026 10:00:12 +0100 Subject: [PATCH 008/889] fix: add default values for _cluster config in cozystack-values Secret Ensure all required keys exist in the _cluster config by merging ConfigMap data with default values. This prevents errors when accessing keys that may not be present in the cozystack ConfigMap. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- packages/core/platform/templates/apps.yaml | 15 ++++++++++++++- packages/core/platform/templates/namespaces.yaml | 15 ++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/packages/core/platform/templates/apps.yaml b/packages/core/platform/templates/apps.yaml index 2477fc17..46c9a370 100644 --- a/packages/core/platform/templates/apps.yaml +++ b/packages/core/platform/templates/apps.yaml @@ -3,6 +3,19 @@ {{- $cozystackScheduling := lookup "v1" "ConfigMap" "cozy-system" "cozystack-scheduling" }} {{- $bundleName := index $cozyConfig.data "bundle-name" }} {{- $bundle := tpl (.Files.Get (printf "bundles/%s.yaml" $bundleName)) . | fromYaml }} +{{/* Default values for _cluster config to ensure all required keys exist */}} +{{- $clusterDefaults := dict + "root-host" "" + "bundle-name" "" + "clusterissuer" "http01" + "oidc-enabled" "false" + "expose-services" "" + "expose-ingress" "tenant-root" + "expose-external-ips" "" + "cluster-domain" "cozy.local" + "api-server-endpoint" "" +}} +{{- $clusterConfig := mergeOverwrite $clusterDefaults ($cozyConfig.data | default dict) }} {{- $host := "example.org" }} {{- $host := "example.org" }} {{- if $cozyConfig.data }} @@ -43,7 +56,7 @@ metadata: type: Opaque stringData: _cluster: | - {{- $cozyConfig.data | toYaml | nindent 4 }} + {{- $clusterConfig | toYaml | nindent 4 }} {{- with $cozystackBranding.data }} branding: {{- . | toYaml | nindent 6 }} diff --git a/packages/core/platform/templates/namespaces.yaml b/packages/core/platform/templates/namespaces.yaml index a4ed5135..58eca481 100644 --- a/packages/core/platform/templates/namespaces.yaml +++ b/packages/core/platform/templates/namespaces.yaml @@ -2,6 +2,19 @@ {{- $cozystackBranding := lookup "v1" "ConfigMap" "cozy-system" "cozystack-branding" }} {{- $cozystackScheduling := lookup "v1" "ConfigMap" "cozy-system" "cozystack-scheduling" }} {{- $bundleName := index $cozyConfig.data "bundle-name" }} +{{/* Default values for _cluster config to ensure all required keys exist */}} +{{- $clusterDefaults := dict + "root-host" "" + "bundle-name" "" + "clusterissuer" "http01" + "oidc-enabled" "false" + "expose-services" "" + "expose-ingress" "tenant-root" + "expose-external-ips" "" + "cluster-domain" "cozy.local" + "api-server-endpoint" "" +}} +{{- $clusterConfig := mergeOverwrite $clusterDefaults ($cozyConfig.data | default dict) }} {{- $bundle := tpl (.Files.Get (printf "bundles/%s.yaml" $bundleName)) . | fromYaml }} {{- $disabledComponents := splitList "," ((index $cozyConfig.data "bundle-disable") | default "") }} {{- $enabledComponents := splitList "," ((index $cozyConfig.data "bundle-enable") | default "") }} @@ -50,7 +63,7 @@ metadata: type: Opaque stringData: _cluster: | - {{- $cozyConfig.data | toYaml | nindent 4 }} + {{- $clusterConfig | toYaml | nindent 4 }} {{- with $cozystackBranding.data }} branding: {{- . | toYaml | nindent 6 }} From 66a756b606f82f8c5a1cde3c23fb55f10d1b372f Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Sun, 4 Jan 2026 10:29:35 +0100 Subject: [PATCH 009/889] fix(ci): ensure correct latest release after backport publishing Replace unreliable getLatestRelease() API with semver-based max version detection. After publishing a backport release, explicitly restore the latest flag on the highest semver release to handle cases where GitHub API ignores make_latest: 'false'. Also remove dead code (unused steps) from tags.yaml workflow. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- .github/workflows/pull-requests-release.yaml | 138 +++++++++++-------- .github/workflows/tags.yaml | 26 ---- 2 files changed, 83 insertions(+), 81 deletions(-) diff --git a/.github/workflows/pull-requests-release.yaml b/.github/workflows/pull-requests-release.yaml index 1e13fa88..72f31b54 100644 --- a/.github/workflows/pull-requests-release.yaml +++ b/.github/workflows/pull-requests-release.yaml @@ -110,67 +110,95 @@ jobs: } } - # Get the latest published release - - name: Get the latest published release - id: latest_release - uses: actions/github-script@v7 - with: - script: | - try { - const rel = await github.rest.repos.getLatestRelease({ - owner: context.repo.owner, - repo: context.repo.repo - }); - core.setOutput('tag', rel.data.tag_name); - } catch (_) { - core.setOutput('tag', ''); - } - - # Compare current tag vs latest using semver-utils - - name: Semver compare - id: semver - uses: madhead/semver-utils@v4.3.0 - with: - version: ${{ steps.get_tag.outputs.tag }} - compare-to: ${{ steps.latest_release.outputs.tag }} - - # Derive flags: prerelease? make_latest? - - name: Calculate publish flags - id: flags - uses: actions/github-script@v7 - with: - script: | - const tag = '${{ steps.get_tag.outputs.tag }}'; // v0.31.5-rc.1 - const m = tag.match(/^v(\d+\.\d+\.\d+)(-(?:alpha|beta|rc)\.\d+)?$/); - if (!m) { - core.setFailed(`❌ tag '${tag}' must match 'vX.Y.Z' or 'vX.Y.Z-(alpha|beta|rc).N'`); - return; - } - const version = m[1] + (m[2] ?? ''); // 0.31.5-rc.1 - const isRc = Boolean(m[2]); - core.setOutput('is_rc', isRc); - const outdated = '${{ steps.semver.outputs.comparison-result }}' === '<'; - core.setOutput('make_latest', isRc || outdated ? 'false' : 'legacy'); - - # Publish draft release with correct flags + # Publish draft release and ensure correct latest flag - name: Publish draft release uses: actions/github-script@v7 with: script: | const tag = '${{ steps.get_tag.outputs.tag }}'; + const m = tag.match(/^v(\d+\.\d+\.\d+)(-(?:alpha|beta|rc)\.\d+)?$/); + if (!m) { + core.setFailed(`❌ tag '${tag}' must match 'vX.Y.Z' or 'vX.Y.Z-(alpha|beta|rc).N'`); + return; + } + const isRc = Boolean(m[2]); + + // Parse semver string to comparable numbers + function parseSemver(v) { + const match = v.replace(/^v/, '').match(/^(\d+)\.(\d+)\.(\d+)/); + if (!match) return null; + return { + major: parseInt(match[1]), + minor: parseInt(match[2]), + patch: parseInt(match[3]) + }; + } + + // Compare two semver objects + function compareSemver(a, b) { + if (a.major !== b.major) return a.major - b.major; + if (a.minor !== b.minor) return a.minor - b.minor; + return a.patch - b.patch; + } + + const currentSemver = parseSemver(tag); + + // Get all releases const releases = await github.rest.repos.listReleases({ owner: context.repo.owner, - repo: context.repo.repo - }); - const draft = releases.data.find(r => r.tag_name === tag && r.draft); - if (!draft) throw new Error(`Draft release for ${tag} not found`); - await github.rest.repos.updateRelease({ - owner: context.repo.owner, - repo: context.repo.repo, - release_id: draft.id, - draft: false, - prerelease: ${{ steps.flags.outputs.is_rc }}, - make_latest: '${{ steps.flags.outputs.make_latest }}' + repo: context.repo.repo, + per_page: 100 }); - console.log(`🚀 Published release for ${tag}`); + // Find draft release to publish + const draft = releases.data.find(r => r.tag_name === tag && r.draft); + if (!draft) throw new Error(`Draft release for ${tag} not found`); + + // Find max semver among published releases (excluding current draft) + const publishedReleases = releases.data + .filter(r => !r.draft && !r.prerelease) + .filter(r => /^v\d+\.\d+\.\d+$/.test(r.tag_name)) + .map(r => ({ id: r.id, tag: r.tag_name, semver: parseSemver(r.tag_name) })) + .filter(r => r.semver !== null); + + let maxRelease = null; + for (const rel of publishedReleases) { + if (!maxRelease || compareSemver(rel.semver, maxRelease.semver) > 0) { + maxRelease = rel; + } + } + + // Determine if this release should be latest + const isOutdated = maxRelease && compareSemver(currentSemver, maxRelease.semver) < 0; + const makeLatest = (isRc || isOutdated) ? 'false' : 'true'; + + if (isRc) { + console.log(`🏷️ ${tag} is a prerelease, make_latest: false`); + } else if (isOutdated) { + console.log(`🏷️ ${tag} < ${maxRelease.tag} (max semver), make_latest: false`); + } else { + console.log(`🏷️ ${tag} is the highest version, make_latest: true`); + } + + // Publish the release + await github.rest.repos.updateRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + release_id: draft.id, + draft: false, + prerelease: isRc, + make_latest: makeLatest + }); + console.log(`🚀 Published release ${tag}`); + + // If this is a backport/outdated release, ensure the correct release is marked as latest + if (isOutdated && maxRelease) { + console.log(`🔧 Ensuring ${maxRelease.tag} remains the latest release...`); + await github.rest.repos.updateRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + release_id: maxRelease.id, + make_latest: 'true' + }); + console.log(`✅ Restored ${maxRelease.tag} as latest release`); + } diff --git a/.github/workflows/tags.yaml b/.github/workflows/tags.yaml index 80100f23..16fcbede 100644 --- a/.github/workflows/tags.yaml +++ b/.github/workflows/tags.yaml @@ -123,32 +123,6 @@ jobs: git commit -m "Prepare release ${GITHUB_REF#refs/tags/}" -s || echo "No changes to commit" git push origin HEAD || true - # Get `latest_version` from latest published release - - name: Get latest published release - if: steps.check_release.outputs.skip == 'false' - id: latest_release - uses: actions/github-script@v7 - with: - script: | - try { - const rel = await github.rest.repos.getLatestRelease({ - owner: context.repo.owner, - repo: context.repo.repo - }); - core.setOutput('tag', rel.data.tag_name); - } catch (_) { - core.setOutput('tag', ''); - } - - # Compare tag (A) with latest (B) - - name: Semver compare - if: steps.check_release.outputs.skip == 'false' - id: semver - uses: madhead/semver-utils@v4.3.0 - with: - version: ${{ steps.tag.outputs.tag }} # A - compare-to: ${{ steps.latest_release.outputs.tag }} # B - # Create or reuse draft release - name: Create / reuse draft release if: steps.check_release.outputs.skip == 'false' From f59665208c2d0d7847150354a5ea6cdd7c291913 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 16 Dec 2025 10:50:19 +0100 Subject: [PATCH 010/889] [kubernetes] Fix endpoints for cilium-gateway Signed-off-by: Andrei Kvapil --- .../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 bfafcaa3ab17e9d679c94b81f2206cead0f5797d Mon Sep 17 00:00:00 2001 From: Andrey Kolkov Date: Fri, 26 Dec 2025 11:44:39 +0400 Subject: [PATCH 011/889] [backups] Implement Velero strategy controller ## What this PR does This patch implements the Reconcile function for BackupJobs with a Velero strategy ref. ### Release note ```release-note [backups] Implement the Velero backup strategy controller. ``` Signed-off-by: Andrey Kolkov --- api/backups/v1alpha1/backupjob_types.go | 2 + cmd/backup-controller/main.go | 13 + examples/desired-backup.yaml | 20 + go.mod | 29 +- go.sum | 62 +- hack/e2e-apps/backup.bats | 226 +++++ hack/e2e-apps/bucket.bats | 6 +- .../backupcontroller/backupjob_controller.go | 29 +- .../velerostrategy_controller.go | 704 ++++++++++++++- .../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 ++++++++++++++---- 13 files changed, 1755 insertions(+), 215 deletions(-) create mode 100644 examples/desired-backup.yaml create mode 100755 hack/e2e-apps/backup.bats create mode 100644 packages/system/backup-controller/templates/strategy.yaml diff --git a/api/backups/v1alpha1/backupjob_types.go b/api/backups/v1alpha1/backupjob_types.go index 1230b209..c594794a 100644 --- a/api/backups/v1alpha1/backupjob_types.go +++ b/api/backups/v1alpha1/backupjob_types.go @@ -85,6 +85,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/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 8a2156cb..6ad144f7 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( 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/vmware-tanzu/velero v1.17.1 go.uber.org/zap v1.27.0 gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.34.1 @@ -80,8 +81,8 @@ 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.62.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.7 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect @@ -90,14 +91,14 @@ require ( 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.60.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect - go.opentelemetry.io/otel v1.35.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.35.0 // indirect - go.opentelemetry.io/otel/sdk v1.34.0 // indirect - go.opentelemetry.io/otel/trace v1.35.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 go.yaml.in/yaml/v2 v2.4.2 // indirect @@ -105,18 +106,18 @@ require ( golang.org/x/crypto v0.42.0 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect golang.org/x/net v0.45.0 // indirect - golang.org/x/oauth2 v0.29.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.11.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-20250303144028-a0af3efb3deb // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect - google.golang.org/grpc v1.72.1 // indirect - google.golang.org/protobuf v1.36.5 // 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 11aad32d..fe868468 100644 --- a/go.sum +++ b/go.sum @@ -144,10 +144,10 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= -github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +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= @@ -179,6 +179,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 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-20221125231312-a49e3df8f510 h1:S2dVYn90KE98chqDkyE9Z4N61UnQd+KOfgp5Iu53llk= @@ -201,24 +203,24 @@ 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.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +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.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= -go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +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= @@ -246,8 +248,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL 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/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= -golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +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= @@ -264,8 +266,8 @@ 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/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= -golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +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= @@ -278,14 +280,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/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950= -google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= -google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= -google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= -google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +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 b/hack/e2e-apps/backup.bats new file mode 100755 index 00000000..0a5c13d9 --- /dev/null +++ b/hack/e2e-apps/backup.bats @@ -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/hack/e2e-apps/bucket.bats b/hack/e2e-apps/bucket.bats index 2621812a..051a90b2 100644 --- a/hack/e2e-apps/bucket.bats +++ b/hack/e2e-apps/bucket.bats @@ -25,6 +25,10 @@ EOF SECRET_KEY=$(jq -r '.spec.secretS3.accessSecretKey' bucket-test-credentials.json) BUCKET_NAME=$(jq -r '.spec.bucketName' bucket-test-credentials.json) + # Tmp for test via s3 endpoint from bucket credentials + S3_ENDPOINT=$(jq -r '.spec.secretS3.endpoint' bucket-test-credentials.json) + echo "# S3 endopint = $S3_ENDPOINT" >&3 + # Start port-forwarding bash -c 'timeout 100s kubectl port-forward service/seaweedfs-s3 -n tenant-root 8333:8333 > /dev/null 2>&1 &' @@ -32,7 +36,7 @@ EOF timeout 30 sh -ec 'until nc -z localhost 8333; do sleep 1; done' # Set up MinIO alias with error handling - mc alias set local https://localhost:8333 $ACCESS_KEY $SECRET_KEY --insecure + mc alias set local $S3_ENDPOINT $ACCESS_KEY $SECRET_KEY --insecure # Upload file to bucket mc cp bucket-test-credentials.json $BUCKET_NAME/bucket-test-credentials.json diff --git a/internal/backupcontroller/backupjob_controller.go b/internal/backupcontroller/backupjob_controller.go index c16a0ec1..fec499b3 100644 --- a/internal/backupcontroller/backupjob_controller.go +++ b/internal/backupcontroller/backupjob_controller.go @@ -6,6 +6,7 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "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/log" @@ -18,31 +19,51 @@ import ( // Velero.strategy.backups.cozystack.io objects. type BackupJobReconciler struct { client.Client - Scheme *runtime.Scheme + 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. diff --git a/internal/backupcontroller/velerostrategy_controller.go b/internal/backupcontroller/velerostrategy_controller.go index 85049b7b..a67355ee 100644 --- a/internal/backupcontroller/velerostrategy_controller.go +++ b/internal/backupcontroller/velerostrategy_controller.go @@ -2,14 +2,716 @@ package backupcontroller import ( "context" + "encoding/json" + "fmt" + "reflect" + "time" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/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/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 + } + + // For now implemented backup logic for apps.cozystack.io VirtualMachine only + logger.Debug("validating BackupJob spec", + "applicationRef", fmt.Sprintf("%s/%s", j.Spec.ApplicationRef.APIGroup, j.Spec.ApplicationRef.Kind), + "storageRef", fmt.Sprintf("%s/%s", j.Spec.StorageRef.APIGroup, j.Spec.StorageRef.Kind)) + + if j.Spec.ApplicationRef.Kind != "VirtualMachine" { + logger.Error(nil, "Unsupported application type", "kind", j.Spec.ApplicationRef.Kind) + return r.markBackupJobFailed(ctx, j, fmt.Sprintf("Unsupported application type: %s", j.Spec.ApplicationRef.Kind)) + } + if j.Spec.ApplicationRef.APIGroup == nil || *j.Spec.ApplicationRef.APIGroup != "apps.cozystack.io" { + logger.Error(nil, "Unsupported application APIGroup", "apiGroup", j.Spec.ApplicationRef.APIGroup, "expected", "apps.cozystack.io") + return r.markBackupJobFailed(ctx, j, fmt.Sprintf("Unsupported application APIGroup: %v, expected apps.cozystack.io", j.Spec.ApplicationRef.APIGroup)) + } + + if j.Spec.StorageRef.Kind != "Bucket" { + logger.Error(nil, "Unsupported storage type", "kind", j.Spec.StorageRef.Kind) + return r.markBackupJobFailed(ctx, j, fmt.Sprintf("Unsupported storage type: %s", j.Spec.StorageRef.Kind)) + } + if j.Spec.StorageRef.APIGroup == nil || *j.Spec.StorageRef.APIGroup != "apps.cozystack.io" { + logger.Error(nil, "Unsupported storage APIGroup", "apiGroup", j.Spec.StorageRef.APIGroup, "expected", "apps.cozystack.io") + return r.markBackupJobFailed(ctx, j, fmt.Sprintf("Unsupported storage APIGroup: %v, expected apps.cozystack.io", j.Spec.StorageRef.APIGroup)) + } + + logger.Debug("BackupJob spec validation passed") + + // 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 + } + timestamp := j.Status.StartedAt.Time.Format("2006-01-02-15-04-05") + veleroBackupName := fmt.Sprintf("%s-%s-%s", j.Namespace, j.Name, timestamp) + logger.Debug("checking for existing Velero Backup", "veleroBackupName", veleroBackupName, "namespace", veleroNamespace) + veleroBackup := &velerov1.Backup{} + veleroBackupKey := client.ObjectKey{Namespace: veleroNamespace, Name: veleroBackupName} + + if err := r.Get(ctx, veleroBackupKey, veleroBackup); err != nil { + if errors.IsNotFound(err) { + // Create Velero Backup + logger.Debug("Velero Backup not found, creating new one", "veleroBackupName", veleroBackupName) + if err := r.createVeleroBackup(ctx, j, veleroBackupName); 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", "veleroBackupName", veleroBackupName) + // Requeue to check status + return ctrl.Result{RequeueAfter: defaultRequeueAfter}, nil + } + logger.Error(err, "failed to get Velero Backup") + return ctrl.Result{}, err + } + logger.Debug("found existing Velero Backup", "veleroBackupName", veleroBackupName, "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, name string) error { + logger := getLogger(ctx) + logger.Debug("createVeleroBackup called", "backupJob", backupJob.Name, "veleroBackupName", name) + + // Resolve StorageRef to get S3 credentials if it's a Bucket + // Prefix with namespace to avoid conflicts in cozy-velero namespace + var locationName string = fmt.Sprintf("%s-%s", backupJob.Namespace, backupJob.Name) + if backupJob.Spec.StorageRef.Kind == "Bucket" { + logger.Debug("resolving Bucket storageRef", "storageRef", backupJob.Spec.StorageRef.Name) + creds, err := r.resolveBucketStorageRef(ctx, backupJob.Spec.StorageRef, backupJob.Namespace) + if err != nil { + logger.Error(err, "failed to resolve Bucket storageRef") + return fmt.Errorf("failed to resolve Bucket storageRef: %w", err) + } + + logger.Debug("discovered S3 credentials from Bucket storageRef", + "bucketName", creds.BucketName, + "endpoint", creds.Endpoint, + "region", creds.Region) + + if err := r.createS3CredsForVelero(ctx, backupJob, creds); err != nil { + return fmt.Errorf("failed to create or update Velero credentials secret: %w", err) + } + // Dynamically create a Velero BackupStorageLocation and VolumeSnapshotLocation using discovered credentials. + + // BackupStorageLocation manifest + // Note: Cannot set owner reference for cross-namespace resources + bsl := &velerov1.BackupStorageLocation{ + ObjectMeta: metav1.ObjectMeta{ + Name: locationName, + Namespace: veleroNamespace, + }, + Spec: velerov1.BackupStorageLocationSpec{ + Provider: "aws", + StorageType: velerov1.StorageType{ + ObjectStorage: &velerov1.ObjectStorageLocation{ + Bucket: creds.BucketName, + }, + }, + Config: map[string]string{ + "checksumAlgorithm": "", + "profile": "default", + "s3ForcePathStyle": "true", + "s3Url": creds.Endpoint, + }, + Credential: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: storageS3SecretName(backupJob.Namespace, backupJob.Name), + }, + Key: "cloud", + }, + }, + } + + // Create or update the BackupStorageLocation + if err := r.createBackupStorageLocation(ctx, bsl); err != nil { + logger.Error(err, "failed to create or update BackupStorageLocation for Velero") + r.Recorder.Event(backupJob, corev1.EventTypeWarning, "BackupStorageLocationCreationFailed", + fmt.Sprintf("Failed to create or update BackupStorageLocation %s/%s: %v", veleroNamespace, locationName, err)) + return fmt.Errorf("failed to create or update Velero BackupStorageLocation: %w", err) + } + r.Recorder.Event(backupJob, corev1.EventTypeNormal, "BackupStorageLocationCreated", + fmt.Sprintf("Created or updated BackupStorageLocation %s/%s", veleroNamespace, locationName)) + + // VolumeSnapshotLocation manifest + // Note: Cannot set owner reference for cross-namespace resources + vsl := &velerov1.VolumeSnapshotLocation{ + ObjectMeta: metav1.ObjectMeta{ + Name: locationName, + Namespace: veleroNamespace, + }, + Spec: velerov1.VolumeSnapshotLocationSpec{ + Provider: "aws", + Credential: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: storageS3SecretName(backupJob.Namespace, backupJob.Name), + }, + Key: "cloud", + }, + Config: map[string]string{ + "region": creds.Region, + "profile": "default", + }, + }, + } + + // Create or update the VolumeSnapshotLocation + if err := r.createVolumeSnapshotLocation(ctx, vsl); err != nil { + logger.Error(err, "failed to create or update VolumeSnapshotLocation for Velero") + r.Recorder.Event(backupJob, corev1.EventTypeWarning, "VolumeSnapshotLocationCreationFailed", + fmt.Sprintf("Failed to create or update VolumeSnapshotLocation %s/%s: %v", veleroNamespace, locationName, err)) + return fmt.Errorf("failed to create or update Velero VolumeSnapshotLocation: %w", err) + } + r.Recorder.Event(backupJob, corev1.EventTypeNormal, "VolumeSnapshotLocationCreated", + fmt.Sprintf("Created or updated VolumeSnapshotLocation %s/%s", veleroNamespace, locationName)) + } + + // Create a Velero Backup (velero.io/v1) using typed object + // Now implemented only for backup of VirtualMachine resources + // Note: Cannot set owner reference for cross-namespace resources + veleroBackup := &velerov1.Backup{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: veleroNamespace, + }, + Spec: velerov1.BackupSpec{ + IncludedNamespaces: []string{backupJob.Namespace}, + IncludedResources: []string{"virtualmachines.kubevirt.io"}, + SnapshotVolumes: boolPtr(true), + StorageLocation: locationName, + VolumeSnapshotLocations: []string{locationName}, + LabelSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app.kubernetes.io/instance": virtualMachinePrefix + backupJob.Spec.ApplicationRef.Name, + }, + }, + }, + } + + if err := r.Create(ctx, veleroBackup); err != nil { + 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-backup", 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/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 f4228ffc20feb94dc9f39653adafc0a10a2cfb9a Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Sun, 4 Jan 2026 12:33:08 +0300 Subject: [PATCH 012/889] [backups] Add templating of velero backups ## What this PR does This patch narrows the scope of the Velero backup strategy controller to simply template Velero Backups according to the application being backed up and the template in the strategy. Creating storage locations is now out of scope. ### Release note ```release-note [backups] Implement templating for Velero backups and remove creation of backup storage locations and volume snapshot locations. ``` 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 | 5 + api/backups/v1alpha1/groupversion_info.go | 7 +- .../{backup.bats => backup.bats.disabled} | 0 hack/e2e-apps/bucket.bats | 6 +- .../backupcontroller/backupjob_controller.go | 19 ++ .../velerostrategy_controller.go | 236 ++++++------------ internal/template/template.go | 68 +++++ internal/template/template_test.go | 68 +++++ 10 files changed, 268 insertions(+), 171 deletions(-) rename hack/e2e-apps/{backup.bats => backup.bats.disabled} (100%) create mode 100644 internal/template/template.go create mode 100644 internal/template/template_test.go 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 c594794a..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 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/hack/e2e-apps/backup.bats b/hack/e2e-apps/backup.bats.disabled similarity index 100% rename from hack/e2e-apps/backup.bats rename to hack/e2e-apps/backup.bats.disabled diff --git a/hack/e2e-apps/bucket.bats b/hack/e2e-apps/bucket.bats index 051a90b2..2621812a 100644 --- a/hack/e2e-apps/bucket.bats +++ b/hack/e2e-apps/bucket.bats @@ -25,10 +25,6 @@ EOF SECRET_KEY=$(jq -r '.spec.secretS3.accessSecretKey' bucket-test-credentials.json) BUCKET_NAME=$(jq -r '.spec.bucketName' bucket-test-credentials.json) - # Tmp for test via s3 endpoint from bucket credentials - S3_ENDPOINT=$(jq -r '.spec.secretS3.endpoint' bucket-test-credentials.json) - echo "# S3 endopint = $S3_ENDPOINT" >&3 - # Start port-forwarding bash -c 'timeout 100s kubectl port-forward service/seaweedfs-s3 -n tenant-root 8333:8333 > /dev/null 2>&1 &' @@ -36,7 +32,7 @@ EOF timeout 30 sh -ec 'until nc -z localhost 8333; do sleep 1; done' # Set up MinIO alias with error handling - mc alias set local $S3_ENDPOINT $ACCESS_KEY $SECRET_KEY --insecure + mc alias set local https://localhost:8333 $ACCESS_KEY $SECRET_KEY --insecure # Upload file to bucket mc cp bucket-test-credentials.json $BUCKET_NAME/bucket-test-credentials.json diff --git a/internal/backupcontroller/backupjob_controller.go b/internal/backupcontroller/backupjob_controller.go index fec499b3..0db2e1dd 100644 --- a/internal/backupcontroller/backupjob_controller.go +++ b/internal/backupcontroller/backupjob_controller.go @@ -2,13 +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" @@ -19,6 +24,8 @@ import ( // Velero.strategy.backups.cozystack.io objects. type BackupJobReconciler struct { client.Client + dynamic.Interface + meta.RESTMapper Scheme *runtime.Scheme Recorder record.EventRecorder } @@ -68,6 +75,18 @@ func (r *BackupJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( // 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 a67355ee..066325c0 100644 --- a/internal/backupcontroller/velerostrategy_controller.go +++ b/internal/backupcontroller/velerostrategy_controller.go @@ -9,6 +9,7 @@ import ( 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" @@ -18,6 +19,7 @@ import ( 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" @@ -87,31 +89,6 @@ func (r *BackupJobReconciler) reconcileVelero(ctx context.Context, j *backupsv1a return ctrl.Result{}, nil } - // For now implemented backup logic for apps.cozystack.io VirtualMachine only - logger.Debug("validating BackupJob spec", - "applicationRef", fmt.Sprintf("%s/%s", j.Spec.ApplicationRef.APIGroup, j.Spec.ApplicationRef.Kind), - "storageRef", fmt.Sprintf("%s/%s", j.Spec.StorageRef.APIGroup, j.Spec.StorageRef.Kind)) - - if j.Spec.ApplicationRef.Kind != "VirtualMachine" { - logger.Error(nil, "Unsupported application type", "kind", j.Spec.ApplicationRef.Kind) - return r.markBackupJobFailed(ctx, j, fmt.Sprintf("Unsupported application type: %s", j.Spec.ApplicationRef.Kind)) - } - if j.Spec.ApplicationRef.APIGroup == nil || *j.Spec.ApplicationRef.APIGroup != "apps.cozystack.io" { - logger.Error(nil, "Unsupported application APIGroup", "apiGroup", j.Spec.ApplicationRef.APIGroup, "expected", "apps.cozystack.io") - return r.markBackupJobFailed(ctx, j, fmt.Sprintf("Unsupported application APIGroup: %v, expected apps.cozystack.io", j.Spec.ApplicationRef.APIGroup)) - } - - if j.Spec.StorageRef.Kind != "Bucket" { - logger.Error(nil, "Unsupported storage type", "kind", j.Spec.StorageRef.Kind) - return r.markBackupJobFailed(ctx, j, fmt.Sprintf("Unsupported storage type: %s", j.Spec.StorageRef.Kind)) - } - if j.Spec.StorageRef.APIGroup == nil || *j.Spec.StorageRef.APIGroup != "apps.cozystack.io" { - logger.Error(nil, "Unsupported storage APIGroup", "apiGroup", j.Spec.StorageRef.APIGroup, "expected", "apps.cozystack.io") - return r.markBackupJobFailed(ctx, j, fmt.Sprintf("Unsupported storage APIGroup: %v, expected apps.cozystack.io", j.Spec.StorageRef.APIGroup)) - } - - logger.Debug("BackupJob spec validation passed") - // 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 { @@ -148,37 +125,53 @@ func (r *BackupJobReconciler) reconcileVelero(ctx context.Context, j *backupsv1a logger.Error(nil, "StartedAt is nil after status update, this should not happen") return ctrl.Result{RequeueAfter: defaultRequeueAfter}, nil } - timestamp := j.Status.StartedAt.Time.Format("2006-01-02-15-04-05") - veleroBackupName := fmt.Sprintf("%s-%s-%s", j.Namespace, j.Name, timestamp) - logger.Debug("checking for existing Velero Backup", "veleroBackupName", veleroBackupName, "namespace", veleroNamespace) - veleroBackup := &velerov1.Backup{} - veleroBackupKey := client.ObjectKey{Namespace: veleroNamespace, Name: veleroBackupName} + 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.Get(ctx, veleroBackupKey, veleroBackup); err != nil { - if errors.IsNotFound(err) { - // Create Velero Backup - logger.Debug("Velero Backup not found, creating new one", "veleroBackupName", veleroBackupName) - if err := r.createVeleroBackup(ctx, j, veleroBackupName); 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", "veleroBackupName", veleroBackupName) - // Requeue to check status - return ctrl.Result{RequeueAfter: defaultRequeueAfter}, nil - } + if err := r.List(ctx, veleroBackupList, opts...); err != nil { logger.Error(err, "failed to get Velero Backup") return ctrl.Result{}, err } - logger.Debug("found existing Velero Backup", "veleroBackupName", veleroBackupName, "phase", veleroBackup.Status.Phase) + + 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 @@ -519,126 +512,43 @@ func (r *BackupJobReconciler) markBackupJobFailed(ctx context.Context, backupJob return ctrl.Result{}, nil } -func (r *BackupJobReconciler) createVeleroBackup(ctx context.Context, backupJob *backupsv1alpha1.BackupJob, name string) error { +func (r *BackupJobReconciler) createVeleroBackup(ctx context.Context, backupJob *backupsv1alpha1.BackupJob, strategy *strategyv1alpha1.Velero) error { logger := getLogger(ctx) - logger.Debug("createVeleroBackup called", "backupJob", backupJob.Name, "veleroBackupName", name) + logger.Debug("createVeleroBackup called", "strategy", strategy.Name) - // Resolve StorageRef to get S3 credentials if it's a Bucket - // Prefix with namespace to avoid conflicts in cozy-velero namespace - var locationName string = fmt.Sprintf("%s-%s", backupJob.Namespace, backupJob.Name) - if backupJob.Spec.StorageRef.Kind == "Bucket" { - logger.Debug("resolving Bucket storageRef", "storageRef", backupJob.Spec.StorageRef.Name) - creds, err := r.resolveBucketStorageRef(ctx, backupJob.Spec.StorageRef, backupJob.Namespace) - if err != nil { - logger.Error(err, "failed to resolve Bucket storageRef") - return fmt.Errorf("failed to resolve Bucket storageRef: %w", err) - } - - logger.Debug("discovered S3 credentials from Bucket storageRef", - "bucketName", creds.BucketName, - "endpoint", creds.Endpoint, - "region", creds.Region) - - if err := r.createS3CredsForVelero(ctx, backupJob, creds); err != nil { - return fmt.Errorf("failed to create or update Velero credentials secret: %w", err) - } - // Dynamically create a Velero BackupStorageLocation and VolumeSnapshotLocation using discovered credentials. - - // BackupStorageLocation manifest - // Note: Cannot set owner reference for cross-namespace resources - bsl := &velerov1.BackupStorageLocation{ - ObjectMeta: metav1.ObjectMeta{ - Name: locationName, - Namespace: veleroNamespace, - }, - Spec: velerov1.BackupStorageLocationSpec{ - Provider: "aws", - StorageType: velerov1.StorageType{ - ObjectStorage: &velerov1.ObjectStorageLocation{ - Bucket: creds.BucketName, - }, - }, - Config: map[string]string{ - "checksumAlgorithm": "", - "profile": "default", - "s3ForcePathStyle": "true", - "s3Url": creds.Endpoint, - }, - Credential: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: storageS3SecretName(backupJob.Namespace, backupJob.Name), - }, - Key: "cloud", - }, - }, - } - - // Create or update the BackupStorageLocation - if err := r.createBackupStorageLocation(ctx, bsl); err != nil { - logger.Error(err, "failed to create or update BackupStorageLocation for Velero") - r.Recorder.Event(backupJob, corev1.EventTypeWarning, "BackupStorageLocationCreationFailed", - fmt.Sprintf("Failed to create or update BackupStorageLocation %s/%s: %v", veleroNamespace, locationName, err)) - return fmt.Errorf("failed to create or update Velero BackupStorageLocation: %w", err) - } - r.Recorder.Event(backupJob, corev1.EventTypeNormal, "BackupStorageLocationCreated", - fmt.Sprintf("Created or updated BackupStorageLocation %s/%s", veleroNamespace, locationName)) - - // VolumeSnapshotLocation manifest - // Note: Cannot set owner reference for cross-namespace resources - vsl := &velerov1.VolumeSnapshotLocation{ - ObjectMeta: metav1.ObjectMeta{ - Name: locationName, - Namespace: veleroNamespace, - }, - Spec: velerov1.VolumeSnapshotLocationSpec{ - Provider: "aws", - Credential: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: storageS3SecretName(backupJob.Namespace, backupJob.Name), - }, - Key: "cloud", - }, - Config: map[string]string{ - "region": creds.Region, - "profile": "default", - }, - }, - } - - // Create or update the VolumeSnapshotLocation - if err := r.createVolumeSnapshotLocation(ctx, vsl); err != nil { - logger.Error(err, "failed to create or update VolumeSnapshotLocation for Velero") - r.Recorder.Event(backupJob, corev1.EventTypeWarning, "VolumeSnapshotLocationCreationFailed", - fmt.Sprintf("Failed to create or update VolumeSnapshotLocation %s/%s: %v", veleroNamespace, locationName, err)) - return fmt.Errorf("failed to create or update Velero VolumeSnapshotLocation: %w", err) - } - r.Recorder.Event(backupJob, corev1.EventTypeNormal, "VolumeSnapshotLocationCreated", - fmt.Sprintf("Created or updated VolumeSnapshotLocation %s/%s", veleroNamespace, locationName)) + 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 } - // Create a Velero Backup (velero.io/v1) using typed object - // Now implemented only for backup of VirtualMachine resources - // Note: Cannot set owner reference for cross-namespace resources + veleroBackupSpec, err := template.Template(&strategy.Spec.Template.Spec, app.Object) + if err != nil { + return err + } veleroBackup := &velerov1.Backup{ ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: veleroNamespace, - }, - Spec: velerov1.BackupSpec{ - IncludedNamespaces: []string{backupJob.Namespace}, - IncludedResources: []string{"virtualmachines.kubevirt.io"}, - SnapshotVolumes: boolPtr(true), - StorageLocation: locationName, - VolumeSnapshotLocations: []string{locationName}, - LabelSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "app.kubernetes.io/instance": virtualMachinePrefix + backupJob.Spec.ApplicationRef.Name, - }, + 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)) @@ -675,7 +585,7 @@ func (r *BackupJobReconciler) createBackupResource(ctx context.Context, backupJo backup := &backupsv1alpha1.Backup{ ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%s-backup", backupJob.Name), + Name: fmt.Sprintf("%s", backupJob.Name), Namespace: backupJob.Namespace, OwnerReferences: []metav1.OwnerReference{ { 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)) + } +} From bf1928c96f8ed61c87d3d4b923b24d83d08f9c85 Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Mon, 5 Jan 2026 15:27:36 +0300 Subject: [PATCH 013/889] [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 --- 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 ce6cabdd..ee57f193 --- 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/cozyhr/raw/refs/heads/main/hack/install.sh" | sh -s -- -v "${COZYHR_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 36836fd84e77a1234b21a7d86df665314490f90a Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Mon, 5 Jan 2026 17:35:56 +0300 Subject: [PATCH 014/889] [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 --- 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 6ca8011dfa85302d96372e8f7176ffdd41a30d77 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 015/889] [ingress] Add topology anti-affinities Signed-off-by: nbykov0 <166552198+nbykov0@users.noreply.github.com> --- 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 2e6181054779d0c89d95dc45467409c4c1fb720c Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Sun, 4 Jan 2026 11:08:51 +0100 Subject: [PATCH 016/889] refactor: replace Helm lookup with valuesFrom mechanism Replace Helm lookup functions with FluxCD valuesFrom mechanism for reading cluster and namespace configuration. Changes: - Create Secret cozystack-values in each namespace with values.yaml key containing _cluster and _namespace configuration as nested YAML - Configure HelmReleases to read from this Secret via valuesFrom (valuesKey defaults to values.yaml, so it can be omitted) - Update cozy-lib helpers to access config via .Values._cluster - Add default values for required _cluster keys to ensure all fields exist - Update Go code (cozystack-api and helm reconciler) to use new format This eliminates the need for Helm lookup functions while maintaining the same configuration interface for charts. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- cmd/cozystack-controller/main.go | 16 -- ...ystackresourcedefinition_helmreconciler.go | 37 +++- internal/controller/system_helm_reconciler.go | 140 --------------- internal/controller/tenant_helm_reconciler.go | 159 ------------------ .../apps/bucket/templates/bucketclaim.yaml | 3 +- .../apps/bucket/templates/helmrelease.yaml | 3 + .../apps/clickhouse/templates/chkeeper.yaml | 3 +- .../apps/clickhouse/templates/clickhouse.yaml | 3 +- .../apps/ferretdb/templates/postgres.yaml | 5 +- .../apps/foundationdb/templates/cluster.yaml | 3 +- .../apps/kubernetes/templates/cluster.yaml | 12 +- .../helmreleases/monitoring-agents.yaml | 3 +- .../helmreleases/vertical-pod-autoscaler.yaml | 6 +- .../apps/kubernetes/templates/ingress.yaml | 3 +- packages/apps/nats/templates/nats.yaml | 6 +- packages/apps/postgres/templates/db.yaml | 5 +- packages/apps/tenant/templates/etcd.yaml | 3 + packages/apps/tenant/templates/info.yaml | 3 + packages/apps/tenant/templates/ingress.yaml | 3 + .../apps/tenant/templates/keycloakgroups.yaml | 3 +- .../apps/tenant/templates/monitoring.yaml | 3 + packages/apps/tenant/templates/namespace.yaml | 86 +++++++--- packages/apps/tenant/templates/seaweedfs.yaml | 3 + .../virtual-machine/templates/_helpers.tpl | 5 +- .../apps/vm-instance/templates/_helpers.tpl | 5 +- packages/apps/vpn/templates/secret.yaml | 3 +- packages/core/platform/templates/apps.yaml | 51 ++++++ .../core/platform/templates/helmreleases.yaml | 3 + .../core/platform/templates/namespaces.yaml | 36 ++++ .../bootbox/templates/matchbox/ingress.yaml | 9 +- .../bootbox/templates/matchbox/machines.yaml | 7 +- .../extra/etcd/templates/etcd-cluster.yaml | 5 +- .../info/templates/dashboard-resourcemap.yaml | 3 +- packages/extra/info/templates/kubeconfig.yaml | 17 +- .../ingress/templates/nginx-ingress.yaml | 8 +- .../templates/alerta/alerta-db.yaml | 5 +- .../monitoring/templates/alerta/alerta.yaml | 9 +- .../monitoring/templates/grafana/db.yaml | 5 +- .../monitoring/templates/grafana/grafana.yaml | 9 +- .../templates/client/cosi-deployment.yaml | 4 +- .../extra/seaweedfs/templates/ingress.yaml | 8 +- .../extra/seaweedfs/templates/seaweedfs.yaml | 8 +- .../cozy-lib/templates/_cozyconfig.tpl | 127 +++++++++++++- packages/system/bucket/templates/ingress.yaml | 8 +- .../templates/cluster-issuers.yaml | 3 +- .../cozystack-api/templates/api-ingress.yaml | 7 +- .../system/dashboard/templates/configmap.yaml | 14 +- .../dashboard/templates/gatekeeper.yaml | 5 +- .../system/dashboard/templates/ingress.yaml | 9 +- .../dashboard/templates/keycloakclient.yaml | 5 +- .../dashboard/templates/nginx-config.yaml | 3 +- .../templates/configure-kk.yaml | 14 +- packages/system/keycloak/templates/db.yaml | 5 +- .../system/keycloak/templates/ingress.yaml | 7 +- packages/system/keycloak/templates/sts.yaml | 5 +- .../templates/cdi-uploadproxy-ingress.yaml | 7 +- .../templates/vm-exportproxy-ingress.yaml | 7 +- .../cozy-lib-tests/tests/quota_values.yaml | 3 + pkg/registry/apps/application/rest.go | 59 ++++++- 59 files changed, 494 insertions(+), 505 deletions(-) delete mode 100644 internal/controller/system_helm_reconciler.go delete mode 100644 internal/controller/tenant_helm_reconciler.go diff --git a/cmd/cozystack-controller/main.go b/cmd/cozystack-controller/main.go index 91889224..0e7199d3 100644 --- a/cmd/cozystack-controller/main.go +++ b/cmd/cozystack-controller/main.go @@ -200,22 +200,6 @@ func main() { os.Exit(1) } - if err = (&controller.TenantHelmReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "TenantHelmReconciler") - os.Exit(1) - } - - if err = (&controller.CozystackConfigReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "CozystackConfigReconciler") - os.Exit(1) - } - cozyAPIKind := "DaemonSet" if reconcileDeployment { cozyAPIKind = "Deployment" diff --git a/internal/controller/cozystackresourcedefinition_helmreconciler.go b/internal/controller/cozystackresourcedefinition_helmreconciler.go index c086b56f..1ee4a2b7 100644 --- a/internal/controller/cozystackresourcedefinition_helmreconciler.go +++ b/internal/controller/cozystackresourcedefinition_helmreconciler.go @@ -97,7 +97,34 @@ func (r *CozystackResourceDefinitionHelmReconciler) updateHelmReleasesForCRD(ctx return nil } -// updateHelmReleaseChart updates the chart in HelmRelease based on CozystackResourceDefinition +// expectedValuesFrom returns the expected valuesFrom configuration for HelmReleases +func expectedValuesFrom() []helmv2.ValuesReference { + return []helmv2.ValuesReference{ + { + Kind: "Secret", + Name: "cozystack-values", + }, + } +} + +// valuesFromEqual compares two ValuesReference slices +func valuesFromEqual(a, b []helmv2.ValuesReference) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i].Kind != b[i].Kind || + a[i].Name != b[i].Name || + a[i].ValuesKey != b[i].ValuesKey || + a[i].TargetPath != b[i].TargetPath || + a[i].Optional != b[i].Optional { + return false + } + } + return true +} + +// updateHelmReleaseChart updates the chart and valuesFrom in HelmRelease based on CozystackResourceDefinition func (r *CozystackResourceDefinitionHelmReconciler) updateHelmReleaseChart(ctx context.Context, hr *helmv2.HelmRelease, crd *cozyv1alpha1.CozystackResourceDefinition) error { logger := log.FromContext(ctx) hrCopy := hr.DeepCopy() @@ -154,6 +181,14 @@ func (r *CozystackResourceDefinitionHelmReconciler) updateHelmReleaseChart(ctx c } } + // Check and update valuesFrom configuration + expected := expectedValuesFrom() + if !valuesFromEqual(hrCopy.Spec.ValuesFrom, expected) { + logger.V(4).Info("Updating HelmRelease valuesFrom", "name", hr.Name, "namespace", hr.Namespace) + hrCopy.Spec.ValuesFrom = expected + updated = true + } + if updated { logger.V(4).Info("Updating HelmRelease chart", "name", hr.Name, "namespace", hr.Namespace) if err := r.Update(ctx, hrCopy); err != nil { diff --git a/internal/controller/system_helm_reconciler.go b/internal/controller/system_helm_reconciler.go deleted file mode 100644 index 6e40027c..00000000 --- a/internal/controller/system_helm_reconciler.go +++ /dev/null @@ -1,140 +0,0 @@ -package controller - -import ( - "context" - "crypto/sha256" - "encoding/hex" - "fmt" - "sort" - "time" - - helmv2 "github.com/fluxcd/helm-controller/api/v2" - corev1 "k8s.io/api/core/v1" - kerrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/event" - "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/predicate" -) - -type CozystackConfigReconciler struct { - client.Client - Scheme *runtime.Scheme -} - -var configMapNames = []string{"cozystack", "cozystack-branding", "cozystack-scheduling"} - -const configMapNamespace = "cozy-system" -const digestAnnotation = "cozystack.io/cozy-config-digest" -const forceReconcileKey = "reconcile.fluxcd.io/forceAt" -const requestedAt = "reconcile.fluxcd.io/requestedAt" - -func (r *CozystackConfigReconciler) Reconcile(ctx context.Context, _ ctrl.Request) (ctrl.Result, error) { - log := log.FromContext(ctx) - time.Sleep(2 * time.Second) - - digest, err := r.computeDigest(ctx) - if err != nil { - log.Error(err, "failed to compute config digest") - return ctrl.Result{}, nil - } - - var helmList helmv2.HelmReleaseList - if err := r.List(ctx, &helmList); err != nil { - return ctrl.Result{}, fmt.Errorf("failed to list HelmReleases: %w", err) - } - - now := time.Now().Format(time.RFC3339Nano) - updated := 0 - - for _, hr := range helmList.Items { - isSystemApp := hr.Labels["cozystack.io/system-app"] == "true" - isTenantRoot := hr.Namespace == "tenant-root" && hr.Name == "tenant-root" - if !isSystemApp && !isTenantRoot { - continue - } - patchTarget := hr.DeepCopy() - - if hr.Annotations == nil { - hr.Annotations = map[string]string{} - } - - if hr.Annotations[digestAnnotation] == digest { - continue - } - patchTarget.Annotations[digestAnnotation] = digest - patchTarget.Annotations[forceReconcileKey] = now - patchTarget.Annotations[requestedAt] = now - - patch := client.MergeFrom(hr.DeepCopy()) - if err := r.Patch(ctx, patchTarget, patch); err != nil { - log.Error(err, "failed to patch HelmRelease", "name", hr.Name, "namespace", hr.Namespace) - continue - } - updated++ - log.Info("patched HelmRelease with new config digest", "name", hr.Name, "namespace", hr.Namespace) - } - - log.Info("finished reconciliation", "updatedHelmReleases", updated) - return ctrl.Result{}, nil -} - -func (r *CozystackConfigReconciler) computeDigest(ctx context.Context) (string, error) { - hash := sha256.New() - - for _, name := range configMapNames { - var cm corev1.ConfigMap - err := r.Get(ctx, client.ObjectKey{Namespace: configMapNamespace, Name: name}, &cm) - if err != nil { - if kerrors.IsNotFound(err) { - continue // ignore missing - } - return "", err - } - - // Sort keys for consistent hashing - var keys []string - for k := range cm.Data { - keys = append(keys, k) - } - sort.Strings(keys) - - for _, k := range keys { - v := cm.Data[k] - fmt.Fprintf(hash, "%s:%s=%s\n", name, k, v) - } - } - - return hex.EncodeToString(hash.Sum(nil)), nil -} - -func (r *CozystackConfigReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - WithEventFilter(predicate.Funcs{ - UpdateFunc: func(e event.UpdateEvent) bool { - cm, ok := e.ObjectNew.(*corev1.ConfigMap) - return ok && cm.Namespace == configMapNamespace && contains(configMapNames, cm.Name) - }, - CreateFunc: func(e event.CreateEvent) bool { - cm, ok := e.Object.(*corev1.ConfigMap) - return ok && cm.Namespace == configMapNamespace && contains(configMapNames, cm.Name) - }, - DeleteFunc: func(e event.DeleteEvent) bool { - cm, ok := e.Object.(*corev1.ConfigMap) - return ok && cm.Namespace == configMapNamespace && contains(configMapNames, cm.Name) - }, - }). - For(&corev1.ConfigMap{}). - Complete(r) -} - -func contains(slice []string, val string) bool { - for _, s := range slice { - if s == val { - return true - } - } - return false -} diff --git a/internal/controller/tenant_helm_reconciler.go b/internal/controller/tenant_helm_reconciler.go deleted file mode 100644 index 28b4ad16..00000000 --- a/internal/controller/tenant_helm_reconciler.go +++ /dev/null @@ -1,159 +0,0 @@ -package controller - -import ( - "context" - "fmt" - "strings" - "time" - - e "errors" - - helmv2 "github.com/fluxcd/helm-controller/api/v2" - "gopkg.in/yaml.v2" - corev1 "k8s.io/api/core/v1" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - "k8s.io/apimachinery/pkg/api/errors" - "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" -) - -type TenantHelmReconciler struct { - client.Client - Scheme *runtime.Scheme -} - -func (r *TenantHelmReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - logger := log.FromContext(ctx) - time.Sleep(2 * time.Second) - - hr := &helmv2.HelmRelease{} - if err := r.Get(ctx, req.NamespacedName, hr); err != nil { - if errors.IsNotFound(err) { - return ctrl.Result{}, nil - } - logger.Error(err, "unable to fetch HelmRelease") - return ctrl.Result{}, err - } - - if !strings.HasPrefix(hr.Name, "tenant-") { - return ctrl.Result{}, nil - } - - if len(hr.Status.Conditions) == 0 || hr.Status.Conditions[0].Type != "Ready" { - return ctrl.Result{}, nil - } - - if len(hr.Status.History) == 0 { - logger.Info("no history in HelmRelease status", "name", hr.Name) - return ctrl.Result{}, nil - } - - if hr.Status.History[0].Status != "deployed" { - return ctrl.Result{}, nil - } - - newDigest := hr.Status.History[0].Digest - var hrList helmv2.HelmReleaseList - childNamespace := getChildNamespace(hr.Namespace, hr.Name) - if childNamespace == "tenant-root" && hr.Name == "tenant-root" { - if hr.Spec.Values == nil { - logger.Error(e.New("hr.Spec.Values is nil"), "cant annotate tenant-root ns") - return ctrl.Result{}, nil - } - err := annotateTenantRootNs(*hr.Spec.Values, r.Client) - if err != nil { - logger.Error(err, "cant annotate tenant-root ns") - return ctrl.Result{}, nil - } - logger.Info("namespace 'tenant-root' annotated") - } - - if err := r.List(ctx, &hrList, client.InNamespace(childNamespace)); err != nil { - logger.Error(err, "unable to list HelmReleases in namespace", "namespace", hr.Name) - return ctrl.Result{}, err - } - - for _, item := range hrList.Items { - if item.Name == hr.Name { - continue - } - oldDigest := item.GetAnnotations()["cozystack.io/tenant-config-digest"] - if oldDigest == newDigest { - continue - } - patchTarget := item.DeepCopy() - - if patchTarget.Annotations == nil { - patchTarget.Annotations = map[string]string{} - } - ts := time.Now().Format(time.RFC3339Nano) - - patchTarget.Annotations["cozystack.io/tenant-config-digest"] = newDigest - patchTarget.Annotations["reconcile.fluxcd.io/forceAt"] = ts - patchTarget.Annotations["reconcile.fluxcd.io/requestedAt"] = ts - - patch := client.MergeFrom(item.DeepCopy()) - if err := r.Patch(ctx, patchTarget, patch); err != nil { - logger.Error(err, "failed to patch HelmRelease", "name", patchTarget.Name) - continue - } - - logger.Info("patched HelmRelease with new digest", "name", patchTarget.Name, "digest", newDigest, "version", hr.Status.History[0].Version) - } - - return ctrl.Result{}, nil -} - -func (r *TenantHelmReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&helmv2.HelmRelease{}). - Complete(r) -} - -func getChildNamespace(currentNamespace, hrName string) string { - tenantName := strings.TrimPrefix(hrName, "tenant-") - - switch { - case currentNamespace == "tenant-root" && hrName == "tenant-root": - // 1) root tenant inside root namespace - return "tenant-root" - - case currentNamespace == "tenant-root": - // 2) any other tenant in root namespace - return fmt.Sprintf("tenant-%s", tenantName) - - default: - // 3) tenant in a dedicated namespace - return fmt.Sprintf("%s-%s", currentNamespace, tenantName) - } -} - -func annotateTenantRootNs(values apiextensionsv1.JSON, c client.Client) error { - var data map[string]interface{} - if err := yaml.Unmarshal(values.Raw, &data); err != nil { - return fmt.Errorf("failed to parse HelmRelease values: %w", err) - } - - host, ok := data["host"].(string) - if !ok || host == "" { - return fmt.Errorf("host field not found or not a string") - } - - var ns corev1.Namespace - if err := c.Get(context.TODO(), client.ObjectKey{Name: "tenant-root"}, &ns); err != nil { - return fmt.Errorf("failed to get namespace tenant-root: %w", err) - } - - if ns.Annotations == nil { - ns.Annotations = map[string]string{} - } - ns.Annotations["namespace.cozystack.io/host"] = host - - if err := c.Update(context.TODO(), &ns); err != nil { - return fmt.Errorf("failed to update namespace: %w", err) - } - - return nil -} diff --git a/packages/apps/bucket/templates/bucketclaim.yaml b/packages/apps/bucket/templates/bucketclaim.yaml index cebf95b4..5cfdc1c1 100644 --- a/packages/apps/bucket/templates/bucketclaim.yaml +++ b/packages/apps/bucket/templates/bucketclaim.yaml @@ -1,5 +1,4 @@ -{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }} -{{- $seaweedfs := index $myNS.metadata.annotations "namespace.cozystack.io/seaweedfs" }} +{{- $seaweedfs := .Values._namespace.seaweedfs }} apiVersion: objectstorage.k8s.io/v1alpha1 kind: BucketClaim metadata: diff --git a/packages/apps/bucket/templates/helmrelease.yaml b/packages/apps/bucket/templates/helmrelease.yaml index 45959572..5d242f84 100644 --- a/packages/apps/bucket/templates/helmrelease.yaml +++ b/packages/apps/bucket/templates/helmrelease.yaml @@ -21,5 +21,8 @@ spec: force: true remediation: retries: -1 + valuesFrom: + - kind: Secret + name: cozystack-values values: bucketName: {{ .Release.Name }} diff --git a/packages/apps/clickhouse/templates/chkeeper.yaml b/packages/apps/clickhouse/templates/chkeeper.yaml index 54824a44..42d88af4 100644 --- a/packages/apps/clickhouse/templates/chkeeper.yaml +++ b/packages/apps/clickhouse/templates/chkeeper.yaml @@ -1,5 +1,4 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $clusterDomain := (index $cozyConfig.data "cluster-domain") | default "cozy.local" }} +{{- $clusterDomain := (index .Values._cluster "cluster-domain") | default "cozy.local" }} {{- if .Values.clickhouseKeeper.enabled }} apiVersion: "clickhouse-keeper.altinity.com/v1" diff --git a/packages/apps/clickhouse/templates/clickhouse.yaml b/packages/apps/clickhouse/templates/clickhouse.yaml index 47e5b56a..b645260b 100644 --- a/packages/apps/clickhouse/templates/clickhouse.yaml +++ b/packages/apps/clickhouse/templates/clickhouse.yaml @@ -1,5 +1,4 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $clusterDomain := (index $cozyConfig.data "cluster-domain") | default "cozy.local" }} +{{- $clusterDomain := (index .Values._cluster "cluster-domain") | default "cozy.local" }} {{- $existingSecret := lookup "v1" "Secret" .Release.Namespace (printf "%s-credentials" .Release.Name) }} {{- $passwords := dict }} {{- $users := .Values.users }} diff --git a/packages/apps/ferretdb/templates/postgres.yaml b/packages/apps/ferretdb/templates/postgres.yaml index 4d1d8e29..f1b1ce2c 100644 --- a/packages/apps/ferretdb/templates/postgres.yaml +++ b/packages/apps/ferretdb/templates/postgres.yaml @@ -50,9 +50,8 @@ spec: postgresUID: 999 postgresGID: 999 enableSuperuserAccess: true - {{- $configMap := lookup "v1" "ConfigMap" "cozy-system" "cozystack-scheduling" }} - {{- if $configMap }} - {{- $rawConstraints := get $configMap.data "globalAppTopologySpreadConstraints" }} + {{- if .Values._cluster.scheduling }} + {{- $rawConstraints := get .Values._cluster.scheduling "globalAppTopologySpreadConstraints" }} {{- if $rawConstraints }} {{- $rawConstraints | fromYaml | toYaml | nindent 2 }} labelSelector: diff --git a/packages/apps/foundationdb/templates/cluster.yaml b/packages/apps/foundationdb/templates/cluster.yaml index 06992cb5..be804233 100644 --- a/packages/apps/foundationdb/templates/cluster.yaml +++ b/packages/apps/foundationdb/templates/cluster.yaml @@ -1,5 +1,4 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" | default (dict "data" (dict)) }} -{{- $clusterDomain := index $cozyConfig.data "cluster-domain" | default "cozy.local" }} +{{- $clusterDomain := index .Values._cluster "cluster-domain" | default "cozy.local" }} --- apiVersion: apps.foundationdb.org/v1beta2 kind: FoundationDBCluster diff --git a/packages/apps/kubernetes/templates/cluster.yaml b/packages/apps/kubernetes/templates/cluster.yaml index 7edd07f5..6acfb107 100644 --- a/packages/apps/kubernetes/templates/cluster.yaml +++ b/packages/apps/kubernetes/templates/cluster.yaml @@ -1,7 +1,6 @@ -{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }} -{{- $etcd := index $myNS.metadata.annotations "namespace.cozystack.io/etcd" }} -{{- $ingress := index $myNS.metadata.annotations "namespace.cozystack.io/ingress" }} -{{- $host := index $myNS.metadata.annotations "namespace.cozystack.io/host" }} +{{- $etcd := .Values._namespace.etcd }} +{{- $ingress := .Values._namespace.ingress }} +{{- $host := .Values._namespace.host }} {{- $kubevirtmachinetemplateNames := list }} {{- define "kubevirtmachinetemplate" -}} spec: @@ -31,9 +30,8 @@ spec: {{- end }} cluster.x-k8s.io/deployment-name: {{ $.Release.Name }}-{{ .groupName }} spec: - {{- $configMap := lookup "v1" "ConfigMap" "cozy-system" "cozystack-scheduling" }} - {{- if $configMap }} - {{- $rawConstraints := get $configMap.data "globalAppTopologySpreadConstraints" }} + {{- if .Values._cluster.scheduling }} + {{- $rawConstraints := get .Values._cluster.scheduling "globalAppTopologySpreadConstraints" }} {{- if $rawConstraints }} {{- $rawConstraints | fromYaml | toYaml | nindent 10 }} labelSelector: diff --git a/packages/apps/kubernetes/templates/helmreleases/monitoring-agents.yaml b/packages/apps/kubernetes/templates/helmreleases/monitoring-agents.yaml index cf93f233..73c3a368 100644 --- a/packages/apps/kubernetes/templates/helmreleases/monitoring-agents.yaml +++ b/packages/apps/kubernetes/templates/helmreleases/monitoring-agents.yaml @@ -1,5 +1,4 @@ -{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }} -{{- $targetTenant := index $myNS.metadata.annotations "namespace.cozystack.io/monitoring" }} +{{- $targetTenant := .Values._namespace.monitoring }} {{- if .Values.addons.monitoringAgents.enabled }} apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease diff --git a/packages/apps/kubernetes/templates/helmreleases/vertical-pod-autoscaler.yaml b/packages/apps/kubernetes/templates/helmreleases/vertical-pod-autoscaler.yaml index 8b615c9c..8a62288d 100644 --- a/packages/apps/kubernetes/templates/helmreleases/vertical-pod-autoscaler.yaml +++ b/packages/apps/kubernetes/templates/helmreleases/vertical-pod-autoscaler.yaml @@ -1,8 +1,6 @@ {{- define "cozystack.defaultVPAValues" -}} -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $clusterDomain := (index $cozyConfig.data "cluster-domain") | default "cozy.local" }} -{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }} -{{- $targetTenant := index $myNS.metadata.annotations "namespace.cozystack.io/monitoring" }} +{{- $clusterDomain := (index .Values._cluster "cluster-domain") | default "cozy.local" }} +{{- $targetTenant := .Values._namespace.monitoring }} vpaForVPA: false vertical-pod-autoscaler: recommender: diff --git a/packages/apps/kubernetes/templates/ingress.yaml b/packages/apps/kubernetes/templates/ingress.yaml index 8dd244cb..7993dba8 100644 --- a/packages/apps/kubernetes/templates/ingress.yaml +++ b/packages/apps/kubernetes/templates/ingress.yaml @@ -1,5 +1,4 @@ -{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }} -{{- $ingress := index $myNS.metadata.annotations "namespace.cozystack.io/ingress" }} +{{- $ingress := .Values._namespace.ingress }} {{- if and (eq .Values.addons.ingressNginx.exposeMethod "Proxied") .Values.addons.ingressNginx.hosts }} --- apiVersion: networking.k8s.io/v1 diff --git a/packages/apps/nats/templates/nats.yaml b/packages/apps/nats/templates/nats.yaml index b05c87b5..3e858fd5 100644 --- a/packages/apps/nats/templates/nats.yaml +++ b/packages/apps/nats/templates/nats.yaml @@ -1,5 +1,4 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $clusterDomain := (index $cozyConfig.data "cluster-domain") | default "cozy.local" }} +{{- $clusterDomain := (index .Values._cluster "cluster-domain") | default "cozy.local" }} {{- $existingSecret := lookup "v1" "Secret" .Release.Namespace (printf "%s-credentials" .Release.Name) }} {{- $passwords := dict }} @@ -53,6 +52,9 @@ spec: force: true remediation: retries: -1 + valuesFrom: + - kind: Secret + name: cozystack-values values: nats: container: diff --git a/packages/apps/postgres/templates/db.yaml b/packages/apps/postgres/templates/db.yaml index 92fb34c6..7557c436 100644 --- a/packages/apps/postgres/templates/db.yaml +++ b/packages/apps/postgres/templates/db.yaml @@ -46,9 +46,8 @@ spec: imageName: ghcr.io/cloudnative-pg/postgresql:{{ include "postgres.versionMap" $ | trimPrefix "v" }} enableSuperuserAccess: true - {{- $configMap := lookup "v1" "ConfigMap" "cozy-system" "cozystack-scheduling" }} - {{- if $configMap }} - {{- $rawConstraints := get $configMap.data "globalAppTopologySpreadConstraints" }} + {{- if .Values._cluster.scheduling }} + {{- $rawConstraints := get .Values._cluster.scheduling "globalAppTopologySpreadConstraints" }} {{- if $rawConstraints }} {{- $rawConstraints | fromYaml | toYaml | nindent 2 }} labelSelector: diff --git a/packages/apps/tenant/templates/etcd.yaml b/packages/apps/tenant/templates/etcd.yaml index 8cd720cc..e67ab597 100644 --- a/packages/apps/tenant/templates/etcd.yaml +++ b/packages/apps/tenant/templates/etcd.yaml @@ -31,4 +31,7 @@ spec: force: true remediation: retries: -1 + valuesFrom: + - kind: Secret + name: cozystack-values {{- end }} diff --git a/packages/apps/tenant/templates/info.yaml b/packages/apps/tenant/templates/info.yaml index 4a66e422..efa01b87 100644 --- a/packages/apps/tenant/templates/info.yaml +++ b/packages/apps/tenant/templates/info.yaml @@ -30,3 +30,6 @@ spec: force: true remediation: retries: -1 + valuesFrom: + - kind: Secret + name: cozystack-values diff --git a/packages/apps/tenant/templates/ingress.yaml b/packages/apps/tenant/templates/ingress.yaml index beb07342..6e870043 100644 --- a/packages/apps/tenant/templates/ingress.yaml +++ b/packages/apps/tenant/templates/ingress.yaml @@ -31,4 +31,7 @@ spec: force: true remediation: retries: -1 + valuesFrom: + - kind: Secret + name: cozystack-values {{- end }} diff --git a/packages/apps/tenant/templates/keycloakgroups.yaml b/packages/apps/tenant/templates/keycloakgroups.yaml index 59288ca4..9e25e60d 100644 --- a/packages/apps/tenant/templates/keycloakgroups.yaml +++ b/packages/apps/tenant/templates/keycloakgroups.yaml @@ -1,5 +1,4 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $oidcEnabled := index $cozyConfig.data "oidc-enabled" }} +{{- $oidcEnabled := index .Values._cluster "oidc-enabled" }} {{- if eq $oidcEnabled "true" }} {{- if .Capabilities.APIVersions.Has "v1.edp.epam.com/v1" }} apiVersion: v1.edp.epam.com/v1 diff --git a/packages/apps/tenant/templates/monitoring.yaml b/packages/apps/tenant/templates/monitoring.yaml index 4986fc21..625670e9 100644 --- a/packages/apps/tenant/templates/monitoring.yaml +++ b/packages/apps/tenant/templates/monitoring.yaml @@ -31,4 +31,7 @@ spec: force: true remediation: retries: -1 + valuesFrom: + - kind: Secret + name: cozystack-values {{- end }} diff --git a/packages/apps/tenant/templates/namespace.yaml b/packages/apps/tenant/templates/namespace.yaml index d97ebf42..5942cba3 100644 --- a/packages/apps/tenant/templates/namespace.yaml +++ b/packages/apps/tenant/templates/namespace.yaml @@ -1,46 +1,63 @@ -{{- define "cozystack.namespace-anotations" }} -{{- $context := index . 0 }} -{{- $existingNS := index . 1 }} -{{- range $x := list "etcd" "monitoring" "ingress" "seaweedfs" }} -{{- if (index $context.Values $x) }} -namespace.cozystack.io/{{ $x }}: "{{ include "tenant.name" $context }}" -{{- else }} -namespace.cozystack.io/{{ $x }}: "{{ index $existingNS.metadata.annotations (printf "namespace.cozystack.io/%s" $x) | required (printf "namespace %s has no namespace.cozystack.io/%s annotation" $context.Release.Namespace $x) }}" -{{- end }} -{{- end }} -{{- end }} - +{{/* Lookup for namespace uid (needed for ownerReferences) */}} {{- $existingNS := lookup "v1" "Namespace" "" .Release.Namespace }} {{- if not $existingNS }} {{- fail (printf "error lookup existing namespace: %s" .Release.Namespace) }} {{- end }} {{- if ne (include "tenant.name" .) "tenant-root" }} +{{/* Compute namespace values once for use in both Secret and labels */}} +{{- $tenantName := include "tenant.name" . }} +{{- $parentNamespace := .Values._namespace | default dict }} +{{- $parentHost := $parentNamespace.host | default "" }} + +{{/* Compute host */}} +{{- $computedHost := "" }} +{{- if .Values.host }} +{{- $computedHost = .Values.host }} +{{- else if $parentHost }} +{{- $computedHost = printf "%s.%s" (splitList "-" $tenantName | last) $parentHost }} +{{- end }} + +{{/* Compute service references */}} +{{- $etcd := $parentNamespace.etcd | default "" }} +{{- if .Values.etcd }} +{{- $etcd = $tenantName }} +{{- end }} + +{{- $ingress := $parentNamespace.ingress | default "" }} +{{- if .Values.ingress }} +{{- $ingress = $tenantName }} +{{- end }} + +{{- $monitoring := $parentNamespace.monitoring | default "" }} +{{- if .Values.monitoring }} +{{- $monitoring = $tenantName }} +{{- end }} + +{{- $seaweedfs := $parentNamespace.seaweedfs | default "" }} +{{- if .Values.seaweedfs }} +{{- $seaweedfs = $tenantName }} +{{- end }} --- apiVersion: v1 kind: Namespace metadata: - name: {{ include "tenant.name" . }} + name: {{ $tenantName }} {{- if hasPrefix "tenant-" .Release.Namespace }} - annotations: - {{- if .Values.host }} - namespace.cozystack.io/host: "{{ .Values.host }}" - {{- else }} - {{ $parentHost := index $existingNS.metadata.annotations "namespace.cozystack.io/host" | required (printf "namespace %s has no namespace.cozystack.io/host annotation" .Release.Namespace) }} - namespace.cozystack.io/host: "{{ splitList "-" (include "tenant.name" .) | last }}.{{ $parentHost }}" - {{- end }} - {{- include "cozystack.namespace-anotations" (list . $existingNS) | nindent 4 }} labels: - tenant.cozystack.io/{{ include "tenant.name" $ }}: "" - {{- if hasPrefix "tenant-" .Release.Namespace }} + tenant.cozystack.io/{{ $tenantName }}: "" {{- $parts := splitList "-" .Release.Namespace }} {{- range $i, $v := $parts }} {{- if ne $i 0 }} tenant.cozystack.io/{{ join "-" (slice $parts 0 (add $i 1)) }}: "" {{- end }} {{- end }} - {{- end }} - {{- include "cozystack.namespace-anotations" (list $ $existingNS) | nindent 4 }} + {{/* Labels for network policies */}} + namespace.cozystack.io/etcd: {{ $etcd | quote }} + namespace.cozystack.io/ingress: {{ $ingress | quote }} + namespace.cozystack.io/monitoring: {{ $monitoring | quote }} + namespace.cozystack.io/seaweedfs: {{ $seaweedfs | quote }} + namespace.cozystack.io/host: {{ $computedHost | quote }} alpha.kubevirt.io/auto-memory-limits-ratio: "1.0" ownerReferences: - apiVersion: v1 @@ -50,4 +67,23 @@ metadata: name: {{ .Release.Namespace }} uid: {{ $existingNS.metadata.uid }} {{- end }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: cozystack-values + namespace: {{ $tenantName }} + labels: + reconcile.fluxcd.io/watch: Enabled +type: Opaque +stringData: + values.yaml: | + _cluster: + {{- .Values._cluster | toYaml | nindent 6 }} + _namespace: + etcd: {{ $etcd | quote }} + ingress: {{ $ingress | quote }} + monitoring: {{ $monitoring | quote }} + seaweedfs: {{ $seaweedfs | quote }} + host: {{ $computedHost | quote }} {{- end }} diff --git a/packages/apps/tenant/templates/seaweedfs.yaml b/packages/apps/tenant/templates/seaweedfs.yaml index 9a714b79..ff0db1e4 100644 --- a/packages/apps/tenant/templates/seaweedfs.yaml +++ b/packages/apps/tenant/templates/seaweedfs.yaml @@ -31,4 +31,7 @@ spec: force: true remediation: retries: -1 + valuesFrom: + - kind: Secret + name: cozystack-values {{- end }} diff --git a/packages/apps/virtual-machine/templates/_helpers.tpl b/packages/apps/virtual-machine/templates/_helpers.tpl index 7b6929ac..e65cad9d 100644 --- a/packages/apps/virtual-machine/templates/_helpers.tpl +++ b/packages/apps/virtual-machine/templates/_helpers.tpl @@ -74,9 +74,8 @@ Generate a stable UUID for cloud-init re-initialization upon upgrade. 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 .Values._cluster.scheduling -}} +{{- $dedicatedNodesForWindowsVMs := get .Values._cluster.scheduling "dedicatedNodesForWindowsVMs" -}} {{- if eq $dedicatedNodesForWindowsVMs "true" -}} {{- $isWindows := hasPrefix "windows" (toString .Values.instanceProfile) -}} affinity: diff --git a/packages/apps/vm-instance/templates/_helpers.tpl b/packages/apps/vm-instance/templates/_helpers.tpl index 7b6929ac..e65cad9d 100644 --- a/packages/apps/vm-instance/templates/_helpers.tpl +++ b/packages/apps/vm-instance/templates/_helpers.tpl @@ -74,9 +74,8 @@ Generate a stable UUID for cloud-init re-initialization upon upgrade. 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 .Values._cluster.scheduling -}} +{{- $dedicatedNodesForWindowsVMs := get .Values._cluster.scheduling "dedicatedNodesForWindowsVMs" -}} {{- if eq $dedicatedNodesForWindowsVMs "true" -}} {{- $isWindows := hasPrefix "windows" (toString .Values.instanceProfile) -}} affinity: diff --git a/packages/apps/vpn/templates/secret.yaml b/packages/apps/vpn/templates/secret.yaml index 79960096..3ef4ac4b 100644 --- a/packages/apps/vpn/templates/secret.yaml +++ b/packages/apps/vpn/templates/secret.yaml @@ -1,5 +1,4 @@ -{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }} -{{- $host := index $myNS.metadata.annotations "namespace.cozystack.io/host" }} +{{- $host := .Values._namespace.host }} {{- $existingSecret := lookup "v1" "Secret" .Release.Namespace (printf "%s-vpn" .Release.Name) }} {{- $accessKeys := list }} {{- $passwords := dict }} diff --git a/packages/core/platform/templates/apps.yaml b/packages/core/platform/templates/apps.yaml index 514dcffb..b42aad87 100644 --- a/packages/core/platform/templates/apps.yaml +++ b/packages/core/platform/templates/apps.yaml @@ -1,6 +1,22 @@ {{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} +{{- $cozystackBranding := lookup "v1" "ConfigMap" "cozy-system" "cozystack-branding" }} +{{- $cozystackScheduling := lookup "v1" "ConfigMap" "cozy-system" "cozystack-scheduling" }} +{{- $kubeRootCa := lookup "v1" "ConfigMap" "kube-system" "kube-root-ca.crt" }} {{- $bundleName := index $cozyConfig.data "bundle-name" }} {{- $bundle := tpl (.Files.Get (printf "bundles/%s.yaml" $bundleName)) . | fromYaml }} +{{/* Default values for _cluster config to ensure all required keys exist */}} +{{- $clusterDefaults := dict + "root-host" "" + "bundle-name" "" + "clusterissuer" "http01" + "oidc-enabled" "false" + "expose-services" "" + "expose-ingress" "tenant-root" + "expose-external-ips" "" + "cluster-domain" "cozy.local" + "api-server-endpoint" "" +}} +{{- $clusterConfig := mergeOverwrite $clusterDefaults ($cozyConfig.data | default dict) }} {{- $host := "example.org" }} {{- $host := "example.org" }} {{- if $cozyConfig.data }} @@ -22,6 +38,8 @@ kind: Namespace metadata: annotations: helm.sh/resource-policy: keep + labels: + tenant.cozystack.io/tenant-root: "" namespace.cozystack.io/etcd: tenant-root namespace.cozystack.io/monitoring: tenant-root namespace.cozystack.io/ingress: tenant-root @@ -29,6 +47,36 @@ metadata: namespace.cozystack.io/host: "{{ $host }}" name: tenant-root --- +apiVersion: v1 +kind: Secret +metadata: + name: cozystack-values + namespace: tenant-root + labels: + reconcile.fluxcd.io/watch: Enabled +type: Opaque +stringData: + values.yaml: | + _cluster: + {{- $clusterConfig | toYaml | nindent 6 }} + {{- with $cozystackBranding.data }} + branding: + {{- . | toYaml | nindent 8 }} + {{- end }} + {{- with $cozystackScheduling.data }} + scheduling: + {{- . | toYaml | nindent 8 }} + {{- end }} + {{- with $kubeRootCa.data }} + kube-root-ca: {{ index . "ca.crt" | b64enc | quote }} + {{- end }} + _namespace: + etcd: tenant-root + monitoring: tenant-root + ingress: tenant-root + seaweedfs: tenant-root + host: {{ $host | quote }} +--- apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease metadata: @@ -56,6 +104,9 @@ spec: kind: HelmRepository name: cozystack-apps namespace: cozy-public + valuesFrom: + - kind: Secret + name: cozystack-values values: host: "{{ $host }}" dependsOn: diff --git a/packages/core/platform/templates/helmreleases.yaml b/packages/core/platform/templates/helmreleases.yaml index 6ed61ed8..e3118b70 100644 --- a/packages/core/platform/templates/helmreleases.yaml +++ b/packages/core/platform/templates/helmreleases.yaml @@ -83,6 +83,9 @@ spec: values: {{- toYaml . | nindent 4}} {{- end }} + valuesFrom: + - kind: Secret + name: cozystack-values {{- with $x.dependsOn }} dependsOn: diff --git a/packages/core/platform/templates/namespaces.yaml b/packages/core/platform/templates/namespaces.yaml index 11d00553..2afe3445 100644 --- a/packages/core/platform/templates/namespaces.yaml +++ b/packages/core/platform/templates/namespaces.yaml @@ -1,5 +1,20 @@ {{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} +{{- $cozystackBranding := lookup "v1" "ConfigMap" "cozy-system" "cozystack-branding" }} +{{- $cozystackScheduling := lookup "v1" "ConfigMap" "cozy-system" "cozystack-scheduling" }} {{- $bundleName := index $cozyConfig.data "bundle-name" }} +{{/* Default values for _cluster config to ensure all required keys exist */}} +{{- $clusterDefaults := dict + "root-host" "" + "bundle-name" "" + "clusterissuer" "http01" + "oidc-enabled" "false" + "expose-services" "" + "expose-ingress" "tenant-root" + "expose-external-ips" "" + "cluster-domain" "cozy.local" + "api-server-endpoint" "" +}} +{{- $clusterConfig := mergeOverwrite $clusterDefaults ($cozyConfig.data | default dict) }} {{- $bundle := tpl (.Files.Get (printf "bundles/%s.yaml" $bundleName)) . | fromYaml }} {{- $disabledComponents := splitList "," ((index $cozyConfig.data "bundle-disable") | default "") }} {{- $enabledComponents := splitList "," ((index $cozyConfig.data "bundle-enable") | default "") }} @@ -37,4 +52,25 @@ metadata: pod-security.kubernetes.io/enforce: privileged {{- end }} name: {{ $namespace }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: cozystack-values + namespace: {{ $namespace }} + labels: + reconcile.fluxcd.io/watch: Enabled +type: Opaque +stringData: + values.yaml: | + _cluster: + {{- $clusterConfig | toYaml | nindent 6 }} + {{- with $cozystackBranding.data }} + branding: + {{- . | toYaml | nindent 8 }} + {{- end }} + {{- with $cozystackScheduling.data }} + scheduling: + {{- . | toYaml | nindent 8 }} + {{- end }} {{- end }} diff --git a/packages/extra/bootbox/templates/matchbox/ingress.yaml b/packages/extra/bootbox/templates/matchbox/ingress.yaml index 31de7716..fa62165b 100644 --- a/packages/extra/bootbox/templates/matchbox/ingress.yaml +++ b/packages/extra/bootbox/templates/matchbox/ingress.yaml @@ -1,9 +1,6 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $issuerType := (index $cozyConfig.data "clusterissuer") | default "http01" }} - -{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }} -{{- $ingress := index $myNS.metadata.annotations "namespace.cozystack.io/ingress" }} -{{- $host := index $myNS.metadata.annotations "namespace.cozystack.io/host" }} +{{- $issuerType := (index .Values._cluster "clusterissuer") | default "http01" }} +{{- $ingress := .Values._namespace.ingress }} +{{- $host := .Values._namespace.host }} apiVersion: networking.k8s.io/v1 kind: Ingress metadata: diff --git a/packages/extra/bootbox/templates/matchbox/machines.yaml b/packages/extra/bootbox/templates/matchbox/machines.yaml index e2733b89..a52c4b40 100644 --- a/packages/extra/bootbox/templates/matchbox/machines.yaml +++ b/packages/extra/bootbox/templates/matchbox/machines.yaml @@ -1,9 +1,4 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $issuerType := (index $cozyConfig.data "clusterissuer") | default "http01" }} - -{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }} -{{- $ingress := index $myNS.metadata.annotations "namespace.cozystack.io/ingress" }} -{{- $host := index $myNS.metadata.annotations "namespace.cozystack.io/host" }} +{{- $host := .Values._namespace.host }} {{ range $m := .Values.machines }} --- diff --git a/packages/extra/etcd/templates/etcd-cluster.yaml b/packages/extra/etcd/templates/etcd-cluster.yaml index 017a3178..a26ae173 100644 --- a/packages/extra/etcd/templates/etcd-cluster.yaml +++ b/packages/extra/etcd/templates/etcd-cluster.yaml @@ -49,10 +49,9 @@ spec: {{- with .Values.resources }} resources: {{- include "cozy-lib.resources.sanitize" (list . $) | nindent 10 }} {{- end }} - {{- $configMap := lookup "v1" "ConfigMap" "cozy-system" "cozystack-scheduling" }} {{- $rawConstraints := "" }} - {{- if $configMap }} - {{- $rawConstraints = get $configMap.data "globalAppTopologySpreadConstraints" }} + {{- if .Values._cluster.scheduling }} + {{- $rawConstraints = get .Values._cluster.scheduling "globalAppTopologySpreadConstraints" }} {{- end }} {{- if $rawConstraints }} {{- $rawConstraints | fromYaml | toYaml | nindent 6 }} diff --git a/packages/extra/info/templates/dashboard-resourcemap.yaml b/packages/extra/info/templates/dashboard-resourcemap.yaml index 39da1b37..aa8b7641 100644 --- a/packages/extra/info/templates/dashboard-resourcemap.yaml +++ b/packages/extra/info/templates/dashboard-resourcemap.yaml @@ -1,5 +1,4 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $oidcEnabled := index $cozyConfig.data "oidc-enabled" }} +{{- $oidcEnabled := index .Values._cluster "oidc-enabled" }} apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: diff --git a/packages/extra/info/templates/kubeconfig.yaml b/packages/extra/info/templates/kubeconfig.yaml index d960a587..1557eacd 100644 --- a/packages/extra/info/templates/kubeconfig.yaml +++ b/packages/extra/info/templates/kubeconfig.yaml @@ -1,23 +1,14 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $host := index $cozyConfig.data "root-host" }} +{{- $host := .Values._namespace.host | default (index .Values._cluster "root-host") }} {{- $k8sClientSecret := lookup "v1" "Secret" "cozy-keycloak" "k8s-client" }} {{- if $k8sClientSecret }} -{{- $apiServerEndpoint := index $cozyConfig.data "api-server-endpoint" }} -{{- $managementKubeconfigEndpoint := default "" (get $cozyConfig.data "management-kubeconfig-endpoint") }} +{{- $apiServerEndpoint := index .Values._cluster "api-server-endpoint" }} +{{- $managementKubeconfigEndpoint := default "" (index .Values._cluster "management-kubeconfig-endpoint") }} {{- if and $managementKubeconfigEndpoint (ne $managementKubeconfigEndpoint "") }} {{- $apiServerEndpoint = $managementKubeconfigEndpoint }} {{- end }} {{- $k8sClient := index $k8sClientSecret.data "client-secret-key" | b64dec }} -{{- $rootSaConfigMap := lookup "v1" "ConfigMap" "kube-system" "kube-root-ca.crt" }} -{{- $k8sCa := index $rootSaConfigMap.data "ca.crt" | b64enc }} - -{{- if .Capabilities.APIVersions.Has "helm.toolkit.fluxcd.io/v2" }} -{{- $tenantRoot := lookup "helm.toolkit.fluxcd.io/v2" "HelmRelease" "tenant-root" "tenant-root" }} -{{- if and $tenantRoot $tenantRoot.spec $tenantRoot.spec.values $tenantRoot.spec.values.host }} -{{- $host = $tenantRoot.spec.values.host }} -{{- end }} -{{- end }} +{{- $k8sCa := index .Values._cluster "kube-root-ca" }} --- apiVersion: v1 kind: Secret diff --git a/packages/extra/ingress/templates/nginx-ingress.yaml b/packages/extra/ingress/templates/nginx-ingress.yaml index e28918e4..d375be2c 100644 --- a/packages/extra/ingress/templates/nginx-ingress.yaml +++ b/packages/extra/ingress/templates/nginx-ingress.yaml @@ -1,6 +1,5 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $exposeIngress := index $cozyConfig.data "expose-ingress" | default "tenant-root" }} -{{- $exposeExternalIPs := (index $cozyConfig.data "expose-external-ips") | default "" | nospace }} +{{- $exposeIngress := (index .Values._cluster "expose-ingress") | default "tenant-root" }} +{{- $exposeExternalIPs := (index .Values._cluster "expose-external-ips") | default "" | nospace }} apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease metadata: @@ -24,6 +23,9 @@ spec: force: true remediation: retries: -1 + valuesFrom: + - kind: Secret + name: cozystack-values values: ingress-nginx: fullnameOverride: {{ trimPrefix "tenant-" .Release.Namespace }}-ingress diff --git a/packages/extra/monitoring/templates/alerta/alerta-db.yaml b/packages/extra/monitoring/templates/alerta/alerta-db.yaml index a2cca187..845cd8ba 100644 --- a/packages/extra/monitoring/templates/alerta/alerta-db.yaml +++ b/packages/extra/monitoring/templates/alerta/alerta-db.yaml @@ -5,9 +5,8 @@ metadata: name: alerta-db spec: instances: 2 - {{- $configMap := lookup "v1" "ConfigMap" "cozy-system" "cozystack-scheduling" }} - {{- if $configMap }} - {{- $rawConstraints := get $configMap.data "globalAppTopologySpreadConstraints" }} + {{- if .Values._cluster.scheduling }} + {{- $rawConstraints := get .Values._cluster.scheduling "globalAppTopologySpreadConstraints" }} {{- if $rawConstraints }} {{- $rawConstraints | fromYaml | toYaml | nindent 2 }} labelSelector: diff --git a/packages/extra/monitoring/templates/alerta/alerta.yaml b/packages/extra/monitoring/templates/alerta/alerta.yaml index 72500948..76b7a02b 100644 --- a/packages/extra/monitoring/templates/alerta/alerta.yaml +++ b/packages/extra/monitoring/templates/alerta/alerta.yaml @@ -1,9 +1,6 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $issuerType := (index $cozyConfig.data "clusterissuer") | default "http01" }} - -{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }} -{{- $ingress := index $myNS.metadata.annotations "namespace.cozystack.io/ingress" }} -{{- $host := index $myNS.metadata.annotations "namespace.cozystack.io/host" }} +{{- $issuerType := (index .Values._cluster "clusterissuer") | default "http01" }} +{{- $ingress := .Values._namespace.ingress }} +{{- $host := .Values._namespace.host }} {{- $apiKey := randAlphaNum 32 }} {{- $existingSecret := lookup "v1" "Secret" .Release.Namespace "alerta" }} diff --git a/packages/extra/monitoring/templates/grafana/db.yaml b/packages/extra/monitoring/templates/grafana/db.yaml index f1781cff..662f5cf4 100644 --- a/packages/extra/monitoring/templates/grafana/db.yaml +++ b/packages/extra/monitoring/templates/grafana/db.yaml @@ -6,9 +6,8 @@ spec: instances: 2 storage: size: {{ .Values.grafana.db.size }} - {{- $configMap := lookup "v1" "ConfigMap" "cozy-system" "cozystack-scheduling" }} - {{- if $configMap }} - {{- $rawConstraints := get $configMap.data "globalAppTopologySpreadConstraints" }} + {{- if .Values._cluster.scheduling }} + {{- $rawConstraints := get .Values._cluster.scheduling "globalAppTopologySpreadConstraints" }} {{- if $rawConstraints }} {{- $rawConstraints | fromYaml | toYaml | nindent 2 }} labelSelector: diff --git a/packages/extra/monitoring/templates/grafana/grafana.yaml b/packages/extra/monitoring/templates/grafana/grafana.yaml index 2397fd10..1eadda9e 100644 --- a/packages/extra/monitoring/templates/grafana/grafana.yaml +++ b/packages/extra/monitoring/templates/grafana/grafana.yaml @@ -1,9 +1,6 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $issuerType := (index $cozyConfig.data "clusterissuer") | default "http01" }} - -{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }} -{{- $ingress := index $myNS.metadata.annotations "namespace.cozystack.io/ingress" }} -{{- $host := index $myNS.metadata.annotations "namespace.cozystack.io/host" }} +{{- $issuerType := (index .Values._cluster "clusterissuer") | default "http01" }} +{{- $ingress := .Values._namespace.ingress }} +{{- $host := .Values._namespace.host }} --- apiVersion: grafana.integreatly.org/v1beta1 kind: Grafana diff --git a/packages/extra/seaweedfs/templates/client/cosi-deployment.yaml b/packages/extra/seaweedfs/templates/client/cosi-deployment.yaml index 5307a67f..f73a61bf 100644 --- a/packages/extra/seaweedfs/templates/client/cosi-deployment.yaml +++ b/packages/extra/seaweedfs/templates/client/cosi-deployment.yaml @@ -1,7 +1,5 @@ {{- if eq .Values.topology "Client" }} -{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }} -{{- $ingress := index $myNS.metadata.annotations "namespace.cozystack.io/ingress" }} -{{- $host := index $myNS.metadata.annotations "namespace.cozystack.io/host" }} +{{- $host := .Values._namespace.host }} --- apiVersion: apps/v1 kind: Deployment diff --git a/packages/extra/seaweedfs/templates/ingress.yaml b/packages/extra/seaweedfs/templates/ingress.yaml index 05bf201d..cf4e837e 100644 --- a/packages/extra/seaweedfs/templates/ingress.yaml +++ b/packages/extra/seaweedfs/templates/ingress.yaml @@ -1,9 +1,5 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $issuerType := (index $cozyConfig.data "clusterissuer") | default "http01" }} - -{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }} -{{- $ingress := index $myNS.metadata.annotations "namespace.cozystack.io/ingress" }} -{{- $host := index $myNS.metadata.annotations "namespace.cozystack.io/host" }} +{{- $ingress := .Values._namespace.ingress }} +{{- $host := .Values._namespace.host }} {{- if and (not (eq .Values.topology "Client")) (.Values.filer.grpcHost) }} --- apiVersion: networking.k8s.io/v1 diff --git a/packages/extra/seaweedfs/templates/seaweedfs.yaml b/packages/extra/seaweedfs/templates/seaweedfs.yaml index 02bac375..747fc410 100644 --- a/packages/extra/seaweedfs/templates/seaweedfs.yaml +++ b/packages/extra/seaweedfs/templates/seaweedfs.yaml @@ -34,9 +34,8 @@ {{- end }} {{- if not (eq .Values.topology "Client") }} -{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }} -{{- $ingress := index $myNS.metadata.annotations "namespace.cozystack.io/ingress" }} -{{- $host := index $myNS.metadata.annotations "namespace.cozystack.io/host" }} +{{- $ingress := .Values._namespace.ingress }} +{{- $host := .Values._namespace.host }} apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease metadata: @@ -60,6 +59,9 @@ spec: force: true remediation: retries: -1 + valuesFrom: + - kind: Secret + name: cozystack-values values: global: serviceAccountName: "{{ .Release.Namespace }}-seaweedfs" diff --git a/packages/library/cozy-lib/templates/_cozyconfig.tpl b/packages/library/cozy-lib/templates/_cozyconfig.tpl index 559f7415..648b2617 100644 --- a/packages/library/cozy-lib/templates/_cozyconfig.tpl +++ b/packages/library/cozy-lib/templates/_cozyconfig.tpl @@ -1,7 +1,130 @@ +{{/* +Cluster-wide configuration helpers. +These helpers read from .Values._cluster which is populated via valuesFrom from Secret cozystack-values. +*/}} + +{{/* +Get the root host for the cluster. +Usage: {{ include "cozy-lib.root-host" . }} +*/}} +{{- define "cozy-lib.root-host" -}} +{{- (index .Values._cluster "root-host") | default "" }} +{{- end }} + +{{/* +Get the bundle name for the cluster. +Usage: {{ include "cozy-lib.bundle-name" . }} +*/}} +{{- define "cozy-lib.bundle-name" -}} +{{- (index .Values._cluster "bundle-name") | default "" }} +{{- end }} + +{{/* +Get the images registry. +Usage: {{ include "cozy-lib.images-registry" . }} +*/}} +{{- define "cozy-lib.images-registry" -}} +{{- (index .Values._cluster "images-registry") | default "" }} +{{- end }} + +{{/* +Get the ipv4 cluster CIDR. +Usage: {{ include "cozy-lib.ipv4-cluster-cidr" . }} +*/}} +{{- define "cozy-lib.ipv4-cluster-cidr" -}} +{{- (index .Values._cluster "ipv4-cluster-cidr") | default "" }} +{{- end }} + +{{/* +Get the ipv4 service CIDR. +Usage: {{ include "cozy-lib.ipv4-service-cidr" . }} +*/}} +{{- define "cozy-lib.ipv4-service-cidr" -}} +{{- (index .Values._cluster "ipv4-service-cidr") | default "" }} +{{- end }} + +{{/* +Get the ipv4 join CIDR. +Usage: {{ include "cozy-lib.ipv4-join-cidr" . }} +*/}} +{{- define "cozy-lib.ipv4-join-cidr" -}} +{{- (index .Values._cluster "ipv4-join-cidr") | default "" }} +{{- end }} + +{{/* +Get scheduling configuration. +Usage: {{ include "cozy-lib.scheduling" . }} +Returns: YAML string of scheduling configuration +*/}} +{{- define "cozy-lib.scheduling" -}} +{{- if .Values._cluster.scheduling }} +{{- .Values._cluster.scheduling | toYaml }} +{{- end }} +{{- end }} + +{{/* +Get branding configuration. +Usage: {{ include "cozy-lib.branding" . }} +Returns: YAML string of branding configuration +*/}} +{{- define "cozy-lib.branding" -}} +{{- if .Values._cluster.branding }} +{{- .Values._cluster.branding | toYaml }} +{{- end }} +{{- end }} + +{{/* +Namespace-specific configuration helpers. +These helpers read from .Values._namespace which is populated via valuesFrom from Secret cozystack-values. +*/}} + +{{/* +Get the host for this namespace. +Usage: {{ include "cozy-lib.ns-host" . }} +*/}} +{{- define "cozy-lib.ns-host" -}} +{{- .Values._namespace.host | default "" }} +{{- end }} + +{{/* +Get the etcd namespace reference. +Usage: {{ include "cozy-lib.ns-etcd" . }} +*/}} +{{- define "cozy-lib.ns-etcd" -}} +{{- .Values._namespace.etcd | default "" }} +{{- end }} + +{{/* +Get the ingress namespace reference. +Usage: {{ include "cozy-lib.ns-ingress" . }} +*/}} +{{- define "cozy-lib.ns-ingress" -}} +{{- .Values._namespace.ingress | default "" }} +{{- end }} + +{{/* +Get the monitoring namespace reference. +Usage: {{ include "cozy-lib.ns-monitoring" . }} +*/}} +{{- define "cozy-lib.ns-monitoring" -}} +{{- .Values._namespace.monitoring | default "" }} +{{- end }} + +{{/* +Get the seaweedfs namespace reference. +Usage: {{ include "cozy-lib.ns-seaweedfs" . }} +*/}} +{{- define "cozy-lib.ns-seaweedfs" -}} +{{- .Values._namespace.seaweedfs | default "" }} +{{- end }} + +{{/* +Legacy helper - kept for backward compatibility during migration. +Loads config into context. Deprecated: use direct .Values._cluster access instead. +*/}} {{- define "cozy-lib.loadCozyConfig" }} {{- include "cozy-lib.checkInput" . }} {{- if not (hasKey (index . 1) "cozyConfig") }} -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $_ := set (index . 1) "cozyConfig" $cozyConfig }} +{{- $_ := set (index . 1) "cozyConfig" (dict "data" ((index . 1).Values._cluster | default dict)) }} {{- end }} {{- end }} diff --git a/packages/system/bucket/templates/ingress.yaml b/packages/system/bucket/templates/ingress.yaml index d8d659c7..f7b4eed4 100644 --- a/packages/system/bucket/templates/ingress.yaml +++ b/packages/system/bucket/templates/ingress.yaml @@ -1,8 +1,6 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $myNS := lookup "v1" "Namespace" "" .Release.Namespace }} -{{- $host := index $myNS.metadata.annotations "namespace.cozystack.io/host" }} -{{- $ingress := index $myNS.metadata.annotations "namespace.cozystack.io/ingress" }} -{{- $issuerType := (index $cozyConfig.data "clusterissuer") | default "http01" }} +{{- $host := .Values._namespace.host }} +{{- $ingress := .Values._namespace.ingress }} +{{- $issuerType := (index .Values._cluster "clusterissuer") | default "http01" }} apiVersion: networking.k8s.io/v1 kind: Ingress diff --git a/packages/system/cert-manager-issuers/templates/cluster-issuers.yaml b/packages/system/cert-manager-issuers/templates/cluster-issuers.yaml index 6a70eef0..e93f3f67 100644 --- a/packages/system/cert-manager-issuers/templates/cluster-issuers.yaml +++ b/packages/system/cert-manager-issuers/templates/cluster-issuers.yaml @@ -1,5 +1,4 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $issuerType := (index $cozyConfig.data "clusterissuer") | default "http01" }} +{{- $issuerType := (index .Values._cluster "clusterissuer") | default "http01" }} apiVersion: cert-manager.io/v1 kind: ClusterIssuer diff --git a/packages/system/cozystack-api/templates/api-ingress.yaml b/packages/system/cozystack-api/templates/api-ingress.yaml index 54fdf54b..9226d887 100644 --- a/packages/system/cozystack-api/templates/api-ingress.yaml +++ b/packages/system/cozystack-api/templates/api-ingress.yaml @@ -1,7 +1,6 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $host := index $cozyConfig.data "root-host" }} -{{- $exposeServices := splitList "," ((index $cozyConfig.data "expose-services") | default "") }} -{{- $exposeIngress := index $cozyConfig.data "expose-ingress" | default "tenant-root" }} +{{- $host := index .Values._cluster "root-host" }} +{{- $exposeServices := splitList "," ((index .Values._cluster "expose-services") | default "") }} +{{- $exposeIngress := (index .Values._cluster "expose-ingress") | default "tenant-root" }} {{- if and (has "api" $exposeServices) }} apiVersion: networking.k8s.io/v1 diff --git a/packages/system/dashboard/templates/configmap.yaml b/packages/system/dashboard/templates/configmap.yaml index bbf71610..f148109f 100644 --- a/packages/system/dashboard/templates/configmap.yaml +++ b/packages/system/dashboard/templates/configmap.yaml @@ -1,4 +1,4 @@ -{{- $brandingConfig:= lookup "v1" "ConfigMap" "cozy-system" "cozystack-branding" }} +{{- $brandingConfig := .Values._cluster.branding | default dict }} {{- $tenantText := "v0.38.2" }} {{- $footerText := "Cozystack" }} @@ -16,9 +16,9 @@ metadata: app.kubernetes.io/instance: incloud-web app.kubernetes.io/name: web data: - CUSTOM_TENANT_TEXT: {{ $brandingConfig | dig "data" "tenantText" $tenantText | quote }} - FOOTER_TEXT: {{ $brandingConfig | dig "data" "footerText" $footerText | quote }} - TITLE_TEXT: {{ $brandingConfig | dig "data" "titleText" $titleText | quote }} - LOGO_TEXT: {{ $brandingConfig | dig "data" "logoText" $logoText | quote }} - CUSTOM_LOGO_SVG: {{ $brandingConfig | dig "data" "logoSvg" $logoSvg | quote }} - ICON_SVG: {{ $brandingConfig | dig "data" "iconSvg" $iconSvg | quote }} + CUSTOM_TENANT_TEXT: {{ $brandingConfig.tenantText | default $tenantText | quote }} + FOOTER_TEXT: {{ $brandingConfig.footerText | default $footerText | quote }} + TITLE_TEXT: {{ $brandingConfig.titleText | default $titleText | quote }} + LOGO_TEXT: {{ $brandingConfig.logoText | default $logoText | quote }} + CUSTOM_LOGO_SVG: {{ $brandingConfig.logoSvg | default $logoSvg | quote }} + ICON_SVG: {{ $brandingConfig.iconSvg | default $iconSvg | quote }} diff --git a/packages/system/dashboard/templates/gatekeeper.yaml b/packages/system/dashboard/templates/gatekeeper.yaml index b4503f4f..40f2565f 100644 --- a/packages/system/dashboard/templates/gatekeeper.yaml +++ b/packages/system/dashboard/templates/gatekeeper.yaml @@ -1,6 +1,5 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $host := index $cozyConfig.data "root-host" }} -{{- $oidcEnabled := index $cozyConfig.data "oidc-enabled" }} +{{- $host := index .Values._cluster "root-host" }} +{{- $oidcEnabled := index .Values._cluster "oidc-enabled" }} apiVersion: apps/v1 kind: Deployment diff --git a/packages/system/dashboard/templates/ingress.yaml b/packages/system/dashboard/templates/ingress.yaml index 5a634e2c..aacc1bc1 100644 --- a/packages/system/dashboard/templates/ingress.yaml +++ b/packages/system/dashboard/templates/ingress.yaml @@ -1,8 +1,7 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $issuerType := (index $cozyConfig.data "clusterissuer") | default "http01" }} -{{- $host := index $cozyConfig.data "root-host" }} -{{- $exposeServices := splitList "," ((index $cozyConfig.data "expose-services") | default "") }} -{{- $exposeIngress := index $cozyConfig.data "expose-ingress" | default "tenant-root" }} +{{- $issuerType := (index .Values._cluster "clusterissuer") | default "http01" }} +{{- $host := index .Values._cluster "root-host" }} +{{- $exposeServices := splitList "," ((index .Values._cluster "expose-services") | default "") }} +{{- $exposeIngress := (index .Values._cluster "expose-ingress") | default "tenant-root" }} {{- if and (has "dashboard" $exposeServices) }} apiVersion: networking.k8s.io/v1 diff --git a/packages/system/dashboard/templates/keycloakclient.yaml b/packages/system/dashboard/templates/keycloakclient.yaml index 55ebc5d5..80f7332e 100644 --- a/packages/system/dashboard/templates/keycloakclient.yaml +++ b/packages/system/dashboard/templates/keycloakclient.yaml @@ -1,6 +1,5 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $host := index $cozyConfig.data "root-host" }} -{{- $extraRedirectUris := splitList "," ((index $cozyConfig.data "extra-keycloak-redirect-uri-for-dashboard") | default "") }} +{{- $host := index .Values._cluster "root-host" }} +{{- $extraRedirectUris := splitList "," ((index .Values._cluster "extra-keycloak-redirect-uri-for-dashboard") | default "") }} {{- $existingK8sSecret := lookup "v1" "Secret" .Release.Namespace "k8s-client" }} {{- $existingDashboardSecret := lookup "v1" "Secret" .Release.Namespace "dashboard-client" }} diff --git a/packages/system/dashboard/templates/nginx-config.yaml b/packages/system/dashboard/templates/nginx-config.yaml index c2d6f624..696e9ce9 100644 --- a/packages/system/dashboard/templates/nginx-config.yaml +++ b/packages/system/dashboard/templates/nginx-config.yaml @@ -1,5 +1,4 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $host := index $cozyConfig.data "root-host" }} +{{- $host := index .Values._cluster "root-host" }} apiVersion: v1 data: diff --git a/packages/system/keycloak-configure/templates/configure-kk.yaml b/packages/system/keycloak-configure/templates/configure-kk.yaml index 3fc92c86..e399b374 100644 --- a/packages/system/keycloak-configure/templates/configure-kk.yaml +++ b/packages/system/keycloak-configure/templates/configure-kk.yaml @@ -1,13 +1,11 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $host := index $cozyConfig.data "root-host" }} -{{- $extraRedirectUris := splitList "," ((index $cozyConfig.data "extra-keycloak-redirect-uri-for-dashboard") | default "") }} -{{- $rootSaConfigMap := lookup "v1" "ConfigMap" "kube-system" "kube-root-ca.crt" }} -{{- $k8sCa := index $rootSaConfigMap.data "ca.crt" | b64enc }} +{{- $host := index .Values._cluster "root-host" }} +{{- $extraRedirectUris := splitList "," ((index .Values._cluster "extra-keycloak-redirect-uri-for-dashboard") | default "") }} +{{- $k8sCa := index .Values._cluster "kube-root-ca" }} {{- $existingK8sSecret := lookup "v1" "Secret" .Release.Namespace "k8s-client" }} {{- $existingKubeappsSecret := lookup "v1" "Secret" .Release.Namespace "kubeapps-client" }} {{- $existingAuthConfig := lookup "v1" "Secret" "cozy-dashboard" "kubeapps-auth-config" }} -{{- $cozystackBranding:= lookup "v1" "ConfigMap" "cozy-system" "cozystack-branding" }} +{{- $brandingConfig := .Values._cluster.branding | default dict }} {{ $k8sClient := "" }} {{- if $existingK8sSecret }} @@ -17,8 +15,8 @@ {{- end }} {{ $branding := "" }} -{{- if $cozystackBranding }} - {{- $branding = index $cozystackBranding.data "branding" }} +{{- if $brandingConfig }} + {{- $branding = $brandingConfig.branding }} {{- end }} --- diff --git a/packages/system/keycloak/templates/db.yaml b/packages/system/keycloak/templates/db.yaml index 70666d7b..1cd98ffc 100644 --- a/packages/system/keycloak/templates/db.yaml +++ b/packages/system/keycloak/templates/db.yaml @@ -6,9 +6,8 @@ spec: instances: 2 storage: size: 20Gi - {{- $configMap := lookup "v1" "ConfigMap" "cozy-system" "cozystack-scheduling" }} - {{- if $configMap }} - {{- $rawConstraints := get $configMap.data "globalAppTopologySpreadConstraints" }} + {{- if .Values._cluster.scheduling }} + {{- $rawConstraints := get .Values._cluster.scheduling "globalAppTopologySpreadConstraints" }} {{- if $rawConstraints }} {{- $rawConstraints | fromYaml | toYaml | nindent 2 }} labelSelector: diff --git a/packages/system/keycloak/templates/ingress.yaml b/packages/system/keycloak/templates/ingress.yaml index 30120619..0e0f56cb 100644 --- a/packages/system/keycloak/templates/ingress.yaml +++ b/packages/system/keycloak/templates/ingress.yaml @@ -1,7 +1,6 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $host := index $cozyConfig.data "root-host" }} -{{- $issuerType := (index $cozyConfig.data "clusterissuer") | default "http01" }} -{{- $exposeIngress := index $cozyConfig.data "expose-ingress" | default "tenant-root" }} +{{- $host := index .Values._cluster "root-host" }} +{{- $issuerType := (index .Values._cluster "clusterissuer") | default "http01" }} +{{- $exposeIngress := (index .Values._cluster "expose-ingress") | default "tenant-root" }} apiVersion: networking.k8s.io/v1 kind: Ingress diff --git a/packages/system/keycloak/templates/sts.yaml b/packages/system/keycloak/templates/sts.yaml index 4705f77c..96d30601 100644 --- a/packages/system/keycloak/templates/sts.yaml +++ b/packages/system/keycloak/templates/sts.yaml @@ -1,6 +1,5 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $host := index $cozyConfig.data "root-host" }} -{{- $clusterDomain := (index $cozyConfig.data "cluster-domain") | default "cozy.local" }} +{{- $host := index .Values._cluster "root-host" }} +{{- $clusterDomain := (index .Values._cluster "cluster-domain") | default "cozy.local" }} {{- $existingPassword := lookup "v1" "Secret" "cozy-keycloak" (printf "%s-credentials" .Release.Name) }} {{- $password := randAlphaNum 16 -}} diff --git a/packages/system/kubevirt-cdi/templates/cdi-uploadproxy-ingress.yaml b/packages/system/kubevirt-cdi/templates/cdi-uploadproxy-ingress.yaml index 58eef4fa..ee89953f 100644 --- a/packages/system/kubevirt-cdi/templates/cdi-uploadproxy-ingress.yaml +++ b/packages/system/kubevirt-cdi/templates/cdi-uploadproxy-ingress.yaml @@ -1,7 +1,6 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $host := index $cozyConfig.data "root-host" }} -{{- $exposeServices := splitList "," ((index $cozyConfig.data "expose-services") | default "") }} -{{- $exposeIngress := index $cozyConfig.data "expose-ingress" | default "tenant-root" }} +{{- $host := index .Values._cluster "root-host" }} +{{- $exposeServices := splitList "," ((index .Values._cluster "expose-services") | default "") }} +{{- $exposeIngress := (index .Values._cluster "expose-ingress") | default "tenant-root" }} {{- if and (has "cdi-uploadproxy" $exposeServices) }} diff --git a/packages/system/kubevirt/templates/vm-exportproxy-ingress.yaml b/packages/system/kubevirt/templates/vm-exportproxy-ingress.yaml index b77743d0..a089f5ef 100644 --- a/packages/system/kubevirt/templates/vm-exportproxy-ingress.yaml +++ b/packages/system/kubevirt/templates/vm-exportproxy-ingress.yaml @@ -1,7 +1,6 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $host := index $cozyConfig.data "root-host" }} -{{- $exposeServices := splitList "," ((index $cozyConfig.data "expose-services") | default "") }} -{{- $exposeIngress := index $cozyConfig.data "expose-ingress" | default "tenant-root" }} +{{- $host := index .Values._cluster "root-host" }} +{{- $exposeServices := splitList "," ((index .Values._cluster "expose-services") | default "") }} +{{- $exposeIngress := (index .Values._cluster "expose-ingress") | default "tenant-root" }} {{- if and (has "vm-exportproxy" $exposeServices) }} apiVersion: networking.k8s.io/v1 diff --git a/packages/tests/cozy-lib-tests/tests/quota_values.yaml b/packages/tests/cozy-lib-tests/tests/quota_values.yaml index bcf6a21e..87e5cf17 100644 --- a/packages/tests/cozy-lib-tests/tests/quota_values.yaml +++ b/packages/tests/cozy-lib-tests/tests/quota_values.yaml @@ -3,3 +3,6 @@ quota: cpu: "20" storage: "5Gi" foobar: "3" + +_cluster: {} +_namespace: {} diff --git a/pkg/registry/apps/application/rest.go b/pkg/registry/apps/application/rest.go index ee189e26..9e8fb891 100644 --- a/pkg/registry/apps/application/rest.go +++ b/pkg/registry/apps/application/rest.go @@ -148,6 +148,11 @@ func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation return nil, fmt.Errorf("expected *appsv1alpha1.Application object, got %T", obj) } + // Validate that values don't contain reserved keys (starting with "_") + if err := validateNoInternalKeys(app.Spec); err != nil { + return nil, apierrors.NewBadRequest(err.Error()) + } + // Convert Application to HelmRelease helmRelease, err := r.ConvertApplicationToHelmRelease(app) if err != nil { @@ -442,6 +447,11 @@ func (r *REST) Update(ctx context.Context, name string, objInfo rest.UpdatedObje return nil, false, fmt.Errorf("expected *appsv1alpha1.Application object, got %T", newObj) } + // Validate that values don't contain reserved keys (starting with "_") + if err := validateNoInternalKeys(app.Spec); err != nil { + return nil, false, apierrors.NewBadRequest(err.Error()) + } + // Convert Application to HelmRelease helmRelease, err := r.ConvertApplicationToHelmRelease(app) if err != nil { @@ -851,8 +861,49 @@ func (r *REST) ConvertApplicationToHelmRelease(app *appsv1alpha1.Application) (* return r.convertApplicationToHelmRelease(app) } +// filterInternalKeys removes keys starting with "_" from the JSON values +func filterInternalKeys(values *apiextv1.JSON) *apiextv1.JSON { + if values == nil || len(values.Raw) == 0 { + return values + } + var data map[string]interface{} + if err := json.Unmarshal(values.Raw, &data); err != nil { + return values + } + for key := range data { + if strings.HasPrefix(key, "_") { + delete(data, key) + } + } + filtered, err := json.Marshal(data) + if err != nil { + return values + } + return &apiextv1.JSON{Raw: filtered} +} + +// validateNoInternalKeys checks that values don't contain keys starting with "_" +func validateNoInternalKeys(values *apiextv1.JSON) error { + if values == nil || len(values.Raw) == 0 { + return nil + } + var data map[string]interface{} + if err := json.Unmarshal(values.Raw, &data); err != nil { + return err + } + for key := range data { + if strings.HasPrefix(key, "_") { + return fmt.Errorf("values key %q is reserved (keys starting with '_' are not allowed)", key) + } + } + return nil +} + // convertHelmReleaseToApplication implements the actual conversion logic func (r *REST) convertHelmReleaseToApplication(hr *helmv2.HelmRelease) (appsv1alpha1.Application, error) { + // Filter out internal keys (starting with "_") from spec + filteredSpec := filterInternalKeys(hr.Spec.Values) + app := appsv1alpha1.Application{ TypeMeta: metav1.TypeMeta{ APIVersion: "apps.cozystack.io/v1alpha1", @@ -868,7 +919,7 @@ func (r *REST) convertHelmReleaseToApplication(hr *helmv2.HelmRelease) (appsv1al Labels: filterPrefixedMap(hr.Labels, LabelPrefix), Annotations: filterPrefixedMap(hr.Annotations, AnnotationPrefix), }, - Spec: hr.Spec.Values, + Spec: filteredSpec, Status: appsv1alpha1.ApplicationStatus{ Version: hr.Status.LastAttemptedRevision, }, @@ -935,6 +986,12 @@ func (r *REST) convertApplicationToHelmRelease(app *appsv1alpha1.Application) (* Retries: -1, }, }, + ValuesFrom: []helmv2.ValuesReference{ + { + Kind: "Secret", + Name: "cozystack-values", + }, + }, Values: app.Spec, }, } From 7b75903ceebb5edb561b66795771c14abeaf954f Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Mon, 5 Jan 2026 18:51:18 +0100 Subject: [PATCH 017/889] feat(operator): add valuesFrom injection to HelmReleases Add automatic injection of cozystack-values secret reference into HelmReleases created by Package reconciler. This enables charts to access cluster and namespace configuration via .Values._cluster and .Values._namespace. Add annotation operator.cozystack.io/skip-cozystack-values to disable injection for specific PackageSources (used for platform PackageSource to avoid circular dependency). Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- internal/operator/package_reconciler.go | 18 ++++++++++++++++++ .../templates/cozystack-operator.yaml | 2 ++ 2 files changed, 20 insertions(+) diff --git a/internal/operator/package_reconciler.go b/internal/operator/package_reconciler.go index 879fe3d9..f46185fd 100644 --- a/internal/operator/package_reconciler.go +++ b/internal/operator/package_reconciler.go @@ -37,6 +37,14 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" ) +const ( + // AnnotationSkipCozystackValues disables injection of cozystack-values secret into HelmRelease + // This annotation should be placed on PackageSource + AnnotationSkipCozystackValues = "operator.cozystack.io/skip-cozystack-values" + // SecretCozystackValues is the name of the secret containing cluster and namespace configuration + SecretCozystackValues = "cozystack-values" +) + // PackageReconciler reconciles Package resources type PackageReconciler struct { client.Client @@ -215,6 +223,16 @@ func (r *PackageReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct }, } + // Add valuesFrom for cozystack-values secret unless disabled by annotation on PackageSource + if packageSource.GetAnnotations()[AnnotationSkipCozystackValues] != "true" { + hr.Spec.ValuesFrom = []helmv2.ValuesReference{ + { + Kind: "Secret", + Name: SecretCozystackValues, + }, + } + } + // Set ownerReference gvk, err := apiutil.GVKForObject(pkg, r.Scheme) if err != nil { diff --git a/packages/core/installer/templates/cozystack-operator.yaml b/packages/core/installer/templates/cozystack-operator.yaml index d5efa8b4..514dcc32 100644 --- a/packages/core/installer/templates/cozystack-operator.yaml +++ b/packages/core/installer/templates/cozystack-operator.yaml @@ -83,6 +83,8 @@ apiVersion: cozystack.io/v1alpha1 kind: PackageSource metadata: name: cozystack.cozystack-platform + annotations: + operator.cozystack.io/skip-cozystack-values: "true" spec: sourceRef: kind: OCIRepository From daa91fd2f36b9c9ee791e99569f613f858c1b842 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 16 Dec 2025 02:37:32 +0100 Subject: [PATCH 018/889] 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 --- 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 095e3094..df127f4e 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,7 @@ build: build-deps 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 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 05b2244b36d1cc025c38f7c6ea13e4b133e79f49 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Mon, 5 Jan 2026 19:59:22 +0100 Subject: [PATCH 019/889] feat(operator): add secret replicator and reconciler improvements Add namespace-based secret replication with label selector approach. The implementation uses configurable secret name, namespace, and target namespace selector. Cache filtering optimizes memory usage. Co-Authored-By: Timofei Larkin Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- cmd/cozystack-operator/main.go | 42 +++++ .../cozyvaluesreplicator.go | 176 ++++++++++++++++++ internal/operator/package_reconciler.go | 1 + internal/operator/packagesource_reconciler.go | 23 +-- 4 files changed, 220 insertions(+), 22 deletions(-) create mode 100644 internal/cozyvaluesreplicator/cozyvaluesreplicator.go diff --git a/cmd/cozystack-operator/main.go b/cmd/cozystack-operator/main.go index d9c79057..13921f0c 100644 --- a/cmd/cozystack-operator/main.go +++ b/cmd/cozystack-operator/main.go @@ -32,12 +32,16 @@ import ( helmv2 "github.com/fluxcd/helm-controller/api/v2" sourcev1 "github.com/fluxcd/source-controller/api/v1" sourcewatcherv1beta1 "github.com/fluxcd/source-watcher/api/v2/v1beta1" + corev1 "k8s.io/api/core/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/labels" "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/cache" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log" @@ -45,6 +49,7 @@ import ( metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" "sigs.k8s.io/controller-runtime/pkg/webhook" + "github.com/cozystack/cozystack/internal/cozyvaluesreplicator" "github.com/cozystack/cozystack/internal/fluxinstall" "github.com/cozystack/cozystack/internal/operator" // +kubebuilder:scaffold:imports @@ -73,6 +78,9 @@ func main() { var enableHTTP2 bool var installFlux bool var cozystackVersion string + var cozyValuesSecretName string + var cozyValuesSecretNamespace string + var cozyValuesNamespaceSelector string var platformSourceURL string var platformSourceName string var platformSourceRef string @@ -92,6 +100,9 @@ func main() { flag.StringVar(&platformSourceURL, "platform-source-url", "", "Platform source URL (oci:// or https://). If specified, generates OCIRepository or GitRepository resource.") flag.StringVar(&platformSourceName, "platform-source-name", "cozystack-packages", "Name for the generated platform source resource (default: cozystack-packages)") flag.StringVar(&platformSourceRef, "platform-source-ref", "", "Reference specification as key=value pairs (e.g., 'branch=main' or 'digest=sha256:...,tag=v1.0'). For OCI: digest, semver, semverFilter, tag. For Git: branch, tag, semver, name, commit.") + flag.StringVar(&cozyValuesSecretName, "cozy-values-secret-name", "cozystack-values", "The name of the secret containing cluster-wide configuration values.") + flag.StringVar(&cozyValuesSecretNamespace, "cozy-values-secret-namespace", "cozy-system", "The namespace of the secret containing cluster-wide configuration values.") + flag.StringVar(&cozyValuesNamespaceSelector, "cozy-values-namespace-selector", "cozystack.io/system=true", "The label selector for namespaces where the cluster-wide configuration values must be replicated.") opts := zap.Options{ Development: true, @@ -110,10 +121,29 @@ func main() { os.Exit(1) } + targetNSSelector, err := labels.Parse(cozyValuesNamespaceSelector) + if err != nil { + setupLog.Error(err, "could not parse namespace label selector") + os.Exit(1) + } + // Start the controller manager setupLog.Info("Starting controller manager") mgr, err := ctrl.NewManager(config, ctrl.Options{ Scheme: scheme, + Cache: cache.Options{ + ByObject: map[client.Object]cache.ByObject{ + // Cache only Secrets named (in any namespace) + &corev1.Secret{}: { + Field: fields.OneTermEqualSelector("metadata.name", cozyValuesSecretName), + }, + + // Cache only Namespaces that match a label selector + &corev1.Namespace{}: { + Label: targetNSSelector, + }, + }, + }, Metrics: metricsserver.Options{ BindAddress: metricsAddr, SecureServing: secureMetrics, @@ -187,6 +217,18 @@ func main() { os.Exit(1) } + // Setup CozyValuesReplicator reconciler + if err := (&cozyvaluesreplicator.SecretReplicatorReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + SourceNamespace: cozyValuesSecretNamespace, + SecretName: cozyValuesSecretName, + TargetNamespaceSelector: targetNSSelector, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "CozyValuesReplicator") + os.Exit(1) + } + // +kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/internal/cozyvaluesreplicator/cozyvaluesreplicator.go b/internal/cozyvaluesreplicator/cozyvaluesreplicator.go new file mode 100644 index 00000000..41b65347 --- /dev/null +++ b/internal/cozyvaluesreplicator/cozyvaluesreplicator.go @@ -0,0 +1,176 @@ +/* +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. +*/ + +package cozyvaluesreplicator + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +// SecretReplicatorReconciler replicates a source secret to namespaces matching a label selector. +type SecretReplicatorReconciler struct { + client.Client + Scheme *runtime.Scheme + + // Source of truth: + SourceNamespace string + SecretName string + + // Namespaces to replicate into: + // (e.g. labels.SelectorFromSet(labels.Set{"tenant":"true"}), or metav1.LabelSelectorAsSelector(...)) + TargetNamespaceSelector labels.Selector +} + +func (r *SecretReplicatorReconciler) SetupWithManager(mgr ctrl.Manager) error { + // 1) Primary watch for requirement (b): + // Reconcile any Secret named r.SecretName in any namespace (includes source too). + // This keeps Secrets in cache and causes "copy changed -> reconcile it" to happen. + secretNameOnly := predicate.NewPredicateFuncs(func(obj client.Object) bool { + return obj.GetName() == r.SecretName + }) + + // 2) Secondary watch for requirement (c): + // When the *source* Secret changes, fan-out reconcile requests to every matching namespace. + onlySourceSecret := predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { return isSourceSecret(e.Object, r) }, + UpdateFunc: func(e event.UpdateEvent) bool { return isSourceSecret(e.ObjectNew, r) }, + DeleteFunc: func(e event.DeleteEvent) bool { return isSourceSecret(e.Object, r) }, + GenericFunc: func(e event.GenericEvent) bool { + return isSourceSecret(e.Object, r) + }, + } + + // Fan-out mapper for source Secret events -> one request per matching target namespace. + fanOutOnSourceSecret := handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, _ client.Object) []reconcile.Request { + // List namespaces *from the cache* (because we also watch Namespaces below). + var nsList corev1.NamespaceList + if err := r.List(ctx, &nsList); err != nil { + // If list fails, best-effort: return nothing; reconcile will be retried by next event. + return nil + } + + reqs := make([]reconcile.Request, 0, len(nsList.Items)) + for i := range nsList.Items { + ns := &nsList.Items[i] + if ns.Name == r.SourceNamespace { + continue + } + if r.TargetNamespaceSelector != nil && !r.TargetNamespaceSelector.Matches(labels.Set(ns.Labels)) { + continue + } + reqs = append(reqs, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: ns.Name, + Name: r.SecretName, + }, + }) + } + return reqs + }) + + // 3) Namespace watch for requirement (a): + // When a namespace is created/updated to match selector, enqueue reconcile for the Secret copy in that namespace. + enqueueOnNamespaceMatch := handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request { + ns, ok := obj.(*corev1.Namespace) + if !ok { + return nil + } + if ns.Name == r.SourceNamespace { + return nil + } + if r.TargetNamespaceSelector != nil && !r.TargetNamespaceSelector.Matches(labels.Set(ns.Labels)) { + return nil + } + return []reconcile.Request{{ + NamespacedName: types.NamespacedName{ + Namespace: ns.Name, + Name: r.SecretName, + }, + }} + }) + + // Only trigger from namespace events where the label match may be (or become) true. + // (You can keep this simple; it's fine if it fires on any update—your Reconcile should be idempotent.) + namespaceMayMatter := predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { + ns, ok := e.Object.(*corev1.Namespace) + return ok && (r.TargetNamespaceSelector == nil || r.TargetNamespaceSelector.Matches(labels.Set(ns.Labels))) + }, + UpdateFunc: func(e event.UpdateEvent) bool { + oldNS, okOld := e.ObjectOld.(*corev1.Namespace) + newNS, okNew := e.ObjectNew.(*corev1.Namespace) + if !okOld || !okNew { + return false + } + // Fire if it matches now OR matched before (covers transitions both ways; reconcile can decide what to do). + oldMatch := r.TargetNamespaceSelector == nil || r.TargetNamespaceSelector.Matches(labels.Set(oldNS.Labels)) + newMatch := r.TargetNamespaceSelector == nil || r.TargetNamespaceSelector.Matches(labels.Set(newNS.Labels)) + return oldMatch || newMatch + }, + DeleteFunc: func(event.DeleteEvent) bool { return false }, // nothing to do on namespace delete + GenericFunc: func(event.GenericEvent) bool { return false }, + } + + return ctrl.NewControllerManagedBy(mgr). + // (b) Watch all Secrets with the chosen name; this also ensures Secret objects are cached. + For(&corev1.Secret{}, builder.WithPredicates(secretNameOnly)). + + // (c) Add a second watch on Secret, but only for the source secret, and fan-out to all namespaces. + Watches( + &corev1.Secret{}, + fanOutOnSourceSecret, + builder.WithPredicates(onlySourceSecret), + ). + + // (a) Watch Namespaces so they're cached and so "namespace appears / starts matching" enqueues reconcile. + Watches( + &corev1.Namespace{}, + enqueueOnNamespaceMatch, + builder.WithPredicates(namespaceMayMatter), + ). + Complete(r) +} + +func isSourceSecret(obj client.Object, r *SecretReplicatorReconciler) bool { + if obj == nil { + return false + } + return obj.GetNamespace() == r.SourceNamespace && obj.GetName() == r.SecretName +} + +func (r *SecretReplicatorReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + if req.Name != r.SecretName || req.Namespace == r.SourceNamespace { + return ctrl.Result{}, nil + } + originalSecret := &corev1.Secret{} + r.Get(ctx, types.NamespacedName{Namespace: r.SourceNamespace, Name: r.SecretName}, originalSecret) + replicatedSecret := originalSecret.DeepCopy() + replicatedSecret.Namespace = req.Namespace + r.Update(ctx, replicatedSecret) + return ctrl.Result{}, nil +} diff --git a/internal/operator/package_reconciler.go b/internal/operator/package_reconciler.go index f46185fd..07237fe5 100644 --- a/internal/operator/package_reconciler.go +++ b/internal/operator/package_reconciler.go @@ -887,6 +887,7 @@ func (r *PackageReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). Named("cozystack-package"). For(&cozyv1alpha1.Package{}). + Owns(&helmv2.HelmRelease{}). Watches( &cozyv1alpha1.PackageSource{}, handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request { diff --git a/internal/operator/packagesource_reconciler.go b/internal/operator/packagesource_reconciler.go index dfbc096d..e79370bd 100644 --- a/internal/operator/packagesource_reconciler.go +++ b/internal/operator/packagesource_reconciler.go @@ -31,9 +31,7 @@ import ( 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/handler" "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/reconcile" ) // PackageSourceReconciler reconciles PackageSource resources @@ -409,26 +407,7 @@ func (r *PackageSourceReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). Named("cozystack-packagesource"). For(&cozyv1alpha1.PackageSource{}). - Watches( - &sourcewatcherv1beta1.ArtifactGenerator{}, - handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request { - ag, ok := obj.(*sourcewatcherv1beta1.ArtifactGenerator) - if !ok { - return nil - } - // Find the PackageSource that owns this ArtifactGenerator by ownerReference - for _, ownerRef := range ag.OwnerReferences { - if ownerRef.Kind == "PackageSource" { - return []reconcile.Request{{ - NamespacedName: types.NamespacedName{ - Name: ownerRef.Name, - }, - }} - } - } - return nil - }), - ). + Owns(&sourcewatcherv1beta1.ArtifactGenerator{}). Complete(r) } From 3a1e7fdd8f55cf73fb6abce10a7250eb2126f481 Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Tue, 6 Jan 2026 18:53:17 +0300 Subject: [PATCH 020/889] Improve Reconcile function Signed-off-by: Timofei Larkin --- .../cozyvaluesreplicator.go | 104 +++++++++++++++++- 1 file changed, 100 insertions(+), 4 deletions(-) diff --git a/internal/cozyvaluesreplicator/cozyvaluesreplicator.go b/internal/cozyvaluesreplicator/cozyvaluesreplicator.go index 41b65347..5f6215a7 100644 --- a/internal/cozyvaluesreplicator/cozyvaluesreplicator.go +++ b/internal/cozyvaluesreplicator/cozyvaluesreplicator.go @@ -20,14 +20,18 @@ import ( "context" corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) @@ -164,13 +168,105 @@ func isSourceSecret(obj client.Object, r *SecretReplicatorReconciler) bool { } func (r *SecretReplicatorReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + logger := log.FromContext(ctx) + + // Ignore requests that don't match our secret name or are for the source namespace if req.Name != r.SecretName || req.Namespace == r.SourceNamespace { return ctrl.Result{}, nil } + + // Verify the target namespace still exists and matches the selector + targetNamespace := &corev1.Namespace{} + if err := r.Get(ctx, types.NamespacedName{Name: req.Namespace}, targetNamespace); err != nil { + if apierrors.IsNotFound(err) { + // Namespace doesn't exist, nothing to do + return ctrl.Result{}, nil + } + logger.Error(err, "Failed to get target namespace", "namespace", req.Namespace) + return ctrl.Result{}, err + } + + // Check if namespace still matches the selector + if r.TargetNamespaceSelector != nil && !r.TargetNamespaceSelector.Matches(labels.Set(targetNamespace.Labels)) { + // Namespace no longer matches selector, delete the replicated secret if it exists + replicatedSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: req.Namespace, + Name: req.Name, + }, + } + if err := r.Delete(ctx, replicatedSecret); err != nil && !apierrors.IsNotFound(err) { + logger.Error(err, "Failed to delete replicated secret from non-matching namespace", + "namespace", req.Namespace, "secret", req.Name) + return ctrl.Result{}, err + } + return ctrl.Result{}, nil + } + + // Get the source secret originalSecret := &corev1.Secret{} - r.Get(ctx, types.NamespacedName{Namespace: r.SourceNamespace, Name: r.SecretName}, originalSecret) - replicatedSecret := originalSecret.DeepCopy() - replicatedSecret.Namespace = req.Namespace - r.Update(ctx, replicatedSecret) + if err := r.Get(ctx, types.NamespacedName{Namespace: r.SourceNamespace, Name: r.SecretName}, originalSecret); err != nil { + if apierrors.IsNotFound(err) { + // Source secret doesn't exist, delete the replicated secret if it exists + replicatedSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: req.Namespace, + Name: req.Name, + }, + } + if err := r.Delete(ctx, replicatedSecret); err != nil && !apierrors.IsNotFound(err) { + logger.Error(err, "Failed to delete replicated secret after source secret deletion", + "namespace", req.Namespace, "secret", req.Name) + return ctrl.Result{}, err + } + return ctrl.Result{}, nil + } + logger.Error(err, "Failed to get source secret", + "namespace", r.SourceNamespace, "secret", r.SecretName) + return ctrl.Result{}, err + } + + // Create or update the replicated secret + replicatedSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: req.Namespace, + Name: req.Name, + }, + } + + _, err := controllerutil.CreateOrUpdate(ctx, r.Client, replicatedSecret, func() error { + // Copy the secret data and type from the source + replicatedSecret.Data = make(map[string][]byte) + for k, v := range originalSecret.Data { + replicatedSecret.Data[k] = v + } + replicatedSecret.Type = originalSecret.Type + + // Copy labels and annotations from source (if any) + if originalSecret.Labels != nil { + if replicatedSecret.Labels == nil { + replicatedSecret.Labels = make(map[string]string) + } + for k, v := range originalSecret.Labels { + replicatedSecret.Labels[k] = v + } + } + if originalSecret.Annotations != nil { + if replicatedSecret.Annotations == nil { + replicatedSecret.Annotations = make(map[string]string) + } + for k, v := range originalSecret.Annotations { + replicatedSecret.Annotations[k] = v + } + } + + return nil + }) + if err != nil { + logger.Error(err, "Failed to create or update replicated secret", + "namespace", req.Namespace, "secret", req.Name) + return ctrl.Result{}, err + } + return ctrl.Result{}, nil } From d73773eaa128149f616a633bc1e3454dfe3a63ed Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 6 Jan 2026 18:06:01 +0100 Subject: [PATCH 021/889] feat(kubeovn): update to v1.14.25 Update kube-ovn from v1.14.11 to v1.14.25. Sync chart templates with upstream changes. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- packages/system/kubeovn/Chart.yaml | 2 +- packages/system/kubeovn/Makefile | 2 +- .../system/kubeovn/charts/kube-ovn/Chart.yaml | 4 +- .../system/kubeovn/charts/kube-ovn/README.md | 12 +++++ .../charts/kube-ovn/templates/_helpers.tpl | 5 +- .../kube-ovn/templates/central-deploy.yaml | 4 ++ .../kube-ovn/templates/controller-deploy.yaml | 4 ++ .../templates/ic-controller-deploy.yaml | 4 ++ .../kube-ovn/templates/kube-ovn-crd.yaml | 48 +++++++++++++++++++ .../kube-ovn/templates/monitor-deploy.yaml | 4 ++ .../kube-ovn/templates/ovn-dpdk-ds.yaml | 4 ++ .../charts/kube-ovn/templates/ovn-sa.yaml | 4 ++ .../charts/kube-ovn/templates/ovncni-ds.yaml | 17 +++++-- .../charts/kube-ovn/templates/ovsovn-ds.yaml | 5 ++ .../charts/kube-ovn/templates/pinger-ds.yaml | 6 ++- .../kube-ovn/templates/post-delete-hook.yaml | 6 ++- .../kube-ovn/templates/upgrade-ovs-ovn.yaml | 6 ++- .../kubeovn/charts/kube-ovn/values.yaml | 3 +- packages/system/kubeovn/values.yaml | 2 +- 19 files changed, 129 insertions(+), 13 deletions(-) diff --git a/packages/system/kubeovn/Chart.yaml b/packages/system/kubeovn/Chart.yaml index d1532794..b1a4e05b 100644 --- a/packages/system/kubeovn/Chart.yaml +++ b/packages/system/kubeovn/Chart.yaml @@ -1,3 +1,3 @@ apiVersion: v2 name: cozy-kubeovn -version: 0.39.0 +version: 0.38.0 diff --git a/packages/system/kubeovn/Makefile b/packages/system/kubeovn/Makefile index a4a0d1ed..45c5ab25 100644 --- a/packages/system/kubeovn/Makefile +++ b/packages/system/kubeovn/Makefile @@ -1,4 +1,4 @@ -KUBEOVN_TAG=v0.39.0 +KUBEOVN_TAG=v0.40.0 export NAME=kubeovn export NAMESPACE=cozy-$(NAME) diff --git a/packages/system/kubeovn/charts/kube-ovn/Chart.yaml b/packages/system/kubeovn/charts/kube-ovn/Chart.yaml index f7be2d3b..0621c7c7 100644 --- a/packages/system/kubeovn/charts/kube-ovn/Chart.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/Chart.yaml @@ -15,12 +15,12 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: v1.14.11 +version: v1.14.25 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "1.14.11" +appVersion: "1.14.25" kubeVersion: ">= 1.29.0-0" diff --git a/packages/system/kubeovn/charts/kube-ovn/README.md b/packages/system/kubeovn/charts/kube-ovn/README.md index 3af408e6..74e5c3f1 100644 --- a/packages/system/kubeovn/charts/kube-ovn/README.md +++ b/packages/system/kubeovn/charts/kube-ovn/README.md @@ -2,6 +2,18 @@ Currently supported version: 1.9 +## Installing the Chart + +### From OCI Registry + +The Helm chart is available from GitHub Container Registry: + +```bash +helm install kube-ovn oci://ghcr.io/kubeovn/charts/kube-ovn --version v1.15.0 +``` + +### From Source + Installation : ```bash diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/_helpers.tpl b/packages/system/kubeovn/charts/kube-ovn/templates/_helpers.tpl index e6697c6e..fd6db240 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/_helpers.tpl +++ b/packages/system/kubeovn/charts/kube-ovn/templates/_helpers.tpl @@ -1,8 +1,10 @@ {{/* -Get IP-addresses of master nodes +Get IP-addresses of master nodes. If no nodes are returned, we assume this is +a dry-run/template call and return nothing. */}} {{- define "kubeovn.nodeIPs" -}} {{- $nodes := lookup "v1" "Node" "" "" -}} +{{- if $nodes -}} {{- $ips := list -}} {{- range $node := $nodes.items -}} {{- $label := splitList "=" $.Values.MASTER_NODES_LABEL }} @@ -25,6 +27,7 @@ Get IP-addresses of master nodes {{- end -}} {{ join "," $ips }} {{- end -}} +{{- end -}} {{/* Number of master nodes diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/central-deploy.yaml b/packages/system/kubeovn/charts/kube-ovn/templates/central-deploy.yaml index bbc1e09d..505e0925 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/central-deploy.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/central-deploy.yaml @@ -39,7 +39,11 @@ spec: topologyKey: kubernetes.io/hostname priorityClassName: system-cluster-critical serviceAccountName: ovn-ovs + automountServiceAccountToken: true hostNetwork: true + securityContext: + seccompProfile: + type: RuntimeDefault initContainers: - name: hostpath-init image: {{ .Values.global.registry.address }}/{{ .Values.global.images.kubeovn.repository }}:{{ .Values.global.images.kubeovn.tag }} 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 5c4587f9..cd0728b3 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/controller-deploy.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/controller-deploy.yaml @@ -46,7 +46,11 @@ spec: topologyKey: kubernetes.io/hostname priorityClassName: system-cluster-critical serviceAccountName: ovn + automountServiceAccountToken: true hostNetwork: true + securityContext: + seccompProfile: + type: RuntimeDefault initContainers: - name: hostpath-init image: {{ .Values.global.registry.address }}/{{ .Values.global.images.kubeovn.repository }}:{{ .Values.global.images.kubeovn.tag }} diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/ic-controller-deploy.yaml b/packages/system/kubeovn/charts/kube-ovn/templates/ic-controller-deploy.yaml index ee3e1461..53ecfa24 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/ic-controller-deploy.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/ic-controller-deploy.yaml @@ -40,7 +40,11 @@ spec: topologyKey: kubernetes.io/hostname priorityClassName: system-cluster-critical serviceAccountName: ovn + automountServiceAccountToken: true hostNetwork: true + securityContext: + seccompProfile: + type: RuntimeDefault initContainers: - name: hostpath-init image: {{ .Values.global.registry.address }}/{{ .Values.global.images.kubeovn.repository }}:{{ .Values.global.images.kubeovn.tag }} diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/kube-ovn-crd.yaml b/packages/system/kubeovn/charts/kube-ovn/templates/kube-ovn-crd.yaml index 3bddfbe1..78ac7d38 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/kube-ovn-crd.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/kube-ovn-crd.yaml @@ -1200,6 +1200,52 @@ spec: required: - key - operator + tolerations: + description: optional tolerations applied to the workload pods + 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 + enum: + - NoSchedule + - PreferNoSchedule + - NoExecute + 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 + enum: + - Exists + - Equal + 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 --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition @@ -2871,6 +2917,8 @@ spec: type: array items: type: string + autoCreateVlanSubinterfaces: + type: boolean required: - defaultInterface status: diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/monitor-deploy.yaml b/packages/system/kubeovn/charts/kube-ovn/templates/monitor-deploy.yaml index e4c3322c..dc4eac22 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/monitor-deploy.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/monitor-deploy.yaml @@ -37,7 +37,11 @@ spec: topologyKey: kubernetes.io/hostname priorityClassName: system-cluster-critical serviceAccountName: kube-ovn-app + automountServiceAccountToken: true hostNetwork: true + securityContext: + seccompProfile: + type: RuntimeDefault initContainers: - name: hostpath-init image: {{ .Values.global.registry.address }}/{{ .Values.global.images.kubeovn.repository }}:{{ .Values.global.images.kubeovn.tag }} diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/ovn-dpdk-ds.yaml b/packages/system/kubeovn/charts/kube-ovn/templates/ovn-dpdk-ds.yaml index 9a1d591f..330c9b6f 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/ovn-dpdk-ds.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/ovn-dpdk-ds.yaml @@ -27,8 +27,12 @@ spec: - operator: Exists priorityClassName: system-node-critical serviceAccountName: ovn-ovs + automountServiceAccountToken: true hostNetwork: true hostPID: true + securityContext: + seccompProfile: + type: RuntimeDefault containers: - name: openvswitch image: {{ .Values.global.registry.address }}/{{ .Values.global.images.kubeovn.repository }}:{{ .Values.DPDK_IMAGE_TAG }} diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/ovn-sa.yaml b/packages/system/kubeovn/charts/kube-ovn/templates/ovn-sa.yaml index 1e5e9b5c..744b3b90 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/ovn-sa.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/ovn-sa.yaml @@ -3,6 +3,7 @@ kind: ServiceAccount metadata: name: ovn namespace: {{ .Values.namespace }} +automountServiceAccountToken: false {{- if .Values.global.registry.imagePullSecrets }} imagePullSecrets: {{- range $index, $secret := .Values.global.registry.imagePullSecrets }} @@ -18,6 +19,7 @@ kind: ServiceAccount metadata: name: ovn-ovs namespace: {{ .Values.namespace }} +automountServiceAccountToken: false {{- if .Values.global.registry.imagePullSecrets }} imagePullSecrets: {{- range $index, $secret := .Values.global.registry.imagePullSecrets }} @@ -33,6 +35,7 @@ kind: ServiceAccount metadata: name: kube-ovn-cni namespace: {{ .Values.namespace }} +automountServiceAccountToken: false {{- if .Values.global.registry.imagePullSecrets }} imagePullSecrets: {{- range $index, $secret := .Values.global.registry.imagePullSecrets }} @@ -48,6 +51,7 @@ kind: ServiceAccount metadata: name: kube-ovn-app namespace: {{ .Values.namespace }} +automountServiceAccountToken: false {{- if .Values.global.registry.imagePullSecrets }} imagePullSecrets: {{- range $index, $secret := .Values.global.registry.imagePullSecrets }} 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 947ec454..c68e041d 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/ovncni-ds.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/ovncni-ds.yaml @@ -26,8 +26,12 @@ spec: operator: Exists priorityClassName: system-node-critical serviceAccountName: kube-ovn-cni + automountServiceAccountToken: true hostNetwork: true hostPID: true + securityContext: + seccompProfile: + type: RuntimeDefault initContainers: - name: hostpath-init image: {{ .Values.global.registry.address }}/{{ .Values.global.images.kubeovn.repository }}:{{ .Values.global.images.kubeovn.tag }} @@ -35,7 +39,9 @@ spec: command: - sh - -xec - - iptables -V + - | + chmod +t /usr/local/sbin + iptables -V securityContext: allowPrivilegeEscalation: true capabilities: @@ -60,16 +66,21 @@ spec: imagePullPolicy: {{ .Values.image.pullPolicy }} command: - /kube-ovn/install-cni.sh - - --cni-conf-dir={{ .Values.cni_conf.CNI_CONF_DIR }} + - --cni-conf-dir={{ .Values.cni_conf.MOUNT_CNI_CONF_DIR }} - --cni-conf-file={{ .Values.cni_conf.CNI_CONF_FILE }} - --cni-conf-name={{- .Values.cni_conf.CNI_CONFIG_PRIORITY -}}-kube-ovn.conflist + env: + - name: POD_IPS + valueFrom: + fieldRef: + fieldPath: status.podIPs securityContext: runAsUser: 0 privileged: true volumeMounts: - mountPath: /opt/cni/bin name: cni-bin - - mountPath: /etc/cni/net.d + - mountPath: {{ .Values.cni_conf.MOUNT_CNI_CONF_DIR }} name: cni-conf {{- if .Values.cni_conf.MOUNT_LOCAL_BIN_DIR }} - mountPath: /usr/local/bin diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/ovsovn-ds.yaml b/packages/system/kubeovn/charts/kube-ovn/templates/ovsovn-ds.yaml index 17743d5f..7146ec71 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/ovsovn-ds.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/ovsovn-ds.yaml @@ -34,8 +34,12 @@ spec: operator: Exists priorityClassName: system-node-critical serviceAccountName: ovn-ovs + automountServiceAccountToken: true hostNetwork: true hostPID: true + securityContext: + seccompProfile: + type: RuntimeDefault initContainers: - name: hostpath-init image: {{ .Values.global.registry.address }}/{{ .Values.global.images.kubeovn.repository }}:{{ .Values.global.images.kubeovn.tag }} @@ -44,6 +48,7 @@ spec: - sh - -xec - | + chmod +t /usr/local/sbin chown -R nobody: /var/run/ovn /var/log/ovn /etc/openvswitch /var/run/openvswitch /var/log/openvswitch iptables -V {{- if not .Values.DISABLE_MODULES_MANAGEMENT }} diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/pinger-ds.yaml b/packages/system/kubeovn/charts/kube-ovn/templates/pinger-ds.yaml index 66a34853..fbc82171 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/pinger-ds.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/pinger-ds.yaml @@ -28,7 +28,11 @@ spec: - key: CriticalAddonsOnly operator: Exists serviceAccountName: kube-ovn-app - hostPID: true + automountServiceAccountToken: true + hostPID: false + securityContext: + seccompProfile: + type: RuntimeDefault initContainers: - name: hostpath-init image: {{ .Values.global.registry.address }}/{{ .Values.global.images.kubeovn.repository }}:{{ .Values.global.images.kubeovn.tag }} diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/post-delete-hook.yaml b/packages/system/kubeovn/charts/kube-ovn/templates/post-delete-hook.yaml index a4c0d618..682b5a96 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/post-delete-hook.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/post-delete-hook.yaml @@ -9,6 +9,7 @@ metadata: "helm.sh/hook": post-delete "helm.sh/hook-weight": "1" "helm.sh/hook-delete-policy": hook-succeeded +automountServiceAccountToken: false --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole @@ -102,8 +103,11 @@ spec: hostNetwork: true nodeSelector: kubernetes.io/os: "linux" - serviceAccount: kube-ovn-post-delete-hook serviceAccountName: kube-ovn-post-delete-hook + automountServiceAccountToken: true + securityContext: + seccompProfile: + type: RuntimeDefault containers: - name: remove-subnet-finalizer image: "{{ .Values.global.registry.address}}/{{ .Values.global.images.kubeovn.repository }}:{{ .Values.global.images.kubeovn.tag }}" diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/upgrade-ovs-ovn.yaml b/packages/system/kubeovn/charts/kube-ovn/templates/upgrade-ovs-ovn.yaml index fc5ac4ba..ab646e03 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/upgrade-ovs-ovn.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/upgrade-ovs-ovn.yaml @@ -11,6 +11,7 @@ metadata: "helm.sh/hook": post-upgrade "helm.sh/hook-weight": "1" "helm.sh/hook-delete-policy": hook-succeeded +automountServiceAccountToken: false --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole @@ -133,8 +134,11 @@ spec: hostNetwork: true nodeSelector: kubernetes.io/os: "linux" - serviceAccount: ovs-ovn-upgrade serviceAccountName: ovs-ovn-upgrade + automountServiceAccountToken: true + securityContext: + seccompProfile: + type: RuntimeDefault containers: - name: ovs-ovn-upgrade image: "{{ .Values.global.registry.address}}/{{ .Values.global.images.kubeovn.repository }}:{{ .Values.global.images.kubeovn.tag }}" diff --git a/packages/system/kubeovn/charts/kube-ovn/values.yaml b/packages/system/kubeovn/charts/kube-ovn/values.yaml index 3652386d..430ea428 100644 --- a/packages/system/kubeovn/charts/kube-ovn/values.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/values.yaml @@ -9,7 +9,7 @@ global: kubeovn: repository: kube-ovn vpcRepository: vpc-nat-gateway - tag: v1.14.11 + tag: v1.14.25 support_arm: true thirdparty: true @@ -111,6 +111,7 @@ debug: cni_conf: CNI_CONFIG_PRIORITY: "01" CNI_CONF_DIR: "/etc/cni/net.d" + MOUNT_CNI_CONF_DIR: "/etc/cni/net.d" CNI_BIN_DIR: "/opt/cni/bin" CNI_CONF_FILE: "/kube-ovn/01-kube-ovn.conflist" LOCAL_BIN_DIR: "/usr/local/bin" diff --git a/packages/system/kubeovn/values.yaml b/packages/system/kubeovn/values.yaml index 89f20e7c..700960ee 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:0e3e9db960a9600d58c33c0787cabd0e9bf263930fd8c9fe65417e258c383d01 + tag: v1.14.25@sha256:d0b29daaf36e81cac0f9fb15d0ea6b1b49f1abba81a14c73b88a2e60ffcc5978 From 6ca44232ab2e2b23f11f3182d8860fc2aca1e623 Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Wed, 17 Dec 2025 13:55:58 +0300 Subject: [PATCH 022/889] [backups,dashboard] User-facing UI ## What this PR does Add representation for backup Plans in the dashboard and a readme describing, how to add further resources to the dashboard. ### Release note ```release-note [backups,dashboard] Add backup Plans to the dashboard. ``` Signed-off-by: Timofei Larkin --- internal/controller/dashboard/sidebar.go | 27 +++- .../controller/dashboard/static_helpers.go | 21 +++ .../controller/dashboard/static_refactored.go | 124 ++++++++++++++++++ 3 files changed, 167 insertions(+), 5 deletions(-) diff --git a/internal/controller/dashboard/sidebar.go b/internal/controller/dashboard/sidebar.go index 97937921..6cba4a95 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,9 @@ 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"} + // 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 +126,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 +145,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 +166,19 @@ 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", + }, + }, + }) + // Add hardcoded Administration section menuItems = append(menuItems, map[string]any{ "key": "administration", @@ -201,6 +217,7 @@ 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-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..344cf23b 100644 --- a/internal/controller/dashboard/static_refactored.go +++ b/internal/controller/dashboard/static_refactored.go @@ -371,6 +371,13 @@ 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"), + }), } } @@ -1433,6 +1440,122 @@ 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}"}) + return []*dashboardv1alpha1.Factory{ createFactory("marketplace", marketplaceSpec), createFactory("namespace-details", namespaceSpec), @@ -1442,6 +1565,7 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory { createFactory("kube-service-details", serviceSpec), createFactory("kube-ingress-details", ingressSpec), createFactory("workloadmonitor-details", workloadmonitorSpec), + createFactory("plan-details", planSpec), } } From 7f2ede81d04ecc45a3a73b076589e11a7e8daee0 Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Thu, 25 Dec 2025 15:56:41 +0300 Subject: [PATCH 023/889] [backups,dashboard] Add dev guide for dashboard Moved README describing how to add new resources to the Cozystack dashboard to a separate commit to directly reference relevant code changes from the document. Signed-off-by: Timofei Larkin --- internal/controller/dashboard/README.md | 355 ++++++++++++++++++++++++ 1 file changed, 355 insertions(+) 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) + From 297acd90cd1835df00902b0f6a00897b187b64b0 Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Wed, 7 Jan 2026 10:06:36 +0300 Subject: [PATCH 024/889] [backups,dashboard] Add BackupJobs and Backups Signed-off-by: Timofei Larkin --- internal/controller/dashboard/sidebar.go | 18 + .../controller/dashboard/static_refactored.go | 331 ++++++++++++++++++ 2 files changed, 349 insertions(+) diff --git a/internal/controller/dashboard/sidebar.go b/internal/controller/dashboard/sidebar.go index 6cba4a95..c6f9d3ba 100644 --- a/internal/controller/dashboard/sidebar.go +++ b/internal/controller/dashboard/sidebar.go @@ -115,6 +115,12 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Cozystack // 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 { @@ -176,6 +182,16 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Cozystack "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", + }, }, }) @@ -218,6 +234,8 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Cozystack "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_refactored.go b/internal/controller/dashboard/static_refactored.go index 344cf23b..4c290c49 100644 --- a/internal/controller/dashboard/static_refactored.go +++ b/internal/controller/dashboard/static_refactored.go @@ -378,6 +378,23 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid 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"), + }), } } @@ -1556,6 +1573,318 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory { } 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), @@ -1566,6 +1895,8 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory { createFactory("kube-ingress-details", ingressSpec), createFactory("workloadmonitor-details", workloadmonitorSpec), createFactory("plan-details", planSpec), + createFactory("backupjob-details", backupJobSpec), + createFactory("backup-details", backupSpec), } } From 8151e1e41a9b2b7e28ce128fdd6c258c6453a505 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Wed, 7 Jan 2026 13:27:06 +0100 Subject: [PATCH 025/889] 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 --- .../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 47b5a5757faf3c4efe640ebb760ca5940fde38ec Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Wed, 7 Jan 2026 14:36:19 +0100 Subject: [PATCH 026/889] feat(linstor): add linstor-scheduler package Add linstor-scheduler-extender for optimal pod placement on nodes with LINSTOR storage. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- .../core/platform/bundles/distro-full.yaml | 6 + packages/core/platform/bundles/paas-full.yaml | 6 + .../platform/sources/linstor-scheduler.yaml | 22 ++ packages/system/linstor-scheduler/.helmignore | 1 + packages/system/linstor-scheduler/Chart.yaml | 3 + packages/system/linstor-scheduler/Makefile | 10 + .../charts/linstor-scheduler/Chart.yaml | 19 ++ .../charts/linstor-scheduler/LICENSE | 201 ++++++++++++++++++ .../charts/linstor-scheduler/README.md | 72 +++++++ .../linstor-scheduler/templates/NOTES.txt | 12 ++ .../linstor-scheduler/templates/_helpers.tpl | 141 ++++++++++++ .../templates/admission-deployment.yaml | 98 +++++++++ .../templates/admission-service.yaml | 20 ++ .../templates/certmanager.yaml | 56 +++++ .../linstor-scheduler/templates/config.yaml | 46 ++++ .../templates/deployment.yaml | 126 +++++++++++ .../linstor-scheduler/templates/hpa.yaml | 32 +++ .../mutatingwebhookconfiguration.yaml | 32 +++ .../templates/poddisruptionbudget.yaml | 18 ++ .../linstor-scheduler/templates/rbac.yaml | 108 ++++++++++ .../templates/serviceaccount.yaml | 12 ++ .../templates/tests/test-scheduler.yaml | 16 ++ .../charts/linstor-scheduler/values.yaml | 106 +++++++++ packages/system/linstor-scheduler/values.yaml | 7 + 24 files changed, 1170 insertions(+) create mode 100644 packages/core/platform/sources/linstor-scheduler.yaml create mode 100644 packages/system/linstor-scheduler/.helmignore create mode 100644 packages/system/linstor-scheduler/Chart.yaml create mode 100644 packages/system/linstor-scheduler/Makefile create mode 100644 packages/system/linstor-scheduler/charts/linstor-scheduler/Chart.yaml create mode 100644 packages/system/linstor-scheduler/charts/linstor-scheduler/LICENSE create mode 100644 packages/system/linstor-scheduler/charts/linstor-scheduler/README.md create mode 100644 packages/system/linstor-scheduler/charts/linstor-scheduler/templates/NOTES.txt create mode 100644 packages/system/linstor-scheduler/charts/linstor-scheduler/templates/_helpers.tpl create mode 100644 packages/system/linstor-scheduler/charts/linstor-scheduler/templates/admission-deployment.yaml create mode 100644 packages/system/linstor-scheduler/charts/linstor-scheduler/templates/admission-service.yaml create mode 100644 packages/system/linstor-scheduler/charts/linstor-scheduler/templates/certmanager.yaml create mode 100644 packages/system/linstor-scheduler/charts/linstor-scheduler/templates/config.yaml create mode 100644 packages/system/linstor-scheduler/charts/linstor-scheduler/templates/deployment.yaml create mode 100644 packages/system/linstor-scheduler/charts/linstor-scheduler/templates/hpa.yaml create mode 100644 packages/system/linstor-scheduler/charts/linstor-scheduler/templates/mutatingwebhookconfiguration.yaml create mode 100644 packages/system/linstor-scheduler/charts/linstor-scheduler/templates/poddisruptionbudget.yaml create mode 100644 packages/system/linstor-scheduler/charts/linstor-scheduler/templates/rbac.yaml create mode 100644 packages/system/linstor-scheduler/charts/linstor-scheduler/templates/serviceaccount.yaml create mode 100644 packages/system/linstor-scheduler/charts/linstor-scheduler/templates/tests/test-scheduler.yaml create mode 100644 packages/system/linstor-scheduler/charts/linstor-scheduler/values.yaml create mode 100644 packages/system/linstor-scheduler/values.yaml diff --git a/packages/core/platform/bundles/distro-full.yaml b/packages/core/platform/bundles/distro-full.yaml index bf915d4e..13d58ded 100644 --- a/packages/core/platform/bundles/distro-full.yaml +++ b/packages/core/platform/bundles/distro-full.yaml @@ -357,6 +357,12 @@ releases: privileged: true dependsOn: [piraeus-operator,cilium,cert-manager,snapshot-controller] +- name: linstor-scheduler + releaseName: linstor-scheduler + chart: cozy-linstor-scheduler + namespace: cozy-linstor + dependsOn: [linstor,cert-manager] + - name: nfs-driver releaseName: nfs-driver chart: cozy-nfs-driver diff --git a/packages/core/platform/bundles/paas-full.yaml b/packages/core/platform/bundles/paas-full.yaml index b82e3ef4..6959fed7 100644 --- a/packages/core/platform/bundles/paas-full.yaml +++ b/packages/core/platform/bundles/paas-full.yaml @@ -430,6 +430,12 @@ releases: privileged: true dependsOn: [piraeus-operator,cilium,kubeovn,cert-manager,snapshot-controller] +- name: linstor-scheduler + releaseName: linstor-scheduler + chart: cozy-linstor-scheduler + namespace: cozy-linstor + dependsOn: [linstor,cert-manager] + - name: nfs-driver releaseName: nfs-driver chart: cozy-nfs-driver diff --git a/packages/core/platform/sources/linstor-scheduler.yaml b/packages/core/platform/sources/linstor-scheduler.yaml new file mode 100644 index 00000000..fad3eeb7 --- /dev/null +++ b/packages/core/platform/sources/linstor-scheduler.yaml @@ -0,0 +1,22 @@ +--- +apiVersion: cozystack.io/v1alpha1 +kind: PackageSource +metadata: + name: cozystack.linstor-scheduler +spec: + sourceRef: + kind: OCIRepository + name: cozystack-packages + namespace: cozy-system + path: / + variants: + - name: default + dependsOn: + - cozystack.linstor + - cozystack.cert-manager + components: + - name: linstor-scheduler + path: system/linstor-scheduler + install: + namespace: cozy-linstor + releaseName: linstor-scheduler diff --git a/packages/system/linstor-scheduler/.helmignore b/packages/system/linstor-scheduler/.helmignore new file mode 100644 index 00000000..1e107f52 --- /dev/null +++ b/packages/system/linstor-scheduler/.helmignore @@ -0,0 +1 @@ +examples diff --git a/packages/system/linstor-scheduler/Chart.yaml b/packages/system/linstor-scheduler/Chart.yaml new file mode 100644 index 00000000..682d263b --- /dev/null +++ b/packages/system/linstor-scheduler/Chart.yaml @@ -0,0 +1,3 @@ +apiVersion: v2 +name: cozy-linstor-scheduler +version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process diff --git a/packages/system/linstor-scheduler/Makefile b/packages/system/linstor-scheduler/Makefile new file mode 100644 index 00000000..90a31bc8 --- /dev/null +++ b/packages/system/linstor-scheduler/Makefile @@ -0,0 +1,10 @@ +export NAME=linstor-scheduler +export NAMESPACE=cozy-linstor + +include ../../../scripts/package.mk + +update: + rm -rf charts + helm repo add piraeus-charts https://piraeus.io/helm-charts/ + helm repo update piraeus-charts + helm pull piraeus-charts/linstor-scheduler --untar --untardir charts diff --git a/packages/system/linstor-scheduler/charts/linstor-scheduler/Chart.yaml b/packages/system/linstor-scheduler/charts/linstor-scheduler/Chart.yaml new file mode 100644 index 00000000..4a521d1f --- /dev/null +++ b/packages/system/linstor-scheduler/charts/linstor-scheduler/Chart.yaml @@ -0,0 +1,19 @@ +apiVersion: v2 +appVersion: v0.3.2 +deprecated: true +description: 'Deploys a new kubernetes scheduler, configured to take advantage of + storage system information. If a Pod is using a LINSTOR volume, the scheduler will + prefer nodes with local data instead of accessing the data via a DRBD diskless. ' +home: https://github.com/piraeusdatastore/helm-charts +icon: https://raw.githubusercontent.com/piraeusdatastore/piraeus/master/artwork/sandbox-artwork/icon/color.svg +keywords: +- storage +- scheduler +maintainers: +- name: The Piraeus Maintainers + url: https://github.com/piraeusdatastore/ +name: linstor-scheduler +sources: +- https://github.com/piraeusdatastore/linstor-scheduler-extender +type: application +version: 0.2.3 diff --git a/packages/system/linstor-scheduler/charts/linstor-scheduler/LICENSE b/packages/system/linstor-scheduler/charts/linstor-scheduler/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/packages/system/linstor-scheduler/charts/linstor-scheduler/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/packages/system/linstor-scheduler/charts/linstor-scheduler/README.md b/packages/system/linstor-scheduler/charts/linstor-scheduler/README.md new file mode 100644 index 00000000..ddf92826 --- /dev/null +++ b/packages/system/linstor-scheduler/charts/linstor-scheduler/README.md @@ -0,0 +1,72 @@ +# linstor-scheduler + +Deploys a new Kubernetes scheduler, extended by +the [linstor-scheduler-extender](https://github.com/piraeusdatastore/linstor-scheduler-extender). + +> [!IMPORTANT] +> The LINSTOR Scheduler is no longer maintained. Prefer using `volumeBindingMode: WaitForFirstConsumer` on your +> StorageClasses. + +The schedule is volume placement aware. That means that it prefers placing Pods on the same nodes as any Persistent +Volume they might use. This works for any setup using LINSTOR, i.e. Piraeus Datastore or LINBIT SDS. + +## Installation + +The scheduler is meant to be installed in the same namespace as LINSTOR itself, otherwise additional steps may be +required. + +If installed along side Piraeus Operator, the LINSTOR endpoint is determined automatically. Otherwise, you need +to set `linstor.endpoint` and `linstor.clientSecret` values as appropriate. + +The following command will install the scheduler for a typical Piraeus Data-Store configuration with TLS enabled: + +``` +helm repo add piraeus-charts https://piraeus.io/helm-charts/ +helm install linstor-scheduler piraeus-charts/linstor-scheduler --set linstorEndpoint=https://piraeus-op-cs.piraeus.svc:3371 --set linstorClientSecret=piraeus-client-secret +``` + +## Usage + +To use the scheduler, you need to configure it on your Pods (or Pod templates): + +```yaml +apiVersion: v1 +kind: Pod +metadata: + name: some-pod +spec: + schedulerName: linstor-scheduler + ... +``` + +## Configuration + +The following options are available: + +| Option | Usage | Default | +|-----------------------------------------------|--------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------| +| `replicaCount` | Number of replicas to deploy. | `1` | +| `linstor.endpoint` | URL of the LINSTOR Controller API. | `""` | +| `linstor.clientSecret` | TLS secret to use to authenticate with the LINSTOR API | `""` | +| `extender.image.repository` | Repository to pull the linstor-scheduler-extender image from. | `quay.io/piraeusdatastore/linstor-scheduler-extender` | +| `extender.image.pullPolicy` | Pull policy to use. Possible values: `IfNotPresent`, `Always`, `Never` | `IfNotPresent` | +| `extender.image.tag` | Override the tag to pull. If not given, defaults to charts `AppVersion`. | `""` | +| `extender.resources` | Resources to request and limit on the container. | `{}` | +| `extender.securityContext` | Configure container security context. Defaults to dropping all capabilties and running as user 1000. | `{capabilities: {drop: [ALL]}, readOnlyRootFilesystem: true, runAsNonRoot: true, runAsUser: 1000}` | +| `scheduler.image.repository` | Repository to pull the kubernetes scheduler image from. | `registry.k8s.io/kube-scheduler` | +| `scheduler.image.pullPolicy` | Pull policy to use. Possible values: `IfNotPresent`, `Always`, `Never` | `IfNotPresent` | +| `scheduler.image.tag` | Override the tag to pull. If not given, defaults to kubernetes version. | `""` | +| `scheduler.image.compatibleKubernetesRelease` | Compatible kubernetes version for this scheduler, used to generate configuration in the right version. | `""` | +| `scheduler.resources` | Resources to request and limit on the container. | `{}` | +| `scheduler.securityContext` | Configure container security context. Defaults to dropping all capabilties and running as user 1000. | `{capabilities: {drop: [ALL]}, readOnlyRootFilesystem: true, runAsNonRoot: true, runAsUser: 1000}` | +| `imagePullSecrets` | Image pull secrets to add to the deployment. | `[]` | +| `podAnnotations` | Annotations to add to every pod in the deployment. | `{}` | +| `podSecurityContext` | Security context to set on the webhook pod. | `{}` | +| `nodeSelector` | Node selector to add to each webhook pod. | `{}` | +| `tolerations` | Tolerations to add to each webhook pod. | `[]` | +| `affinity` | Affinity to set on each webhook pod. | `{}` | +| `rbac.create` | Create the necessary roles and bindings for the snapshot controller. | `true` | +| `serviceAccount.create` | Create the service account resource | `true` | +| `serviceAccount.name` | Sets the name of the service account. If left empty, will use the release name as default | `""` | +| `podDisruptionBudget.enabled` | Enable creation of a pod disruption budget to protect the availability of the scheduler | `true` | +| `autoscaling.enabled` | Enable creation of a horizontal pod autoscaler to ensure availability in case of high usage` | `"false` | diff --git a/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/NOTES.txt b/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/NOTES.txt new file mode 100644 index 00000000..5e426f28 --- /dev/null +++ b/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/NOTES.txt @@ -0,0 +1,12 @@ +Scheduler {{ include "linstor-scheduler.fullname" . }} deployed! + +Used LINSTOR URL: {{ include "linstor-scheduler.linstorEndpoint" .}} + +Please run `helm test {{ .Release.Name }}` to ensure it's properly working. + +Specify the scheduler on your pods to start smart scheduling based on your Persistent Volumes: + +--- +spec: + schedulerName: {{ include "linstor-scheduler.fullname" . }} +--- diff --git a/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/_helpers.tpl b/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/_helpers.tpl new file mode 100644 index 00000000..ad24453b --- /dev/null +++ b/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/_helpers.tpl @@ -0,0 +1,141 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "linstor-scheduler.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "linstor-scheduler.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "linstor-scheduler.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "linstor-scheduler.labels" -}} +helm.sh/chart: {{ include "linstor-scheduler.chart" . }} +{{ include "linstor-scheduler.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "linstor-scheduler.selectorLabels" -}} +app.kubernetes.io/name: {{ include "linstor-scheduler.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "linstor-scheduler.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "linstor-scheduler.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Get the kubernetes version we should assume for creating scheduler configs +*/}} +{{- define "linstor-scheduler.kubeVersion" }} +{{- .Values.scheduler.image.compatibleKubernetesRelease | default .Capabilities.KubeVersion.Version }} +{{- end }} + +{{/* +Find the linstor client secret containing TLS certificates +*/}} +{{- define "linstor-scheduler.linstorClientSecretName" -}} +{{- if .Values.linstor.clientSecret }} +{{- .Values.linstor.clientSecret }} +{{- else if .Capabilities.APIVersions.Has "piraeus.linbit.com/v1/LinstorController" }} +{{- $crs := (lookup "piraeus.linbit.com/v1" "LinstorController" .Release.Namespace "").items }} +{{- if $crs }} +{{- if eq (len $crs) 1 }} +{{- $item := index $crs 0 }} +{{- $item.spec.linstorHttpsClientSecret }} +{{- end }} +{{- end }} +{{- else if .Capabilities.APIVersions.Has "linstor.linbit.com/v1/LinstorController" }} +{{- $crs := (lookup "linstor.linbit.com/v1" "LinstorController" .Release.Namespace "").items }} +{{- if $crs }} +{{- if eq (len $crs) 1 }} +{{- $item := index $crs 0 }} +{{- $item.spec.linstorHttpsClientSecret }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Find the linstor URL by operator resources +*/}} +{{- define "linstor-scheduler.linstorEndpointFromCRD" -}} +{{- if .Capabilities.APIVersions.Has "piraeus.linbit.com/v1/LinstorController" }} +{{- $crs := (lookup "piraeus.linbit.com/v1" "LinstorController" .Release.Namespace "").items }} +{{- if $crs }} +{{- if eq (len $crs) 1 }} +{{- $item := index $crs 0 }} +{{- if include "linstor-scheduler.linstorClientSecretName" . }} +{{- printf "https://%s.%s.svc:3371" $item.metadata.name $item.metadata.namespace }} +{{- else }} +{{- printf "http://%s.%s.svc:3370" $item.metadata.name $item.metadata.namespace }} +{{- end }} +{{- end }} +{{- end }} +{{- else if .Capabilities.APIVersions.Has "linstor.linbit.com/v1/LinstorController" }} +{{- $crs := (lookup "linstor.linbit.com/v1" "LinstorController" .Release.Namespace "").items }} +{{- if $crs }} +{{- if eq (len $crs) 1 }} +{{- $item := index $crs 0 }} +{{- if include "linstor-scheduler.linstorClientSecretName" . }} +{{- printf "https://%s.%s.svc:3371" $item.metadata.name $item.metadata.namespace }} +{{- else }} +{{- printf "http://%s.%s.svc:3370" $item.metadata.name $item.metadata.namespace }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Find the linstor URL either by override or cluster resources +*/}} +{{- define "linstor-scheduler.linstorEndpoint" -}} +{{- if .Values.linstor.endpoint }} +{{- .Values.linstor.endpoint }} +{{- else }} +{{- $piraeus := include "linstor-scheduler.linstorEndpointFromCRD" . }} +{{- if $piraeus }} +{{- $piraeus }} +{{- else }} +{{- fail "Please specify linstor.endpoint, no default URL could be determined" }} +{{- end }} +{{- end }} +{{- end }} diff --git a/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/admission-deployment.yaml b/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/admission-deployment.yaml new file mode 100644 index 00000000..22f5326a --- /dev/null +++ b/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/admission-deployment.yaml @@ -0,0 +1,98 @@ +{{- if .Values.admission.enabled }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "linstor-scheduler.fullname" . }}-admission + labels: + {{- include "linstor-scheduler.labels" . | nindent 4 }} + app.kubernetes.io/component: admission +spec: + replicas: {{ .Values.admission.replicaCount }} + selector: + matchLabels: + {{- include "linstor-scheduler.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: admission + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "linstor-scheduler.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: admission + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "linstor-scheduler.serviceAccountName" . }} + securityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + containers: + - name: linstor-scheduler-admission + image: {{ .Values.extender.image.repository }}:{{ .Values.extender.image.tag | default .Chart.AppVersion }} + imagePullPolicy: {{ .Values.extender.image.pullPolicy }} + command: ["/linstor-scheduler-admission"] + args: + - -scheduler={{ include "linstor-scheduler.fullname" . }} + - -tls-cert-file=/etc/webhook/certs/tls.crt + - -tls-key-file=/etc/webhook/certs/tls.key + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + ports: + - containerPort: 8080 + name: https + protocol: TCP + env: + - name: LS_CONTROLLERS + value: {{ include "linstor-scheduler.linstorEndpoint" . }} + {{- if include "linstor-scheduler.linstorClientSecretName" . }} + - name: LS_USER_CERTIFICATE + valueFrom: + secretKeyRef: + name: {{ include "linstor-scheduler.linstorClientSecretName" . }} + key: tls.crt + - name: LS_USER_KEY + valueFrom: + secretKeyRef: + name: {{ include "linstor-scheduler.linstorClientSecretName" . }} + key: tls.key + - name: LS_ROOT_CA + valueFrom: + secretKeyRef: + name: {{ include "linstor-scheduler.linstorClientSecretName" . }} + key: ca.crt + {{- end }} + volumeMounts: + - name: webhook-certs + mountPath: /etc/webhook/certs + readOnly: true + resources: + {{- toYaml .Values.admission.resources | nindent 12 }} + volumes: + - name: webhook-certs + secret: + secretName: {{ include "linstor-scheduler.fullname" . }}-admission-tls + defaultMode: 0400 + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/admission-service.yaml b/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/admission-service.yaml new file mode 100644 index 00000000..d2abd078 --- /dev/null +++ b/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/admission-service.yaml @@ -0,0 +1,20 @@ +{{- if .Values.admission.enabled }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "linstor-scheduler.fullname" . }}-admission + labels: + {{- include "linstor-scheduler.labels" . | nindent 4 }} + app.kubernetes.io/component: admission +spec: + type: ClusterIP + ports: + - port: 443 + targetPort: 8080 + protocol: TCP + name: https + selector: + {{- include "linstor-scheduler.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: admission +{{- end }} diff --git a/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/certmanager.yaml b/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/certmanager.yaml new file mode 100644 index 00000000..3942555b --- /dev/null +++ b/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/certmanager.yaml @@ -0,0 +1,56 @@ +{{- if .Values.admission.enabled }} +--- +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: {{ include "linstor-scheduler.fullname" . }}-admission-selfsigned + namespace: {{ .Release.Namespace }} + labels: + {{- include "linstor-scheduler.labels" . | nindent 4 }} +spec: + selfSigned: {} +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: {{ include "linstor-scheduler.fullname" . }}-admission-root-ca + namespace: {{ .Release.Namespace }} + labels: + {{- include "linstor-scheduler.labels" . | nindent 4 }} +spec: + secretName: {{ include "linstor-scheduler.fullname" . }}-admission-root-ca + duration: 43800h # 5 years + commonName: {{ include "linstor-scheduler.fullname" . }}-admission-root-ca + issuerRef: + name: {{ include "linstor-scheduler.fullname" . }}-admission-selfsigned + isCA: true +--- +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: {{ include "linstor-scheduler.fullname" . }}-admission-ca-issuer + namespace: {{ .Release.Namespace }} + labels: + {{- include "linstor-scheduler.labels" . | nindent 4 }} +spec: + ca: + secretName: {{ include "linstor-scheduler.fullname" . }}-admission-root-ca +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: {{ include "linstor-scheduler.fullname" . }}-admission-cert + namespace: {{ .Release.Namespace }} + labels: + {{- include "linstor-scheduler.labels" . | nindent 4 }} +spec: + secretName: {{ include "linstor-scheduler.fullname" . }}-admission-tls + duration: 8760h # 1 year + renewBefore: 24h + issuerRef: + name: {{ include "linstor-scheduler.fullname" . }}-admission-ca-issuer + commonName: {{ include "linstor-scheduler.fullname" . }}-admission + dnsNames: + - {{ include "linstor-scheduler.fullname" . }}-admission + - {{ include "linstor-scheduler.fullname" . }}-admission.{{ .Release.Namespace }}.svc +{{- end }} diff --git a/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/config.yaml b/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/config.yaml new file mode 100644 index 00000000..bff77d0f --- /dev/null +++ b/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/config.yaml @@ -0,0 +1,46 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "linstor-scheduler.fullname" . }} + labels: + {{- include "linstor-scheduler.labels" . | nindent 4 }} +data: +{{- if semverCompare ">= 1.22-0" (include "linstor-scheduler.kubeVersion" .) }} + scheduler-config.yaml: |- +{{- if semverCompare ">= 1.25-0" (include "linstor-scheduler.kubeVersion" .) }} + apiVersion: kubescheduler.config.k8s.io/v1 +{{- else if semverCompare ">= 1.23-0" (include "linstor-scheduler.kubeVersion" .) }} + apiVersion: kubescheduler.config.k8s.io/v1beta3 +{{- else }} + apiVersion: kubescheduler.config.k8s.io/v1beta2 +{{- end }} + kind: KubeSchedulerConfiguration + profiles: + - schedulerName: {{ include "linstor-scheduler.fullname" . }} + extenders: + - urlPrefix: http://localhost:8099 + filterVerb: filter + prioritizeVerb: prioritize + weight: 5 + enableHTTPS: false + httpTimeout: 10s + nodeCacheCapable: false +{{- else }} + policy.cfg: |- + { + "kind": "Policy", + "apiVersion": "v1", + "extenders": [ + { + "urlPrefix": "http://localhost:8099", + "apiVersion": "v1beta1", + "filterVerb": "filter", + "prioritizeVerb": "prioritize", + "weight": 5, + "enableHttps": false, + "nodeCacheCapable": false + } + ] + } +{{- end }} diff --git a/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/deployment.yaml b/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/deployment.yaml new file mode 100644 index 00000000..49898b8d --- /dev/null +++ b/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/deployment.yaml @@ -0,0 +1,126 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "linstor-scheduler.fullname" . }} + labels: + {{- include "linstor-scheduler.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "linstor-scheduler.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "linstor-scheduler.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "linstor-scheduler.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: kube-scheduler + image: "{{ .Values.scheduler.image.repository }}:{{ .Values.scheduler.image.tag | default .Capabilities.KubeVersion.Version }}" + securityContext: + {{- toYaml .Values.scheduler.securityContext | nindent 12 }} + command: + - kube-scheduler + {{- if semverCompare ">= 1.22-0" (include "linstor-scheduler.kubeVersion" .) }} + - --config=/etc/kubernetes/scheduler-config.yaml + {{- else }} + - --scheduler-name={{ include "linstor-scheduler.fullname" . }} + - --policy-configmap={{ include "linstor-scheduler.fullname" . }} + - --policy-configmap-namespace=$(NAMESPACE) + {{- end }} + - --leader-elect=true + - --leader-elect-resource-lock=leases + - --leader-elect-resource-name={{ include "linstor-scheduler.fullname" . }} + - --leader-elect-resource-namespace=$(NAMESPACE) + {{- if .Values.scheduler.args }} + {{- toYaml .Values.scheduler.args | nindent 12 }} + {{- end }} + env: + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + imagePullPolicy: {{ .Values.scheduler.image.pullPolicy }} + startupProbe: + httpGet: + path: /healthz + port: 10259 + scheme: HTTPS + livenessProbe: + httpGet: + path: /healthz + port: 10259 + scheme: HTTPS + readinessProbe: + httpGet: + path: /healthz + port: 10259 + scheme: HTTPS + {{- if semverCompare ">= 1.22-0" (include "linstor-scheduler.kubeVersion" .) }} + volumeMounts: + - mountPath: /etc/kubernetes + name: scheduler-config + {{- end }} + resources: + {{- toYaml .Values.scheduler.resources | nindent 12 }} + - name: linstor-scheduler-extender + image: {{ .Values.extender.image.repository }}:{{ .Values.extender.image.tag | default .Chart.AppVersion }} + resources: + {{- toYaml .Values.extender.resources | nindent 12 }} + securityContext: + {{- toYaml .Values.extender.securityContext | nindent 12 }} + imagePullPolicy: {{ .Values.extender.image.pullPolicy }} + args: + - --verbose=true + env: + - name: LS_CONTROLLERS + value: {{ include "linstor-scheduler.linstorEndpoint" . }} + {{- if include "linstor-scheduler.linstorClientSecretName" . }} + - name: LS_USER_CERTIFICATE + valueFrom: + secretKeyRef: + name: {{ include "linstor-scheduler.linstorClientSecretName" . }} + key: tls.crt + - name: LS_USER_KEY + valueFrom: + secretKeyRef: + name: {{ include "linstor-scheduler.linstorClientSecretName" . }} + key: tls.key + - name: LS_ROOT_CA + valueFrom: + secretKeyRef: + name: {{ include "linstor-scheduler.linstorClientSecretName" . }} + key: ca.crt + {{- end }} + {{- if semverCompare ">= 1.22-0" .Capabilities.KubeVersion.Version }} + volumes: + - configMap: + name: {{ include "linstor-scheduler.fullname" . }} + name: scheduler-config + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/hpa.yaml b/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/hpa.yaml new file mode 100644 index 00000000..d370dc75 --- /dev/null +++ b/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/hpa.yaml @@ -0,0 +1,32 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "linstor-scheduler.fullname" . }} + labels: + {{- include "linstor-scheduler.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "linstor-scheduler.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/mutatingwebhookconfiguration.yaml b/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/mutatingwebhookconfiguration.yaml new file mode 100644 index 00000000..fe387dda --- /dev/null +++ b/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/mutatingwebhookconfiguration.yaml @@ -0,0 +1,32 @@ +{{- if .Values.admission.enabled }} +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: {{ include "linstor-scheduler.fullname" . }}-admission + annotations: + cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include "linstor-scheduler.fullname" . }}-admission-cert + labels: + {{- include "linstor-scheduler.labels" . | nindent 4 }} +webhooks: + - name: linstor-scheduler-admission.linbit.com + admissionReviewVersions: ["v1", "v1beta1"] + sideEffects: None + failurePolicy: {{ .Values.admission.failurePolicy }} + clientConfig: + service: + name: {{ include "linstor-scheduler.fullname" . }}-admission + namespace: {{ .Release.Namespace }} + path: /mutate + port: 443 + rules: + - operations: ["CREATE"] + apiGroups: [""] + apiVersions: ["v1"] + resources: ["pods"] + scope: "*" + {{- with .Values.admission.namespaceSelector }} + namespaceSelector: + {{- toYaml . | nindent 6 }} + {{- end }} +{{- end }} diff --git a/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/poddisruptionbudget.yaml b/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/poddisruptionbudget.yaml new file mode 100644 index 00000000..3365f8e6 --- /dev/null +++ b/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/poddisruptionbudget.yaml @@ -0,0 +1,18 @@ +{{- if .Values.podDisruptionBudget.enabled -}} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "linstor-scheduler.fullname" . }} + labels: + {{- include "linstor-scheduler.labels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "linstor-scheduler.selectorLabels" . | nindent 6 }} + {{- if .Values.podDisruptionBudget.minAvailable }} + minAvailable: {{ .Values.podDisruptionBudget.minAvailable }} + {{- end }} + {{- if .Values.podDisruptionBudget.maxUnavailable }} + maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }} + {{- end }} +{{- end -}} diff --git a/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/rbac.yaml b/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/rbac.yaml new file mode 100644 index 00000000..38bf387b --- /dev/null +++ b/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/rbac.yaml @@ -0,0 +1,108 @@ +{{- if .Values.rbac.create }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "linstor-scheduler.fullname" . }} + namespace: {{ .Release.Namespace }} +rules: + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create + - get + - update +{{- if semverCompare "< 1.22-0" (include "linstor-scheduler.kubeVersion" .) }} + - apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch +{{- end }} +{{- if .Values.admission.enabled }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "linstor-scheduler.fullname" . }}-admission +rules: + - apiGroups: [""] + resources: ["pods", "persistentvolumeclaims", "persistentvolumes"] + verbs: ["get"] + - apiGroups: ["storage.k8s.io"] + resources: ["storageclasses"] + verbs: ["get"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "linstor-scheduler.fullname" . }}-admission +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "linstor-scheduler.fullname" . }}-admission +subjects: + - kind: ServiceAccount + name: {{ include "linstor-scheduler.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +{{- end }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "linstor-scheduler.fullname" . | trunc 57 }}-as-ks +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:kube-scheduler +subjects: + - kind: ServiceAccount + name: {{ include "linstor-scheduler.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "linstor-scheduler.fullname" . | trunc 57 }}-as-vs +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:volume-scheduler +subjects: + - kind: ServiceAccount + name: {{ include "linstor-scheduler.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "linstor-scheduler.fullname" . }} + namespace: {{ .Release.Namespace }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "linstor-scheduler.fullname" . }} +subjects: + - kind: ServiceAccount + name: {{ include "linstor-scheduler.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "linstor-scheduler.fullname" . | trunc 58 }}-auth + namespace: kube-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: extension-apiserver-authentication-reader +subjects: + - kind: ServiceAccount + name: {{ include "linstor-scheduler.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/serviceaccount.yaml b/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/serviceaccount.yaml new file mode 100644 index 00000000..2df9a5d5 --- /dev/null +++ b/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "linstor-scheduler.serviceAccountName" . }} + labels: + {{- include "linstor-scheduler.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/tests/test-scheduler.yaml b/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/tests/test-scheduler.yaml new file mode 100644 index 00000000..be95a868 --- /dev/null +++ b/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/tests/test-scheduler.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "linstor-scheduler.fullname" . | trunc 49 }}-test-schedule" + labels: + {{- include "linstor-scheduler.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + # Smoke test: just test the scheduler without volumes + schedulerName: {{ include "linstor-scheduler.fullname" . }} + containers: + - name: wget + image: busybox + command: [] + restartPolicy: Never diff --git a/packages/system/linstor-scheduler/charts/linstor-scheduler/values.yaml b/packages/system/linstor-scheduler/charts/linstor-scheduler/values.yaml new file mode 100644 index 00000000..f0633294 --- /dev/null +++ b/packages/system/linstor-scheduler/charts/linstor-scheduler/values.yaml @@ -0,0 +1,106 @@ +replicaCount: 1 + +linstor: + endpoint: "" + clientSecret: "" + +scheduler: + args: [] + image: + repository: registry.k8s.io/kube-scheduler + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the kubernetes release + tag: "" + # Overrides which config is written. The default is determined by the current Kubernetes version + compatibleKubernetesRelease: "" + securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 + + resources: {} + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +extender: + image: + repository: quay.io/piraeusdatastore/linstor-scheduler-extender + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the app version + tag: "" + securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 + + resources: {} + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + create: true + annotations: {} + name: "" + +rbac: + create: true + +podAnnotations: {} + +podSecurityContext: {} + # fsGroup: 2000 + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 3 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + + +podDisruptionBudget: + enabled: true + minAvailable: 1 + # maxUnavailable: 1 + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +admission: + enabled: false + replicaCount: 2 + failurePolicy: Ignore + namespaceSelector: {} + # matchExpressions: + # - key: kubernetes.io/metadata.name + # operator: NotIn + # values: + # - kube-system + resources: {} + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 10m + # memory: 64Mi diff --git a/packages/system/linstor-scheduler/values.yaml b/packages/system/linstor-scheduler/values.yaml new file mode 100644 index 00000000..652cb61a --- /dev/null +++ b/packages/system/linstor-scheduler/values.yaml @@ -0,0 +1,7 @@ +linstor-scheduler: + fullnameOverride: linstor-scheduler + linstor: + endpoint: "https://linstor-controller.cozy-linstor.svc:3371" + clientSecret: "linstor-client-tls" + admission: + enabled: true From 115df4a2fa9464dbae16fdbb1353d90410828b45 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Wed, 7 Jan 2026 14:59:52 +0100 Subject: [PATCH 027/889] feat(linstor): enable auto-diskful for diskless nodes Add DRBD options to automatically convert diskless nodes to diskful when they remain in Primary state for more than 30 minutes. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- packages/system/linstor/templates/cluster.yaml | 6 ++++++ packages/system/linstor/values.yaml | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/packages/system/linstor/templates/cluster.yaml b/packages/system/linstor/templates/cluster.yaml index 584fd850..bde24726 100644 --- a/packages/system/linstor/templates/cluster.yaml +++ b/packages/system/linstor/templates/cluster.yaml @@ -14,6 +14,12 @@ spec: name: linstor-api-ca kind: Issuer properties: + {{- if .Values.linstor.autoDiskful.enabled }} + - name: DrbdOptions/auto-diskful + value: {{ .Values.linstor.autoDiskful.minutes | quote }} + - name: DrbdOptions/auto-diskful-allow-cleanup + value: {{ .Values.linstor.autoDiskful.allowCleanup | quote }} + {{- end }} - name: DrbdOptions/Net/connect-int value: "15" - name: DrbdOptions/Net/ping-int diff --git a/packages/system/linstor/values.yaml b/packages/system/linstor/values.yaml index 23179bce..1a94793b 100644 --- a/packages/system/linstor/values.yaml +++ b/packages/system/linstor/values.yaml @@ -2,3 +2,9 @@ piraeusServer: image: repository: ghcr.io/cozystack/cozystack/piraeus-server tag: latest@sha256:417532baa2801288147cd9ac9ae260751c1a7754f0b829725d09b72a770c111a + +linstor: + autoDiskful: + enabled: true + minutes: 30 + allowCleanup: true From 2a8a8a480f57a2cadc0c8524782124ad3fea91e0 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 028/889] [seaweedfs] Traffic locality for seaweedfs Signed-off-by: nbykov0 <166552198+nbykov0@users.noreply.github.com> --- 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 afe8283d..830c4faa 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 aede1b9217e9a346c025add2ac98276264dbbd6c 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 029/889] [seaweed] Update to 4.05 Signed-off-by: nbykov0 <166552198+nbykov0@users.noreply.github.com> --- .../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 bcd4b8976aa523e95f05ad5ffb791380ca160977 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 030/889] [seaweed] Use upstream images Signed-off-by: nbykov0 <166552198+nbykov0@users.noreply.github.com> --- 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 7996d6817841938ef8e06aed512a01134eb94968 Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Mon, 5 Jan 2026 10:38:15 +0300 Subject: [PATCH 031/889] [keycloak] Make kubernetes client public ## What this PR does By its nature, the kubernetes client provisioned in keycloak is public: anyone can read the client secret. Therefore the secret is not necessary and the client should be explicitly provisioned as a public client, not as a confidential client. ### Release note ```release-note [keycloak] Change the kubernetes client to be public, without a client secret. ``` Signed-off-by: Timofei Larkin --- packages/extra/info/templates/kubeconfig.yaml | 25 +++++++------ .../dashboard/templates/keycloakclient.yaml | 1 - .../templates/configure-kk.yaml | 37 ++++--------------- 3 files changed, 21 insertions(+), 42 deletions(-) diff --git a/packages/extra/info/templates/kubeconfig.yaml b/packages/extra/info/templates/kubeconfig.yaml index 1557eacd..d2331654 100644 --- a/packages/extra/info/templates/kubeconfig.yaml +++ b/packages/extra/info/templates/kubeconfig.yaml @@ -1,14 +1,18 @@ {{- $host := .Values._namespace.host | default (index .Values._cluster "root-host") }} -{{- $k8sClientSecret := lookup "v1" "Secret" "cozy-keycloak" "k8s-client" }} - -{{- if $k8sClientSecret }} -{{- $apiServerEndpoint := index .Values._cluster "api-server-endpoint" }} -{{- $managementKubeconfigEndpoint := default "" (index .Values._cluster "management-kubeconfig-endpoint") }} -{{- if and $managementKubeconfigEndpoint (ne $managementKubeconfigEndpoint "") }} -{{- $apiServerEndpoint = $managementKubeconfigEndpoint }} -{{- end }} -{{- $k8sClient := index $k8sClientSecret.data "client-secret-key" | b64dec }} -{{- $k8sCa := index .Values._cluster "kube-root-ca" }} +{{- $oidcEnabled := index .Values._cluster "oidc-enabled" }} +{{- if $oidcEnabled }} +{{- $apiServerEndpoint := index .Values._cluster "api-server-endpoint" }} +{{- $managementKubeconfigEndpoint := default "" (index .Values._cluster "management-kubeconfig-endpoint") }} +{{- if and $managementKubeconfigEndpoint (ne $managementKubeconfigEndpoint "") }} +{{- $apiServerEndpoint = $managementKubeconfigEndpoint }} +{{- end }} +{{- $k8sCa := index .Values._cluster "kube-root-ca" }} +{{- if .Capabilities.APIVersions.Has "helm.toolkit.fluxcd.io/v2" }} +{{- $tenantRoot := lookup "helm.toolkit.fluxcd.io/v2" "HelmRelease" "tenant-root" "tenant-root" }} +{{- if and $tenantRoot $tenantRoot.spec $tenantRoot.spec.values $tenantRoot.spec.values.host }} +{{- $host = $tenantRoot.spec.values.host }} +{{- end }} +{{- end }} --- apiVersion: v1 kind: Secret @@ -39,7 +43,6 @@ stringData: - get-token - --oidc-issuer-url=https://keycloak.{{ $host }}/realms/cozy - --oidc-client-id=kubernetes - - --oidc-client-secret={{ $k8sClient }} - --skip-open-browser command: kubectl {{- end }} diff --git a/packages/system/dashboard/templates/keycloakclient.yaml b/packages/system/dashboard/templates/keycloakclient.yaml index 80f7332e..e1caea71 100644 --- a/packages/system/dashboard/templates/keycloakclient.yaml +++ b/packages/system/dashboard/templates/keycloakclient.yaml @@ -1,7 +1,6 @@ {{- $host := index .Values._cluster "root-host" }} {{- $extraRedirectUris := splitList "," ((index .Values._cluster "extra-keycloak-redirect-uri-for-dashboard") | default "") }} -{{- $existingK8sSecret := lookup "v1" "Secret" .Release.Namespace "k8s-client" }} {{- $existingDashboardSecret := lookup "v1" "Secret" .Release.Namespace "dashboard-client" }} {{ $dashboardClient := "" }} diff --git a/packages/system/keycloak-configure/templates/configure-kk.yaml b/packages/system/keycloak-configure/templates/configure-kk.yaml index e399b374..cc15b161 100644 --- a/packages/system/keycloak-configure/templates/configure-kk.yaml +++ b/packages/system/keycloak-configure/templates/configure-kk.yaml @@ -2,18 +2,10 @@ {{- $extraRedirectUris := splitList "," ((index .Values._cluster "extra-keycloak-redirect-uri-for-dashboard") | default "") }} {{- $k8sCa := index .Values._cluster "kube-root-ca" }} -{{- $existingK8sSecret := lookup "v1" "Secret" .Release.Namespace "k8s-client" }} {{- $existingKubeappsSecret := lookup "v1" "Secret" .Release.Namespace "kubeapps-client" }} {{- $existingAuthConfig := lookup "v1" "Secret" "cozy-dashboard" "kubeapps-auth-config" }} {{- $brandingConfig := .Values._cluster.branding | default dict }} -{{ $k8sClient := "" }} -{{- if $existingK8sSecret }} - {{- $k8sClient = index $existingK8sSecret.data "client-secret-key" | b64dec }} -{{- else }} - {{- $k8sClient = randAlphaNum 32 }} -{{- end }} - {{ $branding := "" }} {{- if $brandingConfig }} {{- $branding = $brandingConfig.branding }} @@ -21,17 +13,6 @@ --- -apiVersion: v1 -kind: Secret -metadata: - name: k8s-client - namespace: {{ .Release.Namespace }} -type: Opaque -data: - client-secret-key: {{ $k8sClient | b64enc }} - ---- - apiVersion: v1.edp.epam.com/v1alpha1 kind: ClusterKeycloak metadata: @@ -90,23 +71,19 @@ kind: KeycloakClient metadata: name: keycloakclient spec: - serviceAccount: - enabled: true + advancedProtocolMappers: true + authorizationServicesEnabled: true + clientId: kubernetes + defaultClientScopes: + - groups + name: kubernetes + public: true realmRef: name: keycloakrealm-cozy kind: ClusterKeycloakRealm - secret: $k8s-client:client-secret-key - advancedProtocolMappers: true - authorizationServicesEnabled: true - name: kubernetes - clientId: kubernetes - directAccess: true - public: false webUrl: https://localhost:8000/oauth2/callback webOrigins: - /* - defaultClientScopes: - - groups redirectUris: - http://localhost:18000 - http://localhost:8000 From a2a07471427207d2204dd1b9323dd196a3664e2d Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 8 Jan 2026 12:13:24 +0100 Subject: [PATCH 032/889] fix(linstor): update skip-adjust-when-device-inaccessible patch Add physical device path existence check to prevent race condition where lsblk is called before kernel creates DRBD device node after drbdadm adjust. This fixes issue where resources ended up in Unknown state after satellite restart because: - drbdadm adjust completes successfully (brings devices up) - updateDiscGran() immediately tries lsblk - /dev/drbd* doesn't exist yet (kernel hasn't created node) - lsblk fails with "not a block device" exit code 32 - StorageException interrupts DeviceManager cycle - Device remains in incomplete state The patch now checks Files.exists(devicePath) before lsblk, allowing the check to be retried in next cycle when device node is available. Upstream: https://github.com/LINBIT/linstor-server/pull/471 Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- .../skip-adjust-when-device-inaccessible.diff | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) 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 index 09e7ccf9..18399c41 100644 --- 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 @@ -1,3 +1,28 @@ +diff --git a/satellite/src/main/java/com/linbit/linstor/core/devmgr/DeviceHandlerImpl.java b/satellite/src/main/java/com/linbit/linstor/core/devmgr/DeviceHandlerImpl.java +index abc123def..def456abc 100644 +--- a/satellite/src/main/java/com/linbit/linstor/core/devmgr/DeviceHandlerImpl.java ++++ b/satellite/src/main/java/com/linbit/linstor/core/devmgr/DeviceHandlerImpl.java +@@ -83,6 +83,8 @@ import java.util.TreeMap; + import java.util.TreeSet; + import java.util.concurrent.atomic.AtomicBoolean; + import java.util.function.Function; ++import java.nio.file.Files; ++import java.nio.file.Paths; + + @Singleton + public class DeviceHandlerImpl implements DeviceHandler +@@ -1646,7 +1648,10 @@ public class DeviceHandlerImpl implements DeviceHandler + private void updateDiscGran(VlmProviderObject vlmData) throws DatabaseException, StorageException + { + String devicePath = vlmData.getDevicePath(); +- if (devicePath != null && vlmData.exists()) ++ // Check if device path physically exists before calling lsblk ++ // This is important for DRBD devices which might be temporarily unavailable during adjust ++ // (drbdadm adjust brings devices down/up, and kernel might not have created the device node yet) ++ if (devicePath != null && vlmData.exists() && Files.exists(Paths.get(devicePath))) + { + if (vlmData.getDiscGran() == VlmProviderObject.UNINITIALIZED_SIZE) + { 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 From 5e15a75d890454d7fedc53722a2bf630196415ad Mon Sep 17 00:00:00 2001 From: Andrey Kolkov Date: Tue, 6 Jan 2026 10:57:08 +0400 Subject: [PATCH 033/889] [backups] add restore jobs controller Signed-off-by: Andrey Kolkov --- 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 407e2f2930a75e7ab32ca299d99d5fd8a230ce61 Mon Sep 17 00:00:00 2001 From: nbykov0 <166552198+nbykov0@users.noreply.github.com> Date: Thu, 8 Jan 2026 20:48:28 +0300 Subject: [PATCH 034/889] [multus] Remove memory limit Signed-off-by: nbykov0 <166552198+nbykov0@users.noreply.github.com> --- packages/system/multus/patches/customize-deployment.patch | 3 +-- packages/system/multus/templates/multus-daemonset-thick.yml | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/system/multus/patches/customize-deployment.patch b/packages/system/multus/patches/customize-deployment.patch index 7ea3aeeb..b62d57e7 100644 --- a/packages/system/multus/patches/customize-deployment.patch +++ b/packages/system/multus/patches/customize-deployment.patch @@ -34,7 +34,7 @@ labels: tier: node app: multus -@@ -159,10 +159,10 @@ spec: +@@ -159,10 +159,9 @@ spec: resources: requests: cpu: "100m" @@ -43,7 +43,6 @@ limits: cpu: "100m" - memory: "50Mi" -+ memory: "900Mi" securityContext: privileged: true terminationMessagePolicy: FallbackToLogsOnError diff --git a/packages/system/multus/templates/multus-daemonset-thick.yml b/packages/system/multus/templates/multus-daemonset-thick.yml index ba7eedfb..2f00de85 100644 --- a/packages/system/multus/templates/multus-daemonset-thick.yml +++ b/packages/system/multus/templates/multus-daemonset-thick.yml @@ -162,7 +162,6 @@ spec: memory: "100Mi" limits: cpu: "100m" - memory: "900Mi" securityContext: privileged: true terminationMessagePolicy: FallbackToLogsOnError From 1c7c3b221fbddcfbbcc427555d134dfb18f9e229 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Wed, 7 Jan 2026 21:05:51 +0100 Subject: [PATCH 035/889] feat(linstor): add linstor-csi image build with RWX validation Add custom linstor-csi image build to packages/system/linstor: - Add Dockerfile based on upstream linstor-csi - Import patch from upstream PR #403 for RWX block volume validation (prevents misuse of allow-two-primaries in KubeVirt live migration) - Update Makefile to build both piraeus-server and linstor-csi images - Configure LinstorCluster CR to use custom linstor-csi image in CSI controller and node pods The RWX validation patch ensures that RWX block volumes with allow-two-primaries are only used by pods belonging to the same KubeVirt VM during live migration. Upstream PR: https://github.com/piraeusdatastore/linstor-csi/pull/403 Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- packages/system/linstor/Makefile | 20 +- .../linstor/images/linstor-csi/Dockerfile | 36 + .../patches/001-rwx-validation.diff | 652 ++++++++++++++++++ .../system/linstor/templates/cluster.yaml | 18 + packages/system/linstor/values.yaml | 5 + 5 files changed, 730 insertions(+), 1 deletion(-) create mode 100644 packages/system/linstor/images/linstor-csi/Dockerfile create mode 100644 packages/system/linstor/images/linstor-csi/patches/001-rwx-validation.diff diff --git a/packages/system/linstor/Makefile b/packages/system/linstor/Makefile index da895085..b5d7c227 100644 --- a/packages/system/linstor/Makefile +++ b/packages/system/linstor/Makefile @@ -5,8 +5,11 @@ include ../../../scripts/common-envs.mk include ../../../scripts/package.mk LINSTOR_VERSION ?= 1.32.3 +LINSTOR_CSI_VERSION ?= v1.10.5 -image: +image: image-piraeus-server image-linstor-csi + +image-piraeus-server: docker buildx build images/piraeus-server \ --build-arg LINSTOR_VERSION=$(LINSTOR_VERSION) \ --build-arg K8S_AWAIT_ELECTION_VERSION=v0.4.2 \ @@ -21,3 +24,18 @@ image: 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 + +image-linstor-csi: + docker buildx build images/linstor-csi \ + --build-arg VERSION=$(LINSTOR_CSI_VERSION) \ + --tag $(REGISTRY)/linstor-csi:$(call settag,$(LINSTOR_CSI_VERSION)) \ + --tag $(REGISTRY)/linstor-csi:$(call settag,$(LINSTOR_CSI_VERSION)-$(TAG)) \ + --cache-from type=registry,ref=$(REGISTRY)/linstor-csi:latest \ + --cache-to type=inline \ + --metadata-file images/linstor-csi.json \ + $(BUILDX_ARGS) + REPOSITORY="$(REGISTRY)/linstor-csi" \ + yq -i '.linstorCSI.image.repository = strenv(REPOSITORY)' values.yaml + TAG="$(call settag,$(LINSTOR_CSI_VERSION))@$$(yq e '."containerimage.digest"' images/linstor-csi.json -o json -r)" \ + yq -i '.linstorCSI.image.tag = strenv(TAG)' values.yaml + rm -f images/linstor-csi.json diff --git a/packages/system/linstor/images/linstor-csi/Dockerfile b/packages/system/linstor/images/linstor-csi/Dockerfile new file mode 100644 index 00000000..6cfd44aa --- /dev/null +++ b/packages/system/linstor/images/linstor-csi/Dockerfile @@ -0,0 +1,36 @@ +FROM golang:1.25 AS builder + +ARG VERSION=v1.10.5 +ARG LINSTOR_WAIT_UNTIL_VERSION=v0.3.1 +ARG TARGETARCH +ARG TARGETOS + +WORKDIR /src + +RUN curl -sSL https://github.com/piraeusdatastore/linstor-csi/archive/refs/tags/${VERSION}.tar.gz | tar -xzvf- --strip=1 + +COPY patches /patches +RUN git apply /patches/*.diff + +RUN go mod download + +RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 \ + go build \ + -a \ + -ldflags "-X github.com/piraeusdatastore/linstor-csi/pkg/driver.Version=$VERSION -extldflags -static" \ + -o /linstor-csi \ + ./cmd/linstor-csi/linstor-csi.go + +RUN curl -fsSL https://github.com/LINBIT/linstor-wait-until/releases/download/$LINSTOR_WAIT_UNTIL_VERSION/linstor-wait-until-$LINSTOR_WAIT_UNTIL_VERSION-$TARGETOS-$TARGETARCH.tar.gz | tar xvzC / + +FROM debian:trixie-slim + +RUN apt-get update && apt-get install -y --no-install-recommends \ + xfsprogs e2fsprogs nfs-common \ + && apt-get clean && rm -rf /var/lib/apt/lists/* \ + && ln -sf /proc/mounts /etc/mtab + +COPY --from=builder /linstor-csi / +COPY --from=builder /linstor-wait-until /linstor-wait-until + +ENTRYPOINT ["/linstor-csi"] diff --git a/packages/system/linstor/images/linstor-csi/patches/001-rwx-validation.diff b/packages/system/linstor/images/linstor-csi/patches/001-rwx-validation.diff new file mode 100644 index 00000000..f6f0d83d --- /dev/null +++ b/packages/system/linstor/images/linstor-csi/patches/001-rwx-validation.diff @@ -0,0 +1,652 @@ +diff --git a/pkg/driver/driver.go b/pkg/driver/driver.go +index bea69a8..848bd5b 100644 +--- a/pkg/driver/driver.go ++++ b/pkg/driver/driver.go +@@ -401,6 +401,16 @@ func (d Driver) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolum + } + } + ++ // Validate RWX block volumes on node side using local filesystem check ++ // This provides protection for the edge case where two pods from different VMs land on the same node ++ if req.GetVolumeCapability().GetBlock() != nil && ++ req.GetVolumeCapability().GetAccessMode().GetMode() == csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER { ++ if err := d.validateRWXBlockOnNode(ctx, req.GetVolumeId(), req.GetTargetPath()); err != nil { ++ return nil, status.Errorf(codes.FailedPrecondition, ++ "NodePublishVolume failed for %s: %v", req.GetVolumeId(), err) ++ } ++ } ++ + if block := req.GetVolumeCapability().GetBlock(); block != nil { + volCtx.MountOptions = []string{"bind"} + } +@@ -707,6 +717,255 @@ func (d Driver) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest) + return &csi.DeleteVolumeResponse{}, nil + } + ++// KubeVirtVMLabel is the label that KubeVirt adds to pods to identify the VM they belong to. ++const KubeVirtVMLabel = "vm.kubevirt.io/name" ++ ++// KubeVirtHotplugDiskLabel is the label that KubeVirt adds to hotplug disk pods. ++const KubeVirtHotplugDiskLabel = "kubevirt.io" ++ ++// podGVR is the GroupVersionResource for pods. ++var podGVR = schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"} ++ ++// pvGVR is the GroupVersionResource for persistent volumes. ++var pvGVR = schema.GroupVersionResource{Group: "", Version: "v1", Resource: "persistentvolumes"} ++ ++// getVMNameFromPod extracts the VM name from a pod, handling both regular virt-launcher pods ++// and hotplug disk pods (which reference the virt-launcher pod via ownerReferences). ++func (d Driver) getVMNameFromPod(ctx context.Context, pod *unstructured.Unstructured) (string, error) { ++ labels := pod.GetLabels() ++ if labels == nil { ++ return "", nil ++ } ++ ++ // Direct case: pod has vm.kubevirt.io/name label (virt-launcher pod) ++ if vmName, ok := labels[KubeVirtVMLabel]; ok && vmName != "" { ++ return vmName, nil ++ } ++ ++ // Hotplug disk case: pod has kubevirt.io: hotplug-disk label ++ // Follow ownerReferences to find the virt-launcher pod ++ if hotplugValue, ok := labels[KubeVirtHotplugDiskLabel]; ok && hotplugValue == "hotplug-disk" { ++ ownerRefs := pod.GetOwnerReferences() ++ for _, owner := range ownerRefs { ++ if owner.Kind != "Pod" || owner.Controller == nil || !*owner.Controller { ++ continue ++ } ++ ++ // Get the owner pod (virt-launcher) ++ ownerPod, err := d.kubeClient.Resource(podGVR).Namespace(pod.GetNamespace()).Get(ctx, owner.Name, metav1.GetOptions{}) ++ if err != nil { ++ return "", fmt.Errorf("failed to get owner pod %s: %w", owner.Name, err) ++ } ++ ++ // Extract VM name from owner pod ++ ownerLabels := ownerPod.GetLabels() ++ if ownerLabels != nil { ++ if vmName, ok := ownerLabels[KubeVirtVMLabel]; ok && vmName != "" { ++ d.log.WithFields(logrus.Fields{ ++ "hotplugPod": pod.GetName(), ++ "virtLauncher": owner.Name, ++ "vmName": vmName, ++ }).Debug("resolved VM name from hotplug disk pod via owner reference") ++ ++ return vmName, nil ++ } ++ } ++ ++ return "", fmt.Errorf("owner pod %s does not have %s label", owner.Name, KubeVirtVMLabel) ++ } ++ ++ return "", fmt.Errorf("hotplug disk pod %s has no controller owner reference", pod.GetName()) ++ } ++ ++ return "", nil ++} ++ ++// validateRWXBlockAttachment checks that RWX block volumes are only used by pods belonging to the same VM. ++// This prevents misuse of allow-two-primaries while still permitting live migration. ++// Returns the VM name if validation passes, or an error if: ++// - Multiple pods from different VMs are trying to use the same volume ++// - A pod without the KubeVirt VM label is trying to use a volume already attached elsewhere (strict mode) ++// Returns empty string for VM name when no pods are using the volume or validation is skipped. ++func (d Driver) validateRWXBlockAttachment(ctx context.Context, volumeID string) (string, error) { ++ d.log.WithField("volumeID", volumeID).Info("validateRWXBlockAttachment called") ++ ++ if d.kubeClient == nil { ++ // Not running in Kubernetes, skip validation ++ d.log.Warn("validateRWXBlockAttachment: kubeClient is nil, skipping validation") ++ return "", nil ++ } ++ ++ // Get PV to find PVC reference (volumeID == PV name in CSI) ++ pv, err := d.kubeClient.Resource(pvGVR).Get(ctx, volumeID, metav1.GetOptions{}) ++ if err != nil { ++ d.log.WithError(err).Warn("cannot validate RWX attachment: failed to get PV") ++ return "", nil ++ } ++ ++ // Extract claimRef from PV ++ claimRef, found, _ := unstructured.NestedMap(pv.Object, "spec", "claimRef") ++ if !found { ++ d.log.Warn("cannot validate RWX attachment: PV has no claimRef") ++ return "", nil ++ } ++ ++ pvcName, _, _ := unstructured.NestedString(claimRef, "name") ++ pvcNamespace, _, _ := unstructured.NestedString(claimRef, "namespace") ++ ++ if pvcNamespace == "" || pvcName == "" { ++ d.log.Warn("cannot validate RWX attachment: PVC name or namespace is empty in claimRef") ++ return "", nil ++ } ++ ++ // List all pods in the namespace ++ podList, err := d.kubeClient.Resource(podGVR).Namespace(pvcNamespace).List(ctx, metav1.ListOptions{}) ++ if err != nil { ++ return "", fmt.Errorf("failed to list pods in namespace %s: %w", pvcNamespace, err) ++ } ++ ++ // Filter pods that use this PVC and are in a running/pending state ++ type podInfo struct { ++ name string ++ vmName string ++ } ++ ++ var podsUsingPVC []podInfo ++ ++ for _, item := range podList.Items { ++ // Get pod phase from status ++ phase, _, _ := unstructured.NestedString(item.Object, "status", "phase") ++ if phase == "Succeeded" || phase == "Failed" { ++ continue ++ } ++ ++ // Check if pod uses the PVC ++ volumes, found, _ := unstructured.NestedSlice(item.Object, "spec", "volumes") ++ if !found { ++ continue ++ } ++ ++ for _, vol := range volumes { ++ volMap, ok := vol.(map[string]interface{}) ++ if !ok { ++ continue ++ } ++ ++ pvc, found, _ := unstructured.NestedMap(volMap, "persistentVolumeClaim") ++ if !found { ++ continue ++ } ++ ++ claimName, _, _ := unstructured.NestedString(pvc, "claimName") ++ if claimName == pvcName { ++ // Extract VM name, handling both regular and hotplug disk pods ++ vmName, err := d.getVMNameFromPod(ctx, &item) ++ if err != nil { ++ d.log.WithError(err).WithField("pod", item.GetName()).Warn("failed to get VM name from pod") ++ // Continue with empty vmName - will be caught by strict mode check ++ vmName = "" ++ } ++ ++ podsUsingPVC = append(podsUsingPVC, podInfo{ ++ name: item.GetName(), ++ vmName: vmName, ++ }) ++ ++ break ++ } ++ } ++ } ++ ++ // If 0 or 1 pod uses the PVC, no conflict possible ++ if len(podsUsingPVC) <= 1 { ++ // Return VM name if there's exactly one pod ++ if len(podsUsingPVC) == 1 { ++ d.log.WithFields(logrus.Fields{ ++ "volumeID": volumeID, ++ "vmName": podsUsingPVC[0].vmName, ++ "podCount": 1, ++ "pvcNamespace": pvcNamespace, ++ "pvcName": pvcName, ++ }).Info("validateRWXBlockAttachment: single pod found, returning VM name") ++ ++ return podsUsingPVC[0].vmName, nil ++ } ++ ++ d.log.WithFields(logrus.Fields{ ++ "volumeID": volumeID, ++ "pvcNamespace": pvcNamespace, ++ "pvcName": pvcName, ++ }).Info("validateRWXBlockAttachment: no pods found using PVC") ++ ++ return "", nil ++ } ++ ++ // Check that all pods belong to the same VM ++ var vmName string ++ for _, pod := range podsUsingPVC { ++ if pod.vmName == "" { ++ // Strict mode: if any pod doesn't have the KubeVirt label and there are multiple pods, ++ // deny the attachment ++ return "", fmt.Errorf("RWX block volume %s/%s is used by multiple pods but pod %s does not have the %s label; "+ ++ "RWX block volumes with allow-two-primaries are only supported for KubeVirt live migration", ++ pvcNamespace, pvcName, pod.name, KubeVirtVMLabel) ++ } ++ ++ if vmName == "" { ++ vmName = pod.vmName ++ } else if vmName != pod.vmName { ++ // Different VMs are trying to use the same volume ++ return "", fmt.Errorf("RWX block volume %s/%s is being used by pods from different VMs (%s and %s); "+ ++ "this is not supported - RWX block volumes with allow-two-primaries are only for live migration of a single VM", ++ pvcNamespace, pvcName, vmName, pod.vmName) ++ } ++ } ++ ++ d.log.WithFields(logrus.Fields{ ++ "pvcNamespace": pvcNamespace, ++ "pvcName": pvcName, ++ "vmName": vmName, ++ "podCount": len(podsUsingPVC), ++ }).Debug("RWX block attachment validated: all pods belong to the same VM (likely live migration)") ++ ++ return vmName, nil ++} ++ ++// validateRWXBlockOnNode performs node-side validation of RWX block volumes using local filesystem check. ++// This provides protection against the edge case where two pods from different VMs land on the same node. ++// Since ControllerPublishVolume is called only once per (volumeID, nodeID) pair, not per pod, ++// we need to check if the volume is already mounted for another pod on this node. ++func (d Driver) validateRWXBlockOnNode(ctx context.Context, volumeID, targetPath string) error { ++ // Extract base directory for this volume (contains subdirectories per pod UID) ++ baseDir := filepath.Dir(targetPath) ++ ++ // List existing mounts for this volume ++ entries, err := os.ReadDir(baseDir) ++ if err != nil { ++ if os.IsNotExist(err) { ++ // Directory doesn't exist yet - first mount ++ return nil ++ } ++ ++ d.log.WithError(err).Warn("cannot check existing mounts for RWX validation") ++ ++ return nil ++ } ++ ++ // If there are already other mounts, block the second one ++ if len(entries) > 0 { ++ d.log.WithFields(logrus.Fields{ ++ "volumeID": volumeID, ++ "existingMounts": len(entries), ++ "baseDir": baseDir, ++ }).Warn("blocking RWX block volume mount: already mounted for another pod on this node") ++ ++ return fmt.Errorf("RWX block volume is already mounted for another pod on this node - " + ++ "multiple pods on the same node sharing a block device is not supported (only for live migration across nodes)") ++ } ++ ++ return nil ++} ++ + // ControllerPublishVolume https://github.com/container-storage-interface/spec/blob/v1.9.0/spec.md#controllerpublishvolume + func (d Driver) ControllerPublishVolume(ctx context.Context, req *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error) { + if req.GetVolumeId() == "" { +@@ -751,6 +1010,15 @@ func (d Driver) ControllerPublishVolume(ctx context.Context, req *csi.Controller + // ReadWriteMany block volume + rwxBlock := req.VolumeCapability.AccessMode.GetMode() == csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER && req.VolumeCapability.GetBlock() != nil + ++ // Validate RWX block attachment to prevent misuse of allow-two-primaries ++ if rwxBlock { ++ _, err := d.validateRWXBlockAttachment(ctx, req.GetVolumeId()) ++ if err != nil { ++ return nil, status.Errorf(codes.FailedPrecondition, ++ "ControllerPublishVolume failed for %s: %v", req.GetVolumeId(), err) ++ } ++ } ++ + devPath, err := d.Assignments.Attach(ctx, req.GetVolumeId(), req.GetNodeId(), rwxBlock) + if err != nil { + return nil, status.Errorf(codes.Internal, +diff --git a/pkg/driver/rwx_validation_test.go b/pkg/driver/rwx_validation_test.go +new file mode 100644 +index 0000000..92c1046 +--- /dev/null ++++ b/pkg/driver/rwx_validation_test.go +@@ -0,0 +1,353 @@ ++/* ++CSI Driver for Linstor ++Copyright © 2018 LINBIT USA, LLC ++ ++This program is free software; you can redistribute it and/or modify ++it under the terms of the GNU General Public License as published by ++the Free Software Foundation; either version 2 of the License, or ++(at your option) any later version. ++ ++This program is distributed in the hope that it will be useful, ++but WITHOUT ANY WARRANTY; without even the implied warranty of ++MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++GNU General Public License for more details. ++ ++You should have received a copy of the GNU General Public License ++along with this program; if not, see . ++*/ ++ ++package driver ++ ++import ( ++ "context" ++ "testing" ++ ++ "github.com/sirupsen/logrus" ++ "github.com/stretchr/testify/assert" ++ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ++ "k8s.io/apimachinery/pkg/runtime" ++ "k8s.io/apimachinery/pkg/runtime/schema" ++ dynamicfake "k8s.io/client-go/dynamic/fake" ++) ++ ++func TestValidateRWXBlockAttachment(t *testing.T) { ++ testCases := []struct { ++ name string ++ pods []*unstructured.Unstructured ++ pvcName string ++ namespace string ++ expectError bool ++ errorMsg string ++ }{ ++ { ++ name: "no pods using PVC", ++ pods: []*unstructured.Unstructured{}, ++ pvcName: "test-pvc", ++ namespace: "default", ++ expectError: false, ++ }, ++ { ++ name: "single pod using PVC", ++ pods: []*unstructured.Unstructured{ ++ createUnstructuredPod("pod1", "default", "test-pvc", map[string]string{KubeVirtVMLabel: "vm1"}, "Running"), ++ }, ++ pvcName: "test-pvc", ++ namespace: "default", ++ expectError: false, ++ }, ++ { ++ name: "two pods same VM (live migration)", ++ pods: []*unstructured.Unstructured{ ++ createUnstructuredPod("virt-launcher-vm1-abc", "default", "test-pvc", map[string]string{KubeVirtVMLabel: "vm1"}, "Running"), ++ createUnstructuredPod("virt-launcher-vm1-xyz", "default", "test-pvc", map[string]string{KubeVirtVMLabel: "vm1"}, "Running"), ++ }, ++ pvcName: "test-pvc", ++ namespace: "default", ++ expectError: false, ++ }, ++ { ++ name: "two pods different VMs (should fail)", ++ pods: []*unstructured.Unstructured{ ++ createUnstructuredPod("virt-launcher-vm1-abc", "default", "test-pvc", map[string]string{KubeVirtVMLabel: "vm1"}, "Running"), ++ createUnstructuredPod("virt-launcher-vm2-xyz", "default", "test-pvc", map[string]string{KubeVirtVMLabel: "vm2"}, "Running"), ++ }, ++ pvcName: "test-pvc", ++ namespace: "default", ++ expectError: true, ++ errorMsg: "different VMs", ++ }, ++ { ++ name: "pod without KubeVirt label when multiple pods exist (strict mode)", ++ pods: []*unstructured.Unstructured{ ++ createUnstructuredPod("pod1", "default", "test-pvc", map[string]string{KubeVirtVMLabel: "vm1"}, "Running"), ++ createUnstructuredPod("pod2", "default", "test-pvc", map[string]string{}, "Running"), ++ }, ++ pvcName: "test-pvc", ++ namespace: "default", ++ expectError: true, ++ errorMsg: "does not have the vm.kubevirt.io/name label", ++ }, ++ { ++ name: "completed pods should be ignored", ++ pods: []*unstructured.Unstructured{ ++ createUnstructuredPod("pod1", "default", "test-pvc", map[string]string{KubeVirtVMLabel: "vm1"}, "Running"), ++ createUnstructuredPod("pod2", "default", "test-pvc", map[string]string{KubeVirtVMLabel: "vm2"}, "Succeeded"), ++ }, ++ pvcName: "test-pvc", ++ namespace: "default", ++ expectError: false, ++ }, ++ { ++ name: "failed pods should be ignored", ++ pods: []*unstructured.Unstructured{ ++ createUnstructuredPod("pod1", "default", "test-pvc", map[string]string{KubeVirtVMLabel: "vm1"}, "Running"), ++ createUnstructuredPod("pod2", "default", "test-pvc", map[string]string{KubeVirtVMLabel: "vm2"}, "Failed"), ++ }, ++ pvcName: "test-pvc", ++ namespace: "default", ++ expectError: false, ++ }, ++ { ++ name: "pods in different namespace should not conflict", ++ pods: []*unstructured.Unstructured{ ++ createUnstructuredPod("pod1", "default", "test-pvc", map[string]string{KubeVirtVMLabel: "vm1"}, "Running"), ++ createUnstructuredPod("pod2", "other", "test-pvc", map[string]string{KubeVirtVMLabel: "vm2"}, "Running"), ++ }, ++ pvcName: "test-pvc", ++ namespace: "default", ++ expectError: false, ++ }, ++ { ++ name: "pods using different PVCs should not conflict", ++ pods: []*unstructured.Unstructured{ ++ createUnstructuredPod("pod1", "default", "test-pvc", map[string]string{KubeVirtVMLabel: "vm1"}, "Running"), ++ createUnstructuredPod("pod2", "default", "other-pvc", map[string]string{KubeVirtVMLabel: "vm2"}, "Running"), ++ }, ++ pvcName: "test-pvc", ++ namespace: "default", ++ expectError: false, ++ }, ++ { ++ name: "three pods from same VM (multi-node live migration scenario)", ++ pods: []*unstructured.Unstructured{ ++ createUnstructuredPod("virt-launcher-vm1-a", "default", "test-pvc", map[string]string{KubeVirtVMLabel: "vm1"}, "Running"), ++ createUnstructuredPod("virt-launcher-vm1-b", "default", "test-pvc", map[string]string{KubeVirtVMLabel: "vm1"}, "Running"), ++ createUnstructuredPod("virt-launcher-vm1-c", "default", "test-pvc", map[string]string{KubeVirtVMLabel: "vm1"}, "Pending"), ++ }, ++ pvcName: "test-pvc", ++ namespace: "default", ++ expectError: false, ++ }, ++ { ++ name: "hotplug disk pod with virt-launcher (should succeed)", ++ pods: []*unstructured.Unstructured{ ++ createUnstructuredPod("virt-launcher-vm1-abc", "default", "test-pvc", map[string]string{KubeVirtVMLabel: "vm1"}, "Running"), ++ createHotplugDiskPod("hp-volume-xyz", "default", "test-pvc", "virt-launcher-vm1-abc", "Running"), ++ }, ++ pvcName: "test-pvc", ++ namespace: "default", ++ expectError: false, ++ }, ++ { ++ name: "hotplug disks from different VMs (should fail)", ++ pods: []*unstructured.Unstructured{ ++ createUnstructuredPod("virt-launcher-vm1-abc", "default", "test-pvc", map[string]string{KubeVirtVMLabel: "vm1"}, "Running"), ++ createHotplugDiskPod("hp-volume-vm1", "default", "test-pvc", "virt-launcher-vm1-abc", "Running"), ++ createUnstructuredPod("virt-launcher-vm2-xyz", "default", "test-pvc", map[string]string{KubeVirtVMLabel: "vm2"}, "Running"), ++ createHotplugDiskPod("hp-volume-vm2", "default", "test-pvc", "virt-launcher-vm2-xyz", "Running"), ++ }, ++ pvcName: "test-pvc", ++ namespace: "default", ++ expectError: true, ++ errorMsg: "different VMs", ++ }, ++ } ++ ++ for _, tc := range testCases { ++ t.Run(tc.name, func(t *testing.T) { ++ // Create fake dynamic client with test pods and PV ++ scheme := runtime.NewScheme() ++ ++ // Create PV object that references the PVC ++ pv := createUnstructuredPV("test-volume-id", tc.namespace, tc.pvcName) ++ ++ objects := make([]runtime.Object, 0, len(tc.pods)+1) ++ objects = append(objects, pv) ++ ++ for _, pod := range tc.pods { ++ objects = append(objects, pod) ++ } ++ ++ gvrToListKind := map[schema.GroupVersionResource]string{ ++ podGVR: "PodList", ++ pvGVR: "PersistentVolumeList", ++ } ++ client := dynamicfake.NewSimpleDynamicClientWithCustomListKinds(scheme, gvrToListKind, objects...) ++ ++ // Create driver with fake client ++ logger := logrus.NewEntry(logrus.New()) ++ logger.Logger.SetLevel(logrus.DebugLevel) ++ ++ driver := &Driver{ ++ kubeClient: client, ++ log: logger, ++ } ++ ++ // Run validation ++ vmName, err := driver.validateRWXBlockAttachment(context.Background(), "test-volume-id") ++ ++ if tc.expectError { ++ assert.Error(t, err) ++ ++ if tc.errorMsg != "" { ++ assert.Contains(t, err.Error(), tc.errorMsg) ++ } ++ } else { ++ assert.NoError(t, err) ++ // VM name is returned when there are pods using the volume ++ if len(tc.pods) > 0 { ++ assert.NotEmpty(t, vmName) ++ } ++ } ++ }) ++ } ++} ++ ++func TestValidateRWXBlockAttachmentNoKubeClient(t *testing.T) { ++ // When not running in Kubernetes (no client), validation should be skipped ++ logger := logrus.NewEntry(logrus.New()) ++ driver := &Driver{ ++ kubeClient: nil, ++ log: logger, ++ } ++ ++ vmName, err := driver.validateRWXBlockAttachment(context.Background(), "test-volume-id") ++ assert.NoError(t, err) ++ assert.Empty(t, vmName) ++} ++ ++func TestValidateRWXBlockAttachmentPVNotFound(t *testing.T) { ++ // When PV is not found, validation should be skipped with warning ++ scheme := runtime.NewScheme() ++ ++ gvrToListKind := map[schema.GroupVersionResource]string{ ++ podGVR: "PodList", ++ pvGVR: "PersistentVolumeList", ++ } ++ client := dynamicfake.NewSimpleDynamicClientWithCustomListKinds(scheme, gvrToListKind) ++ ++ logger := logrus.NewEntry(logrus.New()) ++ logger.Logger.SetLevel(logrus.DebugLevel) ++ ++ driver := &Driver{ ++ kubeClient: client, ++ log: logger, ++ } ++ ++ vmName, err := driver.validateRWXBlockAttachment(context.Background(), "non-existent-pv") ++ assert.NoError(t, err) ++ assert.Empty(t, vmName) ++} ++ ++// createUnstructuredPod creates an unstructured pod object for testing. ++func createUnstructuredPod(name, namespace, pvcName string, labels map[string]string, phase string) *unstructured.Unstructured { ++ pod := &unstructured.Unstructured{ ++ Object: map[string]interface{}{ ++ "apiVersion": "v1", ++ "kind": "Pod", ++ "metadata": map[string]interface{}{ ++ "name": name, ++ "namespace": namespace, ++ "labels": toStringInterfaceMap(labels), ++ }, ++ "spec": map[string]interface{}{ ++ "volumes": []interface{}{ ++ map[string]interface{}{ ++ "name": "data", ++ "persistentVolumeClaim": map[string]interface{}{ ++ "claimName": pvcName, ++ }, ++ }, ++ }, ++ }, ++ "status": map[string]interface{}{ ++ "phase": phase, ++ }, ++ }, ++ } ++ ++ return pod ++} ++ ++// createUnstructuredPV creates an unstructured PersistentVolume object for testing. ++func createUnstructuredPV(name, pvcNamespace, pvcName string) *unstructured.Unstructured { ++ pv := &unstructured.Unstructured{ ++ Object: map[string]interface{}{ ++ "apiVersion": "v1", ++ "kind": "PersistentVolume", ++ "metadata": map[string]interface{}{ ++ "name": name, ++ }, ++ "spec": map[string]interface{}{ ++ "claimRef": map[string]interface{}{ ++ "name": pvcName, ++ "namespace": pvcNamespace, ++ }, ++ }, ++ }, ++ } ++ ++ return pv ++} ++ ++// toStringInterfaceMap converts map[string]string to map[string]interface{}. ++func toStringInterfaceMap(m map[string]string) map[string]interface{} { ++ result := make(map[string]interface{}) ++ ++ for k, v := range m { ++ result[k] = v ++ } ++ ++ return result ++} ++ ++// createHotplugDiskPod creates a hotplug disk pod that references a virt-launcher pod via ownerReferences. ++func createHotplugDiskPod(name, namespace, pvcName, ownerPodName, phase string) *unstructured.Unstructured { ++ pod := &unstructured.Unstructured{ ++ Object: map[string]interface{}{ ++ "apiVersion": "v1", ++ "kind": "Pod", ++ "metadata": map[string]interface{}{ ++ "name": name, ++ "namespace": namespace, ++ "labels": map[string]interface{}{ ++ "kubevirt.io": "hotplug-disk", ++ }, ++ "ownerReferences": []interface{}{ ++ map[string]interface{}{ ++ "apiVersion": "v1", ++ "kind": "Pod", ++ "name": ownerPodName, ++ "controller": true, ++ "blockOwnerDeletion": true, ++ }, ++ }, ++ }, ++ "spec": map[string]interface{}{ ++ "volumes": []interface{}{ ++ map[string]interface{}{ ++ "name": "data", ++ "persistentVolumeClaim": map[string]interface{}{ ++ "claimName": pvcName, ++ }, ++ }, ++ }, ++ }, ++ "status": map[string]interface{}{ ++ "phase": phase, ++ }, ++ }, ++ } ++ ++ return pod ++} diff --git a/packages/system/linstor/templates/cluster.yaml b/packages/system/linstor/templates/cluster.yaml index bde24726..ba611e17 100644 --- a/packages/system/linstor/templates/cluster.yaml +++ b/packages/system/linstor/templates/cluster.yaml @@ -60,6 +60,24 @@ spec: configMap: name: linstor-plunger defaultMode: 0755 + csiController: + podTemplate: + spec: + initContainers: + - name: linstor-wait-api-online + image: {{ .Values.linstorCSI.image.repository }}:{{ .Values.linstorCSI.image.tag }} + containers: + - name: linstor-csi + image: {{ .Values.linstorCSI.image.repository }}:{{ .Values.linstorCSI.image.tag }} + csiNode: + podTemplate: + spec: + initContainers: + - name: linstor-wait-node-online + image: {{ .Values.linstorCSI.image.repository }}:{{ .Values.linstorCSI.image.tag }} + containers: + - name: linstor-csi + image: {{ .Values.linstorCSI.image.repository }}:{{ .Values.linstorCSI.image.tag }} patches: - target: kind: Deployment diff --git a/packages/system/linstor/values.yaml b/packages/system/linstor/values.yaml index 1a94793b..470cf2e5 100644 --- a/packages/system/linstor/values.yaml +++ b/packages/system/linstor/values.yaml @@ -8,3 +8,8 @@ linstor: enabled: true minutes: 30 allowCleanup: true + +linstorCSI: + image: + repository: ghcr.io/cozystack/cozystack/linstor-csi + tag: latest@sha256:e8329b3e07c47ec73a7d9644535639fd80dbe5c27a7e03181b999ebe072a3a2e From 0b45fbbd63aea45538299419721ba000004009e5 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Fri, 9 Jan 2026 15:24:57 +0100 Subject: [PATCH 036/889] [platform] fix migration for removing fluxcd-operator Signed-off-by: Andrei Kvapil --- scripts/migrations/21 | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scripts/migrations/21 b/scripts/migrations/21 index 7a367668..abefdc77 100755 --- a/scripts/migrations/21 +++ b/scripts/migrations/21 @@ -3,7 +3,16 @@ set -euo pipefail +for crd in $(kubectl get crd -o name | grep 'fluxcd.io$'); do + kubectl annotate $crd fluxcd.controlplane.io/prune=disabled +done + kubectl delete hr -n cozy-fluxcd fluxcd --ignore-not-found +kubectl delete hr -n cozy-fluxcd fluxcd-operator --ignore-not-found + +for crd in $(kubectl get crd -o name | grep 'fluxcd.io$'); do + kubectl label $crd fluxcd.controlplane.io/name- fluxcd.controlplane.io/namespace- +done # Stamp version kubectl create configmap -n cozy-system cozystack-version \ From 6faa4d6e4d5cd69802ec6338d3b38f327c604c38 Mon Sep 17 00:00:00 2001 From: nbykov0 <166552198+nbykov0@users.noreply.github.com> Date: Fri, 9 Jan 2026 19:48:23 +0300 Subject: [PATCH 037/889] [paas-full] Add multus dependency as other CNIs Signed-off-by: nbykov0 <166552198+nbykov0@users.noreply.github.com> --- packages/core/platform/bundles/paas-full.yaml | 89 ++++++++++--------- 1 file changed, 45 insertions(+), 44 deletions(-) diff --git a/packages/core/platform/bundles/paas-full.yaml b/packages/core/platform/bundles/paas-full.yaml index 6959fed7..d25d885c 100644 --- a/packages/core/platform/bundles/paas-full.yaml +++ b/packages/core/platform/bundles/paas-full.yaml @@ -69,25 +69,25 @@ releases: releaseName: cozystack chart: cozy-cozy-proxy namespace: cozy-system - dependsOn: [cilium,kubeovn] + dependsOn: [cilium,kubeovn,multus] - name: cert-manager-crds releaseName: cert-manager-crds chart: cozy-cert-manager-crds namespace: cozy-cert-manager - dependsOn: [cilium, kubeovn] + dependsOn: [] - name: cozystack-api releaseName: cozystack-api chart: cozy-cozystack-api namespace: cozy-system - dependsOn: [cilium,kubeovn,cozystack-controller] + dependsOn: [cilium,kubeovn,multus,cozystack-controller] - name: cozystack-controller releaseName: cozystack-controller chart: cozy-cozystack-controller namespace: cozy-system - dependsOn: [cilium,kubeovn] + dependsOn: [cilium,kubeovn,multus] {{- if eq (index $cozyConfig.data "telemetry-enabled") "false" }} values: cozystackController: @@ -98,19 +98,19 @@ releases: releaseName: backup-controller chart: cozy-backup-controller namespace: cozy-backup-controller - dependsOn: [cilium,kubeovn] + dependsOn: [cilium,kubeovn,multus] - name: lineage-controller-webhook releaseName: lineage-controller-webhook chart: cozy-lineage-controller-webhook namespace: cozy-system - dependsOn: [cozystack-controller,cilium,kubeovn,cert-manager] + dependsOn: [cozystack-controller,cilium,kubeovn,multus,cert-manager] - name: cozystack-resource-definition-crd releaseName: cozystack-resource-definition-crd chart: cozystack-resource-definition-crd namespace: cozy-system - dependsOn: [cilium,kubeovn,cozystack-api,cozystack-controller] + dependsOn: [cilium,kubeovn,multus,cozystack-api,cozystack-controller] - name: bootbox-rd releaseName: bootbox-rd @@ -266,13 +266,13 @@ releases: releaseName: cert-manager chart: cozy-cert-manager namespace: cozy-cert-manager - dependsOn: [cert-manager-crds] + dependsOn: [cilium,kubeovn,multus,cert-manager-crds] - name: cert-manager-issuers releaseName: cert-manager-issuers chart: cozy-cert-manager-issuers namespace: cozy-cert-manager - dependsOn: [cilium,kubeovn,cert-manager] + dependsOn: [cert-manager] - name: prometheus-operator-crds releaseName: prometheus-operator-crds @@ -284,20 +284,20 @@ releases: releaseName: metrics-server chart: cozy-metrics-server namespace: cozy-monitoring - dependsOn: [cilium,kubeovn,prometheus-operator-crds] + dependsOn: [cilium,kubeovn,multus,prometheus-operator-crds] - name: victoria-metrics-operator releaseName: victoria-metrics-operator chart: cozy-victoria-metrics-operator namespace: cozy-victoria-metrics-operator - dependsOn: [cilium,kubeovn,cert-manager,prometheus-operator-crds] + dependsOn: [cilium,kubeovn,multus,cert-manager,prometheus-operator-crds] - name: monitoring-agents releaseName: monitoring-agents chart: cozy-monitoring-agents namespace: cozy-monitoring privileged: true - dependsOn: [victoria-metrics-operator, vertical-pod-autoscaler-crds, metrics-server] + dependsOn: [victoria-metrics-operator,vertical-pod-autoscaler-crds,metrics-server] values: scrapeRules: etcd: @@ -307,14 +307,14 @@ releases: releaseName: kubevirt-operator chart: cozy-kubevirt-operator namespace: cozy-kubevirt - dependsOn: [cilium,kubeovn,victoria-metrics-operator] + dependsOn: [cilium,kubeovn,multus,victoria-metrics-operator] - name: kubevirt releaseName: kubevirt chart: cozy-kubevirt namespace: cozy-kubevirt privileged: true - dependsOn: [cilium,kubeovn,kubevirt-operator] + dependsOn: [cilium,kubeovn,multus,kubevirt-operator] {{- $cpuAllocationRatio := index $cozyConfig.data "cpu-allocation-ratio" }} {{- if $cpuAllocationRatio }} values: @@ -325,19 +325,19 @@ releases: releaseName: kubevirt-instancetypes chart: cozy-kubevirt-instancetypes namespace: cozy-kubevirt - dependsOn: [cilium,kubeovn,kubevirt-operator,kubevirt] + dependsOn: [cilium,kubeovn,multus,kubevirt-operator,kubevirt] - name: kubevirt-cdi-operator releaseName: kubevirt-cdi-operator chart: cozy-kubevirt-cdi-operator namespace: cozy-kubevirt-cdi - dependsOn: [cilium,kubeovn] + dependsOn: [cilium,kubeovn,multus] - name: kubevirt-cdi releaseName: kubevirt-cdi chart: cozy-kubevirt-cdi namespace: cozy-kubevirt-cdi - dependsOn: [cilium,kubeovn,kubevirt-cdi-operator] + dependsOn: [cilium,kubeovn,multus,kubevirt-cdi-operator] - name: gpu-operator releaseName: gpu-operator @@ -345,7 +345,7 @@ releases: namespace: cozy-gpu-operator privileged: true optional: true - dependsOn: [cilium,kubeovn] + dependsOn: [cilium,kubeovn,multus] valuesFiles: - values.yaml - values-talos.yaml @@ -355,25 +355,25 @@ releases: chart: cozy-metallb namespace: cozy-metallb privileged: true - dependsOn: [cilium,kubeovn] + dependsOn: [cilium,kubeovn,multus] - name: etcd-operator releaseName: etcd-operator chart: cozy-etcd-operator namespace: cozy-etcd-operator - dependsOn: [cilium,kubeovn,cert-manager] + dependsOn: [cilium,kubeovn,multus,cert-manager] - name: grafana-operator releaseName: grafana-operator chart: cozy-grafana-operator namespace: cozy-grafana-operator - dependsOn: [cilium,kubeovn] + dependsOn: [cilium,kubeovn,multus] - name: mariadb-operator releaseName: mariadb-operator chart: cozy-mariadb-operator namespace: cozy-mariadb-operator - dependsOn: [cilium,kubeovn,cert-manager,victoria-metrics-operator] + dependsOn: [cilium,kubeovn,multus,cert-manager,victoria-metrics-operator] values: mariadb-operator: clusterName: {{ $clusterDomain }} @@ -382,13 +382,13 @@ releases: releaseName: postgres-operator chart: cozy-postgres-operator namespace: cozy-postgres-operator - dependsOn: [cilium,kubeovn,cert-manager] + dependsOn: [cilium,kubeovn,multus,cert-manager] - name: kafka-operator releaseName: kafka-operator chart: cozy-kafka-operator namespace: cozy-kafka-operator - dependsOn: [cilium,kubeovn,victoria-metrics-operator] + dependsOn: [cilium,kubeovn,multus,victoria-metrics-operator] values: strimzi-kafka-operator: kubernetesServiceDnsDomain: {{ $clusterDomain }} @@ -397,38 +397,38 @@ releases: releaseName: clickhouse-operator chart: cozy-clickhouse-operator namespace: cozy-clickhouse-operator - dependsOn: [cilium,kubeovn,victoria-metrics-operator] + dependsOn: [cilium,kubeovn,multus,victoria-metrics-operator] - name: foundationdb-operator releaseName: foundationdb-operator chart: cozy-foundationdb-operator namespace: cozy-foundationdb-operator - dependsOn: [cilium,kubeovn,cert-manager] + dependsOn: [cilium,kubeovn,multus,cert-manager] - name: rabbitmq-operator releaseName: rabbitmq-operator chart: cozy-rabbitmq-operator namespace: cozy-rabbitmq-operator - dependsOn: [cilium,kubeovn] + dependsOn: [cilium,kubeovn,multus] - name: redis-operator releaseName: redis-operator chart: cozy-redis-operator namespace: cozy-redis-operator - dependsOn: [cilium,kubeovn] + dependsOn: [cilium,kubeovn,multus] - name: piraeus-operator releaseName: piraeus-operator chart: cozy-piraeus-operator namespace: cozy-linstor - dependsOn: [cilium,kubeovn,cert-manager,victoria-metrics-operator] + dependsOn: [cilium,kubeovn,multus,cert-manager,victoria-metrics-operator] - name: linstor releaseName: linstor chart: cozy-linstor namespace: cozy-linstor privileged: true - dependsOn: [piraeus-operator,cilium,kubeovn,cert-manager,snapshot-controller] + dependsOn: [piraeus-operator,cilium,kubeovn,multus,cert-manager,snapshot-controller] - name: linstor-scheduler releaseName: linstor-scheduler @@ -441,27 +441,27 @@ releases: chart: cozy-nfs-driver namespace: cozy-nfs-driver privileged: true - dependsOn: [cilium,kubeovn] + dependsOn: [cilium,kubeovn,multus] optional: true - name: snapshot-controller releaseName: snapshot-controller chart: cozy-snapshot-controller namespace: cozy-snapshot-controller - dependsOn: [cilium,kubeovn,cert-manager-issuers] + dependsOn: [cilium,kubeovn,multus,cert-manager-issuers] - name: objectstorage-controller releaseName: objectstorage-controller chart: cozy-objectstorage-controller namespace: cozy-objectstorage-controller - dependsOn: [cilium,kubeovn] + dependsOn: [cilium,kubeovn,multus] - name: telepresence releaseName: traffic-manager chart: cozy-telepresence namespace: cozy-telepresence optional: true - dependsOn: [cilium,kubeovn] + dependsOn: [cilium,kubeovn,multus] - name: dashboard releaseName: dashboard @@ -474,6 +474,7 @@ releases: dependsOn: - cilium - kubeovn + - multus {{- if eq $oidcEnabled "true" }} - keycloak-configure {{- end }} @@ -482,56 +483,56 @@ releases: releaseName: kamaji chart: cozy-kamaji namespace: cozy-kamaji - dependsOn: [cilium,kubeovn,cert-manager] + dependsOn: [cilium,kubeovn,multus,cert-manager] - name: capi-operator releaseName: capi-operator chart: cozy-capi-operator namespace: cozy-cluster-api privileged: true - dependsOn: [cilium,kubeovn,cert-manager] + dependsOn: [cilium,kubeovn,multus,cert-manager] - name: capi-providers-bootstrap releaseName: capi-providers-bootstrap chart: cozy-capi-providers-bootstrap namespace: cozy-cluster-api privileged: true - dependsOn: [cilium,kubeovn,capi-operator] + dependsOn: [cilium,kubeovn,multus,capi-operator] - name: capi-providers-core releaseName: capi-providers-core chart: cozy-capi-providers-core namespace: cozy-cluster-api privileged: true - dependsOn: [cilium,kubeovn,capi-operator] + dependsOn: [cilium,kubeovn,multus,capi-operator] - name: capi-providers-cpprovider releaseName: capi-providers-cpprovider chart: cozy-capi-providers-cpprovider namespace: cozy-cluster-api privileged: true - dependsOn: [cilium,kubeovn,capi-operator] + dependsOn: [cilium,kubeovn,multus,capi-operator] - name: capi-providers-infraprovider releaseName: capi-providers-infraprovider chart: cozy-capi-providers-infraprovider namespace: cozy-cluster-api privileged: true - dependsOn: [cilium,kubeovn,capi-operator] + dependsOn: [cilium,kubeovn,multus,capi-operator] - name: external-dns releaseName: external-dns chart: cozy-external-dns namespace: cozy-external-dns optional: true - dependsOn: [cilium,kubeovn] + dependsOn: [cilium,kubeovn,multus] - name: external-secrets-operator releaseName: external-secrets-operator chart: cozy-external-secrets-operator namespace: cozy-external-secrets-operator optional: true - dependsOn: [cilium,kubeovn] + dependsOn: [cilium,kubeovn,multus] - name: bootbox releaseName: bootbox @@ -539,7 +540,7 @@ releases: namespace: cozy-bootbox privileged: true optional: true - dependsOn: [cilium,kubeovn] + dependsOn: [cilium,kubeovn,multus] {{- if $oidcEnabled }} - name: keycloak @@ -588,7 +589,7 @@ releases: chart: cozy-vertical-pod-autoscaler-crds namespace: cozy-vertical-pod-autoscaler privileged: true - dependsOn: [cilium, kubeovn] + dependsOn: [] - name: reloader releaseName: reloader From 88f469b3cd95aa91fd9a5fada9bf2a76cb15c608 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Sat, 10 Jan 2026 00:34:44 +0100 Subject: [PATCH 038/889] fix(registry): implement field selector filtering for label-based resources Controller-runtime cache doesn't support field selectors, causing incorrect filtering when using kubectl with field selectors like --field-selector=metadata.namespace=tenant-kvaps or metadata.name=test. Changes: - Created pkg/registry/fields package with ParseFieldSelector utility - Refactored field selector parsing logic in application, tenantmodule, and tenantsecret registries to use common implementation - Implemented manual filtering for metadata.name and metadata.namespace in List() and Watch() methods - Removed Raw field usage and field selectors from client.ListOptions - Label selectors passed directly via LabelSelector field Field selectors now properly filter resources by name and namespace through manual post-processing after label-based filtering. See: https://github.com/kubernetes-sigs/controller-runtime/issues/612 Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- pkg/registry/apps/application/rest.go | 132 +++++++++++++------------ pkg/registry/core/tenantmodule/rest.go | 117 +++++++++++----------- pkg/registry/core/tenantsecret/rest.go | 38 ++++--- pkg/registry/fields/filter.go | 70 +++++++++++++ 4 files changed, 227 insertions(+), 130 deletions(-) create mode 100644 pkg/registry/fields/filter.go diff --git a/pkg/registry/apps/application/rest.go b/pkg/registry/apps/application/rest.go index 9e8fb891..4f3204d5 100644 --- a/pkg/registry/apps/application/rest.go +++ b/pkg/registry/apps/application/rest.go @@ -28,7 +28,7 @@ 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" - fields "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/fields" labels "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -42,6 +42,7 @@ import ( appsv1alpha1 "github.com/cozystack/cozystack/pkg/apis/apps/v1alpha1" "github.com/cozystack/cozystack/pkg/config" + fieldfilter "github.com/cozystack/cozystack/pkg/registry/fields" "github.com/cozystack/cozystack/pkg/registry/sorting" internalapiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions" apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" @@ -257,26 +258,32 @@ func (r *REST) List(ctx context.Context, options *metainternalversion.ListOption } // Initialize variables for selector mapping - var helmFieldSelector string - var helmLabelSelector string + var helmLabelSelector labels.Selector - // Process field.selector - if options.FieldSelector != nil { - fs, err := fields.ParseSelector(options.FieldSelector.String()) - if err != nil { - klog.Errorf("Invalid field selector: %v", err) - return nil, fmt.Errorf("invalid field selector: %v", err) - } - // Check if selector is for metadata.name - if name, exists := fs.RequiresExactMatch("metadata.name"); exists { - // Convert Application name to HelmRelease name - mappedName := r.releaseConfig.Prefix + name - // Create new field.selector for HelmRelease - helmFieldSelector = fields.OneTermEqualSelector("metadata.name", mappedName).String() - } else { - // If field.selector contains other fields, map them directly - helmFieldSelector = fs.String() - } + // Parse field selector for manual filtering + // controller-runtime cache doesn't support field selectors + // See: https://github.com/kubernetes-sigs/controller-runtime/issues/612 + fieldFilter, err := fieldfilter.ParseFieldSelector(options.FieldSelector) + if err != nil { + klog.Errorf("Error parsing field selector: %v", err) + return nil, err + } + + // If field selector specifies namespace different from context, return empty list + if fieldFilter.Namespace != "" && namespace != "" && namespace != fieldFilter.Namespace { + klog.V(6).Infof("Field selector namespace %s doesn't match context namespace %s, returning empty list", fieldFilter.Namespace, namespace) + return &appsv1alpha1.ApplicationList{ + TypeMeta: metav1.TypeMeta{ + APIVersion: appsv1alpha1.SchemeGroupVersion.String(), + Kind: r.kindName + "List", + }, + }, nil + } + + // Convert Application name to HelmRelease name for manual filtering + var filterByName string + if fieldFilter.Name != "" { + filterByName = r.releaseConfig.Prefix + fieldFilter.Name } // Process label.selector @@ -315,21 +322,16 @@ func (r *REST) List(ctx context.Context, options *metainternalversion.ListOption labelRequirements = append(labelRequirements, prefixedReqs...) } } - helmLabelSelector = labels.NewSelector().Add(labelRequirements...).String() + helmLabelSelector = labels.NewSelector().Add(labelRequirements...) klog.V(6).Infof("Using label selector: %s for kind: %s, group: %s", helmLabelSelector, r.kindName, r.gvk.Group) - // Set ListOptions for HelmRelease with selector mapping - metaOptions := metav1.ListOptions{ - FieldSelector: helmFieldSelector, - LabelSelector: helmLabelSelector, - } - - // List HelmReleases with mapped selectors + // List HelmReleases with label selector only + // Field selectors are not supported by controller-runtime cache, so we filter manually below hrList := &helmv2.HelmReleaseList{} err = r.c.List(ctx, hrList, &client.ListOptions{ - Namespace: namespace, - Raw: &metaOptions, + Namespace: namespace, + LabelSelector: helmLabelSelector, }) if err != nil { klog.Errorf("Error listing HelmReleases: %v", err) @@ -346,6 +348,16 @@ func (r *REST) List(ctx context.Context, options *metainternalversion.ListOption for i := range hrList.Items { hr := &hrList.Items[i] + // Apply manual field selector filtering (metadata.name and metadata.namespace) + // controller-runtime cache doesn't support field selectors + // See: https://github.com/kubernetes-sigs/controller-runtime/issues/612 + if filterByName != "" && hr.Name != filterByName { + continue + } + if !fieldFilter.MatchesNamespace(hr.Namespace) { + continue + } + app, err := r.ConvertHelmReleaseToApplication(hr) if err != nil { klog.Errorf("Error converting HelmRelease %s to Application: %v", hr.GetName(), err) @@ -569,27 +581,21 @@ func (r *REST) Watch(ctx context.Context, options *metainternalversion.ListOptio } // Initialize variables for selector mapping - var helmFieldSelector string - var helmLabelSelector string + var helmLabelSelector labels.Selector - // Process field.selector - if options.FieldSelector != nil { - fs, err := fields.ParseSelector(options.FieldSelector.String()) - if err != nil { - klog.Errorf("Invalid field selector: %v", err) - return nil, fmt.Errorf("invalid field selector: %v", err) - } + // Parse field selector for manual filtering + // controller-runtime cache doesn't support field selectors + // See: https://github.com/kubernetes-sigs/controller-runtime/issues/612 + fieldFilter, err := fieldfilter.ParseFieldSelector(options.FieldSelector) + if err != nil { + klog.Errorf("Error parsing field selector: %v", err) + return nil, err + } - // Check if selector is for metadata.name - if name, exists := fs.RequiresExactMatch("metadata.name"); exists { - // Convert Application name to HelmRelease name - mappedName := r.releaseConfig.Prefix + name - // Create new field.selector for HelmRelease - helmFieldSelector = fields.OneTermEqualSelector("metadata.name", mappedName).String() - } else { - // If field.selector contains other fields, map them directly - helmFieldSelector = fs.String() - } + // Convert Application name to HelmRelease name for manual filtering + var filterByName string + if fieldFilter.Name != "" { + filterByName = r.releaseConfig.Prefix + fieldFilter.Name } // Process label.selector @@ -628,21 +634,15 @@ func (r *REST) Watch(ctx context.Context, options *metainternalversion.ListOptio labelRequirements = append(labelRequirements, prefixedReqs...) } } - helmLabelSelector = labels.NewSelector().Add(labelRequirements...).String() + helmLabelSelector = labels.NewSelector().Add(labelRequirements...) - // Set ListOptions for HelmRelease with selector mapping - metaOptions := metav1.ListOptions{ - Watch: true, - ResourceVersion: options.ResourceVersion, - FieldSelector: helmFieldSelector, - LabelSelector: helmLabelSelector, - } - - // Start watch on HelmRelease with mapped selectors + // Start watch on HelmRelease with label selector only + // Field selectors are not supported by controller-runtime cache + // See: https://github.com/kubernetes-sigs/controller-runtime/issues/612 hrList := &helmv2.HelmReleaseList{} helmWatcher, err := r.w.Watch(ctx, hrList, &client.ListOptions{ - Namespace: namespace, - Raw: &metaOptions, + Namespace: namespace, + LabelSelector: helmLabelSelector, }) if err != nil { klog.Errorf("Error setting up watch for HelmReleases: %v", err) @@ -682,6 +682,16 @@ func (r *REST) Watch(ctx context.Context, options *metainternalversion.ListOptio continue } + // Apply manual field selector filtering (metadata.name and metadata.namespace) + // controller-runtime cache doesn't support field selectors + // See: https://github.com/kubernetes-sigs/controller-runtime/issues/612 + if filterByName != "" && hr.Name != filterByName { + continue + } + if !fieldFilter.MatchesNamespace(hr.Namespace) { + continue + } + // Note: All HelmReleases already match the required labels due to server-side label selector filtering // Convert HelmRelease to Application app, err := r.ConvertHelmReleaseToApplication(hr) diff --git a/pkg/registry/core/tenantmodule/rest.go b/pkg/registry/core/tenantmodule/rest.go index 12da0e0d..b2cda2db 100644 --- a/pkg/registry/core/tenantmodule/rest.go +++ b/pkg/registry/core/tenantmodule/rest.go @@ -40,6 +40,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" corev1alpha1 "github.com/cozystack/cozystack/pkg/apis/core/v1alpha1" + fieldfilter "github.com/cozystack/cozystack/pkg/registry/fields" "github.com/cozystack/cozystack/pkg/registry/sorting" apierrors "k8s.io/apimachinery/pkg/api/errors" ) @@ -161,24 +162,26 @@ func (r *REST) List(ctx context.Context, options *metainternalversion.ListOption } // Initialize variables for selector mapping - var helmFieldSelector string - var helmLabelSelector string + var helmLabelSelector labels.Selector - // Process field.selector - if options.FieldSelector != nil { - fs, err := fields.ParseSelector(options.FieldSelector.String()) - if err != nil { - klog.Errorf("Invalid field selector: %v", err) - return nil, fmt.Errorf("invalid field selector: %v", err) - } - // Check if selector is for metadata.name - if name, exists := fs.RequiresExactMatch("metadata.name"); exists { - // Create new field.selector for HelmRelease - helmFieldSelector = fields.OneTermEqualSelector("metadata.name", name).String() - } else { - // If field.selector contains other fields, map them directly - helmFieldSelector = fs.String() - } + // Parse field selector for manual filtering + // controller-runtime cache doesn't support field selectors + // See: https://github.com/kubernetes-sigs/controller-runtime/issues/612 + fieldFilter, err := fieldfilter.ParseFieldSelector(options.FieldSelector) + if err != nil { + klog.Errorf("Error parsing field selector: %v", err) + return nil, err + } + + // If field selector specifies namespace different from context, return empty list + if fieldFilter.Namespace != "" && namespace != "" && namespace != fieldFilter.Namespace { + klog.V(6).Infof("Field selector namespace %s doesn't match context namespace %s, returning empty list", fieldFilter.Namespace, namespace) + return &corev1alpha1.TenantModuleList{ + TypeMeta: metav1.TypeMeta{ + APIVersion: corev1alpha1.SchemeGroupVersion.String(), + Kind: "TenantModuleList", + }, + }, nil } // Process label.selector - add the tenant module label requirement @@ -202,19 +205,15 @@ func (r *REST) List(ctx context.Context, options *metainternalversion.ListOption } } - helmLabelSelector = labels.NewSelector().Add(labelRequirements...).String() + helmLabelSelector = labels.NewSelector().Add(labelRequirements...) - // Set ListOptions for HelmRelease with selector mapping - metaOptions := metav1.ListOptions{ - FieldSelector: helmFieldSelector, - LabelSelector: helmLabelSelector, - } - - // List HelmReleases with mapped selectors + // List HelmReleases with label selector only + // Field selectors are not supported by controller-runtime cache + // See: https://github.com/kubernetes-sigs/controller-runtime/issues/612 hrList := &helmv2.HelmReleaseList{} err = r.c.List(ctx, hrList, &client.ListOptions{ - Namespace: namespace, - Raw: &metaOptions, + Namespace: namespace, + LabelSelector: helmLabelSelector, }) if err != nil { klog.Errorf("Error listing HelmReleases: %v", err) @@ -226,6 +225,16 @@ func (r *REST) List(ctx context.Context, options *metainternalversion.ListOption // Iterate over HelmReleases and convert to TenantModules for i := range hrList.Items { + // Apply manual field selector filtering (metadata.name and metadata.namespace) + // controller-runtime cache doesn't support field selectors + // See: https://github.com/kubernetes-sigs/controller-runtime/issues/612 + if !fieldFilter.MatchesName(hrList.Items[i].Name) { + continue + } + if !fieldFilter.MatchesNamespace(hrList.Items[i].Namespace) { + continue + } + // Double-check the label requirement if !r.hasTenantModuleLabel(&hrList.Items[i]) { continue @@ -305,25 +314,15 @@ func (r *REST) Watch(ctx context.Context, options *metainternalversion.ListOptio } // Initialize variables for selector mapping - var helmFieldSelector string - var helmLabelSelector string + var helmLabelSelector labels.Selector - // Process field.selector - if options.FieldSelector != nil { - fs, err := fields.ParseSelector(options.FieldSelector.String()) - if err != nil { - klog.Errorf("Invalid field selector: %v", err) - return nil, fmt.Errorf("invalid field selector: %v", err) - } - - // Check if selector is for metadata.name - if name, exists := fs.RequiresExactMatch("metadata.name"); exists { - // Create new field.selector for HelmRelease - helmFieldSelector = fields.OneTermEqualSelector("metadata.name", name).String() - } else { - // If field.selector contains other fields, map them directly - helmFieldSelector = fs.String() - } + // Parse field selector for manual filtering + // controller-runtime cache doesn't support field selectors + // See: https://github.com/kubernetes-sigs/controller-runtime/issues/612 + fieldFilter, err := fieldfilter.ParseFieldSelector(options.FieldSelector) + if err != nil { + klog.Errorf("Error parsing field selector: %v", err) + return nil, err } // Process label.selector - add the tenant module label requirement @@ -347,21 +346,15 @@ func (r *REST) Watch(ctx context.Context, options *metainternalversion.ListOptio } } - helmLabelSelector = labels.NewSelector().Add(labelRequirements...).String() + helmLabelSelector = labels.NewSelector().Add(labelRequirements...) - // Set ListOptions for HelmRelease with selector mapping - metaOptions := metav1.ListOptions{ - Watch: true, - ResourceVersion: options.ResourceVersion, - FieldSelector: helmFieldSelector, - LabelSelector: helmLabelSelector, - } - - // Start watch on HelmRelease with mapped selectors + // Start watch on HelmRelease with label selector only + // Field selectors are not supported by controller-runtime cache + // See: https://github.com/kubernetes-sigs/controller-runtime/issues/612 hrList := &helmv2.HelmReleaseList{} helmWatcher, err := r.w.Watch(ctx, hrList, &client.ListOptions{ - Namespace: namespace, - Raw: &metaOptions, + Namespace: namespace, + LabelSelector: helmLabelSelector, }) if err != nil { klog.Errorf("Error setting up watch for HelmReleases: %v", err) @@ -401,6 +394,16 @@ func (r *REST) Watch(ctx context.Context, options *metainternalversion.ListOptio continue } + // Apply manual field selector filtering (metadata.name and metadata.namespace) + // controller-runtime cache doesn't support field selectors + // See: https://github.com/kubernetes-sigs/controller-runtime/issues/612 + if !fieldFilter.MatchesName(hr.Name) { + continue + } + if !fieldFilter.MatchesNamespace(hr.Namespace) { + continue + } + if !r.hasTenantModuleLabel(hr) { continue } diff --git a/pkg/registry/core/tenantsecret/rest.go b/pkg/registry/core/tenantsecret/rest.go index e5dbf9dd..d069c6ae 100644 --- a/pkg/registry/core/tenantsecret/rest.go +++ b/pkg/registry/core/tenantsecret/rest.go @@ -27,6 +27,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" corev1alpha1 "github.com/cozystack/cozystack/pkg/apis/core/v1alpha1" + fieldfilter "github.com/cozystack/cozystack/pkg/registry/fields" "github.com/cozystack/cozystack/pkg/registry/sorting" ) @@ -248,9 +249,22 @@ func (r *REST) List(ctx context.Context, opts *metainternal.ListOptions) (runtim } } - fieldSel := "" - if opts.FieldSelector != nil { - fieldSel = opts.FieldSelector.String() + // Parse field selector for manual filtering + // controller-runtime cache doesn't support field selectors + // See: https://github.com/kubernetes-sigs/controller-runtime/issues/612 + fieldFilter, err := fieldfilter.ParseFieldSelector(opts.FieldSelector) + if err != nil { + return nil, err + } + + // If field selector specifies namespace different from context, return empty list + if fieldFilter.Namespace != "" && ns != "" && ns != fieldFilter.Namespace { + return &corev1alpha1.TenantSecretList{ + TypeMeta: metav1.TypeMeta{ + APIVersion: corev1alpha1.SchemeGroupVersion.String(), + Kind: kindTenantSecretList, + }, + }, nil } list := &corev1.SecretList{} @@ -258,10 +272,6 @@ func (r *REST) List(ctx context.Context, opts *metainternal.ListOptions) (runtim &client.ListOptions{ Namespace: ns, LabelSelector: ls, - Raw: &metav1.ListOptions{ - LabelSelector: ls.String(), - FieldSelector: fieldSel, - }, }) if err != nil { return nil, err @@ -276,6 +286,15 @@ func (r *REST) List(ctx context.Context, opts *metainternal.ListOptions) (runtim } for i := range list.Items { + // Apply manual field selector filtering (metadata.name and metadata.namespace) + // controller-runtime cache doesn't support field selectors + // See: https://github.com/kubernetes-sigs/controller-runtime/issues/612 + if !fieldFilter.MatchesName(list.Items[i].Name) { + continue + } + if !fieldFilter.MatchesNamespace(list.Items[i].Namespace) { + continue + } out.Items = append(out.Items, *secretToTenant(&list.Items[i])) } sorting.ByNamespacedName[corev1alpha1.TenantSecret, *corev1alpha1.TenantSecret](out.Items) @@ -413,11 +432,6 @@ func (r *REST) Watch(ctx context.Context, opts *metainternal.ListOptions) (watch base, err := r.w.Watch(ctx, secList, &client.ListOptions{ Namespace: ns, LabelSelector: ls, - Raw: &metav1.ListOptions{ - Watch: true, - LabelSelector: ls.String(), - ResourceVersion: opts.ResourceVersion, - }, }) if err != nil { return nil, err diff --git a/pkg/registry/fields/filter.go b/pkg/registry/fields/filter.go new file mode 100644 index 00000000..9b1fc246 --- /dev/null +++ b/pkg/registry/fields/filter.go @@ -0,0 +1,70 @@ +// Copyright 2024 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. + +package fields + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/fields" +) + +// Filter holds field selector filters extracted from a field selector string. +type Filter struct { + // Name is the value from metadata.name field selector, empty if not specified + Name string + // Namespace is the value from metadata.namespace field selector, empty if not specified + Namespace string +} + +// ParseFieldSelector parses a field selector and extracts metadata.name and metadata.namespace values. +// Other field selectors are silently ignored as controller-runtime cache doesn't support them. +// See: https://github.com/kubernetes-sigs/controller-runtime/issues/612 +func ParseFieldSelector(fieldSelector fields.Selector) (*Filter, error) { + if fieldSelector == nil { + return &Filter{}, nil + } + + fs, err := fields.ParseSelector(fieldSelector.String()) + if err != nil { + return nil, fmt.Errorf("invalid field selector: %v", err) + } + + filter := &Filter{} + + // Check if selector is for metadata.name + if name, exists := fs.RequiresExactMatch("metadata.name"); exists { + filter.Name = name + } + + // Check if selector is for metadata.namespace + if namespace, exists := fs.RequiresExactMatch("metadata.namespace"); exists { + filter.Namespace = namespace + } + + // Note: Other field selectors are silently ignored as controller-runtime cache + // doesn't support them. See: https://github.com/kubernetes-sigs/controller-runtime/issues/612 + + return filter, nil +} + +// MatchesName returns true if the filter has no name constraint or if the name matches. +func (f *Filter) MatchesName(name string) bool { + return f.Name == "" || f.Name == name +} + +// MatchesNamespace returns true if the filter has no namespace constraint or if the namespace matches. +func (f *Filter) MatchesNamespace(namespace string) bool { + return f.Namespace == "" || f.Namespace == namespace +} From 248eed338fdf4d8dfacbd0c6b4319e47f0028904 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Fri, 9 Jan 2026 21:36:39 +0100 Subject: [PATCH 039/889] [platform] fix migrations for v0.40 release Signed-off-by: Andrei Kvapil --- scripts/migrations/20 | 4 ++-- scripts/migrations/21 | 17 ++++++++++++++--- scripts/migrations/22 | 21 +++++++++++++++++++++ 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/scripts/migrations/20 b/scripts/migrations/20 index a27464ac..0c885338 100755 --- a/scripts/migrations/20 +++ b/scripts/migrations/20 @@ -33,11 +33,11 @@ else kubectl rollout status deploy/cozystack-api -n cozy-system --timeout=5m || exit 1 fi -helm upgrade --install -n cozy-system cozystack-controller ./packages/system/cozystack-controller/ --take-ownership +cozyhr -n cozy-system -C ./packages/system/cozystack-controller apply cozystack-controller --take-ownership echo "Waiting for cozystack-controller" kubectl rollout status deploy/cozystack-controller -n cozy-system --timeout=5m || exit 1 -helm upgrade --install -n cozy-system lineage-controller-webhook ./packages/system/lineage-controller-webhook/ --take-ownership +cozyhr -n cozy-system -C ./packages/system/lineage-controller-webhook/ apply lineage-controller-webhook --take-ownership echo "Waiting for lineage-webhook" kubectl rollout status ds/lineage-controller-webhook -n cozy-system --timeout=5m || exit 1 diff --git a/scripts/migrations/21 b/scripts/migrations/21 index abefdc77..bd5fda5f 100755 --- a/scripts/migrations/21 +++ b/scripts/migrations/21 @@ -3,14 +3,25 @@ set -euo pipefail +# Disable pruning on Flux CRDs for crd in $(kubectl get crd -o name | grep 'fluxcd.io$'); do kubectl annotate $crd fluxcd.controlplane.io/prune=disabled done -kubectl delete hr -n cozy-fluxcd fluxcd --ignore-not-found -kubectl delete hr -n cozy-fluxcd fluxcd-operator --ignore-not-found +# Remove flux instance +if kubectl get fluxinstance flux -n cozy-fluxcd >/dev/null 2>&1; then + kubectl annotate fluxinstance flux -n cozy-fluxcd fluxcd.controlplane.io/reconcile=disabled + kubectl delete fluxinstance flux -n cozy-fluxcd +fi +kubectl delete deploy -n cozy-fluxcd -l app.kubernetes.io/part-of=flux --ignore-not-found +kubectl delete hr -n cozy-fluxcd fluxcd --ignore-not-found --wait=false -for crd in $(kubectl get crd -o name | grep 'fluxcd.io$'); do +# Remove fluxcd-operator +kubectl delete hr -n cozy-fluxcd fluxcd-operator --ignore-not-found --wait=false +kubectl delete deploy -n cozy-fluxcd flux-operator --ignore-not-found + +# Remove labels from CRDs +for crd in $(kubectl get crd -o name | grep 'fluxcd\.io$'); do kubectl label $crd fluxcd.controlplane.io/name- fluxcd.controlplane.io/namespace- done diff --git a/scripts/migrations/22 b/scripts/migrations/22 index 192e431c..2dc8f281 100755 --- a/scripts/migrations/22 +++ b/scripts/migrations/22 @@ -3,6 +3,17 @@ set -euo pipefail +# Migrate Victoria Metrics Operator CRDs to prometheus-operator-crds Helm release +for crd in $(kubectl get crd -o name | grep 'coreos\.com$'); do + kubectl annotate $crd meta.helm.sh/release-namespace=cozy-victoria-metrics-operator meta.helm.sh/release-name=prometheus-operator-crds --overwrite + kubectl label $crd app.kubernetes.io/managed-by=Helm helm.toolkit.fluxcd.io/namespace=cozy-victoria-metrics-operator helm.toolkit.fluxcd.io/name=prometheus-operator-crds --overwrite +done +kubectl delete secret -n cozy-victoria-metrics-operator -l name=victoria-metrics-operator,owner=helm --ignore-not-found + +# Remove CozyStack Resource Definitions HR +kubectl delete hr -n cozy-system cozystack-resource-definitions --ignore-not-found --wait=false +kubectl delete cozystackresourcedefinitions.cozystack.io --all --ignore-not-found --wait=false + echo "Migrating HelmReleases: adding application labels for tenant-* namespaces" # Function to determine application type from HelmRelease name @@ -155,6 +166,16 @@ kubectl get helmreleases --all-namespaces -l cozystack.io/ui=true -o json | \ echo "Added application labels to $namespace/$name: $labels" done +echo "Migrating PostgreSQL HelmReleases: adding default version v17" + +# Patch all PostgreSQL HelmReleases to add spec.values.version: v17 +kubectl get helmreleases --all-namespaces -l apps.cozystack.io/application.kind=PostgreSQL -o json | \ + jq -r '.items[] | select(.spec.values.version == null) | "\(.metadata.namespace)|\(.metadata.name)"' | \ + while IFS='|' read -r namespace name; do + echo "Patching PostgreSQL HelmRelease $namespace/$name to add version v17" + kubectl patch helmrelease -n "$namespace" "$name" --type=merge -p '{"spec":{"values":{"version":"v17"}}}' + done + echo "Migration completed" # Stamp version From fd3c9cc737ab24bde0eafcba642f3a96b8b5af8d Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Sat, 10 Jan 2026 03:34:50 +0100 Subject: [PATCH 040/889] [docs] Generate missing changelogs for v0.37.10, v0.38.5-v0.38.8, v0.39.2-v0.39.4, and v0.40.0 Generated changelogs for missing releases following changelog.md instructions: - v0.37.10: Backports for dashboard schema fix and VM resize improvements - v0.38.5-v0.38.8: Backports for SeaweedFS, Cilium, VM scheduling, and Multus fixes - v0.39.2-v0.39.4: Backports for VM services, tenant policies, LINSTOR fixes, and Multus dependencies - v0.40.0: Minor release with LINSTOR scheduler, SeaweedFS traffic locality, valuesFrom mechanism, auto-diskful, and version management systems All changelogs include proper PR author attribution using GitHub CLI and user impact descriptions. Signed-off-by: Andrei Kvapil --- docs/changelogs/v0.37.10.md | 16 +++ docs/changelogs/v0.38.5.md | 18 ++++ docs/changelogs/v0.38.6.md | 12 +++ docs/changelogs/v0.38.7.md | 13 +++ docs/changelogs/v0.38.8.md | 12 +++ docs/changelogs/v0.39.2.md | 19 ++++ docs/changelogs/v0.39.3.md | 36 +++++++ docs/changelogs/v0.39.4.md | 12 +++ docs/changelogs/v0.40.0.md | 206 ++++++++++++++++++++++++++++++++++++ 9 files changed, 344 insertions(+) create mode 100644 docs/changelogs/v0.37.10.md create mode 100644 docs/changelogs/v0.38.5.md create mode 100644 docs/changelogs/v0.38.6.md create mode 100644 docs/changelogs/v0.38.7.md create mode 100644 docs/changelogs/v0.38.8.md create mode 100644 docs/changelogs/v0.39.2.md create mode 100644 docs/changelogs/v0.39.3.md create mode 100644 docs/changelogs/v0.39.4.md create mode 100644 docs/changelogs/v0.40.0.md diff --git a/docs/changelogs/v0.37.10.md b/docs/changelogs/v0.37.10.md new file mode 100644 index 00000000..18f0dd8b --- /dev/null +++ b/docs/changelogs/v0.37.10.md @@ -0,0 +1,16 @@ + + +## Features and Improvements + +* **[virtual-machine] Improve check for resizing job**: Improved storage resize logic to only expand persistent volume claims when storage is being increased, preventing unintended storage reduction operations. Added validation to accurately compare current and desired storage sizes before triggering resize operations ([**@kvaps**](https://github.com/kvaps) in #1688, #1702). + +## Fixes + +* **[dashboard] Fix CustomFormsOverride schema to nest properties under spec.properties**: Fixed the logic for generating CustomFormsOverride schema to properly nest properties under `spec.properties` instead of directly under `properties`, ensuring correct form schema generation in the dashboard ([**@kvaps**](https://github.com/kvaps) in #1692, #1699). + +--- + +**Full Changelog**: [v0.37.9...v0.37.10](https://github.com/cozystack/cozystack/compare/v0.37.9...v0.37.10) + diff --git a/docs/changelogs/v0.38.5.md b/docs/changelogs/v0.38.5.md new file mode 100644 index 00000000..0cae8b2a --- /dev/null +++ b/docs/changelogs/v0.38.5.md @@ -0,0 +1,18 @@ + + +## Features and Improvements + +* **[virtual-machine,vm-instance] Add nodeAffinity for Windows VMs based on scheduling config**: Added nodeAffinity configuration to virtual-machine and vm-instance charts to support dedicated nodes for Windows VMs. When `dedicatedNodesForWindowsVMs` is enabled in the `cozystack-scheduling` ConfigMap, Windows VMs are scheduled on nodes with label `scheduling.cozystack.io/vm-windows=true`, while non-Windows VMs prefer nodes without this label ([**@kvaps**](https://github.com/kvaps) in #1693, #1744). +* **[cilium] Enable automatic pod rollout on configmap updates**: Cilium and Cilium operator pods now automatically restart when the cilium-config ConfigMap is updated, ensuring configuration changes are applied immediately without manual intervention ([**@kvaps**](https://github.com/kvaps) in #1728, #1745). +* **Update SeaweedFS v4.02**: Updated SeaweedFS to version 4.02 with improved S3 daemon performance and fixes. This update includes better S3 compatibility and performance improvements ([**@kvaps**](https://github.com/kvaps) in #1725, #1732). + +## Fixes + +* **[apps] Refactor apiserver to use typed objects and fix UnstructuredList GVK**: Refactored 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, fixed 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 fix includes the upstream fix from kubernetes/kubernetes#135537 ([**@kvaps**](https://github.com/kvaps) in #1679, #1709). + +--- + +**Full Changelog**: [v0.38.4...v0.38.5](https://github.com/cozystack/cozystack/compare/v0.38.4...v0.38.5) + diff --git a/docs/changelogs/v0.38.6.md b/docs/changelogs/v0.38.6.md new file mode 100644 index 00000000..9c39b66d --- /dev/null +++ b/docs/changelogs/v0.38.6.md @@ -0,0 +1,12 @@ + + +## Development, Testing, and CI/CD + +* **[kubernetes] Add lb tests for tenant k8s**: Added load balancer tests for tenant Kubernetes clusters, improving test coverage and ensuring proper load balancer functionality in tenant environments ([**@IvanHunters**](https://github.com/IvanHunters) in #1783, #1792). + +--- + +**Full Changelog**: [v0.38.5...v0.38.6](https://github.com/cozystack/cozystack/compare/v0.38.5...v0.38.6) + diff --git a/docs/changelogs/v0.38.7.md b/docs/changelogs/v0.38.7.md new file mode 100644 index 00000000..3edfd94c --- /dev/null +++ b/docs/changelogs/v0.38.7.md @@ -0,0 +1,13 @@ + + +## Fixes + +* **[kubevirt-operator] Fix typo in VMNotRunningFor10Minutes alert**: Fixed typo in VM alert name, ensuring proper alert triggering and monitoring for virtual machines that are not running for extended periods ([**@lexfrei**](https://github.com/lexfrei) in #1770). +* **[kubevirt-operator] Revert incorrect case change in VM alerts**: Reverted incorrect case change in VM alert names to maintain consistency with alert naming conventions ([**@lexfrei**](https://github.com/lexfrei) in #1804, #1805). + +--- + +**Full Changelog**: [v0.38.6...v0.38.7](https://github.com/cozystack/cozystack/compare/v0.38.6...v0.38.7) + diff --git a/docs/changelogs/v0.38.8.md b/docs/changelogs/v0.38.8.md new file mode 100644 index 00000000..0dbfe7b8 --- /dev/null +++ b/docs/changelogs/v0.38.8.md @@ -0,0 +1,12 @@ + + +## Improvements + +* **[multus] Remove memory limit**: Removed memory limit for Multus daemonset due to unpredictable memory consumption spikes during startup after node reboots (reported up to 3Gi). This temporary change prevents out-of-memory issues while the root cause is addressed in future releases ([**@nbykov0**](https://github.com/nbykov0) in #1834). + +--- + +**Full Changelog**: [v0.38.7...v0.38.8](https://github.com/cozystack/cozystack/compare/v0.38.7...v0.38.8) + diff --git a/docs/changelogs/v0.39.2.md b/docs/changelogs/v0.39.2.md new file mode 100644 index 00000000..dacb094f --- /dev/null +++ b/docs/changelogs/v0.39.2.md @@ -0,0 +1,19 @@ + + +## Features and Improvements + +* **[vm] Always expose VMs with a service**: Virtual machines are now always exposed with at least a ClusterIP service, ensuring they have in-cluster DNS names and can be accessed from other pods even without public IP addresses ([**@lllamnyp**](https://github.com/lllamnyp) in #1738, #1751). +* **[tenant] Allow egress to parent ingress pods**: Updated tenant network policies to allow egress traffic to parent cluster ingress pods, enabling proper communication patterns between tenant namespaces and parent cluster ingress controllers ([**@lexfrei**](https://github.com/lexfrei) in #1765, #1776). +* **[system] Add resource requests and limits to etcd-defrag**: Added resource requests and limits to etcd-defrag job to ensure proper resource allocation and prevent resource contention during etcd maintenance operations ([**@matthieu-robin**](https://github.com/matthieu-robin) in #1785, #1786). +* **[tenant] Run cleanup job from system namespace**: Moved tenant cleanup job to run from system namespace, improving security and resource isolation for tenant cleanup operations ([**@lllamnyp**](https://github.com/lllamnyp) in #1774, #1777). + +## Fixes + +* **[kubevirt-operator] Fix typo in VMNotRunningFor10Minutes alert**: Fixed typo in VM alert name, ensuring proper alert triggering and monitoring for virtual machines that are not running for extended periods ([**@lexfrei**](https://github.com/lexfrei) in #1770, #1775). + +--- + +**Full Changelog**: [v0.39.1...v0.39.2](https://github.com/cozystack/cozystack/compare/v0.39.1...v0.39.2) + diff --git a/docs/changelogs/v0.39.3.md b/docs/changelogs/v0.39.3.md new file mode 100644 index 00000000..f493795b --- /dev/null +++ b/docs/changelogs/v0.39.3.md @@ -0,0 +1,36 @@ + + +## Features and Improvements + +* **[seaweedfs] Traffic locality**: Upgraded SeaweedFS to v4.05 with traffic locality capabilities, new admin component with web-based UI, worker component for distributed operations, and enhanced S3 monitoring with Grafana dashboards. Improves S3 service performance by routing requests to nearest available volume servers ([**@nbykov0**](https://github.com/nbykov0) in #1748, #1830). +* **[kube-ovn] Update to v1.14.25**: Updated Kube-OVN to version 1.14.25 with improved stability and new features ([**@kvaps**](https://github.com/kvaps) in #1819, #1837). +* **[linstor] Build linstor-server with custom patches**: Added custom patches to linstor-server build process, enabling platform-specific optimizations and fixes ([**@kvaps**](https://github.com/kvaps) in #1726, #1818). +* **[api, lineage] Tolerate all taints**: Updated API and lineage webhook to tolerate all taints, ensuring controllers can run on any node regardless of taint configuration ([**@nbykov0**](https://github.com/nbykov0) in #1781, #1827). +* **[ingress] Add topology anti-affinities**: Added topology anti-affinity rules to ingress controller deployment for better pod distribution across nodes ([**@kvaps**](https://github.com/kvaps) in commit 25f31022). + +## Fixes + +* **[linstor] fix: prevent DRBD device race condition in updateDiscGran**: Fixed race condition in DRBD device management during granularity updates, preventing potential data corruption or device conflicts ([**@kvaps**](https://github.com/kvaps) in #1829, #1836). +* **fix(linstor): prevent orphaned DRBD devices during toggle-disk retry**: Fixed issue where retry logic during disk toggle operations could leave orphaned DRBD devices, now properly cleans up devices during retry attempts ([**@kvaps**](https://github.com/kvaps) in #1823, #1825). +* **[kubernetes] Fix endpoints for cilium-gateway**: Fixed endpoint configuration for cilium-gateway, ensuring proper service discovery and connectivity ([**@kvaps**](https://github.com/kvaps) in #1729, #1808). +* **[kubevirt-operator] Revert incorrect case change in VM alerts**: Reverted incorrect case change in VM alert names to maintain consistency with alert naming conventions ([**@lexfrei**](https://github.com/lexfrei) in #1804, #1806). + +## System Configuration + +* **[kubeovn] Package from external repo**: Extracted Kube-OVN packaging from main repository to external repository, improving modularity ([**@lllamnyp**](https://github.com/lllamnyp) in #1535). + +## Development, Testing, and CI/CD + +* **[testing] Add aliases and autocomplete**: Added shell aliases and autocomplete support for testing commands, improving developer experience ([**@lllamnyp**](https://github.com/lllamnyp) in #1803, #1809). + +## Dependencies + +* **[seaweedfs] Traffic locality**: Upgraded SeaweedFS to v4.05 with traffic locality capabilities ([**@nbykov0**](https://github.com/nbykov0) in #1748, #1830). +* **[kube-ovn] Update to v1.14.25**: Updated Kube-OVN to version 1.14.25 ([**@kvaps**](https://github.com/kvaps) in #1819, #1837). + +--- + +**Full Changelog**: [v0.39.2...v0.39.3](https://github.com/cozystack/cozystack/compare/v0.39.2...v0.39.3) + diff --git a/docs/changelogs/v0.39.4.md b/docs/changelogs/v0.39.4.md new file mode 100644 index 00000000..36d9f016 --- /dev/null +++ b/docs/changelogs/v0.39.4.md @@ -0,0 +1,12 @@ + + +## Features and Improvements + +* **[paas-full] Add multus dependencies similar to other CNIs**: Added Multus as a dependency in the paas-full package, consistent with how other CNIs are included. This ensures proper dependency management and simplifies the installation process for environments using Multus networking ([**@nbykov0**](https://github.com/nbykov0) in #1835). + +--- + +**Full Changelog**: [v0.39.3...v0.39.4](https://github.com/cozystack/cozystack/compare/v0.39.3...v0.39.4) + diff --git a/docs/changelogs/v0.40.0.md b/docs/changelogs/v0.40.0.md new file mode 100644 index 00000000..caaa08f6 --- /dev/null +++ b/docs/changelogs/v0.40.0.md @@ -0,0 +1,206 @@ +# Cozystack v0.40 — "Enhanced Storage & Platform Architecture" + +This release introduces LINSTOR scheduler for optimal pod placement, SeaweedFS traffic locality, a new valuesFrom-based configuration mechanism, auto-diskful for LINSTOR, automated version management systems, and numerous improvements across the platform. + +## Feature Highlights + +### LINSTOR Scheduler for Optimal Pod Placement + +Cozystack now includes a custom Kubernetes scheduler extender that works alongside the default kube-scheduler to optimize pod placement on nodes with LINSTOR storage. When a pod requests LINSTOR-backed storage, the scheduler communicates with the LINSTOR controller to find nodes that have local replicas of the requested volumes, prioritizing placement on nodes with existing data to minimize network traffic and improve I/O performance. + +The scheduler includes an admission webhook that automatically routes pods using LINSTOR CSI volumes to the custom scheduler, ensuring seamless integration without manual configuration. This feature significantly improves performance for workloads using LINSTOR storage by reducing network latency and improving data locality. + +Learn more about LINSTOR in the [documentation](https://cozystack.io/docs/operations/storage/linstor/). + +### SeaweedFS Traffic Locality + +SeaweedFS has been upgraded to version 4.05 with new traffic locality capabilities that optimize S3 service traffic distribution. The update includes a new admin component with a web-based UI and authentication support, as well as a worker component for distributed operations. These enhancements improve S3 service performance and provide better visibility through enhanced Grafana dashboard panels for buckets, API calls, costs, and performance metrics. + +The traffic locality feature ensures that S3 requests are routed to the nearest available volume servers, reducing latency and improving overall performance for distributed storage operations. TLS certificate support for admin and worker components adds an extra layer of security for management operations. + +### ValuesFrom Configuration Mechanism + +Cozystack now uses FluxCD's valuesFrom mechanism to replace Helm lookup functions for configuration propagation. This architectural improvement provides cleaner config propagation and eliminates the need for force reconcile controllers. Configuration from ConfigMaps (cozystack, cozystack-branding, cozystack-scheduling) and namespace service references (etcd, host, ingress, monitoring, seaweedfs) is now centrally managed through a `cozystack-values` Secret in each namespace. + +This change simplifies Helm chart templates by replacing complex lookup functions with direct value references, improves configuration consistency, and reduces the reconciliation overhead. All HelmReleases now automatically receive cluster and namespace configuration through the valuesFrom mechanism, making configuration management more transparent and maintainable. + +### Auto-diskful for LINSTOR + +The LINSTOR integration now includes automatic diskful functionality that converts diskless nodes to diskful when they hold DRBD resources in Primary state for an extended period (30 minutes). This feature addresses scenarios where workloads are scheduled on nodes without local storage replicas by automatically creating local disk replicas when needed, improving I/O performance for long-running workloads. + +When enabled with cleanup options, the system can automatically remove disk replicas that are no longer needed, preventing storage waste from temporary replicas. This intelligent storage management reduces network traffic for frequently accessed data while maintaining efficient storage utilization. + +### Automated Version Management Systems + +Cozystack now includes automated version management systems for PostgreSQL, Kubernetes, MariaDB, and Redis applications. These systems automatically track upstream versions and provide mechanisms for automated version updates, ensuring that platform users always have access to the latest stable versions while maintaining compatibility with existing deployments. + +The version management systems integrate with the Cozystack API and dashboard, providing administrators with visibility into available versions and update paths. This infrastructure sets the foundation for future automated upgrade workflows and version compatibility management. + +--- + +## Major Features and Improvements + +### Storage + +* **[linstor] Add linstor-scheduler package**: Added LINSTOR scheduler extender for optimal pod placement on nodes with LINSTOR storage. Includes admission webhook that automatically routes pods using LINSTOR CSI volumes to the custom scheduler, ensuring pods are placed on nodes with local replicas to minimize network traffic and improve I/O performance ([**@kvaps**](https://github.com/kvaps) in #1824). +* **[linstor] Enable auto-diskful for diskless nodes**: Enabled DRBD auto-diskful functionality to automatically convert diskless nodes to diskful when they hold volumes in Primary state for more than 30 minutes. Improves I/O performance for long-running workloads by creating local replicas and includes automatic cleanup options to prevent storage waste ([**@kvaps**](https://github.com/kvaps) in #1826). +* **[linstor] Build linstor-server with custom patches**: Added custom patches to linstor-server build process, enabling platform-specific optimizations and fixes ([**@kvaps**](https://github.com/kvaps) in #1726). +* **[seaweedfs] Traffic locality**: Upgraded SeaweedFS to v4.05 with traffic locality capabilities, new admin component with web-based UI, worker component for distributed operations, and enhanced S3 monitoring with Grafana dashboards. Improves S3 service performance by routing requests to nearest available volume servers ([**@nbykov0**](https://github.com/nbykov0) in #1748). +* **[linstor] fix: prevent DRBD device race condition in updateDiscGran**: Fixed race condition in DRBD device management during granularity updates, preventing potential data corruption or device conflicts ([**@kvaps**](https://github.com/kvaps) in #1829). +* **fix(linstor): prevent orphaned DRBD devices during toggle-disk retry**: Fixed issue where retry logic during disk toggle operations could leave orphaned DRBD devices, now properly cleans up devices during retry attempts ([**@kvaps**](https://github.com/kvaps) in #1823). + +### Platform Architecture + +* **[platform] Replace Helm lookup with valuesFrom mechanism**: Replaced Helm lookup functions with FluxCD valuesFrom mechanism for configuration propagation. Configuration from ConfigMaps and namespace references is now managed through `cozystack-values` Secret, simplifying templates and eliminating force reconcile controllers ([**@kvaps**](https://github.com/kvaps) in #1787). +* **[platform] refactor: split cozystack-resource-definitions into separate packages**: Refactored cozystack-resource-definitions into separate packages for better organization and maintainability, improving code structure and reducing coupling between components ([**@kvaps**](https://github.com/kvaps) in #1778). +* **[platform] Separate assets server into dedicated deployment**: Separated assets server from main platform deployment, improving scalability and allowing independent scaling of asset delivery infrastructure ([**@kvaps**](https://github.com/kvaps) in #1705). +* **[core] Extract Talos package from installer**: Extracted Talos package configuration from installer into a separate package, improving modularity and enabling independent updates ([**@kvaps**](https://github.com/kvaps) in #1724). +* **[registry] Add application labels and update filtering mechanism**: Added application labels to registry resources and improved filtering mechanism for better resource discovery and organization ([**@kvaps**](https://github.com/kvaps) in #1707). +* **fix(registry): implement field selector filtering for label-based resources**: Implemented field selector filtering for label-based resources in the registry, improving query performance and resource lookup efficiency ([**@kvaps**](https://github.com/kvaps) in #1845). +* **[platform] Add alphabetical sorting to registry resource lists**: Added alphabetical sorting to registry resource lists in the API and dashboard, improving user experience when browsing available applications ([**@lexfrei**](https://github.com/lexfrei) in #1764). + +### Version Management + +* **[postgres] Add version management system with automated version updates**: Introduced version management system for PostgreSQL with automated version tracking and update mechanisms ([**@kvaps**](https://github.com/kvaps) in #1671). +* **[kubernetes] Add version management system with automated version updates**: Added version management system for Kubernetes tenant clusters with automated version tracking and update capabilities ([**@kvaps**](https://github.com/kvaps) in #1672). +* **[mariadb] Add version management system with automated version updates**: Implemented version management system for MariaDB with automated version tracking and update mechanisms ([**@kvaps**](https://github.com/kvaps) in #1680). +* **[redis] Add version management system with automated version updates**: Added version management system for Redis with automated version tracking and update capabilities ([**@kvaps**](https://github.com/kvaps) in #1681). + +### Networking + +* **[kube-ovn] Update to v1.14.25**: Updated Kube-OVN to version 1.14.25 with improved stability and new features ([**@kvaps**](https://github.com/kvaps) in #1819). +* **[kubeovn] Package from external repo**: Extracted Kube-OVN packaging from main repository to external repository, improving modularity ([**@lllamnyp**](https://github.com/lllamnyp) in #1535). +* **[cilium] Update Cilium to v1.18.5**: Updated Cilium to version 1.18.5 with latest features and bug fixes ([**@lexfrei**](https://github.com/lexfrei) in #1769). +* **[system/cilium] Enable topology-aware routing for services**: Enabled topology-aware routing for Cilium services, improving traffic distribution and reducing latency by routing traffic to endpoints in the same zone when possible ([**@nbykov0**](https://github.com/nbykov0) in #1734). +* **[cilium] Enable automatic pod rollout on configmap updates**: Cilium and Cilium operator pods now automatically restart when the cilium-config ConfigMap is updated, ensuring configuration changes are applied immediately ([**@kvaps**](https://github.com/kvaps) in #1728). +* **[kubernetes] Fix endpoints for cilium-gateway**: Fixed endpoint configuration for cilium-gateway, ensuring proper service discovery and connectivity ([**@kvaps**](https://github.com/kvaps) in #1729). +* **[multus] Increase memory limit**: Increased memory limits for Multus components to handle larger network configurations and reduce out-of-memory issues ([**@nbykov0**](https://github.com/nbykov0) in #1773). +* **[main][paas-full] Add multus dependencies similar to other CNIs**: Added Multus as a dependency in the paas-full package, consistent with how other CNIs are included ([**@nbykov0**](https://github.com/nbykov0) in #1842). + +### Virtual Machines + +* **[vm] Always expose VMs with a service**: Virtual machines are now always exposed with at least a ClusterIP service, ensuring they have in-cluster DNS names and can be accessed from other pods even without public IP addresses ([**@lllamnyp**](https://github.com/lllamnyp) in #1738). +* **[virtual-machine] Improve check for resizing job**: Improved storage resize logic to only expand persistent volume claims when storage is being increased, preventing unintended storage reduction operations ([**@kvaps**](https://github.com/kvaps) in #1688). +* **[virtual-machine,vm-instance] Add nodeAffinity for Windows VMs based on scheduling config**: Added nodeAffinity configuration to virtual-machine and vm-instance charts to support dedicated nodes for Windows VMs ([**@kvaps**](https://github.com/kvaps) in #1693). + +### Monitoring + +* **[monitoring] Add SLACK_SEVERITY_FILTER field and VMAgent for tenant monitoring**: Introduced SLACK_SEVERITY_FILTER environment variable in Alerta deployment to enable filtering of alert severities for Slack notifications. Added VMAgent resource template for scraping metrics within tenant namespaces, improving monitoring granularity ([**@IvanHunters**](https://github.com/IvanHunters) in #1712). +* **[monitoring] Improve tenant metrics collection**: Improved tenant metrics collection mechanisms for better observability and monitoring coverage ([**@IvanHunters**](https://github.com/IvanHunters) in #1684). + +### System Configuration + +* **[api, lineage] Tolerate all taints**: Updated API and lineage webhook to tolerate all taints, ensuring controllers can run on any node regardless of taint configuration ([**@nbykov0**](https://github.com/nbykov0) in #1781). +* **[system] Add resource requests and limits to etcd-defrag**: Added resource requests and limits to etcd-defrag job to ensure proper resource allocation and prevent resource contention ([**@matthieu-robin**](https://github.com/matthieu-robin) in #1785). +* **[system:coredns] update coredns app labels to match Talos coredns labels**: Updated coredns app labels to match Talos coredns labels, ensuring consistency across the platform ([**@nbykov0**](https://github.com/nbykov0) in #1675). +* **[system:monitoring-agents] rename coredns metrics service**: Renamed coredns metrics service to avoid interference with coredns service used for name resolution in tenant k8s clusters ([**@nbykov0**](https://github.com/nbykov0) in #1676). + +### Tenants and Namespaces + +* **[tenant] Allow egress to parent ingress pods**: Updated tenant network policies to allow egress traffic to parent cluster ingress pods, enabling proper communication patterns ([**@lexfrei**](https://github.com/lexfrei) in #1765). +* **[tenant] Run cleanup job from system namespace**: Moved tenant cleanup job to run from system namespace, improving security and resource isolation ([**@lllamnyp**](https://github.com/lllamnyp) in #1774). + +### FluxCD + +* **[fluxcd] Add flux-aio module and migration**: Added FluxCD all-in-one module with migration support, simplifying FluxCD installation and management ([**@kvaps**](https://github.com/kvaps) in #1698). +* **[fluxcd] Enable source-watcher**: Enabled source-watcher in FluxCD configuration for improved GitOps synchronization and faster update detection ([**@kvaps**](https://github.com/kvaps) in #1706). + +### Applications + +* **[dashboard] Fix CustomFormsOverride schema to nest properties under spec.properties**: Fixed CustomFormsOverride schema generation to properly nest properties under `spec.properties` instead of directly under `properties`, ensuring correct form schema generation ([**@kvaps**](https://github.com/kvaps) in #1692). +* **[apps] Refactor apiserver to use typed objects and fix UnstructuredList GVK**: Refactored apiserver REST handlers to use typed objects instead of unstructured.Unstructured, eliminating runtime conversions. Fixed UnstructuredList GVK issue where objects were using the first registered kind instead of the correct kind ([**@kvaps**](https://github.com/kvaps) in #1679). +* **[keycloak] Make kubernetes client public**: Made Kubernetes client public in Keycloak configuration, enabling broader access patterns for Kubernetes integrations ([**@lllamnyp**](https://github.com/lllamnyp) in #1802). + +## Improvements + +* **[granular kubernetes application extensions dependencies]**: Improved dependency management for Kubernetes application extensions with more granular control over dependencies ([**@nbykov0**](https://github.com/nbykov0) in #1683). +* **[core:installer] Address buildx warnings**: Fixed Dockerfile syntax warnings from buildx, ensuring clean builds without warnings ([**@nbykov0**](https://github.com/nbykov0) in #1682). +* **[linstor] Update piraeus-operator v2.10.2**: Updated LINSTOR CSI to version 2.10.2 with improved stability and bug fixes ([**@kvaps**](https://github.com/kvaps) in #1689). +* **Update SeaweedFS v4.02**: Updated SeaweedFS to version 4.02 with improved S3 daemon performance and fixes ([**@kvaps**](https://github.com/kvaps) in #1725). +* **[installer,dx] Rename cozypkg to cozyhr**: Renamed cozypkg tool to cozyhr for better branding and consistency ([**@kvaps**](https://github.com/kvaps) in #1763). + +## Fixes + +* **fix(platform): fix migrations for v0.40 release**: Fixed platform migrations for v0.40 release, ensuring smooth upgrades from previous versions ([**@kvaps**](https://github.com/kvaps) in #1846). +* **[platform] fix migration for removing fluxcd-operator**: Fixed migration logic for removing fluxcd-operator, ensuring clean removal without leaving orphaned resources ([**@kvaps**](https://github.com/kvaps) in commit 4a83d2c7). +* **[kubevirt-operator] Fix typo in VMNotRunningFor10Minutes alert**: Fixed typo in VM alert name, ensuring proper alert triggering and monitoring ([**@kvaps**](https://github.com/kvaps) in #1770). +* **[kubevirt-operator] Revert incorrect case change in VM alerts**: Reverted incorrect case change in VM alert names to maintain consistency ([**@lexfrei**](https://github.com/lexfrei) in #1804). +* **[cozystack-controller] Fix: move crds to definitions**: Fixed CRD placement by moving them to definitions directory, ensuring proper resource organization ([**@kvaps**](https://github.com/kvaps) in #1759). + +## Dependencies + +* **Update SeaweedFS v4.02**: Updated SeaweedFS to version 4.02 ([**@kvaps**](https://github.com/kvaps) in #1725). +* **[seaweedfs] Traffic locality**: Upgraded SeaweedFS to v4.05 with traffic locality capabilities ([**@nbykov0**](https://github.com/nbykov0) in #1748). +* **[linstor] Update piraeus-operator v2.10.2**: Updated piraeus-operator to version 2.10.2 ([**@kvaps**](https://github.com/kvaps) in #1689). +* **[kube-ovn] Update to v1.14.25**: Updated Kube-OVN to version 1.14.25 ([**@kvaps**](https://github.com/kvaps) in #1819). +* **[cilium] Update Cilium to v1.18.5**: Updated Cilium to version 1.18.5 ([**@lexfrei**](https://github.com/lexfrei) in #1769). +* **Update go modules**: Updated Go modules to latest versions ([**@kvaps**](https://github.com/kvaps) in #1736). + +## Development, Testing, and CI/CD + +* **[ci] Fix auto-release workflow**: Fixed auto-release workflow to ensure correct release publishing and tagging ([**@kvaps**](https://github.com/kvaps) in commit 526af294). +* **fix(ci): ensure correct latest release after backport publishing**: Fixed CI workflow to correctly identify and tag the latest release after backport publishing ([**@kvaps**](https://github.com/kvaps) in #1800). +* **[workflows] Add auto patch release workflow**: Added automated patch release workflow for streamlined release management ([**@kvaps**](https://github.com/kvaps) in #1754). +* **[workflow] Add GitHub Action to update release notes from changelogs**: Added GitHub Action to automatically update release notes from changelog files ([**@kvaps**](https://github.com/kvaps) in #1752). +* **[ci] Improve backport workflow with merge_commits skip and conflict resolution**: Improved backport workflow with better merge commit handling and conflict resolution ([**@kvaps**](https://github.com/kvaps) in #1694). +* **[testing] Add aliases and autocomplete**: Added shell aliases and autocomplete support for testing commands, improving developer experience ([**@lllamnyp**](https://github.com/lllamnyp) in #1803). +* **[kubernetes] Add lb tests for tenant k8s**: Added load balancer tests for tenant Kubernetes clusters, improving test coverage ([**@IvanHunters**](https://github.com/IvanHunters) in #1783). +* **[agents] Add instructions for working with unresolved code review comments**: Added documentation and instructions for working with unresolved code review comments in agent workflows ([**@kvaps**](https://github.com/kvaps) in #1710). +* **feat(ci): add /retest command to rerun tests from Prepare environment**: Added `/retest` command to rerun tests from Prepare environment workflow ([**@kvaps**](https://github.com/kvaps) in commit 30c1041e). +* **fix(ci): remove GITHUB_TOKEN extraheader to trigger workflows**: Removed GITHUB_TOKEN extraheader to properly trigger workflows ([**@kvaps**](https://github.com/kvaps) in commit 68a639b3). +* **Fix: Add missing components to `distro-full` bundle**: Fixed missing components in distro-full bundle, ensuring all required components are included ([**@LoneExile**](https://github.com/LoneExile) in #1620). +* **Update Flux Operator (v0.33.0)**: Updated Flux Operator to version 0.33.0 ([**@kingdonb**](https://github.com/kingdonb) in #1649). +* **Add changelogs for v0.38.3 and v.0.38.4**: Added missing changelogs for v0.38.3 and v0.38.4 releases ([**@androndo**](https://github.com/androndo) in #1743). +* **Add changelogs to v.0.39.1**: Added changelog for v0.39.1 release ([**@androndo**](https://github.com/androndo) in #1750). +* **Add Cloupard to ADOPTERS.md**: Added Cloupard to the adopters list ([**@SerjioTT**](https://github.com/SerjioTT) in #1733). + +## Documentation + +* **[website] docs: expand monitoring and alerting documentation**: Expanded monitoring and alerting documentation with comprehensive guides, examples, and troubleshooting information ([**@IvanHunters**](https://github.com/IvanHunters) in [cozystack/website#388](https://github.com/cozystack/website/pull/388)). +* **[website] fix auto-generation of documentation**: Fixed automatic documentation generation process, ensuring all documentation is properly generated and formatted ([**@IvanHunters**](https://github.com/IvanHunters) in [cozystack/website#391](https://github.com/cozystack/website/pull/391)). +* **[website] secure boot**: Added documentation for Secure Boot support in Talos Linux ([**@kvaps**](https://github.com/kvaps) in [cozystack/website#387](https://github.com/cozystack/website/pull/387)). + +## Tools + +* **[talm] feat(helpers): add bond interface discovery helpers**: Added bond interface discovery helpers to talm for easier network configuration ([**@kvaps**](https://github.com/kvaps) in [cozystack/talm#94](https://github.com/cozystack/talm/pull/94)). +* **[talm] feat(talosconfig): add certificate regeneration from secrets.yaml**: Added certificate regeneration functionality to talm talosconfig command, allowing certificates to be regenerated from secrets.yaml ([**@kvaps**](https://github.com/kvaps) in cozystack/talm@1319dde). +* **[talm] fix(init): make name optional for -u flag**: Made name parameter optional for init command with -u flag, improving flexibility ([**@kvaps**](https://github.com/kvaps) in cozystack/talm@da29320). +* **[talm] fix(wrapper): copy NoOptDefVal when remapping -f to -F flag**: Fixed wrapper to properly copy NoOptDefVal when remapping flags, ensuring correct default value handling ([**@kvaps**](https://github.com/kvaps) in cozystack/talm@f6a6f1d). +* **[talm] fix(root): detect project root with secrets.encrypted.yaml**: Fixed root detection to properly identify project root when secrets.encrypted.yaml is present ([**@kvaps**](https://github.com/kvaps) in cozystack/talm@cf56780). +* **[talm] Fix interfaces helper for Talos v1.12**: Fixed interfaces helper to work correctly with Talos v1.12 ([**@kvaps**](https://github.com/kvaps) in cozystack/talm@34984ae). +* **[talm] Fix typo on README.md**: Fixed typo in README documentation ([**@diegolakatos**](https://github.com/diegolakatos) in [cozystack/talm#92](https://github.com/cozystack/talm/pull/92)). +* **[talm] fix(template): return error for invalid YAML in template output**: Fixed template command to return proper error for invalid YAML output ([**@kvaps**](https://github.com/kvaps) in [cozystack/talm#93](https://github.com/cozystack/talm/pull/93)). +* **[talm] feat(cozystack): enable allocateNodeCIDRs by default**: Enabled allocateNodeCIDRs by default in talm cozystack preset ([**@lexfrei**](https://github.com/lexfrei) in [cozystack/talm#91](https://github.com/cozystack/talm/pull/91)). +* **[boot-to-talos] feat(network): add VLAN interface support via netlink**: Added VLAN interface support via netlink in boot-to-talos for advanced network configuration ([**@kvaps**](https://github.com/kvaps) in cozystack/boot-to-talos@02874d7). +* **[boot-to-talos] feat(network): add bond interface support via netlink**: Added bond interface support via netlink in boot-to-talos for network bonding configurations ([**@kvaps**](https://github.com/kvaps) in cozystack/boot-to-talos@067822d). +* **[boot-to-talos] Draft EFI Support**: Added draft EFI support in boot-to-talos for UEFI boot scenarios ([**@kvaps**](https://github.com/kvaps) in cozystack/boot-to-talos@e194bc8). +* **[boot-to-talos] Change default install image size from 2GB to 3GB**: Changed default install image size from 2GB to 3GB to accommodate larger installations ([**@kvaps**](https://github.com/kvaps) in cozystack/boot-to-talos@3bfb035). +* **[cozyhr] feat(values): add valuesFrom support for HelmRelease**: Added valuesFrom support for HelmRelease in cozyhr tool, enabling better configuration management ([**@kvaps**](https://github.com/kvaps) in cozystack/cozyhr@7dff0c8). +* **[cozyhr] Rename cozypkg to cozyhr**: Renamed cozypkg tool to cozyhr for better branding ([**@kvaps**](https://github.com/kvaps) in cozystack/cozyhr@1029461). + +--- + +## Contributors + +We'd like to thank all contributors who made this release possible: + +* [**@IvanHunters**](https://github.com/IvanHunters) +* [**@kvaps**](https://github.com/kvaps) +* [**@lexfrei**](https://github.com/lexfrei) +* [**@lllamnyp**](https://github.com/lllamnyp) +* [**@nbykov0**](https://github.com/nbykov0) +* [**@LoneExile**](https://github.com/LoneExile) +* [**@kingdonb**](https://github.com/kingdonb) +* [**@androndo**](https://github.com/androndo) +* [**@SerjioTT**](https://github.com/SerjioTT) +* [**@matthieu-robin**](https://github.com/matthieu-robin) +* [**@diegolakatos**](https://github.com/diegolakatos) + +--- + +**Full Changelog**: [v0.39.0...v0.40.0](https://github.com/cozystack/cozystack/compare/v0.39.0...v0.40.0) + + + From dc2773ba267ee82ec5128f3c49d57912483ae0d0 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Mon, 12 Jan 2026 15:31:46 +0100 Subject: [PATCH 041/889] [linstor] Update piraeus-server patches with critical fixes Update piraeus-server patches to address critical production issues: - Add fix-duplicate-tcp-ports.diff to prevent duplicate TCP ports after toggle-disk operations (upstream PR #476) - Update skip-adjust-when-device-inaccessible.diff with comprehensive fixes for resources stuck in StandAlone after reboot, Unknown state race condition, and encrypted LUKS resource deletion (upstream PR #477) ```release-note [linstor] Fix DRBD resources stuck in StandAlone state after reboot and encrypted resource deletion issues ``` Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- .../images/piraeus-server/patches/README.md | 6 +- .../patches/fix-duplicate-tcp-ports.diff | 87 ++++++++ .../skip-adjust-when-device-inaccessible.diff | 192 +++++++++++++++--- 3 files changed, 258 insertions(+), 27 deletions(-) create mode 100644 packages/system/linstor/images/piraeus-server/patches/fix-duplicate-tcp-ports.diff diff --git a/packages/system/linstor/images/piraeus-server/patches/README.md b/packages/system/linstor/images/piraeus-server/patches/README.md index c219eaf3..5ee29318 100644 --- a/packages/system/linstor/images/piraeus-server/patches/README.md +++ b/packages/system/linstor/images/piraeus-server/patches/README.md @@ -8,5 +8,7 @@ Custom patches for piraeus-server (linstor-server) v1.32.3. - 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) +- **fix-duplicate-tcp-ports.diff** — Prevent duplicate TCP ports after toggle-disk operations + - Upstream: [#476](https://github.com/LINBIT/linstor-server/pull/476) +- **skip-adjust-when-device-inaccessible.diff** — Fix resources stuck in StandAlone after reboot, Unknown state race condition, and encrypted resource deletion + - Upstream: [#477](https://github.com/LINBIT/linstor-server/pull/477) diff --git a/packages/system/linstor/images/piraeus-server/patches/fix-duplicate-tcp-ports.diff b/packages/system/linstor/images/piraeus-server/patches/fix-duplicate-tcp-ports.diff new file mode 100644 index 00000000..07cd9eac --- /dev/null +++ b/packages/system/linstor/images/piraeus-server/patches/fix-duplicate-tcp-ports.diff @@ -0,0 +1,87 @@ +From 1250abe99d64a0501795e37d3b6af62410002239 Mon Sep 17 00:00:00 2001 +From: Andrei Kvapil +Date: Mon, 12 Jan 2026 13:44:46 +0100 +Subject: [PATCH] fix(drbd): prevent duplicate TCP ports after toggle-disk + operations + +Remove redundant ensureStackDataExists() call with empty payload from +resetStoragePools() method that was causing TCP port conflicts after +toggle-disk operations. + +Root Cause: +----------- +The resetStoragePools() method, introduced in 2019 (commit 95cc17d0b8), +calls ensureStackDataExists() with an empty LayerPayload. This worked +correctly when TCP ports were stored at RscDfn level. + +After the TCP port migration to per-node level (commit f754943463, May +2025), this empty payload results in DrbdRscData being created without +TCP ports assigned. The controller then sends a Pojo with an empty port +Set to satellites. + +On satellites, when DrbdRscData is initialized with an empty port list, +initPorts() uses preferredNewPortsRef from peer resources. Since +SatelliteDynamicNumberPool.tryAllocate() always returns true (no-op), +any port from preferredNewPortsRef is accepted without conflict checking, +leading to duplicate TCP port assignments. + +Impact: +------- +This regression affects toggle-disk operations, particularly: +- Snapshot creation/restore operations +- Manual toggle-disk operations +- Any operation calling resetStoragePools() + +Symptoms include: +- DRBD resources failing to adjust with "port is also used" errors +- Resources stuck in StandAlone or Connecting states +- Multiple resources on the same node using identical TCP ports + +Solution: +--------- +Remove the ensureStackDataExists() call from resetStoragePools() as it +is redundant. The calling code (e.g., CtrlRscToggleDiskApiCallHandler +line 1071) already invokes ensureStackDataExists() with the correct +payload immediately after resetStoragePools(). + +This fix ensures: +1. resetStoragePools() only resets storage pool assignments +2. Layer data creation with proper TCP ports happens via the caller's + ensureStackDataExists() with correct payload +3. No DrbdRscData objects are created without TCP port assignments + +Related Issues: +--------------- +Fixes #454 - Duplicate TCP ports after backup/restore operations +Related to user reports of resources stuck in StandAlone after node +reboots when toggle-disk or backup operations were in progress. + +Testing: +-------- +Verified that: +- Toggle-disk operations no longer create resources without TCP ports +- Backup/restore operations complete without TCP port conflicts +- Resources maintain unique TCP ports across toggle-disk cycles + +Co-Authored-By: Claude +Signed-off-by: Andrei Kvapil +--- + .../linbit/linstor/layer/resource/CtrlRscLayerDataFactory.java | 2 -- + 1 file changed, 2 deletions(-) + +diff --git a/controller/src/main/java/com/linbit/linstor/layer/resource/CtrlRscLayerDataFactory.java b/controller/src/main/java/com/linbit/linstor/layer/resource/CtrlRscLayerDataFactory.java +index 3538b380c..4f589145e 100644 +--- a/controller/src/main/java/com/linbit/linstor/layer/resource/CtrlRscLayerDataFactory.java ++++ b/controller/src/main/java/com/linbit/linstor/layer/resource/CtrlRscLayerDataFactory.java +@@ -276,8 +276,6 @@ public class CtrlRscLayerDataFactory + + rscDataToProcess.addAll(rscData.getChildren()); + } +- +- ensureStackDataExists(rscRef, null, new LayerPayload()); + } + catch (AccessDeniedException exc) + { +-- +2.39.5 (Apple Git-154) + 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 index 18399c41..65c8be7c 100644 --- 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 @@ -1,30 +1,27 @@ -diff --git a/satellite/src/main/java/com/linbit/linstor/core/devmgr/DeviceHandlerImpl.java b/satellite/src/main/java/com/linbit/linstor/core/devmgr/DeviceHandlerImpl.java -index abc123def..def456abc 100644 ---- a/satellite/src/main/java/com/linbit/linstor/core/devmgr/DeviceHandlerImpl.java -+++ b/satellite/src/main/java/com/linbit/linstor/core/devmgr/DeviceHandlerImpl.java -@@ -83,6 +83,8 @@ import java.util.TreeMap; - import java.util.TreeSet; - import java.util.concurrent.atomic.AtomicBoolean; - import java.util.function.Function; -+import java.nio.file.Files; -+import java.nio.file.Paths; +From 6a556821b9a0996d34389a27b941694ce810a44c Mon Sep 17 00:00:00 2001 +From: Andrei Kvapil +Date: Mon, 12 Jan 2026 14:26:57 +0100 +Subject: [PATCH 1/3] Fix: Skip DRBD adjust/res file regeneration when child + layer device is inaccessible + +When deleting encrypted (LUKS) resources, the LUKS layer may close its device +before the DRBD layer attempts to adjust the resource or regenerate the res +file. This causes 'Failed to adjust DRBD resource' errors. + +This fix adds checks before regenerateResFile() and drbdUtils.adjust() +to verify that child layer devices are accessible. If a child device doesn't +exist or is not accessible (e.g., LUKS device is closed during resource +deletion), these operations are skipped and the adjustRequired flag is cleared, +allowing resource deletion to proceed successfully. + +Co-Authored-By: Claude +Signed-off-by: Andrei Kvapil +--- + .../linbit/linstor/layer/drbd/DrbdLayer.java | 72 ++++++++++++++++--- + 1 file changed, 61 insertions(+), 11 deletions(-) - @Singleton - public class DeviceHandlerImpl implements DeviceHandler -@@ -1646,7 +1648,10 @@ public class DeviceHandlerImpl implements DeviceHandler - private void updateDiscGran(VlmProviderObject vlmData) throws DatabaseException, StorageException - { - String devicePath = vlmData.getDevicePath(); -- if (devicePath != null && vlmData.exists()) -+ // Check if device path physically exists before calling lsblk -+ // This is important for DRBD devices which might be temporarily unavailable during adjust -+ // (drbdadm adjust brings devices down/up, and kernel might not have created the device node yet) -+ if (devicePath != null && vlmData.exists() && Files.exists(Paths.get(devicePath))) - { - if (vlmData.getDiscGran() == VlmProviderObject.UNINITIALIZED_SIZE) - { 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 +index 01967a31f..871d830d1 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 @@ -116,3 +113,148 @@ index 01967a3..871d830 100644 } } +-- +2.39.5 (Apple Git-154) + + +From afe51ea674c4a350c27d1f2cacfecf6fe42b8a7a Mon Sep 17 00:00:00 2001 +From: Andrei Kvapil +Date: Mon, 12 Jan 2026 14:27:52 +0100 +Subject: [PATCH 2/3] fix(satellite): skip lsblk when device path doesn't + physically exist + +Add physical device path existence check before calling lsblk in updateDiscGran(). +This prevents race condition when drbdadm adjust temporarily brings devices down/up +and the kernel hasn't created the device node yet. + +Issue: After satellite restart with patched code, some DRBD resources ended up in +Unknown state because: +1. drbdadm adjust successfully completes (brings devices up) +2. updateDiscGran() immediately tries to check discard granularity +3. /dev/drbd* device node doesn't exist yet (kernel hasn't created it) +4. lsblk fails with exit code 32 "not a block device" +5. StorageException interrupts DeviceManager cycle +6. DRBD device remains in incomplete state + +Solution: Check Files.exists(devicePath) before calling lsblk. If device doesn't +exist yet, skip the check - it will be retried in the next DeviceManager cycle +when the device node is available. + +Co-Authored-By: Claude +Signed-off-by: Andrei Kvapil +--- + .../linbit/linstor/core/devmgr/DeviceHandlerImpl.java | 9 +++++++-- + 1 file changed, 7 insertions(+), 2 deletions(-) + +diff --git a/satellite/src/main/java/com/linbit/linstor/core/devmgr/DeviceHandlerImpl.java b/satellite/src/main/java/com/linbit/linstor/core/devmgr/DeviceHandlerImpl.java +index 49138a8fd..1c13cfc9d 100644 +--- a/satellite/src/main/java/com/linbit/linstor/core/devmgr/DeviceHandlerImpl.java ++++ b/satellite/src/main/java/com/linbit/linstor/core/devmgr/DeviceHandlerImpl.java +@@ -68,6 +68,8 @@ import javax.inject.Inject; + import javax.inject.Provider; + import javax.inject.Singleton; + ++import java.nio.file.Files; ++import java.nio.file.Paths; + import java.util.ArrayList; + import java.util.Collection; + import java.util.Collections; +@@ -1645,8 +1647,11 @@ public class DeviceHandlerImpl implements DeviceHandler + + private void updateDiscGran(VlmProviderObject vlmData) throws DatabaseException, StorageException + { +- String devicePath = vlmData.getDevicePath(); +- if (devicePath != null && vlmData.exists()) ++ @Nullable String devicePath = vlmData.getDevicePath(); ++ // Check if device path physically exists before calling lsblk ++ // This is important for DRBD devices which might be temporarily unavailable during adjust ++ // (drbdadm adjust brings devices down/up, and kernel might not have created the device node yet) ++ if (devicePath != null && vlmData.exists() && Files.exists(Paths.get(devicePath))) + { + if (vlmData.getDiscGran() == VlmProviderObject.UNINITIALIZED_SIZE) + { +-- +2.39.5 (Apple Git-154) + + +From de1f22e7c008c5479f85a3b1ebdf8461944210f4 Mon Sep 17 00:00:00 2001 +From: Andrei Kvapil +Date: Mon, 12 Jan 2026 14:28:23 +0100 +Subject: [PATCH 3/3] fix(drbd): only check child devices when disk access is + actually needed +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The previous implementation blocked `drbdadm adjust` whenever child +device paths were unavailable, even for operations that don't require +disk access (like network reconnect from StandAlone to Connected). + +After node reboot, DRBD resources often remain in StandAlone state +because: +1. updateResourceToCurrentDrbdState() correctly detects StandAlone + and sets adjustRequired=true +2. However, canAdjust check fails because child volumes may have + devicePath=null (due to INACTIVE flag, cloning state, or + initialization race) +3. adjust is skipped → adjustRequired=false → resources stay StandAlone + +Root cause: The canAdjust check was added to protect LUKS deletion +scenarios but was applied to ALL cases, including network reconnect +which doesn't need disk access. + +Fix: Check child device accessibility only when disk access is actually +required (volume creation, resize, metadata operations). Network +reconnect operations (StandAlone → Connected) now proceed without +checking child devices. + +This ensures automatic DRBD reconnection after reboot while preserving +protection for LUKS deletion scenarios. + +Co-Authored-By: Claude +Signed-off-by: Andrei Kvapil +--- + .../linbit/linstor/layer/drbd/DrbdLayer.java | 27 ++++++++++++++++++- + 1 file changed, 26 insertions(+), 1 deletion(-) + +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 871d830d1..78b8195a4 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 +@@ -792,7 +792,32 @@ public class DrbdLayer implements DeviceLayer + // This is important for encrypted resources (LUKS) where the device + // might be closed during deletion + boolean canAdjust = true; +- if (!skipDisk && !drbdRscData.getAbsResource().isDrbdDiskless(workerCtx)) ++ ++ // IMPORTANT: Check child volumes only when disk access is actually needed. ++ // For network reconnect (StandAlone -> Connected), disk access is not required. ++ boolean needsDiskAccess = false; ++ ++ // Check if there are pending operations that require disk access ++ for (DrbdVlmData drbdVlmData : drbdRscData.getVlmLayerObjects().values()) ++ { ++ Volume vlm = (Volume) drbdVlmData.getVolume(); ++ StateFlags vlmFlags = vlm.getFlags(); ++ ++ // Disk access is needed if: ++ // - creating a new volume ++ // - resizing ++ // - checking/creating metadata ++ if (!drbdVlmData.exists() || ++ drbdVlmData.checkMetaData() || ++ vlmFlags.isSomeSet(workerCtx, Volume.Flags.RESIZE, Volume.Flags.DRBD_RESIZE)) ++ { ++ needsDiskAccess = true; ++ break; ++ } ++ } ++ ++ // Check child volumes only if disk access is actually needed ++ if (needsDiskAccess && !skipDisk && !drbdRscData.getAbsResource().isDrbdDiskless(workerCtx)) + { + AbsRscLayerObject dataChild = drbdRscData.getChildBySuffix(RscLayerSuffixes.SUFFIX_DATA); + if (dataChild != null) +-- +2.39.5 (Apple Git-154) + From 74589ce9151706ee6687ad7b0233eafcbb987297 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Mon, 12 Jan 2026 22:24:54 +0100 Subject: [PATCH 042/889] fix(linstor): remove node-level RWX validation Remove node-level RWX block validation from linstor-csi as controller-level check is sufficient. The controller already validates that all pods attached to RWX block volume belong to the same VM by extracting vmName from pod owner references (VirtualMachineInstance). This simplifies the validation logic and fixes VM live migration issues. Update linstor-csi image tag with rebuilt image containing the fix. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- .../patches/001-rwx-validation.diff | 62 ++----------------- 1 file changed, 4 insertions(+), 58 deletions(-) diff --git a/packages/system/linstor/images/linstor-csi/patches/001-rwx-validation.diff b/packages/system/linstor/images/linstor-csi/patches/001-rwx-validation.diff index f6f0d83d..9c26f565 100644 --- a/packages/system/linstor/images/linstor-csi/patches/001-rwx-validation.diff +++ b/packages/system/linstor/images/linstor-csi/patches/001-rwx-validation.diff @@ -1,25 +1,8 @@ diff --git a/pkg/driver/driver.go b/pkg/driver/driver.go -index bea69a8..848bd5b 100644 +index bea69a8..69e71a6 100644 --- a/pkg/driver/driver.go +++ b/pkg/driver/driver.go -@@ -401,6 +401,16 @@ func (d Driver) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolum - } - } - -+ // Validate RWX block volumes on node side using local filesystem check -+ // This provides protection for the edge case where two pods from different VMs land on the same node -+ if req.GetVolumeCapability().GetBlock() != nil && -+ req.GetVolumeCapability().GetAccessMode().GetMode() == csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER { -+ if err := d.validateRWXBlockOnNode(ctx, req.GetVolumeId(), req.GetTargetPath()); err != nil { -+ return nil, status.Errorf(codes.FailedPrecondition, -+ "NodePublishVolume failed for %s: %v", req.GetVolumeId(), err) -+ } -+ } -+ - if block := req.GetVolumeCapability().GetBlock(); block != nil { - volCtx.MountOptions = []string{"bind"} - } -@@ -707,6 +717,255 @@ func (d Driver) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest) +@@ -707,6 +707,219 @@ func (d Driver) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest) return &csi.DeleteVolumeResponse{}, nil } @@ -235,54 +218,17 @@ index bea69a8..848bd5b 100644 + + return vmName, nil +} -+ -+// validateRWXBlockOnNode performs node-side validation of RWX block volumes using local filesystem check. -+// This provides protection against the edge case where two pods from different VMs land on the same node. -+// Since ControllerPublishVolume is called only once per (volumeID, nodeID) pair, not per pod, -+// we need to check if the volume is already mounted for another pod on this node. -+func (d Driver) validateRWXBlockOnNode(ctx context.Context, volumeID, targetPath string) error { -+ // Extract base directory for this volume (contains subdirectories per pod UID) -+ baseDir := filepath.Dir(targetPath) -+ -+ // List existing mounts for this volume -+ entries, err := os.ReadDir(baseDir) -+ if err != nil { -+ if os.IsNotExist(err) { -+ // Directory doesn't exist yet - first mount -+ return nil -+ } -+ -+ d.log.WithError(err).Warn("cannot check existing mounts for RWX validation") -+ -+ return nil -+ } -+ -+ // If there are already other mounts, block the second one -+ if len(entries) > 0 { -+ d.log.WithFields(logrus.Fields{ -+ "volumeID": volumeID, -+ "existingMounts": len(entries), -+ "baseDir": baseDir, -+ }).Warn("blocking RWX block volume mount: already mounted for another pod on this node") -+ -+ return fmt.Errorf("RWX block volume is already mounted for another pod on this node - " + -+ "multiple pods on the same node sharing a block device is not supported (only for live migration across nodes)") -+ } -+ -+ return nil -+} + // ControllerPublishVolume https://github.com/container-storage-interface/spec/blob/v1.9.0/spec.md#controllerpublishvolume func (d Driver) ControllerPublishVolume(ctx context.Context, req *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error) { if req.GetVolumeId() == "" { -@@ -751,6 +1010,15 @@ func (d Driver) ControllerPublishVolume(ctx context.Context, req *csi.Controller +@@ -751,6 +964,14 @@ func (d Driver) ControllerPublishVolume(ctx context.Context, req *csi.Controller // ReadWriteMany block volume rwxBlock := req.VolumeCapability.AccessMode.GetMode() == csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER && req.VolumeCapability.GetBlock() != nil + // Validate RWX block attachment to prevent misuse of allow-two-primaries + if rwxBlock { -+ _, err := d.validateRWXBlockAttachment(ctx, req.GetVolumeId()) -+ if err != nil { ++ if _, err := d.validateRWXBlockAttachment(ctx, req.GetVolumeId()); err != nil { + return nil, status.Errorf(codes.FailedPrecondition, + "ControllerPublishVolume failed for %s: %v", req.GetVolumeId(), err) + } From dececc85878c55797ee3859968731e0cc20c3a6a Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 13 Jan 2026 08:04:35 +0100 Subject: [PATCH 043/889] Release v0.40.1 (#1855) This PR prepares the release `v0.40.1`. Signed-off-by: Andrei Kvapil --- packages/apps/kubernetes/images/cluster-autoscaler.tag | 2 +- packages/core/installer/values.yaml | 2 +- packages/core/platform/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/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/lineage-controller-webhook/values.yaml | 2 +- packages/system/linstor/values.yaml | 3 +-- packages/system/objectstorage-controller/values.yaml | 2 +- packages/system/seaweedfs/values.yaml | 2 +- 17 files changed, 21 insertions(+), 22 deletions(-) diff --git a/packages/apps/kubernetes/images/cluster-autoscaler.tag b/packages/apps/kubernetes/images/cluster-autoscaler.tag index d5ab33d1..03a5ad61 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:6f2b1d6b0b2bdc66f1cbb30c59393369cbf070cb8f5fec748f176952273483cc diff --git a/packages/core/installer/values.yaml b/packages/core/installer/values.yaml index 0fcf6c25..61870b23 100644 --- a/packages/core/installer/values.yaml +++ b/packages/core/installer/values.yaml @@ -1,5 +1,5 @@ cozystack: - image: ghcr.io/cozystack/cozystack/installer:v0.38.2@sha256:9ff92b655de6f9bea3cba4cd42dcffabd9aace6966dcfb1cc02dda2420ea4a15 + image: ghcr.io/cozystack/cozystack/installer:v0.40.1@sha256:69a8f95b8ddcf122a8859a957017b3d9e33c5eca3bfbf30c112c109e355f4f1c cozystackOperator: enabled: false image: ghcr.io/cozystack/cozystack/cozystack-operator:latest@sha256:f7f6e0fd9e896b7bfa642d0bfa4378bc14e646bc5c2e86e2e09a82770ef33181 diff --git a/packages/core/platform/values.yaml b/packages/core/platform/values.yaml index 363d1921..84efcb78 100644 --- a/packages/core/platform/values.yaml +++ b/packages/core/platform/values.yaml @@ -1,2 +1,2 @@ assets: - image: ghcr.io/cozystack/cozystack/cozystack-assets:latest@sha256:19b166819d0205293c85d8351a3e038dc4c146b876a8e2ae21dce1d54f0b9e33 + image: ghcr.io/cozystack/cozystack/cozystack-assets:v0.40.1@sha256:ee1248692753820fe11139789680fd24dc4dbdba85a6cb70e086447d9b9b1d99 diff --git a/packages/core/testing/values.yaml b/packages/core/testing/values.yaml index de6bb330..90a9ed18 100644 --- 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.40.1@sha256:78a556a059411cac89249c2e22596223128a1c74fbfcac4787fd74e4a66f66a6 diff --git a/packages/extra/bootbox/images/matchbox.tag b/packages/extra/bootbox/images/matchbox.tag index 2e63d30b..aa8ecbfd 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.40.1@sha256:293d562c134fc4ee0a9a756df32422b849715fe25a2120b69cb415ca98217363 diff --git a/packages/extra/seaweedfs/images/objectstorage-sidecar.tag b/packages/extra/seaweedfs/images/objectstorage-sidecar.tag index ec59113f..1724acc3 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.40.1@sha256:4bb47c8adb34543403a16d1ff61b307939850e8cf5fc365388469e30dfb9681b diff --git a/packages/system/cozystack-api/values.yaml b/packages/system/cozystack-api/values.yaml index 8bf0a803..10a79b25 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.40.1@sha256:408885b509d80ca30a85416f87d07112bc7f070374e71e80b64818fbb24ad1ee localK8sAPIEndpoint: enabled: true replicas: 2 diff --git a/packages/system/cozystack-controller/values.yaml b/packages/system/cozystack-controller/values.yaml index 1b365a43..a28c13c5 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.40.1@sha256:75636007f63e1b2cb51fc2e70782b707330d1e728c32642a4edc500b614f0781 debug: false disableTelemetry: false - cozystackVersion: "v0.38.2" + cozystackVersion: "v0.40.1" cozystackAPIKind: "DaemonSet" diff --git a/packages/system/dashboard/templates/configmap.yaml b/packages/system/dashboard/templates/configmap.yaml index f148109f..861d4eed 100644 --- a/packages/system/dashboard/templates/configmap.yaml +++ b/packages/system/dashboard/templates/configmap.yaml @@ -1,6 +1,6 @@ {{- $brandingConfig := .Values._cluster.branding | default dict }} -{{- $tenantText := "v0.38.2" }} +{{- $tenantText := "v0.40.1" }} {{- $footerText := "Cozystack" }} {{- $titleText := "Cozystack Dashboard" }} {{- $logoText := "" }} diff --git a/packages/system/dashboard/values.yaml b/packages/system/dashboard/values.yaml index fed8149b..94c395bb 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.40.1@sha256:a427114308b9c87ab71b50980cba0d673a730791629164335d2046f7d1740327 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.40.1@sha256:fda379dce49c2cd8cb8d7d2a1d8ec6f7bedb3419c058c4355ecdece1c1e937f4 tokenProxy: - image: ghcr.io/cozystack/cozystack/token-proxy:v0.38.2@sha256:fad27112617bb17816702571e1f39d0ac3fe5283468d25eb12f79906cdab566b + image: ghcr.io/cozystack/cozystack/token-proxy:v0.40.1@sha256:4fc8a11f8a1a81aa0774ae2b1ed2e05d36d0b3ef1e37979cc4994e65114d93ae diff --git a/packages/system/kamaji/values.yaml b/packages/system/kamaji/values.yaml index b5b77213..35c15ef9 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.40.1@sha256:0bcfb2d376224b18a0627d7b18ba6202bb8a553f71796023e12740d9513740c7 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.40.1@sha256:0bcfb2d376224b18a0627d7b18ba6202bb8a553f71796023e12740d9513740c7 diff --git a/packages/system/kubeovn-plunger/values.yaml b/packages/system/kubeovn-plunger/values.yaml index 2e6fc195..10d8508a 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.40.1@sha256:2a17dfa8eec0fc6b2adb564742e74f35bd68ac0d29e5ab1d007496aff3c0e97d ovnCentralName: ovn-central diff --git a/packages/system/kubeovn-webhook/values.yaml b/packages/system/kubeovn-webhook/values.yaml index 9304c59d..c60ff144 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.40.1@sha256:e6334c29d3aaf0dea766c88e3e05b53ad623d1bb497b3c836e6f76adade45b29 diff --git a/packages/system/lineage-controller-webhook/values.yaml b/packages/system/lineage-controller-webhook/values.yaml index ab5ccbbf..55cb019b 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.40.1@sha256:00650eb92c8b93a0afc382a0c5e46ade453e430867be7dab9647a6fc2896f097 debug: false localK8sAPIEndpoint: enabled: true diff --git a/packages/system/linstor/values.yaml b/packages/system/linstor/values.yaml index 470cf2e5..173aacb4 100644 --- a/packages/system/linstor/values.yaml +++ b/packages/system/linstor/values.yaml @@ -1,8 +1,7 @@ piraeusServer: image: repository: ghcr.io/cozystack/cozystack/piraeus-server - tag: latest@sha256:417532baa2801288147cd9ac9ae260751c1a7754f0b829725d09b72a770c111a - + tag: 1.32.3@sha256:1138c8dc0a117360ef70e2e2ab97bc2696419b63f46358f7668c7e01a96c419b linstor: autoDiskful: enabled: true diff --git a/packages/system/objectstorage-controller/values.yaml b/packages/system/objectstorage-controller/values.yaml index 7a5b5c22..aba36be8 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.40.1@sha256:e8f605a5ec4b801dfaf49aa7bce8d7a1ce28df578e23a0b859571f60f98efe8c" diff --git a/packages/system/seaweedfs/values.yaml b/packages/system/seaweedfs/values.yaml index 830c4faa..fb3defa6 100644 --- a/packages/system/seaweedfs/values.yaml +++ b/packages/system/seaweedfs/values.yaml @@ -177,7 +177,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.40.1@sha256:4bb47c8adb34543403a16d1ff61b307939850e8cf5fc365388469e30dfb9681b" certificates: commonName: "SeaweedFS CA" ipAddresses: [] From 3c4f0cd952e91ec54ddb26aaae2f550ebd93a320 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 13 Jan 2026 13:18:13 +0100 Subject: [PATCH 044/889] [linstor] Refactor node-level RWX validation Signed-off-by: Andrei Kvapil --- .../patches/001-rwx-validation.diff | 386 ++++++++++++------ 1 file changed, 253 insertions(+), 133 deletions(-) diff --git a/packages/system/linstor/images/linstor-csi/patches/001-rwx-validation.diff b/packages/system/linstor/images/linstor-csi/patches/001-rwx-validation.diff index 9c26f565..7da0f137 100644 --- a/packages/system/linstor/images/linstor-csi/patches/001-rwx-validation.diff +++ b/packages/system/linstor/images/linstor-csi/patches/001-rwx-validation.diff @@ -1,100 +1,202 @@ +diff --git a/cmd/linstor-csi/linstor-csi.go b/cmd/linstor-csi/linstor-csi.go +index 143f6cee..bd28e06e 100644 +--- a/cmd/linstor-csi/linstor-csi.go ++++ b/cmd/linstor-csi/linstor-csi.go +@@ -41,22 +41,23 @@ import ( + + func main() { + var ( +- lsEndpoint = flag.String("linstor-endpoint", "", "Controller API endpoint for LINSTOR") +- lsSkipTLSVerification = flag.Bool("linstor-skip-tls-verification", false, "If true, do not verify tls") +- csiEndpoint = flag.String("csi-endpoint", "unix:///var/lib/kubelet/plugins/linstor.csi.linbit.com/csi.sock", "CSI endpoint") +- node = flag.String("node", "", "Node ID to pass to node service") +- logLevel = flag.String("log-level", "info", "Enable debug log output. Choose from: panic, fatal, error, warn, info, debug") +- rps = flag.Float64("linstor-api-requests-per-second", 0, "Maximum allowed number of LINSTOR API requests per second. Default: Unlimited") +- burst = flag.Int("linstor-api-burst", 1, "Maximum number of API requests allowed before being limited by requests-per-second. Default: 1 (no bursting)") +- bearerTokenFile = flag.String("bearer-token", "", "Read the bearer token from the given file and use it for authentication.") +- propNs = flag.String("property-namespace", linstor.NamespcAuxiliary, "Limit the reported topology keys to properties from the given namespace.") +- labelBySP = flag.Bool("label-by-storage-pool", true, "Set to false to disable labeling of nodes based on their configured storage pools.") +- nodeCacheTimeout = flag.Duration("node-cache-timeout", 1*time.Minute, "Duration for which the results of node and storage pool related API responses should be cached.") +- resourceCacheTimeout = flag.Duration("resource-cache-timeout", 30*time.Second, "Duration for which the results of resource related API responses should be cached.") +- resyncAfter = flag.Duration("resync-after", 5*time.Minute, "Duration after which reconciliations (such as for VolumeSnapshotClasses) should be rerun. Set to 0 to disable.") +- enableRWX = flag.Bool("enable-rwx", false, "Enable RWX support via NFS (requires running in Kubernetes).") +- namespace = flag.String("nfs-service-namespace", "", "The namespace the NFS service is running in.") +- reactorConfigMapName = flag.String("nfs-reactor-config-map-name", "linstor-csi-nfs-reactor-config", "Name of the config map used to store promoter configuration") ++ lsEndpoint = flag.String("linstor-endpoint", "", "Controller API endpoint for LINSTOR") ++ lsSkipTLSVerification = flag.Bool("linstor-skip-tls-verification", false, "If true, do not verify tls") ++ csiEndpoint = flag.String("csi-endpoint", "unix:///var/lib/kubelet/plugins/linstor.csi.linbit.com/csi.sock", "CSI endpoint") ++ node = flag.String("node", "", "Node ID to pass to node service") ++ logLevel = flag.String("log-level", "info", "Enable debug log output. Choose from: panic, fatal, error, warn, info, debug") ++ rps = flag.Float64("linstor-api-requests-per-second", 0, "Maximum allowed number of LINSTOR API requests per second. Default: Unlimited") ++ burst = flag.Int("linstor-api-burst", 1, "Maximum number of API requests allowed before being limited by requests-per-second. Default: 1 (no bursting)") ++ bearerTokenFile = flag.String("bearer-token", "", "Read the bearer token from the given file and use it for authentication.") ++ propNs = flag.String("property-namespace", linstor.NamespcAuxiliary, "Limit the reported topology keys to properties from the given namespace.") ++ labelBySP = flag.Bool("label-by-storage-pool", true, "Set to false to disable labeling of nodes based on their configured storage pools.") ++ nodeCacheTimeout = flag.Duration("node-cache-timeout", 1*time.Minute, "Duration for which the results of node and storage pool related API responses should be cached.") ++ resourceCacheTimeout = flag.Duration("resource-cache-timeout", 30*time.Second, "Duration for which the results of resource related API responses should be cached.") ++ resyncAfter = flag.Duration("resync-after", 5*time.Minute, "Duration after which reconciliations (such as for VolumeSnapshotClasses) should be rerun. Set to 0 to disable.") ++ enableRWX = flag.Bool("enable-rwx", false, "Enable RWX support via NFS (requires running in Kubernetes).") ++ namespace = flag.String("nfs-service-namespace", "", "The namespace the NFS service is running in.") ++ reactorConfigMapName = flag.String("nfs-reactor-config-map-name", "linstor-csi-nfs-reactor-config", "Name of the config map used to store promoter configuration") ++ disableRWXBlockValidation = flag.Bool("disable-rwx-block-validation", false, "Disable KubeVirt VM ownership validation for RWX block volumes.") + ) + + flag.Var(&volume.DefaultRemoteAccessPolicy, "default-remote-access-policy", "") +@@ -169,6 +170,10 @@ func main() { + opts = append(opts, driver.ConfigureRWX(*namespace, *reactorConfigMapName)) + } + ++ if *disableRWXBlockValidation { ++ opts = append(opts, driver.DisableRWXBlockValidation()) ++ } ++ + drv, err := driver.NewDriver(opts...) + if err != nil { + log.Fatal(err) diff --git a/pkg/driver/driver.go b/pkg/driver/driver.go -index bea69a8..69e71a6 100644 +index bea69a8b..a39674b6 100644 --- a/pkg/driver/driver.go +++ b/pkg/driver/driver.go -@@ -707,6 +707,219 @@ func (d Driver) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest) - return &csi.DeleteVolumeResponse{}, nil +@@ -83,6 +83,8 @@ type Driver struct { + topologyPrefix string + // resyncAfter is the interval after which reconciliations should be retried + resyncAfter time.Duration ++ // disableRWXBlockValidation disables KubeVirt VM ownership validation for RWX block volumes ++ disableRWXBlockValidation bool + + // Embed for forward compatibility. + csi.UnimplementedIdentityServer +@@ -300,6 +302,17 @@ func ResyncAfter(resyncAfter time.Duration) func(*Driver) error { + } } ++// DisableRWXBlockValidation disables the KubeVirt VM ownership validation for RWX block volumes. ++// When disabled, the driver will not check if multiple pods using the same RWX block volume ++// belong to the same VM. This may be needed in environments where the validation causes issues ++// or when using RWX block volumes outside of KubeVirt. ++func DisableRWXBlockValidation() func(*Driver) error { ++ return func(d *Driver) error { ++ d.disableRWXBlockValidation = true ++ return nil ++ } ++} ++ + // GetPluginInfo https://github.com/container-storage-interface/spec/blob/v1.9.0/spec.md#getplugininfo + func (d Driver) GetPluginInfo(ctx context.Context, req *csi.GetPluginInfoRequest) (*csi.GetPluginInfoResponse, error) { + return &csi.GetPluginInfoResponse{ +@@ -751,6 +764,14 @@ func (d Driver) ControllerPublishVolume(ctx context.Context, req *csi.Controller + // ReadWriteMany block volume + rwxBlock := req.VolumeCapability.AccessMode.GetMode() == csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER && req.VolumeCapability.GetBlock() != nil + ++ // Validate RWX block attachment to prevent misuse of allow-two-primaries ++ if rwxBlock && !d.disableRWXBlockValidation { ++ if _, err := utils.ValidateRWXBlockAttachment(ctx, d.kubeClient, d.log, req.GetVolumeId()); err != nil { ++ return nil, status.Errorf(codes.FailedPrecondition, ++ "ControllerPublishVolume failed for %s: %v", req.GetVolumeId(), err) ++ } ++ } ++ + devPath, err := d.Assignments.Attach(ctx, req.GetVolumeId(), req.GetNodeId(), rwxBlock) + if err != nil { + return nil, status.Errorf(codes.Internal, +diff --git a/pkg/utils/rwx_validation.go b/pkg/utils/rwx_validation.go +new file mode 100644 +index 00000000..9fe82768 +--- /dev/null ++++ b/pkg/utils/rwx_validation.go +@@ -0,0 +1,263 @@ ++/* ++CSI Driver for Linstor ++Copyright © 2018 LINBIT USA, LLC ++ ++This program is free software; you can redistribute it and/or modify ++it under the terms of the GNU General Public License as published by ++the Free Software Foundation; either version 2 of the License, or ++(at your option) any later version. ++ ++This program is distributed in the hope that it will be useful, ++but WITHOUT ANY WARRANTY; without even the implied warranty of ++MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++GNU General Public License for more details. ++ ++You should have received a copy of the GNU General Public License ++along with this program; if not, see . ++*/ ++ ++package utils ++ ++import ( ++ "context" ++ "fmt" ++ ++ "github.com/sirupsen/logrus" ++ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ++ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ++ "k8s.io/apimachinery/pkg/runtime/schema" ++ "k8s.io/client-go/dynamic" ++) ++ +// KubeVirtVMLabel is the label that KubeVirt adds to pods to identify the VM they belong to. +const KubeVirtVMLabel = "vm.kubevirt.io/name" + +// KubeVirtHotplugDiskLabel is the label that KubeVirt adds to hotplug disk pods. +const KubeVirtHotplugDiskLabel = "kubevirt.io" + -+// podGVR is the GroupVersionResource for pods. -+var podGVR = schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"} ++// PodGVR is the GroupVersionResource for pods. ++var PodGVR = schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"} + -+// pvGVR is the GroupVersionResource for persistent volumes. -+var pvGVR = schema.GroupVersionResource{Group: "", Version: "v1", Resource: "persistentvolumes"} ++// PVGVR is the GroupVersionResource for persistent volumes. ++var PVGVR = schema.GroupVersionResource{Group: "", Version: "v1", Resource: "persistentvolumes"} + -+// getVMNameFromPod extracts the VM name from a pod, handling both regular virt-launcher pods -+// and hotplug disk pods (which reference the virt-launcher pod via ownerReferences). -+func (d Driver) getVMNameFromPod(ctx context.Context, pod *unstructured.Unstructured) (string, error) { -+ labels := pod.GetLabels() -+ if labels == nil { -+ return "", nil -+ } -+ -+ // Direct case: pod has vm.kubevirt.io/name label (virt-launcher pod) -+ if vmName, ok := labels[KubeVirtVMLabel]; ok && vmName != "" { -+ return vmName, nil -+ } -+ -+ // Hotplug disk case: pod has kubevirt.io: hotplug-disk label -+ // Follow ownerReferences to find the virt-launcher pod -+ if hotplugValue, ok := labels[KubeVirtHotplugDiskLabel]; ok && hotplugValue == "hotplug-disk" { -+ ownerRefs := pod.GetOwnerReferences() -+ for _, owner := range ownerRefs { -+ if owner.Kind != "Pod" || owner.Controller == nil || !*owner.Controller { -+ continue -+ } -+ -+ // Get the owner pod (virt-launcher) -+ ownerPod, err := d.kubeClient.Resource(podGVR).Namespace(pod.GetNamespace()).Get(ctx, owner.Name, metav1.GetOptions{}) -+ if err != nil { -+ return "", fmt.Errorf("failed to get owner pod %s: %w", owner.Name, err) -+ } -+ -+ // Extract VM name from owner pod -+ ownerLabels := ownerPod.GetLabels() -+ if ownerLabels != nil { -+ if vmName, ok := ownerLabels[KubeVirtVMLabel]; ok && vmName != "" { -+ d.log.WithFields(logrus.Fields{ -+ "hotplugPod": pod.GetName(), -+ "virtLauncher": owner.Name, -+ "vmName": vmName, -+ }).Debug("resolved VM name from hotplug disk pod via owner reference") -+ -+ return vmName, nil -+ } -+ } -+ -+ return "", fmt.Errorf("owner pod %s does not have %s label", owner.Name, KubeVirtVMLabel) -+ } -+ -+ return "", fmt.Errorf("hotplug disk pod %s has no controller owner reference", pod.GetName()) -+ } -+ -+ return "", nil -+} -+ -+// validateRWXBlockAttachment checks that RWX block volumes are only used by pods belonging to the same VM. ++// ValidateRWXBlockAttachment checks that RWX block volumes are only used by pods belonging to the same VM. +// This prevents misuse of allow-two-primaries while still permitting live migration. +// Returns the VM name if validation passes, or an error if: +// - Multiple pods from different VMs are trying to use the same volume +// - A pod without the KubeVirt VM label is trying to use a volume already attached elsewhere (strict mode) +// Returns empty string for VM name when no pods are using the volume or validation is skipped. -+func (d Driver) validateRWXBlockAttachment(ctx context.Context, volumeID string) (string, error) { -+ d.log.WithField("volumeID", volumeID).Info("validateRWXBlockAttachment called") ++func ValidateRWXBlockAttachment(ctx context.Context, kubeClient dynamic.Interface, log *logrus.Entry, volumeID string) (string, error) { ++ log.WithField("volumeID", volumeID).Info("validateRWXBlockAttachment called") + -+ if d.kubeClient == nil { ++ if kubeClient == nil { + // Not running in Kubernetes, skip validation -+ d.log.Warn("validateRWXBlockAttachment: kubeClient is nil, skipping validation") ++ log.Warn("validateRWXBlockAttachment: kubeClient is nil, skipping validation") + return "", nil + } + -+ // Get PV to find PVC reference (volumeID == PV name in CSI) -+ pv, err := d.kubeClient.Resource(pvGVR).Get(ctx, volumeID, metav1.GetOptions{}) ++ // Get PV to find PVC reference ++ pv, err := kubeClient.Resource(PVGVR).Get(ctx, volumeID, metav1.GetOptions{}) + if err != nil { -+ d.log.WithError(err).Warn("cannot validate RWX attachment: failed to get PV") ++ log.WithError(err).Warn("cannot validate RWX attachment: failed to get PV") ++ return "", nil ++ } ++ ++ // Verify that PV's volumeHandle matches the volumeID ++ volumeHandle, found, err := unstructured.NestedString(pv.Object, "spec", "csi", "volumeHandle") ++ if err != nil { ++ log.WithError(err).Warnf("cannot validate RWX attachment: failed to read volumeHandle for PV %s", volumeID) ++ ++ return "", nil ++ } ++ ++ if !found { ++ log.Warnf("cannot validate RWX attachment: volumeHandle not found for PV %s", volumeID) ++ ++ return "", nil ++ } ++ ++ if volumeHandle != volumeID { ++ log.WithFields(logrus.Fields{ ++ "volumeID": volumeID, ++ "volumeHandle": volumeHandle, ++ }).Warn("cannot validate RWX attachment: PV volumeHandle does not match volumeID") ++ + return "", nil + } + + // Extract claimRef from PV + claimRef, found, _ := unstructured.NestedMap(pv.Object, "spec", "claimRef") + if !found { -+ d.log.Warn("cannot validate RWX attachment: PV has no claimRef") ++ log.Warn("cannot validate RWX attachment: PV has no claimRef") + return "", nil + } + @@ -102,12 +204,12 @@ index bea69a8..69e71a6 100644 + pvcNamespace, _, _ := unstructured.NestedString(claimRef, "namespace") + + if pvcNamespace == "" || pvcName == "" { -+ d.log.Warn("cannot validate RWX attachment: PVC name or namespace is empty in claimRef") ++ log.Warn("cannot validate RWX attachment: PVC name or namespace is empty in claimRef") + return "", nil + } + + // List all pods in the namespace -+ podList, err := d.kubeClient.Resource(podGVR).Namespace(pvcNamespace).List(ctx, metav1.ListOptions{}) ++ podList, err := kubeClient.Resource(PodGVR).Namespace(pvcNamespace).List(ctx, metav1.ListOptions{}) + if err != nil { + return "", fmt.Errorf("failed to list pods in namespace %s: %w", pvcNamespace, err) + } @@ -139,28 +241,25 @@ index bea69a8..69e71a6 100644 + continue + } + -+ pvc, found, _ := unstructured.NestedMap(volMap, "persistentVolumeClaim") -+ if !found { ++ claimName, found, _ := unstructured.NestedString(volMap, "persistentVolumeClaim", "claimName") ++ if !found || claimName != pvcName { + continue + } + -+ claimName, _, _ := unstructured.NestedString(pvc, "claimName") -+ if claimName == pvcName { -+ // Extract VM name, handling both regular and hotplug disk pods -+ vmName, err := d.getVMNameFromPod(ctx, &item) -+ if err != nil { -+ d.log.WithError(err).WithField("pod", item.GetName()).Warn("failed to get VM name from pod") -+ // Continue with empty vmName - will be caught by strict mode check -+ vmName = "" -+ } -+ -+ podsUsingPVC = append(podsUsingPVC, podInfo{ -+ name: item.GetName(), -+ vmName: vmName, -+ }) -+ -+ break ++ // Extract VM name, handling both regular and hotplug disk pods ++ vmName, err := GetVMNameFromPod(ctx, kubeClient, log, &item) ++ if err != nil { ++ log.WithError(err).WithField("pod", item.GetName()).Warn("failed to get VM name from pod") ++ // Continue with empty vmName - will be caught by strict mode check ++ vmName = "" + } ++ ++ podsUsingPVC = append(podsUsingPVC, podInfo{ ++ name: item.GetName(), ++ vmName: vmName, ++ }) ++ ++ break + } + } + @@ -168,7 +267,7 @@ index bea69a8..69e71a6 100644 + if len(podsUsingPVC) <= 1 { + // Return VM name if there's exactly one pod + if len(podsUsingPVC) == 1 { -+ d.log.WithFields(logrus.Fields{ ++ log.WithFields(logrus.Fields{ + "volumeID": volumeID, + "vmName": podsUsingPVC[0].vmName, + "podCount": 1, @@ -179,7 +278,7 @@ index bea69a8..69e71a6 100644 + return podsUsingPVC[0].vmName, nil + } + -+ d.log.WithFields(logrus.Fields{ ++ log.WithFields(logrus.Fields{ + "volumeID": volumeID, + "pvcNamespace": pvcNamespace, + "pvcName": pvcName, @@ -209,7 +308,7 @@ index bea69a8..69e71a6 100644 + } + } + -+ d.log.WithFields(logrus.Fields{ ++ log.WithFields(logrus.Fields{ + "pvcNamespace": pvcNamespace, + "pvcName": pvcName, + "vmName": vmName, @@ -219,30 +318,62 @@ index bea69a8..69e71a6 100644 + return vmName, nil +} + - // ControllerPublishVolume https://github.com/container-storage-interface/spec/blob/v1.9.0/spec.md#controllerpublishvolume - func (d Driver) ControllerPublishVolume(ctx context.Context, req *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error) { - if req.GetVolumeId() == "" { -@@ -751,6 +964,14 @@ func (d Driver) ControllerPublishVolume(ctx context.Context, req *csi.Controller - // ReadWriteMany block volume - rwxBlock := req.VolumeCapability.AccessMode.GetMode() == csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER && req.VolumeCapability.GetBlock() != nil - -+ // Validate RWX block attachment to prevent misuse of allow-two-primaries -+ if rwxBlock { -+ if _, err := d.validateRWXBlockAttachment(ctx, req.GetVolumeId()); err != nil { -+ return nil, status.Errorf(codes.FailedPrecondition, -+ "ControllerPublishVolume failed for %s: %v", req.GetVolumeId(), err) -+ } ++// GetVMNameFromPod extracts the VM name from a pod, handling both regular virt-launcher pods ++// and hotplug disk pods (which reference the virt-launcher pod via ownerReferences). ++func GetVMNameFromPod(ctx context.Context, kubeClient dynamic.Interface, log *logrus.Entry, pod *unstructured.Unstructured) (string, error) { ++ labels := pod.GetLabels() ++ if labels == nil { ++ return "", nil + } + - devPath, err := d.Assignments.Attach(ctx, req.GetVolumeId(), req.GetNodeId(), rwxBlock) - if err != nil { - return nil, status.Errorf(codes.Internal, -diff --git a/pkg/driver/rwx_validation_test.go b/pkg/driver/rwx_validation_test.go ++ // Direct case: pod has vm.kubevirt.io/name label (virt-launcher pod) ++ if vmName, ok := labels[KubeVirtVMLabel]; ok && vmName != "" { ++ return vmName, nil ++ } ++ ++ // Hotplug disk case: pod has kubevirt.io: hotplug-disk label ++ // Follow ownerReferences to find the virt-launcher pod ++ if hotplugValue, ok := labels[KubeVirtHotplugDiskLabel]; ok && hotplugValue == "hotplug-disk" { ++ ownerRefs := pod.GetOwnerReferences() ++ for _, owner := range ownerRefs { ++ if owner.Kind != "Pod" || owner.Controller == nil || !*owner.Controller { ++ continue ++ } ++ ++ // Get the owner pod (virt-launcher) ++ ownerPod, err := kubeClient.Resource(PodGVR).Namespace(pod.GetNamespace()).Get(ctx, owner.Name, metav1.GetOptions{}) ++ if err != nil { ++ return "", fmt.Errorf("failed to get owner pod %s: %w", owner.Name, err) ++ } ++ ++ // Extract VM name from owner pod ++ ownerLabels := ownerPod.GetLabels() ++ if ownerLabels != nil { ++ if vmName, ok := ownerLabels[KubeVirtVMLabel]; ok && vmName != "" { ++ log.WithFields(logrus.Fields{ ++ "hotplugPod": pod.GetName(), ++ "virtLauncher": owner.Name, ++ "vmName": vmName, ++ }).Debug("resolved VM name from hotplug disk pod via owner reference") ++ ++ return vmName, nil ++ } ++ } ++ ++ return "", fmt.Errorf("owner pod %s does not have %s label", owner.Name, KubeVirtVMLabel) ++ } ++ ++ return "", fmt.Errorf("hotplug disk pod %s has no controller owner reference", pod.GetName()) ++ } ++ ++ return "", nil ++} +diff --git a/pkg/utils/rwx_validation_test.go b/pkg/utils/rwx_validation_test.go new file mode 100644 -index 0000000..92c1046 +index 00000000..d75690f9 --- /dev/null -+++ b/pkg/driver/rwx_validation_test.go -@@ -0,0 +1,353 @@ ++++ b/pkg/utils/rwx_validation_test.go +@@ -0,0 +1,342 @@ +/* +CSI Driver for Linstor +Copyright © 2018 LINBIT USA, LLC @@ -261,7 +392,7 @@ index 0000000..92c1046 +along with this program; if not, see . +*/ + -+package driver ++package utils + +import ( + "context" @@ -424,22 +555,17 @@ index 0000000..92c1046 + } + + gvrToListKind := map[schema.GroupVersionResource]string{ -+ podGVR: "PodList", -+ pvGVR: "PersistentVolumeList", ++ PodGVR: "PodList", ++ PVGVR: "PersistentVolumeList", + } + client := dynamicfake.NewSimpleDynamicClientWithCustomListKinds(scheme, gvrToListKind, objects...) + -+ // Create driver with fake client ++ // Create logger + logger := logrus.NewEntry(logrus.New()) + logger.Logger.SetLevel(logrus.DebugLevel) + -+ driver := &Driver{ -+ kubeClient: client, -+ log: logger, -+ } -+ + // Run validation -+ vmName, err := driver.validateRWXBlockAttachment(context.Background(), "test-volume-id") ++ vmName, err := ValidateRWXBlockAttachment(context.Background(), client, logger, "test-volume-id") + + if tc.expectError { + assert.Error(t, err) @@ -461,12 +587,8 @@ index 0000000..92c1046 +func TestValidateRWXBlockAttachmentNoKubeClient(t *testing.T) { + // When not running in Kubernetes (no client), validation should be skipped + logger := logrus.NewEntry(logrus.New()) -+ driver := &Driver{ -+ kubeClient: nil, -+ log: logger, -+ } + -+ vmName, err := driver.validateRWXBlockAttachment(context.Background(), "test-volume-id") ++ vmName, err := ValidateRWXBlockAttachment(context.Background(), nil, logger, "test-volume-id") + assert.NoError(t, err) + assert.Empty(t, vmName) +} @@ -476,20 +598,15 @@ index 0000000..92c1046 + scheme := runtime.NewScheme() + + gvrToListKind := map[schema.GroupVersionResource]string{ -+ podGVR: "PodList", -+ pvGVR: "PersistentVolumeList", ++ PodGVR: "PodList", ++ PVGVR: "PersistentVolumeList", + } + client := dynamicfake.NewSimpleDynamicClientWithCustomListKinds(scheme, gvrToListKind) + + logger := logrus.NewEntry(logrus.New()) + logger.Logger.SetLevel(logrus.DebugLevel) + -+ driver := &Driver{ -+ kubeClient: client, -+ log: logger, -+ } -+ -+ vmName, err := driver.validateRWXBlockAttachment(context.Background(), "non-existent-pv") ++ vmName, err := ValidateRWXBlockAttachment(context.Background(), client, logger, "non-existent-pv") + assert.NoError(t, err) + assert.Empty(t, vmName) +} @@ -538,6 +655,9 @@ index 0000000..92c1046 + "name": pvcName, + "namespace": pvcNamespace, + }, ++ "csi": map[string]interface{}{ ++ "volumeHandle": name, ++ }, + }, + }, + } From cf8ac03f45574b16e8399ce55f01da09aeb8c240 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 13 Jan 2026 17:22:13 +0100 Subject: [PATCH 045/889] Release v0.40.2 (#1858) This PR prepares the release `v0.40.2`. --- packages/apps/kubernetes/images/kubevirt-csi-driver.tag | 2 +- packages/core/installer/values.yaml | 2 +- packages/core/platform/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/kubevirt-csi-node/values.yaml | 2 +- packages/system/lineage-controller-webhook/values.yaml | 2 +- packages/system/linstor/values.yaml | 3 +-- packages/system/objectstorage-controller/values.yaml | 2 +- packages/system/seaweedfs/values.yaml | 2 +- 19 files changed, 23 insertions(+), 24 deletions(-) diff --git a/packages/apps/kubernetes/images/kubevirt-csi-driver.tag b/packages/apps/kubernetes/images/kubevirt-csi-driver.tag index 80d3d2e6..326da62f 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:0e5d8bbdc587a96e07bbf263f23d4d237dac8bd4528b34f44d31b042c6a0e495 diff --git a/packages/core/installer/values.yaml b/packages/core/installer/values.yaml index 61870b23..0379a40c 100644 --- a/packages/core/installer/values.yaml +++ b/packages/core/installer/values.yaml @@ -1,5 +1,5 @@ cozystack: - image: ghcr.io/cozystack/cozystack/installer:v0.40.1@sha256:69a8f95b8ddcf122a8859a957017b3d9e33c5eca3bfbf30c112c109e355f4f1c + image: ghcr.io/cozystack/cozystack/installer:v0.40.2@sha256:146e0de9d1208eba8342277fa08e0588d66e51bc637c6b3add8ef63cc54ef351 cozystackOperator: enabled: false image: ghcr.io/cozystack/cozystack/cozystack-operator:latest@sha256:f7f6e0fd9e896b7bfa642d0bfa4378bc14e646bc5c2e86e2e09a82770ef33181 diff --git a/packages/core/platform/values.yaml b/packages/core/platform/values.yaml index 84efcb78..26d0859c 100644 --- a/packages/core/platform/values.yaml +++ b/packages/core/platform/values.yaml @@ -1,2 +1,2 @@ assets: - image: ghcr.io/cozystack/cozystack/cozystack-assets:v0.40.1@sha256:ee1248692753820fe11139789680fd24dc4dbdba85a6cb70e086447d9b9b1d99 + image: ghcr.io/cozystack/cozystack/cozystack-assets:v0.40.2@sha256:02730e6a434a8367a970c00a1feaa31f19a856641f6c1d085afed88e9c0b8b2b diff --git a/packages/core/testing/values.yaml b/packages/core/testing/values.yaml index 90a9ed18..b00e9ea3 100644 --- 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.40.1@sha256:78a556a059411cac89249c2e22596223128a1c74fbfcac4787fd74e4a66f66a6 + image: ghcr.io/cozystack/cozystack/e2e-sandbox:v0.40.2@sha256:78a556a059411cac89249c2e22596223128a1c74fbfcac4787fd74e4a66f66a6 diff --git a/packages/extra/bootbox/images/matchbox.tag b/packages/extra/bootbox/images/matchbox.tag index aa8ecbfd..522bb744 100644 --- a/packages/extra/bootbox/images/matchbox.tag +++ b/packages/extra/bootbox/images/matchbox.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/matchbox:v0.40.1@sha256:293d562c134fc4ee0a9a756df32422b849715fe25a2120b69cb415ca98217363 +ghcr.io/cozystack/cozystack/matchbox:v0.40.2@sha256:f72d35fc691021d6b473360f2edaf0b1f176f737ca18f8cb9322d16c69705020 diff --git a/packages/extra/seaweedfs/images/objectstorage-sidecar.tag b/packages/extra/seaweedfs/images/objectstorage-sidecar.tag index 1724acc3..90d0d249 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.40.1@sha256:4bb47c8adb34543403a16d1ff61b307939850e8cf5fc365388469e30dfb9681b +ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.40.2@sha256:6735e6f050355355a13439ff815539a1b92eba09623e500e92dd7d4e600c59b4 diff --git a/packages/system/bucket/images/s3manager.tag b/packages/system/bucket/images/s3manager.tag index 1e12e68b..b71ecf9e 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:07f016720c4692fc270a415a327a88773b90d2821922098496ad1269beac6c94 diff --git a/packages/system/cozystack-api/values.yaml b/packages/system/cozystack-api/values.yaml index 10a79b25..04e86c7c 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.40.1@sha256:408885b509d80ca30a85416f87d07112bc7f070374e71e80b64818fbb24ad1ee + image: ghcr.io/cozystack/cozystack/cozystack-api:v0.40.2@sha256:408885b509d80ca30a85416f87d07112bc7f070374e71e80b64818fbb24ad1ee localK8sAPIEndpoint: enabled: true replicas: 2 diff --git a/packages/system/cozystack-controller/values.yaml b/packages/system/cozystack-controller/values.yaml index a28c13c5..de8eedd5 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.40.1@sha256:75636007f63e1b2cb51fc2e70782b707330d1e728c32642a4edc500b614f0781 + image: ghcr.io/cozystack/cozystack/cozystack-controller:v0.40.2@sha256:1878af4389ee3ddc85f2079cc250bccc14b923fb74c665c9aa902a659a394ba6 debug: false disableTelemetry: false - cozystackVersion: "v0.40.1" + cozystackVersion: "v0.40.2" cozystackAPIKind: "DaemonSet" diff --git a/packages/system/dashboard/templates/configmap.yaml b/packages/system/dashboard/templates/configmap.yaml index 861d4eed..a929b0b1 100644 --- a/packages/system/dashboard/templates/configmap.yaml +++ b/packages/system/dashboard/templates/configmap.yaml @@ -1,6 +1,6 @@ {{- $brandingConfig := .Values._cluster.branding | default dict }} -{{- $tenantText := "v0.40.1" }} +{{- $tenantText := "v0.40.2" }} {{- $footerText := "Cozystack" }} {{- $titleText := "Cozystack Dashboard" }} {{- $logoText := "" }} diff --git a/packages/system/dashboard/values.yaml b/packages/system/dashboard/values.yaml index 94c395bb..091aaa4d 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.40.1@sha256:a427114308b9c87ab71b50980cba0d673a730791629164335d2046f7d1740327 + image: ghcr.io/cozystack/cozystack/openapi-ui:v0.40.2@sha256:a8d3770107c0279add903e6b5acdc5ca8a15551fb07514e40772f8010c5386ef openapiUIK8sBff: - image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v0.40.1@sha256:fda379dce49c2cd8cb8d7d2a1d8ec6f7bedb3419c058c4355ecdece1c1e937f4 + image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v0.40.2@sha256:3071dfc130dbaf185b1a46ed42f0800824e77c2f4e64c5ed87055311f3c5f2b1 tokenProxy: - image: ghcr.io/cozystack/cozystack/token-proxy:v0.40.1@sha256:4fc8a11f8a1a81aa0774ae2b1ed2e05d36d0b3ef1e37979cc4994e65114d93ae + image: ghcr.io/cozystack/cozystack/token-proxy:v0.40.2@sha256:4fc8a11f8a1a81aa0774ae2b1ed2e05d36d0b3ef1e37979cc4994e65114d93ae diff --git a/packages/system/kamaji/values.yaml b/packages/system/kamaji/values.yaml index 35c15ef9..4aea9c22 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.40.1@sha256:0bcfb2d376224b18a0627d7b18ba6202bb8a553f71796023e12740d9513740c7 + tag: v0.40.2@sha256:cf9aae159deb586b1af5e5f6299247fe82f01646d58102bf5ca264c40f9edda2 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.40.1@sha256:0bcfb2d376224b18a0627d7b18ba6202bb8a553f71796023e12740d9513740c7 + - --migrate-image=ghcr.io/cozystack/cozystack/kamaji:v0.40.2@sha256:cf9aae159deb586b1af5e5f6299247fe82f01646d58102bf5ca264c40f9edda2 diff --git a/packages/system/kubeovn-plunger/values.yaml b/packages/system/kubeovn-plunger/values.yaml index 10d8508a..4229b116 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.40.1@sha256:2a17dfa8eec0fc6b2adb564742e74f35bd68ac0d29e5ab1d007496aff3c0e97d +image: ghcr.io/cozystack/cozystack/kubeovn-plunger:v0.40.2@sha256:fdcc081d3c28a023a6950deac4d6124a0290d1bb4bd13c8c723569ac40d6874e ovnCentralName: ovn-central diff --git a/packages/system/kubeovn-webhook/values.yaml b/packages/system/kubeovn-webhook/values.yaml index c60ff144..6ec8f26e 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.40.1@sha256:e6334c29d3aaf0dea766c88e3e05b53ad623d1bb497b3c836e6f76adade45b29 +image: ghcr.io/cozystack/cozystack/kubeovn-webhook:v0.40.2@sha256:e6334c29d3aaf0dea766c88e3e05b53ad623d1bb497b3c836e6f76adade45b29 diff --git a/packages/system/kubevirt-csi-node/values.yaml b/packages/system/kubevirt-csi-node/values.yaml index 8d2a7d45..d3fac521 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:0e5d8bbdc587a96e07bbf263f23d4d237dac8bd4528b34f44d31b042c6a0e495 diff --git a/packages/system/lineage-controller-webhook/values.yaml b/packages/system/lineage-controller-webhook/values.yaml index 55cb019b..554d9ef2 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.40.1@sha256:00650eb92c8b93a0afc382a0c5e46ade453e430867be7dab9647a6fc2896f097 + image: ghcr.io/cozystack/cozystack/lineage-controller-webhook:v0.40.2@sha256:16a700334d4d9cebea2516874c04d0799a450052f3652a0c91668c37d1d0e227 debug: false localK8sAPIEndpoint: enabled: true diff --git a/packages/system/linstor/values.yaml b/packages/system/linstor/values.yaml index 173aacb4..da62e9da 100644 --- a/packages/system/linstor/values.yaml +++ b/packages/system/linstor/values.yaml @@ -7,8 +7,7 @@ linstor: enabled: true minutes: 30 allowCleanup: true - linstorCSI: image: repository: ghcr.io/cozystack/cozystack/linstor-csi - tag: latest@sha256:e8329b3e07c47ec73a7d9644535639fd80dbe5c27a7e03181b999ebe072a3a2e + tag: v1.10.5@sha256:276e35e644cc4f1a17bc3842ded84aef4d9a5d750b5bdf3fc7238e369e88ada8 diff --git a/packages/system/objectstorage-controller/values.yaml b/packages/system/objectstorage-controller/values.yaml index aba36be8..041c3ad2 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.40.1@sha256:e8f605a5ec4b801dfaf49aa7bce8d7a1ce28df578e23a0b859571f60f98efe8c" + image: "ghcr.io/cozystack/cozystack/objectstorage-controller:v0.40.2@sha256:f44845c034643c3b9040b70dc417e633d8ab0cc56a304a88dace733f2b4f1d70" diff --git a/packages/system/seaweedfs/values.yaml b/packages/system/seaweedfs/values.yaml index fb3defa6..fd8f7ff0 100644 --- a/packages/system/seaweedfs/values.yaml +++ b/packages/system/seaweedfs/values.yaml @@ -177,7 +177,7 @@ seaweedfs: bucketClassName: "seaweedfs" region: "" sidecar: - image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.40.1@sha256:4bb47c8adb34543403a16d1ff61b307939850e8cf5fc365388469e30dfb9681b" + image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.40.2@sha256:6735e6f050355355a13439ff815539a1b92eba09623e500e92dd7d4e600c59b4" certificates: commonName: "SeaweedFS CA" ipAddresses: [] From 909c3a5abca21948dd404ac8e5d1048b68193102 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 13 Jan 2026 17:31:36 +0100 Subject: [PATCH 046/889] [docs] Generate changelogs for v0.39.5, v0.40.1, and v0.40.2 Add missing changelog files for recent patch releases: - v0.39.5: LINSTOR critical patches backport - v0.40.1: LINSTOR critical patches backport - v0.40.2: LINSTOR RWX validation refactoring Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- docs/changelogs/v0.39.5.md | 11 +++++++++++ docs/changelogs/v0.40.1.md | 11 +++++++++++ docs/changelogs/v0.40.2.md | 15 +++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 docs/changelogs/v0.39.5.md create mode 100644 docs/changelogs/v0.40.1.md create mode 100644 docs/changelogs/v0.40.2.md diff --git a/docs/changelogs/v0.39.5.md b/docs/changelogs/v0.39.5.md new file mode 100644 index 00000000..de352b1c --- /dev/null +++ b/docs/changelogs/v0.39.5.md @@ -0,0 +1,11 @@ + + +## Fixes + +* **[linstor] Update piraeus-server patches with critical fixes**: Backported critical patches to piraeus-server that address storage stability issues and improve DRBD resource handling. These patches fix edge cases in device management and ensure more reliable storage operations ([**@kvaps**](https://github.com/kvaps) in #1850, #1853). + +--- + +**Full Changelog**: [v0.39.4...v0.39.5](https://github.com/cozystack/cozystack/compare/v0.39.4...v0.39.5) diff --git a/docs/changelogs/v0.40.1.md b/docs/changelogs/v0.40.1.md new file mode 100644 index 00000000..ee718b47 --- /dev/null +++ b/docs/changelogs/v0.40.1.md @@ -0,0 +1,11 @@ + + +## Fixes + +* **[linstor] Update piraeus-server patches with critical fixes**: Backported critical patches to piraeus-server that address storage stability issues and improve DRBD resource handling. These patches fix edge cases in device management and ensure more reliable storage operations ([**@kvaps**](https://github.com/kvaps) in #1850, #1852). + +--- + +**Full Changelog**: [v0.40.0...v0.40.1](https://github.com/cozystack/cozystack/compare/v0.40.0...v0.40.1) diff --git a/docs/changelogs/v0.40.2.md b/docs/changelogs/v0.40.2.md new file mode 100644 index 00000000..5c686b92 --- /dev/null +++ b/docs/changelogs/v0.40.2.md @@ -0,0 +1,15 @@ + + +## Improvements + +* **[linstor] Refactor node-level RWX validation**: Refactored the node-level ReadWriteMany (RWX) validation logic in LINSTOR CSI. The validation has been moved to the CSI driver level with a custom linstor-csi image build, providing more reliable RWX volume handling and clearer error messages when RWX requirements cannot be satisfied ([**@kvaps**](https://github.com/kvaps) in #1856, #1857). + +## Fixes + +* **[linstor] Remove node-level RWX validation**: Removed the problematic node-level RWX validation that was causing issues with volume provisioning. The validation logic has been refactored and moved to a more appropriate location in the LINSTOR CSI driver ([**@kvaps**](https://github.com/kvaps) in #1851). + +--- + +**Full Changelog**: [v0.40.1...v0.40.2](https://github.com/cozystack/cozystack/compare/v0.40.1...v0.40.2) From 74d71606ab4cd2d5db2adcd8dbba0831c4d1e5b2 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Sat, 3 Jan 2026 09:06:47 +0100 Subject: [PATCH 047/889] feat(api): show only hash in version column for applications and modules Fix getVersion to parse "0.1.4+abcdef" format (with "+" separator) instead of incorrectly looking for "sha256:" prefix. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- pkg/registry/apps/application/rest.go | 10 +++++++++- pkg/registry/core/tenantmodule/rest.go | 11 ++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/pkg/registry/apps/application/rest.go b/pkg/registry/apps/application/rest.go index 4f3204d5..c46b5cad 100644 --- a/pkg/registry/apps/application/rest.go +++ b/pkg/registry/apps/application/rest.go @@ -1095,11 +1095,19 @@ func (r *REST) buildTableFromApplication(app appsv1alpha1.Application) metav1.Ta return table } -// getVersion returns the application version or a placeholder if unknown +// getVersion extracts and returns only the revision from the version string +// If version is in format "0.1.4+abcdef", returns "abcdef" +// Otherwise returns the original string or "" if empty func getVersion(version string) string { if version == "" { return "" } + // Check if version contains "+" separator + if idx := strings.LastIndex(version, "+"); idx >= 0 && idx < len(version)-1 { + // Return only the part after "+" + return version[idx+1:] + } + // If no "+" found, return original version return version } diff --git a/pkg/registry/core/tenantmodule/rest.go b/pkg/registry/core/tenantmodule/rest.go index b2cda2db..77fbaaa8 100644 --- a/pkg/registry/core/tenantmodule/rest.go +++ b/pkg/registry/core/tenantmodule/rest.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "net/http" + "strings" "sync" "time" @@ -669,11 +670,19 @@ func (r *REST) buildTableFromTenantModule(module corev1alpha1.TenantModule) meta return table } -// getVersion returns the module version or a placeholder if unknown +// getVersion extracts and returns only the revision from the version string +// If version is in format "0.1.4+abcdef", returns "abcdef" +// Otherwise returns the original string or "" if empty func getVersion(version string) string { if version == "" { return "" } + // Check if version contains "+" separator + if idx := strings.LastIndex(version, "+"); idx >= 0 && idx < len(version)-1 { + // Return only the part after "+" + return version[idx+1:] + } + // If no "+" found, return original version return version } From 43da779eee58759e22188901c232c8618288f2ce Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Wed, 14 Jan 2026 15:41:05 +0100 Subject: [PATCH 048/889] feat(api): add chartRef to CozystackResourceDefinition Replace the chart field with chartRef for referencing Helm charts via ExternalArtifact resources. This enables the Package controller to manage chart sources centrally. Changes: - Add chartRef field to CozystackResourceDefinition spec - Remove chart field (deprecated) - Remove validation (moved to controller) - Update lineage mapper for new field structure - Regenerate openapi specs Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- .../cozystackresourcedefinitions_types.go | 24 ++------- api/v1alpha1/zz_generated.deepcopy.go | 38 +++----------- pkg/apis/apps/fuzzer/fuzzer.go | 4 +- pkg/apis/apps/validation/validation.go | 40 --------------- pkg/apis/core/fuzzer/fuzzer.go | 4 +- pkg/apis/core/validation/validation.go | 40 --------------- pkg/cmd/server/start.go | 11 ++--- pkg/config/config.go | 16 ++---- pkg/generated/openapi/zz_generated.openapi.go | 49 ++++++++++++++++--- pkg/lineage/lineage.go | 5 ++ pkg/lineage/lineage_test.go | 33 ++++++++++++- pkg/lineage/mapper.go | 49 ------------------- pkg/registry/apps/application/rest.go | 15 ++---- 13 files changed, 106 insertions(+), 222 deletions(-) delete mode 100644 pkg/apis/apps/validation/validation.go delete mode 100644 pkg/apis/core/validation/validation.go delete mode 100644 pkg/lineage/mapper.go diff --git a/api/v1alpha1/cozystackresourcedefinitions_types.go b/api/v1alpha1/cozystackresourcedefinitions_types.go index 2f25fb2d..04b24294 100644 --- a/api/v1alpha1/cozystackresourcedefinitions_types.go +++ b/api/v1alpha1/cozystackresourcedefinitions_types.go @@ -17,6 +17,7 @@ limitations under the License. package v1alpha1 import ( + helmv2 "github.com/fluxcd/helm-controller/api/v2" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -61,24 +62,6 @@ type CozystackResourceDefinitionSpec struct { Dashboard *CozystackResourceDefinitionDashboard `json:"dashboard,omitempty"` } -type CozystackResourceDefinitionChart struct { - // Name of the Helm chart - Name string `json:"name"` - // Source reference for the Helm chart - SourceRef SourceRef `json:"sourceRef"` -} - -type SourceRef struct { - // Kind of the source reference - // +kubebuilder:default:="HelmRepository" - Kind string `json:"kind"` - // Name of the source reference - Name string `json:"name"` - // Namespace of the source reference - // +kubebuilder:default:="cozy-public" - Namespace string `json:"namespace"` -} - type CozystackResourceDefinitionApplication struct { // Kind of the application, used for UI and API Kind string `json:"kind"` @@ -91,9 +74,8 @@ type CozystackResourceDefinitionApplication struct { } type CozystackResourceDefinitionRelease struct { - // Helm chart configuration - // +optional - Chart CozystackResourceDefinitionChart `json:"chart,omitempty"` + // Reference to the chart source + ChartRef *helmv2.CrossNamespaceSourceReference `json:"chartRef"` // Labels for the release Labels map[string]string `json:"labels,omitempty"` // Prefix for the release name diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 7fe4f3e2..f060990f 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -21,6 +21,7 @@ limitations under the License. package v1alpha1 import ( + "github.com/fluxcd/helm-controller/api/v2" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -118,22 +119,6 @@ func (in *CozystackResourceDefinitionApplication) DeepCopy() *CozystackResourceD return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CozystackResourceDefinitionChart) DeepCopyInto(out *CozystackResourceDefinitionChart) { - *out = *in - out.SourceRef = in.SourceRef -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CozystackResourceDefinitionChart. -func (in *CozystackResourceDefinitionChart) DeepCopy() *CozystackResourceDefinitionChart { - if in == nil { - return nil - } - out := new(CozystackResourceDefinitionChart) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CozystackResourceDefinitionDashboard) DeepCopyInto(out *CozystackResourceDefinitionDashboard) { *out = *in @@ -205,7 +190,11 @@ func (in *CozystackResourceDefinitionList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CozystackResourceDefinitionRelease) DeepCopyInto(out *CozystackResourceDefinitionRelease) { *out = *in - out.Chart = in.Chart + if in.ChartRef != nil { + in, out := &in.ChartRef, &out.ChartRef + *out = new(v2.CrossNamespaceSourceReference) + **out = **in + } if in.Labels != nil { in, out := &in.Labels, &out.Labels *out = make(map[string]string, len(*in)) @@ -622,21 +611,6 @@ func (in Selector) DeepCopy() Selector { return *out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SourceRef) DeepCopyInto(out *SourceRef) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SourceRef. -func (in *SourceRef) DeepCopy() *SourceRef { - if in == nil { - return nil - } - out := new(SourceRef) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Variant) DeepCopyInto(out *Variant) { *out = *in diff --git a/pkg/apis/apps/fuzzer/fuzzer.go b/pkg/apis/apps/fuzzer/fuzzer.go index fd744ed6..c92c5799 100644 --- a/pkg/apis/apps/fuzzer/fuzzer.go +++ b/pkg/apis/apps/fuzzer/fuzzer.go @@ -17,7 +17,7 @@ limitations under the License. package fuzzer import ( - "github.com/cozystack/cozystack/pkg/apis/apps" + "github.com/cozystack/cozystack/pkg/apis/apps/v1alpha1" fuzz "github.com/google/gofuzz" runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" @@ -26,7 +26,7 @@ import ( // Funcs returns the fuzzer functions for the apps api group. var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} { return []interface{}{ - func(s *apps.ApplicationSpec, c fuzz.Continue) { + func(s *v1alpha1.Application, c fuzz.Continue) { c.FuzzNoCustom(s) // fuzz self without calling this function again }, } diff --git a/pkg/apis/apps/validation/validation.go b/pkg/apis/apps/validation/validation.go deleted file mode 100644 index 84c20c54..00000000 --- a/pkg/apis/apps/validation/validation.go +++ /dev/null @@ -1,40 +0,0 @@ -/* -Copyright 2024 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. -*/ - -package validation - -import ( - "github.com/cozystack/cozystack/pkg/apis/apps" - "k8s.io/apimachinery/pkg/util/validation/field" -) - -// ValidateApplication validates a Application. -func ValidateApplication(f *apps.Application) field.ErrorList { - allErrs := field.ErrorList{} - - allErrs = append(allErrs, ValidateApplicationSpec(&f.Spec, field.NewPath("spec"))...) - - return allErrs -} - -// ValidateApplicationSpec validates a ApplicationSpec. -func ValidateApplicationSpec(s *apps.ApplicationSpec, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - // TODO validation - - return allErrs -} diff --git a/pkg/apis/core/fuzzer/fuzzer.go b/pkg/apis/core/fuzzer/fuzzer.go index dbf3ca39..82a1b5ab 100644 --- a/pkg/apis/core/fuzzer/fuzzer.go +++ b/pkg/apis/core/fuzzer/fuzzer.go @@ -17,7 +17,7 @@ limitations under the License. package fuzzer import ( - "github.com/cozystack/cozystack/pkg/apis/core" + "github.com/cozystack/cozystack/pkg/apis/core/v1alpha1" fuzz "github.com/google/gofuzz" runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" @@ -26,7 +26,7 @@ import ( // Funcs returns the fuzzer functions for the core api group. var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} { return []interface{}{ - func(s *core.TenantNamespaceSpec, c fuzz.Continue) { + func(s *v1alpha1.TenantNamespace, c fuzz.Continue) { c.FuzzNoCustom(s) // fuzz self without calling this function again }, } diff --git a/pkg/apis/core/validation/validation.go b/pkg/apis/core/validation/validation.go deleted file mode 100644 index 060067de..00000000 --- a/pkg/apis/core/validation/validation.go +++ /dev/null @@ -1,40 +0,0 @@ -/* -Copyright 2024 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. -*/ - -package validation - -import ( - "github.com/cozystack/cozystack/pkg/apis/core" - "k8s.io/apimachinery/pkg/util/validation/field" -) - -// ValidateTenantNamespace validates a TenantNamespace. -func ValidateTenantNamespace(f *core.TenantNamespace) field.ErrorList { - allErrs := field.ErrorList{} - - allErrs = append(allErrs, ValidateTenantNamespaceSpec(&f.Spec, field.NewPath("spec"))...) - - return allErrs -} - -// ValidateTenantNamespaceSpec validates a TenantNamespaceSpec. -func ValidateTenantNamespaceSpec(s *core.TenantNamespaceSpec, fldPath *field.Path) field.ErrorList { - allErrs := field.ErrorList{} - - // TODO validation - - return allErrs -} diff --git a/pkg/cmd/server/start.go b/pkg/cmd/server/start.go index 5da8254e..2826cfd5 100644 --- a/pkg/cmd/server/start.go +++ b/pkg/cmd/server/start.go @@ -169,13 +169,10 @@ func (o *CozyServerOptions) Complete() error { Release: config.ReleaseConfig{ Prefix: crd.Spec.Release.Prefix, Labels: crd.Spec.Release.Labels, - Chart: config.ChartConfig{ - Name: crd.Spec.Release.Chart.Name, - SourceRef: config.SourceRefConfig{ - Kind: crd.Spec.Release.Chart.SourceRef.Kind, - Name: crd.Spec.Release.Chart.SourceRef.Name, - Namespace: crd.Spec.Release.Chart.SourceRef.Namespace, - }, + ChartRef: config.ChartRefConfig{ + Kind: crd.Spec.Release.ChartRef.Kind, + Name: crd.Spec.Release.ChartRef.Name, + Namespace: crd.Spec.Release.ChartRef.Namespace, }, }, } diff --git a/pkg/config/config.go b/pkg/config/config.go index 390918a6..1e123e2c 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -38,19 +38,13 @@ type ApplicationConfig struct { // ReleaseConfig contains the release settings. type ReleaseConfig struct { - Prefix string `yaml:"prefix"` - Labels map[string]string `yaml:"labels"` - Chart ChartConfig `yaml:"chart"` + Prefix string `yaml:"prefix"` + Labels map[string]string `yaml:"labels"` + ChartRef ChartRefConfig `yaml:"chartRef"` } -// ChartConfig contains the chart settings. -type ChartConfig struct { - Name string `yaml:"name"` - SourceRef SourceRefConfig `yaml:"sourceRef"` -} - -// SourceRefConfig contains the reference to the chart source. -type SourceRefConfig struct { +// ChartRefConfig references a Flux source artifact for the Helm chart. +type ChartRefConfig struct { Kind string `yaml:"kind"` Name string `yaml:"name"` Namespace string `yaml:"namespace"` 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": { diff --git a/pkg/lineage/lineage.go b/pkg/lineage/lineage.go index 867e1aab..94be77b7 100644 --- a/pkg/lineage/lineage.go +++ b/pkg/lineage/lineage.go @@ -22,6 +22,11 @@ const ( HRLabel = "helm.toolkit.fluxcd.io/name" ) +// AppMapper maps HelmRelease to application metadata. +type AppMapper interface { + Map(*helmv2.HelmRelease) (apiVersion, kind, prefix string, err error) +} + type ObjectID struct { APIVersion string Kind string diff --git a/pkg/lineage/lineage_test.go b/pkg/lineage/lineage_test.go index a705f26c..7da09dfc 100644 --- a/pkg/lineage/lineage_test.go +++ b/pkg/lineage/lineage_test.go @@ -4,8 +4,10 @@ import ( "context" "fmt" "os" + "strings" "testing" + helmv2 "github.com/fluxcd/helm-controller/api/v2" "github.com/go-logr/logr" "github.com/go-logr/zapr" "go.uber.org/zap" @@ -41,12 +43,41 @@ func init() { ctx = logr.NewContext(context.Background(), l) } +// labelsMapper implements AppMapper using HelmRelease labels. +type labelsMapper struct{} + +func (m *labelsMapper) Map(hr *helmv2.HelmRelease) (string, string, string, error) { + if hr.Labels == nil { + return "", "", "", fmt.Errorf("cannot map helm release %s/%s: labels are nil", hr.Namespace, hr.Name) + } + + appKind, ok := hr.Labels["apps.cozystack.io/application.kind"] + if !ok { + return "", "", "", fmt.Errorf("cannot map helm release %s/%s: missing application.kind label", hr.Namespace, hr.Name) + } + + appGroup, ok := hr.Labels["apps.cozystack.io/application.group"] + if !ok { + return "", "", "", fmt.Errorf("cannot map helm release %s/%s: missing application.group label", hr.Namespace, hr.Name) + } + + appName, ok := hr.Labels["apps.cozystack.io/application.name"] + if !ok { + return "", "", "", fmt.Errorf("cannot map helm release %s/%s: missing application.name label", hr.Namespace, hr.Name) + } + + apiVersion := fmt.Sprintf("%s/v1alpha1", appGroup) + prefix := strings.TrimSuffix(hr.Name, appName) + + return apiVersion, appKind, prefix, nil +} + func TestWalkingOwnershipGraph(t *testing.T) { obj, err := dynClient.Resource(schema.GroupVersionResource{"", "v1", "pods"}).Namespace(os.Args[1]).Get(ctx, os.Args[2], metav1.GetOptions{}) if err != nil { t.Fatal(err) } - nodes := WalkOwnershipGraph(ctx, dynClient, mapper, &stubMapper{}, obj) + nodes := WalkOwnershipGraph(ctx, dynClient, mapper, &labelsMapper{}, obj) for _, node := range nodes { fmt.Printf("%#v\n", node) } diff --git a/pkg/lineage/mapper.go b/pkg/lineage/mapper.go deleted file mode 100644 index c424b288..00000000 --- a/pkg/lineage/mapper.go +++ /dev/null @@ -1,49 +0,0 @@ -package lineage - -import ( - "fmt" - "strings" - - helmv2 "github.com/fluxcd/helm-controller/api/v2" -) - -type AppMapper interface { - Map(*helmv2.HelmRelease) (apiVersion, kind, prefix string, err error) -} - -type stubMapper struct{} - -var stubMapperMap = map[string]string{ - "cozystack-extra/bootbox": "apps.cozystack.io/v1alpha1/BootBox/", - "cozystack-apps/bucket": "apps.cozystack.io/v1alpha1/Bucket/bucket-", - "cozystack-apps/clickhouse": "apps.cozystack.io/v1alpha1/ClickHouse/clickhouse-", - "cozystack-extra/etcd": "apps.cozystack.io/v1alpha1/Etcd/", - "cozystack-apps/ferretdb": "apps.cozystack.io/v1alpha1/FerretDB/ferretdb-", - "cozystack-apps/http-cache": "apps.cozystack.io/v1alpha1/HTTPCache/http-cache-", - "cozystack-extra/info": "apps.cozystack.io/v1alpha1/Info/", - "cozystack-extra/ingress": "apps.cozystack.io/v1alpha1/Ingress/", - "cozystack-apps/kafka": "apps.cozystack.io/v1alpha1/Kafka/kafka-", - "cozystack-apps/kubernetes": "apps.cozystack.io/v1alpha1/Kubernetes/kubernetes-", - "cozystack-extra/monitoring": "apps.cozystack.io/v1alpha1/Monitoring/", - "cozystack-apps/mysql": "apps.cozystack.io/v1alpha1/MySQL/mysql-", - "cozystack-apps/nats": "apps.cozystack.io/v1alpha1/NATS/nats-", - "cozystack-apps/postgres": "apps.cozystack.io/v1alpha1/Postgres/postgres-", - "cozystack-apps/rabbitmq": "apps.cozystack.io/v1alpha1/RabbitMQ/rabbitmq-", - "cozystack-apps/redis": "apps.cozystack.io/v1alpha1/Redis/redis-", - "cozystack-extra/seaweedfs": "apps.cozystack.io/v1alpha1/SeaweedFS/", - "cozystack-apps/tcp-balancer": "apps.cozystack.io/v1alpha1/TCPBalancer/tcp-balancer-", - "cozystack-apps/tenant": "apps.cozystack.io/v1alpha1/Tenant/tenant-", - "cozystack-apps/virtual-machine": "apps.cozystack.io/v1alpha1/VirtualMachine/virtual-machine-", - "cozystack-apps/vm-disk": "apps.cozystack.io/v1alpha1/VMDisk/vm-disk-", - "cozystack-apps/vm-instance": "apps.cozystack.io/v1alpha1/VMInstance/vm-instance-", - "cozystack-apps/vpn": "apps.cozystack.io/v1alpha1/VPN/vpn-", -} - -func (s *stubMapper) Map(hr *helmv2.HelmRelease) (string, string, string, error) { - val, ok := stubMapperMap[hr.Spec.Chart.Spec.SourceRef.Name+"/"+hr.Spec.Chart.Spec.Chart] - if !ok { - return "", "", "", fmt.Errorf("cannot map helm release %s/%s to dynamic app", hr.Namespace, hr.Name) - } - split := strings.Split(val, "/") - return strings.Join(split[:2], "/"), split[2], split[3], nil -} diff --git a/pkg/registry/apps/application/rest.go b/pkg/registry/apps/application/rest.go index c46b5cad..6e0ae048 100644 --- a/pkg/registry/apps/application/rest.go +++ b/pkg/registry/apps/application/rest.go @@ -973,17 +973,10 @@ func (r *REST) convertApplicationToHelmRelease(app *appsv1alpha1.Application) (* UID: app.UID, }, Spec: helmv2.HelmReleaseSpec{ - Chart: &helmv2.HelmChartTemplate{ - Spec: helmv2.HelmChartTemplateSpec{ - Chart: r.releaseConfig.Chart.Name, - Version: ">= 0.0.0-0", - ReconcileStrategy: "Revision", - SourceRef: helmv2.CrossNamespaceObjectReference{ - Kind: r.releaseConfig.Chart.SourceRef.Kind, - Name: r.releaseConfig.Chart.SourceRef.Name, - Namespace: r.releaseConfig.Chart.SourceRef.Namespace, - }, - }, + ChartRef: &helmv2.CrossNamespaceSourceReference{ + Kind: r.releaseConfig.ChartRef.Kind, + Name: r.releaseConfig.ChartRef.Name, + Namespace: r.releaseConfig.ChartRef.Namespace, }, Interval: metav1.Duration{Duration: 5 * time.Minute}, Install: &helmv2.Install{ From 99ac2fc7105e8c361273fb6988851207c3188fac Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 15 Jan 2026 14:14:48 +0100 Subject: [PATCH 049/889] refactor(installer): migrate installer to cozystack-operator Remove legacy installer components (cozystack-assets-server, installer.sh script, cozystack container image) in favor of cozystack-operator based deployment. Move migration scripts from scripts/migrations/ to packages/core/platform/images/migrations/ for containerized execution. Add grafana-dashboards image for centralized dashboard management. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- Makefile | 13 +-- cmd/cozystack-assets-server/main.go | 29 ------- packages/core/installer/Makefile | 57 ++++++------- .../installer/images/cozystack/Dockerfile | 41 ---------- .../images/cozystack/Dockerfile.dockerignore | 1 - .../templates/cozystack-operator.yaml | 45 ----------- .../core/installer/templates/cozystack.yaml | 81 ------------------- packages/core/installer/templates/crds.yaml | 2 - .../installer/templates/packagesource.yaml | 43 ++++++++++ packages/core/installer/values.yaml | 9 +-- .../images/cozystack-assets/Dockerfile | 25 ------ .../platform/images/migrations/Dockerfile | 12 +++ .../platform/images/migrations}/migrations/1 | 0 .../platform/images/migrations}/migrations/10 | 0 .../platform/images/migrations}/migrations/11 | 0 .../platform/images/migrations}/migrations/12 | 0 .../platform/images/migrations}/migrations/13 | 0 .../platform/images/migrations}/migrations/14 | 0 .../platform/images/migrations}/migrations/15 | 0 .../platform/images/migrations}/migrations/16 | 0 .../platform/images/migrations}/migrations/17 | 0 .../platform/images/migrations}/migrations/18 | 0 .../platform/images/migrations}/migrations/19 | 0 .../platform/images/migrations}/migrations/2 | 0 .../platform/images/migrations}/migrations/20 | 0 .../platform/images/migrations}/migrations/21 | 0 .../platform/images/migrations}/migrations/22 | 0 .../platform/images/migrations}/migrations/3 | 0 .../platform/images/migrations}/migrations/4 | 0 .../platform/images/migrations}/migrations/5 | 0 .../platform/images/migrations}/migrations/6 | 0 .../platform/images/migrations}/migrations/7 | 0 .../platform/images/migrations}/migrations/8 | 0 .../platform/images/migrations}/migrations/9 | 0 .../images/migrations/run-migrations.sh | 41 ++++++++++ packages/system/grafana-operator/Makefile | 12 +++ .../images/grafana-dashboards.tag | 1 + .../images/grafana-dashboards/Dockerfile | 11 +++ .../Dockerfile.dockerignore | 3 + scripts/installer.sh | 71 ---------------- 40 files changed, 154 insertions(+), 343 deletions(-) delete mode 100644 cmd/cozystack-assets-server/main.go delete mode 100644 packages/core/installer/images/cozystack/Dockerfile delete mode 100644 packages/core/installer/images/cozystack/Dockerfile.dockerignore delete mode 100644 packages/core/installer/templates/cozystack.yaml create mode 100644 packages/core/installer/templates/packagesource.yaml delete mode 100644 packages/core/platform/images/cozystack-assets/Dockerfile create mode 100644 packages/core/platform/images/migrations/Dockerfile rename {scripts => packages/core/platform/images/migrations}/migrations/1 (100%) rename {scripts => packages/core/platform/images/migrations}/migrations/10 (100%) rename {scripts => packages/core/platform/images/migrations}/migrations/11 (100%) rename {scripts => packages/core/platform/images/migrations}/migrations/12 (100%) rename {scripts => packages/core/platform/images/migrations}/migrations/13 (100%) rename {scripts => packages/core/platform/images/migrations}/migrations/14 (100%) rename {scripts => packages/core/platform/images/migrations}/migrations/15 (100%) rename {scripts => packages/core/platform/images/migrations}/migrations/16 (100%) rename {scripts => packages/core/platform/images/migrations}/migrations/17 (100%) rename {scripts => packages/core/platform/images/migrations}/migrations/18 (100%) rename {scripts => packages/core/platform/images/migrations}/migrations/19 (100%) rename {scripts => packages/core/platform/images/migrations}/migrations/2 (100%) rename {scripts => packages/core/platform/images/migrations}/migrations/20 (100%) rename {scripts => packages/core/platform/images/migrations}/migrations/21 (100%) rename {scripts => packages/core/platform/images/migrations}/migrations/22 (100%) rename {scripts => packages/core/platform/images/migrations}/migrations/3 (100%) rename {scripts => packages/core/platform/images/migrations}/migrations/4 (100%) rename {scripts => packages/core/platform/images/migrations}/migrations/5 (100%) rename {scripts => packages/core/platform/images/migrations}/migrations/6 (100%) rename {scripts => packages/core/platform/images/migrations}/migrations/7 (100%) rename {scripts => packages/core/platform/images/migrations}/migrations/8 (100%) rename {scripts => packages/core/platform/images/migrations}/migrations/9 (100%) create mode 100755 packages/core/platform/images/migrations/run-migrations.sh create mode 100644 packages/system/grafana-operator/images/grafana-dashboards.tag create mode 100644 packages/system/grafana-operator/images/grafana-dashboards/Dockerfile create mode 100644 packages/system/grafana-operator/images/grafana-dashboards/Dockerfile.dockerignore delete mode 100755 scripts/installer.sh diff --git a/Makefile b/Makefile index d0f5dd69..971775cb 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: manifests repos assets unit-tests helm-unit-tests +.PHONY: manifests assets unit-tests helm-unit-tests build-deps: @command -V find docker skopeo jq gh helm > /dev/null @@ -16,6 +16,7 @@ build: build-deps 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/backupstrategy-controller image make -C packages/system/lineage-controller-webhook image make -C packages/system/cilium image make -C packages/system/linstor image @@ -26,21 +27,15 @@ build: build-deps make -C packages/system/kamaji image make -C packages/system/bucket image make -C packages/system/objectstorage-controller image + make -C packages/system/grafana-operator image make -C packages/core/testing image make -C packages/core/talos image - make -C packages/core/platform image make -C packages/core/installer image make manifests -repos: - rm -rf _out - make -C packages/system repo - make -C packages/apps repo - make -C packages/extra repo - manifests: mkdir -p _out/assets - (cd packages/core/installer/; helm template -n cozy-installer installer .) > _out/assets/cozystack-installer.yaml + (cd packages/core/installer/; helm template --namespace cozy-installer installer .) > _out/assets/cozystack-installer.yaml assets: make -C packages/core/talos assets diff --git a/cmd/cozystack-assets-server/main.go b/cmd/cozystack-assets-server/main.go deleted file mode 100644 index 75563712..00000000 --- a/cmd/cozystack-assets-server/main.go +++ /dev/null @@ -1,29 +0,0 @@ -package main - -import ( - "flag" - "log" - "net/http" - "path/filepath" -) - -func main() { - addr := flag.String("address", ":8123", "Address to listen on") - dir := flag.String("dir", "/cozystack/assets", "Directory to serve files from") - flag.Parse() - - absDir, err := filepath.Abs(*dir) - if err != nil { - log.Fatalf("Error getting absolute path for %s: %v", *dir, err) - } - - fs := http.FileServer(http.Dir(absDir)) - http.Handle("/", fs) - - log.Printf("Server starting on %s, serving directory %s", *addr, absDir) - - err = http.ListenAndServe(*addr, nil) - if err != nil { - log.Fatalf("Server failed to start: %v", err) - } -} diff --git a/packages/core/installer/Makefile b/packages/core/installer/Makefile index 4c19c258..f4b527bf 100644 --- a/packages/core/installer/Makefile +++ b/packages/core/installer/Makefile @@ -7,51 +7,42 @@ pre-checks: ../../../hack/pre-checks.sh show: - cozyhr show -n $(NAMESPACE) $(NAME) --plain + cozyhr show --namespace $(NAMESPACE) $(NAME) --plain apply: - cozyhr show -n $(NAMESPACE) $(NAME) --plain | kubectl apply -f- + cozyhr show --namespace $(NAMESPACE) $(NAME) --plain | kubectl apply --filename - diff: - cozyhr show -n $(NAMESPACE) $(NAME) --plain | kubectl diff -f - + cozyhr show --namespace $(NAMESPACE) $(NAME) --plain | kubectl diff --filename - -image: pre-checks image-cozystack - -image-cozystack: - docker buildx build -f images/cozystack/Dockerfile ../../.. \ - --tag $(REGISTRY)/installer:$(call settag,$(TAG)) \ - --cache-from type=registry,ref=$(REGISTRY)/installer:latest \ - --cache-to type=inline \ - --metadata-file images/installer.json \ - $(BUILDX_ARGS) - IMAGE="$(REGISTRY)/installer:$(call settag,$(TAG))@$$(yq e '."containerimage.digest"' images/installer.json -o json -r)" \ - yq -i '.cozystack.image = strenv(IMAGE)' values.yaml - rm -f images/installer.json +image: pre-checks image-operator image-packages update-version: TAG="$(call settag,$(TAG))" \ - yq -i '.cozystackOperator.cozystackVersion = strenv(TAG)' values.yaml + yq --inplace '.cozystackOperator.cozystackVersion = strenv(TAG)' values.yaml image-operator: - docker buildx build -f images/cozystack-operator/Dockerfile ../../.. \ - --tag $(REGISTRY)/cozystack-operator:$(call settag,$(TAG)) \ - --cache-from type=registry,ref=$(REGISTRY)/cozystack-operator:latest \ - --cache-to type=inline \ - --metadata-file images/cozystack-operator.json \ - $(BUILDX_ARGS) - IMAGE="$(REGISTRY)/cozystack-operator:$(call settag,$(TAG))@$$(yq e '."containerimage.digest"' images/cozystack-operator.json -o json -r)" \ - yq -i '.cozystackOperator.image = strenv(IMAGE)' values.yaml + docker buildx build --file images/cozystack-operator/Dockerfile ../../.. \ + --tag $(REGISTRY)/cozystack-operator:$(call settag,$(TAG)) \ + --cache-from type=registry,ref=$(REGISTRY)/cozystack-operator:latest \ + --cache-to type=inline \ + --metadata-file images/cozystack-operator.json \ + $(BUILDX_ARGS) + IMAGE="$(REGISTRY)/cozystack-operator:$(call settag,$(TAG))@$$(yq -e '.["containerimage.digest"]' images/cozystack-operator.json -o json -r)" \ + yq --inplace '.cozystackOperator.image = strenv(IMAGE)' values.yaml rm -f images/cozystack-operator.json - image-packages: update-version mkdir -p ../../../_out/assets images flux push artifact \ - oci://$(REGISTRY)/platform-packages:$(call settag,$(TAG)) \ - --path=../../../packages \ - --source=https://github.com/cozystack/cozystack \ - --revision="$$(git describe --tags):$$(git rev-parse HEAD)" \ - 2>&1 | tee images/cozystack-packages.log - export REPO="oci://$(REGISTRY)/platform-packages"; \ - export DIGEST=$$(awk -F@ '/artifact successfully pushed/ {print $$2}' images/cozystack-packages.log; rm -f images/cozystack-packages.log); \ - test -n "$$DIGEST" && yq -i '.cozystackOperator.platformSource = (strenv(REPO) + "@" + strenv(DIGEST))' values.yaml + oci://$(REGISTRY)/cozystack-packages:$(call settag,$(TAG)) \ + --path=../../../packages \ + --source=https://github.com/cozystack/cozystack \ + --revision="$$(git describe --tags):$$(git rev-parse HEAD)" \ + 2>&1 | tee images/cozystack-packages.log + export REPO="oci://$(REGISTRY)/cozystack-packages" \ + export DIGEST=$$(awk --field-separator @ '/artifact successfully pushed/ {print $$2}' images/cozystack-packages.log) && \ + rm -f images/cozystack-packages.log && \ + test -n "$$DIGEST" && \ + yq --inplace '.cozystackOperator.platformSourceUrl = strenv(REPO)' values.yaml && \ + yq --inplace '.cozystackOperator.platformSourceRef = "digest=" + strenv(DIGEST)' values.yaml diff --git a/packages/core/installer/images/cozystack/Dockerfile b/packages/core/installer/images/cozystack/Dockerfile deleted file mode 100644 index 8e1ae219..00000000 --- a/packages/core/installer/images/cozystack/Dockerfile +++ /dev/null @@ -1,41 +0,0 @@ -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 - -# TARGETARCH is a docker special variable: https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope -ARG TARGETARCH - -RUN apk add --no-cache git make -RUN git clone ${K8S_AWAIT_ELECTION_GITREPO} /usr/local/go/k8s-await-election/ \ - && cd /usr/local/go/k8s-await-election \ - && git reset --hard v${K8S_AWAIT_ELECTION_VERSION} \ - && make \ - && mv ./out/k8s-await-election-${TARGETARCH} /k8s-await-election - -FROM golang:1.25-alpine AS builder - -ARG TARGETOS -ARG TARGETARCH - -RUN apk add --no-cache make git -RUN apk add helm --repository=https://dl-cdn.alpinelinux.org/alpine/edge/community - -COPY . /src/ -WORKDIR /src - -RUN go mod download - -FROM alpine:3.22 - -RUN wget -O- https://github.com/cozystack/cozyhr/raw/refs/heads/main/hack/install.sh | sh -s -- -v 1.5.0 - -RUN apk add --no-cache make kubectl helm coreutils git jq openssl - -COPY --from=builder /src/scripts /cozystack/scripts -COPY --from=builder /src/packages/core /cozystack/packages/core -COPY --from=builder /src/packages/system /cozystack/packages/system -COPY --from=k8s-await-election-builder /k8s-await-election /usr/bin/k8s-await-election - -WORKDIR /cozystack -ENTRYPOINT ["/usr/bin/k8s-await-election", "/cozystack/scripts/installer.sh" ] diff --git a/packages/core/installer/images/cozystack/Dockerfile.dockerignore b/packages/core/installer/images/cozystack/Dockerfile.dockerignore deleted file mode 100644 index c1d18d8a..00000000 --- a/packages/core/installer/images/cozystack/Dockerfile.dockerignore +++ /dev/null @@ -1 +0,0 @@ -_out diff --git a/packages/core/installer/templates/cozystack-operator.yaml b/packages/core/installer/templates/cozystack-operator.yaml index 514dcc32..7cc215b1 100644 --- a/packages/core/installer/templates/cozystack-operator.yaml +++ b/packages/core/installer/templates/cozystack-operator.yaml @@ -1,4 +1,3 @@ -{{- if .Values.cozystackOperator.enabled }} --- apiVersion: v1 kind: Namespace @@ -78,47 +77,3 @@ spec: - key: "node.cilium.io/agent-not-ready" operator: "Exists" effect: "NoSchedule" ---- -apiVersion: cozystack.io/v1alpha1 -kind: PackageSource -metadata: - name: cozystack.cozystack-platform - annotations: - operator.cozystack.io/skip-cozystack-values: "true" -spec: - sourceRef: - kind: OCIRepository - name: cozystack-packages - namespace: cozy-system - path: / - variants: - - name: default - components: - - install: - namespace: cozy-system - releaseName: cozystack-platform - name: cozystack-platform - path: core/platform - valuesFiles: - - values.yaml - - name: isp-full - components: - - install: - namespace: cozy-system - releaseName: cozystack-platform - name: cozystack-platform - path: core/platform - valuesFiles: - - values.yaml - - values-isp-full.yaml - - name: isp-hosted - components: - - install: - namespace: cozy-system - releaseName: cozystack-platform - name: cozystack-platform - path: core/platform - valuesFiles: - - values.yaml - - values-isp-hosted.yaml -{{- end }} diff --git a/packages/core/installer/templates/cozystack.yaml b/packages/core/installer/templates/cozystack.yaml deleted file mode 100644 index 24ca0557..00000000 --- a/packages/core/installer/templates/cozystack.yaml +++ /dev/null @@ -1,81 +0,0 @@ -{{- if not .Values.cozystackOperator.enabled }} ---- -apiVersion: v1 -kind: Namespace -metadata: - name: cozy-system - labels: - cozystack.io/system: "true" - pod-security.kubernetes.io/enforce: privileged ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: cozystack - namespace: cozy-system ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: cozystack -subjects: -- kind: ServiceAccount - name: cozystack - namespace: cozy-system -roleRef: - kind: ClusterRole - name: cluster-admin - apiGroup: rbac.authorization.k8s.io ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: cozystack - namespace: cozy-system -spec: - replicas: 1 - selector: - matchLabels: - app: cozystack - strategy: - type: RollingUpdate - rollingUpdate: - maxSurge: 0 - maxUnavailable: 1 - template: - metadata: - labels: - app: cozystack - spec: - hostNetwork: true - serviceAccountName: cozystack - containers: - - name: cozystack - image: "{{ .Values.cozystack.image }}" - env: - - name: KUBERNETES_SERVICE_HOST - value: localhost - - name: INSTALL_FLUX - value: "true" - - name: KUBERNETES_SERVICE_PORT - value: "7445" - - name: K8S_AWAIT_ELECTION_ENABLED - value: "1" - - name: K8S_AWAIT_ELECTION_NAME - value: cozystack - - name: K8S_AWAIT_ELECTION_LOCK_NAME - value: cozystack - - name: K8S_AWAIT_ELECTION_LOCK_NAMESPACE - value: cozy-system - - name: K8S_AWAIT_ELECTION_IDENTITY - valueFrom: - fieldRef: - fieldPath: metadata.name - tolerations: - - key: "node.kubernetes.io/not-ready" - operator: "Exists" - effect: "NoSchedule" - - key: "node.cilium.io/agent-not-ready" - operator: "Exists" - effect: "NoSchedule" -{{- end }} diff --git a/packages/core/installer/templates/crds.yaml b/packages/core/installer/templates/crds.yaml index cffc1205..32b438c1 100644 --- a/packages/core/installer/templates/crds.yaml +++ b/packages/core/installer/templates/crds.yaml @@ -1,6 +1,4 @@ -{{- if .Values.cozystackOperator.enabled }} {{- range $path, $_ := .Files.Glob "definitions/*.yaml" }} --- {{ $.Files.Get $path }} {{- end }} -{{- end }} diff --git a/packages/core/installer/templates/packagesource.yaml b/packages/core/installer/templates/packagesource.yaml new file mode 100644 index 00000000..7a3dd73e --- /dev/null +++ b/packages/core/installer/templates/packagesource.yaml @@ -0,0 +1,43 @@ +--- +apiVersion: cozystack.io/v1alpha1 +kind: PackageSource +metadata: + name: cozystack.cozystack-platform + annotations: + operator.cozystack.io/skip-cozystack-values: "true" +spec: + sourceRef: + kind: OCIRepository + name: cozystack-platform + namespace: cozy-system + path: / + variants: + - name: default + components: + - install: + namespace: cozy-system + releaseName: cozystack-platform + name: platform + path: core/platform + valuesFiles: + - values.yaml + - name: isp-full + components: + - install: + namespace: cozy-system + releaseName: cozystack-platform + name: platform + path: core/platform + valuesFiles: + - values.yaml + - values-isp-full.yaml + - name: isp-hosted + components: + - install: + namespace: cozy-system + releaseName: cozystack-platform + name: platform + path: core/platform + valuesFiles: + - values.yaml + - values-isp-hosted.yaml diff --git a/packages/core/installer/values.yaml b/packages/core/installer/values.yaml index 0379a40c..e38b4dfb 100644 --- a/packages/core/installer/values.yaml +++ b/packages/core/installer/values.yaml @@ -1,8 +1,5 @@ -cozystack: - image: ghcr.io/cozystack/cozystack/installer:v0.40.2@sha256:146e0de9d1208eba8342277fa08e0588d66e51bc637c6b3add8ef63cc54ef351 cozystackOperator: - enabled: false - image: ghcr.io/cozystack/cozystack/cozystack-operator:latest@sha256:f7f6e0fd9e896b7bfa642d0bfa4378bc14e646bc5c2e86e2e09a82770ef33181 - platformSourceUrl: 'oci://ghcr.io/cozystack/cozystack/platform-packages' - platformSourceRef: 'digest=sha256:0576491291b33936cdf770a5c5b5692add97339c1505fc67a92df9d69dfbfdf6' + image: ghcr.io/cozystack/cozystack/cozystack-operator:latest@sha256:1e998a7cf2b9ed10b1320d9b3282e9362283fcda524f22fa0ad40579e7e464ff + platformSourceUrl: 'oci://ghcr.io/cozystack/cozystack/cozystack-packages' + platformSourceRef: 'digest=sha256:319e78e454cdc28e1d6edd701e4b09723bfb12a03c71b77271d136b09002ad7e' cozystackVersion: latest diff --git a/packages/core/platform/images/cozystack-assets/Dockerfile b/packages/core/platform/images/cozystack-assets/Dockerfile deleted file mode 100644 index bf284de3..00000000 --- a/packages/core/platform/images/cozystack-assets/Dockerfile +++ /dev/null @@ -1,25 +0,0 @@ -FROM golang:1.25-alpine AS builder - -ARG TARGETOS -ARG TARGETARCH - -RUN apk add --no-cache make git -RUN apk add helm --repository=https://dl-cdn.alpinelinux.org/alpine/edge/community - -COPY . /src/ -WORKDIR /src - -RUN go mod download - -RUN go build -o /cozystack-assets-server -ldflags '-extldflags "-static" -w -s' ./cmd/cozystack-assets-server - -RUN make repos - -FROM alpine:3.22 - -COPY --from=builder /src/_out/repos /cozystack/assets/repos -COPY --from=builder /cozystack-assets-server /usr/bin/cozystack-assets-server -COPY --from=builder /src/dashboards /cozystack/assets/dashboards - -WORKDIR /cozystack -ENTRYPOINT ["/usr/bin/cozystack-assets-server"] diff --git a/packages/core/platform/images/migrations/Dockerfile b/packages/core/platform/images/migrations/Dockerfile new file mode 100644 index 00000000..137c2768 --- /dev/null +++ b/packages/core/platform/images/migrations/Dockerfile @@ -0,0 +1,12 @@ +FROM alpine:3.22 + +RUN wget -O- https://github.com/cozystack/cozyhr/raw/refs/heads/main/hack/install.sh | sh -s -- -v 1.5.0 + +RUN apk add --no-cache kubectl helm coreutils git jq ca-certificates bash curl + +COPY migrations /migrations +COPY run-migrations.sh /usr/bin/run-migrations.sh + +WORKDIR /migrations + +ENTRYPOINT ["/usr/bin/run-migrations.sh"] diff --git a/scripts/migrations/1 b/packages/core/platform/images/migrations/migrations/1 similarity index 100% rename from scripts/migrations/1 rename to packages/core/platform/images/migrations/migrations/1 diff --git a/scripts/migrations/10 b/packages/core/platform/images/migrations/migrations/10 similarity index 100% rename from scripts/migrations/10 rename to packages/core/platform/images/migrations/migrations/10 diff --git a/scripts/migrations/11 b/packages/core/platform/images/migrations/migrations/11 similarity index 100% rename from scripts/migrations/11 rename to packages/core/platform/images/migrations/migrations/11 diff --git a/scripts/migrations/12 b/packages/core/platform/images/migrations/migrations/12 similarity index 100% rename from scripts/migrations/12 rename to packages/core/platform/images/migrations/migrations/12 diff --git a/scripts/migrations/13 b/packages/core/platform/images/migrations/migrations/13 similarity index 100% rename from scripts/migrations/13 rename to packages/core/platform/images/migrations/migrations/13 diff --git a/scripts/migrations/14 b/packages/core/platform/images/migrations/migrations/14 similarity index 100% rename from scripts/migrations/14 rename to packages/core/platform/images/migrations/migrations/14 diff --git a/scripts/migrations/15 b/packages/core/platform/images/migrations/migrations/15 similarity index 100% rename from scripts/migrations/15 rename to packages/core/platform/images/migrations/migrations/15 diff --git a/scripts/migrations/16 b/packages/core/platform/images/migrations/migrations/16 similarity index 100% rename from scripts/migrations/16 rename to packages/core/platform/images/migrations/migrations/16 diff --git a/scripts/migrations/17 b/packages/core/platform/images/migrations/migrations/17 similarity index 100% rename from scripts/migrations/17 rename to packages/core/platform/images/migrations/migrations/17 diff --git a/scripts/migrations/18 b/packages/core/platform/images/migrations/migrations/18 similarity index 100% rename from scripts/migrations/18 rename to packages/core/platform/images/migrations/migrations/18 diff --git a/scripts/migrations/19 b/packages/core/platform/images/migrations/migrations/19 similarity index 100% rename from scripts/migrations/19 rename to packages/core/platform/images/migrations/migrations/19 diff --git a/scripts/migrations/2 b/packages/core/platform/images/migrations/migrations/2 similarity index 100% rename from scripts/migrations/2 rename to packages/core/platform/images/migrations/migrations/2 diff --git a/scripts/migrations/20 b/packages/core/platform/images/migrations/migrations/20 similarity index 100% rename from scripts/migrations/20 rename to packages/core/platform/images/migrations/migrations/20 diff --git a/scripts/migrations/21 b/packages/core/platform/images/migrations/migrations/21 similarity index 100% rename from scripts/migrations/21 rename to packages/core/platform/images/migrations/migrations/21 diff --git a/scripts/migrations/22 b/packages/core/platform/images/migrations/migrations/22 similarity index 100% rename from scripts/migrations/22 rename to packages/core/platform/images/migrations/migrations/22 diff --git a/scripts/migrations/3 b/packages/core/platform/images/migrations/migrations/3 similarity index 100% rename from scripts/migrations/3 rename to packages/core/platform/images/migrations/migrations/3 diff --git a/scripts/migrations/4 b/packages/core/platform/images/migrations/migrations/4 similarity index 100% rename from scripts/migrations/4 rename to packages/core/platform/images/migrations/migrations/4 diff --git a/scripts/migrations/5 b/packages/core/platform/images/migrations/migrations/5 similarity index 100% rename from scripts/migrations/5 rename to packages/core/platform/images/migrations/migrations/5 diff --git a/scripts/migrations/6 b/packages/core/platform/images/migrations/migrations/6 similarity index 100% rename from scripts/migrations/6 rename to packages/core/platform/images/migrations/migrations/6 diff --git a/scripts/migrations/7 b/packages/core/platform/images/migrations/migrations/7 similarity index 100% rename from scripts/migrations/7 rename to packages/core/platform/images/migrations/migrations/7 diff --git a/scripts/migrations/8 b/packages/core/platform/images/migrations/migrations/8 similarity index 100% rename from scripts/migrations/8 rename to packages/core/platform/images/migrations/migrations/8 diff --git a/scripts/migrations/9 b/packages/core/platform/images/migrations/migrations/9 similarity index 100% rename from scripts/migrations/9 rename to packages/core/platform/images/migrations/migrations/9 diff --git a/packages/core/platform/images/migrations/run-migrations.sh b/packages/core/platform/images/migrations/run-migrations.sh new file mode 100755 index 00000000..c35ade21 --- /dev/null +++ b/packages/core/platform/images/migrations/run-migrations.sh @@ -0,0 +1,41 @@ +#!/bin/sh +set -euo pipefail + +NAMESPACE="${NAMESPACE:-cozy-system}" +CURRENT_VERSION="${CURRENT_VERSION:-0}" +TARGET_VERSION="${TARGET_VERSION:-0}" + +echo "Starting migrations from version $CURRENT_VERSION to $TARGET_VERSION" + +# Check if ConfigMap exists +if ! kubectl get configmap --namespace "$NAMESPACE" cozystack-version >/dev/null 2>&1; then + echo "ConfigMap cozystack-version does not exist, creating it with version $TARGET_VERSION" + kubectl create configmap --namespace "$NAMESPACE" cozystack-version \ + --from-literal=version="$TARGET_VERSION" \ + --dry-run=client --output yaml | kubectl apply --filename - + echo "ConfigMap created with version $TARGET_VERSION" + exit 0 +fi + +# If current version is already at target, nothing to do +if [ "$CURRENT_VERSION" -ge "$TARGET_VERSION" ]; then + echo "Current version $CURRENT_VERSION is already at or above target version $TARGET_VERSION" + exit 0 +fi + +# Run migrations sequentially from current version to target version +for i in $(seq $((CURRENT_VERSION + 1)) $TARGET_VERSION); do + if [ -f "/migrations/$i" ]; then + echo "Running migration $i" + chmod +x /migrations/$i + /migrations/$i || { + echo "Migration $i failed" + exit 1 + } + echo "Migration $i completed successfully" + else + echo "Migration $i not found, skipping" + fi +done + +echo "All migrations completed successfully" diff --git a/packages/system/grafana-operator/Makefile b/packages/system/grafana-operator/Makefile index 82c734d4..a752d25b 100644 --- a/packages/system/grafana-operator/Makefile +++ b/packages/system/grafana-operator/Makefile @@ -1,6 +1,7 @@ export NAME=grafana-operator export NAMESPACE=cozy-grafana-operator +include ../../../scripts/common-envs.mk include ../../../scripts/package.mk update: @@ -8,3 +9,14 @@ update: mkdir -p charts curl -sSL https://github.com/grafana-operator/grafana-operator/archive/refs/heads/master.tar.gz | \ tar xzvf - --strip 3 -C charts grafana-operator-master/deploy/helm/grafana-operator + +image: + docker buildx build --file images/grafana-dashboards/Dockerfile ../../.. \ + --tag $(REGISTRY)/grafana-dashboards:$(call settag,$(TAG)) \ + --cache-from type=registry,ref=$(REGISTRY)/grafana-dashboards:latest \ + --cache-to type=inline \ + --metadata-file images/grafana-dashboards.json \ + $(BUILDX_ARGS) + echo "$(REGISTRY)/grafana-dashboards:$(call settag,$(TAG))@$$(yq --exit-status '.["containerimage.digest"]' images/grafana-dashboards.json --output-format json -r)" \ + > images/grafana-dashboards.tag + rm -f images/grafana-dashboards.json diff --git a/packages/system/grafana-operator/images/grafana-dashboards.tag b/packages/system/grafana-operator/images/grafana-dashboards.tag new file mode 100644 index 00000000..bd0f3b14 --- /dev/null +++ b/packages/system/grafana-operator/images/grafana-dashboards.tag @@ -0,0 +1 @@ +ghcr.io/cozystack/cozystack/grafana-dashboards:latest diff --git a/packages/system/grafana-operator/images/grafana-dashboards/Dockerfile b/packages/system/grafana-operator/images/grafana-dashboards/Dockerfile new file mode 100644 index 00000000..8ea43e76 --- /dev/null +++ b/packages/system/grafana-operator/images/grafana-dashboards/Dockerfile @@ -0,0 +1,11 @@ +FROM alpine:3.22 + +RUN apk add --no-cache darkhttpd + +COPY dashboards /var/www/dashboards + +WORKDIR /var/www + +EXPOSE 8080 + +CMD ["darkhttpd", "/var/www/dashboards", "--port", "8080", "--addr", "0.0.0.0"] diff --git a/packages/system/grafana-operator/images/grafana-dashboards/Dockerfile.dockerignore b/packages/system/grafana-operator/images/grafana-dashboards/Dockerfile.dockerignore new file mode 100644 index 00000000..fa588871 --- /dev/null +++ b/packages/system/grafana-operator/images/grafana-dashboards/Dockerfile.dockerignore @@ -0,0 +1,3 @@ +# Exclude everything except dashboards directory +* +!dashboards/** diff --git a/scripts/installer.sh b/scripts/installer.sh deleted file mode 100755 index 042e895a..00000000 --- a/scripts/installer.sh +++ /dev/null @@ -1,71 +0,0 @@ -#!/bin/sh -set -o pipefail -set -e - -BUNDLE=$(set -x; kubectl get configmap -n cozy-system cozystack -o 'go-template={{index .data "bundle-name"}}') -VERSION=$(find scripts/migrations -mindepth 1 -maxdepth 1 -type f | sort -V | awk -F/ 'END {print $NF+1}') - -run_migrations() { - if ! kubectl get configmap -n cozy-system cozystack-version; then - kubectl create configmap -n cozy-system cozystack-version --from-literal=version="$VERSION" --dry-run=client -o yaml | kubectl create -f- - return - fi - current_version=$(kubectl get configmap -n cozy-system cozystack-version -o jsonpath='{.data.version}') || true - until [ "$current_version" = "$VERSION" ]; do - echo "run migration: $current_version --> $VERSION" - chmod +x scripts/migrations/$current_version - scripts/migrations/$current_version - current_version=$(kubectl get configmap -n cozy-system cozystack-version -o jsonpath='{.data.version}') - done -} - -install_flux() { - if [ "$INSTALL_FLUX" != "true" ]; then - return - fi - make -C packages/core/flux-aio apply - wait_for_crds helmreleases.helm.toolkit.fluxcd.io helmrepositories.source.toolkit.fluxcd.io -} - -wait_for_crds() { - timeout 60 sh -c "until kubectl get crd $*; do sleep 1; done" -} - -cd "$(dirname "$0")/.." - -# Run migrations -run_migrations - -# Install namespaces -make -C packages/core/platform namespaces-apply - -# Install fluxcd -install_flux - -# Install fluxcd certificates -./scripts/issue-flux-certificates.sh - -# Install platform chart -make -C packages/core/platform reconcile - -# Reconcile Helm repositories -kubectl annotate helmrepositories.source.toolkit.fluxcd.io -A -l cozystack.io/repository reconcile.fluxcd.io/requestedAt=$(date +"%Y-%m-%dT%H:%M:%SZ") --overwrite - -# Unsuspend all Cozystack managed charts -kubectl get hr -A -o go-template='{{ range .items }}{{ if .spec.suspend }}{{ .spec.chart.spec.sourceRef.namespace }}/{{ .spec.chart.spec.sourceRef.name }} {{ .metadata.namespace }} {{ .metadata.name }}{{ "\n" }}{{ end }}{{ end }}' | while read repo namespace name; do - case "$repo" in - cozy-system/cozystack-system|cozy-public/cozystack-extra|cozy-public/cozystack-apps) - kubectl patch hr -n "$namespace" "$name" -p '{"spec": {"suspend": null}}' --type=merge --field-manager=flux-client-side-apply - ;; - esac -done - -# Update all Cozystack managed charts to latest version -kubectl get hr -A -l cozystack.io/ui=true --no-headers | awk '{print "kubectl patch helmrelease -n " $1 " " $2 " --type=merge -p '\''{\"spec\":{\"chart\":{\"spec\":{\"version\":\">= 0.0.0-0\"}}}}'\'' "}' | sh -x - -# Reconcile platform chart -trap 'exit' INT TERM -while true; do - sleep 60 & wait - make -C packages/core/platform reconcile -done From 8e9c0dd5adbb03e83a131874c85d8d69cf1d80a0 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 15 Jan 2026 14:14:55 +0100 Subject: [PATCH 050/889] refactor(cozyrds): update chartRef to use ExternalArtifact Update all CozystackResourceDefinition files to use chartRef with ExternalArtifact instead of OCIRepository sourceRef. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- hack/update-crd.sh | 21 +++++++++++++------ .../system/bootbox-rd/cozyrds/bootbox.yaml | 11 +++++----- packages/system/bucket-rd/cozyrds/bucket.yaml | 11 +++++----- .../clickhouse-rd/cozyrds/clickhouse.yaml | 11 +++++----- packages/system/etcd-rd/cozyrds/etcd.yaml | 11 +++++----- .../system/ferretdb-rd/cozyrds/ferretdb.yaml | 11 +++++----- .../foundationdb-rd/cozyrds/foundationdb.yaml | 11 +++++----- .../http-cache-rd/cozyrds/http-cache.yaml | 11 +++++----- packages/system/info-rd/cozyrds/info.yaml | 11 +++++----- .../system/ingress-rd/cozyrds/ingress.yaml | 11 +++++----- packages/system/kafka-rd/cozyrds/kafka.yaml | 11 +++++----- .../kubernetes-rd/cozyrds/kubernetes.yaml | 11 +++++----- .../monitoring-rd/cozyrds/monitoring.yaml | 11 +++++----- packages/system/mysql-rd/cozyrds/mysql.yaml | 11 +++++----- packages/system/nats-rd/cozyrds/nats.yaml | 11 +++++----- .../system/postgres-rd/cozyrds/postgres.yaml | 11 +++++----- .../system/rabbitmq-rd/cozyrds/rabbitmq.yaml | 11 +++++----- packages/system/redis-rd/cozyrds/redis.yaml | 11 +++++----- .../seaweedfs-rd/cozyrds/seaweedfs.yaml | 11 +++++----- .../tcp-balancer-rd/cozyrds/tcp-balancer.yaml | 11 +++++----- packages/system/tenant-rd/cozyrds/tenant.yaml | 11 +++++----- .../cozyrds/virtual-machine.yaml | 11 +++++----- .../cozyrds/virtualprivatecloud.yaml | 11 +++++----- .../system/vm-disk-rd/cozyrds/vm-disk.yaml | 11 +++++----- .../vm-instance-rd/cozyrds/vm-instance.yaml | 11 +++++----- packages/system/vpn-rd/cozyrds/vpn.yaml | 11 +++++----- 26 files changed, 140 insertions(+), 156 deletions(-) diff --git a/hack/update-crd.sh b/hack/update-crd.sh index b619b220..ce9d5d10 100755 --- a/hack/update-crd.sh +++ b/hack/update-crd.sh @@ -65,6 +65,15 @@ case "$PWD" in *"/extra/"*) SOURCE_NAME="cozystack-extra" ;; esac +# Determine variant from PackageSource file +# Look for packages/core/platform/sources/${NAME}-application.yaml +PACKAGE_SOURCE_FILE="../../core/platform/sources/${NAME}-application.yaml" +if [[ -f "$PACKAGE_SOURCE_FILE" ]]; then + VARIANT="$(yq -r '.spec.variants[0].name // "default"' "$PACKAGE_SOURCE_FILE")" +else + VARIANT="default" +fi + # If file doesn't exist, create a minimal skeleton OUT="${OUT:-$CRD_DIR/$NAME.yaml}" if [[ ! -f "$OUT" ]]; then @@ -86,6 +95,7 @@ fi export DESCRIPTION="$DESC" export ICON_B64="$ICON_B64" export SOURCE_NAME="$SOURCE_NAME" +export VARIANT="$VARIANT" export SCHEMA_JSON_MIN="$(jq -c . "$SCHEMA_JSON")" # Generate keysOrder from values.yaml @@ -118,8 +128,8 @@ export KEYS_ORDER="$( # Update only necessary fields in-place # - openAPISchema is loaded from file as a multi-line string (block scalar) # - labels ensure cozystack.io/ui: "true" -# - prefix = "-" -# - sourceRef derived from directory (apps|extra) +# - prefix = "-" or "" for extra +# - chartRef points to ExternalArtifact created by Package controller yq -i ' .apiVersion = (.apiVersion // "cozystack.io/v1alpha1") | .kind = (.kind // "CozystackResourceDefinition") | @@ -128,10 +138,9 @@ yq -i ' (.spec.application.openAPISchema style="literal") | .spec.release.prefix = (strenv(PREFIX)) | .spec.release.labels."cozystack.io/ui" = "true" | - .spec.release.chart.name = strenv(RES_NAME) | - .spec.release.chart.sourceRef.kind = "HelmRepository" | - .spec.release.chart.sourceRef.name = strenv(SOURCE_NAME) | - .spec.release.chart.sourceRef.namespace = "cozy-public" | + .spec.release.chartRef.kind = "ExternalArtifact" | + .spec.release.chartRef.name = ("cozystack-" + strenv(RES_NAME) + "-application-" + strenv(VARIANT) + "-" + strenv(RES_NAME)) | + .spec.release.chartRef.namespace = "cozy-system" | .spec.dashboard.description = strenv(DESCRIPTION) | .spec.dashboard.icon = strenv(ICON_B64) | .spec.dashboard.keysOrder = env(KEYS_ORDER) diff --git a/packages/system/bootbox-rd/cozyrds/bootbox.yaml b/packages/system/bootbox-rd/cozyrds/bootbox.yaml index f47bf8f6..7ed1f52f 100644 --- a/packages/system/bootbox-rd/cozyrds/bootbox.yaml +++ b/packages/system/bootbox-rd/cozyrds/bootbox.yaml @@ -13,12 +13,11 @@ spec: prefix: "" labels: cozystack.io/ui: "true" - chart: - name: bootbox - sourceRef: - kind: HelmRepository - name: cozystack-extra - namespace: cozy-public + sharding.fluxcd.io/key: tenants + chartRef: + kind: ExternalArtifact + name: cozystack-bootbox-application-default-bootbox + namespace: cozy-system dashboard: category: Administration singular: BootBox diff --git a/packages/system/bucket-rd/cozyrds/bucket.yaml b/packages/system/bucket-rd/cozyrds/bucket.yaml index 889a36b5..8596bf5a 100644 --- a/packages/system/bucket-rd/cozyrds/bucket.yaml +++ b/packages/system/bucket-rd/cozyrds/bucket.yaml @@ -13,12 +13,11 @@ spec: prefix: bucket- labels: cozystack.io/ui: "true" - chart: - name: bucket - sourceRef: - kind: HelmRepository - name: cozystack-apps - namespace: cozy-public + sharding.fluxcd.io/key: tenants + chartRef: + kind: ExternalArtifact + name: cozystack-bucket-application-default-bucket + namespace: cozy-system dashboard: singular: Bucket plural: Buckets diff --git a/packages/system/clickhouse-rd/cozyrds/clickhouse.yaml b/packages/system/clickhouse-rd/cozyrds/clickhouse.yaml index 6948fce0..de35d626 100644 --- a/packages/system/clickhouse-rd/cozyrds/clickhouse.yaml +++ b/packages/system/clickhouse-rd/cozyrds/clickhouse.yaml @@ -13,12 +13,11 @@ spec: prefix: clickhouse- labels: cozystack.io/ui: "true" - chart: - name: clickhouse - sourceRef: - kind: HelmRepository - name: cozystack-apps - namespace: cozy-public + sharding.fluxcd.io/key: tenants + chartRef: + kind: ExternalArtifact + name: cozystack-clickhouse-application-default-clickhouse + namespace: cozy-system dashboard: category: PaaS singular: ClickHouse diff --git a/packages/system/etcd-rd/cozyrds/etcd.yaml b/packages/system/etcd-rd/cozyrds/etcd.yaml index 7381469d..e4483602 100644 --- a/packages/system/etcd-rd/cozyrds/etcd.yaml +++ b/packages/system/etcd-rd/cozyrds/etcd.yaml @@ -13,13 +13,12 @@ spec: prefix: "" labels: cozystack.io/ui: "true" + sharding.fluxcd.io/key: tenants internal.cozystack.io/tenantmodule: "true" - chart: - name: etcd - sourceRef: - kind: HelmRepository - name: cozystack-extra - namespace: cozy-public + chartRef: + kind: ExternalArtifact + name: cozystack-etcd-application-default-etcd + namespace: cozy-system dashboard: category: Administration singular: Etcd diff --git a/packages/system/ferretdb-rd/cozyrds/ferretdb.yaml b/packages/system/ferretdb-rd/cozyrds/ferretdb.yaml index f7bdeb82..a3236914 100644 --- a/packages/system/ferretdb-rd/cozyrds/ferretdb.yaml +++ b/packages/system/ferretdb-rd/cozyrds/ferretdb.yaml @@ -13,12 +13,11 @@ spec: prefix: ferretdb- labels: cozystack.io/ui: "true" - chart: - name: ferretdb - sourceRef: - kind: HelmRepository - name: cozystack-apps - namespace: cozy-public + sharding.fluxcd.io/key: tenants + chartRef: + kind: ExternalArtifact + name: cozystack-ferretdb-application-default-ferretdb + namespace: cozy-system dashboard: category: PaaS singular: FerretDB diff --git a/packages/system/foundationdb-rd/cozyrds/foundationdb.yaml b/packages/system/foundationdb-rd/cozyrds/foundationdb.yaml index e7759380..5cc8ccd6 100644 --- a/packages/system/foundationdb-rd/cozyrds/foundationdb.yaml +++ b/packages/system/foundationdb-rd/cozyrds/foundationdb.yaml @@ -13,12 +13,11 @@ spec: prefix: foundationdb- labels: cozystack.io/ui: "true" - chart: - name: foundationdb - sourceRef: - kind: HelmRepository - name: cozystack-apps - namespace: cozy-public + sharding.fluxcd.io/key: tenants + chartRef: + kind: ExternalArtifact + name: cozystack-foundationdb-application-default-foundationdb + namespace: cozy-system dashboard: category: PaaS singular: FoundationDB diff --git a/packages/system/http-cache-rd/cozyrds/http-cache.yaml b/packages/system/http-cache-rd/cozyrds/http-cache.yaml index 70de0418..991e933f 100644 --- a/packages/system/http-cache-rd/cozyrds/http-cache.yaml +++ b/packages/system/http-cache-rd/cozyrds/http-cache.yaml @@ -13,12 +13,11 @@ spec: prefix: http-cache- labels: cozystack.io/ui: "true" - chart: - name: http-cache - sourceRef: - kind: HelmRepository - name: cozystack-apps - namespace: cozy-public + sharding.fluxcd.io/key: tenants + chartRef: + kind: ExternalArtifact + name: cozystack-http-cache-application-default-http-cache + namespace: cozy-system dashboard: category: NaaS singular: HTTP Cache diff --git a/packages/system/info-rd/cozyrds/info.yaml b/packages/system/info-rd/cozyrds/info.yaml index 43cda091..68ed9b32 100644 --- a/packages/system/info-rd/cozyrds/info.yaml +++ b/packages/system/info-rd/cozyrds/info.yaml @@ -13,13 +13,12 @@ spec: prefix: "" labels: cozystack.io/ui: "true" + sharding.fluxcd.io/key: tenants internal.cozystack.io/tenantmodule: "true" - chart: - name: info - sourceRef: - kind: HelmRepository - name: cozystack-extra - namespace: cozy-public + chartRef: + kind: ExternalArtifact + name: cozystack-info-application-default-info + namespace: cozy-system dashboard: name: info category: Administration diff --git a/packages/system/ingress-rd/cozyrds/ingress.yaml b/packages/system/ingress-rd/cozyrds/ingress.yaml index 2e00f6ed..b547c28c 100644 --- a/packages/system/ingress-rd/cozyrds/ingress.yaml +++ b/packages/system/ingress-rd/cozyrds/ingress.yaml @@ -13,13 +13,12 @@ spec: prefix: "" labels: cozystack.io/ui: "true" + sharding.fluxcd.io/key: tenants internal.cozystack.io/tenantmodule: "true" - chart: - name: ingress - sourceRef: - kind: HelmRepository - name: cozystack-extra - namespace: cozy-public + chartRef: + kind: ExternalArtifact + name: cozystack-ingress-application-default-ingress + namespace: cozy-system dashboard: category: Administration singular: Ingress diff --git a/packages/system/kafka-rd/cozyrds/kafka.yaml b/packages/system/kafka-rd/cozyrds/kafka.yaml index 7009d241..5e0a7da1 100644 --- a/packages/system/kafka-rd/cozyrds/kafka.yaml +++ b/packages/system/kafka-rd/cozyrds/kafka.yaml @@ -13,12 +13,11 @@ spec: prefix: kafka- labels: cozystack.io/ui: "true" - chart: - name: kafka - sourceRef: - kind: HelmRepository - name: cozystack-apps - namespace: cozy-public + sharding.fluxcd.io/key: tenants + chartRef: + kind: ExternalArtifact + name: cozystack-kafka-application-default-kafka + namespace: cozy-system dashboard: category: PaaS singular: Kafka diff --git a/packages/system/kubernetes-rd/cozyrds/kubernetes.yaml b/packages/system/kubernetes-rd/cozyrds/kubernetes.yaml index 05ed110f..c5516b91 100644 --- a/packages/system/kubernetes-rd/cozyrds/kubernetes.yaml +++ b/packages/system/kubernetes-rd/cozyrds/kubernetes.yaml @@ -13,12 +13,11 @@ spec: prefix: kubernetes- labels: cozystack.io/ui: "true" - chart: - name: kubernetes - sourceRef: - kind: HelmRepository - name: cozystack-apps - namespace: cozy-public + sharding.fluxcd.io/key: tenants + chartRef: + kind: ExternalArtifact + name: cozystack-kubernetes-application-kubevirt-kubernetes + namespace: cozy-system dashboard: category: IaaS singular: Kubernetes diff --git a/packages/system/monitoring-rd/cozyrds/monitoring.yaml b/packages/system/monitoring-rd/cozyrds/monitoring.yaml index e3f098e4..e8a9a02b 100644 --- a/packages/system/monitoring-rd/cozyrds/monitoring.yaml +++ b/packages/system/monitoring-rd/cozyrds/monitoring.yaml @@ -13,13 +13,12 @@ spec: prefix: "" labels: cozystack.io/ui: "true" + sharding.fluxcd.io/key: tenants internal.cozystack.io/tenantmodule: "true" - chart: - name: monitoring - sourceRef: - kind: HelmRepository - name: cozystack-extra - namespace: cozy-public + chartRef: + kind: ExternalArtifact + name: cozystack-monitoring-application-default-monitoring + namespace: cozy-system dashboard: category: Administration singular: Monitoring diff --git a/packages/system/mysql-rd/cozyrds/mysql.yaml b/packages/system/mysql-rd/cozyrds/mysql.yaml index ba8251b8..bb9a170a 100644 --- a/packages/system/mysql-rd/cozyrds/mysql.yaml +++ b/packages/system/mysql-rd/cozyrds/mysql.yaml @@ -13,12 +13,11 @@ spec: prefix: mysql- labels: cozystack.io/ui: "true" - chart: - name: mysql - sourceRef: - kind: HelmRepository - name: cozystack-apps - namespace: cozy-public + sharding.fluxcd.io/key: tenants + chartRef: + kind: ExternalArtifact + name: cozystack-mysql-application-default-mysql + namespace: cozy-system dashboard: category: PaaS singular: MySQL diff --git a/packages/system/nats-rd/cozyrds/nats.yaml b/packages/system/nats-rd/cozyrds/nats.yaml index 258f8f40..c9b44601 100644 --- a/packages/system/nats-rd/cozyrds/nats.yaml +++ b/packages/system/nats-rd/cozyrds/nats.yaml @@ -13,12 +13,11 @@ spec: prefix: nats- labels: cozystack.io/ui: "true" - chart: - name: nats - sourceRef: - kind: HelmRepository - name: cozystack-apps - namespace: cozy-public + sharding.fluxcd.io/key: tenants + chartRef: + kind: ExternalArtifact + name: cozystack-nats-application-default-nats + namespace: cozy-system dashboard: category: PaaS singular: NATS diff --git a/packages/system/postgres-rd/cozyrds/postgres.yaml b/packages/system/postgres-rd/cozyrds/postgres.yaml index c3d16cb7..05d18b51 100644 --- a/packages/system/postgres-rd/cozyrds/postgres.yaml +++ b/packages/system/postgres-rd/cozyrds/postgres.yaml @@ -13,12 +13,11 @@ spec: prefix: postgres- labels: cozystack.io/ui: "true" - chart: - name: postgres - sourceRef: - kind: HelmRepository - name: cozystack-apps - namespace: cozy-public + sharding.fluxcd.io/key: tenants + chartRef: + kind: ExternalArtifact + name: cozystack-postgres-application-default-postgres + namespace: cozy-system dashboard: category: PaaS singular: PostgreSQL diff --git a/packages/system/rabbitmq-rd/cozyrds/rabbitmq.yaml b/packages/system/rabbitmq-rd/cozyrds/rabbitmq.yaml index 092142ac..39c6962d 100644 --- a/packages/system/rabbitmq-rd/cozyrds/rabbitmq.yaml +++ b/packages/system/rabbitmq-rd/cozyrds/rabbitmq.yaml @@ -13,12 +13,11 @@ spec: prefix: rabbitmq- labels: cozystack.io/ui: "true" - chart: - name: rabbitmq - sourceRef: - kind: HelmRepository - name: cozystack-apps - namespace: cozy-public + sharding.fluxcd.io/key: tenants + chartRef: + kind: ExternalArtifact + name: cozystack-rabbitmq-application-default-rabbitmq + namespace: cozy-system dashboard: category: PaaS singular: RabbitMQ diff --git a/packages/system/redis-rd/cozyrds/redis.yaml b/packages/system/redis-rd/cozyrds/redis.yaml index d23f8d2c..176743ff 100644 --- a/packages/system/redis-rd/cozyrds/redis.yaml +++ b/packages/system/redis-rd/cozyrds/redis.yaml @@ -13,12 +13,11 @@ spec: prefix: redis- labels: cozystack.io/ui: "true" - chart: - name: redis - sourceRef: - kind: HelmRepository - name: cozystack-apps - namespace: cozy-public + sharding.fluxcd.io/key: tenants + chartRef: + kind: ExternalArtifact + name: cozystack-redis-application-default-redis + namespace: cozy-system dashboard: category: PaaS singular: Redis diff --git a/packages/system/seaweedfs-rd/cozyrds/seaweedfs.yaml b/packages/system/seaweedfs-rd/cozyrds/seaweedfs.yaml index 787b5448..b83c7b23 100644 --- a/packages/system/seaweedfs-rd/cozyrds/seaweedfs.yaml +++ b/packages/system/seaweedfs-rd/cozyrds/seaweedfs.yaml @@ -13,13 +13,12 @@ spec: prefix: "" labels: cozystack.io/ui: "true" + sharding.fluxcd.io/key: tenants internal.cozystack.io/tenantmodule: "true" - chart: - name: seaweedfs - sourceRef: - kind: HelmRepository - name: cozystack-extra - namespace: cozy-public + chartRef: + kind: ExternalArtifact + name: cozystack-seaweedfs-application-default-seaweedfs + namespace: cozy-system dashboard: category: Administration singular: SeaweedFS diff --git a/packages/system/tcp-balancer-rd/cozyrds/tcp-balancer.yaml b/packages/system/tcp-balancer-rd/cozyrds/tcp-balancer.yaml index 057bc922..b7ce2e7a 100644 --- a/packages/system/tcp-balancer-rd/cozyrds/tcp-balancer.yaml +++ b/packages/system/tcp-balancer-rd/cozyrds/tcp-balancer.yaml @@ -13,12 +13,11 @@ spec: prefix: tcp-balancer- labels: cozystack.io/ui: "true" - chart: - name: tcp-balancer - sourceRef: - kind: HelmRepository - name: cozystack-apps - namespace: cozy-public + sharding.fluxcd.io/key: tenants + chartRef: + kind: ExternalArtifact + name: cozystack-tcp-balancer-application-default-tcp-balancer + namespace: cozy-system dashboard: category: NaaS singular: TCP Balancer diff --git a/packages/system/tenant-rd/cozyrds/tenant.yaml b/packages/system/tenant-rd/cozyrds/tenant.yaml index a5c497ac..08ed1336 100644 --- a/packages/system/tenant-rd/cozyrds/tenant.yaml +++ b/packages/system/tenant-rd/cozyrds/tenant.yaml @@ -13,12 +13,11 @@ spec: prefix: tenant- labels: cozystack.io/ui: "true" - chart: - name: tenant - sourceRef: - kind: HelmRepository - name: cozystack-apps - namespace: cozy-public + sharding.fluxcd.io/key: tenants + chartRef: + kind: ExternalArtifact + name: cozystack-tenant-application-default-tenant + namespace: cozy-system dashboard: category: Administration singular: Tenant diff --git a/packages/system/virtual-machine-rd/cozyrds/virtual-machine.yaml b/packages/system/virtual-machine-rd/cozyrds/virtual-machine.yaml index a1e384c4..30b2abf2 100644 --- a/packages/system/virtual-machine-rd/cozyrds/virtual-machine.yaml +++ b/packages/system/virtual-machine-rd/cozyrds/virtual-machine.yaml @@ -13,12 +13,11 @@ spec: prefix: virtual-machine- labels: cozystack.io/ui: "true" - chart: - name: virtual-machine - sourceRef: - kind: HelmRepository - name: cozystack-apps - namespace: cozy-public + sharding.fluxcd.io/key: tenants + chartRef: + kind: ExternalArtifact + name: cozystack-virtual-machine-application-kubevirt-virtual-machine + namespace: cozy-system dashboard: category: IaaS singular: Virtual Machine diff --git a/packages/system/virtualprivatecloud-rd/cozyrds/virtualprivatecloud.yaml b/packages/system/virtualprivatecloud-rd/cozyrds/virtualprivatecloud.yaml index 8d05a5b6..a39bf7a2 100644 --- a/packages/system/virtualprivatecloud-rd/cozyrds/virtualprivatecloud.yaml +++ b/packages/system/virtualprivatecloud-rd/cozyrds/virtualprivatecloud.yaml @@ -13,12 +13,11 @@ spec: prefix: "virtualprivatecloud-" labels: cozystack.io/ui: "true" - chart: - name: virtualprivatecloud - sourceRef: - kind: HelmRepository - name: cozystack-apps - namespace: cozy-public + sharding.fluxcd.io/key: tenants + chartRef: + kind: ExternalArtifact + name: cozystack-virtualprivatecloud-application-kubevirt-virtualprivatecloud + namespace: cozy-system dashboard: category: IaaS singular: VPC diff --git a/packages/system/vm-disk-rd/cozyrds/vm-disk.yaml b/packages/system/vm-disk-rd/cozyrds/vm-disk.yaml index c3c1b830..5760b8fc 100644 --- a/packages/system/vm-disk-rd/cozyrds/vm-disk.yaml +++ b/packages/system/vm-disk-rd/cozyrds/vm-disk.yaml @@ -13,12 +13,11 @@ spec: prefix: vm-disk- labels: cozystack.io/ui: "true" - chart: - name: vm-disk - sourceRef: - kind: HelmRepository - name: cozystack-apps - namespace: cozy-public + sharding.fluxcd.io/key: tenants + chartRef: + kind: ExternalArtifact + name: cozystack-vm-disk-application-kubevirt-vm-disk + namespace: cozy-system dashboard: category: IaaS singular: VM Disk diff --git a/packages/system/vm-instance-rd/cozyrds/vm-instance.yaml b/packages/system/vm-instance-rd/cozyrds/vm-instance.yaml index 58eb5f9a..aa89adbc 100644 --- a/packages/system/vm-instance-rd/cozyrds/vm-instance.yaml +++ b/packages/system/vm-instance-rd/cozyrds/vm-instance.yaml @@ -13,12 +13,11 @@ spec: prefix: vm-instance- labels: cozystack.io/ui: "true" - chart: - name: vm-instance - sourceRef: - kind: HelmRepository - name: cozystack-apps - namespace: cozy-public + sharding.fluxcd.io/key: tenants + chartRef: + kind: ExternalArtifact + name: cozystack-vm-instance-application-kubevirt-vm-instance + namespace: cozy-system dashboard: category: IaaS singular: VM Instance diff --git a/packages/system/vpn-rd/cozyrds/vpn.yaml b/packages/system/vpn-rd/cozyrds/vpn.yaml index 59d940c0..4f889c1e 100644 --- a/packages/system/vpn-rd/cozyrds/vpn.yaml +++ b/packages/system/vpn-rd/cozyrds/vpn.yaml @@ -13,12 +13,11 @@ spec: prefix: vpn- labels: cozystack.io/ui: "true" - chart: - name: vpn - sourceRef: - kind: HelmRepository - name: cozystack-apps - namespace: cozy-public + sharding.fluxcd.io/key: tenants + chartRef: + kind: ExternalArtifact + name: cozystack-vpn-application-default-vpn + namespace: cozy-system dashboard: category: NaaS singular: VPN From 2d022e38e31da71cdb2503a0e5d3d94538553850 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 15 Jan 2026 14:15:00 +0100 Subject: [PATCH 051/889] refactor(platform): restructure bundles and add PackageSources Restructure platform bundles from monolithic files to modular directory structure with separate applicationdefinitions. Add PackageSources for better dependency management and migrate from legacy HelmRepositories to new repository format. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- packages/core/platform/Makefile | 33 +- .../core/platform/bundles/distro-full.yaml | 435 ------------- .../core/platform/bundles/distro-hosted.yaml | 186 ------ packages/core/platform/bundles/paas-full.yaml | 612 ------------------ .../core/platform/bundles/paas-hosted.yaml | 417 ------------ .../sources/backupstrategy-controller.yaml | 22 + ...l => capi-provider-bootstrap-kubeadm.yaml} | 13 +- ...ders-core.yaml => capi-provider-core.yaml} | 2 +- ...ider.yaml => capi-provider-cp-kamaji.yaml} | 14 +- ...yaml => capi-provider-infra-kubevirt.yaml} | 14 +- .../platform/sources/cozystack-engine.yaml | 1 + .../core/platform/sources/ingress-nginx.yaml | 22 + packages/core/platform/sources/keycloak.yaml | 4 + .../sources/kubernetes-application.yaml | 8 +- .../core/platform/sources/metrics-server.yaml | 22 + .../sources/monitoring-application.yaml | 1 + .../sources/postgres-application.yaml | 1 + packages/core/platform/templates/_helpers.tpl | 150 ++--- packages/core/platform/templates/apps.yaml | 109 +--- .../core/platform/templates/bundles/iaas.yaml | 20 + .../core/platform/templates/bundles/naas.yaml | 8 + .../core/platform/templates/bundles/paas.yaml | 20 + .../platform/templates/bundles/system.yaml | 72 +++ .../templates/containerd-registry-secret.yaml | 35 + .../platform/templates/cozystack-assets.yaml | 73 --- .../platform/templates/cozystack-version.yaml | 11 + .../core/platform/templates/helmreleases.yaml | 100 --- .../core/platform/templates/helmrepos.yaml | 40 -- .../platform/templates/migration-hook.yaml | 69 ++ .../core/platform/templates/namespaces.yaml | 76 --- .../core/platform/templates/repository.yaml | 17 + .../core/platform/templates/scheduling.yaml | 11 + packages/core/platform/templates/sources.yaml | 2 - packages/core/platform/values-isp-full.yaml | 12 + packages/core/platform/values-isp-hosted.yaml | 12 + packages/core/platform/values.yaml | 93 ++- 36 files changed, 568 insertions(+), 2169 deletions(-) delete mode 100644 packages/core/platform/bundles/distro-full.yaml delete mode 100644 packages/core/platform/bundles/distro-hosted.yaml delete mode 100644 packages/core/platform/bundles/paas-full.yaml delete mode 100644 packages/core/platform/bundles/paas-hosted.yaml create mode 100644 packages/core/platform/sources/backupstrategy-controller.yaml rename packages/core/platform/sources/{capi-providers-bootstrap.yaml => capi-provider-bootstrap-kubeadm.yaml} (58%) rename packages/core/platform/sources/{capi-providers-core.yaml => capi-provider-core.yaml} (92%) rename packages/core/platform/sources/{capi-providers-cpprovider.yaml => capi-provider-cp-kamaji.yaml} (58%) rename packages/core/platform/sources/{capi-providers-infraprovider.yaml => capi-provider-infra-kubevirt.yaml} (57%) create mode 100644 packages/core/platform/sources/ingress-nginx.yaml create mode 100644 packages/core/platform/sources/metrics-server.yaml create mode 100644 packages/core/platform/templates/bundles/iaas.yaml create mode 100644 packages/core/platform/templates/bundles/naas.yaml create mode 100644 packages/core/platform/templates/bundles/paas.yaml create mode 100644 packages/core/platform/templates/bundles/system.yaml create mode 100644 packages/core/platform/templates/containerd-registry-secret.yaml delete mode 100644 packages/core/platform/templates/cozystack-assets.yaml create mode 100644 packages/core/platform/templates/cozystack-version.yaml delete mode 100644 packages/core/platform/templates/helmreleases.yaml delete mode 100644 packages/core/platform/templates/helmrepos.yaml create mode 100644 packages/core/platform/templates/migration-hook.yaml delete mode 100644 packages/core/platform/templates/namespaces.yaml create mode 100644 packages/core/platform/templates/repository.yaml create mode 100644 packages/core/platform/templates/scheduling.yaml create mode 100644 packages/core/platform/values-isp-full.yaml create mode 100644 packages/core/platform/values-isp-hosted.yaml diff --git a/packages/core/platform/Makefile b/packages/core/platform/Makefile index 2ea59153..d5d63e6e 100644 --- a/packages/core/platform/Makefile +++ b/packages/core/platform/Makefile @@ -4,31 +4,26 @@ NAMESPACE=cozy-system include ../../../scripts/common-envs.mk show: - cozyhr show -n $(NAMESPACE) $(NAME) --plain + cozyhr show --namespace $(NAMESPACE) $(NAME) --plain apply: - cozyhr show -n $(NAMESPACE) $(NAME) --plain | kubectl apply -f- - kubectl delete helmreleases.helm.toolkit.fluxcd.io -l cozystack.io/marked-for-deletion=true -A + cozyhr show --namespace $(NAMESPACE) $(NAME) --plain | kubectl apply --filename - + kubectl delete helmreleases.helm.toolkit.fluxcd.io --selector cozystack.io/marked-for-deletion=true --all-namespaces reconcile: apply -namespaces-show: - cozyhr show -n $(NAMESPACE) $(NAME) --plain -s templates/namespaces.yaml - -namespaces-apply: - cozyhr show -n $(NAMESPACE) $(NAME) --plain -s templates/namespaces.yaml | kubectl apply -f- - diff: - cozyhr show -n $(NAMESPACE) $(NAME) --plain | kubectl diff -f- + cozyhr show --namespace $(NAMESPACE) $(NAME) --plain | kubectl diff --filename - -image: image-assets -image-assets: - docker buildx build -f images/cozystack-assets/Dockerfile ../../.. \ - --tag $(REGISTRY)/cozystack-assets:$(call settag,$(TAG)) \ - --cache-from type=registry,ref=$(REGISTRY)/cozystack-assets:latest \ +image: image-migrations + +image-migrations: + docker buildx build --file images/migrations/Dockerfile . \ + --tag $(REGISTRY)/platform-migrations:$(call settag,$(TAG)) \ + --cache-from type=registry,ref=$(REGISTRY)/platform-migrations:latest \ --cache-to type=inline \ - --metadata-file images/cozystack-assets.json \ + --metadata-file images/migrations.json \ $(BUILDX_ARGS) - IMAGE="$(REGISTRY)/cozystack-assets:$(call settag,$(TAG))@$$(yq e '."containerimage.digest"' images/cozystack-assets.json -o json -r)" \ - yq -i '.assets.image = strenv(IMAGE)' values.yaml - rm -f images/cozystack-assets.json + IMAGE="$(REGISTRY)/platform-migrations:$(call settag,$(TAG))@$$(yq --exit-status '.["containerimage.digest"]' images/migrations.json --output-format json --raw-output)" \ + yq --inplace '.migrations.image = strenv(IMAGE)' values.yaml + rm -f images/migrations.json diff --git a/packages/core/platform/bundles/distro-full.yaml b/packages/core/platform/bundles/distro-full.yaml deleted file mode 100644 index 13d58ded..00000000 --- a/packages/core/platform/bundles/distro-full.yaml +++ /dev/null @@ -1,435 +0,0 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $clusterDomain := (index $cozyConfig.data "cluster-domain") | default "cozy.local" }} - -releases: -- name: cilium - releaseName: cilium - chart: cozy-cilium - namespace: cozy-cilium - privileged: true - dependsOn: [] - valuesFiles: - - values.yaml - - values-talos.yaml - values: - cilium: - enableIPv4Masquerade: true - enableIdentityMark: true - ipv4NativeRoutingCIDR: "{{ index $cozyConfig.data "ipv4-pod-cidr" }}" - autoDirectNodeRoutes: true - routingMode: native - -- name: cilium-networkpolicy - releaseName: cilium-networkpolicy - chart: cozy-cilium-networkpolicy - namespace: cozy-cilium - privileged: true - dependsOn: [cilium] - -- name: cozy-proxy - releaseName: cozystack - chart: cozy-cozy-proxy - namespace: cozy-system - optional: true - dependsOn: [cilium] - -- name: cert-manager-crds - releaseName: cert-manager-crds - chart: cozy-cert-manager-crds - namespace: cozy-cert-manager - dependsOn: [cilium] - -- name: cozystack-controller - releaseName: cozystack-controller - chart: cozy-cozystack-controller - namespace: cozy-system - dependsOn: [cilium] - {{- if eq (index $cozyConfig.data "telemetry-enabled") "false" }} - values: - cozystackController: - disableTelemetry: true - {{- end }} - -- name: lineage-controller-webhook - releaseName: lineage-controller-webhook - chart: cozy-lineage-controller-webhook - namespace: cozy-system - dependsOn: [cozystack-controller,cilium,cert-manager] - -- name: cozystack-resource-definition-crd - releaseName: cozystack-resource-definition-crd - chart: cozystack-resource-definition-crd - namespace: cozy-system - dependsOn: [cilium] - -- name: bootbox-rd - releaseName: bootbox-rd - chart: bootbox-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: bucket-rd - releaseName: bucket-rd - chart: bucket-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: clickhouse-rd - releaseName: clickhouse-rd - chart: clickhouse-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: etcd-rd - releaseName: etcd-rd - chart: etcd-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: ferretdb-rd - releaseName: ferretdb-rd - chart: ferretdb-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: foundationdb-rd - releaseName: foundationdb-rd - chart: foundationdb-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: http-cache-rd - releaseName: http-cache-rd - chart: http-cache-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: info-rd - releaseName: info-rd - chart: info-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: ingress-rd - releaseName: ingress-rd - chart: ingress-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: kafka-rd - releaseName: kafka-rd - chart: kafka-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: kubernetes-rd - releaseName: kubernetes-rd - chart: kubernetes-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: monitoring-rd - releaseName: monitoring-rd - chart: monitoring-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: mysql-rd - releaseName: mysql-rd - chart: mysql-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: nats-rd - releaseName: nats-rd - chart: nats-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: postgres-rd - releaseName: postgres-rd - chart: postgres-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: rabbitmq-rd - releaseName: rabbitmq-rd - chart: rabbitmq-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: redis-rd - releaseName: redis-rd - chart: redis-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: seaweedfs-rd - releaseName: seaweedfs-rd - chart: seaweedfs-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: tcp-balancer-rd - releaseName: tcp-balancer-rd - chart: tcp-balancer-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: tenant-rd - releaseName: tenant-rd - chart: tenant-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: virtual-machine-rd - releaseName: virtual-machine-rd - chart: virtual-machine-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: virtualprivatecloud-rd - releaseName: virtualprivatecloud-rd - chart: virtualprivatecloud-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: vm-disk-rd - releaseName: vm-disk-rd - chart: vm-disk-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: vm-instance-rd - releaseName: vm-instance-rd - chart: vm-instance-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: vpn-rd - releaseName: vpn-rd - chart: vpn-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: cert-manager - releaseName: cert-manager - chart: cozy-cert-manager - namespace: cozy-cert-manager - dependsOn: [cert-manager-crds] - -- name: cert-manager-issuers - releaseName: cert-manager-issuers - chart: cozy-cert-manager-issuers - namespace: cozy-cert-manager - dependsOn: [cilium,cert-manager] - -- name: prometheus-operator-crds - releaseName: prometheus-operator-crds - chart: cozy-prometheus-operator-crds - namespace: cozy-victoria-metrics-operator - dependsOn: [] - -- name: metrics-server - releaseName: metrics-server - chart: cozy-metrics-server - namespace: cozy-monitoring - dependsOn: [cilium,prometheus-operator-crds] - -- name: victoria-metrics-operator - releaseName: victoria-metrics-operator - chart: cozy-victoria-metrics-operator - namespace: cozy-victoria-metrics-operator - optional: true - dependsOn: [cilium,cert-manager,prometheus-operator-crds] - -- name: monitoring-agents - releaseName: monitoring-agents - chart: cozy-monitoring-agents - namespace: cozy-monitoring - privileged: true - optional: true - dependsOn: [cilium,victoria-metrics-operator,metrics-server] - values: - scrapeRules: - etcd: - enabled: true - -- name: metallb - releaseName: metallb - chart: cozy-metallb - namespace: cozy-metallb - privileged: true - dependsOn: [cilium] - -- name: etcd-operator - releaseName: etcd-operator - chart: cozy-etcd-operator - namespace: cozy-etcd-operator - optional: true - dependsOn: [cilium,cert-manager] - -- name: grafana-operator - releaseName: grafana-operator - chart: cozy-grafana-operator - namespace: cozy-grafana-operator - optional: true - dependsOn: [cilium] - -- name: mariadb-operator - releaseName: mariadb-operator - chart: cozy-mariadb-operator - namespace: cozy-mariadb-operator - optional: true - dependsOn: [cilium,cert-manager,victoria-metrics-operator] - values: - mariadb-operator: - clusterName: {{ $clusterDomain }} - -- name: postgres-operator - releaseName: postgres-operator - chart: cozy-postgres-operator - namespace: cozy-postgres-operator - optional: true - dependsOn: [cilium,cert-manager,victoria-metrics-operator] - -- name: kafka-operator - releaseName: kafka-operator - chart: cozy-kafka-operator - namespace: cozy-kafka-operator - optional: true - dependsOn: [cilium,victoria-metrics-operator] - values: - strimzi-kafka-operator: - kubernetesServiceDnsDomain: {{ $clusterDomain }} - -- name: clickhouse-operator - releaseName: clickhouse-operator - chart: cozy-clickhouse-operator - namespace: cozy-clickhouse-operator - optional: true - dependsOn: [cilium,victoria-metrics-operator] - -- name: foundationdb-operator - releaseName: foundationdb-operator - chart: cozy-foundationdb-operator - namespace: cozy-foundationdb-operator - optional: true - dependsOn: [cilium,cert-manager] - -- name: rabbitmq-operator - releaseName: rabbitmq-operator - chart: cozy-rabbitmq-operator - namespace: cozy-rabbitmq-operator - optional: true - dependsOn: [cilium] - -- name: redis-operator - releaseName: redis-operator - chart: cozy-redis-operator - namespace: cozy-redis-operator - optional: true - dependsOn: [cilium] - -- name: piraeus-operator - releaseName: piraeus-operator - chart: cozy-piraeus-operator - namespace: cozy-linstor - dependsOn: [cilium,cert-manager] - -- name: snapshot-controller - releaseName: snapshot-controller - chart: cozy-snapshot-controller - namespace: cozy-snapshot-controller - dependsOn: [cilium,cert-manager-issuers] - -- name: objectstorage-controller - releaseName: objectstorage-controller - chart: cozy-objectstorage-controller - namespace: cozy-objectstorage-controller - optional: true - dependsOn: [cilium] - -- name: linstor - releaseName: linstor - chart: cozy-linstor - namespace: cozy-linstor - privileged: true - dependsOn: [piraeus-operator,cilium,cert-manager,snapshot-controller] - -- name: linstor-scheduler - releaseName: linstor-scheduler - chart: cozy-linstor-scheduler - namespace: cozy-linstor - dependsOn: [linstor,cert-manager] - -- name: nfs-driver - releaseName: nfs-driver - chart: cozy-nfs-driver - namespace: cozy-nfs-driver - privileged: true - dependsOn: [cilium] - optional: true - -- name: telepresence - releaseName: traffic-manager - chart: cozy-telepresence - namespace: cozy-telepresence - optional: true - dependsOn: [] - -- name: external-dns - releaseName: external-dns - chart: cozy-external-dns - namespace: cozy-external-dns - optional: true - dependsOn: [cilium] - -- name: external-secrets-operator - releaseName: external-secrets-operator - chart: cozy-external-secrets-operator - namespace: cozy-external-secrets-operator - optional: true - dependsOn: [cilium] - -- name: keycloak - releaseName: keycloak - chart: cozy-keycloak - namespace: cozy-keycloak - optional: true - dependsOn: [postgres-operator] - -- name: keycloak-operator - releaseName: keycloak-operator - chart: cozy-keycloak-operator - namespace: cozy-keycloak - optional: true - dependsOn: [keycloak] - -- name: bootbox - releaseName: bootbox - chart: cozy-bootbox - namespace: cozy-bootbox - privileged: true - optional: true - dependsOn: [cilium] - -- name: reloader - releaseName: reloader - chart: cozy-reloader - namespace: cozy-reloader - -- name: velero - releaseName: velero - chart: cozy-velero - namespace: cozy-velero - privileged: true - optional: true - dependsOn: [cilium] - -- name: hetzner-robotlb - releaseName: robotlb - optional: true - chart: cozy-hetzner-robotlb - namespace: cozy-hetzner-robotlb - dependsOn: [cilium] diff --git a/packages/core/platform/bundles/distro-hosted.yaml b/packages/core/platform/bundles/distro-hosted.yaml deleted file mode 100644 index e2aa5b03..00000000 --- a/packages/core/platform/bundles/distro-hosted.yaml +++ /dev/null @@ -1,186 +0,0 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $clusterDomain := (index $cozyConfig.data "cluster-domain") | default "cozy.local" }} - -releases: -- name: cert-manager-crds - releaseName: cert-manager-crds - chart: cozy-cert-manager-crds - namespace: cozy-cert-manager - dependsOn: [] - -- name: cozystack-controller - releaseName: cozystack-controller - chart: cozy-cozystack-controller - namespace: cozy-system - {{- if eq (index $cozyConfig.data "telemetry-enabled") "false" }} - values: - cozystackController: - disableTelemetry: true - {{- end }} - -- name: lineage-controller-webhook - releaseName: lineage-controller-webhook - chart: cozy-lineage-controller-webhook - namespace: cozy-system - dependsOn: [cozystack-controller,cert-manager] - -- name: cert-manager - releaseName: cert-manager - chart: cozy-cert-manager - namespace: cozy-cert-manager - dependsOn: [cert-manager-crds] - -- name: cert-manager-issuers - releaseName: cert-manager-issuers - chart: cozy-cert-manager-issuers - namespace: cozy-cert-manager - optional: true - dependsOn: [cert-manager] - -- name: prometheus-operator-crds - releaseName: prometheus-operator-crds - chart: cozy-prometheus-operator-crds - namespace: cozy-victoria-metrics-operator - dependsOn: [] - -- name: metrics-server - releaseName: metrics-server - chart: cozy-metrics-server - namespace: cozy-monitoring - dependsOn: [prometheus-operator-crds] - -- name: victoria-metrics-operator - releaseName: victoria-metrics-operator - chart: cozy-victoria-metrics-operator - namespace: cozy-victoria-metrics-operator - optional: true - dependsOn: [prometheus-operator-crds,cert-manager] - -- name: monitoring-agents - releaseName: monitoring-agents - chart: cozy-monitoring-agents - namespace: cozy-monitoring - privileged: true - optional: true - dependsOn: [victoria-metrics-operator, metrics-server] - values: - scrapeRules: - etcd: - enabled: true - -- name: etcd-operator - releaseName: etcd-operator - chart: cozy-etcd-operator - namespace: cozy-etcd-operator - optional: true - dependsOn: [cert-manager] - -- name: grafana-operator - releaseName: grafana-operator - chart: cozy-grafana-operator - namespace: cozy-grafana-operator - optional: true - dependsOn: [] - -- name: mariadb-operator - releaseName: mariadb-operator - chart: cozy-mariadb-operator - namespace: cozy-mariadb-operator - optional: true - dependsOn: [victoria-metrics-operator] - values: - mariadb-operator: - clusterName: {{ $clusterDomain }} - -- name: postgres-operator - releaseName: postgres-operator - chart: cozy-postgres-operator - namespace: cozy-postgres-operator - optional: true - dependsOn: [victoria-metrics-operator] - -- name: kafka-operator - releaseName: kafka-operator - chart: cozy-kafka-operator - namespace: cozy-kafka-operator - optional: true - dependsOn: [victoria-metrics-operator] - values: - strimzi-kafka-operator: - kubernetesServiceDnsDomain: {{ $clusterDomain }} - -- name: clickhouse-operator - releaseName: clickhouse-operator - chart: cozy-clickhouse-operator - namespace: cozy-clickhouse-operator - optional: true - dependsOn: [victoria-metrics-operator] - -- name: foundationdb-operator - releaseName: foundationdb-operator - chart: cozy-foundationdb-operator - namespace: cozy-foundationdb-operator - optional: true - dependsOn: [cert-manager] - -- name: rabbitmq-operator - releaseName: rabbitmq-operator - chart: cozy-rabbitmq-operator - namespace: cozy-rabbitmq-operator - optional: true - dependsOn: [] - -- name: redis-operator - releaseName: redis-operator - chart: cozy-redis-operator - namespace: cozy-redis-operator - optional: true - dependsOn: [] - -- name: telepresence - releaseName: traffic-manager - chart: cozy-telepresence - namespace: cozy-telepresence - optional: true - dependsOn: [] - -- name: external-dns - releaseName: external-dns - chart: cozy-external-dns - namespace: cozy-external-dns - optional: true - dependsOn: [] - -- name: external-secrets-operator - releaseName: external-secrets-operator - chart: cozy-external-secrets-operator - namespace: cozy-external-secrets-operator - optional: true - dependsOn: [] - -- name: keycloak - releaseName: keycloak - chart: cozy-keycloak - namespace: cozy-keycloak - optional: true - dependsOn: [postgres-operator] - -- name: keycloak-operator - releaseName: keycloak-operator - chart: cozy-keycloak-operator - namespace: cozy-keycloak - optional: true - dependsOn: [keycloak] - -- name: velero - releaseName: velero - chart: cozy-velero - namespace: cozy-velero - privileged: true - optional: true - -- name: hetzner-robotlb - releaseName: robotlb - optional: true - chart: cozy-hetzner-robotlb - namespace: cozy-hetzner-robotlb diff --git a/packages/core/platform/bundles/paas-full.yaml b/packages/core/platform/bundles/paas-full.yaml deleted file mode 100644 index d25d885c..00000000 --- a/packages/core/platform/bundles/paas-full.yaml +++ /dev/null @@ -1,612 +0,0 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $clusterDomain := (index $cozyConfig.data "cluster-domain") | default "cozy.local" }} -{{- $oidcEnabled := index $cozyConfig.data "oidc-enabled" }} -{{- $host := index $cozyConfig.data "root-host" }} -{{- if not $host }} -{{- fail "ERROR need root-host in cozystack ConfigMap" }} -{{- end }} -{{- $apiServerEndpoint := index $cozyConfig.data "api-server-endpoint" }} -{{- if not $apiServerEndpoint }} -{{- fail "ERROR need api-server-endpoint in cozystack ConfigMap" }} -{{- end }} - -releases: -- name: cilium - releaseName: cilium - chart: cozy-cilium - namespace: cozy-cilium - privileged: true - dependsOn: [] - valuesFiles: - - values.yaml - - values-talos.yaml - - values-kubeovn.yaml - -- name: cilium-networkpolicy - releaseName: cilium-networkpolicy - chart: cozy-cilium-networkpolicy - namespace: cozy-cilium - privileged: true - dependsOn: [cilium] - -- name: kubeovn - releaseName: kubeovn - chart: cozy-kubeovn - namespace: cozy-kubeovn - privileged: true - dependsOn: [cilium] - values: - cozystack: - nodesHash: {{ include "cozystack.master-node-ips" . | sha256sum }} - kube-ovn: - ipv4: - POD_CIDR: "{{ index $cozyConfig.data "ipv4-pod-cidr" }}" - POD_GATEWAY: "{{ index $cozyConfig.data "ipv4-pod-gateway" }}" - SVC_CIDR: "{{ index $cozyConfig.data "ipv4-svc-cidr" }}" - JOIN_CIDR: "{{ index $cozyConfig.data "ipv4-join-cidr" }}" - -- name: kubeovn-webhook - releaseName: kubeovn-webhook - chart: cozy-kubeovn-webhook - namespace: cozy-kubeovn - privileged: true - dependsOn: [cilium,kubeovn,cert-manager] - -- name: kubeovn-plunger - releaseName: kubeovn-plunger - chart: cozy-kubeovn-plunger - namespace: cozy-kubeovn - dependsOn: [cilium,kubeovn] - -- name: multus - releaseName: multus - chart: cozy-multus - namespace: cozy-multus - privileged: true - dependsOn: [cilium,kubeovn] - -- name: cozy-proxy - releaseName: cozystack - chart: cozy-cozy-proxy - namespace: cozy-system - dependsOn: [cilium,kubeovn,multus] - -- name: cert-manager-crds - releaseName: cert-manager-crds - chart: cozy-cert-manager-crds - namespace: cozy-cert-manager - dependsOn: [] - -- name: cozystack-api - releaseName: cozystack-api - chart: cozy-cozystack-api - namespace: cozy-system - dependsOn: [cilium,kubeovn,multus,cozystack-controller] - -- name: cozystack-controller - releaseName: cozystack-controller - chart: cozy-cozystack-controller - namespace: cozy-system - dependsOn: [cilium,kubeovn,multus] - {{- if eq (index $cozyConfig.data "telemetry-enabled") "false" }} - values: - cozystackController: - disableTelemetry: true - {{- end }} - -- name: backup-controller - releaseName: backup-controller - chart: cozy-backup-controller - namespace: cozy-backup-controller - dependsOn: [cilium,kubeovn,multus] - -- name: lineage-controller-webhook - releaseName: lineage-controller-webhook - chart: cozy-lineage-controller-webhook - namespace: cozy-system - dependsOn: [cozystack-controller,cilium,kubeovn,multus,cert-manager] - -- name: cozystack-resource-definition-crd - releaseName: cozystack-resource-definition-crd - chart: cozystack-resource-definition-crd - namespace: cozy-system - dependsOn: [cilium,kubeovn,multus,cozystack-api,cozystack-controller] - -- name: bootbox-rd - releaseName: bootbox-rd - chart: bootbox-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: bucket-rd - releaseName: bucket-rd - chart: bucket-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: clickhouse-rd - releaseName: clickhouse-rd - chart: clickhouse-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: etcd-rd - releaseName: etcd-rd - chart: etcd-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: ferretdb-rd - releaseName: ferretdb-rd - chart: ferretdb-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: foundationdb-rd - releaseName: foundationdb-rd - chart: foundationdb-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: http-cache-rd - releaseName: http-cache-rd - chart: http-cache-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: info-rd - releaseName: info-rd - chart: info-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: ingress-rd - releaseName: ingress-rd - chart: ingress-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: kafka-rd - releaseName: kafka-rd - chart: kafka-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: kubernetes-rd - releaseName: kubernetes-rd - chart: kubernetes-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: monitoring-rd - releaseName: monitoring-rd - chart: monitoring-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: mysql-rd - releaseName: mysql-rd - chart: mysql-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: nats-rd - releaseName: nats-rd - chart: nats-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: postgres-rd - releaseName: postgres-rd - chart: postgres-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: rabbitmq-rd - releaseName: rabbitmq-rd - chart: rabbitmq-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: redis-rd - releaseName: redis-rd - chart: redis-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: seaweedfs-rd - releaseName: seaweedfs-rd - chart: seaweedfs-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: tcp-balancer-rd - releaseName: tcp-balancer-rd - chart: tcp-balancer-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: tenant-rd - releaseName: tenant-rd - chart: tenant-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: virtual-machine-rd - releaseName: virtual-machine-rd - chart: virtual-machine-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: virtualprivatecloud-rd - releaseName: virtualprivatecloud-rd - chart: virtualprivatecloud-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: vm-disk-rd - releaseName: vm-disk-rd - chart: vm-disk-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: vm-instance-rd - releaseName: vm-instance-rd - chart: vm-instance-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: vpn-rd - releaseName: vpn-rd - chart: vpn-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: cert-manager - releaseName: cert-manager - chart: cozy-cert-manager - namespace: cozy-cert-manager - dependsOn: [cilium,kubeovn,multus,cert-manager-crds] - -- name: cert-manager-issuers - releaseName: cert-manager-issuers - chart: cozy-cert-manager-issuers - namespace: cozy-cert-manager - dependsOn: [cert-manager] - -- name: prometheus-operator-crds - releaseName: prometheus-operator-crds - chart: cozy-prometheus-operator-crds - namespace: cozy-victoria-metrics-operator - dependsOn: [] - -- name: metrics-server - releaseName: metrics-server - chart: cozy-metrics-server - namespace: cozy-monitoring - dependsOn: [cilium,kubeovn,multus,prometheus-operator-crds] - -- name: victoria-metrics-operator - releaseName: victoria-metrics-operator - chart: cozy-victoria-metrics-operator - namespace: cozy-victoria-metrics-operator - dependsOn: [cilium,kubeovn,multus,cert-manager,prometheus-operator-crds] - -- name: monitoring-agents - releaseName: monitoring-agents - chart: cozy-monitoring-agents - namespace: cozy-monitoring - privileged: true - dependsOn: [victoria-metrics-operator,vertical-pod-autoscaler-crds,metrics-server] - values: - scrapeRules: - etcd: - enabled: true - -- name: kubevirt-operator - releaseName: kubevirt-operator - chart: cozy-kubevirt-operator - namespace: cozy-kubevirt - dependsOn: [cilium,kubeovn,multus,victoria-metrics-operator] - -- name: kubevirt - releaseName: kubevirt - chart: cozy-kubevirt - namespace: cozy-kubevirt - privileged: true - dependsOn: [cilium,kubeovn,multus,kubevirt-operator] - {{- $cpuAllocationRatio := index $cozyConfig.data "cpu-allocation-ratio" }} - {{- if $cpuAllocationRatio }} - values: - cpuAllocationRatio: {{ $cpuAllocationRatio }} - {{- end }} - -- name: kubevirt-instancetypes - releaseName: kubevirt-instancetypes - chart: cozy-kubevirt-instancetypes - namespace: cozy-kubevirt - dependsOn: [cilium,kubeovn,multus,kubevirt-operator,kubevirt] - -- name: kubevirt-cdi-operator - releaseName: kubevirt-cdi-operator - chart: cozy-kubevirt-cdi-operator - namespace: cozy-kubevirt-cdi - dependsOn: [cilium,kubeovn,multus] - -- name: kubevirt-cdi - releaseName: kubevirt-cdi - chart: cozy-kubevirt-cdi - namespace: cozy-kubevirt-cdi - dependsOn: [cilium,kubeovn,multus,kubevirt-cdi-operator] - -- name: gpu-operator - releaseName: gpu-operator - chart: cozy-gpu-operator - namespace: cozy-gpu-operator - privileged: true - optional: true - dependsOn: [cilium,kubeovn,multus] - valuesFiles: - - values.yaml - - values-talos.yaml - -- name: metallb - releaseName: metallb - chart: cozy-metallb - namespace: cozy-metallb - privileged: true - dependsOn: [cilium,kubeovn,multus] - -- name: etcd-operator - releaseName: etcd-operator - chart: cozy-etcd-operator - namespace: cozy-etcd-operator - dependsOn: [cilium,kubeovn,multus,cert-manager] - -- name: grafana-operator - releaseName: grafana-operator - chart: cozy-grafana-operator - namespace: cozy-grafana-operator - dependsOn: [cilium,kubeovn,multus] - -- name: mariadb-operator - releaseName: mariadb-operator - chart: cozy-mariadb-operator - namespace: cozy-mariadb-operator - dependsOn: [cilium,kubeovn,multus,cert-manager,victoria-metrics-operator] - values: - mariadb-operator: - clusterName: {{ $clusterDomain }} - -- name: postgres-operator - releaseName: postgres-operator - chart: cozy-postgres-operator - namespace: cozy-postgres-operator - dependsOn: [cilium,kubeovn,multus,cert-manager] - -- name: kafka-operator - releaseName: kafka-operator - chart: cozy-kafka-operator - namespace: cozy-kafka-operator - dependsOn: [cilium,kubeovn,multus,victoria-metrics-operator] - values: - strimzi-kafka-operator: - kubernetesServiceDnsDomain: {{ $clusterDomain }} - -- name: clickhouse-operator - releaseName: clickhouse-operator - chart: cozy-clickhouse-operator - namespace: cozy-clickhouse-operator - dependsOn: [cilium,kubeovn,multus,victoria-metrics-operator] - -- name: foundationdb-operator - releaseName: foundationdb-operator - chart: cozy-foundationdb-operator - namespace: cozy-foundationdb-operator - dependsOn: [cilium,kubeovn,multus,cert-manager] - -- name: rabbitmq-operator - releaseName: rabbitmq-operator - chart: cozy-rabbitmq-operator - namespace: cozy-rabbitmq-operator - dependsOn: [cilium,kubeovn,multus] - -- name: redis-operator - releaseName: redis-operator - chart: cozy-redis-operator - namespace: cozy-redis-operator - dependsOn: [cilium,kubeovn,multus] - -- name: piraeus-operator - releaseName: piraeus-operator - chart: cozy-piraeus-operator - namespace: cozy-linstor - dependsOn: [cilium,kubeovn,multus,cert-manager,victoria-metrics-operator] - -- name: linstor - releaseName: linstor - chart: cozy-linstor - namespace: cozy-linstor - privileged: true - dependsOn: [piraeus-operator,cilium,kubeovn,multus,cert-manager,snapshot-controller] - -- name: linstor-scheduler - releaseName: linstor-scheduler - chart: cozy-linstor-scheduler - namespace: cozy-linstor - dependsOn: [linstor,cert-manager] - -- name: nfs-driver - releaseName: nfs-driver - chart: cozy-nfs-driver - namespace: cozy-nfs-driver - privileged: true - dependsOn: [cilium,kubeovn,multus] - optional: true - -- name: snapshot-controller - releaseName: snapshot-controller - chart: cozy-snapshot-controller - namespace: cozy-snapshot-controller - dependsOn: [cilium,kubeovn,multus,cert-manager-issuers] - -- name: objectstorage-controller - releaseName: objectstorage-controller - chart: cozy-objectstorage-controller - namespace: cozy-objectstorage-controller - dependsOn: [cilium,kubeovn,multus] - -- name: telepresence - releaseName: traffic-manager - chart: cozy-telepresence - namespace: cozy-telepresence - optional: true - dependsOn: [cilium,kubeovn,multus] - -- name: dashboard - releaseName: dashboard - chart: cozy-dashboard - namespace: cozy-dashboard - values: - {{- $dashboardKCconfig := lookup "v1" "ConfigMap" "cozy-dashboard" "kubeapps-auth-config" }} - {{- $dashboardKCValues := dig "data" "values.yaml" "" $dashboardKCconfig | fromYaml }} - {{- toYaml (deepCopy $dashboardKCValues | mergeOverwrite (fromYaml (include "cozystack.defaultDashboardValues" .))) | nindent 4 }} - dependsOn: - - cilium - - kubeovn - - multus - {{- if eq $oidcEnabled "true" }} - - keycloak-configure - {{- end }} - -- name: kamaji - releaseName: kamaji - chart: cozy-kamaji - namespace: cozy-kamaji - dependsOn: [cilium,kubeovn,multus,cert-manager] - -- name: capi-operator - releaseName: capi-operator - chart: cozy-capi-operator - namespace: cozy-cluster-api - privileged: true - dependsOn: [cilium,kubeovn,multus,cert-manager] - -- name: capi-providers-bootstrap - releaseName: capi-providers-bootstrap - chart: cozy-capi-providers-bootstrap - namespace: cozy-cluster-api - privileged: true - dependsOn: [cilium,kubeovn,multus,capi-operator] - -- name: capi-providers-core - releaseName: capi-providers-core - chart: cozy-capi-providers-core - namespace: cozy-cluster-api - privileged: true - dependsOn: [cilium,kubeovn,multus,capi-operator] - -- name: capi-providers-cpprovider - releaseName: capi-providers-cpprovider - chart: cozy-capi-providers-cpprovider - namespace: cozy-cluster-api - privileged: true - dependsOn: [cilium,kubeovn,multus,capi-operator] - -- name: capi-providers-infraprovider - releaseName: capi-providers-infraprovider - chart: cozy-capi-providers-infraprovider - namespace: cozy-cluster-api - privileged: true - dependsOn: [cilium,kubeovn,multus,capi-operator] - -- name: external-dns - releaseName: external-dns - chart: cozy-external-dns - namespace: cozy-external-dns - optional: true - dependsOn: [cilium,kubeovn,multus] - -- name: external-secrets-operator - releaseName: external-secrets-operator - chart: cozy-external-secrets-operator - namespace: cozy-external-secrets-operator - optional: true - dependsOn: [cilium,kubeovn,multus] - -- name: bootbox - releaseName: bootbox - chart: cozy-bootbox - namespace: cozy-bootbox - privileged: true - optional: true - dependsOn: [cilium,kubeovn,multus] - -{{- if $oidcEnabled }} -- name: keycloak - releaseName: keycloak - chart: cozy-keycloak - namespace: cozy-keycloak - dependsOn: [postgres-operator] - -- name: keycloak-operator - releaseName: keycloak-operator - chart: cozy-keycloak-operator - namespace: cozy-keycloak - dependsOn: [keycloak] - -- name: keycloak-configure - releaseName: keycloak-configure - chart: cozy-keycloak-configure - namespace: cozy-keycloak - dependsOn: [keycloak-operator] - values: - cozystack: - configHash: {{ $cozyConfig | toJson | sha256sum }} -{{- end }} - -- name: goldpinger - releaseName: goldpinger - chart: cozy-goldpinger - namespace: cozy-goldpinger - privileged: true - dependsOn: [monitoring-agents] - -- name: vertical-pod-autoscaler - releaseName: vertical-pod-autoscaler - chart: cozy-vertical-pod-autoscaler - namespace: cozy-vertical-pod-autoscaler - privileged: true - dependsOn: [monitoring-agents] - values: - vertical-pod-autoscaler: - recommender: - extraArgs: - prometheus-address: http://vmselect-shortterm.tenant-root.svc.{{ $clusterDomain }}:8481/select/0/prometheus/ - -- name: vertical-pod-autoscaler-crds - releaseName: vertical-pod-autoscaler-crds - chart: cozy-vertical-pod-autoscaler-crds - namespace: cozy-vertical-pod-autoscaler - privileged: true - dependsOn: [] - -- name: reloader - releaseName: reloader - chart: cozy-reloader - namespace: cozy-reloader - -- name: velero - releaseName: velero - chart: cozy-velero - namespace: cozy-velero - privileged: true - optional: true - dependsOn: [monitoring-agents] - -- name: hetzner-robotlb - releaseName: robotlb - optional: true - chart: cozy-hetzner-robotlb - namespace: cozy-hetzner-robotlb - dependsOn: [cilium, kubeovn] diff --git a/packages/core/platform/bundles/paas-hosted.yaml b/packages/core/platform/bundles/paas-hosted.yaml deleted file mode 100644 index 6db50c7f..00000000 --- a/packages/core/platform/bundles/paas-hosted.yaml +++ /dev/null @@ -1,417 +0,0 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $clusterDomain := (index $cozyConfig.data "cluster-domain") | default "cozy.local" }} -{{- $oidcEnabled := index $cozyConfig.data "oidc-enabled" }} -{{- $host := index $cozyConfig.data "root-host" }} -{{- if not $host }} -{{- fail "ERROR need root-host in cozystack ConfigMap" }} -{{- end }} -{{- $apiServerEndpoint := index $cozyConfig.data "api-server-endpoint" }} -{{- if not $apiServerEndpoint }} -{{- fail "ERROR need api-server-endpoint in cozystack ConfigMap" }} -{{- end }} - -releases: -- name: cert-manager-crds - releaseName: cert-manager-crds - chart: cozy-cert-manager-crds - namespace: cozy-cert-manager - dependsOn: [] - -- name: cozystack-api - releaseName: cozystack-api - chart: cozy-cozystack-api - namespace: cozy-system - dependsOn: [cozystack-controller] - values: - cozystackAPI: - localK8sAPIEndpoint: - enabled: false - -- name: cozystack-controller - releaseName: cozystack-controller - chart: cozy-cozystack-controller - namespace: cozy-system - dependsOn: [] - {{- if eq (index $cozyConfig.data "telemetry-enabled") "false" }} - values: - cozystackController: - 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 - namespace: cozy-system - dependsOn: [cozystack-controller,cert-manager] - -- name: cozystack-resource-definition-crd - releaseName: cozystack-resource-definition-crd - chart: cozystack-resource-definition-crd - namespace: cozy-system - dependsOn: [cozystack-api,cozystack-controller] - -- name: bootbox-rd - releaseName: bootbox-rd - chart: bootbox-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: bucket-rd - releaseName: bucket-rd - chart: bucket-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: clickhouse-rd - releaseName: clickhouse-rd - chart: clickhouse-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: etcd-rd - releaseName: etcd-rd - chart: etcd-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: ferretdb-rd - releaseName: ferretdb-rd - chart: ferretdb-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: foundationdb-rd - releaseName: foundationdb-rd - chart: foundationdb-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: http-cache-rd - releaseName: http-cache-rd - chart: http-cache-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: info-rd - releaseName: info-rd - chart: info-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: ingress-rd - releaseName: ingress-rd - chart: ingress-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: kafka-rd - releaseName: kafka-rd - chart: kafka-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: kubernetes-rd - releaseName: kubernetes-rd - chart: kubernetes-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: monitoring-rd - releaseName: monitoring-rd - chart: monitoring-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: mysql-rd - releaseName: mysql-rd - chart: mysql-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: nats-rd - releaseName: nats-rd - chart: nats-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: postgres-rd - releaseName: postgres-rd - chart: postgres-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: rabbitmq-rd - releaseName: rabbitmq-rd - chart: rabbitmq-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: redis-rd - releaseName: redis-rd - chart: redis-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: seaweedfs-rd - releaseName: seaweedfs-rd - chart: seaweedfs-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: tcp-balancer-rd - releaseName: tcp-balancer-rd - chart: tcp-balancer-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: tenant-rd - releaseName: tenant-rd - chart: tenant-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: virtual-machine-rd - releaseName: virtual-machine-rd - chart: virtual-machine-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: virtualprivatecloud-rd - releaseName: virtualprivatecloud-rd - chart: virtualprivatecloud-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: vm-disk-rd - releaseName: vm-disk-rd - chart: vm-disk-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: vm-instance-rd - releaseName: vm-instance-rd - chart: vm-instance-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: vpn-rd - releaseName: vpn-rd - chart: vpn-rd - namespace: cozy-system - dependsOn: [cozystack-resource-definition-crd] - -- name: cert-manager - releaseName: cert-manager - chart: cozy-cert-manager - namespace: cozy-cert-manager - dependsOn: [cert-manager-crds] - -- name: cert-manager-issuers - releaseName: cert-manager-issuers - chart: cozy-cert-manager-issuers - namespace: cozy-cert-manager - dependsOn: [cert-manager] - -- name: prometheus-operator-crds - releaseName: prometheus-operator-crds - chart: cozy-prometheus-operator-crds - namespace: cozy-victoria-metrics-operator - dependsOn: [] - -- name: metrics-server - releaseName: metrics-server - chart: cozy-metrics-server - namespace: cozy-monitoring - dependsOn: [prometheus-operator-crds] - -- name: victoria-metrics-operator - releaseName: victoria-metrics-operator - chart: cozy-victoria-metrics-operator - namespace: cozy-victoria-metrics-operator - dependsOn: [cert-manager,prometheus-operator-crds] - -- name: monitoring-agents - releaseName: monitoring-agents - chart: cozy-monitoring-agents - namespace: cozy-monitoring - privileged: true - dependsOn: [victoria-metrics-operator, vertical-pod-autoscaler-crds, metrics-server] - values: - scrapeRules: - etcd: - enabled: true - -- name: etcd-operator - releaseName: etcd-operator - chart: cozy-etcd-operator - namespace: cozy-etcd-operator - dependsOn: [cert-manager] - -- name: grafana-operator - releaseName: grafana-operator - chart: cozy-grafana-operator - namespace: cozy-grafana-operator - dependsOn: [] - -- name: mariadb-operator - releaseName: mariadb-operator - chart: cozy-mariadb-operator - namespace: cozy-mariadb-operator - dependsOn: [cert-manager,victoria-metrics-operator] - values: - mariadb-operator: - clusterName: {{ $clusterDomain }} - -- name: postgres-operator - releaseName: postgres-operator - chart: cozy-postgres-operator - namespace: cozy-postgres-operator - dependsOn: [cert-manager,victoria-metrics-operator] - -- name: kafka-operator - releaseName: kafka-operator - chart: cozy-kafka-operator - namespace: cozy-kafka-operator - dependsOn: [victoria-metrics-operator] - values: - strimzi-kafka-operator: - kubernetesServiceDnsDomain: {{ $clusterDomain }} - -- name: clickhouse-operator - releaseName: clickhouse-operator - chart: cozy-clickhouse-operator - namespace: cozy-clickhouse-operator - dependsOn: [victoria-metrics-operator] - -- name: foundationdb-operator - releaseName: foundationdb-operator - chart: cozy-foundationdb-operator - namespace: cozy-foundationdb-operator - dependsOn: [cert-manager] - -- name: rabbitmq-operator - releaseName: rabbitmq-operator - chart: cozy-rabbitmq-operator - namespace: cozy-rabbitmq-operator - dependsOn: [] - -- name: redis-operator - releaseName: redis-operator - chart: cozy-redis-operator - namespace: cozy-redis-operator - dependsOn: [] - -- name: piraeus-operator - releaseName: piraeus-operator - chart: cozy-piraeus-operator - namespace: cozy-linstor - dependsOn: [cert-manager] - -- name: objectstorage-controller - releaseName: objectstorage-controller - chart: cozy-objectstorage-controller - namespace: cozy-objectstorage-controller - dependsOn: [] - -- name: telepresence - releaseName: traffic-manager - chart: cozy-telepresence - namespace: cozy-telepresence - optional: true - dependsOn: [] - -- name: external-dns - releaseName: external-dns - chart: cozy-external-dns - namespace: cozy-external-dns - optional: true - dependsOn: [] - -- name: external-secrets-operator - releaseName: external-secrets-operator - chart: cozy-external-secrets-operator - namespace: cozy-external-secrets-operator - optional: true - dependsOn: [] - -- name: dashboard - releaseName: dashboard - chart: cozy-dashboard - namespace: cozy-dashboard - values: - {{- $dashboardKCconfig := lookup "v1" "ConfigMap" "cozy-dashboard" "kubeapps-auth-config" }} - {{- $dashboardKCValues := dig "data" "values.yaml" (dict) $dashboardKCconfig }} - {{- toYaml (deepCopy $dashboardKCValues | mergeOverwrite (fromYaml (include "cozystack.defaultDashboardValues" .))) | nindent 4 }} - {{- if eq $oidcEnabled "true" }} - dependsOn: [keycloak-configure,cozystack-api] - {{- else }} - dependsOn: [] - {{- end }} - -{{- if $oidcEnabled }} -- name: keycloak - releaseName: keycloak - chart: cozy-keycloak - namespace: cozy-keycloak - dependsOn: [postgres-operator] - -- name: keycloak-operator - releaseName: keycloak-operator - chart: cozy-keycloak-operator - namespace: cozy-keycloak - dependsOn: [keycloak] - -- name: keycloak-configure - releaseName: keycloak-configure - chart: cozy-keycloak-configure - namespace: cozy-keycloak - dependsOn: [keycloak-operator] - values: - cozystack: - configHash: {{ $cozyConfig | toJson | sha256sum }} -{{- end }} - -- name: goldpinger - releaseName: goldpinger - chart: cozy-goldpinger - namespace: cozy-goldpinger - privileged: true - dependsOn: [monitoring-agents] - -- name: vertical-pod-autoscaler - releaseName: vertical-pod-autoscaler - chart: cozy-vertical-pod-autoscaler - namespace: cozy-vertical-pod-autoscaler - privileged: true - dependsOn: [monitoring-agents] - values: - vertical-pod-autoscaler: - recommender: - extraArgs: - prometheus-address: http://vmselect-shortterm.tenant-root.svc.{{ $clusterDomain }}:8481/select/0/prometheus/ - -- name: vertical-pod-autoscaler-crds - releaseName: vertical-pod-autoscaler-crds - chart: cozy-vertical-pod-autoscaler-crds - namespace: cozy-vertical-pod-autoscaler - privileged: true - dependsOn: [] - -- name: velero - releaseName: velero - chart: cozy-velero - namespace: cozy-velero - privileged: true - optional: true - dependsOn: [monitoring-agents] - -- name: hetzner-robotlb - releaseName: robotlb - optional: true - chart: cozy-hetzner-robotlb - namespace: cozy-hetzner-robotlb diff --git a/packages/core/platform/sources/backupstrategy-controller.yaml b/packages/core/platform/sources/backupstrategy-controller.yaml new file mode 100644 index 00000000..3647e8dc --- /dev/null +++ b/packages/core/platform/sources/backupstrategy-controller.yaml @@ -0,0 +1,22 @@ +--- +apiVersion: cozystack.io/v1alpha1 +kind: PackageSource +metadata: + name: cozystack.backupstrategy-controller +spec: + sourceRef: + kind: OCIRepository + name: cozystack-packages + namespace: cozy-system + path: / + variants: + - name: default + dependsOn: + - cozystack.networking + components: + - name: backupstrategy-controller + path: system/backupstrategy-controller + install: + privileged: true + namespace: cozy-backupstrategy-controller + releaseName: backupstrategy-controller diff --git a/packages/core/platform/sources/capi-providers-bootstrap.yaml b/packages/core/platform/sources/capi-provider-bootstrap-kubeadm.yaml similarity index 58% rename from packages/core/platform/sources/capi-providers-bootstrap.yaml rename to packages/core/platform/sources/capi-provider-bootstrap-kubeadm.yaml index 0469b1bc..90cea107 100644 --- a/packages/core/platform/sources/capi-providers-bootstrap.yaml +++ b/packages/core/platform/sources/capi-provider-bootstrap-kubeadm.yaml @@ -2,7 +2,7 @@ apiVersion: cozystack.io/v1alpha1 kind: PackageSource metadata: - name: cozystack.capi-providers-bootstrap + name: cozystack.capi-provider-bootstrap-kubeadm spec: sourceRef: kind: OCIRepository @@ -10,6 +10,17 @@ spec: namespace: cozy-system path: / variants: + - name: default + dependsOn: + - cozystack.networking + - cozystack.capi-operator + components: + - name: capi-providers-bootstrap + path: system/capi-providers-bootstrap + install: + privileged: true + namespace: cozy-cluster-api + releaseName: capi-providers-bootstrap - name: kubevirt dependsOn: - cozystack.networking diff --git a/packages/core/platform/sources/capi-providers-core.yaml b/packages/core/platform/sources/capi-provider-core.yaml similarity index 92% rename from packages/core/platform/sources/capi-providers-core.yaml rename to packages/core/platform/sources/capi-provider-core.yaml index e5b6cc99..91af6617 100644 --- a/packages/core/platform/sources/capi-providers-core.yaml +++ b/packages/core/platform/sources/capi-provider-core.yaml @@ -2,7 +2,7 @@ apiVersion: cozystack.io/v1alpha1 kind: PackageSource metadata: - name: cozystack.capi-providers-core + name: cozystack.capi-provider-core spec: sourceRef: kind: OCIRepository diff --git a/packages/core/platform/sources/capi-providers-cpprovider.yaml b/packages/core/platform/sources/capi-provider-cp-kamaji.yaml similarity index 58% rename from packages/core/platform/sources/capi-providers-cpprovider.yaml rename to packages/core/platform/sources/capi-provider-cp-kamaji.yaml index 7b63260d..08c29b8f 100644 --- a/packages/core/platform/sources/capi-providers-cpprovider.yaml +++ b/packages/core/platform/sources/capi-provider-cp-kamaji.yaml @@ -2,7 +2,7 @@ apiVersion: cozystack.io/v1alpha1 kind: PackageSource metadata: - name: cozystack.capi-providers-cpprovider + name: cozystack.capi-provider-cp-kamaji spec: sourceRef: kind: OCIRepository @@ -10,6 +10,18 @@ spec: namespace: cozy-system path: / variants: + - name: default + dependsOn: + - cozystack.networking + - cozystack.capi-operator + - cozystack.kamaji + components: + - name: capi-providers-cpprovider + path: system/capi-providers-cpprovider + install: + privileged: true + namespace: cozy-cluster-api + releaseName: capi-providers-cpprovider - name: kamaji dependsOn: - cozystack.networking diff --git a/packages/core/platform/sources/capi-providers-infraprovider.yaml b/packages/core/platform/sources/capi-provider-infra-kubevirt.yaml similarity index 57% rename from packages/core/platform/sources/capi-providers-infraprovider.yaml rename to packages/core/platform/sources/capi-provider-infra-kubevirt.yaml index 88bebc09..341e1920 100644 --- a/packages/core/platform/sources/capi-providers-infraprovider.yaml +++ b/packages/core/platform/sources/capi-provider-infra-kubevirt.yaml @@ -2,7 +2,7 @@ apiVersion: cozystack.io/v1alpha1 kind: PackageSource metadata: - name: cozystack.capi-providers-infraprovider + name: cozystack.capi-provider-infra-kubevirt spec: sourceRef: kind: OCIRepository @@ -10,6 +10,18 @@ spec: namespace: cozy-system path: / variants: + - name: default + dependsOn: + - cozystack.networking + - cozystack.capi-operator + - cozystack.kubevirt + components: + - name: capi-providers-infraprovider + path: system/capi-providers-infraprovider + install: + privileged: true + namespace: cozy-cluster-api + releaseName: capi-providers-infraprovider - name: kubevirt dependsOn: - cozystack.networking diff --git a/packages/core/platform/sources/cozystack-engine.yaml b/packages/core/platform/sources/cozystack-engine.yaml index ce06b9d8..7d1b03c8 100644 --- a/packages/core/platform/sources/cozystack-engine.yaml +++ b/packages/core/platform/sources/cozystack-engine.yaml @@ -58,6 +58,7 @@ spec: dependsOn: - cozystack.networking - cozystack.keycloak + - cozystack.keycloak-operator libraries: - name: cozy-lib path: library/cozy-lib diff --git a/packages/core/platform/sources/ingress-nginx.yaml b/packages/core/platform/sources/ingress-nginx.yaml new file mode 100644 index 00000000..b51e096f --- /dev/null +++ b/packages/core/platform/sources/ingress-nginx.yaml @@ -0,0 +1,22 @@ +--- +apiVersion: cozystack.io/v1alpha1 +kind: PackageSource +metadata: + name: cozystack.ingress-nginx +spec: + sourceRef: + kind: OCIRepository + name: cozystack-packages + namespace: cozy-system + path: / + variants: + - name: default + dependsOn: + - cozystack.networking + - cozystack.prometheus-operator-crds + components: + - name: ingress-nginx + path: system/ingress-nginx + install: + namespace: cozy-ingress-nginx + releaseName: ingress-nginx diff --git a/packages/core/platform/sources/keycloak.yaml b/packages/core/platform/sources/keycloak.yaml index a9dc64b4..545d70a5 100644 --- a/packages/core/platform/sources/keycloak.yaml +++ b/packages/core/platform/sources/keycloak.yaml @@ -14,9 +14,13 @@ spec: dependsOn: - cozystack.networking - cozystack.postgres-operator + libraries: + - name: cozy-lib + path: library/cozy-lib components: - name: keycloak path: system/keycloak + libraries: ["cozy-lib"] install: namespace: cozy-keycloak releaseName: keycloak diff --git a/packages/core/platform/sources/kubernetes-application.yaml b/packages/core/platform/sources/kubernetes-application.yaml index 0acde1c4..9787cb19 100644 --- a/packages/core/platform/sources/kubernetes-application.yaml +++ b/packages/core/platform/sources/kubernetes-application.yaml @@ -14,10 +14,10 @@ spec: dependsOn: - cozystack.networking - cozystack.capi-operator - - cozystack.capi-providers-bootstrap - - cozystack.capi-providers-core - - cozystack.capi-providers-cpprovider - - cozystack.capi-providers-infraprovider + - cozystack.capi-provider-bootstrap-kubeadm + - cozystack.capi-provider-core + - cozystack.capi-provider-cp-kamaji + - cozystack.capi-provider-infra-kubevirt libraries: - name: cozy-lib path: library/cozy-lib diff --git a/packages/core/platform/sources/metrics-server.yaml b/packages/core/platform/sources/metrics-server.yaml new file mode 100644 index 00000000..0bd17881 --- /dev/null +++ b/packages/core/platform/sources/metrics-server.yaml @@ -0,0 +1,22 @@ +--- +apiVersion: cozystack.io/v1alpha1 +kind: PackageSource +metadata: + name: cozystack.metrics-server +spec: + sourceRef: + kind: OCIRepository + name: cozystack-packages + namespace: cozy-system + path: / + variants: + - name: default + dependsOn: + - cozystack.networking + - cozystack.prometheus-operator-crds + components: + - name: metrics-server + path: system/metrics-server + install: + namespace: cozy-metrics-server + releaseName: metrics-server diff --git a/packages/core/platform/sources/monitoring-application.yaml b/packages/core/platform/sources/monitoring-application.yaml index 0f01eddd..6ea5a2d0 100644 --- a/packages/core/platform/sources/monitoring-application.yaml +++ b/packages/core/platform/sources/monitoring-application.yaml @@ -13,6 +13,7 @@ spec: - name: default dependsOn: - cozystack.networking + - cozystack.postgres-operator libraries: - name: cozy-lib path: library/cozy-lib diff --git a/packages/core/platform/sources/postgres-application.yaml b/packages/core/platform/sources/postgres-application.yaml index b5953069..5d7b220b 100644 --- a/packages/core/platform/sources/postgres-application.yaml +++ b/packages/core/platform/sources/postgres-application.yaml @@ -13,6 +13,7 @@ spec: - name: default dependsOn: - cozystack.networking + - cozystack.postgres-operator libraries: - name: cozy-lib path: library/cozy-lib diff --git a/packages/core/platform/templates/_helpers.tpl b/packages/core/platform/templates/_helpers.tpl index c656c7f8..18105acf 100644 --- a/packages/core/platform/templates/_helpers.tpl +++ b/packages/core/platform/templates/_helpers.tpl @@ -1,108 +1,52 @@ -{{/* -Get IP-addresses of master nodes -*/}} -{{- define "cozystack.master-node-ips" -}} -{{- $nodes := lookup "v1" "Node" "" "" -}} -{{- $ips := list -}} -{{- range $node := $nodes.items -}} - {{- if eq (index $node.metadata.labels "node-role.kubernetes.io/control-plane") "" -}} - {{- range $address := $node.status.addresses -}} - {{- if eq $address.type "InternalIP" -}} - {{- $ips = append $ips $address.address -}} - {{- break -}} - {{- end -}} - {{- end -}} - {{- end -}} +{{- define "cozystack.platform.package" -}} +{{- $name := index . 0 -}} +{{- $variant := default "default" (index . 1) -}} +{{- $root := default $ (index . 2) -}} +{{- $components := dict -}} +{{- if gt (len .) 3 -}} +{{- $components = index . 3 -}} +{{- end -}} +{{- $disabled := default (list) $root.Values.bundles.disabledPackages -}} +{{- if not (has $name $disabled) -}} +--- +apiVersion: cozystack.io/v1alpha1 +kind: Package +metadata: + name: {{ $name }} +spec: + variant: {{ $variant }} +{{- if $components }} + components: +{{ toYaml $components | indent 4 }} +{{- end }} {{- end -}} -{{ join "," $ips }} {{- end -}} -{{/* -Get Kubernetes API Endpoint from cozystack deployment -Returns host:port format -*/}} -{{- define "cozystack.kubernetesAPIEndpoint" -}} -{{- $cozyDeployment := lookup "apps/v1" "Deployment" "cozy-system" "cozystack" }} -{{- $cozyContainers := dig "spec" "template" "spec" "containers" list $cozyDeployment }} -{{- $kubernetesServiceHost := "" }} -{{- $kubernetesServicePort := "" }} -{{- range $cozyContainers }} -{{- if eq .name "cozystack" }} -{{- range .env }} -{{- if eq .name "KUBERNETES_SERVICE_HOST" }} -{{- $kubernetesServiceHost = .value }} -{{- end }} -{{- if eq .name "KUBERNETES_SERVICE_PORT" }} -{{- $kubernetesServicePort = .value }} -{{- end }} -{{- end }} -{{- end }} -{{- end }} -{{- if eq $kubernetesServiceHost "" }} -{{- $kubernetesServiceHost = "kubernetes.default.svc" }} -{{- end }} -{{- if eq $kubernetesServicePort "" }} -{{- $kubernetesServicePort = "443" }} -{{- end }} -{{- printf "%s:%s" $kubernetesServiceHost $kubernetesServicePort }} +{{- define "cozystack.platform.package.default" -}} +{{- $name := index . 0 -}} +{{- $root := index . 1 -}} +{{- include "cozystack.platform.package" (list $name "default" $root) }} {{- end -}} -{{- define "cozystack.defaultDashboardValues" -}} -kubeapps: -{{- if .Capabilities.APIVersions.Has "source.toolkit.fluxcd.io/v1" }} -{{- with (lookup "source.toolkit.fluxcd.io/v1" "HelmRepository" "cozy-public" "").items }} - redis: - master: - podAnnotations: - {{- range $index, $repo := . }} - {{- with (($repo.status).artifact).revision }} - repository.cozystack.io/{{ $repo.metadata.name }}: {{ quote . }} - {{- end }} - {{- end }} -{{- end }} -{{- end }} - frontend: - resourcesPreset: "none" - dashboard: - resourcesPreset: "none" - {{- $cozystackBranding:= lookup "v1" "ConfigMap" "cozy-system" "cozystack-branding" }} - {{- $branding := dig "data" "branding" "" $cozystackBranding }} - {{- if $branding }} - customLocale: - "Kubeapps": {{ $branding }} - {{- end }} - customStyle: | - {{- $logoImage := dig "data" "logo" "" $cozystackBranding }} - {{- if $logoImage }} - .kubeapps-logo { - background-image: {{ $logoImage }} - } - {{- end }} - #serviceaccount-selector { - display: none; - } - .login-moreinfo { - display: none; - } - a[href="#/docs"] { - display: none; - } - .login-group .clr-form-control .clr-control-label { - display: none; - } - .appview-separator div.appview-first-row div.center { - display: none; - } - .appview-separator div.appview-first-row section[aria-labelledby="app-secrets"] { - display: none; - } - .appview-first-row section[aria-labelledby="access-urls-title"] { - width: 100%; - } - .header-version { - display: none; - } - .label.label-info-secondary { - display: none; - } -{{- end }} +{{- define "cozystack.platform.package.optional" -}} +{{- $name := index . 0 -}} +{{- $variant := default "default" (index . 1) -}} +{{- $root := default $ (index . 2) -}} +{{- $disabled := default (list) $root.Values.bundles.disabledPackages -}} +{{- $enabled := default (list) $root.Values.bundles.enabledPackages -}} +{{- if and (has $name $enabled) (not (has $name $disabled)) -}} +--- +apiVersion: cozystack.io/v1alpha1 +kind: Package +metadata: + name: {{ $name }} +spec: + variant: {{ $variant }} +{{- end -}} +{{- end -}} + +{{- define "cozystack.platform.package.optional.default" -}} +{{- $name := index . 0 -}} +{{- $root := index . 1 -}} +{{- include "cozystack.platform.package.optional" (list $name "default" $root) }} +{{- end -}} diff --git a/packages/core/platform/templates/apps.yaml b/packages/core/platform/templates/apps.yaml index b42aad87..d3941067 100644 --- a/packages/core/platform/templates/apps.yaml +++ b/packages/core/platform/templates/apps.yaml @@ -1,118 +1,35 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $cozystackBranding := lookup "v1" "ConfigMap" "cozy-system" "cozystack-branding" }} -{{- $cozystackScheduling := lookup "v1" "ConfigMap" "cozy-system" "cozystack-scheduling" }} {{- $kubeRootCa := lookup "v1" "ConfigMap" "kube-system" "kube-root-ca.crt" }} -{{- $bundleName := index $cozyConfig.data "bundle-name" }} +{{- $bundleName := .Values.bundles.system.variant }} {{- $bundle := tpl (.Files.Get (printf "bundles/%s.yaml" $bundleName)) . | fromYaml }} -{{/* Default values for _cluster config to ensure all required keys exist */}} -{{- $clusterDefaults := dict - "root-host" "" - "bundle-name" "" - "clusterissuer" "http01" - "oidc-enabled" "false" - "expose-services" "" - "expose-ingress" "tenant-root" - "expose-external-ips" "" - "cluster-domain" "cozy.local" - "api-server-endpoint" "" -}} -{{- $clusterConfig := mergeOverwrite $clusterDefaults ($cozyConfig.data | default dict) }} -{{- $host := "example.org" }} -{{- $host := "example.org" }} -{{- if $cozyConfig.data }} - {{- if hasKey $cozyConfig.data "root-host" }} - {{- $host = index $cozyConfig.data "root-host" }} - {{- end }} -{{- end }} -{{- $tenantRoot := dict }} -{{- if .Capabilities.APIVersions.Has "helm.toolkit.fluxcd.io/v2" }} -{{- $tenantRoot = lookup "helm.toolkit.fluxcd.io/v2" "HelmRelease" "tenant-root" "tenant-root" }} -{{- end }} -{{- if and $tenantRoot $tenantRoot.spec $tenantRoot.spec.values $tenantRoot.spec.values.host }} -{{- $host = $tenantRoot.spec.values.host }} -{{- else }} -{{- end }} ---- -apiVersion: v1 -kind: Namespace -metadata: - annotations: - helm.sh/resource-policy: keep - labels: - tenant.cozystack.io/tenant-root: "" - namespace.cozystack.io/etcd: tenant-root - namespace.cozystack.io/monitoring: tenant-root - namespace.cozystack.io/ingress: tenant-root - namespace.cozystack.io/seaweedfs: tenant-root - namespace.cozystack.io/host: "{{ $host }}" - name: tenant-root --- apiVersion: v1 kind: Secret metadata: name: cozystack-values - namespace: tenant-root + namespace: cozy-system labels: reconcile.fluxcd.io/watch: Enabled type: Opaque stringData: values.yaml: | _cluster: - {{- $clusterConfig | toYaml | nindent 6 }} - {{- with $cozystackBranding.data }} + root-host: {{ .Values.publishing.host | quote }} + bundle-name: {{ .Values.bundles.system.variant | quote }} + clusterissuer: {{ .Values.publishing.certificates.issuerType | quote }} + oidc-enabled: {{ .Values.authentication.oidc.enabled | quote }} + expose-services: {{ .Values.publishing.exposedServices | join "," | quote }} + expose-ingress: {{ .Values.publishing.ingressName | quote }} + expose-external-ips: {{ .Values.publishing.externalIPs | join "," | quote }} + cluster-domain: {{ .Values.networking.clusterDomain | quote }} + api-server-endpoint: {{ .Values.publishing.apiServerEndpoint | quote }} + {{- with .Values.branding }} branding: {{- . | toYaml | nindent 8 }} {{- end }} - {{- with $cozystackScheduling.data }} + {{- with .Values.scheduling }} scheduling: {{- . | toYaml | nindent 8 }} {{- end }} {{- with $kubeRootCa.data }} kube-root-ca: {{ index . "ca.crt" | b64enc | quote }} {{- end }} - _namespace: - etcd: tenant-root - monitoring: tenant-root - ingress: tenant-root - seaweedfs: tenant-root - host: {{ $host | quote }} ---- -apiVersion: helm.toolkit.fluxcd.io/v2 -kind: HelmRelease -metadata: - name: tenant-root - namespace: tenant-root - labels: - cozystack.io/ui: "true" - apps.cozystack.io/application.kind: Tenant - apps.cozystack.io/application.group: apps.cozystack.io - apps.cozystack.io/application.name: tenant-root -spec: - interval: 0s - releaseName: tenant-root - install: - remediation: - retries: -1 - upgrade: - remediation: - retries: -1 - chart: - spec: - chart: tenant - version: '>= 0.0.0-0' - sourceRef: - kind: HelmRepository - name: cozystack-apps - namespace: cozy-public - valuesFrom: - - kind: Secret - name: cozystack-values - values: - host: "{{ $host }}" - dependsOn: - {{- range $x := $bundle.releases }} - {{- if has $x.name (list "cilium" "kubeovn") }} - - name: {{ $x.name }} - namespace: {{ $x.namespace }} - {{- end }} - {{- end }} diff --git a/packages/core/platform/templates/bundles/iaas.yaml b/packages/core/platform/templates/bundles/iaas.yaml new file mode 100644 index 00000000..529ec67a --- /dev/null +++ b/packages/core/platform/templates/bundles/iaas.yaml @@ -0,0 +1,20 @@ +{{- if and .Values.bundles.iaas.enabled (ne .Values.bundles.system.variant "isp-full") }} +{{- fail "bundles.iaas.enabled can only be true when bundles.system.variant is 'isp-full'" }} +{{- end }} +{{- if and .Values.bundles.iaas.enabled (eq .Values.bundles.system.variant "isp-full") }} +{{include "cozystack.platform.package.default" (list "cozystack.kubevirt" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.kubevirt-cdi" $) }} +{{include "cozystack.platform.package.optional.default" (list "cozystack.gpu-operator" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.kamaji" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.capi-operator" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.capi-provider-bootstrap-kubeadm" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.capi-provider-core" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.capi-provider-cp-kamaji" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.capi-provider-infra-kubevirt" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.bucket-application" $) }} +{{include "cozystack.platform.package" (list "cozystack.kubernetes-application" "kubevirt" $) }} +{{include "cozystack.platform.package" (list "cozystack.virtual-machine-application" "kubevirt" $) }} +{{include "cozystack.platform.package" (list "cozystack.virtualprivatecloud-application" "kubevirt" $) }} +{{include "cozystack.platform.package" (list "cozystack.vm-disk-application" "kubevirt" $) }} +{{include "cozystack.platform.package" (list "cozystack.vm-instance-application" "kubevirt" $) }} +{{- end }} diff --git a/packages/core/platform/templates/bundles/naas.yaml b/packages/core/platform/templates/bundles/naas.yaml new file mode 100644 index 00000000..7c40a4aa --- /dev/null +++ b/packages/core/platform/templates/bundles/naas.yaml @@ -0,0 +1,8 @@ +{{- if and .Values.bundles.naas.enabled (not (or (eq .Values.bundles.system.variant "isp-full") (eq .Values.bundles.system.variant "isp-hosted"))) }} +{{- fail "bundles.naas.enabled can only be true when bundles.system.variant is 'isp-full' or 'isp-hosted'" }} +{{- end }} +{{- if and .Values.bundles.naas.enabled (or (eq .Values.bundles.system.variant "isp-full") (eq .Values.bundles.system.variant "isp-hosted")) }} +{{include "cozystack.platform.package.default" (list "cozystack.http-cache-application" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.tcp-balancer-application" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.vpn-application" $) }} +{{- end }} diff --git a/packages/core/platform/templates/bundles/paas.yaml b/packages/core/platform/templates/bundles/paas.yaml new file mode 100644 index 00000000..fbaa25a1 --- /dev/null +++ b/packages/core/platform/templates/bundles/paas.yaml @@ -0,0 +1,20 @@ +{{- if and .Values.bundles.paas.enabled (not (or (eq .Values.bundles.system.variant "isp-full") (eq .Values.bundles.system.variant "isp-hosted"))) }} +{{- fail "bundles.paas.enabled can only be true when bundles.system.variant is 'isp-full' or 'isp-hosted'" }} +{{- end }} +{{- if and .Values.bundles.paas.enabled (or (eq .Values.bundles.system.variant "isp-full") (eq .Values.bundles.system.variant "isp-hosted")) }} +{{include "cozystack.platform.package.default" (list "cozystack.mariadb-operator" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.kafka-operator" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.clickhouse-operator" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.foundationdb-operator" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.rabbitmq-operator" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.redis-operator" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.clickhouse-application" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.ferretdb-application" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.foundationdb-application" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.kafka-application" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.mysql-application" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.nats-application" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.postgres-application" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.rabbitmq-application" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.redis-application" $) }} +{{- end }} diff --git a/packages/core/platform/templates/bundles/system.yaml b/packages/core/platform/templates/bundles/system.yaml new file mode 100644 index 00000000..59e44263 --- /dev/null +++ b/packages/core/platform/templates/bundles/system.yaml @@ -0,0 +1,72 @@ +{{- if .Values.bundles.system.enabled }} + +# Networking +{{- if eq .Values.bundles.system.variant "isp-full" }} +{{- $networkingComponents := dict -}} +{{- if .Values.networking -}} +{{- $kubeovnValues := dict "kube-ovn" (dict + "ipv4" (dict + "POD_CIDR" .Values.networking.podCIDR + "POD_GATEWAY" .Values.networking.podGateway + "SVC_CIDR" .Values.networking.serviceCIDR + "JOIN_CIDR" .Values.networking.joinCIDR)) -}} +{{- $_ := set $networkingComponents "kubeovn" (dict "values" $kubeovnValues) -}} +{{- end -}} +{{include "cozystack.platform.package" (list "cozystack.networking" "kubeovn-cilium" $ $networkingComponents) }} +{{include "cozystack.platform.package.default" (list "cozystack.kubeovn-webhook" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.kubeovn-plunger" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.cozy-proxy" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.multus" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.metallb" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.reloader" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.linstor" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.snapshot-controller" $) }} +{{- end }} + +{{- if eq .Values.bundles.system.variant "isp-hosted" }} +{{include "cozystack.platform.package" (list "cozystack.networking" "noop" $) }} +{{- end }} + +# Cozystack Engine +{{- if .Values.authentication.oidc.enabled }} +{{include "cozystack.platform.package" (list "cozystack.cozystack-engine" "oidc" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.keycloak" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.keycloak-operator" $) }} +{{- else }} +{{include "cozystack.platform.package" (list "cozystack.cozystack-engine" "default" $) }} +{{- end }} + +# Common Packages +{{include "cozystack.platform.package.default" (list "cozystack.cert-manager" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.victoria-metrics-operator" $) }} +{{- $tenantComponents := dict -}} +{{- $tenantClusterValues := dict "_cluster" (dict "oidc-enabled" (ternary "true" "false" .Values.authentication.oidc.enabled)) -}} +{{- $_ := set $tenantComponents "tenant" (dict "values" $tenantClusterValues) }} +{{include "cozystack.platform.package" (list "cozystack.tenant-application" "default" $ $tenantComponents) }} +{{include "cozystack.platform.package.default" (list "cozystack.ingress-application" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.seaweedfs-application" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.info-application" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.monitoring-application" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.etcd-application" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.cozystack-basics" $) }} +{{include "cozystack.platform.package.optional.default" (list "cozystack.backupstrategy-controller" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.backup-controller" $) }} +{{include "cozystack.platform.package.optional.default" (list "cozystack.velero" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.vertical-pod-autoscaler" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.monitoring-agents" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.goldpinger" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.prometheus-operator-crds" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.grafana-operator" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.etcd-operator" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.postgres-operator" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.objectstorage-controller" $) }} + +# Optional System Packages (controlled via bundles.enabledPackages) +{{include "cozystack.platform.package.optional.default" (list "cozystack.nfs-driver" $) }} +{{include "cozystack.platform.package.optional.default" (list "cozystack.telepresence" $) }} +{{include "cozystack.platform.package.optional.default" (list "cozystack.external-dns" $) }} +{{include "cozystack.platform.package.optional.default" (list "cozystack.external-secrets-operator" $) }} +{{include "cozystack.platform.package.optional.default" (list "cozystack.bootbox" $) }} +{{include "cozystack.platform.package.optional.default" (list "cozystack.hetzner-robotlb" $) }} + +{{- end }} diff --git a/packages/core/platform/templates/containerd-registry-secret.yaml b/packages/core/platform/templates/containerd-registry-secret.yaml new file mode 100644 index 00000000..6e9fb640 --- /dev/null +++ b/packages/core/platform/templates/containerd-registry-secret.yaml @@ -0,0 +1,35 @@ +{{- if .Values.registries.mirrors }} +apiVersion: v1 +kind: Secret +metadata: + name: patch-containerd + namespace: cozy-system +type: Opaque +stringData: +{{- range $registry, $mirror := .Values.registries.mirrors }} +{{- if $mirror.endpoints }} + {{ $registry }}.toml: | + server = "https://{{ $registry }}" +{{- range $endpoint := $mirror.endpoints }} + [host."{{ $endpoint }}"] + capabilities = ["pull", "resolve"] +{{- $endpointConfig := index $.Values.registries.config $endpoint }} +{{- if $endpointConfig }} +{{- if $endpointConfig.tls }} +{{- if $endpointConfig.tls.insecureSkipVerify }} + skip_verify = true +{{- end }} +{{- end }} +{{- if $endpointConfig.auth }} + [host."{{ $endpoint }}".auth] + username = "{{ $endpointConfig.auth.username }}" + password = "{{ $endpointConfig.auth.password }}" +{{- end }} +{{- else }} + skip_verify = true +{{- end }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} + diff --git a/packages/core/platform/templates/cozystack-assets.yaml b/packages/core/platform/templates/cozystack-assets.yaml deleted file mode 100644 index 61ab8dca..00000000 --- a/packages/core/platform/templates/cozystack-assets.yaml +++ /dev/null @@ -1,73 +0,0 @@ -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: cozystack-assets - namespace: cozy-system - labels: - app: cozystack-assets -spec: - serviceName: cozystack-assets - replicas: 1 - selector: - matchLabels: - app: cozystack-assets - template: - metadata: - labels: - app: cozystack-assets - spec: - hostNetwork: true - containers: - - name: assets-server - image: "{{ .Values.assets.image }}" - args: - - "-dir=/cozystack/assets" - - "-address=:8123" - ports: - - name: http - containerPort: 8123 - hostPort: 8123 - tolerations: - - operator: Exists ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: cozystack-assets-reader - namespace: cozy-system -rules: - - apiGroups: [""] - resources: - - pods/proxy - resourceNames: - - cozystack-assets-0 - verbs: - - get ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: cozystack-assets-reader - namespace: cozy-system -subjects: - - kind: User - name: cozystack-assets-reader - apiGroup: rbac.authorization.k8s.io -roleRef: - kind: Role - name: cozystack-assets-reader - apiGroup: rbac.authorization.k8s.io ---- -apiVersion: v1 -kind: Service -metadata: - name: cozystack-assets - namespace: cozy-system -spec: - ports: - - name: http - port: 80 - targetPort: 8123 - selector: - app: cozystack-assets - type: ClusterIP diff --git a/packages/core/platform/templates/cozystack-version.yaml b/packages/core/platform/templates/cozystack-version.yaml new file mode 100644 index 00000000..8e3ff6c6 --- /dev/null +++ b/packages/core/platform/templates/cozystack-version.yaml @@ -0,0 +1,11 @@ +{{- $configMap := lookup "v1" "ConfigMap" .Release.Namespace "cozystack-version" }} +{{- if not $configMap }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cozystack-version + namespace: {{ .Release.Namespace }} +data: + version: {{ .Values.migrations.targetVersion | quote }} +{{- end }} diff --git a/packages/core/platform/templates/helmreleases.yaml b/packages/core/platform/templates/helmreleases.yaml deleted file mode 100644 index e3118b70..00000000 --- a/packages/core/platform/templates/helmreleases.yaml +++ /dev/null @@ -1,100 +0,0 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $bundleName := index $cozyConfig.data "bundle-name" }} -{{- $bundle := tpl (.Files.Get (printf "bundles/%s.yaml" $bundleName)) . | fromYaml }} -{{- $dependencyNamespaces := dict }} -{{- $disabledComponents := splitList "," ((index $cozyConfig.data "bundle-disable") | default "") }} -{{- $enabledComponents := splitList "," ((index $cozyConfig.data "bundle-enable") | default "") }} -{{- $oidcEnabled := (index (default dict $cozyConfig.data) "oidc-enabled") | default "false" | eq "true" }} - -{{/* collect dependency namespaces from releases */}} -{{- range $x := $bundle.releases }} -{{- $_ := set $dependencyNamespaces $x.name $x.namespace }} -{{- end }} - -{{- range $x := $bundle.releases }} - -{{- $shouldInstall := true }} -{{- $shouldDelete := false }} -{{- $notEnabledOptionalComponent := and ($x.optional) (not (has $x.name $enabledComponents)) }} -{{- $disabledComponent := has $x.name $disabledComponents }} -{{- $isKeycloakComponent := or (eq $x.name "keycloak") (eq $x.name "keycloak-operator") (eq $x.name "keycloak-configure") }} - -{{- if and $isKeycloakComponent (not $oidcEnabled) }} -{{- $shouldInstall = false }} -{{- if $.Capabilities.APIVersions.Has "helm.toolkit.fluxcd.io/v2" }} -{{- if lookup "helm.toolkit.fluxcd.io/v2" "HelmRelease" $x.namespace $x.name }} -{{- $shouldDelete = true }} -{{- end }} -{{- end }} -{{- else if or $disabledComponent $notEnabledOptionalComponent }} -{{- $shouldInstall = false }} -{{- if $.Capabilities.APIVersions.Has "helm.toolkit.fluxcd.io/v2" }} -{{- if lookup "helm.toolkit.fluxcd.io/v2" "HelmRelease" $x.namespace $x.name }} -{{- $shouldDelete = true }} -{{- end }} -{{- end }} -{{- end }} - -{{- if or $shouldInstall $shouldDelete }} ---- -apiVersion: helm.toolkit.fluxcd.io/v2 -kind: HelmRelease -metadata: - name: {{ $x.name }} - namespace: {{ $x.namespace }} - labels: - cozystack.io/repository: system - cozystack.io/system-app: "true" - {{- if $shouldDelete }} - cozystack.io/marked-for-deletion: "true" - {{- end }} -spec: - interval: 5m - releaseName: {{ $x.releaseName | default $x.name }} - install: - crds: CreateReplace - remediation: - retries: -1 - upgrade: - crds: CreateReplace - remediation: - retries: -1 - chart: - spec: - chart: {{ $x.chart }} - reconcileStrategy: Revision - sourceRef: - kind: HelmRepository - name: cozystack-system - namespace: cozy-system - version: '>= 0.0.0-0' - {{- with $x.valuesFiles }} - valuesFiles: - {{- toYaml $x.valuesFiles | nindent 6 }} - {{- end }} - {{- $values := dict }} - {{- with $x.values }} - {{- $values = merge . $values }} - {{- end }} - {{- with index $cozyConfig.data (printf "values-%s" $x.name) }} - {{- $values = mergeOverwrite $values (fromYaml .) }} - {{- end }} - {{- with $values }} - values: - {{- toYaml . | nindent 4}} - {{- end }} - valuesFrom: - - kind: Secret - name: cozystack-values - - {{- with $x.dependsOn }} - dependsOn: - {{- range $dep := . }} - {{- if not (has $dep $disabledComponents) }} - - name: {{ $dep }} - namespace: {{ index $dependencyNamespaces $dep }} - {{- end }} - {{- end }} - {{- end }} -{{- end }} -{{- end }} diff --git a/packages/core/platform/templates/helmrepos.yaml b/packages/core/platform/templates/helmrepos.yaml deleted file mode 100644 index 47954869..00000000 --- a/packages/core/platform/templates/helmrepos.yaml +++ /dev/null @@ -1,40 +0,0 @@ ---- -apiVersion: source.toolkit.fluxcd.io/v1 -kind: HelmRepository -metadata: - name: cozystack-system - namespace: cozy-system - labels: - cozystack.io/repository: system -spec: - interval: 5m0s - url: https://{{ include "cozystack.kubernetesAPIEndpoint" . }}/api/v1/namespaces/cozy-system/pods/cozystack-assets-0/proxy/repos/system - certSecretRef: - name: cozystack-assets-tls ---- -apiVersion: source.toolkit.fluxcd.io/v1 -kind: HelmRepository -metadata: - name: cozystack-apps - namespace: cozy-public - labels: - cozystack.io/ui: "true" - cozystack.io/repository: apps -spec: - interval: 5m0s - url: https://{{ include "cozystack.kubernetesAPIEndpoint" . }}/api/v1/namespaces/cozy-system/pods/cozystack-assets-0/proxy/repos/apps - certSecretRef: - name: cozystack-assets-tls ---- -apiVersion: source.toolkit.fluxcd.io/v1 -kind: HelmRepository -metadata: - name: cozystack-extra - namespace: cozy-public - labels: - cozystack.io/repository: extra -spec: - interval: 5m0s - url: https://{{ include "cozystack.kubernetesAPIEndpoint" . }}/api/v1/namespaces/cozy-system/pods/cozystack-assets-0/proxy/repos/extra - certSecretRef: - name: cozystack-assets-tls diff --git a/packages/core/platform/templates/migration-hook.yaml b/packages/core/platform/templates/migration-hook.yaml new file mode 100644 index 00000000..67b09820 --- /dev/null +++ b/packages/core/platform/templates/migration-hook.yaml @@ -0,0 +1,69 @@ +{{- if .Values.migrations.enabled }} +{{- $shouldRunMigrationHook := false }} +{{- $currentVersion := 0 }} +{{- $targetVersion := .Values.migrations.targetVersion | int }} +{{- $configMap := lookup "v1" "ConfigMap" .Release.Namespace "cozystack-version" }} +{{- if $configMap }} + {{- $currentVersion = dig "data" "version" "0" $configMap | int }} + {{- if lt $currentVersion $targetVersion }} + {{- $shouldRunMigrationHook = true }} + {{- end }} +{{- end }} + +{{- if $shouldRunMigrationHook }} +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: cozystack-migration-hook + annotations: + helm.sh/hook: pre-upgrade,pre-install + helm.sh/hook-weight: "1" + helm.sh/hook-delete-policy: before-hook-creation +spec: + backoffLimit: 3 + template: + metadata: + labels: + policy.cozystack.io/allow-to-apiserver: "true" + spec: + serviceAccountName: cozystack-migration-hook + containers: + - name: migration + image: {{ .Values.migrations.image }} + env: + - name: NAMESPACE + value: {{ .Release.Namespace | quote }} + - name: CURRENT_VERSION + value: {{ $currentVersion | quote }} + - name: TARGET_VERSION + value: {{ $targetVersion | quote }} + restartPolicy: Never +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + annotations: + helm.sh/hook: pre-upgrade,pre-install + helm.sh/hook-weight: "1" + helm.sh/hook-delete-policy: hook-succeeded,before-hook-creation + name: cozystack-migration-hook +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: + - kind: ServiceAccount + name: cozystack-migration-hook + namespace: {{ .Release.Namespace | quote }} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: cozystack-migration-hook + annotations: + helm.sh/hook: pre-upgrade,pre-install + helm.sh/hook-weight: "1" + helm.sh/hook-delete-policy: hook-succeeded,before-hook-creation +{{- end }} +{{- end }} diff --git a/packages/core/platform/templates/namespaces.yaml b/packages/core/platform/templates/namespaces.yaml deleted file mode 100644 index 2afe3445..00000000 --- a/packages/core/platform/templates/namespaces.yaml +++ /dev/null @@ -1,76 +0,0 @@ -{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} -{{- $cozystackBranding := lookup "v1" "ConfigMap" "cozy-system" "cozystack-branding" }} -{{- $cozystackScheduling := lookup "v1" "ConfigMap" "cozy-system" "cozystack-scheduling" }} -{{- $bundleName := index $cozyConfig.data "bundle-name" }} -{{/* Default values for _cluster config to ensure all required keys exist */}} -{{- $clusterDefaults := dict - "root-host" "" - "bundle-name" "" - "clusterissuer" "http01" - "oidc-enabled" "false" - "expose-services" "" - "expose-ingress" "tenant-root" - "expose-external-ips" "" - "cluster-domain" "cozy.local" - "api-server-endpoint" "" -}} -{{- $clusterConfig := mergeOverwrite $clusterDefaults ($cozyConfig.data | default dict) }} -{{- $bundle := tpl (.Files.Get (printf "bundles/%s.yaml" $bundleName)) . | fromYaml }} -{{- $disabledComponents := splitList "," ((index $cozyConfig.data "bundle-disable") | default "") }} -{{- $enabledComponents := splitList "," ((index $cozyConfig.data "bundle-enable") | default "") }} -{{- $namespaces := dict }} - -{{/* collect namespaces from releases */}} -{{- range $x := $bundle.releases }} - {{- if not (hasKey $namespaces $x.namespace) }} - {{- if not (has $x.name $disabledComponents) }} - {{- if or (not $x.optional) (and ($x.optional) (has $x.name $enabledComponents)) }} - {{- $_ := set $namespaces $x.namespace false }} - {{- end }} - {{- end }} - {{- end }} - {{/* if at least one release requires a privileged namespace, then it should be privileged */}} - {{- if or $x.privileged (index $namespaces $x.namespace) }} - {{- $_ := set $namespaces $x.namespace true }} - {{- end }} -{{- end }} - -{{/* Add extra namespaces */}} -{{- $_ := set $namespaces "cozy-system" true }} -{{- $_ := set $namespaces "cozy-public" false }} - -{{- range $namespace, $privileged := $namespaces }} ---- -apiVersion: v1 -kind: Namespace -metadata: - annotations: - "helm.sh/resource-policy": keep - labels: - cozystack.io/system: "true" - {{- if $privileged }} - pod-security.kubernetes.io/enforce: privileged - {{- end }} - name: {{ $namespace }} ---- -apiVersion: v1 -kind: Secret -metadata: - name: cozystack-values - namespace: {{ $namespace }} - labels: - reconcile.fluxcd.io/watch: Enabled -type: Opaque -stringData: - values.yaml: | - _cluster: - {{- $clusterConfig | toYaml | nindent 6 }} - {{- with $cozystackBranding.data }} - branding: - {{- . | toYaml | nindent 8 }} - {{- end }} - {{- with $cozystackScheduling.data }} - scheduling: - {{- . | toYaml | nindent 8 }} - {{- end }} -{{- end }} diff --git a/packages/core/platform/templates/repository.yaml b/packages/core/platform/templates/repository.yaml new file mode 100644 index 00000000..9bd9f5a1 --- /dev/null +++ b/packages/core/platform/templates/repository.yaml @@ -0,0 +1,17 @@ +{{- $sourceRef := .Values.sourceRef }} +{{- $apiVersion := "source.toolkit.fluxcd.io/v1" }} +{{- $kind := $sourceRef.kind }} +{{- $name := $sourceRef.name }} +{{- $namespace := $sourceRef.namespace }} +{{- $sourceRepo := lookup $apiVersion $kind $namespace $name }} +{{- if not $sourceRepo }} +{{- fail (printf "Source repository %s/%s of kind %s not found in namespace %s" $namespace $name $kind $namespace) }} +{{- end }} +--- +apiVersion: {{ $apiVersion }} +kind: {{ $kind }} +metadata: + name: cozystack-packages + namespace: cozy-system +spec: +{{- $sourceRepo.spec | toYaml | nindent 2 }} diff --git a/packages/core/platform/templates/scheduling.yaml b/packages/core/platform/templates/scheduling.yaml new file mode 100644 index 00000000..4486155a --- /dev/null +++ b/packages/core/platform/templates/scheduling.yaml @@ -0,0 +1,11 @@ +{{- if .Values.scheduling.globalAppTopologySpreadConstraints }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cozystack-scheduling + namespace: cozy-system +data: + globalAppTopologySpreadConstraints: | + {{- toYaml .Values.scheduling.globalAppTopologySpreadConstraints | nindent 4 }} +{{- end }} diff --git a/packages/core/platform/templates/sources.yaml b/packages/core/platform/templates/sources.yaml index 8b010f10..9d4eca37 100644 --- a/packages/core/platform/templates/sources.yaml +++ b/packages/core/platform/templates/sources.yaml @@ -1,6 +1,4 @@ -{{/* {{- range $path, $_ := .Files.Glob "sources/*.yaml" }} --- {{ $.Files.Get $path }} {{- end }} -*/}} diff --git a/packages/core/platform/values-isp-full.yaml b/packages/core/platform/values-isp-full.yaml new file mode 100644 index 00000000..c79ff4ba --- /dev/null +++ b/packages/core/platform/values-isp-full.yaml @@ -0,0 +1,12 @@ +migrations: + enabled: true +bundles: + system: + enabled: true + variant: "isp-full" + iaas: + enabled: true + paas: + enabled: true + naas: + enabled: true diff --git a/packages/core/platform/values-isp-hosted.yaml b/packages/core/platform/values-isp-hosted.yaml new file mode 100644 index 00000000..c3af0deb --- /dev/null +++ b/packages/core/platform/values-isp-hosted.yaml @@ -0,0 +1,12 @@ +migrations: + enabled: true +bundles: + system: + enabled: false + variant: "isp-hosted" + iaas: + enabled: false + paas: + enabled: true + naas: + enabled: true diff --git a/packages/core/platform/values.yaml b/packages/core/platform/values.yaml index 26d0859c..70b8ff86 100644 --- a/packages/core/platform/values.yaml +++ b/packages/core/platform/values.yaml @@ -1,2 +1,91 @@ -assets: - image: ghcr.io/cozystack/cozystack/cozystack-assets:v0.40.2@sha256:02730e6a434a8367a970c00a1feaa31f19a856641f6c1d085afed88e9c0b8b2b +sourceRef: + kind: OCIRepository + name: cozystack-platform + namespace: cozy-system + path: / +migrations: + enabled: false + image: ghcr.io/cozystack/cozystack/platform-migrations:latest + targetVersion: 23 +# Bundle deployment configuration +bundles: + system: + enabled: false + variant: "isp-full" # Options: "isp-full", "isp-hosted", "distro-full" + iaas: + enabled: false + paas: + enabled: false + naas: + enabled: false + disabledPackages: [] + enabledPackages: [] +# Network configuration +networking: + clusterDomain: "cozy.local" + podCIDR: "10.244.0.0/16" + podGateway: "10.244.0.1" + serviceCIDR: "10.96.0.0/16" + joinCIDR: "100.64.0.0/16" +# Service publishing and ingress configuration +publishing: + host: "example.org" + ingressName: tenant-root + exposedServices: + - api + - dashboard + - vm-exportproxy + - cdi-uploadproxy + apiServerEndpoint: "" # example: "https://api.example.org" + externalIPs: [] + certificates: + issuerType: http01 # "http01" or "cloudflare" +# Authentication configuration +authentication: + oidc: + enabled: false + dashboard: + extraRedirectUris: [] +# Pod scheduling configuration +scheduling: + topologySpreadConstraints: [] +# UI branding configuration +branding: + dashboard: {} + keycloak: {} +# Container registry mirrors configuration +# +# Example: +# registries: +# mirrors: +# docker.io: +# endpoints: +# - http://10.0.0.1:8082 +# ghcr.io: +# endpoints: +# - http://10.0.0.1:8083 +# gcr.io: +# endpoints: +# - http://10.0.0.1:8084 +# registry.k8s.io: +# endpoints: +# - http://10.0.0.1:8085 +# quay.io: +# endpoints: +# - http://10.0.0.1:8086 +# cr.fluentbit.io: +# endpoints: +# - http://10.0.0.1:8087 +# docker-registry3.mariadb.com: +# endpoints: +# - http://10.0.0.1:8088 +# config: +# "10.0.0.1:8082": +# tls: +# insecureSkipVerify: true +registries: {} +# Resource allocation ratios +resources: + cpuAllocationRatio: 10 + memoryAllocationRatio: 1 + ephemeralStorageAllocationRatio: 40 From 22cd8f1dd14e40d600d5e11e9959c45f68de5e42 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 15 Jan 2026 14:15:09 +0100 Subject: [PATCH 052/889] feat(flux): implement flux sharding for tenant HelmReleases Add dedicated flux-tenants controller with label selector --watch-label-selector=sharding.fluxcd.io/key=tenants to handle tenant workloads separately from platform components. Update all kubernetes app HelmReleases to: - Use chartRef with ExternalArtifact instead of OCIRepository sourceRef - Add sharding.fluxcd.io/key=tenants label - Add cozystack.io/target-cluster-name label Update fluxinstall to parse multiple YAML manifest files and use flux service for storage-adv-addr. Add cozystack-basics package for core tenant/namespace setup. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- hack/cozyreport.sh | 20 + hack/e2e-install-cozystack.bats | 46 +- hack/migrate-to-version-1.0.sh | 152 + ...ystackresourcedefinition_helmreconciler.go | 82 +- internal/fluxinstall/install.go | 49 +- .../fluxinstall/manifests/fluxcd-service.yaml | 18 + .../fluxinstall/manifests/fluxcd-tenants.yaml | 102 + internal/fluxinstall/manifests/fluxcd.yaml | 2 +- internal/operator/package_reconciler.go | 2 + .../apps/bucket/templates/helmrelease.yaml | 15 +- .../helmreleases/cert-manager-crds.yaml | 14 +- .../templates/helmreleases/cert-manager.yaml | 14 +- .../templates/helmreleases/cilium.yaml | 14 +- .../templates/helmreleases/coredns.yaml | 14 +- .../templates/helmreleases/csi.yaml | 14 +- .../templates/helmreleases/fluxcd.yaml | 28 +- .../helmreleases/gateway-api-crds.yaml | 14 +- .../templates/helmreleases/gpu-operator.yaml | 14 +- .../templates/helmreleases/ingress-nginx.yaml | 14 +- .../helmreleases/metrics-server.yaml | 14 +- .../helmreleases/monitoring-agents.yaml | 14 +- .../prometheus-operator-crds.yaml | 14 +- .../templates/helmreleases/velero.yaml | 14 +- .../vertical-pod-autoscaler-crds.yaml | 14 +- .../helmreleases/vertical-pod-autoscaler.yaml | 14 +- .../victoria-metrics-operator.yaml | 14 +- .../helmreleases/volumesnapshot-crd.yaml | 14 +- packages/apps/nats/templates/nats.yaml | 15 +- packages/apps/tenant/templates/etcd.yaml | 14 +- packages/apps/tenant/templates/info.yaml | 14 +- packages/apps/tenant/templates/ingress.yaml | 14 +- .../apps/tenant/templates/monitoring.yaml | 14 +- packages/apps/tenant/templates/seaweedfs.yaml | 14 +- packages/core/flux-aio/Makefile | 24 +- packages/core/flux-aio/manifests | 1 + packages/core/flux-aio/templates/_helpers.tpl | 13 - packages/core/flux-aio/templates/fluxcd.yaml | 11957 ---------------- .../ingress/templates/nginx-ingress.yaml | 15 +- .../monitoring/templates/dashboards.yaml | 4 +- .../extra/seaweedfs/templates/seaweedfs.yaml | 15 +- .../backupstrategy-controller/Chart.yaml | 2 +- ...trategy.backups.cozystack.io_veleroes.yaml | 496 + .../backupstrategy-controller/Dockerfile | 2 +- .../system/bootbox/templates/bootbox.yaml | 14 +- packages/system/cozystack-basics/Chart.yaml | 3 + packages/system/cozystack-basics/Makefile | 4 + .../templates/cozy-public.yaml | 4 + .../templates/cozystack-values-secret.yaml | 19 + .../templates/tenant-root.yaml | 30 + packages/system/cozystack-basics/values.yaml | 1 + ...stack.io_cozystackresourcedefinitions.yaml | 284 +- .../templates/dashboards-deployment.yaml | 41 + .../templates/dashboards-service.yaml | 19 + .../kube-ovn/templates/controller-deploy.yaml | 9 +- .../charts/kube-ovn/templates/ovncni-ds.yaml | 3 +- .../skip-adjust-when-device-inaccessible.diff | 260 - .../templates/vpa-for-vpa.yaml | 13 +- 57 files changed, 1292 insertions(+), 12766 deletions(-) create mode 100755 hack/migrate-to-version-1.0.sh create mode 100644 internal/fluxinstall/manifests/fluxcd-service.yaml create mode 100644 internal/fluxinstall/manifests/fluxcd-tenants.yaml create mode 120000 packages/core/flux-aio/manifests delete mode 100644 packages/core/flux-aio/templates/_helpers.tpl delete mode 100644 packages/core/flux-aio/templates/fluxcd.yaml create mode 100644 packages/system/cozystack-basics/Chart.yaml create mode 100644 packages/system/cozystack-basics/Makefile create mode 100644 packages/system/cozystack-basics/templates/cozy-public.yaml create mode 100644 packages/system/cozystack-basics/templates/cozystack-values-secret.yaml create mode 100644 packages/system/cozystack-basics/templates/tenant-root.yaml create mode 100644 packages/system/cozystack-basics/values.yaml create mode 100644 packages/system/grafana-operator/templates/dashboards-deployment.yaml create mode 100644 packages/system/grafana-operator/templates/dashboards-service.yaml delete mode 100644 packages/system/linstor/images/piraeus-server/patches/skip-adjust-when-device-inaccessible.diff diff --git a/hack/cozyreport.sh b/hack/cozyreport.sh index 3174ed50..28fddfe9 100755 --- a/hack/cozyreport.sh +++ b/hack/cozyreport.sh @@ -56,6 +56,26 @@ kubectl get hr -A --no-headers | awk '$4 != "True"' | \ kubectl describe hr -n $NAMESPACE $NAME > $DIR/describe.txt 2>&1 done +echo "Collecting packages..." +kubectl get packages -A > $REPORT_DIR/kubernetes/packages.txt 2>&1 +kubectl get packages -A --no-headers | awk '$4 != "True"' | \ + while read NAMESPACE NAME _; do + DIR=$REPORT_DIR/kubernetes/packages/$NAMESPACE/$NAME + mkdir -p $DIR + kubectl get package -n $NAMESPACE $NAME -o yaml > $DIR/package.yaml 2>&1 + kubectl describe package -n $NAMESPACE $NAME > $DIR/describe.txt 2>&1 + done + +echo "Collecting packagesources..." +kubectl get packagesources -A > $REPORT_DIR/kubernetes/packagesources.txt 2>&1 +kubectl get packagesources -A --no-headers | awk '$4 != "True"' | \ + while read NAMESPACE NAME _; do + DIR=$REPORT_DIR/kubernetes/packagesources/$NAMESPACE/$NAME + mkdir -p $DIR + kubectl get packagesource -n $NAMESPACE $NAME -o yaml > $DIR/packagesource.yaml 2>&1 + kubectl describe packagesource -n $NAMESPACE $NAME > $DIR/describe.txt 2>&1 + done + echo "Collecting pods..." kubectl get pod -A -o wide > $REPORT_DIR/kubernetes/pods.txt 2>&1 kubectl get pod -A --no-headers | awk '$4 !~ /Running|Succeeded|Completed/' | diff --git a/hack/e2e-install-cozystack.bats b/hack/e2e-install-cozystack.bats index da132132..124bd8bf 100644 --- a/hack/e2e-install-cozystack.bats +++ b/hack/e2e-install-cozystack.bats @@ -8,28 +8,40 @@ } @test "Install Cozystack" { - # Create namespace & configmap required by installer + # Create namespace kubectl create namespace cozy-system --dry-run=client -o yaml | kubectl apply -f - - kubectl create configmap cozystack -n cozy-system \ - --from-literal=bundle-name=paas-full \ - --from-literal=ipv4-pod-cidr=10.244.0.0/16 \ - --from-literal=ipv4-pod-gateway=10.244.0.1 \ - --from-literal=ipv4-svc-cidr=10.96.0.0/16 \ - --from-literal=ipv4-join-cidr=100.64.0.0/16 \ - --from-literal=root-host=example.org \ - --from-literal=api-server-endpoint=https://192.168.123.10:6443 \ - --dry-run=client -o yaml | kubectl apply -f - - # Apply installer manifests from file + # Apply installer manifests (CRDs + operator) kubectl apply -f _out/assets/cozystack-installer.yaml - # Wait for the installer deployment to become available - kubectl wait deployment/cozystack -n cozy-system --timeout=1m --for=condition=Available + # Wait for the operator deployment to become available + kubectl wait deployment/cozystack-operator -n cozy-system --timeout=1m --for=condition=Available + + # Create platform Package with isp-full variant + kubectl apply -f - </dev/null | wc -l) -gt 10 ]; do sleep 1; done' sleep 5 - kubectl get hr -A -l cozystack.io/system-app=true | awk 'NR>1 {print "kubectl wait --timeout=15m --for=condition=ready -n "$1" hr/"$2" &"} END {print "wait"}' | sh -ex + kubectl get hr -A | awk 'NR>1 {print "kubectl wait --timeout=15m --for=condition=ready -n "$1" hr/"$2" &"} END {print "wait"}' | sh -ex # Fail the test if any HelmRelease is not Ready if kubectl get hr -A | grep -v " True " | grep -v NAME; then @@ -142,7 +154,7 @@ EOF # Expose Cozystack services through ingress - kubectl patch configmap/cozystack -n cozy-system --type merge -p '{"data":{"expose-services":"api,dashboard,cdi-uploadproxy,vm-exportproxy,keycloak"}}' + kubectl patch package cozystack.cozystack-platform --type merge -p '{"spec":{"components":{"platform":{"values":{"publishing":{"exposedServices":["api","dashboard","cdi-uploadproxy","vm-exportproxy","keycloak"]}}}}}}' # NGINX ingress controller timeout 60 sh -ec 'until kubectl get deploy root-ingress-controller -n tenant-root >/dev/null 2>&1; do sleep 1; done' @@ -169,7 +181,7 @@ EOF } @test "Keycloak OIDC stack is healthy" { - kubectl patch configmap/cozystack -n cozy-system --type merge -p '{"data":{"oidc-enabled":"true"}}' + kubectl patch package cozystack.cozystack-platform --type merge -p '{"spec":{"components":{"platform":{"values":{"authentication":{"oidc":{"enabled":true}}}}}}}' timeout 120 sh -ec 'until kubectl get hr -n cozy-keycloak keycloak keycloak-configure keycloak-operator >/dev/null 2>&1; do sleep 1; done' kubectl wait hr/keycloak hr/keycloak-configure hr/keycloak-operator -n cozy-keycloak --timeout=10m --for=condition=ready diff --git a/hack/migrate-to-version-1.0.sh b/hack/migrate-to-version-1.0.sh new file mode 100755 index 00000000..3985d6f9 --- /dev/null +++ b/hack/migrate-to-version-1.0.sh @@ -0,0 +1,152 @@ +#!/bin/bash +# Migration script from Cozystack ConfigMaps to Package-based configuration +# This script converts cozystack, cozystack-branding, and cozystack-scheduling +# ConfigMaps into a Package resource with the new values structure. + +set -e + +NAMESPACE="cozy-system" + +echo "Cozystack Migration to v1.0" +echo "===========================" +echo "" +echo "This script will convert existing ConfigMaps to a Package resource." +echo "" + +# Check if kubectl is available +if ! command -v kubectl &> /dev/null; then + echo "Error: kubectl is not installed or not in PATH" + exit 1 +fi + +# Check if we can access the cluster +if ! kubectl get namespace "$NAMESPACE" &> /dev/null; then + echo "Error: Cannot access namespace $NAMESPACE" + exit 1 +fi + +# Read ConfigMap cozystack +echo "Reading ConfigMap cozystack..." +COZYSTACK_CM=$(kubectl get configmap -n "$NAMESPACE" cozystack -o json 2>/dev/null || echo "{}") + +# Read ConfigMap cozystack-branding +echo "Reading ConfigMap cozystack-branding..." +BRANDING_CM=$(kubectl get configmap -n "$NAMESPACE" cozystack-branding -o json 2>/dev/null || echo "{}") + +# Read ConfigMap cozystack-scheduling +echo "Reading ConfigMap cozystack-scheduling..." +SCHEDULING_CM=$(kubectl get configmap -n "$NAMESPACE" cozystack-scheduling -o json 2>/dev/null || echo "{}") + +# Extract values from cozystack ConfigMap +CLUSTER_DOMAIN=$(echo "$COZYSTACK_CM" | jq -r '.data["cluster-domain"] // "cozy.local"') +ROOT_HOST=$(echo "$COZYSTACK_CM" | jq -r '.data["root-host"] // "example.org"') +API_SERVER_ENDPOINT=$(echo "$COZYSTACK_CM" | jq -r '.data["api-server-endpoint"] // ""') +OIDC_ENABLED=$(echo "$COZYSTACK_CM" | jq -r '.data["oidc-enabled"] // "false"') +TELEMETRY_ENABLED=$(echo "$COZYSTACK_CM" | jq -r '.data["telemetry-enabled"] // "true"') +BUNDLE_NAME=$(echo "$COZYSTACK_CM" | jq -r '.data["bundle-name"] // "paas-full"') + +# Network configuration +POD_CIDR=$(echo "$COZYSTACK_CM" | jq -r '.data["ipv4-pod-cidr"] // "10.244.0.0/16"') +POD_GATEWAY=$(echo "$COZYSTACK_CM" | jq -r '.data["ipv4-pod-gateway"] // "10.244.0.1"') +SVC_CIDR=$(echo "$COZYSTACK_CM" | jq -r '.data["ipv4-svc-cidr"] // "10.96.0.0/16"') +JOIN_CIDR=$(echo "$COZYSTACK_CM" | jq -r '.data["ipv4-join-cidr"] // "100.64.0.0/16"') + +# Determine bundle type +case "$BUNDLE_NAME" in + paas-full|distro-full) + SYSTEM_ENABLED="true" + SYSTEM_TYPE="full" + ;; + paas-hosted|distro-hosted) + SYSTEM_ENABLED="false" + SYSTEM_TYPE="hosted" + ;; + *) + SYSTEM_ENABLED="false" + SYSTEM_TYPE="hosted" + ;; +esac + +# Extract branding if available +DASHBOARD_BRANDING=$(echo "$BRANDING_CM" | jq -r '.data["dashboard"] // "{}"') +KEYCLOAK_BRANDING=$(echo "$BRANDING_CM" | jq -r '.data["keycloak"] // "{}"') + +# Extract scheduling if available +SCHEDULING_CONSTRAINTS=$(echo "$SCHEDULING_CM" | jq -r '.data["topologySpreadConstraints"] // "[]"') + +echo "" +echo "Extracted configuration:" +echo " Cluster Domain: $CLUSTER_DOMAIN" +echo " Root Host: $ROOT_HOST" +echo " API Server Endpoint: $API_SERVER_ENDPOINT" +echo " OIDC Enabled: $OIDC_ENABLED" +echo " Bundle Name: $BUNDLE_NAME" +echo " System Enabled: $SYSTEM_ENABLED" +echo " System Type: $SYSTEM_TYPE" +echo "" + +# Generate Package YAML +PACKAGE_YAML=$(cat < 0 { + if hrCopy.Labels == nil { + hrCopy.Labels = make(map[string]string) + } + for key, value := range crd.Spec.Release.Labels { + if hrCopy.Labels[key] != value { + logger.V(4).Info("Updating HelmRelease label", "name", hr.Name, "namespace", hr.Namespace, "label", key, "value", value) + hrCopy.Labels[key] = value + updated = true + } + } + } + if updated { - logger.V(4).Info("Updating HelmRelease chart", "name", hr.Name, "namespace", hr.Namespace) + logger.V(4).Info("Updating HelmRelease", "name", hr.Name, "namespace", hr.Namespace) if err := r.Update(ctx, hrCopy); err != nil { return fmt.Errorf("failed to update HelmRelease: %w", err) } @@ -198,4 +187,3 @@ func (r *CozystackResourceDefinitionHelmReconciler) updateHelmReleaseChart(ctx c return nil } - diff --git a/internal/fluxinstall/install.go b/internal/fluxinstall/install.go index aa834404..2097ecfb 100644 --- a/internal/fluxinstall/install.go +++ b/internal/fluxinstall/install.go @@ -56,26 +56,31 @@ func Install(ctx context.Context, k8sClient client.Client, writeEmbeddedManifest return fmt.Errorf("failed to extract embedded manifests: %w", err) } - // Find the manifest file (should be fluxcd.yaml from cozypkg) - manifestPath := filepath.Join(manifestsDir, "fluxcd.yaml") - if _, err := os.Stat(manifestPath); err != nil { - // Try to find any YAML file if fluxcd.yaml doesn't exist - entries, err := os.ReadDir(manifestsDir) - if err != nil { - return fmt.Errorf("failed to read manifests directory: %w", err) - } - for _, entry := range entries { - if strings.HasSuffix(entry.Name(), ".yaml") { - manifestPath = filepath.Join(manifestsDir, entry.Name()) - break - } + // Find all YAML manifest files + entries, err := os.ReadDir(manifestsDir) + if err != nil { + return fmt.Errorf("failed to read manifests directory: %w", err) + } + + var manifestFiles []string + for _, entry := range entries { + if strings.HasSuffix(entry.Name(), ".yaml") { + manifestFiles = append(manifestFiles, filepath.Join(manifestsDir, entry.Name())) } } - // Parse and apply manifests - objects, err := parseManifests(manifestPath) - if err != nil { - return fmt.Errorf("failed to parse manifests: %w", err) + if len(manifestFiles) == 0 { + return fmt.Errorf("no YAML manifest files found in directory") + } + + // Parse all manifest files + var objects []*unstructured.Unstructured + for _, manifestPath := range manifestFiles { + objs, err := parseManifests(manifestPath) + if err != nil { + return fmt.Errorf("failed to parse manifests from %s: %w", manifestPath, err) + } + objects = append(objects, objs...) } if len(objects) == 0 { @@ -96,7 +101,7 @@ func Install(ctx context.Context, k8sClient client.Client, writeEmbeddedManifest logger.Info("Installing Flux components", "namespace", namespace) // Apply manifests using server-side apply - logger.Info("Applying Flux manifests", "count", len(objects), "manifest", manifestPath, "namespace", namespace) + logger.Info("Applying Flux manifests", "count", len(objects), "files", len(manifestFiles), "namespace", namespace) if err := applyManifests(ctx, k8sClient, objects); err != nil { return fmt.Errorf("failed to apply manifests: %w", err) } @@ -251,11 +256,17 @@ func injectKubernetesServiceEnv(objects []*unstructured.Unstructured) error { continue } - // Navigate to spec.template.spec.containers + // Navigate to spec.template.spec spec, found, err := unstructured.NestedMap(obj.Object, "spec", "template", "spec") if !found { continue } + + // Skip pods that don't use hostNetwork - they should use normal Kubernetes DNS + hostNetwork, _, _ := unstructured.NestedBool(spec, "hostNetwork") + if !hostNetwork { + continue + } if err != nil { if firstErr == nil { firstErr = fmt.Errorf("failed to get spec for %s/%s: %w", kind, obj.GetName(), err) diff --git a/internal/fluxinstall/manifests/fluxcd-service.yaml b/internal/fluxinstall/manifests/fluxcd-service.yaml new file mode 100644 index 00000000..87feaec4 --- /dev/null +++ b/internal/fluxinstall/manifests/fluxcd-service.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/name: flux + app.kubernetes.io/part-of: flux + name: flux + namespace: cozy-fluxcd +spec: + ports: + - name: http + port: 80 + protocol: TCP + targetPort: http-sc + selector: + app.kubernetes.io/name: flux + type: ClusterIP diff --git a/internal/fluxinstall/manifests/fluxcd-tenants.yaml b/internal/fluxinstall/manifests/fluxcd-tenants.yaml new file mode 100644 index 00000000..077cf34c --- /dev/null +++ b/internal/fluxinstall/manifests/fluxcd-tenants.yaml @@ -0,0 +1,102 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/name: flux-tenants + app.kubernetes.io/part-of: flux + app.kubernetes.io/version: v2.7.3 + sharding.fluxcd.io/role: shard + name: flux-tenants + namespace: cozy-fluxcd +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: flux-tenants + strategy: + type: Recreate + template: + metadata: + annotations: + cluster-autoscaler.kubernetes.io/safe-to-evict: "true" + prometheus.io/scrape: "true" + labels: + app.kubernetes.io/name: flux-tenants + spec: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/os + operator: In + values: + - linux + containers: + - args: + - --watch-all-namespaces + - --log-level=info + - --log-encoding=json + - --enable-leader-election=false + - --metrics-addr=:9795 + - --health-addr=:9796 + - --watch-label-selector=sharding.fluxcd.io/key=tenants + - --concurrent=5 + - --requeue-dependency=30s + - --feature-gates=ExternalArtifact=true + env: + - name: RUNTIME_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: GOMEMLIMIT + valueFrom: + resourceFieldRef: + resource: limits.memory + - name: TUF_ROOT + value: /tmp/.sigstore + image: "ghcr.io/fluxcd/helm-controller:v1.4.3" + imagePullPolicy: IfNotPresent + livenessProbe: + httpGet: + path: /healthz + port: healthz + name: helm-controller + ports: + - containerPort: 9795 + name: http-prom + protocol: TCP + - containerPort: 9796 + name: healthz + protocol: TCP + readinessProbe: + httpGet: + path: /readyz + port: healthz + resources: + limits: + memory: 1Gi + requests: + cpu: 100m + memory: 64Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + volumeMounts: + - mountPath: /tmp + name: tmp + priorityClassName: system-cluster-critical + securityContext: + fsGroup: 1337 + serviceAccountName: flux + terminationGracePeriodSeconds: 60 + volumes: + - emptyDir: {} + name: tmp diff --git a/internal/fluxinstall/manifests/fluxcd.yaml b/internal/fluxinstall/manifests/fluxcd.yaml index 237db089..4689d612 100644 --- a/internal/fluxinstall/manifests/fluxcd.yaml +++ b/internal/fluxinstall/manifests/fluxcd.yaml @@ -11871,7 +11871,7 @@ spec: - --health-addr=:9693 - --storage-addr=:9691 - --storage-path=/data - - --storage-adv-addr=source-watcher.$(RUNTIME_NAMESPACE).svc + - --storage-adv-addr=flux.$(RUNTIME_NAMESPACE).svc - --events-addr=http://localhost:9690 env: - name: SOURCE_CONTROLLER_LOCALHOST diff --git a/internal/operator/package_reconciler.go b/internal/operator/package_reconciler.go index 07237fe5..79d78a87 100644 --- a/internal/operator/package_reconciler.go +++ b/internal/operator/package_reconciler.go @@ -211,11 +211,13 @@ func (r *PackageReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct Namespace: "cozy-system", }, Install: &helmv2.Install{ + Timeout: &metav1.Duration{Duration: 10 * 60 * 1000000000}, // 10m Remediation: &helmv2.InstallRemediation{ Retries: -1, }, }, Upgrade: &helmv2.Upgrade{ + Timeout: &metav1.Duration{Duration: 10 * 60 * 1000000000}, // 10m Remediation: &helmv2.UpgradeRemediation{ Retries: -1, }, diff --git a/packages/apps/bucket/templates/helmrelease.yaml b/packages/apps/bucket/templates/helmrelease.yaml index 5d242f84..704470a8 100644 --- a/packages/apps/bucket/templates/helmrelease.yaml +++ b/packages/apps/bucket/templates/helmrelease.yaml @@ -2,16 +2,13 @@ apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease metadata: name: {{ .Release.Name }}-system + labels: + sharding.fluxcd.io/key: tenants spec: - chart: - spec: - chart: cozy-bucket - reconcileStrategy: Revision - sourceRef: - kind: HelmRepository - name: cozystack-system - namespace: cozy-system - version: '>= 0.0.0-0' + chartRef: + kind: ExternalArtifact + name: cozystack-bucket-application-default-bucket-system + namespace: cozy-system interval: 5m timeout: 10m install: diff --git a/packages/apps/kubernetes/templates/helmreleases/cert-manager-crds.yaml b/packages/apps/kubernetes/templates/helmreleases/cert-manager-crds.yaml index 73954e12..be07a8b9 100644 --- a/packages/apps/kubernetes/templates/helmreleases/cert-manager-crds.yaml +++ b/packages/apps/kubernetes/templates/helmreleases/cert-manager-crds.yaml @@ -6,17 +6,13 @@ metadata: labels: cozystack.io/repository: system cozystack.io/target-cluster-name: {{ .Release.Name }} + sharding.fluxcd.io/key: tenants spec: releaseName: cert-manager-crds - chart: - spec: - chart: cozy-cert-manager-crds - reconcileStrategy: Revision - sourceRef: - kind: HelmRepository - name: cozystack-system - namespace: cozy-system - version: '>= 0.0.0-0' + chartRef: + kind: ExternalArtifact + name: cozystack-kubernetes-application-kubevirt-kubernetes-cert-manager-crds + namespace: cozy-system kubeConfig: secretRef: name: {{ .Release.Name }}-admin-kubeconfig diff --git a/packages/apps/kubernetes/templates/helmreleases/cert-manager.yaml b/packages/apps/kubernetes/templates/helmreleases/cert-manager.yaml index e0caf4cb..991ed70f 100644 --- a/packages/apps/kubernetes/templates/helmreleases/cert-manager.yaml +++ b/packages/apps/kubernetes/templates/helmreleases/cert-manager.yaml @@ -6,17 +6,13 @@ metadata: labels: cozystack.io/repository: system cozystack.io/target-cluster-name: {{ .Release.Name }} + sharding.fluxcd.io/key: tenants spec: releaseName: cert-manager - chart: - spec: - chart: cozy-cert-manager - reconcileStrategy: Revision - sourceRef: - kind: HelmRepository - name: cozystack-system - namespace: cozy-system - version: '>= 0.0.0-0' + chartRef: + kind: ExternalArtifact + name: cozystack-kubernetes-application-kubevirt-kubernetes-cert-manager + namespace: cozy-system kubeConfig: secretRef: name: {{ .Release.Name }}-admin-kubeconfig diff --git a/packages/apps/kubernetes/templates/helmreleases/cilium.yaml b/packages/apps/kubernetes/templates/helmreleases/cilium.yaml index c356dc79..64027e94 100644 --- a/packages/apps/kubernetes/templates/helmreleases/cilium.yaml +++ b/packages/apps/kubernetes/templates/helmreleases/cilium.yaml @@ -20,17 +20,13 @@ metadata: labels: cozystack.io/repository: system cozystack.io/target-cluster-name: {{ .Release.Name }} + sharding.fluxcd.io/key: tenants spec: releaseName: cilium - chart: - spec: - chart: cozy-cilium - reconcileStrategy: Revision - sourceRef: - kind: HelmRepository - name: cozystack-system - namespace: cozy-system - version: '>= 0.0.0-0' + chartRef: + kind: ExternalArtifact + name: cozystack-kubernetes-application-kubevirt-kubernetes-cilium + namespace: cozy-system kubeConfig: secretRef: name: {{ .Release.Name }}-admin-kubeconfig diff --git a/packages/apps/kubernetes/templates/helmreleases/coredns.yaml b/packages/apps/kubernetes/templates/helmreleases/coredns.yaml index 37a09a0b..bdb6c682 100644 --- a/packages/apps/kubernetes/templates/helmreleases/coredns.yaml +++ b/packages/apps/kubernetes/templates/helmreleases/coredns.yaml @@ -11,17 +11,13 @@ metadata: labels: cozystack.io/repository: system cozystack.io/target-cluster-name: {{ .Release.Name }} + sharding.fluxcd.io/key: tenants spec: releaseName: coredns - chart: - spec: - chart: cozy-coredns - reconcileStrategy: Revision - sourceRef: - kind: HelmRepository - name: cozystack-system - namespace: cozy-system - version: '>= 0.0.0-0' + chartRef: + kind: ExternalArtifact + name: cozystack-kubernetes-application-kubevirt-kubernetes-coredns + namespace: cozy-system kubeConfig: secretRef: name: {{ .Release.Name }}-admin-kubeconfig diff --git a/packages/apps/kubernetes/templates/helmreleases/csi.yaml b/packages/apps/kubernetes/templates/helmreleases/csi.yaml index 3ecbf1eb..dd2c69a6 100644 --- a/packages/apps/kubernetes/templates/helmreleases/csi.yaml +++ b/packages/apps/kubernetes/templates/helmreleases/csi.yaml @@ -5,18 +5,14 @@ metadata: labels: cozystack.io/repository: system cozystack.io/target-cluster-name: {{ .Release.Name }} + sharding.fluxcd.io/key: tenants spec: interval: 5m releaseName: csi - chart: - spec: - chart: cozy-kubevirt-csi-node - reconcileStrategy: Revision - sourceRef: - kind: HelmRepository - name: cozystack-system - namespace: cozy-system - version: '>= 0.0.0-0' + chartRef: + kind: ExternalArtifact + name: cozystack-kubernetes-application-kubevirt-kubernetes-kubevirt-csi-node + namespace: cozy-system kubeConfig: secretRef: name: {{ .Release.Name }}-admin-kubeconfig diff --git a/packages/apps/kubernetes/templates/helmreleases/fluxcd.yaml b/packages/apps/kubernetes/templates/helmreleases/fluxcd.yaml index 7518601b..76499dfe 100644 --- a/packages/apps/kubernetes/templates/helmreleases/fluxcd.yaml +++ b/packages/apps/kubernetes/templates/helmreleases/fluxcd.yaml @@ -6,17 +6,13 @@ metadata: labels: cozystack.io/repository: system cozystack.io/target-cluster-name: {{ .Release.Name }} + sharding.fluxcd.io/key: tenants spec: releaseName: fluxcd-operator - chart: - spec: - chart: cozy-fluxcd-operator - reconcileStrategy: Revision - sourceRef: - kind: HelmRepository - name: cozystack-system - namespace: cozy-system - version: '>= 0.0.0-0' + chartRef: + kind: ExternalArtifact + name: cozystack-kubernetes-application-kubevirt-kubernetes-fluxcd-operator + namespace: cozy-system kubeConfig: secretRef: name: {{ .Release.Name }}-admin-kubeconfig @@ -53,18 +49,14 @@ metadata: labels: cozystack.io/repository: system cozystack.io/target-cluster-name: {{ .Release.Name }} + sharding.fluxcd.io/key: tenants spec: interval: 5m releaseName: fluxcd - chart: - spec: - chart: cozy-fluxcd - reconcileStrategy: Revision - sourceRef: - kind: HelmRepository - name: cozystack-system - namespace: cozy-system - version: '>= 0.0.0-0' + chartRef: + kind: ExternalArtifact + name: cozystack-kubernetes-application-kubevirt-kubernetes-fluxcd + namespace: cozy-system kubeConfig: secretRef: name: {{ .Release.Name }}-kubeconfig diff --git a/packages/apps/kubernetes/templates/helmreleases/gateway-api-crds.yaml b/packages/apps/kubernetes/templates/helmreleases/gateway-api-crds.yaml index 48a20c5a..2bcc8d4d 100644 --- a/packages/apps/kubernetes/templates/helmreleases/gateway-api-crds.yaml +++ b/packages/apps/kubernetes/templates/helmreleases/gateway-api-crds.yaml @@ -6,17 +6,13 @@ metadata: labels: cozystack.io/repository: system cozystack.io/target-cluster-name: {{ .Release.Name }} + sharding.fluxcd.io/key: tenants spec: releaseName: gateway-api-crds - chart: - spec: - chart: cozy-gateway-api-crds - reconcileStrategy: Revision - sourceRef: - kind: HelmRepository - name: cozystack-system - namespace: cozy-system - version: '>= 0.0.0-0' + chartRef: + kind: ExternalArtifact + name: cozystack-kubernetes-application-kubevirt-kubernetes-gateway-api-crds + namespace: cozy-system kubeConfig: secretRef: name: {{ .Release.Name }}-admin-kubeconfig diff --git a/packages/apps/kubernetes/templates/helmreleases/gpu-operator.yaml b/packages/apps/kubernetes/templates/helmreleases/gpu-operator.yaml index fbee1724..5ef48912 100644 --- a/packages/apps/kubernetes/templates/helmreleases/gpu-operator.yaml +++ b/packages/apps/kubernetes/templates/helmreleases/gpu-operator.yaml @@ -6,17 +6,13 @@ metadata: labels: cozystack.io/repository: system cozystack.io/target-cluster-name: {{ .Release.Name }} + sharding.fluxcd.io/key: tenants spec: releaseName: gpu-operator - chart: - spec: - chart: cozy-gpu-operator - reconcileStrategy: Revision - sourceRef: - kind: HelmRepository - name: cozystack-system - namespace: cozy-system - version: '>= 0.0.0-0' + chartRef: + kind: ExternalArtifact + name: cozystack-kubernetes-application-kubevirt-kubernetes-gpu-operator + namespace: cozy-system kubeConfig: secretRef: name: {{ .Release.Name }}-admin-kubeconfig diff --git a/packages/apps/kubernetes/templates/helmreleases/ingress-nginx.yaml b/packages/apps/kubernetes/templates/helmreleases/ingress-nginx.yaml index f80dd7ae..6f7b0759 100644 --- a/packages/apps/kubernetes/templates/helmreleases/ingress-nginx.yaml +++ b/packages/apps/kubernetes/templates/helmreleases/ingress-nginx.yaml @@ -25,17 +25,13 @@ metadata: labels: cozystack.io/repository: system cozystack.io/target-cluster-name: {{ .Release.Name }} + sharding.fluxcd.io/key: tenants spec: releaseName: ingress-nginx - chart: - spec: - chart: cozy-ingress-nginx - reconcileStrategy: Revision - sourceRef: - kind: HelmRepository - name: cozystack-system - namespace: cozy-system - version: '>= 0.0.0-0' + chartRef: + kind: ExternalArtifact + name: cozystack-kubernetes-application-kubevirt-kubernetes-ingress-nginx + namespace: cozy-system kubeConfig: secretRef: name: {{ .Release.Name }}-admin-kubeconfig diff --git a/packages/apps/kubernetes/templates/helmreleases/metrics-server.yaml b/packages/apps/kubernetes/templates/helmreleases/metrics-server.yaml index 394e6bb4..3cc81a14 100644 --- a/packages/apps/kubernetes/templates/helmreleases/metrics-server.yaml +++ b/packages/apps/kubernetes/templates/helmreleases/metrics-server.yaml @@ -5,17 +5,13 @@ metadata: labels: cozystack.io/repository: system cozystack.io/target-cluster-name: {{ .Release.Name }} + sharding.fluxcd.io/key: tenants spec: releaseName: metrics-server - chart: - spec: - chart: cozy-metrics-server - reconcileStrategy: Revision - sourceRef: - kind: HelmRepository - name: cozystack-system - namespace: cozy-system - version: '>= 0.0.0-0' + chartRef: + kind: ExternalArtifact + name: cozystack-kubernetes-application-kubevirt-kubernetes-metrics-server + namespace: cozy-system kubeConfig: secretRef: name: {{ .Release.Name }}-admin-kubeconfig diff --git a/packages/apps/kubernetes/templates/helmreleases/monitoring-agents.yaml b/packages/apps/kubernetes/templates/helmreleases/monitoring-agents.yaml index 73c3a368..ff54b6e2 100644 --- a/packages/apps/kubernetes/templates/helmreleases/monitoring-agents.yaml +++ b/packages/apps/kubernetes/templates/helmreleases/monitoring-agents.yaml @@ -7,17 +7,13 @@ metadata: labels: cozystack.io/repository: system cozystack.io/target-cluster-name: {{ .Release.Name }} + sharding.fluxcd.io/key: tenants spec: releaseName: cozy-monitoring-agents - chart: - spec: - chart: cozy-monitoring-agents - reconcileStrategy: Revision - sourceRef: - kind: HelmRepository - name: cozystack-system - namespace: cozy-system - version: '>= 0.0.0-0' + chartRef: + kind: ExternalArtifact + name: cozystack-kubernetes-application-kubevirt-kubernetes-monitoring-agents + namespace: cozy-system kubeConfig: secretRef: name: {{ .Release.Name }}-admin-kubeconfig diff --git a/packages/apps/kubernetes/templates/helmreleases/prometheus-operator-crds.yaml b/packages/apps/kubernetes/templates/helmreleases/prometheus-operator-crds.yaml index 63972126..600a7994 100644 --- a/packages/apps/kubernetes/templates/helmreleases/prometheus-operator-crds.yaml +++ b/packages/apps/kubernetes/templates/helmreleases/prometheus-operator-crds.yaml @@ -5,17 +5,13 @@ metadata: labels: cozystack.io/repository: system cozystack.io/target-cluster-name: {{ .Release.Name }} + sharding.fluxcd.io/key: tenants spec: releaseName: prometheus-operator-crds - chart: - spec: - chart: cozy-prometheus-operator-crds - reconcileStrategy: Revision - sourceRef: - kind: HelmRepository - name: cozystack-system - namespace: cozy-system - version: '>= 0.0.0-0' + chartRef: + kind: ExternalArtifact + name: cozystack-kubernetes-application-kubevirt-kubernetes-prometheus-operator-crds + namespace: cozy-system kubeConfig: secretRef: name: {{ .Release.Name }}-admin-kubeconfig diff --git a/packages/apps/kubernetes/templates/helmreleases/velero.yaml b/packages/apps/kubernetes/templates/helmreleases/velero.yaml index 0c918da6..ad236d53 100644 --- a/packages/apps/kubernetes/templates/helmreleases/velero.yaml +++ b/packages/apps/kubernetes/templates/helmreleases/velero.yaml @@ -6,17 +6,13 @@ metadata: labels: cozystack.io/repository: system cozystack.io/target-cluster-name: {{ .Release.Name }} + sharding.fluxcd.io/key: tenants spec: releaseName: velero - chart: - spec: - chart: cozy-velero - reconcileStrategy: Revision - sourceRef: - kind: HelmRepository - name: cozystack-system - namespace: cozy-system - version: '>= 0.0.0-0' + chartRef: + kind: ExternalArtifact + name: cozystack-kubernetes-application-kubevirt-kubernetes-velero + namespace: cozy-system kubeConfig: secretRef: name: {{ .Release.Name }}-admin-kubeconfig diff --git a/packages/apps/kubernetes/templates/helmreleases/vertical-pod-autoscaler-crds.yaml b/packages/apps/kubernetes/templates/helmreleases/vertical-pod-autoscaler-crds.yaml index 71bdc9ee..a3b7a9b4 100644 --- a/packages/apps/kubernetes/templates/helmreleases/vertical-pod-autoscaler-crds.yaml +++ b/packages/apps/kubernetes/templates/helmreleases/vertical-pod-autoscaler-crds.yaml @@ -6,18 +6,14 @@ metadata: labels: cozystack.io/repository: system cozystack.io/target-cluster-name: {{ .Release.Name }} + sharding.fluxcd.io/key: tenants spec: interval: 5m releaseName: vertical-pod-autoscaler-crds - chart: - spec: - chart: cozy-vertical-pod-autoscaler-crds - reconcileStrategy: Revision - sourceRef: - kind: HelmRepository - name: cozystack-system - namespace: cozy-system - version: '>= 0.0.0-0' + chartRef: + kind: ExternalArtifact + name: cozystack-kubernetes-application-kubevirt-kubernetes-vertical-pod-autoscaler-crds + namespace: cozy-system kubeConfig: secretRef: name: {{ .Release.Name }}-admin-kubeconfig diff --git a/packages/apps/kubernetes/templates/helmreleases/vertical-pod-autoscaler.yaml b/packages/apps/kubernetes/templates/helmreleases/vertical-pod-autoscaler.yaml index 8a62288d..178df3e3 100644 --- a/packages/apps/kubernetes/templates/helmreleases/vertical-pod-autoscaler.yaml +++ b/packages/apps/kubernetes/templates/helmreleases/vertical-pod-autoscaler.yaml @@ -32,17 +32,13 @@ metadata: labels: cozystack.io/repository: system cozystack.io/target-cluster-name: {{ .Release.Name }} + sharding.fluxcd.io/key: tenants spec: releaseName: vertical-pod-autoscaler - chart: - spec: - chart: cozy-vertical-pod-autoscaler - reconcileStrategy: Revision - sourceRef: - kind: HelmRepository - name: cozystack-system - namespace: cozy-system - version: '>= 0.0.0-0' + chartRef: + kind: ExternalArtifact + name: cozystack-kubernetes-application-kubevirt-kubernetes-vertical-pod-autoscaler + namespace: cozy-system kubeConfig: secretRef: name: {{ .Release.Name }}-admin-kubeconfig diff --git a/packages/apps/kubernetes/templates/helmreleases/victoria-metrics-operator.yaml b/packages/apps/kubernetes/templates/helmreleases/victoria-metrics-operator.yaml index dbb4d8dc..99744277 100644 --- a/packages/apps/kubernetes/templates/helmreleases/victoria-metrics-operator.yaml +++ b/packages/apps/kubernetes/templates/helmreleases/victoria-metrics-operator.yaml @@ -6,17 +6,13 @@ metadata: labels: cozystack.io/repository: system cozystack.io/target-cluster-name: {{ .Release.Name }} + sharding.fluxcd.io/key: tenants spec: releaseName: cozy-victoria-metrics-operator - chart: - spec: - chart: cozy-victoria-metrics-operator - reconcileStrategy: Revision - sourceRef: - kind: HelmRepository - name: cozystack-system - namespace: cozy-system - version: '>= 0.0.0-0' + chartRef: + kind: ExternalArtifact + name: cozystack-kubernetes-application-kubevirt-kubernetes-victoria-metrics-operator + namespace: cozy-system kubeConfig: secretRef: name: {{ .Release.Name }}-admin-kubeconfig diff --git a/packages/apps/kubernetes/templates/helmreleases/volumesnapshot-crd.yaml b/packages/apps/kubernetes/templates/helmreleases/volumesnapshot-crd.yaml index 83fa32d1..025f01b7 100644 --- a/packages/apps/kubernetes/templates/helmreleases/volumesnapshot-crd.yaml +++ b/packages/apps/kubernetes/templates/helmreleases/volumesnapshot-crd.yaml @@ -5,17 +5,13 @@ metadata: labels: cozystack.io/repository: system cozystack.io/target-cluster-name: {{ .Release.Name }} + sharding.fluxcd.io/key: tenants spec: releaseName: vsnap-crd - chart: - spec: - chart: cozy-vsnap-crd - reconcileStrategy: Revision - sourceRef: - kind: HelmRepository - name: cozystack-system - namespace: cozy-system - version: '>= 0.0.0-0' + chartRef: + kind: ExternalArtifact + name: cozystack-kubernetes-application-kubevirt-kubernetes-volumesnapshot-crd + namespace: cozy-system kubeConfig: secretRef: name: {{ .Release.Name }}-admin-kubeconfig diff --git a/packages/apps/nats/templates/nats.yaml b/packages/apps/nats/templates/nats.yaml index 3e858fd5..4f52ff11 100644 --- a/packages/apps/nats/templates/nats.yaml +++ b/packages/apps/nats/templates/nats.yaml @@ -33,16 +33,13 @@ apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease metadata: name: {{ .Release.Name }}-system + labels: + sharding.fluxcd.io/key: tenants spec: - chart: - spec: - chart: cozy-nats - reconcileStrategy: Revision - sourceRef: - kind: HelmRepository - name: cozystack-system - namespace: cozy-system - version: '>= 0.0.0-0' + chartRef: + kind: ExternalArtifact + name: cozystack-nats-application-default-nats-system + namespace: cozy-system interval: 5m timeout: 10m install: diff --git a/packages/apps/tenant/templates/etcd.yaml b/packages/apps/tenant/templates/etcd.yaml index e67ab597..142f0ca7 100644 --- a/packages/apps/tenant/templates/etcd.yaml +++ b/packages/apps/tenant/templates/etcd.yaml @@ -6,6 +6,7 @@ metadata: namespace: {{ include "tenant.name" . }} labels: cozystack.io/ui: "true" + sharding.fluxcd.io/key: tenants internal.cozystack.io/tenantmodule: "true" app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/managed-by: {{ .Release.Service }} @@ -13,15 +14,10 @@ metadata: apps.cozystack.io/application.group: apps.cozystack.io apps.cozystack.io/application.name: etcd spec: - chart: - spec: - chart: etcd - reconcileStrategy: Revision - sourceRef: - kind: HelmRepository - name: cozystack-extra - namespace: cozy-public - version: '>= 0.0.0-0' + chartRef: + kind: ExternalArtifact + name: cozystack-etcd-application-default-etcd + namespace: cozy-system interval: 5m timeout: 10m install: diff --git a/packages/apps/tenant/templates/info.yaml b/packages/apps/tenant/templates/info.yaml index efa01b87..4c99d551 100644 --- a/packages/apps/tenant/templates/info.yaml +++ b/packages/apps/tenant/templates/info.yaml @@ -5,6 +5,7 @@ metadata: namespace: {{ include "tenant.name" . }} labels: cozystack.io/ui: "true" + sharding.fluxcd.io/key: tenants internal.cozystack.io/tenantmodule: "true" app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/managed-by: {{ .Release.Service }} @@ -12,15 +13,10 @@ metadata: apps.cozystack.io/application.group: apps.cozystack.io apps.cozystack.io/application.name: info spec: - chart: - spec: - chart: info - reconcileStrategy: Revision - sourceRef: - kind: HelmRepository - name: cozystack-extra - namespace: cozy-public - version: '>= 0.0.0-0' + chartRef: + kind: ExternalArtifact + name: cozystack-info-application-default-info + namespace: cozy-system interval: 5m timeout: 10m install: diff --git a/packages/apps/tenant/templates/ingress.yaml b/packages/apps/tenant/templates/ingress.yaml index 6e870043..33f138e5 100644 --- a/packages/apps/tenant/templates/ingress.yaml +++ b/packages/apps/tenant/templates/ingress.yaml @@ -6,6 +6,7 @@ metadata: namespace: {{ include "tenant.name" . }} labels: cozystack.io/ui: "true" + sharding.fluxcd.io/key: tenants internal.cozystack.io/tenantmodule: "true" app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/managed-by: {{ .Release.Service }} @@ -13,15 +14,10 @@ metadata: apps.cozystack.io/application.group: apps.cozystack.io apps.cozystack.io/application.name: ingress spec: - chart: - spec: - chart: ingress - reconcileStrategy: Revision - sourceRef: - kind: HelmRepository - name: cozystack-extra - namespace: cozy-public - version: '>= 0.0.0-0' + chartRef: + kind: ExternalArtifact + name: cozystack-ingress-application-default-ingress + namespace: cozy-system interval: 5m timeout: 10m install: diff --git a/packages/apps/tenant/templates/monitoring.yaml b/packages/apps/tenant/templates/monitoring.yaml index 625670e9..393ef702 100644 --- a/packages/apps/tenant/templates/monitoring.yaml +++ b/packages/apps/tenant/templates/monitoring.yaml @@ -6,6 +6,7 @@ metadata: namespace: {{ include "tenant.name" . }} labels: cozystack.io/ui: "true" + sharding.fluxcd.io/key: tenants internal.cozystack.io/tenantmodule: "true" app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/managed-by: {{ .Release.Service }} @@ -13,15 +14,10 @@ metadata: apps.cozystack.io/application.group: apps.cozystack.io apps.cozystack.io/application.name: monitoring spec: - chart: - spec: - chart: monitoring - reconcileStrategy: Revision - sourceRef: - kind: HelmRepository - name: cozystack-extra - namespace: cozy-public - version: '>= 0.0.0-0' + chartRef: + kind: ExternalArtifact + name: cozystack-monitoring-application-default-monitoring + namespace: cozy-system interval: 5m timeout: 10m install: diff --git a/packages/apps/tenant/templates/seaweedfs.yaml b/packages/apps/tenant/templates/seaweedfs.yaml index ff0db1e4..075856fd 100644 --- a/packages/apps/tenant/templates/seaweedfs.yaml +++ b/packages/apps/tenant/templates/seaweedfs.yaml @@ -6,6 +6,7 @@ metadata: namespace: {{ include "tenant.name" . }} labels: cozystack.io/ui: "true" + sharding.fluxcd.io/key: tenants internal.cozystack.io/tenantmodule: "true" app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/managed-by: {{ .Release.Service }} @@ -13,15 +14,10 @@ metadata: apps.cozystack.io/application.group: apps.cozystack.io apps.cozystack.io/application.name: seaweedfs spec: - chart: - spec: - chart: seaweedfs - reconcileStrategy: Revision - sourceRef: - kind: HelmRepository - name: cozystack-extra - namespace: cozy-public - version: '>= 0.0.0-0' + chartRef: + kind: ExternalArtifact + name: cozystack-seaweedfs-application-default-seaweedfs + namespace: cozy-system interval: 5m timeout: 10m install: diff --git a/packages/core/flux-aio/Makefile b/packages/core/flux-aio/Makefile index 2b3c1408..0121d6d4 100644 --- a/packages/core/flux-aio/Makefile +++ b/packages/core/flux-aio/Makefile @@ -12,23 +12,13 @@ apply: diff: cozyhr show -n $(NAMESPACE) $(NAME) --plain | kubectl diff -f- -update: update-old update-new +MANIFESTS_DIR=../../../internal/fluxinstall/manifests -# TODO: remove old manifest after migration to cozystack-operator -update-old: - timoni bundle build -f flux-aio.cue > templates/fluxcd.yaml - yq eval '(select(.kind == "Namespace") | .metadata.labels."pod-security.kubernetes.io/enforce") = "privileged"' -i templates/fluxcd.yaml - sed -i templates/fluxcd.yaml \ +update: + timoni bundle build -f flux-aio.cue > $(MANIFESTS_DIR)/fluxcd.yaml + yq eval '(select(.kind == "Namespace") | .metadata.labels."pod-security.kubernetes.io/enforce") = "privileged"' -i $(MANIFESTS_DIR)/fluxcd.yaml + sed -i $(MANIFESTS_DIR)/fluxcd.yaml \ -e '/timoni/d' \ -e 's|\.cluster\.local\.,||g' -e 's|\.cluster\.local\,||g' -e 's|\.cluster\.local\.||g' \ - -e '/value: .svc/a \ {{- include "cozy.kubernetes_envs" . | nindent 12 }}' \ - -e '/hostNetwork: true/i \ dnsPolicy: ClusterFirstWithHostNet' - -update-new: - timoni bundle build -f flux-aio.cue > ../../../internal/fluxinstall/manifests/fluxcd.yaml - yq eval '(select(.kind == "Namespace") | .metadata.labels."pod-security.kubernetes.io/enforce") = "privileged"' -i ../../../internal/fluxinstall/manifests/fluxcd.yaml - sed -i ../../../internal/fluxinstall/manifests/fluxcd.yaml \ - -e '/timoni/d' \ - -e 's|\.cluster\.local\.,||g' -e 's|\.cluster\.local\,||g' -e 's|\.cluster\.local\.||g' - # TODO: solve dns issue with hostNetwork for installing helmreleases in tenant k8s clusters - #-e '/hostNetwork: true/i \ dnsPolicy: ClusterFirstWithHostNet' + -e 's|--storage-adv-addr=source-watcher.$$(RUNTIME_NAMESPACE).svc|--storage-adv-addr=flux.$$(RUNTIME_NAMESPACE).svc|' + yq eval '.spec.template.spec.containers[0].image = "'"$$(yq eval 'select(.kind == "Deployment" and .metadata.name == "flux") | .spec.template.spec.containers[] | select(.name == "helm-controller").image' $(MANIFESTS_DIR)/fluxcd.yaml)"'"' -i $(MANIFESTS_DIR)/fluxcd-tenants.yaml diff --git a/packages/core/flux-aio/manifests b/packages/core/flux-aio/manifests new file mode 120000 index 00000000..f9bb5aad --- /dev/null +++ b/packages/core/flux-aio/manifests @@ -0,0 +1 @@ +../../../internal/fluxinstall/manifests \ No newline at end of file diff --git a/packages/core/flux-aio/templates/_helpers.tpl b/packages/core/flux-aio/templates/_helpers.tpl deleted file mode 100644 index e22979ba..00000000 --- a/packages/core/flux-aio/templates/_helpers.tpl +++ /dev/null @@ -1,13 +0,0 @@ -{{- define "cozy.kubernetes_envs" }} -{{- $cozyDeployment := lookup "apps/v1" "Deployment" "cozy-system" "cozystack" }} -{{- $cozyContainers := dig "spec" "template" "spec" "containers" dict $cozyDeployment }} -{{- range $cozyContainers }} -{{- if eq .name "cozystack" }} -{{- range .env }} -{{- if has .name (list "KUBERNETES_SERVICE_HOST" "KUBERNETES_SERVICE_PORT") }} -- {{ toJson . }} -{{- end }} -{{- end }} -{{- end }} -{{- end }} -{{- end }} diff --git a/packages/core/flux-aio/templates/fluxcd.yaml b/packages/core/flux-aio/templates/fluxcd.yaml deleted file mode 100644 index 71a354c6..00000000 --- a/packages/core/flux-aio/templates/fluxcd.yaml +++ /dev/null @@ -1,11957 +0,0 @@ ---- -# Instance: flux ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - app.kubernetes.io/role: cluster-admin - controller-gen.kubebuilder.io/version: v0.19.0 - labels: - app.kubernetes.io/name: flux - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v2.7.3 - name: alerts.notification.toolkit.fluxcd.io -spec: - group: notification.toolkit.fluxcd.io - names: - kind: Alert - listKind: AlertList - plural: alerts - singular: alert - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - deprecated: true - deprecationWarning: v1beta2 Alert is deprecated, upgrade to v1beta3 - name: v1beta2 - schema: - openAPIV3Schema: - description: Alert is the Schema for the alerts API - 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: AlertSpec defines an alerting rule for events involving a list of objects. - properties: - eventMetadata: - additionalProperties: - type: string - description: |- - EventMetadata is an optional field for adding metadata to events dispatched by the - controller. This can be used for enhancing the context of the event. If a field - would override one already present on the original event as generated by the emitter, - then the override doesn't happen, i.e. the original value is preserved, and an info - log is printed. - type: object - eventSeverity: - default: info - description: |- - EventSeverity specifies how to filter events based on severity. - If set to 'info' no events will be filtered. - enum: - - info - - error - type: string - eventSources: - description: |- - EventSources specifies how to filter events based - on the involved object kind, name and namespace. - items: - description: |- - CrossNamespaceObjectReference contains enough information to let you locate the - typed referenced object at cluster level - properties: - apiVersion: - description: API version of the referent - type: string - kind: - description: Kind of the referent - enum: - - Bucket - - GitRepository - - Kustomization - - HelmRelease - - HelmChart - - HelmRepository - - ImageRepository - - ImagePolicy - - ImageUpdateAutomation - - OCIRepository - type: string - 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. - MatchLabels requires the name to be set to `*`. - type: object - name: - description: |- - Name of the referent - If multiple resources are targeted `*` may be set. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: Namespace of the referent - maxLength: 253 - minLength: 1 - type: string - required: - - kind - - name - type: object - type: array - exclusionList: - description: |- - ExclusionList specifies a list of Golang regular expressions - to be used for excluding messages. - items: - type: string - type: array - inclusionList: - description: |- - InclusionList specifies a list of Golang regular expressions - to be used for including messages. - items: - type: string - type: array - providerRef: - description: ProviderRef specifies which Provider this Alert should use. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - summary: - description: Summary holds a short description of the impact and affected cluster. - maxLength: 255 - type: string - suspend: - description: |- - Suspend tells the controller to suspend subsequent - events handling for this Alert. - type: boolean - required: - - eventSources - - providerRef - type: object - status: - default: - observedGeneration: -1 - description: AlertStatus defines the observed state of the Alert. - properties: - conditions: - description: Conditions holds the conditions for the Alert. - 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 - lastHandledReconcileAt: - description: |- - LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value - can be detected. - type: string - observedGeneration: - description: ObservedGeneration is the last observed generation. - format: int64 - type: integer - type: object - type: object - served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta3 - schema: - openAPIV3Schema: - description: Alert is the Schema for the alerts API - 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: AlertSpec defines an alerting rule for events involving a list of objects. - properties: - eventMetadata: - additionalProperties: - type: string - description: |- - EventMetadata is an optional field for adding metadata to events dispatched by the - controller. This can be used for enhancing the context of the event. If a field - would override one already present on the original event as generated by the emitter, - then the override doesn't happen, i.e. the original value is preserved, and an info - log is printed. - type: object - eventSeverity: - default: info - description: |- - EventSeverity specifies how to filter events based on severity. - If set to 'info' no events will be filtered. - enum: - - info - - error - type: string - eventSources: - description: |- - EventSources specifies how to filter events based - on the involved object kind, name and namespace. - items: - description: |- - CrossNamespaceObjectReference contains enough information to let you locate the - typed referenced object at cluster level - properties: - apiVersion: - description: API version of the referent - type: string - kind: - description: Kind of the referent - enum: - - Bucket - - GitRepository - - Kustomization - - HelmRelease - - HelmChart - - HelmRepository - - ImageRepository - - ImagePolicy - - ImageUpdateAutomation - - OCIRepository - type: string - 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. - MatchLabels requires the name to be set to `*`. - type: object - name: - description: |- - Name of the referent - If multiple resources are targeted `*` may be set. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: Namespace of the referent - maxLength: 253 - minLength: 1 - type: string - required: - - kind - - name - type: object - type: array - exclusionList: - description: |- - ExclusionList specifies a list of Golang regular expressions - to be used for excluding messages. - items: - type: string - type: array - inclusionList: - description: |- - InclusionList specifies a list of Golang regular expressions - to be used for including messages. - items: - type: string - type: array - providerRef: - description: ProviderRef specifies which Provider this Alert should use. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - summary: - description: |- - Summary holds a short description of the impact and affected cluster. - Deprecated: Use EventMetadata instead. - maxLength: 255 - type: string - suspend: - description: |- - Suspend tells the controller to suspend subsequent - events handling for this Alert. - type: boolean - required: - - eventSources - - providerRef - type: object - type: object - served: true - storage: true - subresources: {} ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - app.kubernetes.io/role: cluster-admin - controller-gen.kubebuilder.io/version: v0.19.0 - labels: - app.kubernetes.io/name: flux - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v2.7.3 - name: artifactgenerators.source.extensions.fluxcd.io -spec: - group: source.extensions.fluxcd.io - names: - kind: ArtifactGenerator - listKind: ArtifactGeneratorList - plural: artifactgenerators - shortNames: - - ag - singular: artifactgenerator - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - name: v1beta1 - schema: - openAPIV3Schema: - description: ArtifactGenerator is the Schema for the artifactgenerators API. - 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: ArtifactGeneratorSpec defines the desired state of ArtifactGenerator. - properties: - artifacts: - description: OutputArtifacts is a list of output artifacts to be generated. - items: - description: |- - OutputArtifact defines the desired state of an ExternalArtifact - generated by the ArtifactGenerator. - properties: - copy: - description: |- - Copy defines a list of copy operations to perform from the sources to the generated artifact. - The copy operations are performed in the order they are listed with existing files - being overwritten by later copy operations. - items: - properties: - exclude: - description: |- - Exclude specifies a list of glob patterns to exclude - files and dirs matched by the 'From' field. - items: - type: string - maxItems: 100 - type: array - from: - description: |- - From specifies the source (by alias) and the glob pattern to match files. - The format is "@/". - maxLength: 1024 - pattern: ^@([a-z0-9]([a-z0-9_-]*[a-z0-9])?)/(.*)$ - type: string - strategy: - description: |- - Strategy specifies the copy strategy to use. - 'Overwrite' will overwrite existing files in the destination. - 'Merge' is for merging YAML files using Helm values merge strategy. - If not specified, defaults to 'Overwrite'. - enum: - - Overwrite - - Merge - type: string - to: - description: |- - To specifies the destination path within the artifact. - The format is "@artifact/path", the alias "artifact" - refers to the root path of the generated artifact. - maxLength: 1024 - pattern: ^@(artifact)/(.*)$ - type: string - required: - - from - - to - type: object - minItems: 1 - type: array - name: - description: Name is the name of the generated artifact. - maxLength: 253 - pattern: ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - type: string - originRevision: - description: |- - OriginRevision is used to set the 'org.opencontainers.image.revision' - annotation on the generated artifact metadata. - If specified, it must point to an existing source alias in the format "@". - If the referenced source has an origin revision (e.g. a Git commit SHA), - it will be used to set the annotation on the generated artifact. - If the referenced source does not have an origin revision, the field is ignored. - maxLength: 64 - pattern: ^@([a-z0-9]([a-z0-9_-]*[a-z0-9])?)$ - type: string - revision: - description: |- - Revision is the revision of the generated artifact. - If specified, it must point to an existing source alias in the format "@". - If not specified, the revision is automatically set to the digest of the artifact content. - maxLength: 64 - pattern: ^@([a-z0-9]([a-z0-9_-]*[a-z0-9])?)$ - type: string - required: - - copy - - name - type: object - maxItems: 1000 - minItems: 1 - type: array - sources: - description: |- - Sources is a list of references to the Flux source-controller - resources that will be used to generate the artifact. - items: - description: SourceReference contains the reference to a Flux source-controller resource. - properties: - alias: - description: |- - Alias of the source within the ArtifactGenerator context. - The alias must be unique per ArtifactGenerator, and must consist - of lower case alphanumeric characters, underscores, and hyphens. - It must start and end with an alphanumeric character. - maxLength: 63 - pattern: ^[a-z0-9]([a-z0-9_-]*[a-z0-9])?$ - type: string - kind: - description: Kind of the source. - enum: - - Bucket - - GitRepository - - OCIRepository - type: string - name: - description: Name of the source. - maxLength: 253 - pattern: ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$ - type: string - namespace: - description: |- - Namespace of the source. - If not provided, defaults to the same namespace as the ArtifactGenerator. - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - required: - - alias - - kind - - name - type: object - maxItems: 1000 - minItems: 1 - type: array - required: - - artifacts - - sources - type: object - status: - description: ArtifactGeneratorStatus defines the observed state of ArtifactGenerator. - properties: - conditions: - description: Conditions holds the conditions for the ArtifactGenerator. - 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 - inventory: - description: Inventory contains the list of generated ExternalArtifact references. - items: - description: |- - ExternalArtifactReference contains the reference to a - generated ExternalArtifact along with its digest. - properties: - digest: - description: Digest of the referent artifact. - type: string - filename: - description: Filename is the name of the artifact file. - type: string - name: - description: Name of the referent artifact. - type: string - namespace: - description: Namespace of the referent artifact. - type: string - required: - - digest - - filename - - name - - namespace - type: object - type: array - lastHandledReconcileAt: - description: |- - LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value - can be detected. - type: string - observedSourcesDigest: - description: |- - ObservedSourcesDigest is a hash representing the current state of - all the sources referenced by the ArtifactGenerator. - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - app.kubernetes.io/role: cluster-admin - controller-gen.kubebuilder.io/version: v0.19.0 - labels: - app.kubernetes.io/name: flux - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v2.7.3 - name: buckets.source.toolkit.fluxcd.io -spec: - group: source.toolkit.fluxcd.io - names: - kind: Bucket - listKind: BucketList - plural: buckets - singular: bucket - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.endpoint - name: Endpoint - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - name: v1 - schema: - openAPIV3Schema: - description: Bucket is the Schema for the buckets API. - 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: |- - BucketSpec specifies the required configuration to produce an Artifact for - an object storage bucket. - properties: - bucketName: - description: BucketName is the name of the object storage bucket. - type: string - certSecretRef: - description: |- - CertSecretRef can be given the name of a Secret containing - either or both of - - - a PEM-encoded client certificate (`tls.crt`) and private - key (`tls.key`); - - a PEM-encoded CA certificate (`ca.crt`) - - and whichever are supplied, will be used for connecting to the - bucket. The client cert and key are useful if you are - authenticating with a certificate; the CA cert is useful if - you are using a self-signed server certificate. The Secret must - be of type `Opaque` or `kubernetes.io/tls`. - - This field is only supported for the `generic` provider. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - endpoint: - description: Endpoint is the object storage address the BucketName is located at. - type: string - ignore: - description: |- - Ignore overrides the set of excluded patterns in the .sourceignore format - (which is the same as .gitignore). If not provided, a default will be used, - consult the documentation for your version to find out what those are. - type: string - insecure: - description: Insecure allows connecting to a non-TLS HTTP Endpoint. - type: boolean - interval: - description: |- - Interval at which the Bucket Endpoint is checked for updates. - This interval is approximate and may be subject to jitter to ensure - efficient use of resources. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - prefix: - description: Prefix to use for server-side filtering of files in the Bucket. - type: string - provider: - default: generic - description: |- - Provider of the object storage bucket. - Defaults to 'generic', which expects an S3 (API) compatible object - storage. - enum: - - generic - - aws - - gcp - - azure - type: string - proxySecretRef: - description: |- - ProxySecretRef specifies the Secret containing the proxy configuration - to use while communicating with the Bucket server. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - region: - description: Region of the Endpoint where the BucketName is located in. - type: string - secretRef: - description: |- - SecretRef specifies the Secret containing authentication credentials - for the Bucket. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - serviceAccountName: - description: |- - ServiceAccountName is the name of the Kubernetes ServiceAccount used to authenticate - the bucket. This field is only supported for the 'gcp' and 'aws' providers. - For more information about workload identity: - https://fluxcd.io/flux/components/source/buckets/#workload-identity - type: string - sts: - description: |- - STS specifies the required configuration to use a Security Token - Service for fetching temporary credentials to authenticate in a - Bucket provider. - - This field is only supported for the `aws` and `generic` providers. - properties: - certSecretRef: - description: |- - CertSecretRef can be given the name of a Secret containing - either or both of - - - a PEM-encoded client certificate (`tls.crt`) and private - key (`tls.key`); - - a PEM-encoded CA certificate (`ca.crt`) - - and whichever are supplied, will be used for connecting to the - STS endpoint. The client cert and key are useful if you are - authenticating with a certificate; the CA cert is useful if - you are using a self-signed server certificate. The Secret must - be of type `Opaque` or `kubernetes.io/tls`. - - This field is only supported for the `ldap` provider. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - endpoint: - description: |- - Endpoint is the HTTP/S endpoint of the Security Token Service from - where temporary credentials will be fetched. - pattern: ^(http|https)://.*$ - type: string - provider: - description: Provider of the Security Token Service. - enum: - - aws - - ldap - type: string - secretRef: - description: |- - SecretRef specifies the Secret containing authentication credentials - for the STS endpoint. This Secret must contain the fields `username` - and `password` and is supported only for the `ldap` provider. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - required: - - endpoint - - provider - type: object - suspend: - description: |- - Suspend tells the controller to suspend the reconciliation of this - Bucket. - type: boolean - timeout: - default: 60s - description: Timeout for fetch operations, defaults to 60s. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m))+$ - type: string - required: - - bucketName - - endpoint - - interval - type: object - x-kubernetes-validations: - - message: STS configuration is only supported for the 'aws' and 'generic' Bucket providers - rule: self.provider == 'aws' || self.provider == 'generic' || !has(self.sts) - - message: '''aws'' is the only supported STS provider for the ''aws'' Bucket provider' - rule: self.provider != 'aws' || !has(self.sts) || self.sts.provider == 'aws' - - message: '''ldap'' is the only supported STS provider for the ''generic'' Bucket provider' - rule: self.provider != 'generic' || !has(self.sts) || self.sts.provider == 'ldap' - - message: spec.sts.secretRef is not required for the 'aws' STS provider - rule: '!has(self.sts) || self.sts.provider != ''aws'' || !has(self.sts.secretRef)' - - message: spec.sts.certSecretRef is not required for the 'aws' STS provider - rule: '!has(self.sts) || self.sts.provider != ''aws'' || !has(self.sts.certSecretRef)' - - message: ServiceAccountName is not supported for the 'generic' Bucket provider - rule: self.provider != 'generic' || !has(self.serviceAccountName) - - message: cannot set both .spec.secretRef and .spec.serviceAccountName - rule: '!has(self.secretRef) || !has(self.serviceAccountName)' - status: - default: - observedGeneration: -1 - description: BucketStatus records the observed state of a Bucket. - properties: - artifact: - description: Artifact represents the last successful Bucket reconciliation. - properties: - digest: - description: Digest is the digest of the file in the form of ':'. - pattern: ^[a-z0-9]+(?:[.+_-][a-z0-9]+)*:[a-zA-Z0-9=_-]+$ - type: string - lastUpdateTime: - description: |- - LastUpdateTime is the timestamp corresponding to the last update of the - Artifact. - format: date-time - type: string - metadata: - additionalProperties: - type: string - description: Metadata holds upstream information such as OCI annotations. - type: object - path: - description: |- - Path is the relative file path of the Artifact. It can be used to locate - the file in the root of the Artifact storage on the local file system of - the controller managing the Source. - type: string - revision: - description: |- - Revision is a human-readable identifier traceable in the origin source - system. It can be a Git commit SHA, Git tag, a Helm chart version, etc. - type: string - size: - description: Size is the number of bytes in the file. - format: int64 - type: integer - url: - description: |- - URL is the HTTP address of the Artifact as exposed by the controller - managing the Source. It can be used to retrieve the Artifact for - consumption, e.g. by another controller applying the Artifact contents. - type: string - required: - - digest - - lastUpdateTime - - path - - revision - - url - type: object - conditions: - description: Conditions holds the conditions for the Bucket. - 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 - lastHandledReconcileAt: - description: |- - LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value - can be detected. - type: string - observedGeneration: - description: ObservedGeneration is the last observed generation of the Bucket object. - format: int64 - type: integer - observedIgnore: - description: |- - ObservedIgnore is the observed exclusion patterns used for constructing - the source artifact. - type: string - url: - description: |- - URL is the dynamic fetch link for the latest Artifact. - It is provided on a "best effort" basis, and using the precise - BucketStatus.Artifact data is recommended. - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .spec.endpoint - name: Endpoint - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - deprecated: true - deprecationWarning: v1beta2 Bucket is deprecated, upgrade to v1 - name: v1beta2 - schema: - openAPIV3Schema: - description: Bucket is the Schema for the buckets API. - 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: |- - BucketSpec specifies the required configuration to produce an Artifact for - an object storage bucket. - properties: - accessFrom: - description: |- - AccessFrom specifies an Access Control List for allowing cross-namespace - references to this object. - NOTE: Not implemented, provisional as of https://github.com/fluxcd/flux2/pull/2092 - properties: - namespaceSelectors: - description: |- - NamespaceSelectors is the list of namespace selectors to which this ACL applies. - Items in this list are evaluated using a logical OR operation. - items: - description: |- - NamespaceSelector selects the namespaces to which this ACL applies. - An empty map of MatchLabels matches all namespaces in a cluster. - properties: - 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 - type: array - required: - - namespaceSelectors - type: object - bucketName: - description: BucketName is the name of the object storage bucket. - type: string - certSecretRef: - description: |- - CertSecretRef can be given the name of a Secret containing - either or both of - - - a PEM-encoded client certificate (`tls.crt`) and private - key (`tls.key`); - - a PEM-encoded CA certificate (`ca.crt`) - - and whichever are supplied, will be used for connecting to the - bucket. The client cert and key are useful if you are - authenticating with a certificate; the CA cert is useful if - you are using a self-signed server certificate. The Secret must - be of type `Opaque` or `kubernetes.io/tls`. - - This field is only supported for the `generic` provider. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - endpoint: - description: Endpoint is the object storage address the BucketName is located at. - type: string - ignore: - description: |- - Ignore overrides the set of excluded patterns in the .sourceignore format - (which is the same as .gitignore). If not provided, a default will be used, - consult the documentation for your version to find out what those are. - type: string - insecure: - description: Insecure allows connecting to a non-TLS HTTP Endpoint. - type: boolean - interval: - description: |- - Interval at which the Bucket Endpoint is checked for updates. - This interval is approximate and may be subject to jitter to ensure - efficient use of resources. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - prefix: - description: Prefix to use for server-side filtering of files in the Bucket. - type: string - provider: - default: generic - description: |- - Provider of the object storage bucket. - Defaults to 'generic', which expects an S3 (API) compatible object - storage. - enum: - - generic - - aws - - gcp - - azure - type: string - proxySecretRef: - description: |- - ProxySecretRef specifies the Secret containing the proxy configuration - to use while communicating with the Bucket server. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - region: - description: Region of the Endpoint where the BucketName is located in. - type: string - secretRef: - description: |- - SecretRef specifies the Secret containing authentication credentials - for the Bucket. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - sts: - description: |- - STS specifies the required configuration to use a Security Token - Service for fetching temporary credentials to authenticate in a - Bucket provider. - - This field is only supported for the `aws` and `generic` providers. - properties: - certSecretRef: - description: |- - CertSecretRef can be given the name of a Secret containing - either or both of - - - a PEM-encoded client certificate (`tls.crt`) and private - key (`tls.key`); - - a PEM-encoded CA certificate (`ca.crt`) - - and whichever are supplied, will be used for connecting to the - STS endpoint. The client cert and key are useful if you are - authenticating with a certificate; the CA cert is useful if - you are using a self-signed server certificate. The Secret must - be of type `Opaque` or `kubernetes.io/tls`. - - This field is only supported for the `ldap` provider. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - endpoint: - description: |- - Endpoint is the HTTP/S endpoint of the Security Token Service from - where temporary credentials will be fetched. - pattern: ^(http|https)://.*$ - type: string - provider: - description: Provider of the Security Token Service. - enum: - - aws - - ldap - type: string - secretRef: - description: |- - SecretRef specifies the Secret containing authentication credentials - for the STS endpoint. This Secret must contain the fields `username` - and `password` and is supported only for the `ldap` provider. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - required: - - endpoint - - provider - type: object - suspend: - description: |- - Suspend tells the controller to suspend the reconciliation of this - Bucket. - type: boolean - timeout: - default: 60s - description: Timeout for fetch operations, defaults to 60s. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m))+$ - type: string - required: - - bucketName - - endpoint - - interval - type: object - x-kubernetes-validations: - - message: STS configuration is only supported for the 'aws' and 'generic' Bucket providers - rule: self.provider == 'aws' || self.provider == 'generic' || !has(self.sts) - - message: '''aws'' is the only supported STS provider for the ''aws'' Bucket provider' - rule: self.provider != 'aws' || !has(self.sts) || self.sts.provider == 'aws' - - message: '''ldap'' is the only supported STS provider for the ''generic'' Bucket provider' - rule: self.provider != 'generic' || !has(self.sts) || self.sts.provider == 'ldap' - - message: spec.sts.secretRef is not required for the 'aws' STS provider - rule: '!has(self.sts) || self.sts.provider != ''aws'' || !has(self.sts.secretRef)' - - message: spec.sts.certSecretRef is not required for the 'aws' STS provider - rule: '!has(self.sts) || self.sts.provider != ''aws'' || !has(self.sts.certSecretRef)' - status: - default: - observedGeneration: -1 - description: BucketStatus records the observed state of a Bucket. - properties: - artifact: - description: Artifact represents the last successful Bucket reconciliation. - properties: - digest: - description: Digest is the digest of the file in the form of ':'. - pattern: ^[a-z0-9]+(?:[.+_-][a-z0-9]+)*:[a-zA-Z0-9=_-]+$ - type: string - lastUpdateTime: - description: |- - LastUpdateTime is the timestamp corresponding to the last update of the - Artifact. - format: date-time - type: string - metadata: - additionalProperties: - type: string - description: Metadata holds upstream information such as OCI annotations. - type: object - path: - description: |- - Path is the relative file path of the Artifact. It can be used to locate - the file in the root of the Artifact storage on the local file system of - the controller managing the Source. - type: string - revision: - description: |- - Revision is a human-readable identifier traceable in the origin source - system. It can be a Git commit SHA, Git tag, a Helm chart version, etc. - type: string - size: - description: Size is the number of bytes in the file. - format: int64 - type: integer - url: - description: |- - URL is the HTTP address of the Artifact as exposed by the controller - managing the Source. It can be used to retrieve the Artifact for - consumption, e.g. by another controller applying the Artifact contents. - type: string - required: - - digest - - lastUpdateTime - - path - - revision - - url - type: object - conditions: - description: Conditions holds the conditions for the Bucket. - 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 - lastHandledReconcileAt: - description: |- - LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value - can be detected. - type: string - observedGeneration: - description: ObservedGeneration is the last observed generation of the Bucket object. - format: int64 - type: integer - observedIgnore: - description: |- - ObservedIgnore is the observed exclusion patterns used for constructing - the source artifact. - type: string - url: - description: |- - URL is the dynamic fetch link for the latest Artifact. - It is provided on a "best effort" basis, and using the precise - BucketStatus.Artifact data is recommended. - type: string - type: object - type: object - served: true - storage: false - subresources: - status: {} ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - app.kubernetes.io/role: cluster-admin - controller-gen.kubebuilder.io/version: v0.19.0 - labels: - app.kubernetes.io/name: flux - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v2.7.3 - name: externalartifacts.source.toolkit.fluxcd.io -spec: - group: source.toolkit.fluxcd.io - names: - kind: ExternalArtifact - listKind: ExternalArtifactList - plural: externalartifacts - singular: externalartifact - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - - jsonPath: .spec.sourceRef.name - name: Source - type: string - name: v1 - schema: - openAPIV3Schema: - description: ExternalArtifact is the Schema for the external artifacts API - 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: ExternalArtifactSpec defines the desired state of ExternalArtifact - properties: - sourceRef: - description: |- - SourceRef points to the Kubernetes custom resource for - which the artifact is generated. - properties: - apiVersion: - description: API version of the referent, if not specified the Kubernetes preferred version will be used. - type: string - kind: - description: Kind of the referent. - type: string - name: - description: Name of the referent. - type: string - namespace: - description: Namespace of the referent, when not specified it acts as LocalObjectReference. - type: string - required: - - kind - - name - type: object - type: object - status: - description: ExternalArtifactStatus defines the observed state of ExternalArtifact - properties: - artifact: - description: Artifact represents the output of an ExternalArtifact reconciliation. - properties: - digest: - description: Digest is the digest of the file in the form of ':'. - pattern: ^[a-z0-9]+(?:[.+_-][a-z0-9]+)*:[a-zA-Z0-9=_-]+$ - type: string - lastUpdateTime: - description: |- - LastUpdateTime is the timestamp corresponding to the last update of the - Artifact. - format: date-time - type: string - metadata: - additionalProperties: - type: string - description: Metadata holds upstream information such as OCI annotations. - type: object - path: - description: |- - Path is the relative file path of the Artifact. It can be used to locate - the file in the root of the Artifact storage on the local file system of - the controller managing the Source. - type: string - revision: - description: |- - Revision is a human-readable identifier traceable in the origin source - system. It can be a Git commit SHA, Git tag, a Helm chart version, etc. - type: string - size: - description: Size is the number of bytes in the file. - format: int64 - type: integer - url: - description: |- - URL is the HTTP address of the Artifact as exposed by the controller - managing the Source. It can be used to retrieve the Artifact for - consumption, e.g. by another controller applying the Artifact contents. - type: string - required: - - digest - - lastUpdateTime - - path - - revision - - url - type: object - conditions: - description: Conditions holds the conditions for the ExternalArtifact. - 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: {} ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - app.kubernetes.io/role: cluster-admin - controller-gen.kubebuilder.io/version: v0.19.0 - labels: - app.kubernetes.io/name: flux - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v2.7.3 - name: gitrepositories.source.toolkit.fluxcd.io -spec: - group: source.toolkit.fluxcd.io - names: - kind: GitRepository - listKind: GitRepositoryList - plural: gitrepositories - shortNames: - - gitrepo - singular: gitrepository - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.url - name: URL - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - name: v1 - schema: - openAPIV3Schema: - description: GitRepository is the Schema for the gitrepositories API. - 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: |- - GitRepositorySpec specifies the required configuration to produce an - Artifact for a Git repository. - properties: - ignore: - description: |- - Ignore overrides the set of excluded patterns in the .sourceignore format - (which is the same as .gitignore). If not provided, a default will be used, - consult the documentation for your version to find out what those are. - type: string - include: - description: |- - Include specifies a list of GitRepository resources which Artifacts - should be included in the Artifact produced for this GitRepository. - items: - description: |- - GitRepositoryInclude specifies a local reference to a GitRepository which - Artifact (sub-)contents must be included, and where they should be placed. - properties: - fromPath: - description: |- - FromPath specifies the path to copy contents from, defaults to the root - of the Artifact. - type: string - repository: - description: |- - GitRepositoryRef specifies the GitRepository which Artifact contents - must be included. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - toPath: - description: |- - ToPath specifies the path to copy contents to, defaults to the name of - the GitRepositoryRef. - type: string - required: - - repository - type: object - type: array - interval: - description: |- - Interval at which the GitRepository URL is checked for updates. - This interval is approximate and may be subject to jitter to ensure - efficient use of resources. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - provider: - description: |- - Provider used for authentication, can be 'azure', 'github', 'generic'. - When not specified, defaults to 'generic'. - enum: - - generic - - azure - - github - type: string - proxySecretRef: - description: |- - ProxySecretRef specifies the Secret containing the proxy configuration - to use while communicating with the Git server. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - recurseSubmodules: - description: |- - RecurseSubmodules enables the initialization of all submodules within - the GitRepository as cloned from the URL, using their default settings. - type: boolean - ref: - description: |- - Reference specifies the Git reference to resolve and monitor for - changes, defaults to the 'master' branch. - properties: - branch: - description: Branch to check out, defaults to 'master' if no other field is defined. - type: string - commit: - description: |- - Commit SHA to check out, takes precedence over all reference fields. - - This can be combined with Branch to shallow clone the branch, in which - the commit is expected to exist. - type: string - name: - description: |- - Name of the reference to check out; takes precedence over Branch, Tag and SemVer. - - It must be a valid Git reference: https://git-scm.com/docs/git-check-ref-format#_description - Examples: "refs/heads/main", "refs/tags/v0.1.0", "refs/pull/420/head", "refs/merge-requests/1/head" - type: string - semver: - description: SemVer tag expression to check out, takes precedence over Tag. - type: string - tag: - description: Tag to check out, takes precedence over Branch. - type: string - type: object - secretRef: - description: |- - SecretRef specifies the Secret containing authentication credentials for - the GitRepository. - For HTTPS repositories the Secret must contain 'username' and 'password' - fields for basic auth or 'bearerToken' field for token auth. - For SSH repositories the Secret must contain 'identity' - and 'known_hosts' fields. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - serviceAccountName: - description: |- - ServiceAccountName is the name of the Kubernetes ServiceAccount used to - authenticate to the GitRepository. This field is only supported for 'azure' provider. - type: string - sparseCheckout: - description: |- - SparseCheckout specifies a list of directories to checkout when cloning - the repository. If specified, only these directories are included in the - Artifact produced for this GitRepository. - items: - type: string - type: array - suspend: - description: |- - Suspend tells the controller to suspend the reconciliation of this - GitRepository. - type: boolean - timeout: - default: 60s - description: Timeout for Git operations like cloning, defaults to 60s. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m))+$ - type: string - url: - description: URL specifies the Git repository URL, it can be an HTTP/S or SSH address. - pattern: ^(http|https|ssh)://.*$ - type: string - verify: - description: |- - Verification specifies the configuration to verify the Git commit - signature(s). - properties: - mode: - default: HEAD - description: |- - Mode specifies which Git object(s) should be verified. - - The variants "head" and "HEAD" both imply the same thing, i.e. verify - the commit that the HEAD of the Git repository points to. The variant - "head" solely exists to ensure backwards compatibility. - enum: - - head - - HEAD - - Tag - - TagAndHEAD - type: string - secretRef: - description: |- - SecretRef specifies the Secret containing the public keys of trusted Git - authors. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - required: - - secretRef - type: object - required: - - interval - - url - type: object - x-kubernetes-validations: - - message: serviceAccountName can only be set when provider is 'azure' - rule: '!has(self.serviceAccountName) || (has(self.provider) && self.provider == ''azure'')' - status: - default: - observedGeneration: -1 - description: GitRepositoryStatus records the observed state of a Git repository. - properties: - artifact: - description: Artifact represents the last successful GitRepository reconciliation. - properties: - digest: - description: Digest is the digest of the file in the form of ':'. - pattern: ^[a-z0-9]+(?:[.+_-][a-z0-9]+)*:[a-zA-Z0-9=_-]+$ - type: string - lastUpdateTime: - description: |- - LastUpdateTime is the timestamp corresponding to the last update of the - Artifact. - format: date-time - type: string - metadata: - additionalProperties: - type: string - description: Metadata holds upstream information such as OCI annotations. - type: object - path: - description: |- - Path is the relative file path of the Artifact. It can be used to locate - the file in the root of the Artifact storage on the local file system of - the controller managing the Source. - type: string - revision: - description: |- - Revision is a human-readable identifier traceable in the origin source - system. It can be a Git commit SHA, Git tag, a Helm chart version, etc. - type: string - size: - description: Size is the number of bytes in the file. - format: int64 - type: integer - url: - description: |- - URL is the HTTP address of the Artifact as exposed by the controller - managing the Source. It can be used to retrieve the Artifact for - consumption, e.g. by another controller applying the Artifact contents. - type: string - required: - - digest - - lastUpdateTime - - path - - revision - - url - type: object - conditions: - description: Conditions holds the conditions for the GitRepository. - 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 - includedArtifacts: - description: |- - IncludedArtifacts contains a list of the last successfully included - Artifacts as instructed by GitRepositorySpec.Include. - items: - description: Artifact represents the output of a Source reconciliation. - properties: - digest: - description: Digest is the digest of the file in the form of ':'. - pattern: ^[a-z0-9]+(?:[.+_-][a-z0-9]+)*:[a-zA-Z0-9=_-]+$ - type: string - lastUpdateTime: - description: |- - LastUpdateTime is the timestamp corresponding to the last update of the - Artifact. - format: date-time - type: string - metadata: - additionalProperties: - type: string - description: Metadata holds upstream information such as OCI annotations. - type: object - path: - description: |- - Path is the relative file path of the Artifact. It can be used to locate - the file in the root of the Artifact storage on the local file system of - the controller managing the Source. - type: string - revision: - description: |- - Revision is a human-readable identifier traceable in the origin source - system. It can be a Git commit SHA, Git tag, a Helm chart version, etc. - type: string - size: - description: Size is the number of bytes in the file. - format: int64 - type: integer - url: - description: |- - URL is the HTTP address of the Artifact as exposed by the controller - managing the Source. It can be used to retrieve the Artifact for - consumption, e.g. by another controller applying the Artifact contents. - type: string - required: - - digest - - lastUpdateTime - - path - - revision - - url - type: object - type: array - lastHandledReconcileAt: - description: |- - LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value - can be detected. - type: string - observedGeneration: - description: |- - ObservedGeneration is the last observed generation of the GitRepository - object. - format: int64 - type: integer - observedIgnore: - description: |- - ObservedIgnore is the observed exclusion patterns used for constructing - the source artifact. - type: string - observedInclude: - description: |- - ObservedInclude is the observed list of GitRepository resources used to - produce the current Artifact. - items: - description: |- - GitRepositoryInclude specifies a local reference to a GitRepository which - Artifact (sub-)contents must be included, and where they should be placed. - properties: - fromPath: - description: |- - FromPath specifies the path to copy contents from, defaults to the root - of the Artifact. - type: string - repository: - description: |- - GitRepositoryRef specifies the GitRepository which Artifact contents - must be included. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - toPath: - description: |- - ToPath specifies the path to copy contents to, defaults to the name of - the GitRepositoryRef. - type: string - required: - - repository - type: object - type: array - observedRecurseSubmodules: - description: |- - ObservedRecurseSubmodules is the observed resource submodules - configuration used to produce the current Artifact. - type: boolean - observedSparseCheckout: - description: |- - ObservedSparseCheckout is the observed list of directories used to - produce the current Artifact. - items: - type: string - type: array - sourceVerificationMode: - description: |- - SourceVerificationMode is the last used verification mode indicating - which Git object(s) have been verified. - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .spec.url - name: URL - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - deprecated: true - deprecationWarning: v1beta2 GitRepository is deprecated, upgrade to v1 - name: v1beta2 - schema: - openAPIV3Schema: - description: GitRepository is the Schema for the gitrepositories API. - 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: |- - GitRepositorySpec specifies the required configuration to produce an - Artifact for a Git repository. - properties: - accessFrom: - description: |- - AccessFrom specifies an Access Control List for allowing cross-namespace - references to this object. - NOTE: Not implemented, provisional as of https://github.com/fluxcd/flux2/pull/2092 - properties: - namespaceSelectors: - description: |- - NamespaceSelectors is the list of namespace selectors to which this ACL applies. - Items in this list are evaluated using a logical OR operation. - items: - description: |- - NamespaceSelector selects the namespaces to which this ACL applies. - An empty map of MatchLabels matches all namespaces in a cluster. - properties: - 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 - type: array - required: - - namespaceSelectors - type: object - gitImplementation: - default: go-git - description: |- - GitImplementation specifies which Git client library implementation to - use. Defaults to 'go-git', valid values are ('go-git', 'libgit2'). - Deprecated: gitImplementation is deprecated now that 'go-git' is the - only supported implementation. - enum: - - go-git - - libgit2 - type: string - ignore: - description: |- - Ignore overrides the set of excluded patterns in the .sourceignore format - (which is the same as .gitignore). If not provided, a default will be used, - consult the documentation for your version to find out what those are. - type: string - include: - description: |- - Include specifies a list of GitRepository resources which Artifacts - should be included in the Artifact produced for this GitRepository. - items: - description: |- - GitRepositoryInclude specifies a local reference to a GitRepository which - Artifact (sub-)contents must be included, and where they should be placed. - properties: - fromPath: - description: |- - FromPath specifies the path to copy contents from, defaults to the root - of the Artifact. - type: string - repository: - description: |- - GitRepositoryRef specifies the GitRepository which Artifact contents - must be included. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - toPath: - description: |- - ToPath specifies the path to copy contents to, defaults to the name of - the GitRepositoryRef. - type: string - required: - - repository - type: object - type: array - interval: - description: Interval at which to check the GitRepository for updates. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - recurseSubmodules: - description: |- - RecurseSubmodules enables the initialization of all submodules within - the GitRepository as cloned from the URL, using their default settings. - type: boolean - ref: - description: |- - Reference specifies the Git reference to resolve and monitor for - changes, defaults to the 'master' branch. - properties: - branch: - description: Branch to check out, defaults to 'master' if no other field is defined. - type: string - commit: - description: |- - Commit SHA to check out, takes precedence over all reference fields. - - This can be combined with Branch to shallow clone the branch, in which - the commit is expected to exist. - type: string - name: - description: |- - Name of the reference to check out; takes precedence over Branch, Tag and SemVer. - - It must be a valid Git reference: https://git-scm.com/docs/git-check-ref-format#_description - Examples: "refs/heads/main", "refs/tags/v0.1.0", "refs/pull/420/head", "refs/merge-requests/1/head" - type: string - semver: - description: SemVer tag expression to check out, takes precedence over Tag. - type: string - tag: - description: Tag to check out, takes precedence over Branch. - type: string - type: object - secretRef: - description: |- - SecretRef specifies the Secret containing authentication credentials for - the GitRepository. - For HTTPS repositories the Secret must contain 'username' and 'password' - fields for basic auth or 'bearerToken' field for token auth. - For SSH repositories the Secret must contain 'identity' - and 'known_hosts' fields. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - suspend: - description: |- - Suspend tells the controller to suspend the reconciliation of this - GitRepository. - type: boolean - timeout: - default: 60s - description: Timeout for Git operations like cloning, defaults to 60s. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m))+$ - type: string - url: - description: URL specifies the Git repository URL, it can be an HTTP/S or SSH address. - pattern: ^(http|https|ssh)://.*$ - type: string - verify: - description: |- - Verification specifies the configuration to verify the Git commit - signature(s). - properties: - mode: - description: Mode specifies what Git object should be verified, currently ('head'). - enum: - - head - type: string - secretRef: - description: |- - SecretRef specifies the Secret containing the public keys of trusted Git - authors. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - required: - - mode - - secretRef - type: object - required: - - interval - - url - type: object - status: - default: - observedGeneration: -1 - description: GitRepositoryStatus records the observed state of a Git repository. - properties: - artifact: - description: Artifact represents the last successful GitRepository reconciliation. - properties: - digest: - description: Digest is the digest of the file in the form of ':'. - pattern: ^[a-z0-9]+(?:[.+_-][a-z0-9]+)*:[a-zA-Z0-9=_-]+$ - type: string - lastUpdateTime: - description: |- - LastUpdateTime is the timestamp corresponding to the last update of the - Artifact. - format: date-time - type: string - metadata: - additionalProperties: - type: string - description: Metadata holds upstream information such as OCI annotations. - type: object - path: - description: |- - Path is the relative file path of the Artifact. It can be used to locate - the file in the root of the Artifact storage on the local file system of - the controller managing the Source. - type: string - revision: - description: |- - Revision is a human-readable identifier traceable in the origin source - system. It can be a Git commit SHA, Git tag, a Helm chart version, etc. - type: string - size: - description: Size is the number of bytes in the file. - format: int64 - type: integer - url: - description: |- - URL is the HTTP address of the Artifact as exposed by the controller - managing the Source. It can be used to retrieve the Artifact for - consumption, e.g. by another controller applying the Artifact contents. - type: string - required: - - digest - - lastUpdateTime - - path - - revision - - url - type: object - conditions: - description: Conditions holds the conditions for the GitRepository. - 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 - contentConfigChecksum: - description: |- - ContentConfigChecksum is a checksum of all the configurations related to - the content of the source artifact: - - .spec.ignore - - .spec.recurseSubmodules - - .spec.included and the checksum of the included artifacts - observed in .status.observedGeneration version of the object. This can - be used to determine if the content of the included repository has - changed. - It has the format of `:`, for example: `sha256:`. - - Deprecated: Replaced with explicit fields for observed artifact content - config in the status. - type: string - includedArtifacts: - description: |- - IncludedArtifacts contains a list of the last successfully included - Artifacts as instructed by GitRepositorySpec.Include. - items: - description: Artifact represents the output of a Source reconciliation. - properties: - digest: - description: Digest is the digest of the file in the form of ':'. - pattern: ^[a-z0-9]+(?:[.+_-][a-z0-9]+)*:[a-zA-Z0-9=_-]+$ - type: string - lastUpdateTime: - description: |- - LastUpdateTime is the timestamp corresponding to the last update of the - Artifact. - format: date-time - type: string - metadata: - additionalProperties: - type: string - description: Metadata holds upstream information such as OCI annotations. - type: object - path: - description: |- - Path is the relative file path of the Artifact. It can be used to locate - the file in the root of the Artifact storage on the local file system of - the controller managing the Source. - type: string - revision: - description: |- - Revision is a human-readable identifier traceable in the origin source - system. It can be a Git commit SHA, Git tag, a Helm chart version, etc. - type: string - size: - description: Size is the number of bytes in the file. - format: int64 - type: integer - url: - description: |- - URL is the HTTP address of the Artifact as exposed by the controller - managing the Source. It can be used to retrieve the Artifact for - consumption, e.g. by another controller applying the Artifact contents. - type: string - required: - - digest - - lastUpdateTime - - path - - revision - - url - type: object - type: array - lastHandledReconcileAt: - description: |- - LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value - can be detected. - type: string - observedGeneration: - description: |- - ObservedGeneration is the last observed generation of the GitRepository - object. - format: int64 - type: integer - observedIgnore: - description: |- - ObservedIgnore is the observed exclusion patterns used for constructing - the source artifact. - type: string - observedInclude: - description: |- - ObservedInclude is the observed list of GitRepository resources used to - to produce the current Artifact. - items: - description: |- - GitRepositoryInclude specifies a local reference to a GitRepository which - Artifact (sub-)contents must be included, and where they should be placed. - properties: - fromPath: - description: |- - FromPath specifies the path to copy contents from, defaults to the root - of the Artifact. - type: string - repository: - description: |- - GitRepositoryRef specifies the GitRepository which Artifact contents - must be included. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - toPath: - description: |- - ToPath specifies the path to copy contents to, defaults to the name of - the GitRepositoryRef. - type: string - required: - - repository - type: object - type: array - observedRecurseSubmodules: - description: |- - ObservedRecurseSubmodules is the observed resource submodules - configuration used to produce the current Artifact. - type: boolean - url: - description: |- - URL is the dynamic fetch link for the latest Artifact. - It is provided on a "best effort" basis, and using the precise - GitRepositoryStatus.Artifact data is recommended. - type: string - type: object - type: object - served: true - storage: false - subresources: - status: {} ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - app.kubernetes.io/role: cluster-admin - controller-gen.kubebuilder.io/version: v0.19.0 - labels: - app.kubernetes.io/name: flux - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v2.7.3 - name: helmcharts.source.toolkit.fluxcd.io -spec: - group: source.toolkit.fluxcd.io - names: - kind: HelmChart - listKind: HelmChartList - plural: helmcharts - shortNames: - - hc - singular: helmchart - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.chart - name: Chart - type: string - - jsonPath: .spec.version - name: Version - type: string - - jsonPath: .spec.sourceRef.kind - name: Source Kind - type: string - - jsonPath: .spec.sourceRef.name - name: Source Name - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - name: v1 - schema: - openAPIV3Schema: - description: HelmChart is the Schema for the helmcharts API. - 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: HelmChartSpec specifies the desired state of a Helm chart. - properties: - chart: - description: |- - Chart is the name or path the Helm chart is available at in the - SourceRef. - type: string - ignoreMissingValuesFiles: - description: |- - IgnoreMissingValuesFiles controls whether to silently ignore missing values - files rather than failing. - type: boolean - interval: - description: |- - Interval at which the HelmChart SourceRef is checked for updates. - This interval is approximate and may be subject to jitter to ensure - efficient use of resources. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - reconcileStrategy: - default: ChartVersion - description: |- - ReconcileStrategy determines what enables the creation of a new artifact. - Valid values are ('ChartVersion', 'Revision'). - See the documentation of the values for an explanation on their behavior. - Defaults to ChartVersion when omitted. - enum: - - ChartVersion - - Revision - type: string - sourceRef: - description: SourceRef is the reference to the Source the chart is available at. - properties: - apiVersion: - description: APIVersion of the referent. - type: string - kind: - description: |- - Kind of the referent, valid values are ('HelmRepository', 'GitRepository', - 'Bucket'). - enum: - - HelmRepository - - GitRepository - - Bucket - type: string - name: - description: Name of the referent. - type: string - required: - - kind - - name - type: object - suspend: - description: |- - Suspend tells the controller to suspend the reconciliation of this - source. - type: boolean - valuesFiles: - description: |- - ValuesFiles is an alternative list of values files to use as the chart - values (values.yaml is not included by default), expected to be a - relative path in the SourceRef. - Values files are merged in the order of this list with the last file - overriding the first. Ignored when omitted. - items: - type: string - type: array - verify: - description: |- - Verify contains the secret name containing the trusted public keys - used to verify the signature and specifies which provider to use to check - whether OCI image is authentic. - This field is only supported when using HelmRepository source with spec.type 'oci'. - Chart dependencies, which are not bundled in the umbrella chart artifact, are not verified. - properties: - matchOIDCIdentity: - description: |- - MatchOIDCIdentity specifies the identity matching criteria to use - while verifying an OCI artifact which was signed using Cosign keyless - signing. The artifact's identity is deemed to be verified if any of the - specified matchers match against the identity. - items: - description: |- - OIDCIdentityMatch specifies options for verifying the certificate identity, - i.e. the issuer and the subject of the certificate. - properties: - issuer: - description: |- - Issuer specifies the regex pattern to match against to verify - the OIDC issuer in the Fulcio certificate. The pattern must be a - valid Go regular expression. - type: string - subject: - description: |- - Subject specifies the regex pattern to match against to verify - the identity subject in the Fulcio certificate. The pattern must - be a valid Go regular expression. - type: string - required: - - issuer - - subject - type: object - type: array - provider: - default: cosign - description: Provider specifies the technology used to sign the OCI Artifact. - enum: - - cosign - - notation - type: string - secretRef: - description: |- - SecretRef specifies the Kubernetes Secret containing the - trusted public keys. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - required: - - provider - type: object - version: - default: '*' - description: |- - Version is the chart version semver expression, ignored for charts from - GitRepository and Bucket sources. Defaults to latest when omitted. - type: string - required: - - chart - - interval - - sourceRef - type: object - status: - default: - observedGeneration: -1 - description: HelmChartStatus records the observed state of the HelmChart. - properties: - artifact: - description: Artifact represents the output of the last successful reconciliation. - properties: - digest: - description: Digest is the digest of the file in the form of ':'. - pattern: ^[a-z0-9]+(?:[.+_-][a-z0-9]+)*:[a-zA-Z0-9=_-]+$ - type: string - lastUpdateTime: - description: |- - LastUpdateTime is the timestamp corresponding to the last update of the - Artifact. - format: date-time - type: string - metadata: - additionalProperties: - type: string - description: Metadata holds upstream information such as OCI annotations. - type: object - path: - description: |- - Path is the relative file path of the Artifact. It can be used to locate - the file in the root of the Artifact storage on the local file system of - the controller managing the Source. - type: string - revision: - description: |- - Revision is a human-readable identifier traceable in the origin source - system. It can be a Git commit SHA, Git tag, a Helm chart version, etc. - type: string - size: - description: Size is the number of bytes in the file. - format: int64 - type: integer - url: - description: |- - URL is the HTTP address of the Artifact as exposed by the controller - managing the Source. It can be used to retrieve the Artifact for - consumption, e.g. by another controller applying the Artifact contents. - type: string - required: - - digest - - lastUpdateTime - - path - - revision - - url - type: object - conditions: - description: Conditions holds the conditions for the HelmChart. - 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 - lastHandledReconcileAt: - description: |- - LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value - can be detected. - type: string - observedChartName: - description: |- - ObservedChartName is the last observed chart name as specified by the - resolved chart reference. - type: string - observedGeneration: - description: |- - ObservedGeneration is the last observed generation of the HelmChart - object. - format: int64 - type: integer - observedSourceArtifactRevision: - description: |- - ObservedSourceArtifactRevision is the last observed Artifact.Revision - of the HelmChartSpec.SourceRef. - type: string - observedValuesFiles: - description: |- - ObservedValuesFiles are the observed value files of the last successful - reconciliation. - It matches the chart in the last successfully reconciled artifact. - items: - type: string - type: array - url: - description: |- - URL is the dynamic fetch link for the latest Artifact. - It is provided on a "best effort" basis, and using the precise - BucketStatus.Artifact data is recommended. - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .spec.chart - name: Chart - type: string - - jsonPath: .spec.version - name: Version - type: string - - jsonPath: .spec.sourceRef.kind - name: Source Kind - type: string - - jsonPath: .spec.sourceRef.name - name: Source Name - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - deprecated: true - deprecationWarning: v1beta2 HelmChart is deprecated, upgrade to v1 - name: v1beta2 - schema: - openAPIV3Schema: - description: HelmChart is the Schema for the helmcharts API. - 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: HelmChartSpec specifies the desired state of a Helm chart. - properties: - accessFrom: - description: |- - AccessFrom specifies an Access Control List for allowing cross-namespace - references to this object. - NOTE: Not implemented, provisional as of https://github.com/fluxcd/flux2/pull/2092 - properties: - namespaceSelectors: - description: |- - NamespaceSelectors is the list of namespace selectors to which this ACL applies. - Items in this list are evaluated using a logical OR operation. - items: - description: |- - NamespaceSelector selects the namespaces to which this ACL applies. - An empty map of MatchLabels matches all namespaces in a cluster. - properties: - 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 - type: array - required: - - namespaceSelectors - type: object - chart: - description: |- - Chart is the name or path the Helm chart is available at in the - SourceRef. - type: string - ignoreMissingValuesFiles: - description: |- - IgnoreMissingValuesFiles controls whether to silently ignore missing values - files rather than failing. - type: boolean - interval: - description: |- - Interval at which the HelmChart SourceRef is checked for updates. - This interval is approximate and may be subject to jitter to ensure - efficient use of resources. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - reconcileStrategy: - default: ChartVersion - description: |- - ReconcileStrategy determines what enables the creation of a new artifact. - Valid values are ('ChartVersion', 'Revision'). - See the documentation of the values for an explanation on their behavior. - Defaults to ChartVersion when omitted. - enum: - - ChartVersion - - Revision - type: string - sourceRef: - description: SourceRef is the reference to the Source the chart is available at. - properties: - apiVersion: - description: APIVersion of the referent. - type: string - kind: - description: |- - Kind of the referent, valid values are ('HelmRepository', 'GitRepository', - 'Bucket'). - enum: - - HelmRepository - - GitRepository - - Bucket - type: string - name: - description: Name of the referent. - type: string - required: - - kind - - name - type: object - suspend: - description: |- - Suspend tells the controller to suspend the reconciliation of this - source. - type: boolean - valuesFile: - description: |- - ValuesFile is an alternative values file to use as the default chart - values, expected to be a relative path in the SourceRef. Deprecated in - favor of ValuesFiles, for backwards compatibility the file specified here - is merged before the ValuesFiles items. Ignored when omitted. - type: string - valuesFiles: - description: |- - ValuesFiles is an alternative list of values files to use as the chart - values (values.yaml is not included by default), expected to be a - relative path in the SourceRef. - Values files are merged in the order of this list with the last file - overriding the first. Ignored when omitted. - items: - type: string - type: array - verify: - description: |- - Verify contains the secret name containing the trusted public keys - used to verify the signature and specifies which provider to use to check - whether OCI image is authentic. - This field is only supported when using HelmRepository source with spec.type 'oci'. - Chart dependencies, which are not bundled in the umbrella chart artifact, are not verified. - properties: - matchOIDCIdentity: - description: |- - MatchOIDCIdentity specifies the identity matching criteria to use - while verifying an OCI artifact which was signed using Cosign keyless - signing. The artifact's identity is deemed to be verified if any of the - specified matchers match against the identity. - items: - description: |- - OIDCIdentityMatch specifies options for verifying the certificate identity, - i.e. the issuer and the subject of the certificate. - properties: - issuer: - description: |- - Issuer specifies the regex pattern to match against to verify - the OIDC issuer in the Fulcio certificate. The pattern must be a - valid Go regular expression. - type: string - subject: - description: |- - Subject specifies the regex pattern to match against to verify - the identity subject in the Fulcio certificate. The pattern must - be a valid Go regular expression. - type: string - required: - - issuer - - subject - type: object - type: array - provider: - default: cosign - description: Provider specifies the technology used to sign the OCI Artifact. - enum: - - cosign - - notation - type: string - secretRef: - description: |- - SecretRef specifies the Kubernetes Secret containing the - trusted public keys. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - required: - - provider - type: object - version: - default: '*' - description: |- - Version is the chart version semver expression, ignored for charts from - GitRepository and Bucket sources. Defaults to latest when omitted. - type: string - required: - - chart - - interval - - sourceRef - type: object - status: - default: - observedGeneration: -1 - description: HelmChartStatus records the observed state of the HelmChart. - properties: - artifact: - description: Artifact represents the output of the last successful reconciliation. - properties: - digest: - description: Digest is the digest of the file in the form of ':'. - pattern: ^[a-z0-9]+(?:[.+_-][a-z0-9]+)*:[a-zA-Z0-9=_-]+$ - type: string - lastUpdateTime: - description: |- - LastUpdateTime is the timestamp corresponding to the last update of the - Artifact. - format: date-time - type: string - metadata: - additionalProperties: - type: string - description: Metadata holds upstream information such as OCI annotations. - type: object - path: - description: |- - Path is the relative file path of the Artifact. It can be used to locate - the file in the root of the Artifact storage on the local file system of - the controller managing the Source. - type: string - revision: - description: |- - Revision is a human-readable identifier traceable in the origin source - system. It can be a Git commit SHA, Git tag, a Helm chart version, etc. - type: string - size: - description: Size is the number of bytes in the file. - format: int64 - type: integer - url: - description: |- - URL is the HTTP address of the Artifact as exposed by the controller - managing the Source. It can be used to retrieve the Artifact for - consumption, e.g. by another controller applying the Artifact contents. - type: string - required: - - digest - - lastUpdateTime - - path - - revision - - url - type: object - conditions: - description: Conditions holds the conditions for the HelmChart. - 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 - lastHandledReconcileAt: - description: |- - LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value - can be detected. - type: string - observedChartName: - description: |- - ObservedChartName is the last observed chart name as specified by the - resolved chart reference. - type: string - observedGeneration: - description: |- - ObservedGeneration is the last observed generation of the HelmChart - object. - format: int64 - type: integer - observedSourceArtifactRevision: - description: |- - ObservedSourceArtifactRevision is the last observed Artifact.Revision - of the HelmChartSpec.SourceRef. - type: string - observedValuesFiles: - description: |- - ObservedValuesFiles are the observed value files of the last successful - reconciliation. - It matches the chart in the last successfully reconciled artifact. - items: - type: string - type: array - url: - description: |- - URL is the dynamic fetch link for the latest Artifact. - It is provided on a "best effort" basis, and using the precise - BucketStatus.Artifact data is recommended. - type: string - type: object - type: object - served: true - storage: false - subresources: - status: {} ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - app.kubernetes.io/role: cluster-admin - controller-gen.kubebuilder.io/version: v0.19.0 - labels: - app.kubernetes.io/name: flux - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v2.7.3 - name: helmreleases.helm.toolkit.fluxcd.io -spec: - group: helm.toolkit.fluxcd.io - names: - kind: HelmRelease - listKind: HelmReleaseList - plural: helmreleases - shortNames: - - hr - singular: helmrelease - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - name: v2 - schema: - openAPIV3Schema: - description: HelmRelease is the Schema for the helmreleases API - 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: HelmReleaseSpec defines the desired state of a Helm release. - properties: - chart: - description: |- - Chart defines the template of the v1.HelmChart that should be created - for this HelmRelease. - properties: - metadata: - description: ObjectMeta holds the template for metadata like labels and annotations. - properties: - annotations: - additionalProperties: - type: string - description: |- - Annotations is an unstructured key value map stored with a resource that may be - set by external tools to store and retrieve arbitrary metadata. They are not - queryable and should be preserved when modifying objects. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ - type: object - labels: - additionalProperties: - type: string - description: |- - Map of string keys and values that can be used to organize and categorize - (scope and select) objects. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ - type: object - type: object - spec: - description: Spec holds the template for the v1.HelmChartSpec for this HelmRelease. - properties: - chart: - description: The name or path the Helm chart is available at in the SourceRef. - maxLength: 2048 - minLength: 1 - type: string - ignoreMissingValuesFiles: - description: IgnoreMissingValuesFiles controls whether to silently ignore missing values files rather than failing. - type: boolean - interval: - description: |- - Interval at which to check the v1.Source for updates. Defaults to - 'HelmReleaseSpec.Interval'. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - reconcileStrategy: - default: ChartVersion - description: |- - Determines what enables the creation of a new artifact. Valid values are - ('ChartVersion', 'Revision'). - See the documentation of the values for an explanation on their behavior. - Defaults to ChartVersion when omitted. - enum: - - ChartVersion - - Revision - type: string - sourceRef: - description: The name and namespace of the v1.Source the chart is available at. - properties: - apiVersion: - description: APIVersion of the referent. - type: string - kind: - description: Kind of the referent. - enum: - - HelmRepository - - GitRepository - - Bucket - type: string - name: - description: Name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: Namespace of the referent. - maxLength: 63 - minLength: 1 - type: string - required: - - kind - - name - type: object - valuesFiles: - description: |- - Alternative list of values files to use as the chart values (values.yaml - is not included by default), expected to be a relative path in the SourceRef. - Values files are merged in the order of this list with the last file overriding - the first. Ignored when omitted. - items: - type: string - type: array - verify: - description: |- - Verify contains the secret name containing the trusted public keys - used to verify the signature and specifies which provider to use to check - whether OCI image is authentic. - This field is only supported for OCI sources. - Chart dependencies, which are not bundled in the umbrella chart artifact, - are not verified. - properties: - provider: - default: cosign - description: Provider specifies the technology used to sign the OCI Helm chart. - enum: - - cosign - - notation - type: string - secretRef: - description: |- - SecretRef specifies the Kubernetes Secret containing the - trusted public keys. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - required: - - provider - type: object - version: - default: '*' - description: |- - Version semver expression, ignored for charts from v1.GitRepository and - v1beta2.Bucket sources. Defaults to latest when omitted. - type: string - required: - - chart - - sourceRef - type: object - required: - - spec - type: object - chartRef: - description: |- - ChartRef holds a reference to a source controller resource containing the - Helm chart artifact. - properties: - apiVersion: - description: APIVersion of the referent. - type: string - kind: - description: Kind of the referent. - enum: - - OCIRepository - - HelmChart - - ExternalArtifact - type: string - name: - description: Name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: |- - Namespace of the referent, defaults to the namespace of the Kubernetes - resource object that contains the reference. - maxLength: 63 - minLength: 1 - type: string - required: - - kind - - name - type: object - commonMetadata: - description: |- - CommonMetadata specifies the common labels and annotations that are - applied to all resources. Any existing label or annotation will be - overridden if its key matches a common one. - properties: - annotations: - additionalProperties: - type: string - description: Annotations to be added to the object's metadata. - type: object - labels: - additionalProperties: - type: string - description: Labels to be added to the object's metadata. - type: object - type: object - dependsOn: - description: |- - DependsOn may contain a DependencyReference slice with - references to HelmRelease resources that must be ready before this HelmRelease - can be reconciled. - items: - description: DependencyReference defines a HelmRelease dependency on another HelmRelease resource. - properties: - name: - description: Name of the referent. - type: string - namespace: - description: |- - Namespace of the referent, defaults to the namespace of the HelmRelease - resource object that contains the reference. - type: string - readyExpr: - description: |- - ReadyExpr is a CEL expression that can be used to assess the readiness - of a dependency. When specified, the built-in readiness check - is replaced by the logic defined in the CEL expression. - To make the CEL expression additive to the built-in readiness check, - the feature gate `AdditiveCELDependencyCheck` must be set to `true`. - type: string - required: - - name - type: object - type: array - driftDetection: - description: |- - DriftDetection holds the configuration for detecting and handling - differences between the manifest in the Helm storage and the resources - currently existing in the cluster. - properties: - ignore: - description: |- - Ignore contains a list of rules for specifying which changes to ignore - during diffing. - items: - description: |- - IgnoreRule defines a rule to selectively disregard specific changes during - the drift detection process. - properties: - paths: - description: |- - Paths is a list of JSON Pointer (RFC 6901) paths to be excluded from - consideration in a Kubernetes object. - items: - type: string - type: array - target: - description: |- - Target is a selector for specifying Kubernetes objects to which this - rule applies. - If Target is not set, the Paths will be ignored for all Kubernetes - objects within the manifest of the Helm release. - properties: - annotationSelector: - description: |- - AnnotationSelector is a string that follows the label selection expression - https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api - It matches with the resource annotations. - type: string - group: - description: |- - Group is the API group to select resources from. - Together with Version and Kind it is capable of unambiguously identifying and/or selecting resources. - https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - kind: - description: |- - Kind of the API Group to select resources from. - Together with Group and Version it is capable of unambiguously - identifying and/or selecting resources. - https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - labelSelector: - description: |- - LabelSelector is a string that follows the label selection expression - https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api - It matches with the resource labels. - type: string - name: - description: Name to match resources with. - type: string - namespace: - description: Namespace to select resources from. - type: string - version: - description: |- - Version of the API Group to select resources from. - Together with Group and Kind it is capable of unambiguously identifying and/or selecting resources. - https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - type: object - required: - - paths - type: object - type: array - mode: - description: |- - Mode defines how differences should be handled between the Helm manifest - and the manifest currently applied to the cluster. - If not explicitly set, it defaults to DiffModeDisabled. - enum: - - enabled - - warn - - disabled - type: string - type: object - install: - description: Install holds the configuration for Helm install actions for this HelmRelease. - properties: - crds: - description: |- - CRDs upgrade CRDs from the Helm Chart's crds directory according - to the CRD upgrade policy provided here. Valid values are `Skip`, - `Create` or `CreateReplace`. Default is `Create` and if omitted - CRDs are installed but not updated. - - Skip: do neither install nor replace (update) any CRDs. - - Create: new CRDs are created, existing CRDs are neither updated nor deleted. - - CreateReplace: new CRDs are created, existing CRDs are updated (replaced) - but not deleted. - - By default, CRDs are applied (installed) during Helm install action. - With this option users can opt in to CRD replace existing CRDs on Helm - install actions, which is not (yet) natively supported by Helm. - https://helm.sh/docs/chart_best_practices/custom_resource_definitions. - enum: - - Skip - - Create - - CreateReplace - type: string - createNamespace: - description: |- - CreateNamespace tells the Helm install action to create the - HelmReleaseSpec.TargetNamespace if it does not exist yet. - On uninstall, the namespace will not be garbage collected. - type: boolean - disableHooks: - description: DisableHooks prevents hooks from running during the Helm install action. - type: boolean - disableOpenAPIValidation: - description: |- - DisableOpenAPIValidation prevents the Helm install action from validating - rendered templates against the Kubernetes OpenAPI Schema. - type: boolean - disableSchemaValidation: - description: |- - DisableSchemaValidation prevents the Helm install action from validating - the values against the JSON Schema. - type: boolean - disableTakeOwnership: - description: |- - DisableTakeOwnership disables taking ownership of existing resources - during the Helm install action. Defaults to false. - type: boolean - disableWait: - description: |- - DisableWait disables the waiting for resources to be ready after a Helm - install has been performed. - type: boolean - disableWaitForJobs: - description: |- - DisableWaitForJobs disables waiting for jobs to complete after a Helm - install has been performed. - type: boolean - remediation: - description: |- - Remediation holds the remediation configuration for when the Helm install - action for the HelmRelease fails. The default is to not perform any action. - properties: - ignoreTestFailures: - description: |- - IgnoreTestFailures tells the controller to skip remediation when the Helm - tests are run after an install action but fail. Defaults to - 'Test.IgnoreFailures'. - type: boolean - remediateLastFailure: - description: |- - RemediateLastFailure tells the controller to remediate the last failure, when - no retries remain. Defaults to 'false'. - type: boolean - retries: - description: |- - Retries is the number of retries that should be attempted on failures before - bailing. Remediation, using an uninstall, is performed between each attempt. - Defaults to '0', a negative integer equals to unlimited retries. - type: integer - type: object - replace: - description: |- - Replace tells the Helm install action to re-use the 'ReleaseName', but only - if that name is a deleted release which remains in the history. - type: boolean - skipCRDs: - description: |- - SkipCRDs tells the Helm install action to not install any CRDs. By default, - CRDs are installed if not already present. - - Deprecated use CRD policy (`crds`) attribute with value `Skip` instead. - type: boolean - strategy: - description: |- - Strategy defines the install strategy to use for this HelmRelease. - Defaults to 'RemediateOnFailure'. - properties: - name: - description: Name of the install strategy. - enum: - - RemediateOnFailure - - RetryOnFailure - type: string - retryInterval: - description: |- - RetryInterval is the interval at which to retry a failed install. - Can be used only when Name is set to RetryOnFailure. - Defaults to '5m'. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - required: - - name - type: object - x-kubernetes-validations: - - message: .retryInterval cannot be set when .name is 'RemediateOnFailure' - rule: '!has(self.retryInterval) || self.name != ''RemediateOnFailure''' - timeout: - description: |- - Timeout is the time to wait for any individual Kubernetes operation (like - Jobs for hooks) during the performance of a Helm install action. Defaults to - 'HelmReleaseSpec.Timeout'. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - type: object - interval: - description: Interval at which to reconcile the Helm release. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - kubeConfig: - description: |- - KubeConfig for reconciling the HelmRelease on a remote cluster. - When used in combination with HelmReleaseSpec.ServiceAccountName, - forces the controller to act on behalf of that Service Account at the - target cluster. - If the --default-service-account flag is set, its value will be used as - a controller level fallback for when HelmReleaseSpec.ServiceAccountName - is empty. - properties: - configMapRef: - description: |- - ConfigMapRef holds an optional name of a ConfigMap that contains - the following keys: - - - `provider`: the provider to use. One of `aws`, `azure`, `gcp`, or - `generic`. Required. - - `cluster`: the fully qualified resource name of the Kubernetes - cluster in the cloud provider API. Not used by the `generic` - provider. Required when one of `address` or `ca.crt` is not set. - - `address`: the address of the Kubernetes API server. Required - for `generic`. For the other providers, if not specified, the - first address in the cluster resource will be used, and if - specified, it must match one of the addresses in the cluster - resource. - If audiences is not set, will be used as the audience for the - `generic` provider. - - `ca.crt`: the optional PEM-encoded CA certificate for the - Kubernetes API server. If not set, the controller will use the - CA certificate from the cluster resource. - - `audiences`: the optional audiences as a list of - line-break-separated strings for the Kubernetes ServiceAccount - token. Defaults to the `address` for the `generic` provider, or - to specific values for the other providers depending on the - provider. - - `serviceAccountName`: the optional name of the Kubernetes - ServiceAccount in the same namespace that should be used - for authentication. If not specified, the controller - ServiceAccount will be used. - - Mutually exclusive with SecretRef. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - secretRef: - description: |- - SecretRef holds an optional name of a secret that contains a key with - the kubeconfig file as the value. If no key is set, the key will default - to 'value'. Mutually exclusive with ConfigMapRef. - It is recommended that the kubeconfig is self-contained, and the secret - is regularly updated if credentials such as a cloud-access-token expire. - Cloud specific `cmd-path` auth helpers will not function without adding - binaries and credentials to the Pod that is responsible for reconciling - Kubernetes resources. Supported only for the generic provider. - properties: - key: - description: Key in the Secret, when not specified an implementation-specific default key is used. - type: string - name: - description: Name of the Secret. - type: string - required: - - name - type: object - type: object - x-kubernetes-validations: - - message: exactly one of spec.kubeConfig.configMapRef or spec.kubeConfig.secretRef must be specified - rule: has(self.configMapRef) || has(self.secretRef) - - message: exactly one of spec.kubeConfig.configMapRef or spec.kubeConfig.secretRef must be specified - rule: '!has(self.configMapRef) || !has(self.secretRef)' - maxHistory: - description: |- - MaxHistory is the number of revisions saved by Helm for this HelmRelease. - Use '0' for an unlimited number of revisions; defaults to '5'. - type: integer - persistentClient: - description: |- - PersistentClient tells the controller to use a persistent Kubernetes - client for this release. When enabled, the client will be reused for the - duration of the reconciliation, instead of being created and destroyed - for each (step of a) Helm action. - - This can improve performance, but may cause issues with some Helm charts - that for example do create Custom Resource Definitions during installation - outside Helm's CRD lifecycle hooks, which are then not observed to be - available by e.g. post-install hooks. - - If not set, it defaults to true. - type: boolean - postRenderers: - description: |- - PostRenderers holds an array of Helm PostRenderers, which will be applied in order - of their definition. - items: - description: PostRenderer contains a Helm PostRenderer specification. - properties: - kustomize: - description: Kustomization to apply as PostRenderer. - properties: - images: - description: |- - Images is a list of (image name, new name, new tag or digest) - for changing image names, tags or digests. This can also be achieved with a - patch, but this operator is simpler to specify. - items: - description: Image contains an image name, a new name, a new tag or digest, which will replace the original name and tag. - properties: - digest: - description: |- - Digest is the value used to replace the original image tag. - If digest is present NewTag value is ignored. - type: string - name: - description: Name is a tag-less image name. - type: string - newName: - description: NewName is the value used to replace the original name. - type: string - newTag: - description: NewTag is the value used to replace the original tag. - type: string - required: - - name - type: object - type: array - patches: - description: |- - Strategic merge and JSON patches, defined as inline YAML objects, - capable of targeting objects based on kind, label and annotation selectors. - items: - description: |- - Patch contains an inline StrategicMerge or JSON6902 patch, and the target the patch should - be applied to. - properties: - patch: - description: |- - Patch contains an inline StrategicMerge patch or an inline JSON6902 patch with - an array of operation objects. - type: string - target: - description: Target points to the resources that the patch document should be applied to. - properties: - annotationSelector: - description: |- - AnnotationSelector is a string that follows the label selection expression - https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api - It matches with the resource annotations. - type: string - group: - description: |- - Group is the API group to select resources from. - Together with Version and Kind it is capable of unambiguously identifying and/or selecting resources. - https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - kind: - description: |- - Kind of the API Group to select resources from. - Together with Group and Version it is capable of unambiguously - identifying and/or selecting resources. - https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - labelSelector: - description: |- - LabelSelector is a string that follows the label selection expression - https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api - It matches with the resource labels. - type: string - name: - description: Name to match resources with. - type: string - namespace: - description: Namespace to select resources from. - type: string - version: - description: |- - Version of the API Group to select resources from. - Together with Group and Kind it is capable of unambiguously identifying and/or selecting resources. - https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - type: object - required: - - patch - type: object - type: array - type: object - type: object - type: array - releaseName: - description: |- - ReleaseName used for the Helm release. Defaults to a composition of - '[TargetNamespace-]Name'. - maxLength: 53 - minLength: 1 - type: string - rollback: - description: Rollback holds the configuration for Helm rollback actions for this HelmRelease. - properties: - cleanupOnFail: - description: |- - CleanupOnFail allows deletion of new resources created during the Helm - rollback action when it fails. - type: boolean - disableHooks: - description: DisableHooks prevents hooks from running during the Helm rollback action. - type: boolean - disableWait: - description: |- - DisableWait disables the waiting for resources to be ready after a Helm - rollback has been performed. - type: boolean - disableWaitForJobs: - description: |- - DisableWaitForJobs disables waiting for jobs to complete after a Helm - rollback has been performed. - type: boolean - force: - description: Force forces resource updates through a replacement strategy. - type: boolean - recreate: - description: Recreate performs pod restarts for the resource if applicable. - type: boolean - timeout: - description: |- - Timeout is the time to wait for any individual Kubernetes operation (like - Jobs for hooks) during the performance of a Helm rollback action. Defaults to - 'HelmReleaseSpec.Timeout'. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - type: object - serviceAccountName: - description: |- - The name of the Kubernetes service account to impersonate - when reconciling this HelmRelease. - maxLength: 253 - minLength: 1 - type: string - storageNamespace: - description: |- - StorageNamespace used for the Helm storage. - Defaults to the namespace of the HelmRelease. - maxLength: 63 - minLength: 1 - type: string - suspend: - description: |- - Suspend tells the controller to suspend reconciliation for this HelmRelease, - it does not apply to already started reconciliations. Defaults to false. - type: boolean - targetNamespace: - description: |- - TargetNamespace to target when performing operations for the HelmRelease. - Defaults to the namespace of the HelmRelease. - maxLength: 63 - minLength: 1 - type: string - test: - description: Test holds the configuration for Helm test actions for this HelmRelease. - properties: - enable: - description: |- - Enable enables Helm test actions for this HelmRelease after an Helm install - or upgrade action has been performed. - type: boolean - filters: - description: Filters is a list of tests to run or exclude from running. - items: - description: Filter holds the configuration for individual Helm test filters. - properties: - exclude: - description: Exclude specifies whether the named test should be excluded. - type: boolean - name: - description: Name is the name of the test. - maxLength: 253 - minLength: 1 - type: string - required: - - name - type: object - type: array - ignoreFailures: - description: |- - IgnoreFailures tells the controller to skip remediation when the Helm tests - are run but fail. Can be overwritten for tests run after install or upgrade - actions in 'Install.IgnoreTestFailures' and 'Upgrade.IgnoreTestFailures'. - type: boolean - timeout: - description: |- - Timeout is the time to wait for any individual Kubernetes operation during - the performance of a Helm test action. Defaults to 'HelmReleaseSpec.Timeout'. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - type: object - timeout: - description: |- - Timeout is the time to wait for any individual Kubernetes operation (like Jobs - for hooks) during the performance of a Helm action. Defaults to '5m0s'. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - uninstall: - description: Uninstall holds the configuration for Helm uninstall actions for this HelmRelease. - properties: - deletionPropagation: - default: background - description: |- - DeletionPropagation specifies the deletion propagation policy when - a Helm uninstall is performed. - enum: - - background - - foreground - - orphan - type: string - disableHooks: - description: DisableHooks prevents hooks from running during the Helm rollback action. - type: boolean - disableWait: - description: |- - DisableWait disables waiting for all the resources to be deleted after - a Helm uninstall is performed. - type: boolean - keepHistory: - description: |- - KeepHistory tells Helm to remove all associated resources and mark the - release as deleted, but retain the release history. - type: boolean - timeout: - description: |- - Timeout is the time to wait for any individual Kubernetes operation (like - Jobs for hooks) during the performance of a Helm uninstall action. Defaults - to 'HelmReleaseSpec.Timeout'. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - type: object - upgrade: - description: Upgrade holds the configuration for Helm upgrade actions for this HelmRelease. - properties: - cleanupOnFail: - description: |- - CleanupOnFail allows deletion of new resources created during the Helm - upgrade action when it fails. - type: boolean - crds: - description: |- - CRDs upgrade CRDs from the Helm Chart's crds directory according - to the CRD upgrade policy provided here. Valid values are `Skip`, - `Create` or `CreateReplace`. Default is `Skip` and if omitted - CRDs are neither installed nor upgraded. - - Skip: do neither install nor replace (update) any CRDs. - - Create: new CRDs are created, existing CRDs are neither updated nor deleted. - - CreateReplace: new CRDs are created, existing CRDs are updated (replaced) - but not deleted. - - By default, CRDs are not applied during Helm upgrade action. With this - option users can opt-in to CRD upgrade, which is not (yet) natively supported by Helm. - https://helm.sh/docs/chart_best_practices/custom_resource_definitions. - enum: - - Skip - - Create - - CreateReplace - type: string - disableHooks: - description: DisableHooks prevents hooks from running during the Helm upgrade action. - type: boolean - disableOpenAPIValidation: - description: |- - DisableOpenAPIValidation prevents the Helm upgrade action from validating - rendered templates against the Kubernetes OpenAPI Schema. - type: boolean - disableSchemaValidation: - description: |- - DisableSchemaValidation prevents the Helm upgrade action from validating - the values against the JSON Schema. - type: boolean - disableTakeOwnership: - description: |- - DisableTakeOwnership disables taking ownership of existing resources - during the Helm upgrade action. Defaults to false. - type: boolean - disableWait: - description: |- - DisableWait disables the waiting for resources to be ready after a Helm - upgrade has been performed. - type: boolean - disableWaitForJobs: - description: |- - DisableWaitForJobs disables waiting for jobs to complete after a Helm - upgrade has been performed. - type: boolean - force: - description: Force forces resource updates through a replacement strategy. - type: boolean - preserveValues: - description: |- - PreserveValues will make Helm reuse the last release's values and merge in - overrides from 'Values'. Setting this flag makes the HelmRelease - non-declarative. - type: boolean - remediation: - description: |- - Remediation holds the remediation configuration for when the Helm upgrade - action for the HelmRelease fails. The default is to not perform any action. - properties: - ignoreTestFailures: - description: |- - IgnoreTestFailures tells the controller to skip remediation when the Helm - tests are run after an upgrade action but fail. - Defaults to 'Test.IgnoreFailures'. - type: boolean - remediateLastFailure: - description: |- - RemediateLastFailure tells the controller to remediate the last failure, when - no retries remain. Defaults to 'false' unless 'Retries' is greater than 0. - type: boolean - retries: - description: |- - Retries is the number of retries that should be attempted on failures before - bailing. Remediation, using 'Strategy', is performed between each attempt. - Defaults to '0', a negative integer equals to unlimited retries. - type: integer - strategy: - description: Strategy to use for failure remediation. Defaults to 'rollback'. - enum: - - rollback - - uninstall - type: string - type: object - strategy: - description: |- - Strategy defines the upgrade strategy to use for this HelmRelease. - Defaults to 'RemediateOnFailure'. - properties: - name: - description: Name of the upgrade strategy. - enum: - - RemediateOnFailure - - RetryOnFailure - type: string - retryInterval: - description: |- - RetryInterval is the interval at which to retry a failed upgrade. - Can be used only when Name is set to RetryOnFailure. - Defaults to '5m'. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - required: - - name - type: object - x-kubernetes-validations: - - message: .retryInterval can only be set when .name is 'RetryOnFailure' - rule: '!has(self.retryInterval) || self.name == ''RetryOnFailure''' - timeout: - description: |- - Timeout is the time to wait for any individual Kubernetes operation (like - Jobs for hooks) during the performance of a Helm upgrade action. Defaults to - 'HelmReleaseSpec.Timeout'. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - type: object - values: - description: Values holds the values for this Helm release. - x-kubernetes-preserve-unknown-fields: true - valuesFrom: - description: |- - ValuesFrom holds references to resources containing Helm values for this HelmRelease, - and information about how they should be merged. - items: - description: |- - ValuesReference contains a reference to a resource containing Helm values, - and optionally the key they can be found at. - properties: - kind: - description: Kind of the values referent, valid values are ('Secret', 'ConfigMap'). - enum: - - Secret - - ConfigMap - type: string - name: - description: |- - Name of the values referent. Should reside in the same namespace as the - referring resource. - maxLength: 253 - minLength: 1 - type: string - optional: - description: |- - Optional marks this ValuesReference as optional. When set, a not found error - for the values reference is ignored, but any ValuesKey, TargetPath or - transient error will still result in a reconciliation failure. - type: boolean - targetPath: - description: |- - TargetPath is the YAML dot notation path the value should be merged at. When - set, the ValuesKey is expected to be a single flat value. Defaults to 'None', - which results in the values getting merged at the root. - maxLength: 250 - pattern: ^([a-zA-Z0-9_\-.\\\/]|\[[0-9]{1,5}\])+$ - type: string - valuesKey: - description: |- - ValuesKey is the data key where the values.yaml or a specific value can be - found at. Defaults to 'values.yaml'. - maxLength: 253 - pattern: ^[\-._a-zA-Z0-9]+$ - type: string - required: - - kind - - name - type: object - type: array - required: - - interval - type: object - x-kubernetes-validations: - - message: either chart or chartRef must be set - rule: (has(self.chart) && !has(self.chartRef)) || (!has(self.chart) && has(self.chartRef)) - status: - default: - observedGeneration: -1 - description: HelmReleaseStatus defines the observed state of a HelmRelease. - properties: - conditions: - description: Conditions holds the conditions for the HelmRelease. - 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 - failures: - description: |- - Failures is the reconciliation failure count against the latest desired - state. It is reset after a successful reconciliation. - format: int64 - type: integer - helmChart: - description: |- - HelmChart is the namespaced name of the HelmChart resource created by - the controller for the HelmRelease. - type: string - history: - description: |- - History holds the history of Helm releases performed for this HelmRelease - up to the last successfully completed release. - items: - description: |- - Snapshot captures a point-in-time copy of the status information for a Helm release, - as managed by the controller. - properties: - apiVersion: - description: |- - APIVersion is the API version of the Snapshot. - Provisional: when the calculation method of the Digest field is changed, - this field will be used to distinguish between the old and new methods. - type: string - appVersion: - description: AppVersion is the chart app version of the release object in storage. - type: string - chartName: - description: ChartName is the chart name of the release object in storage. - type: string - chartVersion: - description: |- - ChartVersion is the chart version of the release object in - storage. - type: string - configDigest: - description: |- - ConfigDigest is the checksum of the config (better known as - "values") of the release object in storage. - It has the format of `:`. - type: string - deleted: - description: Deleted is when the release was deleted. - format: date-time - type: string - digest: - description: |- - Digest is the checksum of the release object in storage. - It has the format of `:`. - type: string - firstDeployed: - description: FirstDeployed is when the release was first deployed. - format: date-time - type: string - lastDeployed: - description: LastDeployed is when the release was last deployed. - format: date-time - type: string - name: - description: Name is the name of the release. - type: string - namespace: - description: Namespace is the namespace the release is deployed to. - type: string - ociDigest: - description: OCIDigest is the digest of the OCI artifact associated with the release. - type: string - status: - description: Status is the current state of the release. - type: string - testHooks: - additionalProperties: - description: |- - TestHookStatus holds the status information for a test hook as observed - to be run by the controller. - properties: - lastCompleted: - description: LastCompleted is the time the test hook last completed. - format: date-time - type: string - lastStarted: - description: LastStarted is the time the test hook was last started. - format: date-time - type: string - phase: - description: Phase the test hook was observed to be in. - type: string - type: object - description: |- - TestHooks is the list of test hooks for the release as observed to be - run by the controller. - type: object - version: - description: Version is the version of the release object in storage. - type: integer - required: - - chartName - - chartVersion - - configDigest - - digest - - firstDeployed - - lastDeployed - - name - - namespace - - status - - version - type: object - type: array - installFailures: - description: |- - InstallFailures is the install failure count against the latest desired - state. It is reset after a successful reconciliation. - format: int64 - type: integer - lastAttemptedConfigDigest: - description: |- - LastAttemptedConfigDigest is the digest for the config (better known as - "values") of the last reconciliation attempt. - type: string - lastAttemptedGeneration: - description: |- - LastAttemptedGeneration is the last generation the controller attempted - to reconcile. - format: int64 - type: integer - lastAttemptedReleaseAction: - description: |- - LastAttemptedReleaseAction is the last release action performed for this - HelmRelease. It is used to determine the active retry or remediation - strategy. - enum: - - install - - upgrade - type: string - lastAttemptedReleaseActionDuration: - description: |- - LastAttemptedReleaseActionDuration is the duration of the last - release action performed for this HelmRelease. - type: string - lastAttemptedRevision: - description: |- - LastAttemptedRevision is the Source revision of the last reconciliation - attempt. For OCIRepository sources, the 12 first characters of the digest are - appended to the chart version e.g. "1.2.3+1234567890ab". - type: string - lastAttemptedRevisionDigest: - description: |- - LastAttemptedRevisionDigest is the digest of the last reconciliation attempt. - This is only set for OCIRepository sources. - type: string - lastAttemptedValuesChecksum: - description: |- - LastAttemptedValuesChecksum is the SHA1 checksum for the values of the last - reconciliation attempt. - - Deprecated: Use LastAttemptedConfigDigest instead. - type: string - lastHandledForceAt: - description: |- - LastHandledForceAt holds the value of the most recent - force request value, so a change of the annotation value - can be detected. - type: string - lastHandledReconcileAt: - description: |- - LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value - can be detected. - type: string - lastHandledResetAt: - description: |- - LastHandledResetAt holds the value of the most recent reset request - value, so a change of the annotation value can be detected. - type: string - lastReleaseRevision: - description: |- - LastReleaseRevision is the revision of the last successful Helm release. - - Deprecated: Use History instead. - type: integer - observedCommonMetadataDigest: - description: |- - ObservedCommonMetadataDigest is the digest for the common metadata of - the last successful reconciliation attempt. - type: string - observedGeneration: - description: ObservedGeneration is the last observed generation. - format: int64 - type: integer - observedPostRenderersDigest: - description: |- - ObservedPostRenderersDigest is the digest for the post-renderers of - the last successful reconciliation attempt. - type: string - storageNamespace: - description: |- - StorageNamespace is the namespace of the Helm release storage for the - current release. - maxLength: 63 - minLength: 1 - type: string - upgradeFailures: - description: |- - UpgradeFailures is the upgrade failure count against the latest desired - state. It is reset after a successful reconciliation. - format: int64 - type: integer - type: object - type: object - served: true - storage: true - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - deprecated: true - deprecationWarning: v2beta2 HelmRelease is deprecated, upgrade to v2 - name: v2beta2 - schema: - openAPIV3Schema: - description: HelmRelease is the Schema for the helmreleases API - 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: HelmReleaseSpec defines the desired state of a Helm release. - properties: - chart: - description: |- - Chart defines the template of the v1beta2.HelmChart that should be created - for this HelmRelease. - properties: - metadata: - description: ObjectMeta holds the template for metadata like labels and annotations. - properties: - annotations: - additionalProperties: - type: string - description: |- - Annotations is an unstructured key value map stored with a resource that may be - set by external tools to store and retrieve arbitrary metadata. They are not - queryable and should be preserved when modifying objects. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ - type: object - labels: - additionalProperties: - type: string - description: |- - Map of string keys and values that can be used to organize and categorize - (scope and select) objects. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ - type: object - type: object - spec: - description: Spec holds the template for the v1beta2.HelmChartSpec for this HelmRelease. - properties: - chart: - description: The name or path the Helm chart is available at in the SourceRef. - maxLength: 2048 - minLength: 1 - type: string - ignoreMissingValuesFiles: - description: IgnoreMissingValuesFiles controls whether to silently ignore missing values files rather than failing. - type: boolean - interval: - description: |- - Interval at which to check the v1.Source for updates. Defaults to - 'HelmReleaseSpec.Interval'. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - reconcileStrategy: - default: ChartVersion - description: |- - Determines what enables the creation of a new artifact. Valid values are - ('ChartVersion', 'Revision'). - See the documentation of the values for an explanation on their behavior. - Defaults to ChartVersion when omitted. - enum: - - ChartVersion - - Revision - type: string - sourceRef: - description: The name and namespace of the v1.Source the chart is available at. - properties: - apiVersion: - description: APIVersion of the referent. - type: string - kind: - description: Kind of the referent. - enum: - - HelmRepository - - GitRepository - - Bucket - type: string - name: - description: Name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: Namespace of the referent. - maxLength: 63 - minLength: 1 - type: string - required: - - kind - - name - type: object - valuesFile: - description: |- - Alternative values file to use as the default chart values, expected to - be a relative path in the SourceRef. Deprecated in favor of ValuesFiles, - for backwards compatibility the file defined here is merged before the - ValuesFiles items. Ignored when omitted. - type: string - valuesFiles: - description: |- - Alternative list of values files to use as the chart values (values.yaml - is not included by default), expected to be a relative path in the SourceRef. - Values files are merged in the order of this list with the last file overriding - the first. Ignored when omitted. - items: - type: string - type: array - verify: - description: |- - Verify contains the secret name containing the trusted public keys - used to verify the signature and specifies which provider to use to check - whether OCI image is authentic. - This field is only supported for OCI sources. - Chart dependencies, which are not bundled in the umbrella chart artifact, - are not verified. - properties: - provider: - default: cosign - description: Provider specifies the technology used to sign the OCI Helm chart. - enum: - - cosign - - notation - type: string - secretRef: - description: |- - SecretRef specifies the Kubernetes Secret containing the - trusted public keys. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - required: - - provider - type: object - version: - default: '*' - description: |- - Version semver expression, ignored for charts from v1beta2.GitRepository and - v1beta2.Bucket sources. Defaults to latest when omitted. - type: string - required: - - chart - - sourceRef - type: object - required: - - spec - type: object - chartRef: - description: |- - ChartRef holds a reference to a source controller resource containing the - Helm chart artifact. - - Note: this field is provisional to the v2 API, and not actively used - by v2beta2 HelmReleases. - properties: - apiVersion: - description: APIVersion of the referent. - type: string - kind: - description: Kind of the referent. - enum: - - OCIRepository - - HelmChart - type: string - name: - description: Name of the referent. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: |- - Namespace of the referent, defaults to the namespace of the Kubernetes - resource object that contains the reference. - maxLength: 63 - minLength: 1 - type: string - required: - - kind - - name - type: object - dependsOn: - description: |- - DependsOn may contain a meta.NamespacedObjectReference slice with - references to HelmRelease resources that must be ready before this HelmRelease - can be reconciled. - items: - description: |- - NamespacedObjectReference contains enough information to locate the referenced Kubernetes resource object in any - namespace. - properties: - name: - description: Name of the referent. - type: string - namespace: - description: Namespace of the referent, when not specified it acts as LocalObjectReference. - type: string - required: - - name - type: object - type: array - driftDetection: - description: |- - DriftDetection holds the configuration for detecting and handling - differences between the manifest in the Helm storage and the resources - currently existing in the cluster. - properties: - ignore: - description: |- - Ignore contains a list of rules for specifying which changes to ignore - during diffing. - items: - description: |- - IgnoreRule defines a rule to selectively disregard specific changes during - the drift detection process. - properties: - paths: - description: |- - Paths is a list of JSON Pointer (RFC 6901) paths to be excluded from - consideration in a Kubernetes object. - items: - type: string - type: array - target: - description: |- - Target is a selector for specifying Kubernetes objects to which this - rule applies. - If Target is not set, the Paths will be ignored for all Kubernetes - objects within the manifest of the Helm release. - properties: - annotationSelector: - description: |- - AnnotationSelector is a string that follows the label selection expression - https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api - It matches with the resource annotations. - type: string - group: - description: |- - Group is the API group to select resources from. - Together with Version and Kind it is capable of unambiguously identifying and/or selecting resources. - https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - kind: - description: |- - Kind of the API Group to select resources from. - Together with Group and Version it is capable of unambiguously - identifying and/or selecting resources. - https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - labelSelector: - description: |- - LabelSelector is a string that follows the label selection expression - https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api - It matches with the resource labels. - type: string - name: - description: Name to match resources with. - type: string - namespace: - description: Namespace to select resources from. - type: string - version: - description: |- - Version of the API Group to select resources from. - Together with Group and Kind it is capable of unambiguously identifying and/or selecting resources. - https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - type: object - required: - - paths - type: object - type: array - mode: - description: |- - Mode defines how differences should be handled between the Helm manifest - and the manifest currently applied to the cluster. - If not explicitly set, it defaults to DiffModeDisabled. - enum: - - enabled - - warn - - disabled - type: string - type: object - install: - description: Install holds the configuration for Helm install actions for this HelmRelease. - properties: - crds: - description: |- - CRDs upgrade CRDs from the Helm Chart's crds directory according - to the CRD upgrade policy provided here. Valid values are `Skip`, - `Create` or `CreateReplace`. Default is `Create` and if omitted - CRDs are installed but not updated. - - Skip: do neither install nor replace (update) any CRDs. - - Create: new CRDs are created, existing CRDs are neither updated nor deleted. - - CreateReplace: new CRDs are created, existing CRDs are updated (replaced) - but not deleted. - - By default, CRDs are applied (installed) during Helm install action. - With this option users can opt in to CRD replace existing CRDs on Helm - install actions, which is not (yet) natively supported by Helm. - https://helm.sh/docs/chart_best_practices/custom_resource_definitions. - enum: - - Skip - - Create - - CreateReplace - type: string - createNamespace: - description: |- - CreateNamespace tells the Helm install action to create the - HelmReleaseSpec.TargetNamespace if it does not exist yet. - On uninstall, the namespace will not be garbage collected. - type: boolean - disableHooks: - description: DisableHooks prevents hooks from running during the Helm install action. - type: boolean - disableOpenAPIValidation: - description: |- - DisableOpenAPIValidation prevents the Helm install action from validating - rendered templates against the Kubernetes OpenAPI Schema. - type: boolean - disableWait: - description: |- - DisableWait disables the waiting for resources to be ready after a Helm - install has been performed. - type: boolean - disableWaitForJobs: - description: |- - DisableWaitForJobs disables waiting for jobs to complete after a Helm - install has been performed. - type: boolean - remediation: - description: |- - Remediation holds the remediation configuration for when the Helm install - action for the HelmRelease fails. The default is to not perform any action. - properties: - ignoreTestFailures: - description: |- - IgnoreTestFailures tells the controller to skip remediation when the Helm - tests are run after an install action but fail. Defaults to - 'Test.IgnoreFailures'. - type: boolean - remediateLastFailure: - description: |- - RemediateLastFailure tells the controller to remediate the last failure, when - no retries remain. Defaults to 'false'. - type: boolean - retries: - description: |- - Retries is the number of retries that should be attempted on failures before - bailing. Remediation, using an uninstall, is performed between each attempt. - Defaults to '0', a negative integer equals to unlimited retries. - type: integer - type: object - replace: - description: |- - Replace tells the Helm install action to re-use the 'ReleaseName', but only - if that name is a deleted release which remains in the history. - type: boolean - skipCRDs: - description: |- - SkipCRDs tells the Helm install action to not install any CRDs. By default, - CRDs are installed if not already present. - - Deprecated use CRD policy (`crds`) attribute with value `Skip` instead. - type: boolean - timeout: - description: |- - Timeout is the time to wait for any individual Kubernetes operation (like - Jobs for hooks) during the performance of a Helm install action. Defaults to - 'HelmReleaseSpec.Timeout'. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - type: object - interval: - description: Interval at which to reconcile the Helm release. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - kubeConfig: - description: |- - KubeConfig for reconciling the HelmRelease on a remote cluster. - When used in combination with HelmReleaseSpec.ServiceAccountName, - forces the controller to act on behalf of that Service Account at the - target cluster. - If the --default-service-account flag is set, its value will be used as - a controller level fallback for when HelmReleaseSpec.ServiceAccountName - is empty. - properties: - configMapRef: - description: |- - ConfigMapRef holds an optional name of a ConfigMap that contains - the following keys: - - - `provider`: the provider to use. One of `aws`, `azure`, `gcp`, or - `generic`. Required. - - `cluster`: the fully qualified resource name of the Kubernetes - cluster in the cloud provider API. Not used by the `generic` - provider. Required when one of `address` or `ca.crt` is not set. - - `address`: the address of the Kubernetes API server. Required - for `generic`. For the other providers, if not specified, the - first address in the cluster resource will be used, and if - specified, it must match one of the addresses in the cluster - resource. - If audiences is not set, will be used as the audience for the - `generic` provider. - - `ca.crt`: the optional PEM-encoded CA certificate for the - Kubernetes API server. If not set, the controller will use the - CA certificate from the cluster resource. - - `audiences`: the optional audiences as a list of - line-break-separated strings for the Kubernetes ServiceAccount - token. Defaults to the `address` for the `generic` provider, or - to specific values for the other providers depending on the - provider. - - `serviceAccountName`: the optional name of the Kubernetes - ServiceAccount in the same namespace that should be used - for authentication. If not specified, the controller - ServiceAccount will be used. - - Mutually exclusive with SecretRef. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - secretRef: - description: |- - SecretRef holds an optional name of a secret that contains a key with - the kubeconfig file as the value. If no key is set, the key will default - to 'value'. Mutually exclusive with ConfigMapRef. - It is recommended that the kubeconfig is self-contained, and the secret - is regularly updated if credentials such as a cloud-access-token expire. - Cloud specific `cmd-path` auth helpers will not function without adding - binaries and credentials to the Pod that is responsible for reconciling - Kubernetes resources. Supported only for the generic provider. - properties: - key: - description: Key in the Secret, when not specified an implementation-specific default key is used. - type: string - name: - description: Name of the Secret. - type: string - required: - - name - type: object - type: object - x-kubernetes-validations: - - message: exactly one of spec.kubeConfig.configMapRef or spec.kubeConfig.secretRef must be specified - rule: has(self.configMapRef) || has(self.secretRef) - - message: exactly one of spec.kubeConfig.configMapRef or spec.kubeConfig.secretRef must be specified - rule: '!has(self.configMapRef) || !has(self.secretRef)' - maxHistory: - description: |- - MaxHistory is the number of revisions saved by Helm for this HelmRelease. - Use '0' for an unlimited number of revisions; defaults to '5'. - type: integer - persistentClient: - description: |- - PersistentClient tells the controller to use a persistent Kubernetes - client for this release. When enabled, the client will be reused for the - duration of the reconciliation, instead of being created and destroyed - for each (step of a) Helm action. - - This can improve performance, but may cause issues with some Helm charts - that for example do create Custom Resource Definitions during installation - outside Helm's CRD lifecycle hooks, which are then not observed to be - available by e.g. post-install hooks. - - If not set, it defaults to true. - type: boolean - postRenderers: - description: |- - PostRenderers holds an array of Helm PostRenderers, which will be applied in order - of their definition. - items: - description: PostRenderer contains a Helm PostRenderer specification. - properties: - kustomize: - description: Kustomization to apply as PostRenderer. - properties: - images: - description: |- - Images is a list of (image name, new name, new tag or digest) - for changing image names, tags or digests. This can also be achieved with a - patch, but this operator is simpler to specify. - items: - description: Image contains an image name, a new name, a new tag or digest, which will replace the original name and tag. - properties: - digest: - description: |- - Digest is the value used to replace the original image tag. - If digest is present NewTag value is ignored. - type: string - name: - description: Name is a tag-less image name. - type: string - newName: - description: NewName is the value used to replace the original name. - type: string - newTag: - description: NewTag is the value used to replace the original tag. - type: string - required: - - name - type: object - type: array - patches: - description: |- - Strategic merge and JSON patches, defined as inline YAML objects, - capable of targeting objects based on kind, label and annotation selectors. - items: - description: |- - Patch contains an inline StrategicMerge or JSON6902 patch, and the target the patch should - be applied to. - properties: - patch: - description: |- - Patch contains an inline StrategicMerge patch or an inline JSON6902 patch with - an array of operation objects. - type: string - target: - description: Target points to the resources that the patch document should be applied to. - properties: - annotationSelector: - description: |- - AnnotationSelector is a string that follows the label selection expression - https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api - It matches with the resource annotations. - type: string - group: - description: |- - Group is the API group to select resources from. - Together with Version and Kind it is capable of unambiguously identifying and/or selecting resources. - https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - kind: - description: |- - Kind of the API Group to select resources from. - Together with Group and Version it is capable of unambiguously - identifying and/or selecting resources. - https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - labelSelector: - description: |- - LabelSelector is a string that follows the label selection expression - https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api - It matches with the resource labels. - type: string - name: - description: Name to match resources with. - type: string - namespace: - description: Namespace to select resources from. - type: string - version: - description: |- - Version of the API Group to select resources from. - Together with Group and Kind it is capable of unambiguously identifying and/or selecting resources. - https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - type: object - required: - - patch - type: object - type: array - patchesJson6902: - description: |- - JSON 6902 patches, defined as inline YAML objects. - - Deprecated: use Patches instead. - items: - description: JSON6902Patch contains a JSON6902 patch and the target the patch should be applied to. - properties: - patch: - description: Patch contains the JSON6902 patch document with an array of operation objects. - items: - description: |- - JSON6902 is a JSON6902 operation object. - https://datatracker.ietf.org/doc/html/rfc6902#section-4 - properties: - from: - description: |- - From contains a JSON-pointer value that references a location within the target document where the operation is - performed. The meaning of the value depends on the value of Op, and is NOT taken into account by all operations. - type: string - op: - description: |- - Op indicates the operation to perform. Its value MUST be one of "add", "remove", "replace", "move", "copy", or - "test". - https://datatracker.ietf.org/doc/html/rfc6902#section-4 - enum: - - test - - remove - - add - - replace - - move - - copy - type: string - path: - description: |- - Path contains the JSON-pointer value that references a location within the target document where the operation - is performed. The meaning of the value depends on the value of Op. - type: string - value: - description: |- - Value contains a valid JSON structure. The meaning of the value depends on the value of Op, and is NOT taken into - account by all operations. - x-kubernetes-preserve-unknown-fields: true - required: - - op - - path - type: object - type: array - target: - description: Target points to the resources that the patch document should be applied to. - properties: - annotationSelector: - description: |- - AnnotationSelector is a string that follows the label selection expression - https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api - It matches with the resource annotations. - type: string - group: - description: |- - Group is the API group to select resources from. - Together with Version and Kind it is capable of unambiguously identifying and/or selecting resources. - https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - kind: - description: |- - Kind of the API Group to select resources from. - Together with Group and Version it is capable of unambiguously - identifying and/or selecting resources. - https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - labelSelector: - description: |- - LabelSelector is a string that follows the label selection expression - https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api - It matches with the resource labels. - type: string - name: - description: Name to match resources with. - type: string - namespace: - description: Namespace to select resources from. - type: string - version: - description: |- - Version of the API Group to select resources from. - Together with Group and Kind it is capable of unambiguously identifying and/or selecting resources. - https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - type: object - required: - - patch - - target - type: object - type: array - patchesStrategicMerge: - description: |- - Strategic merge patches, defined as inline YAML objects. - - Deprecated: use Patches instead. - items: - x-kubernetes-preserve-unknown-fields: true - type: array - type: object - type: object - type: array - releaseName: - description: |- - ReleaseName used for the Helm release. Defaults to a composition of - '[TargetNamespace-]Name'. - maxLength: 53 - minLength: 1 - type: string - rollback: - description: Rollback holds the configuration for Helm rollback actions for this HelmRelease. - properties: - cleanupOnFail: - description: |- - CleanupOnFail allows deletion of new resources created during the Helm - rollback action when it fails. - type: boolean - disableHooks: - description: DisableHooks prevents hooks from running during the Helm rollback action. - type: boolean - disableWait: - description: |- - DisableWait disables the waiting for resources to be ready after a Helm - rollback has been performed. - type: boolean - disableWaitForJobs: - description: |- - DisableWaitForJobs disables waiting for jobs to complete after a Helm - rollback has been performed. - type: boolean - force: - description: Force forces resource updates through a replacement strategy. - type: boolean - recreate: - description: Recreate performs pod restarts for the resource if applicable. - type: boolean - timeout: - description: |- - Timeout is the time to wait for any individual Kubernetes operation (like - Jobs for hooks) during the performance of a Helm rollback action. Defaults to - 'HelmReleaseSpec.Timeout'. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - type: object - serviceAccountName: - description: |- - The name of the Kubernetes service account to impersonate - when reconciling this HelmRelease. - maxLength: 253 - minLength: 1 - type: string - storageNamespace: - description: |- - StorageNamespace used for the Helm storage. - Defaults to the namespace of the HelmRelease. - maxLength: 63 - minLength: 1 - type: string - suspend: - description: |- - Suspend tells the controller to suspend reconciliation for this HelmRelease, - it does not apply to already started reconciliations. Defaults to false. - type: boolean - targetNamespace: - description: |- - TargetNamespace to target when performing operations for the HelmRelease. - Defaults to the namespace of the HelmRelease. - maxLength: 63 - minLength: 1 - type: string - test: - description: Test holds the configuration for Helm test actions for this HelmRelease. - properties: - enable: - description: |- - Enable enables Helm test actions for this HelmRelease after an Helm install - or upgrade action has been performed. - type: boolean - filters: - description: Filters is a list of tests to run or exclude from running. - items: - description: Filter holds the configuration for individual Helm test filters. - properties: - exclude: - description: Exclude specifies whether the named test should be excluded. - type: boolean - name: - description: Name is the name of the test. - maxLength: 253 - minLength: 1 - type: string - required: - - name - type: object - type: array - ignoreFailures: - description: |- - IgnoreFailures tells the controller to skip remediation when the Helm tests - are run but fail. Can be overwritten for tests run after install or upgrade - actions in 'Install.IgnoreTestFailures' and 'Upgrade.IgnoreTestFailures'. - type: boolean - timeout: - description: |- - Timeout is the time to wait for any individual Kubernetes operation during - the performance of a Helm test action. Defaults to 'HelmReleaseSpec.Timeout'. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - type: object - timeout: - description: |- - Timeout is the time to wait for any individual Kubernetes operation (like Jobs - for hooks) during the performance of a Helm action. Defaults to '5m0s'. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - uninstall: - description: Uninstall holds the configuration for Helm uninstall actions for this HelmRelease. - properties: - deletionPropagation: - default: background - description: |- - DeletionPropagation specifies the deletion propagation policy when - a Helm uninstall is performed. - enum: - - background - - foreground - - orphan - type: string - disableHooks: - description: DisableHooks prevents hooks from running during the Helm rollback action. - type: boolean - disableWait: - description: |- - DisableWait disables waiting for all the resources to be deleted after - a Helm uninstall is performed. - type: boolean - keepHistory: - description: |- - KeepHistory tells Helm to remove all associated resources and mark the - release as deleted, but retain the release history. - type: boolean - timeout: - description: |- - Timeout is the time to wait for any individual Kubernetes operation (like - Jobs for hooks) during the performance of a Helm uninstall action. Defaults - to 'HelmReleaseSpec.Timeout'. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - type: object - upgrade: - description: Upgrade holds the configuration for Helm upgrade actions for this HelmRelease. - properties: - cleanupOnFail: - description: |- - CleanupOnFail allows deletion of new resources created during the Helm - upgrade action when it fails. - type: boolean - crds: - description: |- - CRDs upgrade CRDs from the Helm Chart's crds directory according - to the CRD upgrade policy provided here. Valid values are `Skip`, - `Create` or `CreateReplace`. Default is `Skip` and if omitted - CRDs are neither installed nor upgraded. - - Skip: do neither install nor replace (update) any CRDs. - - Create: new CRDs are created, existing CRDs are neither updated nor deleted. - - CreateReplace: new CRDs are created, existing CRDs are updated (replaced) - but not deleted. - - By default, CRDs are not applied during Helm upgrade action. With this - option users can opt-in to CRD upgrade, which is not (yet) natively supported by Helm. - https://helm.sh/docs/chart_best_practices/custom_resource_definitions. - enum: - - Skip - - Create - - CreateReplace - type: string - disableHooks: - description: DisableHooks prevents hooks from running during the Helm upgrade action. - type: boolean - disableOpenAPIValidation: - description: |- - DisableOpenAPIValidation prevents the Helm upgrade action from validating - rendered templates against the Kubernetes OpenAPI Schema. - type: boolean - disableWait: - description: |- - DisableWait disables the waiting for resources to be ready after a Helm - upgrade has been performed. - type: boolean - disableWaitForJobs: - description: |- - DisableWaitForJobs disables waiting for jobs to complete after a Helm - upgrade has been performed. - type: boolean - force: - description: Force forces resource updates through a replacement strategy. - type: boolean - preserveValues: - description: |- - PreserveValues will make Helm reuse the last release's values and merge in - overrides from 'Values'. Setting this flag makes the HelmRelease - non-declarative. - type: boolean - remediation: - description: |- - Remediation holds the remediation configuration for when the Helm upgrade - action for the HelmRelease fails. The default is to not perform any action. - properties: - ignoreTestFailures: - description: |- - IgnoreTestFailures tells the controller to skip remediation when the Helm - tests are run after an upgrade action but fail. - Defaults to 'Test.IgnoreFailures'. - type: boolean - remediateLastFailure: - description: |- - RemediateLastFailure tells the controller to remediate the last failure, when - no retries remain. Defaults to 'false' unless 'Retries' is greater than 0. - type: boolean - retries: - description: |- - Retries is the number of retries that should be attempted on failures before - bailing. Remediation, using 'Strategy', is performed between each attempt. - Defaults to '0', a negative integer equals to unlimited retries. - type: integer - strategy: - description: Strategy to use for failure remediation. Defaults to 'rollback'. - enum: - - rollback - - uninstall - type: string - type: object - timeout: - description: |- - Timeout is the time to wait for any individual Kubernetes operation (like - Jobs for hooks) during the performance of a Helm upgrade action. Defaults to - 'HelmReleaseSpec.Timeout'. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - type: object - values: - description: Values holds the values for this Helm release. - x-kubernetes-preserve-unknown-fields: true - valuesFrom: - description: |- - ValuesFrom holds references to resources containing Helm values for this HelmRelease, - and information about how they should be merged. - items: - description: |- - ValuesReference contains a reference to a resource containing Helm values, - and optionally the key they can be found at. - properties: - kind: - description: Kind of the values referent, valid values are ('Secret', 'ConfigMap'). - enum: - - Secret - - ConfigMap - type: string - name: - description: |- - Name of the values referent. Should reside in the same namespace as the - referring resource. - maxLength: 253 - minLength: 1 - type: string - optional: - description: |- - Optional marks this ValuesReference as optional. When set, a not found error - for the values reference is ignored, but any ValuesKey, TargetPath or - transient error will still result in a reconciliation failure. - type: boolean - targetPath: - description: |- - TargetPath is the YAML dot notation path the value should be merged at. When - set, the ValuesKey is expected to be a single flat value. Defaults to 'None', - which results in the values getting merged at the root. - maxLength: 250 - pattern: ^([a-zA-Z0-9_\-.\\\/]|\[[0-9]{1,5}\])+$ - type: string - valuesKey: - description: |- - ValuesKey is the data key where the values.yaml or a specific value can be - found at. Defaults to 'values.yaml'. - maxLength: 253 - pattern: ^[\-._a-zA-Z0-9]+$ - type: string - required: - - kind - - name - type: object - type: array - required: - - interval - type: object - x-kubernetes-validations: - - message: either chart or chartRef must be set - rule: (has(self.chart) && !has(self.chartRef)) || (!has(self.chart) && has(self.chartRef)) - status: - default: - observedGeneration: -1 - description: HelmReleaseStatus defines the observed state of a HelmRelease. - properties: - conditions: - description: Conditions holds the conditions for the HelmRelease. - 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 - failures: - description: |- - Failures is the reconciliation failure count against the latest desired - state. It is reset after a successful reconciliation. - format: int64 - type: integer - helmChart: - description: |- - HelmChart is the namespaced name of the HelmChart resource created by - the controller for the HelmRelease. - type: string - history: - description: |- - History holds the history of Helm releases performed for this HelmRelease - up to the last successfully completed release. - items: - description: |- - Snapshot captures a point-in-time copy of the status information for a Helm release, - as managed by the controller. - properties: - apiVersion: - description: |- - APIVersion is the API version of the Snapshot. - Provisional: when the calculation method of the Digest field is changed, - this field will be used to distinguish between the old and new methods. - type: string - appVersion: - description: AppVersion is the chart app version of the release object in storage. - type: string - chartName: - description: ChartName is the chart name of the release object in storage. - type: string - chartVersion: - description: |- - ChartVersion is the chart version of the release object in - storage. - type: string - configDigest: - description: |- - ConfigDigest is the checksum of the config (better known as - "values") of the release object in storage. - It has the format of `:`. - type: string - deleted: - description: Deleted is when the release was deleted. - format: date-time - type: string - digest: - description: |- - Digest is the checksum of the release object in storage. - It has the format of `:`. - type: string - firstDeployed: - description: FirstDeployed is when the release was first deployed. - format: date-time - type: string - lastDeployed: - description: LastDeployed is when the release was last deployed. - format: date-time - type: string - name: - description: Name is the name of the release. - type: string - namespace: - description: Namespace is the namespace the release is deployed to. - type: string - ociDigest: - description: OCIDigest is the digest of the OCI artifact associated with the release. - type: string - status: - description: Status is the current state of the release. - type: string - testHooks: - additionalProperties: - description: |- - TestHookStatus holds the status information for a test hook as observed - to be run by the controller. - properties: - lastCompleted: - description: LastCompleted is the time the test hook last completed. - format: date-time - type: string - lastStarted: - description: LastStarted is the time the test hook was last started. - format: date-time - type: string - phase: - description: Phase the test hook was observed to be in. - type: string - type: object - description: |- - TestHooks is the list of test hooks for the release as observed to be - run by the controller. - type: object - version: - description: Version is the version of the release object in storage. - type: integer - required: - - chartName - - chartVersion - - configDigest - - digest - - firstDeployed - - lastDeployed - - name - - namespace - - status - - version - type: object - type: array - installFailures: - description: |- - InstallFailures is the install failure count against the latest desired - state. It is reset after a successful reconciliation. - format: int64 - type: integer - lastAppliedRevision: - description: |- - LastAppliedRevision is the revision of the last successfully applied - source. - - Deprecated: the revision can now be found in the History. - type: string - lastAttemptedConfigDigest: - description: |- - LastAttemptedConfigDigest is the digest for the config (better known as - "values") of the last reconciliation attempt. - type: string - lastAttemptedGeneration: - description: |- - LastAttemptedGeneration is the last generation the controller attempted - to reconcile. - format: int64 - type: integer - lastAttemptedReleaseAction: - description: |- - LastAttemptedReleaseAction is the last release action performed for this - HelmRelease. It is used to determine the active remediation strategy. - enum: - - install - - upgrade - type: string - lastAttemptedRevision: - description: |- - LastAttemptedRevision is the Source revision of the last reconciliation - attempt. For OCIRepository sources, the 12 first characters of the digest are - appended to the chart version e.g. "1.2.3+1234567890ab". - type: string - lastAttemptedRevisionDigest: - description: |- - LastAttemptedRevisionDigest is the digest of the last reconciliation attempt. - This is only set for OCIRepository sources. - type: string - lastAttemptedValuesChecksum: - description: |- - LastAttemptedValuesChecksum is the SHA1 checksum for the values of the last - reconciliation attempt. - - Deprecated: Use LastAttemptedConfigDigest instead. - type: string - lastHandledForceAt: - description: |- - LastHandledForceAt holds the value of the most recent force request - value, so a change of the annotation value can be detected. - type: string - lastHandledReconcileAt: - description: |- - LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value - can be detected. - type: string - lastHandledResetAt: - description: |- - LastHandledResetAt holds the value of the most recent reset request - value, so a change of the annotation value can be detected. - type: string - lastReleaseRevision: - description: |- - LastReleaseRevision is the revision of the last successful Helm release. - - Deprecated: Use History instead. - type: integer - observedGeneration: - description: ObservedGeneration is the last observed generation. - format: int64 - type: integer - observedPostRenderersDigest: - description: |- - ObservedPostRenderersDigest is the digest for the post-renderers of - the last successful reconciliation attempt. - type: string - storageNamespace: - description: |- - StorageNamespace is the namespace of the Helm release storage for the - current release. - maxLength: 63 - minLength: 1 - type: string - upgradeFailures: - description: |- - UpgradeFailures is the upgrade failure count against the latest desired - state. It is reset after a successful reconciliation. - format: int64 - type: integer - type: object - type: object - served: true - storage: false - subresources: - status: {} ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - app.kubernetes.io/role: cluster-admin - controller-gen.kubebuilder.io/version: v0.19.0 - labels: - app.kubernetes.io/name: flux - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v2.7.3 - name: helmrepositories.source.toolkit.fluxcd.io -spec: - group: source.toolkit.fluxcd.io - names: - kind: HelmRepository - listKind: HelmRepositoryList - plural: helmrepositories - shortNames: - - helmrepo - singular: helmrepository - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.url - name: URL - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - name: v1 - schema: - openAPIV3Schema: - description: HelmRepository is the Schema for the helmrepositories API. - 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: |- - HelmRepositorySpec specifies the required configuration to produce an - Artifact for a Helm repository index YAML. - properties: - accessFrom: - description: |- - AccessFrom specifies an Access Control List for allowing cross-namespace - references to this object. - NOTE: Not implemented, provisional as of https://github.com/fluxcd/flux2/pull/2092 - properties: - namespaceSelectors: - description: |- - NamespaceSelectors is the list of namespace selectors to which this ACL applies. - Items in this list are evaluated using a logical OR operation. - items: - description: |- - NamespaceSelector selects the namespaces to which this ACL applies. - An empty map of MatchLabels matches all namespaces in a cluster. - properties: - 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 - type: array - required: - - namespaceSelectors - type: object - certSecretRef: - description: |- - CertSecretRef can be given the name of a Secret containing - either or both of - - - a PEM-encoded client certificate (`tls.crt`) and private - key (`tls.key`); - - a PEM-encoded CA certificate (`ca.crt`) - - and whichever are supplied, will be used for connecting to the - registry. The client cert and key are useful if you are - authenticating with a certificate; the CA cert is useful if - you are using a self-signed server certificate. The Secret must - be of type `Opaque` or `kubernetes.io/tls`. - - It takes precedence over the values specified in the Secret referred - to by `.spec.secretRef`. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - insecure: - description: |- - Insecure allows connecting to a non-TLS HTTP container registry. - This field is only taken into account if the .spec.type field is set to 'oci'. - type: boolean - interval: - description: |- - Interval at which the HelmRepository URL is checked for updates. - This interval is approximate and may be subject to jitter to ensure - efficient use of resources. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - passCredentials: - description: |- - PassCredentials allows the credentials from the SecretRef to be passed - on to a host that does not match the host as defined in URL. - This may be required if the host of the advertised chart URLs in the - index differ from the defined URL. - Enabling this should be done with caution, as it can potentially result - in credentials getting stolen in a MITM-attack. - type: boolean - provider: - default: generic - description: |- - Provider used for authentication, can be 'aws', 'azure', 'gcp' or 'generic'. - This field is optional, and only taken into account if the .spec.type field is set to 'oci'. - When not specified, defaults to 'generic'. - enum: - - generic - - aws - - azure - - gcp - type: string - secretRef: - description: |- - SecretRef specifies the Secret containing authentication credentials - for the HelmRepository. - For HTTP/S basic auth the secret must contain 'username' and 'password' - fields. - Support for TLS auth using the 'certFile' and 'keyFile', and/or 'caFile' - keys is deprecated. Please use `.spec.certSecretRef` instead. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - suspend: - description: |- - Suspend tells the controller to suspend the reconciliation of this - HelmRepository. - type: boolean - timeout: - description: |- - Timeout is used for the index fetch operation for an HTTPS helm repository, - and for remote OCI Repository operations like pulling for an OCI helm - chart by the associated HelmChart. - Its default value is 60s. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m))+$ - type: string - type: - description: |- - Type of the HelmRepository. - When this field is set to "oci", the URL field value must be prefixed with "oci://". - enum: - - default - - oci - type: string - url: - description: |- - URL of the Helm repository, a valid URL contains at least a protocol and - host. - pattern: ^(http|https|oci)://.*$ - type: string - required: - - url - type: object - status: - default: - observedGeneration: -1 - description: HelmRepositoryStatus records the observed state of the HelmRepository. - properties: - artifact: - description: Artifact represents the last successful HelmRepository reconciliation. - properties: - digest: - description: Digest is the digest of the file in the form of ':'. - pattern: ^[a-z0-9]+(?:[.+_-][a-z0-9]+)*:[a-zA-Z0-9=_-]+$ - type: string - lastUpdateTime: - description: |- - LastUpdateTime is the timestamp corresponding to the last update of the - Artifact. - format: date-time - type: string - metadata: - additionalProperties: - type: string - description: Metadata holds upstream information such as OCI annotations. - type: object - path: - description: |- - Path is the relative file path of the Artifact. It can be used to locate - the file in the root of the Artifact storage on the local file system of - the controller managing the Source. - type: string - revision: - description: |- - Revision is a human-readable identifier traceable in the origin source - system. It can be a Git commit SHA, Git tag, a Helm chart version, etc. - type: string - size: - description: Size is the number of bytes in the file. - format: int64 - type: integer - url: - description: |- - URL is the HTTP address of the Artifact as exposed by the controller - managing the Source. It can be used to retrieve the Artifact for - consumption, e.g. by another controller applying the Artifact contents. - type: string - required: - - digest - - lastUpdateTime - - path - - revision - - url - type: object - conditions: - description: Conditions holds the conditions for the HelmRepository. - 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 - lastHandledReconcileAt: - description: |- - LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value - can be detected. - type: string - observedGeneration: - description: |- - ObservedGeneration is the last observed generation of the HelmRepository - object. - format: int64 - type: integer - url: - description: |- - URL is the dynamic fetch link for the latest Artifact. - It is provided on a "best effort" basis, and using the precise - HelmRepositoryStatus.Artifact data is recommended. - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .spec.url - name: URL - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - deprecated: true - deprecationWarning: v1beta2 HelmRepository is deprecated, upgrade to v1 - name: v1beta2 - schema: - openAPIV3Schema: - description: HelmRepository is the Schema for the helmrepositories API. - 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: |- - HelmRepositorySpec specifies the required configuration to produce an - Artifact for a Helm repository index YAML. - properties: - accessFrom: - description: |- - AccessFrom specifies an Access Control List for allowing cross-namespace - references to this object. - NOTE: Not implemented, provisional as of https://github.com/fluxcd/flux2/pull/2092 - properties: - namespaceSelectors: - description: |- - NamespaceSelectors is the list of namespace selectors to which this ACL applies. - Items in this list are evaluated using a logical OR operation. - items: - description: |- - NamespaceSelector selects the namespaces to which this ACL applies. - An empty map of MatchLabels matches all namespaces in a cluster. - properties: - 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 - type: array - required: - - namespaceSelectors - type: object - certSecretRef: - description: |- - CertSecretRef can be given the name of a Secret containing - either or both of - - - a PEM-encoded client certificate (`tls.crt`) and private - key (`tls.key`); - - a PEM-encoded CA certificate (`ca.crt`) - - and whichever are supplied, will be used for connecting to the - registry. The client cert and key are useful if you are - authenticating with a certificate; the CA cert is useful if - you are using a self-signed server certificate. The Secret must - be of type `Opaque` or `kubernetes.io/tls`. - - It takes precedence over the values specified in the Secret referred - to by `.spec.secretRef`. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - insecure: - description: |- - Insecure allows connecting to a non-TLS HTTP container registry. - This field is only taken into account if the .spec.type field is set to 'oci'. - type: boolean - interval: - description: |- - Interval at which the HelmRepository URL is checked for updates. - This interval is approximate and may be subject to jitter to ensure - efficient use of resources. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - passCredentials: - description: |- - PassCredentials allows the credentials from the SecretRef to be passed - on to a host that does not match the host as defined in URL. - This may be required if the host of the advertised chart URLs in the - index differ from the defined URL. - Enabling this should be done with caution, as it can potentially result - in credentials getting stolen in a MITM-attack. - type: boolean - provider: - default: generic - description: |- - Provider used for authentication, can be 'aws', 'azure', 'gcp' or 'generic'. - This field is optional, and only taken into account if the .spec.type field is set to 'oci'. - When not specified, defaults to 'generic'. - enum: - - generic - - aws - - azure - - gcp - type: string - secretRef: - description: |- - SecretRef specifies the Secret containing authentication credentials - for the HelmRepository. - For HTTP/S basic auth the secret must contain 'username' and 'password' - fields. - Support for TLS auth using the 'certFile' and 'keyFile', and/or 'caFile' - keys is deprecated. Please use `.spec.certSecretRef` instead. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - suspend: - description: |- - Suspend tells the controller to suspend the reconciliation of this - HelmRepository. - type: boolean - timeout: - description: |- - Timeout is used for the index fetch operation for an HTTPS helm repository, - and for remote OCI Repository operations like pulling for an OCI helm - chart by the associated HelmChart. - Its default value is 60s. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m))+$ - type: string - type: - description: |- - Type of the HelmRepository. - When this field is set to "oci", the URL field value must be prefixed with "oci://". - enum: - - default - - oci - type: string - url: - description: |- - URL of the Helm repository, a valid URL contains at least a protocol and - host. - pattern: ^(http|https|oci)://.*$ - type: string - required: - - url - type: object - status: - default: - observedGeneration: -1 - description: HelmRepositoryStatus records the observed state of the HelmRepository. - properties: - artifact: - description: Artifact represents the last successful HelmRepository reconciliation. - properties: - digest: - description: Digest is the digest of the file in the form of ':'. - pattern: ^[a-z0-9]+(?:[.+_-][a-z0-9]+)*:[a-zA-Z0-9=_-]+$ - type: string - lastUpdateTime: - description: |- - LastUpdateTime is the timestamp corresponding to the last update of the - Artifact. - format: date-time - type: string - metadata: - additionalProperties: - type: string - description: Metadata holds upstream information such as OCI annotations. - type: object - path: - description: |- - Path is the relative file path of the Artifact. It can be used to locate - the file in the root of the Artifact storage on the local file system of - the controller managing the Source. - type: string - revision: - description: |- - Revision is a human-readable identifier traceable in the origin source - system. It can be a Git commit SHA, Git tag, a Helm chart version, etc. - type: string - size: - description: Size is the number of bytes in the file. - format: int64 - type: integer - url: - description: |- - URL is the HTTP address of the Artifact as exposed by the controller - managing the Source. It can be used to retrieve the Artifact for - consumption, e.g. by another controller applying the Artifact contents. - type: string - required: - - digest - - lastUpdateTime - - path - - revision - - url - type: object - conditions: - description: Conditions holds the conditions for the HelmRepository. - 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 - lastHandledReconcileAt: - description: |- - LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value - can be detected. - type: string - observedGeneration: - description: |- - ObservedGeneration is the last observed generation of the HelmRepository - object. - format: int64 - type: integer - url: - description: |- - URL is the dynamic fetch link for the latest Artifact. - It is provided on a "best effort" basis, and using the precise - HelmRepositoryStatus.Artifact data is recommended. - type: string - type: object - type: object - served: true - storage: false - subresources: - status: {} ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - app.kubernetes.io/role: cluster-admin - controller-gen.kubebuilder.io/version: v0.19.0 - labels: - app.kubernetes.io/name: flux - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v2.7.3 - name: imagepolicies.image.toolkit.fluxcd.io -spec: - group: image.toolkit.fluxcd.io - names: - kind: ImagePolicy - listKind: ImagePolicyList - plural: imagepolicies - shortNames: - - imgpol - - imagepol - singular: imagepolicy - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .status.latestRef.name - name: Image - type: string - - jsonPath: .status.latestRef.tag - name: Tag - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1 - schema: - openAPIV3Schema: - description: ImagePolicy is the Schema for the imagepolicies API - 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: |- - ImagePolicySpec defines the parameters for calculating the - ImagePolicy. - properties: - digestReflectionPolicy: - default: Never - description: |- - DigestReflectionPolicy governs the setting of the `.status.latestRef.digest` field. - - Never: The digest field will always be set to the empty string. - - IfNotPresent: The digest field will be set to the digest of the elected - latest image if the field is empty and the image did not change. - - Always: The digest field will always be set to the digest of the elected - latest image. - - Default: Never. - enum: - - Always - - IfNotPresent - - Never - type: string - filterTags: - description: |- - FilterTags enables filtering for only a subset of tags based on a set of - rules. If no rules are provided, all the tags from the repository will be - ordered and compared. - properties: - extract: - description: |- - Extract allows a capture group to be extracted from the specified regular - expression pattern, useful before tag evaluation. - type: string - pattern: - description: |- - Pattern specifies a regular expression pattern used to filter for image - tags. - type: string - type: object - imageRepositoryRef: - description: |- - ImageRepositoryRef points at the object specifying the image - being scanned - properties: - name: - description: Name of the referent. - type: string - namespace: - description: Namespace of the referent, when not specified it acts as LocalObjectReference. - type: string - required: - - name - type: object - interval: - description: |- - Interval is the length of time to wait between - refreshing the digest of the latest tag when the - reflection policy is set to "Always". - - Defaults to 10m. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - policy: - description: |- - Policy gives the particulars of the policy to be followed in - selecting the most recent image - properties: - alphabetical: - description: Alphabetical set of rules to use for alphabetical ordering of the tags. - properties: - order: - default: asc - description: |- - Order specifies the sorting order of the tags. Given the letters of the - alphabet as tags, ascending order would select Z, and descending order - would select A. - enum: - - asc - - desc - type: string - type: object - numerical: - description: Numerical set of rules to use for numerical ordering of the tags. - properties: - order: - default: asc - description: |- - Order specifies the sorting order of the tags. Given the integer values - from 0 to 9 as tags, ascending order would select 9, and descending order - would select 0. - enum: - - asc - - desc - type: string - type: object - semver: - description: |- - SemVer gives a semantic version range to check against the tags - available. - properties: - range: - description: |- - Range gives a semver range for the image tag; the highest - version within the range that's a tag yields the latest image. - type: string - required: - - range - type: object - type: object - suspend: - description: |- - This flag tells the controller to suspend subsequent policy reconciliations. - It does not apply to already started reconciliations. Defaults to false. - type: boolean - required: - - imageRepositoryRef - - policy - type: object - x-kubernetes-validations: - - message: spec.interval is only accepted when spec.digestReflectionPolicy is set to 'Always' - rule: '!has(self.interval) || (has(self.digestReflectionPolicy) && self.digestReflectionPolicy == ''Always'')' - - message: spec.interval must be set when spec.digestReflectionPolicy is set to 'Always' - rule: has(self.interval) || !has(self.digestReflectionPolicy) || self.digestReflectionPolicy != 'Always' - status: - default: - observedGeneration: -1 - description: ImagePolicyStatus defines the observed state of ImagePolicy - 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 - lastHandledReconcileAt: - description: |- - LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value - can be detected. - type: string - latestRef: - description: |- - LatestRef gives the first in the list of images scanned by - the image repository, when filtered and ordered according - to the policy. - properties: - digest: - description: Digest is the image's digest. - type: string - name: - description: Name is the bare image's name. - type: string - tag: - description: Tag is the image's tag. - type: string - required: - - name - - tag - type: object - observedGeneration: - format: int64 - type: integer - observedPreviousRef: - description: |- - ObservedPreviousRef is the observed previous LatestRef. It is used - to keep track of the previous and current images. - properties: - digest: - description: Digest is the image's digest. - type: string - name: - description: Name is the bare image's name. - type: string - tag: - description: Tag is the image's tag. - type: string - required: - - name - - tag - type: object - type: object - type: object - served: true - storage: true - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .status.latestRef.name - name: Image - type: string - - jsonPath: .status.latestRef.tag - name: Tag - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - deprecated: true - deprecationWarning: v1beta2 ImagePolicy is deprecated, upgrade to v1 - name: v1beta2 - schema: - openAPIV3Schema: - description: ImagePolicy is the Schema for the imagepolicies API - 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: |- - ImagePolicySpec defines the parameters for calculating the - ImagePolicy. - properties: - digestReflectionPolicy: - default: Never - description: |- - DigestReflectionPolicy governs the setting of the `.status.latestRef.digest` field. - - Never: The digest field will always be set to the empty string. - - IfNotPresent: The digest field will be set to the digest of the elected - latest image if the field is empty and the image did not change. - - Always: The digest field will always be set to the digest of the elected - latest image. - - Default: Never. - enum: - - Always - - IfNotPresent - - Never - type: string - filterTags: - description: |- - FilterTags enables filtering for only a subset of tags based on a set of - rules. If no rules are provided, all the tags from the repository will be - ordered and compared. - properties: - extract: - description: |- - Extract allows a capture group to be extracted from the specified regular - expression pattern, useful before tag evaluation. - type: string - pattern: - description: |- - Pattern specifies a regular expression pattern used to filter for image - tags. - type: string - type: object - imageRepositoryRef: - description: |- - ImageRepositoryRef points at the object specifying the image - being scanned - properties: - name: - description: Name of the referent. - type: string - namespace: - description: Namespace of the referent, when not specified it acts as LocalObjectReference. - type: string - required: - - name - type: object - interval: - description: |- - Interval is the length of time to wait between - refreshing the digest of the latest tag when the - reflection policy is set to "Always". - - Defaults to 10m. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - policy: - description: |- - Policy gives the particulars of the policy to be followed in - selecting the most recent image - properties: - alphabetical: - description: Alphabetical set of rules to use for alphabetical ordering of the tags. - properties: - order: - default: asc - description: |- - Order specifies the sorting order of the tags. Given the letters of the - alphabet as tags, ascending order would select Z, and descending order - would select A. - enum: - - asc - - desc - type: string - type: object - numerical: - description: Numerical set of rules to use for numerical ordering of the tags. - properties: - order: - default: asc - description: |- - Order specifies the sorting order of the tags. Given the integer values - from 0 to 9 as tags, ascending order would select 9, and descending order - would select 0. - enum: - - asc - - desc - type: string - type: object - semver: - description: |- - SemVer gives a semantic version range to check against the tags - available. - properties: - range: - description: |- - Range gives a semver range for the image tag; the highest - version within the range that's a tag yields the latest image. - type: string - required: - - range - type: object - type: object - suspend: - description: |- - This flag tells the controller to suspend subsequent policy reconciliations. - It does not apply to already started reconciliations. Defaults to false. - type: boolean - required: - - imageRepositoryRef - - policy - type: object - x-kubernetes-validations: - - message: spec.interval is only accepted when spec.digestReflectionPolicy is set to 'Always' - rule: '!has(self.interval) || (has(self.digestReflectionPolicy) && self.digestReflectionPolicy == ''Always'')' - - message: spec.interval must be set when spec.digestReflectionPolicy is set to 'Always' - rule: has(self.interval) || !has(self.digestReflectionPolicy) || self.digestReflectionPolicy != 'Always' - status: - default: - observedGeneration: -1 - description: ImagePolicyStatus defines the observed state of ImagePolicy - 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 - lastHandledReconcileAt: - description: |- - LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value - can be detected. - type: string - latestRef: - description: |- - LatestRef gives the first in the list of images scanned by - the image repository, when filtered and ordered according - to the policy. - properties: - digest: - description: Digest is the image's digest. - type: string - name: - description: Name is the bare image's name. - type: string - tag: - description: Tag is the image's tag. - type: string - required: - - name - - tag - type: object - observedGeneration: - format: int64 - type: integer - observedPreviousRef: - description: |- - ObservedPreviousRef is the observed previous LatestRef. It is used - to keep track of the previous and current images. - properties: - digest: - description: Digest is the image's digest. - type: string - name: - description: Name is the bare image's name. - type: string - tag: - description: Tag is the image's tag. - type: string - required: - - name - - tag - type: object - type: object - type: object - served: true - storage: false - subresources: - status: {} ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - app.kubernetes.io/role: cluster-admin - controller-gen.kubebuilder.io/version: v0.19.0 - labels: - app.kubernetes.io/name: flux - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v2.7.3 - name: imagerepositories.image.toolkit.fluxcd.io -spec: - group: image.toolkit.fluxcd.io - names: - kind: ImageRepository - listKind: ImageRepositoryList - plural: imagerepositories - shortNames: - - imgrepo - - imagerepo - singular: imagerepository - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.image - name: Image - type: string - - jsonPath: .status.lastScanResult.tagCount - name: Tags - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - - jsonPath: .status.lastScanResult.scanTime - name: Last scan - priority: 1 - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1 - schema: - openAPIV3Schema: - description: ImageRepository is the Schema for the imagerepositories API - 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: |- - ImageRepositorySpec defines the parameters for scanning an image - repository, e.g., `fluxcd/flux`. - properties: - accessFrom: - description: |- - AccessFrom defines an ACL for allowing cross-namespace references - to the ImageRepository object based on the caller's namespace labels. - properties: - namespaceSelectors: - description: |- - NamespaceSelectors is the list of namespace selectors to which this ACL applies. - Items in this list are evaluated using a logical OR operation. - items: - description: |- - NamespaceSelector selects the namespaces to which this ACL applies. - An empty map of MatchLabels matches all namespaces in a cluster. - properties: - 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 - type: array - required: - - namespaceSelectors - type: object - certSecretRef: - description: |- - CertSecretRef can be given the name of a Secret containing - either or both of - - - a PEM-encoded client certificate (`tls.crt`) and private - key (`tls.key`); - - a PEM-encoded CA certificate (`ca.crt`) - - and whichever are supplied, will be used for connecting to the - registry. The client cert and key are useful if you are - authenticating with a certificate; the CA cert is useful if - you are using a self-signed server certificate. The Secret must - be of type `Opaque` or `kubernetes.io/tls`. - - Note: Support for the `caFile`, `certFile` and `keyFile` keys has - been deprecated. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - exclusionList: - default: - - ^.*\.sig$ - description: |- - ExclusionList is a list of regex strings used to exclude certain tags - from being stored in the database. - items: - type: string - maxItems: 25 - type: array - image: - description: Image is the name of the image repository - type: string - insecure: - description: Insecure allows connecting to a non-TLS HTTP container registry. - type: boolean - interval: - description: |- - Interval is the length of time to wait between - scans of the image repository. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - provider: - default: generic - description: |- - The provider used for authentication, can be 'aws', 'azure', 'gcp' or 'generic'. - When not specified, defaults to 'generic'. - enum: - - generic - - aws - - azure - - gcp - type: string - proxySecretRef: - description: |- - ProxySecretRef specifies the Secret containing the proxy configuration - to use while communicating with the container registry. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - secretRef: - description: |- - SecretRef can be given the name of a secret containing - credentials to use for the image registry. The secret should be - created with `kubectl create secret docker-registry`, or the - equivalent. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - serviceAccountName: - description: |- - ServiceAccountName is the name of the Kubernetes ServiceAccount used to authenticate - the image pull if the service account has attached pull secrets. - maxLength: 253 - type: string - suspend: - description: |- - This flag tells the controller to suspend subsequent image scans. - It does not apply to already started scans. Defaults to false. - type: boolean - timeout: - description: |- - Timeout for image scanning. - Defaults to 'Interval' duration. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m))+$ - type: string - required: - - image - - interval - type: object - status: - default: - observedGeneration: -1 - description: ImageRepositoryStatus defines the observed state of ImageRepository - properties: - canonicalImageName: - description: |- - CanonicalName is the name of the image repository with all the - implied bits made explicit; e.g., `docker.io/library/alpine` - rather than `alpine`. - type: string - 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 - lastHandledReconcileAt: - description: |- - LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value - can be detected. - type: string - lastScanResult: - description: LastScanResult contains the number of fetched tags. - properties: - latestTags: - description: |- - LatestTags is a small sample of the tags found in the last scan. - It's the first 10 tags when sorting all the tags in descending - alphabetical order. - items: - type: string - type: array - revision: - description: Revision is a stable hash of the scanned tags. - type: string - scanTime: - description: ScanTime is the time when the last scan was performed. - format: date-time - type: string - tagCount: - description: TagCount is the number of tags found in the last scan. - type: integer - required: - - tagCount - type: object - observedExclusionList: - description: |- - ObservedExclusionList is a list of observed exclusion list. It reflects - the exclusion rules used for the observed scan result in - spec.lastScanResult. - items: - type: string - type: array - observedGeneration: - description: ObservedGeneration is the last reconciled generation. - format: int64 - type: integer - type: object - type: object - served: true - storage: true - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .spec.image - name: Image - type: string - - jsonPath: .status.lastScanResult.tagCount - name: Tags - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - - jsonPath: .status.lastScanResult.scanTime - name: Last scan - priority: 1 - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - deprecated: true - deprecationWarning: v1beta2 ImageRepository is deprecated, upgrade to v1 - name: v1beta2 - schema: - openAPIV3Schema: - description: ImageRepository is the Schema for the imagerepositories API - 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: |- - ImageRepositorySpec defines the parameters for scanning an image - repository, e.g., `fluxcd/flux`. - properties: - accessFrom: - description: |- - AccessFrom defines an ACL for allowing cross-namespace references - to the ImageRepository object based on the caller's namespace labels. - properties: - namespaceSelectors: - description: |- - NamespaceSelectors is the list of namespace selectors to which this ACL applies. - Items in this list are evaluated using a logical OR operation. - items: - description: |- - NamespaceSelector selects the namespaces to which this ACL applies. - An empty map of MatchLabels matches all namespaces in a cluster. - properties: - 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 - type: array - required: - - namespaceSelectors - type: object - certSecretRef: - description: |- - CertSecretRef can be given the name of a Secret containing - either or both of - - - a PEM-encoded client certificate (`tls.crt`) and private - key (`tls.key`); - - a PEM-encoded CA certificate (`ca.crt`) - - and whichever are supplied, will be used for connecting to the - registry. The client cert and key are useful if you are - authenticating with a certificate; the CA cert is useful if - you are using a self-signed server certificate. The Secret must - be of type `Opaque` or `kubernetes.io/tls`. - - Note: Support for the `caFile`, `certFile` and `keyFile` keys has - been deprecated. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - exclusionList: - default: - - ^.*\.sig$ - description: |- - ExclusionList is a list of regex strings used to exclude certain tags - from being stored in the database. - items: - type: string - maxItems: 25 - type: array - image: - description: Image is the name of the image repository - type: string - insecure: - description: Insecure allows connecting to a non-TLS HTTP container registry. - type: boolean - interval: - description: |- - Interval is the length of time to wait between - scans of the image repository. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - provider: - default: generic - description: |- - The provider used for authentication, can be 'aws', 'azure', 'gcp' or 'generic'. - When not specified, defaults to 'generic'. - enum: - - generic - - aws - - azure - - gcp - type: string - proxySecretRef: - description: |- - ProxySecretRef specifies the Secret containing the proxy configuration - to use while communicating with the container registry. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - secretRef: - description: |- - SecretRef can be given the name of a secret containing - credentials to use for the image registry. The secret should be - created with `kubectl create secret docker-registry`, or the - equivalent. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - serviceAccountName: - description: |- - ServiceAccountName is the name of the Kubernetes ServiceAccount used to authenticate - the image pull if the service account has attached pull secrets. - maxLength: 253 - type: string - suspend: - description: |- - This flag tells the controller to suspend subsequent image scans. - It does not apply to already started scans. Defaults to false. - type: boolean - timeout: - description: |- - Timeout for image scanning. - Defaults to 'Interval' duration. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m))+$ - type: string - required: - - image - - interval - type: object - status: - default: - observedGeneration: -1 - description: ImageRepositoryStatus defines the observed state of ImageRepository - properties: - canonicalImageName: - description: |- - CanonicalName is the name of the image repository with all the - implied bits made explicit; e.g., `docker.io/library/alpine` - rather than `alpine`. - type: string - 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 - lastHandledReconcileAt: - description: |- - LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value - can be detected. - type: string - lastScanResult: - description: LastScanResult contains the number of fetched tags. - properties: - latestTags: - description: |- - LatestTags is a small sample of the tags found in the last scan. - It's the first 10 tags when sorting all the tags in descending - alphabetical order. - items: - type: string - type: array - revision: - description: Revision is a stable hash of the scanned tags. - type: string - scanTime: - description: ScanTime is the time when the last scan was performed. - format: date-time - type: string - tagCount: - description: TagCount is the number of tags found in the last scan. - type: integer - required: - - tagCount - type: object - observedExclusionList: - description: |- - ObservedExclusionList is a list of observed exclusion list. It reflects - the exclusion rules used for the observed scan result in - spec.lastScanResult. - items: - type: string - type: array - observedGeneration: - description: ObservedGeneration is the last reconciled generation. - format: int64 - type: integer - type: object - type: object - served: true - storage: false - subresources: - status: {} ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - app.kubernetes.io/role: cluster-admin - controller-gen.kubebuilder.io/version: v0.19.0 - labels: - app.kubernetes.io/name: flux - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v2.7.3 - name: imageupdateautomations.image.toolkit.fluxcd.io -spec: - group: image.toolkit.fluxcd.io - names: - kind: ImageUpdateAutomation - listKind: ImageUpdateAutomationList - plural: imageupdateautomations - shortNames: - - iua - - imgupd - - imgauto - singular: imageupdateautomation - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - - jsonPath: .status.lastAutomationRunTime - name: Last run - priority: 1 - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1 - schema: - openAPIV3Schema: - description: ImageUpdateAutomation is the Schema for the imageupdateautomations API - 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: ImageUpdateAutomationSpec defines the desired state of ImageUpdateAutomation - properties: - git: - description: |- - GitSpec contains all the git-specific definitions. This is - technically optional, but in practice mandatory until there are - other kinds of source allowed. - properties: - checkout: - description: |- - Checkout gives the parameters for cloning the git repository, - ready to make changes. If not present, the `spec.ref` field from the - referenced `GitRepository` or its default will be used. - properties: - ref: - description: |- - Reference gives a branch, tag or commit to clone from the Git - repository. - properties: - branch: - description: Branch to check out, defaults to 'master' if no other field is defined. - type: string - commit: - description: |- - Commit SHA to check out, takes precedence over all reference fields. - - This can be combined with Branch to shallow clone the branch, in which - the commit is expected to exist. - type: string - name: - description: |- - Name of the reference to check out; takes precedence over Branch, Tag and SemVer. - - It must be a valid Git reference: https://git-scm.com/docs/git-check-ref-format#_description - Examples: "refs/heads/main", "refs/tags/v0.1.0", "refs/pull/420/head", "refs/merge-requests/1/head" - type: string - semver: - description: SemVer tag expression to check out, takes precedence over Tag. - type: string - tag: - description: Tag to check out, takes precedence over Branch. - type: string - type: object - required: - - ref - type: object - commit: - description: Commit specifies how to commit to the git repository. - properties: - author: - description: |- - Author gives the email and optionally the name to use as the - author of commits. - properties: - email: - description: Email gives the email to provide when making a commit. - type: string - name: - description: Name gives the name to provide when making a commit. - type: string - required: - - email - type: object - messageTemplate: - description: |- - MessageTemplate provides a template for the commit message, - into which will be interpolated the details of the change made. - Note: The `Updated` template field has been removed. Use `Changed` instead. - type: string - messageTemplateValues: - additionalProperties: - type: string - description: |- - MessageTemplateValues provides additional values to be available to the - templating rendering. - type: object - signingKey: - description: SigningKey provides the option to sign commits with a GPG key - properties: - secretRef: - description: |- - SecretRef holds the name to a secret that contains a 'git.asc' key - corresponding to the ASCII Armored file containing the GPG signing - keypair as the value. It must be in the same namespace as the - ImageUpdateAutomation. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - required: - - secretRef - type: object - required: - - author - type: object - push: - description: |- - Push specifies how and where to push commits made by the - automation. If missing, commits are pushed (back) to - `.spec.checkout.branch` or its default. - properties: - branch: - description: |- - Branch specifies that commits should be pushed to the branch - named. The branch is created using `.spec.checkout.branch` as the - starting point, if it doesn't already exist. - type: string - options: - additionalProperties: - type: string - description: |- - Options specifies the push options that are sent to the Git - server when performing a push operation. For details, see: - https://git-scm.com/docs/git-push#Documentation/git-push.txt---push-optionltoptiongt - type: object - refspec: - description: |- - Refspec specifies the Git Refspec to use for a push operation. - If both Branch and Refspec are provided, then the commit is pushed - to the branch and also using the specified refspec. - For more details about Git Refspecs, see: - https://git-scm.com/book/en/v2/Git-Internals-The-Refspec - type: string - type: object - required: - - commit - type: object - interval: - description: |- - Interval gives an lower bound for how often the automation - run should be attempted. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - policySelector: - description: |- - PolicySelector allows to filter applied policies based on labels. - By default includes all policies in namespace. - 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 - sourceRef: - description: |- - SourceRef refers to the resource giving access details - to a git repository. - properties: - apiVersion: - description: API version of the referent. - type: string - kind: - default: GitRepository - description: Kind of the referent. - enum: - - GitRepository - type: string - name: - description: Name of the referent. - type: string - namespace: - description: Namespace of the referent, defaults to the namespace of the Kubernetes resource object that contains the reference. - type: string - required: - - kind - - name - type: object - suspend: - description: |- - Suspend tells the controller to not run this automation, until - it is unset (or set to false). Defaults to false. - type: boolean - update: - default: - strategy: Setters - description: |- - Update gives the specification for how to update the files in - the repository. This can be left empty, to use the default - value. - properties: - path: - description: |- - Path to the directory containing the manifests to be updated. - Defaults to 'None', which translates to the root path - of the GitRepositoryRef. - type: string - strategy: - default: Setters - description: Strategy names the strategy to be used. - enum: - - Setters - type: string - type: object - required: - - interval - - sourceRef - type: object - status: - default: - observedGeneration: -1 - description: ImageUpdateAutomationStatus defines the observed state of ImageUpdateAutomation - 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 - lastAutomationRunTime: - description: |- - LastAutomationRunTime records the last time the controller ran - this automation through to completion (even if no updates were - made). - format: date-time - type: string - lastHandledReconcileAt: - description: |- - LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value - can be detected. - type: string - lastPushCommit: - description: |- - LastPushCommit records the SHA1 of the last commit made by the - controller, for this automation object - type: string - lastPushTime: - description: LastPushTime records the time of the last pushed change. - format: date-time - type: string - observedGeneration: - format: int64 - type: integer - observedPolicies: - additionalProperties: - description: ImageRef represents an image reference. - properties: - digest: - description: Digest is the image's digest. - type: string - name: - description: Name is the bare image's name. - type: string - tag: - description: Tag is the image's tag. - type: string - required: - - name - - tag - type: object - description: |- - ObservedPolicies is the list of observed ImagePolicies that were - considered by the ImageUpdateAutomation update process. - type: object - observedSourceRevision: - description: |- - ObservedPolicies []ObservedPolicy `json:"observedPolicies,omitempty"` - ObservedSourceRevision is the last observed source revision. This can be - used to determine if the source has been updated since last observation. - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - - jsonPath: .status.lastAutomationRunTime - name: Last run - priority: 1 - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - deprecated: true - deprecationWarning: v1beta2 ImageUpdateAutomation is deprecated, upgrade to v1 - name: v1beta2 - schema: - openAPIV3Schema: - description: ImageUpdateAutomation is the Schema for the imageupdateautomations API - 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: ImageUpdateAutomationSpec defines the desired state of ImageUpdateAutomation - properties: - git: - description: |- - GitSpec contains all the git-specific definitions. This is - technically optional, but in practice mandatory until there are - other kinds of source allowed. - properties: - checkout: - description: |- - Checkout gives the parameters for cloning the git repository, - ready to make changes. If not present, the `spec.ref` field from the - referenced `GitRepository` or its default will be used. - properties: - ref: - description: |- - Reference gives a branch, tag or commit to clone from the Git - repository. - properties: - branch: - description: Branch to check out, defaults to 'master' if no other field is defined. - type: string - commit: - description: |- - Commit SHA to check out, takes precedence over all reference fields. - - This can be combined with Branch to shallow clone the branch, in which - the commit is expected to exist. - type: string - name: - description: |- - Name of the reference to check out; takes precedence over Branch, Tag and SemVer. - - It must be a valid Git reference: https://git-scm.com/docs/git-check-ref-format#_description - Examples: "refs/heads/main", "refs/tags/v0.1.0", "refs/pull/420/head", "refs/merge-requests/1/head" - type: string - semver: - description: SemVer tag expression to check out, takes precedence over Tag. - type: string - tag: - description: Tag to check out, takes precedence over Branch. - type: string - type: object - required: - - ref - type: object - commit: - description: Commit specifies how to commit to the git repository. - properties: - author: - description: |- - Author gives the email and optionally the name to use as the - author of commits. - properties: - email: - description: Email gives the email to provide when making a commit. - type: string - name: - description: Name gives the name to provide when making a commit. - type: string - required: - - email - type: object - messageTemplate: - description: |- - MessageTemplate provides a template for the commit message, - into which will be interpolated the details of the change made. - Note: The `Updated` template field has been removed. Use `Changed` instead. - type: string - messageTemplateValues: - additionalProperties: - type: string - description: |- - MessageTemplateValues provides additional values to be available to the - templating rendering. - type: object - signingKey: - description: SigningKey provides the option to sign commits with a GPG key - properties: - secretRef: - description: |- - SecretRef holds the name to a secret that contains a 'git.asc' key - corresponding to the ASCII Armored file containing the GPG signing - keypair as the value. It must be in the same namespace as the - ImageUpdateAutomation. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - required: - - secretRef - type: object - required: - - author - type: object - push: - description: |- - Push specifies how and where to push commits made by the - automation. If missing, commits are pushed (back) to - `.spec.checkout.branch` or its default. - properties: - branch: - description: |- - Branch specifies that commits should be pushed to the branch - named. The branch is created using `.spec.checkout.branch` as the - starting point, if it doesn't already exist. - type: string - options: - additionalProperties: - type: string - description: |- - Options specifies the push options that are sent to the Git - server when performing a push operation. For details, see: - https://git-scm.com/docs/git-push#Documentation/git-push.txt---push-optionltoptiongt - type: object - refspec: - description: |- - Refspec specifies the Git Refspec to use for a push operation. - If both Branch and Refspec are provided, then the commit is pushed - to the branch and also using the specified refspec. - For more details about Git Refspecs, see: - https://git-scm.com/book/en/v2/Git-Internals-The-Refspec - type: string - type: object - required: - - commit - type: object - interval: - description: |- - Interval gives an lower bound for how often the automation - run should be attempted. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - policySelector: - description: |- - PolicySelector allows to filter applied policies based on labels. - By default includes all policies in namespace. - 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 - sourceRef: - description: |- - SourceRef refers to the resource giving access details - to a git repository. - properties: - apiVersion: - description: API version of the referent. - type: string - kind: - default: GitRepository - description: Kind of the referent. - enum: - - GitRepository - type: string - name: - description: Name of the referent. - type: string - namespace: - description: Namespace of the referent, defaults to the namespace of the Kubernetes resource object that contains the reference. - type: string - required: - - kind - - name - type: object - suspend: - description: |- - Suspend tells the controller to not run this automation, until - it is unset (or set to false). Defaults to false. - type: boolean - update: - default: - strategy: Setters - description: |- - Update gives the specification for how to update the files in - the repository. This can be left empty, to use the default - value. - properties: - path: - description: |- - Path to the directory containing the manifests to be updated. - Defaults to 'None', which translates to the root path - of the GitRepositoryRef. - type: string - strategy: - default: Setters - description: Strategy names the strategy to be used. - enum: - - Setters - type: string - type: object - required: - - interval - - sourceRef - type: object - status: - default: - observedGeneration: -1 - description: ImageUpdateAutomationStatus defines the observed state of ImageUpdateAutomation - 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 - lastAutomationRunTime: - description: |- - LastAutomationRunTime records the last time the controller ran - this automation through to completion (even if no updates were - made). - format: date-time - type: string - lastHandledReconcileAt: - description: |- - LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value - can be detected. - type: string - lastPushCommit: - description: |- - LastPushCommit records the SHA1 of the last commit made by the - controller, for this automation object - type: string - lastPushTime: - description: LastPushTime records the time of the last pushed change. - format: date-time - type: string - observedGeneration: - format: int64 - type: integer - observedPolicies: - additionalProperties: - description: ImageRef represents an image reference. - properties: - digest: - description: Digest is the image's digest. - type: string - name: - description: Name is the bare image's name. - type: string - tag: - description: Tag is the image's tag. - type: string - required: - - name - - tag - type: object - description: |- - ObservedPolicies is the list of observed ImagePolicies that were - considered by the ImageUpdateAutomation update process. - type: object - observedSourceRevision: - description: |- - ObservedPolicies []ObservedPolicy `json:"observedPolicies,omitempty"` - ObservedSourceRevision is the last observed source revision. This can be - used to determine if the source has been updated since last observation. - type: string - type: object - type: object - served: true - storage: false - subresources: - status: {} ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - app.kubernetes.io/role: cluster-admin - controller-gen.kubebuilder.io/version: v0.19.0 - labels: - app.kubernetes.io/name: flux - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v2.7.3 - name: kustomizations.kustomize.toolkit.fluxcd.io -spec: - group: kustomize.toolkit.fluxcd.io - names: - kind: Kustomization - listKind: KustomizationList - plural: kustomizations - shortNames: - - ks - singular: kustomization - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - name: v1 - schema: - openAPIV3Schema: - description: Kustomization is the Schema for the kustomizations API. - 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: |- - KustomizationSpec defines the configuration to calculate the desired state - from a Source using Kustomize. - properties: - commonMetadata: - description: |- - CommonMetadata specifies the common labels and annotations that are - applied to all resources. Any existing label or annotation will be - overridden if its key matches a common one. - properties: - annotations: - additionalProperties: - type: string - description: Annotations to be added to the object's metadata. - type: object - labels: - additionalProperties: - type: string - description: Labels to be added to the object's metadata. - type: object - type: object - components: - description: Components specifies relative paths to kustomize Components. - items: - type: string - type: array - decryption: - description: Decrypt Kubernetes secrets before applying them on the cluster. - properties: - provider: - description: Provider is the name of the decryption engine. - enum: - - sops - type: string - secretRef: - description: |- - The secret name containing the private OpenPGP keys used for decryption. - A static credential for a cloud provider defined inside the Secret - takes priority to secret-less authentication with the ServiceAccountName - field. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - serviceAccountName: - description: |- - ServiceAccountName is the name of the service account used to - authenticate with KMS services from cloud providers. If a - static credential for a given cloud provider is defined - inside the Secret referenced by SecretRef, that static - credential takes priority. - type: string - required: - - provider - type: object - deletionPolicy: - description: |- - DeletionPolicy can be used to control garbage collection when this - Kustomization is deleted. Valid values are ('MirrorPrune', 'Delete', - 'WaitForTermination', 'Orphan'). 'MirrorPrune' mirrors the Prune field - (orphan if false, delete if true). Defaults to 'MirrorPrune'. - enum: - - MirrorPrune - - Delete - - WaitForTermination - - Orphan - type: string - dependsOn: - description: |- - DependsOn may contain a DependencyReference slice - with references to Kustomization resources that must be ready before this - Kustomization can be reconciled. - items: - description: DependencyReference defines a Kustomization dependency on another Kustomization resource. - properties: - name: - description: Name of the referent. - type: string - namespace: - description: |- - Namespace of the referent, defaults to the namespace of the Kustomization - resource object that contains the reference. - type: string - readyExpr: - description: |- - ReadyExpr is a CEL expression that can be used to assess the readiness - of a dependency. When specified, the built-in readiness check - is replaced by the logic defined in the CEL expression. - To make the CEL expression additive to the built-in readiness check, - the feature gate `AdditiveCELDependencyCheck` must be set to `true`. - type: string - required: - - name - type: object - type: array - force: - default: false - description: |- - Force instructs the controller to recreate resources - when patching fails due to an immutable field change. - type: boolean - healthCheckExprs: - description: |- - HealthCheckExprs is a list of healthcheck expressions for evaluating the - health of custom resources using Common Expression Language (CEL). - The expressions are evaluated only when Wait or HealthChecks are specified. - items: - description: CustomHealthCheck defines the health check for custom resources. - properties: - apiVersion: - description: APIVersion of the custom resource under evaluation. - type: string - current: - description: |- - Current is the CEL expression that determines if the status - of the custom resource has reached the desired state. - type: string - failed: - description: |- - Failed is the CEL expression that determines if the status - of the custom resource has failed to reach the desired state. - type: string - inProgress: - description: |- - InProgress is the CEL expression that determines if the status - of the custom resource has not yet reached the desired state. - type: string - kind: - description: Kind of the custom resource under evaluation. - type: string - required: - - apiVersion - - current - - kind - type: object - type: array - healthChecks: - description: A list of resources to be included in the health assessment. - items: - description: |- - NamespacedObjectKindReference contains enough information to locate the typed referenced Kubernetes resource object - in any namespace. - properties: - apiVersion: - description: API version of the referent, if not specified the Kubernetes preferred version will be used. - type: string - kind: - description: Kind of the referent. - type: string - name: - description: Name of the referent. - type: string - namespace: - description: Namespace of the referent, when not specified it acts as LocalObjectReference. - type: string - required: - - kind - - name - type: object - type: array - ignoreMissingComponents: - description: |- - IgnoreMissingComponents instructs the controller to ignore Components paths - not found in source by removing them from the generated kustomization.yaml - before running kustomize build. - type: boolean - images: - description: |- - Images is a list of (image name, new name, new tag or digest) - for changing image names, tags or digests. This can also be achieved with a - patch, but this operator is simpler to specify. - items: - description: Image contains an image name, a new name, a new tag or digest, which will replace the original name and tag. - properties: - digest: - description: |- - Digest is the value used to replace the original image tag. - If digest is present NewTag value is ignored. - type: string - name: - description: Name is a tag-less image name. - type: string - newName: - description: NewName is the value used to replace the original name. - type: string - newTag: - description: NewTag is the value used to replace the original tag. - type: string - required: - - name - type: object - type: array - interval: - description: |- - The interval at which to reconcile the Kustomization. - This interval is approximate and may be subject to jitter to ensure - efficient use of resources. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - kubeConfig: - description: |- - The KubeConfig for reconciling the Kustomization on a remote cluster. - When used in combination with KustomizationSpec.ServiceAccountName, - forces the controller to act on behalf of that Service Account at the - target cluster. - If the --default-service-account flag is set, its value will be used as - a controller level fallback for when KustomizationSpec.ServiceAccountName - is empty. - properties: - configMapRef: - description: |- - ConfigMapRef holds an optional name of a ConfigMap that contains - the following keys: - - - `provider`: the provider to use. One of `aws`, `azure`, `gcp`, or - `generic`. Required. - - `cluster`: the fully qualified resource name of the Kubernetes - cluster in the cloud provider API. Not used by the `generic` - provider. Required when one of `address` or `ca.crt` is not set. - - `address`: the address of the Kubernetes API server. Required - for `generic`. For the other providers, if not specified, the - first address in the cluster resource will be used, and if - specified, it must match one of the addresses in the cluster - resource. - If audiences is not set, will be used as the audience for the - `generic` provider. - - `ca.crt`: the optional PEM-encoded CA certificate for the - Kubernetes API server. If not set, the controller will use the - CA certificate from the cluster resource. - - `audiences`: the optional audiences as a list of - line-break-separated strings for the Kubernetes ServiceAccount - token. Defaults to the `address` for the `generic` provider, or - to specific values for the other providers depending on the - provider. - - `serviceAccountName`: the optional name of the Kubernetes - ServiceAccount in the same namespace that should be used - for authentication. If not specified, the controller - ServiceAccount will be used. - - Mutually exclusive with SecretRef. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - secretRef: - description: |- - SecretRef holds an optional name of a secret that contains a key with - the kubeconfig file as the value. If no key is set, the key will default - to 'value'. Mutually exclusive with ConfigMapRef. - It is recommended that the kubeconfig is self-contained, and the secret - is regularly updated if credentials such as a cloud-access-token expire. - Cloud specific `cmd-path` auth helpers will not function without adding - binaries and credentials to the Pod that is responsible for reconciling - Kubernetes resources. Supported only for the generic provider. - properties: - key: - description: Key in the Secret, when not specified an implementation-specific default key is used. - type: string - name: - description: Name of the Secret. - type: string - required: - - name - type: object - type: object - x-kubernetes-validations: - - message: exactly one of spec.kubeConfig.configMapRef or spec.kubeConfig.secretRef must be specified - rule: has(self.configMapRef) || has(self.secretRef) - - message: exactly one of spec.kubeConfig.configMapRef or spec.kubeConfig.secretRef must be specified - rule: '!has(self.configMapRef) || !has(self.secretRef)' - namePrefix: - description: NamePrefix will prefix the names of all managed resources. - maxLength: 200 - minLength: 1 - type: string - nameSuffix: - description: NameSuffix will suffix the names of all managed resources. - maxLength: 200 - minLength: 1 - type: string - patches: - description: |- - Strategic merge and JSON patches, defined as inline YAML objects, - capable of targeting objects based on kind, label and annotation selectors. - items: - description: |- - Patch contains an inline StrategicMerge or JSON6902 patch, and the target the patch should - be applied to. - properties: - patch: - description: |- - Patch contains an inline StrategicMerge patch or an inline JSON6902 patch with - an array of operation objects. - type: string - target: - description: Target points to the resources that the patch document should be applied to. - properties: - annotationSelector: - description: |- - AnnotationSelector is a string that follows the label selection expression - https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api - It matches with the resource annotations. - type: string - group: - description: |- - Group is the API group to select resources from. - Together with Version and Kind it is capable of unambiguously identifying and/or selecting resources. - https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - kind: - description: |- - Kind of the API Group to select resources from. - Together with Group and Version it is capable of unambiguously - identifying and/or selecting resources. - https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - labelSelector: - description: |- - LabelSelector is a string that follows the label selection expression - https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api - It matches with the resource labels. - type: string - name: - description: Name to match resources with. - type: string - namespace: - description: Namespace to select resources from. - type: string - version: - description: |- - Version of the API Group to select resources from. - Together with Group and Kind it is capable of unambiguously identifying and/or selecting resources. - https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - type: object - required: - - patch - type: object - type: array - path: - description: |- - Path to the directory containing the kustomization.yaml file, or the - set of plain YAMLs a kustomization.yaml should be generated for. - Defaults to 'None', which translates to the root path of the SourceRef. - type: string - postBuild: - description: |- - PostBuild describes which actions to perform on the YAML manifest - generated by building the kustomize overlay. - properties: - substitute: - additionalProperties: - type: string - description: |- - Substitute holds a map of key/value pairs. - The variables defined in your YAML manifests that match any of the keys - defined in the map will be substituted with the set value. - Includes support for bash string replacement functions - e.g. ${var:=default}, ${var:position} and ${var/substring/replacement}. - type: object - substituteFrom: - description: |- - SubstituteFrom holds references to ConfigMaps and Secrets containing - the variables and their values to be substituted in the YAML manifests. - The ConfigMap and the Secret data keys represent the var names, and they - must match the vars declared in the manifests for the substitution to - happen. - items: - description: |- - SubstituteReference contains a reference to a resource containing - the variables name and value. - properties: - kind: - description: Kind of the values referent, valid values are ('Secret', 'ConfigMap'). - enum: - - Secret - - ConfigMap - type: string - name: - description: |- - Name of the values referent. Should reside in the same namespace as the - referring resource. - maxLength: 253 - minLength: 1 - type: string - optional: - default: false - description: |- - Optional indicates whether the referenced resource must exist, or whether to - tolerate its absence. If true and the referenced resource is absent, proceed - as if the resource was present but empty, without any variables defined. - type: boolean - required: - - kind - - name - type: object - type: array - type: object - prune: - description: Prune enables garbage collection. - type: boolean - retryInterval: - description: |- - The interval at which to retry a previously failed reconciliation. - When not specified, the controller uses the KustomizationSpec.Interval - value to retry failures. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - serviceAccountName: - description: |- - The name of the Kubernetes service account to impersonate - when reconciling this Kustomization. - type: string - sourceRef: - description: Reference of the source where the kustomization file is. - properties: - apiVersion: - description: API version of the referent. - type: string - kind: - description: Kind of the referent. - enum: - - OCIRepository - - GitRepository - - Bucket - - ExternalArtifact - type: string - name: - description: Name of the referent. - type: string - namespace: - description: |- - Namespace of the referent, defaults to the namespace of the Kubernetes - resource object that contains the reference. - type: string - required: - - kind - - name - type: object - suspend: - description: |- - This flag tells the controller to suspend subsequent kustomize executions, - it does not apply to already started executions. Defaults to false. - type: boolean - targetNamespace: - description: |- - TargetNamespace sets or overrides the namespace in the - kustomization.yaml file. - maxLength: 63 - minLength: 1 - type: string - timeout: - description: |- - Timeout for validation, apply and health checking operations. - Defaults to 'Interval' duration. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - wait: - description: |- - Wait instructs the controller to check the health of all the reconciled - resources. When enabled, the HealthChecks are ignored. Defaults to false. - type: boolean - required: - - interval - - prune - - sourceRef - type: object - status: - default: - observedGeneration: -1 - description: KustomizationStatus defines the observed state of a kustomization. - 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 - history: - description: |- - History contains a set of snapshots of the last reconciliation attempts - tracking the revision, the state and the duration of each attempt. - items: - description: |- - Snapshot represents a point-in-time record of a group of resources reconciliation, - including timing information, status, and a unique digest identifier. - properties: - digest: - description: Digest is the checksum in the format `:` of the resources in this snapshot. - type: string - firstReconciled: - description: FirstReconciled is the time when this revision was first reconciled to the cluster. - format: date-time - type: string - lastReconciled: - description: LastReconciled is the time when this revision was last reconciled to the cluster. - format: date-time - type: string - lastReconciledDuration: - description: LastReconciledDuration is time it took to reconcile the resources in this revision. - type: string - lastReconciledStatus: - description: LastReconciledStatus is the status of the last reconciliation. - type: string - metadata: - additionalProperties: - type: string - description: Metadata contains additional information about the snapshot. - type: object - totalReconciliations: - description: TotalReconciliations is the total number of reconciliations that have occurred for this snapshot. - format: int64 - type: integer - required: - - digest - - firstReconciled - - lastReconciled - - lastReconciledDuration - - lastReconciledStatus - - totalReconciliations - type: object - type: array - inventory: - description: |- - Inventory contains the list of Kubernetes resource object references that - have been successfully applied. - properties: - entries: - description: Entries of Kubernetes resource object references. - items: - description: ResourceRef contains the information necessary to locate a resource within a cluster. - properties: - id: - description: |- - ID is the string representation of the Kubernetes resource object's metadata, - in the format '___'. - type: string - v: - description: Version is the API version of the Kubernetes resource object's kind. - type: string - required: - - id - - v - type: object - type: array - required: - - entries - type: object - lastAppliedOriginRevision: - description: |- - The last successfully applied origin revision. - Equals the origin revision of the applied Artifact from the referenced Source. - Usually present on the Metadata of the applied Artifact and depends on the - Source type, e.g. for OCI it's the value associated with the key - "org.opencontainers.image.revision". - type: string - lastAppliedRevision: - description: |- - The last successfully applied revision. - Equals the Revision of the applied Artifact from the referenced Source. - type: string - lastAttemptedRevision: - description: LastAttemptedRevision is the revision of the last reconciliation attempt. - type: string - lastHandledReconcileAt: - description: |- - LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value - can be detected. - type: string - observedGeneration: - description: ObservedGeneration is the last reconciled generation. - format: int64 - type: integer - type: object - type: object - served: true - storage: true - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - deprecated: true - deprecationWarning: v1beta2 Kustomization is deprecated, upgrade to v1 - name: v1beta2 - schema: - openAPIV3Schema: - description: Kustomization is the Schema for the kustomizations API. - 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: KustomizationSpec defines the configuration to calculate the desired state from a Source using Kustomize. - properties: - commonMetadata: - description: |- - CommonMetadata specifies the common labels and annotations that are applied to all resources. - Any existing label or annotation will be overridden if its key matches a common one. - properties: - annotations: - additionalProperties: - type: string - description: Annotations to be added to the object's metadata. - type: object - labels: - additionalProperties: - type: string - description: Labels to be added to the object's metadata. - type: object - type: object - components: - description: Components specifies relative paths to specifications of other Components. - items: - type: string - type: array - decryption: - description: Decrypt Kubernetes secrets before applying them on the cluster. - properties: - provider: - description: Provider is the name of the decryption engine. - enum: - - sops - type: string - secretRef: - description: The secret name containing the private OpenPGP keys used for decryption. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - required: - - provider - type: object - dependsOn: - description: |- - DependsOn may contain a meta.NamespacedObjectReference slice - with references to Kustomization resources that must be ready before this - Kustomization can be reconciled. - items: - description: |- - NamespacedObjectReference contains enough information to locate the referenced Kubernetes resource object in any - namespace. - properties: - name: - description: Name of the referent. - type: string - namespace: - description: Namespace of the referent, when not specified it acts as LocalObjectReference. - type: string - required: - - name - type: object - type: array - force: - default: false - description: |- - Force instructs the controller to recreate resources - when patching fails due to an immutable field change. - type: boolean - healthChecks: - description: A list of resources to be included in the health assessment. - items: - description: |- - NamespacedObjectKindReference contains enough information to locate the typed referenced Kubernetes resource object - in any namespace. - properties: - apiVersion: - description: API version of the referent, if not specified the Kubernetes preferred version will be used. - type: string - kind: - description: Kind of the referent. - type: string - name: - description: Name of the referent. - type: string - namespace: - description: Namespace of the referent, when not specified it acts as LocalObjectReference. - type: string - required: - - kind - - name - type: object - type: array - images: - description: |- - Images is a list of (image name, new name, new tag or digest) - for changing image names, tags or digests. This can also be achieved with a - patch, but this operator is simpler to specify. - items: - description: Image contains an image name, a new name, a new tag or digest, which will replace the original name and tag. - properties: - digest: - description: |- - Digest is the value used to replace the original image tag. - If digest is present NewTag value is ignored. - type: string - name: - description: Name is a tag-less image name. - type: string - newName: - description: NewName is the value used to replace the original name. - type: string - newTag: - description: NewTag is the value used to replace the original tag. - type: string - required: - - name - type: object - type: array - interval: - description: The interval at which to reconcile the Kustomization. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - kubeConfig: - description: |- - The KubeConfig for reconciling the Kustomization on a remote cluster. - When used in combination with KustomizationSpec.ServiceAccountName, - forces the controller to act on behalf of that Service Account at the - target cluster. - If the --default-service-account flag is set, its value will be used as - a controller level fallback for when KustomizationSpec.ServiceAccountName - is empty. - properties: - configMapRef: - description: |- - ConfigMapRef holds an optional name of a ConfigMap that contains - the following keys: - - - `provider`: the provider to use. One of `aws`, `azure`, `gcp`, or - `generic`. Required. - - `cluster`: the fully qualified resource name of the Kubernetes - cluster in the cloud provider API. Not used by the `generic` - provider. Required when one of `address` or `ca.crt` is not set. - - `address`: the address of the Kubernetes API server. Required - for `generic`. For the other providers, if not specified, the - first address in the cluster resource will be used, and if - specified, it must match one of the addresses in the cluster - resource. - If audiences is not set, will be used as the audience for the - `generic` provider. - - `ca.crt`: the optional PEM-encoded CA certificate for the - Kubernetes API server. If not set, the controller will use the - CA certificate from the cluster resource. - - `audiences`: the optional audiences as a list of - line-break-separated strings for the Kubernetes ServiceAccount - token. Defaults to the `address` for the `generic` provider, or - to specific values for the other providers depending on the - provider. - - `serviceAccountName`: the optional name of the Kubernetes - ServiceAccount in the same namespace that should be used - for authentication. If not specified, the controller - ServiceAccount will be used. - - Mutually exclusive with SecretRef. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - secretRef: - description: |- - SecretRef holds an optional name of a secret that contains a key with - the kubeconfig file as the value. If no key is set, the key will default - to 'value'. Mutually exclusive with ConfigMapRef. - It is recommended that the kubeconfig is self-contained, and the secret - is regularly updated if credentials such as a cloud-access-token expire. - Cloud specific `cmd-path` auth helpers will not function without adding - binaries and credentials to the Pod that is responsible for reconciling - Kubernetes resources. Supported only for the generic provider. - properties: - key: - description: Key in the Secret, when not specified an implementation-specific default key is used. - type: string - name: - description: Name of the Secret. - type: string - required: - - name - type: object - type: object - x-kubernetes-validations: - - message: exactly one of spec.kubeConfig.configMapRef or spec.kubeConfig.secretRef must be specified - rule: has(self.configMapRef) || has(self.secretRef) - - message: exactly one of spec.kubeConfig.configMapRef or spec.kubeConfig.secretRef must be specified - rule: '!has(self.configMapRef) || !has(self.secretRef)' - patches: - description: |- - Strategic merge and JSON patches, defined as inline YAML objects, - capable of targeting objects based on kind, label and annotation selectors. - items: - description: |- - Patch contains an inline StrategicMerge or JSON6902 patch, and the target the patch should - be applied to. - properties: - patch: - description: |- - Patch contains an inline StrategicMerge patch or an inline JSON6902 patch with - an array of operation objects. - type: string - target: - description: Target points to the resources that the patch document should be applied to. - properties: - annotationSelector: - description: |- - AnnotationSelector is a string that follows the label selection expression - https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api - It matches with the resource annotations. - type: string - group: - description: |- - Group is the API group to select resources from. - Together with Version and Kind it is capable of unambiguously identifying and/or selecting resources. - https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - kind: - description: |- - Kind of the API Group to select resources from. - Together with Group and Version it is capable of unambiguously - identifying and/or selecting resources. - https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - labelSelector: - description: |- - LabelSelector is a string that follows the label selection expression - https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api - It matches with the resource labels. - type: string - name: - description: Name to match resources with. - type: string - namespace: - description: Namespace to select resources from. - type: string - version: - description: |- - Version of the API Group to select resources from. - Together with Group and Kind it is capable of unambiguously identifying and/or selecting resources. - https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - type: object - required: - - patch - type: object - type: array - patchesJson6902: - description: |- - JSON 6902 patches, defined as inline YAML objects. - Deprecated: Use Patches instead. - items: - description: JSON6902Patch contains a JSON6902 patch and the target the patch should be applied to. - properties: - patch: - description: Patch contains the JSON6902 patch document with an array of operation objects. - items: - description: |- - JSON6902 is a JSON6902 operation object. - https://datatracker.ietf.org/doc/html/rfc6902#section-4 - properties: - from: - description: |- - From contains a JSON-pointer value that references a location within the target document where the operation is - performed. The meaning of the value depends on the value of Op, and is NOT taken into account by all operations. - type: string - op: - description: |- - Op indicates the operation to perform. Its value MUST be one of "add", "remove", "replace", "move", "copy", or - "test". - https://datatracker.ietf.org/doc/html/rfc6902#section-4 - enum: - - test - - remove - - add - - replace - - move - - copy - type: string - path: - description: |- - Path contains the JSON-pointer value that references a location within the target document where the operation - is performed. The meaning of the value depends on the value of Op. - type: string - value: - description: |- - Value contains a valid JSON structure. The meaning of the value depends on the value of Op, and is NOT taken into - account by all operations. - x-kubernetes-preserve-unknown-fields: true - required: - - op - - path - type: object - type: array - target: - description: Target points to the resources that the patch document should be applied to. - properties: - annotationSelector: - description: |- - AnnotationSelector is a string that follows the label selection expression - https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api - It matches with the resource annotations. - type: string - group: - description: |- - Group is the API group to select resources from. - Together with Version and Kind it is capable of unambiguously identifying and/or selecting resources. - https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - kind: - description: |- - Kind of the API Group to select resources from. - Together with Group and Version it is capable of unambiguously - identifying and/or selecting resources. - https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - labelSelector: - description: |- - LabelSelector is a string that follows the label selection expression - https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api - It matches with the resource labels. - type: string - name: - description: Name to match resources with. - type: string - namespace: - description: Namespace to select resources from. - type: string - version: - description: |- - Version of the API Group to select resources from. - Together with Group and Kind it is capable of unambiguously identifying and/or selecting resources. - https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - type: object - required: - - patch - - target - type: object - type: array - patchesStrategicMerge: - description: |- - Strategic merge patches, defined as inline YAML objects. - Deprecated: Use Patches instead. - items: - x-kubernetes-preserve-unknown-fields: true - type: array - path: - description: |- - Path to the directory containing the kustomization.yaml file, or the - set of plain YAMLs a kustomization.yaml should be generated for. - Defaults to 'None', which translates to the root path of the SourceRef. - type: string - postBuild: - description: |- - PostBuild describes which actions to perform on the YAML manifest - generated by building the kustomize overlay. - properties: - substitute: - additionalProperties: - type: string - description: |- - Substitute holds a map of key/value pairs. - The variables defined in your YAML manifests - that match any of the keys defined in the map - will be substituted with the set value. - Includes support for bash string replacement functions - e.g. ${var:=default}, ${var:position} and ${var/substring/replacement}. - type: object - substituteFrom: - description: |- - SubstituteFrom holds references to ConfigMaps and Secrets containing - the variables and their values to be substituted in the YAML manifests. - The ConfigMap and the Secret data keys represent the var names and they - must match the vars declared in the manifests for the substitution to happen. - items: - description: |- - SubstituteReference contains a reference to a resource containing - the variables name and value. - properties: - kind: - description: Kind of the values referent, valid values are ('Secret', 'ConfigMap'). - enum: - - Secret - - ConfigMap - type: string - name: - description: |- - Name of the values referent. Should reside in the same namespace as the - referring resource. - maxLength: 253 - minLength: 1 - type: string - optional: - default: false - description: |- - Optional indicates whether the referenced resource must exist, or whether to - tolerate its absence. If true and the referenced resource is absent, proceed - as if the resource was present but empty, without any variables defined. - type: boolean - required: - - kind - - name - type: object - type: array - type: object - prune: - description: Prune enables garbage collection. - type: boolean - retryInterval: - description: |- - The interval at which to retry a previously failed reconciliation. - When not specified, the controller uses the KustomizationSpec.Interval - value to retry failures. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - serviceAccountName: - description: |- - The name of the Kubernetes service account to impersonate - when reconciling this Kustomization. - type: string - sourceRef: - description: Reference of the source where the kustomization file is. - properties: - apiVersion: - description: API version of the referent. - type: string - kind: - description: Kind of the referent. - enum: - - OCIRepository - - GitRepository - - Bucket - type: string - name: - description: Name of the referent. - type: string - namespace: - description: Namespace of the referent, defaults to the namespace of the Kubernetes resource object that contains the reference. - type: string - required: - - kind - - name - type: object - suspend: - description: |- - This flag tells the controller to suspend subsequent kustomize executions, - it does not apply to already started executions. Defaults to false. - type: boolean - targetNamespace: - description: |- - TargetNamespace sets or overrides the namespace in the - kustomization.yaml file. - maxLength: 63 - minLength: 1 - type: string - timeout: - description: |- - Timeout for validation, apply and health checking operations. - Defaults to 'Interval' duration. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - validation: - description: 'Deprecated: Not used in v1beta2.' - enum: - - none - - client - - server - type: string - wait: - description: |- - Wait instructs the controller to check the health of all the reconciled resources. - When enabled, the HealthChecks are ignored. Defaults to false. - type: boolean - required: - - interval - - prune - - sourceRef - type: object - status: - default: - observedGeneration: -1 - description: KustomizationStatus defines the observed state of a kustomization. - 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 - inventory: - description: Inventory contains the list of Kubernetes resource object references that have been successfully applied. - properties: - entries: - description: Entries of Kubernetes resource object references. - items: - description: ResourceRef contains the information necessary to locate a resource within a cluster. - properties: - id: - description: |- - ID is the string representation of the Kubernetes resource object's metadata, - in the format '___'. - type: string - v: - description: Version is the API version of the Kubernetes resource object's kind. - type: string - required: - - id - - v - type: object - type: array - required: - - entries - type: object - lastAppliedRevision: - description: |- - The last successfully applied revision. - Equals the Revision of the applied Artifact from the referenced Source. - type: string - lastAttemptedRevision: - description: LastAttemptedRevision is the revision of the last reconciliation attempt. - type: string - lastHandledReconcileAt: - description: |- - LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value - can be detected. - type: string - observedGeneration: - description: ObservedGeneration is the last reconciled generation. - format: int64 - type: integer - type: object - type: object - served: true - storage: false - subresources: - status: {} ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - app.kubernetes.io/role: cluster-admin - controller-gen.kubebuilder.io/version: v0.19.0 - labels: - app.kubernetes.io/name: flux - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v2.7.3 - name: ocirepositories.source.toolkit.fluxcd.io -spec: - group: source.toolkit.fluxcd.io - names: - kind: OCIRepository - listKind: OCIRepositoryList - plural: ocirepositories - shortNames: - - ocirepo - singular: ocirepository - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.url - name: URL - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1 - schema: - openAPIV3Schema: - description: OCIRepository is the Schema for the ocirepositories API - 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: OCIRepositorySpec defines the desired state of OCIRepository - properties: - certSecretRef: - description: |- - CertSecretRef can be given the name of a Secret containing - either or both of - - - a PEM-encoded client certificate (`tls.crt`) and private - key (`tls.key`); - - a PEM-encoded CA certificate (`ca.crt`) - - and whichever are supplied, will be used for connecting to the - registry. The client cert and key are useful if you are - authenticating with a certificate; the CA cert is useful if - you are using a self-signed server certificate. The Secret must - be of type `Opaque` or `kubernetes.io/tls`. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - ignore: - description: |- - Ignore overrides the set of excluded patterns in the .sourceignore format - (which is the same as .gitignore). If not provided, a default will be used, - consult the documentation for your version to find out what those are. - type: string - insecure: - description: Insecure allows connecting to a non-TLS HTTP container registry. - type: boolean - interval: - description: |- - Interval at which the OCIRepository URL is checked for updates. - This interval is approximate and may be subject to jitter to ensure - efficient use of resources. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - layerSelector: - description: |- - LayerSelector specifies which layer should be extracted from the OCI artifact. - When not specified, the first layer found in the artifact is selected. - properties: - mediaType: - description: |- - MediaType specifies the OCI media type of the layer - which should be extracted from the OCI Artifact. The - first layer matching this type is selected. - type: string - operation: - description: |- - Operation specifies how the selected layer should be processed. - By default, the layer compressed content is extracted to storage. - When the operation is set to 'copy', the layer compressed content - is persisted to storage as it is. - enum: - - extract - - copy - type: string - type: object - provider: - default: generic - description: |- - The provider used for authentication, can be 'aws', 'azure', 'gcp' or 'generic'. - When not specified, defaults to 'generic'. - enum: - - generic - - aws - - azure - - gcp - type: string - proxySecretRef: - description: |- - ProxySecretRef specifies the Secret containing the proxy configuration - to use while communicating with the container registry. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - ref: - description: |- - The OCI reference to pull and monitor for changes, - defaults to the latest tag. - properties: - digest: - description: |- - Digest is the image digest to pull, takes precedence over SemVer. - The value should be in the format 'sha256:'. - type: string - semver: - description: |- - SemVer is the range of tags to pull selecting the latest within - the range, takes precedence over Tag. - type: string - semverFilter: - description: SemverFilter is a regex pattern to filter the tags within the SemVer range. - type: string - tag: - description: Tag is the image tag to pull, defaults to latest. - type: string - type: object - secretRef: - description: |- - SecretRef contains the secret name containing the registry login - credentials to resolve image metadata. - The secret must be of type kubernetes.io/dockerconfigjson. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - serviceAccountName: - description: |- - ServiceAccountName is the name of the Kubernetes ServiceAccount used to authenticate - the image pull if the service account has attached pull secrets. For more information: - https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#add-imagepullsecrets-to-a-service-account - type: string - suspend: - description: This flag tells the controller to suspend the reconciliation of this source. - type: boolean - timeout: - default: 60s - description: The timeout for remote OCI Repository operations like pulling, defaults to 60s. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m))+$ - type: string - url: - description: |- - URL is a reference to an OCI artifact repository hosted - on a remote container registry. - pattern: ^oci://.*$ - type: string - verify: - description: |- - Verify contains the secret name containing the trusted public keys - used to verify the signature and specifies which provider to use to check - whether OCI image is authentic. - properties: - matchOIDCIdentity: - description: |- - MatchOIDCIdentity specifies the identity matching criteria to use - while verifying an OCI artifact which was signed using Cosign keyless - signing. The artifact's identity is deemed to be verified if any of the - specified matchers match against the identity. - items: - description: |- - OIDCIdentityMatch specifies options for verifying the certificate identity, - i.e. the issuer and the subject of the certificate. - properties: - issuer: - description: |- - Issuer specifies the regex pattern to match against to verify - the OIDC issuer in the Fulcio certificate. The pattern must be a - valid Go regular expression. - type: string - subject: - description: |- - Subject specifies the regex pattern to match against to verify - the identity subject in the Fulcio certificate. The pattern must - be a valid Go regular expression. - type: string - required: - - issuer - - subject - type: object - type: array - provider: - default: cosign - description: Provider specifies the technology used to sign the OCI Artifact. - enum: - - cosign - - notation - type: string - secretRef: - description: |- - SecretRef specifies the Kubernetes Secret containing the - trusted public keys. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - required: - - provider - type: object - required: - - interval - - url - type: object - status: - default: - observedGeneration: -1 - description: OCIRepositoryStatus defines the observed state of OCIRepository - properties: - artifact: - description: Artifact represents the output of the last successful OCI Repository sync. - properties: - digest: - description: Digest is the digest of the file in the form of ':'. - pattern: ^[a-z0-9]+(?:[.+_-][a-z0-9]+)*:[a-zA-Z0-9=_-]+$ - type: string - lastUpdateTime: - description: |- - LastUpdateTime is the timestamp corresponding to the last update of the - Artifact. - format: date-time - type: string - metadata: - additionalProperties: - type: string - description: Metadata holds upstream information such as OCI annotations. - type: object - path: - description: |- - Path is the relative file path of the Artifact. It can be used to locate - the file in the root of the Artifact storage on the local file system of - the controller managing the Source. - type: string - revision: - description: |- - Revision is a human-readable identifier traceable in the origin source - system. It can be a Git commit SHA, Git tag, a Helm chart version, etc. - type: string - size: - description: Size is the number of bytes in the file. - format: int64 - type: integer - url: - description: |- - URL is the HTTP address of the Artifact as exposed by the controller - managing the Source. It can be used to retrieve the Artifact for - consumption, e.g. by another controller applying the Artifact contents. - type: string - required: - - digest - - lastUpdateTime - - path - - revision - - url - type: object - conditions: - description: Conditions holds the conditions for the OCIRepository. - 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 - lastHandledReconcileAt: - description: |- - LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value - can be detected. - type: string - observedGeneration: - description: ObservedGeneration is the last observed generation. - format: int64 - type: integer - observedIgnore: - description: |- - ObservedIgnore is the observed exclusion patterns used for constructing - the source artifact. - type: string - observedLayerSelector: - description: |- - ObservedLayerSelector is the observed layer selector used for constructing - the source artifact. - properties: - mediaType: - description: |- - MediaType specifies the OCI media type of the layer - which should be extracted from the OCI Artifact. The - first layer matching this type is selected. - type: string - operation: - description: |- - Operation specifies how the selected layer should be processed. - By default, the layer compressed content is extracted to storage. - When the operation is set to 'copy', the layer compressed content - is persisted to storage as it is. - enum: - - extract - - copy - type: string - type: object - url: - description: URL is the download link for the artifact output of the last OCI Repository sync. - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .spec.url - name: URL - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - deprecated: true - deprecationWarning: v1beta2 OCIRepository is deprecated, upgrade to v1 - name: v1beta2 - schema: - openAPIV3Schema: - description: OCIRepository is the Schema for the ocirepositories API - 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: OCIRepositorySpec defines the desired state of OCIRepository - properties: - certSecretRef: - description: |- - CertSecretRef can be given the name of a Secret containing - either or both of - - - a PEM-encoded client certificate (`tls.crt`) and private - key (`tls.key`); - - a PEM-encoded CA certificate (`ca.crt`) - - and whichever are supplied, will be used for connecting to the - registry. The client cert and key are useful if you are - authenticating with a certificate; the CA cert is useful if - you are using a self-signed server certificate. The Secret must - be of type `Opaque` or `kubernetes.io/tls`. - - Note: Support for the `caFile`, `certFile` and `keyFile` keys have - been deprecated. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - ignore: - description: |- - Ignore overrides the set of excluded patterns in the .sourceignore format - (which is the same as .gitignore). If not provided, a default will be used, - consult the documentation for your version to find out what those are. - type: string - insecure: - description: Insecure allows connecting to a non-TLS HTTP container registry. - type: boolean - interval: - description: |- - Interval at which the OCIRepository URL is checked for updates. - This interval is approximate and may be subject to jitter to ensure - efficient use of resources. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - layerSelector: - description: |- - LayerSelector specifies which layer should be extracted from the OCI artifact. - When not specified, the first layer found in the artifact is selected. - properties: - mediaType: - description: |- - MediaType specifies the OCI media type of the layer - which should be extracted from the OCI Artifact. The - first layer matching this type is selected. - type: string - operation: - description: |- - Operation specifies how the selected layer should be processed. - By default, the layer compressed content is extracted to storage. - When the operation is set to 'copy', the layer compressed content - is persisted to storage as it is. - enum: - - extract - - copy - type: string - type: object - provider: - default: generic - description: |- - The provider used for authentication, can be 'aws', 'azure', 'gcp' or 'generic'. - When not specified, defaults to 'generic'. - enum: - - generic - - aws - - azure - - gcp - type: string - proxySecretRef: - description: |- - ProxySecretRef specifies the Secret containing the proxy configuration - to use while communicating with the container registry. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - ref: - description: |- - The OCI reference to pull and monitor for changes, - defaults to the latest tag. - properties: - digest: - description: |- - Digest is the image digest to pull, takes precedence over SemVer. - The value should be in the format 'sha256:'. - type: string - semver: - description: |- - SemVer is the range of tags to pull selecting the latest within - the range, takes precedence over Tag. - type: string - semverFilter: - description: SemverFilter is a regex pattern to filter the tags within the SemVer range. - type: string - tag: - description: Tag is the image tag to pull, defaults to latest. - type: string - type: object - secretRef: - description: |- - SecretRef contains the secret name containing the registry login - credentials to resolve image metadata. - The secret must be of type kubernetes.io/dockerconfigjson. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - serviceAccountName: - description: |- - ServiceAccountName is the name of the Kubernetes ServiceAccount used to authenticate - the image pull if the service account has attached pull secrets. For more information: - https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#add-imagepullsecrets-to-a-service-account - type: string - suspend: - description: This flag tells the controller to suspend the reconciliation of this source. - type: boolean - timeout: - default: 60s - description: The timeout for remote OCI Repository operations like pulling, defaults to 60s. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m))+$ - type: string - url: - description: |- - URL is a reference to an OCI artifact repository hosted - on a remote container registry. - pattern: ^oci://.*$ - type: string - verify: - description: |- - Verify contains the secret name containing the trusted public keys - used to verify the signature and specifies which provider to use to check - whether OCI image is authentic. - properties: - matchOIDCIdentity: - description: |- - MatchOIDCIdentity specifies the identity matching criteria to use - while verifying an OCI artifact which was signed using Cosign keyless - signing. The artifact's identity is deemed to be verified if any of the - specified matchers match against the identity. - items: - description: |- - OIDCIdentityMatch specifies options for verifying the certificate identity, - i.e. the issuer and the subject of the certificate. - properties: - issuer: - description: |- - Issuer specifies the regex pattern to match against to verify - the OIDC issuer in the Fulcio certificate. The pattern must be a - valid Go regular expression. - type: string - subject: - description: |- - Subject specifies the regex pattern to match against to verify - the identity subject in the Fulcio certificate. The pattern must - be a valid Go regular expression. - type: string - required: - - issuer - - subject - type: object - type: array - provider: - default: cosign - description: Provider specifies the technology used to sign the OCI Artifact. - enum: - - cosign - - notation - type: string - secretRef: - description: |- - SecretRef specifies the Kubernetes Secret containing the - trusted public keys. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - required: - - provider - type: object - required: - - interval - - url - type: object - status: - default: - observedGeneration: -1 - description: OCIRepositoryStatus defines the observed state of OCIRepository - properties: - artifact: - description: Artifact represents the output of the last successful OCI Repository sync. - properties: - digest: - description: Digest is the digest of the file in the form of ':'. - pattern: ^[a-z0-9]+(?:[.+_-][a-z0-9]+)*:[a-zA-Z0-9=_-]+$ - type: string - lastUpdateTime: - description: |- - LastUpdateTime is the timestamp corresponding to the last update of the - Artifact. - format: date-time - type: string - metadata: - additionalProperties: - type: string - description: Metadata holds upstream information such as OCI annotations. - type: object - path: - description: |- - Path is the relative file path of the Artifact. It can be used to locate - the file in the root of the Artifact storage on the local file system of - the controller managing the Source. - type: string - revision: - description: |- - Revision is a human-readable identifier traceable in the origin source - system. It can be a Git commit SHA, Git tag, a Helm chart version, etc. - type: string - size: - description: Size is the number of bytes in the file. - format: int64 - type: integer - url: - description: |- - URL is the HTTP address of the Artifact as exposed by the controller - managing the Source. It can be used to retrieve the Artifact for - consumption, e.g. by another controller applying the Artifact contents. - type: string - required: - - digest - - lastUpdateTime - - path - - revision - - url - type: object - conditions: - description: Conditions holds the conditions for the OCIRepository. - 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 - contentConfigChecksum: - description: |- - ContentConfigChecksum is a checksum of all the configurations related to - the content of the source artifact: - - .spec.ignore - - .spec.layerSelector - observed in .status.observedGeneration version of the object. This can - be used to determine if the content configuration has changed and the - artifact needs to be rebuilt. - It has the format of `:`, for example: `sha256:`. - - Deprecated: Replaced with explicit fields for observed artifact content - config in the status. - type: string - lastHandledReconcileAt: - description: |- - LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value - can be detected. - type: string - observedGeneration: - description: ObservedGeneration is the last observed generation. - format: int64 - type: integer - observedIgnore: - description: |- - ObservedIgnore is the observed exclusion patterns used for constructing - the source artifact. - type: string - observedLayerSelector: - description: |- - ObservedLayerSelector is the observed layer selector used for constructing - the source artifact. - properties: - mediaType: - description: |- - MediaType specifies the OCI media type of the layer - which should be extracted from the OCI Artifact. The - first layer matching this type is selected. - type: string - operation: - description: |- - Operation specifies how the selected layer should be processed. - By default, the layer compressed content is extracted to storage. - When the operation is set to 'copy', the layer compressed content - is persisted to storage as it is. - enum: - - extract - - copy - type: string - type: object - url: - description: URL is the download link for the artifact output of the last OCI Repository sync. - type: string - type: object - type: object - served: true - storage: false - subresources: - status: {} ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - app.kubernetes.io/role: cluster-admin - controller-gen.kubebuilder.io/version: v0.19.0 - labels: - app.kubernetes.io/name: flux - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v2.7.3 - name: providers.notification.toolkit.fluxcd.io -spec: - group: notification.toolkit.fluxcd.io - names: - kind: Provider - listKind: ProviderList - plural: providers - singular: provider - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - deprecated: true - deprecationWarning: v1beta2 Provider is deprecated, upgrade to v1beta3 - name: v1beta2 - schema: - openAPIV3Schema: - description: Provider is the Schema for the providers API. - 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: ProviderSpec defines the desired state of the Provider. - properties: - address: - description: |- - Address specifies the endpoint, in a generic sense, to where alerts are sent. - What kind of endpoint depends on the specific Provider type being used. - For the generic Provider, for example, this is an HTTP/S address. - For other Provider types this could be a project ID or a namespace. - maxLength: 2048 - type: string - certSecretRef: - description: |- - CertSecretRef specifies the Secret containing - a PEM-encoded CA certificate (in the `ca.crt` key). - - Note: Support for the `caFile` key has - been deprecated. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - channel: - description: Channel specifies the destination channel where events should be posted. - maxLength: 2048 - type: string - interval: - description: Interval at which to reconcile the Provider with its Secret references. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - proxy: - description: Proxy the HTTP/S address of the proxy server. - maxLength: 2048 - pattern: ^(http|https)://.*$ - type: string - secretRef: - description: |- - SecretRef specifies the Secret containing the authentication - credentials for this Provider. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - suspend: - description: |- - Suspend tells the controller to suspend subsequent - events handling for this Provider. - type: boolean - timeout: - description: Timeout for sending alerts to the Provider. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m))+$ - type: string - type: - description: Type specifies which Provider implementation to use. - enum: - - slack - - discord - - msteams - - rocket - - generic - - generic-hmac - - github - - gitlab - - gitea - - bitbucketserver - - bitbucket - - azuredevops - - googlechat - - googlepubsub - - webex - - sentry - - azureeventhub - - telegram - - lark - - matrix - - opsgenie - - alertmanager - - grafana - - githubdispatch - - pagerduty - - datadog - type: string - username: - description: Username specifies the name under which events are posted. - maxLength: 2048 - type: string - required: - - type - type: object - status: - default: - observedGeneration: -1 - description: ProviderStatus defines the observed state of the Provider. - properties: - conditions: - description: Conditions holds the conditions for the Provider. - 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 - lastHandledReconcileAt: - description: |- - LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value - can be detected. - type: string - observedGeneration: - description: ObservedGeneration is the last reconciled generation. - format: int64 - type: integer - type: object - type: object - served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta3 - schema: - openAPIV3Schema: - description: Provider is the Schema for the providers API - 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: ProviderSpec defines the desired state of the Provider. - properties: - address: - description: |- - Address specifies the endpoint, in a generic sense, to where alerts are sent. - What kind of endpoint depends on the specific Provider type being used. - For the generic Provider, for example, this is an HTTP/S address. - For other Provider types this could be a project ID or a namespace. - maxLength: 2048 - type: string - certSecretRef: - description: |- - CertSecretRef specifies the Secret containing TLS certificates - for secure communication. - - Supported configurations: - - CA-only: Server authentication (provide ca.crt only) - - mTLS: Mutual authentication (provide ca.crt + tls.crt + tls.key) - - Client-only: Client authentication with system CA (provide tls.crt + tls.key only) - - Legacy keys "caFile", "certFile", "keyFile" are supported but deprecated. Use "ca.crt", "tls.crt", "tls.key" instead. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - channel: - description: Channel specifies the destination channel where events should be posted. - maxLength: 2048 - type: string - commitStatusExpr: - description: |- - CommitStatusExpr is a CEL expression that evaluates to a string value - that can be used to generate a custom commit status message for use - with eligible Provider types (github, gitlab, gitea, bitbucketserver, - bitbucket, azuredevops). Supported variables are: event, provider, - and alert. - type: string - interval: - description: |- - Interval at which to reconcile the Provider with its Secret references. - Deprecated and not used in v1beta3. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - proxy: - description: |- - Proxy the HTTP/S address of the proxy server. - Deprecated: Use ProxySecretRef instead. Will be removed in v1. - maxLength: 2048 - pattern: ^(http|https)://.*$ - type: string - proxySecretRef: - description: |- - ProxySecretRef specifies the Secret containing the proxy configuration - for this Provider. The Secret should contain an 'address' key with the - HTTP/S address of the proxy server. Optional 'username' and 'password' - keys can be provided for proxy authentication. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - secretRef: - description: |- - SecretRef specifies the Secret containing the authentication - credentials for this Provider. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - serviceAccountName: - description: |- - ServiceAccountName is the name of the Kubernetes ServiceAccount used to - authenticate with cloud provider services through workload identity. - This enables multi-tenant authentication without storing static credentials. - - Supported provider types: azureeventhub, azuredevops, googlepubsub - - When specified, the controller will: - 1. Create an OIDC token for the specified ServiceAccount - 2. Exchange it for cloud provider credentials via STS - 3. Use the obtained credentials for API authentication - - When unspecified, controller-level authentication is used (single-tenant). - - An error is thrown if static credentials are also defined in SecretRef. - This field requires the ObjectLevelWorkloadIdentity feature gate to be enabled. - type: string - suspend: - description: |- - Suspend tells the controller to suspend subsequent - events handling for this Provider. - type: boolean - timeout: - description: Timeout for sending alerts to the Provider. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m))+$ - type: string - type: - description: Type specifies which Provider implementation to use. - enum: - - slack - - discord - - msteams - - rocket - - generic - - generic-hmac - - github - - gitlab - - gitea - - bitbucketserver - - bitbucket - - azuredevops - - googlechat - - googlepubsub - - webex - - sentry - - azureeventhub - - telegram - - lark - - matrix - - opsgenie - - alertmanager - - grafana - - githubdispatch - - pagerduty - - datadog - - nats - - zulip - - otel - type: string - username: - description: Username specifies the name under which events are posted. - maxLength: 2048 - type: string - required: - - type - type: object - x-kubernetes-validations: - - message: spec.commitStatusExpr is only supported for the 'github', 'gitlab', 'gitea', 'bitbucketserver', 'bitbucket', 'azuredevops' provider types - rule: self.type == 'github' || self.type == 'gitlab' || self.type == 'gitea' || self.type == 'bitbucketserver' || self.type == 'bitbucket' || self.type == 'azuredevops' || !has(self.commitStatusExpr) - type: object - served: true - storage: true - subresources: {} ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - app.kubernetes.io/role: cluster-admin - controller-gen.kubebuilder.io/version: v0.19.0 - labels: - app.kubernetes.io/name: flux - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v2.7.3 - name: receivers.notification.toolkit.fluxcd.io -spec: - group: notification.toolkit.fluxcd.io - names: - kind: Receiver - listKind: ReceiverList - plural: receivers - singular: receiver - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - name: v1 - schema: - openAPIV3Schema: - description: Receiver is the Schema for the receivers API. - 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: ReceiverSpec defines the desired state of the Receiver. - properties: - events: - description: |- - Events specifies the list of event types to handle, - e.g. 'push' for GitHub or 'Push Hook' for GitLab. - items: - type: string - type: array - interval: - default: 10m - description: Interval at which to reconcile the Receiver with its Secret references. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - resourceFilter: - description: |- - ResourceFilter is a CEL expression expected to return a boolean that is - evaluated for each resource referenced in the Resources field when a - webhook is received. If the expression returns false then the controller - will not request a reconciliation for the resource. - When the expression is specified the controller will parse it and mark - the object as terminally failed if the expression is invalid or does not - return a boolean. - type: string - resources: - description: A list of resources to be notified about changes. - items: - description: |- - CrossNamespaceObjectReference contains enough information to let you locate the - typed referenced object at cluster level - properties: - apiVersion: - description: API version of the referent - type: string - kind: - description: Kind of the referent - enum: - - Bucket - - GitRepository - - Kustomization - - HelmRelease - - HelmChart - - HelmRepository - - ImageRepository - - ImagePolicy - - ImageUpdateAutomation - - OCIRepository - type: string - 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. - MatchLabels requires the name to be set to `*`. - type: object - name: - description: |- - Name of the referent - If multiple resources are targeted `*` may be set. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: Namespace of the referent - maxLength: 253 - minLength: 1 - type: string - required: - - kind - - name - type: object - type: array - secretRef: - description: |- - SecretRef specifies the Secret containing the token used - to validate the payload authenticity. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - suspend: - description: |- - Suspend tells the controller to suspend subsequent - events handling for this receiver. - type: boolean - type: - description: |- - Type of webhook sender, used to determine - the validation procedure and payload deserialization. - enum: - - generic - - generic-hmac - - github - - gitlab - - bitbucket - - harbor - - dockerhub - - quay - - gcr - - nexus - - acr - - cdevents - type: string - required: - - resources - - secretRef - - type - type: object - status: - default: - observedGeneration: -1 - description: ReceiverStatus defines the observed state of the Receiver. - properties: - conditions: - description: Conditions holds the conditions for the Receiver. - 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 - lastHandledReconcileAt: - description: |- - LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value - can be detected. - type: string - observedGeneration: - description: ObservedGeneration is the last observed generation of the Receiver object. - format: int64 - type: integer - webhookPath: - description: |- - WebhookPath is the generated incoming webhook address in the format - of '/hook/sha256sum(token+name+namespace)'. - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - deprecated: true - deprecationWarning: v1beta2 Receiver is deprecated, upgrade to v1 - name: v1beta2 - schema: - openAPIV3Schema: - description: Receiver is the Schema for the receivers API. - 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: ReceiverSpec defines the desired state of the Receiver. - properties: - events: - description: |- - Events specifies the list of event types to handle, - e.g. 'push' for GitHub or 'Push Hook' for GitLab. - items: - type: string - type: array - interval: - description: Interval at which to reconcile the Receiver with its Secret references. - pattern: ^([0-9]+(\.[0-9]+)?(ms|s|m|h))+$ - type: string - resources: - description: A list of resources to be notified about changes. - items: - description: |- - CrossNamespaceObjectReference contains enough information to let you locate the - typed referenced object at cluster level - properties: - apiVersion: - description: API version of the referent - type: string - kind: - description: Kind of the referent - enum: - - Bucket - - GitRepository - - Kustomization - - HelmRelease - - HelmChart - - HelmRepository - - ImageRepository - - ImagePolicy - - ImageUpdateAutomation - - OCIRepository - type: string - 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. - MatchLabels requires the name to be set to `*`. - type: object - name: - description: |- - Name of the referent - If multiple resources are targeted `*` may be set. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: Namespace of the referent - maxLength: 253 - minLength: 1 - type: string - required: - - kind - - name - type: object - type: array - secretRef: - description: |- - SecretRef specifies the Secret containing the token used - to validate the payload authenticity. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - suspend: - description: |- - Suspend tells the controller to suspend subsequent - events handling for this receiver. - type: boolean - type: - description: |- - Type of webhook sender, used to determine - the validation procedure and payload deserialization. - enum: - - generic - - generic-hmac - - github - - gitlab - - bitbucket - - harbor - - dockerhub - - quay - - gcr - - nexus - - acr - type: string - required: - - resources - - secretRef - - type - type: object - status: - default: - observedGeneration: -1 - description: ReceiverStatus defines the observed state of the Receiver. - properties: - conditions: - description: Conditions holds the conditions for the Receiver. - 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 - lastHandledReconcileAt: - description: |- - LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value - can be detected. - type: string - observedGeneration: - description: ObservedGeneration is the last observed generation of the Receiver object. - format: int64 - type: integer - url: - description: |- - URL is the generated incoming webhook address in the format - of '/hook/sha256sum(token+name+namespace)'. - Deprecated: Replaced by WebhookPath. - type: string - webhookPath: - description: |- - WebhookPath is the generated incoming webhook address in the format - of '/hook/sha256sum(token+name+namespace)'. - type: string - type: object - type: object - served: true - storage: false - subresources: - status: {} ---- -apiVersion: v1 -kind: Namespace -metadata: - annotations: - app.kubernetes.io/role: cluster-admin - labels: - app.kubernetes.io/name: flux - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v2.7.3 - pod-security.kubernetes.io/enforce: privileged - name: cozy-fluxcd ---- -apiVersion: v1 -kind: ResourceQuota -metadata: - annotations: - app.kubernetes.io/role: cluster-admin - labels: - app.kubernetes.io/name: flux - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v2.7.3 - name: flux - namespace: cozy-fluxcd -spec: - hard: - pods: "1000" - scopeSelector: - matchExpressions: - - operator: In - scopeName: PriorityClass - values: - - system-node-critical - - system-cluster-critical ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - annotations: - app.kubernetes.io/role: cluster-admin - labels: - app.kubernetes.io/name: flux - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v2.7.3 - name: flux - namespace: cozy-fluxcd ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - annotations: - app.kubernetes.io/role: cluster-admin - labels: - app.kubernetes.io/name: flux - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v2.7.3 - rbac.authorization.k8s.io/aggregate-to-admin: "true" - rbac.authorization.k8s.io/aggregate-to-edit: "true" - rbac.authorization.k8s.io/aggregate-to-view: "true" - name: flux-view -rules: - - apiGroups: - - notification.toolkit.fluxcd.io - - source.toolkit.fluxcd.io - - helm.toolkit.fluxcd.io - - image.toolkit.fluxcd.io - - kustomize.toolkit.fluxcd.io - - source.extensions.fluxcd.io - resources: - - '*' - verbs: - - get - - list - - watch ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - annotations: - app.kubernetes.io/role: cluster-admin - labels: - app.kubernetes.io/name: flux - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v2.7.3 - name: flux -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: cluster-admin -subjects: - - kind: ServiceAccount - name: flux - namespace: cozy-fluxcd ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - annotations: - app.kubernetes.io/role: cluster-admin - labels: - app.kubernetes.io/name: flux - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v2.7.3 - name: flux - namespace: cozy-fluxcd -spec: - replicas: 1 - selector: - matchLabels: - app.kubernetes.io/name: flux - strategy: - type: Recreate - template: - metadata: - annotations: - cluster-autoscaler.kubernetes.io/safe-to-evict: "true" - prometheus.io/scrape: "true" - labels: - app.kubernetes.io/name: flux - spec: - affinity: - nodeAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - nodeSelectorTerms: - - matchExpressions: - - key: kubernetes.io/os - operator: In - values: - - linux - podAntiAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - labelSelector: - matchExpressions: - - key: app.kubernetes.io/name - operator: In - values: - - flux - topologyKey: kubernetes.io/hostname - containers: - - args: - - --watch-all-namespaces - - --log-level=info - - --log-encoding=json - - --enable-leader-election=false - - --metrics-addr=:9791 - - --health-addr=:9792 - - --storage-addr=:9790 - - --storage-path=/data - - --storage-adv-addr=flux.$(RUNTIME_NAMESPACE).svc - - --concurrent=5 - - --requeue-dependency=30s - - --watch-label-selector=!sharding.fluxcd.io/key - - --helm-cache-max-size=10 - - --helm-cache-ttl=60m - - --helm-cache-purge-interval=5m - - --events-addr=http://localhost:9690 - env: - - name: SOURCE_CONTROLLER_LOCALHOST - value: localhost:9790 - - name: SOURCE_WATCHER_LOCALHOST - value: localhost:9691 - - name: RUNTIME_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: GOMEMLIMIT - valueFrom: - resourceFieldRef: - resource: limits.memory - - name: TUF_ROOT - value: /tmp/.sigstore - - name: NO_PROXY - value: .svc - {{- include "cozy.kubernetes_envs" . | nindent 12 }} - image: ghcr.io/fluxcd/source-controller:v1.7.3 - imagePullPolicy: IfNotPresent - livenessProbe: - httpGet: - path: /healthz - port: healthz-sc - name: source-controller - ports: - - containerPort: 9790 - name: http-sc - protocol: TCP - - containerPort: 9791 - name: http-prom-sc - protocol: TCP - - containerPort: 9792 - name: healthz-sc - protocol: TCP - readinessProbe: - httpGet: - path: / - port: http-sc - resources: - limits: - memory: 1Gi - requests: - cpu: 100m - memory: 64Mi - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - readOnlyRootFilesystem: true - runAsNonRoot: true - seccompProfile: - type: RuntimeDefault - volumeMounts: - - mountPath: /data - name: data - - mountPath: /tmp - name: tmp - - args: - - --watch-all-namespaces - - --log-level=info - - --log-encoding=json - - --enable-leader-election=false - - --metrics-addr=:9793 - - --health-addr=:9794 - - --watch-label-selector=!sharding.fluxcd.io/key - - --concurrent=5 - - --requeue-dependency=30s - - --events-addr=http://localhost:9690 - - --feature-gates=ExternalArtifact=true - env: - - name: SOURCE_CONTROLLER_LOCALHOST - value: localhost:9790 - - name: SOURCE_WATCHER_LOCALHOST - value: localhost:9691 - - name: RUNTIME_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: GOMEMLIMIT - valueFrom: - resourceFieldRef: - resource: limits.memory - - name: TUF_ROOT - value: /tmp/.sigstore - - name: NO_PROXY - value: .svc - {{- include "cozy.kubernetes_envs" . | nindent 12 }} - image: ghcr.io/fluxcd/kustomize-controller:v1.7.2 - imagePullPolicy: IfNotPresent - livenessProbe: - httpGet: - path: /healthz - port: healthz-kc - name: kustomize-controller - ports: - - containerPort: 9793 - name: http-prom-kc - protocol: TCP - - containerPort: 9794 - name: healthz-kc - protocol: TCP - readinessProbe: - httpGet: - path: /readyz - port: healthz-kc - resources: - limits: - memory: 1Gi - requests: - cpu: 100m - memory: 64Mi - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - readOnlyRootFilesystem: true - runAsNonRoot: true - seccompProfile: - type: RuntimeDefault - volumeMounts: - - mountPath: /tmp - name: tmp - - args: - - --watch-all-namespaces - - --log-level=info - - --log-encoding=json - - --enable-leader-election=false - - --metrics-addr=:9795 - - --health-addr=:9796 - - --watch-label-selector=!sharding.fluxcd.io/key - - --concurrent=5 - - --requeue-dependency=30s - - --events-addr=http://localhost:9690 - - --feature-gates=ExternalArtifact=true - env: - - name: SOURCE_CONTROLLER_LOCALHOST - value: localhost:9790 - - name: SOURCE_WATCHER_LOCALHOST - value: localhost:9691 - - name: RUNTIME_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: GOMEMLIMIT - valueFrom: - resourceFieldRef: - resource: limits.memory - - name: TUF_ROOT - value: /tmp/.sigstore - - name: NO_PROXY - value: .svc - {{- include "cozy.kubernetes_envs" . | nindent 12 }} - image: ghcr.io/fluxcd/helm-controller:v1.4.3 - imagePullPolicy: IfNotPresent - livenessProbe: - httpGet: - path: /healthz - port: healthz-hc - name: helm-controller - ports: - - containerPort: 9795 - name: http-prom-hc - protocol: TCP - - containerPort: 9796 - name: healthz-hc - protocol: TCP - readinessProbe: - httpGet: - path: /readyz - port: healthz-hc - resources: - limits: - memory: 1Gi - requests: - cpu: 100m - memory: 64Mi - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - readOnlyRootFilesystem: true - runAsNonRoot: true - seccompProfile: - type: RuntimeDefault - volumeMounts: - - mountPath: /tmp - name: tmp - - args: - - --watch-all-namespaces - - --log-level=info - - --log-encoding=json - - --enable-leader-election=false - - --receiverAddr=:9797 - - --metrics-addr=:9798 - - --health-addr=:9799 - - --events-addr=:9690 - env: - - name: SOURCE_CONTROLLER_LOCALHOST - value: localhost:9790 - - name: SOURCE_WATCHER_LOCALHOST - value: localhost:9691 - - name: RUNTIME_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: GOMEMLIMIT - valueFrom: - resourceFieldRef: - resource: limits.memory - - name: TUF_ROOT - value: /tmp/.sigstore - - name: NO_PROXY - value: .svc - {{- include "cozy.kubernetes_envs" . | nindent 12 }} - image: ghcr.io/fluxcd/notification-controller:v1.7.4 - imagePullPolicy: IfNotPresent - livenessProbe: - httpGet: - path: /healthz - port: healthz-nc - name: notification-controller - ports: - - containerPort: 9690 - name: http-nc - protocol: TCP - - containerPort: 9797 - name: http-webhook-nc - protocol: TCP - - containerPort: 9798 - name: http-prom-nc - protocol: TCP - - containerPort: 9799 - name: healthz-nc - protocol: TCP - readinessProbe: - httpGet: - path: /readyz - port: healthz-nc - resources: - limits: - memory: 1Gi - requests: - cpu: 100m - memory: 64Mi - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - readOnlyRootFilesystem: true - runAsNonRoot: true - seccompProfile: - type: RuntimeDefault - volumeMounts: - - mountPath: /tmp - name: tmp - - args: - - --watch-all-namespaces - - --log-level=info - - --log-encoding=json - - --enable-leader-election=false - - --metrics-addr=:9692 - - --health-addr=:9693 - - --storage-addr=:9691 - - --storage-path=/data - - --storage-adv-addr=source-watcher.$(RUNTIME_NAMESPACE).svc - - --events-addr=http://localhost:9690 - env: - - name: SOURCE_CONTROLLER_LOCALHOST - value: localhost:9790 - - name: SOURCE_WATCHER_LOCALHOST - value: localhost:9691 - - name: RUNTIME_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: GOMEMLIMIT - valueFrom: - resourceFieldRef: - resource: limits.memory - - name: TUF_ROOT - value: /tmp/.sigstore - - name: NO_PROXY - value: .svc - {{- include "cozy.kubernetes_envs" . | nindent 12 }} - image: ghcr.io/fluxcd/source-watcher:v2.0.2 - imagePullPolicy: IfNotPresent - livenessProbe: - httpGet: - path: /healthz - port: healthz-sw - name: source-watcher - ports: - - containerPort: 9691 - name: http-sw - protocol: TCP - - containerPort: 9692 - name: http-prom-sw - protocol: TCP - - containerPort: 9693 - name: healthz-sw - protocol: TCP - readinessProbe: - httpGet: - path: / - port: http-sw - resources: - limits: - memory: 1Gi - requests: - cpu: 100m - memory: 64Mi - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - readOnlyRootFilesystem: true - runAsNonRoot: true - seccompProfile: - type: RuntimeDefault - volumeMounts: - - mountPath: /data - name: data - - mountPath: /tmp - name: tmp - dnsPolicy: ClusterFirstWithHostNet - hostNetwork: true - priorityClassName: system-cluster-critical - securityContext: - fsGroup: 1337 - serviceAccountName: flux - terminationGracePeriodSeconds: 120 - tolerations: - - key: node.kubernetes.io/not-ready - operator: Exists - - effect: NoExecute - key: node.kubernetes.io/unreachable - operator: Exists - tolerationSeconds: 300 - volumes: - - emptyDir: {} - name: data - - emptyDir: {} - name: tmp diff --git a/packages/extra/ingress/templates/nginx-ingress.yaml b/packages/extra/ingress/templates/nginx-ingress.yaml index d375be2c..ca50d276 100644 --- a/packages/extra/ingress/templates/nginx-ingress.yaml +++ b/packages/extra/ingress/templates/nginx-ingress.yaml @@ -4,16 +4,13 @@ apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease metadata: name: ingress-nginx-system + labels: + sharding.fluxcd.io/key: tenants spec: - chart: - spec: - chart: cozy-ingress-nginx - reconcileStrategy: Revision - sourceRef: - kind: HelmRepository - name: cozystack-system - namespace: cozy-system - version: '>= 0.0.0-0' + chartRef: + kind: ExternalArtifact + name: cozystack-ingress-application-default-ingress-system + namespace: cozy-system interval: 5m timeout: 10m install: diff --git a/packages/extra/monitoring/templates/dashboards.yaml b/packages/extra/monitoring/templates/dashboards.yaml index 522df88b..3872f9b7 100644 --- a/packages/extra/monitoring/templates/dashboards.yaml +++ b/packages/extra/monitoring/templates/dashboards.yaml @@ -1,6 +1,6 @@ {{- range (split "\n" (.Files.Get "dashboards.list")) }} {{- $parts := split "/" . }} -{{- if eq (len $parts) 2 }} +{{- if eq (len $parts) 2 }} --- apiVersion: grafana.integreatly.org/v1beta1 kind: GrafanaDashboard @@ -11,6 +11,6 @@ spec: instanceSelector: matchLabels: dashboards: grafana - url: http://cozystack-assets.cozy-system.svc/dashboards/{{ . }}.json + url: http://grafana-dashboards.cozy-grafana-operator.svc/{{ . }}.json {{- end }} {{- end }} diff --git a/packages/extra/seaweedfs/templates/seaweedfs.yaml b/packages/extra/seaweedfs/templates/seaweedfs.yaml index 747fc410..b04318bb 100644 --- a/packages/extra/seaweedfs/templates/seaweedfs.yaml +++ b/packages/extra/seaweedfs/templates/seaweedfs.yaml @@ -40,16 +40,13 @@ apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease metadata: name: {{ .Release.Name }}-system + labels: + sharding.fluxcd.io/key: tenants spec: - chart: - spec: - chart: cozy-seaweedfs - reconcileStrategy: Revision - sourceRef: - kind: HelmRepository - name: cozystack-system - namespace: cozy-system - version: '>= 0.0.0-0' + chartRef: + kind: ExternalArtifact + name: cozystack-seaweedfs-application-default-seaweedfs-system + namespace: cozy-system interval: 5m timeout: 10m install: diff --git a/packages/system/backupstrategy-controller/Chart.yaml b/packages/system/backupstrategy-controller/Chart.yaml index fd135712..bf556f3b 100644 --- a/packages/system/backupstrategy-controller/Chart.yaml +++ b/packages/system/backupstrategy-controller/Chart.yaml @@ -1,3 +1,3 @@ apiVersion: v2 -name: cozy-backup-controller +name: cozy-backupstrategy-controller version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process 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..e817de23 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,502 @@ 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: + 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: + - spec + type: object + required: + - template type: object status: properties: diff --git a/packages/system/backupstrategy-controller/images/backupstrategy-controller/Dockerfile b/packages/system/backupstrategy-controller/images/backupstrategy-controller/Dockerfile index e896f473..c4f60746 100644 --- a/packages/system/backupstrategy-controller/images/backupstrategy-controller/Dockerfile +++ b/packages/system/backupstrategy-controller/images/backupstrategy-controller/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.24-alpine AS builder +FROM golang:1.25-alpine AS builder ARG TARGETOS ARG TARGETARCH diff --git a/packages/system/bootbox/templates/bootbox.yaml b/packages/system/bootbox/templates/bootbox.yaml index 45cd07ce..332f30f4 100644 --- a/packages/system/bootbox/templates/bootbox.yaml +++ b/packages/system/bootbox/templates/bootbox.yaml @@ -5,20 +5,16 @@ metadata: helm.sh/resource-policy: keep labels: cozystack.io/ui: "true" + sharding.fluxcd.io/key: tenants apps.cozystack.io/application.kind: BootBox apps.cozystack.io/application.group: apps.cozystack.io apps.cozystack.io/application.name: bootbox name: bootbox namespace: tenant-root spec: - chart: - spec: - chart: bootbox - reconcileStrategy: Revision - sourceRef: - kind: HelmRepository - name: cozystack-extra - namespace: cozy-public - version: '>= 0.0.0-0' + chartRef: + kind: ExternalArtifact + name: cozystack-bootbox-application-default-bootbox + namespace: cozy-system interval: 1m0s timeout: 5m0s diff --git a/packages/system/cozystack-basics/Chart.yaml b/packages/system/cozystack-basics/Chart.yaml new file mode 100644 index 00000000..8135baef --- /dev/null +++ b/packages/system/cozystack-basics/Chart.yaml @@ -0,0 +1,3 @@ +apiVersion: v2 +name: cozy-basics +version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process diff --git a/packages/system/cozystack-basics/Makefile b/packages/system/cozystack-basics/Makefile new file mode 100644 index 00000000..fe85b39b --- /dev/null +++ b/packages/system/cozystack-basics/Makefile @@ -0,0 +1,4 @@ +export NAME=tenant-root +export NAMESPACE=cozy-system + +include ../../../scripts/package.mk diff --git a/packages/system/cozystack-basics/templates/cozy-public.yaml b/packages/system/cozystack-basics/templates/cozy-public.yaml new file mode 100644 index 00000000..b76816b4 --- /dev/null +++ b/packages/system/cozystack-basics/templates/cozy-public.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: cozy-public diff --git a/packages/system/cozystack-basics/templates/cozystack-values-secret.yaml b/packages/system/cozystack-basics/templates/cozystack-values-secret.yaml new file mode 100644 index 00000000..b625435f --- /dev/null +++ b/packages/system/cozystack-basics/templates/cozystack-values-secret.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: cozystack-values + namespace: tenant-root + labels: + reconcile.fluxcd.io/watch: Enabled +type: Opaque +stringData: + values.yaml: | + _cluster: + {{- .Values._cluster | toYaml | nindent 6 }} + _namespace: + host: {{ index .Values._cluster "root-host" | quote }} + etcd: tenant-root + ingress: tenant-root + monitoring: tenant-root + seaweedfs: tenant-root diff --git a/packages/system/cozystack-basics/templates/tenant-root.yaml b/packages/system/cozystack-basics/templates/tenant-root.yaml new file mode 100644 index 00000000..1d5586d9 --- /dev/null +++ b/packages/system/cozystack-basics/templates/tenant-root.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: tenant-root +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + annotations: + helm.sh/resource-policy: keep + labels: + cozystack.io/ui: "true" + sharding.fluxcd.io/key: tenants + apps.cozystack.io/application.kind: Tenant + apps.cozystack.io/application.group: apps.cozystack.io + apps.cozystack.io/application.name: tenant-root + name: tenant-root + namespace: tenant-root +spec: + chartRef: + kind: ExternalArtifact + name: cozystack-tenant-application-default-tenant + namespace: cozy-system + interval: 1m0s + timeout: 5m0s + values: + _cluster: + oidc-enabled: {{ .Values.oidcEnabled | quote }} + root-host: {{ .Values.rootHost | quote }} diff --git a/packages/system/cozystack-basics/values.yaml b/packages/system/cozystack-basics/values.yaml new file mode 100644 index 00000000..b8b5e457 --- /dev/null +++ b/packages/system/cozystack-basics/values.yaml @@ -0,0 +1 @@ +_cluster: {} diff --git a/packages/system/cozystack-resource-definition-crd/definition/cozystack.io_cozystackresourcedefinitions.yaml b/packages/system/cozystack-resource-definition-crd/definition/cozystack.io_cozystackresourcedefinitions.yaml index 685d4ac5..6c6c15f5 100644 --- a/packages/system/cozystack-resource-definition-crd/definition/cozystack.io_cozystackresourcedefinitions.yaml +++ b/packages/system/cozystack-resource-definition-crd/definition/cozystack.io_cozystackresourcedefinitions.yaml @@ -135,29 +135,22 @@ spec: If a resource matches the selector in any of the elements in the array, it is hidden from the user, regardless of the matches in the include array. items: - description: |- - CozystackResourceDefinitionResourceSelector extends metav1.LabelSelector with resourceNames support. - A resource matches this selector only if it satisfies ALL criteria: - - Label selector conditions (matchExpressions and matchLabels) - - AND has a name that matches one of the names in resourceNames (if specified) - - The resourceNames field supports Go templates with the following variables available: - - {{ .name }}: The name of the managing application (from apps.cozystack.io/application.name) - - {{ .kind }}: The lowercased kind of the managing application (from apps.cozystack.io/application.kind) - - {{ .namespace }}: The namespace of the resource being processed - - Example YAML: - secrets: - include: - - matchExpressions: - - key: badlabel - operator: DoesNotExist - matchLabels: - goodlabel: goodvalue - resourceNames: - - "{{ .name }}-secret" - - "{{ .kind }}-{{ .name }}-tls" - - "specificname" + description: "CozystackResourceDefinitionResourceSelector extends + metav1.LabelSelector with resourceNames support.\nA resource + matches this selector only if it satisfies ALL criteria:\n- + Label selector conditions (matchExpressions and matchLabels)\n- + AND has a name that matches one of the names in resourceNames + (if specified)\n\nThe resourceNames field supports Go templates + with the following variables available:\n- {{ .name }}: The + name of the managing application (from apps.cozystack.io/application.name)\n- + {{ .kind }}: The lowercased kind of the managing application + (from apps.cozystack.io/application.kind)\n- {{ .namespace + }}: The namespace of the resource being processed\n\nExample + YAML:\n\n\tsecrets:\n\t include:\n\t - matchExpressions:\n\t + \ - key: badlabel\n\t operator: DoesNotExist\n\t matchLabels:\n\t + \ goodlabel: goodvalue\n\t resourceNames:\n\t - + \"{{ .name }}-secret\"\n\t - \"{{ .kind }}-{{ .name }}-tls\"\n\t + \ - \"specificname\"" properties: matchExpressions: description: matchExpressions is a list of label selector @@ -217,29 +210,22 @@ spec: matches none of the selectors in the exclude array that resource is marked as a tenant resource and is visible to users. items: - description: |- - CozystackResourceDefinitionResourceSelector extends metav1.LabelSelector with resourceNames support. - A resource matches this selector only if it satisfies ALL criteria: - - Label selector conditions (matchExpressions and matchLabels) - - AND has a name that matches one of the names in resourceNames (if specified) - - The resourceNames field supports Go templates with the following variables available: - - {{ .name }}: The name of the managing application (from apps.cozystack.io/application.name) - - {{ .kind }}: The lowercased kind of the managing application (from apps.cozystack.io/application.kind) - - {{ .namespace }}: The namespace of the resource being processed - - Example YAML: - secrets: - include: - - matchExpressions: - - key: badlabel - operator: DoesNotExist - matchLabels: - goodlabel: goodvalue - resourceNames: - - "{{ .name }}-secret" - - "{{ .kind }}-{{ .name }}-tls" - - "specificname" + description: "CozystackResourceDefinitionResourceSelector extends + metav1.LabelSelector with resourceNames support.\nA resource + matches this selector only if it satisfies ALL criteria:\n- + Label selector conditions (matchExpressions and matchLabels)\n- + AND has a name that matches one of the names in resourceNames + (if specified)\n\nThe resourceNames field supports Go templates + with the following variables available:\n- {{ .name }}: The + name of the managing application (from apps.cozystack.io/application.name)\n- + {{ .kind }}: The lowercased kind of the managing application + (from apps.cozystack.io/application.kind)\n- {{ .namespace + }}: The namespace of the resource being processed\n\nExample + YAML:\n\n\tsecrets:\n\t include:\n\t - matchExpressions:\n\t + \ - key: badlabel\n\t operator: DoesNotExist\n\t matchLabels:\n\t + \ goodlabel: goodvalue\n\t resourceNames:\n\t - + \"{{ .name }}-secret\"\n\t - \"{{ .kind }}-{{ .name }}-tls\"\n\t + \ - \"specificname\"" properties: matchExpressions: description: matchExpressions is a list of label selector @@ -296,34 +282,34 @@ spec: release: description: Release configuration properties: - chart: - description: Helm chart configuration + chartRef: + description: Reference to the chart source properties: - name: - description: Name of the Helm chart + apiVersion: + description: APIVersion of the referent. + type: string + kind: + description: Kind of the referent. + enum: + - OCIRepository + - HelmChart + - ExternalArtifact + type: string + name: + description: Name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace of the referent, defaults to the namespace of the Kubernetes + resource object that contains the reference. + maxLength: 63 + minLength: 1 type: string - sourceRef: - description: Source reference for the Helm chart - properties: - kind: - default: HelmRepository - description: Kind of the source reference - type: string - name: - description: Name of the source reference - type: string - namespace: - default: cozy-public - description: Namespace of the source reference - type: string - required: - - kind - - name - - namespace - type: object required: + - kind - name - - sourceRef type: object labels: additionalProperties: @@ -334,7 +320,7 @@ spec: description: Prefix for the release name type: string required: - - chart + - chartRef - prefix type: object secrets: @@ -346,29 +332,22 @@ spec: If a resource matches the selector in any of the elements in the array, it is hidden from the user, regardless of the matches in the include array. items: - description: |- - CozystackResourceDefinitionResourceSelector extends metav1.LabelSelector with resourceNames support. - A resource matches this selector only if it satisfies ALL criteria: - - Label selector conditions (matchExpressions and matchLabels) - - AND has a name that matches one of the names in resourceNames (if specified) - - The resourceNames field supports Go templates with the following variables available: - - {{ .name }}: The name of the managing application (from apps.cozystack.io/application.name) - - {{ .kind }}: The lowercased kind of the managing application (from apps.cozystack.io/application.kind) - - {{ .namespace }}: The namespace of the resource being processed - - Example YAML: - secrets: - include: - - matchExpressions: - - key: badlabel - operator: DoesNotExist - matchLabels: - goodlabel: goodvalue - resourceNames: - - "{{ .name }}-secret" - - "{{ .kind }}-{{ .name }}-tls" - - "specificname" + description: "CozystackResourceDefinitionResourceSelector extends + metav1.LabelSelector with resourceNames support.\nA resource + matches this selector only if it satisfies ALL criteria:\n- + Label selector conditions (matchExpressions and matchLabels)\n- + AND has a name that matches one of the names in resourceNames + (if specified)\n\nThe resourceNames field supports Go templates + with the following variables available:\n- {{ .name }}: The + name of the managing application (from apps.cozystack.io/application.name)\n- + {{ .kind }}: The lowercased kind of the managing application + (from apps.cozystack.io/application.kind)\n- {{ .namespace + }}: The namespace of the resource being processed\n\nExample + YAML:\n\n\tsecrets:\n\t include:\n\t - matchExpressions:\n\t + \ - key: badlabel\n\t operator: DoesNotExist\n\t matchLabels:\n\t + \ goodlabel: goodvalue\n\t resourceNames:\n\t - + \"{{ .name }}-secret\"\n\t - \"{{ .kind }}-{{ .name }}-tls\"\n\t + \ - \"specificname\"" properties: matchExpressions: description: matchExpressions is a list of label selector @@ -428,29 +407,22 @@ spec: matches none of the selectors in the exclude array that resource is marked as a tenant resource and is visible to users. items: - description: |- - CozystackResourceDefinitionResourceSelector extends metav1.LabelSelector with resourceNames support. - A resource matches this selector only if it satisfies ALL criteria: - - Label selector conditions (matchExpressions and matchLabels) - - AND has a name that matches one of the names in resourceNames (if specified) - - The resourceNames field supports Go templates with the following variables available: - - {{ .name }}: The name of the managing application (from apps.cozystack.io/application.name) - - {{ .kind }}: The lowercased kind of the managing application (from apps.cozystack.io/application.kind) - - {{ .namespace }}: The namespace of the resource being processed - - Example YAML: - secrets: - include: - - matchExpressions: - - key: badlabel - operator: DoesNotExist - matchLabels: - goodlabel: goodvalue - resourceNames: - - "{{ .name }}-secret" - - "{{ .kind }}-{{ .name }}-tls" - - "specificname" + description: "CozystackResourceDefinitionResourceSelector extends + metav1.LabelSelector with resourceNames support.\nA resource + matches this selector only if it satisfies ALL criteria:\n- + Label selector conditions (matchExpressions and matchLabels)\n- + AND has a name that matches one of the names in resourceNames + (if specified)\n\nThe resourceNames field supports Go templates + with the following variables available:\n- {{ .name }}: The + name of the managing application (from apps.cozystack.io/application.name)\n- + {{ .kind }}: The lowercased kind of the managing application + (from apps.cozystack.io/application.kind)\n- {{ .namespace + }}: The namespace of the resource being processed\n\nExample + YAML:\n\n\tsecrets:\n\t include:\n\t - matchExpressions:\n\t + \ - key: badlabel\n\t operator: DoesNotExist\n\t matchLabels:\n\t + \ goodlabel: goodvalue\n\t resourceNames:\n\t - + \"{{ .name }}-secret\"\n\t - \"{{ .kind }}-{{ .name }}-tls\"\n\t + \ - \"specificname\"" properties: matchExpressions: description: matchExpressions is a list of label selector @@ -513,29 +485,22 @@ spec: If a resource matches the selector in any of the elements in the array, it is hidden from the user, regardless of the matches in the include array. items: - description: |- - CozystackResourceDefinitionResourceSelector extends metav1.LabelSelector with resourceNames support. - A resource matches this selector only if it satisfies ALL criteria: - - Label selector conditions (matchExpressions and matchLabels) - - AND has a name that matches one of the names in resourceNames (if specified) - - The resourceNames field supports Go templates with the following variables available: - - {{ .name }}: The name of the managing application (from apps.cozystack.io/application.name) - - {{ .kind }}: The lowercased kind of the managing application (from apps.cozystack.io/application.kind) - - {{ .namespace }}: The namespace of the resource being processed - - Example YAML: - secrets: - include: - - matchExpressions: - - key: badlabel - operator: DoesNotExist - matchLabels: - goodlabel: goodvalue - resourceNames: - - "{{ .name }}-secret" - - "{{ .kind }}-{{ .name }}-tls" - - "specificname" + description: "CozystackResourceDefinitionResourceSelector extends + metav1.LabelSelector with resourceNames support.\nA resource + matches this selector only if it satisfies ALL criteria:\n- + Label selector conditions (matchExpressions and matchLabels)\n- + AND has a name that matches one of the names in resourceNames + (if specified)\n\nThe resourceNames field supports Go templates + with the following variables available:\n- {{ .name }}: The + name of the managing application (from apps.cozystack.io/application.name)\n- + {{ .kind }}: The lowercased kind of the managing application + (from apps.cozystack.io/application.kind)\n- {{ .namespace + }}: The namespace of the resource being processed\n\nExample + YAML:\n\n\tsecrets:\n\t include:\n\t - matchExpressions:\n\t + \ - key: badlabel\n\t operator: DoesNotExist\n\t matchLabels:\n\t + \ goodlabel: goodvalue\n\t resourceNames:\n\t - + \"{{ .name }}-secret\"\n\t - \"{{ .kind }}-{{ .name }}-tls\"\n\t + \ - \"specificname\"" properties: matchExpressions: description: matchExpressions is a list of label selector @@ -595,29 +560,22 @@ spec: matches none of the selectors in the exclude array that resource is marked as a tenant resource and is visible to users. items: - description: |- - CozystackResourceDefinitionResourceSelector extends metav1.LabelSelector with resourceNames support. - A resource matches this selector only if it satisfies ALL criteria: - - Label selector conditions (matchExpressions and matchLabels) - - AND has a name that matches one of the names in resourceNames (if specified) - - The resourceNames field supports Go templates with the following variables available: - - {{ .name }}: The name of the managing application (from apps.cozystack.io/application.name) - - {{ .kind }}: The lowercased kind of the managing application (from apps.cozystack.io/application.kind) - - {{ .namespace }}: The namespace of the resource being processed - - Example YAML: - secrets: - include: - - matchExpressions: - - key: badlabel - operator: DoesNotExist - matchLabels: - goodlabel: goodvalue - resourceNames: - - "{{ .name }}-secret" - - "{{ .kind }}-{{ .name }}-tls" - - "specificname" + description: "CozystackResourceDefinitionResourceSelector extends + metav1.LabelSelector with resourceNames support.\nA resource + matches this selector only if it satisfies ALL criteria:\n- + Label selector conditions (matchExpressions and matchLabels)\n- + AND has a name that matches one of the names in resourceNames + (if specified)\n\nThe resourceNames field supports Go templates + with the following variables available:\n- {{ .name }}: The + name of the managing application (from apps.cozystack.io/application.name)\n- + {{ .kind }}: The lowercased kind of the managing application + (from apps.cozystack.io/application.kind)\n- {{ .namespace + }}: The namespace of the resource being processed\n\nExample + YAML:\n\n\tsecrets:\n\t include:\n\t - matchExpressions:\n\t + \ - key: badlabel\n\t operator: DoesNotExist\n\t matchLabels:\n\t + \ goodlabel: goodvalue\n\t resourceNames:\n\t - + \"{{ .name }}-secret\"\n\t - \"{{ .kind }}-{{ .name }}-tls\"\n\t + \ - \"specificname\"" properties: matchExpressions: description: matchExpressions is a list of label selector diff --git a/packages/system/grafana-operator/templates/dashboards-deployment.yaml b/packages/system/grafana-operator/templates/dashboards-deployment.yaml new file mode 100644 index 00000000..f9b1f99b --- /dev/null +++ b/packages/system/grafana-operator/templates/dashboards-deployment.yaml @@ -0,0 +1,41 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: grafana-dashboards + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: grafana-dashboards + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: dashboards +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: grafana-dashboards + app.kubernetes.io/instance: {{ .Release.Name }} + template: + metadata: + labels: + app.kubernetes.io/name: grafana-dashboards + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: dashboards + spec: + containers: + - name: dashboards + image: {{ $.Files.Get "images/grafana-dashboards.tag" | trim }} + ports: + - containerPort: 8080 + name: http + protocol: TCP + livenessProbe: + httpGet: + path: / + port: 8080 + initialDelaySeconds: 10 + periodSeconds: 10 + readinessProbe: + httpGet: + path: / + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 5 diff --git a/packages/system/grafana-operator/templates/dashboards-service.yaml b/packages/system/grafana-operator/templates/dashboards-service.yaml new file mode 100644 index 00000000..011072c3 --- /dev/null +++ b/packages/system/grafana-operator/templates/dashboards-service.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Service +metadata: + name: grafana-dashboards + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: grafana-dashboards + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: dashboards +spec: + type: ClusterIP + ports: + - port: 80 + targetPort: 8080 + protocol: TCP + name: http + selector: + app.kubernetes.io/name: grafana-dashboards + app.kubernetes.io/instance: {{ .Release.Name }} 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 cd0728b3..219e4ca0 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/controller-deploy.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/controller-deploy.yaml @@ -74,19 +74,18 @@ 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={{ index $cozyConfig.data "ipv4-pod-cidr" }} - - --default-gateway={{ index $cozyConfig.data "ipv4-pod-gateway" }} + - --default-cidr={{ .Values.ipv4.POD_CIDR }} + - --default-gateway={{ .Values.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={{ index $cozyConfig.data "ipv4-join-cidr" }} - - --service-cluster-ip-range={{ index $cozyConfig.data "ipv4-svc-cidr" }} + - --node-switch-cidr={{ .Values.ipv4.JOIN_CIDR }} + - --service-cluster-ip-range={{ .Values.ipv4.SVC_CIDR }} {{- if .Values.global.logVerbosity }} - --v={{ .Values.global.logVerbosity }} {{- end }} 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 c68e041d..d53c06c2 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/ovncni-ds.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/ovncni-ds.yaml @@ -94,12 +94,11 @@ 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={{ index $cozyConfig.data "ipv4-svc-cidr" }} + - --service-cluster-ip-range={{ .Values.ipv4.SVC_CIDR }} {{- if .Values.global.logVerbosity }} - --v={{ .Values.global.logVerbosity }} {{- end }} 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 deleted file mode 100644 index 65c8be7c..00000000 --- a/packages/system/linstor/images/piraeus-server/patches/skip-adjust-when-device-inaccessible.diff +++ /dev/null @@ -1,260 +0,0 @@ -From 6a556821b9a0996d34389a27b941694ce810a44c Mon Sep 17 00:00:00 2001 -From: Andrei Kvapil -Date: Mon, 12 Jan 2026 14:26:57 +0100 -Subject: [PATCH 1/3] Fix: Skip DRBD adjust/res file regeneration when child - layer device is inaccessible - -When deleting encrypted (LUKS) resources, the LUKS layer may close its device -before the DRBD layer attempts to adjust the resource or regenerate the res -file. This causes 'Failed to adjust DRBD resource' errors. - -This fix adds checks before regenerateResFile() and drbdUtils.adjust() -to verify that child layer devices are accessible. If a child device doesn't -exist or is not accessible (e.g., LUKS device is closed during resource -deletion), these operations are skipped and the adjustRequired flag is cleared, -allowing resource deletion to proceed successfully. - -Co-Authored-By: Claude -Signed-off-by: Andrei Kvapil ---- - .../linbit/linstor/layer/drbd/DrbdLayer.java | 72 ++++++++++++++++--- - 1 file changed, 61 insertions(+), 11 deletions(-) - -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 01967a31f..871d830d1 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); - } - } - --- -2.39.5 (Apple Git-154) - - -From afe51ea674c4a350c27d1f2cacfecf6fe42b8a7a Mon Sep 17 00:00:00 2001 -From: Andrei Kvapil -Date: Mon, 12 Jan 2026 14:27:52 +0100 -Subject: [PATCH 2/3] fix(satellite): skip lsblk when device path doesn't - physically exist - -Add physical device path existence check before calling lsblk in updateDiscGran(). -This prevents race condition when drbdadm adjust temporarily brings devices down/up -and the kernel hasn't created the device node yet. - -Issue: After satellite restart with patched code, some DRBD resources ended up in -Unknown state because: -1. drbdadm adjust successfully completes (brings devices up) -2. updateDiscGran() immediately tries to check discard granularity -3. /dev/drbd* device node doesn't exist yet (kernel hasn't created it) -4. lsblk fails with exit code 32 "not a block device" -5. StorageException interrupts DeviceManager cycle -6. DRBD device remains in incomplete state - -Solution: Check Files.exists(devicePath) before calling lsblk. If device doesn't -exist yet, skip the check - it will be retried in the next DeviceManager cycle -when the device node is available. - -Co-Authored-By: Claude -Signed-off-by: Andrei Kvapil ---- - .../linbit/linstor/core/devmgr/DeviceHandlerImpl.java | 9 +++++++-- - 1 file changed, 7 insertions(+), 2 deletions(-) - -diff --git a/satellite/src/main/java/com/linbit/linstor/core/devmgr/DeviceHandlerImpl.java b/satellite/src/main/java/com/linbit/linstor/core/devmgr/DeviceHandlerImpl.java -index 49138a8fd..1c13cfc9d 100644 ---- a/satellite/src/main/java/com/linbit/linstor/core/devmgr/DeviceHandlerImpl.java -+++ b/satellite/src/main/java/com/linbit/linstor/core/devmgr/DeviceHandlerImpl.java -@@ -68,6 +68,8 @@ import javax.inject.Inject; - import javax.inject.Provider; - import javax.inject.Singleton; - -+import java.nio.file.Files; -+import java.nio.file.Paths; - import java.util.ArrayList; - import java.util.Collection; - import java.util.Collections; -@@ -1645,8 +1647,11 @@ public class DeviceHandlerImpl implements DeviceHandler - - private void updateDiscGran(VlmProviderObject vlmData) throws DatabaseException, StorageException - { -- String devicePath = vlmData.getDevicePath(); -- if (devicePath != null && vlmData.exists()) -+ @Nullable String devicePath = vlmData.getDevicePath(); -+ // Check if device path physically exists before calling lsblk -+ // This is important for DRBD devices which might be temporarily unavailable during adjust -+ // (drbdadm adjust brings devices down/up, and kernel might not have created the device node yet) -+ if (devicePath != null && vlmData.exists() && Files.exists(Paths.get(devicePath))) - { - if (vlmData.getDiscGran() == VlmProviderObject.UNINITIALIZED_SIZE) - { --- -2.39.5 (Apple Git-154) - - -From de1f22e7c008c5479f85a3b1ebdf8461944210f4 Mon Sep 17 00:00:00 2001 -From: Andrei Kvapil -Date: Mon, 12 Jan 2026 14:28:23 +0100 -Subject: [PATCH 3/3] fix(drbd): only check child devices when disk access is - actually needed -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -The previous implementation blocked `drbdadm adjust` whenever child -device paths were unavailable, even for operations that don't require -disk access (like network reconnect from StandAlone to Connected). - -After node reboot, DRBD resources often remain in StandAlone state -because: -1. updateResourceToCurrentDrbdState() correctly detects StandAlone - and sets adjustRequired=true -2. However, canAdjust check fails because child volumes may have - devicePath=null (due to INACTIVE flag, cloning state, or - initialization race) -3. adjust is skipped → adjustRequired=false → resources stay StandAlone - -Root cause: The canAdjust check was added to protect LUKS deletion -scenarios but was applied to ALL cases, including network reconnect -which doesn't need disk access. - -Fix: Check child device accessibility only when disk access is actually -required (volume creation, resize, metadata operations). Network -reconnect operations (StandAlone → Connected) now proceed without -checking child devices. - -This ensures automatic DRBD reconnection after reboot while preserving -protection for LUKS deletion scenarios. - -Co-Authored-By: Claude -Signed-off-by: Andrei Kvapil ---- - .../linbit/linstor/layer/drbd/DrbdLayer.java | 27 ++++++++++++++++++- - 1 file changed, 26 insertions(+), 1 deletion(-) - -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 871d830d1..78b8195a4 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 -@@ -792,7 +792,32 @@ public class DrbdLayer implements DeviceLayer - // This is important for encrypted resources (LUKS) where the device - // might be closed during deletion - boolean canAdjust = true; -- if (!skipDisk && !drbdRscData.getAbsResource().isDrbdDiskless(workerCtx)) -+ -+ // IMPORTANT: Check child volumes only when disk access is actually needed. -+ // For network reconnect (StandAlone -> Connected), disk access is not required. -+ boolean needsDiskAccess = false; -+ -+ // Check if there are pending operations that require disk access -+ for (DrbdVlmData drbdVlmData : drbdRscData.getVlmLayerObjects().values()) -+ { -+ Volume vlm = (Volume) drbdVlmData.getVolume(); -+ StateFlags vlmFlags = vlm.getFlags(); -+ -+ // Disk access is needed if: -+ // - creating a new volume -+ // - resizing -+ // - checking/creating metadata -+ if (!drbdVlmData.exists() || -+ drbdVlmData.checkMetaData() || -+ vlmFlags.isSomeSet(workerCtx, Volume.Flags.RESIZE, Volume.Flags.DRBD_RESIZE)) -+ { -+ needsDiskAccess = true; -+ break; -+ } -+ } -+ -+ // Check child volumes only if disk access is actually needed -+ if (needsDiskAccess && !skipDisk && !drbdRscData.getAbsResource().isDrbdDiskless(workerCtx)) - { - AbsRscLayerObject dataChild = drbdRscData.getChildBySuffix(RscLayerSuffixes.SUFFIX_DATA); - if (dataChild != null) --- -2.39.5 (Apple Git-154) - diff --git a/packages/system/vertical-pod-autoscaler/templates/vpa-for-vpa.yaml b/packages/system/vertical-pod-autoscaler/templates/vpa-for-vpa.yaml index 5fb35fca..00fcadd7 100644 --- a/packages/system/vertical-pod-autoscaler/templates/vpa-for-vpa.yaml +++ b/packages/system/vertical-pod-autoscaler/templates/vpa-for-vpa.yaml @@ -16,15 +16,10 @@ metadata: name: vpa-for-vpa namespace: cozy-vpa-for-vpa spec: - chart: - spec: - chart: cozy-vertical-pod-autoscaler - reconcileStrategy: Revision - sourceRef: - kind: HelmRepository - name: cozystack-system - namespace: cozy-system - version: '>= 0.0.0-0' + chartRef: + kind: ExternalArtifact + name: cozystack-system-default-vertical-pod-autoscaler + namespace: cozy-system dependsOn: - name: monitoring-agents namespace: cozy-monitoring From 3618abed627f3a771ad611ae4cdf3e5e7d73c4c1 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 15 Jan 2026 16:06:56 +0100 Subject: [PATCH 053/889] refactor: move scripts to hack directory Move common-envs.mk and package.mk from scripts/ to hack/ directory. Update all Makefile includes to use new paths. Remove unused issue-flux-certificates.sh script. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- Makefile | 2 + {scripts => hack}/common-envs.mk | 0 {scripts => hack}/package.mk | 0 packages/apps/Makefile | 2 +- packages/apps/bucket/Makefile | 2 +- packages/apps/clickhouse/Makefile | 4 +- packages/apps/ferretdb/Makefile | 2 +- packages/apps/foundationdb/Makefile | 2 +- packages/apps/http-cache/Makefile | 4 +- packages/apps/kafka/Makefile | 2 +- packages/apps/kubernetes/Makefile | 4 +- packages/apps/mysql/Makefile | 4 +- packages/apps/nats/Makefile | 2 +- packages/apps/postgres/Makefile | 2 +- packages/apps/rabbitmq/Makefile | 2 +- packages/apps/redis/Makefile | 2 +- packages/apps/tcp-balancer/Makefile | 2 +- packages/apps/tenant/Makefile | 2 +- packages/apps/virtual-machine/Makefile | 2 +- packages/apps/vm-disk/Makefile | 2 +- packages/apps/vm-instance/Makefile | 2 +- packages/apps/vpc/Makefile | 2 +- packages/apps/vpn/Makefile | 2 +- packages/core/flux-aio/Makefile | 2 +- packages/core/installer/Makefile | 2 +- packages/core/platform/Makefile | 2 +- packages/core/talos/Makefile | 2 +- packages/core/testing/Makefile | 2 +- packages/extra/Makefile | 2 +- packages/extra/bootbox/Makefile | 2 +- packages/extra/etcd/Makefile | 2 +- packages/extra/info/Makefile | 2 +- packages/extra/ingress/Makefile | 2 +- packages/extra/monitoring/Makefile | 4 +- packages/extra/seaweedfs/Makefile | 2 +- packages/library/Makefile | 2 +- packages/library/cozy-lib/Makefile | 4 +- packages/system/Makefile | 2 +- packages/system/backup-controller/Makefile | 4 +- .../system/backupstrategy-controller/Makefile | 4 +- packages/system/bootbox-rd/Makefile | 2 +- packages/system/bootbox/Makefile | 2 +- packages/system/bucket-rd/Makefile | 2 +- packages/system/bucket/Makefile | 4 +- packages/system/capi-operator/Makefile | 2 +- .../system/capi-providers-bootstrap/Makefile | 2 +- packages/system/capi-providers-core/Makefile | 2 +- .../system/capi-providers-cpprovider/Makefile | 2 +- .../capi-providers-infraprovider/Makefile | 2 +- packages/system/cert-manager-crds/Makefile | 2 +- packages/system/cert-manager-issuers/Makefile | 2 +- packages/system/cert-manager/Makefile | 2 +- packages/system/cilium-networkpolicy/Makefile | 4 +- packages/system/cilium/Makefile | 4 +- packages/system/clickhouse-operator/Makefile | 2 +- packages/system/clickhouse-rd/Makefile | 2 +- packages/system/coredns/Makefile | 2 +- packages/system/cozy-proxy/Makefile | 4 +- packages/system/cozystack-api/Makefile | 4 +- packages/system/cozystack-basics/Makefile | 2 +- packages/system/cozystack-controller/Makefile | 4 +- .../Makefile | 2 +- packages/system/dashboard/Makefile | 4 +- packages/system/etcd-operator/Makefile | 2 +- packages/system/etcd-rd/Makefile | 2 +- packages/system/external-dns/Makefile | 2 +- .../system/external-secrets-operator/Makefile | 2 +- packages/system/ferretdb-rd/Makefile | 2 +- packages/system/fluxcd-operator/Makefile | 2 +- packages/system/fluxcd/Makefile | 2 +- .../system/foundationdb-operator/Makefile | 2 +- packages/system/foundationdb-rd/Makefile | 2 +- packages/system/gateway-api-crds/Makefile | 2 +- packages/system/goldpinger/Makefile | 2 +- packages/system/gpu-operator/Makefile | 4 +- packages/system/grafana-operator/Makefile | 4 +- packages/system/hetzner-robotlb/Makefile | 2 +- packages/system/http-cache-rd/Makefile | 2 +- packages/system/info-rd/Makefile | 2 +- packages/system/ingress-nginx/Makefile | 2 +- packages/system/ingress-rd/Makefile | 2 +- packages/system/kafka-operator/Makefile | 2 +- packages/system/kafka-rd/Makefile | 2 +- packages/system/kamaji/Makefile | 4 +- packages/system/keycloak-configure/Makefile | 4 +- packages/system/keycloak-operator/Makefile | 4 +- packages/system/keycloak/Makefile | 4 +- packages/system/kubeovn-plunger/Makefile | 4 +- packages/system/kubeovn-webhook/Makefile | 4 +- packages/system/kubeovn/Makefile | 4 +- packages/system/kubernetes-rd/Makefile | 2 +- .../system/kubevirt-cdi-operator/Makefile | 2 +- packages/system/kubevirt-cdi/Makefile | 2 +- .../system/kubevirt-instancetypes/Makefile | 2 +- packages/system/kubevirt-operator/Makefile | 2 +- packages/system/kubevirt/Makefile | 2 +- .../lineage-controller-webhook/Makefile | 4 +- packages/system/linstor-scheduler/Makefile | 2 +- packages/system/linstor/Makefile | 4 +- packages/system/mariadb-operator/Makefile | 2 +- packages/system/metallb/Makefile | 4 +- packages/system/metrics-server/Makefile | 2 +- packages/system/monitoring-agents/Makefile | 2 +- packages/system/monitoring-rd/Makefile | 2 +- packages/system/multus/Makefile | 4 +- packages/system/mysql-rd/Makefile | 2 +- packages/system/nats-rd/Makefile | 2 +- packages/system/nats/Makefile | 2 +- packages/system/nfs-driver/Makefile | 4 +- .../system/objectstorage-controller/Makefile | 4 +- packages/system/piraeus-operator/Makefile | 2 +- packages/system/postgres-operator/Makefile | 2 +- packages/system/postgres-rd/Makefile | 2 +- .../system/prometheus-operator-crds/Makefile | 2 +- packages/system/rabbitmq-operator/Makefile | 2 +- packages/system/rabbitmq-rd/Makefile | 2 +- packages/system/redis-operator/Makefile | 4 +- packages/system/redis-rd/Makefile | 2 +- packages/system/reloader/Makefile | 2 +- packages/system/seaweedfs-rd/Makefile | 2 +- packages/system/seaweedfs/Makefile | 4 +- packages/system/snapshot-controller/Makefile | 2 +- packages/system/tcp-balancer-rd/Makefile | 2 +- packages/system/telepresence/Makefile | 2 +- packages/system/tenant-rd/Makefile | 2 +- packages/system/velero/Makefile | 2 +- .../vertical-pod-autoscaler-crds/Makefile | 2 +- .../system/vertical-pod-autoscaler/Makefile | 2 +- .../system/victoria-metrics-operator/Makefile | 2 +- packages/system/virtual-machine-rd/Makefile | 2 +- .../system/virtualprivatecloud-rd/Makefile | 2 +- packages/system/vm-disk-rd/Makefile | 2 +- packages/system/vm-instance-rd/Makefile | 2 +- packages/system/vpn-rd/Makefile | 2 +- packages/system/vsnap-crd/Makefile | 2 +- scripts/issue-flux-certificates.sh | 63 ------------------- 136 files changed, 166 insertions(+), 227 deletions(-) rename {scripts => hack}/common-envs.mk (100%) rename {scripts => hack}/package.mk (100%) delete mode 100755 scripts/issue-flux-certificates.sh diff --git a/Makefile b/Makefile index 971775cb..c037e023 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,7 @@ .PHONY: manifests assets unit-tests helm-unit-tests +include hack/common-envs.mk + build-deps: @command -V find docker skopeo jq gh helm > /dev/null @yq --version | grep -q "mikefarah" || (echo "mikefarah/yq is required" && exit 1) diff --git a/scripts/common-envs.mk b/hack/common-envs.mk similarity index 100% rename from scripts/common-envs.mk rename to hack/common-envs.mk diff --git a/scripts/package.mk b/hack/package.mk similarity index 100% rename from scripts/package.mk rename to hack/package.mk diff --git a/packages/apps/Makefile b/packages/apps/Makefile index b3917f20..50502f2b 100644 --- a/packages/apps/Makefile +++ b/packages/apps/Makefile @@ -1,7 +1,7 @@ OUT=../../_out/repos/apps CHARTS := $(shell find . -maxdepth 2 -name Chart.yaml | awk -F/ '{print $$2}') -include ../../scripts/common-envs.mk +include ../../hack/common-envs.mk repo: rm -rf "$(OUT)" diff --git a/packages/apps/bucket/Makefile b/packages/apps/bucket/Makefile index d448d0c0..27cd199b 100644 --- a/packages/apps/bucket/Makefile +++ b/packages/apps/bucket/Makefile @@ -1,4 +1,4 @@ -include ../../../scripts/package.mk +include ../../../hack/package.mk generate: cozyvalues-gen -v values.yaml -s values.schema.json -r README.md diff --git a/packages/apps/clickhouse/Makefile b/packages/apps/clickhouse/Makefile index 44ac851f..2c77add9 100644 --- a/packages/apps/clickhouse/Makefile +++ b/packages/apps/clickhouse/Makefile @@ -1,7 +1,7 @@ CLICKHOUSE_BACKUP_TAG = $(shell awk '$$0 ~ /^version:/ {print $$2}' Chart.yaml) -include ../../../scripts/common-envs.mk -include ../../../scripts/package.mk +include ../../../hack/common-envs.mk +include ../../../hack/package.mk generate: cozyvalues-gen -v values.yaml -s values.schema.json -r README.md diff --git a/packages/apps/ferretdb/Makefile b/packages/apps/ferretdb/Makefile index bd52fb05..40b7423f 100644 --- a/packages/apps/ferretdb/Makefile +++ b/packages/apps/ferretdb/Makefile @@ -1,4 +1,4 @@ -include ../../../scripts/package.mk +include ../../../hack/package.mk generate: cozyvalues-gen -v values.yaml -s values.schema.json -r README.md diff --git a/packages/apps/foundationdb/Makefile b/packages/apps/foundationdb/Makefile index b885e4b1..76c84980 100644 --- a/packages/apps/foundationdb/Makefile +++ b/packages/apps/foundationdb/Makefile @@ -1,4 +1,4 @@ -include ../../../scripts/package.mk +include ../../../hack/package.mk generate: cozyvalues-gen -v values.yaml -s values.schema.json -r README.md \ No newline at end of file diff --git a/packages/apps/http-cache/Makefile b/packages/apps/http-cache/Makefile index 3237f634..2cc11f87 100644 --- a/packages/apps/http-cache/Makefile +++ b/packages/apps/http-cache/Makefile @@ -1,7 +1,7 @@ NGINX_CACHE_TAG = $(shell awk '$$1 == "version:" {print $$2}' Chart.yaml) -include ../../../scripts/common-envs.mk -include ../../../scripts/package.mk +include ../../../hack/common-envs.mk +include ../../../hack/package.mk image: image-nginx diff --git a/packages/apps/kafka/Makefile b/packages/apps/kafka/Makefile index 0d71076e..2cffdfa7 100644 --- a/packages/apps/kafka/Makefile +++ b/packages/apps/kafka/Makefile @@ -1,4 +1,4 @@ -include ../../../scripts/package.mk +include ../../../hack/package.mk PRESET_ENUM := ["nano","micro","small","medium","large","xlarge","2xlarge"] generate: diff --git a/packages/apps/kubernetes/Makefile b/packages/apps/kubernetes/Makefile index ae6dd757..e6ed7c6f 100644 --- a/packages/apps/kubernetes/Makefile +++ b/packages/apps/kubernetes/Makefile @@ -1,8 +1,8 @@ KUBERNETES_VERSION = v1.33 KUBERNETES_PKG_TAG = $(shell awk '$$1 == "version:" {print $$2}' Chart.yaml) -include ../../../scripts/common-envs.mk -include ../../../scripts/package.mk +include ../../../hack/common-envs.mk +include ../../../hack/package.mk generate: cozyvalues-gen -v values.yaml -s values.schema.json -r README.md diff --git a/packages/apps/mysql/Makefile b/packages/apps/mysql/Makefile index f4ea3f78..e8f02703 100644 --- a/packages/apps/mysql/Makefile +++ b/packages/apps/mysql/Makefile @@ -1,7 +1,7 @@ MARIADB_BACKUP_TAG = $(shell awk '$$1 == "version:" {print $$2}' Chart.yaml) -include ../../../scripts/common-envs.mk -include ../../../scripts/package.mk +include ../../../hack/common-envs.mk +include ../../../hack/package.mk generate: cozyvalues-gen -v values.yaml -s values.schema.json -r README.md diff --git a/packages/apps/nats/Makefile b/packages/apps/nats/Makefile index 8b1dce9d..d1cfda8e 100644 --- a/packages/apps/nats/Makefile +++ b/packages/apps/nats/Makefile @@ -1,4 +1,4 @@ -include ../../../scripts/package.mk +include ../../../hack/package.mk generate: cozyvalues-gen -v values.yaml -s values.schema.json -r README.md diff --git a/packages/apps/postgres/Makefile b/packages/apps/postgres/Makefile index a2ba188f..aa9b0ccc 100644 --- a/packages/apps/postgres/Makefile +++ b/packages/apps/postgres/Makefile @@ -1,4 +1,4 @@ -include ../../../scripts/package.mk +include ../../../hack/package.mk generate: cozyvalues-gen -v values.yaml -s values.schema.json -r README.md diff --git a/packages/apps/rabbitmq/Makefile b/packages/apps/rabbitmq/Makefile index 8b1dce9d..d1cfda8e 100644 --- a/packages/apps/rabbitmq/Makefile +++ b/packages/apps/rabbitmq/Makefile @@ -1,4 +1,4 @@ -include ../../../scripts/package.mk +include ../../../hack/package.mk generate: cozyvalues-gen -v values.yaml -s values.schema.json -r README.md diff --git a/packages/apps/redis/Makefile b/packages/apps/redis/Makefile index a2ba188f..aa9b0ccc 100644 --- a/packages/apps/redis/Makefile +++ b/packages/apps/redis/Makefile @@ -1,4 +1,4 @@ -include ../../../scripts/package.mk +include ../../../hack/package.mk generate: cozyvalues-gen -v values.yaml -s values.schema.json -r README.md diff --git a/packages/apps/tcp-balancer/Makefile b/packages/apps/tcp-balancer/Makefile index 8b1dce9d..d1cfda8e 100644 --- a/packages/apps/tcp-balancer/Makefile +++ b/packages/apps/tcp-balancer/Makefile @@ -1,4 +1,4 @@ -include ../../../scripts/package.mk +include ../../../hack/package.mk generate: cozyvalues-gen -v values.yaml -s values.schema.json -r README.md diff --git a/packages/apps/tenant/Makefile b/packages/apps/tenant/Makefile index 8b1dce9d..d1cfda8e 100644 --- a/packages/apps/tenant/Makefile +++ b/packages/apps/tenant/Makefile @@ -1,4 +1,4 @@ -include ../../../scripts/package.mk +include ../../../hack/package.mk generate: cozyvalues-gen -v values.yaml -s values.schema.json -r README.md diff --git a/packages/apps/virtual-machine/Makefile b/packages/apps/virtual-machine/Makefile index b70ac622..7c482228 100644 --- a/packages/apps/virtual-machine/Makefile +++ b/packages/apps/virtual-machine/Makefile @@ -1,4 +1,4 @@ -include ../../../scripts/package.mk +include ../../../hack/package.mk generate: cozyvalues-gen -v values.yaml -s values.schema.json -r README.md diff --git a/packages/apps/vm-disk/Makefile b/packages/apps/vm-disk/Makefile index 8b1dce9d..d1cfda8e 100644 --- a/packages/apps/vm-disk/Makefile +++ b/packages/apps/vm-disk/Makefile @@ -1,4 +1,4 @@ -include ../../../scripts/package.mk +include ../../../hack/package.mk generate: cozyvalues-gen -v values.yaml -s values.schema.json -r README.md diff --git a/packages/apps/vm-instance/Makefile b/packages/apps/vm-instance/Makefile index 105ca7db..3b6d471d 100644 --- a/packages/apps/vm-instance/Makefile +++ b/packages/apps/vm-instance/Makefile @@ -1,4 +1,4 @@ -include ../../../scripts/package.mk +include ../../../hack/package.mk generate: cozyvalues-gen -v values.yaml -s values.schema.json -r README.md diff --git a/packages/apps/vpc/Makefile b/packages/apps/vpc/Makefile index 8807454f..ea4f5d1b 100644 --- a/packages/apps/vpc/Makefile +++ b/packages/apps/vpc/Makefile @@ -1,4 +1,4 @@ -include ../../../scripts/package.mk +include ../../../hack/package.mk generate: cozyvalues-gen -v values.yaml -s values.schema.json diff --git a/packages/apps/vpn/Makefile b/packages/apps/vpn/Makefile index 8b1dce9d..d1cfda8e 100644 --- a/packages/apps/vpn/Makefile +++ b/packages/apps/vpn/Makefile @@ -1,4 +1,4 @@ -include ../../../scripts/package.mk +include ../../../hack/package.mk generate: cozyvalues-gen -v values.yaml -s values.schema.json -r README.md diff --git a/packages/core/flux-aio/Makefile b/packages/core/flux-aio/Makefile index 0121d6d4..c5eb27f9 100644 --- a/packages/core/flux-aio/Makefile +++ b/packages/core/flux-aio/Makefile @@ -1,7 +1,7 @@ NAME=flux-aio NAMESPACE=cozy-$(NAME) -include ../../../scripts/common-envs.mk +include ../../../hack/common-envs.mk show: cozyhr show -n $(NAMESPACE) $(NAME) --plain diff --git a/packages/core/installer/Makefile b/packages/core/installer/Makefile index f4b527bf..84f3cbf0 100644 --- a/packages/core/installer/Makefile +++ b/packages/core/installer/Makefile @@ -1,7 +1,7 @@ NAME=installer NAMESPACE=cozy-system -include ../../../scripts/common-envs.mk +include ../../../hack/common-envs.mk pre-checks: ../../../hack/pre-checks.sh diff --git a/packages/core/platform/Makefile b/packages/core/platform/Makefile index d5d63e6e..7a53efa7 100644 --- a/packages/core/platform/Makefile +++ b/packages/core/platform/Makefile @@ -1,7 +1,7 @@ NAME=platform NAMESPACE=cozy-system -include ../../../scripts/common-envs.mk +include ../../../hack/common-envs.mk show: cozyhr show --namespace $(NAMESPACE) $(NAME) --plain diff --git a/packages/core/talos/Makefile b/packages/core/talos/Makefile index 24d9c9f2..4e8a8dfb 100644 --- a/packages/core/talos/Makefile +++ b/packages/core/talos/Makefile @@ -3,7 +3,7 @@ NAMESPACE=cozy-system TALOS_VERSION=$(shell awk '/^version:/ {print $$2}' images/talos/profiles/installer.yaml) -include ../../../scripts/common-envs.mk +include ../../../hack/common-envs.mk update: hack/gen-profiles.sh diff --git a/packages/core/testing/Makefile b/packages/core/testing/Makefile index f345d155..46705011 100644 --- a/packages/core/testing/Makefile +++ b/packages/core/testing/Makefile @@ -6,7 +6,7 @@ SANDBOX_NAME := cozy-e2e-sandbox-$(shell echo "$$(hostname):$$(pwd)" | sha256sum ROOT_DIR = $(dir $(abspath $(firstword $(MAKEFILE_LIST))/../../..)) -include ../../../scripts/common-envs.mk +include ../../../hack/common-envs.mk help: ## Show this help. diff --git a/packages/extra/Makefile b/packages/extra/Makefile index 5872855b..dd3293a0 100644 --- a/packages/extra/Makefile +++ b/packages/extra/Makefile @@ -1,7 +1,7 @@ OUT=../../_out/repos/extra CHARTS := $(shell find . -maxdepth 2 -name Chart.yaml | awk -F/ '{print $$2}') -include ../../scripts/common-envs.mk +include ../../hack/common-envs.mk repo: rm -rf "$(OUT)" diff --git a/packages/extra/bootbox/Makefile b/packages/extra/bootbox/Makefile index d9a1e261..57fa0a77 100644 --- a/packages/extra/bootbox/Makefile +++ b/packages/extra/bootbox/Makefile @@ -1,7 +1,7 @@ NAME=bootbox NAMESPACE=tenant-root -include ../../../scripts/package.mk +include ../../../hack/package.mk generate: cozyvalues-gen -v values.yaml -s values.schema.json -r README.md diff --git a/packages/extra/etcd/Makefile b/packages/extra/etcd/Makefile index b309346c..f37d6e1e 100644 --- a/packages/extra/etcd/Makefile +++ b/packages/extra/etcd/Makefile @@ -1,6 +1,6 @@ NAME=etcd -include ../../../scripts/package.mk +include ../../../hack/package.mk generate: cozyvalues-gen -v values.yaml -s values.schema.json -r README.md diff --git a/packages/extra/info/Makefile b/packages/extra/info/Makefile index e09b33a0..02e04d53 100644 --- a/packages/extra/info/Makefile +++ b/packages/extra/info/Makefile @@ -1,6 +1,6 @@ NAME=etcd -include ../../../scripts/package.mk +include ../../../hack/package.mk generate: cozyvalues-gen -v values.yaml -s values.schema.json -r README.md diff --git a/packages/extra/ingress/Makefile b/packages/extra/ingress/Makefile index 8134cb22..958ce484 100644 --- a/packages/extra/ingress/Makefile +++ b/packages/extra/ingress/Makefile @@ -1,6 +1,6 @@ NAME=ingress -include ../../../scripts/package.mk +include ../../../hack/package.mk update: get-cloudflare-ips diff --git a/packages/extra/monitoring/Makefile b/packages/extra/monitoring/Makefile index b21607ab..1ae213b6 100644 --- a/packages/extra/monitoring/Makefile +++ b/packages/extra/monitoring/Makefile @@ -2,8 +2,8 @@ GRAFANA_TAG = $(shell awk '$$1 == "version:" {print $$2}' Chart.yaml) NAME=monitoring -include ../../../scripts/common-envs.mk -include ../../../scripts/package.mk +include ../../../hack/common-envs.mk +include ../../../hack/package.mk generate: cozyvalues-gen -v values.yaml -s values.schema.json -r README.md diff --git a/packages/extra/seaweedfs/Makefile b/packages/extra/seaweedfs/Makefile index be84180b..eaa1b906 100644 --- a/packages/extra/seaweedfs/Makefile +++ b/packages/extra/seaweedfs/Makefile @@ -1,6 +1,6 @@ NAME=seaweedfs -include ../../../scripts/package.mk +include ../../../hack/package.mk generate: cozyvalues-gen -v values.yaml -s values.schema.json -r README.md diff --git a/packages/library/Makefile b/packages/library/Makefile index da811e7e..620dcf10 100644 --- a/packages/library/Makefile +++ b/packages/library/Makefile @@ -1,7 +1,7 @@ OUT=../../_out/repos/library CHARTS := $(shell find . -maxdepth 2 -name Chart.yaml | awk -F/ '{print $$2}') -include ../../scripts/common-envs.mk +include ../../hack/common-envs.mk repo: rm -rf "$(OUT)" diff --git a/packages/library/cozy-lib/Makefile b/packages/library/cozy-lib/Makefile index 362bf32c..925f90c1 100644 --- a/packages/library/cozy-lib/Makefile +++ b/packages/library/cozy-lib/Makefile @@ -1,5 +1,5 @@ -include ../../../scripts/common-envs.mk -include ../../../scripts/package.mk +include ../../../hack/common-envs.mk +include ../../../hack/package.mk generate: cozyvalues-gen -v values.yaml -s values.schema.json -r README.md diff --git a/packages/system/Makefile b/packages/system/Makefile index e068be8a..9173d353 100644 --- a/packages/system/Makefile +++ b/packages/system/Makefile @@ -2,7 +2,7 @@ OUT=../../_out/repos/system 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 +include ../../hack/common-envs.mk repo: rm -rf "$(OUT)" diff --git a/packages/system/backup-controller/Makefile b/packages/system/backup-controller/Makefile index 56d58f20..472a99ba 100644 --- a/packages/system/backup-controller/Makefile +++ b/packages/system/backup-controller/Makefile @@ -1,8 +1,8 @@ NAME=backup-controller NAMESPACE=cozy-backup-controller -include ../../../scripts/common-envs.mk -include ../../../scripts/package.mk +include ../../../hack/common-envs.mk +include ../../../hack/package.mk image: image-backup-controller diff --git a/packages/system/backupstrategy-controller/Makefile b/packages/system/backupstrategy-controller/Makefile index a5556838..e6f23ca7 100644 --- a/packages/system/backupstrategy-controller/Makefile +++ b/packages/system/backupstrategy-controller/Makefile @@ -1,8 +1,8 @@ NAME=backupstrategy-controller NAMESPACE=cozy-backup-controller -include ../../../scripts/common-envs.mk -include ../../../scripts/package.mk +include ../../../hack/common-envs.mk +include ../../../hack/package.mk image: image-backupstrategy-controller diff --git a/packages/system/bootbox-rd/Makefile b/packages/system/bootbox-rd/Makefile index b570a84f..9d03ab2b 100644 --- a/packages/system/bootbox-rd/Makefile +++ b/packages/system/bootbox-rd/Makefile @@ -1,4 +1,4 @@ export NAME=bootbox-rd export NAMESPACE=cozy-system -include ../../../scripts/package.mk +include ../../../hack/package.mk diff --git a/packages/system/bootbox/Makefile b/packages/system/bootbox/Makefile index ce4e1af0..2e666462 100644 --- a/packages/system/bootbox/Makefile +++ b/packages/system/bootbox/Makefile @@ -1,7 +1,7 @@ export NAME=bootbox export NAMESPACE=cozy-$(NAME) -include ../../../scripts/package.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/bucket-rd/Makefile b/packages/system/bucket-rd/Makefile index 53e89367..259617e8 100644 --- a/packages/system/bucket-rd/Makefile +++ b/packages/system/bucket-rd/Makefile @@ -1,4 +1,4 @@ export NAME=bucket-rd export NAMESPACE=cozy-system -include ../../../scripts/package.mk +include ../../../hack/package.mk diff --git a/packages/system/bucket/Makefile b/packages/system/bucket/Makefile index d555b71c..87944b28 100644 --- a/packages/system/bucket/Makefile +++ b/packages/system/bucket/Makefile @@ -2,8 +2,8 @@ S3MANAGER_TAG=v0.5.0 export NAME=s3manager-system -include ../../../scripts/common-envs.mk -include ../../../scripts/package.mk +include ../../../hack/common-envs.mk +include ../../../hack/package.mk update: @echo Nothing to update diff --git a/packages/system/capi-operator/Makefile b/packages/system/capi-operator/Makefile index dc421cee..9349f549 100644 --- a/packages/system/capi-operator/Makefile +++ b/packages/system/capi-operator/Makefile @@ -5,7 +5,7 @@ export REPO_URL=https://kubernetes-sigs.github.io/cluster-api-operator export CHART_NAME=cluster-api-operator export CHART_VERSION=^0.19 -include ../../../scripts/package.mk +include ../../../hack/package.mk update: clean capi-operator-update rm -rf charts/cluster-api-operator/charts/ diff --git a/packages/system/capi-providers-bootstrap/Makefile b/packages/system/capi-providers-bootstrap/Makefile index f588e439..54d8ca8b 100644 --- a/packages/system/capi-providers-bootstrap/Makefile +++ b/packages/system/capi-providers-bootstrap/Makefile @@ -1,4 +1,4 @@ export NAME=capi-providers-bootstrap export NAMESPACE=cozy-cluster-api -include ../../../scripts/package.mk +include ../../../hack/package.mk diff --git a/packages/system/capi-providers-core/Makefile b/packages/system/capi-providers-core/Makefile index a9bae779..f5f038c1 100644 --- a/packages/system/capi-providers-core/Makefile +++ b/packages/system/capi-providers-core/Makefile @@ -1,4 +1,4 @@ export NAME=capi-providers-core export NAMESPACE=cozy-cluster-api -include ../../../scripts/package.mk +include ../../../hack/package.mk diff --git a/packages/system/capi-providers-cpprovider/Makefile b/packages/system/capi-providers-cpprovider/Makefile index 06f0dd6d..caec8d96 100644 --- a/packages/system/capi-providers-cpprovider/Makefile +++ b/packages/system/capi-providers-cpprovider/Makefile @@ -1,4 +1,4 @@ export NAME=capi-providers-cpprovider export NAMESPACE=cozy-cluster-api -include ../../../scripts/package.mk +include ../../../hack/package.mk diff --git a/packages/system/capi-providers-infraprovider/Makefile b/packages/system/capi-providers-infraprovider/Makefile index 8e4423f3..042a4f0e 100644 --- a/packages/system/capi-providers-infraprovider/Makefile +++ b/packages/system/capi-providers-infraprovider/Makefile @@ -1,4 +1,4 @@ export NAME=capi-providers-infraprovider export NAMESPACE=cozy-cluster-api -include ../../../scripts/package.mk +include ../../../hack/package.mk diff --git a/packages/system/cert-manager-crds/Makefile b/packages/system/cert-manager-crds/Makefile index 0d665914..48e5644c 100644 --- a/packages/system/cert-manager-crds/Makefile +++ b/packages/system/cert-manager-crds/Makefile @@ -1,4 +1,4 @@ -include ../../../scripts/package.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/cert-manager-issuers/Makefile b/packages/system/cert-manager-issuers/Makefile index a7a6ce10..8808dbfd 100644 --- a/packages/system/cert-manager-issuers/Makefile +++ b/packages/system/cert-manager-issuers/Makefile @@ -1,4 +1,4 @@ export NAME=cert-manager-issuers export NAMESPACE=cozy-cert-manager -include ../../../scripts/package.mk +include ../../../hack/package.mk diff --git a/packages/system/cert-manager/Makefile b/packages/system/cert-manager/Makefile index d48ad5be..2d256086 100644 --- a/packages/system/cert-manager/Makefile +++ b/packages/system/cert-manager/Makefile @@ -1,7 +1,7 @@ export NAME=cert-manager export NAMESPACE=cozy-$(NAME) -include ../../../scripts/package.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/cilium-networkpolicy/Makefile b/packages/system/cilium-networkpolicy/Makefile index c81b87e9..a24a54b5 100644 --- a/packages/system/cilium-networkpolicy/Makefile +++ b/packages/system/cilium-networkpolicy/Makefile @@ -1,5 +1,5 @@ export NAME=cilium-networkpolicy export NAMESPACE=cozy-$(NAME) -include ../../../scripts/common-envs.mk -include ../../../scripts/package.mk +include ../../../hack/common-envs.mk +include ../../../hack/package.mk diff --git a/packages/system/cilium/Makefile b/packages/system/cilium/Makefile index 4d1c6ae7..267c75cc 100644 --- a/packages/system/cilium/Makefile +++ b/packages/system/cilium/Makefile @@ -3,8 +3,8 @@ CILIUM_TAG=$(shell awk '$$1 == "version:" {print $$2}' charts/cilium/Chart.yaml) export NAME=cilium export NAMESPACE=cozy-$(NAME) -include ../../../scripts/common-envs.mk -include ../../../scripts/package.mk +include ../../../hack/common-envs.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/clickhouse-operator/Makefile b/packages/system/clickhouse-operator/Makefile index e821b664..289631d6 100644 --- a/packages/system/clickhouse-operator/Makefile +++ b/packages/system/clickhouse-operator/Makefile @@ -1,7 +1,7 @@ export NAME=clickhouse-operator export NAMESPACE=cozy-clickhouse-operator -include ../../../scripts/package.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/clickhouse-rd/Makefile b/packages/system/clickhouse-rd/Makefile index 4ac0738a..792ef31b 100644 --- a/packages/system/clickhouse-rd/Makefile +++ b/packages/system/clickhouse-rd/Makefile @@ -1,4 +1,4 @@ export NAME=clickhouse-rd export NAMESPACE=cozy-system -include ../../../scripts/package.mk +include ../../../hack/package.mk diff --git a/packages/system/coredns/Makefile b/packages/system/coredns/Makefile index a610a862..4aca5b38 100644 --- a/packages/system/coredns/Makefile +++ b/packages/system/coredns/Makefile @@ -1,7 +1,7 @@ export NAME=coredns export NAMESPACE=kube-system -include ../../../scripts/package.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/cozy-proxy/Makefile b/packages/system/cozy-proxy/Makefile index 8f4f6192..c1da95c7 100644 --- a/packages/system/cozy-proxy/Makefile +++ b/packages/system/cozy-proxy/Makefile @@ -1,8 +1,8 @@ NAME=cozy-proxy NAMESPACE=cozy-system -include ../../../scripts/common-envs.mk -include ../../../scripts/package.mk +include ../../../hack/common-envs.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/cozystack-api/Makefile b/packages/system/cozystack-api/Makefile index a3e0d931..c4e6f459 100644 --- a/packages/system/cozystack-api/Makefile +++ b/packages/system/cozystack-api/Makefile @@ -1,8 +1,8 @@ NAME=cozystack-api NAMESPACE=cozy-system -include ../../../scripts/common-envs.mk -include ../../../scripts/package.mk +include ../../../hack/common-envs.mk +include ../../../hack/package.mk run-local: openssl req -nodes -new -x509 -keyout /tmp/ca.key -out /tmp/ca.crt -subj "/CN=kube-ca" diff --git a/packages/system/cozystack-basics/Makefile b/packages/system/cozystack-basics/Makefile index fe85b39b..2d5f6c59 100644 --- a/packages/system/cozystack-basics/Makefile +++ b/packages/system/cozystack-basics/Makefile @@ -1,4 +1,4 @@ export NAME=tenant-root export NAMESPACE=cozy-system -include ../../../scripts/package.mk +include ../../../hack/package.mk diff --git a/packages/system/cozystack-controller/Makefile b/packages/system/cozystack-controller/Makefile index 9bbfb5a6..cb0e5578 100644 --- a/packages/system/cozystack-controller/Makefile +++ b/packages/system/cozystack-controller/Makefile @@ -1,8 +1,8 @@ NAME=cozystack-controller NAMESPACE=cozy-system -include ../../../scripts/common-envs.mk -include ../../../scripts/package.mk +include ../../../hack/common-envs.mk +include ../../../hack/package.mk image: image-cozystack-controller update-version diff --git a/packages/system/cozystack-resource-definition-crd/Makefile b/packages/system/cozystack-resource-definition-crd/Makefile index ddb24aef..caac4557 100644 --- a/packages/system/cozystack-resource-definition-crd/Makefile +++ b/packages/system/cozystack-resource-definition-crd/Makefile @@ -1,4 +1,4 @@ export NAME=cozystack-resource-definition-crd export NAMESPACE=cozy-system -include ../../../scripts/package.mk +include ../../../hack/package.mk diff --git a/packages/system/dashboard/Makefile b/packages/system/dashboard/Makefile index f89cba0d..a9f680a3 100644 --- a/packages/system/dashboard/Makefile +++ b/packages/system/dashboard/Makefile @@ -1,8 +1,8 @@ export NAME=dashboard export NAMESPACE=cozy-$(NAME) -include ../../../scripts/common-envs.mk -include ../../../scripts/package.mk +include ../../../hack/common-envs.mk +include ../../../hack/package.mk update: update-crd update-dockerfiles image: image-openapi-ui image-openapi-ui-k8s-bff image-token-proxy update-tenant-text diff --git a/packages/system/etcd-operator/Makefile b/packages/system/etcd-operator/Makefile index a2154952..54a1edad 100644 --- a/packages/system/etcd-operator/Makefile +++ b/packages/system/etcd-operator/Makefile @@ -1,7 +1,7 @@ export NAME=etcd-operator export NAMESPACE=cozy-${NAME} -include ../../../scripts/package.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/etcd-rd/Makefile b/packages/system/etcd-rd/Makefile index ad91bad9..df993ae6 100644 --- a/packages/system/etcd-rd/Makefile +++ b/packages/system/etcd-rd/Makefile @@ -1,4 +1,4 @@ export NAME=etcd-rd export NAMESPACE=cozy-system -include ../../../scripts/package.mk +include ../../../hack/package.mk diff --git a/packages/system/external-dns/Makefile b/packages/system/external-dns/Makefile index 1ddfa773..5b0197e1 100644 --- a/packages/system/external-dns/Makefile +++ b/packages/system/external-dns/Makefile @@ -1,7 +1,7 @@ export NAME=external-dns export NAMESPACE=cozy-$(NAME) -include ../../../scripts/package.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/external-secrets-operator/Makefile b/packages/system/external-secrets-operator/Makefile index f4d9215d..0e1cd83a 100644 --- a/packages/system/external-secrets-operator/Makefile +++ b/packages/system/external-secrets-operator/Makefile @@ -1,7 +1,7 @@ export NAME=external-secrets-operator export NAMESPACE=cozy-$(NAME) -include ../../../scripts/package.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/ferretdb-rd/Makefile b/packages/system/ferretdb-rd/Makefile index f6814169..1cc7ac30 100644 --- a/packages/system/ferretdb-rd/Makefile +++ b/packages/system/ferretdb-rd/Makefile @@ -1,4 +1,4 @@ export NAME=ferretdb-rd export NAMESPACE=cozy-system -include ../../../scripts/package.mk +include ../../../hack/package.mk diff --git a/packages/system/fluxcd-operator/Makefile b/packages/system/fluxcd-operator/Makefile index a603b85f..d9778f53 100644 --- a/packages/system/fluxcd-operator/Makefile +++ b/packages/system/fluxcd-operator/Makefile @@ -1,7 +1,7 @@ NAME=fluxcd-operator NAMESPACE=cozy-fluxcd -include ../../../scripts/package.mk +include ../../../hack/package.mk apply-locally: cozyhr apply --plain -n $(NAMESPACE) $(NAME) diff --git a/packages/system/fluxcd/Makefile b/packages/system/fluxcd/Makefile index 2c88b52d..9aa32fe9 100644 --- a/packages/system/fluxcd/Makefile +++ b/packages/system/fluxcd/Makefile @@ -1,7 +1,7 @@ NAME=fluxcd NAMESPACE=cozy-$(NAME) -include ../../../scripts/package.mk +include ../../../hack/package.mk apply-locally: cozyhr apply --plain -n $(NAMESPACE) $(NAME) diff --git a/packages/system/foundationdb-operator/Makefile b/packages/system/foundationdb-operator/Makefile index f735e7c5..16ca953f 100644 --- a/packages/system/foundationdb-operator/Makefile +++ b/packages/system/foundationdb-operator/Makefile @@ -1,7 +1,7 @@ export NAME=foundationdb-operator export NAMESPACE=cozy-$(NAME) -include ../../../scripts/package.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/foundationdb-rd/Makefile b/packages/system/foundationdb-rd/Makefile index b6e6d41a..2b3b51c6 100644 --- a/packages/system/foundationdb-rd/Makefile +++ b/packages/system/foundationdb-rd/Makefile @@ -1,4 +1,4 @@ export NAME=foundationdb-rd export NAMESPACE=cozy-system -include ../../../scripts/package.mk +include ../../../hack/package.mk diff --git a/packages/system/gateway-api-crds/Makefile b/packages/system/gateway-api-crds/Makefile index c4311662..ad51d591 100644 --- a/packages/system/gateway-api-crds/Makefile +++ b/packages/system/gateway-api-crds/Makefile @@ -1,7 +1,7 @@ export NAME=gateway-api-crds export NAMESPACE=cozy-$(NAME) -include ../../../scripts/package.mk +include ../../../hack/package.mk update: rm -rf templates diff --git a/packages/system/goldpinger/Makefile b/packages/system/goldpinger/Makefile index 3ddd79ba..0aeef2ad 100644 --- a/packages/system/goldpinger/Makefile +++ b/packages/system/goldpinger/Makefile @@ -1,4 +1,4 @@ -include ../../../scripts/package.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/gpu-operator/Makefile b/packages/system/gpu-operator/Makefile index 286451f3..49b26e26 100644 --- a/packages/system/gpu-operator/Makefile +++ b/packages/system/gpu-operator/Makefile @@ -1,8 +1,8 @@ export NAME=gpu-operator export NAMESPACE=cozy-$(NAME) -include ../../../scripts/common-envs.mk -include ../../../scripts/package.mk +include ../../../hack/common-envs.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/grafana-operator/Makefile b/packages/system/grafana-operator/Makefile index a752d25b..34d36142 100644 --- a/packages/system/grafana-operator/Makefile +++ b/packages/system/grafana-operator/Makefile @@ -1,8 +1,8 @@ export NAME=grafana-operator export NAMESPACE=cozy-grafana-operator -include ../../../scripts/common-envs.mk -include ../../../scripts/package.mk +include ../../../hack/common-envs.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/hetzner-robotlb/Makefile b/packages/system/hetzner-robotlb/Makefile index 9cd69463..c5ea0e4d 100644 --- a/packages/system/hetzner-robotlb/Makefile +++ b/packages/system/hetzner-robotlb/Makefile @@ -1,7 +1,7 @@ export NAME=hetzner-robotlb export NAMESPACE=cozy-$(NAME) -include ../../../scripts/package.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/http-cache-rd/Makefile b/packages/system/http-cache-rd/Makefile index cf367cf4..1d4b5be4 100644 --- a/packages/system/http-cache-rd/Makefile +++ b/packages/system/http-cache-rd/Makefile @@ -1,4 +1,4 @@ export NAME=http-cache-rd export NAMESPACE=cozy-system -include ../../../scripts/package.mk +include ../../../hack/package.mk diff --git a/packages/system/info-rd/Makefile b/packages/system/info-rd/Makefile index 7cfed08d..15cf2513 100644 --- a/packages/system/info-rd/Makefile +++ b/packages/system/info-rd/Makefile @@ -1,4 +1,4 @@ export NAME=info-rd export NAMESPACE=cozy-system -include ../../../scripts/package.mk +include ../../../hack/package.mk diff --git a/packages/system/ingress-nginx/Makefile b/packages/system/ingress-nginx/Makefile index 9ad10ae1..573c253b 100644 --- a/packages/system/ingress-nginx/Makefile +++ b/packages/system/ingress-nginx/Makefile @@ -1,6 +1,6 @@ NAME=ingress-nginx-system -include ../../../scripts/package.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/ingress-rd/Makefile b/packages/system/ingress-rd/Makefile index 82976cc6..be48593e 100644 --- a/packages/system/ingress-rd/Makefile +++ b/packages/system/ingress-rd/Makefile @@ -1,4 +1,4 @@ export NAME=ingress-rd export NAMESPACE=cozy-system -include ../../../scripts/package.mk +include ../../../hack/package.mk diff --git a/packages/system/kafka-operator/Makefile b/packages/system/kafka-operator/Makefile index 32fa2207..0e4811e5 100644 --- a/packages/system/kafka-operator/Makefile +++ b/packages/system/kafka-operator/Makefile @@ -1,7 +1,7 @@ export NAME=kafka-operator export NAMESPACE=cozy-$(NAME) -include ../../../scripts/package.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/kafka-rd/Makefile b/packages/system/kafka-rd/Makefile index 3c333dcf..f7d2b87d 100644 --- a/packages/system/kafka-rd/Makefile +++ b/packages/system/kafka-rd/Makefile @@ -1,4 +1,4 @@ export NAME=kafka-rd export NAMESPACE=cozy-system -include ../../../scripts/package.mk +include ../../../hack/package.mk diff --git a/packages/system/kamaji/Makefile b/packages/system/kamaji/Makefile index c24e2d4e..f82c4c7a 100644 --- a/packages/system/kamaji/Makefile +++ b/packages/system/kamaji/Makefile @@ -1,8 +1,8 @@ export NAME=kamaji export NAMESPACE=cozy-$(NAME) -include ../../../scripts/common-envs.mk -include ../../../scripts/package.mk +include ../../../hack/common-envs.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/keycloak-configure/Makefile b/packages/system/keycloak-configure/Makefile index b9fd5c10..4cd5bd2e 100644 --- a/packages/system/keycloak-configure/Makefile +++ b/packages/system/keycloak-configure/Makefile @@ -1,5 +1,5 @@ export NAME=keycloak-configure export NAMESPACE=cozy-keycloak -include ../../../scripts/common-envs.mk -include ../../../scripts/package.mk +include ../../../hack/common-envs.mk +include ../../../hack/package.mk diff --git a/packages/system/keycloak-operator/Makefile b/packages/system/keycloak-operator/Makefile index a4f50b67..9c8d63c1 100644 --- a/packages/system/keycloak-operator/Makefile +++ b/packages/system/keycloak-operator/Makefile @@ -1,8 +1,8 @@ export NAME=keycloak-operator export NAMESPACE=cozy-keycloak -include ../../../scripts/common-envs.mk -include ../../../scripts/package.mk +include ../../../hack/common-envs.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/keycloak/Makefile b/packages/system/keycloak/Makefile index 62f4e98d..09afc864 100644 --- a/packages/system/keycloak/Makefile +++ b/packages/system/keycloak/Makefile @@ -1,5 +1,5 @@ export NAME=keycloak export NAMESPACE=cozy-keycloak -include ../../../scripts/common-envs.mk -include ../../../scripts/package.mk +include ../../../hack/common-envs.mk +include ../../../hack/package.mk diff --git a/packages/system/kubeovn-plunger/Makefile b/packages/system/kubeovn-plunger/Makefile index a80c7e06..f56d935b 100644 --- a/packages/system/kubeovn-plunger/Makefile +++ b/packages/system/kubeovn-plunger/Makefile @@ -1,8 +1,8 @@ export NAME=kubeovn-plunger export NAMESPACE=cozy-kubeovn -include ../../../scripts/common-envs.mk -include ../../../scripts/package.mk +include ../../../hack/common-envs.mk +include ../../../hack/package.mk image: docker buildx build -f images/kubeovn-plunger/Dockerfile ../../../ \ diff --git a/packages/system/kubeovn-webhook/Makefile b/packages/system/kubeovn-webhook/Makefile index 9134d55f..9b2e2744 100644 --- a/packages/system/kubeovn-webhook/Makefile +++ b/packages/system/kubeovn-webhook/Makefile @@ -1,8 +1,8 @@ export NAME=kubeovn-webhook export NAMESPACE=cozy-kubeovn -include ../../../scripts/common-envs.mk -include ../../../scripts/package.mk +include ../../../hack/common-envs.mk +include ../../../hack/package.mk image: docker buildx build images/kubeovn-webhook \ diff --git a/packages/system/kubeovn/Makefile b/packages/system/kubeovn/Makefile index 45c5ab25..7e6d0d34 100644 --- a/packages/system/kubeovn/Makefile +++ b/packages/system/kubeovn/Makefile @@ -3,8 +3,8 @@ KUBEOVN_TAG=v0.40.0 export NAME=kubeovn export NAMESPACE=cozy-$(NAME) -include ../../../scripts/common-envs.mk -include ../../../scripts/package.mk +include ../../../hack/common-envs.mk +include ../../../hack/package.mk update: rm -rf charts values.yaml Chart.yaml diff --git a/packages/system/kubernetes-rd/Makefile b/packages/system/kubernetes-rd/Makefile index 45969603..2d38b5d3 100644 --- a/packages/system/kubernetes-rd/Makefile +++ b/packages/system/kubernetes-rd/Makefile @@ -1,4 +1,4 @@ export NAME=kubernetes-rd export NAMESPACE=cozy-system -include ../../../scripts/package.mk +include ../../../hack/package.mk diff --git a/packages/system/kubevirt-cdi-operator/Makefile b/packages/system/kubevirt-cdi-operator/Makefile index 7022599f..7232216f 100644 --- a/packages/system/kubevirt-cdi-operator/Makefile +++ b/packages/system/kubevirt-cdi-operator/Makefile @@ -1,7 +1,7 @@ export NAME=kubevirt-cdi-operator export NAMESPACE=cozy-kubevirt-cdi -include ../../../scripts/package.mk +include ../../../hack/package.mk update: rm -rf templates diff --git a/packages/system/kubevirt-cdi/Makefile b/packages/system/kubevirt-cdi/Makefile index 0b3791a1..709e4880 100644 --- a/packages/system/kubevirt-cdi/Makefile +++ b/packages/system/kubevirt-cdi/Makefile @@ -1,7 +1,7 @@ export NAME=kubevirt-cdi export NAMESPACE=cozy-$(NAME) -include ../../../scripts/package.mk +include ../../../hack/package.mk update: rm -rf templates diff --git a/packages/system/kubevirt-instancetypes/Makefile b/packages/system/kubevirt-instancetypes/Makefile index d0498f10..b9f84b83 100644 --- a/packages/system/kubevirt-instancetypes/Makefile +++ b/packages/system/kubevirt-instancetypes/Makefile @@ -1,7 +1,7 @@ export NAME=kubevirt-instancetypes export NAMESPACE=cozy-kubevirt -include ../../../scripts/package.mk +include ../../../hack/package.mk update: rm -rf templates diff --git a/packages/system/kubevirt-operator/Makefile b/packages/system/kubevirt-operator/Makefile index 42bf80a7..69b6a692 100644 --- a/packages/system/kubevirt-operator/Makefile +++ b/packages/system/kubevirt-operator/Makefile @@ -1,7 +1,7 @@ export NAME=kubevirt-operator export NAMESPACE=cozy-kubevirt -include ../../../scripts/package.mk +include ../../../hack/package.mk update: rm -rf templates diff --git a/packages/system/kubevirt/Makefile b/packages/system/kubevirt/Makefile index 2c2e57ab..246001d0 100644 --- a/packages/system/kubevirt/Makefile +++ b/packages/system/kubevirt/Makefile @@ -1,7 +1,7 @@ export NAME=kubevirt export NAMESPACE=cozy-$(NAME) -include ../../../scripts/package.mk +include ../../../hack/package.mk update: rm -rf templates diff --git a/packages/system/lineage-controller-webhook/Makefile b/packages/system/lineage-controller-webhook/Makefile index 04d81a12..d5bada31 100644 --- a/packages/system/lineage-controller-webhook/Makefile +++ b/packages/system/lineage-controller-webhook/Makefile @@ -1,8 +1,8 @@ NAME=lineage-controller-webhook NAMESPACE=cozy-system -include ../../../scripts/common-envs.mk -include ../../../scripts/package.mk +include ../../../hack/common-envs.mk +include ../../../hack/package.mk image: image-lineage-controller-webhook diff --git a/packages/system/linstor-scheduler/Makefile b/packages/system/linstor-scheduler/Makefile index 90a31bc8..79b50c28 100644 --- a/packages/system/linstor-scheduler/Makefile +++ b/packages/system/linstor-scheduler/Makefile @@ -1,7 +1,7 @@ export NAME=linstor-scheduler export NAMESPACE=cozy-linstor -include ../../../scripts/package.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/linstor/Makefile b/packages/system/linstor/Makefile index b5d7c227..b211baec 100644 --- a/packages/system/linstor/Makefile +++ b/packages/system/linstor/Makefile @@ -1,8 +1,8 @@ export NAME=linstor export NAMESPACE=cozy-$(NAME) -include ../../../scripts/common-envs.mk -include ../../../scripts/package.mk +include ../../../hack/common-envs.mk +include ../../../hack/package.mk LINSTOR_VERSION ?= 1.32.3 LINSTOR_CSI_VERSION ?= v1.10.5 diff --git a/packages/system/mariadb-operator/Makefile b/packages/system/mariadb-operator/Makefile index ecbd51d9..905653ca 100644 --- a/packages/system/mariadb-operator/Makefile +++ b/packages/system/mariadb-operator/Makefile @@ -1,7 +1,7 @@ export NAME=mariadb-operator export NAMESPACE=cozy-$(NAME) -include ../../../scripts/package.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/metallb/Makefile b/packages/system/metallb/Makefile index 5323f414..5262e558 100644 --- a/packages/system/metallb/Makefile +++ b/packages/system/metallb/Makefile @@ -1,8 +1,8 @@ export NAME=metallb export NAMESPACE=cozy-$(NAME) -include ../../../scripts/common-envs.mk -include ../../../scripts/package.mk +include ../../../hack/common-envs.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/metrics-server/Makefile b/packages/system/metrics-server/Makefile index fb89db64..87477ea0 100644 --- a/packages/system/metrics-server/Makefile +++ b/packages/system/metrics-server/Makefile @@ -1,7 +1,7 @@ export NAME=metrics-server export NAMESPACE=cozy-monitoring -include ../../../scripts/package.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/monitoring-agents/Makefile b/packages/system/monitoring-agents/Makefile index f324bfef..339b15a2 100644 --- a/packages/system/monitoring-agents/Makefile +++ b/packages/system/monitoring-agents/Makefile @@ -1,7 +1,7 @@ export NAME=monitoring-agents export NAMESPACE=cozy-monitoring -include ../../../scripts/package.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/monitoring-rd/Makefile b/packages/system/monitoring-rd/Makefile index ecce61e3..5b0bf43b 100644 --- a/packages/system/monitoring-rd/Makefile +++ b/packages/system/monitoring-rd/Makefile @@ -1,4 +1,4 @@ export NAME=monitoring-rd export NAMESPACE=cozy-system -include ../../../scripts/package.mk +include ../../../hack/package.mk diff --git a/packages/system/multus/Makefile b/packages/system/multus/Makefile index b7ae5bfc..9b34606d 100644 --- a/packages/system/multus/Makefile +++ b/packages/system/multus/Makefile @@ -1,8 +1,8 @@ export NAME=multus export NAMESPACE=cozy-$(NAME) -include ../../../scripts/common-envs.mk -include ../../../scripts/package.mk +include ../../../hack/common-envs.mk +include ../../../hack/package.mk update: rm -rf templates diff --git a/packages/system/mysql-rd/Makefile b/packages/system/mysql-rd/Makefile index a2d28923..2a8af787 100644 --- a/packages/system/mysql-rd/Makefile +++ b/packages/system/mysql-rd/Makefile @@ -1,4 +1,4 @@ export NAME=mysql-rd export NAMESPACE=cozy-system -include ../../../scripts/package.mk +include ../../../hack/package.mk diff --git a/packages/system/nats-rd/Makefile b/packages/system/nats-rd/Makefile index 2b5b232e..4a72284c 100644 --- a/packages/system/nats-rd/Makefile +++ b/packages/system/nats-rd/Makefile @@ -1,4 +1,4 @@ export NAME=nats-rd export NAMESPACE=cozy-system -include ../../../scripts/package.mk +include ../../../hack/package.mk diff --git a/packages/system/nats/Makefile b/packages/system/nats/Makefile index 25657a89..88883ab0 100644 --- a/packages/system/nats/Makefile +++ b/packages/system/nats/Makefile @@ -1,4 +1,4 @@ -include ../../../scripts/package.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/nfs-driver/Makefile b/packages/system/nfs-driver/Makefile index e3af6c03..2f14aa28 100644 --- a/packages/system/nfs-driver/Makefile +++ b/packages/system/nfs-driver/Makefile @@ -1,8 +1,8 @@ export NAME=nfs-driver export NAMESPACE=cozy-$(NAME) -include ../../../scripts/common-envs.mk -include ../../../scripts/package.mk +include ../../../hack/common-envs.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/objectstorage-controller/Makefile b/packages/system/objectstorage-controller/Makefile index 54c195a0..dacc3353 100644 --- a/packages/system/objectstorage-controller/Makefile +++ b/packages/system/objectstorage-controller/Makefile @@ -1,8 +1,8 @@ export NAME=objectstorage-controller export NAMESPACE=cozy-$(NAME) -include ../../../scripts/common-envs.mk -include ../../../scripts/package.mk +include ../../../hack/common-envs.mk +include ../../../hack/package.mk update: rm -rf templates diff --git a/packages/system/piraeus-operator/Makefile b/packages/system/piraeus-operator/Makefile index a10c7369..b46680f2 100644 --- a/packages/system/piraeus-operator/Makefile +++ b/packages/system/piraeus-operator/Makefile @@ -1,7 +1,7 @@ export NAME=piraeus-operator export NAMESPACE=cozy-linstor -include ../../../scripts/package.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/postgres-operator/Makefile b/packages/system/postgres-operator/Makefile index f1a0bba0..f58f235f 100644 --- a/packages/system/postgres-operator/Makefile +++ b/packages/system/postgres-operator/Makefile @@ -1,7 +1,7 @@ export NAME=postgres-operator export NAMESPACE=cozy-$(NAME) -include ../../../scripts/package.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/postgres-rd/Makefile b/packages/system/postgres-rd/Makefile index 0c8b1cb0..7da8103a 100644 --- a/packages/system/postgres-rd/Makefile +++ b/packages/system/postgres-rd/Makefile @@ -1,4 +1,4 @@ export NAME=postgres-rd export NAMESPACE=cozy-system -include ../../../scripts/package.mk +include ../../../hack/package.mk diff --git a/packages/system/prometheus-operator-crds/Makefile b/packages/system/prometheus-operator-crds/Makefile index 173da9c1..ed55f286 100644 --- a/packages/system/prometheus-operator-crds/Makefile +++ b/packages/system/prometheus-operator-crds/Makefile @@ -1,7 +1,7 @@ export NAME=prometheus-operator-crds export NAMESPACE=cozy-victoria-metrics-operator -include ../../../scripts/package.mk +include ../../../hack/package.mk update: helm repo add prometheus-community https://prometheus-community.github.io/helm-charts diff --git a/packages/system/rabbitmq-operator/Makefile b/packages/system/rabbitmq-operator/Makefile index 12eda697..a75e37cd 100644 --- a/packages/system/rabbitmq-operator/Makefile +++ b/packages/system/rabbitmq-operator/Makefile @@ -1,7 +1,7 @@ export NAME=rabbitmq-operator export NAMESPACE=cozy-$(NAME) -include ../../../scripts/package.mk +include ../../../hack/package.mk update: rm -rf templates/cluster-operator.yml diff --git a/packages/system/rabbitmq-rd/Makefile b/packages/system/rabbitmq-rd/Makefile index 9c5be0d5..7db599d7 100644 --- a/packages/system/rabbitmq-rd/Makefile +++ b/packages/system/rabbitmq-rd/Makefile @@ -1,4 +1,4 @@ export NAME=rabbitmq-rd export NAMESPACE=cozy-system -include ../../../scripts/package.mk +include ../../../hack/package.mk diff --git a/packages/system/redis-operator/Makefile b/packages/system/redis-operator/Makefile index 650cbc72..9e21b1b4 100644 --- a/packages/system/redis-operator/Makefile +++ b/packages/system/redis-operator/Makefile @@ -2,8 +2,8 @@ REDIS_OPERATOR_TAG=$(shell grep -F 'ARG VERSION=' images/redis-operator/Dockerfi export NAME=redis-operator export NAMESPACE=cozy-$(NAME) -include ../../../scripts/common-envs.mk -include ../../../scripts/package.mk +include ../../../hack/common-envs.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/redis-rd/Makefile b/packages/system/redis-rd/Makefile index bed18877..e6aca9de 100644 --- a/packages/system/redis-rd/Makefile +++ b/packages/system/redis-rd/Makefile @@ -1,4 +1,4 @@ export NAME=redis-rd export NAMESPACE=cozy-system -include ../../../scripts/package.mk +include ../../../hack/package.mk diff --git a/packages/system/reloader/Makefile b/packages/system/reloader/Makefile index 378dc23c..6dc1f004 100644 --- a/packages/system/reloader/Makefile +++ b/packages/system/reloader/Makefile @@ -1,7 +1,7 @@ export NAME=reloader export NAMESPACE=cozy-$(NAME) -include ../../../scripts/package.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/seaweedfs-rd/Makefile b/packages/system/seaweedfs-rd/Makefile index 5be03dbb..b4c5a2da 100644 --- a/packages/system/seaweedfs-rd/Makefile +++ b/packages/system/seaweedfs-rd/Makefile @@ -1,4 +1,4 @@ export NAME=seaweedfs-rd export NAMESPACE=cozy-system -include ../../../scripts/package.mk +include ../../../hack/package.mk diff --git a/packages/system/seaweedfs/Makefile b/packages/system/seaweedfs/Makefile index d1f21a80..c3a2f777 100644 --- a/packages/system/seaweedfs/Makefile +++ b/packages/system/seaweedfs/Makefile @@ -1,7 +1,7 @@ export NAME=seaweedfs-system -include ../../../scripts/common-envs.mk -include ../../../scripts/package.mk +include ../../../hack/common-envs.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/snapshot-controller/Makefile b/packages/system/snapshot-controller/Makefile index 65d37f1f..ca5d39e7 100644 --- a/packages/system/snapshot-controller/Makefile +++ b/packages/system/snapshot-controller/Makefile @@ -1,7 +1,7 @@ export NAME=snapshot-controller export NAMESPACE=cozy-$(NAME) -include ../../../scripts/package.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/tcp-balancer-rd/Makefile b/packages/system/tcp-balancer-rd/Makefile index 39a0495f..c85634a6 100644 --- a/packages/system/tcp-balancer-rd/Makefile +++ b/packages/system/tcp-balancer-rd/Makefile @@ -1,4 +1,4 @@ export NAME=tcp-balancer-rd export NAMESPACE=cozy-system -include ../../../scripts/package.mk +include ../../../hack/package.mk diff --git a/packages/system/telepresence/Makefile b/packages/system/telepresence/Makefile index 1aec8917..d7f83e05 100644 --- a/packages/system/telepresence/Makefile +++ b/packages/system/telepresence/Makefile @@ -1,7 +1,7 @@ export NAME=traffic-manager export NAMESPACE=cozy-telepresence -include ../../../scripts/package.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/tenant-rd/Makefile b/packages/system/tenant-rd/Makefile index 37852fe6..11db2069 100644 --- a/packages/system/tenant-rd/Makefile +++ b/packages/system/tenant-rd/Makefile @@ -1,4 +1,4 @@ export NAME=tenant-rd export NAMESPACE=cozy-system -include ../../../scripts/package.mk +include ../../../hack/package.mk diff --git a/packages/system/velero/Makefile b/packages/system/velero/Makefile index ca4ebd5e..44eba951 100644 --- a/packages/system/velero/Makefile +++ b/packages/system/velero/Makefile @@ -1,7 +1,7 @@ export NAME=velero export NAMESPACE=cozy-$(NAME) -include ../../../scripts/package.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/vertical-pod-autoscaler-crds/Makefile b/packages/system/vertical-pod-autoscaler-crds/Makefile index 9290640e..52b84278 100644 --- a/packages/system/vertical-pod-autoscaler-crds/Makefile +++ b/packages/system/vertical-pod-autoscaler-crds/Makefile @@ -1,7 +1,7 @@ export NAME=vertical-pod-autoscaler export NAMESPACE=cozy-$(NAME) -include ../../../scripts/package.mk +include ../../../hack/package.mk update: curl -o ./templates/vpa-v1-crd-gen.yaml https://raw.githubusercontent.com/kubernetes/autoscaler/refs/heads/master/vertical-pod-autoscaler/deploy/vpa-v1-crd-gen.yaml diff --git a/packages/system/vertical-pod-autoscaler/Makefile b/packages/system/vertical-pod-autoscaler/Makefile index 389f9c6e..1e5372e1 100644 --- a/packages/system/vertical-pod-autoscaler/Makefile +++ b/packages/system/vertical-pod-autoscaler/Makefile @@ -1,7 +1,7 @@ export NAME=vertical-pod-autoscaler export NAMESPACE=cozy-$(NAME) -include ../../../scripts/package.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/victoria-metrics-operator/Makefile b/packages/system/victoria-metrics-operator/Makefile index a78b7b76..981a1dbc 100644 --- a/packages/system/victoria-metrics-operator/Makefile +++ b/packages/system/victoria-metrics-operator/Makefile @@ -1,7 +1,7 @@ export NAME=victoria-metrics-operator export NAMESPACE=cozy-$(NAME) -include ../../../scripts/package.mk +include ../../../hack/package.mk update: rm -rf charts diff --git a/packages/system/virtual-machine-rd/Makefile b/packages/system/virtual-machine-rd/Makefile index 4d59946b..79142c9b 100644 --- a/packages/system/virtual-machine-rd/Makefile +++ b/packages/system/virtual-machine-rd/Makefile @@ -1,4 +1,4 @@ export NAME=virtual-machine-rd export NAMESPACE=cozy-system -include ../../../scripts/package.mk +include ../../../hack/package.mk diff --git a/packages/system/virtualprivatecloud-rd/Makefile b/packages/system/virtualprivatecloud-rd/Makefile index 9d9b6c50..013e96b1 100644 --- a/packages/system/virtualprivatecloud-rd/Makefile +++ b/packages/system/virtualprivatecloud-rd/Makefile @@ -1,4 +1,4 @@ export NAME=virtualprivatecloud-rd export NAMESPACE=cozy-system -include ../../../scripts/package.mk +include ../../../hack/package.mk diff --git a/packages/system/vm-disk-rd/Makefile b/packages/system/vm-disk-rd/Makefile index e6de276d..5b73fd4c 100644 --- a/packages/system/vm-disk-rd/Makefile +++ b/packages/system/vm-disk-rd/Makefile @@ -1,4 +1,4 @@ export NAME=vm-disk-rd export NAMESPACE=cozy-system -include ../../../scripts/package.mk +include ../../../hack/package.mk diff --git a/packages/system/vm-instance-rd/Makefile b/packages/system/vm-instance-rd/Makefile index badc4951..621d74bd 100644 --- a/packages/system/vm-instance-rd/Makefile +++ b/packages/system/vm-instance-rd/Makefile @@ -1,4 +1,4 @@ export NAME=vm-instance-rd export NAMESPACE=cozy-system -include ../../../scripts/package.mk +include ../../../hack/package.mk diff --git a/packages/system/vpn-rd/Makefile b/packages/system/vpn-rd/Makefile index c6ee47bb..d37f46fc 100644 --- a/packages/system/vpn-rd/Makefile +++ b/packages/system/vpn-rd/Makefile @@ -1,4 +1,4 @@ export NAME=vpn-rd export NAMESPACE=cozy-system -include ../../../scripts/package.mk +include ../../../hack/package.mk diff --git a/packages/system/vsnap-crd/Makefile b/packages/system/vsnap-crd/Makefile index 57e5a290..005eefbd 100644 --- a/packages/system/vsnap-crd/Makefile +++ b/packages/system/vsnap-crd/Makefile @@ -1,7 +1,7 @@ export NAME=vsnap-crd export NAMESPACE=cozy-$(NAME) -include ../../../scripts/package.mk +include ../../../hack/package.mk update: rm -rf templates diff --git a/scripts/issue-flux-certificates.sh b/scripts/issue-flux-certificates.sh deleted file mode 100755 index 825447e2..00000000 --- a/scripts/issue-flux-certificates.sh +++ /dev/null @@ -1,63 +0,0 @@ -#!/bin/sh -set -e - -if kubectl get secret -n cozy-system cozystack-assets-tls >/dev/null 2>&1 && kubectl get secret -n cozy-public cozystack-assets-tls >/dev/null 2>&1; then - echo "Secret cozystack-assets-tls already exists in both cozy-system and cozy-public namespaces. Exiting." - exit 0 -fi - -USER_CN="cozystack-assets-reader" -CSR_NAME="csr-${USER_CN}-$(date +%s)" - -# make temp directory and cleanup handler -TMPDIR=$(mktemp -d) -trap 'rm -rf "$TMPDIR"' EXIT - -# move into tmpdir -cd "$TMPDIR" - -openssl genrsa -out tls.key 2048 -openssl req -new -key tls.key -subj "/CN=${USER_CN}" -out tls.csr - -CSR_B64=$(base64 < tls.csr | tr -d '\n') - -cat < tls.crt - -kubectl get -n kube-public configmap kube-root-ca.crt \ - -o jsonpath='{.data.ca\.crt}' > ca.crt - -kubectl create secret generic "cozystack-assets-tls" \ - --namespace='cozy-system' \ - --type='kubernetes.io/tls' \ - --from-file=tls.crt \ - --from-file=tls.key \ - --from-file=ca.crt \ - --dry-run=client -o yaml | kubectl apply -f - - -kubectl create secret generic "cozystack-assets-tls" \ - --namespace='cozy-public' \ - --type='kubernetes.io/tls' \ - --from-file=tls.crt \ - --from-file=tls.key \ - --from-file=ca.crt \ - --dry-run=client -o yaml | kubectl apply -f - From 6dbfcbb93e2abecb0a53bac7cbd000dca85e8575 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 15 Jan 2026 15:50:30 +0100 Subject: [PATCH 054/889] feat(cozypkg): add cross-platform build targets with version injection - Add pattern rule for building cozypkg binaries per platform (assets-cozypkg--) with checksums generation - Add Version variable to cozypkg CLI injected via ldflags - Split manifests into separate cozystack-crds.yaml and cozystack-operator.yaml files - Update CI workflow to handle CRDs and operator as separate artifacts - Update e2e tests and upload script for new manifest structure - Suppress git describe stderr when no tags exist Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- .github/workflows/pull-requests.yaml | 47 +++++++++++++++------ Makefile | 25 ++++++++++- cmd/cozypkg/cmd/root.go | 5 ++- hack/e2e-install-cozystack.bats | 11 +++-- hack/upload-assets.sh | 5 ++- packages/core/installer/templates/crds.yaml | 1 - packages/core/testing/Makefile | 3 +- 7 files changed, 74 insertions(+), 23 deletions(-) diff --git a/.github/workflows/pull-requests.yaml b/.github/workflows/pull-requests.yaml index 2de01160..53787ad3 100644 --- a/.github/workflows/pull-requests.yaml +++ b/.github/workflows/pull-requests.yaml @@ -71,11 +71,17 @@ jobs: name: pr-patch path: _out/assets/pr.patch - - name: Upload installer + - name: Upload CRDs uses: actions/upload-artifact@v4 with: - name: cozystack-installer - path: _out/assets/cozystack-installer.yaml + name: cozystack-crds + path: _out/assets/cozystack-crds.yaml + + - name: Upload operator + uses: actions/upload-artifact@v4 + with: + name: cozystack-operator + path: _out/assets/cozystack-operator.yaml - name: Upload Talos image uses: actions/upload-artifact@v4 @@ -88,8 +94,9 @@ jobs: runs-on: ubuntu-latest if: contains(github.event.pull_request.labels.*.name, 'release') outputs: - installer_id: ${{ steps.fetch_assets.outputs.installer_id }} - disk_id: ${{ steps.fetch_assets.outputs.disk_id }} + crds_id: ${{ steps.fetch_assets.outputs.crds_id }} + operator_id: ${{ steps.fetch_assets.outputs.operator_id }} + disk_id: ${{ steps.fetch_assets.outputs.disk_id }} steps: - name: Checkout code @@ -132,14 +139,16 @@ jobs: return; } const find = (n) => draft.assets.find(a => a.name === n)?.id; - const installerId = find('cozystack-installer.yaml'); - const diskId = find('nocloud-amd64.raw.xz'); - if (!installerId || !diskId) { + const crdsId = find('cozystack-crds.yaml'); + const operatorId = find('cozystack-operator.yaml'); + const diskId = find('nocloud-amd64.raw.xz'); + if (!crdsId || !operatorId || !diskId) { core.setFailed('Required assets missing in draft release'); return; } - core.setOutput('installer_id', installerId); - core.setOutput('disk_id', diskId); + core.setOutput('crds_id', crdsId); + core.setOutput('operator_id', operatorId); + core.setOutput('disk_id', diskId); prepare_env: @@ -224,11 +233,18 @@ jobs: run: mkdir -p _out/assets # ▸ Regular PR path – download artefacts produced by the *build* job - - name: "Download installer (regular PR)" + - name: "Download CRDs (regular PR)" if: "!contains(github.event.pull_request.labels.*.name, 'release')" uses: actions/download-artifact@v4 with: - name: cozystack-installer + name: cozystack-crds + path: _out/assets + + - name: "Download operator (regular PR)" + if: "!contains(github.event.pull_request.labels.*.name, 'release')" + uses: actions/download-artifact@v4 + with: + name: cozystack-operator path: _out/assets # ▸ Release PR path – fetch artefacts from the corresponding draft release @@ -237,8 +253,11 @@ jobs: run: | mkdir -p _out/assets curl -sSL -H "Authorization: token ${GH_PAT}" -H "Accept: application/octet-stream" \ - -o _out/assets/cozystack-installer.yaml \ - "https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/assets/${{ needs.resolve_assets.outputs.installer_id }}" + -o _out/assets/cozystack-crds.yaml \ + "https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/assets/${{ needs.resolve_assets.outputs.crds_id }}" + curl -sSL -H "Authorization: token ${GH_PAT}" -H "Accept: application/octet-stream" \ + -o _out/assets/cozystack-operator.yaml \ + "https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/assets/${{ needs.resolve_assets.outputs.operator_id }}" env: GH_PAT: ${{ secrets.GH_PAT }} diff --git a/Makefile b/Makefile index c037e023..d6d536f4 100644 --- a/Makefile +++ b/Makefile @@ -37,11 +37,32 @@ build: build-deps manifests: mkdir -p _out/assets - (cd packages/core/installer/; helm template --namespace cozy-installer installer .) > _out/assets/cozystack-installer.yaml + helm template installer packages/core/installer -n cozy-system \ + -s templates/crds.yaml \ + > _out/assets/cozystack-crds.yaml + helm template installer packages/core/installer -n cozy-system \ + -s templates/cozystack-operator.yaml \ + -s templates/packagesource.yaml \ + > _out/assets/cozystack-operator.yaml -assets: +cozypkg: + go build -ldflags "-X github.com/cozystack/cozystack/cmd/cozypkg/cmd.Version=v$(COZYSTACK_VERSION)" -o _out/bin/cozypkg ./cmd/cozypkg + +assets: assets-talos assets-cozypkg + +assets-talos: make -C packages/core/talos assets +assets-cozypkg: assets-cozypkg-linux-amd64 assets-cozypkg-linux-arm64 assets-cozypkg-darwin-amd64 assets-cozypkg-darwin-arm64 assets-cozypkg-windows-amd64 assets-cozypkg-windows-arm64 + (cd _out/assets/ && sha256sum cozypkg-*.tar.gz) > _out/assets/cozypkg-checksums.txt + +assets-cozypkg-%: + $(eval EXT := $(if $(filter windows,$(firstword $(subst -, ,$*))),.exe,)) + mkdir -p _out/assets + GOOS=$(firstword $(subst -, ,$*)) GOARCH=$(lastword $(subst -, ,$*)) go build -ldflags "-X github.com/cozystack/cozystack/cmd/cozypkg/cmd.Version=v$(COZYSTACK_VERSION)" -o _out/bin/cozypkg-$*/cozypkg$(EXT) ./cmd/cozypkg + cp LICENSE _out/bin/cozypkg-$*/LICENSE + tar -C _out/bin/cozypkg-$* -czf _out/assets/cozypkg-$*.tar.gz LICENSE cozypkg$(EXT) + test: make -C packages/core/testing apply make -C packages/core/testing test diff --git a/cmd/cozypkg/cmd/root.go b/cmd/cozypkg/cmd/root.go index 62bd54aa..69b0ea6d 100644 --- a/cmd/cozypkg/cmd/root.go +++ b/cmd/cozypkg/cmd/root.go @@ -23,6 +23,9 @@ import ( "github.com/spf13/cobra" ) +// Version is set at build time via -ldflags. +var Version = "dev" + // rootCmd represents the base command when called without any subcommands. var rootCmd = &cobra.Command{ Use: "cozypkg", @@ -44,6 +47,6 @@ func Execute() error { } func init() { - // Commands are registered in their respective init() functions + rootCmd.Version = Version } diff --git a/hack/e2e-install-cozystack.bats b/hack/e2e-install-cozystack.bats index 124bd8bf..4a549c4b 100644 --- a/hack/e2e-install-cozystack.bats +++ b/hack/e2e-install-cozystack.bats @@ -1,8 +1,12 @@ #!/usr/bin/env bats @test "Required installer assets exist" { - if [ ! -f _out/assets/cozystack-installer.yaml ]; then - echo "Missing: _out/assets/cozystack-installer.yaml" >&2 + if [ ! -f _out/assets/cozystack-crds.yaml ]; then + echo "Missing: _out/assets/cozystack-crds.yaml" >&2 + exit 1 + fi + if [ ! -f _out/assets/cozystack-operator.yaml ]; then + echo "Missing: _out/assets/cozystack-operator.yaml" >&2 exit 1 fi } @@ -12,7 +16,8 @@ kubectl create namespace cozy-system --dry-run=client -o yaml | kubectl apply -f - # Apply installer manifests (CRDs + operator) - kubectl apply -f _out/assets/cozystack-installer.yaml + kubectl apply -f _out/assets/cozystack-crds.yaml + kubectl apply -f _out/assets/cozystack-operator.yaml # Wait for the operator deployment to become available kubectl wait deployment/cozystack-operator -n cozy-system --timeout=1m --for=condition=Available diff --git a/hack/upload-assets.sh b/hack/upload-assets.sh index 9788c04f..0f1e9aeb 100755 --- a/hack/upload-assets.sh +++ b/hack/upload-assets.sh @@ -3,9 +3,12 @@ set -xe version=${VERSION:-$(git describe --tags)} -gh release upload --clobber $version _out/assets/cozystack-installer.yaml +gh release upload --clobber $version _out/assets/cozystack-crds.yaml +gh release upload --clobber $version _out/assets/cozystack-operator.yaml gh release upload --clobber $version _out/assets/metal-amd64.iso gh release upload --clobber $version _out/assets/metal-amd64.raw.xz gh release upload --clobber $version _out/assets/nocloud-amd64.raw.xz gh release upload --clobber $version _out/assets/kernel-amd64 gh release upload --clobber $version _out/assets/initramfs-metal-amd64.xz +gh release upload --clobber $version _out/assets/cozypkg-*.tar.gz +gh release upload --clobber $version _out/assets/cozypkg-checksums.txt diff --git a/packages/core/installer/templates/crds.yaml b/packages/core/installer/templates/crds.yaml index 32b438c1..7c7ea584 100644 --- a/packages/core/installer/templates/crds.yaml +++ b/packages/core/installer/templates/crds.yaml @@ -1,4 +1,3 @@ {{- range $path, $_ := .Files.Glob "definitions/*.yaml" }} ---- {{ $.Files.Get $path }} {{- end }} diff --git a/packages/core/testing/Makefile b/packages/core/testing/Makefile index 46705011..baccbfce 100644 --- a/packages/core/testing/Makefile +++ b/packages/core/testing/Makefile @@ -31,7 +31,8 @@ copy-nocloud-image: docker cp ../../../_out/assets/nocloud-amd64.raw.xz "${SANDBOX_NAME}":/workspace/_out/assets/nocloud-amd64.raw.xz copy-installer-manifest: - docker cp ../../../_out/assets/cozystack-installer.yaml "${SANDBOX_NAME}":/workspace/_out/assets/cozystack-installer.yaml + docker cp ../../../_out/assets/cozystack-crds.yaml "${SANDBOX_NAME}":/workspace/_out/assets/cozystack-crds.yaml + docker cp ../../../_out/assets/cozystack-operator.yaml "${SANDBOX_NAME}":/workspace/_out/assets/cozystack-operator.yaml prepare-cluster: copy-nocloud-image docker exec "${SANDBOX_NAME}" sh -c 'cd /workspace && hack/cozytest.sh hack/e2e-prepare-cluster.bats' From 57c8cc26d421d5c056f98f57a7dee3138f0241b2 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 15 Jan 2026 17:12:39 +0100 Subject: [PATCH 055/889] refactor(api): rename CozystackResourceDefinition to ApplicationDefinition Rename the CRD and all related types for better clarity: - CozystackResourceDefinition -> ApplicationDefinition - CozystackResourceDefinitionList -> ApplicationDefinitionList - CozystackResourceDefinitionSpec -> ApplicationDefinitionSpec - All related nested types updated accordingly Updated components: - API types and generated deepcopy code - Controllers and reconcilers - Dashboard, lineagecontrollerwebhook, crdmem packages - CRD YAML definition and Helm chart - All 25 cozyrds YAML manifests - Migration scripts and documentation Added migration 23 to remove old cozystack-resource-definition-crd HelmRelease. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- ...pes.go => applicationdefinitions_types.go} | 46 +- api/v1alpha1/zz_generated.deepcopy.go | 438 +++++++++--------- cmd/cozystack-controller/main.go | 8 +- hack/update-codegen.sh | 6 +- hack/update-crd.sh | 4 +- ...go => applicationdefinition_controller.go} | 36 +- ...> applicationdefinition_helmreconciler.go} | 68 +-- internal/controller/dashboard/breadcrumb.go | 2 +- .../controller/dashboard/customcolumns.go | 2 +- .../dashboard/customformsoverride.go | 2 +- .../dashboard/customformsprefill.go | 2 +- internal/controller/dashboard/factory.go | 4 +- internal/controller/dashboard/helpers.go | 4 +- internal/controller/dashboard/manager.go | 18 +- .../controller/dashboard/marketplacepanel.go | 2 +- internal/controller/dashboard/sidebar.go | 10 +- .../controller/dashboard/tableurimapping.go | 2 +- internal/lineagecontrollerwebhook/config.go | 4 +- .../lineagecontrollerwebhook/controller.go | 10 +- internal/lineagecontrollerwebhook/matcher.go | 6 +- internal/lineagecontrollerwebhook/webhook.go | 4 +- internal/shared/crdmem/memory.go | 16 +- .../platform/images/migrations/migrations/23 | 11 + .../platform/sources/cozystack-engine.yaml | 28 +- packages/core/platform/values.yaml | 2 +- .../Chart.yaml | 2 +- .../Makefile | 2 +- .../cozystack.io_applicationdefinitions.yaml} | 24 +- .../templates/crd.yaml | 2 + .../values.yaml | 0 .../system/bootbox-rd/cozyrds/bootbox.yaml | 2 +- packages/system/bucket-rd/cozyrds/bucket.yaml | 2 +- .../clickhouse-rd/cozyrds/clickhouse.yaml | 2 +- .../templates/crd.yaml | 2 - packages/system/etcd-rd/cozyrds/etcd.yaml | 2 +- .../system/ferretdb-rd/cozyrds/ferretdb.yaml | 2 +- .../foundationdb-rd/cozyrds/foundationdb.yaml | 2 +- .../http-cache-rd/cozyrds/http-cache.yaml | 2 +- packages/system/info-rd/cozyrds/info.yaml | 2 +- .../system/ingress-rd/cozyrds/ingress.yaml | 2 +- packages/system/kafka-rd/cozyrds/kafka.yaml | 2 +- .../kubernetes-rd/cozyrds/kubernetes.yaml | 2 +- .../monitoring-rd/cozyrds/monitoring.yaml | 2 +- packages/system/mysql-rd/cozyrds/mysql.yaml | 2 +- packages/system/nats-rd/cozyrds/nats.yaml | 2 +- .../system/postgres-rd/cozyrds/postgres.yaml | 2 +- .../system/rabbitmq-rd/cozyrds/rabbitmq.yaml | 2 +- packages/system/redis-rd/cozyrds/redis.yaml | 2 +- .../seaweedfs-rd/cozyrds/seaweedfs.yaml | 2 +- .../tcp-balancer-rd/cozyrds/tcp-balancer.yaml | 2 +- packages/system/tenant-rd/cozyrds/tenant.yaml | 2 +- .../cozyrds/virtual-machine.yaml | 2 +- .../cozyrds/virtualprivatecloud.yaml | 2 +- .../system/vm-disk-rd/cozyrds/vm-disk.yaml | 2 +- .../vm-instance-rd/cozyrds/vm-instance.yaml | 2 +- packages/system/vpn-rd/cozyrds/vpn.yaml | 2 +- pkg/cmd/server/start.go | 6 +- 57 files changed, 417 insertions(+), 406 deletions(-) rename api/v1alpha1/{cozystackresourcedefinitions_types.go => applicationdefinitions_types.go} (76%) rename internal/controller/{cozystackresource_controller.go => applicationdefinition_controller.go} (74%) rename internal/controller/{cozystackresourcedefinition_helmreconciler.go => applicationdefinition_helmreconciler.go} (62%) create mode 100755 packages/core/platform/images/migrations/migrations/23 rename packages/system/{cozystack-resource-definition-crd => application-definition-crd}/Chart.yaml (74%) rename packages/system/{cozystack-resource-definition-crd => application-definition-crd}/Makefile (57%) rename packages/system/{cozystack-resource-definition-crd/definition/cozystack.io_cozystackresourcedefinitions.yaml => application-definition-crd/definition/cozystack.io_applicationdefinitions.yaml} (97%) create mode 100644 packages/system/application-definition-crd/templates/crd.yaml rename packages/system/{cozystack-resource-definition-crd => application-definition-crd}/values.yaml (100%) delete mode 100644 packages/system/cozystack-resource-definition-crd/templates/crd.yaml diff --git a/api/v1alpha1/cozystackresourcedefinitions_types.go b/api/v1alpha1/applicationdefinitions_types.go similarity index 76% rename from api/v1alpha1/cozystackresourcedefinitions_types.go rename to api/v1alpha1/applicationdefinitions_types.go index 04b24294..4a4cd226 100644 --- a/api/v1alpha1/cozystackresourcedefinitions_types.go +++ b/api/v1alpha1/applicationdefinitions_types.go @@ -24,45 +24,45 @@ import ( // +kubebuilder:object:root=true // +kubebuilder:resource:scope=Cluster -// CozystackResourceDefinition is the Schema for the cozystackresourcedefinitions API -type CozystackResourceDefinition struct { +// ApplicationDefinition is the Schema for the applicationdefinitions API +type ApplicationDefinition struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Spec CozystackResourceDefinitionSpec `json:"spec,omitempty"` + Spec ApplicationDefinitionSpec `json:"spec,omitempty"` } // +kubebuilder:object:root=true -// CozystackResourceDefinitionList contains a list of CozystackResourceDefinitions -type CozystackResourceDefinitionList struct { +// ApplicationDefinitionList contains a list of ApplicationDefinitions +type ApplicationDefinitionList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` - Items []CozystackResourceDefinition `json:"items"` + Items []ApplicationDefinition `json:"items"` } func init() { - SchemeBuilder.Register(&CozystackResourceDefinition{}, &CozystackResourceDefinitionList{}) + SchemeBuilder.Register(&ApplicationDefinition{}, &ApplicationDefinitionList{}) } -type CozystackResourceDefinitionSpec struct { +type ApplicationDefinitionSpec struct { // Application configuration - Application CozystackResourceDefinitionApplication `json:"application"` + Application ApplicationDefinitionApplication `json:"application"` // Release configuration - Release CozystackResourceDefinitionRelease `json:"release"` + Release ApplicationDefinitionRelease `json:"release"` // Secret selectors - Secrets CozystackResourceDefinitionResources `json:"secrets,omitempty"` + Secrets ApplicationDefinitionResources `json:"secrets,omitempty"` // Service selectors - Services CozystackResourceDefinitionResources `json:"services,omitempty"` + Services ApplicationDefinitionResources `json:"services,omitempty"` // Ingress selectors - Ingresses CozystackResourceDefinitionResources `json:"ingresses,omitempty"` + Ingresses ApplicationDefinitionResources `json:"ingresses,omitempty"` // Dashboard configuration for this resource - Dashboard *CozystackResourceDefinitionDashboard `json:"dashboard,omitempty"` + Dashboard *ApplicationDefinitionDashboard `json:"dashboard,omitempty"` } -type CozystackResourceDefinitionApplication struct { +type ApplicationDefinitionApplication struct { // Kind of the application, used for UI and API Kind string `json:"kind"` // OpenAPI schema for the application, used for API validation @@ -73,7 +73,7 @@ type CozystackResourceDefinitionApplication struct { Singular string `json:"singular"` } -type CozystackResourceDefinitionRelease struct { +type ApplicationDefinitionRelease struct { // Reference to the chart source ChartRef *helmv2.CrossNamespaceSourceReference `json:"chartRef"` // Labels for the release @@ -82,7 +82,7 @@ type CozystackResourceDefinitionRelease struct { Prefix string `json:"prefix"` } -// CozystackResourceDefinitionResourceSelector extends metav1.LabelSelector with resourceNames support. +// ApplicationDefinitionResourceSelector extends metav1.LabelSelector with resourceNames support. // A resource matches this selector only if it satisfies ALL criteria: // - Label selector conditions (matchExpressions and matchLabels) // - AND has a name that matches one of the names in resourceNames (if specified) @@ -105,7 +105,7 @@ type CozystackResourceDefinitionRelease struct { // - "{{ .name }}-secret" // - "{{ .kind }}-{{ .name }}-tls" // - "specificname" -type CozystackResourceDefinitionResourceSelector struct { +type ApplicationDefinitionResourceSelector struct { metav1.LabelSelector `json:",inline"` // ResourceNames is a list of resource names to match // If specified, the resource must have one of these exact names to match the selector @@ -113,16 +113,16 @@ type CozystackResourceDefinitionResourceSelector struct { ResourceNames []string `json:"resourceNames,omitempty"` } -type CozystackResourceDefinitionResources struct { +type ApplicationDefinitionResources struct { // Exclude contains an array of resource selectors that target resources. // If a resource matches the selector in any of the elements in the array, it is // hidden from the user, regardless of the matches in the include array. - Exclude []*CozystackResourceDefinitionResourceSelector `json:"exclude,omitempty"` + Exclude []*ApplicationDefinitionResourceSelector `json:"exclude,omitempty"` // Include contains an array of resource selectors that target resources. // If a resource matches the selector in any of the elements in the array, and // matches none of the selectors in the exclude array that resource is marked // as a tenant resource and is visible to users. - Include []*CozystackResourceDefinitionResourceSelector `json:"include,omitempty"` + Include []*ApplicationDefinitionResourceSelector `json:"include,omitempty"` } // ---- Dashboard types ---- @@ -139,8 +139,8 @@ const ( DashboardTabYAML DashboardTab = "yaml" ) -// CozystackResourceDefinitionDashboard describes how this resource appears in the UI. -type CozystackResourceDefinitionDashboard struct { +// ApplicationDefinitionDashboard describes how this resource appears in the UI. +type ApplicationDefinitionDashboard struct { // Human-readable name shown in the UI (e.g., "Bucket") Singular string `json:"singular"` // Plural human-readable name (e.g., "Buckets") diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index f060990f..c1adf95c 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -28,6 +28,225 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApplicationDefinition) DeepCopyInto(out *ApplicationDefinition) { + *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 ApplicationDefinition. +func (in *ApplicationDefinition) DeepCopy() *ApplicationDefinition { + if in == nil { + return nil + } + out := new(ApplicationDefinition) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ApplicationDefinition) 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 *ApplicationDefinitionApplication) DeepCopyInto(out *ApplicationDefinitionApplication) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationDefinitionApplication. +func (in *ApplicationDefinitionApplication) DeepCopy() *ApplicationDefinitionApplication { + if in == nil { + return nil + } + out := new(ApplicationDefinitionApplication) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApplicationDefinitionDashboard) DeepCopyInto(out *ApplicationDefinitionDashboard) { + *out = *in + if in.Tags != nil { + in, out := &in.Tags, &out.Tags + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Tabs != nil { + in, out := &in.Tabs, &out.Tabs + *out = make([]DashboardTab, len(*in)) + copy(*out, *in) + } + if in.KeysOrder != nil { + in, out := &in.KeysOrder, &out.KeysOrder + *out = make([][]string, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = make([]string, len(*in)) + copy(*out, *in) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationDefinitionDashboard. +func (in *ApplicationDefinitionDashboard) DeepCopy() *ApplicationDefinitionDashboard { + if in == nil { + return nil + } + out := new(ApplicationDefinitionDashboard) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApplicationDefinitionList) DeepCopyInto(out *ApplicationDefinitionList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ApplicationDefinition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationDefinitionList. +func (in *ApplicationDefinitionList) DeepCopy() *ApplicationDefinitionList { + if in == nil { + return nil + } + out := new(ApplicationDefinitionList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ApplicationDefinitionList) 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 *ApplicationDefinitionRelease) DeepCopyInto(out *ApplicationDefinitionRelease) { + *out = *in + if in.ChartRef != nil { + in, out := &in.ChartRef, &out.ChartRef + *out = new(v2.CrossNamespaceSourceReference) + **out = **in + } + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *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 ApplicationDefinitionRelease. +func (in *ApplicationDefinitionRelease) DeepCopy() *ApplicationDefinitionRelease { + if in == nil { + return nil + } + out := new(ApplicationDefinitionRelease) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApplicationDefinitionResourceSelector) DeepCopyInto(out *ApplicationDefinitionResourceSelector) { + *out = *in + in.LabelSelector.DeepCopyInto(&out.LabelSelector) + if in.ResourceNames != nil { + in, out := &in.ResourceNames, &out.ResourceNames + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationDefinitionResourceSelector. +func (in *ApplicationDefinitionResourceSelector) DeepCopy() *ApplicationDefinitionResourceSelector { + if in == nil { + return nil + } + out := new(ApplicationDefinitionResourceSelector) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApplicationDefinitionResources) DeepCopyInto(out *ApplicationDefinitionResources) { + *out = *in + if in.Exclude != nil { + in, out := &in.Exclude, &out.Exclude + *out = make([]*ApplicationDefinitionResourceSelector, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(ApplicationDefinitionResourceSelector) + (*in).DeepCopyInto(*out) + } + } + } + if in.Include != nil { + in, out := &in.Include, &out.Include + *out = make([]*ApplicationDefinitionResourceSelector, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(ApplicationDefinitionResourceSelector) + (*in).DeepCopyInto(*out) + } + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationDefinitionResources. +func (in *ApplicationDefinitionResources) DeepCopy() *ApplicationDefinitionResources { + if in == nil { + return nil + } + out := new(ApplicationDefinitionResources) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApplicationDefinitionSpec) DeepCopyInto(out *ApplicationDefinitionSpec) { + *out = *in + out.Application = in.Application + in.Release.DeepCopyInto(&out.Release) + in.Secrets.DeepCopyInto(&out.Secrets) + in.Services.DeepCopyInto(&out.Services) + in.Ingresses.DeepCopyInto(&out.Ingresses) + if in.Dashboard != nil { + in, out := &in.Dashboard, &out.Dashboard + *out = new(ApplicationDefinitionDashboard) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationDefinitionSpec. +func (in *ApplicationDefinitionSpec) DeepCopy() *ApplicationDefinitionSpec { + if in == nil { + return nil + } + out := new(ApplicationDefinitionSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Component) DeepCopyInto(out *Component) { *out = *in @@ -78,225 +297,6 @@ func (in *ComponentInstall) DeepCopy() *ComponentInstall { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CozystackResourceDefinition) DeepCopyInto(out *CozystackResourceDefinition) { - *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 CozystackResourceDefinition. -func (in *CozystackResourceDefinition) DeepCopy() *CozystackResourceDefinition { - if in == nil { - return nil - } - out := new(CozystackResourceDefinition) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *CozystackResourceDefinition) 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 *CozystackResourceDefinitionApplication) DeepCopyInto(out *CozystackResourceDefinitionApplication) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CozystackResourceDefinitionApplication. -func (in *CozystackResourceDefinitionApplication) DeepCopy() *CozystackResourceDefinitionApplication { - if in == nil { - return nil - } - out := new(CozystackResourceDefinitionApplication) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CozystackResourceDefinitionDashboard) DeepCopyInto(out *CozystackResourceDefinitionDashboard) { - *out = *in - if in.Tags != nil { - in, out := &in.Tags, &out.Tags - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.Tabs != nil { - in, out := &in.Tabs, &out.Tabs - *out = make([]DashboardTab, len(*in)) - copy(*out, *in) - } - if in.KeysOrder != nil { - in, out := &in.KeysOrder, &out.KeysOrder - *out = make([][]string, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = make([]string, len(*in)) - copy(*out, *in) - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CozystackResourceDefinitionDashboard. -func (in *CozystackResourceDefinitionDashboard) DeepCopy() *CozystackResourceDefinitionDashboard { - if in == nil { - return nil - } - out := new(CozystackResourceDefinitionDashboard) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CozystackResourceDefinitionList) DeepCopyInto(out *CozystackResourceDefinitionList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]CozystackResourceDefinition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CozystackResourceDefinitionList. -func (in *CozystackResourceDefinitionList) DeepCopy() *CozystackResourceDefinitionList { - if in == nil { - return nil - } - out := new(CozystackResourceDefinitionList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *CozystackResourceDefinitionList) 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 *CozystackResourceDefinitionRelease) DeepCopyInto(out *CozystackResourceDefinitionRelease) { - *out = *in - if in.ChartRef != nil { - in, out := &in.ChartRef, &out.ChartRef - *out = new(v2.CrossNamespaceSourceReference) - **out = **in - } - if in.Labels != nil { - in, out := &in.Labels, &out.Labels - *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 CozystackResourceDefinitionRelease. -func (in *CozystackResourceDefinitionRelease) DeepCopy() *CozystackResourceDefinitionRelease { - if in == nil { - return nil - } - out := new(CozystackResourceDefinitionRelease) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CozystackResourceDefinitionResourceSelector) DeepCopyInto(out *CozystackResourceDefinitionResourceSelector) { - *out = *in - in.LabelSelector.DeepCopyInto(&out.LabelSelector) - if in.ResourceNames != nil { - in, out := &in.ResourceNames, &out.ResourceNames - *out = make([]string, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CozystackResourceDefinitionResourceSelector. -func (in *CozystackResourceDefinitionResourceSelector) DeepCopy() *CozystackResourceDefinitionResourceSelector { - if in == nil { - return nil - } - out := new(CozystackResourceDefinitionResourceSelector) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CozystackResourceDefinitionResources) DeepCopyInto(out *CozystackResourceDefinitionResources) { - *out = *in - if in.Exclude != nil { - in, out := &in.Exclude, &out.Exclude - *out = make([]*CozystackResourceDefinitionResourceSelector, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(CozystackResourceDefinitionResourceSelector) - (*in).DeepCopyInto(*out) - } - } - } - if in.Include != nil { - in, out := &in.Include, &out.Include - *out = make([]*CozystackResourceDefinitionResourceSelector, len(*in)) - for i := range *in { - if (*in)[i] != nil { - in, out := &(*in)[i], &(*out)[i] - *out = new(CozystackResourceDefinitionResourceSelector) - (*in).DeepCopyInto(*out) - } - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CozystackResourceDefinitionResources. -func (in *CozystackResourceDefinitionResources) DeepCopy() *CozystackResourceDefinitionResources { - if in == nil { - return nil - } - out := new(CozystackResourceDefinitionResources) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CozystackResourceDefinitionSpec) DeepCopyInto(out *CozystackResourceDefinitionSpec) { - *out = *in - out.Application = in.Application - in.Release.DeepCopyInto(&out.Release) - in.Secrets.DeepCopyInto(&out.Secrets) - in.Services.DeepCopyInto(&out.Services) - in.Ingresses.DeepCopyInto(&out.Ingresses) - if in.Dashboard != nil { - in, out := &in.Dashboard, &out.Dashboard - *out = new(CozystackResourceDefinitionDashboard) - (*in).DeepCopyInto(*out) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CozystackResourceDefinitionSpec. -func (in *CozystackResourceDefinitionSpec) DeepCopy() *CozystackResourceDefinitionSpec { - if in == nil { - return nil - } - out := new(CozystackResourceDefinitionSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DependencyStatus) DeepCopyInto(out *DependencyStatus) { *out = *in diff --git a/cmd/cozystack-controller/main.go b/cmd/cozystack-controller/main.go index 0e7199d3..a3a17d08 100644 --- a/cmd/cozystack-controller/main.go +++ b/cmd/cozystack-controller/main.go @@ -204,20 +204,20 @@ func main() { if reconcileDeployment { cozyAPIKind = "Deployment" } - if err = (&controller.CozystackResourceDefinitionReconciler{ + if err = (&controller.ApplicationDefinitionReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), CozystackAPIKind: cozyAPIKind, }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "CozystackResourceDefinitionReconciler") + setupLog.Error(err, "unable to create controller", "controller", "ApplicationDefinitionReconciler") os.Exit(1) } - if err = (&controller.CozystackResourceDefinitionHelmReconciler{ + if err = (&controller.ApplicationDefinitionHelmReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "CozystackResourceDefinitionHelmReconciler") + setupLog.Error(err, "unable to create controller", "controller", "ApplicationDefinitionHelmReconciler") os.Exit(1) } diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh index 3b2af7d9..188325e8 100755 --- a/hack/update-codegen.sh +++ b/hack/update-codegen.sh @@ -26,7 +26,7 @@ CONTROLLER_GEN="go run sigs.k8s.io/controller-tools/cmd/controller-gen@v0.16.4" TMPDIR=$(mktemp -d) OPERATOR_CRDDIR=packages/core/installer/definitions COZY_CONTROLLER_CRDDIR=packages/system/cozystack-controller/definitions -COZY_RD_CRDDIR=packages/system/cozystack-resource-definition-crd/definition +COZY_RD_CRDDIR=packages/system/application-definition-crd/definition BACKUPS_CORE_CRDDIR=packages/system/backup-controller/definitions BACKUPSTRATEGY_CRDDIR=packages/system/backupstrategy-controller/definitions @@ -66,8 +66,8 @@ $CONTROLLER_GEN rbac:roleName=manager-role crd paths="./api/..." output:crd:arti mv ${TMPDIR}/cozystack.io_packages.yaml ${OPERATOR_CRDDIR}/cozystack.io_packages.yaml mv ${TMPDIR}/cozystack.io_packagesources.yaml ${OPERATOR_CRDDIR}/cozystack.io_packagesources.yaml -mv ${TMPDIR}/cozystack.io_cozystackresourcedefinitions.yaml \ - ${COZY_RD_CRDDIR}/cozystack.io_cozystackresourcedefinitions.yaml +mv ${TMPDIR}/cozystack.io_applicationdefinitions.yaml \ + ${COZY_RD_CRDDIR}/cozystack.io_applicationdefinitions.yaml mv ${TMPDIR}/backups.cozystack.io*.yaml ${BACKUPS_CORE_CRDDIR}/ mv ${TMPDIR}/strategy.backups.cozystack.io*.yaml ${BACKUPSTRATEGY_CRDDIR}/ diff --git a/hack/update-crd.sh b/hack/update-crd.sh index ce9d5d10..4bd2095a 100755 --- a/hack/update-crd.sh +++ b/hack/update-crd.sh @@ -79,7 +79,7 @@ OUT="${OUT:-$CRD_DIR/$NAME.yaml}" if [[ ! -f "$OUT" ]]; then cat >"$OUT" < 0 { + // Check and update labels from ApplicationDefinition + if len(appDef.Spec.Release.Labels) > 0 { if hrCopy.Labels == nil { hrCopy.Labels = make(map[string]string) } - for key, value := range crd.Spec.Release.Labels { + for key, value := range appDef.Spec.Release.Labels { if hrCopy.Labels[key] != value { logger.V(4).Info("Updating HelmRelease label", "name", hr.Name, "namespace", hr.Namespace, "label", key, "value", value) hrCopy.Labels[key] = value diff --git a/internal/controller/dashboard/breadcrumb.go b/internal/controller/dashboard/breadcrumb.go index 5122f605..aadcacac 100644 --- a/internal/controller/dashboard/breadcrumb.go +++ b/internal/controller/dashboard/breadcrumb.go @@ -14,7 +14,7 @@ import ( ) // ensureBreadcrumb creates or updates a Breadcrumb resource for the given CRD -func (m *Manager) ensureBreadcrumb(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) error { +func (m *Manager) ensureBreadcrumb(ctx context.Context, crd *cozyv1alpha1.ApplicationDefinition) error { group, version, kind := pickGVK(crd) lowerKind := strings.ToLower(kind) diff --git a/internal/controller/dashboard/customcolumns.go b/internal/controller/dashboard/customcolumns.go index 6c23d68b..be1318f8 100644 --- a/internal/controller/dashboard/customcolumns.go +++ b/internal/controller/dashboard/customcolumns.go @@ -21,7 +21,7 @@ import ( // // metadata.name: stock-namespace-.. // spec.id: stock-namespace-/// -func (m *Manager) ensureCustomColumnsOverride(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) (controllerutil.OperationResult, error) { +func (m *Manager) ensureCustomColumnsOverride(ctx context.Context, crd *cozyv1alpha1.ApplicationDefinition) (controllerutil.OperationResult, error) { g, v, kind := pickGVK(crd) plural := pickPlural(kind, crd) // Details page segment uses lowercase kind, mirroring your example diff --git a/internal/controller/dashboard/customformsoverride.go b/internal/controller/dashboard/customformsoverride.go index 2b0daa08..60bc82fc 100644 --- a/internal/controller/dashboard/customformsoverride.go +++ b/internal/controller/dashboard/customformsoverride.go @@ -15,7 +15,7 @@ import ( ) // ensureCustomFormsOverride creates or updates a CustomFormsOverride resource for the given CRD -func (m *Manager) ensureCustomFormsOverride(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) error { +func (m *Manager) ensureCustomFormsOverride(ctx context.Context, crd *cozyv1alpha1.ApplicationDefinition) error { g, v, kind := pickGVK(crd) plural := pickPlural(kind, crd) diff --git a/internal/controller/dashboard/customformsprefill.go b/internal/controller/dashboard/customformsprefill.go index d2761873..35d22ff1 100644 --- a/internal/controller/dashboard/customformsprefill.go +++ b/internal/controller/dashboard/customformsprefill.go @@ -16,7 +16,7 @@ import ( ) // ensureCustomFormsPrefill creates or updates a CustomFormsPrefill resource for the given CRD -func (m *Manager) ensureCustomFormsPrefill(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) (reconcile.Result, error) { +func (m *Manager) ensureCustomFormsPrefill(ctx context.Context, crd *cozyv1alpha1.ApplicationDefinition) (reconcile.Result, error) { logger := log.FromContext(ctx) app := crd.Spec.Application diff --git a/internal/controller/dashboard/factory.go b/internal/controller/dashboard/factory.go index 53d771d0..84578dd0 100644 --- a/internal/controller/dashboard/factory.go +++ b/internal/controller/dashboard/factory.go @@ -15,7 +15,7 @@ import ( ) // ensureFactory creates or updates a Factory resource for the given CRD -func (m *Manager) ensureFactory(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) error { +func (m *Manager) ensureFactory(ctx context.Context, crd *cozyv1alpha1.ApplicationDefinition) error { g, v, kind := pickGVK(crd) plural := pickPlural(kind, crd) @@ -557,7 +557,7 @@ type factoryFlags struct { // factoryFeatureFlags tries several conventional locations so you can evolve the API // without breaking the controller. Defaults are false (hidden). -func factoryFeatureFlags(crd *cozyv1alpha1.CozystackResourceDefinition) factoryFlags { +func factoryFeatureFlags(crd *cozyv1alpha1.ApplicationDefinition) factoryFlags { var f factoryFlags f.Workloads = true diff --git a/internal/controller/dashboard/helpers.go b/internal/controller/dashboard/helpers.go index a0023a05..2a35bbff 100644 --- a/internal/controller/dashboard/helpers.go +++ b/internal/controller/dashboard/helpers.go @@ -23,7 +23,7 @@ type fieldInfo struct { // pickGVK tries to read group/version/kind from the CRD. We prefer the "application" section, // falling back to other likely fields if your schema differs. -func pickGVK(crd *cozyv1alpha1.CozystackResourceDefinition) (group, version, kind string) { +func pickGVK(crd *cozyv1alpha1.ApplicationDefinition) (group, version, kind string) { // Best guess based on your examples: if crd.Spec.Application.Kind != "" { kind = crd.Spec.Application.Kind @@ -41,7 +41,7 @@ func pickGVK(crd *cozyv1alpha1.CozystackResourceDefinition) (group, version, kin } // pickPlural prefers a field on the CRD if you have it; otherwise do a simple lowercase + "s". -func pickPlural(kind string, crd *cozyv1alpha1.CozystackResourceDefinition) string { +func pickPlural(kind string, crd *cozyv1alpha1.ApplicationDefinition) string { // If you have crd.Spec.Application.Plural, prefer it. Example: if crd.Spec.Application.Plural != "" { return crd.Spec.Application.Plural diff --git a/internal/controller/dashboard/manager.go b/internal/controller/dashboard/manager.go index 12e50fbc..23897586 100644 --- a/internal/controller/dashboard/manager.go +++ b/internal/controller/dashboard/manager.go @@ -41,7 +41,7 @@ func AddToScheme(s *runtime.Scheme) error { } // Manager owns logic for creating/updating dashboard resources derived from CRDs. -// It’s easy to extend: add new ensure* methods and wire them into EnsureForCRD. +// It’s easy to extend: add new ensure* methods and wire them into EnsureForAppDef. type Manager struct { client.Client Scheme *runtime.Scheme @@ -56,7 +56,7 @@ func NewManager(c client.Client, scheme *runtime.Scheme) *Manager { func (m *Manager) SetupWithManager(mgr ctrl.Manager) error { if err := ctrl.NewControllerManagedBy(mgr). Named("dashboard-reconciler"). - For(&cozyv1alpha1.CozystackResourceDefinition{}). + For(&cozyv1alpha1.ApplicationDefinition{}). Complete(m); err != nil { return err } @@ -72,7 +72,7 @@ func (m *Manager) SetupWithManager(mgr ctrl.Manager) error { func (m *Manager) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { l := log.FromContext(ctx) - crd := &cozyv1alpha1.CozystackResourceDefinition{} + crd := &cozyv1alpha1.ApplicationDefinition{} err := m.Get(ctx, types.NamespacedName{Name: req.Name}, crd) if err != nil { @@ -85,10 +85,10 @@ func (m *Manager) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, return ctrl.Result{}, err } - return m.EnsureForCRD(ctx, crd) + return m.EnsureForAppDef(ctx, crd) } -// EnsureForCRD is the single entry-point used by the controller. +// EnsureForAppDef is the single entry-point used by the controller. // Add more ensure* calls here as you implement support for other resources: // // - ensureBreadcrumb (implemented) @@ -99,7 +99,7 @@ func (m *Manager) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, // - ensureMarketplacePanel (implemented) // - ensureSidebar (implemented) // - ensureTableUriMapping (implemented) -func (m *Manager) EnsureForCRD(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) (reconcile.Result, error) { +func (m *Manager) EnsureForAppDef(ctx context.Context, crd *cozyv1alpha1.ApplicationDefinition) (reconcile.Result, error) { // Early return if crd.Spec.Dashboard is nil to prevent oscillation if crd.Spec.Dashboard == nil { return reconcile.Result{}, nil @@ -148,7 +148,7 @@ func (m *Manager) InitializeStaticResources(ctx context.Context) error { } // addDashboardLabels adds standard dashboard management labels to a resource -func (m *Manager) addDashboardLabels(obj client.Object, crd *cozyv1alpha1.CozystackResourceDefinition, resourceType string) { +func (m *Manager) addDashboardLabels(obj client.Object, crd *cozyv1alpha1.ApplicationDefinition, resourceType string) { labels := obj.GetLabels() if labels == nil { labels = make(map[string]string) @@ -197,7 +197,7 @@ func (m *Manager) getStaticResourceSelector() client.MatchingLabels { // CleanupOrphanedResources removes dashboard resources that are no longer needed // This should be called after cache warming to ensure all current resources are known func (m *Manager) CleanupOrphanedResources(ctx context.Context) error { - var crdList cozyv1alpha1.CozystackResourceDefinitionList + var crdList cozyv1alpha1.ApplicationDefinitionList if err := m.List(ctx, &crdList, &client.ListOptions{}); err != nil { return err } @@ -228,7 +228,7 @@ func (m *Manager) CleanupOrphanedResources(ctx context.Context) error { } // buildExpectedResourceSet creates a map of expected resource names by type -func (m *Manager) buildExpectedResourceSet(crds []cozyv1alpha1.CozystackResourceDefinition) map[string]map[string]bool { +func (m *Manager) buildExpectedResourceSet(crds []cozyv1alpha1.ApplicationDefinition) map[string]map[string]bool { expected := make(map[string]map[string]bool) // Initialize maps for each resource type diff --git a/internal/controller/dashboard/marketplacepanel.go b/internal/controller/dashboard/marketplacepanel.go index 82a8f336..6bfce752 100644 --- a/internal/controller/dashboard/marketplacepanel.go +++ b/internal/controller/dashboard/marketplacepanel.go @@ -16,7 +16,7 @@ import ( ) // ensureMarketplacePanel creates or updates a MarketplacePanel resource for the given CRD -func (m *Manager) ensureMarketplacePanel(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) (reconcile.Result, error) { +func (m *Manager) ensureMarketplacePanel(ctx context.Context, crd *cozyv1alpha1.ApplicationDefinition) (reconcile.Result, error) { logger := log.FromContext(ctx) mp := &dashv1alpha1.MarketplacePanel{} diff --git a/internal/controller/dashboard/sidebar.go b/internal/controller/dashboard/sidebar.go index c6f9d3ba..5bb378c2 100644 --- a/internal/controller/dashboard/sidebar.go +++ b/internal/controller/dashboard/sidebar.go @@ -28,12 +28,12 @@ import ( // - Categories are ordered strictly as: // 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 { +func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.ApplicationDefinition) error { // Build the full menu once. // 1) Fetch all CRDs - var all []cozyv1alpha1.CozystackResourceDefinition - var crdList cozyv1alpha1.CozystackResourceDefinitionList + var all []cozyv1alpha1.ApplicationDefinition + var crdList cozyv1alpha1.ApplicationDefinitionList if err := m.List(ctx, &crdList, &client.ListOptions{}); err != nil { return err } @@ -263,7 +263,7 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Cozystack // upsertMultipleSidebars creates/updates several Sidebar resources with the same menu spec. func (m *Manager) upsertMultipleSidebars( ctx context.Context, - crd *cozyv1alpha1.CozystackResourceDefinition, + crd *cozyv1alpha1.ApplicationDefinition, ids []string, keysAndTags map[string]any, menuItems []any, @@ -370,7 +370,7 @@ func orderCategoryLabels[T any](cats map[string][]T) []string { } // safeCategory returns spec.dashboard.category or "Resources" if not set. -func safeCategory(def *cozyv1alpha1.CozystackResourceDefinition) string { +func safeCategory(def *cozyv1alpha1.ApplicationDefinition) string { if def == nil || def.Spec.Dashboard == nil { return "Resources" } diff --git a/internal/controller/dashboard/tableurimapping.go b/internal/controller/dashboard/tableurimapping.go index 6e8a395d..e9a4849c 100644 --- a/internal/controller/dashboard/tableurimapping.go +++ b/internal/controller/dashboard/tableurimapping.go @@ -7,7 +7,7 @@ import ( ) // ensureTableUriMapping creates or updates a TableUriMapping resource for the given CRD -func (m *Manager) ensureTableUriMapping(ctx context.Context, crd *cozyv1alpha1.CozystackResourceDefinition) error { +func (m *Manager) ensureTableUriMapping(ctx context.Context, crd *cozyv1alpha1.ApplicationDefinition) error { // Links are fully managed by the CustomColumnsOverride. return nil } diff --git a/internal/lineagecontrollerwebhook/config.go b/internal/lineagecontrollerwebhook/config.go index 7204ab66..ce4b6898 100644 --- a/internal/lineagecontrollerwebhook/config.go +++ b/internal/lineagecontrollerwebhook/config.go @@ -14,14 +14,14 @@ type appRef struct { } type runtimeConfig struct { - appCRDMap map[appRef]*cozyv1alpha1.CozystackResourceDefinition + appCRDMap map[appRef]*cozyv1alpha1.ApplicationDefinition } func (l *LineageControllerWebhook) initConfig() { l.initOnce.Do(func() { if l.config.Load() == nil { l.config.Store(&runtimeConfig{ - appCRDMap: make(map[appRef]*cozyv1alpha1.CozystackResourceDefinition), + appCRDMap: make(map[appRef]*cozyv1alpha1.ApplicationDefinition), }) } }) diff --git a/internal/lineagecontrollerwebhook/controller.go b/internal/lineagecontrollerwebhook/controller.go index 092d16e7..42bdb396 100644 --- a/internal/lineagecontrollerwebhook/controller.go +++ b/internal/lineagecontrollerwebhook/controller.go @@ -8,23 +8,23 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" ) -// +kubebuilder:rbac:groups=cozystack.io,resources=cozystackresourcedefinitions,verbs=list;watch;get +// +kubebuilder:rbac:groups=cozystack.io,resources=applicationdefinitions,verbs=list;watch;get func (c *LineageControllerWebhook) SetupWithManagerAsController(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - For(&cozyv1alpha1.CozystackResourceDefinition{}). + For(&cozyv1alpha1.ApplicationDefinition{}). Complete(c) } func (c *LineageControllerWebhook) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { l := log.FromContext(ctx) - crds := &cozyv1alpha1.CozystackResourceDefinitionList{} + crds := &cozyv1alpha1.ApplicationDefinitionList{} if err := c.List(ctx, crds); err != nil { - l.Error(err, "failed reading CozystackResourceDefinitions") + l.Error(err, "failed reading ApplicationDefinitions") return ctrl.Result{}, err } cfg := &runtimeConfig{ - appCRDMap: make(map[appRef]*cozyv1alpha1.CozystackResourceDefinition), + appCRDMap: make(map[appRef]*cozyv1alpha1.ApplicationDefinition), } for _, crd := range crds.Items { appRef := appRef{ diff --git a/internal/lineagecontrollerwebhook/matcher.go b/internal/lineagecontrollerwebhook/matcher.go index 7c756a49..6e7bd318 100644 --- a/internal/lineagecontrollerwebhook/matcher.go +++ b/internal/lineagecontrollerwebhook/matcher.go @@ -42,7 +42,7 @@ func matchName(ctx context.Context, name string, templateContext map[string]stri return false } -func matchResourceToSelector(ctx context.Context, name string, templateContext, l map[string]string, s *cozyv1alpha1.CozystackResourceDefinitionResourceSelector) bool { +func matchResourceToSelector(ctx context.Context, name string, templateContext, l map[string]string, s *cozyv1alpha1.ApplicationDefinitionResourceSelector) bool { sel, err := metav1.LabelSelectorAsSelector(&s.LabelSelector) if err != nil { log.FromContext(ctx).Error(err, "failed to convert label selector to selector") @@ -53,7 +53,7 @@ func matchResourceToSelector(ctx context.Context, name string, templateContext, return labelMatches && nameMatches } -func matchResourceToSelectorArray(ctx context.Context, name string, templateContext, l map[string]string, ss []*cozyv1alpha1.CozystackResourceDefinitionResourceSelector) bool { +func matchResourceToSelectorArray(ctx context.Context, name string, templateContext, l map[string]string, ss []*cozyv1alpha1.ApplicationDefinitionResourceSelector) bool { for _, s := range ss { if matchResourceToSelector(ctx, name, templateContext, l, s) { return true @@ -62,7 +62,7 @@ func matchResourceToSelectorArray(ctx context.Context, name string, templateCont return false } -func matchResourceToExcludeInclude(ctx context.Context, name string, templateContext, l map[string]string, resources *cozyv1alpha1.CozystackResourceDefinitionResources) bool { +func matchResourceToExcludeInclude(ctx context.Context, name string, templateContext, l map[string]string, resources *cozyv1alpha1.ApplicationDefinitionResources) bool { if resources == nil { return false } diff --git a/internal/lineagecontrollerwebhook/webhook.go b/internal/lineagecontrollerwebhook/webhook.go index 0841c891..299cbaee 100644 --- a/internal/lineagecontrollerwebhook/webhook.go +++ b/internal/lineagecontrollerwebhook/webhook.go @@ -33,8 +33,8 @@ const ( ManagerNameKey = "apps.cozystack.io/application.name" ) -// getResourceSelectors returns the appropriate CozystackResourceDefinitionResources for a given GroupKind -func (h *LineageControllerWebhook) getResourceSelectors(gk schema.GroupKind, crd *cozyv1alpha1.CozystackResourceDefinition) *cozyv1alpha1.CozystackResourceDefinitionResources { +// getResourceSelectors returns the appropriate ApplicationDefinitionResources for a given GroupKind +func (h *LineageControllerWebhook) getResourceSelectors(gk schema.GroupKind, crd *cozyv1alpha1.ApplicationDefinition) *cozyv1alpha1.ApplicationDefinitionResources { switch { case gk.Group == "" && gk.Kind == "Secret": return &crd.Spec.Secrets diff --git a/internal/shared/crdmem/memory.go b/internal/shared/crdmem/memory.go index fbfbeeea..f0446627 100644 --- a/internal/shared/crdmem/memory.go +++ b/internal/shared/crdmem/memory.go @@ -11,13 +11,13 @@ import ( type Memory struct { mu sync.RWMutex - data map[string]cozyv1alpha1.CozystackResourceDefinition + data map[string]cozyv1alpha1.ApplicationDefinition primed bool primeOnce sync.Once } func New() *Memory { - return &Memory{data: make(map[string]cozyv1alpha1.CozystackResourceDefinition)} + return &Memory{data: make(map[string]cozyv1alpha1.ApplicationDefinition)} } var ( @@ -30,7 +30,7 @@ func Global() *Memory { return global } -func (m *Memory) Upsert(obj *cozyv1alpha1.CozystackResourceDefinition) { +func (m *Memory) Upsert(obj *cozyv1alpha1.ApplicationDefinition) { if obj == nil { return } @@ -45,10 +45,10 @@ func (m *Memory) Delete(name string) { m.mu.Unlock() } -func (m *Memory) Snapshot() []cozyv1alpha1.CozystackResourceDefinition { +func (m *Memory) Snapshot() []cozyv1alpha1.ApplicationDefinition { m.mu.RLock() defer m.mu.RUnlock() - out := make([]cozyv1alpha1.CozystackResourceDefinition, 0, len(m.data)) + out := make([]cozyv1alpha1.ApplicationDefinition, 0, len(m.data)) for _, v := range m.data { out = append(out, v) } @@ -72,7 +72,7 @@ func (m *Memory) EnsurePrimingWithManager(mgr ctrl.Manager) error { if ok := mgr.GetCache().WaitForCacheSync(ctx); !ok { return nil } - var list cozyv1alpha1.CozystackResourceDefinitionList + var list cozyv1alpha1.ApplicationDefinitionList if err := mgr.GetClient().List(ctx, &list); err == nil { for i := range list.Items { m.Upsert(&list.Items[i]) @@ -87,11 +87,11 @@ func (m *Memory) EnsurePrimingWithManager(mgr ctrl.Manager) error { return errOut } -func (m *Memory) ListFromCacheOrAPI(ctx context.Context, c client.Client) ([]cozyv1alpha1.CozystackResourceDefinition, error) { +func (m *Memory) ListFromCacheOrAPI(ctx context.Context, c client.Client) ([]cozyv1alpha1.ApplicationDefinition, error) { if m.IsPrimed() { return m.Snapshot(), nil } - var list cozyv1alpha1.CozystackResourceDefinitionList + var list cozyv1alpha1.ApplicationDefinitionList if err := c.List(ctx, &list); err != nil { return nil, err } diff --git a/packages/core/platform/images/migrations/migrations/23 b/packages/core/platform/images/migrations/migrations/23 new file mode 100755 index 00000000..4e8c8e18 --- /dev/null +++ b/packages/core/platform/images/migrations/migrations/23 @@ -0,0 +1,11 @@ +#!/bin/sh +# Migration 23 --> 24 + +set -euo pipefail + +# Remove old cozystack-resource-definition-crd HelmRelease (renamed to application-definition-crd) +kubectl delete hr -n cozy-system cozystack-resource-definition-crd --ignore-not-found + +# Stamp version +kubectl create configmap -n cozy-system cozystack-version \ + --from-literal=version=24 --dry-run=client -o yaml | kubectl apply -f- diff --git a/packages/core/platform/sources/cozystack-engine.yaml b/packages/core/platform/sources/cozystack-engine.yaml index 7d1b03c8..596c81c4 100644 --- a/packages/core/platform/sources/cozystack-engine.yaml +++ b/packages/core/platform/sources/cozystack-engine.yaml @@ -17,18 +17,18 @@ spec: - name: cozy-lib path: library/cozy-lib components: - - name: cozystack-resource-definition-crd - path: system/cozystack-resource-definition-crd + - name: application-definition-crd + path: system/application-definition-crd install: namespace: cozy-system - releaseName: cozystack-resource-definition-crd + releaseName: application-definition-crd - name: cozystack-controller path: system/cozystack-controller install: namespace: cozy-system releaseName: cozystack-controller dependsOn: - - cozystack-resource-definition-crd + - application-definition-crd - name: cozystack-api path: system/cozystack-api install: @@ -36,7 +36,7 @@ spec: releaseName: cozystack-api dependsOn: - cozystack-controller - - cozystack-resource-definition-crd + - application-definition-crd - name: lineage-controller-webhook path: system/lineage-controller-webhook install: @@ -44,7 +44,7 @@ spec: releaseName: lineage-controller-webhook dependsOn: - cozystack-controller - - cozystack-resource-definition-crd + - application-definition-crd - name: dashboard path: system/dashboard install: @@ -53,7 +53,7 @@ spec: dependsOn: - cozystack-api - cozystack-controller - - cozystack-resource-definition-crd + - application-definition-crd - name: oidc dependsOn: - cozystack.networking @@ -63,18 +63,18 @@ spec: - name: cozy-lib path: library/cozy-lib components: - - name: cozystack-resource-definition-crd - path: system/cozystack-resource-definition-crd + - name: application-definition-crd + path: system/application-definition-crd install: namespace: cozy-system - releaseName: cozystack-resource-definition-crd + releaseName: application-definition-crd - name: cozystack-controller path: system/cozystack-controller install: namespace: cozy-system releaseName: cozystack-controller dependsOn: - - cozystack-resource-definition-crd + - application-definition-crd - name: cozystack-api path: system/cozystack-api install: @@ -82,7 +82,7 @@ spec: releaseName: cozystack-api dependsOn: - cozystack-controller - - cozystack-resource-definition-crd + - application-definition-crd - name: lineage-controller-webhook path: system/lineage-controller-webhook install: @@ -90,7 +90,7 @@ spec: releaseName: lineage-controller-webhook dependsOn: - cozystack-controller - - cozystack-resource-definition-crd + - application-definition-crd - name: dashboard path: system/dashboard install: @@ -100,7 +100,7 @@ spec: - cozystack-api - cozystack-controller - keycloak-configure - - cozystack-resource-definition-crd + - application-definition-crd - name: keycloak-configure path: system/keycloak-configure install: diff --git a/packages/core/platform/values.yaml b/packages/core/platform/values.yaml index 70b8ff86..a358ef22 100644 --- a/packages/core/platform/values.yaml +++ b/packages/core/platform/values.yaml @@ -6,7 +6,7 @@ sourceRef: migrations: enabled: false image: ghcr.io/cozystack/cozystack/platform-migrations:latest - targetVersion: 23 + targetVersion: 24 # Bundle deployment configuration bundles: system: diff --git a/packages/system/cozystack-resource-definition-crd/Chart.yaml b/packages/system/application-definition-crd/Chart.yaml similarity index 74% rename from packages/system/cozystack-resource-definition-crd/Chart.yaml rename to packages/system/application-definition-crd/Chart.yaml index 9924c7fa..f272bf43 100644 --- a/packages/system/cozystack-resource-definition-crd/Chart.yaml +++ b/packages/system/application-definition-crd/Chart.yaml @@ -1,3 +1,3 @@ apiVersion: v2 -name: cozystack-resource-definition-crd +name: application-definition-crd version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process diff --git a/packages/system/cozystack-resource-definition-crd/Makefile b/packages/system/application-definition-crd/Makefile similarity index 57% rename from packages/system/cozystack-resource-definition-crd/Makefile rename to packages/system/application-definition-crd/Makefile index caac4557..adb378af 100644 --- a/packages/system/cozystack-resource-definition-crd/Makefile +++ b/packages/system/application-definition-crd/Makefile @@ -1,4 +1,4 @@ -export NAME=cozystack-resource-definition-crd +export NAME=application-definition-crd export NAMESPACE=cozy-system include ../../../hack/package.mk diff --git a/packages/system/cozystack-resource-definition-crd/definition/cozystack.io_cozystackresourcedefinitions.yaml b/packages/system/application-definition-crd/definition/cozystack.io_applicationdefinitions.yaml similarity index 97% rename from packages/system/cozystack-resource-definition-crd/definition/cozystack.io_cozystackresourcedefinitions.yaml rename to packages/system/application-definition-crd/definition/cozystack.io_applicationdefinitions.yaml index 6c6c15f5..44d33865 100644 --- a/packages/system/cozystack-resource-definition-crd/definition/cozystack.io_cozystackresourcedefinitions.yaml +++ b/packages/system/application-definition-crd/definition/cozystack.io_applicationdefinitions.yaml @@ -4,20 +4,20 @@ kind: CustomResourceDefinition metadata: annotations: controller-gen.kubebuilder.io/version: v0.16.4 - name: cozystackresourcedefinitions.cozystack.io + name: applicationdefinitions.cozystack.io spec: group: cozystack.io names: - kind: CozystackResourceDefinition - listKind: CozystackResourceDefinitionList - plural: cozystackresourcedefinitions - singular: cozystackresourcedefinition + kind: ApplicationDefinition + listKind: ApplicationDefinitionList + plural: applicationdefinitions + singular: applicationdefinition scope: Cluster versions: - name: v1alpha1 schema: openAPIV3Schema: - description: CozystackResourceDefinition is the Schema for the cozystackresourcedefinitions + description: ApplicationDefinition is the Schema for the applicationdefinitions API properties: apiVersion: @@ -135,7 +135,7 @@ spec: If a resource matches the selector in any of the elements in the array, it is hidden from the user, regardless of the matches in the include array. items: - description: "CozystackResourceDefinitionResourceSelector extends + description: "ApplicationDefinitionResourceSelector extends metav1.LabelSelector with resourceNames support.\nA resource matches this selector only if it satisfies ALL criteria:\n- Label selector conditions (matchExpressions and matchLabels)\n- @@ -210,7 +210,7 @@ spec: matches none of the selectors in the exclude array that resource is marked as a tenant resource and is visible to users. items: - description: "CozystackResourceDefinitionResourceSelector extends + description: "ApplicationDefinitionResourceSelector extends metav1.LabelSelector with resourceNames support.\nA resource matches this selector only if it satisfies ALL criteria:\n- Label selector conditions (matchExpressions and matchLabels)\n- @@ -332,7 +332,7 @@ spec: If a resource matches the selector in any of the elements in the array, it is hidden from the user, regardless of the matches in the include array. items: - description: "CozystackResourceDefinitionResourceSelector extends + description: "ApplicationDefinitionResourceSelector extends metav1.LabelSelector with resourceNames support.\nA resource matches this selector only if it satisfies ALL criteria:\n- Label selector conditions (matchExpressions and matchLabels)\n- @@ -407,7 +407,7 @@ spec: matches none of the selectors in the exclude array that resource is marked as a tenant resource and is visible to users. items: - description: "CozystackResourceDefinitionResourceSelector extends + description: "ApplicationDefinitionResourceSelector extends metav1.LabelSelector with resourceNames support.\nA resource matches this selector only if it satisfies ALL criteria:\n- Label selector conditions (matchExpressions and matchLabels)\n- @@ -485,7 +485,7 @@ spec: If a resource matches the selector in any of the elements in the array, it is hidden from the user, regardless of the matches in the include array. items: - description: "CozystackResourceDefinitionResourceSelector extends + description: "ApplicationDefinitionResourceSelector extends metav1.LabelSelector with resourceNames support.\nA resource matches this selector only if it satisfies ALL criteria:\n- Label selector conditions (matchExpressions and matchLabels)\n- @@ -560,7 +560,7 @@ spec: matches none of the selectors in the exclude array that resource is marked as a tenant resource and is visible to users. items: - description: "CozystackResourceDefinitionResourceSelector extends + description: "ApplicationDefinitionResourceSelector extends metav1.LabelSelector with resourceNames support.\nA resource matches this selector only if it satisfies ALL criteria:\n- Label selector conditions (matchExpressions and matchLabels)\n- diff --git a/packages/system/application-definition-crd/templates/crd.yaml b/packages/system/application-definition-crd/templates/crd.yaml new file mode 100644 index 00000000..3c94f229 --- /dev/null +++ b/packages/system/application-definition-crd/templates/crd.yaml @@ -0,0 +1,2 @@ +--- +{{ .Files.Get "definition/cozystack.io_applicationdefinitions.yaml" }} diff --git a/packages/system/cozystack-resource-definition-crd/values.yaml b/packages/system/application-definition-crd/values.yaml similarity index 100% rename from packages/system/cozystack-resource-definition-crd/values.yaml rename to packages/system/application-definition-crd/values.yaml diff --git a/packages/system/bootbox-rd/cozyrds/bootbox.yaml b/packages/system/bootbox-rd/cozyrds/bootbox.yaml index 7ed1f52f..5a1871bf 100644 --- a/packages/system/bootbox-rd/cozyrds/bootbox.yaml +++ b/packages/system/bootbox-rd/cozyrds/bootbox.yaml @@ -1,5 +1,5 @@ apiVersion: cozystack.io/v1alpha1 -kind: CozystackResourceDefinition +kind: ApplicationDefinition metadata: name: bootbox spec: diff --git a/packages/system/bucket-rd/cozyrds/bucket.yaml b/packages/system/bucket-rd/cozyrds/bucket.yaml index 8596bf5a..b0e06500 100644 --- a/packages/system/bucket-rd/cozyrds/bucket.yaml +++ b/packages/system/bucket-rd/cozyrds/bucket.yaml @@ -1,5 +1,5 @@ apiVersion: cozystack.io/v1alpha1 -kind: CozystackResourceDefinition +kind: ApplicationDefinition metadata: name: bucket spec: diff --git a/packages/system/clickhouse-rd/cozyrds/clickhouse.yaml b/packages/system/clickhouse-rd/cozyrds/clickhouse.yaml index de35d626..8f116af6 100644 --- a/packages/system/clickhouse-rd/cozyrds/clickhouse.yaml +++ b/packages/system/clickhouse-rd/cozyrds/clickhouse.yaml @@ -1,5 +1,5 @@ apiVersion: cozystack.io/v1alpha1 -kind: CozystackResourceDefinition +kind: ApplicationDefinition metadata: name: clickhouse spec: diff --git a/packages/system/cozystack-resource-definition-crd/templates/crd.yaml b/packages/system/cozystack-resource-definition-crd/templates/crd.yaml deleted file mode 100644 index 40a93ad3..00000000 --- a/packages/system/cozystack-resource-definition-crd/templates/crd.yaml +++ /dev/null @@ -1,2 +0,0 @@ ---- -{{ .Files.Get "definition/cozystack.io_cozystackresourcedefinitions.yaml" }} diff --git a/packages/system/etcd-rd/cozyrds/etcd.yaml b/packages/system/etcd-rd/cozyrds/etcd.yaml index e4483602..eecb4450 100644 --- a/packages/system/etcd-rd/cozyrds/etcd.yaml +++ b/packages/system/etcd-rd/cozyrds/etcd.yaml @@ -1,5 +1,5 @@ apiVersion: cozystack.io/v1alpha1 -kind: CozystackResourceDefinition +kind: ApplicationDefinition metadata: name: etcd spec: diff --git a/packages/system/ferretdb-rd/cozyrds/ferretdb.yaml b/packages/system/ferretdb-rd/cozyrds/ferretdb.yaml index a3236914..427cb0c5 100644 --- a/packages/system/ferretdb-rd/cozyrds/ferretdb.yaml +++ b/packages/system/ferretdb-rd/cozyrds/ferretdb.yaml @@ -1,5 +1,5 @@ apiVersion: cozystack.io/v1alpha1 -kind: CozystackResourceDefinition +kind: ApplicationDefinition metadata: name: ferretdb spec: diff --git a/packages/system/foundationdb-rd/cozyrds/foundationdb.yaml b/packages/system/foundationdb-rd/cozyrds/foundationdb.yaml index 5cc8ccd6..dcd2f9d9 100644 --- a/packages/system/foundationdb-rd/cozyrds/foundationdb.yaml +++ b/packages/system/foundationdb-rd/cozyrds/foundationdb.yaml @@ -1,5 +1,5 @@ apiVersion: cozystack.io/v1alpha1 -kind: CozystackResourceDefinition +kind: ApplicationDefinition metadata: name: foundationdb spec: diff --git a/packages/system/http-cache-rd/cozyrds/http-cache.yaml b/packages/system/http-cache-rd/cozyrds/http-cache.yaml index 991e933f..f379b1c7 100644 --- a/packages/system/http-cache-rd/cozyrds/http-cache.yaml +++ b/packages/system/http-cache-rd/cozyrds/http-cache.yaml @@ -1,5 +1,5 @@ apiVersion: cozystack.io/v1alpha1 -kind: CozystackResourceDefinition +kind: ApplicationDefinition metadata: name: http-cache spec: diff --git a/packages/system/info-rd/cozyrds/info.yaml b/packages/system/info-rd/cozyrds/info.yaml index 68ed9b32..ee76049a 100644 --- a/packages/system/info-rd/cozyrds/info.yaml +++ b/packages/system/info-rd/cozyrds/info.yaml @@ -1,5 +1,5 @@ apiVersion: cozystack.io/v1alpha1 -kind: CozystackResourceDefinition +kind: ApplicationDefinition metadata: name: info spec: diff --git a/packages/system/ingress-rd/cozyrds/ingress.yaml b/packages/system/ingress-rd/cozyrds/ingress.yaml index b547c28c..0812a12d 100644 --- a/packages/system/ingress-rd/cozyrds/ingress.yaml +++ b/packages/system/ingress-rd/cozyrds/ingress.yaml @@ -1,5 +1,5 @@ apiVersion: cozystack.io/v1alpha1 -kind: CozystackResourceDefinition +kind: ApplicationDefinition metadata: name: ingress spec: diff --git a/packages/system/kafka-rd/cozyrds/kafka.yaml b/packages/system/kafka-rd/cozyrds/kafka.yaml index 5e0a7da1..121e46a7 100644 --- a/packages/system/kafka-rd/cozyrds/kafka.yaml +++ b/packages/system/kafka-rd/cozyrds/kafka.yaml @@ -1,5 +1,5 @@ apiVersion: cozystack.io/v1alpha1 -kind: CozystackResourceDefinition +kind: ApplicationDefinition metadata: name: kafka spec: diff --git a/packages/system/kubernetes-rd/cozyrds/kubernetes.yaml b/packages/system/kubernetes-rd/cozyrds/kubernetes.yaml index c5516b91..93dfb33b 100644 --- a/packages/system/kubernetes-rd/cozyrds/kubernetes.yaml +++ b/packages/system/kubernetes-rd/cozyrds/kubernetes.yaml @@ -1,5 +1,5 @@ apiVersion: cozystack.io/v1alpha1 -kind: CozystackResourceDefinition +kind: ApplicationDefinition metadata: name: kubernetes spec: diff --git a/packages/system/monitoring-rd/cozyrds/monitoring.yaml b/packages/system/monitoring-rd/cozyrds/monitoring.yaml index e8a9a02b..f039dca2 100644 --- a/packages/system/monitoring-rd/cozyrds/monitoring.yaml +++ b/packages/system/monitoring-rd/cozyrds/monitoring.yaml @@ -1,5 +1,5 @@ apiVersion: cozystack.io/v1alpha1 -kind: CozystackResourceDefinition +kind: ApplicationDefinition metadata: name: monitoring spec: diff --git a/packages/system/mysql-rd/cozyrds/mysql.yaml b/packages/system/mysql-rd/cozyrds/mysql.yaml index bb9a170a..4e94111c 100644 --- a/packages/system/mysql-rd/cozyrds/mysql.yaml +++ b/packages/system/mysql-rd/cozyrds/mysql.yaml @@ -1,5 +1,5 @@ apiVersion: cozystack.io/v1alpha1 -kind: CozystackResourceDefinition +kind: ApplicationDefinition metadata: name: mysql spec: diff --git a/packages/system/nats-rd/cozyrds/nats.yaml b/packages/system/nats-rd/cozyrds/nats.yaml index c9b44601..2e825774 100644 --- a/packages/system/nats-rd/cozyrds/nats.yaml +++ b/packages/system/nats-rd/cozyrds/nats.yaml @@ -1,5 +1,5 @@ apiVersion: cozystack.io/v1alpha1 -kind: CozystackResourceDefinition +kind: ApplicationDefinition metadata: name: nats spec: diff --git a/packages/system/postgres-rd/cozyrds/postgres.yaml b/packages/system/postgres-rd/cozyrds/postgres.yaml index 05d18b51..2582d578 100644 --- a/packages/system/postgres-rd/cozyrds/postgres.yaml +++ b/packages/system/postgres-rd/cozyrds/postgres.yaml @@ -1,5 +1,5 @@ apiVersion: cozystack.io/v1alpha1 -kind: CozystackResourceDefinition +kind: ApplicationDefinition metadata: name: postgres spec: diff --git a/packages/system/rabbitmq-rd/cozyrds/rabbitmq.yaml b/packages/system/rabbitmq-rd/cozyrds/rabbitmq.yaml index 39c6962d..46f6d47e 100644 --- a/packages/system/rabbitmq-rd/cozyrds/rabbitmq.yaml +++ b/packages/system/rabbitmq-rd/cozyrds/rabbitmq.yaml @@ -1,5 +1,5 @@ apiVersion: cozystack.io/v1alpha1 -kind: CozystackResourceDefinition +kind: ApplicationDefinition metadata: name: rabbitmq spec: diff --git a/packages/system/redis-rd/cozyrds/redis.yaml b/packages/system/redis-rd/cozyrds/redis.yaml index 176743ff..174b16bb 100644 --- a/packages/system/redis-rd/cozyrds/redis.yaml +++ b/packages/system/redis-rd/cozyrds/redis.yaml @@ -1,5 +1,5 @@ apiVersion: cozystack.io/v1alpha1 -kind: CozystackResourceDefinition +kind: ApplicationDefinition metadata: name: redis spec: diff --git a/packages/system/seaweedfs-rd/cozyrds/seaweedfs.yaml b/packages/system/seaweedfs-rd/cozyrds/seaweedfs.yaml index b83c7b23..176ce49b 100644 --- a/packages/system/seaweedfs-rd/cozyrds/seaweedfs.yaml +++ b/packages/system/seaweedfs-rd/cozyrds/seaweedfs.yaml @@ -1,5 +1,5 @@ apiVersion: cozystack.io/v1alpha1 -kind: CozystackResourceDefinition +kind: ApplicationDefinition metadata: name: seaweedfs spec: diff --git a/packages/system/tcp-balancer-rd/cozyrds/tcp-balancer.yaml b/packages/system/tcp-balancer-rd/cozyrds/tcp-balancer.yaml index b7ce2e7a..17310d6d 100644 --- a/packages/system/tcp-balancer-rd/cozyrds/tcp-balancer.yaml +++ b/packages/system/tcp-balancer-rd/cozyrds/tcp-balancer.yaml @@ -1,5 +1,5 @@ apiVersion: cozystack.io/v1alpha1 -kind: CozystackResourceDefinition +kind: ApplicationDefinition metadata: name: tcp-balancer spec: diff --git a/packages/system/tenant-rd/cozyrds/tenant.yaml b/packages/system/tenant-rd/cozyrds/tenant.yaml index 08ed1336..bbdcedfe 100644 --- a/packages/system/tenant-rd/cozyrds/tenant.yaml +++ b/packages/system/tenant-rd/cozyrds/tenant.yaml @@ -1,5 +1,5 @@ apiVersion: cozystack.io/v1alpha1 -kind: CozystackResourceDefinition +kind: ApplicationDefinition metadata: name: tenant spec: diff --git a/packages/system/virtual-machine-rd/cozyrds/virtual-machine.yaml b/packages/system/virtual-machine-rd/cozyrds/virtual-machine.yaml index 30b2abf2..c89f2c7a 100644 --- a/packages/system/virtual-machine-rd/cozyrds/virtual-machine.yaml +++ b/packages/system/virtual-machine-rd/cozyrds/virtual-machine.yaml @@ -1,5 +1,5 @@ apiVersion: cozystack.io/v1alpha1 -kind: CozystackResourceDefinition +kind: ApplicationDefinition metadata: name: virtual-machine spec: diff --git a/packages/system/virtualprivatecloud-rd/cozyrds/virtualprivatecloud.yaml b/packages/system/virtualprivatecloud-rd/cozyrds/virtualprivatecloud.yaml index a39bf7a2..1ab67253 100644 --- a/packages/system/virtualprivatecloud-rd/cozyrds/virtualprivatecloud.yaml +++ b/packages/system/virtualprivatecloud-rd/cozyrds/virtualprivatecloud.yaml @@ -1,5 +1,5 @@ apiVersion: cozystack.io/v1alpha1 -kind: CozystackResourceDefinition +kind: ApplicationDefinition metadata: name: virtualprivatecloud spec: diff --git a/packages/system/vm-disk-rd/cozyrds/vm-disk.yaml b/packages/system/vm-disk-rd/cozyrds/vm-disk.yaml index 5760b8fc..d8c1f689 100644 --- a/packages/system/vm-disk-rd/cozyrds/vm-disk.yaml +++ b/packages/system/vm-disk-rd/cozyrds/vm-disk.yaml @@ -1,5 +1,5 @@ apiVersion: cozystack.io/v1alpha1 -kind: CozystackResourceDefinition +kind: ApplicationDefinition metadata: name: vm-disk spec: diff --git a/packages/system/vm-instance-rd/cozyrds/vm-instance.yaml b/packages/system/vm-instance-rd/cozyrds/vm-instance.yaml index aa89adbc..cd18a3b3 100644 --- a/packages/system/vm-instance-rd/cozyrds/vm-instance.yaml +++ b/packages/system/vm-instance-rd/cozyrds/vm-instance.yaml @@ -1,5 +1,5 @@ apiVersion: cozystack.io/v1alpha1 -kind: CozystackResourceDefinition +kind: ApplicationDefinition metadata: name: vm-instance spec: diff --git a/packages/system/vpn-rd/cozyrds/vpn.yaml b/packages/system/vpn-rd/cozyrds/vpn.yaml index 4f889c1e..a3bf6422 100644 --- a/packages/system/vpn-rd/cozyrds/vpn.yaml +++ b/packages/system/vpn-rd/cozyrds/vpn.yaml @@ -1,5 +1,5 @@ apiVersion: cozystack.io/v1alpha1 -kind: CozystackResourceDefinition +kind: ApplicationDefinition metadata: name: vpn spec: diff --git a/pkg/cmd/server/start.go b/pkg/cmd/server/start.go index 2826cfd5..6ff8d2d1 100644 --- a/pkg/cmd/server/start.go +++ b/pkg/cmd/server/start.go @@ -124,7 +124,7 @@ func (o *CozyServerOptions) Complete() error { return fmt.Errorf("client initialization failed: %w", err) } - crdList := &v1alpha1.CozystackResourceDefinitionList{} + crdList := &v1alpha1.ApplicationDefinitionList{} // Retry with exponential backoff for at least 30 minutes const maxRetryDuration = 30 * time.Minute @@ -142,11 +142,11 @@ func (o *CozyServerOptions) Complete() error { // Check if we've exceeded the maximum retry duration if time.Since(startTime) >= maxRetryDuration { - return fmt.Errorf("failed to list CozystackResourceDefinitions after %v: %w", maxRetryDuration, err) + return fmt.Errorf("failed to list ApplicationDefinitions after %v: %w", maxRetryDuration, err) } // Log the error and wait before retrying - fmt.Printf("Failed to list CozystackResourceDefinitions (retrying in %v): %v\n", delay, err) + fmt.Printf("Failed to list ApplicationDefinitions (retrying in %v): %v\n", delay, err) time.Sleep(delay) delay = time.Duration(float64(delay) * 1.5) From f004668622f434a940b510827930d976b8405708 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Fri, 16 Jan 2026 00:15:03 +0100 Subject: [PATCH 056/889] fix(talos): skip rebuilding assets if files already exist Add file existence checks to talos asset targets to prevent rebuilding kernel, initramfs, installer, iso, nocloud, and metal assets when they already exist from a previous build step. This fixes duplicate talos builds in the release pipeline where `make build` and `make assets` both triggered talos image generation. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- packages/core/talos/Makefile | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/core/talos/Makefile b/packages/core/talos/Makefile index 4e8a8dfb..e06793b9 100644 --- a/packages/core/talos/Makefile +++ b/packages/core/talos/Makefile @@ -30,9 +30,27 @@ image-matchbox: assets: talos-iso talos-nocloud talos-metal talos-kernel talos-initramfs -talos-initramfs talos-kernel talos-installer talos-iso talos-nocloud talos-metal: +talos-initramfs: + test -f ../../../_out/assets/initramfs-metal-amd64.xz || $(MAKE) build-talos-initramfs + +talos-kernel: + test -f ../../../_out/assets/kernel-amd64 || $(MAKE) build-talos-kernel + +talos-installer: + test -f ../../../_out/assets/installer-amd64.tar || $(MAKE) build-talos-installer + +talos-iso: + test -f ../../../_out/assets/metal-amd64.iso || $(MAKE) build-talos-iso + +talos-nocloud: + test -f ../../../_out/assets/nocloud-amd64.raw.xz || $(MAKE) build-talos-nocloud + +talos-metal: + test -f ../../../_out/assets/metal-amd64.raw.xz || $(MAKE) build-talos-metal + +build-talos-initramfs build-talos-kernel build-talos-installer build-talos-iso build-talos-nocloud build-talos-metal: mkdir -p ../../../_out/assets - cat images/talos/profiles/$(subst talos-,,$@).yaml | \ + cat images/talos/profiles/$(subst build-talos-,,$@).yaml | \ docker run --rm -i -v /dev:/dev --privileged "ghcr.io/siderolabs/imager:$(TALOS_VERSION)" --tar-to-stdout - | \ tar -C ../../../_out/assets -xzf- From 09290b5586ed417bc55e59ebe807a6fb10b7e886 Mon Sep 17 00:00:00 2001 From: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com> Date: Thu, 15 Jan 2026 23:32:41 +0000 Subject: [PATCH 057/889] Prepare release v1.0.0-alpha.1 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-cloud-provider.tag | 2 +- packages/apps/kubernetes/images/kubevirt-csi-driver.tag | 2 +- packages/apps/kubernetes/images/ubuntu-container-disk.tag | 2 +- packages/apps/mysql/images/mariadb-backup.tag | 2 +- packages/core/installer/values.yaml | 6 +++--- packages/core/testing/values.yaml | 2 +- packages/extra/bootbox/images/matchbox.tag | 2 +- packages/extra/monitoring/images/grafana.tag | 2 +- packages/extra/seaweedfs/images/objectstorage-sidecar.tag | 2 +- packages/system/backup-controller/values.yaml | 2 +- packages/system/backupstrategy-controller/values.yaml | 2 +- packages/system/bucket/images/s3manager.tag | 2 +- packages/system/cilium/values.yaml | 4 ++-- 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 +++--- .../system/grafana-operator/images/grafana-dashboards.tag | 2 +- packages/system/kamaji/values.yaml | 4 ++-- packages/system/kubeovn-plunger/values.yaml | 2 +- packages/system/kubeovn-webhook/values.yaml | 2 +- packages/system/kubevirt-csi-node/values.yaml | 2 +- packages/system/lineage-controller-webhook/values.yaml | 2 +- packages/system/linstor/values.yaml | 4 ++-- packages/system/metallb/values.yaml | 4 ++-- packages/system/objectstorage-controller/values.yaml | 2 +- packages/system/seaweedfs/values.yaml | 2 +- 28 files changed, 37 insertions(+), 37 deletions(-) diff --git a/packages/apps/http-cache/images/nginx-cache.tag b/packages/apps/http-cache/images/nginx-cache.tag index 185dcc66..3f0bbd33 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:31ebc09cfa11d8b438d2bbb32fa61b133aaf4b48b1a1282c9e59b5c127af61c1 diff --git a/packages/apps/kubernetes/images/kubevirt-cloud-provider.tag b/packages/apps/kubernetes/images/kubevirt-cloud-provider.tag index 8120dd48..ca661f3a 100644 --- a/packages/apps/kubernetes/images/kubevirt-cloud-provider.tag +++ b/packages/apps/kubernetes/images/kubevirt-cloud-provider.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/kubevirt-cloud-provider:0.0.0@sha256:5335c044313b69ee13b30ca4941687e509005e55f4ae25723861edbf2fbd6dd2 +ghcr.io/cozystack/cozystack/kubevirt-cloud-provider:0.0.0@sha256:dee69d15fa8616aa6a1e5a67fc76370e7698a7f58b25e30650eb39c9fb826de8 diff --git a/packages/apps/kubernetes/images/kubevirt-csi-driver.tag b/packages/apps/kubernetes/images/kubevirt-csi-driver.tag index 326da62f..ea6c26b9 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:0e5d8bbdc587a96e07bbf263f23d4d237dac8bd4528b34f44d31b042c6a0e495 +ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:15b94dca216b73336e7f39f4ea1b76b7656890d6be8a8cf0d9a786b4006781f9 diff --git a/packages/apps/kubernetes/images/ubuntu-container-disk.tag b/packages/apps/kubernetes/images/ubuntu-container-disk.tag index a35d1278..06de29fa 100644 --- a/packages/apps/kubernetes/images/ubuntu-container-disk.tag +++ b/packages/apps/kubernetes/images/ubuntu-container-disk.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/ubuntu-container-disk:v1.33@sha256:a09724a7f95283f9130b3da2a89d81c4c6051c6edf0392a81b6fc90f404b76b6 +ghcr.io/cozystack/cozystack/ubuntu-container-disk:v1.33@sha256:d25e567bc8b17b596e050f5ff410e36112c7966e33f4b372c752e7350bacc894 diff --git a/packages/apps/mysql/images/mariadb-backup.tag b/packages/apps/mysql/images/mariadb-backup.tag index af6247da..792c010b 100644 --- a/packages/apps/mysql/images/mariadb-backup.tag +++ b/packages/apps/mysql/images/mariadb-backup.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/mariadb-backup:0.0.0@sha256:1c0beb1b23a109b0e13727b4c73d2c74830e11cede92858ab20101b66f45a858 +ghcr.io/cozystack/cozystack/mariadb-backup:0.0.0@sha256:aca403030ff5d831415d72367866fdf291fab73ee2cfddbe4c93c2915a316ab1 diff --git a/packages/core/installer/values.yaml b/packages/core/installer/values.yaml index e38b4dfb..7639d999 100644 --- a/packages/core/installer/values.yaml +++ b/packages/core/installer/values.yaml @@ -1,5 +1,5 @@ cozystackOperator: - image: ghcr.io/cozystack/cozystack/cozystack-operator:latest@sha256:1e998a7cf2b9ed10b1320d9b3282e9362283fcda524f22fa0ad40579e7e464ff + image: ghcr.io/cozystack/cozystack/cozystack-operator:v1.0.0-alpha.1@sha256:d1beb9728b5be3ff6ee59eab071ac739cbb69d95f4bae173baa56323e97b0e5a platformSourceUrl: 'oci://ghcr.io/cozystack/cozystack/cozystack-packages' - platformSourceRef: 'digest=sha256:319e78e454cdc28e1d6edd701e4b09723bfb12a03c71b77271d136b09002ad7e' - cozystackVersion: latest + platformSourceRef: 'digest=sha256:ab03446b256c0faa9de0656854319f3b56e1252dab6f7b51bc6c95609d192556' + cozystackVersion: v1.0.0-alpha.1 diff --git a/packages/core/testing/values.yaml b/packages/core/testing/values.yaml index b00e9ea3..af105f30 100644 --- 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.40.2@sha256:78a556a059411cac89249c2e22596223128a1c74fbfcac4787fd74e4a66f66a6 + image: ghcr.io/cozystack/cozystack/e2e-sandbox:v1.0.0-alpha.1@sha256:78a556a059411cac89249c2e22596223128a1c74fbfcac4787fd74e4a66f66a6 diff --git a/packages/extra/bootbox/images/matchbox.tag b/packages/extra/bootbox/images/matchbox.tag index 522bb744..8ec9a817 100644 --- a/packages/extra/bootbox/images/matchbox.tag +++ b/packages/extra/bootbox/images/matchbox.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/matchbox:v0.40.2@sha256:f72d35fc691021d6b473360f2edaf0b1f176f737ca18f8cb9322d16c69705020 +ghcr.io/cozystack/cozystack/matchbox:v1.0.0-alpha.1@sha256:fa4b9a4d2e94648e5f17d9e46f8dfaddee423652676052b1976f5be998fa9cad diff --git a/packages/extra/monitoring/images/grafana.tag b/packages/extra/monitoring/images/grafana.tag index 9d53b4f1..06fa403d 100644 --- a/packages/extra/monitoring/images/grafana.tag +++ b/packages/extra/monitoring/images/grafana.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/grafana:0.0.0@sha256:c63978e1ed0304e8518b31ddee56c4e8115541b997d8efbe1c0a74da57140399 +ghcr.io/cozystack/cozystack/grafana:0.0.0@sha256:8ce0cd90c8f614cdabf5a41f8aa50b7dfbd02b31b9a0bd7897927e7f89968e07 diff --git a/packages/extra/seaweedfs/images/objectstorage-sidecar.tag b/packages/extra/seaweedfs/images/objectstorage-sidecar.tag index 90d0d249..cc735976 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.40.2@sha256:6735e6f050355355a13439ff815539a1b92eba09623e500e92dd7d4e600c59b4 +ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.0.0-alpha.1@sha256:d342ac197b97b81ec42712ecd9d762c6c7710a401736e9d09c64a5b8d59774cc diff --git a/packages/system/backup-controller/values.yaml b/packages/system/backup-controller/values.yaml index 557c201a..7d7d8740 100644 --- a/packages/system/backup-controller/values.yaml +++ b/packages/system/backup-controller/values.yaml @@ -1,5 +1,5 @@ backupController: - image: "" + image: "ghcr.io/cozystack/cozystack/backup-controller:v1.0.0-alpha.1@sha256:17502897a36a0723b2ca65e88b336e6d64d67d2ff578d6aa22165f2e0c718cc6" replicas: 2 debug: false metrics: diff --git a/packages/system/backupstrategy-controller/values.yaml b/packages/system/backupstrategy-controller/values.yaml index 6b04244a..752af01a 100644 --- a/packages/system/backupstrategy-controller/values.yaml +++ b/packages/system/backupstrategy-controller/values.yaml @@ -1,5 +1,5 @@ backupStrategyController: - image: "" + image: "ghcr.io/cozystack/cozystack/backupstrategy-controller:v1.0.0-alpha.1@sha256:d13aa9b1221484e6e70cb9d773b0d1cc0644eeaae2e78470cd5a89061dff9e05" replicas: 2 debug: false metrics: diff --git a/packages/system/bucket/images/s3manager.tag b/packages/system/bucket/images/s3manager.tag index b71ecf9e..88b26eac 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:07f016720c4692fc270a415a327a88773b90d2821922098496ad1269beac6c94 +ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:79463ccddbbd2a4048863944a858b2aced3f6e1c2d77f7ef4fa57e30624f6e35 diff --git a/packages/system/cilium/values.yaml b/packages/system/cilium/values.yaml index 95159b1f..1c759ac6 100644 --- a/packages/system/cilium/values.yaml +++ b/packages/system/cilium/values.yaml @@ -15,8 +15,8 @@ cilium: mode: "kubernetes" image: repository: ghcr.io/cozystack/cozystack/cilium - tag: 1.17.8 - digest: "sha256:81262986a41487bfa3d0465091d3a386def5bd1ab476350bd4af2fdee5846fe6" + tag: 1.18.5 + digest: "sha256:c14a0bcb1a1531c72725b3a4c40aefa2bcd5c129810bf58ea8e37d3fcff2a326" envoy: enabled: false rollOutCiliumPods: true diff --git a/packages/system/cozystack-api/values.yaml b/packages/system/cozystack-api/values.yaml index 04e86c7c..40b8ca78 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.40.2@sha256:408885b509d80ca30a85416f87d07112bc7f070374e71e80b64818fbb24ad1ee + image: ghcr.io/cozystack/cozystack/cozystack-api:v1.0.0-alpha.1@sha256:ad2d688c29b15d6cc77c526e9dd57bd4b2887a79ac1fb3fea636e69bc11d3b28 localK8sAPIEndpoint: enabled: true replicas: 2 diff --git a/packages/system/cozystack-controller/values.yaml b/packages/system/cozystack-controller/values.yaml index de8eedd5..7d0826a5 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.40.2@sha256:1878af4389ee3ddc85f2079cc250bccc14b923fb74c665c9aa902a659a394ba6 + image: ghcr.io/cozystack/cozystack/cozystack-controller:v1.0.0-alpha.1@sha256:b9c3f3ca0a65a7ed43d690e805ec42e1a7b3091418b7082e60478a82a5c8ddc3 debug: false disableTelemetry: false - cozystackVersion: "v0.40.2" + cozystackVersion: "v1.0.0-alpha.1" cozystackAPIKind: "DaemonSet" diff --git a/packages/system/dashboard/templates/configmap.yaml b/packages/system/dashboard/templates/configmap.yaml index a929b0b1..dd739a5c 100644 --- a/packages/system/dashboard/templates/configmap.yaml +++ b/packages/system/dashboard/templates/configmap.yaml @@ -1,6 +1,6 @@ {{- $brandingConfig := .Values._cluster.branding | default dict }} -{{- $tenantText := "v0.40.2" }} +{{- $tenantText := "v1.0.0-alpha.1" }} {{- $footerText := "Cozystack" }} {{- $titleText := "Cozystack Dashboard" }} {{- $logoText := "" }} diff --git a/packages/system/dashboard/values.yaml b/packages/system/dashboard/values.yaml index 091aaa4d..ec896d8b 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.40.2@sha256:a8d3770107c0279add903e6b5acdc5ca8a15551fb07514e40772f8010c5386ef + image: ghcr.io/cozystack/cozystack/openapi-ui:v1.0.0-alpha.1@sha256:7a3cf1a20dac3bf5f7ecad6cf2ebd5be034814ec6f8ca9ea5b467596481ae3ec openapiUIK8sBff: - image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v0.40.2@sha256:3071dfc130dbaf185b1a46ed42f0800824e77c2f4e64c5ed87055311f3c5f2b1 + image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v1.0.0-alpha.1@sha256:6e16fb9bad7ec5f05fdfe5be83f21846b6cd0cedd4a692319b0106257683330b tokenProxy: - image: ghcr.io/cozystack/cozystack/token-proxy:v0.40.2@sha256:4fc8a11f8a1a81aa0774ae2b1ed2e05d36d0b3ef1e37979cc4994e65114d93ae + image: ghcr.io/cozystack/cozystack/token-proxy:v1.0.0-alpha.1@sha256:73887f80d96e7e3c16f1cebab521b05b4308bf4662ccc6724e6a8a9745ed8254 diff --git a/packages/system/grafana-operator/images/grafana-dashboards.tag b/packages/system/grafana-operator/images/grafana-dashboards.tag index bd0f3b14..41aeb19c 100644 --- a/packages/system/grafana-operator/images/grafana-dashboards.tag +++ b/packages/system/grafana-operator/images/grafana-dashboards.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/grafana-dashboards:latest +ghcr.io/cozystack/cozystack/grafana-dashboards:v1.0.0-alpha.1@sha256:e320863f0b04a71b0eccd5a4608aad34a882c79c66f3b4c2c4d1089f9ee3bb25 diff --git a/packages/system/kamaji/values.yaml b/packages/system/kamaji/values.yaml index 4aea9c22..24d5ed70 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.40.2@sha256:cf9aae159deb586b1af5e5f6299247fe82f01646d58102bf5ca264c40f9edda2 + tag: v1.0.0-alpha.1@sha256:777f1df253dc97da7f42019857e7a893c34bd98da1cf36e38533adbdf601acf7 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.40.2@sha256:cf9aae159deb586b1af5e5f6299247fe82f01646d58102bf5ca264c40f9edda2 + - --migrate-image=ghcr.io/cozystack/cozystack/kamaji:v1.0.0-alpha.1@sha256:777f1df253dc97da7f42019857e7a893c34bd98da1cf36e38533adbdf601acf7 diff --git a/packages/system/kubeovn-plunger/values.yaml b/packages/system/kubeovn-plunger/values.yaml index 4229b116..c3917ffb 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.40.2@sha256:fdcc081d3c28a023a6950deac4d6124a0290d1bb4bd13c8c723569ac40d6874e +image: ghcr.io/cozystack/cozystack/kubeovn-plunger:v1.0.0-alpha.1@sha256:dc5fc9d7a54b2608e088b24c8bef03693e450e8952cf0476266a1b68eeb7e455 ovnCentralName: ovn-central diff --git a/packages/system/kubeovn-webhook/values.yaml b/packages/system/kubeovn-webhook/values.yaml index 6ec8f26e..34a48ef1 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.40.2@sha256:e6334c29d3aaf0dea766c88e3e05b53ad623d1bb497b3c836e6f76adade45b29 +image: ghcr.io/cozystack/cozystack/kubeovn-webhook:v1.0.0-alpha.1@sha256:e18f9fd679e38f65362a8d0042f25468272f6d081136ad47027168d8e7e07a4a diff --git a/packages/system/kubevirt-csi-node/values.yaml b/packages/system/kubevirt-csi-node/values.yaml index d3fac521..5644e6d8 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:0e5d8bbdc587a96e07bbf263f23d4d237dac8bd4528b34f44d31b042c6a0e495 + image: ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:15b94dca216b73336e7f39f4ea1b76b7656890d6be8a8cf0d9a786b4006781f9 diff --git a/packages/system/lineage-controller-webhook/values.yaml b/packages/system/lineage-controller-webhook/values.yaml index 554d9ef2..41d00955 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.40.2@sha256:16a700334d4d9cebea2516874c04d0799a450052f3652a0c91668c37d1d0e227 + image: ghcr.io/cozystack/cozystack/lineage-controller-webhook:v1.0.0-alpha.1@sha256:887c264efecb31cb7ca38034ecf325cf461e2302d703422364d8b20e4f18c56b debug: false localK8sAPIEndpoint: enabled: true diff --git a/packages/system/linstor/values.yaml b/packages/system/linstor/values.yaml index da62e9da..684167cb 100644 --- a/packages/system/linstor/values.yaml +++ b/packages/system/linstor/values.yaml @@ -1,7 +1,7 @@ piraeusServer: image: repository: ghcr.io/cozystack/cozystack/piraeus-server - tag: 1.32.3@sha256:1138c8dc0a117360ef70e2e2ab97bc2696419b63f46358f7668c7e01a96c419b + tag: 1.32.3@sha256:0e78fa31a3fe4ec2af43d1e59a9fc0f6d765780e32d473e18e1c495714051802 linstor: autoDiskful: enabled: true @@ -10,4 +10,4 @@ linstor: linstorCSI: image: repository: ghcr.io/cozystack/cozystack/linstor-csi - tag: v1.10.5@sha256:276e35e644cc4f1a17bc3842ded84aef4d9a5d750b5bdf3fc7238e369e88ada8 + tag: v1.10.5@sha256:68465f120cfeec3d7ccbb389dd9bdbf7df1675da3ab9ba91c3feff21a799bc36 diff --git a/packages/system/metallb/values.yaml b/packages/system/metallb/values.yaml index d392f4c4..dd5f76e6 100644 --- a/packages/system/metallb/values.yaml +++ b/packages/system/metallb/values.yaml @@ -4,8 +4,8 @@ metallb: controller: image: repository: ghcr.io/cozystack/cozystack/metallb-controller - tag: v0.15.2@sha256:0e9080234fc8eedab78ad2831fb38df375c383e901a752d72b353c8d13b9605f + tag: v0.15.2@sha256:623ce74b5802bff6e29f29478ccab29ce4162a64148be006c69e16cc3207e289 speaker: image: repository: ghcr.io/cozystack/cozystack/metallb-speaker - tag: v0.15.2@sha256:e14d4c328c3ab91a6eadfeea90da96388503492d165e7e8582f291b1872e53b2 + tag: v0.15.2@sha256:f264058afd9228452a260ab9c9dd1859404745627a2a38c2ba4671e27f3b3bb2 diff --git a/packages/system/objectstorage-controller/values.yaml b/packages/system/objectstorage-controller/values.yaml index 041c3ad2..1d56ae5d 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.40.2@sha256:f44845c034643c3b9040b70dc417e633d8ab0cc56a304a88dace733f2b4f1d70" + image: "ghcr.io/cozystack/cozystack/objectstorage-controller:v1.0.0-alpha.1@sha256:76756860145d49abe1585c8ca600267a07fcdbb0b359bc9c127baf9efb8e006b" diff --git a/packages/system/seaweedfs/values.yaml b/packages/system/seaweedfs/values.yaml index fd8f7ff0..941755aa 100644 --- a/packages/system/seaweedfs/values.yaml +++ b/packages/system/seaweedfs/values.yaml @@ -177,7 +177,7 @@ seaweedfs: bucketClassName: "seaweedfs" region: "" sidecar: - image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.40.2@sha256:6735e6f050355355a13439ff815539a1b92eba09623e500e92dd7d4e600c59b4" + image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.0.0-alpha.1@sha256:d342ac197b97b81ec42712ecd9d762c6c7710a401736e9d09c64a5b8d59774cc" certificates: commonName: "SeaweedFS CA" ipAddresses: [] From cc70dabe8518878a0efe87ac13be847a9e57ef63 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Fri, 16 Jan 2026 15:39:01 +0100 Subject: [PATCH 058/889] [docs] Generate changelog for v1.0.0-alpha.1 Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- docs/changelogs/v1.0.0-alpha.1.md | 109 ++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 docs/changelogs/v1.0.0-alpha.1.md diff --git a/docs/changelogs/v1.0.0-alpha.1.md b/docs/changelogs/v1.0.0-alpha.1.md new file mode 100644 index 00000000..5ca60dd9 --- /dev/null +++ b/docs/changelogs/v1.0.0-alpha.1.md @@ -0,0 +1,109 @@ +# Cozystack v1.0.0-alpha.1 — "Package-Based Architecture" + +This alpha release introduces a fundamental architectural shift from HelmRelease bundles to Package-based deployment managed by the cozystack-operator. It includes significant API changes that rename the core CRD, Flux sharding for improved tenant workload distribution, enhanced monitoring capabilities, and various improvements to virtual machines, tenants, and the build workflow. + +> **⚠️ Alpha Release Warning**: This is a pre-release version intended for testing and early adoption. Breaking changes may occur before the stable v1.0.0 release. + +## Breaking Changes + +### API Rename: CozystackResourceDefinition → ApplicationDefinition + +The `CozystackResourceDefinition` CRD has been renamed to `ApplicationDefinition` for better clarity and consistency. This change affects: +- All Go types and controller files +- CRD Helm chart renamed from `cozystack-resource-definition-crd` to `application-definition-crd` +- All cozyrds YAML manifests updated to use `kind: ApplicationDefinition` + +A migration (v24) is included to handle the transition automatically. + +### Package-Based Deployment + +The platform now uses Package resources managed by cozystack-operator instead of HelmRelease bundles. Key changes: +- Restructured values.yaml with full configuration support (networking, publishing, authentication, scheduling, branding, resources) +- Added values-isp-full.yaml and values-isp-hosted.yaml for bundle variants +- Package resources replace old HelmRelease templates +- PackageSources moved from sources/ to templates/sources/ +- Migration script `hack/migrate-to-version-1.0.sh` provided for converting ConfigMaps to Package resources + +--- + +## Major Features and Improvements + +### Platform Architecture + +* **[platform] Migrate from HelmRelease bundles to Package-based deployment**: Replaced HelmRelease bundle system with Package resources managed by cozystack-operator. Includes restructured values.yaml with full configuration support and migration tooling ([**@kvaps**](https://github.com/kvaps) in #1816). +* **refactor(api): rename CozystackResourceDefinition to ApplicationDefinition**: Renamed CRD and all related types for better clarity and consistency. Updated all Go types, controllers, and 25+ YAML manifests ([**@kvaps**](https://github.com/kvaps) in #1864). +* **feat(flux): implement flux sharding for tenant HelmReleases**: Added Flux sharding support to distribute tenant HelmRelease reconciliation across multiple controllers, improving scalability in multi-tenant environments ([**@kvaps**](https://github.com/kvaps) in #1816). +* **refactor(installer): migrate installer to cozystack-operator**: Moved installer functionality to cozystack-operator for unified management ([**@kvaps**](https://github.com/kvaps) in #1816). +* **feat(api): add chartRef to ApplicationDefinition**: Added chartRef field to support ExternalArtifact references for flexible chart sourcing ([**@kvaps**](https://github.com/kvaps) in #1816). +* **feat(api): show only hash in version column for applications and modules**: Simplified version display in API responses for cleaner output ([**@kvaps**](https://github.com/kvaps) in #1816). + +### Virtual Machines + +* **[vm] Always expose VMs with a service**: Virtual machines are now always exposed with at least a ClusterIP service, ensuring they have in-cluster DNS names and can be accessed from other pods even without public IP addresses ([**@lllamnyp**](https://github.com/lllamnyp) in #1738, #1751). + +### Monitoring + +* **[monitoring] Add SLACK_SEVERITY_FILTER field and VMAgent for tenant monitoring**: Introduced 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, added a VMAgent resource template for scraping metrics within tenant namespaces, improving monitoring granularity and control ([**@IvanHunters**](https://github.com/IvanHunters) in #1712). + +### Tenants + +* **[tenant] Allow egress to parent ingress pods**: Updated tenant network policies to allow egress traffic to parent cluster ingress pods, enabling proper communication patterns between tenant namespaces and parent cluster ingress controllers ([**@lexfrei**](https://github.com/lexfrei) in #1765, #1776). +* **[tenant] Run cleanup job from system namespace**: Moved tenant cleanup job to run from system namespace, improving security and resource isolation for tenant cleanup operations ([**@lllamnyp**](https://github.com/lllamnyp) in #1774, #1777). + +### System + +* **[system] Add resource requests and limits to etcd-defrag**: Added resource requests and limits to etcd-defrag job to ensure proper resource allocation and prevent resource contention during etcd maintenance operations ([**@matthieu-robin**](https://github.com/matthieu-robin) in #1785, #1786). + +### Development and Build + +* **feat(cozypkg): add cross-platform build targets with version injection**: Added cross-platform build targets (linux/amd64, linux/arm64, darwin/amd64, darwin/arm64) for cozypkg/cozyhr tool with automatic version injection from git tags ([**@kvaps**](https://github.com/kvaps) in #1862). +* **refactor: move scripts to hack directory**: Reorganized scripts to standard hack/ location following Kubernetes project conventions ([**@kvaps**](https://github.com/kvaps) in #1863). + +## Fixes + +* **fix(talos): skip rebuilding assets if files already exist**: Improved Talos package build process to avoid redundant asset rebuilds when files are already present, reducing build time ([**@kvaps**](https://github.com/kvaps)). +* **[kubevirt-operator] Fix typo in VMNotRunningFor10Minutes alert**: Fixed typo in VM alert name, ensuring proper alert triggering and monitoring for virtual machines that are not running for extended periods ([**@lexfrei**](https://github.com/lexfrei) in #1770, #1775). + +## Documentation + +* **[website] docs(storage): simplify NFS driver setup instructions**: Simplified NFS driver setup documentation with clearer instructions ([**@kvaps**](https://github.com/kvaps) in [cozystack/website#399](https://github.com/cozystack/website/pull/399)). +* **[website] Add Hidora organization support details**: Added Hidora to the support page with organization details ([**@matthieu-robin**](https://github.com/matthieu-robin) in [cozystack/website#397](https://github.com/cozystack/website/pull/397)). +* **[website] Update LinkedIn link for Hidora organization**: Updated LinkedIn link for Hidora organization on the support page ([**@matthieu-robin**](https://github.com/matthieu-robin) in [cozystack/website#398](https://github.com/cozystack/website/pull/398)). + +--- + +## Migration Guide + +### From v0.39.x / v0.40.x to v1.0.0-alpha.1 + +1. **Backup your cluster** before upgrading +2. Run the migration script: `hack/migrate-to-version-1.0.sh` +3. The migration will: + - Convert ConfigMaps to Package resources + - Rename CozystackResourceDefinition to ApplicationDefinition + - Update HelmRelease references to use Package-based deployment + +### Known Issues + +- This is an alpha release; some features may be incomplete or change before stable release +- Migration script should be tested in a non-production environment first + +--- + +## Contributors + +We'd like to thank all contributors who made this release possible: + +* [**@IvanHunters**](https://github.com/IvanHunters) +* [**@kvaps**](https://github.com/kvaps) +* [**@lexfrei**](https://github.com/lexfrei) +* [**@lllamnyp**](https://github.com/lllamnyp) +* [**@matthieu-robin**](https://github.com/matthieu-robin) + +--- + +**Full Changelog**: [v0.39.0...v1.0.0-alpha.1](https://github.com/cozystack/cozystack/compare/v0.39.0...v1.0.0-alpha.1) + + From 8e26028a132a2faef758d3f57bcd117832d5ff20 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Fri, 16 Jan 2026 15:51:11 +0100 Subject: [PATCH 059/889] [docs] Update changelog for v1.0.0-alpha.1: add cozystack-operator and backup system Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- docs/changelogs/v1.0.0-alpha.1.md | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/docs/changelogs/v1.0.0-alpha.1.md b/docs/changelogs/v1.0.0-alpha.1.md index 5ca60dd9..4411b35f 100644 --- a/docs/changelogs/v1.0.0-alpha.1.md +++ b/docs/changelogs/v1.0.0-alpha.1.md @@ -1,6 +1,6 @@ # Cozystack v1.0.0-alpha.1 — "Package-Based Architecture" -This alpha release introduces a fundamental architectural shift from HelmRelease bundles to Package-based deployment managed by the cozystack-operator. It includes significant API changes that rename the core CRD, Flux sharding for improved tenant workload distribution, enhanced monitoring capabilities, and various improvements to virtual machines, tenants, and the build workflow. +This alpha release introduces a fundamental architectural shift from HelmRelease bundles to Package-based deployment managed by the new cozystack-operator. It includes a comprehensive backup system with Velero integration, significant API changes that rename the core CRD, Flux sharding for improved tenant workload distribution, enhanced monitoring capabilities, and various improvements to virtual machines, tenants, and the build workflow. > **⚠️ Alpha Release Warning**: This is a pre-release version intended for testing and early adoption. Breaking changes may occur before the stable v1.0.0 release. @@ -28,6 +28,29 @@ The platform now uses Package resources managed by cozystack-operator instead of ## Major Features and Improvements +### Cozystack Operator + +A new operator has been introduced to manage Package and PackageSource resources, providing declarative package management for the platform: + +* **[cozystack-operator] Introduce API objects: packages and packagesources**: Added new CRDs for declarative package management, defining the API for Package and PackageSource resources ([**@kvaps**](https://github.com/kvaps) in #1740). +* **[cozystack-operator] Introduce Cozystack-operator core logic**: Implemented core reconciliation logic for the operator, handling Package and PackageSource lifecycle management ([**@kvaps**](https://github.com/kvaps) in #1741). +* **[cozystack-operator] Add Package and PackageSource reconcilers**: Added controllers for Package and PackageSource resources with full reconciliation support ([**@kvaps**](https://github.com/kvaps) in #1755). +* **[cozystack-operator] Add deployment files**: Added Kubernetes deployment manifests for running cozystack-operator in the cluster ([**@kvaps**](https://github.com/kvaps) in #1761). +* **[platform] Add PackageSources for cozystack-operator**: Added PackageSource definitions for cozystack-operator integration ([**@kvaps**](https://github.com/kvaps) in #1760). +* **[cozypkg] Add tool for managing Package and PackageSources**: Added CLI tool for managing Package and PackageSource resources ([**@kvaps**](https://github.com/kvaps) in #1756). + +### Backup System + +Comprehensive backup functionality has been added with Velero integration for managing application backups: + +* **[backups] Implement core backup Plan controller**: Core controller for managing backup schedules and plans, providing the foundation for backup orchestration ([**@lllamnyp**](https://github.com/lllamnyp) in #1640). +* **[backups] Build and deploy backup controller**: Deployment infrastructure for the backup controller, including container image builds and Kubernetes manifests ([**@lllamnyp**](https://github.com/lllamnyp) in #1685). +* **[backups] Scaffold a backup strategy API group**: Added API group for backup strategies, enabling pluggable backup implementations ([**@lllamnyp**](https://github.com/lllamnyp) in #1687). +* **[backups] Add indices to core backup resources**: Added indices to backup resources for improved query performance ([**@lllamnyp**](https://github.com/lllamnyp) in #1719). +* **[backups] Stub the Job backup strategy controller**: Added stub implementation for Job-based backup strategy ([**@lllamnyp**](https://github.com/lllamnyp) in #1720). +* **[backups] Implement Velero strategy controller**: Integration with Velero for backup operations, enabling enterprise-grade backup capabilities ([**@androndo**](https://github.com/androndo) in #1762). +* **[backups,dashboard] User-facing UI**: Dashboard interface for managing backups and backup jobs, providing visibility into backup status and history ([**@lllamnyp**](https://github.com/lllamnyp) in #1737). + ### Platform Architecture * **[platform] Migrate from HelmRelease bundles to Package-based deployment**: Replaced HelmRelease bundle system with Package resources managed by cozystack-operator. Includes restructured values.yaml with full configuration support and migration tooling ([**@kvaps**](https://github.com/kvaps) in #1816). @@ -63,6 +86,7 @@ The platform now uses Package resources managed by cozystack-operator instead of * **fix(talos): skip rebuilding assets if files already exist**: Improved Talos package build process to avoid redundant asset rebuilds when files are already present, reducing build time ([**@kvaps**](https://github.com/kvaps)). * **[kubevirt-operator] Fix typo in VMNotRunningFor10Minutes alert**: Fixed typo in VM alert name, ensuring proper alert triggering and monitoring for virtual machines that are not running for extended periods ([**@lexfrei**](https://github.com/lexfrei) in #1770, #1775). +* **[backups] Fix malformed glob and split in template**: Fixed malformed glob pattern and split operation in backup template processing ([**@lllamnyp**](https://github.com/lllamnyp) in #1708). ## Documentation @@ -74,7 +98,7 @@ The platform now uses Package resources managed by cozystack-operator instead of ## Migration Guide -### From v0.39.x / v0.40.x to v1.0.0-alpha.1 +### From v0.38.x / v0.39.x to v1.0.0-alpha.1 1. **Backup your cluster** before upgrading 2. Run the migration script: `hack/migrate-to-version-1.0.sh` @@ -94,6 +118,7 @@ The platform now uses Package resources managed by cozystack-operator instead of We'd like to thank all contributors who made this release possible: +* [**@androndo**](https://github.com/androndo) * [**@IvanHunters**](https://github.com/IvanHunters) * [**@kvaps**](https://github.com/kvaps) * [**@lexfrei**](https://github.com/lexfrei) @@ -102,7 +127,7 @@ We'd like to thank all contributors who made this release possible: --- -**Full Changelog**: [v0.39.0...v1.0.0-alpha.1](https://github.com/cozystack/cozystack/compare/v0.39.0...v1.0.0-alpha.1) +**Full Changelog**: [v0.38.0...v1.0.0-alpha.1](https://github.com/cozystack/cozystack/compare/v0.38.0...v1.0.0-alpha.1) + +## Improvements + +* **[kubernetes] Increase default apiServer resourcesPreset to large**: Increased the default resource preset for kube-apiserver to `large` to ensure more reliable operation under higher workloads and prevent resource constraints ([**@kvaps**](https://github.com/kvaps) in #1875, #1882). + +* **[kubernetes] Increase kube-apiserver startup probe threshold**: Increased the startup probe threshold for kube-apiserver to allow more time for the API server to become ready, especially in scenarios with slow storage or high load ([**@kvaps**](https://github.com/kvaps) in #1876, #1883). + +* **[etcd] Increase probe thresholds for better recovery**: Increased etcd probe thresholds to provide more time for recovery operations, improving cluster resilience during network issues or temporary slowdowns ([**@kvaps**](https://github.com/kvaps) in #1874, #1878). + +## Fixes + +* **[dashboard] Fix view of loadbalancer IP in services window**: Fixed an issue where load balancer IP addresses were not displayed correctly in the services window of the dashboard ([**@IvanHunters**](https://github.com/IvanHunters) in #1884, #1887). + +## Dependencies + +* **Update Talos Linux v1.11.6**: Updated Talos Linux to v1.11.6 with latest security patches and improvements ([**@kvaps**](https://github.com/kvaps) in #1879). + +--- + +**Full Changelog**: [v0.40.3...v0.40.4](https://github.com/cozystack/cozystack/compare/v0.40.3...v0.40.4) diff --git a/docs/changelogs/v0.41.0.md b/docs/changelogs/v0.41.0.md new file mode 100644 index 00000000..a25dfe38 --- /dev/null +++ b/docs/changelogs/v0.41.0.md @@ -0,0 +1,33 @@ + + +## Improvements + +* **[linstor] Update piraeus-server patches with critical fixes**: Backported critical patches to piraeus-server that address storage stability issues and improve DRBD resource handling. These patches fix edge cases in device management and ensure more reliable storage operations ([**@kvaps**](https://github.com/kvaps) in #1850, #1852). + +* **[linstor] Refactor node-level RWX validation**: Refactored the node-level ReadWriteMany (RWX) validation logic in LINSTOR CSI. The validation has been moved to the CSI driver level with a custom linstor-csi image build, providing more reliable RWX volume handling and clearer error messages when RWX requirements cannot be satisfied ([**@kvaps**](https://github.com/kvaps) in #1856, #1857). + +* **[kubernetes] Increase default apiServer resourcesPreset to large**: Increased the default resource preset for kube-apiserver to `large` to ensure more reliable operation under higher workloads and prevent resource constraints ([**@kvaps**](https://github.com/kvaps) in #1875, #1882). + +* **[kubernetes] Increase kube-apiserver startup probe threshold**: Increased the startup probe threshold for kube-apiserver to allow more time for the API server to become ready, especially in scenarios with slow storage or high load ([**@kvaps**](https://github.com/kvaps) in #1876, #1883). + +* **[etcd] Increase probe thresholds for better recovery**: Increased etcd probe thresholds to provide more time for recovery operations, improving cluster resilience during network issues or temporary slowdowns ([**@kvaps**](https://github.com/kvaps) in #1874, #1878). + +## Fixes + +* **[linstor] Remove node-level RWX validation**: Removed the problematic node-level RWX validation that was causing issues with volume provisioning. The validation logic has been refactored and moved to a more appropriate location in the LINSTOR CSI driver ([**@kvaps**](https://github.com/kvaps) in #1851). + +* **[apiserver] Fix Watch resourceVersion and bookmark handling**: Fixed issues with Watch API handling of resourceVersion and bookmarks, ensuring proper event streaming and state synchronization for API clients ([**@kvaps**](https://github.com/kvaps) in #1860). + +* **[dashboard] Fix view of loadbalancer IP in services window**: Fixed an issue where load balancer IP addresses were not displayed correctly in the services window of the dashboard ([**@IvanHunters**](https://github.com/IvanHunters) in #1884, #1887). + +## Dependencies + +* **[cilium] Update cilium to v1.18.6**: Updated Cilium CNI to v1.18.6 with security fixes and performance improvements ([**@sircthulhu**](https://github.com/sircthulhu) in #1868, #1870). + +* **Update Talos Linux v1.11.6**: Updated Talos Linux to v1.11.6 with latest security patches and improvements ([**@kvaps**](https://github.com/kvaps) in #1879). + +--- + +**Full Changelog**: [v0.40.0...v0.41.0](https://github.com/cozystack/cozystack/compare/v0.40.0...v0.41.0) diff --git a/docs/changelogs/v1.0.0-alpha.2.md b/docs/changelogs/v1.0.0-alpha.2.md new file mode 100644 index 00000000..925d859f --- /dev/null +++ b/docs/changelogs/v1.0.0-alpha.2.md @@ -0,0 +1,58 @@ + + +> **⚠️ Alpha Release Warning**: This is a pre-release version intended for testing and early adoption. Breaking changes may occur before the stable v1.0.0 release. + +## Major Features and Improvements + +### New Applications + +* **[apps] Add MongoDB managed application**: Added MongoDB as a new managed application, providing a fully managed MongoDB database with automatic scaling, backups, and high availability support ([**@lexfrei**](https://github.com/lexfrei) in #1822). + +### Networking + +* **[kilo] Introduce kilo**: Added Kilo WireGuard mesh networking support. Kilo provides secure WireGuard-based VPN mesh for connecting Kubernetes nodes across different networks and regions ([**@kvaps**](https://github.com/kvaps) in #1691). + +* **[local-ccm] Add local-ccm package**: Added local cloud controller manager package for managing load balancer services in local/bare-metal environments without a cloud provider ([**@kvaps**](https://github.com/kvaps) in #1831). + +### Platform + +* **[platform] Add flux-plunger controller**: Added flux-plunger controller to automatically fix stuck HelmRelease errors by cleaning up failed resources and retrying reconciliation ([**@kvaps**](https://github.com/kvaps) in #1843). + +* **[platform] Split telemetry between operator and controller**: Separated telemetry collection between cozystack-operator and cozystack-controller for better metrics isolation and monitoring capabilities ([**@kvaps**](https://github.com/kvaps) in #1869). + +* **[platform] Remove cozystack.io/ui label**: Cleaned up deprecated `cozystack.io/ui` labels from platform components ([**@kvaps**](https://github.com/kvaps) in #1872). + +## Improvements + +* **[kubernetes] Increase default apiServer resourcesPreset to large**: Increased the default resource preset for kube-apiserver to `large` to ensure more reliable operation under higher workloads ([**@kvaps**](https://github.com/kvaps) in #1875). + +* **[kubernetes] Increase kube-apiserver startup probe threshold**: Increased the startup probe threshold for kube-apiserver to allow more time for the API server to become ready ([**@kvaps**](https://github.com/kvaps) in #1876). + +* **[etcd] Increase probe thresholds for better recovery**: Increased etcd probe thresholds to provide more time for recovery operations, improving cluster resilience ([**@kvaps**](https://github.com/kvaps) in #1874). + +## Fixes + +* **[apiserver] Fix Watch resourceVersion and bookmark handling**: Fixed issues with Watch API handling of resourceVersion and bookmarks, ensuring proper event streaming and state synchronization ([**@kvaps**](https://github.com/kvaps) in #1860). + +* **[dashboard] Fix view of loadbalancer IP in services window**: Fixed an issue where load balancer IP addresses were not displayed correctly in the services window of the dashboard ([**@IvanHunters**](https://github.com/IvanHunters) in #1884). + +## Dependencies + +* **[cilium] Update cilium to v1.18.6**: Updated Cilium CNI to v1.18.6 with security fixes and performance improvements ([**@sircthulhu**](https://github.com/sircthulhu) in #1868). + +* **Update Talos Linux v1.12.1**: Updated Talos Linux to v1.12.1 with latest features, security patches and improvements ([**@kvaps**](https://github.com/kvaps) in #1877). + +--- + +## Contributors + +* [**@IvanHunters**](https://github.com/IvanHunters) +* [**@kvaps**](https://github.com/kvaps) +* [**@lexfrei**](https://github.com/lexfrei) +* [**@sircthulhu**](https://github.com/sircthulhu) + +--- + +**Full Changelog**: [v1.0.0-alpha.1...v1.0.0-alpha.2](https://github.com/cozystack/cozystack/compare/v1.0.0-alpha.1...v1.0.0-alpha.2) From 0510ff1e2d3100c0a7aa17abcb6dd618fa176672 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 20 Jan 2026 03:56:27 +0100 Subject: [PATCH 077/889] [docs] Add MongoDB highlight and documentation to v0.41.0 and v1.0.0-alpha.2 changelogs Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- docs/changelogs/v0.41.0.md | 30 ++++++++++++++++++++++++++++++ docs/changelogs/v1.0.0-alpha.2.md | 13 +++++++++++++ 2 files changed, 43 insertions(+) diff --git a/docs/changelogs/v0.41.0.md b/docs/changelogs/v0.41.0.md index a25dfe38..3084e493 100644 --- a/docs/changelogs/v0.41.0.md +++ b/docs/changelogs/v0.41.0.md @@ -2,6 +2,24 @@ https://github.com/cozystack/cozystack/releases/tag/v0.41.0 --> +# Cozystack v0.41.0 — "MongoDB" + +This release introduces MongoDB as a new managed application, expanding Cozystack's database offerings alongside existing PostgreSQL, MySQL, and Redis services. The release also includes storage improvements, Kubernetes stability enhancements, and updated documentation. + +## Feature Highlights + +### MongoDB Managed Application + +Cozystack now includes MongoDB as a fully managed database service. Users can deploy production-ready MongoDB instances directly from the application catalog with minimal configuration. + +Key capabilities: +- **Replica Set deployment**: Automatic configuration of MongoDB replica sets for high availability +- **Persistent storage**: Integration with Cozystack storage backends for reliable data persistence +- **Resource management**: Configurable CPU, memory, and storage resources +- **Monitoring integration**: Built-in metrics export for platform monitoring + +Deploy MongoDB through the Cozystack dashboard or using the standard application deployment workflow ([**@lexfrei**](https://github.com/lexfrei) in #1822, #1881). + ## Improvements * **[linstor] Update piraeus-server patches with critical fixes**: Backported critical patches to piraeus-server that address storage stability issues and improve DRBD resource handling. These patches fix edge cases in device management and ensure more reliable storage operations ([**@kvaps**](https://github.com/kvaps) in #1850, #1852). @@ -28,6 +46,18 @@ https://github.com/cozystack/cozystack/releases/tag/v0.41.0 * **Update Talos Linux v1.11.6**: Updated Talos Linux to v1.11.6 with latest security patches and improvements ([**@kvaps**](https://github.com/kvaps) in #1879). +## Documentation + +* **[website] Add documentation for creating and managing cloned virtual machines**: Added comprehensive guide for VM cloning operations ([**@sircthulhu**](https://github.com/sircthulhu) in [cozystack/website#401](https://github.com/cozystack/website/pull/401)). + +* **[website] Simplify NFS driver setup instructions**: Improved NFS driver setup documentation with clearer instructions ([**@kvaps**](https://github.com/kvaps) in [cozystack/website#399](https://github.com/cozystack/website/pull/399)). + +* **[website] Update Talos installation docs for Hetzner and Servers.com**: Updated installation documentation with improved instructions for Hetzner and Servers.com environments ([**@kvaps**](https://github.com/kvaps) in [cozystack/website#395](https://github.com/cozystack/website/pull/395)). + +* **[website] Add Hetzner RobotLB documentation**: Added documentation for configuring public IP with Hetzner RobotLB ([**@kvaps**](https://github.com/kvaps) in [cozystack/website#394](https://github.com/cozystack/website/pull/394)). + +* **[website] Add Hidora organization support details**: Added Hidora to the support page with organization details ([**@matthieu-robin**](https://github.com/matthieu-robin) in [cozystack/website#397](https://github.com/cozystack/website/pull/397), [cozystack/website#398](https://github.com/cozystack/website/pull/398)). + --- **Full Changelog**: [v0.40.0...v0.41.0](https://github.com/cozystack/cozystack/compare/v0.40.0...v0.41.0) diff --git a/docs/changelogs/v1.0.0-alpha.2.md b/docs/changelogs/v1.0.0-alpha.2.md index 925d859f..14bd89a2 100644 --- a/docs/changelogs/v1.0.0-alpha.2.md +++ b/docs/changelogs/v1.0.0-alpha.2.md @@ -44,6 +44,18 @@ https://github.com/cozystack/cozystack/releases/tag/v1.0.0-alpha.2 * **Update Talos Linux v1.12.1**: Updated Talos Linux to v1.12.1 with latest features, security patches and improvements ([**@kvaps**](https://github.com/kvaps) in #1877). +## Documentation + +* **[website] Add documentation for creating and managing cloned virtual machines**: Added comprehensive guide for VM cloning operations ([**@sircthulhu**](https://github.com/sircthulhu) in [cozystack/website#401](https://github.com/cozystack/website/pull/401)). + +* **[website] Simplify NFS driver setup instructions**: Improved NFS driver setup documentation with clearer instructions ([**@kvaps**](https://github.com/kvaps) in [cozystack/website#399](https://github.com/cozystack/website/pull/399)). + +* **[website] Update Talos installation docs for Hetzner and Servers.com**: Updated installation documentation with improved instructions for Hetzner and Servers.com environments ([**@kvaps**](https://github.com/kvaps) in [cozystack/website#395](https://github.com/cozystack/website/pull/395)). + +* **[website] Add Hetzner RobotLB documentation**: Added documentation for configuring public IP with Hetzner RobotLB ([**@kvaps**](https://github.com/kvaps) in [cozystack/website#394](https://github.com/cozystack/website/pull/394)). + +* **[website] Add Hidora organization support details**: Added Hidora to the support page with organization details ([**@matthieu-robin**](https://github.com/matthieu-robin) in [cozystack/website#397](https://github.com/cozystack/website/pull/397), [cozystack/website#398](https://github.com/cozystack/website/pull/398)). + --- ## Contributors @@ -51,6 +63,7 @@ https://github.com/cozystack/cozystack/releases/tag/v1.0.0-alpha.2 * [**@IvanHunters**](https://github.com/IvanHunters) * [**@kvaps**](https://github.com/kvaps) * [**@lexfrei**](https://github.com/lexfrei) +* [**@matthieu-robin**](https://github.com/matthieu-robin) * [**@sircthulhu**](https://github.com/sircthulhu) --- From 0880bb107ebb8161222dcb075b4dbf7336242d8c Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 20 Jan 2026 08:21:43 +0100 Subject: [PATCH 078/889] Fix ApplicationDefinition for MongoDB Signed-off-by: Andrei Kvapil --- packages/system/mongodb-rd/cozyrds/mongodb.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/system/mongodb-rd/cozyrds/mongodb.yaml b/packages/system/mongodb-rd/cozyrds/mongodb.yaml index 2ad11696..996dde18 100644 --- a/packages/system/mongodb-rd/cozyrds/mongodb.yaml +++ b/packages/system/mongodb-rd/cozyrds/mongodb.yaml @@ -1,5 +1,5 @@ apiVersion: cozystack.io/v1alpha1 -kind: CozystackResourceDefinition +kind: ApplicationDefinition metadata: name: mongodb spec: From bb72dd885c8ac2662bf9ea35480457f55fdab564 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 20 Jan 2026 08:46:21 +0100 Subject: [PATCH 079/889] [kilo] rename joinCIDR to transitCIDR Signed-off-by: Andrei Kvapil --- packages/system/kilo/templates/kilo.yaml | 2 +- packages/system/kilo/values.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/system/kilo/templates/kilo.yaml b/packages/system/kilo/templates/kilo.yaml index 8fc362c0..298c0400 100644 --- a/packages/system/kilo/templates/kilo.yaml +++ b/packages/system/kilo/templates/kilo.yaml @@ -38,7 +38,7 @@ spec: {{- with .Values.kilo.serviceCIDR }} - --service-cidr={{ . }} {{- end }} - {{- with .Values.kilo.joinCIDR }} + {{- with .Values.kilo.transitCIDR }} - --subnet={{ . }} {{- end }} - --internal-cidr=$(NODE_IP)/32 diff --git a/packages/system/kilo/values.yaml b/packages/system/kilo/values.yaml index 9fde46a0..d2ef28ab 100644 --- a/packages/system/kilo/values.yaml +++ b/packages/system/kilo/values.yaml @@ -5,6 +5,6 @@ kilo: repository: 999669/kilo podCIDR: 10.244.0.0/16 serviceCIDR: 10.96.0.0/16 - joinCIDR: 100.64.0.0/16 + transitCIDR: 100.66.0.0/16 meshGranularity: location cleanUpInterface: false From 4ffe4533518f2fc7c3d7d4ed9b578c119369f925 Mon Sep 17 00:00:00 2001 From: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com> Date: Tue, 20 Jan 2026 07:50:59 +0000 Subject: [PATCH 080/889] Prepare release v1.0.0-alpha.2 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/apps/kubernetes/images/ubuntu-container-disk.tag | 2 +- packages/core/installer/values.yaml | 4 ++-- packages/core/testing/values.yaml | 2 +- packages/extra/bootbox/images/matchbox.tag | 2 +- packages/extra/seaweedfs/images/objectstorage-sidecar.tag | 2 +- packages/system/backup-controller/values.yaml | 2 +- packages/system/backupstrategy-controller/values.yaml | 2 +- packages/system/bucket/images/s3manager.tag | 2 +- packages/system/cilium/values.yaml | 6 +++--- packages/system/cozystack-api/values.yaml | 2 +- packages/system/cozystack-controller/values.yaml | 2 +- packages/system/dashboard/templates/configmap.yaml | 2 +- packages/system/dashboard/values.yaml | 6 +++--- .../system/grafana-operator/images/grafana-dashboards.tag | 2 +- packages/system/kamaji/values.yaml | 4 ++-- packages/system/kilo/values.yaml | 4 ++-- packages/system/kubeovn-plunger/values.yaml | 2 +- packages/system/kubeovn-webhook/values.yaml | 2 +- packages/system/kubevirt-csi-node/values.yaml | 2 +- packages/system/lineage-controller-webhook/values.yaml | 2 +- packages/system/linstor/values.yaml | 2 +- packages/system/objectstorage-controller/values.yaml | 2 +- packages/system/seaweedfs/values.yaml | 2 +- 25 files changed, 32 insertions(+), 32 deletions(-) diff --git a/packages/apps/http-cache/images/nginx-cache.tag b/packages/apps/http-cache/images/nginx-cache.tag index 3f0bbd33..ee4d8890 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:31ebc09cfa11d8b438d2bbb32fa61b133aaf4b48b1a1282c9e59b5c127af61c1 +ghcr.io/cozystack/cozystack/nginx-cache:0.0.0@sha256:9e34fd50393b418d9516aadb488067a3a63675b045811beb1c0afc9c61e149e8 diff --git a/packages/apps/kubernetes/images/kubevirt-csi-driver.tag b/packages/apps/kubernetes/images/kubevirt-csi-driver.tag index ea6c26b9..420fbc66 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:15b94dca216b73336e7f39f4ea1b76b7656890d6be8a8cf0d9a786b4006781f9 +ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:31aee6f63c25a443974b56011d2907cedfeba245aa7caf732d7fc437b2b3ed50 diff --git a/packages/apps/kubernetes/images/ubuntu-container-disk.tag b/packages/apps/kubernetes/images/ubuntu-container-disk.tag index 06de29fa..930a3fd2 100644 --- a/packages/apps/kubernetes/images/ubuntu-container-disk.tag +++ b/packages/apps/kubernetes/images/ubuntu-container-disk.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/ubuntu-container-disk:v1.33@sha256:d25e567bc8b17b596e050f5ff410e36112c7966e33f4b372c752e7350bacc894 +ghcr.io/cozystack/cozystack/ubuntu-container-disk:v1.33@sha256:71a74ca30f75967bae309be2758f19aa3d37c60b19426b9b622ff1c33a80362f diff --git a/packages/core/installer/values.yaml b/packages/core/installer/values.yaml index 0d618fdb..6d244871 100644 --- a/packages/core/installer/values.yaml +++ b/packages/core/installer/values.yaml @@ -1,4 +1,4 @@ cozystackOperator: - image: ghcr.io/cozystack/cozystack/cozystack-operator:v1.0.0-alpha.1@sha256:d1beb9728b5be3ff6ee59eab071ac739cbb69d95f4bae173baa56323e97b0e5a + image: ghcr.io/cozystack/cozystack/cozystack-operator:v1.0.0-alpha.2@sha256:5bd4c8bb7bddb9e32555ef72ee3a04b5d8e0f488d7dce904ac2d7c9e0706debf platformSourceUrl: 'oci://ghcr.io/cozystack/cozystack/cozystack-packages' - platformSourceRef: 'digest=sha256:ab03446b256c0faa9de0656854319f3b56e1252dab6f7b51bc6c95609d192556' + platformSourceRef: 'digest=sha256:1b860c13b99da6fde37f2af3a5375aca234200836e2fa39a839ac30fa237962e' diff --git a/packages/core/testing/values.yaml b/packages/core/testing/values.yaml index af105f30..9e577ed3 100644 --- a/packages/core/testing/values.yaml +++ b/packages/core/testing/values.yaml @@ -1,2 +1,2 @@ e2e: - image: ghcr.io/cozystack/cozystack/e2e-sandbox:v1.0.0-alpha.1@sha256:78a556a059411cac89249c2e22596223128a1c74fbfcac4787fd74e4a66f66a6 + image: ghcr.io/cozystack/cozystack/e2e-sandbox:v1.0.0-alpha.2@sha256:fde7616aacaf5939388bbe74eb6d946147ce855b9cceb47092f620b75ba2c98a diff --git a/packages/extra/bootbox/images/matchbox.tag b/packages/extra/bootbox/images/matchbox.tag index 8ec9a817..ac9a2f76 100644 --- a/packages/extra/bootbox/images/matchbox.tag +++ b/packages/extra/bootbox/images/matchbox.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/matchbox:v1.0.0-alpha.1@sha256:fa4b9a4d2e94648e5f17d9e46f8dfaddee423652676052b1976f5be998fa9cad +ghcr.io/cozystack/cozystack/matchbox:v1.0.0-alpha.2@sha256:6a96746d43fde16db12bb2e0e6fc39823f4a26f41fc0be785bf6716c759009e1 diff --git a/packages/extra/seaweedfs/images/objectstorage-sidecar.tag b/packages/extra/seaweedfs/images/objectstorage-sidecar.tag index cc735976..dd3c00a1 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:v1.0.0-alpha.1@sha256:d342ac197b97b81ec42712ecd9d762c6c7710a401736e9d09c64a5b8d59774cc +ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.0.0-alpha.2@sha256:aebbe6efe34c0a0cc2e87dafbf9dbb021d7e44796ef9378a47ca81cf72a407bb diff --git a/packages/system/backup-controller/values.yaml b/packages/system/backup-controller/values.yaml index 7d7d8740..0c20d434 100644 --- a/packages/system/backup-controller/values.yaml +++ b/packages/system/backup-controller/values.yaml @@ -1,5 +1,5 @@ backupController: - image: "ghcr.io/cozystack/cozystack/backup-controller:v1.0.0-alpha.1@sha256:17502897a36a0723b2ca65e88b336e6d64d67d2ff578d6aa22165f2e0c718cc6" + image: "ghcr.io/cozystack/cozystack/backup-controller:v1.0.0-alpha.2@sha256:3914624192b4d420b79f0d985d37a03acb13411c694e23c160de16b1d877c52f" replicas: 2 debug: false metrics: diff --git a/packages/system/backupstrategy-controller/values.yaml b/packages/system/backupstrategy-controller/values.yaml index 752af01a..cd71b6e1 100644 --- a/packages/system/backupstrategy-controller/values.yaml +++ b/packages/system/backupstrategy-controller/values.yaml @@ -1,5 +1,5 @@ backupStrategyController: - image: "ghcr.io/cozystack/cozystack/backupstrategy-controller:v1.0.0-alpha.1@sha256:d13aa9b1221484e6e70cb9d773b0d1cc0644eeaae2e78470cd5a89061dff9e05" + image: "ghcr.io/cozystack/cozystack/backupstrategy-controller:v1.0.0-alpha.2@sha256:a39b76c99f252916ca9b6606a68fbd5b0861988dd65426803855c2884117e49c" replicas: 2 debug: false metrics: diff --git a/packages/system/bucket/images/s3manager.tag b/packages/system/bucket/images/s3manager.tag index 88b26eac..1e30a0e1 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:79463ccddbbd2a4048863944a858b2aced3f6e1c2d77f7ef4fa57e30624f6e35 +ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:db548ff13f27f98e888b8f3ffb0f859a8f44334f1502d567d1881b79b6e5eb09 diff --git a/packages/system/cilium/values.yaml b/packages/system/cilium/values.yaml index 8cec970c..49a028f0 100644 --- a/packages/system/cilium/values.yaml +++ b/packages/system/cilium/values.yaml @@ -15,10 +15,10 @@ cilium: mode: "kubernetes" image: repository: ghcr.io/cozystack/cozystack/cilium - tag: 1.18.5 - digest: "sha256:c14a0bcb1a1531c72725b3a4c40aefa2bcd5c129810bf58ea8e37d3fcff2a326" + tag: 1.18.6 + digest: "sha256:4f4585f8adc3b8becd15d3999f3900a4d3d650f2ab7f85ca8c661f3807113d01" envoy: enabled: false rollOutCiliumPods: true operator: - rollOutPods: true \ No newline at end of file + rollOutPods: true diff --git a/packages/system/cozystack-api/values.yaml b/packages/system/cozystack-api/values.yaml index 40b8ca78..75f0d6d1 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:v1.0.0-alpha.1@sha256:ad2d688c29b15d6cc77c526e9dd57bd4b2887a79ac1fb3fea636e69bc11d3b28 + image: ghcr.io/cozystack/cozystack/cozystack-api:v1.0.0-alpha.2@sha256:5bde4dd3e8d3a880475c9e872925f841f6131f51f9a5017ec5c2a0f0bd288838 localK8sAPIEndpoint: enabled: true replicas: 2 diff --git a/packages/system/cozystack-controller/values.yaml b/packages/system/cozystack-controller/values.yaml index e5110294..633d0b28 100644 --- a/packages/system/cozystack-controller/values.yaml +++ b/packages/system/cozystack-controller/values.yaml @@ -1,5 +1,5 @@ cozystackController: - image: ghcr.io/cozystack/cozystack/cozystack-controller:v1.0.0-alpha.1@sha256:b9c3f3ca0a65a7ed43d690e805ec42e1a7b3091418b7082e60478a82a5c8ddc3 + image: ghcr.io/cozystack/cozystack/cozystack-controller:v1.0.0-alpha.2@sha256:9cc3d4b05709d3e7495abcbe4c792467a9a439fd089a67df0e4a75fd3bb8ee81 debug: false disableTelemetry: false cozystackAPIKind: "DaemonSet" diff --git a/packages/system/dashboard/templates/configmap.yaml b/packages/system/dashboard/templates/configmap.yaml index dd739a5c..675a396d 100644 --- a/packages/system/dashboard/templates/configmap.yaml +++ b/packages/system/dashboard/templates/configmap.yaml @@ -1,6 +1,6 @@ {{- $brandingConfig := .Values._cluster.branding | default dict }} -{{- $tenantText := "v1.0.0-alpha.1" }} +{{- $tenantText := "v1.0.0-alpha.2" }} {{- $footerText := "Cozystack" }} {{- $titleText := "Cozystack Dashboard" }} {{- $logoText := "" }} diff --git a/packages/system/dashboard/values.yaml b/packages/system/dashboard/values.yaml index ec896d8b..fcbaeb8c 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:v1.0.0-alpha.1@sha256:7a3cf1a20dac3bf5f7ecad6cf2ebd5be034814ec6f8ca9ea5b467596481ae3ec + image: ghcr.io/cozystack/cozystack/openapi-ui:v1.0.0-alpha.2@sha256:5661e877919c8625c27b89ee5dfda4a93d43e660d033fc4dbeb27f336c05d908 openapiUIK8sBff: - image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v1.0.0-alpha.1@sha256:6e16fb9bad7ec5f05fdfe5be83f21846b6cd0cedd4a692319b0106257683330b + image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v1.0.0-alpha.2@sha256:d33583995dc81a47c1dcbe45dbd866fa9097f88f4b6eb78b408dca432f15bd38 tokenProxy: - image: ghcr.io/cozystack/cozystack/token-proxy:v1.0.0-alpha.1@sha256:73887f80d96e7e3c16f1cebab521b05b4308bf4662ccc6724e6a8a9745ed8254 + image: ghcr.io/cozystack/cozystack/token-proxy:v1.0.0-alpha.2@sha256:73887f80d96e7e3c16f1cebab521b05b4308bf4662ccc6724e6a8a9745ed8254 diff --git a/packages/system/grafana-operator/images/grafana-dashboards.tag b/packages/system/grafana-operator/images/grafana-dashboards.tag index 41aeb19c..892bb131 100644 --- a/packages/system/grafana-operator/images/grafana-dashboards.tag +++ b/packages/system/grafana-operator/images/grafana-dashboards.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/grafana-dashboards:v1.0.0-alpha.1@sha256:e320863f0b04a71b0eccd5a4608aad34a882c79c66f3b4c2c4d1089f9ee3bb25 +ghcr.io/cozystack/cozystack/grafana-dashboards:v1.0.0-alpha.2@sha256:57051daa5686b7dcb0799b6a7c01e676b910a2ac224437ac0ad639a0e66566c2 diff --git a/packages/system/kamaji/values.yaml b/packages/system/kamaji/values.yaml index 24d5ed70..0995afc7 100644 --- a/packages/system/kamaji/values.yaml +++ b/packages/system/kamaji/values.yaml @@ -3,7 +3,7 @@ kamaji: deploy: false image: pullPolicy: IfNotPresent - tag: v1.0.0-alpha.1@sha256:777f1df253dc97da7f42019857e7a893c34bd98da1cf36e38533adbdf601acf7 + tag: v1.0.0-alpha.2@sha256:d5f2fa2972ba33cd2ccb855256e4bda4734d7e250638811b77f2e0dc72ad6b19 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:v1.0.0-alpha.1@sha256:777f1df253dc97da7f42019857e7a893c34bd98da1cf36e38533adbdf601acf7 + - --migrate-image=ghcr.io/cozystack/cozystack/kamaji:v1.0.0-alpha.2@sha256:d5f2fa2972ba33cd2ccb855256e4bda4734d7e250638811b77f2e0dc72ad6b19 diff --git a/packages/system/kilo/values.yaml b/packages/system/kilo/values.yaml index d2ef28ab..565191a9 100644 --- a/packages/system/kilo/values.yaml +++ b/packages/system/kilo/values.yaml @@ -1,8 +1,8 @@ kilo: image: pullPolicy: IfNotPresent - tag: 0.6.0@sha256:5115b6ea3f7f492763e8bfe5b4ae23d0b65bde20162b00d5a42420918b20d328 - repository: 999669/kilo + tag: v1.0.0-alpha.2@sha256:161fec69f38f536529139318babbd7d654bd47e7bc654828c26eb0ddb516eb79 + repository: ghcr.io/cozystack/cozystack/kilo podCIDR: 10.244.0.0/16 serviceCIDR: 10.96.0.0/16 transitCIDR: 100.66.0.0/16 diff --git a/packages/system/kubeovn-plunger/values.yaml b/packages/system/kubeovn-plunger/values.yaml index c3917ffb..d821bdac 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:v1.0.0-alpha.1@sha256:dc5fc9d7a54b2608e088b24c8bef03693e450e8952cf0476266a1b68eeb7e455 +image: ghcr.io/cozystack/cozystack/kubeovn-plunger:v1.0.0-alpha.2@sha256:81d7e64b67b1e4fefa88ab0cf69c113a3fb44b04810074b616116102adcc3104 ovnCentralName: ovn-central diff --git a/packages/system/kubeovn-webhook/values.yaml b/packages/system/kubeovn-webhook/values.yaml index 34a48ef1..df35d690 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:v1.0.0-alpha.1@sha256:e18f9fd679e38f65362a8d0042f25468272f6d081136ad47027168d8e7e07a4a +image: ghcr.io/cozystack/cozystack/kubeovn-webhook:v1.0.0-alpha.2@sha256:e18f9fd679e38f65362a8d0042f25468272f6d081136ad47027168d8e7e07a4a diff --git a/packages/system/kubevirt-csi-node/values.yaml b/packages/system/kubevirt-csi-node/values.yaml index 5644e6d8..1fb152da 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:15b94dca216b73336e7f39f4ea1b76b7656890d6be8a8cf0d9a786b4006781f9 + image: ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:31aee6f63c25a443974b56011d2907cedfeba245aa7caf732d7fc437b2b3ed50 diff --git a/packages/system/lineage-controller-webhook/values.yaml b/packages/system/lineage-controller-webhook/values.yaml index 41d00955..0c4a1694 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:v1.0.0-alpha.1@sha256:887c264efecb31cb7ca38034ecf325cf461e2302d703422364d8b20e4f18c56b + image: ghcr.io/cozystack/cozystack/lineage-controller-webhook:v1.0.0-alpha.2@sha256:e3ed98fce423359de7dd25094de959213342f5664d266c8d1041ecd2e9c82d59 debug: false localK8sAPIEndpoint: enabled: true diff --git a/packages/system/linstor/values.yaml b/packages/system/linstor/values.yaml index 684167cb..9f7f022e 100644 --- a/packages/system/linstor/values.yaml +++ b/packages/system/linstor/values.yaml @@ -10,4 +10,4 @@ linstor: linstorCSI: image: repository: ghcr.io/cozystack/cozystack/linstor-csi - tag: v1.10.5@sha256:68465f120cfeec3d7ccbb389dd9bdbf7df1675da3ab9ba91c3feff21a799bc36 + tag: v1.10.5@sha256:353a8bea0b41f832975132da3569da7d4fce85980474edce41a2a37097c7c3a9 diff --git a/packages/system/objectstorage-controller/values.yaml b/packages/system/objectstorage-controller/values.yaml index 1d56ae5d..86ec9cd4 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:v1.0.0-alpha.1@sha256:76756860145d49abe1585c8ca600267a07fcdbb0b359bc9c127baf9efb8e006b" + image: "ghcr.io/cozystack/cozystack/objectstorage-controller:v1.0.0-alpha.2@sha256:e535bd383e2ec275cc403cd557996e26df3d206a5541c53037246641cf6a4f2e" diff --git a/packages/system/seaweedfs/values.yaml b/packages/system/seaweedfs/values.yaml index 941755aa..88a05e9c 100644 --- a/packages/system/seaweedfs/values.yaml +++ b/packages/system/seaweedfs/values.yaml @@ -177,7 +177,7 @@ seaweedfs: bucketClassName: "seaweedfs" region: "" sidecar: - image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.0.0-alpha.1@sha256:d342ac197b97b81ec42712ecd9d762c6c7710a401736e9d09c64a5b8d59774cc" + image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.0.0-alpha.2@sha256:aebbe6efe34c0a0cc2e87dafbf9dbb021d7e44796ef9378a47ca81cf72a407bb" certificates: commonName: "SeaweedFS CA" ipAddresses: [] From 207a5171f0c0ef4680a4ddd9e073beeaa023c324 Mon Sep 17 00:00:00 2001 From: Kirill Ilin Date: Tue, 20 Jan 2026 18:00:13 +0500 Subject: [PATCH 081/889] [monitoring-agents] Set minReplicas to 1 for VPA for VMAgent Signed-off-by: Kirill Ilin --- packages/system/monitoring-agents/templates/vpa.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/system/monitoring-agents/templates/vpa.yaml b/packages/system/monitoring-agents/templates/vpa.yaml index fa672d82..4da978e7 100644 --- a/packages/system/monitoring-agents/templates/vpa.yaml +++ b/packages/system/monitoring-agents/templates/vpa.yaml @@ -9,6 +9,7 @@ spec: name: vmagent updatePolicy: updateMode: Auto + minReplicas: 1 resourcePolicy: containerPolicies: - containerName: config-reloader From 505b693c359849d4a2d928e050f6660da96ea9db Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Sat, 3 Jan 2026 01:01:04 +0100 Subject: [PATCH 082/889] [ci] Run e2e tests on shared runners Signed-off-by: Andrei Kvapil --- .github/workflows/pull-requests.yaml | 189 +++++++++------------------ hack/e2e-prepare-cluster.bats | 46 ++++--- 2 files changed, 91 insertions(+), 144 deletions(-) diff --git a/.github/workflows/pull-requests.yaml b/.github/workflows/pull-requests.yaml index 53787ad3..347bfd30 100644 --- a/.github/workflows/pull-requests.yaml +++ b/.github/workflows/pull-requests.yaml @@ -151,9 +151,10 @@ jobs: core.setOutput('disk_id', diskId); - prepare_env: - name: "Prepare environment" - runs-on: [self-hosted] + e2e: + name: "E2E Tests" + runs-on: [oracle-vm-24cpu-96gb-x86-64] + #runs-on: [oracle-vm-32cpu-128gb-x86-64] permissions: contents: read packages: read @@ -173,6 +174,20 @@ jobs: name: talos-image path: _out/assets + - name: "Download CRDs (regular PR)" + if: "!contains(github.event.pull_request.labels.*.name, 'release')" + uses: actions/download-artifact@v4 + with: + name: cozystack-crds + path: _out/assets + + - name: "Download operator (regular PR)" + if: "!contains(github.event.pull_request.labels.*.name, 'release')" + uses: actions/download-artifact@v4 + with: + name: cozystack-operator + path: _out/assets + - name: Download PR patch if: "!contains(github.event.pull_request.labels.*.name, 'release')" uses: actions/download-artifact@v4 @@ -193,13 +208,19 @@ jobs: curl -sSL -H "Authorization: token ${GH_PAT}" -H "Accept: application/octet-stream" \ -o _out/assets/nocloud-amd64.raw.xz \ "https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/assets/${{ needs.resolve_assets.outputs.disk_id }}" + curl -sSL -H "Authorization: token ${GH_PAT}" -H "Accept: application/octet-stream" \ + -o _out/assets/cozystack-crds.yaml \ + "https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/assets/${{ needs.resolve_assets.outputs.crds_id }}" + curl -sSL -H "Authorization: token ${GH_PAT}" -H "Accept: application/octet-stream" \ + -o _out/assets/cozystack-operator.yaml \ + "https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/assets/${{ needs.resolve_assets.outputs.operator_id }}" env: GH_PAT: ${{ secrets.GH_PAT }} - name: Set sandbox ID run: echo "SANDBOX_NAME=cozy-e2e-sandbox-$(echo "${GITHUB_REPOSITORY}:${GITHUB_WORKFLOW}:${GITHUB_REF}" | sha256sum | cut -c1-10)" >> $GITHUB_ENV - # ▸ Start actual job steps + # ▸ Prepare environment - name: Prepare workspace run: | rm -rf /tmp/$SANDBOX_NAME @@ -219,57 +240,7 @@ jobs: done echo "✅ The task completed successfully after $attempt attempts" - install_cozystack: - name: "Install Cozystack" - runs-on: [self-hosted] - permissions: - contents: read - packages: read - needs: ["prepare_env", "resolve_assets"] - if: ${{ always() && needs.prepare_env.result == 'success' }} - - steps: - - name: Prepare _out/assets directory - run: mkdir -p _out/assets - - # ▸ Regular PR path – download artefacts produced by the *build* job - - name: "Download CRDs (regular PR)" - if: "!contains(github.event.pull_request.labels.*.name, 'release')" - uses: actions/download-artifact@v4 - with: - name: cozystack-crds - path: _out/assets - - - name: "Download operator (regular PR)" - if: "!contains(github.event.pull_request.labels.*.name, 'release')" - uses: actions/download-artifact@v4 - with: - name: cozystack-operator - path: _out/assets - - # ▸ Release PR path – fetch artefacts from the corresponding draft release - - name: Download assets from draft release (release PR) - if: contains(github.event.pull_request.labels.*.name, 'release') - run: | - mkdir -p _out/assets - curl -sSL -H "Authorization: token ${GH_PAT}" -H "Accept: application/octet-stream" \ - -o _out/assets/cozystack-crds.yaml \ - "https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/assets/${{ needs.resolve_assets.outputs.crds_id }}" - curl -sSL -H "Authorization: token ${GH_PAT}" -H "Accept: application/octet-stream" \ - -o _out/assets/cozystack-operator.yaml \ - "https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/assets/${{ needs.resolve_assets.outputs.operator_id }}" - env: - GH_PAT: ${{ secrets.GH_PAT }} - - # ▸ Start actual job steps - - name: Set sandbox ID - run: echo "SANDBOX_NAME=cozy-e2e-sandbox-$(echo "${GITHUB_REPOSITORY}:${GITHUB_WORKFLOW}:${GITHUB_REF}" | sha256sum | cut -c1-10)" >> $GITHUB_ENV - - - name: Sync _out/assets directory - run: | - mkdir -p /tmp/$SANDBOX_NAME/_out/assets - mv _out/assets/* /tmp/$SANDBOX_NAME/_out/assets/ - + # ▸ Install Cozystack - name: Install Cozystack into sandbox run: | cd /tmp/$SANDBOX_NAME @@ -282,107 +253,77 @@ jobs: fi echo "❌ Attempt $attempt failed, retrying..." done - echo "✅ The task completed successfully after $attempt attempts." + echo "✅ The task completed successfully after $attempt attempts" - name: Run OpenAPI tests run: | cd /tmp/$SANDBOX_NAME make -C packages/core/testing SANDBOX_NAME=$SANDBOX_NAME test-openapi - detect_test_matrix: - name: "Detect e2e test matrix" - runs-on: ubuntu-latest - outputs: - matrix: ${{ steps.set.outputs.matrix }} - - steps: - - uses: actions/checkout@v4 - - id: set - run: | - apps=$(ls hack/e2e-apps/*.bats | cut -f3 -d/ | cut -f1 -d. | jq -R | jq -cs) - echo "matrix={\"app\":$apps}" >> "$GITHUB_OUTPUT" - - test_apps: - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.detect_test_matrix.outputs.matrix) }} - name: Test ${{ matrix.app }} - runs-on: [self-hosted] - needs: [install_cozystack,detect_test_matrix] - if: ${{ always() && (needs.install_cozystack.result == 'success' && needs.detect_test_matrix.result == 'success') }} - - steps: - - name: Set sandbox ID - run: echo "SANDBOX_NAME=cozy-e2e-sandbox-$(echo "${GITHUB_REPOSITORY}:${GITHUB_WORKFLOW}:${GITHUB_REF}" | sha256sum | cut -c1-10)" >> $GITHUB_ENV - - - name: E2E Apps + # ▸ Run E2E tests + - name: Run E2E tests + id: e2e_tests run: | cd /tmp/$SANDBOX_NAME - attempt=0 - until make -C packages/core/testing SANDBOX_NAME=$SANDBOX_NAME test-apps-${{ matrix.app }}; do - attempt=$((attempt + 1)) - if [ $attempt -ge 3 ]; then - echo "❌ Attempt $attempt failed, exiting..." - exit 1 + failed_tests="" + for app in $(ls hack/e2e-apps/*.bats | xargs -n1 basename | cut -d. -f1); do + echo "::group::Testing $app" + attempt=0 + success=false + until [ $attempt -ge 3 ]; do + if make -C packages/core/testing SANDBOX_NAME=$SANDBOX_NAME test-apps-$app; then + success=true + break + fi + attempt=$((attempt + 1)) + echo "❌ Attempt $attempt failed, retrying..." + done + if [ "$success" = true ]; then + echo "✅ Test $app completed successfully" + else + echo "❌ Test $app failed after $attempt attempts" + failed_tests="$failed_tests $app" fi - echo "❌ Attempt $attempt failed, retrying..." + echo "::endgroup::" done - echo "✅ The task completed successfully after $attempt attempts" - - collect_debug_information: - name: Collect debug information - runs-on: [self-hosted] - needs: [test_apps] - if: ${{ always() }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set sandbox ID - run: echo "SANDBOX_NAME=cozy-e2e-sandbox-$(echo "${GITHUB_REPOSITORY}:${GITHUB_WORKFLOW}:${GITHUB_REF}" | sha256sum | cut -c1-10)" >> $GITHUB_ENV + if [ -n "$failed_tests" ]; then + echo "❌ Failed tests:$failed_tests" + exit 1 + fi + echo "✅ All E2E tests passed" + # ▸ Collect debug information (always runs) - name: Collect report + if: always() run: | cd /tmp/$SANDBOX_NAME - make -C packages/core/testing SANDBOX_NAME=$SANDBOX_NAME collect-report + make -C packages/core/testing SANDBOX_NAME=$SANDBOX_NAME collect-report || true - name: Upload cozyreport.tgz + if: always() uses: actions/upload-artifact@v4 with: name: cozyreport path: /tmp/${{ env.SANDBOX_NAME }}/_out/cozyreport.tgz - name: Collect images list + if: always() run: | cd /tmp/$SANDBOX_NAME - make -C packages/core/testing SANDBOX_NAME=$SANDBOX_NAME collect-images + make -C packages/core/testing SANDBOX_NAME=$SANDBOX_NAME collect-images || true - name: Upload image list + if: always() uses: actions/upload-artifact@v4 with: name: image-list path: /tmp/${{ env.SANDBOX_NAME }}/_out/images.txt - cleanup: - name: Tear down environment - runs-on: [self-hosted] - needs: [collect_debug_information] - if: ${{ always() && needs.test_apps.result == 'success' }} - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - fetch-tags: true - - - name: Set sandbox ID - run: echo "SANDBOX_NAME=cozy-e2e-sandbox-$(echo "${GITHUB_REPOSITORY}:${GITHUB_WORKFLOW}:${GITHUB_REF}" | sha256sum | cut -c1-10)" >> $GITHUB_ENV - + # ▸ Tear down environment (always runs) - name: Tear down sandbox - run: make -C packages/core/testing SANDBOX_NAME=$SANDBOX_NAME delete + if: always() + run: make -C packages/core/testing SANDBOX_NAME=$SANDBOX_NAME delete || true - name: Remove workspace + if: always() run: rm -rf /tmp/$SANDBOX_NAME - - diff --git a/hack/e2e-prepare-cluster.bats b/hack/e2e-prepare-cluster.bats index 650ca643..df90b91a 100644 --- a/hack/e2e-prepare-cluster.bats +++ b/hack/e2e-prepare-cluster.bats @@ -136,25 +136,28 @@ machine: mirrors: docker.io: endpoints: - - https://dockerio.nexus.aenix.org - cr.fluentbit.io: - endpoints: - - https://fluentbit.nexus.aenix.org - docker-registry3.mariadb.com: - endpoints: - - https://mariadb.nexus.aenix.org - gcr.io: - endpoints: - - https://gcr.nexus.aenix.org - ghcr.io: - endpoints: - - https://ghcr.nexus.aenix.org - quay.io: - endpoints: - - https://quay.nexus.aenix.org - registry.k8s.io: - endpoints: - - https://k8s.nexus.aenix.org + - https://mirror.gcr.io + #docker.io: + # endpoints: + # - https://dockerio.nexus.aenix.org + #cr.fluentbit.io: + # endpoints: + # - https://fluentbit.nexus.aenix.org + #docker-registry3.mariadb.com: + # endpoints: + # - https://mariadb.nexus.aenix.org + #gcr.io: + # endpoints: + # - https://gcr.nexus.aenix.org + #ghcr.io: + # endpoints: + # - https://ghcr.nexus.aenix.org + #quay.io: + # endpoints: + # - https://quay.nexus.aenix.org + #registry.k8s.io: + # endpoints: + # - https://k8s.nexus.aenix.org files: - content: | [plugins] @@ -236,7 +239,10 @@ EOF timeout 10 sh -ec 'until talosctl bootstrap -n 192.168.123.11 -e 192.168.123.11; do sleep 1; done' # Wait until etcd is healthy - timeout 180 sh -ec 'until talosctl etcd members -n 192.168.123.11,192.168.123.12,192.168.123.13 -e 192.168.123.10 >/dev/null 2>&1; do sleep 1; done' + if ! timeout 180 sh -ec 'until talosctl etcd members -n 192.168.123.11,192.168.123.12,192.168.123.13 -e 192.168.123.10 >/dev/null 2>&1; do sleep 1; done'; then + talosctl dmesg -n 192.168.123.11,192.168.123.12,192.168.123.13 -e 192.168.123.10 || true + exit 1 + fi timeout 60 sh -ec 'while talosctl etcd members -n 192.168.123.11,192.168.123.12,192.168.123.13 -e 192.168.123.10 2>&1 | grep -q "rpc error"; do sleep 1; done' # Retrieve kubeconfig From 0b95a72fa332164038bb2ad2afaa9a87b2494a3c Mon Sep 17 00:00:00 2001 From: Kirill Ilin Date: Wed, 21 Jan 2026 16:49:13 +0500 Subject: [PATCH 083/889] [kubernetes] Add enum validation for IngressNginx exposeMethod Signed-off-by: Kirill Ilin --- packages/apps/kubernetes/values.schema.json | 6 +++++- packages/apps/kubernetes/values.yaml | 6 +++++- packages/system/kubernetes-rd/cozyrds/kubernetes.yaml | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/apps/kubernetes/values.schema.json b/packages/apps/kubernetes/values.schema.json index 69b5145d..1f6c9361 100644 --- a/packages/apps/kubernetes/values.schema.json +++ b/packages/apps/kubernetes/values.schema.json @@ -150,7 +150,11 @@ "exposeMethod": { "description": "Method to expose the controller. Allowed values: `Proxied`, `LoadBalancer`.", "type": "string", - "default": "Proxied" + "default": "Proxied", + "enum": [ + "Proxied", + "LoadBalancer" + ] }, "hosts": { "description": "Domains routed to this tenant cluster when `exposeMethod` is `Proxied`.", diff --git a/packages/apps/kubernetes/values.yaml b/packages/apps/kubernetes/values.yaml index ba7d3f6d..7f15752f 100644 --- a/packages/apps/kubernetes/values.yaml +++ b/packages/apps/kubernetes/values.yaml @@ -76,9 +76,13 @@ host: "" ## @typedef {struct} GatewayAPIAddon - Gateway API addon. ## @field {bool} enabled - Enable Gateway API. +## @enum {string} IngressNginxExposeMethod - Method to expose the controller +## @value Proxied +## @value LoadBalancer + ## @typedef {struct} IngressNginxAddon - Ingress-NGINX controller. ## @field {bool} enabled - Enable the controller (requires nodes labeled `ingress-nginx`). -## @field {string} exposeMethod - Method to expose the controller. Allowed values: `Proxied`, `LoadBalancer`. +## @field {IngressNginxExposeMethod} exposeMethod - Method to expose the controller. Allowed values: `Proxied`, `LoadBalancer`. ## @field {[]string} hosts - Domains routed to this tenant cluster when `exposeMethod` is `Proxied`. ## @field {object} valuesOverride - Custom Helm values overrides. diff --git a/packages/system/kubernetes-rd/cozyrds/kubernetes.yaml b/packages/system/kubernetes-rd/cozyrds/kubernetes.yaml index 6c84f5e4..289ef25e 100644 --- a/packages/system/kubernetes-rd/cozyrds/kubernetes.yaml +++ b/packages/system/kubernetes-rd/cozyrds/kubernetes.yaml @@ -8,7 +8,7 @@ spec: singular: kubernetes plural: kuberneteses openAPISchema: |- - {"title":"Chart Values","type":"object","properties":{"addons":{"description":"Cluster addons configuration.","type":"object","default":{},"required":["certManager","cilium","coredns","fluxcd","gatewayAPI","gpuOperator","ingressNginx","monitoringAgents","velero","verticalPodAutoscaler"],"properties":{"certManager":{"description":"Cert-manager addon.","type":"object","default":{},"required":["enabled","valuesOverride"],"properties":{"enabled":{"description":"Enable cert-manager.","type":"boolean","default":false},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"cilium":{"description":"Cilium CNI plugin.","type":"object","default":{},"required":["valuesOverride"],"properties":{"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"coredns":{"description":"CoreDNS addon.","type":"object","default":{},"required":["valuesOverride"],"properties":{"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"fluxcd":{"description":"FluxCD GitOps operator.","type":"object","default":{},"required":["enabled","valuesOverride"],"properties":{"enabled":{"description":"Enable FluxCD.","type":"boolean","default":false},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"gatewayAPI":{"description":"Gateway API addon.","type":"object","default":{},"required":["enabled"],"properties":{"enabled":{"description":"Enable Gateway API.","type":"boolean","default":false}}},"gpuOperator":{"description":"NVIDIA GPU Operator.","type":"object","default":{},"required":["enabled","valuesOverride"],"properties":{"enabled":{"description":"Enable GPU Operator.","type":"boolean","default":false},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"ingressNginx":{"description":"Ingress-NGINX controller.","type":"object","default":{},"required":["enabled","exposeMethod","valuesOverride"],"properties":{"enabled":{"description":"Enable the controller (requires nodes labeled `ingress-nginx`).","type":"boolean","default":false},"exposeMethod":{"description":"Method to expose the controller. Allowed values: `Proxied`, `LoadBalancer`.","type":"string","default":"Proxied"},"hosts":{"description":"Domains routed to this tenant cluster when `exposeMethod` is `Proxied`.","type":"array","default":[],"items":{"type":"string"}},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"monitoringAgents":{"description":"Monitoring agents.","type":"object","default":{},"required":["enabled","valuesOverride"],"properties":{"enabled":{"description":"Enable monitoring agents.","type":"boolean","default":false},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"velero":{"description":"Velero backup/restore addon.","type":"object","default":{},"required":["enabled","valuesOverride"],"properties":{"enabled":{"description":"Enable Velero.","type":"boolean","default":false},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"verticalPodAutoscaler":{"description":"Vertical Pod Autoscaler.","type":"object","default":{},"required":["valuesOverride"],"properties":{"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}}}},"controlPlane":{"description":"Kubernetes control-plane configuration.","type":"object","default":{},"required":["apiServer","controllerManager","konnectivity","replicas","scheduler"],"properties":{"apiServer":{"description":"API Server configuration.","type":"object","default":{},"required":["resources","resourcesPreset"],"properties":{"resources":{"description":"CPU and memory resources for API Server.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available.","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 (RAM) available.","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}}},"resourcesPreset":{"description":"Preset if `resources` omitted.","type":"string","default":"large","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}},"controllerManager":{"description":"Controller Manager configuration.","type":"object","default":{},"required":["resources","resourcesPreset"],"properties":{"resources":{"description":"CPU and memory resources for Controller Manager.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available.","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 (RAM) available.","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}}},"resourcesPreset":{"description":"Preset if `resources` omitted.","type":"string","default":"micro","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}},"konnectivity":{"description":"Konnectivity configuration.","type":"object","default":{},"required":["server"],"properties":{"server":{"description":"Konnectivity Server configuration.","type":"object","default":{},"required":["resources","resourcesPreset"],"properties":{"resources":{"description":"CPU and memory resources for Konnectivity.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available.","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 (RAM) available.","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}}},"resourcesPreset":{"description":"Preset if `resources` omitted.","type":"string","default":"micro","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}}}},"replicas":{"description":"Number of control-plane replicas.","type":"integer","default":2},"scheduler":{"description":"Scheduler configuration.","type":"object","default":{},"required":["resources","resourcesPreset"],"properties":{"resources":{"description":"CPU and memory resources for Scheduler.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available.","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 (RAM) available.","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}}},"resourcesPreset":{"description":"Preset if `resources` omitted.","type":"string","default":"micro","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}}}},"host":{"description":"External hostname for Kubernetes cluster. Defaults to `.` if empty.","type":"string","default":""},"nodeGroups":{"description":"Worker nodes configuration map.","type":"object","default":{"md0":{"ephemeralStorage":"20Gi","gpus":[],"instanceType":"u1.medium","maxReplicas":10,"minReplicas":0,"resources":{},"roles":["ingress-nginx"]}},"additionalProperties":{"type":"object","required":["ephemeralStorage","instanceType","maxReplicas","minReplicas","resources"],"properties":{"ephemeralStorage":{"description":"Ephemeral storage size.","default":"20Gi","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},"gpus":{"description":"List of GPUs to attach (NVIDIA driver requires at least 4 GiB RAM).","type":"array","items":{"type":"object","required":["name"],"properties":{"name":{"description":"Name of GPU, such as \"nvidia.com/AD102GL_L40S\".","type":"string"}}}},"instanceType":{"description":"Virtual machine instance type.","type":"string","default":"u1.medium"},"maxReplicas":{"description":"Maximum number of replicas.","type":"integer","default":10},"minReplicas":{"description":"Minimum number of replicas.","type":"integer","default":0},"resources":{"description":"CPU and memory resources for each worker node.","type":"object","properties":{"cpu":{"description":"CPU available.","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 (RAM) available.","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}}},"roles":{"description":"List of node roles.","type":"array","items":{"type":"string"}}}}},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":"replicated"},"version":{"description":"Kubernetes major.minor version to deploy","type":"string","default":"v1.33","enum":["v1.33","v1.32","v1.31","v1.30","v1.29","v1.28"]}}} + {"title":"Chart Values","type":"object","properties":{"addons":{"description":"Cluster addons configuration.","type":"object","default":{},"required":["certManager","cilium","coredns","fluxcd","gatewayAPI","gpuOperator","ingressNginx","monitoringAgents","velero","verticalPodAutoscaler"],"properties":{"certManager":{"description":"Cert-manager addon.","type":"object","default":{},"required":["enabled","valuesOverride"],"properties":{"enabled":{"description":"Enable cert-manager.","type":"boolean","default":false},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"cilium":{"description":"Cilium CNI plugin.","type":"object","default":{},"required":["valuesOverride"],"properties":{"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"coredns":{"description":"CoreDNS addon.","type":"object","default":{},"required":["valuesOverride"],"properties":{"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"fluxcd":{"description":"FluxCD GitOps operator.","type":"object","default":{},"required":["enabled","valuesOverride"],"properties":{"enabled":{"description":"Enable FluxCD.","type":"boolean","default":false},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"gatewayAPI":{"description":"Gateway API addon.","type":"object","default":{},"required":["enabled"],"properties":{"enabled":{"description":"Enable Gateway API.","type":"boolean","default":false}}},"gpuOperator":{"description":"NVIDIA GPU Operator.","type":"object","default":{},"required":["enabled","valuesOverride"],"properties":{"enabled":{"description":"Enable GPU Operator.","type":"boolean","default":false},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"ingressNginx":{"description":"Ingress-NGINX controller.","type":"object","default":{},"required":["enabled","exposeMethod","valuesOverride"],"properties":{"enabled":{"description":"Enable the controller (requires nodes labeled `ingress-nginx`).","type":"boolean","default":false},"exposeMethod":{"description":"Method to expose the controller. Allowed values: `Proxied`, `LoadBalancer`.","type":"string","default":"Proxied","enum":["Proxied","LoadBalancer"]},"hosts":{"description":"Domains routed to this tenant cluster when `exposeMethod` is `Proxied`.","type":"array","default":[],"items":{"type":"string"}},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"monitoringAgents":{"description":"Monitoring agents.","type":"object","default":{},"required":["enabled","valuesOverride"],"properties":{"enabled":{"description":"Enable monitoring agents.","type":"boolean","default":false},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"velero":{"description":"Velero backup/restore addon.","type":"object","default":{},"required":["enabled","valuesOverride"],"properties":{"enabled":{"description":"Enable Velero.","type":"boolean","default":false},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"verticalPodAutoscaler":{"description":"Vertical Pod Autoscaler.","type":"object","default":{},"required":["valuesOverride"],"properties":{"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}}}},"controlPlane":{"description":"Kubernetes control-plane configuration.","type":"object","default":{},"required":["apiServer","controllerManager","konnectivity","replicas","scheduler"],"properties":{"apiServer":{"description":"API Server configuration.","type":"object","default":{},"required":["resources","resourcesPreset"],"properties":{"resources":{"description":"CPU and memory resources for API Server.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available.","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 (RAM) available.","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}}},"resourcesPreset":{"description":"Preset if `resources` omitted.","type":"string","default":"large","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}},"controllerManager":{"description":"Controller Manager configuration.","type":"object","default":{},"required":["resources","resourcesPreset"],"properties":{"resources":{"description":"CPU and memory resources for Controller Manager.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available.","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 (RAM) available.","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}}},"resourcesPreset":{"description":"Preset if `resources` omitted.","type":"string","default":"micro","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}},"konnectivity":{"description":"Konnectivity configuration.","type":"object","default":{},"required":["server"],"properties":{"server":{"description":"Konnectivity Server configuration.","type":"object","default":{},"required":["resources","resourcesPreset"],"properties":{"resources":{"description":"CPU and memory resources for Konnectivity.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available.","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 (RAM) available.","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}}},"resourcesPreset":{"description":"Preset if `resources` omitted.","type":"string","default":"micro","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}}}},"replicas":{"description":"Number of control-plane replicas.","type":"integer","default":2},"scheduler":{"description":"Scheduler configuration.","type":"object","default":{},"required":["resources","resourcesPreset"],"properties":{"resources":{"description":"CPU and memory resources for Scheduler.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available.","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 (RAM) available.","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}}},"resourcesPreset":{"description":"Preset if `resources` omitted.","type":"string","default":"micro","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}}}},"host":{"description":"External hostname for Kubernetes cluster. Defaults to `.` if empty.","type":"string","default":""},"nodeGroups":{"description":"Worker nodes configuration map.","type":"object","default":{"md0":{"ephemeralStorage":"20Gi","gpus":[],"instanceType":"u1.medium","maxReplicas":10,"minReplicas":0,"resources":{},"roles":["ingress-nginx"]}},"additionalProperties":{"type":"object","required":["ephemeralStorage","instanceType","maxReplicas","minReplicas","resources"],"properties":{"ephemeralStorage":{"description":"Ephemeral storage size.","default":"20Gi","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},"gpus":{"description":"List of GPUs to attach (NVIDIA driver requires at least 4 GiB RAM).","type":"array","items":{"type":"object","required":["name"],"properties":{"name":{"description":"Name of GPU, such as \"nvidia.com/AD102GL_L40S\".","type":"string"}}}},"instanceType":{"description":"Virtual machine instance type.","type":"string","default":"u1.medium"},"maxReplicas":{"description":"Maximum number of replicas.","type":"integer","default":10},"minReplicas":{"description":"Minimum number of replicas.","type":"integer","default":0},"resources":{"description":"CPU and memory resources for each worker node.","type":"object","properties":{"cpu":{"description":"CPU available.","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 (RAM) available.","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}}},"roles":{"description":"List of node roles.","type":"array","items":{"type":"string"}}}}},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":"replicated"},"version":{"description":"Kubernetes major.minor version to deploy","type":"string","default":"v1.33","enum":["v1.33","v1.32","v1.31","v1.30","v1.29","v1.28"]}}} release: prefix: kubernetes- labels: From 4eb3c36301034cf6ee95547b478ac9a3f11aa257 Mon Sep 17 00:00:00 2001 From: Kirill Ilin Date: Wed, 21 Jan 2026 17:07:08 +0500 Subject: [PATCH 084/889] [tenant] remove `isolated` flag and enforce isolation Signed-off-by: Kirill Ilin --- packages/apps/tenant/README.md | 1 - packages/apps/tenant/templates/networkpolicy.yaml | 2 -- packages/apps/tenant/values.schema.json | 5 ----- packages/apps/tenant/values.yaml | 3 --- packages/system/tenant-rd/cozyrds/tenant.yaml | 4 ++-- 5 files changed, 2 insertions(+), 13 deletions(-) diff --git a/packages/apps/tenant/README.md b/packages/apps/tenant/README.md index 222c98a5..30fa9429 100644 --- a/packages/apps/tenant/README.md +++ b/packages/apps/tenant/README.md @@ -76,6 +76,5 @@ tenant-u1 | `monitoring` | Deploy own Monitoring Stack. | `bool` | `false` | | `ingress` | Deploy own Ingress Controller. | `bool` | `false` | | `seaweedfs` | Deploy own SeaweedFS. | `bool` | `false` | -| `isolated` | Enforce tenant namespace with network policies (default: true). | `bool` | `true` | | `resourceQuotas` | Define resource quotas for the tenant. | `map[string]quantity` | `{}` | diff --git a/packages/apps/tenant/templates/networkpolicy.yaml b/packages/apps/tenant/templates/networkpolicy.yaml index 2c15a877..d9f87856 100644 --- a/packages/apps/tenant/templates/networkpolicy.yaml +++ b/packages/apps/tenant/templates/networkpolicy.yaml @@ -1,4 +1,3 @@ -{{- if .Values.isolated }} --- apiVersion: cilium.io/v2 kind: CiliumNetworkPolicy @@ -220,4 +219,3 @@ spec: - toEndpoints: - matchLabels: cozystack.io/service: ingress -{{- end }} diff --git a/packages/apps/tenant/values.schema.json b/packages/apps/tenant/values.schema.json index 7690e2b4..2c3d5f89 100644 --- a/packages/apps/tenant/values.schema.json +++ b/packages/apps/tenant/values.schema.json @@ -17,11 +17,6 @@ "type": "boolean", "default": false }, - "isolated": { - "description": "Enforce tenant namespace with network policies (default: true).", - "type": "boolean", - "default": true - }, "monitoring": { "description": "Deploy own Monitoring Stack.", "type": "boolean", diff --git a/packages/apps/tenant/values.yaml b/packages/apps/tenant/values.yaml index 4543bb6b..58bb451f 100644 --- a/packages/apps/tenant/values.yaml +++ b/packages/apps/tenant/values.yaml @@ -17,8 +17,5 @@ ingress: false ## @param {bool} seaweedfs - Deploy own SeaweedFS. seaweedfs: false -## @param {bool} isolated - Enforce tenant namespace with network policies (default: true). -isolated: true - ## @param {map[string]quantity} resourceQuotas - Define resource quotas for the tenant. resourceQuotas: {} diff --git a/packages/system/tenant-rd/cozyrds/tenant.yaml b/packages/system/tenant-rd/cozyrds/tenant.yaml index 398e9204..9b09692c 100644 --- a/packages/system/tenant-rd/cozyrds/tenant.yaml +++ b/packages/system/tenant-rd/cozyrds/tenant.yaml @@ -8,7 +8,7 @@ spec: singular: tenant plural: tenants openAPISchema: |- - {"title":"Chart Values","type":"object","properties":{"etcd":{"description":"Deploy own Etcd cluster.","type":"boolean","default":false},"host":{"description":"The hostname used to access tenant services (defaults to using the tenant name as a subdomain for its parent tenant host).","type":"string","default":""},"ingress":{"description":"Deploy own Ingress Controller.","type":"boolean","default":false},"isolated":{"description":"Enforce tenant namespace with network policies (default: true).","type":"boolean","default":true},"monitoring":{"description":"Deploy own Monitoring Stack.","type":"boolean","default":false},"resourceQuotas":{"description":"Define resource quotas for the tenant.","type":"object","default":{},"additionalProperties":{"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}},"seaweedfs":{"description":"Deploy own SeaweedFS.","type":"boolean","default":false}}} + {"title":"Chart Values","type":"object","properties":{"etcd":{"description":"Deploy own Etcd cluster.","type":"boolean","default":false},"host":{"description":"The hostname used to access tenant services (defaults to using the tenant name as a subdomain for its parent tenant host).","type":"string","default":""},"ingress":{"description":"Deploy own Ingress Controller.","type":"boolean","default":false},"monitoring":{"description":"Deploy own Monitoring Stack.","type":"boolean","default":false},"resourceQuotas":{"description":"Define resource quotas for the tenant.","type":"object","default":{},"additionalProperties":{"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}},"seaweedfs":{"description":"Deploy own SeaweedFS.","type":"boolean","default":false}}} release: prefix: tenant- labels: @@ -23,7 +23,7 @@ spec: plural: Tenants description: Separated tenant namespace icon: PHN2ZyB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgdmlld0JveD0iMCAwIDE0NCAxNDQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNDQiIGhlaWdodD0iMTQ0IiByeD0iMjQiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcl82ODdfMzQwMykiLz4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzY4N18zNDAzKSI+CjxwYXRoIGQ9Ik03MiAyOUM2Ni4zOTI2IDI5IDYxLjAxNDggMzEuMjM4OCA1Ny4wNDk3IDM1LjIyNEM1My4wODQ3IDM5LjIwOTEgNTAuODU3MSA0NC42MTQxIDUwLjg1NzEgNTAuMjVDNTAuODU3MSA1NS44ODU5IDUzLjA4NDcgNjEuMjkwOSA1Ny4wNDk3IDY1LjI3NkM2MS4wMTQ4IDY5LjI2MTIgNjYuMzkyNiA3MS41IDcyIDcxLjVDNzcuNjA3NCA3MS41IDgyLjk4NTIgNjkuMjYxMiA4Ni45NTAzIDY1LjI3NkM5MC45MTUzIDYxLjI5MDkgOTMuMTQyOSA1NS44ODU5IDkzLjE0MjkgNTAuMjVDOTMuMTQyOSA0NC42MTQxIDkwLjkxNTMgMzkuMjA5MSA4Ni45NTAzIDM1LjIyNEM4Mi45ODUyIDMxLjIzODggNzcuNjA3NCAyOSA3MiAyOVpNNjAuOTgyNiA4My4zMDM3QzYwLjQ1NCA4Mi41ODk4IDU5LjU5NTEgODIuMTkxNCA1OC43MTk2IDgyLjI3NDRDNDUuMzg5NyA4My43MzU0IDM1IDk1LjEwNzQgMzUgMTA4LjkwM0MzNSAxMTEuNzI2IDM3LjI3OTUgMTE0IDQwLjA3MSAxMTRIMTAzLjkyOUMxMDYuNzM3IDExNCAxMDkgMTExLjcwOSAxMDkgMTA4LjkwM0MxMDkgOTUuMTA3NCA5OC42MTAzIDgzLjc1MiA4NS4yNjM4IDgyLjI5MUM4NC4zODg0IDgyLjE5MTQgODMuNTI5NSA4Mi42MDY0IDgzLjAwMDkgODMuMzIwM0w3NC4wOTc4IDk1LjI0MDJDNzMuMDQwNiA5Ni42NTE0IDcwLjkyNjMgOTYuNjUxNCA2OS44NjkyIDk1LjI0MDJMNjAuOTY2MSA4My4zMjAzTDYwLjk4MjYgODMuMzAzN1oiIGZpbGw9ImJsYWNrIi8+CjwvZz4KPGRlZnM+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQwX2xpbmVhcl82ODdfMzQwMyIgeDE9IjcyIiB5MT0iMTQ0IiB4Mj0iLTEuMjgxN2UtMDUiIHkyPSI0IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiNDMEQ2RkYiLz4KPHN0b3Agb2Zmc2V0PSIwLjMiIHN0b3AtY29sb3I9IiNDNERBRkYiLz4KPHN0b3Agb2Zmc2V0PSIwLjY1IiBzdG9wLWNvbG9yPSIjRDNFOUZGIi8+CjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI0U5RkZGRiIvPgo8L2xpbmVhckdyYWRpZW50Pgo8Y2xpcFBhdGggaWQ9ImNsaXAwXzY4N18zNDAzIj4KPHJlY3Qgd2lkdGg9Ijc0IiBoZWlnaHQ9Ijg1IiBmaWxsPSJ3aGl0ZSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMzUgMjkpIi8+CjwvY2xpcFBhdGg+CjwvZGVmcz4KPC9zdmc+Cg== - keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "host"], ["spec", "etcd"], ["spec", "monitoring"], ["spec", "ingress"], ["spec", "seaweedfs"], ["spec", "isolated"], ["spec", "resourceQuotas"]] + keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "host"], ["spec", "etcd"], ["spec", "monitoring"], ["spec", "ingress"], ["spec", "seaweedfs"], ["spec", "resourceQuotas"]] secrets: exclude: [] include: [] From 9e94a699a0345db6a7dd5e31c5866f47ff359164 Mon Sep 17 00:00:00 2001 From: Andrey Kolkov Date: Sun, 18 Jan 2026 13:26:37 +0400 Subject: [PATCH 085/889] feat(backups): implement interface changes related backups class introduction Signed-off-by: Andrey Kolkov --- api/backups/v1alpha1/backup_types.go | 4 - api/backups/v1alpha1/backupclass_types.go | 85 +++++ api/backups/v1alpha1/backupjob_types.go | 12 +- api/backups/v1alpha1/plan_types.go | 12 +- api/backups/v1alpha1/zz_generated.deepcopy.go | 152 +++++++- .../backupcontroller/backupclass_resolver.go | 84 ++++ .../backupclass_resolver_test.go | 358 ++++++++++++++++++ .../backupcontroller/backupjob_controller.go | 35 +- .../backupcontroller/factory/backupjob.go | 23 +- .../factory/backupjob_test.go | 167 ++++++++ .../jobstrategy_controller.go | 3 +- internal/backupcontroller/plan_controller.go | 12 +- .../velerostrategy_controller.go | 328 ++-------------- 13 files changed, 929 insertions(+), 346 deletions(-) create mode 100644 api/backups/v1alpha1/backupclass_types.go create mode 100644 internal/backupcontroller/backupclass_resolver.go create mode 100644 internal/backupcontroller/backupclass_resolver_test.go create mode 100644 internal/backupcontroller/factory/backupjob_test.go diff --git a/api/backups/v1alpha1/backup_types.go b/api/backups/v1alpha1/backup_types.go index 2f18b34f..9371c27f 100644 --- a/api/backups/v1alpha1/backup_types.go +++ b/api/backups/v1alpha1/backup_types.go @@ -57,10 +57,6 @@ type BackupSpec struct { // +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"` diff --git a/api/backups/v1alpha1/backupclass_types.go b/api/backups/v1alpha1/backupclass_types.go new file mode 100644 index 00000000..19bcfd52 --- /dev/null +++ b/api/backups/v1alpha1/backupclass_types.go @@ -0,0 +1,85 @@ +// 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" + "k8s.io/apimachinery/pkg/runtime" +) + +func init() { + SchemeBuilder.Register(func(s *runtime.Scheme) error { + s.AddKnownTypes(GroupVersion, + &BackupClass{}, + &BackupClassList{}, + ) + return nil + }) +} + +// +kubebuilder:object:root=true +// +kubebuilder:resource:scope=Cluster +// +kubebuilder:subresource:status + +// BackupClass defines a class of backup configurations that can be referenced +// by BackupJob and Plan resources. It encapsulates strategy and storage configuration +// per application type. +type BackupClass struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec BackupClassSpec `json:"spec,omitempty"` + Status BackupClassStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// BackupClassList contains a list of BackupClasses. +type BackupClassList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []BackupClass `json:"items"` +} + +// BackupClassSpec defines the desired state of a BackupClass. +type BackupClassSpec struct { + // Strategies is a list of backup strategies, each matching a specific application type. + Strategies []BackupClassStrategy `json:"strategies"` +} + +// BackupClassStrategy defines a backup strategy for a specific application type. +type BackupClassStrategy struct { + // StrategyRef references the driver-specific BackupStrategy (e.g., Velero). + StrategyRef corev1.TypedLocalObjectReference `json:"strategyRef"` + + // Application specifies which application types this strategy applies to. + Application ApplicationSelector `json:"application"` + + // Parameters holds strategy-specific and storage-specific parameters. + // Common parameters include: + // - backupStorageLocationName: Name of Velero BackupStorageLocation + // +optional + Parameters map[string]string `json:"parameters,omitempty"` +} + +// ApplicationSelector specifies which application types a strategy applies to. +type ApplicationSelector struct { + // APIGroup is the API group of the application. + // If not specified, defaults to "apps.cozystack.io". + // +optional + APIGroup *string `json:"apiGroup,omitempty"` + + // Kind is the kind of the application (e.g., VirtualMachine, MySQL). + Kind string `json:"kind"` +} + +// BackupClassStatus defines the observed state of a BackupClass. +type BackupClassStatus struct { + // Conditions represents the latest available observations of a BackupClass's state. + // +optional + Conditions []metav1.Condition `json:"conditions,omitempty"` +} diff --git a/api/backups/v1alpha1/backupjob_types.go b/api/backups/v1alpha1/backupjob_types.go index c27b5347..f9aa42e6 100644 --- a/api/backups/v1alpha1/backupjob_types.go +++ b/api/backups/v1alpha1/backupjob_types.go @@ -46,15 +46,13 @@ type BackupJobSpec struct { // ApplicationRef holds a reference to the managed application whose state // is being backed up. + // If apiGroup is not specified, it defaults to "apps.cozystack.io". 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"` + // BackupClassName references a BackupClass that contains strategy and storage configuration. + // The BackupClass will be resolved to determine the appropriate strategy and storage + // based on the ApplicationRef. + BackupClassName string `json:"backupClassName"` } // BackupJobStatus represents the observed state of a BackupJob. diff --git a/api/backups/v1alpha1/plan_types.go b/api/backups/v1alpha1/plan_types.go index c59d5eab..df807eb5 100644 --- a/api/backups/v1alpha1/plan_types.go +++ b/api/backups/v1alpha1/plan_types.go @@ -65,15 +65,13 @@ type PlanList struct { type PlanSpec struct { // ApplicationRef holds a reference to the managed application, // whose state and configuration must be backed up. + // If apiGroup is not specified, it defaults to "apps.cozystack.io". 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"` + // BackupClassName references a BackupClass that contains strategy and storage configuration. + // The BackupClass will be resolved to determine the appropriate strategy and storage + // based on the ApplicationRef. + BackupClassName string `json:"backupClassName"` // Schedule specifies when backup copies are created. Schedule PlanSchedule `json:"schedule"` diff --git a/api/backups/v1alpha1/zz_generated.deepcopy.go b/api/backups/v1alpha1/zz_generated.deepcopy.go index fe825947..6fbc1d16 100644 --- a/api/backups/v1alpha1/zz_generated.deepcopy.go +++ b/api/backups/v1alpha1/zz_generated.deepcopy.go @@ -136,8 +136,6 @@ func (in *BackupJobSpec) DeepCopyInto(out *BackupJobSpec) { **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. @@ -226,7 +224,6 @@ func (in *BackupSpec) DeepCopyInto(out *BackupSpec) { *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 { @@ -353,8 +350,6 @@ func (in *PlanSchedule) DeepCopy() *PlanSchedule { 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 } @@ -499,3 +494,150 @@ func (in *RestoreJobStatus) DeepCopy() *RestoreJobStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackupClass) DeepCopyInto(out *BackupClass) { + *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 BackupClass. +func (in *BackupClass) DeepCopy() *BackupClass { + if in == nil { + return nil + } + out := new(BackupClass) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BackupClass) 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 *BackupClassList) DeepCopyInto(out *BackupClassList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]BackupClass, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupClassList. +func (in *BackupClassList) DeepCopy() *BackupClassList { + if in == nil { + return nil + } + out := new(BackupClassList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BackupClassList) 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 *BackupClassSpec) DeepCopyInto(out *BackupClassSpec) { + *out = *in + if in.Strategies != nil { + in, out := &in.Strategies, &out.Strategies + *out = make([]BackupClassStrategy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupClassSpec. +func (in *BackupClassSpec) DeepCopy() *BackupClassSpec { + if in == nil { + return nil + } + out := new(BackupClassSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackupClassStrategy) DeepCopyInto(out *BackupClassStrategy) { + *out = *in + in.StrategyRef.DeepCopyInto(&out.StrategyRef) + in.Application.DeepCopyInto(&out.Application) + if in.Parameters != nil { + in, out := &in.Parameters, &out.Parameters + *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 BackupClassStrategy. +func (in *BackupClassStrategy) DeepCopy() *BackupClassStrategy { + if in == nil { + return nil + } + out := new(BackupClassStrategy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApplicationSelector) DeepCopyInto(out *ApplicationSelector) { + *out = *in + if in.APIGroup != nil { + in, out := &in.APIGroup, &out.APIGroup + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationSelector. +func (in *ApplicationSelector) DeepCopy() *ApplicationSelector { + if in == nil { + return nil + } + out := new(ApplicationSelector) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackupClassStatus) DeepCopyInto(out *BackupClassStatus) { + *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 BackupClassStatus. +func (in *BackupClassStatus) DeepCopy() *BackupClassStatus { + if in == nil { + return nil + } + out := new(BackupClassStatus) + in.DeepCopyInto(out) + return out +} diff --git a/internal/backupcontroller/backupclass_resolver.go b/internal/backupcontroller/backupclass_resolver.go new file mode 100644 index 00000000..c07e778c --- /dev/null +++ b/internal/backupcontroller/backupclass_resolver.go @@ -0,0 +1,84 @@ +package backupcontroller + +import ( + "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1" +) + +const ( + // DefaultApplicationAPIGroup is the default API group for applications + // when not specified in ApplicationRef or ApplicationSelector. + DefaultApplicationAPIGroup = "apps.cozystack.io" +) + +// NormalizeApplicationRef sets the default apiGroup to "apps.cozystack.io" if it's not specified. +// This function is exported so it can be used by other packages (e.g., factory). +func NormalizeApplicationRef(ref corev1.TypedLocalObjectReference) corev1.TypedLocalObjectReference { + if ref.APIGroup == nil || *ref.APIGroup == "" { + defaultGroup := DefaultApplicationAPIGroup + ref.APIGroup = &defaultGroup + } + return ref +} + +// normalizeApplicationRef is an internal alias for consistency within this package. +func normalizeApplicationRef(ref corev1.TypedLocalObjectReference) corev1.TypedLocalObjectReference { + return NormalizeApplicationRef(ref) +} + +// ResolvedBackupConfig contains the resolved strategy and storage configuration +// from a BackupClass. +type ResolvedBackupConfig struct { + StrategyRef corev1.TypedLocalObjectReference + StorageRef *corev1.TypedLocalObjectReference // Optional, may come from parameters + Parameters map[string]string +} + +// ResolveBackupClass resolves a BackupClass and finds the matching strategy for the given application. +// It normalizes the applicationRef's apiGroup (defaults to apps.cozystack.io if not specified) +// and matches it against the strategies in the BackupClass. +func ResolveBackupClass( + ctx context.Context, + c client.Client, + backupClassName string, + applicationRef corev1.TypedLocalObjectReference, +) (*ResolvedBackupConfig, error) { + // Normalize applicationRef (default apiGroup if not specified) + applicationRef = normalizeApplicationRef(applicationRef) + + // Get BackupClass + backupClass := &backupsv1alpha1.BackupClass{} + if err := c.Get(ctx, client.ObjectKey{Name: backupClassName}, backupClass); err != nil { + return nil, fmt.Errorf("failed to get BackupClass %s: %w", backupClassName, err) + } + + // Determine application API group (already normalized, but extract for matching) + appAPIGroup := DefaultApplicationAPIGroup + if applicationRef.APIGroup != nil { + appAPIGroup = *applicationRef.APIGroup + } + + // Find matching strategy + for _, strategy := range backupClass.Spec.Strategies { + // Normalize strategy's application selector (default apiGroup if not specified) + strategyAPIGroup := DefaultApplicationAPIGroup + if strategy.Application.APIGroup != nil && *strategy.Application.APIGroup != "" { + strategyAPIGroup = *strategy.Application.APIGroup + } + + if strategyAPIGroup == appAPIGroup && strategy.Application.Kind == applicationRef.Kind { + return &ResolvedBackupConfig{ + StrategyRef: strategy.StrategyRef, + Parameters: strategy.Parameters, + }, nil + } + } + + return nil, fmt.Errorf("no matching strategy found in BackupClass %s for application %s/%s", + backupClassName, appAPIGroup, applicationRef.Kind) +} diff --git a/internal/backupcontroller/backupclass_resolver_test.go b/internal/backupcontroller/backupclass_resolver_test.go new file mode 100644 index 00000000..4c2b03fc --- /dev/null +++ b/internal/backupcontroller/backupclass_resolver_test.go @@ -0,0 +1,358 @@ +package backupcontroller + +import ( + "context" + "testing" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1" + strategyv1alpha1 "github.com/cozystack/cozystack/api/backups/strategy/v1alpha1" +) + +func TestNormalizeApplicationRef(t *testing.T) { + tests := []struct { + name string + input corev1.TypedLocalObjectReference + expected string + }{ + { + name: "apiGroup not specified - should default to apps.cozystack.io", + input: corev1.TypedLocalObjectReference{ + Kind: "VirtualMachine", + Name: "vm1", + }, + expected: DefaultApplicationAPIGroup, + }, + { + name: "apiGroup is nil - should default to apps.cozystack.io", + input: corev1.TypedLocalObjectReference{ + APIGroup: nil, + Kind: "VirtualMachine", + Name: "vm1", + }, + expected: DefaultApplicationAPIGroup, + }, + { + name: "apiGroup is empty string - should default to apps.cozystack.io", + input: corev1.TypedLocalObjectReference{ + APIGroup: stringPtr(""), + Kind: "VirtualMachine", + Name: "vm1", + }, + expected: DefaultApplicationAPIGroup, + }, + { + name: "apiGroup is explicitly set - should keep it", + input: corev1.TypedLocalObjectReference{ + APIGroup: stringPtr("custom.api.group.io"), + Kind: "CustomApp", + Name: "custom-app", + }, + expected: "custom.api.group.io", + }, + { + name: "apiGroup is apps.cozystack.io - should keep it", + input: corev1.TypedLocalObjectReference{ + APIGroup: stringPtr(DefaultApplicationAPIGroup), + Kind: "VirtualMachine", + Name: "vm1", + }, + expected: DefaultApplicationAPIGroup, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := NormalizeApplicationRef(tt.input) + if result.APIGroup == nil { + t.Errorf("NormalizeApplicationRef() returned nil APIGroup, expected %s", tt.expected) + return + } + if *result.APIGroup != tt.expected { + t.Errorf("NormalizeApplicationRef() APIGroup = %v, want %v", *result.APIGroup, tt.expected) + } + // Verify other fields are preserved + if result.Kind != tt.input.Kind { + t.Errorf("NormalizeApplicationRef() Kind = %v, want %v", result.Kind, tt.input.Kind) + } + if result.Name != tt.input.Name { + t.Errorf("NormalizeApplicationRef() Name = %v, want %v", result.Name, tt.input.Name) + } + }) + } +} + +func TestResolveBackupClass(t *testing.T) { + scheme := runtime.NewScheme() + err := backupsv1alpha1.AddToScheme(scheme) + if err != nil { + t.Fatalf("Failed to add backupsv1alpha1 to scheme: %v", err) + } + err = strategyv1alpha1.AddToScheme(scheme) + if err != nil { + t.Fatalf("Failed to add strategyv1alpha1 to scheme: %v", err) + } + + tests := []struct { + name string + backupClass *backupsv1alpha1.BackupClass + applicationRef corev1.TypedLocalObjectReference + backupClassName string + wantErr bool + expectedKind string + expectedParams map[string]string + }{ + { + name: "successful resolution - matches VirtualMachine strategy", + backupClass: &backupsv1alpha1.BackupClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "velero", + }, + Spec: backupsv1alpha1.BackupClassSpec{ + Strategies: []backupsv1alpha1.BackupClassStrategy{ + { + StrategyRef: corev1.TypedLocalObjectReference{ + APIGroup: stringPtr("strategy.backups.cozystack.io"), + Kind: "Velero", + Name: "velero-strategy-vm", + }, + Application: backupsv1alpha1.ApplicationSelector{ + Kind: "VirtualMachine", + }, + Parameters: map[string]string{ + "backupStorageLocationName": "default", + }, + }, + { + StrategyRef: corev1.TypedLocalObjectReference{ + APIGroup: stringPtr("strategy.backups.cozystack.io"), + Kind: "Velero", + Name: "velero-strategy-mysql", + }, + Application: backupsv1alpha1.ApplicationSelector{ + Kind: "MySQL", + }, + Parameters: map[string]string{ + "backupStorageLocationName": "mysql-storage", + }, + }, + }, + }, + }, + applicationRef: corev1.TypedLocalObjectReference{ + Kind: "VirtualMachine", + Name: "vm1", + }, + backupClassName: "velero", + wantErr: false, + expectedKind: "VirtualMachine", + expectedParams: map[string]string{ + "backupStorageLocationName": "default", + }, + }, + { + name: "successful resolution - matches MySQL strategy with explicit apiGroup", + backupClass: &backupsv1alpha1.BackupClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "velero", + }, + Spec: backupsv1alpha1.BackupClassSpec{ + Strategies: []backupsv1alpha1.BackupClassStrategy{ + { + StrategyRef: corev1.TypedLocalObjectReference{ + APIGroup: stringPtr("strategy.backups.cozystack.io"), + Kind: "Velero", + Name: "velero-strategy-mysql", + }, + Application: backupsv1alpha1.ApplicationSelector{ + APIGroup: stringPtr("apps.cozystack.io"), + Kind: "MySQL", + }, + Parameters: map[string]string{ + "backupStorageLocationName": "mysql-storage", + }, + }, + }, + }, + }, + applicationRef: corev1.TypedLocalObjectReference{ + APIGroup: stringPtr("apps.cozystack.io"), + Kind: "MySQL", + Name: "mysql1", + }, + backupClassName: "velero", + wantErr: false, + expectedKind: "MySQL", + expectedParams: map[string]string{ + "backupStorageLocationName": "mysql-storage", + }, + }, + { + name: "successful resolution - applicationRef without apiGroup defaults correctly", + backupClass: &backupsv1alpha1.BackupClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "velero", + }, + Spec: backupsv1alpha1.BackupClassSpec{ + Strategies: []backupsv1alpha1.BackupClassStrategy{ + { + StrategyRef: corev1.TypedLocalObjectReference{ + APIGroup: stringPtr("strategy.backups.cozystack.io"), + Kind: "Velero", + Name: "velero-strategy-vm", + }, + Application: backupsv1alpha1.ApplicationSelector{ + Kind: "VirtualMachine", + }, + Parameters: map[string]string{ + "backupStorageLocationName": "default", + }, + }, + }, + }, + }, + applicationRef: corev1.TypedLocalObjectReference{ + // No APIGroup specified + Kind: "VirtualMachine", + Name: "vm1", + }, + backupClassName: "velero", + wantErr: false, + expectedKind: "VirtualMachine", + expectedParams: map[string]string{ + "backupStorageLocationName": "default", + }, + }, + { + name: "error - BackupClass not found", + backupClass: &backupsv1alpha1.BackupClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "velero", + }, + Spec: backupsv1alpha1.BackupClassSpec{ + Strategies: []backupsv1alpha1.BackupClassStrategy{}, + }, + }, + applicationRef: corev1.TypedLocalObjectReference{ + Kind: "VirtualMachine", + Name: "vm1", + }, + backupClassName: "nonexistent", + wantErr: true, + }, + { + name: "error - no matching strategy found", + backupClass: &backupsv1alpha1.BackupClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "velero", + }, + Spec: backupsv1alpha1.BackupClassSpec{ + Strategies: []backupsv1alpha1.BackupClassStrategy{ + { + StrategyRef: corev1.TypedLocalObjectReference{ + APIGroup: stringPtr("strategy.backups.cozystack.io"), + Kind: "Velero", + Name: "velero-strategy-vm", + }, + Application: backupsv1alpha1.ApplicationSelector{ + Kind: "VirtualMachine", + }, + }, + }, + }, + }, + applicationRef: corev1.TypedLocalObjectReference{ + Kind: "PostgreSQL", // Not in BackupClass + Name: "pg1", + }, + backupClassName: "velero", + wantErr: true, + }, + { + name: "error - apiGroup mismatch", + backupClass: &backupsv1alpha1.BackupClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "velero", + }, + Spec: backupsv1alpha1.BackupClassSpec{ + Strategies: []backupsv1alpha1.BackupClassStrategy{ + { + StrategyRef: corev1.TypedLocalObjectReference{ + APIGroup: stringPtr("strategy.backups.cozystack.io"), + Kind: "Velero", + Name: "velero-strategy-vm", + }, + Application: backupsv1alpha1.ApplicationSelector{ + APIGroup: stringPtr("custom.api.group.io"), + Kind: "VirtualMachine", + }, + }, + }, + }, + }, + applicationRef: corev1.TypedLocalObjectReference{ + APIGroup: stringPtr("apps.cozystack.io"), // Different apiGroup + Kind: "VirtualMachine", + Name: "vm1", + }, + backupClassName: "velero", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + fakeClient := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(tt.backupClass). + Build() + + resolved, err := ResolveBackupClass(ctx, fakeClient, tt.backupClassName, tt.applicationRef) + + if tt.wantErr { + if err == nil { + t.Errorf("ResolveBackupClass() expected error but got none") + } + return + } + + if err != nil { + t.Errorf("ResolveBackupClass() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if resolved == nil { + t.Errorf("ResolveBackupClass() returned nil, expected ResolvedBackupConfig") + return + } + + // Verify strategy ref + if resolved.StrategyRef.Kind != "Velero" { + t.Errorf("ResolveBackupClass() StrategyRef.Kind = %v, want Velero", resolved.StrategyRef.Kind) + } + + // Verify parameters + if tt.expectedParams != nil { + for key, expectedValue := range tt.expectedParams { + actualValue, ok := resolved.Parameters[key] + if !ok { + t.Errorf("ResolveBackupClass() Parameters[%s] not found", key) + continue + } + if actualValue != expectedValue { + t.Errorf("ResolveBackupClass() Parameters[%s] = %v, want %v", key, actualValue, expectedValue) + } + } + } + }) + } +} + +func stringPtr(s string) *string { + return &s +} diff --git a/internal/backupcontroller/backupjob_controller.go b/internal/backupcontroller/backupjob_controller.go index 0db2e1dd..636b73c7 100644 --- a/internal/backupcontroller/backupjob_controller.go +++ b/internal/backupcontroller/backupjob_controller.go @@ -45,29 +45,42 @@ func (r *BackupJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( return ctrl.Result{}, err } - if j.Spec.StrategyRef.APIGroup == nil { - logger.V(1).Info("BackupJob has nil StrategyRef.APIGroup, skipping", "backupjob", j.Name) + // Normalize ApplicationRef (default apiGroup if not specified) + normalizedAppRef := normalizeApplicationRef(j.Spec.ApplicationRef) + + // Resolve BackupClass + resolved, err := ResolveBackupClass(ctx, r.Client, j.Spec.BackupClassName, normalizedAppRef) + if err != nil { + logger.Error(err, "failed to resolve BackupClass", "backupClassName", j.Spec.BackupClassName) + return ctrl.Result{}, err + } + + strategyRef := resolved.StrategyRef + + // Validate strategyRef + if strategyRef.APIGroup == nil { + logger.V(1).Info("BackupJob resolved StrategyRef has nil 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", + if *strategyRef.APIGroup != strategyv1alpha1.GroupVersion.Group { + logger.V(1).Info("BackupJob resolved StrategyRef.APIGroup doesn't match, skipping", "backupjob", j.Name, "expected", strategyv1alpha1.GroupVersion.Group, - "got", *j.Spec.StrategyRef.APIGroup) + "got", *strategyRef.APIGroup) return ctrl.Result{}, nil } - logger.Info("processing BackupJob", "backupjob", j.Name, "strategyKind", j.Spec.StrategyRef.Kind) - switch j.Spec.StrategyRef.Kind { + logger.Info("processing BackupJob", "backupjob", j.Name, "strategyKind", strategyRef.Kind, "backupClassName", j.Spec.BackupClassName) + switch strategyRef.Kind { case strategyv1alpha1.JobStrategyKind: - return r.reconcileJob(ctx, j) + return r.reconcileJob(ctx, j, resolved) case strategyv1alpha1.VeleroStrategyKind: - return r.reconcileVelero(ctx, j) + return r.reconcileVelero(ctx, j, resolved) default: - logger.V(1).Info("BackupJob StrategyRef.Kind not supported, skipping", + logger.V(1).Info("BackupJob resolved StrategyRef.Kind not supported, skipping", "backupjob", j.Name, - "kind", j.Spec.StrategyRef.Kind, + "kind", strategyRef.Kind, "supported", []string{strategyv1alpha1.JobStrategyKind, strategyv1alpha1.VeleroStrategyKind}) return ctrl.Result{}, nil } diff --git a/internal/backupcontroller/factory/backupjob.go b/internal/backupcontroller/factory/backupjob.go index 722e5d99..11741946 100644 --- a/internal/backupcontroller/factory/backupjob.go +++ b/internal/backupcontroller/factory/backupjob.go @@ -9,7 +9,25 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +const ( + // defaultApplicationAPIGroup is the default API group for applications + // when not specified in ApplicationRef. + defaultApplicationAPIGroup = "apps.cozystack.io" +) + +// normalizeApplicationRef sets the default apiGroup to "apps.cozystack.io" if it's not specified. +func normalizeApplicationRef(ref corev1.TypedLocalObjectReference) corev1.TypedLocalObjectReference { + if ref.APIGroup == nil || *ref.APIGroup == "" { + defaultGroup := defaultApplicationAPIGroup + ref.APIGroup = &defaultGroup + } + return ref +} + func BackupJob(p *backupsv1alpha1.Plan, scheduledFor time.Time) *backupsv1alpha1.BackupJob { + // Normalize ApplicationRef (default apiGroup if not specified) + appRef := normalizeApplicationRef(*p.Spec.ApplicationRef.DeepCopy()) + job := &backupsv1alpha1.BackupJob{ ObjectMeta: metav1.ObjectMeta{ Name: fmt.Sprintf("%s-%d", p.Name, scheduledFor.Unix()/60), @@ -19,9 +37,8 @@ func BackupJob(p *backupsv1alpha1.Plan, scheduledFor time.Time) *backupsv1alpha1 PlanRef: &corev1.LocalObjectReference{ Name: p.Name, }, - ApplicationRef: *p.Spec.ApplicationRef.DeepCopy(), - StorageRef: *p.Spec.StorageRef.DeepCopy(), - StrategyRef: *p.Spec.StrategyRef.DeepCopy(), + ApplicationRef: appRef, + BackupClassName: p.Spec.BackupClassName, }, } return job diff --git a/internal/backupcontroller/factory/backupjob_test.go b/internal/backupcontroller/factory/backupjob_test.go new file mode 100644 index 00000000..2fab5ff8 --- /dev/null +++ b/internal/backupcontroller/factory/backupjob_test.go @@ -0,0 +1,167 @@ +package factory + +import ( + "testing" + "time" + + backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestBackupJob(t *testing.T) { + tests := []struct { + name string + plan *backupsv1alpha1.Plan + scheduled time.Time + validate func(*testing.T, *backupsv1alpha1.BackupJob) + }{ + { + name: "creates BackupJob with BackupClassName", + plan: &backupsv1alpha1.Plan{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-plan", + Namespace: "default", + }, + Spec: backupsv1alpha1.PlanSpec{ + ApplicationRef: corev1.TypedLocalObjectReference{ + Kind: "VirtualMachine", + Name: "vm1", + }, + BackupClassName: "velero", + Schedule: backupsv1alpha1.PlanSchedule{ + Type: backupsv1alpha1.PlanScheduleTypeCron, + Cron: "0 2 * * *", + }, + }, + }, + scheduled: time.Date(2024, 1, 1, 2, 0, 0, 0, time.UTC), + validate: func(t *testing.T, job *backupsv1alpha1.BackupJob) { + if job.Name == "" { + t.Error("BackupJob name should be set") + } + if job.Namespace != "default" { + t.Errorf("BackupJob namespace = %v, want default", job.Namespace) + } + if job.Spec.BackupClassName != "velero" { + t.Errorf("BackupJob BackupClassName = %v, want velero", job.Spec.BackupClassName) + } + if job.Spec.ApplicationRef.Kind != "VirtualMachine" { + t.Errorf("BackupJob ApplicationRef.Kind = %v, want VirtualMachine", job.Spec.ApplicationRef.Kind) + } + if job.Spec.ApplicationRef.Name != "vm1" { + t.Errorf("BackupJob ApplicationRef.Name = %v, want vm1", job.Spec.ApplicationRef.Name) + } + if job.Spec.PlanRef == nil || job.Spec.PlanRef.Name != "test-plan" { + t.Errorf("BackupJob PlanRef = %v, want {Name: test-plan}", job.Spec.PlanRef) + } + }, + }, + { + name: "normalizes ApplicationRef apiGroup", + plan: &backupsv1alpha1.Plan{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-plan", + Namespace: "default", + }, + Spec: backupsv1alpha1.PlanSpec{ + ApplicationRef: corev1.TypedLocalObjectReference{ + // No APIGroup specified + Kind: "MySQL", + Name: "mysql1", + }, + BackupClassName: "velero", + Schedule: backupsv1alpha1.PlanSchedule{ + Type: backupsv1alpha1.PlanScheduleTypeCron, + Cron: "0 2 * * *", + }, + }, + }, + scheduled: time.Date(2024, 1, 1, 2, 0, 0, 0, time.UTC), + validate: func(t *testing.T, job *backupsv1alpha1.BackupJob) { + if job.Spec.ApplicationRef.APIGroup == nil { + t.Error("BackupJob ApplicationRef.APIGroup should be set (normalized)") + return + } + if *job.Spec.ApplicationRef.APIGroup != "apps.cozystack.io" { + t.Errorf("BackupJob ApplicationRef.APIGroup = %v, want apps.cozystack.io", *job.Spec.ApplicationRef.APIGroup) + } + }, + }, + { + name: "preserves explicit ApplicationRef apiGroup", + plan: &backupsv1alpha1.Plan{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-plan", + Namespace: "default", + }, + Spec: backupsv1alpha1.PlanSpec{ + ApplicationRef: corev1.TypedLocalObjectReference{ + APIGroup: stringPtr("custom.api.group.io"), + Kind: "CustomApp", + Name: "custom1", + }, + BackupClassName: "velero", + Schedule: backupsv1alpha1.PlanSchedule{ + Type: backupsv1alpha1.PlanScheduleTypeCron, + Cron: "0 2 * * *", + }, + }, + }, + scheduled: time.Date(2024, 1, 1, 2, 0, 0, 0, time.UTC), + validate: func(t *testing.T, job *backupsv1alpha1.BackupJob) { + if job.Spec.ApplicationRef.APIGroup == nil { + t.Error("BackupJob ApplicationRef.APIGroup should be preserved") + return + } + if *job.Spec.ApplicationRef.APIGroup != "custom.api.group.io" { + t.Errorf("BackupJob ApplicationRef.APIGroup = %v, want custom.api.group.io", *job.Spec.ApplicationRef.APIGroup) + } + }, + }, + { + name: "generates unique job name based on timestamp", + plan: &backupsv1alpha1.Plan{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-plan", + Namespace: "default", + }, + Spec: backupsv1alpha1.PlanSpec{ + ApplicationRef: corev1.TypedLocalObjectReference{ + Kind: "VirtualMachine", + Name: "vm1", + }, + BackupClassName: "velero", + Schedule: backupsv1alpha1.PlanSchedule{ + Type: backupsv1alpha1.PlanScheduleTypeCron, + Cron: "0 2 * * *", + }, + }, + }, + scheduled: time.Date(2024, 1, 1, 2, 0, 0, 0, time.UTC), + validate: func(t *testing.T, job *backupsv1alpha1.BackupJob) { + if job.Name == "" { + t.Error("BackupJob name should be generated") + } + // Name should start with plan name + if len(job.Name) < len("test-plan") { + t.Errorf("BackupJob name = %v, should start with test-plan", job.Name) + } + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + job := BackupJob(tt.plan, tt.scheduled) + if job == nil { + t.Fatal("BackupJob() returned nil") + } + tt.validate(t, job) + }) + } +} + +func stringPtr(s string) *string { + return &s +} diff --git a/internal/backupcontroller/jobstrategy_controller.go b/internal/backupcontroller/jobstrategy_controller.go index e52e38f2..1ff4df59 100644 --- a/internal/backupcontroller/jobstrategy_controller.go +++ b/internal/backupcontroller/jobstrategy_controller.go @@ -9,7 +9,8 @@ import ( backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1" ) -func (r *BackupJobReconciler) reconcileJob(ctx context.Context, j *backupsv1alpha1.BackupJob) (ctrl.Result, error) { +func (r *BackupJobReconciler) reconcileJob(ctx context.Context, j *backupsv1alpha1.BackupJob, resolved *ResolvedBackupConfig) (ctrl.Result, error) { _ = log.FromContext(ctx) + _ = resolved // TODO: Use resolved config when implementing job strategy return ctrl.Result{}, nil } diff --git a/internal/backupcontroller/plan_controller.go b/internal/backupcontroller/plan_controller.go index 046fdce4..4945802d 100644 --- a/internal/backupcontroller/plan_controller.go +++ b/internal/backupcontroller/plan_controller.go @@ -20,8 +20,8 @@ import ( ) const ( - minRequeueDelay = 30 * time.Second - startingDeadlineSeconds = 300 * time.Second + minRequeueDelay = 30 * time.Second + startingDeadline = 300 * time.Second ) // PlanReconciler reconciles a Plan object @@ -45,7 +45,7 @@ func (r *PlanReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl. return ctrl.Result{}, err } - tCheck := time.Now().Add(-startingDeadlineSeconds) + tCheck := time.Now().Add(-startingDeadline) 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) @@ -78,7 +78,7 @@ func (r *PlanReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl. tNext := sch.Next(tCheck) if time.Now().Before(tNext) { - return ctrl.Result{RequeueAfter: tNext.Sub(time.Now())}, nil + return ctrl.Result{RequeueAfter: time.Until(tNext)}, nil } job := factory.BackupJob(p, tNext) @@ -88,12 +88,12 @@ func (r *PlanReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl. if err := r.Create(ctx, job); err != nil { if apierrors.IsAlreadyExists(err) { - return ctrl.Result{RequeueAfter: startingDeadlineSeconds}, nil + return ctrl.Result{RequeueAfter: startingDeadline}, nil } return ctrl.Result{}, err } - return ctrl.Result{RequeueAfter: startingDeadlineSeconds}, nil + return ctrl.Result{RequeueAfter: startingDeadline}, nil } // SetupWithManager registers our controller with the Manager and sets up watches. diff --git a/internal/backupcontroller/velerostrategy_controller.go b/internal/backupcontroller/velerostrategy_controller.go index 066325c0..0a023356 100644 --- a/internal/backupcontroller/velerostrategy_controller.go +++ b/internal/backupcontroller/velerostrategy_controller.go @@ -2,16 +2,13 @@ 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" @@ -49,19 +46,6 @@ type S3Credentials struct { 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 @@ -70,15 +54,11 @@ const ( 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) { +func (r *BackupJobReconciler) reconcileVelero(ctx context.Context, j *backupsv1alpha1.BackupJob, resolved *ResolvedBackupConfig) (ctrl.Result, error) { logger := getLogger(ctx) logger.Debug("reconciling Velero strategy", "backupjob", j.Name, "phase", j.Status.Phase) @@ -105,13 +85,13 @@ func (r *BackupJobReconciler) reconcileVelero(ctx context.Context, j *backupsv1a 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) + // Step 2: Resolve inputs - Read Strategy from resolved config + logger.Debug("fetching Velero strategy", "strategyName", resolved.StrategyRef.Name) veleroStrategy := &strategyv1alpha1.Velero{} - if err := r.Get(ctx, client.ObjectKey{Name: j.Spec.StrategyRef.Name}, veleroStrategy); err != nil { + if err := r.Get(ctx, client.ObjectKey{Name: resolved.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, "Velero strategy not found", "strategyName", resolved.StrategyRef.Name) + return r.markBackupJobFailed(ctx, j, fmt.Sprintf("Velero strategy not found: %s", resolved.StrategyRef.Name)) } logger.Error(err, "failed to get Velero strategy") return ctrl.Result{}, err @@ -195,7 +175,7 @@ func (r *BackupJobReconciler) reconcileVelero(ctx context.Context, j *backupsv1a if phase == "Completed" { // Check if we already created the Backup resource if j.Status.BackupRef == nil { - backup, err := r.createBackupResource(ctx, j, veleroBackup) + backup, err := r.createBackupResource(ctx, j, veleroBackup, resolved) if err != nil { return r.markBackupJobFailed(ctx, j, fmt.Sprintf("failed to create Backup resource: %v", err)) } @@ -226,267 +206,12 @@ func (r *BackupJobReconciler) reconcileVelero(ctx context.Context, j *backupsv1a 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 -} +// NOTE: The following functions were removed as they are no longer used after migrating to BackupClass API: +// - resolveBucketStorageRef: Previously resolved S3 credentials from Bucket storageRef +// - createS3CredsForVelero: Previously created Velero S3 credentials secrets +// - createBackupStorageLocation: Previously created Velero BackupStorageLocation resources +// - createVolumeSnapshotLocation: Previously created Velero VolumeSnapshotLocation resources +// These functions may be needed in the future if we decide to support StorageRef resolution through BackupClass. func (r *BackupJobReconciler) markBackupJobFailed(ctx context.Context, backupJob *backupsv1alpha1.BackupJob, message string) (ctrl.Result, error) { logger := getLogger(ctx) @@ -561,13 +286,9 @@ func (r *BackupJobReconciler) createVeleroBackup(ctx context.Context, backupJob return nil } -func (r *BackupJobReconciler) createBackupResource(ctx context.Context, backupJob *backupsv1alpha1.BackupJob, veleroBackup *velerov1.Backup) (*backupsv1alpha1.Backup, error) { +func (r *BackupJobReconciler) createBackupResource(ctx context.Context, backupJob *backupsv1alpha1.BackupJob, veleroBackup *velerov1.Backup, resolved *ResolvedBackupConfig) (*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), - } + _ = logger // logger may be used in future // Get takenAt from Velero Backup creation timestamp or status takenAt := metav1.Now() @@ -583,9 +304,14 @@ func (r *BackupJobReconciler) createBackupResource(ctx context.Context, backupJo "velero.io/backup-namespace": veleroBackup.Namespace, } + // Create a basic artifact referencing the Velero backup + artifact := &backupsv1alpha1.BackupArtifact{ + URI: fmt.Sprintf("velero://%s/%s", backupJob.Namespace, veleroBackup.Name), + } + backup := &backupsv1alpha1.Backup{ ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%s", backupJob.Name), + Name: backupJob.Name, Namespace: backupJob.Namespace, OwnerReferences: []metav1.OwnerReference{ { @@ -599,13 +325,15 @@ func (r *BackupJobReconciler) createBackupResource(ctx context.Context, backupJo }, Spec: backupsv1alpha1.BackupSpec{ ApplicationRef: backupJob.Spec.ApplicationRef, - StorageRef: backupJob.Spec.StorageRef, - StrategyRef: backupJob.Spec.StrategyRef, + // StorageRef is not set as it's now resolved from BackupClass parameters + // The storage location is managed via Velero's BackupStorageLocation + StrategyRef: resolved.StrategyRef, TakenAt: takenAt, DriverMetadata: driverMetadata, }, Status: backupsv1alpha1.BackupStatus{ - Phase: backupsv1alpha1.BackupPhaseReady, + Phase: backupsv1alpha1.BackupPhaseReady, + Artifact: artifact, }, } @@ -613,10 +341,6 @@ func (r *BackupJobReconciler) createBackupResource(ctx context.Context, backupJo 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 From 41646b253e60726a06dae16a0f3fbeb272e6b7f9 Mon Sep 17 00:00:00 2001 From: Andrey Kolkov Date: Mon, 19 Jan 2026 13:09:25 +0400 Subject: [PATCH 086/889] use everywhere NormalizeApplicationRef Signed-off-by: Andrey Kolkov --- .../backupcontroller/backupclass_resolver.go | 11 +++------- .../backupcontroller/backupjob_controller.go | 2 +- .../backupcontroller/factory/backupjob.go | 20 +++---------------- 3 files changed, 7 insertions(+), 26 deletions(-) diff --git a/internal/backupcontroller/backupclass_resolver.go b/internal/backupcontroller/backupclass_resolver.go index c07e778c..20a14ba4 100644 --- a/internal/backupcontroller/backupclass_resolver.go +++ b/internal/backupcontroller/backupclass_resolver.go @@ -20,17 +20,12 @@ const ( // This function is exported so it can be used by other packages (e.g., factory). func NormalizeApplicationRef(ref corev1.TypedLocalObjectReference) corev1.TypedLocalObjectReference { if ref.APIGroup == nil || *ref.APIGroup == "" { - defaultGroup := DefaultApplicationAPIGroup - ref.APIGroup = &defaultGroup + apiGroup := DefaultApplicationAPIGroup + ref.APIGroup = &apiGroup } return ref } -// normalizeApplicationRef is an internal alias for consistency within this package. -func normalizeApplicationRef(ref corev1.TypedLocalObjectReference) corev1.TypedLocalObjectReference { - return NormalizeApplicationRef(ref) -} - // ResolvedBackupConfig contains the resolved strategy and storage configuration // from a BackupClass. type ResolvedBackupConfig struct { @@ -49,7 +44,7 @@ func ResolveBackupClass( applicationRef corev1.TypedLocalObjectReference, ) (*ResolvedBackupConfig, error) { // Normalize applicationRef (default apiGroup if not specified) - applicationRef = normalizeApplicationRef(applicationRef) + applicationRef = NormalizeApplicationRef(applicationRef) // Get BackupClass backupClass := &backupsv1alpha1.BackupClass{} diff --git a/internal/backupcontroller/backupjob_controller.go b/internal/backupcontroller/backupjob_controller.go index 636b73c7..db7e701a 100644 --- a/internal/backupcontroller/backupjob_controller.go +++ b/internal/backupcontroller/backupjob_controller.go @@ -46,7 +46,7 @@ func (r *BackupJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( } // Normalize ApplicationRef (default apiGroup if not specified) - normalizedAppRef := normalizeApplicationRef(j.Spec.ApplicationRef) + normalizedAppRef := NormalizeApplicationRef(j.Spec.ApplicationRef) // Resolve BackupClass resolved, err := ResolveBackupClass(ctx, r.Client, j.Spec.BackupClassName, normalizedAppRef) diff --git a/internal/backupcontroller/factory/backupjob.go b/internal/backupcontroller/factory/backupjob.go index 11741946..cf8ae544 100644 --- a/internal/backupcontroller/factory/backupjob.go +++ b/internal/backupcontroller/factory/backupjob.go @@ -5,28 +5,14 @@ import ( "time" backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1" + "github.com/cozystack/cozystack/internal/backupcontroller" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -const ( - // defaultApplicationAPIGroup is the default API group for applications - // when not specified in ApplicationRef. - defaultApplicationAPIGroup = "apps.cozystack.io" -) - -// normalizeApplicationRef sets the default apiGroup to "apps.cozystack.io" if it's not specified. -func normalizeApplicationRef(ref corev1.TypedLocalObjectReference) corev1.TypedLocalObjectReference { - if ref.APIGroup == nil || *ref.APIGroup == "" { - defaultGroup := defaultApplicationAPIGroup - ref.APIGroup = &defaultGroup - } - return ref -} - func BackupJob(p *backupsv1alpha1.Plan, scheduledFor time.Time) *backupsv1alpha1.BackupJob { // Normalize ApplicationRef (default apiGroup if not specified) - appRef := normalizeApplicationRef(*p.Spec.ApplicationRef.DeepCopy()) + appRef := backupcontroller.NormalizeApplicationRef(*p.Spec.ApplicationRef.DeepCopy()) job := &backupsv1alpha1.BackupJob{ ObjectMeta: metav1.ObjectMeta{ @@ -37,7 +23,7 @@ func BackupJob(p *backupsv1alpha1.Plan, scheduledFor time.Time) *backupsv1alpha1 PlanRef: &corev1.LocalObjectReference{ Name: p.Name, }, - ApplicationRef: appRef, + ApplicationRef: appRef, BackupClassName: p.Spec.BackupClassName, }, } From d7ae3213ff178c80de4e88e9ba463614b01ddb97 Mon Sep 17 00:00:00 2001 From: Andrey Kolkov Date: Mon, 19 Jan 2026 14:16:28 +0400 Subject: [PATCH 087/889] removed unused Signed-off-by: Andrey Kolkov --- internal/backupcontroller/backupclass_resolver.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/backupcontroller/backupclass_resolver.go b/internal/backupcontroller/backupclass_resolver.go index 20a14ba4..593e3bb0 100644 --- a/internal/backupcontroller/backupclass_resolver.go +++ b/internal/backupcontroller/backupclass_resolver.go @@ -30,7 +30,6 @@ func NormalizeApplicationRef(ref corev1.TypedLocalObjectReference) corev1.TypedL // from a BackupClass. type ResolvedBackupConfig struct { StrategyRef corev1.TypedLocalObjectReference - StorageRef *corev1.TypedLocalObjectReference // Optional, may come from parameters Parameters map[string]string } From 5924c484c95075d04e9571be8e0c557781c61814 Mon Sep 17 00:00:00 2001 From: Andrey Kolkov Date: Mon, 19 Jan 2026 14:26:10 +0400 Subject: [PATCH 088/889] clean-up comments Signed-off-by: Andrey Kolkov --- internal/backupcontroller/velerostrategy_controller.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/internal/backupcontroller/velerostrategy_controller.go b/internal/backupcontroller/velerostrategy_controller.go index 0a023356..0d43098c 100644 --- a/internal/backupcontroller/velerostrategy_controller.go +++ b/internal/backupcontroller/velerostrategy_controller.go @@ -206,13 +206,6 @@ func (r *BackupJobReconciler) reconcileVelero(ctx context.Context, j *backupsv1a return ctrl.Result{RequeueAfter: 5 * time.Second}, nil } -// NOTE: The following functions were removed as they are no longer used after migrating to BackupClass API: -// - resolveBucketStorageRef: Previously resolved S3 credentials from Bucket storageRef -// - createS3CredsForVelero: Previously created Velero S3 credentials secrets -// - createBackupStorageLocation: Previously created Velero BackupStorageLocation resources -// - createVolumeSnapshotLocation: Previously created Velero VolumeSnapshotLocation resources -// These functions may be needed in the future if we decide to support StorageRef resolution through BackupClass. - func (r *BackupJobReconciler) markBackupJobFailed(ctx context.Context, backupJob *backupsv1alpha1.BackupJob, message string) (ctrl.Result, error) { logger := getLogger(ctx) now := metav1.Now() @@ -288,7 +281,6 @@ func (r *BackupJobReconciler) createVeleroBackup(ctx context.Context, backupJob func (r *BackupJobReconciler) createBackupResource(ctx context.Context, backupJob *backupsv1alpha1.BackupJob, veleroBackup *velerov1.Backup, resolved *ResolvedBackupConfig) (*backupsv1alpha1.Backup, error) { logger := getLogger(ctx) - _ = logger // logger may be used in future // Get takenAt from Velero Backup creation timestamp or status takenAt := metav1.Now() @@ -325,8 +317,6 @@ func (r *BackupJobReconciler) createBackupResource(ctx context.Context, backupJo }, Spec: backupsv1alpha1.BackupSpec{ ApplicationRef: backupJob.Spec.ApplicationRef, - // StorageRef is not set as it's now resolved from BackupClass parameters - // The storage location is managed via Velero's BackupStorageLocation StrategyRef: resolved.StrategyRef, TakenAt: takenAt, DriverMetadata: driverMetadata, From ba04063662800d2713afa0b448b4736e8ee0182a Mon Sep 17 00:00:00 2001 From: Andrey Kolkov Date: Mon, 19 Jan 2026 14:41:19 +0400 Subject: [PATCH 089/889] buildfix: restore against cyclic dependency Signed-off-by: Andrey Kolkov --- .../backupcontroller/factory/backupjob.go | 19 +++++++++++++++++-- .../jobstrategy_controller.go | 2 +- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/internal/backupcontroller/factory/backupjob.go b/internal/backupcontroller/factory/backupjob.go index cf8ae544..6ca2ee21 100644 --- a/internal/backupcontroller/factory/backupjob.go +++ b/internal/backupcontroller/factory/backupjob.go @@ -5,14 +5,29 @@ import ( "time" backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1" - "github.com/cozystack/cozystack/internal/backupcontroller" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +const ( + // defaultApplicationAPIGroup is the default API group for applications + // when not specified in ApplicationRef. + defaultApplicationAPIGroup = "apps.cozystack.io" +) + +// normalizeApplicationRef sets the default apiGroup to "apps.cozystack.io" if it's not specified. +// This is a local copy to avoid import cycle with backupcontroller package. +func normalizeApplicationRef(ref corev1.TypedLocalObjectReference) corev1.TypedLocalObjectReference { + if ref.APIGroup == nil || *ref.APIGroup == "" { + apiGroup := defaultApplicationAPIGroup + ref.APIGroup = &apiGroup + } + return ref +} + func BackupJob(p *backupsv1alpha1.Plan, scheduledFor time.Time) *backupsv1alpha1.BackupJob { // Normalize ApplicationRef (default apiGroup if not specified) - appRef := backupcontroller.NormalizeApplicationRef(*p.Spec.ApplicationRef.DeepCopy()) + appRef := normalizeApplicationRef(*p.Spec.ApplicationRef.DeepCopy()) job := &backupsv1alpha1.BackupJob{ ObjectMeta: metav1.ObjectMeta{ diff --git a/internal/backupcontroller/jobstrategy_controller.go b/internal/backupcontroller/jobstrategy_controller.go index 1ff4df59..04b814dd 100644 --- a/internal/backupcontroller/jobstrategy_controller.go +++ b/internal/backupcontroller/jobstrategy_controller.go @@ -11,6 +11,6 @@ import ( func (r *BackupJobReconciler) reconcileJob(ctx context.Context, j *backupsv1alpha1.BackupJob, resolved *ResolvedBackupConfig) (ctrl.Result, error) { _ = log.FromContext(ctx) - _ = resolved // TODO: Use resolved config when implementing job strategy + _ = resolved // Use resolved BackupClass parameters when implementing your job strategy return ctrl.Result{}, nil } From 79bd3ad0d51e66d92a8eca6b2f1edc780f482810 Mon Sep 17 00:00:00 2001 From: Andrey Kolkov Date: Tue, 20 Jan 2026 10:07:46 +0400 Subject: [PATCH 090/889] fix templating Signed-off-by: Andrey Kolkov --- .../velerostrategy_controller.go | 11 +- .../velerostrategy_controller_test.go | 207 ++++++++++++++++++ 2 files changed, 215 insertions(+), 3 deletions(-) create mode 100644 internal/backupcontroller/velerostrategy_controller_test.go diff --git a/internal/backupcontroller/velerostrategy_controller.go b/internal/backupcontroller/velerostrategy_controller.go index 0d43098c..4a1225f6 100644 --- a/internal/backupcontroller/velerostrategy_controller.go +++ b/internal/backupcontroller/velerostrategy_controller.go @@ -123,7 +123,7 @@ func (r *BackupJobReconciler) reconcileVelero(ctx context.Context, j *backupsv1a 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 { + if err := r.createVeleroBackup(ctx, j, veleroStrategy, resolved); err != nil { logger.Error(err, "failed to create Velero Backup") return r.markBackupJobFailed(ctx, j, fmt.Sprintf("failed to create Velero Backup: %v", err)) } @@ -230,7 +230,7 @@ func (r *BackupJobReconciler) markBackupJobFailed(ctx context.Context, backupJob return ctrl.Result{}, nil } -func (r *BackupJobReconciler) createVeleroBackup(ctx context.Context, backupJob *backupsv1alpha1.BackupJob, strategy *strategyv1alpha1.Velero) error { +func (r *BackupJobReconciler) createVeleroBackup(ctx context.Context, backupJob *backupsv1alpha1.BackupJob, strategy *strategyv1alpha1.Velero, resolved *ResolvedBackupConfig) error { logger := getLogger(ctx) logger.Debug("createVeleroBackup called", "strategy", strategy.Name) @@ -247,7 +247,12 @@ func (r *BackupJobReconciler) createVeleroBackup(ctx context.Context, backupJob return err } - veleroBackupSpec, err := template.Template(&strategy.Spec.Template.Spec, app.Object) + templateContext := map[string]interface{}{ + "Application": app.Object, + "Parameters": resolved.Parameters, + } + + veleroBackupSpec, err := template.Template(&strategy.Spec.Template.Spec, templateContext) if err != nil { return err } diff --git a/internal/backupcontroller/velerostrategy_controller_test.go b/internal/backupcontroller/velerostrategy_controller_test.go new file mode 100644 index 00000000..0575a681 --- /dev/null +++ b/internal/backupcontroller/velerostrategy_controller_test.go @@ -0,0 +1,207 @@ +package backupcontroller + +import ( + "context" + "testing" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + dynamicfake "k8s.io/client-go/dynamic/fake" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client" + clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake" + + strategyv1alpha1 "github.com/cozystack/cozystack/api/backups/strategy/v1alpha1" + backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1" + velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" +) + +// mockRESTMapper implements meta.RESTMapper for testing +type mockRESTMapper struct { + mapping *meta.RESTMapping +} + +func (m *mockRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (*meta.RESTMapping, error) { + return m.mapping, nil +} + +func (m *mockRESTMapper) RESTMappings(gk schema.GroupKind, versions ...string) ([]*meta.RESTMapping, error) { + return []*meta.RESTMapping{m.mapping}, nil +} + +func (m *mockRESTMapper) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) { + return m.mapping.GroupVersionKind, nil +} + +func (m *mockRESTMapper) KindsFor(resource schema.GroupVersionResource) ([]schema.GroupVersionKind, error) { + return []schema.GroupVersionKind{m.mapping.GroupVersionKind}, nil +} + +func (m *mockRESTMapper) ResourceFor(input schema.GroupVersionResource) (schema.GroupVersionResource, error) { + return m.mapping.Resource, nil +} + +func (m *mockRESTMapper) ResourcesFor(input schema.GroupVersionResource) ([]schema.GroupVersionResource, error) { + return []schema.GroupVersionResource{m.mapping.Resource}, nil +} + +func (m *mockRESTMapper) ResourceSingularizer(resource string) (singular string, err error) { + return resource, nil +} + +func TestCreateVeleroBackup_TemplateContext(t *testing.T) { + // Setup scheme + testScheme := runtime.NewScheme() + _ = scheme.AddToScheme(testScheme) + _ = backupsv1alpha1.AddToScheme(testScheme) + _ = strategyv1alpha1.AddToScheme(testScheme) + _ = velerov1.AddToScheme(testScheme) + + // Create test application (VirtualMachine-like object) + testApp := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-vm", + Namespace: "default", + Labels: map[string]string{ + "apps.cozystack.io/application.Kind": "VirtualMachine", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "test-container", + Image: "test-image:latest", + }, + }, + }, + } + + // Create dynamic client with the test application + dynamicClient := dynamicfake.NewSimpleDynamicClient(testScheme, testApp) + + // Create REST mapping + mapping := &meta.RESTMapping{ + Resource: schema.GroupVersionResource{ + Group: "", + Version: "v1", + Resource: "pods", + }, + GroupVersionKind: schema.GroupVersionKind{ + Group: "", + Version: "v1", + Kind: "Pod", + }, + Scope: meta.RESTScopeNamespace, + } + + // Create BackupJob + backupJob := &backupsv1alpha1.BackupJob{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-backup-job", + Namespace: "default", + }, + Spec: backupsv1alpha1.BackupJobSpec{ + ApplicationRef: corev1.TypedLocalObjectReference{ + APIGroup: stringPtr(""), + Kind: "Pod", + Name: "test-vm", + }, + BackupClassName: "velero", + }, + } + + // Create Velero strategy with template that uses Application and Parameters + veleroStrategy := &strategyv1alpha1.Velero{ + ObjectMeta: metav1.ObjectMeta{ + Name: "velero-strategy", + }, + Spec: strategyv1alpha1.VeleroSpec{ + Template: strategyv1alpha1.VeleroTemplate{ + Spec: velerov1.BackupSpec{ + // Use template variables to verify context is passed correctly + LabelSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "{{ .Application.metadata.name }}", + }, + }, + // Use Parameters in template + StorageLocation: "{{ .Parameters.backupStorageLocationName }}", + }, + }, + }, + } + + // Create ResolvedBackupConfig with parameters + resolved := &ResolvedBackupConfig{ + StrategyRef: corev1.TypedLocalObjectReference{ + APIGroup: stringPtr("strategy.backups.cozystack.io"), + Kind: "Velero", + Name: "velero-strategy", + }, + Parameters: map[string]string{ + "backupStorageLocationName": "default-storage", + }, + } + + // Create fake client for controller + fakeClient := clientfake.NewClientBuilder(). + WithScheme(testScheme). + WithObjects(backupJob, veleroStrategy). + Build() + + // Create reconciler with fake event recorder + reconciler := &BackupJobReconciler{ + Client: fakeClient, + Interface: dynamicClient, + RESTMapper: &mockRESTMapper{mapping: mapping}, + Scheme: testScheme, + Recorder: record.NewFakeRecorder(10), + } + + // Create context with logger + ctx := context.Background() + + // Call createVeleroBackup + err := reconciler.createVeleroBackup(ctx, backupJob, veleroStrategy, resolved) + if err != nil { + t.Fatalf("createVeleroBackup() error = %v", err) + } + + // Verify that the template was executed correctly by checking the created Velero Backup + // The template should have replaced {{ .Application.metadata.name }} with "test-vm" + // and {{ .Parameters.backupStorageLocationName }} with "default-storage" + + // Get the created Velero Backup + veleroBackups := &velerov1.BackupList{} + err = fakeClient.List(ctx, veleroBackups, client.InNamespace(veleroNamespace)) + if err != nil { + t.Fatalf("Failed to list Velero Backups: %v", err) + } + + if len(veleroBackups.Items) == 0 { + t.Fatal("Expected Velero Backup to be created, but none found") + } + + veleroBackup := veleroBackups.Items[0] + + // Verify template context was used correctly: + // 1. Application.metadata.name should be replaced with "test-vm" + if veleroBackup.Spec.LabelSelector != nil { + if appLabel, ok := veleroBackup.Spec.LabelSelector.MatchLabels["app"]; ok { + if appLabel != "test-vm" { + t.Errorf("Template context Application.metadata.name not applied correctly. Expected 'test-vm', got '%s'", appLabel) + } + } else { + t.Error("Template context Application.metadata.name not found in label selector") + } + } + + // 2. Parameters.backupStorageLocationName should be replaced with "default-storage" + if veleroBackup.Spec.StorageLocation != "default-storage" { + t.Errorf("Template context Parameters.backupStorageLocationName not applied correctly. Expected 'default-storage', got '%s'", veleroBackup.Spec.StorageLocation) + } +} From 02ace2e482e7f6de48130b3cec4888c6824caad0 Mon Sep 17 00:00:00 2001 From: Andrey Kolkov Date: Tue, 20 Jan 2026 10:07:54 +0400 Subject: [PATCH 091/889] Simplify checks in tests Signed-off-by: Andrey Kolkov --- .../backupclass_resolver_test.go | 103 ++++++++++-------- 1 file changed, 60 insertions(+), 43 deletions(-) diff --git a/internal/backupcontroller/backupclass_resolver_test.go b/internal/backupcontroller/backupclass_resolver_test.go index 4c2b03fc..a606f241 100644 --- a/internal/backupcontroller/backupclass_resolver_test.go +++ b/internal/backupcontroller/backupclass_resolver_test.go @@ -5,19 +5,20 @@ import ( "testing" corev1 "k8s.io/api/core/v1" + apiequality "k8s.io/apimachinery/pkg/api/equality" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client/fake" - backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1" strategyv1alpha1 "github.com/cozystack/cozystack/api/backups/strategy/v1alpha1" + backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1" ) func TestNormalizeApplicationRef(t *testing.T) { tests := []struct { name string input corev1.TypedLocalObjectReference - expected string + expected corev1.TypedLocalObjectReference }{ { name: "apiGroup not specified - should default to apps.cozystack.io", @@ -25,7 +26,11 @@ func TestNormalizeApplicationRef(t *testing.T) { Kind: "VirtualMachine", Name: "vm1", }, - expected: DefaultApplicationAPIGroup, + expected: corev1.TypedLocalObjectReference{ + APIGroup: stringPtr(DefaultApplicationAPIGroup), + Kind: "VirtualMachine", + Name: "vm1", + }, }, { name: "apiGroup is nil - should default to apps.cozystack.io", @@ -34,7 +39,11 @@ func TestNormalizeApplicationRef(t *testing.T) { Kind: "VirtualMachine", Name: "vm1", }, - expected: DefaultApplicationAPIGroup, + expected: corev1.TypedLocalObjectReference{ + APIGroup: stringPtr(DefaultApplicationAPIGroup), + Kind: "VirtualMachine", + Name: "vm1", + }, }, { name: "apiGroup is empty string - should default to apps.cozystack.io", @@ -43,7 +52,11 @@ func TestNormalizeApplicationRef(t *testing.T) { Kind: "VirtualMachine", Name: "vm1", }, - expected: DefaultApplicationAPIGroup, + expected: corev1.TypedLocalObjectReference{ + APIGroup: stringPtr(DefaultApplicationAPIGroup), + Kind: "VirtualMachine", + Name: "vm1", + }, }, { name: "apiGroup is explicitly set - should keep it", @@ -52,7 +65,11 @@ func TestNormalizeApplicationRef(t *testing.T) { Kind: "CustomApp", Name: "custom-app", }, - expected: "custom.api.group.io", + expected: corev1.TypedLocalObjectReference{ + APIGroup: stringPtr("custom.api.group.io"), + Kind: "CustomApp", + Name: "custom-app", + }, }, { name: "apiGroup is apps.cozystack.io - should keep it", @@ -61,26 +78,19 @@ func TestNormalizeApplicationRef(t *testing.T) { Kind: "VirtualMachine", Name: "vm1", }, - expected: DefaultApplicationAPIGroup, + expected: corev1.TypedLocalObjectReference{ + APIGroup: stringPtr(DefaultApplicationAPIGroup), + Kind: "VirtualMachine", + Name: "vm1", + }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := NormalizeApplicationRef(tt.input) - if result.APIGroup == nil { - t.Errorf("NormalizeApplicationRef() returned nil APIGroup, expected %s", tt.expected) - return - } - if *result.APIGroup != tt.expected { - t.Errorf("NormalizeApplicationRef() APIGroup = %v, want %v", *result.APIGroup, tt.expected) - } - // Verify other fields are preserved - if result.Kind != tt.input.Kind { - t.Errorf("NormalizeApplicationRef() Kind = %v, want %v", result.Kind, tt.input.Kind) - } - if result.Name != tt.input.Name { - t.Errorf("NormalizeApplicationRef() Name = %v, want %v", result.Name, tt.input.Name) + if !apiequality.Semantic.DeepEqual(result, tt.expected) { + t.Errorf("NormalizeApplicationRef() = %v, want %v", result, tt.expected) } }) } @@ -98,13 +108,13 @@ func TestResolveBackupClass(t *testing.T) { } tests := []struct { - name string - backupClass *backupsv1alpha1.BackupClass - applicationRef corev1.TypedLocalObjectReference - backupClassName string - wantErr bool - expectedKind string - expectedParams map[string]string + name string + backupClass *backupsv1alpha1.BackupClass + applicationRef corev1.TypedLocalObjectReference + backupClassName string + wantErr bool + expectedStrategyRef *corev1.TypedLocalObjectReference + expectedParams map[string]string }{ { name: "successful resolution - matches VirtualMachine strategy", @@ -149,7 +159,11 @@ func TestResolveBackupClass(t *testing.T) { }, backupClassName: "velero", wantErr: false, - expectedKind: "VirtualMachine", + expectedStrategyRef: &corev1.TypedLocalObjectReference{ + APIGroup: stringPtr("strategy.backups.cozystack.io"), + Kind: "Velero", + Name: "velero-strategy-vm", + }, expectedParams: map[string]string{ "backupStorageLocationName": "default", }, @@ -186,7 +200,11 @@ func TestResolveBackupClass(t *testing.T) { }, backupClassName: "velero", wantErr: false, - expectedKind: "MySQL", + expectedStrategyRef: &corev1.TypedLocalObjectReference{ + APIGroup: stringPtr("strategy.backups.cozystack.io"), + Kind: "Velero", + Name: "velero-strategy-mysql", + }, expectedParams: map[string]string{ "backupStorageLocationName": "mysql-storage", }, @@ -222,7 +240,11 @@ func TestResolveBackupClass(t *testing.T) { }, backupClassName: "velero", wantErr: false, - expectedKind: "VirtualMachine", + expectedStrategyRef: &corev1.TypedLocalObjectReference{ + APIGroup: stringPtr("strategy.backups.cozystack.io"), + Kind: "Velero", + Name: "velero-strategy-vm", + }, expectedParams: map[string]string{ "backupStorageLocationName": "default", }, @@ -331,22 +353,17 @@ func TestResolveBackupClass(t *testing.T) { return } - // Verify strategy ref - if resolved.StrategyRef.Kind != "Velero" { - t.Errorf("ResolveBackupClass() StrategyRef.Kind = %v, want Velero", resolved.StrategyRef.Kind) + // Verify strategy ref using apimachinery equality + if tt.expectedStrategyRef != nil { + if !apiequality.Semantic.DeepEqual(resolved.StrategyRef, *tt.expectedStrategyRef) { + t.Errorf("ResolveBackupClass() StrategyRef = %v, want %v", resolved.StrategyRef, *tt.expectedStrategyRef) + } } - // Verify parameters + // Verify parameters using apimachinery equality if tt.expectedParams != nil { - for key, expectedValue := range tt.expectedParams { - actualValue, ok := resolved.Parameters[key] - if !ok { - t.Errorf("ResolveBackupClass() Parameters[%s] not found", key) - continue - } - if actualValue != expectedValue { - t.Errorf("ResolveBackupClass() Parameters[%s] = %v, want %v", key, actualValue, expectedValue) - } + if !apiequality.Semantic.DeepEqual(resolved.Parameters, tt.expectedParams) { + t.Errorf("ResolveBackupClass() Parameters = %v, want %v", resolved.Parameters, tt.expectedParams) } } }) From 272d2b7a206a15d7dab4e75eb9400f392687aa25 Mon Sep 17 00:00:00 2001 From: Andrey Kolkov Date: Tue, 20 Jan 2026 10:32:13 +0400 Subject: [PATCH 092/889] updated design doc Signed-off-by: Andrey Kolkov --- api/backups/v1alpha1/DESIGN.md | 115 ++++++++++++++++++++++++--------- 1 file changed, 85 insertions(+), 30 deletions(-) diff --git a/api/backups/v1alpha1/DESIGN.md b/api/backups/v1alpha1/DESIGN.md index 455dab59..abf93cfe 100644 --- a/api/backups/v1alpha1/DESIGN.md +++ b/api/backups/v1alpha1/DESIGN.md @@ -100,13 +100,13 @@ Describe **when**, **how**, and **where** to back up a specific managed applicat ```go type PlanSpec struct { // Application to back up. + // If apiGroup is not specified, it defaults to "apps.cozystack.io". 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"` + // BackupClassName references a BackupClass that contains strategy and other parameters (e.g. storage reference). + // The BackupClass will be resolved to determine the appropriate strategy and parameters + // based on the ApplicationRef. + BackupClassName string `json:"backupClassName"` // When backups should run. Schedule PlanSchedule `json:"schedule"` @@ -145,12 +145,12 @@ Core Plan controller: * 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"` + * `spec.applicationRef = plan.spec.applicationRef` (normalized with default apiGroup if not specified) + * `spec.backupClassName = plan.spec.backupClassName` * Set `ownerReferences` so the `BackupJob` is owned by the `Plan`. +**Note:** The `BackupJob` controller resolves the `BackupClass` to determine the appropriate strategy and parameters, based on the `ApplicationRef`. The strategy template is processed with a context containing the `Application` object and `Parameters` from the `BackupClass`. + The Plan controller does **not**: * Execute backups itself. @@ -159,17 +159,64 @@ The Plan controller does **not**: --- -### 4.2 Storage +### 4.2 BackupClass -**API Shape** +**Group/Kind** +`backups.cozystack.io/v1alpha1, Kind=BackupClass` -TBD +**Purpose** +Define a class of backup configurations that encapsulate strategy and parameters per application type. `BackupClass` is a cluster-scoped resource that allows admins to configure backup strategies and parameters in a reusable way. -**Storage usage** +**Key fields (spec)** -* `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. +```go +type BackupClassSpec struct { + // Strategies is a list of backup strategies, each matching a specific application type. + Strategies []BackupClassStrategy `json:"strategies"` +} + +type BackupClassStrategy struct { + // StrategyRef references the driver-specific BackupStrategy (e.g., Velero). + StrategyRef corev1.TypedLocalObjectReference `json:"strategyRef"` + + // Application specifies which application types this strategy applies to. + // If apiGroup is not specified, it defaults to "apps.cozystack.io". + Application ApplicationSelector `json:"application"` + + // Parameters holds strategy-specific parameters, like storage reference. + // Common parameters include: + // - backupStorageLocationName: Name of Velero BackupStorageLocation + // +optional + Parameters map[string]string `json:"parameters,omitempty"` +} + +type ApplicationSelector struct { + // APIGroup is the API group of the application. + // If not specified, defaults to "apps.cozystack.io". + // +optional + APIGroup *string `json:"apiGroup,omitempty"` + + // Kind is the kind of the application (e.g., VirtualMachine, MySQL). + Kind string `json:"kind"` +} +``` + +**BackupClass resolution** + +* When a `BackupJob` or `Plan` references a `BackupClass` via `backupClassName`, the controller: + 1. Fetches the `BackupClass` by name. + 2. Matches the `ApplicationRef` against strategies in the `BackupClass`: + * Normalizes `ApplicationRef.apiGroup` (defaults to `"apps.cozystack.io"` if not specified). + * Finds a strategy where `ApplicationSelector` matches the `ApplicationRef` (apiGroup and kind). + 3. Returns the matched `StrategyRef` and `Parameters`. +* Strategy templates (e.g., Velero's `backupTemplate.spec`) are processed with a context containing: + * `Application`: The application object being backed up. + * `Parameters`: The parameters from the matched `BackupClassStrategy`. + +**Parameters** + +* Parameters are passed via `Parameters` in the `BackupClass` (e.g., `backupStorageLocationName` for Velero). +* The driver uses these parameters to resolve the actual resources (e.g., Velero's `BackupStorageLocation` CRD). --- @@ -189,16 +236,13 @@ type BackupJobSpec struct { PlanRef *corev1.LocalObjectReference `json:"planRef,omitempty"` // Application to back up. + // If apiGroup is not specified, it defaults to "apps.cozystack.io". 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"` + // BackupClassName references a BackupClass that contains strategy and related parameters + // The BackupClass will be resolved to determine the appropriate strategy and parameters + // based on the ApplicationRef. + BackupClassName string `json:"backupClassName"` } ``` @@ -223,7 +267,9 @@ type BackupJobStatus struct { * Each driver controller: * Watches `BackupJob`. - * Reconciles runs where `spec.strategyRef.apiGroup/kind` matches its **strategy type(s)**. + * Resolves the `BackupClass` referenced by `spec.backupClassName`. + * Matches the `ApplicationRef` against strategies in the `BackupClass` to find the appropriate strategy. + * Reconciles runs where the resolved strategy's `apiGroup/kind` matches its **strategy type(s)**. * Driver responsibilities: 1. On first reconcile: @@ -232,7 +278,12 @@ type BackupJobStatus struct { * Set `status.phase = Running`. 2. Resolve inputs: - * Read `Strategy` (driver-owned CRD), `Storage`, `Application`, optionally `Plan`. + * Resolve `BackupClass` from `spec.backupClassName`. + * Match `ApplicationRef` against `BackupClass` strategies to get `StrategyRef` and `Parameters`. + * Read `Strategy` (driver-owned CRD) from `StrategyRef`. + * Read `Application` from `ApplicationRef`. + * Extract parameters from `Parameters` (e.g., `backupStorageLocationName` for Velero). + * Process strategy template with context: `Application` object and `Parameters` from `BackupClass`. 3. Execute backup logic (implementation-specific). 4. On success: @@ -264,13 +315,14 @@ Represent a single **backup artifact** for a given application, decoupled from a 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"` } ``` +**Note:** Parameters are no stored directly in `Backup`. Instead, they are resolved from `BackupClass` parameters when the backup was created. The storage location is managed by the driver (e.g., Velero's `BackupStorageLocation`) and referenced via parameters in the `BackupClass`. + **Key fields (status)** ```go @@ -290,7 +342,8 @@ type BackupStatus struct { * Creates a `Backup` in the same namespace (typically owned by the `BackupJob`). * Populates `spec` fields with: - * The application, storage, strategy references. + * The application reference. + * The strategy reference (resolved from `BackupClass` during `BackupJob` execution). * `takenAt`. * Optional `driverMetadata`. * Sets `status` with: @@ -306,6 +359,8 @@ type BackupStatus struct { * Anchor `RestoreJob` operations. * Implement higher-level policies (retention) if needed. +**Note:** Parameters are resolved from `BackupClass` when the `BackupJob` is created. The driver uses these parameters to determine where to store backups. The storage location itself is managed by the driver (e.g., Velero's `BackupStorageLocation` CRD) and is not directly referenced in the `Backup` resource. When restoring, the driver resolves the storage location from the original `BackupClass` parameters or from the driver's own metadata. + --- ### 4.5 RestoreJob @@ -353,13 +408,13 @@ type RestoreJobStatus struct { * Determines effective: * **Strategy**: `backup.spec.strategyRef`. - * **Storage**: `backup.spec.storageRef`. + * **Storage**: Resolved from driver metadata or `BackupClass` parameters (e.g., `backupStorageLocationName` stored in `driverMetadata` or resolved from the original `BackupClass`). * **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. + * Resolve `Backup`, storage location (from driver metadata or `BackupClass`), `Strategy`, target application. * Execute restore logic (implementation-specific). * On success: From e3aab2481032ee7f5d37d2dd9c06dd383808fd52 Mon Sep 17 00:00:00 2001 From: Andrey Kolkov Date: Tue, 20 Jan 2026 11:09:35 +0400 Subject: [PATCH 093/889] fixes Signed-off-by: Andrey Kolkov --- api/backups/v1alpha1/DESIGN.md | 8 +++++--- internal/backupcontroller/velerostrategy_controller.go | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/api/backups/v1alpha1/DESIGN.md b/api/backups/v1alpha1/DESIGN.md index abf93cfe..bab753b2 100644 --- a/api/backups/v1alpha1/DESIGN.md +++ b/api/backups/v1alpha1/DESIGN.md @@ -321,7 +321,7 @@ type BackupSpec struct { } ``` -**Note:** Parameters are no stored directly in `Backup`. Instead, they are resolved from `BackupClass` parameters when the backup was created. The storage location is managed by the driver (e.g., Velero's `BackupStorageLocation`) and referenced via parameters in the `BackupClass`. +**Note:** Parameters are not stored directly in `Backup`. Instead, they are resolved from `BackupClass` parameters when the backup was created. The storage location is managed by the driver (e.g., Velero's `BackupStorageLocation`) and referenced via parameters in the `BackupClass`. **Key fields (status)** @@ -469,8 +469,10 @@ The Cozystack backups core API: * Uses a single group, `backups.cozystack.io`, for all core CRDs. * Cleanly separates: - * **When & where** (Plan + Storage) – core-owned. + * **When** (Plan schedule) – core-owned. + * **How & where** (BackupClass) – central configuration unit that encapsulates strategy and parameters (e.g., storage reference) per application type, resolved per BackupJob/Plan. + * **Execution** (BackupJob) – created by Plan when schedule fires, resolves BackupClass to get strategy and parameters, then delegates to driver. * **What backup artifacts exist** (Backup) – driver-created but cluster-visible. - * **Execution lifecycle** (BackupJob, RestoreJob) – shared contract boundary. + * **Restore lifecycle** (RestoreJob) – shared contract boundary. * Allows multiple strategy drivers to implement backup/restore logic without entangling their implementation with the core API. diff --git a/internal/backupcontroller/velerostrategy_controller.go b/internal/backupcontroller/velerostrategy_controller.go index 4a1225f6..0986d648 100644 --- a/internal/backupcontroller/velerostrategy_controller.go +++ b/internal/backupcontroller/velerostrategy_controller.go @@ -303,7 +303,7 @@ func (r *BackupJobReconciler) createBackupResource(ctx context.Context, backupJo // Create a basic artifact referencing the Velero backup artifact := &backupsv1alpha1.BackupArtifact{ - URI: fmt.Sprintf("velero://%s/%s", backupJob.Namespace, veleroBackup.Name), + URI: fmt.Sprintf("velero://%s/%s", veleroBackup.Namespace, veleroBackup.Name), } backup := &backupsv1alpha1.Backup{ From f254c5f03e5866ff70385e3ef428bbbfa0207b84 Mon Sep 17 00:00:00 2001 From: Andrey Kolkov Date: Tue, 20 Jan 2026 11:12:48 +0400 Subject: [PATCH 094/889] fix test Signed-off-by: Andrey Kolkov --- .../velerostrategy_controller_test.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/internal/backupcontroller/velerostrategy_controller_test.go b/internal/backupcontroller/velerostrategy_controller_test.go index 0575a681..a7de2076 100644 --- a/internal/backupcontroller/velerostrategy_controller_test.go +++ b/internal/backupcontroller/velerostrategy_controller_test.go @@ -190,14 +190,15 @@ func TestCreateVeleroBackup_TemplateContext(t *testing.T) { // Verify template context was used correctly: // 1. Application.metadata.name should be replaced with "test-vm" - if veleroBackup.Spec.LabelSelector != nil { - if appLabel, ok := veleroBackup.Spec.LabelSelector.MatchLabels["app"]; ok { - if appLabel != "test-vm" { - t.Errorf("Template context Application.metadata.name not applied correctly. Expected 'test-vm', got '%s'", appLabel) - } - } else { - t.Error("Template context Application.metadata.name not found in label selector") + if veleroBackup.Spec.LabelSelector == nil { + t.Fatal("Expected LabelSelector to be set by template") + } + if appLabel, ok := veleroBackup.Spec.LabelSelector.MatchLabels["app"]; ok { + if appLabel != "test-vm" { + t.Errorf("Template context Application.metadata.name not applied correctly. Expected 'test-vm', got '%s'", appLabel) } + } else { + t.Error("Template context Application.metadata.name not found in label selector") } // 2. Parameters.backupStorageLocationName should be replaced with "default-storage" From f6641c15470ed0a97f3f502250dc8abafebf16a4 Mon Sep 17 00:00:00 2001 From: Andrey Kolkov Date: Tue, 20 Jan 2026 11:58:25 +0400 Subject: [PATCH 095/889] fixes Signed-off-by: Andrey Kolkov --- api/backups/v1alpha1/zz_generated.deepcopy.go | 294 +++++++++--------- .../backupcontroller/backupjob_controller.go | 36 +++ .../backups.cozystack.io_backupclasses.yaml | 171 ++++++++++ .../backups.cozystack.io_backupjobs.yaml | 54 +--- .../backups.cozystack.io_backups.yaml | 23 -- .../backups.cozystack.io_plans.yaml | 54 +--- 6 files changed, 370 insertions(+), 262 deletions(-) create mode 100644 packages/system/backup-controller/definitions/backups.cozystack.io_backupclasses.yaml diff --git a/api/backups/v1alpha1/zz_generated.deepcopy.go b/api/backups/v1alpha1/zz_generated.deepcopy.go index 6fbc1d16..b2e4a680 100644 --- a/api/backups/v1alpha1/zz_generated.deepcopy.go +++ b/api/backups/v1alpha1/zz_generated.deepcopy.go @@ -26,6 +26,26 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApplicationSelector) DeepCopyInto(out *ApplicationSelector) { + *out = *in + if in.APIGroup != nil { + in, out := &in.APIGroup, &out.APIGroup + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationSelector. +func (in *ApplicationSelector) DeepCopy() *ApplicationSelector { + if in == nil { + return nil + } + out := new(ApplicationSelector) + in.DeepCopyInto(out) + return out +} + // 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 @@ -68,6 +88,133 @@ func (in *BackupArtifact) DeepCopy() *BackupArtifact { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackupClass) DeepCopyInto(out *BackupClass) { + *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 BackupClass. +func (in *BackupClass) DeepCopy() *BackupClass { + if in == nil { + return nil + } + out := new(BackupClass) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BackupClass) 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 *BackupClassList) DeepCopyInto(out *BackupClassList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]BackupClass, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupClassList. +func (in *BackupClassList) DeepCopy() *BackupClassList { + if in == nil { + return nil + } + out := new(BackupClassList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BackupClassList) 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 *BackupClassSpec) DeepCopyInto(out *BackupClassSpec) { + *out = *in + if in.Strategies != nil { + in, out := &in.Strategies, &out.Strategies + *out = make([]BackupClassStrategy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupClassSpec. +func (in *BackupClassSpec) DeepCopy() *BackupClassSpec { + if in == nil { + return nil + } + out := new(BackupClassSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackupClassStatus) DeepCopyInto(out *BackupClassStatus) { + *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 BackupClassStatus. +func (in *BackupClassStatus) DeepCopy() *BackupClassStatus { + if in == nil { + return nil + } + out := new(BackupClassStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackupClassStrategy) DeepCopyInto(out *BackupClassStrategy) { + *out = *in + in.StrategyRef.DeepCopyInto(&out.StrategyRef) + in.Application.DeepCopyInto(&out.Application) + if in.Parameters != nil { + in, out := &in.Parameters, &out.Parameters + *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 BackupClassStrategy. +func (in *BackupClassStrategy) DeepCopy() *BackupClassStrategy { + if in == nil { + return nil + } + out := new(BackupClassStrategy) + 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 @@ -494,150 +641,3 @@ func (in *RestoreJobStatus) DeepCopy() *RestoreJobStatus { in.DeepCopyInto(out) return out } - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *BackupClass) DeepCopyInto(out *BackupClass) { - *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 BackupClass. -func (in *BackupClass) DeepCopy() *BackupClass { - if in == nil { - return nil - } - out := new(BackupClass) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *BackupClass) 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 *BackupClassList) DeepCopyInto(out *BackupClassList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]BackupClass, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupClassList. -func (in *BackupClassList) DeepCopy() *BackupClassList { - if in == nil { - return nil - } - out := new(BackupClassList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *BackupClassList) 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 *BackupClassSpec) DeepCopyInto(out *BackupClassSpec) { - *out = *in - if in.Strategies != nil { - in, out := &in.Strategies, &out.Strategies - *out = make([]BackupClassStrategy, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupClassSpec. -func (in *BackupClassSpec) DeepCopy() *BackupClassSpec { - if in == nil { - return nil - } - out := new(BackupClassSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *BackupClassStrategy) DeepCopyInto(out *BackupClassStrategy) { - *out = *in - in.StrategyRef.DeepCopyInto(&out.StrategyRef) - in.Application.DeepCopyInto(&out.Application) - if in.Parameters != nil { - in, out := &in.Parameters, &out.Parameters - *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 BackupClassStrategy. -func (in *BackupClassStrategy) DeepCopy() *BackupClassStrategy { - if in == nil { - return nil - } - out := new(BackupClassStrategy) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ApplicationSelector) DeepCopyInto(out *ApplicationSelector) { - *out = *in - if in.APIGroup != nil { - in, out := &in.APIGroup, &out.APIGroup - *out = new(string) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationSelector. -func (in *ApplicationSelector) DeepCopy() *ApplicationSelector { - if in == nil { - return nil - } - out := new(ApplicationSelector) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *BackupClassStatus) DeepCopyInto(out *BackupClassStatus) { - *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 BackupClassStatus. -func (in *BackupClassStatus) DeepCopy() *BackupClassStatus { - if in == nil { - return nil - } - out := new(BackupClassStatus) - in.DeepCopyInto(out) - return out -} diff --git a/internal/backupcontroller/backupjob_controller.go b/internal/backupcontroller/backupjob_controller.go index db7e701a..c04ec85d 100644 --- a/internal/backupcontroller/backupjob_controller.go +++ b/internal/backupcontroller/backupjob_controller.go @@ -14,7 +14,10 @@ import ( 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/handler" "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" strategyv1alpha1 "github.com/cozystack/cozystack/api/backups/strategy/v1alpha1" backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1" @@ -88,6 +91,17 @@ func (r *BackupJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( // SetupWithManager registers our controller with the Manager and sets up watches. func (r *BackupJobReconciler) SetupWithManager(mgr ctrl.Manager) error { + // index BackupJob by backupClassName for efficient lookups when BackupClass changes + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &backupsv1alpha1.BackupJob{}, "spec.backupClassName", func(obj client.Object) []string { + job := obj.(*backupsv1alpha1.BackupJob) + if job.Spec.BackupClassName == "" { + return []string{} + } + return []string{job.Spec.BackupClassName} + }); err != nil { + return err + } + cfg := mgr.GetConfig() var err error if r.Interface, err = dynamic.NewForConfig(cfg); err != nil { @@ -102,5 +116,27 @@ func (r *BackupJobReconciler) SetupWithManager(mgr ctrl.Manager) error { } return ctrl.NewControllerManagedBy(mgr). For(&backupsv1alpha1.BackupJob{}). + // Requeue BackupJobs when their referenced BackupClass changes + WatchesRawSource(source.Kind( + mgr.GetCache(), + &backupsv1alpha1.BackupClass{}, + handler.TypedEnqueueRequestsFromMapFunc(func(ctx context.Context, bc *backupsv1alpha1.BackupClass) []reconcile.Request { + var jobs backupsv1alpha1.BackupJobList + if err := r.List(ctx, &jobs, client.MatchingFields{"spec.backupClassName": bc.Name}); err != nil { + return nil + } + + reqs := make([]reconcile.Request, 0, len(jobs.Items)) + for _, job := range jobs.Items { + reqs = append(reqs, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: job.Namespace, + Name: job.Name, + }, + }) + } + return reqs + }), + )). Complete(r) } diff --git a/packages/system/backup-controller/definitions/backups.cozystack.io_backupclasses.yaml b/packages/system/backup-controller/definitions/backups.cozystack.io_backupclasses.yaml new file mode 100644 index 00000000..b0f0fec2 --- /dev/null +++ b/packages/system/backup-controller/definitions/backups.cozystack.io_backupclasses.yaml @@ -0,0 +1,171 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.4 + name: backupclasses.backups.cozystack.io +spec: + group: backups.cozystack.io + names: + kind: BackupClass + listKind: BackupClassList + plural: backupclasses + singular: backupclass + scope: Cluster + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + BackupClass defines a class of backup configurations that can be referenced + by BackupJob and Plan resources. It encapsulates strategy and storage configuration + per application type. + 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: BackupClassSpec defines the desired state of a BackupClass. + properties: + strategies: + description: Strategies is a list of backup strategies, each matching + a specific application type. + items: + description: BackupClassStrategy defines a backup strategy for a + specific application type. + properties: + application: + description: Application specifies which application types this + strategy applies to. + properties: + apiGroup: + description: |- + APIGroup is the API group of the application. + If not specified, defaults to "apps.cozystack.io". + type: string + kind: + description: Kind is the kind of the application (e.g., + VirtualMachine, MySQL). + type: string + required: + - kind + type: object + parameters: + additionalProperties: + type: string + description: |- + Parameters holds strategy-specific and storage-specific parameters. + Common parameters include: + - backupStorageLocationName: Name of Velero BackupStorageLocation + type: object + strategyRef: + description: StrategyRef references the driver-specific BackupStrategy + (e.g., Velero). + 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: + - application + - strategyRef + type: object + type: array + required: + - strategies + type: object + status: + description: BackupClassStatus defines the observed state of a BackupClass. + properties: + conditions: + description: Conditions represents the latest available observations + of a BackupClass'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 + type: object + type: object + served: true + storage: true + subresources: + status: {} 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 74349181..686c079f 100644 --- a/packages/system/backup-controller/definitions/backups.cozystack.io_backupjobs.yaml +++ b/packages/system/backup-controller/definitions/backups.cozystack.io_backupjobs.yaml @@ -50,6 +50,7 @@ spec: description: |- ApplicationRef holds a reference to the managed application whose state is being backed up. + If apiGroup is not specified, it defaults to "apps.cozystack.io". properties: apiGroup: description: |- @@ -68,6 +69,12 @@ spec: - name type: object x-kubernetes-map-type: atomic + backupClassName: + description: |- + BackupClassName references a BackupClass that contains strategy and storage configuration. + The BackupClass will be resolved to determine the appropriate strategy and storage + based on the ApplicationRef. + type: string planRef: description: |- PlanRef refers to the Plan that requested this backup run. @@ -84,54 +91,9 @@ spec: 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 + - backupClassName type: object status: description: BackupJobStatus represents the observed state of a BackupJob. 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 13d9bc62..6d55cb84 100644 --- a/packages/system/backup-controller/definitions/backups.cozystack.io_backups.yaml +++ b/packages/system/backup-controller/definitions/backups.cozystack.io_backups.yaml @@ -85,28 +85,6 @@ spec: 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 @@ -137,7 +115,6 @@ spec: type: string required: - applicationRef - - storageRef - strategyRef - takenAt type: object 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 1c6829cc..94c09334 100644 --- a/packages/system/backup-controller/definitions/backups.cozystack.io_plans.yaml +++ b/packages/system/backup-controller/definitions/backups.cozystack.io_plans.yaml @@ -47,6 +47,7 @@ spec: description: |- ApplicationRef holds a reference to the managed application, whose state and configuration must be backed up. + If apiGroup is not specified, it defaults to "apps.cozystack.io". properties: apiGroup: description: |- @@ -65,6 +66,12 @@ spec: - name type: object x-kubernetes-map-type: atomic + backupClassName: + description: |- + BackupClassName references a BackupClass that contains strategy and storage configuration. + The BackupClass will be resolved to determine the appropriate strategy and storage + based on the ApplicationRef. + type: string schedule: description: Schedule specifies when backup copies are created. properties: @@ -80,55 +87,10 @@ spec: [`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 + - backupClassName - schedule - - storageRef - - strategyRef type: object status: properties: From 2cca1bc8d85ae016b36c0cf5ffcbc8c030edd2a7 Mon Sep 17 00:00:00 2001 From: Andrey Kolkov Date: Thu, 22 Jan 2026 12:30:41 +0400 Subject: [PATCH 096/889] add webhook for validation backupClass is immutable in BackupJob Signed-off-by: Andrey Kolkov --- api/backups/v1alpha1/backupjob_types.go | 2 + api/backups/v1alpha1/backupjob_webhook.go | 71 +++++++++++++++++++++++ cmd/backup-controller/main.go | 6 ++ 3 files changed, 79 insertions(+) create mode 100644 api/backups/v1alpha1/backupjob_webhook.go diff --git a/api/backups/v1alpha1/backupjob_types.go b/api/backups/v1alpha1/backupjob_types.go index f9aa42e6..6675c71d 100644 --- a/api/backups/v1alpha1/backupjob_types.go +++ b/api/backups/v1alpha1/backupjob_types.go @@ -52,6 +52,8 @@ type BackupJobSpec struct { // BackupClassName references a BackupClass that contains strategy and storage configuration. // The BackupClass will be resolved to determine the appropriate strategy and storage // based on the ApplicationRef. + // This field is immutable once the BackupJob is created. + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="backupClassName is immutable" BackupClassName string `json:"backupClassName"` } diff --git a/api/backups/v1alpha1/backupjob_webhook.go b/api/backups/v1alpha1/backupjob_webhook.go new file mode 100644 index 00000000..8c167c22 --- /dev/null +++ b/api/backups/v1alpha1/backupjob_webhook.go @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: Apache-2.0 +package v1alpha1 + +import ( + "context" + "fmt" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +// SetupWebhookWithManager registers the BackupJob webhook with the manager. +func SetupBackupJobWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(&BackupJob{}). + Complete() +} + +// +kubebuilder:webhook:path=/mutate-backups-cozystack-io-v1alpha1-backupjob,mutating=true,failurePolicy=fail,sideEffects=None,groups=backups.cozystack.io,resources=backupjobs,verbs=create;update,versions=v1alpha1,name=mbackupjob.kb.io,admissionReviewVersions=v1 + +var _ webhook.Defaulter = &BackupJob{} + +// Default implements webhook.Defaulter so a webhook will be registered for the type +func (j *BackupJob) Default() { + // No defaults needed for BackupJob currently +} + +// +kubebuilder:webhook:path=/validate-backups-cozystack-io-v1alpha1-backupjob,mutating=false,failurePolicy=fail,sideEffects=None,groups=backups.cozystack.io,resources=backupjobs,verbs=create;update,versions=v1alpha1,name=vbackupjob.kb.io,admissionReviewVersions=v1 + +var _ webhook.Validator = &BackupJob{} + +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type +func (j *BackupJob) ValidateCreate() (admission.Warnings, error) { + logger := log.FromContext(context.Background()) + logger.Info("validating BackupJob creation", "name", j.Name, "namespace", j.Namespace) + + // Validate that backupClassName is set + if j.Spec.BackupClassName == "" { + return nil, fmt.Errorf("backupClassName is required and cannot be empty") + } + + return nil, nil +} + +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type +func (j *BackupJob) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { + logger := log.FromContext(context.Background()) + logger.Info("validating BackupJob update", "name", j.Name, "namespace", j.Namespace) + + oldJob, ok := old.(*BackupJob) + if !ok { + return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a BackupJob but got a %T", old)) + } + + // Enforce immutability of backupClassName + if oldJob.Spec.BackupClassName != "" && oldJob.Spec.BackupClassName != j.Spec.BackupClassName { + return nil, fmt.Errorf("backupClassName is immutable and cannot be changed from %q to %q", oldJob.Spec.BackupClassName, j.Spec.BackupClassName) + } + + return nil, nil +} + +// ValidateDelete implements webhook.Validator so a webhook will be registered for the type +func (j *BackupJob) ValidateDelete() (admission.Warnings, error) { + // No validation needed for deletion + return nil, nil +} diff --git a/cmd/backup-controller/main.go b/cmd/backup-controller/main.go index a99f9350..3278ecf7 100644 --- a/cmd/backup-controller/main.go +++ b/cmd/backup-controller/main.go @@ -168,6 +168,12 @@ func main() { os.Exit(1) } + // Register BackupJob webhook for validation (immutability of backupClassName) + if err = backupsv1alpha1.SetupBackupJobWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "BackupJob") + os.Exit(1) + } + // +kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { From 0e05578f81974c055882637a60563426b0f4b2a6 Mon Sep 17 00:00:00 2001 From: Andrey Kolkov Date: Thu, 22 Jan 2026 12:51:14 +0400 Subject: [PATCH 097/889] fix tests Signed-off-by: Andrey Kolkov --- api/backups/v1alpha1/backupjob_webhook.go | 5 - .../v1alpha1/backupjob_webhook_test.go | 332 ++++++++++++++++++ 2 files changed, 332 insertions(+), 5 deletions(-) create mode 100644 api/backups/v1alpha1/backupjob_webhook_test.go diff --git a/api/backups/v1alpha1/backupjob_webhook.go b/api/backups/v1alpha1/backupjob_webhook.go index 8c167c22..06ec81b9 100644 --- a/api/backups/v1alpha1/backupjob_webhook.go +++ b/api/backups/v1alpha1/backupjob_webhook.go @@ -9,7 +9,6 @@ import ( "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/webhook" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) @@ -22,8 +21,6 @@ func SetupBackupJobWebhookWithManager(mgr ctrl.Manager) error { // +kubebuilder:webhook:path=/mutate-backups-cozystack-io-v1alpha1-backupjob,mutating=true,failurePolicy=fail,sideEffects=None,groups=backups.cozystack.io,resources=backupjobs,verbs=create;update,versions=v1alpha1,name=mbackupjob.kb.io,admissionReviewVersions=v1 -var _ webhook.Defaulter = &BackupJob{} - // Default implements webhook.Defaulter so a webhook will be registered for the type func (j *BackupJob) Default() { // No defaults needed for BackupJob currently @@ -31,8 +28,6 @@ func (j *BackupJob) Default() { // +kubebuilder:webhook:path=/validate-backups-cozystack-io-v1alpha1-backupjob,mutating=false,failurePolicy=fail,sideEffects=None,groups=backups.cozystack.io,resources=backupjobs,verbs=create;update,versions=v1alpha1,name=vbackupjob.kb.io,admissionReviewVersions=v1 -var _ webhook.Validator = &BackupJob{} - // ValidateCreate implements webhook.Validator so a webhook will be registered for the type func (j *BackupJob) ValidateCreate() (admission.Warnings, error) { logger := log.FromContext(context.Background()) diff --git a/api/backups/v1alpha1/backupjob_webhook_test.go b/api/backups/v1alpha1/backupjob_webhook_test.go new file mode 100644 index 00000000..5d85a455 --- /dev/null +++ b/api/backups/v1alpha1/backupjob_webhook_test.go @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: Apache-2.0 +package v1alpha1 + +import ( + "testing" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +func TestBackupJob_ValidateCreate(t *testing.T) { + tests := []struct { + name string + job *BackupJob + wantErr bool + errMsg string + }{ + { + name: "valid BackupJob with backupClassName", + job: &BackupJob{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + Namespace: "default", + }, + Spec: BackupJobSpec{ + ApplicationRef: corev1.TypedLocalObjectReference{ + Kind: "VirtualMachine", + Name: "vm1", + }, + BackupClassName: "velero", + }, + }, + wantErr: false, + }, + { + name: "BackupJob with empty backupClassName should be rejected", + job: &BackupJob{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + Namespace: "default", + }, + Spec: BackupJobSpec{ + ApplicationRef: corev1.TypedLocalObjectReference{ + Kind: "VirtualMachine", + Name: "vm1", + }, + BackupClassName: "", + }, + }, + wantErr: true, + errMsg: "backupClassName is required and cannot be empty", + }, + { + name: "BackupJob with whitespace-only backupClassName should be rejected", + job: &BackupJob{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + Namespace: "default", + }, + Spec: BackupJobSpec{ + ApplicationRef: corev1.TypedLocalObjectReference{ + Kind: "VirtualMachine", + Name: "vm1", + }, + BackupClassName: " ", + }, + }, + wantErr: false, // Whitespace is technically not empty, but this is acceptable + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + warnings, err := tt.job.ValidateCreate() + if (err != nil) != tt.wantErr { + t.Errorf("ValidateCreate() error = %v, wantErr %v", err, tt.wantErr) + return + } + if tt.wantErr && err != nil { + if tt.errMsg != "" && err.Error() != tt.errMsg { + t.Errorf("ValidateCreate() error message = %v, want %v", err.Error(), tt.errMsg) + } + } + if warnings != nil && len(warnings) > 0 { + t.Logf("ValidateCreate() warnings = %v", warnings) + } + }) + } +} + +func TestBackupJob_ValidateUpdate(t *testing.T) { + baseJob := &BackupJob{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + Namespace: "default", + }, + Spec: BackupJobSpec{ + ApplicationRef: corev1.TypedLocalObjectReference{ + Kind: "VirtualMachine", + Name: "vm1", + }, + BackupClassName: "velero", + }, + } + + tests := []struct { + name string + old runtime.Object + new *BackupJob + wantErr bool + errMsg string + }{ + { + name: "update with same backupClassName should succeed", + old: baseJob, + new: &BackupJob{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + Namespace: "default", + }, + Spec: BackupJobSpec{ + ApplicationRef: corev1.TypedLocalObjectReference{ + Kind: "VirtualMachine", + Name: "vm1", + }, + BackupClassName: "velero", // Same as old + }, + }, + wantErr: false, + }, + { + name: "update changing backupClassName should be rejected", + old: baseJob, + new: &BackupJob{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + Namespace: "default", + }, + Spec: BackupJobSpec{ + ApplicationRef: corev1.TypedLocalObjectReference{ + Kind: "VirtualMachine", + Name: "vm1", + }, + BackupClassName: "different-class", // Changed! + }, + }, + wantErr: true, + errMsg: "backupClassName is immutable and cannot be changed from \"velero\" to \"different-class\"", + }, + { + name: "update changing other fields but keeping backupClassName should succeed", + old: baseJob, + new: &BackupJob{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + Namespace: "default", + Labels: map[string]string{ + "new-label": "value", + }, + }, + Spec: BackupJobSpec{ + ApplicationRef: corev1.TypedLocalObjectReference{ + Kind: "VirtualMachine", + Name: "vm2", // Changed application + }, + BackupClassName: "velero", // Same as old + }, + }, + wantErr: false, + }, + { + name: "update when old backupClassName is empty should allow setting it", + old: &BackupJob{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + Namespace: "default", + }, + Spec: BackupJobSpec{ + ApplicationRef: corev1.TypedLocalObjectReference{ + Kind: "VirtualMachine", + Name: "vm1", + }, + BackupClassName: "", // Empty in old + }, + }, + new: &BackupJob{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + Namespace: "default", + }, + Spec: BackupJobSpec{ + ApplicationRef: corev1.TypedLocalObjectReference{ + Kind: "VirtualMachine", + Name: "vm1", + }, + BackupClassName: "velero", // Setting it for the first time + }, + }, + wantErr: false, // Allowed because old was empty + }, + { + name: "update changing from non-empty to different non-empty should be rejected", + old: &BackupJob{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + Namespace: "default", + }, + Spec: BackupJobSpec{ + ApplicationRef: corev1.TypedLocalObjectReference{ + Kind: "VirtualMachine", + Name: "vm1", + }, + BackupClassName: "class-a", + }, + }, + new: &BackupJob{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + Namespace: "default", + }, + Spec: BackupJobSpec{ + ApplicationRef: corev1.TypedLocalObjectReference{ + Kind: "VirtualMachine", + Name: "vm1", + }, + BackupClassName: "class-b", // Changed from class-a + }, + }, + wantErr: true, + errMsg: "backupClassName is immutable and cannot be changed from \"class-a\" to \"class-b\"", + }, + { + name: "update with invalid old object type should be rejected", + old: &corev1.Pod{ // Wrong type - will be cast to runtime.Object in test + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + Namespace: "default", + }, + }, + new: baseJob, + wantErr: true, + errMsg: "expected a BackupJob but got a", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + warnings, err := tt.new.ValidateUpdate(tt.old) + if (err != nil) != tt.wantErr { + t.Errorf("ValidateUpdate() error = %v, wantErr %v", err, tt.wantErr) + if err != nil { + t.Logf("Error message: %v", err.Error()) + } + return + } + if tt.wantErr && err != nil { + if tt.errMsg != "" { + if tt.errMsg != "" && !contains(err.Error(), tt.errMsg) { + t.Errorf("ValidateUpdate() error message = %v, want contains %v", err.Error(), tt.errMsg) + } + } + } + if warnings != nil && len(warnings) > 0 { + t.Logf("ValidateUpdate() warnings = %v", warnings) + } + }) + } +} + +func TestBackupJob_ValidateDelete(t *testing.T) { + job := &BackupJob{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + Namespace: "default", + }, + Spec: BackupJobSpec{ + ApplicationRef: corev1.TypedLocalObjectReference{ + Kind: "VirtualMachine", + Name: "vm1", + }, + BackupClassName: "velero", + }, + } + + warnings, err := job.ValidateDelete() + if err != nil { + t.Errorf("ValidateDelete() should never return an error, got %v", err) + } + if warnings != nil && len(warnings) > 0 { + t.Logf("ValidateDelete() warnings = %v", warnings) + } +} + +func TestBackupJob_Default(t *testing.T) { + job := &BackupJob{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-job", + Namespace: "default", + }, + Spec: BackupJobSpec{ + ApplicationRef: corev1.TypedLocalObjectReference{ + Kind: "VirtualMachine", + Name: "vm1", + }, + BackupClassName: "velero", + }, + } + + // Default() should not panic and should not modify the object + originalClassName := job.Spec.BackupClassName + job.Default() + if job.Spec.BackupClassName != originalClassName { + t.Errorf("Default() should not modify backupClassName, got %v, want %v", job.Spec.BackupClassName, originalClassName) + } +} + +// Helper function to check if a string contains a substring +func contains(s, substr string) bool { + if len(substr) == 0 { + return true + } + if len(s) < len(substr) { + return false + } + for i := 0; i <= len(s)-len(substr); i++ { + if s[i:i+len(substr)] == substr { + return true + } + } + return false +} From ffde02c9924aa3f7f9406f48274263a6837b8901 Mon Sep 17 00:00:00 2001 From: Andrey Kolkov Date: Thu, 22 Jan 2026 12:59:42 +0400 Subject: [PATCH 098/889] fix setup cache for backupClasses Signed-off-by: Andrey Kolkov --- cmd/backup-controller/main.go | 7 ++++++ cmd/backupstrategy-controller/main.go | 7 ++++++ .../backupcontroller/backupjob_controller.go | 25 ------------------- 3 files changed, 14 insertions(+), 25 deletions(-) diff --git a/cmd/backup-controller/main.go b/cmd/backup-controller/main.go index 3278ecf7..bbb92790 100644 --- a/cmd/backup-controller/main.go +++ b/cmd/backup-controller/main.go @@ -29,6 +29,8 @@ import ( 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/cache" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" "sigs.k8s.io/controller-runtime/pkg/metrics/filters" @@ -134,6 +136,11 @@ func main() { HealthProbeBindAddress: probeAddr, LeaderElection: enableLeaderElection, LeaderElectionID: "core.backups.cozystack.io", + Cache: cache.Options{ + ByObject: map[client.Object]cache.ByObject{ + &backupsv1alpha1.BackupClass{}: {}, + }, + }, // 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 diff --git a/cmd/backupstrategy-controller/main.go b/cmd/backupstrategy-controller/main.go index 61edc40a..68b33902 100644 --- a/cmd/backupstrategy-controller/main.go +++ b/cmd/backupstrategy-controller/main.go @@ -29,6 +29,8 @@ import ( 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/cache" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" "sigs.k8s.io/controller-runtime/pkg/metrics/filters" @@ -130,6 +132,11 @@ func main() { HealthProbeBindAddress: probeAddr, LeaderElection: enableLeaderElection, LeaderElectionID: "strategy.backups.cozystack.io", + Cache: cache.Options{ + ByObject: map[client.Object]cache.ByObject{ + &backupsv1alpha1.BackupClass{}: {}, + }, + }, // 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 diff --git a/internal/backupcontroller/backupjob_controller.go b/internal/backupcontroller/backupjob_controller.go index c04ec85d..58294972 100644 --- a/internal/backupcontroller/backupjob_controller.go +++ b/internal/backupcontroller/backupjob_controller.go @@ -14,10 +14,7 @@ import ( 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/handler" "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - "sigs.k8s.io/controller-runtime/pkg/source" strategyv1alpha1 "github.com/cozystack/cozystack/api/backups/strategy/v1alpha1" backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1" @@ -116,27 +113,5 @@ func (r *BackupJobReconciler) SetupWithManager(mgr ctrl.Manager) error { } return ctrl.NewControllerManagedBy(mgr). For(&backupsv1alpha1.BackupJob{}). - // Requeue BackupJobs when their referenced BackupClass changes - WatchesRawSource(source.Kind( - mgr.GetCache(), - &backupsv1alpha1.BackupClass{}, - handler.TypedEnqueueRequestsFromMapFunc(func(ctx context.Context, bc *backupsv1alpha1.BackupClass) []reconcile.Request { - var jobs backupsv1alpha1.BackupJobList - if err := r.List(ctx, &jobs, client.MatchingFields{"spec.backupClassName": bc.Name}); err != nil { - return nil - } - - reqs := make([]reconcile.Request, 0, len(jobs.Items)) - for _, job := range jobs.Items { - reqs = append(reqs, reconcile.Request{ - NamespacedName: types.NamespacedName{ - Namespace: job.Namespace, - Name: job.Name, - }, - }) - } - return reqs - }), - )). Complete(r) } From 875549786924db8ae2c4240b262fb9b731369437 Mon Sep 17 00:00:00 2001 From: Andrey Kolkov Date: Thu, 22 Jan 2026 13:55:00 +0400 Subject: [PATCH 099/889] fixes Signed-off-by: Andrey Kolkov --- api/backups/v1alpha1/backupjob_types.go | 14 ++++++++++++++ api/backups/v1alpha1/backupjob_webhook.go | 7 ++++--- api/backups/v1alpha1/backupjob_webhook_test.go | 8 +++++--- .../backupcontroller/backupclass_resolver.go | 17 +++++++---------- internal/backupcontroller/factory/backupjob.go | 18 +----------------- 5 files changed, 31 insertions(+), 33 deletions(-) diff --git a/api/backups/v1alpha1/backupjob_types.go b/api/backups/v1alpha1/backupjob_types.go index 6675c71d..7b251ceb 100644 --- a/api/backups/v1alpha1/backupjob_types.go +++ b/api/backups/v1alpha1/backupjob_types.go @@ -24,6 +24,10 @@ func init() { const ( OwningJobNameLabel = thisGroup + "/owned-by.BackupJobName" OwningJobNamespaceLabel = thisGroup + "/owned-by.BackupJobNamespace" + + // DefaultApplicationAPIGroup is the default API group for applications + // when not specified in ApplicationRef or ApplicationSelector. + DefaultApplicationAPIGroup = "apps.cozystack.io" ) // BackupJobPhase represents the lifecycle phase of a BackupJob. @@ -114,3 +118,13 @@ type BackupJobList struct { metav1.ListMeta `json:"metadata,omitempty"` Items []BackupJob `json:"items"` } + +// NormalizeApplicationRef sets the default apiGroup to DefaultApplicationAPIGroup if it's not specified. +// This function is exported so it can be used by other packages (e.g., controllers, factories). +func NormalizeApplicationRef(ref corev1.TypedLocalObjectReference) corev1.TypedLocalObjectReference { + if ref.APIGroup == nil || *ref.APIGroup == "" { + apiGroup := DefaultApplicationAPIGroup + ref.APIGroup = &apiGroup + } + return ref +} diff --git a/api/backups/v1alpha1/backupjob_webhook.go b/api/backups/v1alpha1/backupjob_webhook.go index 06ec81b9..064605c2 100644 --- a/api/backups/v1alpha1/backupjob_webhook.go +++ b/api/backups/v1alpha1/backupjob_webhook.go @@ -4,6 +4,7 @@ package v1alpha1 import ( "context" "fmt" + "strings" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -23,7 +24,7 @@ func SetupBackupJobWebhookWithManager(mgr ctrl.Manager) error { // Default implements webhook.Defaulter so a webhook will be registered for the type func (j *BackupJob) Default() { - // No defaults needed for BackupJob currently + j.Spec.ApplicationRef = NormalizeApplicationRef(j.Spec.ApplicationRef) } // +kubebuilder:webhook:path=/validate-backups-cozystack-io-v1alpha1-backupjob,mutating=false,failurePolicy=fail,sideEffects=None,groups=backups.cozystack.io,resources=backupjobs,verbs=create;update,versions=v1alpha1,name=vbackupjob.kb.io,admissionReviewVersions=v1 @@ -34,7 +35,7 @@ func (j *BackupJob) ValidateCreate() (admission.Warnings, error) { logger.Info("validating BackupJob creation", "name", j.Name, "namespace", j.Namespace) // Validate that backupClassName is set - if j.Spec.BackupClassName == "" { + if strings.TrimSpace(j.Spec.BackupClassName) == "" { return nil, fmt.Errorf("backupClassName is required and cannot be empty") } @@ -52,7 +53,7 @@ func (j *BackupJob) ValidateUpdate(old runtime.Object) (admission.Warnings, erro } // Enforce immutability of backupClassName - if oldJob.Spec.BackupClassName != "" && oldJob.Spec.BackupClassName != j.Spec.BackupClassName { + if oldJob.Spec.BackupClassName != j.Spec.BackupClassName { return nil, fmt.Errorf("backupClassName is immutable and cannot be changed from %q to %q", oldJob.Spec.BackupClassName, j.Spec.BackupClassName) } diff --git a/api/backups/v1alpha1/backupjob_webhook_test.go b/api/backups/v1alpha1/backupjob_webhook_test.go index 5d85a455..d143c5be 100644 --- a/api/backups/v1alpha1/backupjob_webhook_test.go +++ b/api/backups/v1alpha1/backupjob_webhook_test.go @@ -66,7 +66,8 @@ func TestBackupJob_ValidateCreate(t *testing.T) { BackupClassName: " ", }, }, - wantErr: false, // Whitespace is technically not empty, but this is acceptable + wantErr: true, + errMsg: "backupClassName is required and cannot be empty", }, } @@ -170,7 +171,7 @@ func TestBackupJob_ValidateUpdate(t *testing.T) { wantErr: false, }, { - name: "update when old backupClassName is empty should allow setting it", + name: "update when old backupClassName is empty should be rejected", old: &BackupJob{ ObjectMeta: metav1.ObjectMeta{ Name: "test-job", @@ -197,7 +198,8 @@ func TestBackupJob_ValidateUpdate(t *testing.T) { BackupClassName: "velero", // Setting it for the first time }, }, - wantErr: false, // Allowed because old was empty + wantErr: true, + errMsg: "backupClassName is immutable", }, { name: "update changing from non-empty to different non-empty should be rejected", diff --git a/internal/backupcontroller/backupclass_resolver.go b/internal/backupcontroller/backupclass_resolver.go index 593e3bb0..a1449c49 100644 --- a/internal/backupcontroller/backupclass_resolver.go +++ b/internal/backupcontroller/backupclass_resolver.go @@ -13,17 +13,14 @@ import ( const ( // DefaultApplicationAPIGroup is the default API group for applications // when not specified in ApplicationRef or ApplicationSelector. - DefaultApplicationAPIGroup = "apps.cozystack.io" + // Deprecated: Use backupsv1alpha1.DefaultApplicationAPIGroup instead. + DefaultApplicationAPIGroup = backupsv1alpha1.DefaultApplicationAPIGroup ) -// NormalizeApplicationRef sets the default apiGroup to "apps.cozystack.io" if it's not specified. -// This function is exported so it can be used by other packages (e.g., factory). +// NormalizeApplicationRef sets the default apiGroup to DefaultApplicationAPIGroup if it's not specified. +// Deprecated: Use backupsv1alpha1.NormalizeApplicationRef instead. func NormalizeApplicationRef(ref corev1.TypedLocalObjectReference) corev1.TypedLocalObjectReference { - if ref.APIGroup == nil || *ref.APIGroup == "" { - apiGroup := DefaultApplicationAPIGroup - ref.APIGroup = &apiGroup - } - return ref + return backupsv1alpha1.NormalizeApplicationRef(ref) } // ResolvedBackupConfig contains the resolved strategy and storage configuration @@ -52,7 +49,7 @@ func ResolveBackupClass( } // Determine application API group (already normalized, but extract for matching) - appAPIGroup := DefaultApplicationAPIGroup + appAPIGroup := backupsv1alpha1.DefaultApplicationAPIGroup if applicationRef.APIGroup != nil { appAPIGroup = *applicationRef.APIGroup } @@ -60,7 +57,7 @@ func ResolveBackupClass( // Find matching strategy for _, strategy := range backupClass.Spec.Strategies { // Normalize strategy's application selector (default apiGroup if not specified) - strategyAPIGroup := DefaultApplicationAPIGroup + strategyAPIGroup := backupsv1alpha1.DefaultApplicationAPIGroup if strategy.Application.APIGroup != nil && *strategy.Application.APIGroup != "" { strategyAPIGroup = *strategy.Application.APIGroup } diff --git a/internal/backupcontroller/factory/backupjob.go b/internal/backupcontroller/factory/backupjob.go index 6ca2ee21..1c51d434 100644 --- a/internal/backupcontroller/factory/backupjob.go +++ b/internal/backupcontroller/factory/backupjob.go @@ -9,25 +9,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -const ( - // defaultApplicationAPIGroup is the default API group for applications - // when not specified in ApplicationRef. - defaultApplicationAPIGroup = "apps.cozystack.io" -) - -// normalizeApplicationRef sets the default apiGroup to "apps.cozystack.io" if it's not specified. -// This is a local copy to avoid import cycle with backupcontroller package. -func normalizeApplicationRef(ref corev1.TypedLocalObjectReference) corev1.TypedLocalObjectReference { - if ref.APIGroup == nil || *ref.APIGroup == "" { - apiGroup := defaultApplicationAPIGroup - ref.APIGroup = &apiGroup - } - return ref -} - func BackupJob(p *backupsv1alpha1.Plan, scheduledFor time.Time) *backupsv1alpha1.BackupJob { // Normalize ApplicationRef (default apiGroup if not specified) - appRef := normalizeApplicationRef(*p.Spec.ApplicationRef.DeepCopy()) + appRef := backupsv1alpha1.NormalizeApplicationRef(*p.Spec.ApplicationRef.DeepCopy()) job := &backupsv1alpha1.BackupJob{ ObjectMeta: metav1.ObjectMeta{ From 73d6e3013ef6f0a5484061328585260976bee134 Mon Sep 17 00:00:00 2001 From: Andrey Kolkov Date: Thu, 22 Jan 2026 14:00:17 +0400 Subject: [PATCH 100/889] fix Signed-off-by: Andrey Kolkov --- api/backups/v1alpha1/backup_types.go | 3 +- api/backups/v1alpha1/backupclass_types.go | 41 ++++++++++++++++++- .../backupcontroller/backupclass_resolver.go | 2 +- .../backupclass_resolver_test.go | 20 ++++----- .../velerostrategy_controller_test.go | 2 +- 5 files changed, 53 insertions(+), 15 deletions(-) diff --git a/api/backups/v1alpha1/backup_types.go b/api/backups/v1alpha1/backup_types.go index 9371c27f..da4cf02e 100644 --- a/api/backups/v1alpha1/backup_types.go +++ b/api/backups/v1alpha1/backup_types.go @@ -59,7 +59,8 @@ type BackupSpec struct { // 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"` + // This references a cluster-scoped resource, so it does not include a namespace. + StrategyRef TypedClusterObjectReference `json:"strategyRef"` // TakenAt is the time at which the backup was taken (as reported by the // driver). It may differ slightly from metadata.creationTimestamp. diff --git a/api/backups/v1alpha1/backupclass_types.go b/api/backups/v1alpha1/backupclass_types.go index 19bcfd52..a7a2714d 100644 --- a/api/backups/v1alpha1/backupclass_types.go +++ b/api/backups/v1alpha1/backupclass_types.go @@ -6,7 +6,6 @@ package v1alpha1 import ( - corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -54,7 +53,8 @@ type BackupClassSpec struct { // BackupClassStrategy defines a backup strategy for a specific application type. type BackupClassStrategy struct { // StrategyRef references the driver-specific BackupStrategy (e.g., Velero). - StrategyRef corev1.TypedLocalObjectReference `json:"strategyRef"` + // This references a cluster-scoped resource, so it does not include a namespace. + StrategyRef TypedClusterObjectReference `json:"strategyRef"` // Application specifies which application types this strategy applies to. Application ApplicationSelector `json:"application"` @@ -66,6 +66,43 @@ type BackupClassStrategy struct { Parameters map[string]string `json:"parameters,omitempty"` } +// TypedClusterObjectReference contains enough information to let you locate a +// cluster-scoped typed resource. It does not include a namespace because +// cluster-scoped resources do not have namespaces. +type TypedClusterObjectReference struct { + // 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. + // +optional + APIGroup *string `json:"apiGroup,omitempty"` + + // Kind is the type of resource being referenced. + Kind string `json:"kind"` + + // Name is the name of resource being referenced. + Name string `json:"name"` +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TypedClusterObjectReference) DeepCopyInto(out *TypedClusterObjectReference) { + *out = *in + if in.APIGroup != nil { + in, out := &in.APIGroup, &out.APIGroup + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TypedClusterObjectReference. +func (in *TypedClusterObjectReference) DeepCopy() *TypedClusterObjectReference { + if in == nil { + return nil + } + out := new(TypedClusterObjectReference) + in.DeepCopyInto(out) + return out +} + // ApplicationSelector specifies which application types a strategy applies to. type ApplicationSelector struct { // APIGroup is the API group of the application. diff --git a/internal/backupcontroller/backupclass_resolver.go b/internal/backupcontroller/backupclass_resolver.go index a1449c49..b28c16cb 100644 --- a/internal/backupcontroller/backupclass_resolver.go +++ b/internal/backupcontroller/backupclass_resolver.go @@ -26,7 +26,7 @@ func NormalizeApplicationRef(ref corev1.TypedLocalObjectReference) corev1.TypedL // ResolvedBackupConfig contains the resolved strategy and storage configuration // from a BackupClass. type ResolvedBackupConfig struct { - StrategyRef corev1.TypedLocalObjectReference + StrategyRef backupsv1alpha1.TypedClusterObjectReference Parameters map[string]string } diff --git a/internal/backupcontroller/backupclass_resolver_test.go b/internal/backupcontroller/backupclass_resolver_test.go index a606f241..2fe77006 100644 --- a/internal/backupcontroller/backupclass_resolver_test.go +++ b/internal/backupcontroller/backupclass_resolver_test.go @@ -113,7 +113,7 @@ func TestResolveBackupClass(t *testing.T) { applicationRef corev1.TypedLocalObjectReference backupClassName string wantErr bool - expectedStrategyRef *corev1.TypedLocalObjectReference + expectedStrategyRef *backupsv1alpha1.TypedClusterObjectReference expectedParams map[string]string }{ { @@ -125,7 +125,7 @@ func TestResolveBackupClass(t *testing.T) { Spec: backupsv1alpha1.BackupClassSpec{ Strategies: []backupsv1alpha1.BackupClassStrategy{ { - StrategyRef: corev1.TypedLocalObjectReference{ + StrategyRef: backupsv1alpha1.TypedClusterObjectReference{ APIGroup: stringPtr("strategy.backups.cozystack.io"), Kind: "Velero", Name: "velero-strategy-vm", @@ -138,7 +138,7 @@ func TestResolveBackupClass(t *testing.T) { }, }, { - StrategyRef: corev1.TypedLocalObjectReference{ + StrategyRef: backupsv1alpha1.TypedClusterObjectReference{ APIGroup: stringPtr("strategy.backups.cozystack.io"), Kind: "Velero", Name: "velero-strategy-mysql", @@ -159,7 +159,7 @@ func TestResolveBackupClass(t *testing.T) { }, backupClassName: "velero", wantErr: false, - expectedStrategyRef: &corev1.TypedLocalObjectReference{ + expectedStrategyRef: &backupsv1alpha1.TypedClusterObjectReference{ APIGroup: stringPtr("strategy.backups.cozystack.io"), Kind: "Velero", Name: "velero-strategy-vm", @@ -177,7 +177,7 @@ func TestResolveBackupClass(t *testing.T) { Spec: backupsv1alpha1.BackupClassSpec{ Strategies: []backupsv1alpha1.BackupClassStrategy{ { - StrategyRef: corev1.TypedLocalObjectReference{ + StrategyRef: backupsv1alpha1.TypedClusterObjectReference{ APIGroup: stringPtr("strategy.backups.cozystack.io"), Kind: "Velero", Name: "velero-strategy-mysql", @@ -200,7 +200,7 @@ func TestResolveBackupClass(t *testing.T) { }, backupClassName: "velero", wantErr: false, - expectedStrategyRef: &corev1.TypedLocalObjectReference{ + expectedStrategyRef: &backupsv1alpha1.TypedClusterObjectReference{ APIGroup: stringPtr("strategy.backups.cozystack.io"), Kind: "Velero", Name: "velero-strategy-mysql", @@ -218,7 +218,7 @@ func TestResolveBackupClass(t *testing.T) { Spec: backupsv1alpha1.BackupClassSpec{ Strategies: []backupsv1alpha1.BackupClassStrategy{ { - StrategyRef: corev1.TypedLocalObjectReference{ + StrategyRef: backupsv1alpha1.TypedClusterObjectReference{ APIGroup: stringPtr("strategy.backups.cozystack.io"), Kind: "Velero", Name: "velero-strategy-vm", @@ -240,7 +240,7 @@ func TestResolveBackupClass(t *testing.T) { }, backupClassName: "velero", wantErr: false, - expectedStrategyRef: &corev1.TypedLocalObjectReference{ + expectedStrategyRef: &backupsv1alpha1.TypedClusterObjectReference{ APIGroup: stringPtr("strategy.backups.cozystack.io"), Kind: "Velero", Name: "velero-strategy-vm", @@ -275,7 +275,7 @@ func TestResolveBackupClass(t *testing.T) { Spec: backupsv1alpha1.BackupClassSpec{ Strategies: []backupsv1alpha1.BackupClassStrategy{ { - StrategyRef: corev1.TypedLocalObjectReference{ + StrategyRef: backupsv1alpha1.TypedClusterObjectReference{ APIGroup: stringPtr("strategy.backups.cozystack.io"), Kind: "Velero", Name: "velero-strategy-vm", @@ -303,7 +303,7 @@ func TestResolveBackupClass(t *testing.T) { Spec: backupsv1alpha1.BackupClassSpec{ Strategies: []backupsv1alpha1.BackupClassStrategy{ { - StrategyRef: corev1.TypedLocalObjectReference{ + StrategyRef: backupsv1alpha1.TypedClusterObjectReference{ APIGroup: stringPtr("strategy.backups.cozystack.io"), Kind: "Velero", Name: "velero-strategy-vm", diff --git a/internal/backupcontroller/velerostrategy_controller_test.go b/internal/backupcontroller/velerostrategy_controller_test.go index a7de2076..0241a2b9 100644 --- a/internal/backupcontroller/velerostrategy_controller_test.go +++ b/internal/backupcontroller/velerostrategy_controller_test.go @@ -137,7 +137,7 @@ func TestCreateVeleroBackup_TemplateContext(t *testing.T) { // Create ResolvedBackupConfig with parameters resolved := &ResolvedBackupConfig{ - StrategyRef: corev1.TypedLocalObjectReference{ + StrategyRef: backupsv1alpha1.TypedClusterObjectReference{ APIGroup: stringPtr("strategy.backups.cozystack.io"), Kind: "Velero", Name: "velero-strategy", From beb6e1a0ba3c8759c04236a6c28a6c06e3e93d0a Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 22 Jan 2026 17:53:52 +0100 Subject: [PATCH 101/889] [mongodb] Remove user-configurable images from MongoDB chart Remove the ability for users to specify custom container images (images.pmm and images.backup) in the MongoDB application values. This is a security hardening measure - allowing users to specify arbitrary container images could lead to running malicious or compromised images, supply chain attacks, or privilege escalation. The images are now hardcoded in the template: - percona/pmm-client:2.44.1 - percona/percona-backup-mongodb:2.11.0 Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- packages/apps/mongodb/README.md | 9 -------- packages/apps/mongodb/templates/mongodb.yaml | 4 ++-- packages/apps/mongodb/values.schema.json | 21 ------------------- packages/apps/mongodb/values.yaml | 13 ------------ .../system/mongodb-rd/cozyrds/mongodb.yaml | 4 ++-- 5 files changed, 4 insertions(+), 47 deletions(-) diff --git a/packages/apps/mongodb/README.md b/packages/apps/mongodb/README.md index 84f2bf3f..a5fb17fb 100644 --- a/packages/apps/mongodb/README.md +++ b/packages/apps/mongodb/README.md @@ -52,15 +52,6 @@ Run `helm upgrade` after MongoDB is ready to populate the credentials secret wit | `version` | MongoDB major version to deploy. | `string` | `v8` | -### Image configuration - -| Name | Description | Type | Value | -| --------------- | -------------------------------------- | -------- | --------------------------------------- | -| `images` | Container images used by the operator. | `object` | `{}` | -| `images.pmm` | PMM client image for monitoring. | `string` | `percona/pmm-client:2.44.1` | -| `images.backup` | Percona Backup for MongoDB image. | `string` | `percona/percona-backup-mongodb:2.11.0` | - - ### Sharding configuration | Name | Description | Type | Value | diff --git a/packages/apps/mongodb/templates/mongodb.yaml b/packages/apps/mongodb/templates/mongodb.yaml index ffd358f4..dabb51d4 100644 --- a/packages/apps/mongodb/templates/mongodb.yaml +++ b/packages/apps/mongodb/templates/mongodb.yaml @@ -23,7 +23,7 @@ spec: pmm: enabled: false - image: {{ .Values.images.pmm }} + image: percona/pmm-client:2.44.1 serverHost: "" sharding: @@ -123,7 +123,7 @@ spec: backup: enabled: {{ .Values.backup.enabled | default false }} - image: {{ .Values.images.backup }} + image: percona/percona-backup-mongodb:2.11.0 {{- if .Values.backup.enabled }} storages: s3-storage: diff --git a/packages/apps/mongodb/values.schema.json b/packages/apps/mongodb/values.schema.json index 74ffec7c..817c3463 100644 --- a/packages/apps/mongodb/values.schema.json +++ b/packages/apps/mongodb/values.schema.json @@ -78,27 +78,6 @@ "type": "boolean", "default": false }, - "images": { - "description": "Container images used by the operator.", - "type": "object", - "default": {}, - "required": [ - "backup", - "pmm" - ], - "properties": { - "backup": { - "description": "Percona Backup for MongoDB image.", - "type": "string", - "default": "percona/percona-backup-mongodb:2.11.0" - }, - "pmm": { - "description": "PMM client image for monitoring.", - "type": "string", - "default": "percona/pmm-client:2.44.1" - } - } - }, "replicas": { "description": "Number of MongoDB replicas in replica set.", "type": "integer", diff --git a/packages/apps/mongodb/values.yaml b/packages/apps/mongodb/values.yaml index ea3a18dd..003a0c9e 100644 --- a/packages/apps/mongodb/values.yaml +++ b/packages/apps/mongodb/values.yaml @@ -42,19 +42,6 @@ external: false ## @param {Version} version - MongoDB major version to deploy. version: v8 -## -## @section Image configuration -## - -## @typedef {struct} Images - Container image configuration. -## @field {string} pmm - PMM client image for monitoring. -## @field {string} backup - Percona Backup for MongoDB image. - -## @param {Images} images - Container images used by the operator. -images: - pmm: "percona/pmm-client:2.44.1" - backup: "percona/percona-backup-mongodb:2.11.0" - ## ## @section Sharding configuration ## diff --git a/packages/system/mongodb-rd/cozyrds/mongodb.yaml b/packages/system/mongodb-rd/cozyrds/mongodb.yaml index 996dde18..0edb0d00 100644 --- a/packages/system/mongodb-rd/cozyrds/mongodb.yaml +++ b/packages/system/mongodb-rd/cozyrds/mongodb.yaml @@ -8,7 +8,7 @@ spec: singular: mongodb plural: mongodbs openAPISchema: |- - {"title":"Chart Values","type":"object","properties":{"backup":{"description":"Backup configuration.","type":"object","default":{},"required":["enabled"],"properties":{"destinationPath":{"description":"Destination path for backups (e.g. s3://bucket/path/).","type":"string","default":"s3://bucket/path/to/folder/"},"enabled":{"description":"Enable regular backups.","type":"boolean","default":false},"endpointURL":{"description":"S3 endpoint URL for uploads.","type":"string","default":"http://minio-gateway-service:9000"},"retentionPolicy":{"description":"Retention policy (e.g. \"30d\").","type":"string","default":"30d"},"s3AccessKey":{"description":"Access key for S3 authentication.","type":"string","default":""},"s3SecretKey":{"description":"Secret key for S3 authentication.","type":"string","default":""},"schedule":{"description":"Cron schedule for automated backups.","type":"string","default":"0 2 * * *"}}},"bootstrap":{"description":"Bootstrap configuration.","type":"object","default":{},"required":["backupName","enabled"],"properties":{"backupName":{"description":"Name of backup to restore from.","type":"string","default":""},"enabled":{"description":"Whether to restore from a backup.","type":"boolean","default":false},"recoveryTime":{"description":"Timestamp for point-in-time recovery; empty means latest.","type":"string","default":""}}},"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"images":{"description":"Container images used by the operator.","type":"object","default":{},"required":["backup","pmm"],"properties":{"backup":{"description":"Percona Backup for MongoDB image.","type":"string","default":"percona/percona-backup-mongodb:2.11.0"},"pmm":{"description":"PMM client image for monitoring.","type":"string","default":"percona/pmm-client:2.44.1"}}},"replicas":{"description":"Number of MongoDB replicas in replica set.","type":"integer","default":3},"resources":{"description":"Explicit CPU and memory configuration for each MongoDB replica. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available to each replica.","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 (RAM) available to each replica.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"small","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]},"sharding":{"description":"Enable sharded cluster mode. When disabled, deploys a replica set.","type":"boolean","default":false},"shardingConfig":{"description":"Configuration for sharded cluster mode.","type":"object","default":{},"required":["configServerSize","configServers","mongos"],"properties":{"configServerSize":{"description":"PVC size for config servers.","default":"3Gi","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},"configServers":{"description":"Number of config server replicas.","type":"integer","default":3},"mongos":{"description":"Number of mongos router replicas.","type":"integer","default":2},"shards":{"description":"List of shard configurations.","type":"array","default":[{"name":"rs0","replicas":3,"size":"10Gi"}],"items":{"type":"object","required":["name","replicas","size"],"properties":{"name":{"description":"Shard name.","type":"string"},"replicas":{"description":"Number of replicas in this shard.","type":"integer"},"size":{"description":"PVC size for this shard.","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}}}}}},"size":{"description":"Persistent Volume Claim size available for application data.","default":"10Gi","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},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":""},"users":{"description":"Custom MongoDB users configuration map.","type":"object","default":{},"additionalProperties":{"type":"object","required":["db"],"properties":{"db":{"description":"Database to authenticate against.","type":"string"},"password":{"description":"Password for the user (auto-generated if omitted).","type":"string"},"roles":{"description":"List of MongoDB roles with database scope.","type":"array","items":{"type":"object","required":["db","name"],"properties":{"db":{"description":"Database the role applies to.","type":"string"},"name":{"description":"Role name (e.g., readWrite, dbAdmin, clusterAdmin).","type":"string"}}}}}}},"version":{"description":"MongoDB major version to deploy.","type":"string","default":"v8","enum":["v8","v7","v6"]}}} + {"title":"Chart Values","type":"object","properties":{"backup":{"description":"Backup configuration.","type":"object","default":{},"required":["enabled"],"properties":{"destinationPath":{"description":"Destination path for backups (e.g. s3://bucket/path/).","type":"string","default":"s3://bucket/path/to/folder/"},"enabled":{"description":"Enable regular backups.","type":"boolean","default":false},"endpointURL":{"description":"S3 endpoint URL for uploads.","type":"string","default":"http://minio-gateway-service:9000"},"retentionPolicy":{"description":"Retention policy (e.g. \"30d\").","type":"string","default":"30d"},"s3AccessKey":{"description":"Access key for S3 authentication.","type":"string","default":""},"s3SecretKey":{"description":"Secret key for S3 authentication.","type":"string","default":""},"schedule":{"description":"Cron schedule for automated backups.","type":"string","default":"0 2 * * *"}}},"bootstrap":{"description":"Bootstrap configuration.","type":"object","default":{},"required":["backupName","enabled"],"properties":{"backupName":{"description":"Name of backup to restore from.","type":"string","default":""},"enabled":{"description":"Whether to restore from a backup.","type":"boolean","default":false},"recoveryTime":{"description":"Timestamp for point-in-time recovery; empty means latest.","type":"string","default":""}}},"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"replicas":{"description":"Number of MongoDB replicas in replica set.","type":"integer","default":3},"resources":{"description":"Explicit CPU and memory configuration for each MongoDB replica. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available to each replica.","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 (RAM) available to each replica.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"small","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]},"sharding":{"description":"Enable sharded cluster mode. When disabled, deploys a replica set.","type":"boolean","default":false},"shardingConfig":{"description":"Configuration for sharded cluster mode.","type":"object","default":{},"required":["configServerSize","configServers","mongos"],"properties":{"configServerSize":{"description":"PVC size for config servers.","default":"3Gi","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},"configServers":{"description":"Number of config server replicas.","type":"integer","default":3},"mongos":{"description":"Number of mongos router replicas.","type":"integer","default":2},"shards":{"description":"List of shard configurations.","type":"array","default":[{"name":"rs0","replicas":3,"size":"10Gi"}],"items":{"type":"object","required":["name","replicas","size"],"properties":{"name":{"description":"Shard name.","type":"string"},"replicas":{"description":"Number of replicas in this shard.","type":"integer"},"size":{"description":"PVC size for this shard.","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}}}}}},"size":{"description":"Persistent Volume Claim size available for application data.","default":"10Gi","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},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":""},"users":{"description":"Custom MongoDB users configuration map.","type":"object","default":{},"additionalProperties":{"type":"object","required":["db"],"properties":{"db":{"description":"Database to authenticate against.","type":"string"},"password":{"description":"Password for the user (auto-generated if omitted).","type":"string"},"roles":{"description":"List of MongoDB roles with database scope.","type":"array","items":{"type":"object","required":["db","name"],"properties":{"db":{"description":"Database the role applies to.","type":"string"},"name":{"description":"Role name (e.g., readWrite, dbAdmin, clusterAdmin).","type":"string"}}}}}}},"version":{"description":"MongoDB major version to deploy.","type":"string","default":"v8","enum":["v8","v7","v6"]}}} release: prefix: mongodb- labels: @@ -25,7 +25,7 @@ spec: tags: - database icon: PHN2ZyB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgdmlld0JveD0iMCAwIDE0NCAxNDQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNDQiIGhlaWdodD0iMTQ0IiByeD0iMjQiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcl9tb25nb2RiKSIvPgo8cGF0aCBkPSJNNzIgMjRDNzIgMjQgNzIgMjQgNzIgMjRDNzIgMjQgNTggNDAgNTggNjJDNTggODQgNzIgMTIwIDcyIDEyMEM3MiAxMjAgODYgODQgODYgNjJDODYgNDAgNzIgMjQgNzIgMjRaIiBmaWxsPSIjMDBFRDY0Ii8+CjxwYXRoIGQ9Ik03MiAxMjBDNzIgMTIwIDg2IDg0IDg2IDYyQzg2IDQwIDcyIDI0IDcyIDI0IiBzdHJva2U9IiMwMDY4NEEiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxwYXRoIGQ9Ik03MiAyNEM3MiAyNCA1OCA0MCA1OCA2MkM1OCA4NCA3MiAxMjAgNzIgMTIwIiBzdHJva2U9IiMwMDFFMkIiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxyZWN0IHg9IjY5IiB5PSIxMDgiIHdpZHRoPSI2IiBoZWlnaHQ9IjE2IiByeD0iMiIgZmlsbD0iIzAwNjg0QSIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyX21vbmdvZGIiIHgxPSIxNDAiIHkxPSIxMzAuNSIgeDI9IjQiIHkyPSI5LjQ5OTk5IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiMwMDFFMkIiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMDIzNDMwIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg== - keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "replicas"], ["spec", "resources"], ["spec", "resourcesPreset"], ["spec", "size"], ["spec", "storageClass"], ["spec", "external"], ["spec", "version"], ["spec", "images"], ["spec", "images", "pmm"], ["spec", "images", "backup"], ["spec", "sharding"], ["spec", "shardingConfig"], ["spec", "shardingConfig", "configServers"], ["spec", "shardingConfig", "configServerSize"], ["spec", "shardingConfig", "mongos"], ["spec", "shardingConfig", "shards"], ["spec", "users"], ["spec", "backup"], ["spec", "backup", "enabled"], ["spec", "backup", "schedule"], ["spec", "backup", "retentionPolicy"], ["spec", "backup", "destinationPath"], ["spec", "backup", "endpointURL"], ["spec", "backup", "s3AccessKey"], ["spec", "backup", "s3SecretKey"], ["spec", "bootstrap"], ["spec", "bootstrap", "enabled"], ["spec", "bootstrap", "recoveryTime"], ["spec", "bootstrap", "backupName"]] + keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "replicas"], ["spec", "resources"], ["spec", "resourcesPreset"], ["spec", "size"], ["spec", "storageClass"], ["spec", "external"], ["spec", "version"], ["spec", "sharding"], ["spec", "shardingConfig"], ["spec", "shardingConfig", "configServers"], ["spec", "shardingConfig", "configServerSize"], ["spec", "shardingConfig", "mongos"], ["spec", "shardingConfig", "shards"], ["spec", "users"], ["spec", "backup"], ["spec", "backup", "enabled"], ["spec", "backup", "schedule"], ["spec", "backup", "retentionPolicy"], ["spec", "backup", "destinationPath"], ["spec", "backup", "endpointURL"], ["spec", "backup", "s3AccessKey"], ["spec", "backup", "s3SecretKey"], ["spec", "bootstrap"], ["spec", "bootstrap", "enabled"], ["spec", "bootstrap", "recoveryTime"], ["spec", "bootstrap", "backupName"]] secrets: exclude: [] include: From b4271c4702b179724676d8c0f8334f4aafc0b201 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 22 Jan 2026 20:30:01 +0100 Subject: [PATCH 102/889] [tenant] Document resourceQuotas configuration Add documentation for the resourceQuotas parameter explaining supported resource types and their usage. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- packages/apps/tenant/README.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/packages/apps/tenant/README.md b/packages/apps/tenant/README.md index 222c98a5..6b407cfb 100644 --- a/packages/apps/tenant/README.md +++ b/packages/apps/tenant/README.md @@ -79,3 +79,34 @@ tenant-u1 | `isolated` | Enforce tenant namespace with network policies (default: true). | `bool` | `true` | | `resourceQuotas` | Define resource quotas for the tenant. | `map[string]quantity` | `{}` | + +## Configuration + +### Resource Quotas + +The `resourceQuotas` parameter allows you to limit resources available to the tenant. Supported keys include: + +**Compute resources** (converted to `requests.X` and `limits.X`): +- `cpu` - Total CPU cores (e.g., `"4"` or `"500m"`) +- `memory` - Total memory (e.g., `"4Gi"` or `"512Mi"`) +- `ephemeral-storage` - Ephemeral storage limit (e.g., `"10Gi"`) +- `storage` - Total persistent storage (e.g., `"100Gi"`) + +**Object count quotas** (passed as-is): +- `pods` - Maximum number of pods +- `services` - Maximum number of services +- `services.loadbalancers` - Maximum number of LoadBalancer services +- `services.nodeports` - Maximum number of NodePort services +- `configmaps` - Maximum number of ConfigMaps +- `secrets` - Maximum number of Secrets +- `persistentvolumeclaims` - Maximum number of PVCs + +**Example:** +```yaml +resourceQuotas: + cpu: 4 + memory: 4Gi + storage: 10Gi + services.loadbalancers: "3" + pods: "50" +``` From d1d0627f0e79de7558b9e4bbb64e2300b204707e Mon Sep 17 00:00:00 2001 From: Andrey Kolkov Date: Fri, 23 Jan 2026 00:26:28 +0400 Subject: [PATCH 103/889] Revert "fix about wrong strategy ref type" This reverts commit 73d6e3013ef6f0a5484061328585260976bee134. Signed-off-by: Andrey Kolkov --- api/backups/v1alpha1/backup_types.go | 3 +- api/backups/v1alpha1/backupclass_types.go | 41 +------------------ .../backupcontroller/backupclass_resolver.go | 2 +- .../backupclass_resolver_test.go | 20 ++++----- .../velerostrategy_controller_test.go | 2 +- 5 files changed, 15 insertions(+), 53 deletions(-) diff --git a/api/backups/v1alpha1/backup_types.go b/api/backups/v1alpha1/backup_types.go index da4cf02e..9371c27f 100644 --- a/api/backups/v1alpha1/backup_types.go +++ b/api/backups/v1alpha1/backup_types.go @@ -59,8 +59,7 @@ type BackupSpec struct { // StrategyRef refers to the driver-specific BackupStrategy that was used // to create this backup. This allows the driver to later perform restores. - // This references a cluster-scoped resource, so it does not include a namespace. - StrategyRef TypedClusterObjectReference `json:"strategyRef"` + 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. diff --git a/api/backups/v1alpha1/backupclass_types.go b/api/backups/v1alpha1/backupclass_types.go index a7a2714d..19bcfd52 100644 --- a/api/backups/v1alpha1/backupclass_types.go +++ b/api/backups/v1alpha1/backupclass_types.go @@ -6,6 +6,7 @@ package v1alpha1 import ( + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -53,8 +54,7 @@ type BackupClassSpec struct { // BackupClassStrategy defines a backup strategy for a specific application type. type BackupClassStrategy struct { // StrategyRef references the driver-specific BackupStrategy (e.g., Velero). - // This references a cluster-scoped resource, so it does not include a namespace. - StrategyRef TypedClusterObjectReference `json:"strategyRef"` + StrategyRef corev1.TypedLocalObjectReference `json:"strategyRef"` // Application specifies which application types this strategy applies to. Application ApplicationSelector `json:"application"` @@ -66,43 +66,6 @@ type BackupClassStrategy struct { Parameters map[string]string `json:"parameters,omitempty"` } -// TypedClusterObjectReference contains enough information to let you locate a -// cluster-scoped typed resource. It does not include a namespace because -// cluster-scoped resources do not have namespaces. -type TypedClusterObjectReference struct { - // 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. - // +optional - APIGroup *string `json:"apiGroup,omitempty"` - - // Kind is the type of resource being referenced. - Kind string `json:"kind"` - - // Name is the name of resource being referenced. - Name string `json:"name"` -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TypedClusterObjectReference) DeepCopyInto(out *TypedClusterObjectReference) { - *out = *in - if in.APIGroup != nil { - in, out := &in.APIGroup, &out.APIGroup - *out = new(string) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TypedClusterObjectReference. -func (in *TypedClusterObjectReference) DeepCopy() *TypedClusterObjectReference { - if in == nil { - return nil - } - out := new(TypedClusterObjectReference) - in.DeepCopyInto(out) - return out -} - // ApplicationSelector specifies which application types a strategy applies to. type ApplicationSelector struct { // APIGroup is the API group of the application. diff --git a/internal/backupcontroller/backupclass_resolver.go b/internal/backupcontroller/backupclass_resolver.go index b28c16cb..a1449c49 100644 --- a/internal/backupcontroller/backupclass_resolver.go +++ b/internal/backupcontroller/backupclass_resolver.go @@ -26,7 +26,7 @@ func NormalizeApplicationRef(ref corev1.TypedLocalObjectReference) corev1.TypedL // ResolvedBackupConfig contains the resolved strategy and storage configuration // from a BackupClass. type ResolvedBackupConfig struct { - StrategyRef backupsv1alpha1.TypedClusterObjectReference + StrategyRef corev1.TypedLocalObjectReference Parameters map[string]string } diff --git a/internal/backupcontroller/backupclass_resolver_test.go b/internal/backupcontroller/backupclass_resolver_test.go index 2fe77006..a606f241 100644 --- a/internal/backupcontroller/backupclass_resolver_test.go +++ b/internal/backupcontroller/backupclass_resolver_test.go @@ -113,7 +113,7 @@ func TestResolveBackupClass(t *testing.T) { applicationRef corev1.TypedLocalObjectReference backupClassName string wantErr bool - expectedStrategyRef *backupsv1alpha1.TypedClusterObjectReference + expectedStrategyRef *corev1.TypedLocalObjectReference expectedParams map[string]string }{ { @@ -125,7 +125,7 @@ func TestResolveBackupClass(t *testing.T) { Spec: backupsv1alpha1.BackupClassSpec{ Strategies: []backupsv1alpha1.BackupClassStrategy{ { - StrategyRef: backupsv1alpha1.TypedClusterObjectReference{ + StrategyRef: corev1.TypedLocalObjectReference{ APIGroup: stringPtr("strategy.backups.cozystack.io"), Kind: "Velero", Name: "velero-strategy-vm", @@ -138,7 +138,7 @@ func TestResolveBackupClass(t *testing.T) { }, }, { - StrategyRef: backupsv1alpha1.TypedClusterObjectReference{ + StrategyRef: corev1.TypedLocalObjectReference{ APIGroup: stringPtr("strategy.backups.cozystack.io"), Kind: "Velero", Name: "velero-strategy-mysql", @@ -159,7 +159,7 @@ func TestResolveBackupClass(t *testing.T) { }, backupClassName: "velero", wantErr: false, - expectedStrategyRef: &backupsv1alpha1.TypedClusterObjectReference{ + expectedStrategyRef: &corev1.TypedLocalObjectReference{ APIGroup: stringPtr("strategy.backups.cozystack.io"), Kind: "Velero", Name: "velero-strategy-vm", @@ -177,7 +177,7 @@ func TestResolveBackupClass(t *testing.T) { Spec: backupsv1alpha1.BackupClassSpec{ Strategies: []backupsv1alpha1.BackupClassStrategy{ { - StrategyRef: backupsv1alpha1.TypedClusterObjectReference{ + StrategyRef: corev1.TypedLocalObjectReference{ APIGroup: stringPtr("strategy.backups.cozystack.io"), Kind: "Velero", Name: "velero-strategy-mysql", @@ -200,7 +200,7 @@ func TestResolveBackupClass(t *testing.T) { }, backupClassName: "velero", wantErr: false, - expectedStrategyRef: &backupsv1alpha1.TypedClusterObjectReference{ + expectedStrategyRef: &corev1.TypedLocalObjectReference{ APIGroup: stringPtr("strategy.backups.cozystack.io"), Kind: "Velero", Name: "velero-strategy-mysql", @@ -218,7 +218,7 @@ func TestResolveBackupClass(t *testing.T) { Spec: backupsv1alpha1.BackupClassSpec{ Strategies: []backupsv1alpha1.BackupClassStrategy{ { - StrategyRef: backupsv1alpha1.TypedClusterObjectReference{ + StrategyRef: corev1.TypedLocalObjectReference{ APIGroup: stringPtr("strategy.backups.cozystack.io"), Kind: "Velero", Name: "velero-strategy-vm", @@ -240,7 +240,7 @@ func TestResolveBackupClass(t *testing.T) { }, backupClassName: "velero", wantErr: false, - expectedStrategyRef: &backupsv1alpha1.TypedClusterObjectReference{ + expectedStrategyRef: &corev1.TypedLocalObjectReference{ APIGroup: stringPtr("strategy.backups.cozystack.io"), Kind: "Velero", Name: "velero-strategy-vm", @@ -275,7 +275,7 @@ func TestResolveBackupClass(t *testing.T) { Spec: backupsv1alpha1.BackupClassSpec{ Strategies: []backupsv1alpha1.BackupClassStrategy{ { - StrategyRef: backupsv1alpha1.TypedClusterObjectReference{ + StrategyRef: corev1.TypedLocalObjectReference{ APIGroup: stringPtr("strategy.backups.cozystack.io"), Kind: "Velero", Name: "velero-strategy-vm", @@ -303,7 +303,7 @@ func TestResolveBackupClass(t *testing.T) { Spec: backupsv1alpha1.BackupClassSpec{ Strategies: []backupsv1alpha1.BackupClassStrategy{ { - StrategyRef: backupsv1alpha1.TypedClusterObjectReference{ + StrategyRef: corev1.TypedLocalObjectReference{ APIGroup: stringPtr("strategy.backups.cozystack.io"), Kind: "Velero", Name: "velero-strategy-vm", diff --git a/internal/backupcontroller/velerostrategy_controller_test.go b/internal/backupcontroller/velerostrategy_controller_test.go index 0241a2b9..a7de2076 100644 --- a/internal/backupcontroller/velerostrategy_controller_test.go +++ b/internal/backupcontroller/velerostrategy_controller_test.go @@ -137,7 +137,7 @@ func TestCreateVeleroBackup_TemplateContext(t *testing.T) { // Create ResolvedBackupConfig with parameters resolved := &ResolvedBackupConfig{ - StrategyRef: backupsv1alpha1.TypedClusterObjectReference{ + StrategyRef: corev1.TypedLocalObjectReference{ APIGroup: stringPtr("strategy.backups.cozystack.io"), Kind: "Velero", Name: "velero-strategy", From f1a3f4db290f5b7d424fc88199bb3256fd7f6723 Mon Sep 17 00:00:00 2001 From: Kirill Ilin Date: Fri, 23 Jan 2026 11:50:48 +0500 Subject: [PATCH 104/889] [dashboard] Add External IPs tab for LoadBalancer services Introduce a new "External IPs" sidebar and associated dashboard factory to display services of type LoadBalancer. Signed-off-by: Kirill Ilin --- internal/controller/dashboard/sidebar.go | 8 ++++ .../controller/dashboard/static_refactored.go | 41 +++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/internal/controller/dashboard/sidebar.go b/internal/controller/dashboard/sidebar.go index 5bb378c2..a6ade387 100644 --- a/internal/controller/dashboard/sidebar.go +++ b/internal/controller/dashboard/sidebar.go @@ -111,6 +111,8 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Applicati keysAndTags["services"] = []any{"service-sidebar"} keysAndTags["secrets"] = []any{"secret-sidebar"} keysAndTags["ingresses"] = []any{"ingress-sidebar"} + // Add sidebar for v1/services type loadbalancer + keysAndTags["loadbalancer-services"] = []any{"external-ips-sidebar"} // Add sidebar for backups.cozystack.io Plan resource keysAndTags["plans"] = []any{"plan-sidebar"} @@ -210,6 +212,11 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Applicati "label": "Modules", "link": "/openapi-ui/{clusterName}/{namespace}/api-table/core.cozystack.io/v1alpha1/tenantmodules", }, + map[string]any{ + "key": "loadbalancer-services", + "label": "External IPs", + "link": "/openapi-ui/{clusterName}/{namespace}/factory/external-ips", + }, map[string]any{ "key": "tenants", "label": "Tenants", @@ -236,6 +243,7 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Applicati "stock-project-factory-plan-details", "stock-project-factory-backupjob-details", "stock-project-factory-backup-details", + "stock-project-factory-external-ips", "stock-project-api-form", "stock-project-api-table", "stock-project-builtin-form", diff --git a/internal/controller/dashboard/static_refactored.go b/internal/controller/dashboard/static_refactored.go index 4039898d..daaa36b4 100644 --- a/internal/controller/dashboard/static_refactored.go +++ b/internal/controller/dashboard/static_refactored.go @@ -1885,6 +1885,46 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory { } backupSpec := createUnifiedFactory(backupConfig, backupTabs, []any{"/api/clusters/{2}/k8s/apis/backups.cozystack.io/v1alpha1/namespaces/{3}/backups/{6}"}) + // External IPs factory (filtered services) + externalIPsTabs := []any{ + map[string]any{ + "key": "services", + "label": "Services", + "children": []any{ + map[string]any{ + "type": "EnrichedTable", + "data": map[string]any{ + "id": "external-ips-table", + "fetchUrl": "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/services", + "clusterNamePartOfUrl": "{2}", + "baseprefix": "/openapi-ui", + "customizationId": "factory-details-v1.services", + "pathToItems": []any{"items"}, + "fieldSelector": map[string]any{ + "spec.type": "LoadBalancer", + }, + }, + }, + }, + }, + } + externalIPsSpec := map[string]any{ + "key": "external-ips", + "sidebarTags": []any{"external-ips-sidebar"}, + "withScrollableMainContentCard": true, + "urlsToFetch": []any{}, + "data": []any{ + map[string]any{ + "type": "antdTabs", + "data": map[string]any{ + "id": "tabs-root", + "defaultActiveKey": "services", + "items": externalIPsTabs, + }, + }, + }, + } + return []*dashboardv1alpha1.Factory{ createFactory("marketplace", marketplaceSpec), createFactory("namespace-details", namespaceSpec), @@ -1897,6 +1937,7 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory { createFactory("plan-details", planSpec), createFactory("backupjob-details", backupJobSpec), createFactory("backup-details", backupSpec), + createFactory("external-ips", externalIPsSpec), } } From ee759dd11e68058bf8a81c44cbcd56f0f801bfc4 Mon Sep 17 00:00:00 2001 From: Kirill Ilin Date: Fri, 23 Jan 2026 14:28:23 +0500 Subject: [PATCH 105/889] [dashboard] Fix filtering on Pods tab for Service Signed-off-by: Kirill Ilin --- internal/controller/dashboard/static_refactored.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/controller/dashboard/static_refactored.go b/internal/controller/dashboard/static_refactored.go index 4039898d..d7954ed8 100644 --- a/internal/controller/dashboard/static_refactored.go +++ b/internal/controller/dashboard/static_refactored.go @@ -1144,7 +1144,7 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory { "clusterNamePartOfUrl": "{2}", "customizationId": "factory-node-details-/v1/pods", "fetchUrl": "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/pods", - "labelsSelectorFull": map[string]any{ + "labelSelectorFull": map[string]any{ "pathToLabels": ".spec.selector", "reqIndex": 0, }, From befbdf0964ba5efa18fe86d7220bf198c9b120e7 Mon Sep 17 00:00:00 2001 From: Kirill Ilin Date: Mon, 26 Jan 2026 18:13:06 +0500 Subject: [PATCH 106/889] [kubernetes] show Service and Ingress resources for kubernetes app in dashboard Signed-off-by: Kirill Ilin --- packages/apps/kubernetes/templates/cloud-config.yaml | 5 +++++ packages/apps/kubernetes/templates/ingress.yaml | 10 ++++++++++ packages/system/kubernetes-rd/cozyrds/kubernetes.yaml | 4 ++++ 3 files changed, 19 insertions(+) diff --git a/packages/apps/kubernetes/templates/cloud-config.yaml b/packages/apps/kubernetes/templates/cloud-config.yaml index b1399b11..a4b7f2c9 100644 --- a/packages/apps/kubernetes/templates/cloud-config.yaml +++ b/packages/apps/kubernetes/templates/cloud-config.yaml @@ -10,3 +10,8 @@ data: enableEPSController: true selectorless: true namespace: {{ .Release.Namespace }} + infraLabels: + apps.cozystack.io/application.group: apps.cozystack.io + apps.cozystack.io/application.kind: Kubernetes + apps.cozystack.io/application.name: {{ .Release.Name | trimPrefix "kubernetes-" }} + internal.cozystack.io/tenantresource: "true" diff --git a/packages/apps/kubernetes/templates/ingress.yaml b/packages/apps/kubernetes/templates/ingress.yaml index 7993dba8..2e7f9933 100644 --- a/packages/apps/kubernetes/templates/ingress.yaml +++ b/packages/apps/kubernetes/templates/ingress.yaml @@ -14,6 +14,11 @@ metadata: } nginx.ingress.kubernetes.io/ssl-passthrough: "true" nginx.ingress.kubernetes.io/ssl-redirect: "false" + labels: + apps.cozystack.io/application.group: apps.cozystack.io + apps.cozystack.io/application.kind: Kubernetes + apps.cozystack.io/application.name: {{ .Release.Name | trimPrefix "kubernetes-" }} + internal.cozystack.io/tenantresource: "true" spec: ingressClassName: "{{ $ingress }}" rules: @@ -41,6 +46,11 @@ apiVersion: v1 kind: Service metadata: name: {{ .Release.Name }}-ingress-nginx + labels: + apps.cozystack.io/application.group: apps.cozystack.io + apps.cozystack.io/application.kind: Kubernetes + apps.cozystack.io/application.name: {{ .Release.Name | trimPrefix "kubernetes-" }} + internal.cozystack.io/tenantresource: "true" spec: ports: - appProtocol: http diff --git a/packages/system/kubernetes-rd/cozyrds/kubernetes.yaml b/packages/system/kubernetes-rd/cozyrds/kubernetes.yaml index 289ef25e..1b4c7361 100644 --- a/packages/system/kubernetes-rd/cozyrds/kubernetes.yaml +++ b/packages/system/kubernetes-rd/cozyrds/kubernetes.yaml @@ -37,8 +37,12 @@ spec: include: - resourceNames: - kubernetes-{{ .name }} + - kubernetes-{{ .name }}-ingress-nginx + - matchLabels: + cluster.x-k8s.io/cluster-name: kubernetes-{{ .name }} ingresses: exclude: [] include: - resourceNames: - kubernetes-{{ .name }} + - kubernetes-{{ .name }}-ingress-nginx From 7cebafbafd138d45ae0413158a4060a53ace3ad7 Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Mon, 26 Jan 2026 19:44:17 +0300 Subject: [PATCH 107/889] [dashboard] Improve dashboard session params ## What this PR does This patch enables the `offline_access` scope for the dashbord keycloak client, so that users get a refresh token which gatekeeper can use to automatically refresh an expiring access token. Also session timeouts were increased. ### Release note ```release-note [dashboard] Increase session timeouts, add the offline_access scope, enable refresh tokens to improve the overall user experience when working with the dashboard. ``` Signed-off-by: Timofei Larkin --- packages/system/dashboard/templates/gatekeeper.yaml | 1 + packages/system/dashboard/templates/keycloakclient.yaml | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/packages/system/dashboard/templates/gatekeeper.yaml b/packages/system/dashboard/templates/gatekeeper.yaml index 40f2565f..984ec03e 100644 --- a/packages/system/dashboard/templates/gatekeeper.yaml +++ b/packages/system/dashboard/templates/gatekeeper.yaml @@ -64,6 +64,7 @@ spec: - --cookie-secure=true - --cookie-secret=$(OAUTH2_PROXY_COOKIE_SECRET) - --skip-provider-button + - --scope=openid email profile offline_access env: - name: OAUTH2_PROXY_CLIENT_ID value: dashboard diff --git a/packages/system/dashboard/templates/keycloakclient.yaml b/packages/system/dashboard/templates/keycloakclient.yaml index e1caea71..d8e47e8a 100644 --- a/packages/system/dashboard/templates/keycloakclient.yaml +++ b/packages/system/dashboard/templates/keycloakclient.yaml @@ -66,6 +66,12 @@ spec: defaultClientScopes: - groups - kubernetes-client + optionalClientScopes: + - offline_access + attributes: + post.logout.redirect.uris: "+" + client.session.idle.timeout: "86400" + client.session.max.lifespan: "604800" redirectUris: - "https://dashboard.{{ $host }}/oauth2/callback/*" {{- range $i, $v := $extraRedirectUris }} From 17a5dadd63d460ef3489a7d65ee4f076b3c07989 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 27 Jan 2026 16:44:20 +0100 Subject: [PATCH 108/889] Remove apply-locally target from kilo Signed-off-by: Andrei Kvapil --- packages/system/kilo/Makefile | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/system/kilo/Makefile b/packages/system/kilo/Makefile index b0d23033..3d278db3 100644 --- a/packages/system/kilo/Makefile +++ b/packages/system/kilo/Makefile @@ -4,9 +4,6 @@ export NAMESPACE=cozy-$(NAME) include ../../../hack/common-envs.mk include ../../../hack/package.mk -apply-locally: - cozypkg apply --plain -n $(NAMESPACE) $(NAME) - update: wget https://raw.githubusercontent.com/squat/kilo/refs/heads/main/manifests/crds.yaml -O templates/crds.yaml wget https://raw.githubusercontent.com/squat/kilo/refs/heads/main/manifests/kilo-typhoon-flannel.yaml -O templates/kilo.yaml From b864c040698ab6165772f5b9836b71cc2b2d98b6 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 27 Jan 2026 16:44:47 +0100 Subject: [PATCH 109/889] Update cozyhr v1.6.1 Signed-off-by: Andrei Kvapil --- packages/core/platform/images/migrations/Dockerfile | 2 +- packages/core/testing/images/e2e-sandbox/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/platform/images/migrations/Dockerfile b/packages/core/platform/images/migrations/Dockerfile index 137c2768..b35808d5 100644 --- a/packages/core/platform/images/migrations/Dockerfile +++ b/packages/core/platform/images/migrations/Dockerfile @@ -1,6 +1,6 @@ FROM alpine:3.22 -RUN wget -O- https://github.com/cozystack/cozyhr/raw/refs/heads/main/hack/install.sh | sh -s -- -v 1.5.0 +RUN wget -O- https://github.com/cozystack/cozyhr/raw/refs/heads/main/hack/install.sh | sh -s -- -v 1.6.1 RUN apk add --no-cache kubectl helm coreutils git jq ca-certificates bash curl diff --git a/packages/core/testing/images/e2e-sandbox/Dockerfile b/packages/core/testing/images/e2e-sandbox/Dockerfile index ee57f193..2f94991f 100644 --- a/packages/core/testing/images/e2e-sandbox/Dockerfile +++ b/packages/core/testing/images/e2e-sandbox/Dockerfile @@ -3,7 +3,7 @@ FROM ubuntu:22.04 ARG KUBECTL_VERSION=1.33.2 ARG TALOSCTL_VERSION=1.10.4 ARG HELM_VERSION=3.18.3 -ARG COZYHR_VERSION=1.5.0 +ARG COZYHR_VERSION=1.6.1 ARG TARGETOS ARG TARGETARCH From af320a86a0a8d06d2107148bc8d5ee7ca0682391 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 27 Jan 2026 18:06:30 +0100 Subject: [PATCH 110/889] fix(telemetry): use APIReader instead of cached client in operator The cozystack-operator's manager cache is filtered to only include specific secrets and namespaces with certain labels. This caused telemetry collection to fail because resources like kube-system namespace, nodes, services, and PVs were not in the cache. Switch to using mgr.GetAPIReader() which bypasses the cache and queries the API server directly. This is appropriate for telemetry since it only runs every 15 minutes. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- cmd/cozystack-operator/main.go | 4 +++- internal/telemetry/operator_collector.go | 16 ++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/cmd/cozystack-operator/main.go b/cmd/cozystack-operator/main.go index f835aba1..188ce160 100644 --- a/cmd/cozystack-operator/main.go +++ b/cmd/cozystack-operator/main.go @@ -262,7 +262,9 @@ func main() { } // Initialize telemetry collector - collector, err := telemetry.NewOperatorCollector(mgr.GetClient(), &telemetryConfig, config) + // Use APIReader (non-cached) because the manager's cache is filtered + // and doesn't include resources needed for telemetry (e.g., kube-system namespace, nodes, etc.) + collector, err := telemetry.NewOperatorCollector(mgr.GetAPIReader(), &telemetryConfig, config) if err != nil { setupLog.V(1).Info("unable to create telemetry collector, telemetry will be disabled", "error", err) } diff --git a/internal/telemetry/operator_collector.go b/internal/telemetry/operator_collector.go index aecb5551..8b4ec302 100644 --- a/internal/telemetry/operator_collector.go +++ b/internal/telemetry/operator_collector.go @@ -22,7 +22,7 @@ import ( // OperatorCollector handles telemetry data collection for cozystack-operator type OperatorCollector struct { - client client.Client + reader client.Reader discoveryClient discovery.DiscoveryInterface config *Config ticker *time.Ticker @@ -30,13 +30,13 @@ type OperatorCollector struct { } // NewOperatorCollector creates a new telemetry collector for cozystack-operator -func NewOperatorCollector(c client.Client, config *Config, kubeConfig *rest.Config) (*OperatorCollector, error) { +func NewOperatorCollector(r client.Reader, config *Config, kubeConfig *rest.Config) (*OperatorCollector, error) { discoveryClient, err := discovery.NewDiscoveryClientForConfig(kubeConfig) if err != nil { return nil, fmt.Errorf("failed to create discovery client: %w", err) } return &OperatorCollector{ - client: c, + reader: r, discoveryClient: discoveryClient, config: config, }, nil @@ -108,7 +108,7 @@ func (c *OperatorCollector) collect(ctx context.Context) { // Get cluster ID from kube-system namespace var kubeSystemNS corev1.Namespace - if err := c.client.Get(ctx, types.NamespacedName{Name: "kube-system"}, &kubeSystemNS); err != nil { + if err := c.reader.Get(ctx, types.NamespacedName{Name: "kube-system"}, &kubeSystemNS); err != nil { logger.Info(fmt.Sprintf("Failed to get kube-system namespace: %v", err)) return } @@ -124,7 +124,7 @@ func (c *OperatorCollector) collect(ctx context.Context) { // Get nodes var nodeList corev1.NodeList - if err := c.client.List(ctx, &nodeList); err != nil { + if err := c.reader.List(ctx, &nodeList); err != nil { logger.Info(fmt.Sprintf("Failed to list nodes: %v", err)) return } @@ -183,7 +183,7 @@ func (c *OperatorCollector) collect(ctx context.Context) { // Collect LoadBalancer services metrics var serviceList corev1.ServiceList - if err := c.client.List(ctx, &serviceList); err != nil { + if err := c.reader.List(ctx, &serviceList); err != nil { logger.Info(fmt.Sprintf("Failed to list Services: %v", err)) } else { lbCount := 0 @@ -197,7 +197,7 @@ func (c *OperatorCollector) collect(ctx context.Context) { // Collect PV metrics grouped by driver and size var pvList corev1.PersistentVolumeList - if err := c.client.List(ctx, &pvList); err != nil { + if err := c.reader.List(ctx, &pvList); err != nil { logger.Info(fmt.Sprintf("Failed to list PVs: %v", err)) } else { pvMetrics := make(map[string]map[string]int) // size -> driver -> count @@ -236,7 +236,7 @@ func (c *OperatorCollector) collect(ctx context.Context) { // Collect installed packages var packageList cozyv1alpha1.PackageList - if err := c.client.List(ctx, &packageList); err != nil { + if err := c.reader.List(ctx, &packageList); err != nil { logger.Info(fmt.Sprintf("Failed to list Packages: %v", err)) } else { for _, pkg := range packageList.Items { From 0de4755d56aee62dabdf44da34473dac2b06e7be Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 27 Jan 2026 23:06:44 +0100 Subject: [PATCH 111/889] docs(changelogs): add changelogs for v0.40.3, v0.41.1, v0.41.2, v0.41.3 Add missing changelog documentation for recent patch releases. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- docs/changelogs/v0.40.3.md | 15 +++++++++++++++ docs/changelogs/v0.41.1.md | 11 +++++++++++ docs/changelogs/v0.41.2.md | 13 +++++++++++++ docs/changelogs/v0.41.3.md | 15 +++++++++++++++ 4 files changed, 54 insertions(+) create mode 100644 docs/changelogs/v0.40.3.md create mode 100644 docs/changelogs/v0.41.1.md create mode 100644 docs/changelogs/v0.41.2.md create mode 100644 docs/changelogs/v0.41.3.md diff --git a/docs/changelogs/v0.40.3.md b/docs/changelogs/v0.40.3.md new file mode 100644 index 00000000..a62a70bc --- /dev/null +++ b/docs/changelogs/v0.40.3.md @@ -0,0 +1,15 @@ + + +## Fixes + +* **[apiserver] Fix Watch resourceVersion and bookmark handling**: Fixed issues with Watch API handling of resourceVersion and bookmarks, ensuring proper event streaming and state synchronization for API clients ([**@kvaps**](https://github.com/kvaps) in #1860). + +## Dependencies + +* **[cilium] Update Cilium to v1.18.6**: Updated Cilium CNI to v1.18.6 with security fixes and performance improvements ([**@sircthulhu**](https://github.com/sircthulhu) in #1868, #1870). + +--- + +**Full Changelog**: [v0.40.2...v0.40.3](https://github.com/cozystack/cozystack/compare/v0.40.2...v0.40.3) diff --git a/docs/changelogs/v0.41.1.md b/docs/changelogs/v0.41.1.md new file mode 100644 index 00000000..d084d644 --- /dev/null +++ b/docs/changelogs/v0.41.1.md @@ -0,0 +1,11 @@ + + +## Improvements + +* **[kubernetes] Add enum validation for IngressNginx exposeMethod**: Added enum validation for the `exposeMethod` field in IngressNginx configuration, preventing invalid values and improving user experience with clear valid options ([**@sircthulhu**](https://github.com/sircthulhu) in #1895, #1897). + +--- + +**Full Changelog**: [v0.41.0...v0.41.1](https://github.com/cozystack/cozystack/compare/v0.41.0...v0.41.1) diff --git a/docs/changelogs/v0.41.2.md b/docs/changelogs/v0.41.2.md new file mode 100644 index 00000000..8b3a8261 --- /dev/null +++ b/docs/changelogs/v0.41.2.md @@ -0,0 +1,13 @@ + + +## Improvements + +* **[monitoring-agents] Set minReplicas to 1 for VPA for VMAgent**: Configured VPA (Vertical Pod Autoscaler) to maintain at least 1 replica for VMAgent, ensuring monitoring availability during scaling operations ([**@sircthulhu**](https://github.com/sircthulhu) in #1894, #1905). + +* **[mongodb] Remove user-configurable images from MongoDB chart**: Removed user-configurable image options from the MongoDB chart to simplify configuration and ensure consistency with tested image versions ([**@kvaps**](https://github.com/kvaps) in #1901, #1904). + +--- + +**Full Changelog**: [v0.41.1...v0.41.2](https://github.com/cozystack/cozystack/compare/v0.41.1...v0.41.2) diff --git a/docs/changelogs/v0.41.3.md b/docs/changelogs/v0.41.3.md new file mode 100644 index 00000000..6d04dad3 --- /dev/null +++ b/docs/changelogs/v0.41.3.md @@ -0,0 +1,15 @@ + + +## Improvements + +* **[kubernetes] Show Service and Ingress resources for Kubernetes app in dashboard**: Added visibility of Service and Ingress resources for Kubernetes applications in the dashboard, improving resource management and monitoring capabilities ([**@sircthulhu**](https://github.com/sircthulhu) in #1912, #1915). + +## Fixes + +* **[dashboard] Fix filtering on Pods tab for Service**: Fixed an issue where pod filtering was not working correctly on the Pods tab when viewing Services in the dashboard ([**@sircthulhu**](https://github.com/sircthulhu) in #1909, #1914). + +--- + +**Full Changelog**: [v0.41.2...v0.41.3](https://github.com/cozystack/cozystack/compare/v0.41.2...v0.41.3) From b0fa330d886f00b5fd77042f815040c987eb5bb5 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 27 Jan 2026 23:24:42 +0100 Subject: [PATCH 112/889] refactor(mongodb): unify users and databases configuration Align MongoDB configuration with postgres and mysql patterns: - Add separate `databases` section with `roles.admin` and `roles.readonly` - Simplify `users` to only contain optional password field - All users now authenticate via `admin` database (MongoDB best practice) - Admin role maps to readWrite + dbAdmin permissions - Readonly role maps to read permission Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- hack/e2e-apps/mongodb.bats | 8 +++ packages/apps/mongodb/templates/mongodb.yaml | 24 +++++-- packages/apps/mongodb/tests/mongodb_test.yaml | 70 +++++++++++++------ .../apps/mongodb/tests/user-secrets_test.yaml | 28 ++------ packages/apps/mongodb/values.schema.json | 56 ++++++++------- packages/apps/mongodb/values.yaml | 38 ++++++---- 6 files changed, 135 insertions(+), 89 deletions(-) diff --git a/hack/e2e-apps/mongodb.bats b/hack/e2e-apps/mongodb.bats index 61ebada8..794baf63 100644 --- a/hack/e2e-apps/mongodb.bats +++ b/hack/e2e-apps/mongodb.bats @@ -14,6 +14,14 @@ spec: replicas: 1 storageClass: "" resourcesPreset: "nano" + users: + testuser: + password: xai7Wepo + databases: + testdb: + roles: + admin: + - testuser backup: enabled: false EOF diff --git a/packages/apps/mongodb/templates/mongodb.yaml b/packages/apps/mongodb/templates/mongodb.yaml index dabb51d4..67969758 100644 --- a/packages/apps/mongodb/templates/mongodb.yaml +++ b/packages/apps/mongodb/templates/mongodb.yaml @@ -103,18 +103,34 @@ spec: {{- end }} {{- if .Values.users }} + {{- /* Build a map of username -> list of roles from databases config */}} + {{- $userRoles := dict }} + {{- range $dbname, $db := .Values.databases }} + {{- range $user := $db.roles.admin }} + {{- $roles := index $userRoles $user | default list }} + {{- $roles = append $roles (dict "name" "readWrite" "db" $dbname) }} + {{- $roles = append $roles (dict "name" "dbAdmin" "db" $dbname) }} + {{- $_ := set $userRoles $user $roles }} + {{- end }} + {{- range $user := $db.roles.readonly }} + {{- $roles := index $userRoles $user | default list }} + {{- $roles = append $roles (dict "name" "read" "db" $dbname) }} + {{- $_ := set $userRoles $user $roles }} + {{- end }} + {{- end }} users: {{- range $username, $user := .Values.users }} - {{- if not $user.roles }} - {{- fail (printf "users.%s.roles is required and cannot be empty" $username) }} + {{- $roles := index $userRoles $username }} + {{- if not $roles }} + {{- fail (printf "user '%s' is not assigned to any database role in databases.*.roles" $username) }} {{- end }} - name: {{ $username }} - db: {{ $user.db }} + db: admin passwordSecretRef: name: {{ $.Release.Name }}-user-{{ $username }} key: password roles: - {{- range $user.roles }} + {{- range $roles }} - name: {{ .name }} db: {{ .db }} {{- end }} diff --git a/packages/apps/mongodb/tests/mongodb_test.yaml b/packages/apps/mongodb/tests/mongodb_test.yaml index db46fe79..4154ca25 100644 --- a/packages/apps/mongodb/tests/mongodb_test.yaml +++ b/packages/apps/mongodb/tests/mongodb_test.yaml @@ -352,12 +352,13 @@ tests: _cluster: cluster-domain: cozy.local users: {} + databases: {} asserts: - notExists: path: spec.users documentIndex: 0 - - it: configures users when defined + - it: configures users with admin role release: name: test-mongodb namespace: tenant-test @@ -365,11 +366,12 @@ tests: _cluster: cluster-domain: cozy.local users: - appuser: - db: appdb + appuser: {} + databases: + appdb: roles: - - name: readWrite - db: appdb + admin: + - appuser asserts: - exists: path: spec.users @@ -380,7 +382,7 @@ tests: documentIndex: 0 - equal: path: spec.users[0].db - value: appdb + value: admin documentIndex: 0 - equal: path: spec.users[0].passwordSecretRef.name @@ -391,7 +393,7 @@ tests: value: password documentIndex: 0 - - it: configures user roles + - it: assigns readWrite and dbAdmin roles for admin users release: name: test-mongodb namespace: tenant-test @@ -399,28 +401,31 @@ tests: _cluster: cluster-domain: cozy.local users: - admin: - db: admin + appuser: {} + databases: + mydb: roles: - - name: clusterAdmin - db: admin - - name: userAdminAnyDatabase - db: admin + admin: + - appuser asserts: - equal: path: spec.users[0].roles[0].name - value: clusterAdmin + value: readWrite documentIndex: 0 - equal: path: spec.users[0].roles[0].db - value: admin + value: mydb documentIndex: 0 - equal: path: spec.users[0].roles[1].name - value: userAdminAnyDatabase + value: dbAdmin + documentIndex: 0 + - equal: + path: spec.users[0].roles[1].db + value: mydb documentIndex: 0 - - it: fails when user has empty roles + - it: assigns read role for readonly users release: name: test-mongodb namespace: tenant-test @@ -428,12 +433,35 @@ tests: _cluster: cluster-domain: cozy.local users: - myuser: - db: mydb - roles: [] + reader: {} + databases: + mydb: + roles: + readonly: + - reader + asserts: + - equal: + path: spec.users[0].roles[0].name + value: read + documentIndex: 0 + - equal: + path: spec.users[0].roles[0].db + value: mydb + documentIndex: 0 + + - it: fails when user is not assigned to any database role + release: + name: test-mongodb + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + users: + myuser: {} + databases: {} asserts: - failedTemplate: - errorMessage: "users.myuser.roles is required and cannot be empty" + errorMessage: "user 'myuser' is not assigned to any database role in databases.*.roles" ########################### # Backup configuration # diff --git a/packages/apps/mongodb/tests/user-secrets_test.yaml b/packages/apps/mongodb/tests/user-secrets_test.yaml index b16e4243..6d9fb388 100644 --- a/packages/apps/mongodb/tests/user-secrets_test.yaml +++ b/packages/apps/mongodb/tests/user-secrets_test.yaml @@ -22,11 +22,7 @@ tests: namespace: tenant-test set: users: - myuser: - db: mydb - roles: - - name: readWrite - db: mydb + myuser: {} asserts: - hasDocuments: count: 1 @@ -48,16 +44,8 @@ tests: namespace: tenant-test set: users: - user1: - db: db1 - roles: - - name: readWrite - db: db1 - user2: - db: db2 - roles: - - name: dbAdmin - db: db2 + user1: {} + user2: {} asserts: - hasDocuments: count: 2 @@ -71,10 +59,6 @@ tests: users: myuser: password: "mysecretpassword" - db: mydb - roles: - - name: readWrite - db: mydb asserts: - equal: path: stringData.password @@ -87,11 +71,7 @@ tests: namespace: tenant-prod set: users: - admin: - db: admin - roles: - - name: clusterAdmin - db: admin + admin: {} asserts: - equal: path: metadata.name diff --git a/packages/apps/mongodb/values.schema.json b/packages/apps/mongodb/values.schema.json index 817c3463..9b9d30aa 100644 --- a/packages/apps/mongodb/values.schema.json +++ b/packages/apps/mongodb/values.schema.json @@ -232,40 +232,28 @@ "type": "string", "default": "" }, - "users": { - "description": "Custom MongoDB users configuration map.", + "databases": { + "description": "Databases configuration map.", "type": "object", "default": {}, "additionalProperties": { "type": "object", - "required": [ - "db" - ], "properties": { - "db": { - "description": "Database to authenticate against.", - "type": "string" - }, - "password": { - "description": "Password for the user (auto-generated if omitted).", - "type": "string" - }, "roles": { - "description": "List of MongoDB roles with database scope.", - "type": "array", - "items": { - "type": "object", - "required": [ - "db", - "name" - ], - "properties": { - "db": { - "description": "Database the role applies to.", + "description": "Roles assigned to users.", + "type": "object", + "properties": { + "admin": { + "description": "List of users with admin privileges (readWrite + dbAdmin).", + "type": "array", + "items": { "type": "string" - }, - "name": { - "description": "Role name (e.g., readWrite, dbAdmin, clusterAdmin).", + } + }, + "readonly": { + "description": "List of users with read-only privileges.", + "type": "array", + "items": { "type": "string" } } @@ -274,6 +262,20 @@ } } }, + "users": { + "description": "Users configuration map.", + "type": "object", + "default": {}, + "additionalProperties": { + "type": "object", + "properties": { + "password": { + "description": "Password for the user (auto-generated if omitted).", + "type": "string" + } + } + } + }, "version": { "description": "MongoDB major version to deploy.", "type": "string", diff --git a/packages/apps/mongodb/values.yaml b/packages/apps/mongodb/values.yaml index 003a0c9e..df8fbe37 100644 --- a/packages/apps/mongodb/values.yaml +++ b/packages/apps/mongodb/values.yaml @@ -74,26 +74,38 @@ shardingConfig: ## @section Users configuration ## -## @typedef {struct} Role - MongoDB role configuration. -## @field {string} name - Role name (e.g., readWrite, dbAdmin, clusterAdmin). -## @field {string} db - Database the role applies to. - ## @typedef {struct} User - User configuration. ## @field {string} [password] - Password for the user (auto-generated if omitted). -## @field {string} db - Database to authenticate against. -## @field {[]Role} roles - List of MongoDB roles with database scope. -## @param {map[string]User} users - Custom MongoDB users configuration map. +## @param {map[string]User} users - Users configuration map. users: {} ## Example: ## users: -## myuser: -## db: mydb +## user1: +## password: strongpassword +## user2: {} + +## +## @section Databases configuration +## + +## @typedef {struct} DatabaseRoles - Role assignments for a database. +## @field {[]string} [admin] - List of users with admin privileges (readWrite + dbAdmin). +## @field {[]string} [readonly] - List of users with read-only privileges. + +## @typedef {struct} Database - Database configuration. +## @field {DatabaseRoles} [roles] - Roles assigned to users. + +## @param {map[string]Database} databases - Databases configuration map. +databases: {} +## Example: +## databases: +## myapp: ## roles: -## - name: readWrite -## db: mydb -## - name: dbAdmin -## db: mydb +## admin: +## - user1 +## readonly: +## - user2 ## ## @section Backup parameters From a56fc00c5cc6bb72595fd56cf1846caf88a1b8b1 Mon Sep 17 00:00:00 2001 From: Kirill Ilin Date: Wed, 28 Jan 2026 12:27:13 +0500 Subject: [PATCH 113/889] [dashboard] Add "Edit" button to all resources Signed-off-by: Kirill Ilin --- internal/controller/dashboard/ui_helpers.go | 16 ++++++++++++++++ internal/controller/dashboard/unified_helpers.go | 13 ++++++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/internal/controller/dashboard/ui_helpers.go b/internal/controller/dashboard/ui_helpers.go index fb29a608..6c7e3000 100644 --- a/internal/controller/dashboard/ui_helpers.go +++ b/internal/controller/dashboard/ui_helpers.go @@ -102,6 +102,22 @@ func antdFlex(id string, gap float64, children []any) map[string]any { } } +func antdFlexSpaceBetween(id string, children []any) map[string]any { + if id == "" { + id = generateContainerID("auto", "flex") + } + + return map[string]any{ + "type": "antdFlex", + "data": map[string]any{ + "id": id, + "align": "center", + "justify": "space-between", + }, + "children": children, + } +} + func antdFlexVertical(id string, gap float64, children []any) map[string]any { // Auto-generate ID if not provided if id == "" { diff --git a/internal/controller/dashboard/unified_helpers.go b/internal/controller/dashboard/unified_helpers.go index b25d5b65..d5edf207 100644 --- a/internal/controller/dashboard/unified_helpers.go +++ b/internal/controller/dashboard/unified_helpers.go @@ -237,9 +237,16 @@ func createUnifiedFactory(config UnifiedResourceConfig, tabs []any, urlsToFetch "lineHeight": "24px", }) - header := antdFlex(generateContainerID("header", "row"), float64(6), []any{ - badge, - nameText, + header := antdFlexSpaceBetween(generateContainerID("header", "row"), []any{ + antdFlex(generateContainerID("header", "title-text"), float64(6), []any{ + badge, + nameText, + }), + antdLink(generateLinkID("header", "edit"), + "Edit", + fmt.Sprintf("/openapi-ui/{2}/{3}/forms/apis/{reqsJsonPath[0]['.apiVersion']['-']}/%s/{reqsJsonPath[0]['.metadata.name']['-']}", + config.Plural), + ), }) // Add marginBottom style to header From 1741651b0cd64e8f0757739ee31a4378136e6523 Mon Sep 17 00:00:00 2001 From: nbykov0 <166552198+nbykov0@users.noreply.github.com> Date: Wed, 28 Jan 2026 20:08:35 +0300 Subject: [PATCH 114/889] Post upgrade fixes for v1.0 packagesources Signed-off-by: nbykov0 <166552198+nbykov0@users.noreply.github.com> --- packages/core/platform/sources/cozy-proxy.yaml | 2 +- packages/core/platform/sources/metrics-server.yaml | 2 +- packages/core/platform/sources/monitoring-agents.yaml | 1 + packages/core/platform/templates/bundles/system.yaml | 2 ++ .../system/vertical-pod-autoscaler/templates/vpa-for-vpa.yaml | 2 +- 5 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/core/platform/sources/cozy-proxy.yaml b/packages/core/platform/sources/cozy-proxy.yaml index fac0f5fc..041b968f 100644 --- a/packages/core/platform/sources/cozy-proxy.yaml +++ b/packages/core/platform/sources/cozy-proxy.yaml @@ -18,4 +18,4 @@ spec: path: system/cozy-proxy install: namespace: cozy-system - releaseName: cozystack + releaseName: cozy-proxy diff --git a/packages/core/platform/sources/metrics-server.yaml b/packages/core/platform/sources/metrics-server.yaml index 0bd17881..607cd7f0 100644 --- a/packages/core/platform/sources/metrics-server.yaml +++ b/packages/core/platform/sources/metrics-server.yaml @@ -18,5 +18,5 @@ spec: - name: metrics-server path: system/metrics-server install: - namespace: cozy-metrics-server + namespace: cozy-monitoring releaseName: metrics-server diff --git a/packages/core/platform/sources/monitoring-agents.yaml b/packages/core/platform/sources/monitoring-agents.yaml index 9585c5aa..206699b6 100644 --- a/packages/core/platform/sources/monitoring-agents.yaml +++ b/packages/core/platform/sources/monitoring-agents.yaml @@ -13,6 +13,7 @@ spec: - name: default dependsOn: - cozystack.networking + - cozystack.metrics-server - cozystack.victoria-metrics-operator - cozystack.vertical-pod-autoscaler components: diff --git a/packages/core/platform/templates/bundles/system.yaml b/packages/core/platform/templates/bundles/system.yaml index a1e6a195..96a88333 100644 --- a/packages/core/platform/templates/bundles/system.yaml +++ b/packages/core/platform/templates/bundles/system.yaml @@ -20,6 +20,7 @@ {{include "cozystack.platform.package.default" (list "cozystack.metallb" $) }} {{include "cozystack.platform.package.default" (list "cozystack.reloader" $) }} {{include "cozystack.platform.package.default" (list "cozystack.linstor" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.linstor-scheduler" $) }} {{include "cozystack.platform.package.default" (list "cozystack.snapshot-controller" $) }} {{- end }} @@ -54,6 +55,7 @@ {{include "cozystack.platform.package.default" (list "cozystack.backup-controller" $) }} {{include "cozystack.platform.package.optional.default" (list "cozystack.velero" $) }} {{include "cozystack.platform.package.default" (list "cozystack.vertical-pod-autoscaler" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.metrics-server" $) }} {{include "cozystack.platform.package.default" (list "cozystack.monitoring-agents" $) }} {{include "cozystack.platform.package.default" (list "cozystack.goldpinger" $) }} {{include "cozystack.platform.package.default" (list "cozystack.prometheus-operator-crds" $) }} diff --git a/packages/system/vertical-pod-autoscaler/templates/vpa-for-vpa.yaml b/packages/system/vertical-pod-autoscaler/templates/vpa-for-vpa.yaml index 00fcadd7..2df053b5 100644 --- a/packages/system/vertical-pod-autoscaler/templates/vpa-for-vpa.yaml +++ b/packages/system/vertical-pod-autoscaler/templates/vpa-for-vpa.yaml @@ -18,7 +18,7 @@ metadata: spec: chartRef: kind: ExternalArtifact - name: cozystack-system-default-vertical-pod-autoscaler + name: cozystack-vertical-pod-autoscaler-default-vpa-for-vpa namespace: cozy-system dependsOn: - name: monitoring-agents From 9e63bd533c0b36879a2b7f532dbc650beb53ce92 Mon Sep 17 00:00:00 2001 From: Kirill Ilin Date: Wed, 28 Jan 2026 18:11:59 +0500 Subject: [PATCH 115/889] [dashboard] Add resource quota usage to tenant info resource Signed-off-by: Kirill Ilin --- .../controller/dashboard/customcolumns.go | 3 -- internal/controller/dashboard/factory.go | 42 +++++++++++++++++++ .../controller/dashboard/static_refactored.go | 8 ++++ 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/internal/controller/dashboard/customcolumns.go b/internal/controller/dashboard/customcolumns.go index be1318f8..19a5a16a 100644 --- a/internal/controller/dashboard/customcolumns.go +++ b/internal/controller/dashboard/customcolumns.go @@ -34,9 +34,6 @@ func (m *Manager) ensureCustomColumnsOverride(ctx context.Context, crd *cozyv1al obj.SetName(name) href := fmt.Sprintf("/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/%s/{reqsJsonPath[0]['.metadata.name']['-']}", detailsSegment) - if g == "apps.cozystack.io" && kind == "Tenant" && plural == "tenants" { - href = "/openapi-ui/{2}/{reqsJsonPath[0]['.status.namespace']['-']}/api-table/core.cozystack.io/v1alpha1/tenantmodules" - } desired := map[string]any{ "spec": map[string]any{ diff --git a/internal/controller/dashboard/factory.go b/internal/controller/dashboard/factory.go index 84578dd0..63bb7a82 100644 --- a/internal/controller/dashboard/factory.go +++ b/internal/controller/dashboard/factory.go @@ -174,6 +174,48 @@ func detailsTab(kind, endpoint, schemaJSON string, keysOrder [][]string) map[str }), ) } + if kind == "Info" { + rightColStack = append(rightColStack, + antdFlexVertical("resource-quotas-block", 4, []any{ + antdText("resource-quotas-label", true, "Resource Quotas", map[string]any{ + "fontSize": float64(20), + "marginBottom": float64(12), + }), + map[string]any{ + "type": "EnrichedTable", + "data": map[string]any{ + "id": "resource-quotas-table", + "baseprefix": "/openapi-ui", + "clusterNamePartOfUrl": "{2}", + "customizationId": "factory-resource-quotas", + "fetchUrl": "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/resourcequotas", + "pathToItems": []any{`items`}, + }, + }, + }), + ) + } + if kind == "Tenant" { + rightColStack = append(rightColStack, + antdFlexVertical("resource-quotas-block", 4, []any{ + antdText("resource-quotas-label", true, "Resource Quotas", map[string]any{ + "fontSize": float64(20), + "marginBottom": float64(12), + }), + map[string]any{ + "type": "EnrichedTable", + "data": map[string]any{ + "id": "resource-quotas-table", + "baseprefix": "/openapi-ui", + "clusterNamePartOfUrl": "{2}", + "customizationId": "factory-resource-quotas", + "fetchUrl": "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/resourcequotas", + "pathToItems": []any{`items`}, + }, + }, + }), + ) + } return map[string]any{ "key": "details", diff --git a/internal/controller/dashboard/static_refactored.go b/internal/controller/dashboard/static_refactored.go index b0e78e56..5b3acd61 100644 --- a/internal/controller/dashboard/static_refactored.go +++ b/internal/controller/dashboard/static_refactored.go @@ -189,6 +189,14 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid createStringColumn("Values", "_flatMapData_Value"), }), + // Factory resource quotas + createCustomColumnsOverride("factory-resource-quotas", []any{ + createFlatMapColumn("Data", ".spec.hard"), + createStringColumn("Resource", "_flatMapData_Key"), + createStringColumn("Hard", "_flatMapData_Value"), + createStringColumn("Used", ".status.used['{_flatMapData_Key}']"), + }), + // Factory ingress details rules createCustomColumnsOverride("factory-kube-ingress-details-rules", []any{ createStringColumn("Host", ".host"), From f485b5b92a89c7d28837ee98a45acaa6ec9486a0 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 27 Jan 2026 23:26:45 +0100 Subject: [PATCH 116/889] feat(mongodb): add migration for users/databases config Add migration script to convert existing MongoDB HelmReleases from old users format to new users + databases format. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- packages/apps/mongodb/README.md | 22 ++++-- packages/apps/mongodb/values.schema.json | 60 +++++++-------- .../platform/images/migrations/migrations/24 | 73 +++++++++++++++++++ .../system/mongodb-rd/cozyrds/mongodb.yaml | 4 +- 4 files changed, 119 insertions(+), 40 deletions(-) create mode 100755 packages/core/platform/images/migrations/migrations/24 diff --git a/packages/apps/mongodb/README.md b/packages/apps/mongodb/README.md index a5fb17fb..82127c59 100644 --- a/packages/apps/mongodb/README.md +++ b/packages/apps/mongodb/README.md @@ -69,14 +69,20 @@ Run `helm upgrade` after MongoDB is ready to populate the credentials secret wit ### Users configuration -| Name | Description | Type | Value | -| --------------------------- | --------------------------------------------------- | ------------------- | ----- | -| `users` | Custom MongoDB users configuration map. | `map[string]object` | `{}` | -| `users[name].password` | Password for the user (auto-generated if omitted). | `string` | `""` | -| `users[name].db` | Database to authenticate against. | `string` | `""` | -| `users[name].roles` | List of MongoDB roles with database scope. | `[]object` | `[]` | -| `users[name].roles[i].name` | Role name (e.g., readWrite, dbAdmin, clusterAdmin). | `string` | `""` | -| `users[name].roles[i].db` | Database the role applies to. | `string` | `""` | +| Name | Description | Type | Value | +| ---------------------- | -------------------------------------------------- | ------------------- | ----- | +| `users` | Users configuration map. | `map[string]object` | `{}` | +| `users[name].password` | Password for the user (auto-generated if omitted). | `string` | `""` | + + +### Databases configuration + +| Name | Description | Type | Value | +| -------------------------------- | ---------------------------------------------------------- | ------------------- | ----- | +| `databases` | Databases configuration map. | `map[string]object` | `{}` | +| `databases[name].roles` | Roles assigned to users. | `object` | `{}` | +| `databases[name].roles.admin` | List of users with admin privileges (readWrite + dbAdmin). | `[]string` | `[]` | +| `databases[name].roles.readonly` | List of users with read-only privileges. | `[]string` | `[]` | ### Backup parameters diff --git a/packages/apps/mongodb/values.schema.json b/packages/apps/mongodb/values.schema.json index 9b9d30aa..3a66d60d 100644 --- a/packages/apps/mongodb/values.schema.json +++ b/packages/apps/mongodb/values.schema.json @@ -73,6 +73,36 @@ } } }, + "databases": { + "description": "Databases configuration map.", + "type": "object", + "default": {}, + "additionalProperties": { + "type": "object", + "properties": { + "roles": { + "description": "Roles assigned to users.", + "type": "object", + "properties": { + "admin": { + "description": "List of users with admin privileges (readWrite + dbAdmin).", + "type": "array", + "items": { + "type": "string" + } + }, + "readonly": { + "description": "List of users with read-only privileges.", + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + }, "external": { "description": "Enable external access from outside the cluster.", "type": "boolean", @@ -232,36 +262,6 @@ "type": "string", "default": "" }, - "databases": { - "description": "Databases configuration map.", - "type": "object", - "default": {}, - "additionalProperties": { - "type": "object", - "properties": { - "roles": { - "description": "Roles assigned to users.", - "type": "object", - "properties": { - "admin": { - "description": "List of users with admin privileges (readWrite + dbAdmin).", - "type": "array", - "items": { - "type": "string" - } - }, - "readonly": { - "description": "List of users with read-only privileges.", - "type": "array", - "items": { - "type": "string" - } - } - } - } - } - } - }, "users": { "description": "Users configuration map.", "type": "object", diff --git a/packages/core/platform/images/migrations/migrations/24 b/packages/core/platform/images/migrations/migrations/24 new file mode 100755 index 00000000..cb34b041 --- /dev/null +++ b/packages/core/platform/images/migrations/migrations/24 @@ -0,0 +1,73 @@ +#!/bin/sh +# Migration 24 --> 25 +# Migrate MongoDB users configuration to new databases format + +set -euo pipefail + +echo "Migrating MongoDB HelmReleases: converting users to databases format" + +# Process all MongoDB HelmReleases +kubectl get helmreleases --all-namespaces -o json | \ + jq -r '.items[] | select(.metadata.name | startswith("mongodb-")) | "\(.metadata.namespace)|\(.metadata.name)"' | \ + while IFS='|' read -r namespace name; do + echo "Processing MongoDB HelmRelease $namespace/$name" + + # Get current spec.values + values=$(kubectl get helmrelease -n "$namespace" "$name" -o jsonpath='{.spec.values}') + + # Check if users exist and have old format (with db and roles fields) + has_old_format=$(echo "$values" | jq -r '.users // {} | to_entries[] | select(.value.db != null or .value.roles != null) | .key' | head -1) + + if [ -z "$has_old_format" ]; then + echo "Skipping $namespace/$name: no users with old format found" + continue + fi + + echo "Converting users configuration for $namespace/$name" + + # Build new configuration using jq + new_values=$(echo "$values" | jq ' + # Extract users and build new format + .users as $old_users | + + # Build databases from user roles + ($old_users // {} | to_entries | reduce .[] as $user ( + {}; + ($user.value.roles // []) as $roles | + reduce $roles[] as $role ( + .; + # Determine role type: readWrite/dbAdmin -> admin, read -> readonly + if ($role.name == "readWrite" or $role.name == "dbAdmin") then + .[$role.db].roles.admin = ((.[$role.db].roles.admin // []) + [$user.key] | unique) + elif ($role.name == "read") then + .[$role.db].roles.readonly = ((.[$role.db].roles.readonly // []) + [$user.key] | unique) + else + . + end + ) + )) as $databases | + + # Build new users (only keep password if present) + ($old_users // {} | to_entries | reduce .[] as $user ( + {}; + if $user.value.password then + .[$user.key] = {password: $user.value.password} + else + .[$user.key] = {} + end + )) as $new_users | + + # Update values + . + {users: $new_users} + (if ($databases | length) > 0 then {databases: $databases} else {} end) + ') + + # Patch the HelmRelease + kubectl patch helmrelease -n "$namespace" "$name" --type=merge -p "{\"spec\":{\"values\":$new_values}}" + echo "Successfully migrated $namespace/$name" + done + +echo "MongoDB migration completed" + +# Stamp version +kubectl create configmap -n cozy-system cozystack-version \ + --from-literal=version=25 --dry-run=client -o yaml | kubectl apply -f- diff --git a/packages/system/mongodb-rd/cozyrds/mongodb.yaml b/packages/system/mongodb-rd/cozyrds/mongodb.yaml index 0edb0d00..54909131 100644 --- a/packages/system/mongodb-rd/cozyrds/mongodb.yaml +++ b/packages/system/mongodb-rd/cozyrds/mongodb.yaml @@ -8,7 +8,7 @@ spec: singular: mongodb plural: mongodbs openAPISchema: |- - {"title":"Chart Values","type":"object","properties":{"backup":{"description":"Backup configuration.","type":"object","default":{},"required":["enabled"],"properties":{"destinationPath":{"description":"Destination path for backups (e.g. s3://bucket/path/).","type":"string","default":"s3://bucket/path/to/folder/"},"enabled":{"description":"Enable regular backups.","type":"boolean","default":false},"endpointURL":{"description":"S3 endpoint URL for uploads.","type":"string","default":"http://minio-gateway-service:9000"},"retentionPolicy":{"description":"Retention policy (e.g. \"30d\").","type":"string","default":"30d"},"s3AccessKey":{"description":"Access key for S3 authentication.","type":"string","default":""},"s3SecretKey":{"description":"Secret key for S3 authentication.","type":"string","default":""},"schedule":{"description":"Cron schedule for automated backups.","type":"string","default":"0 2 * * *"}}},"bootstrap":{"description":"Bootstrap configuration.","type":"object","default":{},"required":["backupName","enabled"],"properties":{"backupName":{"description":"Name of backup to restore from.","type":"string","default":""},"enabled":{"description":"Whether to restore from a backup.","type":"boolean","default":false},"recoveryTime":{"description":"Timestamp for point-in-time recovery; empty means latest.","type":"string","default":""}}},"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"replicas":{"description":"Number of MongoDB replicas in replica set.","type":"integer","default":3},"resources":{"description":"Explicit CPU and memory configuration for each MongoDB replica. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available to each replica.","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 (RAM) available to each replica.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"small","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]},"sharding":{"description":"Enable sharded cluster mode. When disabled, deploys a replica set.","type":"boolean","default":false},"shardingConfig":{"description":"Configuration for sharded cluster mode.","type":"object","default":{},"required":["configServerSize","configServers","mongos"],"properties":{"configServerSize":{"description":"PVC size for config servers.","default":"3Gi","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},"configServers":{"description":"Number of config server replicas.","type":"integer","default":3},"mongos":{"description":"Number of mongos router replicas.","type":"integer","default":2},"shards":{"description":"List of shard configurations.","type":"array","default":[{"name":"rs0","replicas":3,"size":"10Gi"}],"items":{"type":"object","required":["name","replicas","size"],"properties":{"name":{"description":"Shard name.","type":"string"},"replicas":{"description":"Number of replicas in this shard.","type":"integer"},"size":{"description":"PVC size for this shard.","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}}}}}},"size":{"description":"Persistent Volume Claim size available for application data.","default":"10Gi","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},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":""},"users":{"description":"Custom MongoDB users configuration map.","type":"object","default":{},"additionalProperties":{"type":"object","required":["db"],"properties":{"db":{"description":"Database to authenticate against.","type":"string"},"password":{"description":"Password for the user (auto-generated if omitted).","type":"string"},"roles":{"description":"List of MongoDB roles with database scope.","type":"array","items":{"type":"object","required":["db","name"],"properties":{"db":{"description":"Database the role applies to.","type":"string"},"name":{"description":"Role name (e.g., readWrite, dbAdmin, clusterAdmin).","type":"string"}}}}}}},"version":{"description":"MongoDB major version to deploy.","type":"string","default":"v8","enum":["v8","v7","v6"]}}} + {"title":"Chart Values","type":"object","properties":{"backup":{"description":"Backup configuration.","type":"object","default":{},"required":["enabled"],"properties":{"destinationPath":{"description":"Destination path for backups (e.g. s3://bucket/path/).","type":"string","default":"s3://bucket/path/to/folder/"},"enabled":{"description":"Enable regular backups.","type":"boolean","default":false},"endpointURL":{"description":"S3 endpoint URL for uploads.","type":"string","default":"http://minio-gateway-service:9000"},"retentionPolicy":{"description":"Retention policy (e.g. \"30d\").","type":"string","default":"30d"},"s3AccessKey":{"description":"Access key for S3 authentication.","type":"string","default":""},"s3SecretKey":{"description":"Secret key for S3 authentication.","type":"string","default":""},"schedule":{"description":"Cron schedule for automated backups.","type":"string","default":"0 2 * * *"}}},"bootstrap":{"description":"Bootstrap configuration.","type":"object","default":{},"required":["backupName","enabled"],"properties":{"backupName":{"description":"Name of backup to restore from.","type":"string","default":""},"enabled":{"description":"Whether to restore from a backup.","type":"boolean","default":false},"recoveryTime":{"description":"Timestamp for point-in-time recovery; empty means latest.","type":"string","default":""}}},"databases":{"description":"Databases configuration map.","type":"object","default":{},"additionalProperties":{"type":"object","properties":{"roles":{"description":"Roles assigned to users.","type":"object","properties":{"admin":{"description":"List of users with admin privileges (readWrite + dbAdmin).","type":"array","items":{"type":"string"}},"readonly":{"description":"List of users with read-only privileges.","type":"array","items":{"type":"string"}}}}}}},"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"replicas":{"description":"Number of MongoDB replicas in replica set.","type":"integer","default":3},"resources":{"description":"Explicit CPU and memory configuration for each MongoDB replica. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available to each replica.","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 (RAM) available to each replica.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"small","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]},"sharding":{"description":"Enable sharded cluster mode. When disabled, deploys a replica set.","type":"boolean","default":false},"shardingConfig":{"description":"Configuration for sharded cluster mode.","type":"object","default":{},"required":["configServerSize","configServers","mongos"],"properties":{"configServerSize":{"description":"PVC size for config servers.","default":"3Gi","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},"configServers":{"description":"Number of config server replicas.","type":"integer","default":3},"mongos":{"description":"Number of mongos router replicas.","type":"integer","default":2},"shards":{"description":"List of shard configurations.","type":"array","default":[{"name":"rs0","replicas":3,"size":"10Gi"}],"items":{"type":"object","required":["name","replicas","size"],"properties":{"name":{"description":"Shard name.","type":"string"},"replicas":{"description":"Number of replicas in this shard.","type":"integer"},"size":{"description":"PVC size for this shard.","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}}}}}},"size":{"description":"Persistent Volume Claim size available for application data.","default":"10Gi","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},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":""},"users":{"description":"Users configuration map.","type":"object","default":{},"additionalProperties":{"type":"object","properties":{"password":{"description":"Password for the user (auto-generated if omitted).","type":"string"}}}},"version":{"description":"MongoDB major version to deploy.","type":"string","default":"v8","enum":["v8","v7","v6"]}}} release: prefix: mongodb- labels: @@ -25,7 +25,7 @@ spec: tags: - database icon: PHN2ZyB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgdmlld0JveD0iMCAwIDE0NCAxNDQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNDQiIGhlaWdodD0iMTQ0IiByeD0iMjQiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcl9tb25nb2RiKSIvPgo8cGF0aCBkPSJNNzIgMjRDNzIgMjQgNzIgMjQgNzIgMjRDNzIgMjQgNTggNDAgNTggNjJDNTggODQgNzIgMTIwIDcyIDEyMEM3MiAxMjAgODYgODQgODYgNjJDODYgNDAgNzIgMjQgNzIgMjRaIiBmaWxsPSIjMDBFRDY0Ii8+CjxwYXRoIGQ9Ik03MiAxMjBDNzIgMTIwIDg2IDg0IDg2IDYyQzg2IDQwIDcyIDI0IDcyIDI0IiBzdHJva2U9IiMwMDY4NEEiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxwYXRoIGQ9Ik03MiAyNEM3MiAyNCA1OCA0MCA1OCA2MkM1OCA4NCA3MiAxMjAgNzIgMTIwIiBzdHJva2U9IiMwMDFFMkIiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxyZWN0IHg9IjY5IiB5PSIxMDgiIHdpZHRoPSI2IiBoZWlnaHQ9IjE2IiByeD0iMiIgZmlsbD0iIzAwNjg0QSIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyX21vbmdvZGIiIHgxPSIxNDAiIHkxPSIxMzAuNSIgeDI9IjQiIHkyPSI5LjQ5OTk5IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiMwMDFFMkIiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMDIzNDMwIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg== - keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "replicas"], ["spec", "resources"], ["spec", "resourcesPreset"], ["spec", "size"], ["spec", "storageClass"], ["spec", "external"], ["spec", "version"], ["spec", "sharding"], ["spec", "shardingConfig"], ["spec", "shardingConfig", "configServers"], ["spec", "shardingConfig", "configServerSize"], ["spec", "shardingConfig", "mongos"], ["spec", "shardingConfig", "shards"], ["spec", "users"], ["spec", "backup"], ["spec", "backup", "enabled"], ["spec", "backup", "schedule"], ["spec", "backup", "retentionPolicy"], ["spec", "backup", "destinationPath"], ["spec", "backup", "endpointURL"], ["spec", "backup", "s3AccessKey"], ["spec", "backup", "s3SecretKey"], ["spec", "bootstrap"], ["spec", "bootstrap", "enabled"], ["spec", "bootstrap", "recoveryTime"], ["spec", "bootstrap", "backupName"]] + keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "replicas"], ["spec", "resources"], ["spec", "resourcesPreset"], ["spec", "size"], ["spec", "storageClass"], ["spec", "external"], ["spec", "version"], ["spec", "sharding"], ["spec", "shardingConfig"], ["spec", "shardingConfig", "configServers"], ["spec", "shardingConfig", "configServerSize"], ["spec", "shardingConfig", "mongos"], ["spec", "shardingConfig", "shards"], ["spec", "users"], ["spec", "databases"], ["spec", "backup"], ["spec", "backup", "enabled"], ["spec", "backup", "schedule"], ["spec", "backup", "retentionPolicy"], ["spec", "backup", "destinationPath"], ["spec", "backup", "endpointURL"], ["spec", "backup", "s3AccessKey"], ["spec", "backup", "s3SecretKey"], ["spec", "bootstrap"], ["spec", "bootstrap", "enabled"], ["spec", "bootstrap", "recoveryTime"], ["spec", "bootstrap", "backupName"]] secrets: exclude: [] include: From 68d8271ede5becb0d4213ce23cb9f602686b5342 Mon Sep 17 00:00:00 2001 From: Kirill Ilin Date: Thu, 29 Jan 2026 18:20:50 +0500 Subject: [PATCH 117/889] [dashboard] Fix resource quota table on Tenant page - Remove table from info - fix namespace selector - add patch to allow usage of flatMap in jsonpath Signed-off-by: Kirill Ilin --- internal/controller/dashboard/factory.go | 23 +---- .../controller/dashboard/static_refactored.go | 2 +- .../patches/flatmap-dynamic-key.diff | 90 +++++++++++++++++++ 3 files changed, 92 insertions(+), 23 deletions(-) create mode 100644 packages/system/dashboard/images/openapi-ui/openapi-k8s-toolkit/patches/flatmap-dynamic-key.diff diff --git a/internal/controller/dashboard/factory.go b/internal/controller/dashboard/factory.go index 63bb7a82..3fa61961 100644 --- a/internal/controller/dashboard/factory.go +++ b/internal/controller/dashboard/factory.go @@ -174,27 +174,6 @@ func detailsTab(kind, endpoint, schemaJSON string, keysOrder [][]string) map[str }), ) } - if kind == "Info" { - rightColStack = append(rightColStack, - antdFlexVertical("resource-quotas-block", 4, []any{ - antdText("resource-quotas-label", true, "Resource Quotas", map[string]any{ - "fontSize": float64(20), - "marginBottom": float64(12), - }), - map[string]any{ - "type": "EnrichedTable", - "data": map[string]any{ - "id": "resource-quotas-table", - "baseprefix": "/openapi-ui", - "clusterNamePartOfUrl": "{2}", - "customizationId": "factory-resource-quotas", - "fetchUrl": "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/resourcequotas", - "pathToItems": []any{`items`}, - }, - }, - }), - ) - } if kind == "Tenant" { rightColStack = append(rightColStack, antdFlexVertical("resource-quotas-block", 4, []any{ @@ -209,7 +188,7 @@ func detailsTab(kind, endpoint, schemaJSON string, keysOrder [][]string) map[str "baseprefix": "/openapi-ui", "clusterNamePartOfUrl": "{2}", "customizationId": "factory-resource-quotas", - "fetchUrl": "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/resourcequotas", + "fetchUrl": "/api/clusters/{2}/k8s/api/v1/namespaces/{reqsJsonPath[0]['.status.namespace']}/resourcequotas", "pathToItems": []any{`items`}, }, }, diff --git a/internal/controller/dashboard/static_refactored.go b/internal/controller/dashboard/static_refactored.go index 5b3acd61..b2ace34b 100644 --- a/internal/controller/dashboard/static_refactored.go +++ b/internal/controller/dashboard/static_refactored.go @@ -194,7 +194,7 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid createFlatMapColumn("Data", ".spec.hard"), createStringColumn("Resource", "_flatMapData_Key"), createStringColumn("Hard", "_flatMapData_Value"), - createStringColumn("Used", ".status.used['{_flatMapData_Key}']"), + createStringColumn("Used", ".status.used[_flatMapData_Key]"), }), // Factory ingress details rules diff --git a/packages/system/dashboard/images/openapi-ui/openapi-k8s-toolkit/patches/flatmap-dynamic-key.diff b/packages/system/dashboard/images/openapi-ui/openapi-k8s-toolkit/patches/flatmap-dynamic-key.diff new file mode 100644 index 00000000..03212014 --- /dev/null +++ b/packages/system/dashboard/images/openapi-ui/openapi-k8s-toolkit/patches/flatmap-dynamic-key.diff @@ -0,0 +1,90 @@ +diff --git a/src/components/molecules/EnrichedTable/organisms/EnrichedTableProvider/utils.ts b/src/components/molecules/EnrichedTable/organisms/EnrichedTableProvider/utils.ts +index 87a0f12..fb2e1cc 100644 +--- a/src/components/molecules/EnrichedTable/organisms/EnrichedTableProvider/utils.ts ++++ b/src/components/molecules/EnrichedTable/organisms/EnrichedTableProvider/utils.ts +@@ -134,22 +134,6 @@ export const prepare = ({ + // impossible in k8s + return {} + }) +- if (customFields.length > 0) { +- dataSource = dataSource.map((el: TJSON) => { +- const newFieldsForComplexJsonPath: Record = {} +- customFields.forEach(({ dataIndex, jsonPath }) => { +- const jpQueryResult = jp.query(el, `$${jsonPath}`) +- newFieldsForComplexJsonPath[dataIndex] = +- Array.isArray(jpQueryResult) && jpQueryResult.length === 1 ? jpQueryResult[0] : jpQueryResult +- }) +- if (typeof el === 'object') { +- return { ...el, ...newFieldsForComplexJsonPath } +- } +- // impossible in k8s +- return { ...newFieldsForComplexJsonPath } +- }) +- } +- + // Handle flatMap: expand rows for map objects + // Process all flatMap columns sequentially + if (flatMapColumns.length > 0 && dataSource) { +@@ -204,6 +188,62 @@ export const prepare = ({ + currentDataSource = expandedDataSource + }) + dataSource = currentDataSource ++ } ++ ++ if (customFields.length > 0) { ++ dataSource = dataSource.map((el: TJSON) => { ++ const newFieldsForComplexJsonPath: Record = {} ++ customFields.forEach(({ dataIndex, jsonPath }) => { ++ let fieldValue: TJSON = null ++ let handled = false ++ ++ const flatMapMatch = jsonPath.match(/^(.*)\[(_flatMap[^\]]+_Key)\](.*)$/) ++ if (flatMapMatch && el && typeof el === 'object' && !Array.isArray(el)) { ++ const basePath = flatMapMatch[1] ++ const keyField = flatMapMatch[2] ++ const tailPath = flatMapMatch[3] ++ const keyValue = (el as Record)[keyField] ++ if (keyValue !== null && keyValue !== undefined) { ++ const baseResult = jp.query(el, `$${basePath}`)[0] ++ if (baseResult && typeof baseResult === 'object' && !Array.isArray(baseResult)) { ++ const baseValue = (baseResult as Record)[String(keyValue)] ++ if (tailPath) { ++ const normalizedTailPath = ++ tailPath.startsWith('.') || tailPath.startsWith('[') ? tailPath : `.${tailPath}` ++ const tailResult = jp.query(baseValue, `$${normalizedTailPath}`) ++ fieldValue = Array.isArray(tailResult) && tailResult.length === 1 ? tailResult[0] : tailResult ++ } else { ++ fieldValue = baseValue as TJSON ++ } ++ handled = true ++ } ++ } ++ } ++ ++ if (!handled) { ++ let resolvedJsonPath = jsonPath ++ if (el && typeof el === 'object' && !Array.isArray(el)) { ++ resolvedJsonPath = jsonPath.replace(/\[(_flatMap[^\]]+_Key)\]/g, (match, keyField) => { ++ const keyValue = (el as Record)[keyField] ++ if (keyValue === null || keyValue === undefined) { ++ return match ++ } ++ const escaped = String(keyValue).replace(/'/g, "\\'") ++ return `['${escaped}']` ++ }) ++ } ++ const jpQueryResult = jp.query(el, `$${resolvedJsonPath}`) ++ fieldValue = Array.isArray(jpQueryResult) && jpQueryResult.length === 1 ? jpQueryResult[0] : jpQueryResult ++ } ++ ++ newFieldsForComplexJsonPath[dataIndex] = fieldValue ++ }) ++ if (typeof el === 'object') { ++ return { ...el, ...newFieldsForComplexJsonPath } ++ } ++ // impossible in k8s ++ return { ...newFieldsForComplexJsonPath } ++ }) + } + } else { + dataSource = dataItems.map((el: TJSON) => { From 063e9a49bdaab202d9e983a8d8dfa7ef3d5f596d Mon Sep 17 00:00:00 2001 From: nbykov0 <166552198+nbykov0@users.noreply.github.com> Date: Thu, 29 Jan 2026 18:02:03 +0300 Subject: [PATCH 118/889] Post upgrade fixes for v1.0 migration script Signed-off-by: nbykov0 <166552198+nbykov0@users.noreply.github.com> --- hack/migrate-to-version-1.0.sh | 49 +++++++++++++++++----- packages/core/platform/templates/apps.yaml | 1 + packages/core/platform/values.yaml | 9 ++-- 3 files changed, 42 insertions(+), 17 deletions(-) diff --git a/hack/migrate-to-version-1.0.sh b/hack/migrate-to-version-1.0.sh index 3985d6f9..743aaf30 100755 --- a/hack/migrate-to-version-1.0.sh +++ b/hack/migrate-to-version-1.0.sh @@ -7,8 +7,9 @@ set -e NAMESPACE="cozy-system" -echo "Cozystack Migration to v1.0" -echo "===========================" +echo "=============================" +echo " Cozystack Migration to v1.0 " +echo "=============================" echo "" echo "This script will convert existing ConfigMaps to a Package resource." echo "" @@ -19,6 +20,12 @@ if ! command -v kubectl &> /dev/null; then exit 1 fi +# Check if jq is available +if ! command -v jq &> /dev/null; then + echo "Error: jq is not installed or not in PATH" + exit 1 +fi + # Check if we can access the cluster if ! kubectl get namespace "$NAMESPACE" &> /dev/null; then echo "Error: Cannot access namespace $NAMESPACE" @@ -42,6 +49,7 @@ CLUSTER_DOMAIN=$(echo "$COZYSTACK_CM" | jq -r '.data["cluster-domain"] // "cozy. ROOT_HOST=$(echo "$COZYSTACK_CM" | jq -r '.data["root-host"] // "example.org"') API_SERVER_ENDPOINT=$(echo "$COZYSTACK_CM" | jq -r '.data["api-server-endpoint"] // ""') OIDC_ENABLED=$(echo "$COZYSTACK_CM" | jq -r '.data["oidc-enabled"] // "false"') +KEYCLOAK_REDIRECTS=$(echo "$COZYSTACK_CM" | jq -r '.data["extra-keycloak-redirect-uri-for-dashboard"] // ""' ) TELEMETRY_ENABLED=$(echo "$COZYSTACK_CM" | jq -r '.data["telemetry-enabled"] // "true"') BUNDLE_NAME=$(echo "$COZYSTACK_CM" | jq -r '.data["bundle-name"] // "paas-full"') @@ -51,6 +59,13 @@ POD_GATEWAY=$(echo "$COZYSTACK_CM" | jq -r '.data["ipv4-pod-gateway"] // "10.244 SVC_CIDR=$(echo "$COZYSTACK_CM" | jq -r '.data["ipv4-svc-cidr"] // "10.96.0.0/16"') JOIN_CIDR=$(echo "$COZYSTACK_CM" | jq -r '.data["ipv4-join-cidr"] // "100.64.0.0/16"') +EXTERNAL_IPS=$(echo "$COZYSTACK_CM" | jq -r '.data["expose-external-ips"] // ""') +if [ -z "$EXTERNAL_IPS" ]; then + EXTERNAL_IPS="[]" +else + EXTERNAL_IPS=$(echo "$EXTERNAL_IPS" | sed 's/,/\n/g' | awk 'BEGIN{print}{print " - "$0}') +fi + # Determine bundle type case "$BUNDLE_NAME" in paas-full|distro-full) @@ -67,12 +82,24 @@ case "$BUNDLE_NAME" in ;; esac +# Update bundle naming +BUNDLE_NAME=$(echo "$BUNDLE_NAME" | sed 's/paas/isp/') + # Extract branding if available -DASHBOARD_BRANDING=$(echo "$BRANDING_CM" | jq -r '.data["dashboard"] // "{}"') -KEYCLOAK_BRANDING=$(echo "$BRANDING_CM" | jq -r '.data["keycloak"] // "{}"') +BRANDING=$(echo "$BRANDING_CM" | jq -r '.data // {} | to_entries[] | "\(.key): \"\(.value)\""') +if [ -z "$BRANDING" ]; then + BRANDING="{}" +else + BRANDING=$(echo "$BRANDING" | awk 'BEGIN{print}{print " " $0}') +fi # Extract scheduling if available -SCHEDULING_CONSTRAINTS=$(echo "$SCHEDULING_CM" | jq -r '.data["topologySpreadConstraints"] // "[]"') +SCHEDULING_CONSTRAINTS=$(echo "$SCHEDULING_CM" | jq -r '.data.["globalAppTopologySpreadConstraints"] // ""') +if [ -z "$SCHEDULING_CONSTRAINTS" ]; then + SCHEDULING_CONSTRAINTS='""' +else + SCHEDULING_CONSTRAINTS=$(echo "$SCHEDULING_CONSTRAINTS" | awk 'BEGIN{print}{print " " $0}') +fi echo "" echo "Extracted configuration:" @@ -116,21 +143,21 @@ spec: publishing: host: "$ROOT_HOST" apiServerEndpoint: "$API_SERVER_ENDPOINT" + externalIPs: $EXTERNAL_IPS authentication: oidc: enabled: $OIDC_ENABLED + keycloakExtraRedirectUri: "$KEYCLOAK_REDIRECTS" scheduling: - topologySpreadConstraints: $SCHEDULING_CONSTRAINTS - branding: - dashboard: $DASHBOARD_BRANDING - keycloak: $KEYCLOAK_BRANDING + globalAppTopologySpreadConstraints: $SCHEDULING_CONSTRAINTS + branding: $BRANDING EOF ) echo "Generated Package resource:" echo "---" echo "$PACKAGE_YAML" -echo "---" +echo "..." echo "" read -p "Do you want to apply this Package? (y/N) " -n 1 -r @@ -149,4 +176,4 @@ else fi echo "" -echo "Migration complete!" +echo "All done!" diff --git a/packages/core/platform/templates/apps.yaml b/packages/core/platform/templates/apps.yaml index d3941067..8a716066 100644 --- a/packages/core/platform/templates/apps.yaml +++ b/packages/core/platform/templates/apps.yaml @@ -17,6 +17,7 @@ stringData: bundle-name: {{ .Values.bundles.system.variant | quote }} clusterissuer: {{ .Values.publishing.certificates.issuerType | quote }} oidc-enabled: {{ .Values.authentication.oidc.enabled | quote }} + extra-keycloak-redirect-uri-for-dashboard: {{ index .Values.authentication.oidc.keycloakExtraRedirectUri | quote }} expose-services: {{ .Values.publishing.exposedServices | join "," | quote }} expose-ingress: {{ .Values.publishing.ingressName | quote }} expose-external-ips: {{ .Values.publishing.externalIPs | join "," | quote }} diff --git a/packages/core/platform/values.yaml b/packages/core/platform/values.yaml index a358ef22..f8a0a9e4 100644 --- a/packages/core/platform/values.yaml +++ b/packages/core/platform/values.yaml @@ -44,15 +44,12 @@ publishing: authentication: oidc: enabled: false - dashboard: - extraRedirectUris: [] + keycloakExtraRedirectUri: "" # Pod scheduling configuration scheduling: - topologySpreadConstraints: [] + globalAppTopologySpreadConstraints: "" # UI branding configuration -branding: - dashboard: {} - keycloak: {} +branding: {} # Container registry mirrors configuration # # Example: From ded52c127972ad5ab2dcbc06d64eebc0f5832d88 Mon Sep 17 00:00:00 2001 From: Kirill Ilin Date: Fri, 30 Jan 2026 13:54:01 +0500 Subject: [PATCH 119/889] [dashboard] Add external ips count to Tenant details page Signed-off-by: Kirill Ilin --- internal/controller/dashboard/factory.go | 4 ++ pkg/apis/apps/v1alpha1/types.go | 3 ++ pkg/registry/apps/application/rest.go | 50 ++++++++++++++++++++---- 3 files changed, 49 insertions(+), 8 deletions(-) diff --git a/internal/controller/dashboard/factory.go b/internal/controller/dashboard/factory.go index 63bb7a82..1016b30a 100644 --- a/internal/controller/dashboard/factory.go +++ b/internal/controller/dashboard/factory.go @@ -196,6 +196,10 @@ func detailsTab(kind, endpoint, schemaJSON string, keysOrder [][]string) map[str ) } if kind == "Tenant" { + leftColStack = append(leftColStack, antdFlexVertical("tenant-external-ip-count", 4, []any{ + antdText("tenant-external-ip-count-label", true, "External IPs count", nil), + parsedText("tenant-external-ip-count-value", `{reqsJsonPath[0]['.status.externalIPsCount']['0']}`, nil), + })) rightColStack = append(rightColStack, antdFlexVertical("resource-quotas-block", 4, []any{ antdText("resource-quotas-label", true, "Resource Quotas", map[string]any{ diff --git a/pkg/apis/apps/v1alpha1/types.go b/pkg/apis/apps/v1alpha1/types.go index 0118b0de..51247efb 100644 --- a/pkg/apis/apps/v1alpha1/types.go +++ b/pkg/apis/apps/v1alpha1/types.go @@ -47,6 +47,9 @@ type ApplicationStatus struct { // Namespace holds the computed namespace for Tenant applications. // +optional Namespace string `json:"namespace,omitempty"` + // ExternalIPsCount holds the number of LoadBalancer services with assigned external IPs for Tenant applications. + // +optional + ExternalIPsCount int32 `json:"externalIPsCount,omitempty"` } // GetConditions returns the status conditions of the object. diff --git a/pkg/registry/apps/application/rest.go b/pkg/registry/apps/application/rest.go index b42f322c..a69e23db 100644 --- a/pkg/registry/apps/application/rest.go +++ b/pkg/registry/apps/application/rest.go @@ -27,6 +27,7 @@ import ( "time" helmv2 "github.com/fluxcd/helm-controller/api/v2" + corev1 "k8s.io/api/core/v1" metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" @@ -186,7 +187,7 @@ func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation } // Convert the created HelmRelease back to Application - convertedApp, err := r.ConvertHelmReleaseToApplication(helmRelease) + convertedApp, err := r.ConvertHelmReleaseToApplication(ctx, helmRelease) if err != nil { klog.Errorf("Conversion error from HelmRelease to Application for resource %s: %v", helmRelease.GetName(), err) return nil, fmt.Errorf("conversion error: %v", err) @@ -233,7 +234,7 @@ func (r *REST) Get(ctx context.Context, name string, options *metav1.GetOptions) } // Convert HelmRelease to Application - convertedApp, err := r.ConvertHelmReleaseToApplication(helmRelease) + convertedApp, err := r.ConvertHelmReleaseToApplication(ctx, helmRelease) if err != nil { klog.Errorf("Conversion error from HelmRelease to Application for resource %s: %v", name, err) return nil, fmt.Errorf("conversion error: %v", err) @@ -360,7 +361,7 @@ func (r *REST) List(ctx context.Context, options *metainternalversion.ListOption continue } - app, err := r.ConvertHelmReleaseToApplication(hr) + app, err := r.ConvertHelmReleaseToApplication(ctx, hr) if err != nil { klog.Errorf("Error converting HelmRelease %s to Application: %v", hr.GetName(), err) continue @@ -514,7 +515,7 @@ func (r *REST) Update(ctx context.Context, name string, objInfo rest.UpdatedObje } // Convert the updated HelmRelease back to Application - convertedApp, err := r.ConvertHelmReleaseToApplication(helmRelease) + convertedApp, err := r.ConvertHelmReleaseToApplication(ctx, helmRelease) if err != nil { klog.Errorf("Conversion error from HelmRelease to Application for resource %s: %v", helmRelease.GetName(), err) return nil, false, fmt.Errorf("conversion error: %v", err) @@ -796,7 +797,7 @@ func (r *REST) Watch(ctx context.Context, options *metainternalversion.ListOptio // Note: All HelmReleases already match the required labels due to server-side label selector filtering // Convert HelmRelease to Application - app, err := r.ConvertHelmReleaseToApplication(hr) + app, err := r.ConvertHelmReleaseToApplication(ctx, hr) if err != nil { klog.Errorf("Error converting HelmRelease to Application: %v", err) continue @@ -967,11 +968,11 @@ func filterPrefixedMap(original map[string]string, prefix string) map[string]str } // ConvertHelmReleaseToApplication converts a HelmRelease to an Application -func (r *REST) ConvertHelmReleaseToApplication(hr *helmv2.HelmRelease) (appsv1alpha1.Application, error) { +func (r *REST) ConvertHelmReleaseToApplication(ctx context.Context, hr *helmv2.HelmRelease) (appsv1alpha1.Application, error) { klog.V(6).Infof("Converting HelmRelease to Application for resource %s", hr.GetName()) // Convert HelmRelease struct to Application struct - app, err := r.convertHelmReleaseToApplication(hr) + app, err := r.convertHelmReleaseToApplication(ctx, hr) if err != nil { klog.Errorf("Error converting from HelmRelease to Application: %v", err) return appsv1alpha1.Application{}, err @@ -1029,7 +1030,7 @@ func validateNoInternalKeys(values *apiextv1.JSON) error { } // convertHelmReleaseToApplication implements the actual conversion logic -func (r *REST) convertHelmReleaseToApplication(hr *helmv2.HelmRelease) (appsv1alpha1.Application, error) { +func (r *REST) convertHelmReleaseToApplication(ctx context.Context, hr *helmv2.HelmRelease) (appsv1alpha1.Application, error) { // Filter out internal keys (starting with "_") from spec filteredSpec := filterInternalKeys(hr.Spec.Values) @@ -1071,6 +1072,12 @@ func (r *REST) convertHelmReleaseToApplication(hr *helmv2.HelmRelease) (appsv1al // Add namespace field for Tenant applications if r.kindName == "Tenant" { app.Status.Namespace = r.computeTenantNamespace(hr.Namespace, app.Name) + externalIPsCount, err := r.countTenantExternalIPs(ctx, app.Status.Namespace) + if err != nil { + klog.Warningf("Failed to count external IPs for tenant %s/%s: %v", hr.Namespace, app.Name, err) + } else { + app.Status.ExternalIPsCount = externalIPsCount + } } return app, nil @@ -1265,6 +1272,33 @@ func (r *REST) computeTenantNamespace(currentNamespace, tenantName string) strin } } +func (r *REST) countTenantExternalIPs(ctx context.Context, namespace string) (int32, error) { + if namespace == "" { + return 0, nil + } + + var services corev1.ServiceList + if err := r.c.List(ctx, &services, client.InNamespace(namespace)); err != nil { + return 0, err + } + + var count int32 + for i := range services.Items { + svc := &services.Items[i] + if svc.Spec.Type != corev1.ServiceTypeLoadBalancer { + continue + } + for _, ingress := range svc.Status.LoadBalancer.Ingress { + if ingress.IP != "" { + count++ + break + } + } + } + + return count, nil +} + // Destroy releases resources associated with REST func (r *REST) Destroy() { // No additional actions needed to release resources. From 40dc20f0f1bc0ee659e205efaec6c90dc692364e Mon Sep 17 00:00:00 2001 From: Kirill Ilin Date: Fri, 30 Jan 2026 15:13:24 +0500 Subject: [PATCH 120/889] [cozystack-api] Add field index for Service spec.type and filter by LoadBalancer type Signed-off-by: Kirill Ilin --- pkg/apiserver/apiserver.go | 11 +++++++++++ pkg/registry/apps/application/rest.go | 10 ++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index fccbf9a9..bc2b4857 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -149,6 +149,17 @@ func (c completedConfig) New() (*CozyServer, error) { return nil, fmt.Errorf("failed to build manager: %w", err) } + if err := mgr.GetFieldIndexer().IndexField( + context.Background(), + &corev1.Service{}, + "spec.type", + func(rawObj client.Object) []string { + svc := rawObj.(*corev1.Service) + return []string{string(svc.Spec.Type)} + }); err != nil { + return nil, fmt.Errorf("failed to index service spec.type field: %w", err) + } + ctx := ctrl.SetupSignalHandler() if err = mustGetInformers(ctx, mgr, diff --git a/pkg/registry/apps/application/rest.go b/pkg/registry/apps/application/rest.go index a69e23db..00d22486 100644 --- a/pkg/registry/apps/application/rest.go +++ b/pkg/registry/apps/application/rest.go @@ -1278,16 +1278,18 @@ func (r *REST) countTenantExternalIPs(ctx context.Context, namespace string) (in } var services corev1.ServiceList - if err := r.c.List(ctx, &services, client.InNamespace(namespace)); err != nil { + if err := r.c.List( + ctx, + &services, + client.InNamespace(namespace), + client.MatchingFields{"spec.type": string(corev1.ServiceTypeLoadBalancer)}, + ); err != nil { return 0, err } var count int32 for i := range services.Items { svc := &services.Items[i] - if svc.Spec.Type != corev1.ServiceTypeLoadBalancer { - continue - } for _, ingress := range svc.Status.LoadBalancer.Ingress { if ingress.IP != "" { count++ From 083ce146094172b11cbc2ee09f2cdece83faa907 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Thu, 29 Jan 2026 21:18:04 +0300 Subject: [PATCH 121/889] feat(platform): add configurable Kubernetes API server settings for non-Talos deployments Add networking.apiServer.host and networking.apiServer.port to platform values to allow overriding the default Talos KubePrism settings (localhost:7445). Also add networking.cilium.cgroup.autoMount for non-Talos clusters that need Cilium to mount cgroups automatically. Changes: - packages/core/platform/values.yaml: Add apiServer and cilium.cgroup settings - packages/core/platform/templates/bundles/system.yaml: Pass cilium values - packages/core/installer/values.yaml: Add kubernetesServiceHost/Port - packages/core/installer/templates/cozystack-operator.yaml: Template env vars Closes: #1933 Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- .../core/installer/templates/cozystack-operator.yaml | 4 ++-- packages/core/platform/templates/bundles/system.yaml | 5 +++++ packages/core/platform/values.yaml | 10 ++++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/core/installer/templates/cozystack-operator.yaml b/packages/core/installer/templates/cozystack-operator.yaml index a88b2590..31028757 100644 --- a/packages/core/installer/templates/cozystack-operator.yaml +++ b/packages/core/installer/templates/cozystack-operator.yaml @@ -65,9 +65,9 @@ spec: {{- end }} env: - name: KUBERNETES_SERVICE_HOST - value: localhost + value: {{ .Values.cozystackOperator.kubernetesServiceHost | quote }} - name: KUBERNETES_SERVICE_PORT - value: "7445" + value: {{ .Values.cozystackOperator.kubernetesServicePort | quote }} hostNetwork: true tolerations: - key: "node.kubernetes.io/not-ready" diff --git a/packages/core/platform/templates/bundles/system.yaml b/packages/core/platform/templates/bundles/system.yaml index 96a88333..f683bf03 100644 --- a/packages/core/platform/templates/bundles/system.yaml +++ b/packages/core/platform/templates/bundles/system.yaml @@ -11,6 +11,11 @@ "SVC_CIDR" .Values.networking.serviceCIDR "JOIN_CIDR" .Values.networking.joinCIDR)) -}} {{- $_ := set $networkingComponents "kubeovn" (dict "values" $kubeovnValues) -}} +{{- $ciliumValues := dict "cilium" (dict + "k8sServiceHost" .Values.networking.apiServer.host + "k8sServicePort" .Values.networking.apiServer.port + "cgroup" (dict "autoMount" (dict "enabled" .Values.networking.cilium.cgroup.autoMount))) -}} +{{- $_ := set $networkingComponents "cilium" (dict "values" $ciliumValues) -}} {{- end -}} {{include "cozystack.platform.package" (list "cozystack.networking" "kubeovn-cilium" $ $networkingComponents) }} {{include "cozystack.platform.package.default" (list "cozystack.kubeovn-webhook" $) }} diff --git a/packages/core/platform/values.yaml b/packages/core/platform/values.yaml index f8a0a9e4..4665e86b 100644 --- a/packages/core/platform/values.yaml +++ b/packages/core/platform/values.yaml @@ -27,6 +27,16 @@ networking: podGateway: "10.244.0.1" serviceCIDR: "10.96.0.0/16" joinCIDR: "100.64.0.0/16" + # Kubernetes API server configuration for Cilium + # Defaults are for Talos KubePrism; override for non-Talos clusters + apiServer: + host: "localhost" + port: "7445" + # Cilium cgroup configuration + # Set autoMount to true for non-Talos clusters + cilium: + cgroup: + autoMount: false # Service publishing and ingress configuration publishing: host: "example.org" From 28152b62ecae1c2438c98380622461b4f1bcb297 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 30 Jan 2026 11:12:06 +0300 Subject: [PATCH 122/889] feat(platform): add MASTER_NODES support for KubeOVN on non-Talos clusters - Add networking.kubeovn.MASTER_NODES to platform values.yaml - Conditionally include MASTER_NODES in kubeovn values when set - Enables KubeOVN deployment on k3s/kubeadm without control-plane node labels Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- .../core/platform/templates/bundles/system.yaml | 16 ++++++++++------ packages/core/platform/values.yaml | 4 ++++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/core/platform/templates/bundles/system.yaml b/packages/core/platform/templates/bundles/system.yaml index f683bf03..2ef10965 100644 --- a/packages/core/platform/templates/bundles/system.yaml +++ b/packages/core/platform/templates/bundles/system.yaml @@ -4,12 +4,16 @@ {{- if eq .Values.bundles.system.variant "isp-full" }} {{- $networkingComponents := dict -}} {{- if .Values.networking -}} -{{- $kubeovnValues := dict "kube-ovn" (dict - "ipv4" (dict - "POD_CIDR" .Values.networking.podCIDR - "POD_GATEWAY" .Values.networking.podGateway - "SVC_CIDR" .Values.networking.serviceCIDR - "JOIN_CIDR" .Values.networking.joinCIDR)) -}} +{{- $kubeovnIpv4 := dict + "POD_CIDR" .Values.networking.podCIDR + "POD_GATEWAY" .Values.networking.podGateway + "SVC_CIDR" .Values.networking.serviceCIDR + "JOIN_CIDR" .Values.networking.joinCIDR -}} +{{- $kubeovnDict := dict "ipv4" $kubeovnIpv4 -}} +{{- if .Values.networking.kubeovn.MASTER_NODES -}} +{{- $_ := set $kubeovnDict "MASTER_NODES" .Values.networking.kubeovn.MASTER_NODES -}} +{{- end -}} +{{- $kubeovnValues := dict "kube-ovn" $kubeovnDict -}} {{- $_ := set $networkingComponents "kubeovn" (dict "values" $kubeovnValues) -}} {{- $ciliumValues := dict "cilium" (dict "k8sServiceHost" .Values.networking.apiServer.host diff --git a/packages/core/platform/values.yaml b/packages/core/platform/values.yaml index 4665e86b..8ea9f27d 100644 --- a/packages/core/platform/values.yaml +++ b/packages/core/platform/values.yaml @@ -37,6 +37,10 @@ networking: cilium: cgroup: autoMount: false + # KubeOVN configuration for non-Talos clusters + # MASTER_NODES: comma-separated list of master node IPs (empty = use helm lookup) + kubeovn: + MASTER_NODES: "" # Service publishing and ingress configuration publishing: host: "example.org" From 72c7290351adec4115dea4a0590bc16bdbff7d2a Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 30 Jan 2026 12:38:53 +0300 Subject: [PATCH 123/889] feat(linstor): make Talos-specific configuration conditional Add talos.enabled value (default: true) to control whether LinstorSatelliteConfiguration cozystack-talos is created. This allows deploying linstor on non-Talos clusters (Ubuntu, Debian, etc.) where DRBD init containers are required. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/system/linstor/templates/satellites-talos.yaml | 2 ++ packages/system/linstor/values.yaml | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/packages/system/linstor/templates/satellites-talos.yaml b/packages/system/linstor/templates/satellites-talos.yaml index c5be9204..21da32a6 100644 --- a/packages/system/linstor/templates/satellites-talos.yaml +++ b/packages/system/linstor/templates/satellites-talos.yaml @@ -1,3 +1,4 @@ +{{- if .Values.talos.enabled }} apiVersion: piraeus.io/v1 kind: LinstorSatelliteConfiguration metadata: @@ -41,3 +42,4 @@ spec: hostPath: path: /var/etc/lvm/archive type: DirectoryOrCreate +{{- end }} diff --git a/packages/system/linstor/values.yaml b/packages/system/linstor/values.yaml index 9f7f022e..8c19ba2a 100644 --- a/packages/system/linstor/values.yaml +++ b/packages/system/linstor/values.yaml @@ -2,6 +2,11 @@ piraeusServer: image: repository: ghcr.io/cozystack/cozystack/piraeus-server tag: 1.32.3@sha256:0e78fa31a3fe4ec2af43d1e59a9fc0f6d765780e32d473e18e1c495714051802 + +# Talos-specific workarounds (disable for generic Linux like Ubuntu/Debian) +talos: + enabled: true + linstor: autoDiskful: enabled: true From a5de8379c5383bbc8828168d84ed96a0ecb8b15e Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 30 Jan 2026 12:39:02 +0300 Subject: [PATCH 124/889] fix(linstor-scheduler): strip distribution suffix from Kubernetes version k3s and RKE2 include distribution suffixes in version string (e.g., v1.35.0+k3s1) which are not valid container image tags. Strip everything after '+' using regexReplaceAll to produce clean version tags like v1.35.0. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- .../charts/linstor-scheduler/templates/_helpers.tpl | 6 ++++-- .../charts/linstor-scheduler/templates/deployment.yaml | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/_helpers.tpl b/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/_helpers.tpl index ad24453b..04c793f9 100644 --- a/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/_helpers.tpl +++ b/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/_helpers.tpl @@ -62,10 +62,12 @@ Create the name of the service account to use {{- end }} {{/* -Get the kubernetes version we should assume for creating scheduler configs +Get the kubernetes version we should assume for creating scheduler configs. +Strips distribution suffixes like +k3s1, +rke2r1 from version string. */}} {{- define "linstor-scheduler.kubeVersion" }} -{{- .Values.scheduler.image.compatibleKubernetesRelease | default .Capabilities.KubeVersion.Version }} +{{- $version := .Values.scheduler.image.compatibleKubernetesRelease | default .Capabilities.KubeVersion.Version }} +{{- regexReplaceAll "\\+.*$" $version "" }} {{- end }} {{/* diff --git a/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/deployment.yaml b/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/deployment.yaml index 49898b8d..ea43cb83 100644 --- a/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/deployment.yaml +++ b/packages/system/linstor-scheduler/charts/linstor-scheduler/templates/deployment.yaml @@ -30,7 +30,7 @@ spec: {{- toYaml .Values.podSecurityContext | nindent 8 }} containers: - name: kube-scheduler - image: "{{ .Values.scheduler.image.repository }}:{{ .Values.scheduler.image.tag | default .Capabilities.KubeVersion.Version }}" + image: "{{ .Values.scheduler.image.repository }}:{{ .Values.scheduler.image.tag | default (include "linstor-scheduler.kubeVersion" .) }}" securityContext: {{- toYaml .Values.scheduler.securityContext | nindent 12 }} command: From b7028aaa2ae8c8396adfd51aa829ae9f837ffbdf Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 30 Jan 2026 12:39:07 +0300 Subject: [PATCH 125/889] feat(networking): add generic variants for non-Talos clusters Add cilium-generic and kubeovn-cilium-generic variants that exclude values-talos.yaml. These variants are suitable for kubeadm, k3s, RKE2 and other non-Talos Kubernetes distributions. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- .../core/platform/sources/networking.yaml | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/packages/core/platform/sources/networking.yaml b/packages/core/platform/sources/networking.yaml index 57fff6fc..9694dcc3 100644 --- a/packages/core/platform/sources/networking.yaml +++ b/packages/core/platform/sources/networking.yaml @@ -33,6 +33,27 @@ spec: releaseName: cilium-networkpolicy dependsOn: - cilium + # Generic Cilium variant for non-Talos clusters (kubeadm, k3s, RKE2, etc.) + - name: cilium-generic + dependsOn: [] + components: + - name: cilium + path: system/cilium + valuesFiles: + - values.yaml + install: + privileged: true + namespace: cozy-cilium + releaseName: cilium + dependsOn: [] + - name: cilium-networkpolicy + path: system/cilium-networkpolicy + install: + privileged: true + namespace: cozy-cilium + releaseName: cilium-networkpolicy + dependsOn: + - cilium - name: kubeovn-cilium dependsOn: [] components: @@ -63,3 +84,33 @@ spec: releaseName: kubeovn dependsOn: - cilium + # Generic KubeOVN+Cilium variant for non-Talos clusters (kubeadm, k3s, RKE2, etc.) + - name: kubeovn-cilium-generic + dependsOn: [] + components: + - name: cilium + path: system/cilium + valuesFiles: + - values.yaml + - values-kubeovn.yaml + install: + privileged: true + namespace: cozy-cilium + releaseName: cilium + dependsOn: [] + - name: cilium-networkpolicy + path: system/cilium-networkpolicy + install: + privileged: true + namespace: cozy-cilium + releaseName: cilium-networkpolicy + dependsOn: + - cilium + - name: kubeovn + path: system/kubeovn + install: + privileged: true + namespace: cozy-kubeovn + releaseName: kubeovn + dependsOn: + - cilium From 47e69e58a06acc088677ecb858267dd8bce4f155 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 30 Jan 2026 12:39:13 +0300 Subject: [PATCH 126/889] feat(platform): add isp-full-generic bundle variant Add isp-full-generic bundle variant for deploying Cozystack on non-Talos Kubernetes clusters (kubeadm, k3s, RKE2, etc.). This variant: - Uses kubeovn-cilium-generic networking (no values-talos.yaml) - Passes talos.enabled=false to linstor (keeps DRBD init containers) - Supports all standard networking configuration via platform values Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- .../platform/templates/bundles/system.yaml | 35 +++++++++++++++++++ packages/core/platform/values.yaml | 2 +- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/packages/core/platform/templates/bundles/system.yaml b/packages/core/platform/templates/bundles/system.yaml index 2ef10965..ab369892 100644 --- a/packages/core/platform/templates/bundles/system.yaml +++ b/packages/core/platform/templates/bundles/system.yaml @@ -37,6 +37,41 @@ {{include "cozystack.platform.package" (list "cozystack.networking" "noop" $) }} {{- end }} +{{- if eq .Values.bundles.system.variant "isp-full-generic" }} +{{- $networkingComponents := dict -}} +{{- if .Values.networking -}} +{{- $kubeovnIpv4 := dict + "POD_CIDR" .Values.networking.podCIDR + "POD_GATEWAY" .Values.networking.podGateway + "SVC_CIDR" .Values.networking.serviceCIDR + "JOIN_CIDR" .Values.networking.joinCIDR -}} +{{- $kubeovnDict := dict "ipv4" $kubeovnIpv4 -}} +{{- if .Values.networking.kubeovn.MASTER_NODES -}} +{{- $_ := set $kubeovnDict "MASTER_NODES" .Values.networking.kubeovn.MASTER_NODES -}} +{{- end -}} +{{- $kubeovnValues := dict "kube-ovn" $kubeovnDict -}} +{{- $_ := set $networkingComponents "kubeovn" (dict "values" $kubeovnValues) -}} +{{- $ciliumValues := dict "cilium" (dict + "k8sServiceHost" .Values.networking.apiServer.host + "k8sServicePort" .Values.networking.apiServer.port + "cgroup" (dict "autoMount" (dict "enabled" .Values.networking.cilium.cgroup.autoMount))) -}} +{{- $_ := set $networkingComponents "cilium" (dict "values" $ciliumValues) -}} +{{- end -}} +{{- /* Use kubeovn-cilium-generic variant (no values-talos.yaml) */ -}} +{{include "cozystack.platform.package" (list "cozystack.networking" "kubeovn-cilium-generic" $ $networkingComponents) }} +{{include "cozystack.platform.package.default" (list "cozystack.kubeovn-webhook" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.kubeovn-plunger" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.cozy-proxy" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.multus" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.metallb" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.reloader" $) }} +{{- /* Pass talos.enabled: false to linstor for generic Linux */ -}} +{{- $linstorComponents := dict "linstor" (dict "values" (dict "talos" (dict "enabled" false))) -}} +{{include "cozystack.platform.package" (list "cozystack.linstor" "default" $ $linstorComponents) }} +{{include "cozystack.platform.package.default" (list "cozystack.linstor-scheduler" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.snapshot-controller" $) }} +{{- end }} + # Cozystack Engine {{- if .Values.authentication.oidc.enabled }} {{include "cozystack.platform.package" (list "cozystack.cozystack-engine" "oidc" $) }} diff --git a/packages/core/platform/values.yaml b/packages/core/platform/values.yaml index 8ea9f27d..34a251e6 100644 --- a/packages/core/platform/values.yaml +++ b/packages/core/platform/values.yaml @@ -11,7 +11,7 @@ migrations: bundles: system: enabled: false - variant: "isp-full" # Options: "isp-full", "isp-hosted", "distro-full" + variant: "isp-full" # Options: "isp-full", "isp-full-generic", "isp-hosted", "distro-full" iaas: enabled: false paas: From 9681387c9830af8b2cd54fd7ebf4fbaa0ae6a943 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 30 Jan 2026 13:55:38 +0300 Subject: [PATCH 127/889] fix(platform): parse apiServerEndpoint URL for generic k8s bundle The isp-full-generic bundle now automatically extracts host and port from publishing.apiServerEndpoint URL for Cilium and KubeOVN configuration. This eliminates the need for manual networking.apiServer values when api-server-endpoint is set in the ConfigMap. Changes: - Parse apiServerEndpoint URL to extract host and port for Cilium - Auto-detect MASTER_NODES for KubeOVN from apiServerEndpoint if not explicitly set - Hardcode cgroup.autoMount=true for generic k8s (non-Talos) Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- .../platform/templates/bundles/system.yaml | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/packages/core/platform/templates/bundles/system.yaml b/packages/core/platform/templates/bundles/system.yaml index ab369892..ad3630a9 100644 --- a/packages/core/platform/templates/bundles/system.yaml +++ b/packages/core/platform/templates/bundles/system.yaml @@ -40,21 +40,41 @@ {{- if eq .Values.bundles.system.variant "isp-full-generic" }} {{- $networkingComponents := dict -}} {{- if .Values.networking -}} +{{- /* Parse apiServerEndpoint URL for generic k8s (used by both Cilium and KubeOVN) */ -}} +{{- $apiHost := .Values.networking.apiServer.host -}} +{{- $apiPort := .Values.networking.apiServer.port -}} +{{- if .Values.publishing.apiServerEndpoint -}} +{{- $parsed := urlParse .Values.publishing.apiServerEndpoint -}} +{{- $hostPort := splitList ":" $parsed.host -}} +{{- $apiHost = index $hostPort 0 -}} +{{- if gt (len $hostPort) 1 -}} +{{- $apiPort = index $hostPort 1 -}} +{{- else -}} +{{- $apiPort = "6443" -}} +{{- end -}} +{{- end -}} +{{- /* KubeOVN IPv4 configuration */ -}} {{- $kubeovnIpv4 := dict "POD_CIDR" .Values.networking.podCIDR "POD_GATEWAY" .Values.networking.podGateway "SVC_CIDR" .Values.networking.serviceCIDR "JOIN_CIDR" .Values.networking.joinCIDR -}} {{- $kubeovnDict := dict "ipv4" $kubeovnIpv4 -}} -{{- if .Values.networking.kubeovn.MASTER_NODES -}} -{{- $_ := set $kubeovnDict "MASTER_NODES" .Values.networking.kubeovn.MASTER_NODES -}} +{{- /* Set MASTER_NODES: explicit value > parsed from apiServerEndpoint > empty (use helm lookup) */ -}} +{{- $masterNodes := .Values.networking.kubeovn.MASTER_NODES -}} +{{- if and (not $masterNodes) .Values.publishing.apiServerEndpoint -}} +{{- $masterNodes = $apiHost -}} +{{- end -}} +{{- if $masterNodes -}} +{{- $_ := set $kubeovnDict "MASTER_NODES" $masterNodes -}} {{- end -}} {{- $kubeovnValues := dict "kube-ovn" $kubeovnDict -}} {{- $_ := set $networkingComponents "kubeovn" (dict "values" $kubeovnValues) -}} +{{- /* Cilium configuration - for generic k8s, always enable cgroup autoMount */ -}} {{- $ciliumValues := dict "cilium" (dict - "k8sServiceHost" .Values.networking.apiServer.host - "k8sServicePort" .Values.networking.apiServer.port - "cgroup" (dict "autoMount" (dict "enabled" .Values.networking.cilium.cgroup.autoMount))) -}} + "k8sServiceHost" $apiHost + "k8sServicePort" $apiPort + "cgroup" (dict "autoMount" (dict "enabled" true))) -}} {{- $_ := set $networkingComponents "cilium" (dict "values" $ciliumValues) -}} {{- end -}} {{- /* Use kubeovn-cilium-generic variant (no values-talos.yaml) */ -}} From f72860c827e3691679050792b92404df1414677e Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 30 Jan 2026 14:32:07 +0300 Subject: [PATCH 128/889] feat(platform): add values-isp-full-generic.yaml for generic k8s bundle Create dedicated values file for isp-full-generic bundle variant with bundles.system.variant set to "isp-full-generic" instead of "isp-full". This fixes the ArtifactGenerator generating identical digests for both isp-full and isp-full-generic variants, which caused the wrong bundle template to be selected. Also adds isp-full-generic variant to installer PackageSource definition. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/core/installer/templates/packagesource.yaml | 10 ++++++++++ packages/core/platform/values-isp-full-generic.yaml | 12 ++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 packages/core/platform/values-isp-full-generic.yaml diff --git a/packages/core/installer/templates/packagesource.yaml b/packages/core/installer/templates/packagesource.yaml index 7a3dd73e..f4a5f6d0 100644 --- a/packages/core/installer/templates/packagesource.yaml +++ b/packages/core/installer/templates/packagesource.yaml @@ -41,3 +41,13 @@ spec: valuesFiles: - values.yaml - values-isp-hosted.yaml + - name: isp-full-generic + components: + - install: + namespace: cozy-system + releaseName: cozystack-platform + name: platform + path: core/platform + valuesFiles: + - values.yaml + - values-isp-full-generic.yaml diff --git a/packages/core/platform/values-isp-full-generic.yaml b/packages/core/platform/values-isp-full-generic.yaml new file mode 100644 index 00000000..d5515bab --- /dev/null +++ b/packages/core/platform/values-isp-full-generic.yaml @@ -0,0 +1,12 @@ +migrations: + enabled: true +bundles: + system: + enabled: true + variant: "isp-full-generic" + iaas: + enabled: true + paas: + enabled: true + naas: + enabled: true From c3197d59fa175ef7f013dc32c21807b668d2e765 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 30 Jan 2026 14:49:55 +0300 Subject: [PATCH 129/889] fix(platform): disable iaas/paas/naas bundles for generic variant The isp-full-generic variant should have iaas, paas, and naas bundles disabled by default since these bundles require 'isp-full' or 'isp-hosted' variants as per validation rules in their respective templates. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/core/platform/values-isp-full-generic.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/platform/values-isp-full-generic.yaml b/packages/core/platform/values-isp-full-generic.yaml index d5515bab..17ca19f3 100644 --- a/packages/core/platform/values-isp-full-generic.yaml +++ b/packages/core/platform/values-isp-full-generic.yaml @@ -5,8 +5,8 @@ bundles: enabled: true variant: "isp-full-generic" iaas: - enabled: true + enabled: false paas: - enabled: true + enabled: false naas: - enabled: true + enabled: false From 467b1eb350bff27d034e8854790b337b4e4d79a3 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 30 Jan 2026 14:59:40 +0300 Subject: [PATCH 130/889] fix(platform): preserve trailing newlines in package helper templates Remove trailing dash from {{- end -}} constructs in _helpers.tpl to preserve trailing newlines after each rendered package YAML document. Without this fix, consecutive packages are rendered without proper separators: variant: default--- apiVersion: cozystack.io/v1alpha1 This causes YAML parse errors: "mapping key 'apiVersion' already defined" because the YAML parser sees a single document with duplicate keys instead of two separate documents. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/core/platform/templates/_helpers.tpl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/core/platform/templates/_helpers.tpl b/packages/core/platform/templates/_helpers.tpl index 18105acf..2c454177 100644 --- a/packages/core/platform/templates/_helpers.tpl +++ b/packages/core/platform/templates/_helpers.tpl @@ -19,14 +19,14 @@ spec: components: {{ toYaml $components | indent 4 }} {{- end }} -{{- end -}} -{{- end -}} +{{- end }} +{{ end }} {{- define "cozystack.platform.package.default" -}} {{- $name := index . 0 -}} {{- $root := index . 1 -}} {{- include "cozystack.platform.package" (list $name "default" $root) }} -{{- end -}} +{{ end }} {{- define "cozystack.platform.package.optional" -}} {{- $name := index . 0 -}} @@ -42,11 +42,11 @@ metadata: name: {{ $name }} spec: variant: {{ $variant }} -{{- end -}} -{{- end -}} +{{- end }} +{{ end }} {{- define "cozystack.platform.package.optional.default" -}} {{- $name := index . 0 -}} {{- $root := index . 1 -}} {{- include "cozystack.platform.package.optional" (list $name "default" $root) }} -{{- end -}} +{{ end }} From 4132dff70a5e8fe734a44e962bee5b4dda449634 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 30 Jan 2026 15:07:20 +0300 Subject: [PATCH 131/889] fix(platform): read values from cozystack ConfigMap in isp-full-generic For isp-full-generic bundle variant, the template now looks up the cozystack ConfigMap to read cluster-specific settings instead of only relying on chart default values. This fixes the issue where Cilium was trying to connect to localhost:7445 (Talos KubePrism default) instead of the actual API server endpoint. Values read from ConfigMap: - api-server-endpoint: parsed to extract host/port for Cilium - ipv4-pod-cidr, ipv4-pod-gateway, ipv4-svc-cidr, ipv4-join-cidr: for KubeOVN Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- .../platform/templates/bundles/system.yaml | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/packages/core/platform/templates/bundles/system.yaml b/packages/core/platform/templates/bundles/system.yaml index ad3630a9..12ce708b 100644 --- a/packages/core/platform/templates/bundles/system.yaml +++ b/packages/core/platform/templates/bundles/system.yaml @@ -41,10 +41,16 @@ {{- $networkingComponents := dict -}} {{- if .Values.networking -}} {{- /* Parse apiServerEndpoint URL for generic k8s (used by both Cilium and KubeOVN) */ -}} +{{- /* First try cozystack ConfigMap, then fall back to chart values */ -}} +{{- $cozystackCm := lookup "v1" "ConfigMap" "cozy-system" "cozystack" -}} +{{- $apiServerEndpoint := .Values.publishing.apiServerEndpoint -}} +{{- if and $cozystackCm $cozystackCm.data -}} +{{- $apiServerEndpoint = default $apiServerEndpoint (index $cozystackCm.data "api-server-endpoint") -}} +{{- end -}} {{- $apiHost := .Values.networking.apiServer.host -}} {{- $apiPort := .Values.networking.apiServer.port -}} -{{- if .Values.publishing.apiServerEndpoint -}} -{{- $parsed := urlParse .Values.publishing.apiServerEndpoint -}} +{{- if $apiServerEndpoint -}} +{{- $parsed := urlParse $apiServerEndpoint -}} {{- $hostPort := splitList ":" $parsed.host -}} {{- $apiHost = index $hostPort 0 -}} {{- if gt (len $hostPort) 1 -}} @@ -53,16 +59,26 @@ {{- $apiPort = "6443" -}} {{- end -}} {{- end -}} -{{- /* KubeOVN IPv4 configuration */ -}} +{{- /* KubeOVN IPv4 configuration - read from ConfigMap or fall back to chart values */ -}} +{{- $podCIDR := .Values.networking.podCIDR -}} +{{- $podGateway := .Values.networking.podGateway -}} +{{- $svcCIDR := .Values.networking.serviceCIDR -}} +{{- $joinCIDR := .Values.networking.joinCIDR -}} +{{- if and $cozystackCm $cozystackCm.data -}} +{{- $podCIDR = default $podCIDR (index $cozystackCm.data "ipv4-pod-cidr") -}} +{{- $podGateway = default $podGateway (index $cozystackCm.data "ipv4-pod-gateway") -}} +{{- $svcCIDR = default $svcCIDR (index $cozystackCm.data "ipv4-svc-cidr") -}} +{{- $joinCIDR = default $joinCIDR (index $cozystackCm.data "ipv4-join-cidr") -}} +{{- end -}} {{- $kubeovnIpv4 := dict - "POD_CIDR" .Values.networking.podCIDR - "POD_GATEWAY" .Values.networking.podGateway - "SVC_CIDR" .Values.networking.serviceCIDR - "JOIN_CIDR" .Values.networking.joinCIDR -}} + "POD_CIDR" $podCIDR + "POD_GATEWAY" $podGateway + "SVC_CIDR" $svcCIDR + "JOIN_CIDR" $joinCIDR -}} {{- $kubeovnDict := dict "ipv4" $kubeovnIpv4 -}} {{- /* Set MASTER_NODES: explicit value > parsed from apiServerEndpoint > empty (use helm lookup) */ -}} {{- $masterNodes := .Values.networking.kubeovn.MASTER_NODES -}} -{{- if and (not $masterNodes) .Values.publishing.apiServerEndpoint -}} +{{- if and (not $masterNodes) $apiServerEndpoint -}} {{- $masterNodes = $apiHost -}} {{- end -}} {{- if $masterNodes -}} From 0e207d466e79820c9c8001703d33102390ff8e9f Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 30 Jan 2026 15:22:26 +0300 Subject: [PATCH 132/889] fix(platform): set MASTER_NODES_LABEL for generic k8s in isp-full-generic k3s and kubeadm set node-role.kubernetes.io/control-plane=true, while Talos uses an empty value. KubeOVN node selector needs the exact label value to match. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/core/platform/templates/bundles/system.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/core/platform/templates/bundles/system.yaml b/packages/core/platform/templates/bundles/system.yaml index 12ce708b..48837105 100644 --- a/packages/core/platform/templates/bundles/system.yaml +++ b/packages/core/platform/templates/bundles/system.yaml @@ -84,6 +84,8 @@ {{- if $masterNodes -}} {{- $_ := set $kubeovnDict "MASTER_NODES" $masterNodes -}} {{- end -}} +{{- /* For generic k8s (k3s, kubeadm), control-plane label has value "true" */ -}} +{{- $_ := set $kubeovnDict "MASTER_NODES_LABEL" "node-role.kubernetes.io/control-plane=true" -}} {{- $kubeovnValues := dict "kube-ovn" $kubeovnDict -}} {{- $_ := set $networkingComponents "kubeovn" (dict "values" $kubeovnValues) -}} {{- /* Cilium configuration - for generic k8s, always enable cgroup autoMount */ -}} From d384c6faf6df8d03d5d58c7733a6ee31876f2b8b Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 30 Jan 2026 15:50:10 +0300 Subject: [PATCH 133/889] fix(lineage-webhook): make nodeSelector configurable for generic k8s On Talos, control-plane nodes have label node-role.kubernetes.io/control-plane with empty value. On generic k8s (k3s, kubeadm), the same label has value "true". The lineage-controller-webhook DaemonSet was hardcoded to use empty value, causing 0 pods scheduled on k3s clusters. Changes: - Add nodeSelector to lineage-controller-webhook values (default: empty for Talos) - Update DaemonSet template to use configurable nodeSelector - Pass nodeSelector with value "true" for isp-full-generic bundle variant Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/core/platform/templates/bundles/system.yaml | 11 +++++++++-- .../templates/daemonset.yaml | 4 +++- .../system/lineage-controller-webhook/values.yaml | 5 +++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/packages/core/platform/templates/bundles/system.yaml b/packages/core/platform/templates/bundles/system.yaml index 48837105..515cffb7 100644 --- a/packages/core/platform/templates/bundles/system.yaml +++ b/packages/core/platform/templates/bundles/system.yaml @@ -111,12 +111,19 @@ {{- end }} # Cozystack Engine +{{- $cozystackEngineComponents := dict -}} +{{- /* For generic k8s, lineage-controller-webhook needs nodeSelector with value "true" */ -}} +{{- if eq .Values.bundles.system.variant "isp-full-generic" -}} +{{- $lineageNodeSelector := dict "node-role.kubernetes.io/control-plane" "true" -}} +{{- $lineageValues := dict "lineageControllerWebhook" (dict "nodeSelector" $lineageNodeSelector) -}} +{{- $_ := set $cozystackEngineComponents "lineage-controller-webhook" (dict "values" $lineageValues) -}} +{{- end -}} {{- if .Values.authentication.oidc.enabled }} -{{include "cozystack.platform.package" (list "cozystack.cozystack-engine" "oidc" $) }} +{{include "cozystack.platform.package" (list "cozystack.cozystack-engine" "oidc" $ $cozystackEngineComponents) }} {{include "cozystack.platform.package.default" (list "cozystack.keycloak" $) }} {{include "cozystack.platform.package.default" (list "cozystack.keycloak-operator" $) }} {{- else }} -{{include "cozystack.platform.package" (list "cozystack.cozystack-engine" "default" $) }} +{{include "cozystack.platform.package" (list "cozystack.cozystack-engine" "default" $ $cozystackEngineComponents) }} {{- end }} # Common Packages diff --git a/packages/system/lineage-controller-webhook/templates/daemonset.yaml b/packages/system/lineage-controller-webhook/templates/daemonset.yaml index b6b73ac7..860aee6e 100644 --- a/packages/system/lineage-controller-webhook/templates/daemonset.yaml +++ b/packages/system/lineage-controller-webhook/templates/daemonset.yaml @@ -13,8 +13,10 @@ spec: labels: app: lineage-controller-webhook spec: + {{- with .Values.lineageControllerWebhook.nodeSelector }} nodeSelector: - node-role.kubernetes.io/control-plane: "" + {{- toYaml . | nindent 8 }} + {{- end }} tolerations: - operator: Exists serviceAccountName: lineage-controller-webhook diff --git a/packages/system/lineage-controller-webhook/values.yaml b/packages/system/lineage-controller-webhook/values.yaml index 0c4a1694..8225bd3c 100644 --- a/packages/system/lineage-controller-webhook/values.yaml +++ b/packages/system/lineage-controller-webhook/values.yaml @@ -3,3 +3,8 @@ lineageControllerWebhook: debug: false localK8sAPIEndpoint: enabled: true + # nodeSelector for the DaemonSet + # Talos uses empty value: "node-role.kubernetes.io/control-plane": "" + # Generic k8s (k3s, kubeadm) uses: "node-role.kubernetes.io/control-plane": "true" + nodeSelector: + node-role.kubernetes.io/control-plane: "" From 58476a4d4afe06fe6d3f36b1029c5911ed93c651 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 30 Jan 2026 15:57:24 +0300 Subject: [PATCH 134/889] fix(cozystack-api): make nodeSelector configurable for generic k8s Same issue as lineage-controller-webhook: DaemonSet uses hardcoded nodeSelector with empty value, but k3s/kubeadm use value "true". Changes: - Add nodeSelector to cozystack-api values (default: empty for Talos) - Update deployment template to use configurable nodeSelector - Pass nodeSelector for both cozystack-api and lineage-controller-webhook in isp-full-generic bundle variant Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/core/platform/templates/bundles/system.yaml | 10 +++++++--- .../system/cozystack-api/templates/deployment.yaml | 4 +++- packages/system/cozystack-api/values.yaml | 5 +++++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/packages/core/platform/templates/bundles/system.yaml b/packages/core/platform/templates/bundles/system.yaml index 515cffb7..9a8665b0 100644 --- a/packages/core/platform/templates/bundles/system.yaml +++ b/packages/core/platform/templates/bundles/system.yaml @@ -112,10 +112,14 @@ # Cozystack Engine {{- $cozystackEngineComponents := dict -}} -{{- /* For generic k8s, lineage-controller-webhook needs nodeSelector with value "true" */ -}} +{{- /* For generic k8s, DaemonSets with control-plane nodeSelector need value "true" */ -}} {{- if eq .Values.bundles.system.variant "isp-full-generic" -}} -{{- $lineageNodeSelector := dict "node-role.kubernetes.io/control-plane" "true" -}} -{{- $lineageValues := dict "lineageControllerWebhook" (dict "nodeSelector" $lineageNodeSelector) -}} +{{- $genericNodeSelector := dict "node-role.kubernetes.io/control-plane" "true" -}} +{{- /* cozystack-api DaemonSet */ -}} +{{- $apiValues := dict "cozystackAPI" (dict "nodeSelector" $genericNodeSelector) -}} +{{- $_ := set $cozystackEngineComponents "cozystack-api" (dict "values" $apiValues) -}} +{{- /* lineage-controller-webhook DaemonSet */ -}} +{{- $lineageValues := dict "lineageControllerWebhook" (dict "nodeSelector" $genericNodeSelector) -}} {{- $_ := set $cozystackEngineComponents "lineage-controller-webhook" (dict "values" $lineageValues) -}} {{- end -}} {{- if .Values.authentication.oidc.enabled }} diff --git a/packages/system/cozystack-api/templates/deployment.yaml b/packages/system/cozystack-api/templates/deployment.yaml index ee7e532f..aa877266 100644 --- a/packages/system/cozystack-api/templates/deployment.yaml +++ b/packages/system/cozystack-api/templates/deployment.yaml @@ -25,8 +25,10 @@ spec: - operator: Exists serviceAccountName: cozystack-api {{- if .Values.cozystackAPI.localK8sAPIEndpoint.enabled }} + {{- with .Values.cozystackAPI.nodeSelector }} nodeSelector: - node-role.kubernetes.io/control-plane: "" + {{- toYaml . | nindent 8 }} + {{- end }} {{- end }} containers: - name: cozystack-api diff --git a/packages/system/cozystack-api/values.yaml b/packages/system/cozystack-api/values.yaml index 75f0d6d1..7876981a 100644 --- a/packages/system/cozystack-api/values.yaml +++ b/packages/system/cozystack-api/values.yaml @@ -3,3 +3,8 @@ cozystackAPI: localK8sAPIEndpoint: enabled: true replicas: 2 + # nodeSelector for DaemonSet mode (localK8sAPIEndpoint.enabled: true) + # Talos uses empty value: "node-role.kubernetes.io/control-plane": "" + # Generic k8s (k3s, kubeadm) uses: "node-role.kubernetes.io/control-plane": "true" + nodeSelector: + node-role.kubernetes.io/control-plane: "" From 4f297eb2627ab40427a59e6299635d678c364805 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 30 Jan 2026 16:04:24 +0300 Subject: [PATCH 135/889] fix(platform): read root-host from ConfigMap in apps.yaml The cozystack-values secret was using the default publishing.host value from values.yaml (example.org) instead of reading from the cozystack ConfigMap where the actual root-host is configured. Add ConfigMap lookup to apps.yaml to read root-host from cozystack ConfigMap, falling back to chart values if not present. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/core/platform/templates/apps.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/core/platform/templates/apps.yaml b/packages/core/platform/templates/apps.yaml index 8a716066..ee3b35cf 100644 --- a/packages/core/platform/templates/apps.yaml +++ b/packages/core/platform/templates/apps.yaml @@ -1,6 +1,12 @@ {{- $kubeRootCa := lookup "v1" "ConfigMap" "kube-system" "kube-root-ca.crt" }} +{{- $cozystackCm := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }} {{- $bundleName := .Values.bundles.system.variant }} {{- $bundle := tpl (.Files.Get (printf "bundles/%s.yaml" $bundleName)) . | fromYaml }} +{{- /* Read root-host from ConfigMap if available, else use chart values */ -}} +{{- $rootHost := .Values.publishing.host -}} +{{- if and $cozystackCm $cozystackCm.data -}} +{{- $rootHost = default $rootHost (index $cozystackCm.data "root-host") -}} +{{- end -}} --- apiVersion: v1 kind: Secret @@ -13,7 +19,7 @@ type: Opaque stringData: values.yaml: | _cluster: - root-host: {{ .Values.publishing.host | quote }} + root-host: {{ $rootHost | quote }} bundle-name: {{ .Values.bundles.system.variant | quote }} clusterissuer: {{ .Values.publishing.certificates.issuerType | quote }} oidc-enabled: {{ .Values.authentication.oidc.enabled | quote }} From f32f40f645d08cc3fd17b2ccf9d3be9cc46196ee Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 30 Jan 2026 18:16:52 +0300 Subject: [PATCH 136/889] feat(platform): enable iaas, paas, naas bundles for isp-full-generic variant - Allow iaas, paas, naas bundles when using isp-full-generic variant - Enable all bundles by default in values-isp-full-generic.yaml - Update bundle templates to accept isp-full-generic alongside isp-full Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/core/platform/templates/bundles/iaas.yaml | 6 +++--- packages/core/platform/templates/bundles/naas.yaml | 6 +++--- packages/core/platform/templates/bundles/paas.yaml | 6 +++--- packages/core/platform/values-isp-full-generic.yaml | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/core/platform/templates/bundles/iaas.yaml b/packages/core/platform/templates/bundles/iaas.yaml index 529ec67a..7c856b8c 100644 --- a/packages/core/platform/templates/bundles/iaas.yaml +++ b/packages/core/platform/templates/bundles/iaas.yaml @@ -1,7 +1,7 @@ -{{- if and .Values.bundles.iaas.enabled (ne .Values.bundles.system.variant "isp-full") }} -{{- fail "bundles.iaas.enabled can only be true when bundles.system.variant is 'isp-full'" }} +{{- if and .Values.bundles.iaas.enabled (not (or (eq .Values.bundles.system.variant "isp-full") (eq .Values.bundles.system.variant "isp-full-generic"))) }} +{{- fail "bundles.iaas.enabled can only be true when bundles.system.variant is 'isp-full' or 'isp-full-generic'" }} {{- end }} -{{- if and .Values.bundles.iaas.enabled (eq .Values.bundles.system.variant "isp-full") }} +{{- if and .Values.bundles.iaas.enabled (or (eq .Values.bundles.system.variant "isp-full") (eq .Values.bundles.system.variant "isp-full-generic")) }} {{include "cozystack.platform.package.default" (list "cozystack.kubevirt" $) }} {{include "cozystack.platform.package.default" (list "cozystack.kubevirt-cdi" $) }} {{include "cozystack.platform.package.optional.default" (list "cozystack.gpu-operator" $) }} diff --git a/packages/core/platform/templates/bundles/naas.yaml b/packages/core/platform/templates/bundles/naas.yaml index 7c40a4aa..9682d447 100644 --- a/packages/core/platform/templates/bundles/naas.yaml +++ b/packages/core/platform/templates/bundles/naas.yaml @@ -1,7 +1,7 @@ -{{- if and .Values.bundles.naas.enabled (not (or (eq .Values.bundles.system.variant "isp-full") (eq .Values.bundles.system.variant "isp-hosted"))) }} -{{- fail "bundles.naas.enabled can only be true when bundles.system.variant is 'isp-full' or 'isp-hosted'" }} +{{- if and .Values.bundles.naas.enabled (not (or (eq .Values.bundles.system.variant "isp-full") (eq .Values.bundles.system.variant "isp-full-generic") (eq .Values.bundles.system.variant "isp-hosted"))) }} +{{- fail "bundles.naas.enabled can only be true when bundles.system.variant is 'isp-full', 'isp-full-generic', or 'isp-hosted'" }} {{- end }} -{{- if and .Values.bundles.naas.enabled (or (eq .Values.bundles.system.variant "isp-full") (eq .Values.bundles.system.variant "isp-hosted")) }} +{{- if and .Values.bundles.naas.enabled (or (eq .Values.bundles.system.variant "isp-full") (eq .Values.bundles.system.variant "isp-full-generic") (eq .Values.bundles.system.variant "isp-hosted")) }} {{include "cozystack.platform.package.default" (list "cozystack.http-cache-application" $) }} {{include "cozystack.platform.package.default" (list "cozystack.tcp-balancer-application" $) }} {{include "cozystack.platform.package.default" (list "cozystack.vpn-application" $) }} diff --git a/packages/core/platform/templates/bundles/paas.yaml b/packages/core/platform/templates/bundles/paas.yaml index 890ca514..359f0959 100644 --- a/packages/core/platform/templates/bundles/paas.yaml +++ b/packages/core/platform/templates/bundles/paas.yaml @@ -1,7 +1,7 @@ -{{- if and .Values.bundles.paas.enabled (not (or (eq .Values.bundles.system.variant "isp-full") (eq .Values.bundles.system.variant "isp-hosted"))) }} -{{- fail "bundles.paas.enabled can only be true when bundles.system.variant is 'isp-full' or 'isp-hosted'" }} +{{- if and .Values.bundles.paas.enabled (not (or (eq .Values.bundles.system.variant "isp-full") (eq .Values.bundles.system.variant "isp-full-generic") (eq .Values.bundles.system.variant "isp-hosted"))) }} +{{- fail "bundles.paas.enabled can only be true when bundles.system.variant is 'isp-full', 'isp-full-generic', or 'isp-hosted'" }} {{- end }} -{{- if and .Values.bundles.paas.enabled (or (eq .Values.bundles.system.variant "isp-full") (eq .Values.bundles.system.variant "isp-hosted")) }} +{{- if and .Values.bundles.paas.enabled (or (eq .Values.bundles.system.variant "isp-full") (eq .Values.bundles.system.variant "isp-full-generic") (eq .Values.bundles.system.variant "isp-hosted")) }} {{include "cozystack.platform.package.default" (list "cozystack.mariadb-operator" $) }} {{include "cozystack.platform.package.default" (list "cozystack.kafka-operator" $) }} {{include "cozystack.platform.package.default" (list "cozystack.clickhouse-operator" $) }} diff --git a/packages/core/platform/values-isp-full-generic.yaml b/packages/core/platform/values-isp-full-generic.yaml index 17ca19f3..d5515bab 100644 --- a/packages/core/platform/values-isp-full-generic.yaml +++ b/packages/core/platform/values-isp-full-generic.yaml @@ -5,8 +5,8 @@ bundles: enabled: true variant: "isp-full-generic" iaas: - enabled: false + enabled: true paas: - enabled: false + enabled: true naas: - enabled: false + enabled: true From 7927033864670af7afe8dfb37867fe34abaf8d18 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 30 Jan 2026 18:24:25 +0300 Subject: [PATCH 137/889] feat(platform): enable velero in isp-full-generic variant Add velero to enabledPackages list for isp-full-generic to enable backup functionality by default. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/core/platform/values-isp-full-generic.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/core/platform/values-isp-full-generic.yaml b/packages/core/platform/values-isp-full-generic.yaml index d5515bab..7f96507c 100644 --- a/packages/core/platform/values-isp-full-generic.yaml +++ b/packages/core/platform/values-isp-full-generic.yaml @@ -10,3 +10,5 @@ bundles: enabled: true naas: enabled: true + enabledPackages: + - velero From cb90df4969e5caefec8110a99997cbbaf4129271 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 30 Jan 2026 18:49:35 +0300 Subject: [PATCH 138/889] fix(platform): use correct package names in enabledPackages The enabledPackages list must use fully qualified package names (e.g., cozystack.velero) to match the helper template check. Also add backupstrategy-controller which is required by backup-controller. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/core/platform/values-isp-full-generic.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/platform/values-isp-full-generic.yaml b/packages/core/platform/values-isp-full-generic.yaml index 7f96507c..4fd5ff59 100644 --- a/packages/core/platform/values-isp-full-generic.yaml +++ b/packages/core/platform/values-isp-full-generic.yaml @@ -11,4 +11,5 @@ bundles: naas: enabled: true enabledPackages: - - velero + - cozystack.velero + - cozystack.backupstrategy-controller From 39fbb374aaae79ccf9b9bfc0ca70419fdda3e1b9 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 30 Jan 2026 18:54:00 +0300 Subject: [PATCH 139/889] fix(velero): replace bitnami kubectl image with rancher/kubectl Bitnami images are forbidden and bitnamilegacy/kubectl:1.35 doesn't exist. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/system/velero/charts/velero/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/system/velero/charts/velero/values.yaml b/packages/system/velero/charts/velero/values.yaml index 30eb7b5f..0630cc6f 100644 --- a/packages/system/velero/charts/velero/values.yaml +++ b/packages/system/velero/charts/velero/values.yaml @@ -338,7 +338,7 @@ metrics: kubectl: image: - repository: docker.io/bitnamilegacy/kubectl + repository: rancher/kubectl # Digest value example: sha256:d238835e151cec91c6a811fe3a89a66d3231d9f64d09e5f3c49552672d271f38. # If used, it will take precedence over the kubectl.image.tag. # digest: From 052935a0422f337aad88dd3fd6dc99f4c9723e77 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 30 Jan 2026 19:14:11 +0300 Subject: [PATCH 140/889] fix(velero): set explicit kubectl tag v1.35.0 rancher/kubectl uses 'v' prefix for tags (v1.35.0 vs 1.35). Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/system/velero/charts/velero/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/system/velero/charts/velero/values.yaml b/packages/system/velero/charts/velero/values.yaml index 0630cc6f..9c14fd3c 100644 --- a/packages/system/velero/charts/velero/values.yaml +++ b/packages/system/velero/charts/velero/values.yaml @@ -343,7 +343,7 @@ kubectl: # If used, it will take precedence over the kubectl.image.tag. # digest: # kubectl image tag. If used, it will take precedence over the cluster Kubernetes version. - # tag: 1.16.15 + tag: v1.35.0 # Container Level Security Context for the 'kubectl' container of the crd jobs. Optional. # See: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container containerSecurityContext: {} From 5e7087a1600cad28ff1e1f51732b61b9711fa93d Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 30 Jan 2026 19:18:10 +0300 Subject: [PATCH 141/889] fix(velero): use alpine/k8s for kubectl with shell rancher/kubectl is a minimal image without shell. alpine/k8s includes kubectl and shell for running upgrade jobs. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/system/velero/charts/velero/values.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/system/velero/charts/velero/values.yaml b/packages/system/velero/charts/velero/values.yaml index 9c14fd3c..00ec9380 100644 --- a/packages/system/velero/charts/velero/values.yaml +++ b/packages/system/velero/charts/velero/values.yaml @@ -338,12 +338,12 @@ metrics: kubectl: image: - repository: rancher/kubectl + repository: alpine/k8s # Digest value example: sha256:d238835e151cec91c6a811fe3a89a66d3231d9f64d09e5f3c49552672d271f38. # If used, it will take precedence over the kubectl.image.tag. # digest: # kubectl image tag. If used, it will take precedence over the cluster Kubernetes version. - tag: v1.35.0 + tag: "1.35.0" # Container Level Security Context for the 'kubectl' container of the crd jobs. Optional. # See: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container containerSecurityContext: {} From ce24ddf7a5a9cf3e96a75f6555f4068366b09de4 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 30 Jan 2026 19:21:59 +0300 Subject: [PATCH 142/889] fix(velero): disable upgradeCRDs job The CRD upgrade pre-install job has image compatibility issues. CRDs are installed as part of the Helm chart install anyway. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/system/velero/values.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/system/velero/values.yaml b/packages/system/velero/values.yaml index 62f13c1f..4d503543 100644 --- a/packages/system/velero/values.yaml +++ b/packages/system/velero/values.yaml @@ -1,4 +1,7 @@ velero: + # Disable CRD upgrade job - CRDs are installed as part of helm chart + # The upgrade job has issues with kubectl image compatibility + upgradeCRDs: false initContainers: - name: velero-plugin-for-aws image: velero/velero-plugin-for-aws:v1.12.1 From 11eb255640dbadfee9a6591739f90afda418b908 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 30 Jan 2026 19:26:17 +0300 Subject: [PATCH 143/889] fix(backup-controller): add required template spec to Velero strategy The Velero CRD requires spec.template.spec field. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/system/backup-controller/templates/strategy.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/system/backup-controller/templates/strategy.yaml b/packages/system/backup-controller/templates/strategy.yaml index 68109332..a8396d66 100644 --- a/packages/system/backup-controller/templates/strategy.yaml +++ b/packages/system/backup-controller/templates/strategy.yaml @@ -2,4 +2,9 @@ apiVersion: strategy.backups.cozystack.io/v1alpha1 kind: Velero metadata: name: velero-strategy-default -spec: {} +spec: + template: + spec: + ttl: 720h + includedNamespaces: + - "*" From 395a57bc1b084d464a49bc6831c9d8102058d91b Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 30 Jan 2026 21:18:03 +0300 Subject: [PATCH 144/889] refactor(platform): simplify networking values, hardcode CNI-specific config - Remove apiServer and cilium.cgroup from user-facing values.yaml - Hardcode Talos-specific values (localhost:7445, cgroup autoMount: false) directly in isp-full bundle template - isp-full-generic already hardcodes its own values - Improve MASTER_NODES comment explaining helm lookup behavior This hides implementation details from users as requested in review. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- .../core/platform/templates/bundles/system.yaml | 7 ++++--- packages/core/platform/values.yaml | 17 +++++------------ 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/packages/core/platform/templates/bundles/system.yaml b/packages/core/platform/templates/bundles/system.yaml index 9a8665b0..d272962b 100644 --- a/packages/core/platform/templates/bundles/system.yaml +++ b/packages/core/platform/templates/bundles/system.yaml @@ -15,10 +15,11 @@ {{- end -}} {{- $kubeovnValues := dict "kube-ovn" $kubeovnDict -}} {{- $_ := set $networkingComponents "kubeovn" (dict "values" $kubeovnValues) -}} +{{- /* For Talos (isp-full): use KubePrism endpoint and disable cgroup autoMount */ -}} {{- $ciliumValues := dict "cilium" (dict - "k8sServiceHost" .Values.networking.apiServer.host - "k8sServicePort" .Values.networking.apiServer.port - "cgroup" (dict "autoMount" (dict "enabled" .Values.networking.cilium.cgroup.autoMount))) -}} + "k8sServiceHost" "localhost" + "k8sServicePort" "7445" + "cgroup" (dict "autoMount" (dict "enabled" false))) -}} {{- $_ := set $networkingComponents "cilium" (dict "values" $ciliumValues) -}} {{- end -}} {{include "cozystack.platform.package" (list "cozystack.networking" "kubeovn-cilium" $ $networkingComponents) }} diff --git a/packages/core/platform/values.yaml b/packages/core/platform/values.yaml index 34a251e6..f7fec687 100644 --- a/packages/core/platform/values.yaml +++ b/packages/core/platform/values.yaml @@ -27,18 +27,11 @@ networking: podGateway: "10.244.0.1" serviceCIDR: "10.96.0.0/16" joinCIDR: "100.64.0.0/16" - # Kubernetes API server configuration for Cilium - # Defaults are for Talos KubePrism; override for non-Talos clusters - apiServer: - host: "localhost" - port: "7445" - # Cilium cgroup configuration - # Set autoMount to true for non-Talos clusters - cilium: - cgroup: - autoMount: false - # KubeOVN configuration for non-Talos clusters - # MASTER_NODES: comma-separated list of master node IPs (empty = use helm lookup) + # KubeOVN master nodes override (optional) + # By default, KubeOVN helm chart uses `lookup` to find control-plane nodes + # by label `node-role.kubernetes.io/control-plane`. On fresh clusters or + # during initial deployment, lookup may return empty results. + # Set this to comma-separated list of master node IPs to override. kubeovn: MASTER_NODES: "" # Service publishing and ingress configuration From 77ce8227b4ee4a79b10773af5e50e61a11e6df5c Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 30 Jan 2026 21:20:22 +0300 Subject: [PATCH 145/889] feat(installer): split operator manifest into talos/generic/hosted variants Per review feedback, create separate manifest files for different deployment targets instead of using templated values: - cozystack-operator.yaml (Talos): hardcoded localhost:7445 (KubePrism) - cozystack-operator-generic.yaml: reads from cozystack-operator-config ConfigMap (user must create before applying) - cozystack-operator-hosted.yaml: no env override (uses in-cluster SA) This keeps installation flow clean - users apply the manifest matching their deployment target without needing to modify original files. Build system updated to generate all three variants. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- Makefile | 11 +++ hack/upload-assets.sh | 2 + .../templates/cozystack-operator-generic.yaml | 90 +++++++++++++++++++ .../templates/cozystack-operator-hosted.yaml | 77 ++++++++++++++++ .../templates/cozystack-operator.yaml | 5 +- 5 files changed, 183 insertions(+), 2 deletions(-) create mode 100644 packages/core/installer/templates/cozystack-operator-generic.yaml create mode 100644 packages/core/installer/templates/cozystack-operator-hosted.yaml diff --git a/Makefile b/Makefile index 0cf71d0b..c66f8c03 100644 --- a/Makefile +++ b/Makefile @@ -41,10 +41,21 @@ manifests: helm template installer packages/core/installer -n cozy-system \ -s templates/crds.yaml \ > _out/assets/cozystack-crds.yaml + # Talos variant (default) helm template installer packages/core/installer -n cozy-system \ -s templates/cozystack-operator.yaml \ -s templates/packagesource.yaml \ > _out/assets/cozystack-operator.yaml + # Generic Kubernetes variant (k3s, kubeadm, RKE2) + helm template installer packages/core/installer -n cozy-system \ + -s templates/cozystack-operator-generic.yaml \ + -s templates/packagesource.yaml \ + > _out/assets/cozystack-operator-generic.yaml + # Hosted variant (managed Kubernetes) + helm template installer packages/core/installer -n cozy-system \ + -s templates/cozystack-operator-hosted.yaml \ + -s templates/packagesource.yaml \ + > _out/assets/cozystack-operator-hosted.yaml cozypkg: go build -ldflags "-X github.com/cozystack/cozystack/cmd/cozypkg/cmd.Version=v$(COZYSTACK_VERSION)" -o _out/bin/cozypkg ./cmd/cozypkg diff --git a/hack/upload-assets.sh b/hack/upload-assets.sh index 0f1e9aeb..e846261c 100755 --- a/hack/upload-assets.sh +++ b/hack/upload-assets.sh @@ -5,6 +5,8 @@ version=${VERSION:-$(git describe --tags)} gh release upload --clobber $version _out/assets/cozystack-crds.yaml gh release upload --clobber $version _out/assets/cozystack-operator.yaml +gh release upload --clobber $version _out/assets/cozystack-operator-generic.yaml +gh release upload --clobber $version _out/assets/cozystack-operator-hosted.yaml gh release upload --clobber $version _out/assets/metal-amd64.iso gh release upload --clobber $version _out/assets/metal-amd64.raw.xz gh release upload --clobber $version _out/assets/nocloud-amd64.raw.xz diff --git a/packages/core/installer/templates/cozystack-operator-generic.yaml b/packages/core/installer/templates/cozystack-operator-generic.yaml new file mode 100644 index 00000000..510a92c8 --- /dev/null +++ b/packages/core/installer/templates/cozystack-operator-generic.yaml @@ -0,0 +1,90 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: cozy-system + labels: + cozystack.io/system: "true" + pod-security.kubernetes.io/enforce: privileged +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: cozystack + namespace: cozy-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cozystack +subjects: +- kind: ServiceAccount + name: cozystack + namespace: cozy-system +roleRef: + kind: ClusterRole + name: cluster-admin + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cozystack-operator + namespace: cozy-system +spec: + replicas: 1 + selector: + matchLabels: + app: cozystack-operator + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 0 + maxUnavailable: 1 + template: + metadata: + labels: + app: cozystack-operator + spec: + serviceAccountName: cozystack + containers: + - name: cozystack-operator + image: "{{ .Values.cozystackOperator.image }}" + args: + - --leader-elect=true + - --install-flux=true + - --metrics-bind-address=0 + - --health-probe-bind-address= + {{- if .Values.cozystackOperator.disableTelemetry }} + - --disable-telemetry + {{- end }} + - --platform-source-name=cozystack-platform + - --platform-source-url={{ .Values.cozystackOperator.platformSourceUrl }} + {{- if .Values.cozystackOperator.platformSourceRef }} + - --platform-source-ref={{ .Values.cozystackOperator.platformSourceRef }} + {{- end }} + env: + # Generic Kubernetes: read from ConfigMap + # Create cozystack-operator-config ConfigMap before applying this manifest + - name: KUBERNETES_SERVICE_HOST + valueFrom: + configMapKeyRef: + name: cozystack-operator-config + key: KUBERNETES_SERVICE_HOST + optional: false + - name: KUBERNETES_SERVICE_PORT + valueFrom: + configMapKeyRef: + name: cozystack-operator-config + key: KUBERNETES_SERVICE_PORT + optional: false + hostNetwork: true + tolerations: + - key: "node.kubernetes.io/not-ready" + operator: "Exists" + - key: "node.kubernetes.io/unreachable" + operator: "Exists" + - key: "node.cilium.io/agent-not-ready" + operator: "Exists" + - key: "node.cloudprovider.kubernetes.io/uninitialized" + operator: "Exists" diff --git a/packages/core/installer/templates/cozystack-operator-hosted.yaml b/packages/core/installer/templates/cozystack-operator-hosted.yaml new file mode 100644 index 00000000..38ccdf75 --- /dev/null +++ b/packages/core/installer/templates/cozystack-operator-hosted.yaml @@ -0,0 +1,77 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: cozy-system + labels: + cozystack.io/system: "true" + pod-security.kubernetes.io/enforce: privileged +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: cozystack + namespace: cozy-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cozystack +subjects: +- kind: ServiceAccount + name: cozystack + namespace: cozy-system +roleRef: + kind: ClusterRole + name: cluster-admin + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cozystack-operator + namespace: cozy-system +spec: + replicas: 1 + selector: + matchLabels: + app: cozystack-operator + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 0 + maxUnavailable: 1 + template: + metadata: + labels: + app: cozystack-operator + spec: + serviceAccountName: cozystack + containers: + - name: cozystack-operator + image: "{{ .Values.cozystackOperator.image }}" + args: + - --leader-elect=true + - --install-flux=true + - --metrics-bind-address=0 + - --health-probe-bind-address= + {{- if .Values.cozystackOperator.disableTelemetry }} + - --disable-telemetry + {{- end }} + - --platform-source-name=cozystack-platform + - --platform-source-url={{ .Values.cozystackOperator.platformSourceUrl }} + {{- if .Values.cozystackOperator.platformSourceRef }} + - --platform-source-ref={{ .Values.cozystackOperator.platformSourceRef }} + {{- end }} + # Hosted: use in-cluster service account, no env override needed + env: [] + hostNetwork: true + tolerations: + - key: "node.kubernetes.io/not-ready" + operator: "Exists" + - key: "node.kubernetes.io/unreachable" + operator: "Exists" + - key: "node.cilium.io/agent-not-ready" + operator: "Exists" + - key: "node.cloudprovider.kubernetes.io/uninitialized" + operator: "Exists" diff --git a/packages/core/installer/templates/cozystack-operator.yaml b/packages/core/installer/templates/cozystack-operator.yaml index 31028757..c40dc663 100644 --- a/packages/core/installer/templates/cozystack-operator.yaml +++ b/packages/core/installer/templates/cozystack-operator.yaml @@ -64,10 +64,11 @@ spec: - --platform-source-ref={{ .Values.cozystackOperator.platformSourceRef }} {{- end }} env: + # Talos KubePrism endpoint - name: KUBERNETES_SERVICE_HOST - value: {{ .Values.cozystackOperator.kubernetesServiceHost | quote }} + value: "localhost" - name: KUBERNETES_SERVICE_PORT - value: {{ .Values.cozystackOperator.kubernetesServicePort | quote }} + value: "7445" hostNetwork: true tolerations: - key: "node.kubernetes.io/not-ready" From 116d9aeb50efeb87724520f68646a3a266e92a09 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 30 Jan 2026 21:23:59 +0300 Subject: [PATCH 146/889] refactor(platform): deduplicate common packages in system bundle Extract common system packages shared between isp-full and isp-full-generic into a helper template `cozystack.platform.system.common-packages`. Packages moved to helper: - kubeovn-webhook, kubeovn-plunger, cozy-proxy - multus, metallb, reloader - linstor-scheduler, snapshot-controller Packages NOT in helper (differ between variants): - networking (variant differs: kubeovn-cilium vs kubeovn-cilium-generic) - linstor (talos.enabled differs) Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/core/platform/templates/_helpers.tpl | 16 ++++++++++++++++ .../platform/templates/bundles/system.yaml | 18 ++---------------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/packages/core/platform/templates/_helpers.tpl b/packages/core/platform/templates/_helpers.tpl index 2c454177..684ee812 100644 --- a/packages/core/platform/templates/_helpers.tpl +++ b/packages/core/platform/templates/_helpers.tpl @@ -50,3 +50,19 @@ spec: {{- $root := index . 1 -}} {{- include "cozystack.platform.package.optional" (list $name "default" $root) }} {{ end }} + +{{/* +Common system packages shared between isp-full and isp-full-generic bundles. +Does NOT include: networking (variant differs), linstor (talos.enabled differs) +*/}} +{{- define "cozystack.platform.system.common-packages" -}} +{{- $root := . -}} +{{include "cozystack.platform.package.default" (list "cozystack.kubeovn-webhook" $root) }} +{{include "cozystack.platform.package.default" (list "cozystack.kubeovn-plunger" $root) }} +{{include "cozystack.platform.package.default" (list "cozystack.cozy-proxy" $root) }} +{{include "cozystack.platform.package.default" (list "cozystack.multus" $root) }} +{{include "cozystack.platform.package.default" (list "cozystack.metallb" $root) }} +{{include "cozystack.platform.package.default" (list "cozystack.reloader" $root) }} +{{include "cozystack.platform.package.default" (list "cozystack.linstor-scheduler" $root) }} +{{include "cozystack.platform.package.default" (list "cozystack.snapshot-controller" $root) }} +{{- end }} diff --git a/packages/core/platform/templates/bundles/system.yaml b/packages/core/platform/templates/bundles/system.yaml index d272962b..62f96521 100644 --- a/packages/core/platform/templates/bundles/system.yaml +++ b/packages/core/platform/templates/bundles/system.yaml @@ -23,15 +23,8 @@ {{- $_ := set $networkingComponents "cilium" (dict "values" $ciliumValues) -}} {{- end -}} {{include "cozystack.platform.package" (list "cozystack.networking" "kubeovn-cilium" $ $networkingComponents) }} -{{include "cozystack.platform.package.default" (list "cozystack.kubeovn-webhook" $) }} -{{include "cozystack.platform.package.default" (list "cozystack.kubeovn-plunger" $) }} -{{include "cozystack.platform.package.default" (list "cozystack.cozy-proxy" $) }} -{{include "cozystack.platform.package.default" (list "cozystack.multus" $) }} -{{include "cozystack.platform.package.default" (list "cozystack.metallb" $) }} -{{include "cozystack.platform.package.default" (list "cozystack.reloader" $) }} +{{include "cozystack.platform.system.common-packages" $ }} {{include "cozystack.platform.package.default" (list "cozystack.linstor" $) }} -{{include "cozystack.platform.package.default" (list "cozystack.linstor-scheduler" $) }} -{{include "cozystack.platform.package.default" (list "cozystack.snapshot-controller" $) }} {{- end }} {{- if eq .Values.bundles.system.variant "isp-hosted" }} @@ -98,17 +91,10 @@ {{- end -}} {{- /* Use kubeovn-cilium-generic variant (no values-talos.yaml) */ -}} {{include "cozystack.platform.package" (list "cozystack.networking" "kubeovn-cilium-generic" $ $networkingComponents) }} -{{include "cozystack.platform.package.default" (list "cozystack.kubeovn-webhook" $) }} -{{include "cozystack.platform.package.default" (list "cozystack.kubeovn-plunger" $) }} -{{include "cozystack.platform.package.default" (list "cozystack.cozy-proxy" $) }} -{{include "cozystack.platform.package.default" (list "cozystack.multus" $) }} -{{include "cozystack.platform.package.default" (list "cozystack.metallb" $) }} -{{include "cozystack.platform.package.default" (list "cozystack.reloader" $) }} +{{include "cozystack.platform.system.common-packages" $ }} {{- /* Pass talos.enabled: false to linstor for generic Linux */ -}} {{- $linstorComponents := dict "linstor" (dict "values" (dict "talos" (dict "enabled" false))) -}} {{include "cozystack.platform.package" (list "cozystack.linstor" "default" $ $linstorComponents) }} -{{include "cozystack.platform.package.default" (list "cozystack.linstor-scheduler" $) }} -{{include "cozystack.platform.package.default" (list "cozystack.snapshot-controller" $) }} {{- end }} # Cozystack Engine From 191661768608a643439bbce430efc046c83b306c Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 30 Jan 2026 21:29:21 +0300 Subject: [PATCH 147/889] fix(platform): remove apiServer fallback from values Removed reference to .Values.networking.apiServer which was removed from values.yaml. Use empty defaults and let ConfigMap or apiServerEndpoint parsing override them. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/core/platform/templates/bundles/system.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/platform/templates/bundles/system.yaml b/packages/core/platform/templates/bundles/system.yaml index 62f96521..1ee7002e 100644 --- a/packages/core/platform/templates/bundles/system.yaml +++ b/packages/core/platform/templates/bundles/system.yaml @@ -41,8 +41,8 @@ {{- if and $cozystackCm $cozystackCm.data -}} {{- $apiServerEndpoint = default $apiServerEndpoint (index $cozystackCm.data "api-server-endpoint") -}} {{- end -}} -{{- $apiHost := .Values.networking.apiServer.host -}} -{{- $apiPort := .Values.networking.apiServer.port -}} +{{- $apiHost := "" -}} +{{- $apiPort := "6443" -}} {{- if $apiServerEndpoint -}} {{- $parsed := urlParse $apiServerEndpoint -}} {{- $hostPort := splitList ":" $parsed.host -}} From 281715b36575e0fe889cff9a9362ccdab7646435 Mon Sep 17 00:00:00 2001 From: IvanHunters Date: Sun, 1 Feb 2026 22:48:28 +0300 Subject: [PATCH 148/889] fix manifests for kubernetes deployment Signed-off-by: IvanHunters --- packages/apps/kubernetes/templates/cluster.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/apps/kubernetes/templates/cluster.yaml b/packages/apps/kubernetes/templates/cluster.yaml index 6acfb107..3d9c854a 100644 --- a/packages/apps/kubernetes/templates/cluster.yaml +++ b/packages/apps/kubernetes/templates/cluster.yaml @@ -292,6 +292,12 @@ metadata: {{- end }} spec: clusterName: {{ $.Release.Name }} + replicas: 2 + strategy: + rollingUpdate: + maxSurge: {{ $group.maxReplicas }} + maxUnavailable: 1 + type: RollingUpdate selector: matchLabels: cluster.x-k8s.io/cluster-name: {{ $.Release.Name }} @@ -326,6 +332,7 @@ metadata: namespace: {{ $.Release.Namespace }} spec: clusterName: {{ $.Release.Name }} + maxUnhealthy: 0 nodeStartupTimeout: 10m selector: matchLabels: From 8a034c58b189f9e18276d16feecaef7e0e6785c1 Mon Sep 17 00:00:00 2001 From: nbykov0 <166552198+nbykov0@users.noreply.github.com> Date: Mon, 2 Feb 2026 13:08:16 +0300 Subject: [PATCH 149/889] [branding] Separate values for keycloak Signed-off-by: nbykov0 <166552198+nbykov0@users.noreply.github.com> --- .../templates/configure-kk.yaml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/system/keycloak-configure/templates/configure-kk.yaml b/packages/system/keycloak-configure/templates/configure-kk.yaml index cc15b161..29afc6c0 100644 --- a/packages/system/keycloak-configure/templates/configure-kk.yaml +++ b/packages/system/keycloak-configure/templates/configure-kk.yaml @@ -5,12 +5,6 @@ {{- $existingKubeappsSecret := lookup "v1" "Secret" .Release.Namespace "kubeapps-client" }} {{- $existingAuthConfig := lookup "v1" "Secret" "cozy-dashboard" "kubeapps-auth-config" }} {{- $brandingConfig := .Values._cluster.branding | default dict }} - -{{ $branding := "" }} -{{- if $brandingConfig }} - {{- $branding = $brandingConfig.branding }} -{{- end }} - --- apiVersion: v1.edp.epam.com/v1alpha1 @@ -32,9 +26,15 @@ metadata: spec: realmName: cozy clusterKeycloakRef: keycloak-cozy - {{- if $branding }} - displayHtmlName: {{ $branding }} - displayName: {{ $branding }} + {{- if $brandingConfig }} + {{- if hasKey $brandingConfig "brandName" }} + displayName: {{ $brandingConfig.brandName }} + {{- end }} + {{- if hasKey $brandingConfig "brandHtmlName" }} + displayHtmlName: {{ $brandingConfig.brandHtmlName }} + {{- else if hasKey $brandingConfig "branding" }} + displayHtmlName: {{ $brandingConfig.branding }} + {{- end }} {{- end }} --- From c21d1e40895d1babc8e4b5fdf31cfcf2661ae8fd Mon Sep 17 00:00:00 2001 From: Kirill Ilin Date: Mon, 2 Feb 2026 15:26:15 +0500 Subject: [PATCH 150/889] [kubernetes] use NodePort service instead of hostNetwork for ingress-nginx Signed-off-by: Kirill Ilin --- .../kubernetes/templates/helmreleases/ingress-nginx.yaml | 7 +++++-- packages/apps/kubernetes/templates/ingress.yaml | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/apps/kubernetes/templates/helmreleases/ingress-nginx.yaml b/packages/apps/kubernetes/templates/helmreleases/ingress-nginx.yaml index 6f7b0759..5cafff90 100644 --- a/packages/apps/kubernetes/templates/helmreleases/ingress-nginx.yaml +++ b/packages/apps/kubernetes/templates/helmreleases/ingress-nginx.yaml @@ -4,9 +4,12 @@ ingress-nginx: controller: kind: DaemonSet {{- if eq .Values.addons.ingressNginx.exposeMethod "Proxied" }} - hostNetwork: true service: - enabled: false + enabled: true + type: NodePort + nodePorts: + http: 30000 + https: 30001 {{- end }} {{- if not .Values.addons.certManager.enabled }} admissionWebhooks: diff --git a/packages/apps/kubernetes/templates/ingress.yaml b/packages/apps/kubernetes/templates/ingress.yaml index 2e7f9933..4adb7458 100644 --- a/packages/apps/kubernetes/templates/ingress.yaml +++ b/packages/apps/kubernetes/templates/ingress.yaml @@ -56,11 +56,11 @@ spec: - appProtocol: http name: http port: 80 - targetPort: 80 + targetPort: 30000 - appProtocol: https name: https port: 443 - targetPort: 443 + targetPort: 30001 selector: cluster.x-k8s.io/cluster-name: {{ .Release.Name }} node-role.kubernetes.io/ingress-nginx: "" From 1e8da1fca4f5faeb103ccdd53a8944dba2af1a57 Mon Sep 17 00:00:00 2001 From: Matthieu ROBIN Date: Mon, 2 Feb 2026 13:23:10 +0100 Subject: [PATCH 151/889] Add instance profile label to workload monitor Signed-off-by: Matthieu ROBIN --- internal/controller/workloadmonitor_controller.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/controller/workloadmonitor_controller.go b/internal/controller/workloadmonitor_controller.go index d4d86b97..677fda82 100644 --- a/internal/controller/workloadmonitor_controller.go +++ b/internal/controller/workloadmonitor_controller.go @@ -466,6 +466,8 @@ func (r *WorkloadMonitorReconciler) getWorkloadMetadata(obj client.Object) map[s annotations := obj.GetAnnotations() if instanceType, ok := annotations["kubevirt.io/cluster-instancetype-name"]; ok { labels["workloads.cozystack.io/kubevirt-vmi-instance-type"] = instanceType + if instanceProfile, ok := annotations["kubevirt.io/cluster-instanceprofile-name"]; ok { + labels["workloads.cozystack.io/kubevirt-vmi-instance-profile"] = instanceProfile } return labels } From 8f3d686492b5b8988f77de70eb67b672c3b52894 Mon Sep 17 00:00:00 2001 From: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com> Date: Mon, 2 Feb 2026 12:34:58 +0000 Subject: [PATCH 152/889] Prepare release v1.0.0-beta.2 Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com> --- packages/apps/kubernetes/images/kubevirt-csi-driver.tag | 2 +- packages/apps/mysql/images/mariadb-backup.tag | 2 +- packages/core/installer/values.yaml | 4 ++-- packages/core/testing/values.yaml | 2 +- packages/extra/bootbox/images/matchbox.tag | 2 +- packages/extra/seaweedfs/images/objectstorage-sidecar.tag | 2 +- packages/system/backup-controller/values.yaml | 2 +- packages/system/backupstrategy-controller/values.yaml | 2 +- packages/system/bucket/images/s3manager.tag | 2 +- packages/system/cozystack-api/values.yaml | 2 +- packages/system/cozystack-controller/values.yaml | 2 +- packages/system/dashboard/templates/configmap.yaml | 2 +- packages/system/dashboard/values.yaml | 6 +++--- .../system/grafana-operator/images/grafana-dashboards.tag | 2 +- packages/system/kamaji/values.yaml | 4 ++-- packages/system/kilo/values.yaml | 2 +- packages/system/kubeovn-plunger/values.yaml | 2 +- packages/system/kubeovn-webhook/values.yaml | 2 +- packages/system/kubevirt-csi-node/values.yaml | 2 +- packages/system/lineage-controller-webhook/values.yaml | 2 +- packages/system/linstor/values.yaml | 4 +--- packages/system/objectstorage-controller/values.yaml | 2 +- packages/system/seaweedfs/values.yaml | 2 +- 23 files changed, 27 insertions(+), 29 deletions(-) diff --git a/packages/apps/kubernetes/images/kubevirt-csi-driver.tag b/packages/apps/kubernetes/images/kubevirt-csi-driver.tag index 420fbc66..02f8f103 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:31aee6f63c25a443974b56011d2907cedfeba245aa7caf732d7fc437b2b3ed50 +ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:726d9287e8caaea94eaf24c4f44734e3fbf4f8aa032b66b81848ebf95297cffe diff --git a/packages/apps/mysql/images/mariadb-backup.tag b/packages/apps/mysql/images/mariadb-backup.tag index 792c010b..1e381661 100644 --- a/packages/apps/mysql/images/mariadb-backup.tag +++ b/packages/apps/mysql/images/mariadb-backup.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/mariadb-backup:0.0.0@sha256:aca403030ff5d831415d72367866fdf291fab73ee2cfddbe4c93c2915a316ab1 +ghcr.io/cozystack/cozystack/mariadb-backup:0.0.0@sha256:0ddbbec0568dcb9fbc317cd9cc654e826dbe88ba3f184fa9b6b58aacb93b4570 diff --git a/packages/core/installer/values.yaml b/packages/core/installer/values.yaml index 6d244871..2325647e 100644 --- a/packages/core/installer/values.yaml +++ b/packages/core/installer/values.yaml @@ -1,4 +1,4 @@ cozystackOperator: - image: ghcr.io/cozystack/cozystack/cozystack-operator:v1.0.0-alpha.2@sha256:5bd4c8bb7bddb9e32555ef72ee3a04b5d8e0f488d7dce904ac2d7c9e0706debf + image: ghcr.io/cozystack/cozystack/cozystack-operator:v1.0.0-beta.2@sha256:aaea9d8430187f208e6464cb6f102dcbbcdb0584b6a7a8a690ad91fc1f5d45e6 platformSourceUrl: 'oci://ghcr.io/cozystack/cozystack/cozystack-packages' - platformSourceRef: 'digest=sha256:1b860c13b99da6fde37f2af3a5375aca234200836e2fa39a839ac30fa237962e' + platformSourceRef: 'digest=sha256:f59e562f2c91446117773ad457251d567706ea2964251a2b0acc65060fd1f3bc' diff --git a/packages/core/testing/values.yaml b/packages/core/testing/values.yaml index 9e577ed3..1e77f663 100644 --- a/packages/core/testing/values.yaml +++ b/packages/core/testing/values.yaml @@ -1,2 +1,2 @@ e2e: - image: ghcr.io/cozystack/cozystack/e2e-sandbox:v1.0.0-alpha.2@sha256:fde7616aacaf5939388bbe74eb6d946147ce855b9cceb47092f620b75ba2c98a + image: ghcr.io/cozystack/cozystack/e2e-sandbox:v1.0.0-beta.2@sha256:eac71ef0de3450fce96255629e77903630c63ade62b81e7055f1a689f92ee153 diff --git a/packages/extra/bootbox/images/matchbox.tag b/packages/extra/bootbox/images/matchbox.tag index ac9a2f76..05ee31c2 100644 --- a/packages/extra/bootbox/images/matchbox.tag +++ b/packages/extra/bootbox/images/matchbox.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/matchbox:v1.0.0-alpha.2@sha256:6a96746d43fde16db12bb2e0e6fc39823f4a26f41fc0be785bf6716c759009e1 +ghcr.io/cozystack/cozystack/matchbox:v1.0.0-beta.2@sha256:3d8c93822ca7b344b718a9ab0fb196d8fab92fcf852907975b39528d129811f0 diff --git a/packages/extra/seaweedfs/images/objectstorage-sidecar.tag b/packages/extra/seaweedfs/images/objectstorage-sidecar.tag index dd3c00a1..1db178ca 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:v1.0.0-alpha.2@sha256:aebbe6efe34c0a0cc2e87dafbf9dbb021d7e44796ef9378a47ca81cf72a407bb +ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.0.0-beta.2@sha256:ea035d4eff4a05d9d83f487d00438504cc27a95c0ee78c534d6eed53f4b2f04e diff --git a/packages/system/backup-controller/values.yaml b/packages/system/backup-controller/values.yaml index 0c20d434..84038de8 100644 --- a/packages/system/backup-controller/values.yaml +++ b/packages/system/backup-controller/values.yaml @@ -1,5 +1,5 @@ backupController: - image: "ghcr.io/cozystack/cozystack/backup-controller:v1.0.0-alpha.2@sha256:3914624192b4d420b79f0d985d37a03acb13411c694e23c160de16b1d877c52f" + image: "ghcr.io/cozystack/cozystack/backup-controller:v1.0.0-beta.2@sha256:556cc41e4ec24c173e01c1679cf96915c7ca8c0b532b7945d461bf3797c8afbc" replicas: 2 debug: false metrics: diff --git a/packages/system/backupstrategy-controller/values.yaml b/packages/system/backupstrategy-controller/values.yaml index cd71b6e1..d4c9f253 100644 --- a/packages/system/backupstrategy-controller/values.yaml +++ b/packages/system/backupstrategy-controller/values.yaml @@ -1,5 +1,5 @@ backupStrategyController: - image: "ghcr.io/cozystack/cozystack/backupstrategy-controller:v1.0.0-alpha.2@sha256:a39b76c99f252916ca9b6606a68fbd5b0861988dd65426803855c2884117e49c" + image: "ghcr.io/cozystack/cozystack/backupstrategy-controller:v1.0.0-beta.2@sha256:5cbc85679790aa14fb55568439c669a704e29f02236df179abbb3a25c56a2aa7" replicas: 2 debug: false metrics: diff --git a/packages/system/bucket/images/s3manager.tag b/packages/system/bucket/images/s3manager.tag index 1e30a0e1..8ef294d1 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:db548ff13f27f98e888b8f3ffb0f859a8f44334f1502d567d1881b79b6e5eb09 +ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:3013e13ba967070948653cc5b913a920dea93a24370b10731fafcfd8fb6a21b0 diff --git a/packages/system/cozystack-api/values.yaml b/packages/system/cozystack-api/values.yaml index 7876981a..b5abfaec 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:v1.0.0-alpha.2@sha256:5bde4dd3e8d3a880475c9e872925f841f6131f51f9a5017ec5c2a0f0bd288838 + image: ghcr.io/cozystack/cozystack/cozystack-api:v1.0.0-beta.2@sha256:2815ee65d13e3bde376ca3975a1106e1e32b8b911f2004d85a824a9fc30eebbd localK8sAPIEndpoint: enabled: true replicas: 2 diff --git a/packages/system/cozystack-controller/values.yaml b/packages/system/cozystack-controller/values.yaml index 633d0b28..7adbea35 100644 --- a/packages/system/cozystack-controller/values.yaml +++ b/packages/system/cozystack-controller/values.yaml @@ -1,5 +1,5 @@ cozystackController: - image: ghcr.io/cozystack/cozystack/cozystack-controller:v1.0.0-alpha.2@sha256:9cc3d4b05709d3e7495abcbe4c792467a9a439fd089a67df0e4a75fd3bb8ee81 + image: ghcr.io/cozystack/cozystack/cozystack-controller:v1.0.0-beta.2@sha256:3149c8341de741bce35de27591f72be82f22634ebc5dba8889b2bbae7892579a debug: false disableTelemetry: false cozystackAPIKind: "DaemonSet" diff --git a/packages/system/dashboard/templates/configmap.yaml b/packages/system/dashboard/templates/configmap.yaml index 675a396d..c3ee0595 100644 --- a/packages/system/dashboard/templates/configmap.yaml +++ b/packages/system/dashboard/templates/configmap.yaml @@ -1,6 +1,6 @@ {{- $brandingConfig := .Values._cluster.branding | default dict }} -{{- $tenantText := "v1.0.0-alpha.2" }} +{{- $tenantText := "v1.0.0-beta.2" }} {{- $footerText := "Cozystack" }} {{- $titleText := "Cozystack Dashboard" }} {{- $logoText := "" }} diff --git a/packages/system/dashboard/values.yaml b/packages/system/dashboard/values.yaml index fcbaeb8c..b1eae64c 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:v1.0.0-alpha.2@sha256:5661e877919c8625c27b89ee5dfda4a93d43e660d033fc4dbeb27f336c05d908 + image: ghcr.io/cozystack/cozystack/openapi-ui:v1.0.0-beta.2@sha256:ed695cd89086df9bae6e3519bbcaeb2f26a36109f000dec4bc917bedcbd17d69 openapiUIK8sBff: - image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v1.0.0-alpha.2@sha256:d33583995dc81a47c1dcbe45dbd866fa9097f88f4b6eb78b408dca432f15bd38 + image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v1.0.0-beta.2@sha256:1f7827a1978bd9c81ac924dd0e78f6a3ce834a9a64af55047e220812bc15a944 tokenProxy: - image: ghcr.io/cozystack/cozystack/token-proxy:v1.0.0-alpha.2@sha256:73887f80d96e7e3c16f1cebab521b05b4308bf4662ccc6724e6a8a9745ed8254 + image: ghcr.io/cozystack/cozystack/token-proxy:v1.0.0-beta.2@sha256:063d34c25333e110dd7fad999279d4a5497e918723f1341991af48632b96ada1 diff --git a/packages/system/grafana-operator/images/grafana-dashboards.tag b/packages/system/grafana-operator/images/grafana-dashboards.tag index 892bb131..dd4b8a60 100644 --- a/packages/system/grafana-operator/images/grafana-dashboards.tag +++ b/packages/system/grafana-operator/images/grafana-dashboards.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/grafana-dashboards:v1.0.0-alpha.2@sha256:57051daa5686b7dcb0799b6a7c01e676b910a2ac224437ac0ad639a0e66566c2 +ghcr.io/cozystack/cozystack/grafana-dashboards:v1.0.0-beta.2@sha256:e866b5b3874b9d390b341183d2ee070e1387440c14cfe51af831695def6dc2ec diff --git a/packages/system/kamaji/values.yaml b/packages/system/kamaji/values.yaml index 0995afc7..f8f169f9 100644 --- a/packages/system/kamaji/values.yaml +++ b/packages/system/kamaji/values.yaml @@ -3,7 +3,7 @@ kamaji: deploy: false image: pullPolicy: IfNotPresent - tag: v1.0.0-alpha.2@sha256:d5f2fa2972ba33cd2ccb855256e4bda4734d7e250638811b77f2e0dc72ad6b19 + tag: v1.0.0-beta.2@sha256:d5f2fa2972ba33cd2ccb855256e4bda4734d7e250638811b77f2e0dc72ad6b19 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:v1.0.0-alpha.2@sha256:d5f2fa2972ba33cd2ccb855256e4bda4734d7e250638811b77f2e0dc72ad6b19 + - --migrate-image=ghcr.io/cozystack/cozystack/kamaji:v1.0.0-beta.2@sha256:d5f2fa2972ba33cd2ccb855256e4bda4734d7e250638811b77f2e0dc72ad6b19 diff --git a/packages/system/kilo/values.yaml b/packages/system/kilo/values.yaml index 565191a9..7bcfc55c 100644 --- a/packages/system/kilo/values.yaml +++ b/packages/system/kilo/values.yaml @@ -1,7 +1,7 @@ kilo: image: pullPolicy: IfNotPresent - tag: v1.0.0-alpha.2@sha256:161fec69f38f536529139318babbd7d654bd47e7bc654828c26eb0ddb516eb79 + tag: v1.0.0-beta.2@sha256:45ad01a89ebb5311735660a0d1a1df36eda4b6f960be1b6319d2b94d2a7db701 repository: ghcr.io/cozystack/cozystack/kilo podCIDR: 10.244.0.0/16 serviceCIDR: 10.96.0.0/16 diff --git a/packages/system/kubeovn-plunger/values.yaml b/packages/system/kubeovn-plunger/values.yaml index d821bdac..ed7c84dd 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:v1.0.0-alpha.2@sha256:81d7e64b67b1e4fefa88ab0cf69c113a3fb44b04810074b616116102adcc3104 +image: ghcr.io/cozystack/cozystack/kubeovn-plunger:v1.0.0-beta.2@sha256:387fe9eca078edfb631511a091da9f2a7fcdc214867b4e2c269b55122a0f4ce7 ovnCentralName: ovn-central diff --git a/packages/system/kubeovn-webhook/values.yaml b/packages/system/kubeovn-webhook/values.yaml index df35d690..244879e7 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:v1.0.0-alpha.2@sha256:e18f9fd679e38f65362a8d0042f25468272f6d081136ad47027168d8e7e07a4a +image: ghcr.io/cozystack/cozystack/kubeovn-webhook:v1.0.0-beta.2@sha256:e18f9fd679e38f65362a8d0042f25468272f6d081136ad47027168d8e7e07a4a diff --git a/packages/system/kubevirt-csi-node/values.yaml b/packages/system/kubevirt-csi-node/values.yaml index 1fb152da..8457b6d1 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:31aee6f63c25a443974b56011d2907cedfeba245aa7caf732d7fc437b2b3ed50 + image: ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:726d9287e8caaea94eaf24c4f44734e3fbf4f8aa032b66b81848ebf95297cffe diff --git a/packages/system/lineage-controller-webhook/values.yaml b/packages/system/lineage-controller-webhook/values.yaml index 8225bd3c..6c19c22b 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:v1.0.0-alpha.2@sha256:e3ed98fce423359de7dd25094de959213342f5664d266c8d1041ecd2e9c82d59 + image: ghcr.io/cozystack/cozystack/lineage-controller-webhook:v1.0.0-beta.2@sha256:e2ffc29d244b9b5916ab048c338a8f284a47b0f4bd00e903dcf36df2a0299b72 debug: false localK8sAPIEndpoint: enabled: true diff --git a/packages/system/linstor/values.yaml b/packages/system/linstor/values.yaml index 8c19ba2a..8c22ac6b 100644 --- a/packages/system/linstor/values.yaml +++ b/packages/system/linstor/values.yaml @@ -2,11 +2,9 @@ piraeusServer: image: repository: ghcr.io/cozystack/cozystack/piraeus-server tag: 1.32.3@sha256:0e78fa31a3fe4ec2af43d1e59a9fc0f6d765780e32d473e18e1c495714051802 - # Talos-specific workarounds (disable for generic Linux like Ubuntu/Debian) talos: enabled: true - linstor: autoDiskful: enabled: true @@ -15,4 +13,4 @@ linstor: linstorCSI: image: repository: ghcr.io/cozystack/cozystack/linstor-csi - tag: v1.10.5@sha256:353a8bea0b41f832975132da3569da7d4fce85980474edce41a2a37097c7c3a9 + tag: v1.10.5@sha256:68465f120cfeec3d7ccbb389dd9bdbf7df1675da3ab9ba91c3feff21a799bc36 diff --git a/packages/system/objectstorage-controller/values.yaml b/packages/system/objectstorage-controller/values.yaml index 86ec9cd4..aa3756ad 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:v1.0.0-alpha.2@sha256:e535bd383e2ec275cc403cd557996e26df3d206a5541c53037246641cf6a4f2e" + image: "ghcr.io/cozystack/cozystack/objectstorage-controller:v1.0.0-beta.2@sha256:1f35e09bae32cd11c6ce2268556cac76b8da68b448208aea3c13071306087534" diff --git a/packages/system/seaweedfs/values.yaml b/packages/system/seaweedfs/values.yaml index 88a05e9c..3a60c779 100644 --- a/packages/system/seaweedfs/values.yaml +++ b/packages/system/seaweedfs/values.yaml @@ -177,7 +177,7 @@ seaweedfs: bucketClassName: "seaweedfs" region: "" sidecar: - image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.0.0-alpha.2@sha256:aebbe6efe34c0a0cc2e87dafbf9dbb021d7e44796ef9378a47ca81cf72a407bb" + image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.0.0-beta.2@sha256:ea035d4eff4a05d9d83f487d00438504cc27a95c0ee78c534d6eed53f4b2f04e" certificates: commonName: "SeaweedFS CA" ipAddresses: [] From 3f59ce487632aa67f598e030163f5a23a08610df Mon Sep 17 00:00:00 2001 From: Matthieu ROBIN Date: Mon, 2 Feb 2026 13:47:56 +0100 Subject: [PATCH 153/889] Update internal/controller/workloadmonitor_controller.go Co-authored-by: Timofei Larkin Signed-off-by: Matthieu ROBIN --- internal/controller/workloadmonitor_controller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/controller/workloadmonitor_controller.go b/internal/controller/workloadmonitor_controller.go index 677fda82..201fefee 100644 --- a/internal/controller/workloadmonitor_controller.go +++ b/internal/controller/workloadmonitor_controller.go @@ -466,7 +466,7 @@ func (r *WorkloadMonitorReconciler) getWorkloadMetadata(obj client.Object) map[s annotations := obj.GetAnnotations() if instanceType, ok := annotations["kubevirt.io/cluster-instancetype-name"]; ok { labels["workloads.cozystack.io/kubevirt-vmi-instance-type"] = instanceType - if instanceProfile, ok := annotations["kubevirt.io/cluster-instanceprofile-name"]; ok { + if instanceProfile, ok := annotations["kubevirt.io/cluster-preference-name"]; ok { labels["workloads.cozystack.io/kubevirt-vmi-instance-profile"] = instanceProfile } return labels From 09cd9e05c3d277d3c852e7317b7a2a80b0f77627 Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Mon, 2 Feb 2026 15:52:25 +0300 Subject: [PATCH 154/889] Apply suggestion from @gemini-code-assist[bot] Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Signed-off-by: Timofei Larkin --- internal/controller/workloadmonitor_controller.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/controller/workloadmonitor_controller.go b/internal/controller/workloadmonitor_controller.go index 201fefee..a684df9b 100644 --- a/internal/controller/workloadmonitor_controller.go +++ b/internal/controller/workloadmonitor_controller.go @@ -466,8 +466,9 @@ func (r *WorkloadMonitorReconciler) getWorkloadMetadata(obj client.Object) map[s annotations := obj.GetAnnotations() if instanceType, ok := annotations["kubevirt.io/cluster-instancetype-name"]; ok { labels["workloads.cozystack.io/kubevirt-vmi-instance-type"] = instanceType - if instanceProfile, ok := annotations["kubevirt.io/cluster-preference-name"]; ok { - labels["workloads.cozystack.io/kubevirt-vmi-instance-profile"] = instanceProfile + } + if instanceProfile, ok := annotations["kubevirt.io/cluster-instanceprofile-name"]; ok { + labels["workloads.cozystack.io/kubevirt-vmi-instance-profile"] = instanceProfile } return labels } From 3a8e8fc290e82c29a7f5e0241ca23372c05d54de Mon Sep 17 00:00:00 2001 From: Kirill Ilin Date: Mon, 2 Feb 2026 17:51:39 +0500 Subject: [PATCH 155/889] [vm] allow changing field external after creation Service will be recreated Signed-off-by: Kirill Ilin --- .../templates/vm-update-hook.yaml | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/apps/virtual-machine/templates/vm-update-hook.yaml b/packages/apps/virtual-machine/templates/vm-update-hook.yaml index 1197c027..3c7d7d34 100644 --- a/packages/apps/virtual-machine/templates/vm-update-hook.yaml +++ b/packages/apps/virtual-machine/templates/vm-update-hook.yaml @@ -3,14 +3,17 @@ {{- $existingVM := lookup "kubevirt.io/v1" "VirtualMachine" $namespace $vmName -}} {{- $existingPVC := lookup "v1" "PersistentVolumeClaim" $namespace $vmName -}} +{{- $existingService := lookup "v1" "Service" $namespace $vmName -}} {{- $instanceType := .Values.instanceType | default "" -}} {{- $instanceProfile := .Values.instanceProfile | default "" -}} {{- $desiredStorage := .Values.systemDisk.storage | default "" -}} +{{- $desiredServiceType := ternary "LoadBalancer" "ClusterIP" .Values.external -}} {{- $needUpdateType := false -}} {{- $needUpdateProfile := false -}} {{- $needResizePVC := false -}} +{{- $needRecreateService := false -}} {{- if and $existingVM $instanceType -}} {{- if not (eq $existingVM.spec.instancetype.name $instanceType) -}} @@ -35,7 +38,14 @@ {{- end -}} {{- end -}} -{{- if or $needUpdateType $needUpdateProfile $needResizePVC }} +{{- if $existingService -}} + {{- $currentServiceType := $existingService.spec.type -}} + {{- if ne $currentServiceType $desiredServiceType -}} + {{- $needRecreateService = true -}} + {{- end -}} +{{- end -}} + +{{- if or $needUpdateType $needUpdateProfile $needResizePVC $needRecreateService }} apiVersion: batch/v1 kind: Job metadata: @@ -86,6 +96,11 @@ spec: --type merge \ -p '{"spec":{"resources":{"requests":{"storage":"{{ $desiredStorage }}"}}}}' {{- end }} + + {{- if $needRecreateService }} + echo "Removing Service..." + kubectl delete service --cascade=orphan -n {{ $namespace }} {{ $vmName }} + {{- end }} --- apiVersion: v1 kind: ServiceAccount @@ -111,6 +126,10 @@ rules: - apiGroups: [""] resources: ["persistentvolumeclaims"] verbs: ["patch", "get", "list", "watch"] + - apiGroups: [""] + resources: ["services"] + resourceNames: ["{{ $vmName }}"] + verbs: ["delete"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding From 7320edd71d453ebfc676f98e67e3cd645e0c9c62 Mon Sep 17 00:00:00 2001 From: mattia-eleuteri Date: Mon, 2 Feb 2026 15:26:26 +0100 Subject: [PATCH 156/889] fix coredns serviceaccount to match kubernetes bootstrap rbac The Kubernetes bootstrap creates a ClusterRoleBinding 'system:kube-dns' that references ServiceAccount 'kube-dns' in 'kube-system'. However, the coredns chart was using the 'default' ServiceAccount because serviceAccount.create was not enabled. This caused CoreDNS pods to fail with 'Failed to watch' errors after restarts, as they lacked RBAC permissions to watch the Kubernetes API. Configure the chart to create the 'kube-dns' ServiceAccount, which matches the expected binding from Kubernetes bootstrap. Co-Authored-By: Claude Opus 4.5 Signed-off-by: mattia-eleuteri --- 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 a1061a1e..7f6403b8 100644 --- a/packages/system/coredns/values.yaml +++ b/packages/system/coredns/values.yaml @@ -6,3 +6,6 @@ coredns: k8sAppLabelOverride: kube-dns service: name: kube-dns + serviceAccount: + create: true + name: kube-dns From 8e210044f6de23eeb619a200142962993c9f5635 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Mon, 2 Feb 2026 19:03:29 +0100 Subject: [PATCH 157/889] feat(system): add cluster-autoscaler package Add cluster-autoscaler system package with support for multiple cloud providers. Each provider has its own PackageSource and values file, allowing simultaneous deployment in multi-cloud setups. Supported providers: - Hetzner Cloud - Azure Each instance uses a unique leader-elect-resource-name to prevent conflicts when running multiple autoscalers in the same cluster. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- .../sources/cluster-autoscaler-azure.yaml | 25 + .../sources/cluster-autoscaler-hetzner.yaml | 25 + packages/system/cluster-autoscaler/Chart.yaml | 4 + packages/system/cluster-autoscaler/Makefile | 10 + .../charts/cluster-autoscaler/.helmignore | 23 + .../charts/cluster-autoscaler/Chart.yaml | 13 + .../charts/cluster-autoscaler/README.md | 545 ++++++++++++++++++ .../cluster-autoscaler/README.md.gotmpl | 426 ++++++++++++++ .../cluster-autoscaler/templates/NOTES.txt | 18 + .../cluster-autoscaler/templates/_helpers.tpl | 160 +++++ .../templates/clusterrole.yaml | 210 +++++++ .../templates/clusterrolebinding.yaml | 22 + .../templates/configmap.yaml | 416 +++++++++++++ .../templates/deployment.yaml | 380 ++++++++++++ .../templates/extra-manifests.yaml | 4 + .../cluster-autoscaler/templates/pdb.yaml | 29 + .../templates/podsecuritypolicy.yaml | 42 ++ .../priority-expander-configmap.yaml | 25 + .../templates/prometheusrule.yaml | 15 + .../cluster-autoscaler/templates/role.yaml | 96 +++ .../templates/rolebinding.yaml | 23 + .../cluster-autoscaler/templates/secret.yaml | 32 + .../cluster-autoscaler/templates/service.yaml | 43 ++ .../templates/serviceaccount.yaml | 17 + .../templates/servicemonitor.yaml | 36 ++ .../cluster-autoscaler/templates/vpa.yaml | 22 + .../charts/cluster-autoscaler/values.yaml | 507 ++++++++++++++++ .../cluster-autoscaler/values-azure.yaml | 4 + .../cluster-autoscaler/values-hetzner.yaml | 4 + .../system/cluster-autoscaler/values.yaml | 1 + 30 files changed, 3177 insertions(+) create mode 100644 packages/core/platform/sources/cluster-autoscaler-azure.yaml create mode 100644 packages/core/platform/sources/cluster-autoscaler-hetzner.yaml create mode 100644 packages/system/cluster-autoscaler/Chart.yaml create mode 100644 packages/system/cluster-autoscaler/Makefile create mode 100644 packages/system/cluster-autoscaler/charts/cluster-autoscaler/.helmignore create mode 100644 packages/system/cluster-autoscaler/charts/cluster-autoscaler/Chart.yaml create mode 100644 packages/system/cluster-autoscaler/charts/cluster-autoscaler/README.md create mode 100644 packages/system/cluster-autoscaler/charts/cluster-autoscaler/README.md.gotmpl create mode 100644 packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/NOTES.txt create mode 100644 packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/_helpers.tpl create mode 100644 packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/clusterrole.yaml create mode 100644 packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/clusterrolebinding.yaml create mode 100644 packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/configmap.yaml create mode 100644 packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/deployment.yaml create mode 100644 packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/extra-manifests.yaml create mode 100644 packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/pdb.yaml create mode 100644 packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/podsecuritypolicy.yaml create mode 100644 packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/priority-expander-configmap.yaml create mode 100644 packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/prometheusrule.yaml create mode 100644 packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/role.yaml create mode 100644 packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/rolebinding.yaml create mode 100644 packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/secret.yaml create mode 100644 packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/service.yaml create mode 100644 packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/serviceaccount.yaml create mode 100644 packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/servicemonitor.yaml create mode 100644 packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/vpa.yaml create mode 100644 packages/system/cluster-autoscaler/charts/cluster-autoscaler/values.yaml create mode 100644 packages/system/cluster-autoscaler/values-azure.yaml create mode 100644 packages/system/cluster-autoscaler/values-hetzner.yaml create mode 100644 packages/system/cluster-autoscaler/values.yaml diff --git a/packages/core/platform/sources/cluster-autoscaler-azure.yaml b/packages/core/platform/sources/cluster-autoscaler-azure.yaml new file mode 100644 index 00000000..d17b716d --- /dev/null +++ b/packages/core/platform/sources/cluster-autoscaler-azure.yaml @@ -0,0 +1,25 @@ +--- +apiVersion: cozystack.io/v1alpha1 +kind: PackageSource +metadata: + name: cozystack.cluster-autoscaler-azure +spec: + sourceRef: + kind: OCIRepository + name: cozystack-packages + namespace: cozy-system + path: / + variants: + - name: default + dependsOn: + - cozystack.networking + components: + - name: cluster-autoscaler-azure + path: system/cluster-autoscaler + valuesFiles: + - values.yaml + - values-azure.yaml + install: + privileged: true + namespace: cozy-cluster-autoscaler-azure + releaseName: cluster-autoscaler-azure diff --git a/packages/core/platform/sources/cluster-autoscaler-hetzner.yaml b/packages/core/platform/sources/cluster-autoscaler-hetzner.yaml new file mode 100644 index 00000000..ca2c4557 --- /dev/null +++ b/packages/core/platform/sources/cluster-autoscaler-hetzner.yaml @@ -0,0 +1,25 @@ +--- +apiVersion: cozystack.io/v1alpha1 +kind: PackageSource +metadata: + name: cozystack.cluster-autoscaler-hetzner +spec: + sourceRef: + kind: OCIRepository + name: cozystack-packages + namespace: cozy-system + path: / + variants: + - name: default + dependsOn: + - cozystack.networking + components: + - name: cluster-autoscaler-hetzner + path: system/cluster-autoscaler + valuesFiles: + - values.yaml + - values-hetzner.yaml + install: + privileged: true + namespace: cozy-cluster-autoscaler-hetzner + releaseName: cluster-autoscaler-hetzner diff --git a/packages/system/cluster-autoscaler/Chart.yaml b/packages/system/cluster-autoscaler/Chart.yaml new file mode 100644 index 00000000..9aa2bb43 --- /dev/null +++ b/packages/system/cluster-autoscaler/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v2 +name: cozy-cluster-autoscaler +description: Cluster Autoscaler for automatic node scaling +version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process diff --git a/packages/system/cluster-autoscaler/Makefile b/packages/system/cluster-autoscaler/Makefile new file mode 100644 index 00000000..605d4d4e --- /dev/null +++ b/packages/system/cluster-autoscaler/Makefile @@ -0,0 +1,10 @@ +export NAME=cluster-autoscaler +export NAMESPACE=cozy-$(NAME) + +include ../../../hack/package.mk + +update: + rm -rf charts + helm repo add autoscaler https://kubernetes.github.io/autoscaler + helm repo update autoscaler + helm pull autoscaler/cluster-autoscaler --untar --untardir charts diff --git a/packages/system/cluster-autoscaler/charts/cluster-autoscaler/.helmignore b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/.helmignore new file mode 100644 index 00000000..0e8a0eb3 --- /dev/null +++ b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/packages/system/cluster-autoscaler/charts/cluster-autoscaler/Chart.yaml b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/Chart.yaml new file mode 100644 index 00000000..0c46f2c8 --- /dev/null +++ b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/Chart.yaml @@ -0,0 +1,13 @@ +apiVersion: v2 +appVersion: 1.35.0 +description: Scales Kubernetes worker nodes within autoscaling groups. +home: https://github.com/kubernetes/autoscaler +icon: https://github.com/kubernetes/kubernetes/raw/master/logo/logo.png +maintainers: +- email: guyjtempleton@googlemail.com + name: gjtempleton +name: cluster-autoscaler +sources: +- https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler +type: application +version: 9.55.0 diff --git a/packages/system/cluster-autoscaler/charts/cluster-autoscaler/README.md b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/README.md new file mode 100644 index 00000000..8a9da750 --- /dev/null +++ b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/README.md @@ -0,0 +1,545 @@ +# cluster-autoscaler + +Scales Kubernetes worker nodes within autoscaling groups. + +## TL;DR + +```console +$ helm repo add autoscaler https://kubernetes.github.io/autoscaler + +# Method 1 - Using Autodiscovery +$ helm install my-release autoscaler/cluster-autoscaler \ + --set 'autoDiscovery.clusterName'= + +# Method 2 - Specifying groups manually +$ helm install my-release autoscaler/cluster-autoscaler \ + --set "autoscalingGroups[0].name=your-asg-name" \ + --set "autoscalingGroups[0].maxSize=10" \ + --set "autoscalingGroups[0].minSize=1" +``` + +## Introduction + +This chart bootstraps a cluster-autoscaler deployment on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager. + +## Prerequisites + +- Helm 3+ +- Kubernetes 1.8+ + - [Older versions](https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler#releases) may work by overriding the `image`. Cluster autoscaler internally simulates the scheduler and bugs between mismatched versions may be subtle. +- Azure AKS specific Prerequisites: + - Kubernetes 1.10+ with RBAC-enabled. + +## Previous Helm Chart + +The previous `cluster-autoscaler` Helm chart hosted at [helm/charts](https://github.com/helm/charts) has been moved to this repository in accordance with the [Deprecation timeline](https://github.com/helm/charts#deprecation-timeline). Note that a few things have changed between this version and the old version: + +- This repository **only** supports Helm chart installations using Helm 3+ since the `apiVersion` on the charts has been marked as `v2`. +- Previous versions of the Helm chart have not been migrated + +## Migration from 1.X to 9.X+ versions of this Chart + +**TL;DR:** +You should choose to use versions >=9.0.0 of the `cluster-autoscaler` chart published from this repository; previous versions, and the `cluster-autoscaler-chart` with versioning 1.X.X published from this repository are deprecated. + +
+ Previous versions of this chart - further details +On initial migration of this chart from the `helm/charts` repository this chart was renamed from `cluster-autoscaler` to `cluster-autoscaler-chart` due to technical limitations. This affected all `1.X` releases of the chart, version 2.0.0 of this chart exists only to mark the [`cluster-autoscaler-chart` chart](https://artifacthub.io/packages/helm/cluster-autoscaler/cluster-autoscaler-chart) as deprecated. + +Releases of the chart from `9.0.0` onwards return the naming of the chart to `cluster-autoscaler` and return to following the versioning established by the chart's previous location at . + +To migrate from a 1.X release of the chart to a `9.0.0` or later release, you should first uninstall your `1.X` install of the `cluster-autoscaler-chart` chart, before performing the installation of the new `cluster-autoscaler` chart. +
+ +## Migration from 9.0 to 9.1 + +Starting from `9.1.0` the `envFromConfigMap` value is expected to contain the name of a ConfigMap that is used as ref for `envFrom`, similar to `envFromSecret`. If you want to keep the previous behaviour of `envFromConfigMap` you must rename it to `extraEnvConfigMaps`. + +## Installing the Chart + +**By default, no deployment is created and nothing will autoscale**. + +You must provide some minimal configuration, either to specify instance groups or enable auto-discovery. It is not recommended to do both. + +Either: + +- Set `autoDiscovery.clusterName` and provide additional autodiscovery options if necessary **or** +- Set static node group configurations for one or more node groups (using `autoscalingGroups` or `autoscalingGroupsnamePrefix`). + +To create a valid configuration, follow instructions for your cloud provider: + +- [AWS](#aws---using-auto-discovery-of-tagged-instance-groups) +- [GCE](#gce) +- [Azure](#azure) +- [OpenStack Magnum](#openstack-magnum) +- [Cluster API](#cluster-api) +- [Exoscale](#exoscale) +- [Hetzner Cloud](#hetzner-cloud) +- [Civo](#civo) + +### Templating the autoDiscovery.clusterName + +The cluster name can be templated in the `autoDiscovery.clusterName` variable. This is useful when the cluster name is dynamically generated based on other values coming from external systems like Argo CD or Flux. This also allows you to use global Helm values to set the cluster name, e.g., `autoDiscovery.clusterName={{ .Values.global.clusterName }}`, so that you don't need to set it in more than 1 location in the values file. + +### AWS - Using auto-discovery of tagged instance groups + +Auto-discovery finds ASGs tags as below and automatically manages them based on the min and max size specified in the ASG. `cloudProvider=aws` only. + +- Tag the ASGs with keys to match `.Values.autoDiscovery.tags`, by default: `k8s.io/cluster-autoscaler/enabled` and `k8s.io/cluster-autoscaler/` +- Verify the [IAM Permissions](#aws---iam) +- Set `autoDiscovery.clusterName=` +- Set `awsRegion=` +- Set (option) `awsAccessKeyID=` and `awsSecretAccessKey=` if you want to [use AWS credentials directly instead of an instance role](https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/aws/README.md#using-aws-credentials) + +```console +$ helm install my-release autoscaler/cluster-autoscaler \ + --set autoDiscovery.clusterName= \ + --set awsRegion= +``` + +Alternatively with your own AWS credentials + +```console +$ helm install my-release autoscaler/cluster-autoscaler \ + --set autoDiscovery.clusterName= \ + --set awsRegion= \ + --set awsAccessKeyID= \ + --set awsSecretAccessKey= +``` + +#### Specifying groups manually + +Without autodiscovery, specify an array of elements each containing ASG name, min size, max size. The sizes specified here will be applied to the ASG, assuming IAM permissions are correctly configured. + +- Verify the [IAM Permissions](#aws---iam) +- Either provide a yaml file setting `autoscalingGroups` (see values.yaml) or use `--set` e.g.: + +```console +$ helm install my-release autoscaler/cluster-autoscaler \ + --set "autoscalingGroups[0].name=your-asg-name" \ + --set "autoscalingGroups[0].maxSize=10" \ + --set "autoscalingGroups[0].minSize=1" +``` + +#### Auto-discovery + +For auto-discovery of instances to work, they must be tagged with the keys in `.Values.autoDiscovery.tags`, which by default are `k8s.io/cluster-autoscaler/enabled` and `k8s.io/cluster-autoscaler/`. + +The value of the tag does not matter, only the key. + +An example kops spec excerpt: + +```yaml +apiVersion: kops/v1alpha2 +kind: Cluster +metadata: + name: my.cluster.internal +spec: + additionalPolicies: + node: | + [ + {"Effect":"Allow","Action":["autoscaling:DescribeAutoScalingGroups","autoscaling:DescribeAutoScalingInstances","autoscaling:DescribeLaunchConfigurations","autoscaling:DescribeTags","autoscaling:SetDesiredCapacity","autoscaling:TerminateInstanceInAutoScalingGroup"],"Resource":"*"} + ] + ... +--- +apiVersion: kops/v1alpha2 +kind: InstanceGroup +metadata: + labels: + kops.k8s.io/cluster: my.cluster.internal + name: my-instances +spec: + cloudLabels: + k8s.io/cluster-autoscaler/enabled: "" + k8s.io/cluster-autoscaler/my.cluster.internal: "" + image: kops.io/k8s-1.8-debian-jessie-amd64-hvm-ebs-2018-01-14 + machineType: r4.large + maxSize: 4 + minSize: 0 +``` + +In this example you would need to `--set autoDiscovery.clusterName=my.cluster.internal` when installing. + +It is not recommended to try to mix this with setting `autoscalingGroups`. + +See [autoscaler AWS documentation](https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/aws/README.md#auto-discovery-setup) for a more discussion of the setup. + +### GCE + +The following parameters are required: + +- `autoDiscovery.clusterName=any-name` +- `cloud-provider=gce` +- `autoscalingGroupsnamePrefix[0].name=your-ig-prefix,autoscalingGroupsnamePrefix[0].maxSize=10,autoscalingGroupsnamePrefix[0].minSize=1` + +To use Managed Instance Group (MIG) auto-discovery, provide a YAML file setting `autoscalingGroupsnamePrefix` (see values.yaml) or use `--set` when installing the Chart - e.g. + +```console +$ helm install my-release autoscaler/cluster-autoscaler \ + --set "autoscalingGroupsnamePrefix[0].name=your-ig-prefix,autoscalingGroupsnamePrefix[0].maxSize=10,autoscalingGroupsnamePrefix[0].minSize=1" \ + --set autoDiscovery.clusterName= \ + --set cloudProvider=gce +``` + +Note that `your-ig-prefix` should be a _prefix_ matching one or more MIGs, and _not_ the full name of the MIG. For example, to match multiple instance groups - `k8s-node-group-a-standard`, `k8s-node-group-b-gpu`, you would use a prefix of `k8s-node-group-`. + +Prefixes will be rendered using `tpl` function so you can use any value of your choice if that's a valid prefix. For instance (ignore escaping characters): `gke-{{ .Values.autoDiscovery.clusterName }}` + +In the event you want to explicitly specify MIGs instead of using auto-discovery, set members of the `autoscalingGroups` array directly - e.g. + +``` +# where 'n' is the index, starting at 0 +--set autoscalingGroups[n].name=https://content.googleapis.com/compute/v1/projects/$PROJECTID/zones/$ZONENAME/instanceGroups/$FULL-MIG-NAME,autoscalingGroups[n].maxSize=$MAXSIZE,autoscalingGroups[n].minSize=$MINSIZE +``` + +### Azure + +The following parameters are required: + +- `cloudProvider=azure` +- `autoscalingGroups[0].name=your-vmss,autoscalingGroups[0].maxSize=10,autoscalingGroups[0].minSize=1` +- `azureClientID: "your-service-principal-app-id"` +- `azureClientSecret: "your-service-principal-client-secret"` +- `azureSubscriptionID: "your-azure-subscription-id"` +- `azureTenantID: "your-azure-tenant-id"` +- `azureResourceGroup: "your-aks-cluster-resource-group-name"` +- `azureVMType: "vmss"` + +### OpenStack Magnum + +`cloudProvider: magnum` must be set, and then one of + +- `magnumClusterName=` and `autoscalingGroups` with the names of node groups and min/max node counts +- or `autoDiscovery.clusterName=` with one or more `autoDiscovery.roles`. + +Additionally, `cloudConfigPath: "/etc/kubernetes/cloud-config"` must be set as this should be the location of the cloud-config file on the host. + +Example values files can be found [here](../../cluster-autoscaler/cloudprovider/magnum/examples). + +Install the chart with + +```console +$ helm install my-release autoscaler/cluster-autoscaler -f myvalues.yaml +``` + +### Cluster-API + +`cloudProvider: clusterapi` must be set, and then one or more of + +- `autoDiscovery.clusterName` +- or `autoDiscovery.namespace` +- or `autoDiscovery.labels` + +See [here](https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/clusterapi/README.md#configuring-node-group-auto-discovery) for more details. + +Additional config parameters available, see the `values.yaml` for more details + +- `clusterAPIMode` +- `clusterAPIKubeconfigSecret` +- `clusterAPIWorkloadKubeconfigPath` +- `clusterAPICloudConfigPath` + +### Exoscale + +Create a `values.yaml` file with the following content: +```yaml +cloudProvider: exoscale +autoDiscovery: + clusterName: cluster.local # this value is not used, but must be set +``` + +Optionally, you may specify the minimum and maximum size of a particular nodepool by adding the following to the `values.yaml` file: +```yaml +autoscalingGroups: + - name: your-nodepool-name + maxSize: 10 + minSize: 1 +``` + +Create an Exoscale API key with appropriate permissions as described in [cluster-autoscaler/cloudprovider/exoscale/README.md](https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/exoscale/README.md). +A secret of name `-exoscale-cluster-autoscaler` needs to be created, containing the api key and secret, as well as the zone. + +```console +$ kubectl create secret generic my-release-exoscale-cluster-autoscaler \ + --from-literal=api-key="EXOxxxxxxxxxxxxxxxxxxxxxxxx" \ + --from-literal=api-secret="xxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" --from-literal=api-zone="ch-gva-2" +``` + +After creating the secret, the chart may be installed: + +```console +$ helm install my-release autoscaler/cluster-autoscaler -f values.yaml +``` + +Read [cluster-autoscaler/cloudprovider/exoscale/README.md](https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/exoscale/README.md) for further information on the setup without helm. + +### Hetzner Cloud + +The following parameters are required: + +- `cloudProvider=hetzner` +- `extraEnv.HCLOUD_TOKEN=...` +- `autoscalingGroups=...` + +Each autoscaling group requires an additional `instanceType` and `region` key to be set. + +Read [cluster-autoscaler/cloudprovider/hetzner/README.md](https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/hetzner/README.md) for further information on the setup without helm. + +### Civo + +The following parameters are required: + +- `cloudProvider=civo` +- `autoscalingGroups=...` + +When installing the helm chart to the namespace `kube-system`, you can set `secretKeyRefNameOverride` to `civo-api-access`. +Otherwise specify the following parameters: + +- `civoApiUrl=https://api.civo.com` +- `civoApiKey=...` +- `civoClusterID=...` +- `civoRegion=...` + +Read [cluster-autoscaler/cloudprovider/civo/README.md](https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/civo/README.md) for further information on the setup without helm. + +## Uninstalling the Chart + +To uninstall `my-release`: + +```console +$ helm uninstall my-release +``` + +The command removes all the Kubernetes components associated with the chart and deletes the release. + +> **Tip**: List all releases using `helm list` or start clean with `helm uninstall my-release` + +## Additional Configuration + +### AWS - IAM + +The worker running the cluster autoscaler will need access to certain resources and actions depending on the version you run and your configuration of it. + +For the up-to-date IAM permissions required, please see the [cluster autoscaler's AWS Cloudprovider Readme](https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/aws/README.md#iam-policy) and switch to the tag of the cluster autoscaler image you are using. + +### AWS - IAM Roles for Service Accounts (IRSA) + +For Kubernetes clusters that use Amazon EKS, the service account can be configured with an IAM role using [IAM Roles for Service Accounts](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) to avoid needing to grant access to the worker nodes for AWS resources. + +In order to accomplish this, you will first need to create a new IAM role with the above mentions policies. Take care in [configuring the trust relationship](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts-technical-overview.html#iam-role-configuration) to restrict access just to the service account used by cluster autoscaler. + +Once you have the IAM role configured, you would then need to `--set rbac.serviceAccount.annotations."eks\.amazonaws\.com/role-arn"=arn:aws:iam::123456789012:role/MyRoleName` when installing. Alternatively, you can embed templates in values (ignore escaping characters): + +```yaml +rbac: + serviceAccount: + annotations: + eks.amazonaws.com/role-arn: "{{ .Values.aws.myroleARN }}" +``` + +### Azure - Using azure workload identity + +You can use the project [Azure workload identity](https://github.com/Azure/azure-workload-identity), to automatically configure the correct setup for your pods to use federated identity with Azure. + +You can also set the correct settings yourself instead of relying on this project. + +For example the following configuration will configure the Autoscaler to use your federated identity: + +```yaml +azureUseWorkloadIdentityExtension: true +extraEnv: + AZURE_CLIENT_ID: USER ASSIGNED IDENTITY CLIENT ID + AZURE_TENANT_ID: USER ASSIGNED IDENTITY TENANT ID + AZURE_FEDERATED_TOKEN_FILE: /var/run/secrets/tokens/azure-identity-token + AZURE_AUTHORITY_HOST: https://login.microsoftonline.com/ +extraVolumes: +- name: azure-identity-token + projected: + defaultMode: 420 + sources: + - serviceAccountToken: + audience: api://AzureADTokenExchange + expirationSeconds: 3600 + path: azure-identity-token +extraVolumeMounts: +- mountPath: /var/run/secrets/tokens + name: azure-identity-token + readOnly: true +``` + +### Custom arguments + +You can use the `customArgs` value to give any argument to cluster autoscaler command. + +Typical use case is to give an environment variable as an argument which will be interpolated at execution time. + +This is helpful when you need to inject values from configmap or secret. + +## Troubleshooting + +The chart will succeed even if the container arguments are incorrect. A few minutes after starting `kubectl logs -l "app=aws-cluster-autoscaler" --tail=50` should loop through something like + +``` +polling_autoscaler.go:111] Poll finished +static_autoscaler.go:97] Starting main loop +utils.go:435] No pod using affinity / antiaffinity found in cluster, disabling affinity predicate for this loop +static_autoscaler.go:230] Filtering out schedulables +``` + +If not, find a pod that the deployment created and `describe` it, paying close attention to the arguments under `Command`. e.g.: + +``` +Containers: + cluster-autoscaler: + Command: + ./cluster-autoscaler + --cloud-provider=aws +# if specifying ASGs manually + --nodes=1:10:your-scaling-group-name +# if using autodiscovery + --node-group-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/ + --v=4 +``` + +### PodSecurityPolicy + +Though enough for the majority of installations, the default PodSecurityPolicy _could_ be too restrictive depending on the specifics of your release. Please make sure to check that the template fits with any customizations made or disable it by setting `rbac.pspEnabled` to `false`. + +### VerticalPodAutoscaler + +The CA Helm Chart can install a [`VerticalPodAutoscaler`](https://github.com/kubernetes/autoscaler/blob/master/vertical-pod-autoscaler/README.md) object from Chart version `9.27.0` +onwards for the Cluster Autoscaler Deployment to scale the CA as appropriate, but for that, we +need to install the VPA to the cluster separately. A VPA can help minimize wasted resources +when usage spikes periodically or remediate containers that are being OOMKilled. + +The following example snippet can be used to install VPA that allows scaling down from the default recommendations of the deployment template: + +```yaml +vpa: + enabled: true + containerPolicy: + minAllowed: + cpu: 20m + memory: 50Mi +``` + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| additionalLabels | object | `{}` | Labels to add to each object of the chart. | +| affinity | object | `{}` | Affinity for pod assignment | +| autoDiscovery.clusterName | string | `nil` | Enable autodiscovery for `cloudProvider=aws`, for groups matching `autoDiscovery.tags`. autoDiscovery.clusterName -- Enable autodiscovery for `cloudProvider=azure`, using tags defined in https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/azure/README.md#auto-discovery-setup. Enable autodiscovery for `cloudProvider=clusterapi`, for groups matching `autoDiscovery.labels`. Enable autodiscovery for `cloudProvider=gce`, but no MIG tagging required. Enable autodiscovery for `cloudProvider=magnum`, for groups matching `autoDiscovery.roles`. | +| autoDiscovery.labels | list | `[]` | Cluster-API labels to match https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/clusterapi/README.md#configuring-node-group-auto-discovery | +| autoDiscovery.namespace | string | `nil` | Enable autodiscovery via cluster namespace for for `cloudProvider=clusterapi` | +| autoDiscovery.roles | list | `["worker"]` | Magnum node group roles to match. | +| autoDiscovery.tags | list | `["k8s.io/cluster-autoscaler/enabled","k8s.io/cluster-autoscaler/{{ .Values.autoDiscovery.clusterName }}"]` | ASG tags to match, run through `tpl`. | +| autoscalingGroups | list | `[]` | For AWS, Azure AKS, Exoscale or Magnum. At least one element is required if not using `autoDiscovery`. For example:
 - name: asg1
maxSize: 2
minSize: 1
For Hetzner Cloud, the `instanceType` and `region` keys are also required.
 - name: mypool
maxSize: 2
minSize: 1
instanceType: CPX21
region: FSN1
| +| autoscalingGroupsnamePrefix | list | `[]` | For GCE. At least one element is required if not using `autoDiscovery`. For example:
 - name: ig01
maxSize: 10
minSize: 0
| +| awsAccessKeyID | string | `""` | AWS access key ID ([if AWS user keys used](https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/aws/README.md#using-aws-credentials)) | +| awsRegion | string | `""` | AWS region (required if `cloudProvider=aws`) | +| awsSecretAccessKey | string | `""` | AWS access secret key ([if AWS user keys used](https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/aws/README.md#using-aws-credentials)) | +| azureClientID | string | `""` | Service Principal ClientID with contributor permission to Cluster and Node ResourceGroup. Required if `cloudProvider=azure` | +| azureClientSecret | string | `""` | Service Principal ClientSecret with contributor permission to Cluster and Node ResourceGroup. Required if `cloudProvider=azure` | +| azureEnableForceDelete | bool | `false` | Whether to force delete VMs or VMSS instances when scaling down. | +| azureResourceGroup | string | `""` | Azure resource group that the cluster is located. Required if `cloudProvider=azure` | +| azureSubscriptionID | string | `""` | Azure subscription where the resources are located. Required if `cloudProvider=azure` | +| azureTenantID | string | `""` | Azure tenant where the resources are located. Required if `cloudProvider=azure` | +| azureUseManagedIdentityExtension | bool | `false` | Whether to use Azure's managed identity extension for credentials. If using MSI, ensure subscription ID, resource group, and azure AKS cluster name are set. You can only use one authentication method at a time, either azureUseWorkloadIdentityExtension or azureUseManagedIdentityExtension should be set. | +| azureUseWorkloadIdentityExtension | bool | `false` | Whether to use Azure's workload identity extension for credentials. See the project here: https://github.com/Azure/azure-workload-identity for more details. You can only use one authentication method at a time, either azureUseWorkloadIdentityExtension or azureUseManagedIdentityExtension should be set. | +| azureUserAssignedIdentityID | string | `""` | When vmss has multiple user assigned identity assigned, azureUserAssignedIdentityID specifies which identity to be used | +| azureVMType | string | `"vmss"` | Azure VM type. | +| civoApiKey | string | `""` | API key for the Civo API. Required if `cloudProvider=civo` | +| civoApiUrl | string | `"https://api.civo.com"` | URL for the Civo API. Required if `cloudProvider=civo` | +| civoClusterID | string | `""` | Cluster ID for the Civo cluster. Required if `cloudProvider=civo` | +| civoRegion | string | `""` | Region for the Civo cluster. Required if `cloudProvider=civo` | +| cloudConfigPath | string | `""` | Configuration file for cloud provider. | +| cloudProvider | string | `"aws"` | The cloud provider where the autoscaler runs. Currently only `gce`, `aws`, `azure`, `magnum`, `clusterapi`, `civo` and `coreweave` are supported. `aws` supported for AWS. `gce` for GCE. `azure` for Azure AKS. `magnum` for OpenStack Magnum, `clusterapi` for Cluster API. `civo` for Civo Cloud. `coreweave` for CoreWeave. | +| clusterAPICloudConfigPath | string | `"/etc/kubernetes/mgmt-kubeconfig"` | Path to kubeconfig for connecting to Cluster API Management Cluster, only used if `clusterAPIMode=kubeconfig-kubeconfig or incluster-kubeconfig` | +| clusterAPIConfigMapsNamespace | string | `""` | Namespace on the workload cluster to store Leader election and status configmaps | +| clusterAPIKubeconfigSecret | string | `""` | Secret containing kubeconfig for connecting to Cluster API managed workloadcluster Required if `cloudProvider=clusterapi` and `clusterAPIMode=kubeconfig-kubeconfig,kubeconfig-incluster or incluster-kubeconfig` | +| clusterAPIMode | string | `"incluster-incluster"` | Cluster API mode, see https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/clusterapi/README.md#connecting-cluster-autoscaler-to-cluster-api-management-and-workload-clusters Syntax: workloadClusterMode-ManagementClusterMode for `kubeconfig-kubeconfig`, `incluster-kubeconfig` and `single-kubeconfig` you always must mount the external kubeconfig using either `extraVolumeSecrets` or `extraMounts` and `extraVolumes` if you dont set `clusterAPIKubeconfigSecret`and thus use an in-cluster config or want to use a non capi generated kubeconfig you must do so for the workload kubeconfig as well | +| clusterAPIWorkloadKubeconfigPath | string | `"/etc/kubernetes/value"` | Path to kubeconfig for connecting to Cluster API managed workloadcluster, only used if `clusterAPIMode=kubeconfig-kubeconfig or kubeconfig-incluster` | +| containerSecurityContext | object | `{}` | [Security context for container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/) | +| customArgs | list | `[]` | Additional custom container arguments. Refer to https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/FAQ.md#what-are-the-parameters-to-ca for the full list of cluster autoscaler parameters and their default values. List of arguments as strings. | +| deployment.annotations | object | `{}` | Annotations to add to the Deployment object. | +| deployment.selector | object | `{}` | Labels for Deployment `spec.selector.matchLabels`. | +| dnsConfig | object | `{}` | [Pod's DNS Config](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-dns-config) | +| dnsPolicy | string | `"ClusterFirst"` | Defaults to `ClusterFirst`. Valid values are: `ClusterFirstWithHostNet`, `ClusterFirst`, `Default` or `None`. If autoscaler does not depend on cluster DNS, recommended to set this to `Default`. | +| envFromConfigMap | string | `""` | ConfigMap name to use as envFrom. | +| envFromSecret | string | `""` | Secret name to use as envFrom. | +| expanderPriorities | object | `{}` | The expanderPriorities is used if `extraArgs.expander` contains `priority` and expanderPriorities is also set with the priorities. If `extraArgs.expander` contains `priority`, then expanderPriorities is used to define cluster-autoscaler-priority-expander priorities. See: https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/expander/priority/readme.md | +| extraArgs | object | `{"logtostderr":true,"stderrthreshold":"info","v":4}` | Additional container arguments. Refer to https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/FAQ.md#what-are-the-parameters-to-ca for the full list of cluster autoscaler parameters and their default values. Everything after the first _ will be ignored allowing the use of multi-string arguments. | +| extraEnv | object | `{}` | Additional container environment variables. | +| extraEnvConfigMaps | object | `{}` | Additional container environment variables from ConfigMaps. | +| extraEnvSecrets | object | `{}` | Additional container environment variables from Secrets. | +| extraObjects | list | `[]` | Extra K8s manifests to deploy | +| extraVolumeMounts | list | `[]` | Additional volumes to mount. | +| extraVolumeSecrets | object | `{}` | Additional volumes to mount from Secrets. | +| extraVolumes | list | `[]` | Additional volumes. | +| fullnameOverride | string | `""` | String to fully override `cluster-autoscaler.fullname` template. | +| hostNetwork | bool | `false` | Whether to expose network interfaces of the host machine to pods. | +| image.pullPolicy | string | `"IfNotPresent"` | Image pull policy | +| image.pullSecrets | list | `[]` | Image pull secrets | +| image.repository | string | `"registry.k8s.io/autoscaling/cluster-autoscaler"` | Image repository | +| image.tag | string | `"v1.34.2"` | Image tag | +| initContainers | list | `[]` | Any additional init containers. | +| kubeTargetVersionOverride | string | `""` | Allow overriding the `.Capabilities.KubeVersion.GitVersion` check. Useful for `helm template` commands. | +| kwokConfigMapName | string | `"kwok-provider-config"` | configmap for configuring kwok provider | +| magnumCABundlePath | string | `"/etc/kubernetes/ca-bundle.crt"` | Path to the host's CA bundle, from `ca-file` in the cloud-config file. | +| magnumClusterName | string | `""` | Cluster name or ID in Magnum. Required if `cloudProvider=magnum` and not setting `autoDiscovery.clusterName`. | +| nameOverride | string | `""` | String to partially override `cluster-autoscaler.fullname` template (will maintain the release name) | +| nodeSelector | object | `{}` | Node labels for pod assignment. Ref: https://kubernetes.io/docs/user-guide/node-selection/. | +| podAnnotations | object | `{}` | Annotations to add to each pod. | +| podDisruptionBudget | object | `{"annotations":{},"maxUnavailable":1,"selector":{}}` | Pod disruption budget. | +| podDisruptionBudget.annotations | object | `{}` | Annotations to add to the PodDisruptionBudget. | +| podDisruptionBudget.selector | object | `{}` | Override labels for PodDisruptionBudget `spec.selector.matchLabels`. | +| podLabels | object | `{}` | Labels to add to each pod. | +| priorityClassName | string | `"system-cluster-critical"` | priorityClassName | +| priorityConfigMapAnnotations | object | `{}` | Annotations to add to `cluster-autoscaler-priority-expander` ConfigMap. | +| prometheusRule.additionalLabels | object | `{}` | Additional labels to be set in metadata. | +| prometheusRule.enabled | bool | `false` | If true, creates a Prometheus Operator PrometheusRule. | +| prometheusRule.interval | string | `nil` | How often rules in the group are evaluated (falls back to `global.evaluation_interval` if not set). | +| prometheusRule.namespace | string | `"monitoring"` | Namespace which Prometheus is running in. | +| prometheusRule.rules | list | `[]` | Rules spec template (see https://github.com/prometheus-operator/prometheus-operator/blob/master/Documentation/api.md#rule). | +| rbac.additionalRules | list | `[]` | Additional rules for role/clusterrole | +| rbac.annotations | object | `{}` | Additional annotations to add to RBAC resources (Role/RoleBinding/ClusterRole/ClusterRoleBinding). | +| rbac.clusterScoped | bool | `true` | if set to false will only provision RBAC to alter resources in the current namespace. Most useful for Cluster-API | +| rbac.create | bool | `true` | If `true`, create and use RBAC resources. | +| rbac.pspEnabled | bool | `false` | If `true`, creates and uses RBAC resources required in the cluster with [Pod Security Policies](https://kubernetes.io/docs/concepts/policy/pod-security-policy/) enabled. Must be used with `rbac.create` set to `true`. | +| rbac.serviceAccount.annotations | object | `{}` | Additional Service Account annotations. | +| rbac.serviceAccount.automountServiceAccountToken | bool | `true` | Automount API credentials for a Service Account. | +| rbac.serviceAccount.create | bool | `true` | If `true` and `rbac.create` is also true, a Service Account will be created. | +| rbac.serviceAccount.name | string | `""` | The name of the ServiceAccount to use. If not set and create is `true`, a name is generated using the fullname template. | +| replicaCount | int | `1` | Desired number of pods | +| resources | object | `{}` | Pod resource requests and limits. | +| revisionHistoryLimit | int | `10` | The number of revisions to keep. | +| secretKeyRefNameOverride | string | `""` | Overrides the name of the Secret to use when loading the secretKeyRef for AWS, Azure and Civo env variables | +| securityContext | object | `{}` | [Security context for pod](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/) | +| service.annotations | object | `{}` | Annotations to add to service | +| service.clusterIP | string | `""` | IP address to assign to service | +| service.create | bool | `true` | If `true`, a Service will be created. | +| service.externalIPs | list | `[]` | List of IP addresses at which the service is available. Ref: https://kubernetes.io/docs/concepts/services-networking/service/#external-ips. | +| service.labels | object | `{}` | Labels to add to service | +| service.loadBalancerIP | string | `""` | IP address to assign to load balancer (if supported). | +| service.loadBalancerSourceRanges | list | `[]` | List of IP CIDRs allowed access to load balancer (if supported). | +| service.portName | string | `"http"` | Name for service port. | +| service.selector | object | `{}` | Override labels for Service `spec.selector`. | +| service.servicePort | int | `8085` | Service port to expose. | +| service.type | string | `"ClusterIP"` | Type of service to create. | +| serviceMonitor.annotations | object | `{}` | Annotations to add to service monitor | +| serviceMonitor.enabled | bool | `false` | If true, creates a Prometheus Operator ServiceMonitor. | +| serviceMonitor.interval | string | `"10s"` | Interval that Prometheus scrapes Cluster Autoscaler metrics. | +| serviceMonitor.metricRelabelings | object | `{}` | MetricRelabelConfigs to apply to samples before ingestion. | +| serviceMonitor.namespace | string | `"monitoring"` | Namespace which Prometheus is running in. | +| serviceMonitor.path | string | `"/metrics"` | The path to scrape for metrics; autoscaler exposes `/metrics` (this is standard) | +| serviceMonitor.relabelings | object | `{}` | RelabelConfigs to apply to metrics before scraping. | +| serviceMonitor.selector | object | `{"release":"prometheus-operator"}` | Default to kube-prometheus install (CoreOS recommended), but should be set according to Prometheus install. | +| tolerations | list | `[]` | List of node taints to tolerate (requires Kubernetes >= 1.6). | +| topologySpreadConstraints | list | `[]` | You can use topology spread constraints to control how Pods are spread across your cluster among failure-domains such as regions, zones, nodes, and other user-defined topology domains. (requires Kubernetes >= 1.19). | +| updateStrategy | object | `{}` | [Deployment update strategy](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy) | +| vpa | object | `{"containerPolicy":{},"enabled":false,"recommender":"default","updateMode":"Auto"}` | Configure a VerticalPodAutoscaler for the cluster-autoscaler Deployment. | +| vpa.containerPolicy | object | `{}` | [ContainerResourcePolicy](https://github.com/kubernetes/autoscaler/blob/vertical-pod-autoscaler/v0.13.0/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1/types.go#L159). The containerName is always set to the deployment's container name. This value is required if VPA is enabled. | +| vpa.enabled | bool | `false` | If true, creates a VerticalPodAutoscaler. | +| vpa.recommender | string | `"default"` | Name of the VPA recommender that will provide recommendations for vertical scaling. | +| vpa.updateMode | string | `"Auto"` | [UpdateMode](https://github.com/kubernetes/autoscaler/blob/vertical-pod-autoscaler/v0.13.0/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1/types.go#L124) | diff --git a/packages/system/cluster-autoscaler/charts/cluster-autoscaler/README.md.gotmpl b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/README.md.gotmpl new file mode 100644 index 00000000..0567e283 --- /dev/null +++ b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/README.md.gotmpl @@ -0,0 +1,426 @@ +{{ template "chart.header" . }} + +{{ template "chart.description" . }} + +## TL;DR + +```console +$ helm repo add autoscaler https://kubernetes.github.io/autoscaler + +# Method 1 - Using Autodiscovery +$ helm install my-release autoscaler/cluster-autoscaler \ + --set 'autoDiscovery.clusterName'= + +# Method 2 - Specifying groups manually +$ helm install my-release autoscaler/cluster-autoscaler \ + --set "autoscalingGroups[0].name=your-asg-name" \ + --set "autoscalingGroups[0].maxSize=10" \ + --set "autoscalingGroups[0].minSize=1" +``` + +## Introduction + +This chart bootstraps a cluster-autoscaler deployment on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager. + +## Prerequisites + +- Helm 3+ +- Kubernetes 1.8+ + - [Older versions](https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler#releases) may work by overriding the `image`. Cluster autoscaler internally simulates the scheduler and bugs between mismatched versions may be subtle. +- Azure AKS specific Prerequisites: + - Kubernetes 1.10+ with RBAC-enabled. + +## Previous Helm Chart + +The previous `cluster-autoscaler` Helm chart hosted at [helm/charts](https://github.com/helm/charts) has been moved to this repository in accordance with the [Deprecation timeline](https://github.com/helm/charts#deprecation-timeline). Note that a few things have changed between this version and the old version: + +- This repository **only** supports Helm chart installations using Helm 3+ since the `apiVersion` on the charts has been marked as `v2`. +- Previous versions of the Helm chart have not been migrated + +## Migration from 1.X to 9.X+ versions of this Chart + +**TL;DR:** +You should choose to use versions >=9.0.0 of the `cluster-autoscaler` chart published from this repository; previous versions, and the `cluster-autoscaler-chart` with versioning 1.X.X published from this repository are deprecated. + +
+ Previous versions of this chart - further details +On initial migration of this chart from the `helm/charts` repository this chart was renamed from `cluster-autoscaler` to `cluster-autoscaler-chart` due to technical limitations. This affected all `1.X` releases of the chart, version 2.0.0 of this chart exists only to mark the [`cluster-autoscaler-chart` chart](https://artifacthub.io/packages/helm/cluster-autoscaler/cluster-autoscaler-chart) as deprecated. + +Releases of the chart from `9.0.0` onwards return the naming of the chart to `cluster-autoscaler` and return to following the versioning established by the chart's previous location at . + +To migrate from a 1.X release of the chart to a `9.0.0` or later release, you should first uninstall your `1.X` install of the `cluster-autoscaler-chart` chart, before performing the installation of the new `cluster-autoscaler` chart. +
+ +## Migration from 9.0 to 9.1 + +Starting from `9.1.0` the `envFromConfigMap` value is expected to contain the name of a ConfigMap that is used as ref for `envFrom`, similar to `envFromSecret`. If you want to keep the previous behaviour of `envFromConfigMap` you must rename it to `extraEnvConfigMaps`. + +## Installing the Chart + +**By default, no deployment is created and nothing will autoscale**. + +You must provide some minimal configuration, either to specify instance groups or enable auto-discovery. It is not recommended to do both. + +Either: + +- Set `autoDiscovery.clusterName` and provide additional autodiscovery options if necessary **or** +- Set static node group configurations for one or more node groups (using `autoscalingGroups` or `autoscalingGroupsnamePrefix`). + +To create a valid configuration, follow instructions for your cloud provider: + +- [AWS](#aws---using-auto-discovery-of-tagged-instance-groups) +- [GCE](#gce) +- [Azure](#azure) +- [OpenStack Magnum](#openstack-magnum) +- [Cluster API](#cluster-api) +- [Exoscale](#exoscale) +- [Hetzner Cloud](#hetzner-cloud) +- [Civo](#civo) + +### Templating the autoDiscovery.clusterName + +The cluster name can be templated in the `autoDiscovery.clusterName` variable. This is useful when the cluster name is dynamically generated based on other values coming from external systems like Argo CD or Flux. This also allows you to use global Helm values to set the cluster name, e.g., `autoDiscovery.clusterName={{`{{ .Values.global.clusterName }}`}}`, so that you don't need to set it in more than 1 location in the values file. + +### AWS - Using auto-discovery of tagged instance groups + +Auto-discovery finds ASGs tags as below and automatically manages them based on the min and max size specified in the ASG. `cloudProvider=aws` only. + +- Tag the ASGs with keys to match `.Values.autoDiscovery.tags`, by default: `k8s.io/cluster-autoscaler/enabled` and `k8s.io/cluster-autoscaler/` +- Verify the [IAM Permissions](#aws---iam) +- Set `autoDiscovery.clusterName=` +- Set `awsRegion=` +- Set (option) `awsAccessKeyID=` and `awsSecretAccessKey=` if you want to [use AWS credentials directly instead of an instance role](https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/aws/README.md#using-aws-credentials) + +```console +$ helm install my-release autoscaler/cluster-autoscaler \ + --set autoDiscovery.clusterName= \ + --set awsRegion= +``` + +Alternatively with your own AWS credentials + +```console +$ helm install my-release autoscaler/cluster-autoscaler \ + --set autoDiscovery.clusterName= \ + --set awsRegion= \ + --set awsAccessKeyID= \ + --set awsSecretAccessKey= +``` + +#### Specifying groups manually + +Without autodiscovery, specify an array of elements each containing ASG name, min size, max size. The sizes specified here will be applied to the ASG, assuming IAM permissions are correctly configured. + +- Verify the [IAM Permissions](#aws---iam) +- Either provide a yaml file setting `autoscalingGroups` (see values.yaml) or use `--set` e.g.: + +```console +$ helm install my-release autoscaler/cluster-autoscaler \ + --set "autoscalingGroups[0].name=your-asg-name" \ + --set "autoscalingGroups[0].maxSize=10" \ + --set "autoscalingGroups[0].minSize=1" +``` + +#### Auto-discovery + +For auto-discovery of instances to work, they must be tagged with the keys in `.Values.autoDiscovery.tags`, which by default are `k8s.io/cluster-autoscaler/enabled` and `k8s.io/cluster-autoscaler/`. + +The value of the tag does not matter, only the key. + +An example kops spec excerpt: + +```yaml +apiVersion: kops/v1alpha2 +kind: Cluster +metadata: + name: my.cluster.internal +spec: + additionalPolicies: + node: | + [ + {"Effect":"Allow","Action":["autoscaling:DescribeAutoScalingGroups","autoscaling:DescribeAutoScalingInstances","autoscaling:DescribeLaunchConfigurations","autoscaling:DescribeTags","autoscaling:SetDesiredCapacity","autoscaling:TerminateInstanceInAutoScalingGroup"],"Resource":"*"} + ] + ... +--- +apiVersion: kops/v1alpha2 +kind: InstanceGroup +metadata: + labels: + kops.k8s.io/cluster: my.cluster.internal + name: my-instances +spec: + cloudLabels: + k8s.io/cluster-autoscaler/enabled: "" + k8s.io/cluster-autoscaler/my.cluster.internal: "" + image: kops.io/k8s-1.8-debian-jessie-amd64-hvm-ebs-2018-01-14 + machineType: r4.large + maxSize: 4 + minSize: 0 +``` + +In this example you would need to `--set autoDiscovery.clusterName=my.cluster.internal` when installing. + +It is not recommended to try to mix this with setting `autoscalingGroups`. + +See [autoscaler AWS documentation](https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/aws/README.md#auto-discovery-setup) for a more discussion of the setup. + +### GCE + +The following parameters are required: + +- `autoDiscovery.clusterName=any-name` +- `cloud-provider=gce` +- `autoscalingGroupsnamePrefix[0].name=your-ig-prefix,autoscalingGroupsnamePrefix[0].maxSize=10,autoscalingGroupsnamePrefix[0].minSize=1` + +To use Managed Instance Group (MIG) auto-discovery, provide a YAML file setting `autoscalingGroupsnamePrefix` (see values.yaml) or use `--set` when installing the Chart - e.g. + +```console +$ helm install my-release autoscaler/cluster-autoscaler \ + --set "autoscalingGroupsnamePrefix[0].name=your-ig-prefix,autoscalingGroupsnamePrefix[0].maxSize=10,autoscalingGroupsnamePrefix[0].minSize=1" \ + --set autoDiscovery.clusterName= \ + --set cloudProvider=gce +``` + +Note that `your-ig-prefix` should be a _prefix_ matching one or more MIGs, and _not_ the full name of the MIG. For example, to match multiple instance groups - `k8s-node-group-a-standard`, `k8s-node-group-b-gpu`, you would use a prefix of `k8s-node-group-`. + +Prefixes will be rendered using `tpl` function so you can use any value of your choice if that's a valid prefix. For instance (ignore escaping characters): `gke-{{`{{ .Values.autoDiscovery.clusterName }}`}}` + +In the event you want to explicitly specify MIGs instead of using auto-discovery, set members of the `autoscalingGroups` array directly - e.g. + +``` +# where 'n' is the index, starting at 0 +--set autoscalingGroups[n].name=https://content.googleapis.com/compute/v1/projects/$PROJECTID/zones/$ZONENAME/instanceGroups/$FULL-MIG-NAME,autoscalingGroups[n].maxSize=$MAXSIZE,autoscalingGroups[n].minSize=$MINSIZE +``` + +### Azure + +The following parameters are required: + +- `cloudProvider=azure` +- `autoscalingGroups[0].name=your-vmss,autoscalingGroups[0].maxSize=10,autoscalingGroups[0].minSize=1` +- `azureClientID: "your-service-principal-app-id"` +- `azureClientSecret: "your-service-principal-client-secret"` +- `azureSubscriptionID: "your-azure-subscription-id"` +- `azureTenantID: "your-azure-tenant-id"` +- `azureResourceGroup: "your-aks-cluster-resource-group-name"` +- `azureVMType: "vmss"` + +### OpenStack Magnum + +`cloudProvider: magnum` must be set, and then one of + +- `magnumClusterName=` and `autoscalingGroups` with the names of node groups and min/max node counts +- or `autoDiscovery.clusterName=` with one or more `autoDiscovery.roles`. + +Additionally, `cloudConfigPath: "/etc/kubernetes/cloud-config"` must be set as this should be the location of the cloud-config file on the host. + +Example values files can be found [here](../../cluster-autoscaler/cloudprovider/magnum/examples). + +Install the chart with + +```console +$ helm install my-release autoscaler/cluster-autoscaler -f myvalues.yaml +``` + +### Cluster-API + +`cloudProvider: clusterapi` must be set, and then one or more of + +- `autoDiscovery.clusterName` +- or `autoDiscovery.namespace` +- or `autoDiscovery.labels` + +See [here](https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/clusterapi/README.md#configuring-node-group-auto-discovery) for more details. + +Additional config parameters available, see the `values.yaml` for more details + +- `clusterAPIMode` +- `clusterAPIKubeconfigSecret` +- `clusterAPIWorkloadKubeconfigPath` +- `clusterAPICloudConfigPath` + +### Exoscale + +Create a `values.yaml` file with the following content: +```yaml +cloudProvider: exoscale +autoDiscovery: + clusterName: cluster.local # this value is not used, but must be set +``` + +Optionally, you may specify the minimum and maximum size of a particular nodepool by adding the following to the `values.yaml` file: +```yaml +autoscalingGroups: + - name: your-nodepool-name + maxSize: 10 + minSize: 1 +``` + +Create an Exoscale API key with appropriate permissions as described in [cluster-autoscaler/cloudprovider/exoscale/README.md](https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/exoscale/README.md). +A secret of name `-exoscale-cluster-autoscaler` needs to be created, containing the api key and secret, as well as the zone. + +```console +$ kubectl create secret generic my-release-exoscale-cluster-autoscaler \ + --from-literal=api-key="EXOxxxxxxxxxxxxxxxxxxxxxxxx" \ + --from-literal=api-secret="xxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" --from-literal=api-zone="ch-gva-2" +``` + +After creating the secret, the chart may be installed: + +```console +$ helm install my-release autoscaler/cluster-autoscaler -f values.yaml +``` + +Read [cluster-autoscaler/cloudprovider/exoscale/README.md](https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/exoscale/README.md) for further information on the setup without helm. + +### Hetzner Cloud + +The following parameters are required: + +- `cloudProvider=hetzner` +- `extraEnv.HCLOUD_TOKEN=...` +- `autoscalingGroups=...` + +Each autoscaling group requires an additional `instanceType` and `region` key to be set. + +Read [cluster-autoscaler/cloudprovider/hetzner/README.md](https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/hetzner/README.md) for further information on the setup without helm. + +### Civo + +The following parameters are required: + +- `cloudProvider=civo` +- `autoscalingGroups=...` + +When installing the helm chart to the namespace `kube-system`, you can set `secretKeyRefNameOverride` to `civo-api-access`. +Otherwise specify the following parameters: + +- `civoApiUrl=https://api.civo.com` +- `civoApiKey=...` +- `civoClusterID=...` +- `civoRegion=...` + +Read [cluster-autoscaler/cloudprovider/civo/README.md](https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/civo/README.md) for further information on the setup without helm. + +## Uninstalling the Chart + +To uninstall `my-release`: + +```console +$ helm uninstall my-release +``` + +The command removes all the Kubernetes components associated with the chart and deletes the release. + +> **Tip**: List all releases using `helm list` or start clean with `helm uninstall my-release` + +## Additional Configuration + +### AWS - IAM + +The worker running the cluster autoscaler will need access to certain resources and actions depending on the version you run and your configuration of it. + +For the up-to-date IAM permissions required, please see the [cluster autoscaler's AWS Cloudprovider Readme](https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/aws/README.md#iam-policy) and switch to the tag of the cluster autoscaler image you are using. + +### AWS - IAM Roles for Service Accounts (IRSA) + +For Kubernetes clusters that use Amazon EKS, the service account can be configured with an IAM role using [IAM Roles for Service Accounts](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) to avoid needing to grant access to the worker nodes for AWS resources. + +In order to accomplish this, you will first need to create a new IAM role with the above mentions policies. Take care in [configuring the trust relationship](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts-technical-overview.html#iam-role-configuration) to restrict access just to the service account used by cluster autoscaler. + +Once you have the IAM role configured, you would then need to `--set rbac.serviceAccount.annotations."eks\.amazonaws\.com/role-arn"=arn:aws:iam::123456789012:role/MyRoleName` when installing. Alternatively, you can embed templates in values (ignore escaping characters): + +```yaml +rbac: + serviceAccount: + annotations: + eks.amazonaws.com/role-arn: "{{`{{ .Values.aws.myroleARN `}}}}" +``` + +### Azure - Using azure workload identity + +You can use the project [Azure workload identity](https://github.com/Azure/azure-workload-identity), to automatically configure the correct setup for your pods to use federated identity with Azure. + +You can also set the correct settings yourself instead of relying on this project. + +For example the following configuration will configure the Autoscaler to use your federated identity: + +```yaml +azureUseWorkloadIdentityExtension: true +extraEnv: + AZURE_CLIENT_ID: USER ASSIGNED IDENTITY CLIENT ID + AZURE_TENANT_ID: USER ASSIGNED IDENTITY TENANT ID + AZURE_FEDERATED_TOKEN_FILE: /var/run/secrets/tokens/azure-identity-token + AZURE_AUTHORITY_HOST: https://login.microsoftonline.com/ +extraVolumes: +- name: azure-identity-token + projected: + defaultMode: 420 + sources: + - serviceAccountToken: + audience: api://AzureADTokenExchange + expirationSeconds: 3600 + path: azure-identity-token +extraVolumeMounts: +- mountPath: /var/run/secrets/tokens + name: azure-identity-token + readOnly: true +``` + +### Custom arguments + +You can use the `customArgs` value to give any argument to cluster autoscaler command. + +Typical use case is to give an environment variable as an argument which will be interpolated at execution time. + +This is helpful when you need to inject values from configmap or secret. + +## Troubleshooting + +The chart will succeed even if the container arguments are incorrect. A few minutes after starting `kubectl logs -l "app=aws-cluster-autoscaler" --tail=50` should loop through something like + +``` +polling_autoscaler.go:111] Poll finished +static_autoscaler.go:97] Starting main loop +utils.go:435] No pod using affinity / antiaffinity found in cluster, disabling affinity predicate for this loop +static_autoscaler.go:230] Filtering out schedulables +``` + +If not, find a pod that the deployment created and `describe` it, paying close attention to the arguments under `Command`. e.g.: + +``` +Containers: + cluster-autoscaler: + Command: + ./cluster-autoscaler + --cloud-provider=aws +# if specifying ASGs manually + --nodes=1:10:your-scaling-group-name +# if using autodiscovery + --node-group-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/ + --v=4 +``` + +### PodSecurityPolicy + +Though enough for the majority of installations, the default PodSecurityPolicy _could_ be too restrictive depending on the specifics of your release. Please make sure to check that the template fits with any customizations made or disable it by setting `rbac.pspEnabled` to `false`. + +### VerticalPodAutoscaler + +The CA Helm Chart can install a [`VerticalPodAutoscaler`](https://github.com/kubernetes/autoscaler/blob/master/vertical-pod-autoscaler/README.md) object from Chart version `9.27.0` +onwards for the Cluster Autoscaler Deployment to scale the CA as appropriate, but for that, we +need to install the VPA to the cluster separately. A VPA can help minimize wasted resources +when usage spikes periodically or remediate containers that are being OOMKilled. + +The following example snippet can be used to install VPA that allows scaling down from the default recommendations of the deployment template: + +```yaml +vpa: + enabled: true + containerPolicy: + minAllowed: + cpu: 20m + memory: 50Mi +``` + +{{ template "chart.valuesSection" . }} diff --git a/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/NOTES.txt b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/NOTES.txt new file mode 100644 index 00000000..1a87a3d1 --- /dev/null +++ b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/NOTES.txt @@ -0,0 +1,18 @@ +{{- if or ( or .Values.autoDiscovery.clusterName .Values.autoDiscovery.namespace .Values.autoDiscovery.labels ) .Values.autoscalingGroups }} + +To verify that cluster-autoscaler has started, run: + + kubectl --namespace={{ .Release.Namespace }} get pods -l "app.kubernetes.io/name={{ template "cluster-autoscaler.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" + +{{- else -}} + +############################################################################## +#### ERROR: You must specify values for either #### +#### autoDiscovery or autoscalingGroups[] #### +############################################################################## + +The deployment and pod will not be created and the installation is not functional +See README: + open https://github.com/kubernetes/autoscaler/tree/master/charts/cluster-autoscaler + +{{- end -}} diff --git a/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/_helpers.tpl b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/_helpers.tpl new file mode 100644 index 00000000..c7e80f4d --- /dev/null +++ b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/_helpers.tpl @@ -0,0 +1,160 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "cluster-autoscaler.name" -}} +{{- default (printf "%s-%s" .Values.cloudProvider .Chart.Name) .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "cluster-autoscaler.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default (printf "%s-%s" .Values.cloudProvider .Chart.Name) .Values.nameOverride -}} +{{- if ne $name .Release.Name -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s" $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "cluster-autoscaler.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Return instance and name labels. +*/}} +{{- define "cluster-autoscaler.instance-name" -}} +app.kubernetes.io/instance: {{ .Release.Name | quote }} +app.kubernetes.io/name: {{ include "cluster-autoscaler.name" . | quote }} +{{- end -}} + + +{{/* +Return labels, including instance and name. +*/}} +{{- define "cluster-autoscaler.labels" -}} +{{ include "cluster-autoscaler.instance-name" . }} +app.kubernetes.io/managed-by: {{ .Release.Service | quote }} +helm.sh/chart: {{ include "cluster-autoscaler.chart" . | quote }} +{{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels }} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for deployment. +*/}} +{{- define "deployment.apiVersion" -}} +{{- $kubeTargetVersion := default .Capabilities.KubeVersion.GitVersion .Values.kubeTargetVersionOverride }} +{{- if semverCompare "<1.9-0" $kubeTargetVersion -}} +{{- print "apps/v1beta2" -}} +{{- else -}} +{{- print "apps/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for podsecuritypolicy. +*/}} +{{- define "podsecuritypolicy.apiVersion" -}} +{{- $kubeTargetVersion := default .Capabilities.KubeVersion.GitVersion .Values.kubeTargetVersionOverride }} +{{- if semverCompare "<1.10-0" $kubeTargetVersion -}} +{{- print "extensions/v1beta1" -}} +{{- else -}} +{{- print "policy/v1beta1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for podDisruptionBudget. +*/}} +{{- define "podDisruptionBudget.apiVersion" -}} +{{- $kubeTargetVersion := default .Capabilities.KubeVersion.GitVersion .Values.kubeTargetVersionOverride }} +{{- if semverCompare "<1.21-0" $kubeTargetVersion -}} +{{- print "policy/v1beta1" -}} +{{- else -}} +{{- print "policy/v1" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the service account name used by the pod. +*/}} +{{- define "cluster-autoscaler.serviceAccountName" -}} +{{- if .Values.rbac.serviceAccount.create -}} + {{ default (include "cluster-autoscaler.fullname" .) .Values.rbac.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.rbac.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{/* +Return true if the priority expander is enabled +*/}} +{{- define "cluster-autoscaler.priorityExpanderEnabled" -}} +{{- $expanders := splitList "," (default "" .Values.extraArgs.expander) -}} +{{- if has "priority" $expanders -}} +{{- true -}} +{{- end -}} +{{- end -}} + +{{/* +autoDiscovery.clusterName for clusterapi. +*/}} +{{- define "cluster-autoscaler.capiAutodiscovery.clusterName" -}} +{{- print "clusterName=" -}}{{ tpl (.Values.autoDiscovery.clusterName) . }} +{{- end -}} + +{{/* +autoDiscovery.namespace for clusterapi. +*/}} +{{- define "cluster-autoscaler.capiAutodiscovery.namespace" -}} +{{- print "namespace=" }}{{ .Values.autoDiscovery.namespace -}} +{{- end -}} + +{{/* +autoDiscovery.labels for clusterapi. +*/}} +{{- define "cluster-autoscaler.capiAutodiscovery.labels" -}} +{{- range $i, $el := .Values.autoDiscovery.labels -}} +{{- if $i -}}{{- print "," -}}{{- end -}} +{{- range $key, $val := $el -}} +{{- $key -}}{{- print "=" -}}{{- $val -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Return the autodiscoveryparameters for clusterapi. +*/}} +{{- define "cluster-autoscaler.capiAutodiscoveryConfig" -}} +{{- if .Values.autoDiscovery.clusterName -}} +{{ include "cluster-autoscaler.capiAutodiscovery.clusterName" . }} + {{- if .Values.autoDiscovery.namespace }} + {{- print "," -}} + {{ include "cluster-autoscaler.capiAutodiscovery.namespace" . }} + {{- end -}} + {{- if .Values.autoDiscovery.labels }} + {{- print "," -}} + {{ include "cluster-autoscaler.capiAutodiscovery.labels" . }} + {{- end -}} +{{- else if .Values.autoDiscovery.namespace -}} +{{ include "cluster-autoscaler.capiAutodiscovery.namespace" . }} + {{- if .Values.autoDiscovery.labels }} + {{- print "," -}} + {{ include "cluster-autoscaler.capiAutodiscovery.labels" . }} + {{- end -}} +{{- else if .Values.autoDiscovery.labels -}} + {{ include "cluster-autoscaler.capiAutodiscovery.labels" . }} +{{- end -}} +{{- end -}} diff --git a/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/clusterrole.yaml b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/clusterrole.yaml new file mode 100644 index 00000000..e6a9018e --- /dev/null +++ b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/clusterrole.yaml @@ -0,0 +1,210 @@ +{{- if and .Values.rbac.create .Values.rbac.clusterScoped -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: +{{- with .Values.rbac.annotations }} + annotations: + {{- range $k, $v := . }} + {{- printf "%s: %s" (tpl $k $) (tpl $v $) | nindent 4 }} + {{- end }} +{{- end }} + labels: +{{ include "cluster-autoscaler.labels" . | indent 4 }} + name: {{ template "cluster-autoscaler.fullname" . }} +rules: +{{- if (eq .Values.cloudProvider "coreweave") }} + - apiGroups: + - "compute.coreweave.com" + resources: + - nodepools + verbs: + - get + - list + - patch + - update +{{- end }} + - apiGroups: + - "" + resources: + - events + - endpoints + verbs: + - create + - patch + - apiGroups: + - "" + resources: + - pods/eviction + verbs: + - create + - apiGroups: + - "" + resources: + - pods/status + verbs: + - update + - apiGroups: + - "" + resources: + - endpoints + resourceNames: + - cluster-autoscaler + verbs: + - get + - update + - apiGroups: + - "" + resources: + - nodes + verbs: + - watch + - list +{{- if (eq .Values.cloudProvider "kwok") }} + - create +{{- end }} +{{- if or (eq .Values.cloudProvider "kwok") (eq .Values.cloudProvider "huaweicloud") }} + - delete +{{- end }} + - get + - update + - apiGroups: + - resource.k8s.io + resources: + - resourceslices + - deviceclasses + - resourceclaims + verbs: + - watch + - list + - get + - apiGroups: + - "" + resources: + - namespaces + - pods + - services + - replicationcontrollers + - persistentvolumeclaims + - persistentvolumes + verbs: + - watch + - list + - get + - apiGroups: + - batch + resources: + - jobs + - cronjobs + verbs: + - watch + - list + - get + - apiGroups: + - batch + - extensions + resources: + - jobs + verbs: + - get + - list + - patch + - watch + - apiGroups: + - extensions + resources: + - replicasets + - daemonsets + verbs: + - watch + - list + - get + - apiGroups: + - policy + resources: + - poddisruptionbudgets + verbs: + - watch + - list + - apiGroups: + - apps + resources: + - daemonsets + - replicasets + - statefulsets + verbs: + - watch + - list + - get + - apiGroups: + - storage.k8s.io + resources: + - storageclasses + - csinodes + - csidrivers + - csistoragecapacities + - volumeattachments + verbs: + - watch + - list + - get + - apiGroups: + - "" + resources: + - configmaps + verbs: + - list + - watch + - get + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create + - apiGroups: + - coordination.k8s.io + resourceNames: + - cluster-autoscaler + resources: + - leases + verbs: + - get + - update +{{- if .Values.rbac.pspEnabled }} + - apiGroups: + - extensions + - policy + resources: + - podsecuritypolicies + resourceNames: + - {{ template "cluster-autoscaler.fullname" . }} + verbs: + - use +{{- end -}} +{{- if and ( and ( eq .Values.cloudProvider "clusterapi" ) ( .Values.rbac.clusterScoped ) ( or ( eq .Values.clusterAPIMode "incluster-incluster" ) ( eq .Values.clusterAPIMode "kubeconfig-incluster" ) ))}} + - apiGroups: + - cluster.x-k8s.io + resources: + - machinedeployments + - machinepools + - machines + - machinesets + verbs: + - get + - list + - update + - watch + - apiGroups: + - cluster.x-k8s.io + resources: + - machinedeployments/scale + - machinepools/scale + verbs: + - get + - patch + - update +{{- end }} +{{- if .Values.rbac.additionalRules }} +{{ toYaml .Values.rbac.additionalRules | indent 2 }} +{{- end }} +{{- end -}} diff --git a/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/clusterrolebinding.yaml b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/clusterrolebinding.yaml new file mode 100644 index 00000000..59e6ef67 --- /dev/null +++ b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/clusterrolebinding.yaml @@ -0,0 +1,22 @@ +{{- if and .Values.rbac.create .Values.rbac.clusterScoped -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: +{{- with .Values.rbac.annotations }} + annotations: + {{- range $k, $v := . }} + {{- printf "%s: %s" (tpl $k $) (tpl $v $) | nindent 4 }} + {{- end }} +{{- end }} + labels: +{{ include "cluster-autoscaler.labels" . | indent 4 }} + name: {{ template "cluster-autoscaler.fullname" . }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ template "cluster-autoscaler.fullname" . }} +subjects: + - kind: ServiceAccount + name: {{ template "cluster-autoscaler.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +{{- end -}} diff --git a/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/configmap.yaml b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/configmap.yaml new file mode 100644 index 00000000..6cd0c406 --- /dev/null +++ b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/configmap.yaml @@ -0,0 +1,416 @@ +{{- if or (eq .Values.cloudProvider "kwok") }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Values.kwokConfigMapName | default "kwok-provider-config" }} + namespace: {{ .Release.Namespace }} +data: + config: |- + # if you see '\n' everywhere, remove all the trailing spaces + apiVersion: v1alpha1 + readNodesFrom: configmap # possible values: [cluster,configmap] + nodegroups: + # to specify how to group nodes into a nodegroup + # e.g., you want to treat nodes with same instance type as a nodegroup + # node1: m5.xlarge + # node2: c5.xlarge + # node3: m5.xlarge + # nodegroup1: [node1,node3] + # nodegroup2: [node2] + fromNodeLabelKey: "kwok-nodegroup" + # you can either specify fromNodeLabelKey OR fromNodeAnnotation + # (both are not allowed) + # fromNodeAnnotation: "eks.amazonaws.com/nodegroup" + nodes: + # gpuConfig: + # # to tell kwok provider what label should be considered as GPU label + # gpuLabelKey: "k8s.amazonaws.com/accelerator" + # availableGPUTypes: + # "nvidia-tesla-k80": {} + # "nvidia-tesla-p100": {} + configmap: + name: kwok-provider-templates + kwok: {} # default: fetch latest release of kwok from github and install it + # # you can also manually specify which kwok release you want to install + # # for example: + # kwok: + # release: v0.3.0 + # # you can also disable installing kwok in CA code (and install your own kwok release) + # kwok: + # install: false (true if not specified) +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: kwok-provider-templates + namespace: {{ .Release.Namespace }} +data: + templates: |- + # if you see '\n' everywhere, remove all the trailing spaces + apiVersion: v1 + items: + - apiVersion: v1 + kind: Node + metadata: + annotations: + kubeadm.alpha.kubernetes.io/cri-socket: unix:///run/containerd/containerd.sock + node.alpha.kubernetes.io/ttl: "0" + volumes.kubernetes.io/controller-managed-attach-detach: "true" + creationTimestamp: "2023-05-31T04:39:16Z" + labels: + beta.kubernetes.io/arch: amd64 + beta.kubernetes.io/os: linux + kubernetes.io/arch: amd64 + kubernetes.io/hostname: kind-control-plane + kwok-nodegroup: control-plane + kubernetes.io/os: linux + node-role.kubernetes.io/control-plane: "" + node.kubernetes.io/exclude-from-external-load-balancers: "" + name: kind-control-plane + resourceVersion: "506" + uid: 86716ec7-3071-4091-b055-77b4361d1dca + spec: + podCIDR: 10.244.0.0/24 + podCIDRs: + - 10.244.0.0/24 + providerID: kind://docker/kind/kind-control-plane + taints: + - effect: NoSchedule + key: node-role.kubernetes.io/control-plane + status: + addresses: + - address: 172.18.0.2 + type: InternalIP + - address: kind-control-plane + type: Hostname + allocatable: + cpu: "12" + ephemeral-storage: 959786032Ki + hugepages-1Gi: "0" + hugepages-2Mi: "0" + memory: 32781516Ki + pods: "110" + capacity: + cpu: "12" + ephemeral-storage: 959786032Ki + hugepages-1Gi: "0" + hugepages-2Mi: "0" + memory: 32781516Ki + pods: "110" + conditions: + - lastHeartbeatTime: "2023-05-31T04:39:58Z" + lastTransitionTime: "2023-05-31T04:39:13Z" + message: kubelet has sufficient memory available + reason: KubeletHasSufficientMemory + status: "False" + type: MemoryPressure + - lastHeartbeatTime: "2023-05-31T04:39:58Z" + lastTransitionTime: "2023-05-31T04:39:13Z" + message: kubelet has no disk pressure + reason: KubeletHasNoDiskPressure + status: "False" + type: DiskPressure + - lastHeartbeatTime: "2023-05-31T04:39:58Z" + lastTransitionTime: "2023-05-31T04:39:13Z" + message: kubelet has sufficient PID available + reason: KubeletHasSufficientPID + status: "False" + type: PIDPressure + - lastHeartbeatTime: "2023-05-31T04:39:58Z" + lastTransitionTime: "2023-05-31T04:39:46Z" + message: kubelet is posting ready status + reason: KubeletReady + status: "True" + type: Ready + daemonEndpoints: + kubeletEndpoint: + Port: 10250 + images: + - names: + - registry.k8s.io/etcd:3.5.6-0 + sizeBytes: 102542580 + - names: + - docker.io/library/import-2023-03-30@sha256:ba097b515c8c40689733c0f19de377e9bf8995964b7d7150c2045f3dfd166657 + - registry.k8s.io/kube-apiserver:v1.26.3 + sizeBytes: 80392681 + - names: + - docker.io/library/import-2023-03-30@sha256:8dbb345de79d1c44f59a7895da702a5f71997ae72aea056609445c397b0c10dc + - registry.k8s.io/kube-controller-manager:v1.26.3 + sizeBytes: 68538487 + - names: + - docker.io/library/import-2023-03-30@sha256:44db4d50a5f9c8efbac0d37ea974d1c0419a5928f90748d3d491a041a00c20b5 + - registry.k8s.io/kube-proxy:v1.26.3 + sizeBytes: 67217404 + - names: + - docker.io/library/import-2023-03-30@sha256:3dd2337f70af979c7362b5e52bbdfcb3a5fd39c78d94d02145150cd2db86ba39 + - registry.k8s.io/kube-scheduler:v1.26.3 + sizeBytes: 57761399 + - names: + - docker.io/kindest/kindnetd:v20230330-48f316cd@sha256:c19d6362a6a928139820761475a38c24c0cf84d507b9ddf414a078cf627497af + - docker.io/kindest/kindnetd@sha256:c19d6362a6a928139820761475a38c24c0cf84d507b9ddf414a078cf627497af + sizeBytes: 27726335 + - names: + - docker.io/kindest/local-path-provisioner:v0.0.23-kind.0@sha256:f2d0a02831ff3a03cf51343226670d5060623b43a4cfc4808bd0875b2c4b9501 + sizeBytes: 18664669 + - names: + - registry.k8s.io/coredns/coredns:v1.9.3 + sizeBytes: 14837849 + - names: + - docker.io/kindest/local-path-helper:v20230330-48f316cd@sha256:135203f2441f916fb13dad1561d27f60a6f11f50ec288b01a7d2ee9947c36270 + sizeBytes: 3052037 + - names: + - registry.k8s.io/pause:3.7 + sizeBytes: 311278 + nodeInfo: + architecture: amd64 + bootID: 2d71b318-5d07-4de2-9e61-2da28cf5bbf0 + containerRuntimeVersion: containerd://1.6.19-46-g941215f49 + kernelVersion: 5.15.0-72-generic + kubeProxyVersion: v1.26.3 + kubeletVersion: v1.26.3 + machineID: 96f8c8b8c8ae4600a3654341f207586e + operatingSystem: linux + osImage: Ubuntu 22.04.2 LTS + systemUUID: 111aa932-7f99-4bef-aaf7-36aa7fb9b012 + - apiVersion: v1 + kind: Node + metadata: + annotations: + kubeadm.alpha.kubernetes.io/cri-socket: unix:///run/containerd/containerd.sock + node.alpha.kubernetes.io/ttl: "0" + volumes.kubernetes.io/controller-managed-attach-detach: "true" + creationTimestamp: "2023-05-31T04:39:57Z" + labels: + beta.kubernetes.io/arch: amd64 + beta.kubernetes.io/os: linux + kubernetes.io/arch: amd64 + kubernetes.io/hostname: kind-worker + kwok-nodegroup: kind-worker + kubernetes.io/os: linux + name: kind-worker + resourceVersion: "577" + uid: 2ac0eb71-e5cf-4708-bbbf-476e8f19842b + spec: + podCIDR: 10.244.2.0/24 + podCIDRs: + - 10.244.2.0/24 + providerID: kind://docker/kind/kind-worker + status: + addresses: + - address: 172.18.0.3 + type: InternalIP + - address: kind-worker + type: Hostname + allocatable: + cpu: "12" + ephemeral-storage: 959786032Ki + hugepages-1Gi: "0" + hugepages-2Mi: "0" + memory: 32781516Ki + pods: "110" + capacity: + cpu: "12" + ephemeral-storage: 959786032Ki + hugepages-1Gi: "0" + hugepages-2Mi: "0" + memory: 32781516Ki + pods: "110" + conditions: + - lastHeartbeatTime: "2023-05-31T04:40:17Z" + lastTransitionTime: "2023-05-31T04:39:57Z" + message: kubelet has sufficient memory available + reason: KubeletHasSufficientMemory + status: "False" + type: MemoryPressure + - lastHeartbeatTime: "2023-05-31T04:40:17Z" + lastTransitionTime: "2023-05-31T04:39:57Z" + message: kubelet has no disk pressure + reason: KubeletHasNoDiskPressure + status: "False" + type: DiskPressure + - lastHeartbeatTime: "2023-05-31T04:40:17Z" + lastTransitionTime: "2023-05-31T04:39:57Z" + message: kubelet has sufficient PID available + reason: KubeletHasSufficientPID + status: "False" + type: PIDPressure + - lastHeartbeatTime: "2023-05-31T04:40:17Z" + lastTransitionTime: "2023-05-31T04:40:05Z" + message: kubelet is posting ready status + reason: KubeletReady + status: "True" + type: Ready + daemonEndpoints: + kubeletEndpoint: + Port: 10250 + images: + - names: + - registry.k8s.io/etcd:3.5.6-0 + sizeBytes: 102542580 + - names: + - docker.io/library/import-2023-03-30@sha256:ba097b515c8c40689733c0f19de377e9bf8995964b7d7150c2045f3dfd166657 + - registry.k8s.io/kube-apiserver:v1.26.3 + sizeBytes: 80392681 + - names: + - docker.io/library/import-2023-03-30@sha256:8dbb345de79d1c44f59a7895da702a5f71997ae72aea056609445c397b0c10dc + - registry.k8s.io/kube-controller-manager:v1.26.3 + sizeBytes: 68538487 + - names: + - docker.io/library/import-2023-03-30@sha256:44db4d50a5f9c8efbac0d37ea974d1c0419a5928f90748d3d491a041a00c20b5 + - registry.k8s.io/kube-proxy:v1.26.3 + sizeBytes: 67217404 + - names: + - docker.io/library/import-2023-03-30@sha256:3dd2337f70af979c7362b5e52bbdfcb3a5fd39c78d94d02145150cd2db86ba39 + - registry.k8s.io/kube-scheduler:v1.26.3 + sizeBytes: 57761399 + - names: + - docker.io/kindest/kindnetd:v20230330-48f316cd@sha256:c19d6362a6a928139820761475a38c24c0cf84d507b9ddf414a078cf627497af + - docker.io/kindest/kindnetd@sha256:c19d6362a6a928139820761475a38c24c0cf84d507b9ddf414a078cf627497af + sizeBytes: 27726335 + - names: + - docker.io/kindest/local-path-provisioner:v0.0.23-kind.0@sha256:f2d0a02831ff3a03cf51343226670d5060623b43a4cfc4808bd0875b2c4b9501 + sizeBytes: 18664669 + - names: + - registry.k8s.io/coredns/coredns:v1.9.3 + sizeBytes: 14837849 + - names: + - docker.io/kindest/local-path-helper:v20230330-48f316cd@sha256:135203f2441f916fb13dad1561d27f60a6f11f50ec288b01a7d2ee9947c36270 + sizeBytes: 3052037 + - names: + - registry.k8s.io/pause:3.7 + sizeBytes: 311278 + nodeInfo: + architecture: amd64 + bootID: 2d71b318-5d07-4de2-9e61-2da28cf5bbf0 + containerRuntimeVersion: containerd://1.6.19-46-g941215f49 + kernelVersion: 5.15.0-72-generic + kubeProxyVersion: v1.26.3 + kubeletVersion: v1.26.3 + machineID: a98a13ff474d476294935341f1ba9816 + operatingSystem: linux + osImage: Ubuntu 22.04.2 LTS + systemUUID: 5f3c1af8-a385-4776-85e4-73d7f4252b44 + - apiVersion: v1 + kind: Node + metadata: + annotations: + kubeadm.alpha.kubernetes.io/cri-socket: unix:///run/containerd/containerd.sock + node.alpha.kubernetes.io/ttl: "0" + volumes.kubernetes.io/controller-managed-attach-detach: "true" + creationTimestamp: "2023-05-31T04:39:57Z" + labels: + beta.kubernetes.io/arch: amd64 + beta.kubernetes.io/os: linux + kubernetes.io/arch: amd64 + kubernetes.io/hostname: kind-worker2 + kwok-nodegroup: kind-worker2 + kubernetes.io/os: linux + name: kind-worker2 + resourceVersion: "578" + uid: edc7df38-feb2-4089-9955-780562bdd21e + spec: + podCIDR: 10.244.1.0/24 + podCIDRs: + - 10.244.1.0/24 + providerID: kind://docker/kind/kind-worker2 + status: + addresses: + - address: 172.18.0.4 + type: InternalIP + - address: kind-worker2 + type: Hostname + allocatable: + cpu: "12" + ephemeral-storage: 959786032Ki + hugepages-1Gi: "0" + hugepages-2Mi: "0" + memory: 32781516Ki + pods: "110" + capacity: + cpu: "12" + ephemeral-storage: 959786032Ki + hugepages-1Gi: "0" + hugepages-2Mi: "0" + memory: 32781516Ki + pods: "110" + conditions: + - lastHeartbeatTime: "2023-05-31T04:40:17Z" + lastTransitionTime: "2023-05-31T04:39:57Z" + message: kubelet has sufficient memory available + reason: KubeletHasSufficientMemory + status: "False" + type: MemoryPressure + - lastHeartbeatTime: "2023-05-31T04:40:17Z" + lastTransitionTime: "2023-05-31T04:39:57Z" + message: kubelet has no disk pressure + reason: KubeletHasNoDiskPressure + status: "False" + type: DiskPressure + - lastHeartbeatTime: "2023-05-31T04:40:17Z" + lastTransitionTime: "2023-05-31T04:39:57Z" + message: kubelet has sufficient PID available + reason: KubeletHasSufficientPID + status: "False" + type: PIDPressure + - lastHeartbeatTime: "2023-05-31T04:40:17Z" + lastTransitionTime: "2023-05-31T04:40:08Z" + message: kubelet is posting ready status + reason: KubeletReady + status: "True" + type: Ready + daemonEndpoints: + kubeletEndpoint: + Port: 10250 + images: + - names: + - registry.k8s.io/etcd:3.5.6-0 + sizeBytes: 102542580 + - names: + - docker.io/library/import-2023-03-30@sha256:ba097b515c8c40689733c0f19de377e9bf8995964b7d7150c2045f3dfd166657 + - registry.k8s.io/kube-apiserver:v1.26.3 + sizeBytes: 80392681 + - names: + - docker.io/library/import-2023-03-30@sha256:8dbb345de79d1c44f59a7895da702a5f71997ae72aea056609445c397b0c10dc + - registry.k8s.io/kube-controller-manager:v1.26.3 + sizeBytes: 68538487 + - names: + - docker.io/library/import-2023-03-30@sha256:44db4d50a5f9c8efbac0d37ea974d1c0419a5928f90748d3d491a041a00c20b5 + - registry.k8s.io/kube-proxy:v1.26.3 + sizeBytes: 67217404 + - names: + - docker.io/library/import-2023-03-30@sha256:3dd2337f70af979c7362b5e52bbdfcb3a5fd39c78d94d02145150cd2db86ba39 + - registry.k8s.io/kube-scheduler:v1.26.3 + sizeBytes: 57761399 + - names: + - docker.io/kindest/kindnetd:v20230330-48f316cd@sha256:c19d6362a6a928139820761475a38c24c0cf84d507b9ddf414a078cf627497af + - docker.io/kindest/kindnetd@sha256:c19d6362a6a928139820761475a38c24c0cf84d507b9ddf414a078cf627497af + sizeBytes: 27726335 + - names: + - docker.io/kindest/local-path-provisioner:v0.0.23-kind.0@sha256:f2d0a02831ff3a03cf51343226670d5060623b43a4cfc4808bd0875b2c4b9501 + sizeBytes: 18664669 + - names: + - registry.k8s.io/coredns/coredns:v1.9.3 + sizeBytes: 14837849 + - names: + - docker.io/kindest/local-path-helper:v20230330-48f316cd@sha256:135203f2441f916fb13dad1561d27f60a6f11f50ec288b01a7d2ee9947c36270 + sizeBytes: 3052037 + - names: + - registry.k8s.io/pause:3.7 + sizeBytes: 311278 + nodeInfo: + architecture: amd64 + bootID: 2d71b318-5d07-4de2-9e61-2da28cf5bbf0 + containerRuntimeVersion: containerd://1.6.19-46-g941215f49 + kernelVersion: 5.15.0-72-generic + kubeProxyVersion: v1.26.3 + kubeletVersion: v1.26.3 + machineID: fa9f4cd3b3a743bc867b04e44941dcb2 + operatingSystem: linux + osImage: Ubuntu 22.04.2 LTS + systemUUID: f36c0f00-8ba5-4c8c-88bc-2981c8d377b9 + kind: List + metadata: + resourceVersion: "" + + +{{- end }} diff --git a/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/deployment.yaml b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/deployment.yaml new file mode 100644 index 00000000..cb4982b3 --- /dev/null +++ b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/deployment.yaml @@ -0,0 +1,380 @@ +{{- if or ( or .Values.autoDiscovery.clusterName .Values.autoDiscovery.namespace .Values.autoDiscovery.labels ) .Values.autoscalingGroups }} +{{/* one of the above is required */}} +apiVersion: {{ template "deployment.apiVersion" . }} +kind: Deployment +metadata: + annotations: +{{ toYaml .Values.deployment.annotations | indent 4 }} + labels: +{{ include "cluster-autoscaler.labels" . | indent 4 }} + name: {{ template "cluster-autoscaler.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: + replicas: {{ .Values.replicaCount }} + revisionHistoryLimit: {{ .Values.revisionHistoryLimit }} + selector: + matchLabels: +{{- if .Values.deployment.selector }} +{{ toYaml .Values.deployment.selector | indent 6 }} +{{- else }} +{{ include "cluster-autoscaler.instance-name" . | indent 6 }} + {{- if .Values.podLabels }} +{{ toYaml .Values.podLabels | indent 6 }} + {{- end }} +{{- end }} +{{- if .Values.updateStrategy }} + strategy: + {{ toYaml .Values.updateStrategy | nindent 4 | trim }} +{{- end }} + template: + metadata: + {{- if .Values.podAnnotations }} + annotations: +{{ toYaml .Values.podAnnotations | indent 8 }} + {{- end }} + labels: +{{ include "cluster-autoscaler.instance-name" . | indent 8 }} + {{- if .Values.additionalLabels }} +{{ toYaml .Values.additionalLabels | indent 8 }} + {{- end }} + {{- if .Values.podLabels }} +{{ toYaml .Values.podLabels | indent 8 }} + {{- end }} + spec: + {{- if .Values.priorityClassName }} + priorityClassName: "{{ .Values.priorityClassName }}" + {{- end }} + {{- with .Values.dnsConfig }} + dnsConfig: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.dnsPolicy }} + dnsPolicy: "{{ .Values.dnsPolicy }}" + {{- end }} + {{- if .Values.hostNetwork }} + hostNetwork: {{ .Values.hostNetwork }} + {{- end }} + {{- with .Values.initContainers }} + initContainers: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ template "cluster-autoscaler.name" . }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: "{{ .Values.image.pullPolicy }}" + command: + - ./cluster-autoscaler + - --cloud-provider={{ .Values.cloudProvider }} + {{- if and (eq .Values.cloudProvider "clusterapi") (eq .Values.clusterAPIMode "kubeconfig-incluster") }} + - --namespace={{ .Values.clusterAPIConfigMapsNamespace | default "kube-system" }} + {{- else }} + - --namespace={{ .Release.Namespace }} + {{- end }} + {{- if .Values.autoscalingGroups }} + {{- range .Values.autoscalingGroups }} + {{- if eq $.Values.cloudProvider "hetzner" }} + - --nodes={{ .minSize }}:{{ .maxSize }}:{{ .instanceType }}:{{ .region }}:{{ .name }} + {{- else }} + - --nodes={{ .minSize }}:{{ .maxSize }}:{{ .name }} + {{- end }} + {{- end }} + {{- end }} + {{- if eq .Values.cloudProvider "rancher" }} + {{- if .Values.cloudConfigPath }} + - --cloud-config={{ .Values.cloudConfigPath }} + {{- end }} + {{- end }} + {{- if eq .Values.cloudProvider "aws" }} + {{- if .Values.autoDiscovery.clusterName }} + - --node-group-auto-discovery=asg:tag={{ tpl (join "," .Values.autoDiscovery.tags) . }} + {{- end }} + {{- if .Values.cloudConfigPath }} + - --cloud-config={{ .Values.cloudConfigPath }} + {{- end }} + {{- else if eq .Values.cloudProvider "gce" }} + {{- if .Values.autoscalingGroupsnamePrefix }} + {{- range .Values.autoscalingGroupsnamePrefix }} + - --node-group-auto-discovery=mig:namePrefix={{ tpl .name $ }},min={{ .minSize }},max={{ .maxSize }} + {{- end }} + {{- end }} + {{- if eq .Values.cloudProvider "oci" }} + {{- if .Values.cloudConfigPath }} + - --nodes={{ .minSize }}:{{ .maxSize }}:{{ .name }} + - --balance-similar-node-groups + {{- end }} + {{- end }} + {{- else if eq .Values.cloudProvider "magnum" }} + {{- if .Values.autoDiscovery.clusterName }} + - --cluster-name={{ tpl (.Values.autoDiscovery.clusterName) . }} + - --node-group-auto-discovery=magnum:role={{ tpl (join "," .Values.autoDiscovery.roles) . }} + {{- else }} + - --cluster-name={{ tpl (.Values.magnumClusterName) . }} + {{- end }} + {{- else if eq .Values.cloudProvider "clusterapi" }} + {{- if or .Values.autoDiscovery.clusterName .Values.autoDiscovery.labels .Values.autoDiscovery.namespace }} + - --node-group-auto-discovery=clusterapi:{{ template "cluster-autoscaler.capiAutodiscoveryConfig" . }} + {{- end }} + {{- if eq .Values.clusterAPIMode "incluster-kubeconfig"}} + - --cloud-config={{ .Values.clusterAPICloudConfigPath }} + {{- else if eq .Values.clusterAPIMode "kubeconfig-incluster"}} + - --kubeconfig={{ .Values.clusterAPIWorkloadKubeconfigPath }} + - --clusterapi-cloud-config-authoritative + {{- else if eq .Values.clusterAPIMode "kubeconfig-kubeconfig"}} + - --kubeconfig={{ .Values.clusterAPIWorkloadKubeconfigPath }} + - --cloud-config={{ .Values.clusterAPICloudConfigPath }} + {{- else if eq .Values.clusterAPIMode "single-kubeconfig"}} + - --kubeconfig={{ .Values.clusterAPIWorkloadKubeconfigPath }} + {{- end }} + {{- else if eq .Values.cloudProvider "azure" }} + {{- if .Values.autoDiscovery.clusterName }} + - --node-group-auto-discovery=label:cluster-autoscaler-enabled=true,cluster-autoscaler-name={{ tpl (.Values.autoDiscovery.clusterName) . }} + {{- end }} + {{- end }} + {{- if eq .Values.cloudProvider "magnum" }} + - --cloud-config={{ .Values.cloudConfigPath }} + {{- end }} + {{- range $key, $value := .Values.extraArgs }} + {{- if not (kindIs "invalid" $value) }} + - --{{ $key | mustRegexFind "^[^_]+" }}={{ $value }} + {{- else }} + - --{{ $key | mustRegexFind "^[^_]+" }} + {{- end }} + {{- end }} + {{- range .Values.customArgs }} + - {{ . }} + {{- end }} + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: SERVICE_ACCOUNT + valueFrom: + fieldRef: + fieldPath: spec.serviceAccountName + {{- if and (eq .Values.cloudProvider "aws") (ne (tpl .Values.awsRegion $) "") }} + - name: AWS_REGION + value: "{{ tpl .Values.awsRegion $ }}" + {{- if .Values.awsAccessKeyID }} + - name: AWS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + key: AwsAccessKeyId + name: {{ default (include "cluster-autoscaler.fullname" .) .Values.secretKeyRefNameOverride }} + {{- end }} + {{- if .Values.awsSecretAccessKey }} + - name: AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + key: AwsSecretAccessKey + name: {{ default (include "cluster-autoscaler.fullname" .) .Values.secretKeyRefNameOverride }} + {{- end }} + {{- else if eq .Values.cloudProvider "azure" }} + - name: ARM_SUBSCRIPTION_ID + valueFrom: + secretKeyRef: + key: SubscriptionID + name: {{ default (include "cluster-autoscaler.fullname" .) .Values.secretKeyRefNameOverride }} + - name: ARM_RESOURCE_GROUP + valueFrom: + secretKeyRef: + key: ResourceGroup + name: {{ default (include "cluster-autoscaler.fullname" .) .Values.secretKeyRefNameOverride }} + - name: ARM_VM_TYPE + valueFrom: + secretKeyRef: + key: VMType + name: {{ default (include "cluster-autoscaler.fullname" .) .Values.secretKeyRefNameOverride }} + - name: AZURE_ENABLE_FORCE_DELETE + value: "{{ .Values.azureEnableForceDelete }}" + {{- if .Values.azureUseWorkloadIdentityExtension }} + - name: ARM_USE_WORKLOAD_IDENTITY_EXTENSION + value: "true" + {{- else if .Values.azureUseManagedIdentityExtension }} + - name: ARM_USE_MANAGED_IDENTITY_EXTENSION + value: "true" + - name: ARM_USER_ASSIGNED_IDENTITY_ID + valueFrom: + secretKeyRef: + key: UserAssignedIdentityID + name: {{ template "cluster-autoscaler.fullname" . }} + {{- else }} + - name: ARM_TENANT_ID + valueFrom: + secretKeyRef: + key: TenantID + name: {{ default (include "cluster-autoscaler.fullname" .) .Values.secretKeyRefNameOverride }} + - name: ARM_CLIENT_ID + valueFrom: + secretKeyRef: + key: ClientID + name: {{ default (include "cluster-autoscaler.fullname" .) .Values.secretKeyRefNameOverride }} + - name: ARM_CLIENT_SECRET + valueFrom: + secretKeyRef: + key: ClientSecret + name: {{ default (include "cluster-autoscaler.fullname" .) .Values.secretKeyRefNameOverride }} + {{- end }} + {{- else if eq .Values.cloudProvider "exoscale" }} + - name: EXOSCALE_API_KEY + valueFrom: + secretKeyRef: + key: api-key + name: {{ default (include "cluster-autoscaler.fullname" .) .Values.secretKeyRefNameOverride }} + - name: EXOSCALE_API_SECRET + valueFrom: + secretKeyRef: + key: api-secret + name: {{ default (include "cluster-autoscaler.fullname" .) .Values.secretKeyRefNameOverride }} + - name: EXOSCALE_ZONE + valueFrom: + secretKeyRef: + key: api-zone + name: {{ default (include "cluster-autoscaler.fullname" .) .Values.secretKeyRefNameOverride }} + {{- else if eq .Values.cloudProvider "kwok" }} + - name: KWOK_PROVIDER_CONFIGMAP + value: "{{.Values.kwokConfigMapName | default "kwok-provider-config"}}" + {{- else if eq .Values.cloudProvider "civo" }} + - name: CIVO_API_URL + valueFrom: + secretKeyRef: + key: api-url + name: {{ default (include "cluster-autoscaler.fullname" .) .Values.secretKeyRefNameOverride }} + - name: CIVO_API_KEY + valueFrom: + secretKeyRef: + key: api-key + name: {{ default (include "cluster-autoscaler.fullname" .) .Values.secretKeyRefNameOverride }} + - name: CIVO_CLUSTER_ID + valueFrom: + secretKeyRef: + key: cluster-id + name: {{ default (include "cluster-autoscaler.fullname" .) .Values.secretKeyRefNameOverride }} + - name: CIVO_REGION + valueFrom: + secretKeyRef: + key: region + name: {{ default (include "cluster-autoscaler.fullname" .) .Values.secretKeyRefNameOverride }} + {{- end }} + {{- range $key, $value := .Values.extraEnv }} + - name: {{ $key }} + value: "{{ $value }}" + {{- end }} + {{- range $key, $value := .Values.extraEnvConfigMaps }} + - name: {{ $key }} + valueFrom: + configMapKeyRef: + name: {{ default (include "cluster-autoscaler.fullname" $) $value.name }} + key: {{ required "Must specify key!" $value.key }} + {{- end }} + {{- range $key, $value := .Values.extraEnvSecrets }} + - name: {{ $key }} + valueFrom: + secretKeyRef: + name: {{ default (include "cluster-autoscaler.fullname" $) $value.name }} + key: {{ required "Must specify key!" $value.key }} + {{- end }} + {{- if or .Values.envFromSecret .Values.envFromConfigMap }} + envFrom: + {{- if .Values.envFromSecret }} + - secretRef: + name: {{ .Values.envFromSecret }} + {{- end }} + {{- if .Values.envFromConfigMap }} + - configMapRef: + name: {{ .Values.envFromConfigMap }} + {{- end }} + {{- end }} + livenessProbe: + httpGet: + path: /health-check + port: 8085 + ports: + - containerPort: 8085 + resources: +{{ toYaml .Values.resources | indent 12 }} + {{- if .Values.containerSecurityContext }} + securityContext: + {{ toYaml .Values.containerSecurityContext | nindent 12 | trim }} + {{- end }} + {{- if or (eq .Values.cloudProvider "magnum") .Values.extraVolumeSecrets .Values.extraVolumeMounts .Values.clusterAPIKubeconfigSecret }} + volumeMounts: + {{- if eq .Values.cloudProvider "magnum" }} + - name: cloudconfig + mountPath: {{ .Values.cloudConfigPath }} + readOnly: true + {{- end }} + {{- if and (eq .Values.cloudProvider "magnum") (.Values.magnumCABundlePath) }} + - name: ca-bundle + mountPath: {{ .Values.magnumCABundlePath }} + readOnly: true + {{- end }} + {{- range $key, $value := .Values.extraVolumeSecrets }} + - name: {{ $key }} + mountPath: {{ required "Must specify mountPath!" $value.mountPath }} + readOnly: true + {{- end }} + {{- if .Values.clusterAPIKubeconfigSecret }} + - name: cluster-api-kubeconfig + mountPath: {{ .Values.clusterAPIWorkloadKubeconfigPath | trimSuffix "/value" }} + {{- end }} + {{- if .Values.extraVolumeMounts }} + {{- toYaml .Values.extraVolumeMounts | nindent 12 }} + {{- end }} + {{- end }} + {{- if .Values.affinity }} + affinity: +{{ toYaml .Values.affinity | indent 8 }} + {{- end }} + {{- if .Values.nodeSelector }} + nodeSelector: +{{ toYaml .Values.nodeSelector | indent 8 }} + {{- end }} + serviceAccountName: {{ template "cluster-autoscaler.serviceAccountName" . }} + tolerations: +{{ toYaml .Values.tolerations | indent 8 }} + {{- if .Values.topologySpreadConstraints }} + topologySpreadConstraints: +{{ toYaml .Values.topologySpreadConstraints | indent 8 }} + {{- end }} + {{- if .Values.securityContext }} + securityContext: + {{ toYaml .Values.securityContext | nindent 8 | trim }} + {{- end }} + {{- if or (eq .Values.cloudProvider "magnum") .Values.extraVolumeSecrets .Values.extraVolumes .Values.clusterAPIKubeconfigSecret }} + volumes: + {{- if eq .Values.cloudProvider "magnum" }} + - name: cloudconfig + hostPath: + path: {{ .Values.cloudConfigPath }} + {{- end }} + {{- if and (eq .Values.cloudProvider "magnum") (.Values.magnumCABundlePath) }} + - name: ca-bundle + hostPath: + path: {{ .Values.magnumCABundlePath }} + {{- end }} + {{- range $key, $value := .Values.extraVolumeSecrets }} + - name: {{ $key }} + secret: + secretName: {{ default (include "cluster-autoscaler.fullname" $) $value.name }} + {{- if $value.items }} + items: + {{- toYaml $value.items | nindent 14 }} + {{- end }} + {{- end }} + {{- if .Values.extraVolumes }} + {{- toYaml .Values.extraVolumes | nindent 8 }} + {{- end }} + {{- if .Values.clusterAPIKubeconfigSecret }} + - name: cluster-api-kubeconfig + secret: + secretName: {{ .Values.clusterAPIKubeconfigSecret }} + {{- end }} + {{- end }} + {{- if .Values.image.pullSecrets }} + imagePullSecrets: + {{- range .Values.image.pullSecrets }} + - name: {{ . }} + {{- end }} + {{- end }} +{{- end }} diff --git a/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/extra-manifests.yaml b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/extra-manifests.yaml new file mode 100644 index 00000000..a9bb3b6b --- /dev/null +++ b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/extra-manifests.yaml @@ -0,0 +1,4 @@ +{{ range .Values.extraObjects }} +--- +{{ tpl (toYaml .) $ }} +{{ end }} diff --git a/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/pdb.yaml b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/pdb.yaml new file mode 100644 index 00000000..0f8a69ec --- /dev/null +++ b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/pdb.yaml @@ -0,0 +1,29 @@ +{{- if .Values.podDisruptionBudget -}} +{{- if and .Values.podDisruptionBudget.minAvailable .Values.podDisruptionBudget.maxUnavailable }} + {{- fail "Only one of podDisruptionBudget.minAvailable or podDisruptionBudget.maxUnavailable should be set." }} +{{- end }}apiVersion: {{ template "podDisruptionBudget.apiVersion" . }} +kind: PodDisruptionBudget +metadata: +{{- if .Values.podDisruptionBudget.annotations }} + annotations: +{{ toYaml .Values.podDisruptionBudget.annotations | indent 4 }} +{{- end }} + labels: +{{ include "cluster-autoscaler.labels" . | indent 4 }} + name: {{ template "cluster-autoscaler.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: + selector: + matchLabels: +{{- if .Values.podDisruptionBudget.selector }} +{{ toYaml .Values.podDisruptionBudget.selector | indent 6 }} +{{- else }} +{{ include "cluster-autoscaler.instance-name" . | indent 6 }} + {{- if and .Values.podDisruptionBudget.minAvailable (not .Values.podDisruptionBudget.maxUnavailable) }} + minAvailable: {{ .Values.podDisruptionBudget.minAvailable }} + {{- end }} + {{- if and .Values.podDisruptionBudget.maxUnavailable (not .Values.podDisruptionBudget.minAvailable) }} + maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }} + {{- end }} +{{- end -}} +{{- end }} diff --git a/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/podsecuritypolicy.yaml b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/podsecuritypolicy.yaml new file mode 100644 index 00000000..e3ce5997 --- /dev/null +++ b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/podsecuritypolicy.yaml @@ -0,0 +1,42 @@ +{{- if .Values.rbac.pspEnabled }} +apiVersion: {{ template "podsecuritypolicy.apiVersion" . }} +kind: PodSecurityPolicy +metadata: + name: {{ template "cluster-autoscaler.fullname" . }} + labels: +{{ include "cluster-autoscaler.labels" . | indent 4 }} +spec: + # Prevents running in privileged mode + privileged: false + # Required to prevent escalations to root. + allowPrivilegeEscalation: false + requiredDropCapabilities: + - ALL + volumes: + - 'configMap' + - 'secret' + - 'hostPath' + - 'emptyDir' + - 'projected' + - 'downwardAPI' + hostNetwork: {{ .Values.hostNetwork }} + hostIPC: false + hostPID: false + runAsUser: + rule: RunAsAny + seLinux: + rule: RunAsAny + supplementalGroups: + rule: 'MustRunAs' + ranges: + # Forbid adding the root group. + - min: 1 + max: 65535 + fsGroup: + rule: 'MustRunAs' + ranges: + # Forbid adding the root group. + - min: 1 + max: 65535 + readOnlyRootFilesystem: false +{{- end }} diff --git a/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/priority-expander-configmap.yaml b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/priority-expander-configmap.yaml new file mode 100644 index 00000000..8259f14f --- /dev/null +++ b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/priority-expander-configmap.yaml @@ -0,0 +1,25 @@ +{{- if hasKey .Values.extraArgs "expander" }} +{{- if and (.Values.expanderPriorities) (include "cluster-autoscaler.priorityExpanderEnabled" .) -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: cluster-autoscaler-priority-expander + namespace: {{ .Release.Namespace }} + labels: +{{ include "cluster-autoscaler.labels" . | indent 4 }} + {{- if .Values.priorityConfigMapAnnotations }} + annotations: +{{ toYaml .Values.priorityConfigMapAnnotations | indent 4 }} + {{- end }} +data: + priorities: |- +{{- if kindIs "string" .Values.expanderPriorities }} +{{ .Values.expanderPriorities | indent 4 }} +{{- else }} +{{- range $k,$v := .Values.expanderPriorities }} + {{ $k | int }}: + {{- toYaml $v | nindent 6 }} +{{- end -}} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/prometheusrule.yaml b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/prometheusrule.yaml new file mode 100644 index 00000000..097c969e --- /dev/null +++ b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/prometheusrule.yaml @@ -0,0 +1,15 @@ +{{- if .Values.prometheusRule.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: {{ include "cluster-autoscaler.fullname" . }} + {{- if .Values.prometheusRule.namespace }} + namespace: {{ .Values.prometheusRule.namespace }} + {{- end }} + labels: {{- toYaml .Values.prometheusRule.additionalLabels | nindent 4 }} +spec: + groups: + - name: {{ include "cluster-autoscaler.fullname" . }} + interval: {{ .Values.prometheusRule.interval }} + rules: {{- tpl (toYaml .Values.prometheusRule.rules) . | nindent 8 }} +{{- end }} diff --git a/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/role.yaml b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/role.yaml new file mode 100644 index 00000000..80cf30a1 --- /dev/null +++ b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/role.yaml @@ -0,0 +1,96 @@ +{{- if .Values.rbac.create -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: +{{- with .Values.rbac.annotations }} + annotations: + {{- range $k, $v := . }} + {{- printf "%s: %s" (tpl $k $) (tpl $v $) | nindent 4 }} + {{- end }} +{{- end }} + labels: +{{ include "cluster-autoscaler.labels" . | indent 4 }} + name: {{ template "cluster-autoscaler.fullname" . }} + namespace: {{ .Release.Namespace }} +rules: + - apiGroups: + - "" + resources: + - configmaps + verbs: + - create +{{- if (include "cluster-autoscaler.priorityExpanderEnabled" .) }} + - list + - watch +{{- end }} + - apiGroups: + - "" + resources: + - configmaps + resourceNames: + - cluster-autoscaler-status +{{- if (include "cluster-autoscaler.priorityExpanderEnabled" .) }} + - cluster-autoscaler-priority-expander +{{- end }} + verbs: + - delete + - get + - update +{{- if (include "cluster-autoscaler.priorityExpanderEnabled" .) }} + - watch +{{- end }} +{{- if eq (default "" (index .Values.extraArgs "leader-elect-resource-lock")) "configmaps" }} + - apiGroups: + - "" + resources: + - configmaps + resourceNames: + - cluster-autoscaler + verbs: + - get + - update +{{- end }} +{{- if and ( and ( eq .Values.cloudProvider "clusterapi" ) ( not .Values.rbac.clusterScoped ) ( or ( eq .Values.clusterAPIMode "incluster-incluster" ) ( eq .Values.clusterAPIMode "kubeconfig-incluster" ) ))}} + - apiGroups: + - cluster.x-k8s.io + resources: + - machinedeployments + - machinepools + - machines + - machinesets + verbs: + - get + - list + - update + - watch + - apiGroups: + - cluster.x-k8s.io + resources: + - machinedeployments/scale + - machinepools/scale + verbs: + - get + - patch + - update +{{- end }} +{{- if ( not .Values.rbac.clusterScoped ) }} + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create + - apiGroups: + - coordination.k8s.io + resourceNames: + - cluster-autoscaler + resources: + - leases + verbs: + - get + - update +{{- end }} +{{- if .Values.rbac.additionalRules }} +{{ toYaml .Values.rbac.additionalRules | indent 2}} +{{- end }} +{{- end -}} diff --git a/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/rolebinding.yaml b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/rolebinding.yaml new file mode 100644 index 00000000..9436aabe --- /dev/null +++ b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/rolebinding.yaml @@ -0,0 +1,23 @@ +{{- if .Values.rbac.create -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: +{{- with .Values.rbac.annotations }} + annotations: + {{- range $k, $v := . }} + {{- printf "%s: %s" (tpl $k $) (tpl $v $) | nindent 4 }} + {{- end }} +{{- end }} + labels: +{{ include "cluster-autoscaler.labels" . | indent 4 }} + name: {{ template "cluster-autoscaler.fullname" . }} + namespace: {{ .Release.Namespace }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ template "cluster-autoscaler.fullname" . }} +subjects: + - kind: ServiceAccount + name: {{ template "cluster-autoscaler.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +{{- end -}} diff --git a/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/secret.yaml b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/secret.yaml new file mode 100644 index 00000000..760cc3c5 --- /dev/null +++ b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/secret.yaml @@ -0,0 +1,32 @@ +{{- if not .Values.secretKeyRefNameOverride }} +{{- $isAzure := eq .Values.cloudProvider "azure" }} +{{- $isAws := eq .Values.cloudProvider "aws" }} +{{- $awsCredentialsProvided := and .Values.awsAccessKeyID .Values.awsSecretAccessKey }} +{{- $isCivo := eq .Values.cloudProvider "civo" }} + +{{- if or $isAzure (and $isAws $awsCredentialsProvided) $isCivo }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "cluster-autoscaler.fullname" . }} + namespace: {{ .Release.Namespace }} +data: +{{- if $isAzure }} + ClientID: "{{ .Values.azureClientID | b64enc }}" + ClientSecret: "{{ .Values.azureClientSecret | b64enc }}" + ResourceGroup: "{{ .Values.azureResourceGroup | b64enc }}" + SubscriptionID: "{{ .Values.azureSubscriptionID | b64enc }}" + TenantID: "{{ .Values.azureTenantID | b64enc }}" + VMType: "{{ .Values.azureVMType | b64enc }}" + UserAssignedIdentityID: "{{ .Values.azureUserAssignedIdentityID | b64enc }}" +{{- else if $isAws }} + AwsAccessKeyId: "{{ .Values.awsAccessKeyID | b64enc }}" + AwsSecretAccessKey: "{{ .Values.awsSecretAccessKey | b64enc }}" +{{- else if $isCivo }} + api-url: "{{ .Values.civoApiUrl | b64enc }}" + api-key: "{{ .Values.civoApiKey | b64enc }}" + cluster-id: "{{ .Values.civoClusterID | b64enc }}" + region: "{{ .Values.civoRegion | b64enc }}" +{{- end }} +{{- end }} +{{- end }} diff --git a/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/service.yaml b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/service.yaml new file mode 100644 index 00000000..c8bd4079 --- /dev/null +++ b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/service.yaml @@ -0,0 +1,43 @@ +{{- if .Values.service.create }} +apiVersion: v1 +kind: Service +metadata: +{{- if .Values.service.annotations }} + annotations: +{{ toYaml .Values.service.annotations | indent 4 }} +{{- end }} + labels: +{{ include "cluster-autoscaler.labels" . | indent 4 }} +{{- if .Values.service.labels }} +{{ toYaml .Values.service.labels | indent 4 }} +{{- end }} + name: {{ template "cluster-autoscaler.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: +{{- if .Values.service.clusterIP }} + clusterIP: "{{ .Values.service.clusterIP }}" +{{- end }} +{{- if .Values.service.externalIPs }} + externalIPs: +{{ toYaml .Values.service.externalIPs | indent 4 }} +{{- end }} +{{- if .Values.service.loadBalancerIP }} + loadBalancerIP: "{{ .Values.service.loadBalancerIP }}" +{{- end }} +{{- if .Values.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: +{{ toYaml .Values.service.loadBalancerSourceRanges | indent 4 }} +{{- end }} + ports: + - port: {{ .Values.service.servicePort }} + protocol: TCP + targetPort: 8085 + name: {{ .Values.service.portName }} + selector: +{{- if .Values.service.selector }} +{{ toYaml .Values.service.selector | indent 4 }} +{{- else }} +{{ include "cluster-autoscaler.instance-name" . | indent 4 }} +{{- end }} + type: "{{ .Values.service.type }}" +{{- end }} diff --git a/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/serviceaccount.yaml b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/serviceaccount.yaml new file mode 100644 index 00000000..465b5aad --- /dev/null +++ b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/serviceaccount.yaml @@ -0,0 +1,17 @@ +{{- if and .Values.rbac.create .Values.rbac.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: +{{ include "cluster-autoscaler.labels" . | indent 4 }} + name: {{ template "cluster-autoscaler.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + +{{- with .Values.rbac.serviceAccount.annotations }} + annotations: + {{- range $k, $v := . }} + {{- printf "%s: %s" (tpl $k $) (tpl $v $) | nindent 4 }} + {{- end }} +{{- end }} +automountServiceAccountToken: {{ .Values.rbac.serviceAccount.automountServiceAccountToken }} +{{- end }} diff --git a/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/servicemonitor.yaml b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/servicemonitor.yaml new file mode 100644 index 00000000..9ce83a2e --- /dev/null +++ b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/servicemonitor.yaml @@ -0,0 +1,36 @@ +{{ if .Values.serviceMonitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "cluster-autoscaler.fullname" . }} + {{- if .Values.serviceMonitor.namespace }} + namespace: {{ .Values.serviceMonitor.namespace }} + {{- end }} + {{- if .Values.serviceMonitor.annotations }} + annotations: +{{ toYaml .Values.serviceMonitor.annotations | indent 4 }} + {{- end }} + labels: + {{- range $key, $value := .Values.serviceMonitor.selector }} + {{ $key }}: {{ $value | quote }} + {{- end }} +spec: + selector: + matchLabels: +{{ include "cluster-autoscaler.instance-name" . | indent 6 }} + endpoints: + - port: {{ .Values.service.portName }} + interval: {{ .Values.serviceMonitor.interval }} + path: {{ .Values.serviceMonitor.path }} + {{- if .Values.serviceMonitor.relabelings }} + relabelings: +{{ tpl (toYaml .Values.serviceMonitor.relabelings | indent 6) . }} + {{- end }} + {{- if .Values.serviceMonitor.metricRelabelings }} + metricRelabelings: +{{ tpl (toYaml .Values.serviceMonitor.metricRelabelings | indent 6) . }} + {{- end }} + namespaceSelector: + matchNames: + - {{.Release.Namespace}} +{{ end }} diff --git a/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/vpa.yaml b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/vpa.yaml new file mode 100644 index 00000000..560dab00 --- /dev/null +++ b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/templates/vpa.yaml @@ -0,0 +1,22 @@ +{{- if .Values.vpa.enabled -}} +apiVersion: autoscaling.k8s.io/v1 +kind: VerticalPodAutoscaler +metadata: + labels: +{{ include "cluster-autoscaler.labels" . | indent 4 }} + name: {{ template "cluster-autoscaler.fullname" . }} + namespace: {{ .Release.Namespace }} +spec: + targetRef: + apiVersion: {{ template "deployment.apiVersion" . }} + kind: Deployment + name: {{ template "cluster-autoscaler.fullname" . }} + updatePolicy: + updateMode: {{ .Values.vpa.updateMode | quote }} + recommenders: + - name: {{ .Values.vpa.recommender | quote }} + resourcePolicy: + containerPolicies: + - containerName: {{ template "cluster-autoscaler.name" . }} + {{- .Values.vpa.containerPolicy | toYaml | nindent 6 }} +{{- end -}} diff --git a/packages/system/cluster-autoscaler/charts/cluster-autoscaler/values.yaml b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/values.yaml new file mode 100644 index 00000000..9e41d35b --- /dev/null +++ b/packages/system/cluster-autoscaler/charts/cluster-autoscaler/values.yaml @@ -0,0 +1,507 @@ +## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity +# affinity -- Affinity for pod assignment +affinity: {} + +# additionalLabels -- Labels to add to each object of the chart. +additionalLabels: {} + +autoDiscovery: + # cloudProviders `aws`, `gce`, `azure`, `magnum`, `clusterapi` and `oci` are supported by auto-discovery at this time + # AWS: Set tags as described in https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/aws/README.md#auto-discovery-setup + + # autoDiscovery.clusterName -- Enable autodiscovery for `cloudProvider=aws`, for groups matching `autoDiscovery.tags`. + # autoDiscovery.clusterName -- Enable autodiscovery for `cloudProvider=azure`, using tags defined in https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/azure/README.md#auto-discovery-setup. + # Enable autodiscovery for `cloudProvider=clusterapi`, for groups matching `autoDiscovery.labels`. + # Enable autodiscovery for `cloudProvider=gce`, but no MIG tagging required. + # Enable autodiscovery for `cloudProvider=magnum`, for groups matching `autoDiscovery.roles`. + clusterName: # cluster.local + + # autoDiscovery.namespace -- Enable autodiscovery via cluster namespace for for `cloudProvider=clusterapi` + namespace: # default + + # autoDiscovery.tags -- ASG tags to match, run through `tpl`. + tags: + - k8s.io/cluster-autoscaler/enabled + - k8s.io/cluster-autoscaler/{{ .Values.autoDiscovery.clusterName }} + # - kubernetes.io/cluster/{{ .Values.autoDiscovery.clusterName }} + + # autoDiscovery.roles -- Magnum node group roles to match. + roles: + - worker + + # autoDiscovery.labels -- Cluster-API labels to match https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/clusterapi/README.md#configuring-node-group-auto-discovery + labels: [] + # - color: green + # - shape: circle +# autoscalingGroups -- For AWS, Azure AKS, Exoscale or Magnum. At least one element is required if not using `autoDiscovery`. For example: +#
+# - name: asg1
+# maxSize: 2
+# minSize: 1 +#
+# For Hetzner Cloud, the `instanceType` and `region` keys are also required. +#
+# - name: mypool
+# maxSize: 2
+# minSize: 1
+# instanceType: CPX21
+# region: FSN1 +#
+autoscalingGroups: [] +# - name: asg1 +# maxSize: 2 +# minSize: 1 +# - name: asg2 +# maxSize: 2 +# minSize: 1 + +# autoscalingGroupsnamePrefix -- For GCE. At least one element is required if not using `autoDiscovery`. For example: +#
+# - name: ig01
+# maxSize: 10
+# minSize: 0 +#
+autoscalingGroupsnamePrefix: [] +# - name: ig01 +# maxSize: 10 +# minSize: 0 +# - name: ig02 +# maxSize: 10 +# minSize: 0 + +# awsAccessKeyID -- AWS access key ID ([if AWS user keys used](https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/aws/README.md#using-aws-credentials)) +awsAccessKeyID: "" + +# awsRegion -- AWS region (required if `cloudProvider=aws`) +awsRegion: "" + +# awsSecretAccessKey -- AWS access secret key ([if AWS user keys used](https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/aws/README.md#using-aws-credentials)) +awsSecretAccessKey: "" + +# azureClientID -- Service Principal ClientID with contributor permission to Cluster and Node ResourceGroup. +# Required if `cloudProvider=azure` +azureClientID: "" + +# azureClientSecret -- Service Principal ClientSecret with contributor permission to Cluster and Node ResourceGroup. +# Required if `cloudProvider=azure` +azureClientSecret: "" + +# azureResourceGroup -- Azure resource group that the cluster is located. +# Required if `cloudProvider=azure` +azureResourceGroup: "" + +# azureSubscriptionID -- Azure subscription where the resources are located. +# Required if `cloudProvider=azure` +azureSubscriptionID: "" + +# azureTenantID -- Azure tenant where the resources are located. +# Required if `cloudProvider=azure` +azureTenantID: "" + +# azureUseManagedIdentityExtension -- Whether to use Azure's managed identity extension for credentials. If using MSI, ensure subscription ID, resource group, and azure AKS cluster name are set. You can only use one authentication method at a time, either azureUseWorkloadIdentityExtension or azureUseManagedIdentityExtension should be set. +azureUseManagedIdentityExtension: false + +# azureUserAssignedIdentityID -- When vmss has multiple user assigned identity assigned, azureUserAssignedIdentityID specifies which identity to be used +azureUserAssignedIdentityID: "" + +# azureUseWorkloadIdentityExtension -- Whether to use Azure's workload identity extension for credentials. See the project here: https://github.com/Azure/azure-workload-identity for more details. You can only use one authentication method at a time, either azureUseWorkloadIdentityExtension or azureUseManagedIdentityExtension should be set. +azureUseWorkloadIdentityExtension: false + +# azureVMType -- Azure VM type. +azureVMType: "vmss" + +# azureEnableForceDelete -- Whether to force delete VMs or VMSS instances when scaling down. +azureEnableForceDelete: false + +# civoApiUrl -- URL for the Civo API. +# Required if `cloudProvider=civo` +civoApiUrl: "https://api.civo.com" + +# civoApiKey -- API key for the Civo API. +# Required if `cloudProvider=civo` +civoApiKey: "" + +# civoClusterID -- Cluster ID for the Civo cluster. +# Required if `cloudProvider=civo` +civoClusterID: "" + +# civoRegion -- Region for the Civo cluster. +# Required if `cloudProvider=civo` +civoRegion: "" + +# cloudConfigPath -- Configuration file for cloud provider. +cloudConfigPath: "" + +# cloudProvider -- The cloud provider where the autoscaler runs. +# Currently only `gce`, `aws`, `azure`, `magnum`, `clusterapi`, `civo` and `coreweave` are supported. +# `aws` supported for AWS. `gce` for GCE. `azure` for Azure AKS. +# `magnum` for OpenStack Magnum, `clusterapi` for Cluster API. +# `civo` for Civo Cloud. +# `coreweave` for CoreWeave. +cloudProvider: aws + +# clusterAPICloudConfigPath -- Path to kubeconfig for connecting to Cluster API Management Cluster, only used if `clusterAPIMode=kubeconfig-kubeconfig or incluster-kubeconfig` +clusterAPICloudConfigPath: /etc/kubernetes/mgmt-kubeconfig + +# clusterAPIConfigMapsNamespace -- Namespace on the workload cluster to store Leader election and status configmaps +clusterAPIConfigMapsNamespace: "" + +# clusterAPIKubeconfigSecret -- Secret containing kubeconfig for connecting to Cluster API managed workloadcluster +# Required if `cloudProvider=clusterapi` and `clusterAPIMode=kubeconfig-kubeconfig,kubeconfig-incluster or incluster-kubeconfig` +clusterAPIKubeconfigSecret: "" + +# clusterAPIMode -- Cluster API mode, see https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/clusterapi/README.md#connecting-cluster-autoscaler-to-cluster-api-management-and-workload-clusters +# Syntax: workloadClusterMode-ManagementClusterMode +# for `kubeconfig-kubeconfig`, `incluster-kubeconfig` and `single-kubeconfig` you always must mount the external kubeconfig using either `extraVolumeSecrets` or `extraMounts` and `extraVolumes` +# if you dont set `clusterAPIKubeconfigSecret`and thus use an in-cluster config or want to use a non capi generated kubeconfig you must do so for the workload kubeconfig as well +clusterAPIMode: incluster-incluster # incluster-incluster, incluster-kubeconfig, kubeconfig-incluster, kubeconfig-kubeconfig, single-kubeconfig + +# clusterAPIWorkloadKubeconfigPath -- Path to kubeconfig for connecting to Cluster API managed workloadcluster, only used if `clusterAPIMode=kubeconfig-kubeconfig or kubeconfig-incluster` +clusterAPIWorkloadKubeconfigPath: /etc/kubernetes/value + +# containerSecurityContext -- [Security context for container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/) +containerSecurityContext: {} + # allowPrivilegeEscalation: false + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + +deployment: + # deployment.annotations -- Annotations to add to the Deployment object. + annotations: {} + # deployment.selector -- Labels for Deployment `spec.selector.matchLabels`. + selector: {} + +# dnsConfig -- [Pod's DNS Config](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-dns-config) +dnsConfig: {} + # nameservers: + # - 1.2.3.4 + # searches: + # - ns1.svc.cluster-domain.example + # - my.dns.search.suffix + # options: + # - name: ndots + # value: "2" + # - name: edns0 + +# dnsPolicy -- Defaults to `ClusterFirst`. Valid values are: +# `ClusterFirstWithHostNet`, `ClusterFirst`, `Default` or `None`. +# If autoscaler does not depend on cluster DNS, recommended to set this to `Default`. +dnsPolicy: ClusterFirst + +# envFromConfigMap -- ConfigMap name to use as envFrom. +envFromConfigMap: "" + +# envFromSecret -- Secret name to use as envFrom. +envFromSecret: "" + +## Priorities Expander +# expanderPriorities -- The expanderPriorities is used if `extraArgs.expander` contains `priority` and expanderPriorities is also set with the priorities. +# If `extraArgs.expander` contains `priority`, then expanderPriorities is used to define cluster-autoscaler-priority-expander priorities. +# See: https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/expander/priority/readme.md +expanderPriorities: {} + +# extraArgs -- Additional container arguments. +# Refer to https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/FAQ.md#what-are-the-parameters-to-ca for the full list of cluster autoscaler +# parameters and their default values. +# Everything after the first _ will be ignored allowing the use of multi-string arguments. +extraArgs: + logtostderr: true + stderrthreshold: info + v: 4 + # write-status-configmap: true + # status-config-map-name: cluster-autoscaler-status + # leader-elect: true + # leader-elect-resource-lock: endpoints + # skip-nodes-with-local-storage: true + # expander: random + # scale-down-enabled: true + # balance-similar-node-groups: true + # min-replica-count: 0 + # scale-down-utilization-threshold: 0.5 + # scale-down-non-empty-candidates-count: 30 + # max-node-provision-time: 15m0s + # scan-interval: 10s + # scale-down-delay-after-add: 10m + # scale-down-delay-after-delete: 0s + # scale-down-delay-after-failure: 3m + # scale-down-unneeded-time: 10m + # node-deletion-delay-timeout: 2m + # node-deletion-batcher-interval: 0s + # skip-nodes-with-system-pods: true + # balancing-ignore-label_1: first-label-to-ignore + # balancing-ignore-label_2: second-label-to-ignore + +# customArgs -- Additional custom container arguments. +# Refer to https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/FAQ.md#what-are-the-parameters-to-ca for the full list of cluster autoscaler +# parameters and their default values. +# List of arguments as strings. +customArgs: [] + +# extraEnv -- Additional container environment variables. +extraEnv: {} + +# extraEnvConfigMaps -- Additional container environment variables from ConfigMaps. +extraEnvConfigMaps: {} + +# extraEnvSecrets -- Additional container environment variables from Secrets. +extraEnvSecrets: {} + +# extraObjects -- Extra K8s manifests to deploy +extraObjects: [] +# - apiVersion: v1 +# kind: ConfigMap +# metadata: +# name: my-configmap +# data: +# key: "value" +# - apiVersion: scheduling.k8s.io/v1 +# kind: PriorityClass +# metadata: +# name: high-priority +# value: 1000000 +# globalDefault: false +# description: "This priority class should be used for XYZ service pods only." + +# extraVolumeMounts -- Additional volumes to mount. +extraVolumeMounts: [] + # - name: ssl-certs + # mountPath: /etc/ssl/certs/ca-certificates.crt + # readOnly: true + +# extraVolumes -- Additional volumes. +extraVolumes: [] + # - name: ssl-certs + # hostPath: + # path: /etc/ssl/certs/ca-bundle.crt + +# extraVolumeSecrets -- Additional volumes to mount from Secrets. +extraVolumeSecrets: {} + # autoscaler-vol: + # mountPath: /data/autoscaler/ + # custom-vol: + # name: custom-secret + # mountPath: /data/custom/ + # items: + # - key: subkey + # path: mypath + +# initContainers -- Any additional init containers. +initContainers: [] + +# fullnameOverride -- String to fully override `cluster-autoscaler.fullname` template. +fullnameOverride: "" + +# hostNetwork -- Whether to expose network interfaces of the host machine to pods. +hostNetwork: false + +image: + # image.repository -- Image repository + repository: registry.k8s.io/autoscaling/cluster-autoscaler + # image.tag -- Image tag + tag: v1.34.2 + # image.pullPolicy -- Image pull policy + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # image.pullSecrets -- Image pull secrets + pullSecrets: [] + # - myRegistrKeySecretName + +# kubeTargetVersionOverride -- Allow overriding the `.Capabilities.KubeVersion.GitVersion` check. Useful for `helm template` commands. +kubeTargetVersionOverride: "" + +# kwokConfigMapName -- configmap for configuring kwok provider +kwokConfigMapName: "kwok-provider-config" + +# magnumCABundlePath -- Path to the host's CA bundle, from `ca-file` in the cloud-config file. +magnumCABundlePath: "/etc/kubernetes/ca-bundle.crt" + +# magnumClusterName -- Cluster name or ID in Magnum. +# Required if `cloudProvider=magnum` and not setting `autoDiscovery.clusterName`. +magnumClusterName: "" + +# nameOverride -- String to partially override `cluster-autoscaler.fullname` template (will maintain the release name) +nameOverride: "" + +# nodeSelector -- Node labels for pod assignment. Ref: https://kubernetes.io/docs/user-guide/node-selection/. +nodeSelector: {} + +# podAnnotations -- Annotations to add to each pod. +podAnnotations: {} + +# podDisruptionBudget -- Pod disruption budget. +podDisruptionBudget: + # podDisruptionBudget.annotations -- Annotations to add to the PodDisruptionBudget. + annotations: {} + # podDisruptionBudget.selector -- Override labels for PodDisruptionBudget `spec.selector.matchLabels`. + selector: {} + maxUnavailable: 1 + # minAvailable: 2 + +# podLabels -- Labels to add to each pod. +podLabels: {} + +# priorityClassName -- priorityClassName +priorityClassName: "system-cluster-critical" + +# priorityConfigMapAnnotations -- Annotations to add to `cluster-autoscaler-priority-expander` ConfigMap. +priorityConfigMapAnnotations: {} + # key1: "value1" + # key2: "value2" + +## Custom PrometheusRule to be defined +## The value is evaluated as a template, so, for example, the value can depend on .Release or .Chart +## ref: https://github.com/coreos/prometheus-operator#customresourcedefinitions +prometheusRule: + # prometheusRule.enabled -- If true, creates a Prometheus Operator PrometheusRule. + enabled: false + # prometheusRule.additionalLabels -- Additional labels to be set in metadata. + additionalLabels: {} + # prometheusRule.namespace -- Namespace which Prometheus is running in. + namespace: monitoring + # prometheusRule.interval -- How often rules in the group are evaluated (falls back to `global.evaluation_interval` if not set). + interval: null + # prometheusRule.rules -- Rules spec template (see https://github.com/prometheus-operator/prometheus-operator/blob/master/Documentation/api.md#rule). + rules: [] + +rbac: + # rbac.create -- If `true`, create and use RBAC resources. + create: true + # rbac.pspEnabled -- If `true`, creates and uses RBAC resources required in the cluster with [Pod Security Policies](https://kubernetes.io/docs/concepts/policy/pod-security-policy/) enabled. + # Must be used with `rbac.create` set to `true`. + pspEnabled: false + # rbac.clusterScoped -- if set to false will only provision RBAC to alter resources in the current namespace. Most useful for Cluster-API + clusterScoped: true + # rbac.annotations -- Additional annotations to add to RBAC resources (Role/RoleBinding/ClusterRole/ClusterRoleBinding). + annotations: {} + serviceAccount: + # rbac.serviceAccount.annotations -- Additional Service Account annotations. + annotations: {} + # rbac.serviceAccount.create -- If `true` and `rbac.create` is also true, a Service Account will be created. + create: true + # rbac.serviceAccount.name -- The name of the ServiceAccount to use. If not set and create is `true`, a name is generated using the fullname template. + name: "" + # rbac.serviceAccount.automountServiceAccountToken -- Automount API credentials for a Service Account. + automountServiceAccountToken: true + # rbac.additionalRules -- Additional rules for role/clusterrole + additionalRules: [] + # - apiGroups: + # - infrastructure.cluster.x-k8s.io + # resources: + # - kubemarkmachinetemplates + # verbs: + # - get + # - list + # - watch + + +# replicaCount -- Desired number of pods +replicaCount: 1 + +# resources -- Pod resource requests and limits. +resources: {} + # limits: + # cpu: 100m + # memory: 300Mi + # requests: + # cpu: 100m + # memory: 300Mi + +# revisionHistoryLimit -- The number of revisions to keep. +revisionHistoryLimit: 10 + +# securityContext -- [Security context for pod](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/) +securityContext: {} + # runAsNonRoot: true + # runAsUser: 65534 + # runAsGroup: 65534 + # seccompProfile: + # type: RuntimeDefault + +service: + # service.create -- If `true`, a Service will be created. + create: true + # service.annotations -- Annotations to add to service + annotations: {} + # service.labels -- Labels to add to service + labels: {} + # service.externalIPs -- List of IP addresses at which the service is available. Ref: https://kubernetes.io/docs/concepts/services-networking/service/#external-ips. + externalIPs: [] + + # service.selector -- Override labels for Service `spec.selector`. + selector: {} + + # service.clusterIP -- IP address to assign to service + clusterIP: "" + + # service.loadBalancerIP -- IP address to assign to load balancer (if supported). + loadBalancerIP: "" + # service.loadBalancerSourceRanges -- List of IP CIDRs allowed access to load balancer (if supported). + loadBalancerSourceRanges: [] + # service.servicePort -- Service port to expose. + servicePort: 8085 + # service.portName -- Name for service port. + portName: http + # service.type -- Type of service to create. + type: ClusterIP + +## Are you using Prometheus Operator? +serviceMonitor: + # serviceMonitor.enabled -- If true, creates a Prometheus Operator ServiceMonitor. + enabled: false + # serviceMonitor.interval -- Interval that Prometheus scrapes Cluster Autoscaler metrics. + interval: 10s + # serviceMonitor.namespace -- Namespace which Prometheus is running in. + namespace: monitoring + ## [Prometheus Selector Label](https://github.com/helm/charts/tree/master/stable/prometheus-operator#prometheus-operator-1) + ## [Kube Prometheus Selector Label](https://github.com/helm/charts/tree/master/stable/prometheus-operator#exporters) + # serviceMonitor.selector -- Default to kube-prometheus install (CoreOS recommended), but should be set according to Prometheus install. + selector: + release: prometheus-operator + # serviceMonitor.path -- The path to scrape for metrics; autoscaler exposes `/metrics` (this is standard) + path: /metrics + # serviceMonitor.annotations -- Annotations to add to service monitor + annotations: {} + ## [RelabelConfig](https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/api.md#monitoring.coreos.com/v1.RelabelConfig) + # serviceMonitor.relabelings -- RelabelConfigs to apply to metrics before scraping. + relabelings: {} + ## [RelabelConfig](https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/api.md#monitoring.coreos.com/v1.RelabelConfig) + # serviceMonitor.metricRelabelings -- MetricRelabelConfigs to apply to samples before ingestion. + metricRelabelings: {} + +# tolerations -- List of node taints to tolerate (requires Kubernetes >= 1.6). +tolerations: [] + +# topologySpreadConstraints -- You can use topology spread constraints to control how Pods are spread across your cluster among failure-domains such as regions, zones, nodes, and other user-defined topology domains. (requires Kubernetes >= 1.19). +topologySpreadConstraints: [] + # - maxSkew: 1 + # topologyKey: topology.kubernetes.io/zone + # whenUnsatisfiable: DoNotSchedule + # labelSelector: + # matchLabels: + # app.kubernetes.io/instance: cluster-autoscaler + +# updateStrategy -- [Deployment update strategy](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy) +updateStrategy: {} + # rollingUpdate: + # maxSurge: 1 + # maxUnavailable: 0 + # type: RollingUpdate + +# vpa -- Configure a VerticalPodAutoscaler for the cluster-autoscaler Deployment. +vpa: + # vpa.enabled -- If true, creates a VerticalPodAutoscaler. + enabled: false + # vpa.updateMode -- [UpdateMode](https://github.com/kubernetes/autoscaler/blob/vertical-pod-autoscaler/v0.13.0/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1/types.go#L124) + updateMode: "Auto" + # vpa.containerPolicy -- [ContainerResourcePolicy](https://github.com/kubernetes/autoscaler/blob/vertical-pod-autoscaler/v0.13.0/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1/types.go#L159). The containerName is always set to the deployment's container name. This value is required if VPA is enabled. + containerPolicy: {} + # vpa.recommender -- Name of the VPA recommender that will provide recommendations for vertical scaling. + recommender: default + +# secretKeyRefNameOverride -- Overrides the name of the Secret to use when loading the secretKeyRef for AWS, Azure and Civo env variables +secretKeyRefNameOverride: "" diff --git a/packages/system/cluster-autoscaler/values-azure.yaml b/packages/system/cluster-autoscaler/values-azure.yaml new file mode 100644 index 00000000..7de2a0f9 --- /dev/null +++ b/packages/system/cluster-autoscaler/values-azure.yaml @@ -0,0 +1,4 @@ +cluster-autoscaler: + cloudProvider: azure + extraArgs: + leader-elect-resource-name: azure-cluster-autoscaler diff --git a/packages/system/cluster-autoscaler/values-hetzner.yaml b/packages/system/cluster-autoscaler/values-hetzner.yaml new file mode 100644 index 00000000..4d07736d --- /dev/null +++ b/packages/system/cluster-autoscaler/values-hetzner.yaml @@ -0,0 +1,4 @@ +cluster-autoscaler: + cloudProvider: hetzner + extraArgs: + leader-elect-resource-name: hetzner-cluster-autoscaler diff --git a/packages/system/cluster-autoscaler/values.yaml b/packages/system/cluster-autoscaler/values.yaml new file mode 100644 index 00000000..85eacb2d --- /dev/null +++ b/packages/system/cluster-autoscaler/values.yaml @@ -0,0 +1 @@ +cluster-autoscaler: {} From 3e0217bbbaf2327d530c5d1856c498ffeb87eef6 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Mon, 2 Feb 2026 20:13:39 +0100 Subject: [PATCH 158/889] fix(cluster-autoscaler): add RBAC rules for leader election leases Add coordination.k8s.io/leases permissions to Role via additionalRules. This fixes leader election failures in the cluster-autoscaler. Also add documentation for Hetzner Cloud setup with Talos Linux. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- .../system/cluster-autoscaler/docs/hetzner.md | 237 ++++++++++++++++++ .../cluster-autoscaler/values-azure.yaml | 12 +- .../cluster-autoscaler/values-hetzner.yaml | 12 +- 3 files changed, 257 insertions(+), 4 deletions(-) create mode 100644 packages/system/cluster-autoscaler/docs/hetzner.md diff --git a/packages/system/cluster-autoscaler/docs/hetzner.md b/packages/system/cluster-autoscaler/docs/hetzner.md new file mode 100644 index 00000000..2de9c49a --- /dev/null +++ b/packages/system/cluster-autoscaler/docs/hetzner.md @@ -0,0 +1,237 @@ +# Cluster Autoscaler for Hetzner Cloud + +This guide explains how to configure cluster-autoscaler for automatic node scaling in Hetzner Cloud with Talos Linux. + +## Prerequisites + +- Hetzner Cloud account with API token +- `hcloud` CLI installed +- Existing Talos Kubernetes cluster +- Talos worker machine config + +## Step 1: Create Talos Image in Hetzner Cloud + +Hetzner doesn't support direct image uploads, so we need to create a snapshot via a temporary server. + +### 1.1 Configure hcloud CLI + +```bash +export HCLOUD_TOKEN="" +``` + +### 1.2 Create temporary server in rescue mode + +```bash +# Create server (without starting) +hcloud server create \ + --name talos-image-builder \ + --type cpx22 \ + --image ubuntu-24.04 \ + --location fsn1 \ + --ssh-key \ + --start-after-create=false + +# Enable rescue mode and start +hcloud server enable-rescue --type linux64 --ssh-key talos-image-builder +hcloud server poweron talos-image-builder +``` + +### 1.3 Get server IP and write Talos image + +```bash +# Get server IP +SERVER_IP=$(hcloud server ip talos-image-builder) + +# SSH into rescue mode and write image +ssh root@$SERVER_IP + +# Inside rescue mode: +wget -O- "https://factory.talos.dev/image///hcloud-amd64.raw.xz" \ + | xz -d \ + | dd of=/dev/sda bs=4M status=progress +sync +exit +``` + +Get your schematic ID from https://factory.talos.dev with required extensions: +- `siderolabs/qemu-guest-agent` (required for Hetzner) +- Other extensions as needed (zfs, drbd, etc.) + +### 1.4 Create snapshot and cleanup + +```bash +# Power off and create snapshot +hcloud server poweroff talos-image-builder +hcloud server create-image --type snapshot --description "Talos v1.11.6" talos-image-builder + +# Get snapshot ID (save this for later) +hcloud image list --type snapshot + +# Delete temporary server +hcloud server delete talos-image-builder +``` + +## Step 2: Create Kubernetes Secrets + +### 2.1 Create namespace (if not exists) + +```bash +kubectl create namespace cozy-cluster-autoscaler-hetzner +``` + +### 2.2 Create secret with Hetzner API token + +```bash +kubectl -n cozy-cluster-autoscaler-hetzner create secret generic hetzner-credentials \ + --from-literal=token= +``` + +### 2.3 Create secret with Talos machine config + +The machine config must be base64-encoded for the `HCLOUD_CLOUD_INIT` environment variable. + +```bash +# Encode your worker.yaml +cat worker.yaml | base64 -w0 > worker.b64 + +# Create secret +kubectl -n cozy-cluster-autoscaler-hetzner create secret generic talos-config \ + --from-file=cloud-init=worker.b64 +``` + +## Step 3: Configure Cluster Autoscaler + +Create or update your values file for the cluster-autoscaler-hetzner package: + +```yaml +cluster-autoscaler: + cloudProvider: hetzner + + autoscalingGroups: + - name: workers-fsn1 + minSize: 0 + maxSize: 10 + instanceType: CPX21 # 3 vCPU, 4GB RAM + region: FSN1 + + extraEnv: + HCLOUD_IMAGE: "" + HCLOUD_NETWORK: "" # Optional: private network + HCLOUD_FIREWALL: "" # Optional: firewall + HCLOUD_SSH_KEY: "" # Optional: SSH key + HCLOUD_PUBLIC_IPV4: "true" + HCLOUD_PUBLIC_IPV6: "false" + + extraEnvSecrets: + HCLOUD_TOKEN: + name: hetzner-credentials + key: token + HCLOUD_CLOUD_INIT: + name: talos-config + key: cloud-init +``` + +## Step 4: Deploy + +### Via Cozystack Package + +Update the Package resource with your configuration: + +```yaml +apiVersion: cozystack.io/v1alpha1 +kind: Package +metadata: + name: cozystack.cluster-autoscaler-hetzner +spec: + variant: default + components: + cluster-autoscaler-hetzner: + values: + cluster-autoscaler: + autoscalingGroups: + - name: workers-fsn1 + minSize: 0 + maxSize: 10 + instanceType: cpx22 + region: FSN1 + extraEnv: + HCLOUD_IMAGE: "" + HCLOUD_SSH_KEY: "" + HCLOUD_PUBLIC_IPV4: "true" + HCLOUD_PUBLIC_IPV6: "false" + extraEnvSecrets: + HCLOUD_TOKEN: + name: hetzner-credentials + key: token + HCLOUD_CLOUD_INIT: + name: talos-config + key: cloud-init +``` + +Apply with: +```bash +kubectl apply -f package.yaml +``` + +### Via Helm (direct) + +```bash +helm upgrade --install cluster-autoscaler-hetzner \ + ./packages/system/cluster-autoscaler \ + -n cozy-cluster-autoscaler-hetzner \ + -f values-hetzner.yaml \ + -f my-values.yaml +``` + +## Step 5: Verify + +```bash +# Check autoscaler logs +kubectl -n cozy-cluster-autoscaler-hetzner logs -l app.kubernetes.io/name=cluster-autoscaler -f + +# Check autoscaler status +kubectl -n cozy-cluster-autoscaler-hetzner get configmap cluster-autoscaler-status -o yaml + +# Test scale-up by creating pending pods +kubectl run test-pending --image=nginx --requests='cpu=2,memory=4Gi' +``` + +## Hetzner Server Types + +| Type | vCPU | RAM | Good for | +|------|------|-----|----------| +| cpx22 | 2 | 4GB | Small workloads | +| cpx32 | 4 | 8GB | General purpose | +| cpx42 | 8 | 16GB | Medium workloads | +| cpx52 | 16 | 32GB | Large workloads | +| ccx13 | 2 dedicated | 8GB | CPU-intensive | +| ccx23 | 4 dedicated | 16GB | CPU-intensive | +| ccx33 | 8 dedicated | 32GB | CPU-intensive | +| cax11 | 2 ARM | 4GB | ARM workloads | +| cax21 | 4 ARM | 8GB | ARM workloads | + +> **Note**: Some older server types (cpx11, cpx21, etc.) may be unavailable in certain regions. + +## Hetzner Regions + +- `FSN1` - Falkenstein, Germany +- `NBG1` - Nuremberg, Germany +- `HEL1` - Helsinki, Finland +- `ASH` - Ashburn, USA +- `HIL` - Hillsboro, USA + +## Troubleshooting + +### Nodes not joining cluster + +1. Check that machine config is correct and base64-encoded +2. Verify network connectivity (private network, firewall rules) +3. Check Talos image has `qemu-guest-agent` extension + +### Scale-down not working + +Talos caches absent nodes for up to 30 minutes. Wait or restart autoscaler. + +### Image not found + +Verify snapshot ID exists: `hcloud image list --type snapshot` diff --git a/packages/system/cluster-autoscaler/values-azure.yaml b/packages/system/cluster-autoscaler/values-azure.yaml index 7de2a0f9..7720481a 100644 --- a/packages/system/cluster-autoscaler/values-azure.yaml +++ b/packages/system/cluster-autoscaler/values-azure.yaml @@ -1,4 +1,12 @@ cluster-autoscaler: cloudProvider: azure - extraArgs: - leader-elect-resource-name: azure-cluster-autoscaler + rbac: + additionalRules: + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create + - get + - update diff --git a/packages/system/cluster-autoscaler/values-hetzner.yaml b/packages/system/cluster-autoscaler/values-hetzner.yaml index 4d07736d..8e19fc53 100644 --- a/packages/system/cluster-autoscaler/values-hetzner.yaml +++ b/packages/system/cluster-autoscaler/values-hetzner.yaml @@ -1,4 +1,12 @@ cluster-autoscaler: cloudProvider: hetzner - extraArgs: - leader-elect-resource-name: hetzner-cluster-autoscaler + rbac: + additionalRules: + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create + - get + - update From 48a61bbae8218dd3e7121f548dd0e091ac004d71 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Mon, 2 Feb 2026 22:03:26 +0100 Subject: [PATCH 159/889] docs(cluster-autoscaler): add comprehensive Hetzner setup guide Add detailed documentation for setting up cluster-autoscaler with Hetzner Cloud and Talos Linux, including: - Talos image creation via rescue mode - vSwitch (private network) configuration - Correct Talos machine config structure for nodeLabels and nodeIP - Package deployment with RBAC rules for leader election - Testing with pod anti-affinity - Configuration reference tables (env vars, server types, regions) - Troubleshooting section for common issues - Kilo mesh networking integration Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- .../system/cluster-autoscaler/docs/hetzner.md | 278 +++++++++++++----- 1 file changed, 207 insertions(+), 71 deletions(-) diff --git a/packages/system/cluster-autoscaler/docs/hetzner.md b/packages/system/cluster-autoscaler/docs/hetzner.md index 2de9c49a..c7f0b55d 100644 --- a/packages/system/cluster-autoscaler/docs/hetzner.md +++ b/packages/system/cluster-autoscaler/docs/hetzner.md @@ -71,71 +71,97 @@ hcloud image list --type snapshot hcloud server delete talos-image-builder ``` -## Step 2: Create Kubernetes Secrets +## Step 2: Create Hetzner vSwitch (Optional but Recommended) -### 2.1 Create namespace (if not exists) +Create a private network for communication between nodes: ```bash -kubectl create namespace cozy-cluster-autoscaler-hetzner +# Create network +hcloud network create --name cozystack-vswitch --ip-range 10.100.0.0/16 + +# Add subnet for your region (eu-central covers FSN1, NBG1) +hcloud network add-subnet cozystack-vswitch \ + --type cloud \ + --network-zone eu-central \ + --ip-range 10.100.0.0/24 ``` -### 2.2 Create secret with Hetzner API token +## Step 3: Create Talos Machine Config + +Create a worker machine config for autoscaled nodes. Important fields: + +```yaml +version: v1alpha1 +machine: + type: worker + token: + ca: + crt: + # Node labels (applied automatically on join) + nodeLabels: + kilo.squat.ai/location: hetzner-cloud + kubelet: + image: ghcr.io/siderolabs/kubelet:v1.33.1 + # Use vSwitch IP as internal IP + nodeIP: + validSubnets: + - 10.100.0.0/24 + # Required for external cloud provider + extraArgs: + cloud-provider: external + extraConfig: + maxPods: 512 + defaultRuntimeSeccompProfileEnabled: true + disableManifestsDirectory: true + # Registry mirrors (recommended to avoid rate limiting) + registries: + mirrors: + docker.io: + endpoints: + - https://mirror.gcr.io +cluster: + controlPlane: + endpoint: https://:6443 + clusterName: + network: + cni: + name: none + podSubnets: + - 10.244.0.0/16 + serviceSubnets: + - 10.96.0.0/16 + token: + ca: + crt: +``` + +> **Important**: Ensure kubelet version matches your cluster version. Talos 1.11.6 doesn't support Kubernetes 1.35+. + +## Step 4: Create Kubernetes Secrets + +### 4.1 Create secret with Hetzner API token ```bash kubectl -n cozy-cluster-autoscaler-hetzner create secret generic hetzner-credentials \ --from-literal=token= ``` -### 2.3 Create secret with Talos machine config +### 4.2 Create secret with Talos machine config -The machine config must be base64-encoded for the `HCLOUD_CLOUD_INIT` environment variable. +The machine config must be base64-encoded: ```bash -# Encode your worker.yaml -cat worker.yaml | base64 -w0 > worker.b64 +# Encode your worker.yaml (single line base64) +base64 -i worker.yaml -o worker.b64 # Create secret kubectl -n cozy-cluster-autoscaler-hetzner create secret generic talos-config \ --from-file=cloud-init=worker.b64 ``` -## Step 3: Configure Cluster Autoscaler +## Step 5: Deploy Cluster Autoscaler -Create or update your values file for the cluster-autoscaler-hetzner package: - -```yaml -cluster-autoscaler: - cloudProvider: hetzner - - autoscalingGroups: - - name: workers-fsn1 - minSize: 0 - maxSize: 10 - instanceType: CPX21 # 3 vCPU, 4GB RAM - region: FSN1 - - extraEnv: - HCLOUD_IMAGE: "" - HCLOUD_NETWORK: "" # Optional: private network - HCLOUD_FIREWALL: "" # Optional: firewall - HCLOUD_SSH_KEY: "" # Optional: SSH key - HCLOUD_PUBLIC_IPV4: "true" - HCLOUD_PUBLIC_IPV6: "false" - - extraEnvSecrets: - HCLOUD_TOKEN: - name: hetzner-credentials - key: token - HCLOUD_CLOUD_INIT: - name: talos-config - key: cloud-init -``` - -## Step 4: Deploy - -### Via Cozystack Package - -Update the Package resource with your configuration: +Create the Package resource: ```yaml apiVersion: cozystack.io/v1alpha1 @@ -157,6 +183,7 @@ spec: extraEnv: HCLOUD_IMAGE: "" HCLOUD_SSH_KEY: "" + HCLOUD_NETWORK: "cozystack-vswitch" HCLOUD_PUBLIC_IPV4: "true" HCLOUD_PUBLIC_IPV6: "false" extraEnvSecrets: @@ -166,37 +193,93 @@ spec: HCLOUD_CLOUD_INIT: name: talos-config key: cloud-init + rbac: + additionalRules: + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create + - get + - update ``` -Apply with: +Apply: ```bash kubectl apply -f package.yaml ``` -### Via Helm (direct) +## Step 6: Test Autoscaling -```bash -helm upgrade --install cluster-autoscaler-hetzner \ - ./packages/system/cluster-autoscaler \ - -n cozy-cluster-autoscaler-hetzner \ - -f values-hetzner.yaml \ - -f my-values.yaml +Create a deployment with pod anti-affinity to force scale-up: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: test-autoscaler +spec: + replicas: 5 + selector: + matchLabels: + app: test-autoscaler + template: + metadata: + labels: + app: test-autoscaler + spec: + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: + app: test-autoscaler + topologyKey: kubernetes.io/hostname + containers: + - name: nginx + image: nginx + resources: + requests: + cpu: "100m" + memory: "128Mi" ``` -## Step 5: Verify +If you have fewer nodes than replicas, the autoscaler will create new Hetzner servers. + +## Step 7: Verify ```bash # Check autoscaler logs -kubectl -n cozy-cluster-autoscaler-hetzner logs -l app.kubernetes.io/name=cluster-autoscaler -f +kubectl -n cozy-cluster-autoscaler-hetzner logs deployment/cluster-autoscaler-hetzner-hetzner-cluster-autoscaler -f -# Check autoscaler status -kubectl -n cozy-cluster-autoscaler-hetzner get configmap cluster-autoscaler-status -o yaml +# Check nodes +kubectl get nodes -o wide -# Test scale-up by creating pending pods -kubectl run test-pending --image=nginx --requests='cpu=2,memory=4Gi' +# Verify node labels and internal IP +kubectl get node --show-labels ``` -## Hetzner Server Types +Expected result for autoscaled nodes: +- Internal IP from vSwitch range (e.g., 10.100.0.2) +- Label `kilo.squat.ai/location=hetzner-cloud` + +## Configuration Reference + +### Environment Variables + +| Variable | Description | Required | +|----------|-------------|----------| +| `HCLOUD_TOKEN` | Hetzner API token | Yes | +| `HCLOUD_IMAGE` | Talos snapshot ID | Yes | +| `HCLOUD_CLOUD_INIT` | Base64-encoded machine config | Yes | +| `HCLOUD_NETWORK` | vSwitch network name/ID | No | +| `HCLOUD_SSH_KEY` | SSH key name/ID | No | +| `HCLOUD_FIREWALL` | Firewall name/ID | No | +| `HCLOUD_PUBLIC_IPV4` | Assign public IPv4 | No (default: true) | +| `HCLOUD_PUBLIC_IPV6` | Assign public IPv6 | No (default: false) | + +### Hetzner Server Types | Type | vCPU | RAM | Good for | |------|------|-----|----------| @@ -212,26 +295,79 @@ kubectl run test-pending --image=nginx --requests='cpu=2,memory=4Gi' > **Note**: Some older server types (cpx11, cpx21, etc.) may be unavailable in certain regions. -## Hetzner Regions +### Hetzner Regions -- `FSN1` - Falkenstein, Germany -- `NBG1` - Nuremberg, Germany -- `HEL1` - Helsinki, Finland -- `ASH` - Ashburn, USA -- `HIL` - Hillsboro, USA +| Code | Location | +|------|----------| +| FSN1 | Falkenstein, Germany | +| NBG1 | Nuremberg, Germany | +| HEL1 | Helsinki, Finland | +| ASH | Ashburn, USA | +| HIL | Hillsboro, USA | ## Troubleshooting ### Nodes not joining cluster -1. Check that machine config is correct and base64-encoded -2. Verify network connectivity (private network, firewall rules) -3. Check Talos image has `qemu-guest-agent` extension +1. Check VNC console via Hetzner Cloud Console or: + ```bash + hcloud server request-console + ``` +2. Common errors: + - **"unknown keys found during decoding"**: Check Talos config format. `nodeLabels` goes under `machine`, `nodeIP` goes under `machine.kubelet` + - **"kubelet image is not valid"**: Kubernetes version mismatch. Use kubelet version compatible with your Talos version + - **"failed to load config"**: Machine config syntax error + +### Nodes have wrong Internal IP + +Ensure `machine.kubelet.nodeIP.validSubnets` is set to your vSwitch subnet: +```yaml +machine: + kubelet: + nodeIP: + validSubnets: + - 10.100.0.0/24 +``` + +### Scale-up not triggered + +1. Check autoscaler logs for errors +2. Verify RBAC permissions (leases access required) +3. Check if pods are actually pending: + ```bash + kubectl get pods --field-selector=status.phase=Pending + ``` + +### Registry rate limiting (403 errors) + +Add registry mirrors to Talos config: +```yaml +machine: + registries: + mirrors: + docker.io: + endpoints: + - https://mirror.gcr.io + registry.k8s.io: + endpoints: + - https://registry.k8s.io +``` ### Scale-down not working -Talos caches absent nodes for up to 30 minutes. Wait or restart autoscaler. +Talos caches absent nodes for up to 30 minutes. Wait or restart autoscaler: +```bash +kubectl -n cozy-cluster-autoscaler-hetzner rollout restart deployment cluster-autoscaler-hetzner-hetzner-cluster-autoscaler +``` -### Image not found +## Integration with Kilo -Verify snapshot ID exists: `hcloud image list --type snapshot` +For multi-location clusters using Kilo mesh networking, add location label to machine config: + +```yaml +machine: + nodeLabels: + kilo.squat.ai/location: hetzner-cloud +``` + +This allows Kilo to create proper WireGuard tunnels between your bare-metal nodes and Hetzner Cloud nodes. From 337ee88170797584c833c6f135740aa802e6d9ed Mon Sep 17 00:00:00 2001 From: Andrey Kolkov Date: Tue, 27 Jan 2026 11:21:29 +0400 Subject: [PATCH 160/889] feat(backups): updated dashboard for backupClass Signed-off-by: Andrey Kolkov --- .../controller/dashboard/static_refactored.go | 65 +++++++++++++------ 1 file changed, 44 insertions(+), 21 deletions(-) diff --git a/internal/controller/dashboard/static_refactored.go b/internal/controller/dashboard/static_refactored.go index b2ace34b..4b244396 100644 --- a/internal/controller/dashboard/static_refactored.go +++ b/internal/controller/dashboard/static_refactored.go @@ -495,6 +495,27 @@ func CreateAllCustomFormsOverrides() []*dashboardv1alpha1.CustomFormsOverride { createFormItem("spec.ports", "Ports", "array"), }, }), + + // Plans form override - backups.cozystack.io/v1alpha1 + createCustomFormsOverride("default-/backups.cozystack.io/v1alpha1/plans", map[string]any{ + "formItems": []any{ + createFormItem("metadata.name", "Name", "text"), + createFormItem("metadata.namespace", "Namespace", "text"), + createFormItem("spec.applicationRef.kind", "Application Kind", "text"), + createFormItem("spec.applicationRef.name", "Application Name", "text"), + createFormItemWithAPI("spec.backupClassName", "Backup Class", "select", map[string]any{ + "api": map[string]any{ + "fetchUrl": "/api/clusters/{clusterName}/k8s/apis/backups.cozystack.io/v1alpha1/backupclasses", + "pathToItems": []any{"items"}, + "pathToValue": []any{"metadata", "name"}, + "pathToLabel": []any{"metadata", "name"}, + "clusterNameVar": "clusterName", + }, + }), + createFormItem("spec.schedule.type", "Schedule Type", "text"), + createFormItem("spec.schedule.cron", "Schedule Cron", "text"), + }, + }), } } @@ -1556,13 +1577,9 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory { 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-backup-class-name-block", 4, []any{ + antdText("backup-class-name-label", true, "Backup Class", nil), + parsedText("backup-class-name-value", "{reqsJsonPath[0]['.spec.backupClassName']['-']}", nil), }), antdFlexVertical("spec-schedule-type-block", 4, []any{ antdText("schedule-type-label", true, "Schedule Type", nil), @@ -1680,13 +1697,9 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory { 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("spec-backup-class-name-block", 4, []any{ + antdText("backup-class-name-label", true, "Backup Class", nil), + parsedText("backup-class-name-value", "{reqsJsonPath[0]['.spec.backupClassName']['-']}", nil), }), antdFlexVertical("status-backup-ref-block", 4, []any{ antdText("backup-ref-label", true, "Backup Ref", nil), @@ -1864,13 +1877,9 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory { 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-backup-class-name-block", 4, []any{ + antdText("backup-class-name-label", true, "Backup Class", nil), + parsedText("backup-class-name-value", "{reqsJsonPath[0]['.spec.backupClassName']['-']}", nil), }), antdFlexVertical("status-artifact-uri-block", 4, []any{ antdText("artifact-uri-label", true, "Artifact URI", nil), @@ -2070,6 +2079,20 @@ func createFormItem(path, label, fieldType string) map[string]any { } } +// createFormItemWithAPI creates a form item with API endpoint for resource-based selects +func createFormItemWithAPI(path, label, fieldType string, apiConfig map[string]any) map[string]any { + item := map[string]any{ + "path": path, + "label": label, + "type": fieldType, + } + // Merge API configuration into the form item + for key, value := range apiConfig { + item[key] = value + } + return item +} + // ---------------- Workloadmonitor specific functions ---------------- // createNamespaceHeader creates a header specifically for namespace with correct colors and text From 919e70d184080a6998266db70f8b31724554a1f7 Mon Sep 17 00:00:00 2001 From: Andrey Kolkov Date: Tue, 27 Jan 2026 12:04:35 +0400 Subject: [PATCH 161/889] add manifests to pkgs Signed-off-by: Andrey Kolkov --- .../backup-controller/templates/class.yaml | 14 ++++++++ .../cozyrds/backup-strategy.yaml | 32 +++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 packages/system/backup-controller/templates/class.yaml create mode 100644 packages/system/virtual-machine-rd/cozyrds/backup-strategy.yaml diff --git a/packages/system/backup-controller/templates/class.yaml b/packages/system/backup-controller/templates/class.yaml new file mode 100644 index 00000000..cb3164f2 --- /dev/null +++ b/packages/system/backup-controller/templates/class.yaml @@ -0,0 +1,14 @@ +kind: BackupClass +metadata: + name: velero +spec: + strategies: + - strategyRef: + apiGroup: strategy.backups.cozystack.io + kind: Velero + name: velero-backup-strategy-for-virtualmachines + application: + apiVersion: apps.cozystack.io + kind: VirtualMachine + parameters: + backupStorageLocationName: default diff --git a/packages/system/virtual-machine-rd/cozyrds/backup-strategy.yaml b/packages/system/virtual-machine-rd/cozyrds/backup-strategy.yaml new file mode 100644 index 00000000..1312540b --- /dev/null +++ b/packages/system/virtual-machine-rd/cozyrds/backup-strategy.yaml @@ -0,0 +1,32 @@ +apiVersion: strategy.backups.cozystack.io/v1alpha1 +kind: Velero +metadata: + name: velero-backup-strategy-for-virtualmachines +spec: + backupTemplate: + spec: # see https://velero.io/docs/v1.9/api-types/backup/ + includedNamespaces: + - {{ .metadata.namespace }} + + labelSelector: + apps.cozystack.io/application.Kind: {{ .kind }} + + includedResources: + - helmreleases.helm.toolkit.fluxcd.io + - virtualmachines.kubevirt.io + - virtualmachineinstances.kubevirt.io + - datavolumes.cdi.kubevirt.io + - persistentvolumeclaims + - services + - configmaps + - secrets + + storageLocation: {{ .Parameters.backupStorageLocationName }} + + volumeSnapshotLocations: + - {{ .Parameters.backupStorageLocationName }} + snapshotVolumes: true + snapshotMoveData: true + + ttl: 720h0m0s + itemOperationTimeout: 24h0m0s From 3cdf031da62d1a012638eec42599e025083dfab4 Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Tue, 3 Feb 2026 22:57:22 +0300 Subject: [PATCH 162/889] Update codeowners Signed-off-by: Timofei Larkin --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1530cb1c..4b70683c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @kvaps @lllamnyp @nbykov0 +* @kvaps @lllamnyp @lexfrei @androndo From 3383499e918809fdfa937e717cb60b21d4c33f6e Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 3 Feb 2026 23:52:54 +0100 Subject: [PATCH 163/889] fix(bootbox): auto-create bootbox-application as dependency - Remove duplicate bootbox-system component from bootbox-application PackageSource (was duplicating system/bootbox installation) - Auto-create cozystack.bootbox-application Package when bootbox is enabled in bundles.enabledPackages - This ensures bootbox-rd (ApplicationDefinition) is properly installed as a dependency before cozystack.bootbox Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- packages/core/platform/sources/bootbox-application.yaml | 5 ----- packages/core/platform/templates/bundles/system.yaml | 5 ++++- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/core/platform/sources/bootbox-application.yaml b/packages/core/platform/sources/bootbox-application.yaml index a1c8a481..ee640176 100644 --- a/packages/core/platform/sources/bootbox-application.yaml +++ b/packages/core/platform/sources/bootbox-application.yaml @@ -17,11 +17,6 @@ spec: - name: cozy-lib path: library/cozy-lib components: - - name: bootbox-system - path: system/bootbox - install: - releaseName: bootbox-application - namespace: cozy-system - name: bootbox path: extra/bootbox libraries: ["cozy-lib"] diff --git a/packages/core/platform/templates/bundles/system.yaml b/packages/core/platform/templates/bundles/system.yaml index 1ee7002e..faab22eb 100644 --- a/packages/core/platform/templates/bundles/system.yaml +++ b/packages/core/platform/templates/bundles/system.yaml @@ -149,7 +149,10 @@ {{include "cozystack.platform.package.optional.default" (list "cozystack.telepresence" $) }} {{include "cozystack.platform.package.optional.default" (list "cozystack.external-dns" $) }} {{include "cozystack.platform.package.optional.default" (list "cozystack.external-secrets-operator" $) }} -{{include "cozystack.platform.package.optional.default" (list "cozystack.bootbox" $) }} +{{- if has "cozystack.bootbox" (default (list) .Values.bundles.enabledPackages) }} +{{include "cozystack.platform.package.default" (list "cozystack.bootbox-application" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.bootbox" $) }} +{{- end }} {{include "cozystack.platform.package.optional.default" (list "cozystack.hetzner-robotlb" $) }} {{- end }} From e31b559e677216d22d816abb17452fd929e33117 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Wed, 4 Feb 2026 00:14:24 +0100 Subject: [PATCH 164/889] fix(migrations): remove legacy installer in migration 23 Add cleanup of old installer components during migration: - Delete cozystack deployment - Delete cozystack-assets statefulset This ensures no conflict between old installer and new operator when upgrading to v1.0 architecture. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- packages/core/platform/images/migrations/migrations/23 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/core/platform/images/migrations/migrations/23 b/packages/core/platform/images/migrations/migrations/23 index 4e8c8e18..49eab01b 100755 --- a/packages/core/platform/images/migrations/migrations/23 +++ b/packages/core/platform/images/migrations/migrations/23 @@ -6,6 +6,10 @@ set -euo pipefail # Remove old cozystack-resource-definition-crd HelmRelease (renamed to application-definition-crd) kubectl delete hr -n cozy-system cozystack-resource-definition-crd --ignore-not-found +# Remove legacy installer components +kubectl delete deploy cozystack -n cozy-system --ignore-not-found +kubectl delete sts cozystack-assets -n cozy-system --ignore-not-found + # Stamp version kubectl create configmap -n cozy-system cozystack-version \ --from-literal=version=24 --dry-run=client -o yaml | kubectl apply -f- From 627fc1bc8697f138392d69766bb5b53a012aa0c7 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Wed, 4 Feb 2026 00:25:37 +0100 Subject: [PATCH 165/889] fix(build): fix platform migrations image build - Fix docker build context for migrations image - Fix yq flags for compatibility with Mike Farah's yq v4 - Add platform image to main build target Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- Makefile | 1 + packages/core/platform/Makefile | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index c66f8c03..53d4f82b 100644 --- a/Makefile +++ b/Makefile @@ -33,6 +33,7 @@ build: build-deps make -C packages/system/grafana-operator image make -C packages/core/testing image make -C packages/core/talos image + make -C packages/core/platform image make -C packages/core/installer image make manifests diff --git a/packages/core/platform/Makefile b/packages/core/platform/Makefile index 7a53efa7..e47af225 100644 --- a/packages/core/platform/Makefile +++ b/packages/core/platform/Makefile @@ -18,12 +18,12 @@ diff: image: image-migrations image-migrations: - docker buildx build --file images/migrations/Dockerfile . \ + docker buildx build images/migrations \ --tag $(REGISTRY)/platform-migrations:$(call settag,$(TAG)) \ --cache-from type=registry,ref=$(REGISTRY)/platform-migrations:latest \ --cache-to type=inline \ --metadata-file images/migrations.json \ $(BUILDX_ARGS) - IMAGE="$(REGISTRY)/platform-migrations:$(call settag,$(TAG))@$$(yq --exit-status '.["containerimage.digest"]' images/migrations.json --output-format json --raw-output)" \ + IMAGE="$(REGISTRY)/platform-migrations:$(call settag,$(TAG))@$$(yq -e -o json -r '.["containerimage.digest"]' images/migrations.json)" \ yq --inplace '.migrations.image = strenv(IMAGE)' values.yaml rm -f images/migrations.json From 277b516fa2212232657d100d78812960dd264dbc Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Wed, 4 Feb 2026 00:50:05 +0100 Subject: [PATCH 166/889] fix(migrations): move legacy cleanup to migration 25 Migration 23 was already executed on existing clusters, so the legacy installer removal never ran. This moves the cleanup to a new migration 25 which will execute during upgrade. Changes: - Revert migration 23 to original state (only removes cozystack-resource-definition-crd) - Add migration 25 for v1.0 upgrade cleanup: - Remove legacy cozystack installer deployment - Remove legacy cozystack-assets statefulset - Remove bootbox and bootbox-rd HelmReleases - Update targetVersion from 24 to 26 - Fix jq syntax error in migrate-to-version-1.0.sh Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- hack/migrate-to-version-1.0.sh | 2 +- .../platform/images/migrations/migrations/23 | 4 ---- .../platform/images/migrations/migrations/25 | 23 +++++++++++++++++++ packages/core/platform/values.yaml | 4 ++-- 4 files changed, 26 insertions(+), 7 deletions(-) create mode 100755 packages/core/platform/images/migrations/migrations/25 diff --git a/hack/migrate-to-version-1.0.sh b/hack/migrate-to-version-1.0.sh index 743aaf30..3c0c64f8 100755 --- a/hack/migrate-to-version-1.0.sh +++ b/hack/migrate-to-version-1.0.sh @@ -94,7 +94,7 @@ else fi # Extract scheduling if available -SCHEDULING_CONSTRAINTS=$(echo "$SCHEDULING_CM" | jq -r '.data.["globalAppTopologySpreadConstraints"] // ""') +SCHEDULING_CONSTRAINTS=$(echo "$SCHEDULING_CM" | jq -r '.data["globalAppTopologySpreadConstraints"] // ""') if [ -z "$SCHEDULING_CONSTRAINTS" ]; then SCHEDULING_CONSTRAINTS='""' else diff --git a/packages/core/platform/images/migrations/migrations/23 b/packages/core/platform/images/migrations/migrations/23 index 49eab01b..4e8c8e18 100755 --- a/packages/core/platform/images/migrations/migrations/23 +++ b/packages/core/platform/images/migrations/migrations/23 @@ -6,10 +6,6 @@ set -euo pipefail # Remove old cozystack-resource-definition-crd HelmRelease (renamed to application-definition-crd) kubectl delete hr -n cozy-system cozystack-resource-definition-crd --ignore-not-found -# Remove legacy installer components -kubectl delete deploy cozystack -n cozy-system --ignore-not-found -kubectl delete sts cozystack-assets -n cozy-system --ignore-not-found - # Stamp version kubectl create configmap -n cozy-system cozystack-version \ --from-literal=version=24 --dry-run=client -o yaml | kubectl apply -f- diff --git a/packages/core/platform/images/migrations/migrations/25 b/packages/core/platform/images/migrations/migrations/25 new file mode 100755 index 00000000..655cce23 --- /dev/null +++ b/packages/core/platform/images/migrations/migrations/25 @@ -0,0 +1,23 @@ +#!/bin/sh +# Migration 25 --> 26 +# Remove legacy installer components during upgrade to v1.0 + +set -euo pipefail + +echo "Removing legacy installer components..." + +# Delete old cozystack installer deployment +kubectl delete deploy cozystack -n cozy-system --ignore-not-found + +# Delete old cozystack-assets statefulset +kubectl delete sts cozystack-assets -n cozy-system --ignore-not-found + +# Delete old bootbox HelmReleases (will be recreated by new platform chart if enabled) +kubectl delete hr bootbox -n cozy-system --ignore-not-found +kubectl delete hr bootbox-rd -n cozy-system --ignore-not-found + +echo "Legacy cleanup completed" + +# Stamp version +kubectl create configmap -n cozy-system cozystack-version \ + --from-literal=version=26 --dry-run=client -o yaml | kubectl apply -f- diff --git a/packages/core/platform/values.yaml b/packages/core/platform/values.yaml index f7fec687..55e56e62 100644 --- a/packages/core/platform/values.yaml +++ b/packages/core/platform/values.yaml @@ -5,8 +5,8 @@ sourceRef: path: / migrations: enabled: false - image: ghcr.io/cozystack/cozystack/platform-migrations:latest - targetVersion: 24 + image: ghcr.io/cozystack/cozystack/platform-migrations:latest@sha256:24507a6004ac46e6690327e9063a7b73b87e60d5c740ec680bffa1d0895ab4c8 + targetVersion: 26 # Bundle deployment configuration bundles: system: From 59c3b7eb298f55f010e273b28fb6e1943c8f1fc3 Mon Sep 17 00:00:00 2001 From: IvanHunters Date: Thu, 29 Jan 2026 15:19:17 +0300 Subject: [PATCH 167/889] move monitoring chart from extra to system package Signed-off-by: IvanHunters --- packages/extra/monitoring/charts/cozy-lib | 1 - .../monitoring/templates/helmrelease.yaml | 26 +++ .../{extra => system}/monitoring/.helmignore | 0 .../{extra => system}/monitoring/Chart.yaml | 0 .../{extra => system}/monitoring/Makefile | 0 .../{extra => system}/monitoring/README.md | 0 .../monitoring/charts/cozy-lib/.helmignore | 23 ++ .../monitoring/charts/cozy-lib/Chart.yaml | 5 + .../monitoring/charts/cozy-lib/Makefile | 8 + .../monitoring/charts/cozy-lib/README.md | 1 + .../charts/cozy-lib/templates/_checkinput.tpl | 5 + .../charts/cozy-lib/templates/_cozyconfig.tpl | 130 +++++++++++ .../charts/cozy-lib/templates/_network.tpl | 23 ++ .../charts/cozy-lib/templates/_rbac.tpl | 106 +++++++++ .../cozy-lib/templates/_resourcepresets.tpl | 50 ++++ .../charts/cozy-lib/templates/_resources.tpl | 219 ++++++++++++++++++ .../charts/cozy-lib/values.schema.json | 5 + .../monitoring/charts/cozy-lib/values.yaml | 1 + .../monitoring/dashboards.list | 0 .../monitoring/images/grafana.tag | 0 .../monitoring/images/grafana/Dockerfile | 0 .../monitoring/logos/monitoring.svg | 0 .../monitoring/patches/1.diff | 0 .../templates/alerta/alerta-db.yaml | 0 .../monitoring/templates/alerta/alerta.yaml | 0 .../templates/check-release-name.yaml | 0 .../templates/dashboard-resourcemap.yaml | 0 .../monitoring/templates/dashboards.yaml | 0 .../monitoring/templates/grafana/db.yaml | 0 .../monitoring/templates/grafana/grafana.yaml | 0 .../monitoring/templates/grafana/secret.yaml | 0 .../templates/vlogs/grafana-datasource.yaml | 0 .../monitoring/templates/vlogs/vlogs.yaml | 0 .../templates/vm/grafana-datasource.yaml | 0 .../monitoring/templates/vm/vmagent.yaml | 0 .../templates/vm/vmalert-scrape.yaml | 0 .../monitoring/templates/vm/vmalert.yaml | 0 .../templates/vm/vmcluster-scrape.yaml | 0 .../monitoring/templates/vm/vmcluster.yaml | 0 .../monitoring/templates/vpa.yaml | 0 .../monitoring/values.schema.json | 0 .../{extra => system}/monitoring/values.yaml | 0 42 files changed, 602 insertions(+), 1 deletion(-) delete mode 120000 packages/extra/monitoring/charts/cozy-lib create mode 100644 packages/extra/monitoring/templates/helmrelease.yaml rename packages/{extra => system}/monitoring/.helmignore (100%) rename packages/{extra => system}/monitoring/Chart.yaml (100%) rename packages/{extra => system}/monitoring/Makefile (100%) rename packages/{extra => system}/monitoring/README.md (100%) create mode 100644 packages/system/monitoring/charts/cozy-lib/.helmignore create mode 100644 packages/system/monitoring/charts/cozy-lib/Chart.yaml create mode 100644 packages/system/monitoring/charts/cozy-lib/Makefile create mode 100644 packages/system/monitoring/charts/cozy-lib/README.md create mode 100644 packages/system/monitoring/charts/cozy-lib/templates/_checkinput.tpl create mode 100644 packages/system/monitoring/charts/cozy-lib/templates/_cozyconfig.tpl create mode 100644 packages/system/monitoring/charts/cozy-lib/templates/_network.tpl create mode 100644 packages/system/monitoring/charts/cozy-lib/templates/_rbac.tpl create mode 100644 packages/system/monitoring/charts/cozy-lib/templates/_resourcepresets.tpl create mode 100644 packages/system/monitoring/charts/cozy-lib/templates/_resources.tpl create mode 100644 packages/system/monitoring/charts/cozy-lib/values.schema.json create mode 100644 packages/system/monitoring/charts/cozy-lib/values.yaml rename packages/{extra => system}/monitoring/dashboards.list (100%) rename packages/{extra => system}/monitoring/images/grafana.tag (100%) rename packages/{extra => system}/monitoring/images/grafana/Dockerfile (100%) rename packages/{extra => system}/monitoring/logos/monitoring.svg (100%) rename packages/{extra => system}/monitoring/patches/1.diff (100%) rename packages/{extra => system}/monitoring/templates/alerta/alerta-db.yaml (100%) rename packages/{extra => system}/monitoring/templates/alerta/alerta.yaml (100%) rename packages/{extra => system}/monitoring/templates/check-release-name.yaml (100%) rename packages/{extra => system}/monitoring/templates/dashboard-resourcemap.yaml (100%) rename packages/{extra => system}/monitoring/templates/dashboards.yaml (100%) rename packages/{extra => system}/monitoring/templates/grafana/db.yaml (100%) rename packages/{extra => system}/monitoring/templates/grafana/grafana.yaml (100%) rename packages/{extra => system}/monitoring/templates/grafana/secret.yaml (100%) rename packages/{extra => system}/monitoring/templates/vlogs/grafana-datasource.yaml (100%) rename packages/{extra => system}/monitoring/templates/vlogs/vlogs.yaml (100%) rename packages/{extra => system}/monitoring/templates/vm/grafana-datasource.yaml (100%) rename packages/{extra => system}/monitoring/templates/vm/vmagent.yaml (100%) rename packages/{extra => system}/monitoring/templates/vm/vmalert-scrape.yaml (100%) rename packages/{extra => system}/monitoring/templates/vm/vmalert.yaml (100%) rename packages/{extra => system}/monitoring/templates/vm/vmcluster-scrape.yaml (100%) rename packages/{extra => system}/monitoring/templates/vm/vmcluster.yaml (100%) rename packages/{extra => system}/monitoring/templates/vpa.yaml (100%) rename packages/{extra => system}/monitoring/values.schema.json (100%) rename packages/{extra => system}/monitoring/values.yaml (100%) diff --git a/packages/extra/monitoring/charts/cozy-lib b/packages/extra/monitoring/charts/cozy-lib deleted file mode 120000 index e1813509..00000000 --- a/packages/extra/monitoring/charts/cozy-lib +++ /dev/null @@ -1 +0,0 @@ -../../../library/cozy-lib \ No newline at end of file diff --git a/packages/extra/monitoring/templates/helmrelease.yaml b/packages/extra/monitoring/templates/helmrelease.yaml new file mode 100644 index 00000000..834134a8 --- /dev/null +++ b/packages/extra/monitoring/templates/helmrelease.yaml @@ -0,0 +1,26 @@ +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + annotations: + helm.sh/resource-policy: keep + labels: + sharding.fluxcd.io/key: tenants + apps.cozystack.io/application.kind: Monitoring + apps.cozystack.io/application.group: apps.cozystack.io + apps.cozystack.io/application.name: monitoring + name: monitoring + namespace: {{ .Release.Namespace }} +spec: + chartRef: + kind: ExternalArtifact + name: cozystack-monitoring-application-default-monitoring + namespace: cozy-system + interval: 5m + timeout: 10m + install: + remediation: + retries: -1 + upgrade: + force: true + remediation: + retries: -1 diff --git a/packages/extra/monitoring/.helmignore b/packages/system/monitoring/.helmignore similarity index 100% rename from packages/extra/monitoring/.helmignore rename to packages/system/monitoring/.helmignore diff --git a/packages/extra/monitoring/Chart.yaml b/packages/system/monitoring/Chart.yaml similarity index 100% rename from packages/extra/monitoring/Chart.yaml rename to packages/system/monitoring/Chart.yaml diff --git a/packages/extra/monitoring/Makefile b/packages/system/monitoring/Makefile similarity index 100% rename from packages/extra/monitoring/Makefile rename to packages/system/monitoring/Makefile diff --git a/packages/extra/monitoring/README.md b/packages/system/monitoring/README.md similarity index 100% rename from packages/extra/monitoring/README.md rename to packages/system/monitoring/README.md diff --git a/packages/system/monitoring/charts/cozy-lib/.helmignore b/packages/system/monitoring/charts/cozy-lib/.helmignore new file mode 100644 index 00000000..0e8a0eb3 --- /dev/null +++ b/packages/system/monitoring/charts/cozy-lib/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/packages/system/monitoring/charts/cozy-lib/Chart.yaml b/packages/system/monitoring/charts/cozy-lib/Chart.yaml new file mode 100644 index 00000000..0cda13e5 --- /dev/null +++ b/packages/system/monitoring/charts/cozy-lib/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v2 +name: cozy-lib +description: Common Cozystack templates +type: library +version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process diff --git a/packages/system/monitoring/charts/cozy-lib/Makefile b/packages/system/monitoring/charts/cozy-lib/Makefile new file mode 100644 index 00000000..925f90c1 --- /dev/null +++ b/packages/system/monitoring/charts/cozy-lib/Makefile @@ -0,0 +1,8 @@ +include ../../../hack/common-envs.mk +include ../../../hack/package.mk + +generate: + cozyvalues-gen -v values.yaml -s values.schema.json -r README.md + +test: + $(MAKE) -C ../../tests/cozy-lib-tests/ test diff --git a/packages/system/monitoring/charts/cozy-lib/README.md b/packages/system/monitoring/charts/cozy-lib/README.md new file mode 100644 index 00000000..d4bf18ad --- /dev/null +++ b/packages/system/monitoring/charts/cozy-lib/README.md @@ -0,0 +1 @@ +## Parameters diff --git a/packages/system/monitoring/charts/cozy-lib/templates/_checkinput.tpl b/packages/system/monitoring/charts/cozy-lib/templates/_checkinput.tpl new file mode 100644 index 00000000..d67bea61 --- /dev/null +++ b/packages/system/monitoring/charts/cozy-lib/templates/_checkinput.tpl @@ -0,0 +1,5 @@ +{{- define "cozy-lib.checkInput" }} +{{- if not (kindIs "slice" .) }} +{{- fail (printf "called cozy-lib function without global scope, expected [, $], got %s" (kindOf .)) }} +{{- end }} +{{- end }} diff --git a/packages/system/monitoring/charts/cozy-lib/templates/_cozyconfig.tpl b/packages/system/monitoring/charts/cozy-lib/templates/_cozyconfig.tpl new file mode 100644 index 00000000..648b2617 --- /dev/null +++ b/packages/system/monitoring/charts/cozy-lib/templates/_cozyconfig.tpl @@ -0,0 +1,130 @@ +{{/* +Cluster-wide configuration helpers. +These helpers read from .Values._cluster which is populated via valuesFrom from Secret cozystack-values. +*/}} + +{{/* +Get the root host for the cluster. +Usage: {{ include "cozy-lib.root-host" . }} +*/}} +{{- define "cozy-lib.root-host" -}} +{{- (index .Values._cluster "root-host") | default "" }} +{{- end }} + +{{/* +Get the bundle name for the cluster. +Usage: {{ include "cozy-lib.bundle-name" . }} +*/}} +{{- define "cozy-lib.bundle-name" -}} +{{- (index .Values._cluster "bundle-name") | default "" }} +{{- end }} + +{{/* +Get the images registry. +Usage: {{ include "cozy-lib.images-registry" . }} +*/}} +{{- define "cozy-lib.images-registry" -}} +{{- (index .Values._cluster "images-registry") | default "" }} +{{- end }} + +{{/* +Get the ipv4 cluster CIDR. +Usage: {{ include "cozy-lib.ipv4-cluster-cidr" . }} +*/}} +{{- define "cozy-lib.ipv4-cluster-cidr" -}} +{{- (index .Values._cluster "ipv4-cluster-cidr") | default "" }} +{{- end }} + +{{/* +Get the ipv4 service CIDR. +Usage: {{ include "cozy-lib.ipv4-service-cidr" . }} +*/}} +{{- define "cozy-lib.ipv4-service-cidr" -}} +{{- (index .Values._cluster "ipv4-service-cidr") | default "" }} +{{- end }} + +{{/* +Get the ipv4 join CIDR. +Usage: {{ include "cozy-lib.ipv4-join-cidr" . }} +*/}} +{{- define "cozy-lib.ipv4-join-cidr" -}} +{{- (index .Values._cluster "ipv4-join-cidr") | default "" }} +{{- end }} + +{{/* +Get scheduling configuration. +Usage: {{ include "cozy-lib.scheduling" . }} +Returns: YAML string of scheduling configuration +*/}} +{{- define "cozy-lib.scheduling" -}} +{{- if .Values._cluster.scheduling }} +{{- .Values._cluster.scheduling | toYaml }} +{{- end }} +{{- end }} + +{{/* +Get branding configuration. +Usage: {{ include "cozy-lib.branding" . }} +Returns: YAML string of branding configuration +*/}} +{{- define "cozy-lib.branding" -}} +{{- if .Values._cluster.branding }} +{{- .Values._cluster.branding | toYaml }} +{{- end }} +{{- end }} + +{{/* +Namespace-specific configuration helpers. +These helpers read from .Values._namespace which is populated via valuesFrom from Secret cozystack-values. +*/}} + +{{/* +Get the host for this namespace. +Usage: {{ include "cozy-lib.ns-host" . }} +*/}} +{{- define "cozy-lib.ns-host" -}} +{{- .Values._namespace.host | default "" }} +{{- end }} + +{{/* +Get the etcd namespace reference. +Usage: {{ include "cozy-lib.ns-etcd" . }} +*/}} +{{- define "cozy-lib.ns-etcd" -}} +{{- .Values._namespace.etcd | default "" }} +{{- end }} + +{{/* +Get the ingress namespace reference. +Usage: {{ include "cozy-lib.ns-ingress" . }} +*/}} +{{- define "cozy-lib.ns-ingress" -}} +{{- .Values._namespace.ingress | default "" }} +{{- end }} + +{{/* +Get the monitoring namespace reference. +Usage: {{ include "cozy-lib.ns-monitoring" . }} +*/}} +{{- define "cozy-lib.ns-monitoring" -}} +{{- .Values._namespace.monitoring | default "" }} +{{- end }} + +{{/* +Get the seaweedfs namespace reference. +Usage: {{ include "cozy-lib.ns-seaweedfs" . }} +*/}} +{{- define "cozy-lib.ns-seaweedfs" -}} +{{- .Values._namespace.seaweedfs | default "" }} +{{- end }} + +{{/* +Legacy helper - kept for backward compatibility during migration. +Loads config into context. Deprecated: use direct .Values._cluster access instead. +*/}} +{{- define "cozy-lib.loadCozyConfig" }} +{{- include "cozy-lib.checkInput" . }} +{{- if not (hasKey (index . 1) "cozyConfig") }} +{{- $_ := set (index . 1) "cozyConfig" (dict "data" ((index . 1).Values._cluster | default dict)) }} +{{- end }} +{{- end }} diff --git a/packages/system/monitoring/charts/cozy-lib/templates/_network.tpl b/packages/system/monitoring/charts/cozy-lib/templates/_network.tpl new file mode 100644 index 00000000..4908f7d5 --- /dev/null +++ b/packages/system/monitoring/charts/cozy-lib/templates/_network.tpl @@ -0,0 +1,23 @@ +{{- define "cozy-lib.network.defaultDisableLoadBalancerNodePorts" }} +{{/* Default behavior prior to introduction */}} +{{- `true` }} +{{- end }} + +{{/* +Invoke as {{ include "cozy-lib.network.disableLoadBalancerNodePorts" $ }}. +Detects whether the current load balancer class requires nodeports to function +correctly. Currently just checks if Hetzner's RobotLB is enabled, which does +require nodeports, and so, returns `false`. Otherwise assumes that metallb is +in use and returns `true`. +*/}} + +{{- define "cozy-lib.network.disableLoadBalancerNodePorts" }} +{{- include "cozy-lib.loadCozyConfig" (list "" .) }} +{{- $cozyConfig := index . "cozyConfig" }} +{{- if not $cozyConfig }} +{{- include "cozy-lib.network.defaultDisableLoadBalancerNodePorts" . }} +{{- else }} +{{- $enabledComponents := splitList "," ((index $cozyConfig.data "bundle-enable") | default "") }} +{{- not (has "robotlb" $enabledComponents) }} +{{- end }} +{{- end }} diff --git a/packages/system/monitoring/charts/cozy-lib/templates/_rbac.tpl b/packages/system/monitoring/charts/cozy-lib/templates/_rbac.tpl new file mode 100644 index 00000000..0759c0a3 --- /dev/null +++ b/packages/system/monitoring/charts/cozy-lib/templates/_rbac.tpl @@ -0,0 +1,106 @@ +{{- define "cozy-lib.rbac.accessLevelMap" }} +view: 0 +use: 1 +admin: 2 +super-admin: 3 +{{- end }} + +{{- define "cozy-lib.rbac.accessLevelToInt" }} +{{- $accessMap := include "cozy-lib.rbac.accessLevelMap" "" | fromYaml }} +{{- $accessLevel := dig . -1 $accessMap | int }} +{{- if eq $accessLevel -1 }} +{{- printf "encountered access level of %s, allowed values are %s" . ($accessMap | keys) | fail }} +{{- end }} +{{- $accessLevel }} +{{- end }} + +{{- define "cozy-lib.rbac.accessLevelsAtOrAbove" }} +{{- $minLevelInt := include "cozy-lib.rbac.accessLevelToInt" . | int }} +{{- range $k, $v := (include "cozy-lib.rbac.accessLevelMap" "" | fromYaml) }} +{{- if ge (int $v) $minLevelInt }} +- {{ $k }} +{{- end }} +{{- end }} +{{- end }} + +{{- define "cozy-lib.rbac.allParentTenantsAndThis" }} +{{- if not (hasPrefix "tenant-" .) }} +{{- printf "'%s' is not a valid tenant identifier" . | fail }} +{{- end }} +{{- $parts := append (splitList "-" .) "" }} +{{- $tenants := list }} +{{- range untilStep 2 (len $parts) 1 }} +{{- $tenants = append $tenants (slice $parts 0 . | join "-") }} +{{- end }} +{{- range $tenants }} +- {{ . }} +{{- end }} +{{- if not (eq . "tenant-root") }} +- tenant-root +{{- end }} +{{- end }} + +{{- define "cozy-lib.rbac.groupSubject" -}} +- kind: Group + name: {{ . }} + apiGroup: rbac.authorization.k8s.io +{{- end }} + +{{- define "cozy-lib.rbac.serviceAccountSubject" -}} +- kind: ServiceAccount + name: {{ . }} + namespace: {{ . }} +{{- end }} + +{{- /* + A helper function to get a list of groups that should have access, given a + minimal access level and the tenant. Invoked as: + {{ include "cozy-lib.rbac.subjectsForTenantAndAccessLevel" (list "use" $) }} + For an example input of (list "use" $) and a .Release.Namespace of + tenant-abc-def it will return: + --- + - kind: Group + name: tenant-abc-admin + apiGroup: rbac.authorization.k8s.io + - kind: Group + name: tenant-abc-def-admin + apiGroup: rbac.authorization.k8s.io + - kind: Group + name: tenant-abc-super-admin + apiGroup: rbac.authorization.k8s.io + - kind: Group + name: tenant-abc-def-super-admin + apiGroup: rbac.authorization.k8s.io + - kind: Group + name: tenant-abc-use + apiGroup: rbac.authorization.k8s.io + - kind: Group + name: tenant-abc-def-use + apiGroup: rbac.authorization.k8s.io + + in other words, all roles including use and higher and for tenant-abc-def, as + well as all parent, grandparent, etc. tenants. +*/}} +{{- define "cozy-lib.rbac.subjectsForTenantAndAccessLevel" }} +{{- include "cozy-lib.checkInput" . }} +{{- $level := index . 0 }} +{{- $tenant := index . 1 }} +{{- $levels := include "cozy-lib.rbac.accessLevelsAtOrAbove" $level | fromYamlArray }} +{{- $tenants := include "cozy-lib.rbac.allParentTenantsAndThis" $tenant | fromYamlArray }} +{{- range $t := $tenants }} +{{- include "cozy-lib.rbac.serviceAccountSubject" $t }}{{ printf "\n" }} +{{- range $l := $levels }} +{{- include "cozy-lib.rbac.groupSubject" (printf "%s-%s" $t $l) }}{{ printf "\n" }} +{{- end }} +{{- end}} +{{- end }} + +{{- define "cozy-lib.rbac.subjectsForTenant" }} +{{- include "cozy-lib.checkInput" . }} +{{- $level := index . 0 }} +{{- $tenant := index . 1 }} +{{- $tenants := include "cozy-lib.rbac.allParentTenantsAndThis" $tenant | fromYamlArray }} +{{- range $t := $tenants }} +{{- include "cozy-lib.rbac.groupSubject" (printf "%s-%s" $t $level) }}{{ printf "\n" }} +{{- end}} +{{- end }} diff --git a/packages/system/monitoring/charts/cozy-lib/templates/_resourcepresets.tpl b/packages/system/monitoring/charts/cozy-lib/templates/_resourcepresets.tpl new file mode 100644 index 00000000..89d55342 --- /dev/null +++ b/packages/system/monitoring/charts/cozy-lib/templates/_resourcepresets.tpl @@ -0,0 +1,50 @@ +{{/* vim: set filetype=mustache: */}} + +{{- define "cozy-lib.resources.unsanitizedPreset" }} + +{{- $baseCPU := dict + "nano" (dict "cpu" "250m" ) + "micro" (dict "cpu" "500m" ) + "small" (dict "cpu" "1" ) + "medium" (dict "cpu" "1" ) + "large" (dict "cpu" "2" ) + "xlarge" (dict "cpu" "4" ) + "2xlarge" (dict "cpu" "8" ) +}} +{{- $baseMemory := dict + "nano" (dict "memory" "128Mi" ) + "micro" (dict "memory" "256Mi" ) + "small" (dict "memory" "512Mi" ) + "medium" (dict "memory" "1Gi" ) + "large" (dict "memory" "2Gi" ) + "xlarge" (dict "memory" "4Gi" ) + "2xlarge" (dict "memory" "8Gi" ) +}} +{{- $baseEphemeralStorage := dict + "nano" (dict "ephemeral-storage" "2Gi" ) + "micro" (dict "ephemeral-storage" "2Gi" ) + "small" (dict "ephemeral-storage" "2Gi" ) + "medium" (dict "ephemeral-storage" "2Gi" ) + "large" (dict "ephemeral-storage" "2Gi" ) + "xlarge" (dict "ephemeral-storage" "2Gi" ) + "2xlarge" (dict "ephemeral-storage" "2Gi" ) +}} + +{{- $presets := merge $baseCPU $baseMemory $baseEphemeralStorage }} +{{- if not (hasKey $presets .) -}} +{{- printf "ERROR: Preset key '%s' invalid. Allowed values are %s" . (join "," (keys $presets)) | fail -}} +{{- end }} +{{- index $presets . | toYaml }} +{{- end }} + +{{/* + Return a resource request/limit object based on a given preset. + {{- include "cozy-lib.resources.preset" list ("nano" $) }} +*/}} +{{- define "cozy-lib.resources.preset" -}} +{{- $cpuAllocationRatio := include "cozy-lib.resources.cpuAllocationRatio" . | float64 }} +{{- $args := index . 0 }} +{{- $global := index . 1 }} +{{- $unsanitizedPreset := include "cozy-lib.resources.unsanitizedPreset" list ($args $global) | fromYaml }} +{{- include "cozy-lib.resources.sanitize" (list $unsanitizedPreset $global) }} +{{- end -}} diff --git a/packages/system/monitoring/charts/cozy-lib/templates/_resources.tpl b/packages/system/monitoring/charts/cozy-lib/templates/_resources.tpl new file mode 100644 index 00000000..cb055ea1 --- /dev/null +++ b/packages/system/monitoring/charts/cozy-lib/templates/_resources.tpl @@ -0,0 +1,219 @@ +{{- define "cozy-lib.resources.defaultCpuAllocationRatio" }} +{{- `10` }} +{{- end }} +{{- define "cozy-lib.resources.defaultMemoryAllocationRatio" }} +{{- `1` }} +{{- end }} +{{- define "cozy-lib.resources.defaultEphemeralStorageAllocationRatio" }} +{{- `40` }} +{{- end }} + +{{- define "cozy-lib.resources.cpuAllocationRatio" }} +{{- include "cozy-lib.loadCozyConfig" . }} +{{- $cozyConfig := index . 1 "cozyConfig" }} +{{- if not $cozyConfig }} +{{- include "cozy-lib.resources.defaultCpuAllocationRatio" . }} +{{- else }} +{{- dig "data" "cpu-allocation-ratio" (include "cozy-lib.resources.defaultCpuAllocationRatio" dict) $cozyConfig }} +{{- end }} +{{- end }} + +{{- define "cozy-lib.resources.memoryAllocationRatio" }} +{{- include "cozy-lib.loadCozyConfig" . }} +{{- $cozyConfig := index . 1 "cozyConfig" }} +{{- if not $cozyConfig }} +{{- include "cozy-lib.resources.defaultMemoryAllocationRatio" . }} +{{- else }} +{{- dig "data" "memory-allocation-ratio" (include "cozy-lib.resources.defaultMemoryAllocationRatio" dict) $cozyConfig }} +{{- end }} +{{- end }} + +{{- define "cozy-lib.resources.ephemeralStorageAllocationRatio" }} +{{- include "cozy-lib.loadCozyConfig" . }} +{{- $cozyConfig := index . 1 "cozyConfig" }} +{{- if not $cozyConfig }} +{{- include "cozy-lib.resources.defaultEphemeralStorageAllocationRatio" . }} +{{- else }} +{{- dig "data" "ephemeral-storage-allocation-ratio" (include "cozy-lib.resources.defaultEphemeralStorageAllocationRatio" dict) $cozyConfig }} +{{- end }} +{{- end }} + + +{{- define "cozy-lib.resources.toFloat" -}} + {{- $value := . -}} + {{- $unit := 1.0 -}} + {{- if typeIs "string" . -}} + {{- $base2 := dict "Ki" 0x1p10 "Mi" 0x1p20 "Gi" 0x1p30 "Ti" 0x1p40 "Pi" 0x1p50 "Ei" 0x1p60 -}} + {{- $base10 := dict "m" 1e-3 "k" 1e3 "M" 1e6 "G" 1e9 "T" 1e12 "P" 1e15 "E" 1e18 -}} + {{- range $k, $v := merge $base2 $base10 -}} + {{- if hasSuffix $k $ -}} + {{- $value = trimSuffix $k $ -}} + {{- $unit = $v -}} + {{- end -}} + {{- end -}} + {{- end -}} + {{- mulf (float64 $value) $unit | toString -}} +{{- end -}} + +{{- /* + A sanitized resource map is a dict with resource-name to resource-quantity. + All resources are returned with equal **requests** and **limits**, except for + **cpu**, whose *request* is reduced by the CPU-allocation ratio obtained from + `cozy-lib.resources.cpuAllocationRatio`. + + The template now expects **one flat map** as input (no nested `requests:` / + `limits:` sections). Each value in that map is taken as the *limit* for the + corresponding resource. Usage example: + + {{ include "cozy-lib.resources.sanitize" list (.Values.resources $) }} + + Example input: + ============== + cpu: "2" + memory: 256Mi + devices.com/nvidia: "1" + + Example output (cpuAllocationRatio = 10): + ========================================= + limits: + cpu: "2" + memory: 256Mi + devices.com/nvidia: "1" + requests: + cpu: 200m # 2 / 10 + memory: 256Mi # = limit + devices.com/nvidia: "1" # = limit +*/}} +{{- define "cozy-lib.resources.sanitize" }} +{{- $cpuAllocationRatio := include "cozy-lib.resources.cpuAllocationRatio" . | float64 }} +{{- $memoryAllocationRatio := include "cozy-lib.resources.memoryAllocationRatio" . | float64 }} +{{- $ephemeralStorageAllocationRatio := include "cozy-lib.resources.ephemeralStorageAllocationRatio" . | float64 }} +{{- $args := index . 0 }} +{{- $output := dict "requests" dict "limits" dict }} +{{- if or (hasKey $args "limits") (hasKey $args "requests") }} +{{- fail "ERROR: A flat map of resources expected, not nested `requests:` or `limits:` sections." -}} +{{- end }} +{{- range $k, $v := $args }} +{{- if eq $k "cpu" }} +{{- $vcpuRequestF64 := (include "cozy-lib.resources.toFloat" $v) | float64 }} +{{- $cpuRequestF64 := divf $vcpuRequestF64 $cpuAllocationRatio }} +{{- $_ := set $output.requests $k ($cpuRequestF64 | toString) }} +{{- $_ := set $output.limits $k ($v | toString) }} +{{- else if eq $k "memory" }} +{{- $vMemoryRequestF64 := (include "cozy-lib.resources.toFloat" $v) | float64 }} +{{- $memoryRequestF64 := divf $vMemoryRequestF64 $memoryAllocationRatio }} +{{- $_ := set $output.requests $k ($memoryRequestF64 | int | toString ) }} +{{- $_ := set $output.limits $k ($v | toString) }} +{{- else if eq $k "ephemeral-storage" }} +{{- $vEphemeralStorageRequestF64 := (include "cozy-lib.resources.toFloat" $v) | float64 }} +{{- $ephemeralStorageRequestF64 := divf $vEphemeralStorageRequestF64 $ephemeralStorageAllocationRatio }} +{{- $_ := set $output.requests $k ($ephemeralStorageRequestF64 | int | toString) }} +{{- $_ := set $output.limits $k ($v | toString) }} +{{- else }} +{{- $_ := set $output.requests $k $v }} +{{- $_ := set $output.limits $k $v }} +{{- end }} +{{- end }} +{{- $output | toYaml }} +{{- end }} + +{{- /* + The defaultingSanitize helper takes a 3-element list as its argument: + {{- include "cozy-lib.resources.defaultingSanitize" list ( + .Values.resourcesPreset + .Values.resources + $ + ) }} + and returns the same result as + {{- include "cozy-lib.resources.sanitize" list ( + .Values.resources + $ + ) }}, however if cpu, memory, or ephemeral storage is not specified in + .Values.resources, it is filled from the resource presets. + + Example input (cpuAllocationRatio = 10): + ======================================== + resources: + cpu: "1" + resourcesPreset: "nano" + + Example output: + =============== + resources: + limits: + cpu: "1" # == user input + ephemeral-storage: 2Gi # == default ephemeral storage limit + memory: 128Mi # from "nano" + requests: + cpu: 100m # == 1 / 10 + ephemeral-storage: 50Mi # == default ephemeral storage request + memory: 128Mi # memory request == limit +*/}} +{{- define "cozy-lib.resources.defaultingSanitize" }} +{{- $preset := index . 0 }} +{{- $resources := index . 1 }} +{{- $global := index . 2 }} +{{- $presetMap := include "cozy-lib.resources.unsanitizedPreset" $preset | fromYaml }} +{{- $mergedMap := deepCopy (default (dict) $resources) | mergeOverwrite $presetMap }} +{{- include "cozy-lib.resources.sanitize" (list $mergedMap $global) }} +{{- end }} + +{{- /* + javaHeap takes a .Values.resources and returns Java heap settings based on + memory requests and limits. -Xmx is set to 75% of memory limits, -Xms is + set to the lesser of requests or 25% of limits. Accepts only sanitized + resource maps. +*/}} +{{- define "cozy-lib.resources.javaHeap" }} +{{- $memoryRequestInt := include "cozy-lib.resources.toFloat" .requests.memory | float64 | int64 }} +{{- $memoryLimitInt := include "cozy-lib.resources.toFloat" .limits.memory | float64 | int64 }} +{{- /* 4194304 is 4Mi */}} +{{- $xmxMi := div (mul $memoryLimitInt 3) 4194304 }} +{{- $xmsMi := min (div $memoryLimitInt 4194304) (div $memoryRequestInt 1048576) }} +{{- printf `-Xms%dm -Xmx%dm` $xmsMi $xmxMi }} +{{- end }} + +{{- define "cozy-lib.resources.flatten" -}} +{{- $out := dict -}} +{{- $res := include "cozy-lib.resources.sanitize" . | fromYaml -}} +{{- range $section, $values := $res }} +{{- range $k, $v := $values }} +{{- with include "cozy-lib.resources.flattenResource" (list $section $k) }} +{{- $_ := set $out . $v }} +{{- end }} +{{- end }} +{{- end }} +{{- $out | toYaml }} +{{- end }} + +{{/* + This is a helper function that takes an argument like `list "limits" "services.loadbalancers"` + or `list "limits" "storage"` or `list "requests" "cpu"` and returns "services.loadbalancers", + "", and "requests.cpu", respectively, thus transforming them to an acceptable format for k8s + ResourceQuotas objects. +*/}} +{{- define "cozy-lib.resources.flattenResource" }} +{{- $rawQuotaKeys := list + "pods" + "services" + "services.loadbalancers" + "services.nodeports" + "services.clusterip" + "configmaps" + "secrets" + "persistentvolumeclaims" + "replicationcontrollers" + "resourcequotas" +-}} +{{- $section := index . 0 }} +{{- $type := index . 1 }} +{{- $out := "" }} +{{- if and (eq $section "limits") (eq $type "storage") }} +{{- $out = "" }} +{{- else if and (eq $section "limits") (has $type $rawQuotaKeys) }} +{{- $out = $type }} +{{- else if not (has $type $rawQuotaKeys) }} +{{- $out = printf "%s.%s" $section $type }} +{{- end }} +{{- $out -}} +{{- end }} diff --git a/packages/system/monitoring/charts/cozy-lib/values.schema.json b/packages/system/monitoring/charts/cozy-lib/values.schema.json new file mode 100644 index 00000000..decc79aa --- /dev/null +++ b/packages/system/monitoring/charts/cozy-lib/values.schema.json @@ -0,0 +1,5 @@ +{ + "title": "Chart Values", + "type": "object", + "properties": {} +} \ No newline at end of file diff --git a/packages/system/monitoring/charts/cozy-lib/values.yaml b/packages/system/monitoring/charts/cozy-lib/values.yaml new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/packages/system/monitoring/charts/cozy-lib/values.yaml @@ -0,0 +1 @@ +{} diff --git a/packages/extra/monitoring/dashboards.list b/packages/system/monitoring/dashboards.list similarity index 100% rename from packages/extra/monitoring/dashboards.list rename to packages/system/monitoring/dashboards.list diff --git a/packages/extra/monitoring/images/grafana.tag b/packages/system/monitoring/images/grafana.tag similarity index 100% rename from packages/extra/monitoring/images/grafana.tag rename to packages/system/monitoring/images/grafana.tag diff --git a/packages/extra/monitoring/images/grafana/Dockerfile b/packages/system/monitoring/images/grafana/Dockerfile similarity index 100% rename from packages/extra/monitoring/images/grafana/Dockerfile rename to packages/system/monitoring/images/grafana/Dockerfile diff --git a/packages/extra/monitoring/logos/monitoring.svg b/packages/system/monitoring/logos/monitoring.svg similarity index 100% rename from packages/extra/monitoring/logos/monitoring.svg rename to packages/system/monitoring/logos/monitoring.svg diff --git a/packages/extra/monitoring/patches/1.diff b/packages/system/monitoring/patches/1.diff similarity index 100% rename from packages/extra/monitoring/patches/1.diff rename to packages/system/monitoring/patches/1.diff diff --git a/packages/extra/monitoring/templates/alerta/alerta-db.yaml b/packages/system/monitoring/templates/alerta/alerta-db.yaml similarity index 100% rename from packages/extra/monitoring/templates/alerta/alerta-db.yaml rename to packages/system/monitoring/templates/alerta/alerta-db.yaml diff --git a/packages/extra/monitoring/templates/alerta/alerta.yaml b/packages/system/monitoring/templates/alerta/alerta.yaml similarity index 100% rename from packages/extra/monitoring/templates/alerta/alerta.yaml rename to packages/system/monitoring/templates/alerta/alerta.yaml diff --git a/packages/extra/monitoring/templates/check-release-name.yaml b/packages/system/monitoring/templates/check-release-name.yaml similarity index 100% rename from packages/extra/monitoring/templates/check-release-name.yaml rename to packages/system/monitoring/templates/check-release-name.yaml diff --git a/packages/extra/monitoring/templates/dashboard-resourcemap.yaml b/packages/system/monitoring/templates/dashboard-resourcemap.yaml similarity index 100% rename from packages/extra/monitoring/templates/dashboard-resourcemap.yaml rename to packages/system/monitoring/templates/dashboard-resourcemap.yaml diff --git a/packages/extra/monitoring/templates/dashboards.yaml b/packages/system/monitoring/templates/dashboards.yaml similarity index 100% rename from packages/extra/monitoring/templates/dashboards.yaml rename to packages/system/monitoring/templates/dashboards.yaml diff --git a/packages/extra/monitoring/templates/grafana/db.yaml b/packages/system/monitoring/templates/grafana/db.yaml similarity index 100% rename from packages/extra/monitoring/templates/grafana/db.yaml rename to packages/system/monitoring/templates/grafana/db.yaml diff --git a/packages/extra/monitoring/templates/grafana/grafana.yaml b/packages/system/monitoring/templates/grafana/grafana.yaml similarity index 100% rename from packages/extra/monitoring/templates/grafana/grafana.yaml rename to packages/system/monitoring/templates/grafana/grafana.yaml diff --git a/packages/extra/monitoring/templates/grafana/secret.yaml b/packages/system/monitoring/templates/grafana/secret.yaml similarity index 100% rename from packages/extra/monitoring/templates/grafana/secret.yaml rename to packages/system/monitoring/templates/grafana/secret.yaml diff --git a/packages/extra/monitoring/templates/vlogs/grafana-datasource.yaml b/packages/system/monitoring/templates/vlogs/grafana-datasource.yaml similarity index 100% rename from packages/extra/monitoring/templates/vlogs/grafana-datasource.yaml rename to packages/system/monitoring/templates/vlogs/grafana-datasource.yaml diff --git a/packages/extra/monitoring/templates/vlogs/vlogs.yaml b/packages/system/monitoring/templates/vlogs/vlogs.yaml similarity index 100% rename from packages/extra/monitoring/templates/vlogs/vlogs.yaml rename to packages/system/monitoring/templates/vlogs/vlogs.yaml diff --git a/packages/extra/monitoring/templates/vm/grafana-datasource.yaml b/packages/system/monitoring/templates/vm/grafana-datasource.yaml similarity index 100% rename from packages/extra/monitoring/templates/vm/grafana-datasource.yaml rename to packages/system/monitoring/templates/vm/grafana-datasource.yaml diff --git a/packages/extra/monitoring/templates/vm/vmagent.yaml b/packages/system/monitoring/templates/vm/vmagent.yaml similarity index 100% rename from packages/extra/monitoring/templates/vm/vmagent.yaml rename to packages/system/monitoring/templates/vm/vmagent.yaml diff --git a/packages/extra/monitoring/templates/vm/vmalert-scrape.yaml b/packages/system/monitoring/templates/vm/vmalert-scrape.yaml similarity index 100% rename from packages/extra/monitoring/templates/vm/vmalert-scrape.yaml rename to packages/system/monitoring/templates/vm/vmalert-scrape.yaml diff --git a/packages/extra/monitoring/templates/vm/vmalert.yaml b/packages/system/monitoring/templates/vm/vmalert.yaml similarity index 100% rename from packages/extra/monitoring/templates/vm/vmalert.yaml rename to packages/system/monitoring/templates/vm/vmalert.yaml diff --git a/packages/extra/monitoring/templates/vm/vmcluster-scrape.yaml b/packages/system/monitoring/templates/vm/vmcluster-scrape.yaml similarity index 100% rename from packages/extra/monitoring/templates/vm/vmcluster-scrape.yaml rename to packages/system/monitoring/templates/vm/vmcluster-scrape.yaml diff --git a/packages/extra/monitoring/templates/vm/vmcluster.yaml b/packages/system/monitoring/templates/vm/vmcluster.yaml similarity index 100% rename from packages/extra/monitoring/templates/vm/vmcluster.yaml rename to packages/system/monitoring/templates/vm/vmcluster.yaml diff --git a/packages/extra/monitoring/templates/vpa.yaml b/packages/system/monitoring/templates/vpa.yaml similarity index 100% rename from packages/extra/monitoring/templates/vpa.yaml rename to packages/system/monitoring/templates/vpa.yaml diff --git a/packages/extra/monitoring/values.schema.json b/packages/system/monitoring/values.schema.json similarity index 100% rename from packages/extra/monitoring/values.schema.json rename to packages/system/monitoring/values.schema.json diff --git a/packages/extra/monitoring/values.yaml b/packages/system/monitoring/values.yaml similarity index 100% rename from packages/extra/monitoring/values.yaml rename to packages/system/monitoring/values.yaml From 5d11d7a7aea3221e98fc5201330ed8ca71759c8d Mon Sep 17 00:00:00 2001 From: IvanHunters Date: Fri, 30 Jan 2026 05:06:03 +0300 Subject: [PATCH 168/889] Adding the ability to deploy system monitoring Signed-off-by: IvanHunters --- ...oring-application.yaml => monitoring.yaml} | 17 +- packages/extra/monitoring/Chart.yaml | 6 + packages/extra/monitoring/Makefile | 21 ++ .../{system => extra}/monitoring/README.md | 0 packages/extra/monitoring/dashboards.list | 45 +++++ .../templates/check-release-name.yaml | 0 .../templates/dashboard-resourcemap.yaml | 0 .../monitoring/templates/helmrelease.yaml | 72 ++++++- .../monitoring/values.schema.json | 0 packages/extra/monitoring/values.yaml | 180 ++++++++++++++++++ packages/system/monitoring/Makefile | 5 +- .../monitoring/templates/vm/vmagent.yaml | 2 +- 12 files changed, 332 insertions(+), 16 deletions(-) rename packages/core/platform/sources/{monitoring-application.yaml => monitoring.yaml} (57%) create mode 100644 packages/extra/monitoring/Chart.yaml create mode 100644 packages/extra/monitoring/Makefile rename packages/{system => extra}/monitoring/README.md (100%) create mode 100644 packages/extra/monitoring/dashboards.list rename packages/{system => extra}/monitoring/templates/check-release-name.yaml (100%) rename packages/{system => extra}/monitoring/templates/dashboard-resourcemap.yaml (100%) rename packages/{system => extra}/monitoring/values.schema.json (100%) create mode 100644 packages/extra/monitoring/values.yaml diff --git a/packages/core/platform/sources/monitoring-application.yaml b/packages/core/platform/sources/monitoring.yaml similarity index 57% rename from packages/core/platform/sources/monitoring-application.yaml rename to packages/core/platform/sources/monitoring.yaml index 6ea5a2d0..600f4ed6 100644 --- a/packages/core/platform/sources/monitoring-application.yaml +++ b/packages/core/platform/sources/monitoring.yaml @@ -1,8 +1,10 @@ ---- + apiVersion: cozystack.io/v1alpha1 kind: PackageSource metadata: - name: cozystack.monitoring-application + name: cozystack.monitoring-independent-application + annotations: + operator.cozystack.io/skip-cozystack-values: "true" spec: sourceRef: kind: OCIRepository @@ -12,6 +14,8 @@ spec: variants: - name: default dependsOn: + - cozystack.cozystack-controller + - cozystack.vertical-pod-autoscaler - cozystack.networking - cozystack.postgres-operator libraries: @@ -19,10 +23,9 @@ spec: path: library/cozy-lib components: - name: monitoring - path: extra/monitoring + path: system/monitoring libraries: ["cozy-lib"] - - name: monitoring-rd - path: system/monitoring-rd install: - namespace: cozy-system - releaseName: monitoring-rd + privileged: true + namespace: cozy-monitoring + releaseName: monitoring \ No newline at end of file diff --git a/packages/extra/monitoring/Chart.yaml b/packages/extra/monitoring/Chart.yaml new file mode 100644 index 00000000..2db12ec5 --- /dev/null +++ b/packages/extra/monitoring/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: monitoring +description: Monitoring and observability stack +icon: /logos/monitoring.svg +type: application +version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process diff --git a/packages/extra/monitoring/Makefile b/packages/extra/monitoring/Makefile new file mode 100644 index 00000000..1ae213b6 --- /dev/null +++ b/packages/extra/monitoring/Makefile @@ -0,0 +1,21 @@ +GRAFANA_TAG = $(shell awk '$$1 == "version:" {print $$2}' Chart.yaml) + +NAME=monitoring + +include ../../../hack/common-envs.mk +include ../../../hack/package.mk + +generate: + cozyvalues-gen -v values.yaml -s values.schema.json -r README.md + ../../../hack/update-crd.sh + +image: + docker buildx build images/grafana \ + --tag $(REGISTRY)/grafana:$(call settag,$(GRAFANA_TAG)) \ + --cache-from type=registry,ref=$(REGISTRY)/grafana:latest \ + --cache-to type=inline \ + --metadata-file images/grafana.json \ + $(BUILDX_ARGS) + echo "$(REGISTRY)/grafana:$(call settag,$(GRAFANA_TAG))@$$(yq e '."containerimage.digest"' images/grafana.json -o json -r)" \ + > images/grafana.tag + rm -f images/grafana.json diff --git a/packages/system/monitoring/README.md b/packages/extra/monitoring/README.md similarity index 100% rename from packages/system/monitoring/README.md rename to packages/extra/monitoring/README.md diff --git a/packages/extra/monitoring/dashboards.list b/packages/extra/monitoring/dashboards.list new file mode 100644 index 00000000..1f4b2eea --- /dev/null +++ b/packages/extra/monitoring/dashboards.list @@ -0,0 +1,45 @@ +dotdc/k8s-views-global +dotdc/k8s-views-namespaces +dotdc/k8s-system-coredns +dotdc/k8s-views-pods +cache/nginx-vts-stats +victoria-metrics/vmalert +victoria-metrics/vmagent +victoria-metrics/victoriametrics-cluster +victoria-metrics/backupmanager +victoria-metrics/victoriametrics +victoria-metrics/operator +ingress/controller-detail +ingress/controllers +ingress/namespaces +ingress/vhosts +ingress/vhost-detail +ingress/namespace-detail +db/cloudnativepg +db/maria-db +db/redis +main/controller +main/namespaces +main/capacity-planning +main/ntp +main/namespace +main/pod +main/volumes +main/node +main/nodes +control-plane/control-plane-status +control-plane/deprecated-resources +control-plane/dns-coredns +control-plane/kube-etcd +kubevirt/kubevirt-control-plane +flux/flux-control-plane +flux/flux-stats +kafka/strimzi-kafka +goldpinger/goldpinger +clickhouse/altinity-clickhouse-operator-dashboard +storage/linstor +seaweedfs/seaweedfs +hubble/overview +hubble/dns-namespace +hubble/l7-http-metrics +hubble/network-overview diff --git a/packages/system/monitoring/templates/check-release-name.yaml b/packages/extra/monitoring/templates/check-release-name.yaml similarity index 100% rename from packages/system/monitoring/templates/check-release-name.yaml rename to packages/extra/monitoring/templates/check-release-name.yaml diff --git a/packages/system/monitoring/templates/dashboard-resourcemap.yaml b/packages/extra/monitoring/templates/dashboard-resourcemap.yaml similarity index 100% rename from packages/system/monitoring/templates/dashboard-resourcemap.yaml rename to packages/extra/monitoring/templates/dashboard-resourcemap.yaml diff --git a/packages/extra/monitoring/templates/helmrelease.yaml b/packages/extra/monitoring/templates/helmrelease.yaml index 834134a8..9f147434 100644 --- a/packages/extra/monitoring/templates/helmrelease.yaml +++ b/packages/extra/monitoring/templates/helmrelease.yaml @@ -1,16 +1,22 @@ apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease metadata: - annotations: - helm.sh/resource-policy: keep + name: monitoring + namespace: {{ .Release.Namespace }} labels: sharding.fluxcd.io/key: tenants + internal.cozystack.io/tenantmodule: "true" + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} apps.cozystack.io/application.kind: Monitoring apps.cozystack.io/application.group: apps.cozystack.io apps.cozystack.io/application.name: monitoring - name: monitoring - namespace: {{ .Release.Namespace }} spec: + dependsOn: + - name: cozystack.cozystack-controller + namespace: cozy-system + - name: cozystack.vertical-pod-autoscaler + namespace: cozy-system chartRef: kind: ExternalArtifact name: cozystack-monitoring-application-default-monitoring @@ -24,3 +30,61 @@ spec: force: true remediation: retries: -1 + values: + enabled: true + namespace: cozy-monitoring + host: "" + metricsStorages: + - name: shortterm + retentionPeriod: "3d" + deduplicationInterval: "15s" + storage: 10Gi + storageClassName: "" + - name: longterm + retentionPeriod: "14d" + deduplicationInterval: "5m" + storage: 10Gi + storageClassName: "" + logsStorages: + - name: generic + retentionPeriod: "1" + storage: 10Gi + storageClassName: replicated + alerta: + storage: 10Gi + storageClassName: "" + resources: + limits: + cpu: "1" + memory: 1Gi + requests: + cpu: 100m + memory: 256Mi + alerts: + telegram: + token: "" + chatID: "" + disabledSeverity: [] + slack: + url: "" + disabledSeverity: [] + grafana: + db: + size: 10Gi + resources: + limits: + cpu: "1" + memory: 1Gi + requests: + cpu: 100m + memory: 256Mi + vmagent: + externalLabels: + cluster: cozystack + remoteWrite: + urls: + - http://vminsert-shortterm:8480/insert/0/prometheus + - http://vminsert-longterm:8480/insert/0/prometheus + valuesFrom: + - kind: Secret + name: cozystack-values \ No newline at end of file diff --git a/packages/system/monitoring/values.schema.json b/packages/extra/monitoring/values.schema.json similarity index 100% rename from packages/system/monitoring/values.schema.json rename to packages/extra/monitoring/values.schema.json diff --git a/packages/extra/monitoring/values.yaml b/packages/extra/monitoring/values.yaml new file mode 100644 index 00000000..ab32b1b2 --- /dev/null +++ b/packages/extra/monitoring/values.yaml @@ -0,0 +1,180 @@ +## @section Common parameters +## + +## @param {string} host - The hostname used to access Grafana externally (defaults to 'grafana' subdomain for the tenant host). +host: "" + +## +## @section Metrics storage configuration +## + +## @typedef {struct} Request - Minimum guaranteed resources. +## @field {quantity} [cpu] - CPU request. +## @field {quantity} [memory] - Memory request. + +## @typedef {struct} Limit - Maximum allowed resources. +## @field {quantity} [cpu] - CPU limit. +## @field {quantity} [memory] - Memory limit. + +## @typedef {struct} VMComponent - VictoriaMetrics component configuration. +## @field {Request} [minAllowed] - Minimum guaranteed resources. +## @field {Limit} [maxAllowed] - Maximum allowed resources. + +## @typedef {struct} Resources - Combined resource configuration. +## @field {Request} [requests] - Resource requests. +## @field {Limit} [limits] - Resource limits. + +## @typedef {struct} MetricsStorage - Configuration of metrics storage instance. +## @field {string} name - Name of the storage instance. +## @field {string} retentionPeriod - Retention period for metrics. +## @field {string} deduplicationInterval - Deduplication interval for metrics. +## @field {string} storage="10Gi" - Persistent volume size. +## @field {string} [storageClassName] - StorageClass used for the data. +## @field {VMComponent} [vminsert] - Configuration for vminsert. +## @field {VMComponent} [vmselect] - Configuration for vmselect. +## @field {VMComponent} [vmstorage] - Configuration for vmstorage. + +## @param {[]MetricsStorage} metricsStorages - Configuration of metrics storage instances. +metricsStorages: +## Example: +## metricsStorages: +## - name: shortterm +## retentionPeriod: "3d" +## deduplicationInterval: "15s" +## storage: 10Gi +## storageClassName: "" +## vminsert: +## minAllowed: +## cpu: 200m +## memory: 512Mi +## maxAllowed: +## cpu: 1500m +## memory: 3Gi +## vmselect: +## minAllowed: +## cpu: 300m +## memory: 1Gi +## maxAllowed: +## cpu: 3500m +## memory: 6Gi +## vmstorage: +## minAllowed: +## cpu: 500m +## memory: 2Gi +## maxAllowed: +## cpu: 4000m +## memory: 8Gi +- name: shortterm + retentionPeriod: "3d" + deduplicationInterval: "15s" + storage: 10Gi + storageClassName: "" +- name: longterm + retentionPeriod: "14d" + deduplicationInterval: "5m" + storage: 10Gi + storageClassName: "" + +## +## @section Logs storage configuration +## + +## @typedef {struct} LogsStorage - Configuration of logs storage instance. +## @field {string} name - Name of the storage instance. +## @field {string} retentionPeriod="1" - Retention period for logs. +## @field {string} storage="10Gi" - Persistent volume size. +## @field {string} storageClassName="replicated" - StorageClass used to store the data. + +## @param {[]LogsStorage} logsStorages - Configuration of logs storage instances. +logsStorages: +- name: generic + retentionPeriod: "1" + storage: 10Gi + storageClassName: replicated + +## +## @section Alerta configuration +## + +## @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"]). + +## @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. +## @field {SlackAlerts} [slack] - Configuration for Slack alerts. + +## @typedef {struct} Alerta - Configuration for the Alerta service. +## @field {string} [storage] - Persistent volume size for the database. +## @field {string} [storageClassName] - StorageClass used for the database. +## @field {Resources} [resources] - Resource configuration. +## @field {Alerts} [alerts] - Alert routing configuration. + +## @param {Alerta} alerta - Configuration for the Alerta service. +alerta: + storage: 10Gi + storageClassName: "" + resources: + limits: + cpu: "1" + memory: 1Gi + requests: + cpu: 100m + memory: 256Mi + alerts: + telegram: + token: "" + chatID: "" + disabledSeverity: [] + slack: + url: "" + disabledSeverity: [] +## +## @section Grafana configuration +## + +## @typedef {struct} GrafanaDB - Grafana database configuration. +## @field {string} [size] - Persistent volume size for the database. + +## @typedef {struct} Grafana - Grafana service configuration. +## @field {GrafanaDB} [db] - Database configuration. +## @field {Resources} [resources] - Resource configuration. + +## @param {Grafana} grafana - Configuration for Grafana. +grafana: + db: + size: 10Gi + resources: + limits: + cpu: "1" + memory: 1Gi + requests: + cpu: 100m + memory: 256Mi +## +## @section Vmagent configuration +## + +## @typedef {struct} VmagentRemoteWrite - Remote write configuration. +## @typedef {stringSlice} VmagentRemoteWriteURLs - List of remoteWrite endpoint URLs. + +## @typedef {struct} VmagentExternalLabels - External labels for metrics. +## @field {string} [] - Label value for the given key. + +## @typedef {struct} Vmagent - VictoriaMetrics Agent configuration. +## @field {VmagentExternalLabels} [externalLabels] - External labels applied to all metrics. +## @field {VmagentRemoteWrite} [remoteWrite] - Remote write configuration. + +## @param {Vmagent} vmagent - Configuration for VictoriaMetrics Agent. +vmagent: + externalLabels: + cluster: cozystack + remoteWrite: + urls: + - http://vminsert-shortterm:8480/insert/0/prometheus + - http://vminsert-longterm:8480/insert/0/prometheus diff --git a/packages/system/monitoring/Makefile b/packages/system/monitoring/Makefile index 1ae213b6..e35543cd 100644 --- a/packages/system/monitoring/Makefile +++ b/packages/system/monitoring/Makefile @@ -1,14 +1,11 @@ GRAFANA_TAG = $(shell awk '$$1 == "version:" {print $$2}' Chart.yaml) NAME=monitoring +NAMESPACE=cozy-$(NAME) include ../../../hack/common-envs.mk include ../../../hack/package.mk -generate: - cozyvalues-gen -v values.yaml -s values.schema.json -r README.md - ../../../hack/update-crd.sh - image: docker buildx build images/grafana \ --tag $(REGISTRY)/grafana:$(call settag,$(GRAFANA_TAG)) \ diff --git a/packages/system/monitoring/templates/vm/vmagent.yaml b/packages/system/monitoring/templates/vm/vmagent.yaml index 2c6c391a..fb3edd30 100644 --- a/packages/system/monitoring/templates/vm/vmagent.yaml +++ b/packages/system/monitoring/templates/vm/vmagent.yaml @@ -1,4 +1,4 @@ -{{- if ne .Release.Namespace "tenant-root" }} +{{- if and (ne .Release.Namespace "tenant-root") (ne .Release.Namespace "cozy-monitoring") }} apiVersion: operator.victoriametrics.com/v1beta1 kind: VMAgent metadata: From 2463154070a839c39ba608bc73a413ebbcc0e59f Mon Sep 17 00:00:00 2001 From: IvanHunters Date: Fri, 30 Jan 2026 16:47:05 +0300 Subject: [PATCH 169/889] Fix after review Signed-off-by: IvanHunters --- .../sources/monitoring-application.yaml | 30 ++++++++++ .../core/platform/sources/monitoring.yaml | 32 ++++++++++- .../monitoring/templates/helmrelease.yaml | 55 ------------------- 3 files changed, 60 insertions(+), 57 deletions(-) create mode 100644 packages/core/platform/sources/monitoring-application.yaml diff --git a/packages/core/platform/sources/monitoring-application.yaml b/packages/core/platform/sources/monitoring-application.yaml new file mode 100644 index 00000000..ae0d9241 --- /dev/null +++ b/packages/core/platform/sources/monitoring-application.yaml @@ -0,0 +1,30 @@ + +apiVersion: cozystack.io/v1alpha1 +kind: PackageSource +metadata: + name: cozystack.monitoring-application +spec: + sourceRef: + kind: OCIRepository + name: cozystack-packages + namespace: cozy-system + path: / + variants: + - name: default + dependsOn: + - cozystack.cozystack-controller + - cozystack.vertical-pod-autoscaler + - cozystack.networking + - cozystack.postgres-operator + libraries: + - name: cozy-lib + path: library/cozy-lib + components: + - name: monitoring + path: extra/monitoring + libraries: ["cozy-lib"] + - name: monitoring-rd + path: system/monitoring-rd + install: + namespace: cozy-system + releaseName: monitoring-rd \ No newline at end of file diff --git a/packages/core/platform/sources/monitoring.yaml b/packages/core/platform/sources/monitoring.yaml index 600f4ed6..c88ab412 100644 --- a/packages/core/platform/sources/monitoring.yaml +++ b/packages/core/platform/sources/monitoring.yaml @@ -2,7 +2,7 @@ apiVersion: cozystack.io/v1alpha1 kind: PackageSource metadata: - name: cozystack.monitoring-independent-application + name: cozystack.monitoring annotations: operator.cozystack.io/skip-cozystack-values: "true" spec: @@ -18,6 +18,9 @@ spec: - cozystack.vertical-pod-autoscaler - cozystack.networking - cozystack.postgres-operator + - cozystack.grafana-operator + - cozystack.victoria-metrics-operator + - cozystack.prometheus-operator-crds libraries: - name: cozy-lib path: library/cozy-lib @@ -28,4 +31,29 @@ spec: install: privileged: true namespace: cozy-monitoring - releaseName: monitoring \ No newline at end of file + releaseName: monitoring + - name: vertical-pod-autoscaler + path: system/vertical-pod-autoscaler + install: + namespace: cozy-vertical-pod-autoscaler + releaseName: vertical-pod-autoscaler + - name: postgres-operator + path: system/postgres-operator + install: + namespace: cozy-postgres-operator + releaseName: postgres-operator + - name: grafana-operator + path: system/grafana-operator + install: + namespace: cozy-grafana-operator + releaseName: grafana-operator + - name: victoria-metrics-operator + path: system/victoria-metrics-operator + install: + namespace: cozy-victoria-metrics-operator + releaseName: victoria-metrics-operator + - name: prometheus-operator-crds + path: system/prometheus-operator-crds + install: + namespace: cozy-prometheus-operator-crds + releaseName: prometheus-operator-crds \ No newline at end of file diff --git a/packages/extra/monitoring/templates/helmrelease.yaml b/packages/extra/monitoring/templates/helmrelease.yaml index 9f147434..3560c6b3 100644 --- a/packages/extra/monitoring/templates/helmrelease.yaml +++ b/packages/extra/monitoring/templates/helmrelease.yaml @@ -30,61 +30,6 @@ spec: force: true remediation: retries: -1 - values: - enabled: true - namespace: cozy-monitoring - host: "" - metricsStorages: - - name: shortterm - retentionPeriod: "3d" - deduplicationInterval: "15s" - storage: 10Gi - storageClassName: "" - - name: longterm - retentionPeriod: "14d" - deduplicationInterval: "5m" - storage: 10Gi - storageClassName: "" - logsStorages: - - name: generic - retentionPeriod: "1" - storage: 10Gi - storageClassName: replicated - alerta: - storage: 10Gi - storageClassName: "" - resources: - limits: - cpu: "1" - memory: 1Gi - requests: - cpu: 100m - memory: 256Mi - alerts: - telegram: - token: "" - chatID: "" - disabledSeverity: [] - slack: - url: "" - disabledSeverity: [] - grafana: - db: - size: 10Gi - resources: - limits: - cpu: "1" - memory: 1Gi - requests: - cpu: 100m - memory: 256Mi - vmagent: - externalLabels: - cluster: cozystack - remoteWrite: - urls: - - http://vminsert-shortterm:8480/insert/0/prometheus - - http://vminsert-longterm:8480/insert/0/prometheus valuesFrom: - kind: Secret name: cozystack-values \ No newline at end of file From 0b3845c94157ed2ba9e7c80ef0aa104083be80ae Mon Sep 17 00:00:00 2001 From: IvanHunters Date: Fri, 30 Jan 2026 17:53:19 +0300 Subject: [PATCH 170/889] make cozy-lib as a symbolic link Signed-off-by: IvanHunters --- packages/system/monitoring/charts/cozy-lib | 1 + .../monitoring/charts/cozy-lib/.helmignore | 23 -- .../monitoring/charts/cozy-lib/Chart.yaml | 5 - .../monitoring/charts/cozy-lib/Makefile | 8 - .../monitoring/charts/cozy-lib/README.md | 1 - .../charts/cozy-lib/templates/_checkinput.tpl | 5 - .../charts/cozy-lib/templates/_cozyconfig.tpl | 130 ----------- .../charts/cozy-lib/templates/_network.tpl | 23 -- .../charts/cozy-lib/templates/_rbac.tpl | 106 --------- .../cozy-lib/templates/_resourcepresets.tpl | 50 ---- .../charts/cozy-lib/templates/_resources.tpl | 219 ------------------ .../charts/cozy-lib/values.schema.json | 5 - .../monitoring/charts/cozy-lib/values.yaml | 1 - 13 files changed, 1 insertion(+), 576 deletions(-) create mode 120000 packages/system/monitoring/charts/cozy-lib delete mode 100644 packages/system/monitoring/charts/cozy-lib/.helmignore delete mode 100644 packages/system/monitoring/charts/cozy-lib/Chart.yaml delete mode 100644 packages/system/monitoring/charts/cozy-lib/Makefile delete mode 100644 packages/system/monitoring/charts/cozy-lib/README.md delete mode 100644 packages/system/monitoring/charts/cozy-lib/templates/_checkinput.tpl delete mode 100644 packages/system/monitoring/charts/cozy-lib/templates/_cozyconfig.tpl delete mode 100644 packages/system/monitoring/charts/cozy-lib/templates/_network.tpl delete mode 100644 packages/system/monitoring/charts/cozy-lib/templates/_rbac.tpl delete mode 100644 packages/system/monitoring/charts/cozy-lib/templates/_resourcepresets.tpl delete mode 100644 packages/system/monitoring/charts/cozy-lib/templates/_resources.tpl delete mode 100644 packages/system/monitoring/charts/cozy-lib/values.schema.json delete mode 100644 packages/system/monitoring/charts/cozy-lib/values.yaml diff --git a/packages/system/monitoring/charts/cozy-lib b/packages/system/monitoring/charts/cozy-lib new file mode 120000 index 00000000..e1813509 --- /dev/null +++ b/packages/system/monitoring/charts/cozy-lib @@ -0,0 +1 @@ +../../../library/cozy-lib \ No newline at end of file diff --git a/packages/system/monitoring/charts/cozy-lib/.helmignore b/packages/system/monitoring/charts/cozy-lib/.helmignore deleted file mode 100644 index 0e8a0eb3..00000000 --- a/packages/system/monitoring/charts/cozy-lib/.helmignore +++ /dev/null @@ -1,23 +0,0 @@ -# Patterns to ignore when building packages. -# This supports shell glob matching, relative path matching, and -# negation (prefixed with !). Only one pattern per line. -.DS_Store -# Common VCS dirs -.git/ -.gitignore -.bzr/ -.bzrignore -.hg/ -.hgignore -.svn/ -# Common backup files -*.swp -*.bak -*.tmp -*.orig -*~ -# Various IDEs -.project -.idea/ -*.tmproj -.vscode/ diff --git a/packages/system/monitoring/charts/cozy-lib/Chart.yaml b/packages/system/monitoring/charts/cozy-lib/Chart.yaml deleted file mode 100644 index 0cda13e5..00000000 --- a/packages/system/monitoring/charts/cozy-lib/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v2 -name: cozy-lib -description: Common Cozystack templates -type: library -version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process diff --git a/packages/system/monitoring/charts/cozy-lib/Makefile b/packages/system/monitoring/charts/cozy-lib/Makefile deleted file mode 100644 index 925f90c1..00000000 --- a/packages/system/monitoring/charts/cozy-lib/Makefile +++ /dev/null @@ -1,8 +0,0 @@ -include ../../../hack/common-envs.mk -include ../../../hack/package.mk - -generate: - cozyvalues-gen -v values.yaml -s values.schema.json -r README.md - -test: - $(MAKE) -C ../../tests/cozy-lib-tests/ test diff --git a/packages/system/monitoring/charts/cozy-lib/README.md b/packages/system/monitoring/charts/cozy-lib/README.md deleted file mode 100644 index d4bf18ad..00000000 --- a/packages/system/monitoring/charts/cozy-lib/README.md +++ /dev/null @@ -1 +0,0 @@ -## Parameters diff --git a/packages/system/monitoring/charts/cozy-lib/templates/_checkinput.tpl b/packages/system/monitoring/charts/cozy-lib/templates/_checkinput.tpl deleted file mode 100644 index d67bea61..00000000 --- a/packages/system/monitoring/charts/cozy-lib/templates/_checkinput.tpl +++ /dev/null @@ -1,5 +0,0 @@ -{{- define "cozy-lib.checkInput" }} -{{- if not (kindIs "slice" .) }} -{{- fail (printf "called cozy-lib function without global scope, expected [, $], got %s" (kindOf .)) }} -{{- end }} -{{- end }} diff --git a/packages/system/monitoring/charts/cozy-lib/templates/_cozyconfig.tpl b/packages/system/monitoring/charts/cozy-lib/templates/_cozyconfig.tpl deleted file mode 100644 index 648b2617..00000000 --- a/packages/system/monitoring/charts/cozy-lib/templates/_cozyconfig.tpl +++ /dev/null @@ -1,130 +0,0 @@ -{{/* -Cluster-wide configuration helpers. -These helpers read from .Values._cluster which is populated via valuesFrom from Secret cozystack-values. -*/}} - -{{/* -Get the root host for the cluster. -Usage: {{ include "cozy-lib.root-host" . }} -*/}} -{{- define "cozy-lib.root-host" -}} -{{- (index .Values._cluster "root-host") | default "" }} -{{- end }} - -{{/* -Get the bundle name for the cluster. -Usage: {{ include "cozy-lib.bundle-name" . }} -*/}} -{{- define "cozy-lib.bundle-name" -}} -{{- (index .Values._cluster "bundle-name") | default "" }} -{{- end }} - -{{/* -Get the images registry. -Usage: {{ include "cozy-lib.images-registry" . }} -*/}} -{{- define "cozy-lib.images-registry" -}} -{{- (index .Values._cluster "images-registry") | default "" }} -{{- end }} - -{{/* -Get the ipv4 cluster CIDR. -Usage: {{ include "cozy-lib.ipv4-cluster-cidr" . }} -*/}} -{{- define "cozy-lib.ipv4-cluster-cidr" -}} -{{- (index .Values._cluster "ipv4-cluster-cidr") | default "" }} -{{- end }} - -{{/* -Get the ipv4 service CIDR. -Usage: {{ include "cozy-lib.ipv4-service-cidr" . }} -*/}} -{{- define "cozy-lib.ipv4-service-cidr" -}} -{{- (index .Values._cluster "ipv4-service-cidr") | default "" }} -{{- end }} - -{{/* -Get the ipv4 join CIDR. -Usage: {{ include "cozy-lib.ipv4-join-cidr" . }} -*/}} -{{- define "cozy-lib.ipv4-join-cidr" -}} -{{- (index .Values._cluster "ipv4-join-cidr") | default "" }} -{{- end }} - -{{/* -Get scheduling configuration. -Usage: {{ include "cozy-lib.scheduling" . }} -Returns: YAML string of scheduling configuration -*/}} -{{- define "cozy-lib.scheduling" -}} -{{- if .Values._cluster.scheduling }} -{{- .Values._cluster.scheduling | toYaml }} -{{- end }} -{{- end }} - -{{/* -Get branding configuration. -Usage: {{ include "cozy-lib.branding" . }} -Returns: YAML string of branding configuration -*/}} -{{- define "cozy-lib.branding" -}} -{{- if .Values._cluster.branding }} -{{- .Values._cluster.branding | toYaml }} -{{- end }} -{{- end }} - -{{/* -Namespace-specific configuration helpers. -These helpers read from .Values._namespace which is populated via valuesFrom from Secret cozystack-values. -*/}} - -{{/* -Get the host for this namespace. -Usage: {{ include "cozy-lib.ns-host" . }} -*/}} -{{- define "cozy-lib.ns-host" -}} -{{- .Values._namespace.host | default "" }} -{{- end }} - -{{/* -Get the etcd namespace reference. -Usage: {{ include "cozy-lib.ns-etcd" . }} -*/}} -{{- define "cozy-lib.ns-etcd" -}} -{{- .Values._namespace.etcd | default "" }} -{{- end }} - -{{/* -Get the ingress namespace reference. -Usage: {{ include "cozy-lib.ns-ingress" . }} -*/}} -{{- define "cozy-lib.ns-ingress" -}} -{{- .Values._namespace.ingress | default "" }} -{{- end }} - -{{/* -Get the monitoring namespace reference. -Usage: {{ include "cozy-lib.ns-monitoring" . }} -*/}} -{{- define "cozy-lib.ns-monitoring" -}} -{{- .Values._namespace.monitoring | default "" }} -{{- end }} - -{{/* -Get the seaweedfs namespace reference. -Usage: {{ include "cozy-lib.ns-seaweedfs" . }} -*/}} -{{- define "cozy-lib.ns-seaweedfs" -}} -{{- .Values._namespace.seaweedfs | default "" }} -{{- end }} - -{{/* -Legacy helper - kept for backward compatibility during migration. -Loads config into context. Deprecated: use direct .Values._cluster access instead. -*/}} -{{- define "cozy-lib.loadCozyConfig" }} -{{- include "cozy-lib.checkInput" . }} -{{- if not (hasKey (index . 1) "cozyConfig") }} -{{- $_ := set (index . 1) "cozyConfig" (dict "data" ((index . 1).Values._cluster | default dict)) }} -{{- end }} -{{- end }} diff --git a/packages/system/monitoring/charts/cozy-lib/templates/_network.tpl b/packages/system/monitoring/charts/cozy-lib/templates/_network.tpl deleted file mode 100644 index 4908f7d5..00000000 --- a/packages/system/monitoring/charts/cozy-lib/templates/_network.tpl +++ /dev/null @@ -1,23 +0,0 @@ -{{- define "cozy-lib.network.defaultDisableLoadBalancerNodePorts" }} -{{/* Default behavior prior to introduction */}} -{{- `true` }} -{{- end }} - -{{/* -Invoke as {{ include "cozy-lib.network.disableLoadBalancerNodePorts" $ }}. -Detects whether the current load balancer class requires nodeports to function -correctly. Currently just checks if Hetzner's RobotLB is enabled, which does -require nodeports, and so, returns `false`. Otherwise assumes that metallb is -in use and returns `true`. -*/}} - -{{- define "cozy-lib.network.disableLoadBalancerNodePorts" }} -{{- include "cozy-lib.loadCozyConfig" (list "" .) }} -{{- $cozyConfig := index . "cozyConfig" }} -{{- if not $cozyConfig }} -{{- include "cozy-lib.network.defaultDisableLoadBalancerNodePorts" . }} -{{- else }} -{{- $enabledComponents := splitList "," ((index $cozyConfig.data "bundle-enable") | default "") }} -{{- not (has "robotlb" $enabledComponents) }} -{{- end }} -{{- end }} diff --git a/packages/system/monitoring/charts/cozy-lib/templates/_rbac.tpl b/packages/system/monitoring/charts/cozy-lib/templates/_rbac.tpl deleted file mode 100644 index 0759c0a3..00000000 --- a/packages/system/monitoring/charts/cozy-lib/templates/_rbac.tpl +++ /dev/null @@ -1,106 +0,0 @@ -{{- define "cozy-lib.rbac.accessLevelMap" }} -view: 0 -use: 1 -admin: 2 -super-admin: 3 -{{- end }} - -{{- define "cozy-lib.rbac.accessLevelToInt" }} -{{- $accessMap := include "cozy-lib.rbac.accessLevelMap" "" | fromYaml }} -{{- $accessLevel := dig . -1 $accessMap | int }} -{{- if eq $accessLevel -1 }} -{{- printf "encountered access level of %s, allowed values are %s" . ($accessMap | keys) | fail }} -{{- end }} -{{- $accessLevel }} -{{- end }} - -{{- define "cozy-lib.rbac.accessLevelsAtOrAbove" }} -{{- $minLevelInt := include "cozy-lib.rbac.accessLevelToInt" . | int }} -{{- range $k, $v := (include "cozy-lib.rbac.accessLevelMap" "" | fromYaml) }} -{{- if ge (int $v) $minLevelInt }} -- {{ $k }} -{{- end }} -{{- end }} -{{- end }} - -{{- define "cozy-lib.rbac.allParentTenantsAndThis" }} -{{- if not (hasPrefix "tenant-" .) }} -{{- printf "'%s' is not a valid tenant identifier" . | fail }} -{{- end }} -{{- $parts := append (splitList "-" .) "" }} -{{- $tenants := list }} -{{- range untilStep 2 (len $parts) 1 }} -{{- $tenants = append $tenants (slice $parts 0 . | join "-") }} -{{- end }} -{{- range $tenants }} -- {{ . }} -{{- end }} -{{- if not (eq . "tenant-root") }} -- tenant-root -{{- end }} -{{- end }} - -{{- define "cozy-lib.rbac.groupSubject" -}} -- kind: Group - name: {{ . }} - apiGroup: rbac.authorization.k8s.io -{{- end }} - -{{- define "cozy-lib.rbac.serviceAccountSubject" -}} -- kind: ServiceAccount - name: {{ . }} - namespace: {{ . }} -{{- end }} - -{{- /* - A helper function to get a list of groups that should have access, given a - minimal access level and the tenant. Invoked as: - {{ include "cozy-lib.rbac.subjectsForTenantAndAccessLevel" (list "use" $) }} - For an example input of (list "use" $) and a .Release.Namespace of - tenant-abc-def it will return: - --- - - kind: Group - name: tenant-abc-admin - apiGroup: rbac.authorization.k8s.io - - kind: Group - name: tenant-abc-def-admin - apiGroup: rbac.authorization.k8s.io - - kind: Group - name: tenant-abc-super-admin - apiGroup: rbac.authorization.k8s.io - - kind: Group - name: tenant-abc-def-super-admin - apiGroup: rbac.authorization.k8s.io - - kind: Group - name: tenant-abc-use - apiGroup: rbac.authorization.k8s.io - - kind: Group - name: tenant-abc-def-use - apiGroup: rbac.authorization.k8s.io - - in other words, all roles including use and higher and for tenant-abc-def, as - well as all parent, grandparent, etc. tenants. -*/}} -{{- define "cozy-lib.rbac.subjectsForTenantAndAccessLevel" }} -{{- include "cozy-lib.checkInput" . }} -{{- $level := index . 0 }} -{{- $tenant := index . 1 }} -{{- $levels := include "cozy-lib.rbac.accessLevelsAtOrAbove" $level | fromYamlArray }} -{{- $tenants := include "cozy-lib.rbac.allParentTenantsAndThis" $tenant | fromYamlArray }} -{{- range $t := $tenants }} -{{- include "cozy-lib.rbac.serviceAccountSubject" $t }}{{ printf "\n" }} -{{- range $l := $levels }} -{{- include "cozy-lib.rbac.groupSubject" (printf "%s-%s" $t $l) }}{{ printf "\n" }} -{{- end }} -{{- end}} -{{- end }} - -{{- define "cozy-lib.rbac.subjectsForTenant" }} -{{- include "cozy-lib.checkInput" . }} -{{- $level := index . 0 }} -{{- $tenant := index . 1 }} -{{- $tenants := include "cozy-lib.rbac.allParentTenantsAndThis" $tenant | fromYamlArray }} -{{- range $t := $tenants }} -{{- include "cozy-lib.rbac.groupSubject" (printf "%s-%s" $t $level) }}{{ printf "\n" }} -{{- end}} -{{- end }} diff --git a/packages/system/monitoring/charts/cozy-lib/templates/_resourcepresets.tpl b/packages/system/monitoring/charts/cozy-lib/templates/_resourcepresets.tpl deleted file mode 100644 index 89d55342..00000000 --- a/packages/system/monitoring/charts/cozy-lib/templates/_resourcepresets.tpl +++ /dev/null @@ -1,50 +0,0 @@ -{{/* vim: set filetype=mustache: */}} - -{{- define "cozy-lib.resources.unsanitizedPreset" }} - -{{- $baseCPU := dict - "nano" (dict "cpu" "250m" ) - "micro" (dict "cpu" "500m" ) - "small" (dict "cpu" "1" ) - "medium" (dict "cpu" "1" ) - "large" (dict "cpu" "2" ) - "xlarge" (dict "cpu" "4" ) - "2xlarge" (dict "cpu" "8" ) -}} -{{- $baseMemory := dict - "nano" (dict "memory" "128Mi" ) - "micro" (dict "memory" "256Mi" ) - "small" (dict "memory" "512Mi" ) - "medium" (dict "memory" "1Gi" ) - "large" (dict "memory" "2Gi" ) - "xlarge" (dict "memory" "4Gi" ) - "2xlarge" (dict "memory" "8Gi" ) -}} -{{- $baseEphemeralStorage := dict - "nano" (dict "ephemeral-storage" "2Gi" ) - "micro" (dict "ephemeral-storage" "2Gi" ) - "small" (dict "ephemeral-storage" "2Gi" ) - "medium" (dict "ephemeral-storage" "2Gi" ) - "large" (dict "ephemeral-storage" "2Gi" ) - "xlarge" (dict "ephemeral-storage" "2Gi" ) - "2xlarge" (dict "ephemeral-storage" "2Gi" ) -}} - -{{- $presets := merge $baseCPU $baseMemory $baseEphemeralStorage }} -{{- if not (hasKey $presets .) -}} -{{- printf "ERROR: Preset key '%s' invalid. Allowed values are %s" . (join "," (keys $presets)) | fail -}} -{{- end }} -{{- index $presets . | toYaml }} -{{- end }} - -{{/* - Return a resource request/limit object based on a given preset. - {{- include "cozy-lib.resources.preset" list ("nano" $) }} -*/}} -{{- define "cozy-lib.resources.preset" -}} -{{- $cpuAllocationRatio := include "cozy-lib.resources.cpuAllocationRatio" . | float64 }} -{{- $args := index . 0 }} -{{- $global := index . 1 }} -{{- $unsanitizedPreset := include "cozy-lib.resources.unsanitizedPreset" list ($args $global) | fromYaml }} -{{- include "cozy-lib.resources.sanitize" (list $unsanitizedPreset $global) }} -{{- end -}} diff --git a/packages/system/monitoring/charts/cozy-lib/templates/_resources.tpl b/packages/system/monitoring/charts/cozy-lib/templates/_resources.tpl deleted file mode 100644 index cb055ea1..00000000 --- a/packages/system/monitoring/charts/cozy-lib/templates/_resources.tpl +++ /dev/null @@ -1,219 +0,0 @@ -{{- define "cozy-lib.resources.defaultCpuAllocationRatio" }} -{{- `10` }} -{{- end }} -{{- define "cozy-lib.resources.defaultMemoryAllocationRatio" }} -{{- `1` }} -{{- end }} -{{- define "cozy-lib.resources.defaultEphemeralStorageAllocationRatio" }} -{{- `40` }} -{{- end }} - -{{- define "cozy-lib.resources.cpuAllocationRatio" }} -{{- include "cozy-lib.loadCozyConfig" . }} -{{- $cozyConfig := index . 1 "cozyConfig" }} -{{- if not $cozyConfig }} -{{- include "cozy-lib.resources.defaultCpuAllocationRatio" . }} -{{- else }} -{{- dig "data" "cpu-allocation-ratio" (include "cozy-lib.resources.defaultCpuAllocationRatio" dict) $cozyConfig }} -{{- end }} -{{- end }} - -{{- define "cozy-lib.resources.memoryAllocationRatio" }} -{{- include "cozy-lib.loadCozyConfig" . }} -{{- $cozyConfig := index . 1 "cozyConfig" }} -{{- if not $cozyConfig }} -{{- include "cozy-lib.resources.defaultMemoryAllocationRatio" . }} -{{- else }} -{{- dig "data" "memory-allocation-ratio" (include "cozy-lib.resources.defaultMemoryAllocationRatio" dict) $cozyConfig }} -{{- end }} -{{- end }} - -{{- define "cozy-lib.resources.ephemeralStorageAllocationRatio" }} -{{- include "cozy-lib.loadCozyConfig" . }} -{{- $cozyConfig := index . 1 "cozyConfig" }} -{{- if not $cozyConfig }} -{{- include "cozy-lib.resources.defaultEphemeralStorageAllocationRatio" . }} -{{- else }} -{{- dig "data" "ephemeral-storage-allocation-ratio" (include "cozy-lib.resources.defaultEphemeralStorageAllocationRatio" dict) $cozyConfig }} -{{- end }} -{{- end }} - - -{{- define "cozy-lib.resources.toFloat" -}} - {{- $value := . -}} - {{- $unit := 1.0 -}} - {{- if typeIs "string" . -}} - {{- $base2 := dict "Ki" 0x1p10 "Mi" 0x1p20 "Gi" 0x1p30 "Ti" 0x1p40 "Pi" 0x1p50 "Ei" 0x1p60 -}} - {{- $base10 := dict "m" 1e-3 "k" 1e3 "M" 1e6 "G" 1e9 "T" 1e12 "P" 1e15 "E" 1e18 -}} - {{- range $k, $v := merge $base2 $base10 -}} - {{- if hasSuffix $k $ -}} - {{- $value = trimSuffix $k $ -}} - {{- $unit = $v -}} - {{- end -}} - {{- end -}} - {{- end -}} - {{- mulf (float64 $value) $unit | toString -}} -{{- end -}} - -{{- /* - A sanitized resource map is a dict with resource-name to resource-quantity. - All resources are returned with equal **requests** and **limits**, except for - **cpu**, whose *request* is reduced by the CPU-allocation ratio obtained from - `cozy-lib.resources.cpuAllocationRatio`. - - The template now expects **one flat map** as input (no nested `requests:` / - `limits:` sections). Each value in that map is taken as the *limit* for the - corresponding resource. Usage example: - - {{ include "cozy-lib.resources.sanitize" list (.Values.resources $) }} - - Example input: - ============== - cpu: "2" - memory: 256Mi - devices.com/nvidia: "1" - - Example output (cpuAllocationRatio = 10): - ========================================= - limits: - cpu: "2" - memory: 256Mi - devices.com/nvidia: "1" - requests: - cpu: 200m # 2 / 10 - memory: 256Mi # = limit - devices.com/nvidia: "1" # = limit -*/}} -{{- define "cozy-lib.resources.sanitize" }} -{{- $cpuAllocationRatio := include "cozy-lib.resources.cpuAllocationRatio" . | float64 }} -{{- $memoryAllocationRatio := include "cozy-lib.resources.memoryAllocationRatio" . | float64 }} -{{- $ephemeralStorageAllocationRatio := include "cozy-lib.resources.ephemeralStorageAllocationRatio" . | float64 }} -{{- $args := index . 0 }} -{{- $output := dict "requests" dict "limits" dict }} -{{- if or (hasKey $args "limits") (hasKey $args "requests") }} -{{- fail "ERROR: A flat map of resources expected, not nested `requests:` or `limits:` sections." -}} -{{- end }} -{{- range $k, $v := $args }} -{{- if eq $k "cpu" }} -{{- $vcpuRequestF64 := (include "cozy-lib.resources.toFloat" $v) | float64 }} -{{- $cpuRequestF64 := divf $vcpuRequestF64 $cpuAllocationRatio }} -{{- $_ := set $output.requests $k ($cpuRequestF64 | toString) }} -{{- $_ := set $output.limits $k ($v | toString) }} -{{- else if eq $k "memory" }} -{{- $vMemoryRequestF64 := (include "cozy-lib.resources.toFloat" $v) | float64 }} -{{- $memoryRequestF64 := divf $vMemoryRequestF64 $memoryAllocationRatio }} -{{- $_ := set $output.requests $k ($memoryRequestF64 | int | toString ) }} -{{- $_ := set $output.limits $k ($v | toString) }} -{{- else if eq $k "ephemeral-storage" }} -{{- $vEphemeralStorageRequestF64 := (include "cozy-lib.resources.toFloat" $v) | float64 }} -{{- $ephemeralStorageRequestF64 := divf $vEphemeralStorageRequestF64 $ephemeralStorageAllocationRatio }} -{{- $_ := set $output.requests $k ($ephemeralStorageRequestF64 | int | toString) }} -{{- $_ := set $output.limits $k ($v | toString) }} -{{- else }} -{{- $_ := set $output.requests $k $v }} -{{- $_ := set $output.limits $k $v }} -{{- end }} -{{- end }} -{{- $output | toYaml }} -{{- end }} - -{{- /* - The defaultingSanitize helper takes a 3-element list as its argument: - {{- include "cozy-lib.resources.defaultingSanitize" list ( - .Values.resourcesPreset - .Values.resources - $ - ) }} - and returns the same result as - {{- include "cozy-lib.resources.sanitize" list ( - .Values.resources - $ - ) }}, however if cpu, memory, or ephemeral storage is not specified in - .Values.resources, it is filled from the resource presets. - - Example input (cpuAllocationRatio = 10): - ======================================== - resources: - cpu: "1" - resourcesPreset: "nano" - - Example output: - =============== - resources: - limits: - cpu: "1" # == user input - ephemeral-storage: 2Gi # == default ephemeral storage limit - memory: 128Mi # from "nano" - requests: - cpu: 100m # == 1 / 10 - ephemeral-storage: 50Mi # == default ephemeral storage request - memory: 128Mi # memory request == limit -*/}} -{{- define "cozy-lib.resources.defaultingSanitize" }} -{{- $preset := index . 0 }} -{{- $resources := index . 1 }} -{{- $global := index . 2 }} -{{- $presetMap := include "cozy-lib.resources.unsanitizedPreset" $preset | fromYaml }} -{{- $mergedMap := deepCopy (default (dict) $resources) | mergeOverwrite $presetMap }} -{{- include "cozy-lib.resources.sanitize" (list $mergedMap $global) }} -{{- end }} - -{{- /* - javaHeap takes a .Values.resources and returns Java heap settings based on - memory requests and limits. -Xmx is set to 75% of memory limits, -Xms is - set to the lesser of requests or 25% of limits. Accepts only sanitized - resource maps. -*/}} -{{- define "cozy-lib.resources.javaHeap" }} -{{- $memoryRequestInt := include "cozy-lib.resources.toFloat" .requests.memory | float64 | int64 }} -{{- $memoryLimitInt := include "cozy-lib.resources.toFloat" .limits.memory | float64 | int64 }} -{{- /* 4194304 is 4Mi */}} -{{- $xmxMi := div (mul $memoryLimitInt 3) 4194304 }} -{{- $xmsMi := min (div $memoryLimitInt 4194304) (div $memoryRequestInt 1048576) }} -{{- printf `-Xms%dm -Xmx%dm` $xmsMi $xmxMi }} -{{- end }} - -{{- define "cozy-lib.resources.flatten" -}} -{{- $out := dict -}} -{{- $res := include "cozy-lib.resources.sanitize" . | fromYaml -}} -{{- range $section, $values := $res }} -{{- range $k, $v := $values }} -{{- with include "cozy-lib.resources.flattenResource" (list $section $k) }} -{{- $_ := set $out . $v }} -{{- end }} -{{- end }} -{{- end }} -{{- $out | toYaml }} -{{- end }} - -{{/* - This is a helper function that takes an argument like `list "limits" "services.loadbalancers"` - or `list "limits" "storage"` or `list "requests" "cpu"` and returns "services.loadbalancers", - "", and "requests.cpu", respectively, thus transforming them to an acceptable format for k8s - ResourceQuotas objects. -*/}} -{{- define "cozy-lib.resources.flattenResource" }} -{{- $rawQuotaKeys := list - "pods" - "services" - "services.loadbalancers" - "services.nodeports" - "services.clusterip" - "configmaps" - "secrets" - "persistentvolumeclaims" - "replicationcontrollers" - "resourcequotas" --}} -{{- $section := index . 0 }} -{{- $type := index . 1 }} -{{- $out := "" }} -{{- if and (eq $section "limits") (eq $type "storage") }} -{{- $out = "" }} -{{- else if and (eq $section "limits") (has $type $rawQuotaKeys) }} -{{- $out = $type }} -{{- else if not (has $type $rawQuotaKeys) }} -{{- $out = printf "%s.%s" $section $type }} -{{- end }} -{{- $out -}} -{{- end }} diff --git a/packages/system/monitoring/charts/cozy-lib/values.schema.json b/packages/system/monitoring/charts/cozy-lib/values.schema.json deleted file mode 100644 index decc79aa..00000000 --- a/packages/system/monitoring/charts/cozy-lib/values.schema.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "title": "Chart Values", - "type": "object", - "properties": {} -} \ No newline at end of file diff --git a/packages/system/monitoring/charts/cozy-lib/values.yaml b/packages/system/monitoring/charts/cozy-lib/values.yaml deleted file mode 100644 index 0967ef42..00000000 --- a/packages/system/monitoring/charts/cozy-lib/values.yaml +++ /dev/null @@ -1 +0,0 @@ -{} From 19afeff924bf417586b406d6a150d50f2e1a115c Mon Sep 17 00:00:00 2001 From: IvanHunters Date: Fri, 30 Jan 2026 18:04:28 +0300 Subject: [PATCH 171/889] review changes Signed-off-by: IvanHunters --- Makefile | 2 +- .../sources/monitoring-application.yaml | 6 +- packages/system/monitoring/Chart.yaml | 3 - packages/system/monitoring/dashboards.list | 45 -------- packages/system/monitoring/values.yaml | 100 +----------------- 5 files changed, 4 insertions(+), 152 deletions(-) delete mode 100644 packages/system/monitoring/dashboards.list diff --git a/Makefile b/Makefile index 53d4f82b..ba0c8df7 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ build: build-deps make -C packages/apps/mysql image make -C packages/apps/clickhouse image make -C packages/apps/kubernetes image - make -C packages/extra/monitoring image + make -C packages/system/monitoring image make -C packages/system/cozystack-api image make -C packages/system/cozystack-controller image make -C packages/system/backup-controller image diff --git a/packages/core/platform/sources/monitoring-application.yaml b/packages/core/platform/sources/monitoring-application.yaml index ae0d9241..6ea5a2d0 100644 --- a/packages/core/platform/sources/monitoring-application.yaml +++ b/packages/core/platform/sources/monitoring-application.yaml @@ -1,4 +1,4 @@ - +--- apiVersion: cozystack.io/v1alpha1 kind: PackageSource metadata: @@ -12,8 +12,6 @@ spec: variants: - name: default dependsOn: - - cozystack.cozystack-controller - - cozystack.vertical-pod-autoscaler - cozystack.networking - cozystack.postgres-operator libraries: @@ -27,4 +25,4 @@ spec: path: system/monitoring-rd install: namespace: cozy-system - releaseName: monitoring-rd \ No newline at end of file + releaseName: monitoring-rd diff --git a/packages/system/monitoring/Chart.yaml b/packages/system/monitoring/Chart.yaml index 2db12ec5..a97b02de 100644 --- a/packages/system/monitoring/Chart.yaml +++ b/packages/system/monitoring/Chart.yaml @@ -1,6 +1,3 @@ apiVersion: v2 name: monitoring -description: Monitoring and observability stack -icon: /logos/monitoring.svg -type: application version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process diff --git a/packages/system/monitoring/dashboards.list b/packages/system/monitoring/dashboards.list deleted file mode 100644 index 1f4b2eea..00000000 --- a/packages/system/monitoring/dashboards.list +++ /dev/null @@ -1,45 +0,0 @@ -dotdc/k8s-views-global -dotdc/k8s-views-namespaces -dotdc/k8s-system-coredns -dotdc/k8s-views-pods -cache/nginx-vts-stats -victoria-metrics/vmalert -victoria-metrics/vmagent -victoria-metrics/victoriametrics-cluster -victoria-metrics/backupmanager -victoria-metrics/victoriametrics -victoria-metrics/operator -ingress/controller-detail -ingress/controllers -ingress/namespaces -ingress/vhosts -ingress/vhost-detail -ingress/namespace-detail -db/cloudnativepg -db/maria-db -db/redis -main/controller -main/namespaces -main/capacity-planning -main/ntp -main/namespace -main/pod -main/volumes -main/node -main/nodes -control-plane/control-plane-status -control-plane/deprecated-resources -control-plane/dns-coredns -control-plane/kube-etcd -kubevirt/kubevirt-control-plane -flux/flux-control-plane -flux/flux-stats -kafka/strimzi-kafka -goldpinger/goldpinger -clickhouse/altinity-clickhouse-operator-dashboard -storage/linstor -seaweedfs/seaweedfs -hubble/overview -hubble/dns-namespace -hubble/l7-http-metrics -hubble/network-overview diff --git a/packages/system/monitoring/values.yaml b/packages/system/monitoring/values.yaml index ab32b1b2..564c654d 100644 --- a/packages/system/monitoring/values.yaml +++ b/packages/system/monitoring/values.yaml @@ -1,40 +1,5 @@ -## @section Common parameters -## - -## @param {string} host - The hostname used to access Grafana externally (defaults to 'grafana' subdomain for the tenant host). host: "" -## -## @section Metrics storage configuration -## - -## @typedef {struct} Request - Minimum guaranteed resources. -## @field {quantity} [cpu] - CPU request. -## @field {quantity} [memory] - Memory request. - -## @typedef {struct} Limit - Maximum allowed resources. -## @field {quantity} [cpu] - CPU limit. -## @field {quantity} [memory] - Memory limit. - -## @typedef {struct} VMComponent - VictoriaMetrics component configuration. -## @field {Request} [minAllowed] - Minimum guaranteed resources. -## @field {Limit} [maxAllowed] - Maximum allowed resources. - -## @typedef {struct} Resources - Combined resource configuration. -## @field {Request} [requests] - Resource requests. -## @field {Limit} [limits] - Resource limits. - -## @typedef {struct} MetricsStorage - Configuration of metrics storage instance. -## @field {string} name - Name of the storage instance. -## @field {string} retentionPeriod - Retention period for metrics. -## @field {string} deduplicationInterval - Deduplication interval for metrics. -## @field {string} storage="10Gi" - Persistent volume size. -## @field {string} [storageClassName] - StorageClass used for the data. -## @field {VMComponent} [vminsert] - Configuration for vminsert. -## @field {VMComponent} [vmselect] - Configuration for vmselect. -## @field {VMComponent} [vmstorage] - Configuration for vmstorage. - -## @param {[]MetricsStorage} metricsStorages - Configuration of metrics storage instances. metricsStorages: ## Example: ## metricsStorages: @@ -75,47 +40,11 @@ metricsStorages: storage: 10Gi storageClassName: "" -## -## @section Logs storage configuration -## - -## @typedef {struct} LogsStorage - Configuration of logs storage instance. -## @field {string} name - Name of the storage instance. -## @field {string} retentionPeriod="1" - Retention period for logs. -## @field {string} storage="10Gi" - Persistent volume size. -## @field {string} storageClassName="replicated" - StorageClass used to store the data. - -## @param {[]LogsStorage} logsStorages - Configuration of logs storage instances. logsStorages: - name: generic retentionPeriod: "1" storage: 10Gi storageClassName: replicated - -## -## @section Alerta configuration -## - -## @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"]). - -## @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. -## @field {SlackAlerts} [slack] - Configuration for Slack alerts. - -## @typedef {struct} Alerta - Configuration for the Alerta service. -## @field {string} [storage] - Persistent volume size for the database. -## @field {string} [storageClassName] - StorageClass used for the database. -## @field {Resources} [resources] - Resource configuration. -## @field {Alerts} [alerts] - Alert routing configuration. - -## @param {Alerta} alerta - Configuration for the Alerta service. alerta: storage: 10Gi storageClassName: "" @@ -134,18 +63,6 @@ alerta: slack: url: "" disabledSeverity: [] -## -## @section Grafana configuration -## - -## @typedef {struct} GrafanaDB - Grafana database configuration. -## @field {string} [size] - Persistent volume size for the database. - -## @typedef {struct} Grafana - Grafana service configuration. -## @field {GrafanaDB} [db] - Database configuration. -## @field {Resources} [resources] - Resource configuration. - -## @param {Grafana} grafana - Configuration for Grafana. grafana: db: size: 10Gi @@ -156,25 +73,10 @@ grafana: requests: cpu: 100m memory: 256Mi -## -## @section Vmagent configuration -## - -## @typedef {struct} VmagentRemoteWrite - Remote write configuration. -## @typedef {stringSlice} VmagentRemoteWriteURLs - List of remoteWrite endpoint URLs. - -## @typedef {struct} VmagentExternalLabels - External labels for metrics. -## @field {string} [] - Label value for the given key. - -## @typedef {struct} Vmagent - VictoriaMetrics Agent configuration. -## @field {VmagentExternalLabels} [externalLabels] - External labels applied to all metrics. -## @field {VmagentRemoteWrite} [remoteWrite] - Remote write configuration. - -## @param {Vmagent} vmagent - Configuration for VictoriaMetrics Agent. vmagent: externalLabels: cluster: cozystack remoteWrite: urls: - http://vminsert-shortterm:8480/insert/0/prometheus - - http://vminsert-longterm:8480/insert/0/prometheus + - http://vminsert-longterm:8480/insert/0/prometheus \ No newline at end of file From 874a23846041709851b6e65dbd4d13c436818010 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 3 Feb 2026 18:53:37 +0100 Subject: [PATCH 172/889] feat(monitoring): add migration for monitoring-system HelmRelease Add migration 26 that re-labels monitoring resources to be owned by monitoring-system HelmRelease instead of monitoring. This allows seamless transition from direct resource management in extra/monitoring to delegated management via system/monitoring chart. Migration steps: - Find all monitoring HelmReleases in tenant namespaces - Suspend HelmRelease to prevent reconciliation - Delete helm secrets to orphan resources - Relabel all resources to monitoring-system - Delete suspended HelmRelease Also updates: - monitoring-application.yaml: add monitoring-system component - helmrelease.yaml: reference monitoring-system ExternalArtifact - values.yaml: bump targetVersion to 25 Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- .../platform/images/migrations/migrations/26 | 140 ++++++++++++++++++ .../sources/monitoring-application.yaml | 5 + packages/core/platform/values.yaml | 4 +- packages/extra/monitoring/charts/cozy-lib | 1 + .../monitoring/logos/monitoring.svg | 0 .../monitoring/templates/helmrelease.yaml | 19 +-- 6 files changed, 152 insertions(+), 17 deletions(-) create mode 100755 packages/core/platform/images/migrations/migrations/26 create mode 120000 packages/extra/monitoring/charts/cozy-lib rename packages/{system => extra}/monitoring/logos/monitoring.svg (100%) diff --git a/packages/core/platform/images/migrations/migrations/26 b/packages/core/platform/images/migrations/migrations/26 new file mode 100755 index 00000000..5f385b6e --- /dev/null +++ b/packages/core/platform/images/migrations/migrations/26 @@ -0,0 +1,140 @@ +#!/bin/sh +# Migration 26 --> 27 +# Migrate monitoring resources from extra/monitoring to system/monitoring +# This migration re-labels resources so they become owned by monitoring-system HelmRelease + +set -euo pipefail + +echo "Starting monitoring migration to monitoring-system" + +# Function to relabel resources of a given type in a namespace +relabel_resources() { + local ns="$1" + local resource_type="$2" + + # Get resources with monitoring release annotation + local resources + resources=$(kubectl get "$resource_type" -n "$ns" -o json 2>/dev/null | \ + jq -r '.items[] | select(.metadata.annotations["meta.helm.sh/release-name"] == "monitoring") | .metadata.name' 2>/dev/null || true) + + for resource_name in $resources; do + # Skip dashboard-resourcemap resources (they stay with parent monitoring release) + if echo "$resource_name" | grep -q "dashboard-resources"; then + echo " Skipping $resource_type/$resource_name (stays with parent release)" + continue + fi + + echo " Relabeling $resource_type/$resource_name" + + # Update annotations and labels + kubectl annotate "$resource_type" -n "$ns" "$resource_name" \ + meta.helm.sh/release-name=monitoring-system --overwrite 2>/dev/null || true + + kubectl label "$resource_type" -n "$ns" "$resource_name" \ + helm.toolkit.fluxcd.io/name=monitoring-system --overwrite 2>/dev/null || true + done +} + +# Find all tenant namespaces with monitoring HelmRelease +echo "Finding tenant namespaces with monitoring HelmRelease..." +NAMESPACES=$(kubectl get hr --all-namespaces -l apps.cozystack.io/application.kind=Monitoring \ + -o jsonpath='{range .items[*]}{.metadata.namespace}{"\n"}{end}' 2>/dev/null | sort -u || true) + +if [ -z "$NAMESPACES" ]; then + echo "No monitoring HelmReleases found in tenant namespaces, skipping migration" + kubectl create configmap -n cozy-system cozystack-version \ + --from-literal=version=27 --dry-run=client -o yaml | kubectl apply -f- + exit 0 +fi + +echo "Found namespaces with monitoring:" +echo "$NAMESPACES" + +# Process each namespace +for ns in $NAMESPACES; do + echo "" + echo "=========================================" + echo "Processing namespace: $ns" + echo "=========================================" + + # Check if monitoring HelmRelease exists + if ! kubectl get hr -n "$ns" monitoring >/dev/null 2>&1; then + echo "No monitoring HelmRelease in $ns, skipping" + continue + fi + + # Step 1: Suspend the HelmRelease + echo "" + echo "Step 1: Suspending HelmRelease monitoring..." + kubectl patch hr -n "$ns" monitoring --type=merge -p '{"spec":{"suspend":true}}' 2>/dev/null || true + + # Wait a moment for reconciliation to stop + sleep 2 + + # Step 2: Delete helm secrets for the monitoring release + echo "" + echo "Step 2: Deleting helm secrets for monitoring release..." + kubectl delete secrets -n "$ns" -l name=monitoring,owner=helm --ignore-not-found + + # Step 3: Relabel resources to be owned by monitoring-system + echo "" + echo "Step 3: Relabeling resources to monitoring-system..." + + # Core resources + echo "Processing core resources..." + relabel_resources "$ns" "secrets" + relabel_resources "$ns" "configmaps" + relabel_resources "$ns" "services" + + # Apps resources + echo "Processing apps resources..." + relabel_resources "$ns" "deployments.apps" + + # Networking resources + echo "Processing networking resources..." + relabel_resources "$ns" "ingresses.networking.k8s.io" + + # PostgreSQL operator resources + echo "Processing PostgreSQL resources..." + relabel_resources "$ns" "clusters.postgresql.cnpg.io" + + # Grafana operator resources + echo "Processing Grafana resources..." + relabel_resources "$ns" "grafanas.grafana.integreatly.org" + relabel_resources "$ns" "grafanadashboards.grafana.integreatly.org" + relabel_resources "$ns" "grafanadatasources.grafana.integreatly.org" + + # VictoriaMetrics operator resources + echo "Processing VictoriaMetrics resources..." + relabel_resources "$ns" "vmagents.operator.victoriametrics.com" + relabel_resources "$ns" "vmalerts.operator.victoriametrics.com" + relabel_resources "$ns" "vmalertmanagers.operator.victoriametrics.com" + relabel_resources "$ns" "vmclusters.operator.victoriametrics.com" + relabel_resources "$ns" "vmservicescrapes.operator.victoriametrics.com" + relabel_resources "$ns" "vlogs.operator.victoriametrics.com" + + # VPA resources + echo "Processing VPA resources..." + relabel_resources "$ns" "verticalpodautoscalers.autoscaling.k8s.io" + + # Cozystack resources + echo "Processing Cozystack resources..." + relabel_resources "$ns" "workloadmonitors.cozystack.io" + + # Step 4: Delete the suspended HelmRelease (Flux won't delete resources when HR is suspended) + echo "" + echo "Step 4: Deleting suspended HelmRelease monitoring..." + kubectl delete hr -n "$ns" monitoring --ignore-not-found + + echo "" + echo "Completed migration for namespace: $ns" +done + +echo "" +echo "=========================================" +echo "Monitoring migration completed successfully" +echo "=========================================" + +# Stamp version +kubectl create configmap -n cozy-system cozystack-version \ + --from-literal=version=27 --dry-run=client -o yaml | kubectl apply -f- diff --git a/packages/core/platform/sources/monitoring-application.yaml b/packages/core/platform/sources/monitoring-application.yaml index 6ea5a2d0..119aaa37 100644 --- a/packages/core/platform/sources/monitoring-application.yaml +++ b/packages/core/platform/sources/monitoring-application.yaml @@ -14,10 +14,15 @@ spec: dependsOn: - cozystack.networking - cozystack.postgres-operator + - cozystack.grafana-operator + - cozystack.victoria-metrics-operator libraries: - name: cozy-lib path: library/cozy-lib components: + - name: monitoring-system + path: system/monitoring + libraries: ["cozy-lib"] - name: monitoring path: extra/monitoring libraries: ["cozy-lib"] diff --git a/packages/core/platform/values.yaml b/packages/core/platform/values.yaml index 55e56e62..4709b82e 100644 --- a/packages/core/platform/values.yaml +++ b/packages/core/platform/values.yaml @@ -5,8 +5,8 @@ sourceRef: path: / migrations: enabled: false - image: ghcr.io/cozystack/cozystack/platform-migrations:latest@sha256:24507a6004ac46e6690327e9063a7b73b87e60d5c740ec680bffa1d0895ab4c8 - targetVersion: 26 + image: ghcr.io/cozystack/cozystack/platform-migrations:latest + targetVersion: 27 # Bundle deployment configuration bundles: system: diff --git a/packages/extra/monitoring/charts/cozy-lib b/packages/extra/monitoring/charts/cozy-lib new file mode 120000 index 00000000..e1813509 --- /dev/null +++ b/packages/extra/monitoring/charts/cozy-lib @@ -0,0 +1 @@ +../../../library/cozy-lib \ No newline at end of file diff --git a/packages/system/monitoring/logos/monitoring.svg b/packages/extra/monitoring/logos/monitoring.svg similarity index 100% rename from packages/system/monitoring/logos/monitoring.svg rename to packages/extra/monitoring/logos/monitoring.svg diff --git a/packages/extra/monitoring/templates/helmrelease.yaml b/packages/extra/monitoring/templates/helmrelease.yaml index 3560c6b3..0c3c2377 100644 --- a/packages/extra/monitoring/templates/helmrelease.yaml +++ b/packages/extra/monitoring/templates/helmrelease.yaml @@ -1,25 +1,13 @@ apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease metadata: - name: monitoring - namespace: {{ .Release.Namespace }} + name: {{ .Release.Name }}-system labels: sharding.fluxcd.io/key: tenants - internal.cozystack.io/tenantmodule: "true" - app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/managed-by: {{ .Release.Service }} - apps.cozystack.io/application.kind: Monitoring - apps.cozystack.io/application.group: apps.cozystack.io - apps.cozystack.io/application.name: monitoring spec: - dependsOn: - - name: cozystack.cozystack-controller - namespace: cozy-system - - name: cozystack.vertical-pod-autoscaler - namespace: cozy-system chartRef: kind: ExternalArtifact - name: cozystack-monitoring-application-default-monitoring + name: cozystack-monitoring-application-default-monitoring-system namespace: cozy-system interval: 5m timeout: 10m @@ -32,4 +20,5 @@ spec: retries: -1 valuesFrom: - kind: Secret - name: cozystack-values \ No newline at end of file + name: cozystack-values + values: {{- .Values | toYaml | nindent 4 }} From 2cb299e60272f698b0a37bf22dd4b1ae556a72ed Mon Sep 17 00:00:00 2001 From: mattia-eleuteri Date: Wed, 4 Feb 2026 10:05:04 +0100 Subject: [PATCH 173/889] fix(postgres-operator): correct PromQL syntax in CNPGClusterOffline alert Remove extra closing parenthesis in the CNPGClusterOffline alert expression that causes vmalert pods to crash with "bad prometheus expr" error. Signed-off-by: mattia-eleuteri --- .../system/postgres-operator/alerts/cnpg-default-alerts.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/system/postgres-operator/alerts/cnpg-default-alerts.yaml b/packages/system/postgres-operator/alerts/cnpg-default-alerts.yaml index a23fb3f4..9c346e41 100644 --- a/packages/system/postgres-operator/alerts/cnpg-default-alerts.yaml +++ b/packages/system/postgres-operator/alerts/cnpg-default-alerts.yaml @@ -92,7 +92,7 @@ spec: potential service disruption and/or data loss. runbook_url: https://github.com/cloudnative-pg/charts/blob/main/charts/cluster/docs/runbooks/CNPGClusterOffline.md expr: | - sum by (namespace, pod) (cnpg_collector_up)) OR on() vector(0) == 0 + (sum by (namespace, pod) (cnpg_collector_up) OR on() vector(0)) == 0 for: 5m labels: severity: critical From 23e399bd9af19f001035c29545cda6638bf5ac3e Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Wed, 4 Feb 2026 07:50:10 +0300 Subject: [PATCH 174/889] [dashboard] Verify JWT token ## What this PR does When OIDC is disabled, the dashboard's token-proxy now properly validates bearer tokens against the k8s API's JWKS url. ### Release note ```release-note [dashboard] Verify bearer tokens against the issuer's JWKS url. ``` Signed-off-by: Timofei Larkin --- .../dashboard/images/token-proxy/go.mod | 17 +- .../dashboard/images/token-proxy/go.sum | 41 +++- .../dashboard/images/token-proxy/main.go | 221 ++++++++++++++---- .../dashboard/templates/gatekeeper-sa.yaml | 27 +++ .../dashboard/templates/gatekeeper.yaml | 1 - 5 files changed, 258 insertions(+), 49 deletions(-) diff --git a/packages/system/dashboard/images/token-proxy/go.mod b/packages/system/dashboard/images/token-proxy/go.mod index 4ac8c21c..01f15bc8 100644 --- a/packages/system/dashboard/images/token-proxy/go.mod +++ b/packages/system/dashboard/images/token-proxy/go.mod @@ -3,6 +3,21 @@ module token-proxy go 1.24.0 require ( - github.com/golang-jwt/jwt/v5 v5.3.0 github.com/gorilla/securecookie v1.1.2 + github.com/lestrrat-go/httprc/v3 v3.0.2 + github.com/lestrrat-go/jwx/v3 v3.0.13 +) + +require ( + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect + github.com/goccy/go-json v0.10.3 // indirect + github.com/lestrrat-go/blackmagic v1.0.4 // indirect + github.com/lestrrat-go/dsig v1.0.0 // indirect + github.com/lestrrat-go/dsig-secp256k1 v1.0.0 // indirect + github.com/lestrrat-go/httpcc v1.0.1 // indirect + github.com/lestrrat-go/option/v2 v2.0.0 // indirect + github.com/segmentio/asm v1.2.1 // indirect + github.com/valyala/fastjson v1.6.7 // indirect + golang.org/x/crypto v0.46.0 // indirect + golang.org/x/sys v0.39.0 // indirect ) diff --git a/packages/system/dashboard/images/token-proxy/go.sum b/packages/system/dashboard/images/token-proxy/go.sum index 68e4e8cc..f9f2e7e5 100644 --- a/packages/system/dashboard/images/token-proxy/go.sum +++ b/packages/system/dashboard/images/token-proxy/go.sum @@ -1,6 +1,43 @@ -github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= -github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 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/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA= +github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw= +github.com/lestrrat-go/dsig v1.0.0 h1:OE09s2r9Z81kxzJYRn07TFM9XA4akrUdoMwr0L8xj38= +github.com/lestrrat-go/dsig v1.0.0/go.mod h1:dEgoOYYEJvW6XGbLasr8TFcAxoWrKlbQvmJgCR0qkDo= +github.com/lestrrat-go/dsig-secp256k1 v1.0.0 h1:JpDe4Aybfl0soBvoVwjqDbp+9S1Y2OM7gcrVVMFPOzY= +github.com/lestrrat-go/dsig-secp256k1 v1.0.0/go.mod h1:CxUgAhssb8FToqbL8NjSPoGQlnO4w3LG1P0qPWQm/NU= +github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= +github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/httprc/v3 v3.0.2 h1:7u4HUaD0NQbf2/n5+fyp+T10hNCsAnwKfqn4A4Baif0= +github.com/lestrrat-go/httprc/v3 v3.0.2/go.mod h1:mSMtkZW92Z98M5YoNNztbRGxbXHql7tSitCvaxvo9l0= +github.com/lestrrat-go/jwx/v3 v3.0.13 h1:AdHKiPIYeCSnOJtvdpipPg/0SuFh9rdkN+HF3O0VdSk= +github.com/lestrrat-go/jwx/v3 v3.0.13/go.mod h1:2m0PV1A9tM4b/jVLMx8rh6rBl7F6WGb3EG2hufN9OQU= +github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss= +github.com/lestrrat-go/option/v2 v2.0.0/go.mod h1:oSySsmzMoR0iRzCDCaUfsCzxQHUEuhOViQObyy7S6Vg= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0= +github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/valyala/fastjson v1.6.7 h1:ZE4tRy0CIkh+qDc5McjatheGX2czdn8slQjomexVpBM= +github.com/valyala/fastjson v1.6.7/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +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= diff --git a/packages/system/dashboard/images/token-proxy/main.go b/packages/system/dashboard/images/token-proxy/main.go index a4a177d1..35fc9b70 100644 --- a/packages/system/dashboard/images/token-proxy/main.go +++ b/packages/system/dashboard/images/token-proxy/main.go @@ -1,6 +1,9 @@ package main import ( + "context" + "crypto/tls" + "crypto/x509" "encoding/base64" "encoding/json" "flag" @@ -13,10 +16,13 @@ import ( "os" "path" "strings" + "sync" "time" - "github.com/golang-jwt/jwt/v5" "github.com/gorilla/securecookie" + "github.com/lestrrat-go/httprc/v3" + "github.com/lestrrat-go/jwx/v3/jwk" + "github.com/lestrrat-go/jwx/v3/jwt" ) /* ----------------------------- flags ------------------------------------ */ @@ -26,7 +32,9 @@ var ( cookieName, cookieSecretB64 string cookieSecure bool cookieRefresh time.Duration - tokenCheckURL string + jwksURL string + saTokenPath string + saCACertPath string ) func init() { @@ -38,7 +46,70 @@ func init() { flag.StringVar(&cookieSecretB64, "cookie-secret", "", "Base64-encoded cookie secret") flag.BoolVar(&cookieSecure, "cookie-secure", false, "Set Secure flag on cookie") flag.DurationVar(&cookieRefresh, "cookie-refresh", 0, "Cookie refresh interval (e.g. 1h)") - flag.StringVar(&tokenCheckURL, "token-check-url", "", "URL for external token validation") + flag.StringVar(&jwksURL, "jwks-url", "https://kubernetes.default.svc/openid/v1/jwks", "JWKS URL for token verification") + flag.StringVar(&saTokenPath, "sa-token-path", "/var/run/secrets/kubernetes.io/serviceaccount/token", "Path to service account token") + flag.StringVar(&saCACertPath, "sa-ca-cert-path", "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt", "Path to service account CA certificate") + + flag.Parse() + + // Initialize jwkCache + ctx := context.Background() + // Load CA certificate + caCert, err := os.ReadFile(saCACertPath) + if err != nil { + jwkCacheErr := fmt.Errorf("failed to read CA cert: %w", err) + panic(jwkCacheErr) + } + caCertPool := x509.NewCertPool() + if !caCertPool.AppendCertsFromPEM(caCert) { + jwkCacheErr := fmt.Errorf("failed to parse CA cert") + panic(jwkCacheErr) + } + + // Create transport with SA token injection + transport := &saTokenTransport{ + base: &http.Transport{ + TLSClientConfig: &tls.Config{ + RootCAs: caCertPool, + }, + }, + tokenPath: saTokenPath, + } + transport.startRefresh(ctx, 5*time.Minute) + + httpClient := &http.Client{ + Transport: transport, + Timeout: 10 * time.Second, + } + + // Create httprc client with custom HTTP client + httprcClient := httprc.NewClient( + httprc.WithHTTPClient(httpClient), + ) + + // Create JWK cache + jwkCache, err = jwk.NewCache(ctx, httprcClient) + if err != nil { + jwkCacheErr := fmt.Errorf("failed to create JWK cache: %w", err) + panic(jwkCacheErr) + } + + // Register the JWKS URL with refresh settings + if err := jwkCache.Register(ctx, jwksURL, + jwk.WithMinInterval(5*time.Minute), + jwk.WithMaxInterval(15*time.Minute), + ); err != nil { + jwkCacheErr := fmt.Errorf("failed to register JWKS URL: %w", err) + panic(jwkCacheErr) + } + + // Perform initial fetch to ensure the JWKS is available + if _, err := jwkCache.Refresh(ctx, jwksURL); err != nil { + jwkCacheErr := fmt.Errorf("failed to fetch initial JWKS: %w", err) + panic(jwkCacheErr) + } + + log.Printf("JWK cache initialized with JWKS URL: %s", jwksURL) } /* ----------------------------- templates -------------------------------- */ @@ -117,42 +188,94 @@ var loginTmpl = template.Must(template.New("login").Parse(` `)) -/* ----------------------------- helpers ---------------------------------- */ +/* ----------------------------- JWK cache -------------------------------- */ -func decodeJWT(raw string) jwt.MapClaims { - if raw == "" { - return jwt.MapClaims{} - } - tkn, _, err := new(jwt.Parser).ParseUnverified(raw, jwt.MapClaims{}) - if err != nil || tkn == nil { - return jwt.MapClaims{} - } - if c, ok := tkn.Claims.(jwt.MapClaims); ok { - return c - } - return jwt.MapClaims{} +var ( + jwkCache *jwk.Cache +) + +// saTokenTransport adds the service account token to requests and refreshes it periodically. +type saTokenTransport struct { + base http.RoundTripper + tokenPath string + mu sync.RWMutex + token string } -func externalTokenCheck(raw string) error { - if tokenCheckURL == "" { +func (t *saTokenTransport) RoundTrip(req *http.Request) (*http.Response, error) { + t.mu.RLock() + token := t.token + t.mu.RUnlock() + + if token != "" { + req = req.Clone(req.Context()) + req.Header.Set("Authorization", "Bearer "+token) + } + return t.base.RoundTrip(req) +} + +func (t *saTokenTransport) refreshToken() { + data, err := os.ReadFile(t.tokenPath) + if err != nil { + log.Printf("warning: failed to read SA token: %v", err) + return + } + t.mu.Lock() + t.token = string(data) + t.mu.Unlock() +} + +func (t *saTokenTransport) startRefresh(ctx context.Context, interval time.Duration) { + t.refreshToken() + go func() { + ticker := time.NewTicker(interval) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + t.refreshToken() + } + } + }() +} + +/* ----------------------------- helpers ---------------------------------- */ + +// verifyAndParseJWT verifies the token signature and returns the parsed token. +func verifyAndParseJWT(ctx context.Context, raw string) (jwt.Token, error) { + if raw == "" { + return nil, fmt.Errorf("empty token") + } + + keySet, err := jwkCache.Lookup(ctx, jwksURL) + if err != nil { + return nil, fmt.Errorf("failed to get JWKS: %w", err) + } + + token, err := jwt.Parse([]byte(raw), jwt.WithKeySet(keySet)) + if err != nil { + return nil, fmt.Errorf("failed to verify token: %w", err) + } + + return token, nil +} + +// getClaim extracts a claim value from a verified token. +func getClaim(token jwt.Token, key string) any { + if token == nil { return nil } - req, _ := http.NewRequest(http.MethodGet, tokenCheckURL, nil) - req.Header.Set("Authorization", "Bearer "+raw) - cli := &http.Client{Timeout: 5 * time.Second} - resp, err := cli.Do(req) - if err != nil { - return err + var val any + if err := token.Get(key, &val); err != nil { + return nil } - resp.Body.Close() - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("status %d", resp.StatusCode) - } - return nil + return val } func encodeSession(sc *securecookie.SecureCookie, token string, exp, issued int64) (string, error) { - v := map[string]interface{}{ + v := map[string]any{ "access_token": token, "expires": exp, "issued": issued, @@ -166,7 +289,6 @@ func encodeSession(sc *securecookie.SecureCookie, token string, exp, issued int6 /* ----------------------------- main ------------------------------------- */ func main() { - flag.Parse() if upstream == "" { log.Fatal("--upstream is required") } @@ -214,7 +336,11 @@ func main() { }{Action: signIn, Err: "Token required"}) return } - if err := externalTokenCheck(token); err != nil { + + // Verify token signature using JWKS + verifiedToken, err := verifyAndParseJWT(r.Context(), token) + if err != nil { + log.Printf("token verification failed: %v", err) _ = loginTmpl.Execute(w, struct { Action string Err string @@ -223,9 +349,8 @@ func main() { } exp := time.Now().Add(24 * time.Hour).Unix() - claims := decodeJWT(token) - if v, ok := claims["exp"].(float64); ok { - exp = int64(v) + if expTime, ok := verifiedToken.Expiration(); ok && !expTime.IsZero() { + exp = expTime.Unix() } session, _ := encodeSession(sc, token, exp, time.Now().Unix()) http.SetCookie(w, &http.Cookie{ @@ -264,7 +389,7 @@ func main() { return } var token string - var sess map[string]interface{} + var sess map[string]any if sc != nil { if err := sc.Decode(cookieName, c.Value, &sess); err != nil { http.Error(w, "unauthorized", http.StatusUnauthorized) @@ -273,19 +398,25 @@ func main() { token, _ = sess["access_token"].(string) } else { token = c.Value - sess = map[string]interface{}{ + sess = map[string]any{ "expires": time.Now().Add(24 * time.Hour).Unix(), "issued": time.Now().Unix(), } } - claims := decodeJWT(token) - out := map[string]interface{}{ + // Re-verify the token to ensure it's still valid + verifiedToken, err := verifyAndParseJWT(r.Context(), token) + if err != nil { + http.Error(w, "unauthorized", http.StatusUnauthorized) + return + } + + out := map[string]any{ "token": token, - "sub": claims["sub"], - "email": claims["email"], - "preferred_username": claims["preferred_username"], - "groups": claims["groups"], + "sub": getClaim(verifiedToken, "sub"), + "email": getClaim(verifiedToken, "email"), + "preferred_username": getClaim(verifiedToken, "preferred_username"), + "groups": getClaim(verifiedToken, "groups"), "expires": sess["expires"], "issued": sess["issued"], "cookie_refresh_enable": cookieRefresh > 0, @@ -303,7 +434,7 @@ func main() { return } var token string - var sess map[string]interface{} + var sess map[string]any if sc != nil { if err := sc.Decode(cookieName, c.Value, &sess); err != nil { http.Redirect(w, r, signIn, http.StatusFound) @@ -312,7 +443,7 @@ func main() { token, _ = sess["access_token"].(string) } else { token = c.Value - sess = map[string]interface{}{ + sess = map[string]any{ "expires": time.Now().Add(24 * time.Hour).Unix(), "issued": time.Now().Unix(), } diff --git a/packages/system/dashboard/templates/gatekeeper-sa.yaml b/packages/system/dashboard/templates/gatekeeper-sa.yaml index 8f5cabcd..f9043c99 100644 --- a/packages/system/dashboard/templates/gatekeeper-sa.yaml +++ b/packages/system/dashboard/templates/gatekeeper-sa.yaml @@ -2,3 +2,30 @@ apiVersion: v1 kind: ServiceAccount metadata: name: incloud-web-gatekeeper +{{- $oidcEnabled := index .Values._cluster "oidc-enabled" }} +{{- if ne $oidcEnabled "true" }} +--- +# ClusterRole to allow token-proxy to fetch JWKS for JWT verification +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: incloud-web-gatekeeper-jwks +rules: +- nonResourceURLs: + - /openid/v1/jwks + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: incloud-web-gatekeeper-jwks +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: incloud-web-gatekeeper-jwks +subjects: +- kind: ServiceAccount + name: incloud-web-gatekeeper + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/packages/system/dashboard/templates/gatekeeper.yaml b/packages/system/dashboard/templates/gatekeeper.yaml index 984ec03e..d87c2deb 100644 --- a/packages/system/dashboard/templates/gatekeeper.yaml +++ b/packages/system/dashboard/templates/gatekeeper.yaml @@ -89,7 +89,6 @@ spec: - --cookie-name=kc-access - --cookie-secure=true - --cookie-secret=$(TOKEN_PROXY_COOKIE_SECRET) - - --token-check-url=http://incloud-web-nginx.{{ .Release.Namespace }}.svc:8080/api/clusters/default/k8s/apis/core.cozystack.io/v1alpha1/tenantnamespaces env: - name: TOKEN_PROXY_COOKIE_SECRET valueFrom: From 000b5ff76ca0d21cdeaa643ceab1adf11a05e8ff Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Tue, 3 Feb 2026 21:39:15 +0300 Subject: [PATCH 175/889] [backups] Signed-off-by: Timofei Larkin --- api/backups/strategy/v1alpha1/velero_types.go | 5 +- .../v1alpha1/zz_generated.deepcopy.go | 6 + api/backups/v1alpha1/backupjob_types.go | 1 + api/backups/v1alpha1/backupjob_webhook.go | 67 --- .../v1alpha1/backupjob_webhook_test.go | 334 -------------- cmd/backup-controller/main.go | 15 - hack/update-codegen.sh | 2 +- .../backupcontroller/backupjob_controller.go | 5 +- .../backupcontroller/restorejob_controller.go | 3 +- .../velerostrategy_controller.go | 59 +-- .../backups.cozystack.io_backupjobs.yaml | 5 + .../backups.cozystack.io_restorejobs.yaml | 8 +- .../backup-controller/templates/class.yaml | 14 - .../backup-controller/templates/strategy.yaml | 10 - ...trategy.backups.cozystack.io_veleroes.yaml | 422 ++++++++++++++++++ .../cozyrds/backup-strategy.yaml | 6 +- pkg/generated/openapi/zz_generated.openapi.go | 7 + 17 files changed, 481 insertions(+), 488 deletions(-) delete mode 100644 api/backups/v1alpha1/backupjob_webhook.go delete mode 100644 api/backups/v1alpha1/backupjob_webhook_test.go delete mode 100644 packages/system/backup-controller/templates/class.yaml delete 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 ae15fa5e..2bfeabb7 100644 --- a/api/backups/strategy/v1alpha1/velero_types.go +++ b/api/backups/strategy/v1alpha1/velero_types.go @@ -55,8 +55,9 @@ 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"` - RestoreSpec velerov1.RestoreSpec `json:"restoreSpec"` + Spec velerov1.BackupSpec `json:"spec"` + // +optional + RestoreSpec *velerov1.RestoreSpec `json:"restoreSpec,omitempty"` } type VeleroStatus struct { diff --git a/api/backups/strategy/v1alpha1/zz_generated.deepcopy.go b/api/backups/strategy/v1alpha1/zz_generated.deepcopy.go index c1989ac9..7f109878 100644 --- a/api/backups/strategy/v1alpha1/zz_generated.deepcopy.go +++ b/api/backups/strategy/v1alpha1/zz_generated.deepcopy.go @@ -21,6 +21,7 @@ limitations under the License. package v1alpha1 import ( + velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -223,6 +224,11 @@ func (in *VeleroStatus) DeepCopy() *VeleroStatus { func (in *VeleroTemplate) DeepCopyInto(out *VeleroTemplate) { *out = *in in.Spec.DeepCopyInto(&out.Spec) + if in.RestoreSpec != nil { + in, out := &in.RestoreSpec, &out.RestoreSpec + *out = new(velerov1.RestoreSpec) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VeleroTemplate. diff --git a/api/backups/v1alpha1/backupjob_types.go b/api/backups/v1alpha1/backupjob_types.go index 7b251ceb..111b5f1d 100644 --- a/api/backups/v1alpha1/backupjob_types.go +++ b/api/backups/v1alpha1/backupjob_types.go @@ -57,6 +57,7 @@ type BackupJobSpec struct { // The BackupClass will be resolved to determine the appropriate strategy and storage // based on the ApplicationRef. // This field is immutable once the BackupJob is created. + // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="backupClassName is immutable" BackupClassName string `json:"backupClassName"` } diff --git a/api/backups/v1alpha1/backupjob_webhook.go b/api/backups/v1alpha1/backupjob_webhook.go deleted file mode 100644 index 064605c2..00000000 --- a/api/backups/v1alpha1/backupjob_webhook.go +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -package v1alpha1 - -import ( - "context" - "fmt" - "strings" - - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" -) - -// SetupWebhookWithManager registers the BackupJob webhook with the manager. -func SetupBackupJobWebhookWithManager(mgr ctrl.Manager) error { - return ctrl.NewWebhookManagedBy(mgr). - For(&BackupJob{}). - Complete() -} - -// +kubebuilder:webhook:path=/mutate-backups-cozystack-io-v1alpha1-backupjob,mutating=true,failurePolicy=fail,sideEffects=None,groups=backups.cozystack.io,resources=backupjobs,verbs=create;update,versions=v1alpha1,name=mbackupjob.kb.io,admissionReviewVersions=v1 - -// Default implements webhook.Defaulter so a webhook will be registered for the type -func (j *BackupJob) Default() { - j.Spec.ApplicationRef = NormalizeApplicationRef(j.Spec.ApplicationRef) -} - -// +kubebuilder:webhook:path=/validate-backups-cozystack-io-v1alpha1-backupjob,mutating=false,failurePolicy=fail,sideEffects=None,groups=backups.cozystack.io,resources=backupjobs,verbs=create;update,versions=v1alpha1,name=vbackupjob.kb.io,admissionReviewVersions=v1 - -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (j *BackupJob) ValidateCreate() (admission.Warnings, error) { - logger := log.FromContext(context.Background()) - logger.Info("validating BackupJob creation", "name", j.Name, "namespace", j.Namespace) - - // Validate that backupClassName is set - if strings.TrimSpace(j.Spec.BackupClassName) == "" { - return nil, fmt.Errorf("backupClassName is required and cannot be empty") - } - - return nil, nil -} - -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (j *BackupJob) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { - logger := log.FromContext(context.Background()) - logger.Info("validating BackupJob update", "name", j.Name, "namespace", j.Namespace) - - oldJob, ok := old.(*BackupJob) - if !ok { - return nil, apierrors.NewBadRequest(fmt.Sprintf("expected a BackupJob but got a %T", old)) - } - - // Enforce immutability of backupClassName - if oldJob.Spec.BackupClassName != j.Spec.BackupClassName { - return nil, fmt.Errorf("backupClassName is immutable and cannot be changed from %q to %q", oldJob.Spec.BackupClassName, j.Spec.BackupClassName) - } - - return nil, nil -} - -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (j *BackupJob) ValidateDelete() (admission.Warnings, error) { - // No validation needed for deletion - return nil, nil -} diff --git a/api/backups/v1alpha1/backupjob_webhook_test.go b/api/backups/v1alpha1/backupjob_webhook_test.go deleted file mode 100644 index d143c5be..00000000 --- a/api/backups/v1alpha1/backupjob_webhook_test.go +++ /dev/null @@ -1,334 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -package v1alpha1 - -import ( - "testing" - - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" -) - -func TestBackupJob_ValidateCreate(t *testing.T) { - tests := []struct { - name string - job *BackupJob - wantErr bool - errMsg string - }{ - { - name: "valid BackupJob with backupClassName", - job: &BackupJob{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-job", - Namespace: "default", - }, - Spec: BackupJobSpec{ - ApplicationRef: corev1.TypedLocalObjectReference{ - Kind: "VirtualMachine", - Name: "vm1", - }, - BackupClassName: "velero", - }, - }, - wantErr: false, - }, - { - name: "BackupJob with empty backupClassName should be rejected", - job: &BackupJob{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-job", - Namespace: "default", - }, - Spec: BackupJobSpec{ - ApplicationRef: corev1.TypedLocalObjectReference{ - Kind: "VirtualMachine", - Name: "vm1", - }, - BackupClassName: "", - }, - }, - wantErr: true, - errMsg: "backupClassName is required and cannot be empty", - }, - { - name: "BackupJob with whitespace-only backupClassName should be rejected", - job: &BackupJob{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-job", - Namespace: "default", - }, - Spec: BackupJobSpec{ - ApplicationRef: corev1.TypedLocalObjectReference{ - Kind: "VirtualMachine", - Name: "vm1", - }, - BackupClassName: " ", - }, - }, - wantErr: true, - errMsg: "backupClassName is required and cannot be empty", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - warnings, err := tt.job.ValidateCreate() - if (err != nil) != tt.wantErr { - t.Errorf("ValidateCreate() error = %v, wantErr %v", err, tt.wantErr) - return - } - if tt.wantErr && err != nil { - if tt.errMsg != "" && err.Error() != tt.errMsg { - t.Errorf("ValidateCreate() error message = %v, want %v", err.Error(), tt.errMsg) - } - } - if warnings != nil && len(warnings) > 0 { - t.Logf("ValidateCreate() warnings = %v", warnings) - } - }) - } -} - -func TestBackupJob_ValidateUpdate(t *testing.T) { - baseJob := &BackupJob{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-job", - Namespace: "default", - }, - Spec: BackupJobSpec{ - ApplicationRef: corev1.TypedLocalObjectReference{ - Kind: "VirtualMachine", - Name: "vm1", - }, - BackupClassName: "velero", - }, - } - - tests := []struct { - name string - old runtime.Object - new *BackupJob - wantErr bool - errMsg string - }{ - { - name: "update with same backupClassName should succeed", - old: baseJob, - new: &BackupJob{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-job", - Namespace: "default", - }, - Spec: BackupJobSpec{ - ApplicationRef: corev1.TypedLocalObjectReference{ - Kind: "VirtualMachine", - Name: "vm1", - }, - BackupClassName: "velero", // Same as old - }, - }, - wantErr: false, - }, - { - name: "update changing backupClassName should be rejected", - old: baseJob, - new: &BackupJob{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-job", - Namespace: "default", - }, - Spec: BackupJobSpec{ - ApplicationRef: corev1.TypedLocalObjectReference{ - Kind: "VirtualMachine", - Name: "vm1", - }, - BackupClassName: "different-class", // Changed! - }, - }, - wantErr: true, - errMsg: "backupClassName is immutable and cannot be changed from \"velero\" to \"different-class\"", - }, - { - name: "update changing other fields but keeping backupClassName should succeed", - old: baseJob, - new: &BackupJob{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-job", - Namespace: "default", - Labels: map[string]string{ - "new-label": "value", - }, - }, - Spec: BackupJobSpec{ - ApplicationRef: corev1.TypedLocalObjectReference{ - Kind: "VirtualMachine", - Name: "vm2", // Changed application - }, - BackupClassName: "velero", // Same as old - }, - }, - wantErr: false, - }, - { - name: "update when old backupClassName is empty should be rejected", - old: &BackupJob{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-job", - Namespace: "default", - }, - Spec: BackupJobSpec{ - ApplicationRef: corev1.TypedLocalObjectReference{ - Kind: "VirtualMachine", - Name: "vm1", - }, - BackupClassName: "", // Empty in old - }, - }, - new: &BackupJob{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-job", - Namespace: "default", - }, - Spec: BackupJobSpec{ - ApplicationRef: corev1.TypedLocalObjectReference{ - Kind: "VirtualMachine", - Name: "vm1", - }, - BackupClassName: "velero", // Setting it for the first time - }, - }, - wantErr: true, - errMsg: "backupClassName is immutable", - }, - { - name: "update changing from non-empty to different non-empty should be rejected", - old: &BackupJob{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-job", - Namespace: "default", - }, - Spec: BackupJobSpec{ - ApplicationRef: corev1.TypedLocalObjectReference{ - Kind: "VirtualMachine", - Name: "vm1", - }, - BackupClassName: "class-a", - }, - }, - new: &BackupJob{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-job", - Namespace: "default", - }, - Spec: BackupJobSpec{ - ApplicationRef: corev1.TypedLocalObjectReference{ - Kind: "VirtualMachine", - Name: "vm1", - }, - BackupClassName: "class-b", // Changed from class-a - }, - }, - wantErr: true, - errMsg: "backupClassName is immutable and cannot be changed from \"class-a\" to \"class-b\"", - }, - { - name: "update with invalid old object type should be rejected", - old: &corev1.Pod{ // Wrong type - will be cast to runtime.Object in test - ObjectMeta: metav1.ObjectMeta{ - Name: "test-job", - Namespace: "default", - }, - }, - new: baseJob, - wantErr: true, - errMsg: "expected a BackupJob but got a", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - warnings, err := tt.new.ValidateUpdate(tt.old) - if (err != nil) != tt.wantErr { - t.Errorf("ValidateUpdate() error = %v, wantErr %v", err, tt.wantErr) - if err != nil { - t.Logf("Error message: %v", err.Error()) - } - return - } - if tt.wantErr && err != nil { - if tt.errMsg != "" { - if tt.errMsg != "" && !contains(err.Error(), tt.errMsg) { - t.Errorf("ValidateUpdate() error message = %v, want contains %v", err.Error(), tt.errMsg) - } - } - } - if warnings != nil && len(warnings) > 0 { - t.Logf("ValidateUpdate() warnings = %v", warnings) - } - }) - } -} - -func TestBackupJob_ValidateDelete(t *testing.T) { - job := &BackupJob{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-job", - Namespace: "default", - }, - Spec: BackupJobSpec{ - ApplicationRef: corev1.TypedLocalObjectReference{ - Kind: "VirtualMachine", - Name: "vm1", - }, - BackupClassName: "velero", - }, - } - - warnings, err := job.ValidateDelete() - if err != nil { - t.Errorf("ValidateDelete() should never return an error, got %v", err) - } - if warnings != nil && len(warnings) > 0 { - t.Logf("ValidateDelete() warnings = %v", warnings) - } -} - -func TestBackupJob_Default(t *testing.T) { - job := &BackupJob{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-job", - Namespace: "default", - }, - Spec: BackupJobSpec{ - ApplicationRef: corev1.TypedLocalObjectReference{ - Kind: "VirtualMachine", - Name: "vm1", - }, - BackupClassName: "velero", - }, - } - - // Default() should not panic and should not modify the object - originalClassName := job.Spec.BackupClassName - job.Default() - if job.Spec.BackupClassName != originalClassName { - t.Errorf("Default() should not modify backupClassName, got %v, want %v", job.Spec.BackupClassName, originalClassName) - } -} - -// Helper function to check if a string contains a substring -func contains(s, substr string) bool { - if len(substr) == 0 { - return true - } - if len(s) < len(substr) { - return false - } - for i := 0; i <= len(s)-len(substr); i++ { - if s[i:i+len(substr)] == substr { - return true - } - } - return false -} diff --git a/cmd/backup-controller/main.go b/cmd/backup-controller/main.go index fcc31d71..16dfc44c 100644 --- a/cmd/backup-controller/main.go +++ b/cmd/backup-controller/main.go @@ -162,21 +162,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) - } - - // Register BackupJob webhook for validation (immutability of backupClassName) - if err = backupsv1alpha1.SetupBackupJobWebhookWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create webhook", "webhook", "BackupJob") - os.Exit(1) - } - // +kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh index 188325e8..b97c2ade 100755 --- a/hack/update-codegen.sh +++ b/hack/update-codegen.sh @@ -19,7 +19,7 @@ set -o nounset set -o pipefail SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. -CODEGEN_PKG=${CODEGEN_PKG:-$(cd "${SCRIPT_ROOT}"; ls -d -1 ./vendor/k8s.io/code-generator 2>/dev/null || echo ../code-generator)} +CODEGEN_PKG=${CODEGEN_PKG:-~/go/pkg/mod/k8s.io/code-generator@v0.34.1} 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" diff --git a/internal/backupcontroller/backupjob_controller.go b/internal/backupcontroller/backupjob_controller.go index c1ac71b5..b926722f 100644 --- a/internal/backupcontroller/backupjob_controller.go +++ b/internal/backupcontroller/backupjob_controller.go @@ -2,7 +2,6 @@ package backupcontroller import ( "context" - "fmt" "net/http" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -22,8 +21,8 @@ import ( backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1" ) -// BackupVeleroStrategyReconciler reconciles BackupJob with a strategy referencing -// Velero.strategy.backups.cozystack.io objects. +// BackupJobReconciler reconciles BackupJob with a strategy from the +// strategy.backups.cozystack.io API group. type BackupJobReconciler struct { client.Client dynamic.Interface diff --git a/internal/backupcontroller/restorejob_controller.go b/internal/backupcontroller/restorejob_controller.go index 19941959..71307ba3 100644 --- a/internal/backupcontroller/restorejob_controller.go +++ b/internal/backupcontroller/restorejob_controller.go @@ -106,9 +106,10 @@ func (r *RestoreJobReconciler) SetupWithManager(mgr ctrl.Manager) error { // getTargetApplicationRef determines the effective target application reference. // According to DESIGN.md, if spec.targetApplicationRef is omitted, drivers SHOULD // restore into backup.spec.applicationRef. +// The returned reference is normalized to ensure APIGroup has a default value. func (r *RestoreJobReconciler) getTargetApplicationRef(restoreJob *backupsv1alpha1.RestoreJob, backup *backupsv1alpha1.Backup) corev1.TypedLocalObjectReference { if restoreJob.Spec.TargetApplicationRef != nil { - return *restoreJob.Spec.TargetApplicationRef + return backupsv1alpha1.NormalizeApplicationRef(*restoreJob.Spec.TargetApplicationRef) } return backup.Spec.ApplicationRef } diff --git a/internal/backupcontroller/velerostrategy_controller.go b/internal/backupcontroller/velerostrategy_controller.go index a26aab56..6809bd4f 100644 --- a/internal/backupcontroller/velerostrategy_controller.go +++ b/internal/backupcontroller/velerostrategy_controller.go @@ -208,30 +208,6 @@ func (r *BackupJobReconciler) reconcileVelero(ctx context.Context, j *backupsv1a return ctrl.Result{RequeueAfter: 5 * time.Second}, 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, resolved *ResolvedBackupConfig) error { logger := getLogger(ctx) logger.Debug("createVeleroBackup called", "strategy", strategy.Name) @@ -476,39 +452,48 @@ func (r *RestoreJobReconciler) createVeleroRestore(ctx context.Context, restoreJ 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) + // Build template context + templateContext := map[string]interface{}{ + "Application": app.Object, + // TODO: Parameters are not currently stored on Backup, so they're unavailable during restore. + // This is a design limitation that should be addressed by persisting Parameters on the Backup object. + "Parameters": map[string]string{}, + } + + // Template the restore spec from the strategy, or use defaults if not specified + var veleroRestoreSpec velerov1.RestoreSpec + if strategy.Spec.Template.RestoreSpec != nil { + templatedSpec, err := template.Template(strategy.Spec.Template.RestoreSpec, templateContext) + if err != nil { + return fmt.Errorf("failed to template Velero Restore spec: %w", err) + } + veleroRestoreSpec = *templatedSpec } // Set the backupName in the spec (required by Velero) veleroRestoreSpec.BackupName = veleroBackupName + generateName := fmt.Sprintf("%s.%s-", restoreJob.Namespace, restoreJob.Name) veleroRestore := &velerov1.Restore{ ObjectMeta: metav1.ObjectMeta{ - GenerateName: fmt.Sprintf("%s.%s-", restoreJob.Namespace, restoreJob.Name), + GenerateName: generateName, Namespace: veleroNamespace, Labels: map[string]string{ backupsv1alpha1.OwningJobNameLabel: restoreJob.Name, backupsv1alpha1.OwningJobNamespaceLabel: restoreJob.Namespace, }, }, - Spec: *veleroRestoreSpec, + 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) + logger.Error(err, "failed to create Velero Restore", "generateName", generateName) r.Recorder.Event(restoreJob, corev1.EventTypeWarning, "VeleroRestoreCreationFailed", - fmt.Sprintf("Failed to create Velero Restore %s/%s: %v", veleroNamespace, name, err)) + fmt.Sprintf("Failed to create Velero Restore %s/%s: %v", veleroNamespace, generateName, 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)) + fmt.Sprintf("Created Velero Restore %s/%s", veleroNamespace, veleroRestore.Name)) return nil } 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 686c079f..7a0ed6f5 100644 --- a/packages/system/backup-controller/definitions/backups.cozystack.io_backupjobs.yaml +++ b/packages/system/backup-controller/definitions/backups.cozystack.io_backupjobs.yaml @@ -74,7 +74,12 @@ spec: BackupClassName references a BackupClass that contains strategy and storage configuration. The BackupClass will be resolved to determine the appropriate strategy and storage based on the ApplicationRef. + This field is immutable once the BackupJob is created. + minLength: 1 type: string + x-kubernetes-validations: + - message: backupClassName is immutable + rule: self == oldSelf planRef: description: |- PlanRef refers to the Plan that requested this backup run. 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/backup-controller/templates/class.yaml b/packages/system/backup-controller/templates/class.yaml deleted file mode 100644 index cb3164f2..00000000 --- a/packages/system/backup-controller/templates/class.yaml +++ /dev/null @@ -1,14 +0,0 @@ -kind: BackupClass -metadata: - name: velero -spec: - strategies: - - strategyRef: - apiGroup: strategy.backups.cozystack.io - kind: Velero - name: velero-backup-strategy-for-virtualmachines - application: - apiVersion: apps.cozystack.io - kind: VirtualMachine - parameters: - backupStorageLocationName: default diff --git a/packages/system/backup-controller/templates/strategy.yaml b/packages/system/backup-controller/templates/strategy.yaml deleted file mode 100644 index a8396d66..00000000 --- a/packages/system/backup-controller/templates/strategy.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: strategy.backups.cozystack.io/v1alpha1 -kind: Velero -metadata: - name: velero-strategy-default -spec: - template: - spec: - ttl: 720h - includedNamespaces: - - "*" 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 e817de23..34254aed 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 @@ -45,6 +45,428 @@ spec: 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. diff --git a/packages/system/virtual-machine-rd/cozyrds/backup-strategy.yaml b/packages/system/virtual-machine-rd/cozyrds/backup-strategy.yaml index 1312540b..b84f08e4 100644 --- a/packages/system/virtual-machine-rd/cozyrds/backup-strategy.yaml +++ b/packages/system/virtual-machine-rd/cozyrds/backup-strategy.yaml @@ -3,13 +3,13 @@ kind: Velero metadata: name: velero-backup-strategy-for-virtualmachines spec: - backupTemplate: + template: spec: # see https://velero.io/docs/v1.9/api-types/backup/ includedNamespaces: - - {{ .metadata.namespace }} + - {{ .Application.metadata.namespace }} labelSelector: - apps.cozystack.io/application.Kind: {{ .kind }} + apps.cozystack.io/application.Kind: {{ .Application.kind }} includedResources: - helmreleases.helm.toolkit.fluxcd.io diff --git a/pkg/generated/openapi/zz_generated.openapi.go b/pkg/generated/openapi/zz_generated.openapi.go index ba5ab71b..3ad28827 100644 --- a/pkg/generated/openapi/zz_generated.openapi.go +++ b/pkg/generated/openapi/zz_generated.openapi.go @@ -258,6 +258,13 @@ func schema_pkg_apis_apps_v1alpha1_ApplicationStatus(ref common.ReferenceCallbac Format: "", }, }, + "externalIPsCount": { + SchemaProps: spec.SchemaProps{ + Description: "ExternalIPsCount holds the number of LoadBalancer services with assigned external IPs for Tenant applications.", + Type: []string{"integer"}, + Format: "int32", + }, + }, }, }, }, From d19b008bba33d46942e226e33941ee9d9421d316 Mon Sep 17 00:00:00 2001 From: IvanHunters Date: Wed, 4 Feb 2026 23:38:02 +0300 Subject: [PATCH 176/889] [seaweedfs] Increase certificate duration to 10 years Change TLS certificate duration from 90 days to 10 years to prevent certificate expiration issues. Also adjust renewBefore from 15 to 30 days. Signed-off-by: IvanHunters --- packages/system/seaweedfs/values.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/system/seaweedfs/values.yaml b/packages/system/seaweedfs/values.yaml index 3a60c779..8db83fc0 100644 --- a/packages/system/seaweedfs/values.yaml +++ b/packages/system/seaweedfs/values.yaml @@ -183,8 +183,8 @@ seaweedfs: ipAddresses: [] keyAlgorithm: RSA keySize: 2048 - duration: 2160h # 90d - renewBefore: 360h # 15d + duration: 87600h # 10 years + renewBefore: 720h # 30d db: replicas: 2 size: 10Gi From 976b0011aca00e9a5a5a426557bd81171177fe47 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 5 Feb 2026 21:39:31 +0100 Subject: [PATCH 177/889] ci: replace Gemini with GitHub Copilot CLI for changelog generation Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- .github/workflows/tags.yaml | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tags.yaml b/.github/workflows/tags.yaml index 89e14c3d..a954534a 100644 --- a/.github/workflows/tags.yaml +++ b/.github/workflows/tags.yaml @@ -291,17 +291,24 @@ jobs: echo "Changelog file $CHANGELOG_FILE does not exist" fi + - name: Setup Node.js + if: steps.check_changelog.outputs.exists == 'false' + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Install GitHub Copilot CLI + if: steps.check_changelog.outputs.exists == 'false' + run: npm i -g @github/copilot + - name: Generate changelog using AI if: steps.check_changelog.outputs.exists == 'false' - # Uses official run-gemini-cli action from GitHub Marketplace - # Requires GEMINI_API_KEY secret to be set in repository settings - # See: https://github.com/marketplace/actions/run-gemini-cli - uses: google-github-actions/run-gemini-cli@v0.1.18 - with: - prompt: "prepare changelog file for tagged release v${{ steps.tag.outputs.version }}, use @docs/agents/changelog.md for it. Create the changelog file at docs/changelogs/v${{ steps.tag.outputs.version }}.md" env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GH_PAT }} + run: | + copilot --prompt "prepare changelog file for tagged release v${{ steps.tag.outputs.version }}, use @docs/agents/changelog.md for it. Create the changelog file at docs/changelogs/v${{ steps.tag.outputs.version }}.md" \ + --allow-all-tools --allow-all-paths < /dev/null - name: Create changelog branch and commit if: steps.check_changelog.outputs.exists == 'false' From 3c75e8819028c0898b462c491c3f2f55a5541e08 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 5 Feb 2026 22:00:38 +0100 Subject: [PATCH 178/889] ci: remove unused base_branch computation from changelog job Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- .github/workflows/tags.yaml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tags.yaml b/.github/workflows/tags.yaml index eb4ebe49..ad9dd0a9 100644 --- a/.github/workflows/tags.yaml +++ b/.github/workflows/tags.yaml @@ -223,7 +223,7 @@ jobs: pull-requests: write if: needs.prepare-release.result == 'success' steps: - - name: Parse tag and get base branch + - name: Parse tag id: tag uses: actions/github-script@v7 with: @@ -235,15 +235,9 @@ jobs: return; } const version = m[1] + (m[2] ?? ''); - const [maj, min] = m[1].split('.'); - - // Determine base branch: if patch release (Z > 0), use release-X.Y, else use main - const patch = parseInt(m[1].split('.')[2]); - const baseBranch = patch > 0 ? `release-${maj}.${min}` : 'main'; - + core.setOutput('version', version); core.setOutput('tag', ref); - core.setOutput('base_branch', baseBranch); - name: Checkout main branch uses: actions/checkout@v4 From 97b5ea24c7d673dd5313f29ddca86f4e32b49435 Mon Sep 17 00:00:00 2001 From: IvanHunters Date: Fri, 6 Feb 2026 00:32:05 +0300 Subject: [PATCH 179/889] fix(monitoring): remove cozystack-controller dependency The monitoring package depends on cozystack.cozystack-controller which is only installed as part of cozystack-engine. This prevents monitoring from becoming ready when cozystack-engine is not enabled. Remove this dependency to allow monitoring to work independently. Signed-off-by: IvanHunters --- packages/core/platform/sources/monitoring.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core/platform/sources/monitoring.yaml b/packages/core/platform/sources/monitoring.yaml index c88ab412..2b56c300 100644 --- a/packages/core/platform/sources/monitoring.yaml +++ b/packages/core/platform/sources/monitoring.yaml @@ -14,7 +14,6 @@ spec: variants: - name: default dependsOn: - - cozystack.cozystack-controller - cozystack.vertical-pod-autoscaler - cozystack.networking - cozystack.postgres-operator From 69c0392dc631fd06c4b4d6e5e30ab779755c52c9 Mon Sep 17 00:00:00 2001 From: IvanHunters Date: Fri, 6 Feb 2026 02:01:37 +0300 Subject: [PATCH 180/889] fix(linstor): extract piraeus-operator CRDs into separate package Helm does not reliably install all CRDs from templates/ directory, particularly when the crds.yaml file is large. This causes linstorsatellites.piraeus.io CRD to be missing, breaking satellite pod creation. Changes: - Create new piraeus-operator-crds package with all piraeus CRDs - Add piraeus-operator-crds as dependency for piraeus-operator - Set privileged: true for CRDs package to ensure namespace has correct PodSecurity labels from the start - Disable installCRDs in piraeus-operator since CRDs come from separate package Signed-off-by: IvanHunters --- packages/core/platform/sources/linstor.yaml | 8 + .../system/piraeus-operator-crds/.helmignore | 1 + .../system/piraeus-operator-crds/Chart.yaml | 3 + .../system/piraeus-operator-crds/Makefile | 1 + .../piraeus-operator-crds/templates/crds.yaml | 2171 +++++++++++++++++ .../system/piraeus-operator-crds/values.yaml | 1 + packages/system/piraeus-operator/values.yaml | 2 +- 7 files changed, 2186 insertions(+), 1 deletion(-) create mode 100644 packages/system/piraeus-operator-crds/.helmignore create mode 100644 packages/system/piraeus-operator-crds/Chart.yaml create mode 100644 packages/system/piraeus-operator-crds/Makefile create mode 100644 packages/system/piraeus-operator-crds/templates/crds.yaml create mode 100644 packages/system/piraeus-operator-crds/values.yaml diff --git a/packages/core/platform/sources/linstor.yaml b/packages/core/platform/sources/linstor.yaml index f440affe..6e2ec01a 100644 --- a/packages/core/platform/sources/linstor.yaml +++ b/packages/core/platform/sources/linstor.yaml @@ -18,11 +18,19 @@ spec: - cozystack.reloader - cozystack.snapshot-controller components: + - name: piraeus-operator-crds + path: system/piraeus-operator-crds + install: + privileged: true + namespace: cozy-linstor + releaseName: piraeus-operator-crds - name: piraeus-operator path: system/piraeus-operator install: namespace: cozy-linstor releaseName: piraeus-operator + dependsOn: + - piraeus-operator-crds - name: linstor path: system/linstor install: diff --git a/packages/system/piraeus-operator-crds/.helmignore b/packages/system/piraeus-operator-crds/.helmignore new file mode 100644 index 00000000..f3c7a7c5 --- /dev/null +++ b/packages/system/piraeus-operator-crds/.helmignore @@ -0,0 +1 @@ +Makefile diff --git a/packages/system/piraeus-operator-crds/Chart.yaml b/packages/system/piraeus-operator-crds/Chart.yaml new file mode 100644 index 00000000..81ccbd88 --- /dev/null +++ b/packages/system/piraeus-operator-crds/Chart.yaml @@ -0,0 +1,3 @@ +apiVersion: v2 +name: cozy-piraeus-operator-crds +version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process diff --git a/packages/system/piraeus-operator-crds/Makefile b/packages/system/piraeus-operator-crds/Makefile new file mode 100644 index 00000000..e633a368 --- /dev/null +++ b/packages/system/piraeus-operator-crds/Makefile @@ -0,0 +1 @@ +include ../../../hack/package.mk diff --git a/packages/system/piraeus-operator-crds/templates/crds.yaml b/packages/system/piraeus-operator-crds/templates/crds.yaml new file mode 100644 index 00000000..996674b3 --- /dev/null +++ b/packages/system/piraeus-operator-crds/templates/crds.yaml @@ -0,0 +1,2171 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: linstorclusters.piraeus.io +spec: + group: piraeus.io + names: + kind: LinstorCluster + listKind: LinstorClusterList + plural: linstorclusters + singular: linstorcluster + scope: Cluster + versions: + - additionalPrinterColumns: + - description: If the LINSTOR Cluster is available + jsonPath: .status.conditions[?(@.type=='Available')].status + name: Available + type: string + - description: If the LINSTOR Cluster is fully configured + jsonPath: .status.conditions[?(@.type=='Configured')].status + name: Configured + type: string + - description: The version of the LINSTOR Cluster + jsonPath: .status.version + name: Version + priority: 10 + type: string + - description: The number of running/expected Satellites + jsonPath: .status.satellites + name: Satellites + type: string + - description: The used capacity in all storage pools + jsonPath: .status.capacity + name: Used Capacity + type: string + - description: The number of volumes in the cluster + jsonPath: .status.numberOfVolumes + name: Volumes + type: integer + - description: The number of snapshots in the cluster + jsonPath: .status.numberOfSnapshots + name: Snapshots + priority: 10 + type: integer + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: LinstorCluster is the Schema for the linstorclusters API + 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: LinstorClusterSpec defines the desired state of LinstorCluster + properties: + affinityController: + description: AffinityController controls the deployment of the Affinity + Controller Deployment. + properties: + enabled: + default: true + description: Enable the component. + type: boolean + podTemplate: + description: |- + Template to apply to Pods of the component. + + The template is applied as a patch to the default deployment, so it can be "sparse", not listing any + containers or volumes that should remain unchanged. + See https://kubernetes.io/docs/concepts/workloads/pods/#pod-templates + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + replicas: + description: Number of desired pods. Defaults to 1. + format: int32 + minimum: 0 + type: integer + type: object + apiTLS: + description: |- + ApiTLS secures the LINSTOR API. + + This configures the TLS key and certificate used to secure the LINSTOR API. + nullable: true + properties: + affinityControllerSecretName: + description: |- + AffinityControllerSecretName references a secret holding the TLS key and certificate used by the Affinity + Controller to monitor volume state. Defaults to "linstor-affinity-controller-tls". + type: string + apiSecretName: + description: |- + ApiSecretName references a secret holding the TLS key and certificate used to protect the API. + Defaults to "linstor-api-tls". + type: string + caReference: + description: |- + CAReference configures the CA certificate to use when validating TLS certificates. + If not set, the TLS secret is expected to contain a "ca.crt" containing the CA certificate. + properties: + key: + default: ca.crt + description: |- + Key to select in the resource. + Defaults to ca.crt if not specified. + type: string + kind: + default: Secret + description: Kind of the resource containing the CA Certificate, + either a ConfigMap or Secret. + enum: + - ConfigMap + - Secret + type: string + name: + description: Name of the resource containing the CA Certificate. + type: string + optional: + description: Optional specifies whether the resource and its + key must exist. + type: boolean + required: + - name + type: object + certManager: + description: |- + CertManager references a cert-manager Issuer or ClusterIssuer. + If set, cert-manager.io/Certificate resources will be created, provisioning the secrets referenced in + *SecretName using the issuer configured here. + properties: + group: + description: Group of the resource being referred to. + type: string + kind: + description: Kind of the resource being referred to. + type: string + name: + description: Name of the resource being referred to. + type: string + required: + - name + type: object + clientSecretName: + description: |- + ClientSecretName references a secret holding the TLS key and certificate used by the operator to configure + the cluster. Defaults to "linstor-client-tls". + type: string + csiControllerSecretName: + description: |- + CsiControllerSecretName references a secret holding the TLS key and certificate used by the CSI Controller + to provision volumes. Defaults to "linstor-csi-controller-tls". + type: string + csiNodeSecretName: + description: |- + CsiNodeSecretName references a secret holding the TLS key and certificate used by the CSI Nodes to query + the volume state. Defaults to "linstor-csi-node-tls". + type: string + nfsServerSecretName: + description: |- + NFSServerSecretName references a secret holding the TLS key and certificate used by the NFS Server to query + the cluster state. Defaults to "linstor-csi-nfs-server-tls". + type: string + type: object + controller: + description: Controller controls the deployment of the LINSTOR Controller + Deployment. + properties: + enabled: + default: true + description: Enable the component. + type: boolean + podTemplate: + description: |- + Template to apply to Pods of the component. + + The template is applied as a patch to the default deployment, so it can be "sparse", not listing any + containers or volumes that should remain unchanged. + See https://kubernetes.io/docs/concepts/workloads/pods/#pod-templates + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + type: object + csiController: + description: CSIController controls the deployment of the CSI Controller + Deployment. + properties: + enabled: + default: true + description: Enable the component. + type: boolean + podTemplate: + description: |- + Template to apply to Pods of the component. + + The template is applied as a patch to the default deployment, so it can be "sparse", not listing any + containers or volumes that should remain unchanged. + See https://kubernetes.io/docs/concepts/workloads/pods/#pod-templates + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + replicas: + description: Number of desired pods. Defaults to 1. + format: int32 + minimum: 0 + type: integer + type: object + csiNode: + description: CSINode controls the deployment of the CSI Node DaemonSet. + properties: + enabled: + default: true + description: Enable the component. + type: boolean + podTemplate: + description: |- + Template to apply to Pods of the component. + + The template is applied as a patch to the default deployment, so it can be "sparse", not listing any + containers or volumes that should remain unchanged. + See https://kubernetes.io/docs/concepts/workloads/pods/#pod-templates + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + type: object + externalController: + description: |- + ExternalController references an external controller. + When set, the Operator will skip deploying a LINSTOR Controller and instead use the external cluster + to register satellites. + properties: + url: + description: URL of the external controller. + minLength: 3 + type: string + required: + - url + type: object + highAvailabilityController: + description: HighAvailabilityController controls the deployment of + the High Availability Controller DaemonSet. + properties: + enabled: + default: true + description: Enable the component. + type: boolean + podTemplate: + description: |- + Template to apply to Pods of the component. + + The template is applied as a patch to the default deployment, so it can be "sparse", not listing any + containers or volumes that should remain unchanged. + See https://kubernetes.io/docs/concepts/workloads/pods/#pod-templates + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + type: object + internalTLS: + description: |- + InternalTLS secures the connection between LINSTOR Controller and Satellite. + + This configures the client certificate used when the Controller connects to a Satellite. This only has an effect + when the Satellite is configured to for secure connections using `LinstorSatellite.spec.internalTLS`. + nullable: true + properties: + caReference: + description: |- + CAReference configures the CA certificate to use when validating TLS certificates. + If not set, the TLS secret is expected to contain a "ca.crt" containing the CA certificate. + properties: + key: + default: ca.crt + description: |- + Key to select in the resource. + Defaults to ca.crt if not specified. + type: string + kind: + default: Secret + description: Kind of the resource containing the CA Certificate, + either a ConfigMap or Secret. + enum: + - ConfigMap + - Secret + type: string + name: + description: Name of the resource containing the CA Certificate. + type: string + optional: + description: Optional specifies whether the resource and its + key must exist. + type: boolean + required: + - name + type: object + certManager: + description: |- + CertManager references a cert-manager Issuer or ClusterIssuer. + If set, a Certificate resource will be created, provisioning the secret references in SecretName using the + issuer configured here. + properties: + group: + description: Group of the resource being referred to. + type: string + kind: + description: Kind of the resource being referred to. + type: string + name: + description: Name of the resource being referred to. + type: string + required: + - name + type: object + secretName: + description: SecretName references a secret holding the TLS key + and certificates. + type: string + type: object + linstorPassphraseSecret: + description: |- + LinstorPassphraseSecret used to configure the LINSTOR master passphrase. + + The referenced secret must contain a single key "MASTER_PASSPHRASE". The master passphrase is used to + * Derive encryption keys for volumes using the LUKS layer. + * Store credentials for accessing remotes for backups. + See https://linbit.com/drbd-user-guide/linstor-guide-1_0-en/#s-encrypt_commands for more information. + type: string + nfsServer: + description: NFSServer controls the deployment of the LINSTOR CSI + NFS Server DaemonSet. + properties: + enabled: + default: true + description: Enable the component. + type: boolean + podTemplate: + description: |- + Template to apply to Pods of the component. + + The template is applied as a patch to the default deployment, so it can be "sparse", not listing any + containers or volumes that should remain unchanged. + See https://kubernetes.io/docs/concepts/workloads/pods/#pod-templates + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + type: object + nodeAffinity: + description: |- + NodeAffinity selects the nodes on which LINSTOR Satellites will be deployed. + See https://kubernetes.io/docs/concepts/configuration/assign-pod-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 + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector selects the nodes on which LINSTOR Satellites will be deployed. + See https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ + type: object + patches: + description: |- + Patches is a list of kustomize patches to apply. + + See https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/patches/ for how to create patches. + items: + description: Patch represent either a Strategic Merge Patch or a + JSON patch and its targets. + properties: + options: + description: Options is a list of options for the patch + properties: + allowKindChange: + description: AllowKindChange allows kind changes to the + resource. + type: boolean + allowNameChange: + description: AllowNameChange allows name changes to the + resource. + type: boolean + type: object + patch: + description: Patch is the content of a patch. + minLength: 1 + type: string + target: + description: Target points to the resources that the patch is + applied to + properties: + annotationSelector: + description: |- + AnnotationSelector is a string that follows the label selection expression + https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api + It matches against the resource annotations. + type: string + group: + type: string + kind: + type: string + labelSelector: + description: |- + LabelSelector is a string that follows the label selection expression + https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api + It matches against the resource labels. + type: string + name: + description: Name of the resource. + type: string + namespace: + description: Namespace the resource belongs to, if it can + belong to a namespace. + type: string + version: + type: string + type: object + required: + - patch + type: object + type: array + properties: + description: |- + Properties to apply on the cluster level. + + Use to create default settings for DRBD that should apply to all resources or to configure some other cluster + wide default. + items: + properties: + name: + description: Name of the property to set. + minLength: 1 + type: string + value: + description: Value to set the property to. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + repository: + description: Repository used to pull workload images. + type: string + tolerations: + description: |- + Tolerations selects the nodes on which LINSTOR Satellites will be deployed. + + The default tolerations for DaemonSets are automatically added. + 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 + type: object + status: + description: LinstorClusterStatus defines the observed state of LinstorCluster + properties: + availableCapacityBytes: + description: The number of bytes in total in all storage pools in + the LINSTOR Cluster. + format: int64 + type: integer + capacity: + description: Capacity mirrors the information from TotalCapacityBytes + and FreeCapacityBytes in a human-readable string + type: string + conditions: + description: Current LINSTOR Cluster 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 + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + freeCapacityBytes: + description: The number of bytes free in all storage pools in the + LINSTOR Cluster. + format: int64 + type: integer + numberOfSnapshots: + description: The number of snapshots in the LINSTOR Cluster. + format: int32 + type: integer + numberOfVolumes: + description: The number of volumes in the LINSTOR Cluster. + format: int32 + type: integer + runningSatellites: + description: The number of LINSTOR Satellites currently running. + format: int32 + type: integer + satellites: + description: Satellites mirrors the information from ScheduledSatellites + and RunningSatellites in a human-readable string + type: string + scheduledSatellites: + description: The number of LINSTOR Satellites that are expected to + run. + format: int32 + type: integer + version: + description: The Version of the LINSTOR Cluster. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: linstornodeconnections.piraeus.io +spec: + group: piraeus.io + names: + kind: LinstorNodeConnection + listKind: LinstorNodeConnectionList + plural: linstornodeconnections + singular: linstornodeconnection + scope: Cluster + versions: + - additionalPrinterColumns: + - description: If the LINSTOR Node Connection is fully configured + jsonPath: .status.conditions[?(@.type=='Configured')].status + name: Configured + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: LinstorNodeConnection is the Schema for the linstornodeconnections + API + 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: LinstorNodeConnectionSpec defines the desired state of LinstorNodeConnection + properties: + paths: + description: Paths configure the network path used when connecting + two nodes. + items: + properties: + interface: + description: Interface to use on both nodes. + type: string + name: + description: Name of the path. + type: string + required: + - interface + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + properties: + description: |- + Properties to apply for the node connection. + + Use to create default settings for DRBD that should apply to all resources connections between a set of + cluster nodes. + items: + properties: + name: + description: Name of the property to set. + minLength: 1 + type: string + value: + description: Value to set the property to. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + selector: + description: |- + Selector selects which pair of Satellites the connection should apply to. + If not given, the connection will be applied to all connections. + items: + description: SelectorTerm matches pairs of nodes by checking that + the nodes match all specified requirements. + properties: + matchLabels: + description: MatchLabels is a list of match expressions that + the node pairs must meet. + items: + properties: + key: + description: Key is the name of a node label. + minLength: 1 + type: string + op: + default: Exists + description: |- + Op to apply to the label. + Exists (default) checks for the presence of the label on both nodes in the pair. + DoesNotExist checks that the label is not present on either node in the pair. + In checks for the presence of the label value given by Values on both nodes in the pair. + NotIn checks that both nodes in the pair do not have any of the label values given by Values. + Same checks that the label value is equal in the node pair. + NotSame checks that the label value is not equal in the node pair. + enum: + - Exists + - DoesNotExist + - In + - NotIn + - Same + - NotSame + type: string + values: + description: Values to match on, using the provided Op. + items: + type: string + type: array + required: + - key + type: object + type: array + required: + - matchLabels + type: object + type: array + type: object + status: + description: LinstorNodeConnectionStatus defines the observed state of + LinstorNodeConnection + properties: + conditions: + description: Current LINSTOR Node Connection 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 + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: linstorsatelliteconfigurations.piraeus.io +spec: + group: piraeus.io + names: + kind: LinstorSatelliteConfiguration + listKind: LinstorSatelliteConfigurationList + plural: linstorsatelliteconfigurations + singular: linstorsatelliteconfiguration + scope: Cluster + versions: + - additionalPrinterColumns: + - description: The node selector used + jsonPath: .spec.nodeSelector + name: Selector + type: string + - description: If the Configuration was applied + jsonPath: .status.conditions[?(@.type=='Applied')].status + name: Applied + type: string + - description: Number of Satellites this Configuration has been applied to + jsonPath: .status.matched + name: Matched + type: integer + - description: Satellites this Configuration has been applied to + jsonPath: .status.appliedTo + name: Satellites + priority: 10 + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: LinstorSatelliteConfiguration is the Schema for the linstorsatelliteconfigurations + API + 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: |- + LinstorSatelliteConfigurationSpec defines a partial, desired state of a LinstorSatelliteSpec. + + All the LinstorSatelliteConfiguration resources with matching NodeSelector will + be merged into a single LinstorSatelliteSpec. + properties: + deletionPolicy: + description: |- + DeletionPolicy configures the way LinstorSatellite resources are deleted. + + A LinstorSatellite may be deleted because: + * It no longer matches the affinity and node selector of the LinstorCluster resource. + * The node it references has been removed from Kubernetes. + * It was manually deleted outside the Operator. + + A LinstorSatellite may store the last copy of a volume, in which case it is not desirable to unconditionally remove + the satellite from the cluster. For this reason, the following deletion policies exist: + + * DeletionPolicyEvacuate will start evacuation of the LINSTOR Satellite and wait until it completes before removing the LinstorSatellite object, comparable to the "linstor node evacuate" command. + * DeletionPolicyRetain will retain the LINSTOR Satellite, keeping it registered in LINSTOR, but removing associated Kubernetes resources. + * DeletionPolicyDelete will remove the LINSTOR Satellite from the LINSTOR Cluster without prior eviction, comparable to the "linstor node lost" command. + enum: + - Evacuate + - 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. + + If set, the control traffic between LINSTOR Controller and Satellite will be encrypted using mTLS. + nullable: true + properties: + caReference: + description: |- + CAReference configures the CA certificate to use when validating TLS certificates. + If not set, the TLS secret is expected to contain a "ca.crt" containing the CA certificate. + properties: + key: + default: ca.crt + description: |- + Key to select in the resource. + Defaults to ca.crt if not specified. + type: string + kind: + default: Secret + description: Kind of the resource containing the CA Certificate, + either a ConfigMap or Secret. + enum: + - ConfigMap + - Secret + type: string + name: + description: Name of the resource containing the CA Certificate. + type: string + optional: + description: Optional specifies whether the resource and its + key must exist. + type: boolean + required: + - name + type: object + certManager: + description: |- + CertManager references a cert-manager Issuer or ClusterIssuer. + If set, a Certificate resource will be created, provisioning the secret references in SecretName using the + issuer configured here. + properties: + group: + description: Group of the resource being referred to. + type: string + kind: + description: Kind of the resource being referred to. + type: string + name: + description: Name of the resource being referred to. + type: string + required: + - name + type: object + secretName: + description: SecretName references a secret holding the TLS key + and certificates. + type: string + tlsHandshakeDaemon: + description: |- + TLSHandshakeDaemon enables tlshd for establishing TLS sessions for use by DRBD. + + If enabled, adds a new sidecar to the LINSTOR Satellite that runs the tlshd handshake daemon. + The daemon uses the TLS certificate and key to establish secure connections on behalf of DRBD. + type: boolean + type: object + ipFamilies: + description: |- + IPFamilies configures the IP Family (IPv4 or IPv6) to use to connect to the LINSTOR Satellite. + + If set, the control traffic between LINSTOR Controller and Satellite will use only the given IP Family. + If not set, the Operator will configure all families found in the Satellites Pods' Status. + items: + description: IPFamily represents the IP Family (IPv4 or IPv6). + enum: + - IPv4 + - IPv6 + type: string + type: array + nodeAffinity: + description: |- + NodeAffinity selects which LinstorSatellite resources this spec should be applied to. + See https://kubernetes.io/docs/concepts/configuration/assign-pod-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 + nodeSelector: + additionalProperties: + type: string + description: |- + NodeSelector selects which LinstorSatellite resources this spec should be applied to. + See https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ + type: object + patches: + description: |- + Patches is a list of kustomize patches to apply. + + See https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/patches/ for how to create patches. + items: + description: Patch represent either a Strategic Merge Patch or a + JSON patch and its targets. + properties: + options: + description: Options is a list of options for the patch + properties: + allowKindChange: + description: AllowKindChange allows kind changes to the + resource. + type: boolean + allowNameChange: + description: AllowNameChange allows name changes to the + resource. + type: boolean + type: object + patch: + description: Patch is the content of a patch. + minLength: 1 + type: string + target: + description: Target points to the resources that the patch is + applied to + properties: + annotationSelector: + description: |- + AnnotationSelector is a string that follows the label selection expression + https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api + It matches against the resource annotations. + type: string + group: + type: string + kind: + type: string + labelSelector: + description: |- + LabelSelector is a string that follows the label selection expression + https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api + It matches against the resource labels. + type: string + name: + description: Name of the resource. + type: string + namespace: + description: Namespace the resource belongs to, if it can + belong to a namespace. + type: string + version: + type: string + type: object + required: + - patch + type: object + type: array + podTemplate: + description: |- + Template to apply to Satellite Pods. + + The template is applied as a patch to the default resource, so it can be "sparse", not listing any + containers or volumes that should remain unchanged. + See https://kubernetes.io/docs/concepts/workloads/pods/#pod-templates + type: object + x-kubernetes-map-type: atomic + x-kubernetes-preserve-unknown-fields: true + properties: + description: Properties is a list of properties to set on the node. + items: + properties: + expandFrom: + description: |- + ExpandFrom can reference multiple resource fields at once. + It either sets the property to an aggregate value based on matched resource fields, or expands to multiple + properties. + properties: + delimiter: + description: Delimiter used to join multiple key and value + pairs together. + type: string + nameTemplate: + description: |- + NameTemplate defines how the property key is expanded. + If set, the template is appended to the defined property name, creating multiple properties instead of one + aggregate. + * $1 is replaced with the matched key. + * $2 is replaced with the matched value. + type: string + nodeFieldRef: + description: Select a field of the node. Supports `metadata.name`, + `metadata.labels['']`, `metadata.annotations['']`. + minLength: 1 + type: string + valueTemplate: + description: |- + ValueTemplate defines how the property value is expanded. + * $1 is replaced with the matched key. + * $2 is replaced with the matched value. + type: string + required: + - nodeFieldRef + type: object + name: + description: Name of the property to set. + minLength: 1 + type: string + optional: + description: Optional values are only set if they have a non-empty + value + type: boolean + value: + description: Value to set the property to. + type: string + valueFrom: + description: ValueFrom sets the value from an existing resource. + properties: + nodeFieldRef: + description: Select a field of the node. Supports `metadata.name`, + `metadata.labels['']`, `metadata.annotations['']`. + minLength: 1 + type: string + required: + - nodeFieldRef + type: object + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + storagePools: + description: StoragePools is a list of storage pools to configure + on the node. + items: + properties: + filePool: + description: Configures a file system based storage pool, allocating + a regular file per volume. + properties: + directory: + description: Directory is the path to the host directory + used to store volume data. + type: string + type: object + fileThinPool: + description: Configures a file system based storage pool, allocating + a sparse file per volume. + properties: + directory: + description: Directory is the path to the host directory + used to store volume data. + type: string + type: object + lvmPool: + description: Configures a LVM Volume Group as storage pool. + properties: + volumeGroup: + type: string + type: object + lvmThinPool: + description: Configures a LVM Thin Pool as storage pool. + properties: + thinPool: + description: ThinPool is the name of the thinpool LV (without + VG prefix). + type: string + volumeGroup: + type: string + type: object + name: + description: Name of the storage pool in linstor. + minLength: 3 + type: string + properties: + description: Properties to set on the storage pool. + items: + properties: + expandFrom: + description: |- + ExpandFrom can reference multiple resource fields at once. + It either sets the property to an aggregate value based on matched resource fields, or expands to multiple + properties. + properties: + delimiter: + description: Delimiter used to join multiple key and + value pairs together. + type: string + nameTemplate: + description: |- + NameTemplate defines how the property key is expanded. + If set, the template is appended to the defined property name, creating multiple properties instead of one + aggregate. + * $1 is replaced with the matched key. + * $2 is replaced with the matched value. + type: string + nodeFieldRef: + description: Select a field of the node. Supports + `metadata.name`, `metadata.labels['']`, `metadata.annotations['']`. + minLength: 1 + type: string + valueTemplate: + description: |- + ValueTemplate defines how the property value is expanded. + * $1 is replaced with the matched key. + * $2 is replaced with the matched value. + type: string + required: + - nodeFieldRef + type: object + name: + description: Name of the property to set. + minLength: 1 + type: string + optional: + description: Optional values are only set if they have + a non-empty value + type: boolean + value: + description: Value to set the property to. + type: string + valueFrom: + description: ValueFrom sets the value from an existing + resource. + properties: + nodeFieldRef: + description: Select a field of the node. Supports + `metadata.name`, `metadata.labels['']`, `metadata.annotations['']`. + minLength: 1 + type: string + required: + - nodeFieldRef + type: object + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + source: + properties: + hostDevices: + description: HostDevices is a list of device paths used + to configure the given pool. + items: + type: string + minItems: 1 + type: array + type: object + zfsPool: + description: Configures a ZFS system based storage pool, allocating + zvols from the given zpool. + properties: + zPool: + description: ZPool is the name of the ZFS zpool. + type: string + type: object + zfsThinPool: + description: Configures a ZFS system based storage pool, allocating + sparse zvols from the given zpool. + properties: + zPool: + description: ZPool is the name of the ZFS zpool. + type: string + type: object + required: + - name + type: object + type: array + type: object + status: + description: LinstorSatelliteConfigurationStatus defines the observed + state of LinstorSatelliteConfiguration + properties: + appliedTo: + description: AppliedTo lists the LinstorSatellite resource this configuration + was applied to + items: + type: string + type: array + conditions: + description: Current LINSTOR Satellite Config 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 + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + matched: + description: Number of configured LinstorSatellite resource. + format: int64 + type: integer + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: linstorsatellites.piraeus.io +spec: + group: piraeus.io + names: + kind: LinstorSatellite + listKind: LinstorSatelliteList + plural: linstorsatellites + singular: linstorsatellite + scope: Cluster + versions: + - additionalPrinterColumns: + - description: If the LINSTOR Satellite is connected + jsonPath: .status.conditions[?(@.type=='Available')].status + name: Connected + type: string + - description: If the LINSTOR Satellite is fully configured + jsonPath: .status.conditions[?(@.type=='Configured')].status + name: Configured + type: string + - description: The Satellite Configurations applied to this Satellite + jsonPath: .metadata.annotations.piraeus\.io/applied-configurations + name: Applied Configurations + priority: 10 + type: string + - description: The deletion policy of the Satellite + jsonPath: .spec.deletionPolicy + name: Deletion Policy + type: string + - description: The used capacity on the node + jsonPath: .status.capacity + name: Used Capacity + type: string + - description: The number of volumes on the node + jsonPath: .status.numberOfVolumes + name: Volumes + type: integer + - description: The number of snapshots on the node + jsonPath: .status.numberOfSnapshots + name: Snapshots + priority: 10 + type: integer + - description: The storage providers supported by the node + jsonPath: .status.storageProviders + name: Storage Providers + priority: 10 + type: string + - description: The device layers supported by the node + jsonPath: .status.deviceLayers + name: Device Layers + priority: 10 + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: LinstorSatellite is the Schema for the linstorsatellites API + 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: LinstorSatelliteSpec defines the desired state of LinstorSatellite + properties: + clusterRef: + description: ClusterRef references the LinstorCluster used to create + this LinstorSatellite. + properties: + caReference: + description: |- + CAReference configures the CA certificate to use when validating TLS certificates. + If not set, the TLS secret is expected to contain a "ca.crt" containing the CA certificate. + properties: + key: + default: ca.crt + description: |- + Key to select in the resource. + Defaults to ca.crt if not specified. + type: string + kind: + default: Secret + description: Kind of the resource containing the CA Certificate, + either a ConfigMap or Secret. + enum: + - ConfigMap + - Secret + type: string + name: + description: Name of the resource containing the CA Certificate. + type: string + optional: + description: Optional specifies whether the resource and its + key must exist. + type: boolean + required: + - name + type: object + clientSecretName: + description: ClientSecretName references the secret used by the + operator to validate the https endpoint. + type: string + externalController: + description: |- + ExternalController references an external controller. + When set, the Operator uses the external cluster to register satellites. + properties: + url: + description: URL of the external controller. + minLength: 3 + type: string + required: + - url + type: object + name: + description: Name of the LinstorCluster resource controlling this + satellite. + type: string + type: object + deletionPolicy: + default: Retain + description: |- + DeletionPolicy configures the way LinstorSatellite resources are deleted. + + A LinstorSatellite may be deleted because: + * It no longer matches the affinity and node selector of the LinstorCluster resource. + * The node it references has been removed from Kubernetes. + * It was manually deleted outside the Operator. + + A LinstorSatellite may store the last copy of a volume, in which case it is not desirable to unconditionally remove + the satellite from the cluster. For this reason, the following deletion policies exist: + + * DeletionPolicyEvacuate will start evacuation of the LINSTOR Satellite and wait until it completes before removing the LinstorSatellite object, comparable to the "linstor node evacuate" command. + * DeletionPolicyRetain will retain the LINSTOR Satellite, keeping it registered in LINSTOR, but removing associated Kubernetes resources. + * DeletionPolicyDelete will remove the LINSTOR Satellite from the LINSTOR Cluster without prior eviction, comparable to the "linstor node lost" command. + enum: + - Evacuate + - 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. + + If set, the control traffic between LINSTOR Controller and Satellite will be encrypted using mTLS. + The Controller will use the client key from `LinstorCluster.spec.internalTLS` when connecting. + nullable: true + properties: + caReference: + description: |- + CAReference configures the CA certificate to use when validating TLS certificates. + If not set, the TLS secret is expected to contain a "ca.crt" containing the CA certificate. + properties: + key: + default: ca.crt + description: |- + Key to select in the resource. + Defaults to ca.crt if not specified. + type: string + kind: + default: Secret + description: Kind of the resource containing the CA Certificate, + either a ConfigMap or Secret. + enum: + - ConfigMap + - Secret + type: string + name: + description: Name of the resource containing the CA Certificate. + type: string + optional: + description: Optional specifies whether the resource and its + key must exist. + type: boolean + required: + - name + type: object + certManager: + description: |- + CertManager references a cert-manager Issuer or ClusterIssuer. + If set, a Certificate resource will be created, provisioning the secret references in SecretName using the + issuer configured here. + properties: + group: + description: Group of the resource being referred to. + type: string + kind: + description: Kind of the resource being referred to. + type: string + name: + description: Name of the resource being referred to. + type: string + required: + - name + type: object + secretName: + description: SecretName references a secret holding the TLS key + and certificates. + type: string + tlsHandshakeDaemon: + description: |- + TLSHandshakeDaemon enables tlshd for establishing TLS sessions for use by DRBD. + + If enabled, adds a new sidecar to the LINSTOR Satellite that runs the tlshd handshake daemon. + The daemon uses the TLS certificate and key to establish secure connections on behalf of DRBD. + type: boolean + type: object + ipFamilies: + description: |- + IPFamilies configures the IP Family (IPv4 or IPv6) to use to connect to the LINSTOR Satellite. + + If set, the control traffic between LINSTOR Controller and Satellite will use only the given IP Family. + If not set, the Operator will configure all families found in the Satellites Pods' Status. + items: + description: IPFamily represents the IP Family (IPv4 or IPv6). + enum: + - IPv4 + - IPv6 + type: string + type: array + patches: + description: |- + Patches is a list of kustomize patches to apply. + + See https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/patches/ for how to create patches. + items: + description: Patch represent either a Strategic Merge Patch or a + JSON patch and its targets. + properties: + options: + description: Options is a list of options for the patch + properties: + allowKindChange: + description: AllowKindChange allows kind changes to the + resource. + type: boolean + allowNameChange: + description: AllowNameChange allows name changes to the + resource. + type: boolean + type: object + patch: + description: Patch is the content of a patch. + minLength: 1 + type: string + target: + description: Target points to the resources that the patch is + applied to + properties: + annotationSelector: + description: |- + AnnotationSelector is a string that follows the label selection expression + https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api + It matches against the resource annotations. + type: string + group: + type: string + kind: + type: string + labelSelector: + description: |- + LabelSelector is a string that follows the label selection expression + https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api + It matches against the resource labels. + type: string + name: + description: Name of the resource. + type: string + namespace: + description: Namespace the resource belongs to, if it can + belong to a namespace. + type: string + version: + type: string + type: object + required: + - patch + type: object + type: array + properties: + description: Properties is a list of properties to set on the node. + items: + properties: + expandFrom: + description: |- + ExpandFrom can reference multiple resource fields at once. + It either sets the property to an aggregate value based on matched resource fields, or expands to multiple + properties. + properties: + delimiter: + description: Delimiter used to join multiple key and value + pairs together. + type: string + nameTemplate: + description: |- + NameTemplate defines how the property key is expanded. + If set, the template is appended to the defined property name, creating multiple properties instead of one + aggregate. + * $1 is replaced with the matched key. + * $2 is replaced with the matched value. + type: string + nodeFieldRef: + description: Select a field of the node. Supports `metadata.name`, + `metadata.labels['']`, `metadata.annotations['']`. + minLength: 1 + type: string + valueTemplate: + description: |- + ValueTemplate defines how the property value is expanded. + * $1 is replaced with the matched key. + * $2 is replaced with the matched value. + type: string + required: + - nodeFieldRef + type: object + name: + description: Name of the property to set. + minLength: 1 + type: string + optional: + description: Optional values are only set if they have a non-empty + value + type: boolean + value: + description: Value to set the property to. + type: string + valueFrom: + description: ValueFrom sets the value from an existing resource. + properties: + nodeFieldRef: + description: Select a field of the node. Supports `metadata.name`, + `metadata.labels['']`, `metadata.annotations['']`. + minLength: 1 + type: string + required: + - nodeFieldRef + type: object + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + repository: + description: Repository used to pull workload images. + type: string + storagePools: + description: StoragePools is a list of storage pools to configure + on the node. + items: + properties: + filePool: + description: Configures a file system based storage pool, allocating + a regular file per volume. + properties: + directory: + description: Directory is the path to the host directory + used to store volume data. + type: string + type: object + fileThinPool: + description: Configures a file system based storage pool, allocating + a sparse file per volume. + properties: + directory: + description: Directory is the path to the host directory + used to store volume data. + type: string + type: object + lvmPool: + description: Configures a LVM Volume Group as storage pool. + properties: + volumeGroup: + type: string + type: object + lvmThinPool: + description: Configures a LVM Thin Pool as storage pool. + properties: + thinPool: + description: ThinPool is the name of the thinpool LV (without + VG prefix). + type: string + volumeGroup: + type: string + type: object + name: + description: Name of the storage pool in linstor. + minLength: 3 + type: string + properties: + description: Properties to set on the storage pool. + items: + properties: + expandFrom: + description: |- + ExpandFrom can reference multiple resource fields at once. + It either sets the property to an aggregate value based on matched resource fields, or expands to multiple + properties. + properties: + delimiter: + description: Delimiter used to join multiple key and + value pairs together. + type: string + nameTemplate: + description: |- + NameTemplate defines how the property key is expanded. + If set, the template is appended to the defined property name, creating multiple properties instead of one + aggregate. + * $1 is replaced with the matched key. + * $2 is replaced with the matched value. + type: string + nodeFieldRef: + description: Select a field of the node. Supports + `metadata.name`, `metadata.labels['']`, `metadata.annotations['']`. + minLength: 1 + type: string + valueTemplate: + description: |- + ValueTemplate defines how the property value is expanded. + * $1 is replaced with the matched key. + * $2 is replaced with the matched value. + type: string + required: + - nodeFieldRef + type: object + name: + description: Name of the property to set. + minLength: 1 + type: string + optional: + description: Optional values are only set if they have + a non-empty value + type: boolean + value: + description: Value to set the property to. + type: string + valueFrom: + description: ValueFrom sets the value from an existing + resource. + properties: + nodeFieldRef: + description: Select a field of the node. Supports + `metadata.name`, `metadata.labels['']`, `metadata.annotations['']`. + minLength: 1 + type: string + required: + - nodeFieldRef + type: object + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + source: + properties: + hostDevices: + description: HostDevices is a list of device paths used + to configure the given pool. + items: + type: string + minItems: 1 + type: array + type: object + zfsPool: + description: Configures a ZFS system based storage pool, allocating + zvols from the given zpool. + properties: + zPool: + description: ZPool is the name of the ZFS zpool. + type: string + type: object + zfsThinPool: + description: Configures a ZFS system based storage pool, allocating + sparse zvols from the given zpool. + properties: + zPool: + description: ZPool is the name of the ZFS zpool. + type: string + type: object + required: + - name + type: object + type: array + required: + - clusterRef + type: object + status: + description: LinstorSatelliteStatus defines the observed state of LinstorSatellite + properties: + availableCapacityBytes: + description: The number of bytes in total in all storage pools on + this Satellite. + format: int64 + type: integer + capacity: + description: Capacity mirrors the information from TotalCapacityBytes + and FreeCapacityBytes in a human-readable string. + type: string + conditions: + description: Current LINSTOR Satellite 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 + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + deviceLayers: + description: DeviceLayers lists the device layers (LUKS, CACHE, etc...) + this Satellite supports. + items: + type: string + type: array + freeCapacityBytes: + description: The number of bytes free in all storage pools on this + Satellite. + format: int64 + type: integer + numberOfSnapshots: + description: The number of snapshots on this Satellite. + format: int32 + type: integer + numberOfVolumes: + description: The number of volumes on this Satellite. + format: int32 + type: integer + storageProviders: + description: StorageProviders lists the storage providers (LVM, ZFS, + etc...) this Satellite supports. + items: + type: string + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/packages/system/piraeus-operator-crds/values.yaml b/packages/system/piraeus-operator-crds/values.yaml new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/packages/system/piraeus-operator-crds/values.yaml @@ -0,0 +1 @@ +{} diff --git a/packages/system/piraeus-operator/values.yaml b/packages/system/piraeus-operator/values.yaml index 4cb448d1..eb4ec1ac 100644 --- a/packages/system/piraeus-operator/values.yaml +++ b/packages/system/piraeus-operator/values.yaml @@ -1,5 +1,5 @@ piraeus: - installCRDs: true + installCRDs: false autogenerate: false tls: certManagerIssuerRef: From cea30708bc9a70c47bf03533037e2e83ad5d20df Mon Sep 17 00:00:00 2001 From: IvanHunters Date: Fri, 6 Feb 2026 02:14:02 +0300 Subject: [PATCH 181/889] fix(monitoring): remove duplicate components from PackageSource Remove components that are already defined in separate PackageSources to avoid race conditions and HelmRelease conflicts: - vertical-pod-autoscaler - postgres-operator - grafana-operator - victoria-metrics-operator - prometheus-operator-crds These components should be installed via their dedicated PackageSources. Signed-off-by: IvanHunters --- .../core/platform/sources/monitoring.yaml | 27 +------------------ 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/packages/core/platform/sources/monitoring.yaml b/packages/core/platform/sources/monitoring.yaml index 2b56c300..804aec56 100644 --- a/packages/core/platform/sources/monitoring.yaml +++ b/packages/core/platform/sources/monitoring.yaml @@ -30,29 +30,4 @@ spec: install: privileged: true namespace: cozy-monitoring - releaseName: monitoring - - name: vertical-pod-autoscaler - path: system/vertical-pod-autoscaler - install: - namespace: cozy-vertical-pod-autoscaler - releaseName: vertical-pod-autoscaler - - name: postgres-operator - path: system/postgres-operator - install: - namespace: cozy-postgres-operator - releaseName: postgres-operator - - name: grafana-operator - path: system/grafana-operator - install: - namespace: cozy-grafana-operator - releaseName: grafana-operator - - name: victoria-metrics-operator - path: system/victoria-metrics-operator - install: - namespace: cozy-victoria-metrics-operator - releaseName: victoria-metrics-operator - - name: prometheus-operator-crds - path: system/prometheus-operator-crds - install: - namespace: cozy-prometheus-operator-crds - releaseName: prometheus-operator-crds \ No newline at end of file + releaseName: monitoring \ No newline at end of file From 0a3a38c3b6272dfe9327e6c6699746567314ac85 Mon Sep 17 00:00:00 2001 From: IvanHunters Date: Fri, 6 Feb 2026 02:27:29 +0300 Subject: [PATCH 182/889] feat(local-ccm): add node-lifecycle-controller component Add optional node-lifecycle-controller that automatically deletes unreachable NotReady nodes from the cluster. This solves the problem of "zombie" node objects left behind when cluster autoscaler deletes cloud instances. Features: - Monitors nodes matching a label selector - Deletes nodes that are NotReady for configurable duration - Verifies unreachability via ICMP ping before deletion - Supports protected labels to prevent deletion of specific nodes - Leader election for HA deployment Disabled by default. Enable with: nodeLifecycleController: enabled: true Signed-off-by: IvanHunters --- .../charts/local-ccm/templates/_helpers.tpl | 32 +++++++++ ...node-lifecycle-controller-clusterrole.yaml | 18 +++++ ...fecycle-controller-clusterrolebinding.yaml | 16 +++++ .../node-lifecycle-controller-deployment.yaml | 72 +++++++++++++++++++ ...e-lifecycle-controller-serviceaccount.yaml | 8 +++ .../local-ccm/charts/local-ccm/values.yaml | 41 +++++++++++ 6 files changed, 187 insertions(+) create mode 100644 packages/system/local-ccm/charts/local-ccm/templates/node-lifecycle-controller-clusterrole.yaml create mode 100644 packages/system/local-ccm/charts/local-ccm/templates/node-lifecycle-controller-clusterrolebinding.yaml create mode 100644 packages/system/local-ccm/charts/local-ccm/templates/node-lifecycle-controller-deployment.yaml create mode 100644 packages/system/local-ccm/charts/local-ccm/templates/node-lifecycle-controller-serviceaccount.yaml diff --git a/packages/system/local-ccm/charts/local-ccm/templates/_helpers.tpl b/packages/system/local-ccm/charts/local-ccm/templates/_helpers.tpl index 0de841f5..435ef6e4 100644 --- a/packages/system/local-ccm/charts/local-ccm/templates/_helpers.tpl +++ b/packages/system/local-ccm/charts/local-ccm/templates/_helpers.tpl @@ -63,3 +63,35 @@ Create the name of the service account to use {{- default "default" .Values.serviceAccount.name }} {{- end }} {{- end }} + +{{/* +Node Lifecycle Controller fullname +*/}} +{{- define "node-lifecycle-controller.fullname" -}} +{{- printf "%s-lifecycle" (include "local-ccm.fullname" .) | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Node Lifecycle Controller labels +*/}} +{{- define "node-lifecycle-controller.labels" -}} +helm.sh/chart: {{ include "local-ccm.chart" . }} +{{ include "node-lifecycle-controller.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- with .Values.labels }} +{{ toYaml . }} +{{- end }} +{{- end }} + +{{/* +Node Lifecycle Controller selector labels +*/}} +{{- define "node-lifecycle-controller.selectorLabels" -}} +app.kubernetes.io/name: node-lifecycle-controller +app.kubernetes.io/instance: {{ .Release.Name }} +app: node-lifecycle-controller +component: node-lifecycle +{{- end }} diff --git a/packages/system/local-ccm/charts/local-ccm/templates/node-lifecycle-controller-clusterrole.yaml b/packages/system/local-ccm/charts/local-ccm/templates/node-lifecycle-controller-clusterrole.yaml new file mode 100644 index 00000000..4d5e078d --- /dev/null +++ b/packages/system/local-ccm/charts/local-ccm/templates/node-lifecycle-controller-clusterrole.yaml @@ -0,0 +1,18 @@ +{{- if .Values.nodeLifecycleController.enabled }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "node-lifecycle-controller.fullname" . }} + labels: + {{- include "node-lifecycle-controller.labels" . | nindent 4 }} +rules: +- apiGroups: [""] + resources: ["nodes"] + verbs: ["get", "list", "watch", "delete"] +- apiGroups: [""] + resources: ["events"] + verbs: ["create", "patch"] +- apiGroups: ["coordination.k8s.io"] + resources: ["leases"] + verbs: ["get", "create", "update"] +{{- end }} diff --git a/packages/system/local-ccm/charts/local-ccm/templates/node-lifecycle-controller-clusterrolebinding.yaml b/packages/system/local-ccm/charts/local-ccm/templates/node-lifecycle-controller-clusterrolebinding.yaml new file mode 100644 index 00000000..dadf74b2 --- /dev/null +++ b/packages/system/local-ccm/charts/local-ccm/templates/node-lifecycle-controller-clusterrolebinding.yaml @@ -0,0 +1,16 @@ +{{- if .Values.nodeLifecycleController.enabled }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "node-lifecycle-controller.fullname" . }} + labels: + {{- include "node-lifecycle-controller.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "node-lifecycle-controller.fullname" . }} +subjects: +- kind: ServiceAccount + name: {{ include "node-lifecycle-controller.fullname" . }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/packages/system/local-ccm/charts/local-ccm/templates/node-lifecycle-controller-deployment.yaml b/packages/system/local-ccm/charts/local-ccm/templates/node-lifecycle-controller-deployment.yaml new file mode 100644 index 00000000..c7450ff5 --- /dev/null +++ b/packages/system/local-ccm/charts/local-ccm/templates/node-lifecycle-controller-deployment.yaml @@ -0,0 +1,72 @@ +{{- if .Values.nodeLifecycleController.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "node-lifecycle-controller.fullname" . }} + labels: + {{- include "node-lifecycle-controller.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.nodeLifecycleController.replicaCount }} + selector: + matchLabels: + {{- include "node-lifecycle-controller.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "node-lifecycle-controller.selectorLabels" . | nindent 8 }} + spec: + serviceAccountName: {{ include "node-lifecycle-controller.fullname" . }} + hostNetwork: true + dnsPolicy: ClusterFirstWithHostNet + securityContext: + runAsNonRoot: false + tolerations: + - key: node-role.kubernetes.io/control-plane + operator: Exists + effect: NoSchedule + - key: node-role.kubernetes.io/master + operator: Exists + effect: NoSchedule + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + preference: + matchExpressions: + - key: node-role.kubernetes.io/control-plane + operator: Exists + containers: + - name: controller + image: "{{ .Values.nodeLifecycleController.image.repository }}:{{ .Values.nodeLifecycleController.image.tag }}" + imagePullPolicy: {{ .Values.nodeLifecycleController.image.pullPolicy }} + command: + - /node-lifecycle-controller + args: + {{- if .Values.nodeLifecycleController.nodeSelector }} + - --node-selector={{ .Values.nodeLifecycleController.nodeSelector }} + {{- end }} + {{- if .Values.nodeLifecycleController.protectedLabels }} + - --protected-labels={{ .Values.nodeLifecycleController.protectedLabels }} + {{- end }} + - --not-ready-timeout={{ .Values.nodeLifecycleController.notReadyTimeout }} + - --ping-timeout={{ .Values.nodeLifecycleController.pingTimeout }} + - --ping-count={{ .Values.nodeLifecycleController.pingCount }} + - --reconcile-interval={{ .Values.nodeLifecycleController.reconcileInterval }} + - --leader-elect={{ .Values.nodeLifecycleController.leaderElection.enabled }} + - --leader-elect-id={{ .Values.nodeLifecycleController.leaderElection.resourceName }} + - --namespace=$(POD_NAMESPACE) + - --dry-run={{ .Values.nodeLifecycleController.dryRun }} + - --v={{ .Values.nodeLifecycleController.verbosity }} + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + securityContext: + capabilities: + add: + - NET_RAW + runAsUser: 0 + resources: + {{- toYaml .Values.nodeLifecycleController.resources | nindent 10 }} +{{- end }} diff --git a/packages/system/local-ccm/charts/local-ccm/templates/node-lifecycle-controller-serviceaccount.yaml b/packages/system/local-ccm/charts/local-ccm/templates/node-lifecycle-controller-serviceaccount.yaml new file mode 100644 index 00000000..a7a4f72d --- /dev/null +++ b/packages/system/local-ccm/charts/local-ccm/templates/node-lifecycle-controller-serviceaccount.yaml @@ -0,0 +1,8 @@ +{{- if .Values.nodeLifecycleController.enabled }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "node-lifecycle-controller.fullname" . }} + labels: + {{- include "node-lifecycle-controller.labels" . | nindent 4 }} +{{- end }} diff --git a/packages/system/local-ccm/charts/local-ccm/values.yaml b/packages/system/local-ccm/charts/local-ccm/values.yaml index cdd14c0c..702bffff 100644 --- a/packages/system/local-ccm/charts/local-ccm/values.yaml +++ b/packages/system/local-ccm/charts/local-ccm/values.yaml @@ -55,3 +55,44 @@ affinity: labels: {} # Additional annotations for pods podAnnotations: {} + +# Node Lifecycle Controller configuration +# Automatically deletes unreachable NotReady nodes from the cluster +nodeLifecycleController: + enabled: false + image: + repository: ghcr.io/cozystack/node-lifecycle-controller + tag: v0.1.0 + pullPolicy: IfNotPresent + # Label selector for nodes to manage + # Only nodes matching this selector will be considered for deletion + # Example: "node.kubernetes.io/instance-type" (nodes created by autoscaler) + nodeSelector: "" + # Comma-separated list of labels that protect nodes from deletion + protectedLabels: "" + # Duration a node must be NotReady before considering deletion + notReadyTimeout: 5m + # Timeout for ping checks + pingTimeout: 5s + # Number of ping attempts before considering node unreachable + pingCount: 3 + # Interval between reconciliation loops + reconcileInterval: 30s + # Log actions without actually deleting nodes + dryRun: false + # Verbosity level (0-5) + verbosity: 2 + # Leader election for HA + leaderElection: + enabled: true + resourceName: node-lifecycle-controller + # Number of replicas + replicaCount: 1 + # Pod resources + resources: + requests: + cpu: 10m + memory: 32Mi + limits: + cpu: 100m + memory: 64Mi From 41b7829d4d1b4018016b05a39eb2b90e0387bd2f Mon Sep 17 00:00:00 2001 From: IvanHunters Date: Fri, 6 Feb 2026 09:39:05 +0300 Subject: [PATCH 183/889] fix(monitoring): add missing dashboards.list file Add dashboards.list that defines which Grafana dashboards to create. Without this file, no GrafanaDashboard resources were being generated. Signed-off-by: IvanHunters --- packages/system/monitoring/dashboards.list | 45 ++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 packages/system/monitoring/dashboards.list diff --git a/packages/system/monitoring/dashboards.list b/packages/system/monitoring/dashboards.list new file mode 100644 index 00000000..1f4b2eea --- /dev/null +++ b/packages/system/monitoring/dashboards.list @@ -0,0 +1,45 @@ +dotdc/k8s-views-global +dotdc/k8s-views-namespaces +dotdc/k8s-system-coredns +dotdc/k8s-views-pods +cache/nginx-vts-stats +victoria-metrics/vmalert +victoria-metrics/vmagent +victoria-metrics/victoriametrics-cluster +victoria-metrics/backupmanager +victoria-metrics/victoriametrics +victoria-metrics/operator +ingress/controller-detail +ingress/controllers +ingress/namespaces +ingress/vhosts +ingress/vhost-detail +ingress/namespace-detail +db/cloudnativepg +db/maria-db +db/redis +main/controller +main/namespaces +main/capacity-planning +main/ntp +main/namespace +main/pod +main/volumes +main/node +main/nodes +control-plane/control-plane-status +control-plane/deprecated-resources +control-plane/dns-coredns +control-plane/kube-etcd +kubevirt/kubevirt-control-plane +flux/flux-control-plane +flux/flux-stats +kafka/strimzi-kafka +goldpinger/goldpinger +clickhouse/altinity-clickhouse-operator-dashboard +storage/linstor +seaweedfs/seaweedfs +hubble/overview +hubble/dns-namespace +hubble/l7-http-metrics +hubble/network-overview From b71e4fe956b694b87e17555a432275273fac968e Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 6 Feb 2026 13:30:58 +0300 Subject: [PATCH 184/889] [qdrant] Add Qdrant vector database application MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Qdrant as a new managed application following the HelmRelease-based vendoring pattern (same as nats, ingress, seaweedfs). - packages/system/qdrant/ — vendored upstream Qdrant Helm chart - packages/apps/qdrant/ — wrapper chart creating Flux HelmRelease CR - packages/system/qdrant-rd/ — ApplicationDefinition CRD - hack/e2e-apps/qdrant.bats — E2E test Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- hack/e2e-apps/qdrant.bats | 25 ++ packages/apps/qdrant/.helmignore | 4 + packages/apps/qdrant/Chart.yaml | 11 + packages/apps/qdrant/Makefile | 5 + packages/apps/qdrant/README.md | 52 +++ packages/apps/qdrant/charts/cozy-lib | 1 + packages/apps/qdrant/logos/qdrant.svg | 20 ++ .../templates/dashboard-resourcemap.yaml | 51 +++ packages/apps/qdrant/templates/qdrant.yaml | 43 +++ packages/apps/qdrant/values.schema.json | 82 +++++ packages/apps/qdrant/values.yaml | 34 ++ packages/system/qdrant-rd/Chart.yaml | 3 + packages/system/qdrant-rd/Makefile | 4 + packages/system/qdrant-rd/cozyrds/qdrant.yaml | 41 +++ .../system/qdrant-rd/templates/cozyrd.yaml | 4 + packages/system/qdrant-rd/values.yaml | 1 + packages/system/qdrant/Chart.yaml | 3 + packages/system/qdrant/Makefile | 7 + .../system/qdrant/charts/qdrant/.helmignore | 2 + .../system/qdrant/charts/qdrant/CHANGELOG.md | 8 + .../system/qdrant/charts/qdrant/Chart.yaml | 37 +++ packages/system/qdrant/charts/qdrant/LICENSE | 201 +++++++++++ .../system/qdrant/charts/qdrant/README.md | 157 +++++++++ .../qdrant/charts/qdrant/templates/NOTES.txt | 24 ++ .../charts/qdrant/templates/_helpers.tpl | 145 ++++++++ .../charts/qdrant/templates/configmap.yaml | 33 ++ .../charts/qdrant/templates/ingress.yaml | 53 +++ .../qdrant/charts/qdrant/templates/pdb.yaml | 26 ++ .../charts/qdrant/templates/secret.yaml | 15 + .../qdrant/templates/service-headless.yaml | 35 ++ .../charts/qdrant/templates/service.yaml | 39 +++ .../qdrant/templates/serviceaccount.yaml | 16 + .../qdrant/templates/servicemonitor.yaml | 52 +++ .../charts/qdrant/templates/statefulset.yaml | 312 ++++++++++++++++++ .../templates/tests/test-db-interaction.yaml | 128 +++++++ .../system/qdrant/charts/qdrant/values.yaml | 293 ++++++++++++++++ packages/system/qdrant/values.yaml | 1 + 37 files changed, 1968 insertions(+) create mode 100755 hack/e2e-apps/qdrant.bats create mode 100644 packages/apps/qdrant/.helmignore create mode 100644 packages/apps/qdrant/Chart.yaml create mode 100644 packages/apps/qdrant/Makefile create mode 100644 packages/apps/qdrant/README.md create mode 120000 packages/apps/qdrant/charts/cozy-lib create mode 100644 packages/apps/qdrant/logos/qdrant.svg create mode 100644 packages/apps/qdrant/templates/dashboard-resourcemap.yaml create mode 100644 packages/apps/qdrant/templates/qdrant.yaml create mode 100644 packages/apps/qdrant/values.schema.json create mode 100644 packages/apps/qdrant/values.yaml create mode 100644 packages/system/qdrant-rd/Chart.yaml create mode 100644 packages/system/qdrant-rd/Makefile create mode 100644 packages/system/qdrant-rd/cozyrds/qdrant.yaml create mode 100644 packages/system/qdrant-rd/templates/cozyrd.yaml create mode 100644 packages/system/qdrant-rd/values.yaml create mode 100644 packages/system/qdrant/Chart.yaml create mode 100644 packages/system/qdrant/Makefile create mode 100644 packages/system/qdrant/charts/qdrant/.helmignore create mode 100644 packages/system/qdrant/charts/qdrant/CHANGELOG.md create mode 100644 packages/system/qdrant/charts/qdrant/Chart.yaml create mode 100644 packages/system/qdrant/charts/qdrant/LICENSE create mode 100644 packages/system/qdrant/charts/qdrant/README.md create mode 100644 packages/system/qdrant/charts/qdrant/templates/NOTES.txt create mode 100644 packages/system/qdrant/charts/qdrant/templates/_helpers.tpl create mode 100644 packages/system/qdrant/charts/qdrant/templates/configmap.yaml create mode 100644 packages/system/qdrant/charts/qdrant/templates/ingress.yaml create mode 100644 packages/system/qdrant/charts/qdrant/templates/pdb.yaml create mode 100644 packages/system/qdrant/charts/qdrant/templates/secret.yaml create mode 100644 packages/system/qdrant/charts/qdrant/templates/service-headless.yaml create mode 100644 packages/system/qdrant/charts/qdrant/templates/service.yaml create mode 100644 packages/system/qdrant/charts/qdrant/templates/serviceaccount.yaml create mode 100644 packages/system/qdrant/charts/qdrant/templates/servicemonitor.yaml create mode 100644 packages/system/qdrant/charts/qdrant/templates/statefulset.yaml create mode 100644 packages/system/qdrant/charts/qdrant/templates/tests/test-db-interaction.yaml create mode 100644 packages/system/qdrant/charts/qdrant/values.yaml create mode 100644 packages/system/qdrant/values.yaml diff --git a/hack/e2e-apps/qdrant.bats b/hack/e2e-apps/qdrant.bats new file mode 100755 index 00000000..63b82a3b --- /dev/null +++ b/hack/e2e-apps/qdrant.bats @@ -0,0 +1,25 @@ +#!/usr/bin/env bats + +@test "Create Qdrant" { + name='test' + kubectl apply -f- < 1. | `int` | `1` | +| `resources` | Explicit CPU and memory configuration for each Qdrant replica. When omitted, the preset defined in `resourcesPreset` is applied. | `object` | `{}` | +| `resources.cpu` | CPU available to each replica. | `quantity` | `""` | +| `resources.memory` | Memory (RAM) available to each replica. | `quantity` | `""` | +| `resourcesPreset` | Default sizing preset used when `resources` is omitted. | `string` | `small` | +| `size` | Persistent Volume Claim size available for vector data storage. | `quantity` | `10Gi` | +| `storageClass` | StorageClass used to store the data. | `string` | `""` | +| `external` | Enable external access from outside the cluster. | `bool` | `false` | + + +## Parameter examples and reference + +### resources and resourcesPreset + +`resources` sets explicit CPU and memory configurations for each replica. +When left empty, the preset defined in `resourcesPreset` is applied. + +```yaml +resources: + cpu: 4000m + memory: 4Gi +``` + +`resourcesPreset` sets named CPU and memory configurations for each replica. +This setting is ignored if the corresponding `resources` value is set. + +| Preset name | CPU | memory | +|-------------|--------|---------| +| `nano` | `250m` | `128Mi` | +| `micro` | `500m` | `256Mi` | +| `small` | `1` | `512Mi` | +| `medium` | `1` | `1Gi` | +| `large` | `2` | `2Gi` | +| `xlarge` | `4` | `4Gi` | +| `2xlarge` | `8` | `8Gi` | diff --git a/packages/apps/qdrant/charts/cozy-lib b/packages/apps/qdrant/charts/cozy-lib new file mode 120000 index 00000000..e1813509 --- /dev/null +++ b/packages/apps/qdrant/charts/cozy-lib @@ -0,0 +1 @@ +../../../library/cozy-lib \ No newline at end of file diff --git a/packages/apps/qdrant/logos/qdrant.svg b/packages/apps/qdrant/logos/qdrant.svg new file mode 100644 index 00000000..3c940b5e --- /dev/null +++ b/packages/apps/qdrant/logos/qdrant.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/packages/apps/qdrant/templates/dashboard-resourcemap.yaml b/packages/apps/qdrant/templates/dashboard-resourcemap.yaml new file mode 100644 index 00000000..b0a85bcc --- /dev/null +++ b/packages/apps/qdrant/templates/dashboard-resourcemap.yaml @@ -0,0 +1,51 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ .Release.Name }}-dashboard-resources +rules: +- apiGroups: + - "" + resources: + - services + resourceNames: + - {{ .Release.Name }} + - {{ .Release.Name }}-headless + verbs: ["get", "list", "watch"] +- apiGroups: + - "" + resources: + - secrets + resourceNames: + - {{ .Release.Name }}-apikey + verbs: ["get", "list", "watch"] +- apiGroups: + - cozystack.io + resources: + - workloadmonitors + resourceNames: + - {{ .Release.Name }} + verbs: ["get", "list", "watch"] +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ .Release.Name }}-dashboard-resources +subjects: +{{ include "cozy-lib.rbac.subjectsForTenantAndAccessLevel" (list "use" .Release.Namespace) }} +roleRef: + kind: Role + name: {{ .Release.Name }}-dashboard-resources + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: cozystack.io/v1alpha1 +kind: WorkloadMonitor +metadata: + name: {{ .Release.Name }} +spec: + minReplicas: 1 + replicas: {{ .Values.replicas }} + kind: qdrant + type: qdrant + selector: + app.kubernetes.io/instance: {{ .Release.Name }}-system + version: {{ .Chart.Version }} diff --git a/packages/apps/qdrant/templates/qdrant.yaml b/packages/apps/qdrant/templates/qdrant.yaml new file mode 100644 index 00000000..888bd05e --- /dev/null +++ b/packages/apps/qdrant/templates/qdrant.yaml @@ -0,0 +1,43 @@ +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: {{ .Release.Name }}-system + labels: + sharding.fluxcd.io/key: tenants +spec: + chartRef: + kind: ExternalArtifact + name: cozystack-qdrant-application-default-qdrant-system + namespace: cozy-system + interval: 5m + timeout: 10m + install: + remediation: + retries: -1 + upgrade: + force: true + remediation: + retries: -1 + valuesFrom: + - kind: Secret + name: cozystack-values + values: + qdrant: + fullnameOverride: {{ .Release.Name }} + replicaCount: {{ .Values.replicas }} + resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.resourcesPreset .Values.resources $) | nindent 8 }} + apiKey: true + livenessProbe: + enabled: true + persistence: + size: {{ .Values.size }} + {{- with .Values.storageClass }} + storageClassName: {{ . }} + {{- end }} + metrics: + serviceMonitor: + enabled: true + {{- if .Values.external }} + service: + type: LoadBalancer + {{- end }} diff --git a/packages/apps/qdrant/values.schema.json b/packages/apps/qdrant/values.schema.json new file mode 100644 index 00000000..de3331c4 --- /dev/null +++ b/packages/apps/qdrant/values.schema.json @@ -0,0 +1,82 @@ +{ + "title": "Chart Values", + "type": "object", + "properties": { + "external": { + "description": "Enable external access from outside the cluster.", + "type": "boolean", + "default": false + }, + "replicas": { + "description": "Number of Qdrant replicas. Cluster mode is automatically enabled when replicas \u003e 1.", + "type": "integer", + "default": 1 + }, + "resources": { + "description": "Explicit CPU and memory configuration for each Qdrant replica. When omitted, the preset defined in `resourcesPreset` is applied.", + "type": "object", + "default": {}, + "properties": { + "cpu": { + "description": "CPU available to each replica.", + "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 (RAM) available to each replica.", + "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 + } + } + }, + "resourcesPreset": { + "description": "Default sizing preset used when `resources` is omitted.", + "type": "string", + "default": "small", + "enum": [ + "nano", + "micro", + "small", + "medium", + "large", + "xlarge", + "2xlarge" + ] + }, + "size": { + "description": "Persistent Volume Claim size available for vector data storage.", + "default": "10Gi", + "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 + }, + "storageClass": { + "description": "StorageClass used to store the data.", + "type": "string", + "default": "" + } + } +} \ No newline at end of file diff --git a/packages/apps/qdrant/values.yaml b/packages/apps/qdrant/values.yaml new file mode 100644 index 00000000..7abd25ff --- /dev/null +++ b/packages/apps/qdrant/values.yaml @@ -0,0 +1,34 @@ +## +## @section Common parameters +## + +## @typedef {struct} Resources - Explicit CPU and memory configuration for each Qdrant replica. +## @field {quantity} [cpu] - CPU available to each replica. +## @field {quantity} [memory] - Memory (RAM) available to each replica. + +## @enum {string} ResourcesPreset - Default sizing preset. +## @value nano +## @value micro +## @value small +## @value medium +## @value large +## @value xlarge +## @value 2xlarge + +## @param {int} replicas - Number of Qdrant replicas. Cluster mode is automatically enabled when replicas > 1. +replicas: 1 + +## @param {Resources} [resources] - Explicit CPU and memory configuration for each Qdrant replica. When omitted, the preset defined in `resourcesPreset` is applied. +resources: {} + +## @param {ResourcesPreset} resourcesPreset="small" - Default sizing preset used when `resources` is omitted. +resourcesPreset: "small" + +## @param {quantity} size - Persistent Volume Claim size available for vector data storage. +size: 10Gi + +## @param {string} storageClass - StorageClass used to store the data. +storageClass: "" + +## @param {bool} external - Enable external access from outside the cluster. +external: false diff --git a/packages/system/qdrant-rd/Chart.yaml b/packages/system/qdrant-rd/Chart.yaml new file mode 100644 index 00000000..e4d7beb2 --- /dev/null +++ b/packages/system/qdrant-rd/Chart.yaml @@ -0,0 +1,3 @@ +apiVersion: v2 +name: qdrant-rd +version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process diff --git a/packages/system/qdrant-rd/Makefile b/packages/system/qdrant-rd/Makefile new file mode 100644 index 00000000..0b22a9ca --- /dev/null +++ b/packages/system/qdrant-rd/Makefile @@ -0,0 +1,4 @@ +export NAME=qdrant-rd +export NAMESPACE=cozy-system + +include ../../../hack/package.mk diff --git a/packages/system/qdrant-rd/cozyrds/qdrant.yaml b/packages/system/qdrant-rd/cozyrds/qdrant.yaml new file mode 100644 index 00000000..c89692c0 --- /dev/null +++ b/packages/system/qdrant-rd/cozyrds/qdrant.yaml @@ -0,0 +1,41 @@ +apiVersion: cozystack.io/v1alpha1 +kind: ApplicationDefinition +metadata: + name: qdrant +spec: + application: + kind: Qdrant + plural: qdrants + singular: qdrant + openAPISchema: |- + {"title":"Chart Values","type":"object","properties":{"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"replicas":{"description":"Number of Qdrant replicas. Cluster mode is automatically enabled when replicas > 1.","type":"integer","default":1},"resources":{"description":"Explicit CPU and memory configuration for each Qdrant replica. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available to each replica.","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 (RAM) available to each replica.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"small","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]},"size":{"description":"Persistent Volume Claim size available for vector data storage.","default":"10Gi","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},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":""}}} + release: + prefix: qdrant- + labels: + sharding.fluxcd.io/key: tenants + chartRef: + kind: ExternalArtifact + name: cozystack-qdrant-application-default-qdrant + namespace: cozy-system + dashboard: + description: Managed Qdrant vector database service + icon: PHN2ZyB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgdmlld0JveD0iMCAwIDE0NCAxNDQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNDQiIGhlaWdodD0iMTQ0IiByeD0iMjQiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcikiLz4KPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjIsIDIyKSBzY2FsZSgwLjU4KSI+CiAgPHBvbHlnb24gZmlsbD0iI2RjMjQ0YyIgcG9pbnRzPSI4Ni42IDAgMCA1MCAwIDE1MCA4Ni42IDIwMCAxMTkuMDggMTgxLjI1IDExOS4wOCAxNDMuNzUgODYuNiAxNjIuNSAzMi40OCAxMzEuMjUgMzIuNDggNjguNzUgODYuNiAzNy41IDE0MC43MyA2OC43NSAxNDAuNzMgMTkzLjc1IDE3My4yMSAxNzUgMTczLjIxIDUwIDg2LjYgMCIvPgogIDxwb2x5Z29uIGZpbGw9IiNkYzI0NGMiIHBvaW50cz0iNTQuMTMgODEuMjUgNTQuMTMgMTE4Ljc1IDg2LjYgMTM3LjUgMTE5LjA4IDExOC43NSAxMTkuMDggODEuMjUgODYuNiA2Mi41IDU0LjEzIDgxLjI1Ii8+CiAgPHBvbHlnb24gZmlsbD0iIzllMGQzOCIgcG9pbnRzPSIxMTkuMDggMTQzLjc1IDExOS4wOCAxODEuMjUgODYuNiAyMDAgODYuNiAxNjIuNSAxMTkuMDggMTQzLjc1Ii8+CiAgPHBvbHlnb24gZmlsbD0iIzllMGQzOCIgcG9pbnRzPSIxNzMuMjEgNTAgMTczLjIxIDE3NSAxNDAuNzMgMTkzLjc1IDE0MC43MyA2OC43NSAxNzMuMjEgNTAiLz4KICA8cG9seWdvbiBmaWxsPSIjZmY1MTZiIiBwb2ludHM9IjE3My4yMSA1MCAxNDAuNzMgNjguNzUgODYuNiAzNy41IDMyLjQ4IDY4Ljc1IDAgNTAgODYuNiAwIDE3My4yMSA1MCIvPgogIDxwb2x5Z29uIGZpbGw9IiNkYzI0NGMiIHBvaW50cz0iODYuNiAxNjIuNSA4Ni42IDIwMCAwIDE1MCAwIDUwIDMyLjQ4IDY4Ljc1IDMyLjQ4IDEzMS4yNSA4Ni42IDE2Mi41Ii8+CiAgPHBvbHlnb24gZmlsbD0iI2ZmNTE2YiIgcG9pbnRzPSIxMTkuMDggODEuMjUgODYuNiAxMDAgNTQuMTMgODEuMjUgODYuNiA2Mi41IDExOS4wOCA4MS4yNSIvPgogIDxwb2x5Z29uIGZpbGw9IiNkYzI0NGMiIHBvaW50cz0iODYuNiAxMDAgODYuNiAxMzcuNSA1NC4xMyAxMTguNzUgNTQuMTMgODEuMjUgODYuNiAxMDAiLz4KICA8cG9seWdvbiBmaWxsPSIjOWUwZDM4IiBwb2ludHM9IjExOS4wOCA4MS4yNSAxMTkuMDggMTE4Ljc1IDg2LjYgMTM3LjUgODYuNiAxMDAgMTE5LjA4IDgxLjI1Ii8+CjwvZz4KPGRlZnM+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQwX2xpbmVhciIgeDE9IjE4OSIgeTE9IjIxMC41IiB4Mj0iMCIgeTI9IjAiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iIzVjMDUyMCIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNmZmQwZDgiLz4KPC9saW5lYXJHcmFkaWVudD4KPC9kZWZzPgo8L3N2Zz4K + category: PaaS + singular: Qdrant + plural: Qdrant + tags: + - vector-database + - ai + - ml + keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "replicas"], ["spec", "resources"], ["spec", "resourcesPreset"], ["spec", "size"], ["spec", "storageClass"], ["spec", "external"]] + secrets: + exclude: [] + include: + - resourceNames: + - qdrant-{{ .name }}-apikey + services: + exclude: [] + include: + - resourceNames: + - qdrant-{{ .name }} + - qdrant-{{ .name }}-headless diff --git a/packages/system/qdrant-rd/templates/cozyrd.yaml b/packages/system/qdrant-rd/templates/cozyrd.yaml new file mode 100644 index 00000000..e079ddcf --- /dev/null +++ b/packages/system/qdrant-rd/templates/cozyrd.yaml @@ -0,0 +1,4 @@ +{{- range $path, $_ := .Files.Glob "cozyrds/*" }} +--- +{{ $.Files.Get $path }} +{{- end }} diff --git a/packages/system/qdrant-rd/values.yaml b/packages/system/qdrant-rd/values.yaml new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/packages/system/qdrant-rd/values.yaml @@ -0,0 +1 @@ +{} diff --git a/packages/system/qdrant/Chart.yaml b/packages/system/qdrant/Chart.yaml new file mode 100644 index 00000000..ef927c65 --- /dev/null +++ b/packages/system/qdrant/Chart.yaml @@ -0,0 +1,3 @@ +apiVersion: v2 +name: cozy-qdrant +version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process diff --git a/packages/system/qdrant/Makefile b/packages/system/qdrant/Makefile new file mode 100644 index 00000000..7e6fbe93 --- /dev/null +++ b/packages/system/qdrant/Makefile @@ -0,0 +1,7 @@ +include ../../../hack/package.mk + +update: + rm -rf charts + helm repo add qdrant https://qdrant.github.io/qdrant-helm + helm repo update qdrant + helm pull qdrant/qdrant --untar --untardir charts diff --git a/packages/system/qdrant/charts/qdrant/.helmignore b/packages/system/qdrant/charts/qdrant/.helmignore new file mode 100644 index 00000000..f4b11987 --- /dev/null +++ b/packages/system/qdrant/charts/qdrant/.helmignore @@ -0,0 +1,2 @@ +.git +.github diff --git a/packages/system/qdrant/charts/qdrant/CHANGELOG.md b/packages/system/qdrant/charts/qdrant/CHANGELOG.md new file mode 100644 index 00000000..7d985e71 --- /dev/null +++ b/packages/system/qdrant/charts/qdrant/CHANGELOG.md @@ -0,0 +1,8 @@ +# Changelog + +## [qdrant-1.16.3](https://github.com/qdrant/qdrant-helm/tree/qdrant-1.16.3) (2025-12-19) + +- Update Qdrant to v1.16.3 +- Add support for global additional labels [#424](https://github.com/qdrant/qdrant-helm/pull/424) +- Add support for setting persistent volume claim labels [#418](https://github.com/qdrant/qdrant-helm/pull/418) +- Add the ability to set minReadySecond [#417](https://github.com/qdrant/qdrant-helm/pull/417) diff --git a/packages/system/qdrant/charts/qdrant/Chart.yaml b/packages/system/qdrant/charts/qdrant/Chart.yaml new file mode 100644 index 00000000..03227eff --- /dev/null +++ b/packages/system/qdrant/charts/qdrant/Chart.yaml @@ -0,0 +1,37 @@ +annotations: + artifacthub.io/category: database + artifacthub.io/changes: | + - kind: added + description: Update Qdrant to v1.16.3 + - kind: added + description: Add support for global additional labels + links: + - name: GitHub Pull Request + url: https://github.com/qdrant/qdrant-helm/pull/424 + - kind: added + description: Add support for setting persistent volume claim labels + links: + - name: GitHub Pull Request + url: https://github.com/qdrant/qdrant-helm/pull/418 + - kind: fixed + description: Add the ability to set minReadySecond + links: + - name: GitHub Pull Request + url: https://github.com/qdrant/qdrant-helm/pull/417 +apiVersion: v2 +appVersion: v1.16.3 +description: Qdrant - Vector Database for the next generation of AI applications. +home: https://qdrant.tech +icon: https://qdrant.github.io/qdrant-helm/logo_with_text.svg +keywords: +- vector database +maintainers: +- email: info@qdrant.com + name: qdrant + url: https://github.com/qdrant +name: qdrant +sources: +- https://github.com/qdrant/qdrant +- https://github.com/qdrant/qdrant-helm +type: application +version: 1.16.3 diff --git a/packages/system/qdrant/charts/qdrant/LICENSE b/packages/system/qdrant/charts/qdrant/LICENSE new file mode 100644 index 00000000..f49a4e16 --- /dev/null +++ b/packages/system/qdrant/charts/qdrant/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. \ No newline at end of file diff --git a/packages/system/qdrant/charts/qdrant/README.md b/packages/system/qdrant/charts/qdrant/README.md new file mode 100644 index 00000000..512eda17 --- /dev/null +++ b/packages/system/qdrant/charts/qdrant/README.md @@ -0,0 +1,157 @@ +# Qdrant helm chart + +[Qdrant documentation](https://qdrant.tech/documentation/) + +## TLDR + +```bash +helm repo add qdrant https://qdrant.github.io/qdrant-helm +helm repo update +helm upgrade -i your-qdrant-installation-name qdrant/qdrant +``` + +## Description + +This chart installs and bootstraps a Qdrant instance. + +## Prerequisites + +- Kubernetes v1.24+ (as you need grpc probe) +- Helm +- PV provisioner (by the infrastructure) + +## Installation & Setup + +You can install the chart from source via: + +```bash +helm upgrade -i your-qdrant-installation-name charts/qdrant +``` + +Uninstall via: + +```bash +helm uninstall your-qdrant-installation-name +``` + +Delete the volume with + +```bash +kubectl delete pvc -l app.kubernetes.io/instance=your-qdrant-installation-name +``` + +## Configuration + +For documentation of the settings please refer to [Qdrant Configuration File](https://github.com/qdrant/qdrant/blob/master/config/config.yaml) +All of these configuration options could be overwritten under config in `values.yaml`. +A modification example is provided there. + +### Overrides + +You can override any value in the Qdrant configuration by setting the Helm values under the key `config`. Those settings get included verbatim in a file called `config/production.yml` which is explained further here [Qdrant Order and Priority](https://qdrant.tech/documentation/guides/configuration/#order-and-priority) as well as an [example](https://github.com/qdrant/qdrant-helm/blob/b0bb6fc6d3eb9c0813c79bb5a78dc21aebc2b81d/charts/qdrant/values.yaml#L140). + +### Distributed setup + +Running a distributed cluster just needs a few changes in your `values.yaml` file. +Increase the number of replicas to the desired number of nodes and set `config.cluster.enabled` to true. + +Depending on your environment or cloud provider you might need to change the service in the `values.yaml` as well. +For example on AWS EKS you would need to change the `cluster.type` to `NodePort`. + +## Updating StatefulSets + +This Helm chart uses a Kubernetes [StatefulSet](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/) to manage your Qdrant cluster. StatefulSets have many fields that are immutable, meaning that you cannot change these fields without deleting and recreating the StatefulSet. If you try to change these fields, you will get an error like this: + +``` +Error: UPGRADE FAILED: cannot patch "qdrant" with kind StatefulSet: StatefulSet.apps "qdrant" is invalid: spec: Forbidden: updates to statefulset spec for fields other than 'replicas', 'ordinals', 'template', 'updateStrategy', 'persistentVolumeClaimRetentionPolicy' and 'minReadySeconds' are forbidden +``` + +If you need to change any immutable field, the process is described below, using the most common example of expanding a PVC volume. + +1. Delete the StatefulSet while leaving the Pods running: + + ```bash + kubectl delete statefulset --cascade=orphan qdrant + ``` + +2. Manually edit all PersistentVolumeClaims to increase their sizes: + + ```bash + # For each PersistentVolumeClaim: + kubectl edit pvc qdrant-storage-qdrant-0 + ``` + +3. Update your Helm values to match the new PVC size. +4. Reinstall the Helm chart using your updated values: + + ```bash + helm upgrade --install qdrant qdrant/qdrant -f my-values.yaml + ``` + +Some storage providers allow resizing volumes in-place, but most require a pod restart before the new size will take effect: + +```bash +kubectl rollout restart statefulset qdrant +``` + +### Immutable Pod fields + +In addition to immutable fields on StatefulSets, Pods also have some fields which are immutable, which means the above method may not work for some changes, such as setting `snapshotPersistence.enabled: true`. In that case, after following the above method, you'll see an error like this when you `kubectl describe` your StatefulSet: + +``` +pod updates may not change fields other than `spec.containers[*].image`, +`spec.initContainers[*].image`,`spec.activeDeadlineSeconds`, +`spec.tolerations` (only additions to existing tolerations), +`spec.terminationGracePeriodSeconds` (allow it to be set to 1 if it was previously negative) +``` + +To fix this, you must manually delete all of your Qdrant pods, starting with node-0. This will cause your cluster to go down, but will allow the StatefulSet to recreate your Pods with the correct configuration. + +## Restoring from Snapshots + +This helm chart allows you to restore a snapshot into your Qdrant cluster either from an internal or external PersistentVolumeClaim. + +### Restoring from the built-in PVC + +If you have set `snapshotPersistence.enabled: true` (recommended for production), this helm chart will create a separate PersistentVolume for snapshots, and any snapshots you create will be stored in that PersistentVolume. + +To restore from one of these snapshots, set the following values: + +```yaml +snapshotRestoration: + enabled: true + # Set blank to indicate we are not using an external PVC + pvcName: "" + snapshots: + - /qdrant/snapshots///: +``` + +And run "helm upgrade". This will restart your cluster and restore the specified collection from the snapshot. Qdrant will refuse to overwrite an existing collection, so ensure the collection is deleted before restoring. + +After the snapshot is restored, remove the above values and run "helm upgrade" again to trigger another rolling restart. Otherwise, the snapshot restore will be attempted again if your cluster ever restarts. + +### Restoring from an external PVC + +If you wish to restore from an externally-created snapshot, using the API is recommended: https://qdrant.github.io/qdrant/redoc/index.html#tag/collections/operation/recover_from_uploaded_snapshot + +If the file is too large, you can separately create a PersistentVolumeClaim, store your data in there, and refer to this separate PersistentVolumeClaim in this helm chart. + +Once you have created this PersistentVolumeClaim (must be in the same namespace as your Qdrant cluster), set the following values: + +```yml +snapshotRestoration: + enabled: true + pvcName: "" + snapshots: + - /qdrant/snapshots///: +``` + +And run "helm upgrade". This will restart your cluster and restore the specified collection from the snapshot. Qdrant will refuse to overwrite an existing collection, so ensure the collection is deleted before restoring. + +After the snapshot is restored, remove the above values and run "helm upgrade" again to trigger another rolling restart. Otherwise, the snapshot restore will be attempted again if your cluster ever restarts. + +## Metrics endpoints + +Metrics are available through rest api (default port set to 6333) at `/metrics` + +Refer to [qdrant metrics configuration](https://qdrant.tech/documentation/telemetry/#metrics) for more information. diff --git a/packages/system/qdrant/charts/qdrant/templates/NOTES.txt b/packages/system/qdrant/charts/qdrant/templates/NOTES.txt new file mode 100644 index 00000000..450736c3 --- /dev/null +++ b/packages/system/qdrant/charts/qdrant/templates/NOTES.txt @@ -0,0 +1,24 @@ +Qdrant {{ .Chart.AppVersion }} has been deployed successfully. + +The full Qdrant documentation is available at https://qdrant.tech/documentation/. + +To forward Qdrant's ports execute one of the following commands: + export POD_NAME=$(kubectl get pods --namespace {{ $.Release.Namespace }} -l "app.kubernetes.io/name={{ include "qdrant.name" . }},app.kubernetes.io/instance={{ $.Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + +{{- if contains "ClusterIP" .Values.service.type }} + {{- range .Values.service.ports }} + +If you want to use Qdrant via {{ .name }} execute the following commands + kubectl --namespace {{ $.Release.Namespace }} port-forward $POD_NAME {{ .targetPort }}:{{ .targetPort }} + {{- end }} +{{- end }} + +{{- if .Values.ingress.enabled }} + +If you want to access Qdrant through the ingress controller +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- end }} diff --git a/packages/system/qdrant/charts/qdrant/templates/_helpers.tpl b/packages/system/qdrant/charts/qdrant/templates/_helpers.tpl new file mode 100644 index 00000000..f45ce29c --- /dev/null +++ b/packages/system/qdrant/charts/qdrant/templates/_helpers.tpl @@ -0,0 +1,145 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "qdrant.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "qdrant.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "qdrant.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "qdrant.labels" -}} +helm.sh/chart: {{ include "qdrant.chart" . }} +{{ include "qdrant.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "qdrant.selectorLabels" -}} +app: {{ include "qdrant.name" . }} +app.kubernetes.io/name: {{ include "qdrant.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Additional (global) labels +*/}} +{{- define "qdrant.additionalLabels" -}} +{{- with .Values.additionalLabels }} +{{- range $key, $value := . }} +{{ $key }}: {{ $value | quote }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "qdrant.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "qdrant.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Create secret +*/}} +{{- define "qdrant.secret" -}} +{{- $readOnlyApiKey := false }} +{{- $apiKey := false }} +{{- if kindIs "map" .Values.apiKey -}} +{{- if .Values.apiKey.valueFrom -}} +{{- /* Retrieve the value from the secret as specified in valueFrom */ -}} +{{- $secretName := .Values.apiKey.valueFrom.secretKeyRef.name -}} +{{- $secretKey := .Values.apiKey.valueFrom.secretKeyRef.key -}} +{{- $secretObj := (lookup "v1" "Secret" .Release.Namespace $secretName) | default dict -}} +{{- $secretData := (get $secretObj "data") | default dict -}} +{{- $apiKey = (get $secretData $secretKey | b64dec) -}} +{{- end -}} +{{- else if .Values.apiKey | toJson | eq "true" -}} +{{- /* Retrieve existing randomly generated api key or create a new one */ -}} +{{- $secretObj := (lookup "v1" "Secret" .Release.Namespace (printf "%s-apikey" (include "qdrant.fullname" . ))) | default dict -}} +{{- $secretData := (get $secretObj "data") | default dict -}} +{{- $apiKey = (get $secretData "api-key" | b64dec) | default (randAlphaNum 32) -}} +{{- else if .Values.apiKey -}} +{{- $apiKey = .Values.apiKey -}} +{{- end -}} +{{- if kindIs "map" .Values.readOnlyApiKey -}} +{{- if .Values.readOnlyApiKey.valueFrom -}} +{{- /* Retrieve the value from the secret as specified in valueFrom */ -}} +{{- $secretName := .Values.readOnlyApiKey.valueFrom.secretKeyRef.name -}} +{{- $secretKey := .Values.readOnlyApiKey.valueFrom.secretKeyRef.key -}} +{{- $secretObj := (lookup "v1" "Secret" .Release.Namespace $secretName) | default dict -}} +{{- $secretData := (get $secretObj "data") | default dict -}} +{{- $readOnlyApiKey = (get $secretData $secretKey | b64dec) -}} +{{- end -}} +{{- else if eq (.Values.readOnlyApiKey | toJson) "true" -}} +{{- /* retrieve existing randomly generated api key or create new one */ -}} +{{- $secretObj := (lookup "v1" "Secret" .Release.Namespace (printf "%s-apikey" (include "qdrant.fullname" . ))) | default dict -}} +{{- $secretData := (get $secretObj "data") | default dict -}} +{{- $readOnlyApiKey = (get $secretData "read-only-api-key" | b64dec) | default (randAlphaNum 32) -}} +{{- else if .Values.readOnlyApiKey -}} +{{- $readOnlyApiKey = .Values.readOnlyApiKey -}} +{{- end -}} +{{- if and $apiKey $readOnlyApiKey -}} +api-key: {{ $apiKey | b64enc }} +read-only-api-key: {{ $readOnlyApiKey | b64enc }} +local.yaml: {{ printf "service:\n api_key: %s\n read_only_api_key: %s" $apiKey $readOnlyApiKey | b64enc }} +{{- else if $apiKey -}} +api-key: {{ $apiKey | b64enc }} +local.yaml: {{ printf "service:\n api_key: %s" $apiKey | b64enc }} +{{- else if $readOnlyApiKey -}} +read-only-api-key: {{ $readOnlyApiKey | b64enc }} +local.yaml: {{ printf "service:\n read_only_api_key: %s" $readOnlyApiKey | b64enc }} +{{- end -}} +{{- end -}} + +{{/* +Protocol to use for inter cluster communication +*/}} +{{- define "qdrant.p2p.protocol" -}} +{{ if eq (.Values.config.cluster.p2p.enable_tls | toJson) "true" -}} +https +{{- else -}} +http +{{- end -}} +{{- end -}} + +{{/* +Port to use for inter cluster communication +*/}} +{{- define "qdrant.p2p.port" -}} +{{- default 6335 .Values.config.cluster.p2p.port -}} +{{- end -}} diff --git a/packages/system/qdrant/charts/qdrant/templates/configmap.yaml b/packages/system/qdrant/charts/qdrant/templates/configmap.yaml new file mode 100644 index 00000000..ea061b07 --- /dev/null +++ b/packages/system/qdrant/charts/qdrant/templates/configmap.yaml @@ -0,0 +1,33 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "qdrant.fullname" . }} + labels: + {{- include "qdrant.labels" . | nindent 4 }} + {{- include "qdrant.additionalLabels" . | nindent 4 }} +{{- with .Values.additionalAnnotations }} + annotations: + {{- toYaml . | nindent 4 }} +{{- end }} +data: + initialize.sh: | + #!/bin/sh + echo "Soft limits" + ulimit -a -S + echo "Hard limits" + ulimit -a -H + ulimit -n $(ulimit -Hn) + SET_INDEX=${HOSTNAME##*-} + {{- if and (.Values.snapshotRestoration.enabled) (eq (.Values.replicaCount | quote) (1 | quote)) }} + echo "Starting initializing for pod $SET_INDEX and snapshots restoration" + exec ./entrypoint.sh --uri '{{ include "qdrant.p2p.protocol" . }}://{{ include "qdrant.fullname" . }}-0.{{ include "qdrant.fullname" . }}-headless:{{ include "qdrant.p2p.port" . }}' {{ range .Values.snapshotRestoration.snapshots }} --snapshot {{ . }} {{ end }} + {{- else }} + echo "Starting initializing for pod $SET_INDEX" + if [ "$SET_INDEX" = "0" ]; then + exec ./entrypoint.sh --uri '{{ include "qdrant.p2p.protocol" . }}://{{ include "qdrant.fullname" . }}-0.{{ include "qdrant.fullname" . }}-headless:{{ include "qdrant.p2p.port" . }}' + else + exec ./entrypoint.sh --bootstrap '{{ include "qdrant.p2p.protocol" . }}://{{ include "qdrant.fullname" . }}-0.{{ include "qdrant.fullname" . }}-headless:{{ include "qdrant.p2p.port" . }}' --uri '{{ include "qdrant.p2p.protocol" . }}://{{ include "qdrant.fullname" . }}-'"$SET_INDEX"'.{{ include "qdrant.fullname" . }}-headless:{{ include "qdrant.p2p.port" . }}' + fi + {{ end }} + production.yaml: | + {{- tpl (toYaml .Values.config) . | nindent 4 }} diff --git a/packages/system/qdrant/charts/qdrant/templates/ingress.yaml b/packages/system/qdrant/charts/qdrant/templates/ingress.yaml new file mode 100644 index 00000000..6ab27985 --- /dev/null +++ b/packages/system/qdrant/charts/qdrant/templates/ingress.yaml @@ -0,0 +1,53 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "qdrant.fullname" . }} + labels: + {{- include "qdrant.labels" . | nindent 4 }} + {{- include "qdrant.additionalLabels" . | nindent 4 }} + {{- with .Values.ingress.additionalLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} +{{- if or .Values.ingress.annotations .Values.additionalAnnotations }} + annotations: +{{- with .Values.additionalAnnotations }} + {{- toYaml . | nindent 4 }} +{{- end }} +{{- with .Values.ingress.annotations }} + {{- toYaml . | nindent 4 }} +{{- end }} +{{- end }} +spec: + {{- if .Values.ingress.ingressClassName }} + ingressClassName: {{ .Values.ingress.ingressClassName }} + {{- end }} + {{- with .Values.ingress.hosts }} + rules: + {{- range . }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path | quote }} + pathType: {{ .pathType | default "Prefix" | quote }} + backend: + service: + name: {{ default .serviceName (include "qdrant.fullname" $) }} + port: + number: {{ .servicePort }} + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls}} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName | quote }} + {{- end }} + {{- end }} +{{- end }} + diff --git a/packages/system/qdrant/charts/qdrant/templates/pdb.yaml b/packages/system/qdrant/charts/qdrant/templates/pdb.yaml new file mode 100644 index 00000000..9904f185 --- /dev/null +++ b/packages/system/qdrant/charts/qdrant/templates/pdb.yaml @@ -0,0 +1,26 @@ +{{- if .Values.podDisruptionBudget.enabled }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "qdrant.fullname" . }} + labels: + {{- include "qdrant.labels" . | nindent 4 }} + {{- include "qdrant.additionalLabels" . | nindent 4 }} +{{- with .Values.additionalAnnotations }} + annotations: + {{- toYaml . | nindent 4 }} +{{- end }} +spec: + {{- with .Values.podDisruptionBudget.maxUnavailable}} + maxUnavailable: {{ . }} + {{- end }} + {{- with .Values.podDisruptionBudget.minAvailable }} + minAvailable: {{ . }} + {{- end }} + {{- with .Values.podDisruptionBudget.unhealthyPodEvictionPolicy }} + unhealthyPodEvictionPolicy: {{ . }} + {{- end }} + selector: + matchLabels: + {{- include "qdrant.selectorLabels" . | nindent 6 }} +{{- end }} diff --git a/packages/system/qdrant/charts/qdrant/templates/secret.yaml b/packages/system/qdrant/charts/qdrant/templates/secret.yaml new file mode 100644 index 00000000..d95db526 --- /dev/null +++ b/packages/system/qdrant/charts/qdrant/templates/secret.yaml @@ -0,0 +1,15 @@ +{{- if or .Values.apiKey .Values.readOnlyApiKey }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "qdrant.fullname" . }}-apikey + labels: + {{- include "qdrant.labels" . | nindent 4 }} + {{- include "qdrant.additionalLabels" . | nindent 4 }} +{{- with .Values.additionalAnnotations }} + annotations: + {{- toYaml . | nindent 4 }} +{{- end }} +data: +{{ include "qdrant.secret" . | indent 2}} +{{- end }} diff --git a/packages/system/qdrant/charts/qdrant/templates/service-headless.yaml b/packages/system/qdrant/charts/qdrant/templates/service-headless.yaml new file mode 100644 index 00000000..642b1c23 --- /dev/null +++ b/packages/system/qdrant/charts/qdrant/templates/service-headless.yaml @@ -0,0 +1,35 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "qdrant.fullname" . }}-headless + labels: + {{- include "qdrant.labels" . | nindent 4 }} + {{- include "qdrant.additionalLabels" . | nindent 4 }} + app.kubernetes.io/component: cluster-discovery +{{- with .Values.service.additionalLabels }} +{{- toYaml . | nindent 4 }} +{{- end }} +{{- if or .Values.service.annotations .Values.additionalAnnotations }} + annotations: +{{- with .Values.additionalAnnotations }} + {{- toYaml . | nindent 4 }} +{{- end }} +{{- with .Values.service.annotations }} + {{- toYaml . | nindent 4 }} +{{- end }} +{{- end }} +spec: + clusterIP: None + publishNotReadyAddresses: true + ports: + {{- range .Values.service.ports }} + - name: {{ .name }} + port: {{ .port }} + targetPort: {{ .targetPort }} + protocol: {{ .protocol | default "TCP" }} + {{- if .appProtocol }} + appProtocol: {{ .appProtocol }} + {{- end }} + {{- end }} + selector: + {{- include "qdrant.selectorLabels" . | nindent 4 }} diff --git a/packages/system/qdrant/charts/qdrant/templates/service.yaml b/packages/system/qdrant/charts/qdrant/templates/service.yaml new file mode 100644 index 00000000..b16e065b --- /dev/null +++ b/packages/system/qdrant/charts/qdrant/templates/service.yaml @@ -0,0 +1,39 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "qdrant.fullname" . }} + labels: + {{- include "qdrant.labels" . | nindent 4 }} + {{- include "qdrant.additionalLabels" . | nindent 4 }} +{{- with .Values.service.additionalLabels }} +{{- toYaml . | nindent 4 }} +{{- end }} +{{- if or .Values.service.annotations .Values.additionalAnnotations }} + annotations: +{{- with .Values.additionalAnnotations }} + {{- toYaml . | nindent 4 }} +{{- end }} +{{- with .Values.service.annotations }} + {{- toYaml . | nindent 4 }} +{{- end }} +{{- end }} +spec: + type: {{ .Values.service.type | default "ClusterIP" }} + {{- if .Values.service.loadBalancerIP }} + loadBalancerIP: {{ .Values.service.loadBalancerIP }} + {{- end }} + ports: + {{- range .Values.service.ports }} + - name: {{ .name }} + port: {{ .port }} + targetPort: {{ .targetPort }} + {{- if and (or (eq $.Values.service.type "NodePort") (eq $.Values.service.type "LoadBalancer")) (not (empty .nodePort)) }} + nodePort: {{ .nodePort }} + {{- end }} + protocol: {{ .protocol | default "TCP" }} + {{- if .appProtocol }} + appProtocol: {{ .appProtocol }} + {{- end }} + {{- end }} + selector: + {{- include "qdrant.selectorLabels" . | nindent 4 }} diff --git a/packages/system/qdrant/charts/qdrant/templates/serviceaccount.yaml b/packages/system/qdrant/charts/qdrant/templates/serviceaccount.yaml new file mode 100644 index 00000000..b3b5ddf2 --- /dev/null +++ b/packages/system/qdrant/charts/qdrant/templates/serviceaccount.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "qdrant.fullname" . }} +{{- if or .Values.serviceAccount.annotations .Values.additionalAnnotations }} + annotations: +{{- with .Values.additionalAnnotations }} + {{- toYaml . | nindent 4 }} +{{- end }} +{{- with .Values.serviceAccount.annotations }} + {{- toYaml . | nindent 4 }} +{{- end }} +{{- end }} + labels: + {{- include "qdrant.labels" . | nindent 4 }} + {{- include "qdrant.additionalLabels" . | nindent 4 }} \ No newline at end of file diff --git a/packages/system/qdrant/charts/qdrant/templates/servicemonitor.yaml b/packages/system/qdrant/charts/qdrant/templates/servicemonitor.yaml new file mode 100644 index 00000000..45af6b09 --- /dev/null +++ b/packages/system/qdrant/charts/qdrant/templates/servicemonitor.yaml @@ -0,0 +1,52 @@ +{{- if .Values.metrics.serviceMonitor.enabled }} +kind: ServiceMonitor +apiVersion: monitoring.coreos.com/v1 +metadata: + labels: + {{- include "qdrant.labels" . | nindent 4 }} + {{- include "qdrant.additionalLabels" . | nindent 4 }} +{{- with .Values.metrics.serviceMonitor.additionalLabels }} +{{- toYaml . | nindent 4 }} +{{- end }} + name: {{ include "qdrant.fullname" . }} +{{- with .Values.additionalAnnotations }} + annotations: + {{- toYaml . | nindent 4 }} +{{- end }} +spec: + endpoints: + - honorLabels: true + interval: {{ .Values.metrics.serviceMonitor.scrapeInterval }} + path: {{ .Values.metrics.serviceMonitor.targetPath }} + port: {{ .Values.metrics.serviceMonitor.targetPort }} + scheme: http + scrapeTimeout: {{ .Values.metrics.serviceMonitor.scrapeTimeout }} +{{- if .Values.metrics.serviceMonitor.metricRelabelings }} + metricRelabelings: +{{ tpl (toYaml .Values.metrics.serviceMonitor.metricRelabelings | indent 8) . }} +{{- end }} +{{- if .Values.metrics.serviceMonitor.relabelings }} + relabelings: +{{ tpl (toYaml .Values.metrics.serviceMonitor.relabelings | indent 8) . }} +{{- end }} +{{- if .Values.metrics.serviceMonitor.authorization }} + authorization: +{{ tpl (toYaml .Values.metrics.serviceMonitor.authorization | indent 8) . }} +{{- else if .Values.readOnlyApiKey }} + authorization: + type: Bearer + credentials: + name: {{ include "qdrant.fullname" . }}-apikey + key: read-only-api-key +{{- else if .Values.apiKey }} + authorization: + type: Bearer + credentials: + name: {{ include "qdrant.fullname" . }}-apikey + key: api-key +{{- end }} + selector: + matchLabels: + {{- include "qdrant.labels" . | nindent 6 }} + app.kubernetes.io/component: cluster-discovery +{{- end }} diff --git a/packages/system/qdrant/charts/qdrant/templates/statefulset.yaml b/packages/system/qdrant/charts/qdrant/templates/statefulset.yaml new file mode 100644 index 00000000..dac36df8 --- /dev/null +++ b/packages/system/qdrant/charts/qdrant/templates/statefulset.yaml @@ -0,0 +1,312 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "qdrant.fullname" . }} + labels: + {{- include "qdrant.labels" . | nindent 4 }} + {{- include "qdrant.additionalLabels" . | nindent 4 }} +{{- with .Values.additionalAnnotations }} + annotations: + {{- toYaml . | nindent 4 }} +{{- end }} +spec: + replicas: {{ .Values.replicaCount }} + podManagementPolicy: {{ .Values.podManagementPolicy }} + {{- if .Values.minReadySeconds }} + minReadySeconds: {{ .Values.minReadySeconds }} + {{- end }} + updateStrategy: + type: RollingUpdate + rollingUpdate: + partition: 0 + selector: + matchLabels: + {{- include "qdrant.selectorLabels" . | nindent 6 }} + serviceName: {{ include "qdrant.fullname" . }}-headless + template: + metadata: + annotations: + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + {{- if or .Values.apiKey .Values.readOnlyApiKey }} + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + {{- end }} + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "qdrant.selectorLabels" . | nindent 8 }} + {{- include "qdrant.additionalLabels" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.priorityClassName }} + priorityClassName: "{{ .Values.priorityClassName }}" + {{- end }} + {{- if .Values.shareProcessNamespace }} + shareProcessNamespace: {{ .Values.shareProcessNamespace }} + {{- end }} + initContainers: + {{- if and .Values.updateVolumeFsOwnership (not .Values.image.useUnprivilegedImage) }} + {{- if and .Values.containerSecurityContext .Values.containerSecurityContext.runAsUser }} + - name: ensure-dir-ownership + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + command: + - chown + - -R + - {{ int64 .Values.containerSecurityContext.runAsUser }}:{{ int64 .Values.podSecurityContext.fsGroup }} + - /qdrant/storage + - /qdrant/snapshots + {{- if and .Values.snapshotRestoration.enabled .Values.snapshotRestoration.pvcName }} + - {{ .Values.snapshotRestoration.mountPath }} + {{- end }} + volumeMounts: + - name: {{ .Values.persistence.storageVolumeName | default "qdrant-storage" }} + mountPath: /qdrant/storage + {{- if .Values.persistence.storageSubPath }} + subPath: "{{ .Values.persistence.storageSubPath }}" + {{- end }} + - name: {{ .Values.snapshotPersistence.snapshotsVolumeName | default "qdrant-snapshots" }} + mountPath: /qdrant/snapshots + {{- if .Values.snapshotPersistence.snapshotsSubPath }} + subPath: "{{ .Values.snapshotPersistence.snapshotsSubPath }}" + {{- end }} + {{- if and .Values.snapshotRestoration.enabled .Values.snapshotRestoration.pvcName }} + - name: qdrant-snapshot-restoration + mountPath: {{ .Values.snapshotRestoration.mountPath }} + {{- end }} + {{- end }} + {{- end }} + containers: + {{- if .Values.sidecarContainers -}} + {{- toYaml .Values.sidecarContainers | trim | nindent 8 }} + {{- end}} + - name: {{ .Chart.Name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}{{ .Values.image.useUnprivilegedImage | ternary "-unprivileged" "" }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + env: + - name: QDRANT_INIT_FILE_PATH + value: /qdrant/init/.qdrant-initialized + {{- range .Values.env }} + - name: {{ .name }} + {{- if .valueFrom }} + valueFrom: {{- toYaml .valueFrom | nindent 16 }} + {{- else }} + value: {{ .value | quote }} + {{- end }} + {{- end }} + command: ["/bin/bash", "-c"] + {{- with .Values.args }} + args: + {{- toYaml . | nindent 10 }} + {{- end }} + ports: + {{- range .Values.service.ports }} + - name: {{ .name }} + containerPort: {{ .targetPort }} + protocol: {{ .protocol }} + {{- end }} + + {{- $values := .Values -}} + {{- range .Values.service.ports }} + {{- if and $values.livenessProbe.enabled .checksEnabled }} + livenessProbe: + {{- if eq .name "grpc"}} + grpc: + port: {{ .targetPort }} + {{- end }} + {{- if eq .name "http"}} + httpGet: + path: / + port: {{ .targetPort }} + {{- if and $values.config.service $values.config.service.enable_tls }} + scheme: HTTPS + {{- end }} + {{- end }} + initialDelaySeconds: {{ $values.livenessProbe.initialDelaySeconds }} + timeoutSeconds: {{ $values.livenessProbe.timeoutSeconds }} + periodSeconds: {{ $values.livenessProbe.periodSeconds }} + successThreshold: {{ $values.livenessProbe.successThreshold }} + failureThreshold: {{ $values.livenessProbe.failureThreshold }} + {{- end }} + {{- if and $values.readinessProbe.enabled .checksEnabled }} + readinessProbe: + {{- if eq .name "grpc"}} + grpc: + port: {{ .targetPort }} + {{- end }} + {{- if eq .name "http"}} + httpGet: + path: "{{- if semverCompare ">=1.7.3" ($.Values.image.tag | default $.Chart.AppVersion) -}}/readyz{{else}}/{{end}}" + port: {{ .targetPort }} + {{- if and $values.config.service $values.config.service.enable_tls }} + scheme: HTTPS + {{- end }} + {{- end }} + initialDelaySeconds: {{ $values.readinessProbe.initialDelaySeconds }} + timeoutSeconds: {{ $values.readinessProbe.timeoutSeconds }} + periodSeconds: {{ $values.readinessProbe.periodSeconds }} + successThreshold: {{ $values.readinessProbe.successThreshold }} + failureThreshold: {{ $values.readinessProbe.failureThreshold }} + {{- end }} + {{- if and $values.startupProbe.enabled .checksEnabled }} + startupProbe: + {{- if eq .name "grpc"}} + grpc: + port: {{ .targetPort }} + {{- end }} + {{- if eq .name "http"}} + httpGet: + path: / + port: {{ .targetPort }} + {{- if and $values.config.service $values.config.service.enable_tls }} + scheme: HTTPS + {{- end }} + {{- end }} + initialDelaySeconds: {{ $values.startupProbe.initialDelaySeconds }} + timeoutSeconds: {{ $values.startupProbe.timeoutSeconds }} + periodSeconds: {{ $values.startupProbe.periodSeconds }} + successThreshold: {{ $values.startupProbe.successThreshold }} + failureThreshold: {{ $values.startupProbe.failureThreshold }} + {{- end }} + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.containerSecurityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.lifecycle }} + lifecycle: + {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - name: {{ .Values.persistence.storageVolumeName | default "qdrant-storage" }} + mountPath: /qdrant/storage + {{- if .Values.persistence.storageSubPath }} + subPath: "{{ .Values.persistence.storageSubPath }}" + {{- end }} + - name: qdrant-config + mountPath: /qdrant/config/initialize.sh + subPath: initialize.sh + - name: qdrant-config + mountPath: /qdrant/config/production.yaml + subPath: production.yaml + {{- if or .Values.apiKey .Values.readOnlyApiKey }} + - name: qdrant-secret + mountPath: /qdrant/config/local.yaml + subPath: local.yaml + {{- end }} + {{- if and .Values.snapshotRestoration.enabled .Values.snapshotRestoration.pvcName }} + - name: qdrant-snapshot-restoration + mountPath: {{ .Values.snapshotRestoration.mountPath }} + {{- end }} + - name: {{ .Values.snapshotPersistence.snapshotsVolumeName | default "qdrant-snapshots" }} + mountPath: /qdrant/snapshots + {{- if .Values.snapshotPersistence.snapshotsSubPath }} + subPath: "{{ .Values.snapshotPersistence.snapshotsSubPath }}" + {{- end }} + - name: qdrant-init + mountPath: /qdrant/init + {{- if .Values.additionalVolumeMounts }} +{{- toYaml .Values.additionalVolumeMounts | default "" | nindent 10 }} + {{- end}} + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- tpl (toYaml .) $ | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.topologySpreadConstraints}} + topologySpreadConstraints: + {{- tpl (toYaml .) $ | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "qdrant.fullname" . }} + volumes: + - name: qdrant-config + configMap: + name: {{ include "qdrant.fullname" . }} + defaultMode: 0755 + {{- if and .Values.snapshotRestoration.enabled .Values.snapshotRestoration.pvcName }} + - name: qdrant-snapshot-restoration + persistentVolumeClaim: + claimName: {{ .Values.snapshotRestoration.pvcName }} + {{- end }} + {{- if not .Values.snapshotPersistence.enabled }} + - name: {{ .Values.snapshotPersistence.snapshotsVolumeName | default "qdrant-snapshots" }} + emptyDir: {} + {{- end }} + - name: qdrant-init + emptyDir: {} + {{- if or .Values.apiKey .Values.readOnlyApiKey }} + - name: qdrant-secret + secret: + secretName: {{ include "qdrant.fullname" . }}-apikey + defaultMode: 0600 + {{- end }} + {{- if .Values.additionalVolumes }} +{{- toYaml .Values.additionalVolumes | default "" | nindent 8 }} + {{- end}} + volumeClaimTemplates: + - metadata: + name: {{ .Values.persistence.storageVolumeName | default "qdrant-storage" }} + labels: + app: {{ template "qdrant.name" . }} + {{- with .Values.persistence.additionalLabels }} + {{- toYaml . | nindent 10 }} + {{- end }} + {{- with .Values.persistence.annotations }} + annotations: + {{- toYaml . | nindent 10 }} + {{- end }} + spec: + storageClassName: {{ .Values.persistence.storageClassName }} + {{- if .Values.persistence.volumeAttributesClassName }} + volumeAttributesClassName: {{ .Values.persistence.volumeAttributesClassName }} + {{- end }} + accessModes: + {{- range .Values.persistence.accessModes }} + - {{ . | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.size | quote }} + {{- if .Values.snapshotPersistence.enabled }} + - metadata: + name: {{ .Values.snapshotPersistence.snapshotsVolumeName | default "qdrant-snapshots" }} + labels: + app: {{ template "qdrant.name" . }} + {{- with .Values.snapshotPersistence.additionalLabels }} + {{- toYaml . | nindent 10 }} + {{- end }} + {{- with .Values.snapshotPersistence.annotations }} + annotations: + {{- toYaml . | nindent 10 }} + {{- end }} + spec: + storageClassName: {{ .Values.snapshotPersistence.storageClassName }} + {{- if .Values.snapshotPersistence.volumeAttributesClassName }} + volumeAttributesClassName: {{ .Values.snapshotPersistence.volumeAttributesClassName }} + {{- end }} + accessModes: + {{- range .Values.snapshotPersistence.accessModes }} + - {{ . | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.snapshotPersistence.size | quote }} + {{- end }} diff --git a/packages/system/qdrant/charts/qdrant/templates/tests/test-db-interaction.yaml b/packages/system/qdrant/charts/qdrant/templates/tests/test-db-interaction.yaml new file mode 100644 index 00000000..3b754088 --- /dev/null +++ b/packages/system/qdrant/charts/qdrant/templates/tests/test-db-interaction.yaml @@ -0,0 +1,128 @@ +{{- $root := . }} +{{- $namespace := .Release.Namespace }} +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "qdrant.fullname" . }}-test-db-interaction" + labels: + {{- include "qdrant.labels" . | nindent 4 }} + {{- include "qdrant.additionalLabels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +{{- with .Values.additionalAnnotations }} + {{- toYaml . | nindent 4 }} +{{- end }} +spec: + containers: + - name: test-script + image: {{ .Values.chartTests.dbInteraction.image | quote }} + args: ['bash', '/app/entrypoint.sh'] + volumeMounts: + - mountPath: /app + name: test-script + {{- if .Values.additionalVolumeMounts }} +{{- toYaml .Values.additionalVolumeMounts | default "" | nindent 8 }} + {{- end}} + resources: + {{- toYaml .Values.chartTests.dbInteraction.resources | nindent 8 }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 4 }} + {{- end }} + volumes: + - name: test-script + configMap: + name: "{{ include "qdrant.fullname" . }}-test-db-interaction" + {{- if .Values.additionalVolumes }} +{{- toYaml .Values.additionalVolumes | default "" | nindent 4 }} + {{- end}} + restartPolicy: Never + serviceAccountName: {{ include "qdrant.fullname" . }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: "{{ include "qdrant.fullname" . }}-test-db-interaction" + labels: + {{- include "qdrant.labels" . | nindent 4 }} + {{- include "qdrant.additionalLabels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +{{- with .Values.additionalAnnotations }} + {{- toYaml . | nindent 4 }} +{{- end }} +data: + entrypoint.sh: | + #!/bin/bash + set -xe + # Kind's networking is very flaky + echo 'connect-timeout = 5' > $HOME/.curlrc + echo 'retry = 60' >> $HOME/.curlrc + echo 'retry-delay = 5' >> $HOME/.curlrc + echo 'retry-all-errors' >> $HOME/.curlrc + # Don't clutter the logs with progress bars + echo 'no-progress-meter' >> $HOME/.curlrc + # Ensure errors cause the script to fail, but show the response body + echo 'fail-with-body' >> $HOME/.curlrc + + if [ -d /mnt/secrets/certs ]; then + cp /mnt/secrets/certs/ca.pem /usr/share/pki/trust/anchors/private-ca.pem + update-ca-certificates + fi + + QDRANT_COLLECTION="test_collection" + {{- range .Values.service.ports }} + {{- if eq .name "http" }} + echo "Connecting to {{ include "qdrant.fullname" $root }}.{{ $namespace }}:{{ .port }}" + QDRANT_URL="http://{{ include "qdrant.fullname" $root }}.{{ $namespace }}:{{ .port }}" + {{- if and $root.Values.config.service $root.Values.config.service.enable_tls }} + echo "Using https" + QDRANT_URL="https://{{ include "qdrant.fullname" $root }}.{{ $namespace }}:{{ .port }}" + {{- end }} + {{- end }} + {{- end }} + API_KEY_HEADER="" + {{- if .Values.apiKey }} + API_KEY_HEADER="Api-key: {{ .Values.apiKey }}" + {{- else if .Values.readOnlyApiKey }} + API_KEY_HEADER="Api-key: {{ .Values.readOnlyApiKey }}" + {{- end }} + + # Delete collection if exists + curl -X DELETE -H "${API_KEY_HEADER}" $QDRANT_URL/collections/${QDRANT_COLLECTION} + + # Create collection + curl -X PUT \ + -H 'Content-Type: application-json' \ + -d '{"vectors":{"size":4,"distance":"Dot"}}' \ + -H "${API_KEY_HEADER}" \ + $QDRANT_URL/collections/${QDRANT_COLLECTION} + + # Insert points + curl -X PUT \ + -H 'Content-Type: application-json' \ + -d '{"points":[ + {"id":1,"vector":[0.05, 0.61, 0.76, 0.74],"payload":{"city":"Berlin"}}, + {"id":2,"vector":[0.19, 0.81, 0.75, 0.11],"payload":{"city":"London"}}, + {"id":3,"vector":[0.36, 0.55, 0.47, 0.94],"payload":{"city":"Moscow"}}, + {"id":4,"vector":[0.18, 0.01, 0.85, 0.80],"payload":{"city":"New York"}}, + {"id":5,"vector":[0.24, 0.18, 0.22, 0.44],"payload":{"city":"Beijing"}}, + {"id":6,"vector":[0.35, 0.08, 0.11, 0.44],"payload":{"city":"Mumbai"}} + ]}' \ + -H "${API_KEY_HEADER}" \ + $QDRANT_URL/collections/${QDRANT_COLLECTION}/points + + # Run query + curl -X POST \ + -H 'Content-Type: application-json' \ + -d '{"vector":[0.2, 0.1, 0.9, 0.7],"limit":3}' \ + -H "${API_KEY_HEADER}" \ + $QDRANT_URL/collections/${QDRANT_COLLECTION}/points/search diff --git a/packages/system/qdrant/charts/qdrant/values.yaml b/packages/system/qdrant/charts/qdrant/values.yaml new file mode 100644 index 00000000..8a2fd9dc --- /dev/null +++ b/packages/system/qdrant/charts/qdrant/values.yaml @@ -0,0 +1,293 @@ +replicaCount: 1 + +image: + repository: docker.io/qdrant/qdrant + pullPolicy: IfNotPresent + tag: "" + useUnprivilegedImage: false + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" +args: ["./config/initialize.sh"] +env: [] + # - name: QDRANT_ALLOW_RECOVERY_MODE + # value: true +additionalLabels: {} + +# checks - Readiness and liveness checks can only be enabled for either http (REST) or grpc (multiple checks not supported) +# grpc checks are only available from k8s 1.24+ so as of per default we check http +service: + type: ClusterIP + additionalLabels: {} + annotations: {} + loadBalancerIP: "" + ports: + - name: http + port: 6333 + targetPort: 6333 + protocol: TCP + checksEnabled: true + # appProtocol: http + - name: grpc + port: 6334 + targetPort: 6334 + protocol: TCP + checksEnabled: false + # appProtocol: http2 + - name: p2p + port: 6335 + targetPort: 6335 + protocol: TCP + checksEnabled: false + +ingress: + enabled: false + ingressClassName: "" + additionalLabels: {} + annotations: {} + # kubernetes.io/ingress.class: alb + hosts: + - host: example-domain.com + paths: + - path: / + pathType: Prefix + servicePort: 6333 + tls: [] + # - hosts: + # - example-domain.com + # secretName: tls-secret-name + +livenessProbe: + enabled: false + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 1 + failureThreshold: 6 + successThreshold: 1 + +readinessProbe: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 1 + failureThreshold: 6 + successThreshold: 1 + +startupProbe: + enabled: false + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 1 + failureThreshold: 30 + successThreshold: 1 + +additionalLabels: {} +# additionalAnnotations will be added to all top-level resources (StatefulSet, Service, ConfigMap, etc.) +additionalAnnotations: {} +podAnnotations: {} +podLabels: {} + +resources: {} + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +containerSecurityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 2000 + allowPrivilegeEscalation: false + privileged: false + readOnlyRootFilesystem: true + +podSecurityContext: + fsGroup: 3000 + fsGroupChangePolicy: Always + +lifecycle: + preStop: + exec: + # Sleeping before shutdown allows Qdrant to process requests that were + # in-flight before the node is removed from load-balancing. + # If using an external load balancer, you may need to increase this + # duration to be greater than the LB's health check interval. + command: ["sleep", "3"] + +# Unless .Values.image.useUnprivilegedImage is set to true, ensures that the pre-existing +# files on the storage and snapshot volume are owned by the container's user and fsGroup. +updateVolumeFsOwnership: true + +nodeSelector: {} + +tolerations: [] + +affinity: {} + # podAntiAffinity: + # requiredDuringSchedulingIgnoredDuringExecution: + # - labelSelector: + # matchExpressions: + # - key: app.kubernetes.io/name + # operator: In + # values: + # - '{{ include "qdrant.name" . }}' + # - key: app.kubernetes.io/instance + # operator: In + # values: + # - '{{ .Release.Name }}' + # topologyKey: "kubernetes.io/hostname" + +topologySpreadConstraints: [] + +persistence: + accessModes: ["ReadWriteOnce"] + size: 10Gi + annotations: {} + additionalLabels: {} + # storageVolumeName: qdrant-storage + # storageSubPath: "" + # storageClassName: local-path + # volumeAttributesClassName: "" + +# If you use snapshots or the snapshot shard transfer mechanism, we recommend +# creating a separate volume of the same size as your main volume so that your +# cluster won't crash if the snapshot is too big. +snapshotPersistence: + enabled: false + accessModes: ["ReadWriteOnce"] + size: 10Gi + annotations: {} + additionalLabels: {} + # snapshotsVolumeName: qdrant-snapshots + # snapshotsSubPath: "" + # You can change the storageClassName to ensure snapshots are saved to cold storage. + # storageClassName: local-path + # volumeAttributesClassName: "" + +snapshotRestoration: + enabled: false + # Set pvcName if you want to restore from a separately-created PVC. Only supported for single-node clusters unless the PVC is ReadWriteMany. + # If you set snapshotPersistence.enabled and want to restore a snapshot from there, you can leave this blank to skip mounting an external volume. + pvcName: snapshots-pvc + # Must not conflict with /qdrant/snapshots or /qdrant/storage + mountPath: /qdrant/snapshot-restoration + snapshots: + # - /qdrant/snapshot-restoration/test_collection/test_collection-2022-10-24-13-56-50.snapshot:test_collection + +# modification example for configuration to overwrite defaults +config: + cluster: + enabled: true + p2p: + port: 6335 + enable_tls: false + consensus: + tick_period_ms: 100 + service: + enable_tls: false + +sidecarContainers: [] +# sidecarContainers: +# - name: my-sidecar +# image: qdrant/my-sidecar-image +# imagePullPolicy: Always +# ports: +# - name: my-port +# containerPort: 5000 +# protocol: TCP +# resources: +# requests: +# memory: 10Mi +# cpu: 10m +# limits: +# memory: 100Mi +# cpu: 100m + +metrics: + serviceMonitor: + enabled: false + additionalLabels: {} + scrapeInterval: 30s + scrapeTimeout: 10s + targetPort: http + targetPath: "/metrics" + ## MetricRelabelConfigs to apply to samples after scraping, but before ingestion. + ## ref: https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/api.md#relabelconfig + ## + metricRelabelings: [] + ## RelabelConfigs to apply to samples before scraping + ## ref: https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/api.md#relabelconfig + ## + relabelings: [] + ## Authorization to apply to the metrics endpoint for the cases when the API key(s) are configured externally + ## ref: https://prometheus-operator.dev/docs/api-reference/api/#monitoring.coreos.com/v1.SafeAuthorization + ## + authorization: {} + # authorization: + # type: Bearer + # credentials: + # name: external-secret-with-api-key + # key: api-key + +serviceAccount: + annotations: {} + +priorityClassName: "" + +shareProcessNamespace: false + +# We discourage changing this setting. Using the "OrderedReady" policy in a +# multi-node cluster will cause a deadlock where nodes refuse to become +# "Ready" until all nodes are running. +podManagementPolicy: Parallel + +podDisruptionBudget: + enabled: false + maxUnavailable: 1 + # do not enable if you are using not in 1.27 + unhealthyPodEvictionPolicy: "" + # minAvailable: 1 + +# api key for authentication at qdrant +# false: no api key will be configured +# true: an api key will be auto-generated +# string: the given string will be set as an apikey +# Also supports reading in from an external secret using +# valueFrom: +# secretKeyRef: +# name: +# key: +# apiKey: false + +# read-only api key for authentication at qdrant +# false: no read-only api key will be configured +# true: an read-only api key will be auto-generated +# string: the given string will be set as a read-only apikey +# Also supports reading in from an external secret using +# valueFrom: +# secretKeyRef: +# name: +# key: +# readOnlyApiKey: false + +additionalVolumes: [] +# - name: volumeName +# emptyDir: {} + +additionalVolumeMounts: [] +# - name: volumeName +# mountPath: "/mount/path" + +chartTests: + dbInteraction: + image: registry.suse.com/bci/bci-base:latest + resources: + requests: + cpu: 100m + memory: 200Mi + limits: + cpu: 100m + memory: 200Mi diff --git a/packages/system/qdrant/values.yaml b/packages/system/qdrant/values.yaml new file mode 100644 index 00000000..c675678f --- /dev/null +++ b/packages/system/qdrant/values.yaml @@ -0,0 +1 @@ +qdrant: {} From 330cbe70d4b4215d059adac153864b1c8a3aa3d3 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Fri, 6 Feb 2026 12:46:14 +0100 Subject: [PATCH 185/889] fix(dashboard): add startupProbe to prevent container restarts on slow hardware Kubelet kills bff and web containers on slow hardware because the livenessProbe only allows 33 seconds for startup. Add startupProbe with failureThreshold=30 and periodSeconds=2, giving containers up to 60 seconds to start before livenessProbe kicks in. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- packages/system/dashboard/templates/web.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/system/dashboard/templates/web.yaml b/packages/system/dashboard/templates/web.yaml index 17bc9c78..d1c789b6 100644 --- a/packages/system/dashboard/templates/web.yaml +++ b/packages/system/dashboard/templates/web.yaml @@ -63,6 +63,13 @@ spec: periodSeconds: 10 successThreshold: 1 timeoutSeconds: 2 + startupProbe: + httpGet: + path: /healthcheck + port: 64231 + scheme: HTTP + failureThreshold: 30 + periodSeconds: 2 name: bff ports: - containerPort: 64231 @@ -183,6 +190,13 @@ spec: periodSeconds: 10 successThreshold: 1 timeoutSeconds: 2 + startupProbe: + httpGet: + path: /healthcheck + port: 8080 + scheme: HTTP + failureThreshold: 30 + periodSeconds: 2 name: web ports: - containerPort: 8080 From 90ac6de475677a90aa8dab613376a2a8a735405e Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Fri, 6 Feb 2026 12:53:51 +0100 Subject: [PATCH 186/889] feat(kubernetes): auto-enable Gateway API support in cert-manager When the Gateway API addon is enabled, automatically configure cert-manager with enableGatewayAPI: true. Uses the same default values + mergeOverwrite pattern as Cilium for consistency. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- .../templates/helmreleases/cert-manager.yaml | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/apps/kubernetes/templates/helmreleases/cert-manager.yaml b/packages/apps/kubernetes/templates/helmreleases/cert-manager.yaml index 991ed70f..6857581a 100644 --- a/packages/apps/kubernetes/templates/helmreleases/cert-manager.yaml +++ b/packages/apps/kubernetes/templates/helmreleases/cert-manager.yaml @@ -1,3 +1,13 @@ +{{- define "cozystack.defaultCertManagerValues" -}} +{{- if $.Values.addons.gatewayAPI.enabled }} +cert-manager: + config: + apiVersion: controller.config.cert-manager.io/v1alpha1 + kind: ControllerConfiguration + enableGatewayAPI: true +{{- end }} +{{- end }} + {{- if .Values.addons.certManager.enabled }} apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease @@ -29,11 +39,8 @@ spec: force: true remediation: retries: -1 - {{- with .Values.addons.certManager.valuesOverride }} values: - {{- toYaml . | nindent 4 }} - {{- end }} - + {{- toYaml (deepCopy .Values.addons.certManager.valuesOverride | mergeOverwrite (fromYaml (include "cozystack.defaultCertManagerValues" .))) | nindent 4 }} dependsOn: {{- if lookup "helm.toolkit.fluxcd.io/v2" "HelmRelease" .Release.Namespace .Release.Name }} - name: {{ .Release.Name }} From bb638f34477dbc3d61de6c9da55adf52ef57cace Mon Sep 17 00:00:00 2001 From: IvanHunters Date: Fri, 6 Feb 2026 18:33:13 +0300 Subject: [PATCH 187/889] fix(monitoring): parametrize namespace for monitoring-agents - Replace hardcoded tenant-root with {{ .Release.Namespace }} in vmagent and fluent-bit configs - Add ExternalName services in cozystack-basics to redirect monitoring traffic from cozy-monitoring to tenant-root when engine is deployed - Add missing components to monitoring PackageSource Co-Authored-By: Claude Signed-off-by: IvanHunters --- .../core/platform/sources/monitoring.yaml | 12 ++++++++- .../monitoring-external-services.yaml | 27 +++++++++++++++++++ packages/system/monitoring-agents/values.yaml | 14 +++++----- 3 files changed, 45 insertions(+), 8 deletions(-) create mode 100644 packages/system/cozystack-basics/templates/monitoring-external-services.yaml diff --git a/packages/core/platform/sources/monitoring.yaml b/packages/core/platform/sources/monitoring.yaml index 804aec56..ea2007ec 100644 --- a/packages/core/platform/sources/monitoring.yaml +++ b/packages/core/platform/sources/monitoring.yaml @@ -30,4 +30,14 @@ spec: install: privileged: true namespace: cozy-monitoring - releaseName: monitoring \ No newline at end of file + releaseName: monitoring + - name: vertical-pod-autoscaler + path: system/vertical-pod-autoscaler + - name: postgres-operator + path: system/postgres-operator + - name: grafana-operator + path: system/grafana-operator + - name: victoria-metrics-operator + path: system/victoria-metrics-operator + - name: prometheus-operator-crds + path: system/prometheus-operator-crds \ No newline at end of file diff --git a/packages/system/cozystack-basics/templates/monitoring-external-services.yaml b/packages/system/cozystack-basics/templates/monitoring-external-services.yaml new file mode 100644 index 00000000..0f8b4d5c --- /dev/null +++ b/packages/system/cozystack-basics/templates/monitoring-external-services.yaml @@ -0,0 +1,27 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: vlogs-generic + namespace: cozy-monitoring +spec: + type: ExternalName + externalName: vlogs-generic.tenant-root.svc.cluster.local +--- +apiVersion: v1 +kind: Service +metadata: + name: vminsert-shortterm + namespace: cozy-monitoring +spec: + type: ExternalName + externalName: vminsert-shortterm.tenant-root.svc.cluster.local +--- +apiVersion: v1 +kind: Service +metadata: + name: vminsert-longterm + namespace: cozy-monitoring +spec: + type: ExternalName + externalName: vminsert-longterm.tenant-root.svc.cluster.local diff --git a/packages/system/monitoring-agents/values.yaml b/packages/system/monitoring-agents/values.yaml index 7b095c6c..3b79de95 100644 --- a/packages/system/monitoring-agents/values.yaml +++ b/packages/system/monitoring-agents/values.yaml @@ -275,11 +275,11 @@ kube-state-metrics: vmagent: externalLabels: cluster: cozystack - tenant: tenant-root + tenant: '{{ .Release.Namespace }}' remoteWrite: urls: - - http://vminsert-shortterm.tenant-root.svc:8480/insert/0/prometheus - - http://vminsert-longterm.tenant-root.svc:8480/insert/0/prometheus + - 'http://vminsert-shortterm.{{ .Release.Namespace }}.svc:8480/insert/0/prometheus' + - 'http://vminsert-longterm.{{ .Release.Namespace }}.svc:8480/insert/0/prometheus' extraArgs: {} fluent-bit: @@ -342,7 +342,7 @@ fluent-bit: [OUTPUT] Name http Match kube.* - Host vlogs-generic.tenant-root.svc + Host vlogs-generic.{{ .Release.Namespace }}.svc port 9428 compress gzip uri /insert/jsonline?_stream_fields=log_source,stream,kubernetes_pod_name,kubernetes_container_name,kubernetes_namespace_name&_msg_field=log&_time_field=date @@ -353,7 +353,7 @@ fluent-bit: [OUTPUT] Name http Match events.* - Host vlogs-generic.tenant-root.svc + Host vlogs-generic.{{ .Release.Namespace }}.svc port 9428 compress gzip uri /insert/jsonline?_stream_fields=log_source,reason,meatdata_namespace,metadata_name&_msg_field=message&_time_field=date @@ -364,7 +364,7 @@ fluent-bit: [OUTPUT] Name http Match audit.* - Host vlogs-generic.tenant-root.svc + Host vlogs-generic.{{ .Release.Namespace }}.svc port 9428 compress gzip uri /insert/jsonline?_stream_fields=log_source,stage,user_username,verb,requestUri&_msg_field=requestURI&_time_field=date @@ -416,7 +416,7 @@ fluent-bit: [FILTER] Name modify Match * - Add tenant tenant-root + Add tenant {{ .Release.Namespace }} [FILTER] Name modify Match * From 1e293995de5decf861113f0f9458fd3bb15317f8 Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Fri, 6 Feb 2026 18:38:50 +0300 Subject: [PATCH 188/889] [ci] Choose runner conditional on label ## What this PR does This patch adds a conditional for running on a statically defined VM the maintainers have SSH access to if the pull request has a `debug` label. This is useful for debugging failing workflows when the diagnostic info from the pipeline is insufficient. ### Release note ```release-note [ci] Run builds on a static VM with SSH access if the PR has a debug label. ``` Signed-off-by: Timofei Larkin --- .github/workflows/pull-requests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-requests.yaml b/.github/workflows/pull-requests.yaml index 347bfd30..73efc4b6 100644 --- a/.github/workflows/pull-requests.yaml +++ b/.github/workflows/pull-requests.yaml @@ -153,7 +153,7 @@ jobs: e2e: name: "E2E Tests" - runs-on: [oracle-vm-24cpu-96gb-x86-64] + runs-on: ${{ contains(github.event.pull_request.labels.*.name, 'debug') && 'self-hosted' || 'oracle-vm-24cpu-96gb-x86-64' }} #runs-on: [oracle-vm-32cpu-128gb-x86-64] permissions: contents: read From 748d2ed56f8f0c69c37fa4c1fec5ab5cfccbe044 Mon Sep 17 00:00:00 2001 From: IvanHunters Date: Fri, 6 Feb 2026 18:47:46 +0300 Subject: [PATCH 189/889] refactor(monitoring): move WorkloadMonitor resources to extra/monitoring WorkloadMonitor resources are only needed when extra/monitoring is deployed. Move them from system/monitoring to extra/monitoring package. Co-Authored-By: Claude Signed-off-by: IvanHunters --- .../templates/workloadmonitors.yaml | 150 ++++++++++++++++++ .../templates/alerta/alerta-db.yaml | 14 -- .../monitoring/templates/alerta/alerta.yaml | 28 ---- .../monitoring/templates/grafana/db.yaml | 14 -- .../monitoring/templates/grafana/grafana.yaml | 13 -- .../monitoring/templates/vlogs/vlogs.yaml | 15 -- .../monitoring/templates/vm/vmalert.yaml | 14 -- .../monitoring/templates/vm/vmcluster.yaml | 45 ------ 8 files changed, 150 insertions(+), 143 deletions(-) create mode 100644 packages/extra/monitoring/templates/workloadmonitors.yaml diff --git a/packages/extra/monitoring/templates/workloadmonitors.yaml b/packages/extra/monitoring/templates/workloadmonitors.yaml new file mode 100644 index 00000000..6dbfa8c1 --- /dev/null +++ b/packages/extra/monitoring/templates/workloadmonitors.yaml @@ -0,0 +1,150 @@ +{{- range .Values.metricsStorages }} +--- +apiVersion: cozystack.io/v1alpha1 +kind: WorkloadMonitor +metadata: + name: {{ .name }}-vmstorage +spec: + replicas: 2 + minReplicas: 1 + kind: monitoring + type: vmstorage + selector: + app.kubernetes.io/component: monitoring + app.kubernetes.io/instance: {{ .name }} + app.kubernetes.io/name: vmstorage + version: {{ $.Chart.Version }} +--- +apiVersion: cozystack.io/v1alpha1 +kind: WorkloadMonitor +metadata: + name: {{ .name }}-vmselect +spec: + replicas: 2 + minReplicas: 1 + kind: monitoring + type: vmselect + selector: + app.kubernetes.io/component: monitoring + app.kubernetes.io/instance: {{ .name }} + app.kubernetes.io/name: vmselect + version: {{ $.Chart.Version }} +--- +apiVersion: cozystack.io/v1alpha1 +kind: WorkloadMonitor +metadata: + name: {{ .name }}-vminsert +spec: + replicas: 2 + minReplicas: 1 + kind: monitoring + type: vminsert + selector: + app.kubernetes.io/component: monitoring + app.kubernetes.io/instance: {{ .name }} + app.kubernetes.io/name: vminsert + version: {{ $.Chart.Version }} +{{- end }} +{{- range .Values.metricsStorages }} +--- +apiVersion: cozystack.io/v1alpha1 +kind: WorkloadMonitor +metadata: + name: vmalert-{{ .name }} +spec: + replicas: 1 + minReplicas: 1 + kind: monitoring + type: vmalert + selector: + app.kubernetes.io/instance: vmalert-{{ .name }} + app.kubernetes.io/name: vmalert + version: {{ $.Chart.Version }} +{{- break }} +{{- end }} +{{- range .Values.logsStorages }} +--- +apiVersion: cozystack.io/v1alpha1 +kind: WorkloadMonitor +metadata: + name: vlogs-{{ .name }} +spec: + replicas: 1 + minReplicas: 1 + kind: monitoring + type: vlogs + selector: + app.kubernetes.io/component: monitoring + app.kubernetes.io/instance: {{ .name }} + app.kubernetes.io/name: vlogs + version: {{ $.Chart.Version }} +{{- end }} +--- +apiVersion: cozystack.io/v1alpha1 +kind: WorkloadMonitor +metadata: + name: grafana +spec: + replicas: 2 + minReplicas: 1 + kind: monitoring + type: grafana + selector: + app: grafana + version: {{ $.Chart.Version }} +--- +apiVersion: cozystack.io/v1alpha1 +kind: WorkloadMonitor +metadata: + name: grafana-db +spec: + replicas: 2 + minReplicas: 1 + kind: monitoring + type: postgres + selector: + cnpg.io/cluster: grafana-db + cnpg.io/podRole: instance + version: {{ $.Chart.Version }} +--- +apiVersion: cozystack.io/v1alpha1 +kind: WorkloadMonitor +metadata: + name: alerta-db +spec: + replicas: 2 + minReplicas: 1 + kind: monitoring + type: postgres + selector: + cnpg.io/cluster: alerta-db + cnpg.io/podRole: instance + version: {{ $.Chart.Version }} +--- +apiVersion: cozystack.io/v1alpha1 +kind: WorkloadMonitor +metadata: + name: alerta +spec: + replicas: 1 + minReplicas: 1 + kind: monitoring + type: alerta + selector: + app: alerta + release: alerta + version: {{ $.Chart.Version }} +--- +apiVersion: cozystack.io/v1alpha1 +kind: WorkloadMonitor +metadata: + name: alertmanager +spec: + replicas: 3 + minReplicas: 2 + kind: monitoring + type: alertmanager + selector: + app.kubernetes.io/instance: alertmanager + app.kubernetes.io/name: vmalertmanager + version: {{ $.Chart.Version }} diff --git a/packages/system/monitoring/templates/alerta/alerta-db.yaml b/packages/system/monitoring/templates/alerta/alerta-db.yaml index 845cd8ba..fc03cd31 100644 --- a/packages/system/monitoring/templates/alerta/alerta-db.yaml +++ b/packages/system/monitoring/templates/alerta/alerta-db.yaml @@ -33,17 +33,3 @@ spec: inheritedMetadata: labels: policy.cozystack.io/allow-to-apiserver: "true" ---- -apiVersion: cozystack.io/v1alpha1 -kind: WorkloadMonitor -metadata: - name: alerta-db -spec: - replicas: 2 - minReplicas: 1 - kind: monitoring - type: postgres - selector: - cnpg.io/cluster: alerta-db - cnpg.io/podRole: instance - version: {{ $.Chart.Version }} diff --git a/packages/system/monitoring/templates/alerta/alerta.yaml b/packages/system/monitoring/templates/alerta/alerta.yaml index 76b7a02b..d8baaa02 100644 --- a/packages/system/monitoring/templates/alerta/alerta.yaml +++ b/packages/system/monitoring/templates/alerta/alerta.yaml @@ -193,20 +193,6 @@ spec: port: name: http --- -apiVersion: cozystack.io/v1alpha1 -kind: WorkloadMonitor -metadata: - name: alerta -spec: - replicas: 1 - minReplicas: 1 - kind: monitoring - type: alerta - selector: - app: alerta - release: alerta - version: {{ $.Chart.Version }} ---- apiVersion: v1 kind: Secret metadata: @@ -259,17 +245,3 @@ spec: podMetadata: labels: policy.cozystack.io/allow-to-apiserver: "true" ---- -apiVersion: cozystack.io/v1alpha1 -kind: WorkloadMonitor -metadata: - name: alertmanager -spec: - replicas: 3 - minReplicas: 2 - kind: monitoring - type: alertmanager - selector: - app.kubernetes.io/instance: alertmanager - app.kubernetes.io/name: vmalertmanager - version: {{ $.Chart.Version }} diff --git a/packages/system/monitoring/templates/grafana/db.yaml b/packages/system/monitoring/templates/grafana/db.yaml index 662f5cf4..b20a8fd8 100644 --- a/packages/system/monitoring/templates/grafana/db.yaml +++ b/packages/system/monitoring/templates/grafana/db.yaml @@ -27,17 +27,3 @@ spec: inheritedMetadata: labels: policy.cozystack.io/allow-to-apiserver: "true" ---- -apiVersion: cozystack.io/v1alpha1 -kind: WorkloadMonitor -metadata: - name: grafana-db -spec: - replicas: 2 - minReplicas: 1 - kind: monitoring - type: postgres - selector: - cnpg.io/cluster: grafana-db - cnpg.io/podRole: instance - version: {{ $.Chart.Version }} diff --git a/packages/system/monitoring/templates/grafana/grafana.yaml b/packages/system/monitoring/templates/grafana/grafana.yaml index 1eadda9e..314a1682 100644 --- a/packages/system/monitoring/templates/grafana/grafana.yaml +++ b/packages/system/monitoring/templates/grafana/grafana.yaml @@ -93,16 +93,3 @@ spec: - hosts: - "{{ printf "grafana.%s" (.Values.host | default $host) }}" secretName: grafana-ingress-tls ---- -apiVersion: cozystack.io/v1alpha1 -kind: WorkloadMonitor -metadata: - name: grafana -spec: - replicas: 2 - minReplicas: 1 - kind: monitoring - type: grafana - selector: - app: grafana - version: {{ $.Chart.Version }} diff --git a/packages/system/monitoring/templates/vlogs/vlogs.yaml b/packages/system/monitoring/templates/vlogs/vlogs.yaml index a82bca00..adedb1e8 100644 --- a/packages/system/monitoring/templates/vlogs/vlogs.yaml +++ b/packages/system/monitoring/templates/vlogs/vlogs.yaml @@ -19,19 +19,4 @@ spec: accessModes: [ReadWriteOnce] retentionPeriod: "{{ .retentionPeriod }}" removePvcAfterDelete: true ---- -apiVersion: cozystack.io/v1alpha1 -kind: WorkloadMonitor -metadata: - name: vlogs-{{ .name }} -spec: - replicas: 1 - minReplicas: 1 - kind: monitoring - type: vlogs - selector: - app.kubernetes.io/component: monitoring - app.kubernetes.io/instance: {{ .name }} - app.kubernetes.io/name: vlogs - version: {{ $.Chart.Version }} {{- end }} diff --git a/packages/system/monitoring/templates/vm/vmalert.yaml b/packages/system/monitoring/templates/vm/vmalert.yaml index d66fc42c..8da03e15 100644 --- a/packages/system/monitoring/templates/vm/vmalert.yaml +++ b/packages/system/monitoring/templates/vm/vmalert.yaml @@ -23,19 +23,5 @@ spec: url: http://vminsert-{{ .name }}.{{ $.Release.Namespace }}.svc:8480/insert/0/prometheus/api/v1/write resources: {} selectAllByDefault: true ---- -apiVersion: cozystack.io/v1alpha1 -kind: WorkloadMonitor -metadata: - name: vmalert-{{ .name }} -spec: - replicas: 1 - minReplicas: 1 - kind: monitoring - type: vmalert - selector: - app.kubernetes.io/instance: vmalert-{{ .name }} - app.kubernetes.io/name: vmalert - version: {{ $.Chart.Version }} {{- break }} {{- end }} diff --git a/packages/system/monitoring/templates/vm/vmcluster.yaml b/packages/system/monitoring/templates/vm/vmcluster.yaml index 5f86afa3..1bc39923 100644 --- a/packages/system/monitoring/templates/vm/vmcluster.yaml +++ b/packages/system/monitoring/templates/vm/vmcluster.yaml @@ -49,49 +49,4 @@ spec: requests: storage: {{ .storage }} storageDataPath: /vm-data ---- -apiVersion: cozystack.io/v1alpha1 -kind: WorkloadMonitor -metadata: - name: {{ .name }}-vmstorage -spec: - replicas: 2 - minReplicas: 1 - kind: monitoring - type: vmstorage - selector: - app.kubernetes.io/component: monitoring - app.kubernetes.io/instance: {{ .name }} - app.kubernetes.io/name: vmstorage - version: {{ $.Chart.Version }} ---- -apiVersion: cozystack.io/v1alpha1 -kind: WorkloadMonitor -metadata: - name: {{ .name }}-vmselect -spec: - replicas: 2 - minReplicas: 1 - kind: monitoring - type: vmselect - selector: - app.kubernetes.io/component: monitoring - app.kubernetes.io/instance: {{ .name }} - app.kubernetes.io/name: vmselect - version: {{ $.Chart.Version }} ---- -apiVersion: cozystack.io/v1alpha1 -kind: WorkloadMonitor -metadata: - name: {{ .name }}-vminsert -spec: - replicas: 2 - minReplicas: 1 - kind: monitoring - type: vminsert - selector: - app.kubernetes.io/component: monitoring - app.kubernetes.io/instance: {{ .name }} - app.kubernetes.io/name: vminsert - version: {{ $.Chart.Version }} {{- end }} From c815dd46c75cb68fc1c191bf49285c41d1eab0e5 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Fri, 6 Feb 2026 20:06:08 +0100 Subject: [PATCH 190/889] docs(cluster-autoscaler): add comprehensive Azure setup guide Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- .../system/cluster-autoscaler/docs/azure.md | 401 ++++++++++++++++++ 1 file changed, 401 insertions(+) create mode 100644 packages/system/cluster-autoscaler/docs/azure.md diff --git a/packages/system/cluster-autoscaler/docs/azure.md b/packages/system/cluster-autoscaler/docs/azure.md new file mode 100644 index 00000000..6c00bea0 --- /dev/null +++ b/packages/system/cluster-autoscaler/docs/azure.md @@ -0,0 +1,401 @@ +# Cluster Autoscaler for Azure + +This guide explains how to configure cluster-autoscaler for automatic node scaling in Azure with Talos Linux. + +## Prerequisites + +- Azure subscription with Contributor Service Principal +- `az` CLI installed +- Existing Talos Kubernetes cluster with Kilo WireGuard mesh +- Talos worker machine config + +## Step 1: Create Azure Infrastructure + +### 1.1 Login with Service Principal + +```bash +az login --service-principal \ + --username "" \ + --password "" \ + --tenant "" +``` + +### 1.2 Create Resource Group + +```bash +az group create \ + --name cozystack-autoscaler \ + --location germanywestcentral +``` + +### 1.3 Create VNet and Subnet + +```bash +az network vnet create \ + --resource-group cozystack-autoscaler \ + --name cozystack-vnet \ + --address-prefix 10.2.0.0/16 \ + --subnet-name workers \ + --subnet-prefix 10.2.0.0/24 \ + --location germanywestcentral +``` + +### 1.4 Create Network Security Group + +```bash +az network nsg create \ + --resource-group cozystack-autoscaler \ + --name cozystack-nsg \ + --location germanywestcentral + +# Allow WireGuard +az network nsg rule create \ + --resource-group cozystack-autoscaler \ + --nsg-name cozystack-nsg \ + --name AllowWireGuard \ + --priority 100 \ + --direction Inbound \ + --access Allow \ + --protocol Udp \ + --destination-port-ranges 51820 + +# Allow Talos API +az network nsg rule create \ + --resource-group cozystack-autoscaler \ + --nsg-name cozystack-nsg \ + --name AllowTalosAPI \ + --priority 110 \ + --direction Inbound \ + --access Allow \ + --protocol Tcp \ + --destination-port-ranges 50000 + +# Associate NSG with subnet +az network vnet subnet update \ + --resource-group cozystack-autoscaler \ + --vnet-name cozystack-vnet \ + --name workers \ + --network-security-group cozystack-nsg +``` + +## Step 2: Create Talos Image + +### 2.1 Generate Schematic ID + +Create a schematic at [factory.talos.dev](https://factory.talos.dev) with required extensions: + +```bash +curl -s -X POST https://factory.talos.dev/schematics \ + -H "Content-Type: application/json" \ + -d '{ + "customization": { + "systemExtensions": { + "officialExtensions": [ + "siderolabs/amd-ucode", + "siderolabs/amdgpu-firmware", + "siderolabs/bnx2-bnx2x", + "siderolabs/drbd", + "siderolabs/i915-ucode", + "siderolabs/intel-ice-firmware", + "siderolabs/intel-ucode", + "siderolabs/qlogic-firmware", + "siderolabs/zfs" + ] + } + } + }' +``` + +Save the returned `id` as `SCHEMATIC_ID`. + +### 2.2 Create Storage Account and Upload VHD + +```bash +# Create storage account +az storage account create \ + --name cozystacktalos \ + --resource-group cozystack-autoscaler \ + --location germanywestcentral \ + --sku Standard_LRS + +# Download Talos Azure image +curl -L -o azure-amd64.raw.xz \ + "https://factory.talos.dev/image/${SCHEMATIC_ID}/v1.11.6/azure-amd64.raw.xz" + +# Decompress +xz -d azure-amd64.raw.xz + +# Convert to VHD +qemu-img convert -f raw -o subformat=fixed,force_size -O vpc \ + azure-amd64.raw azure-amd64.vhd + +# Get VHD size +VHD_SIZE=$(stat -f%z azure-amd64.vhd) # macOS +# VHD_SIZE=$(stat -c%s azure-amd64.vhd) # Linux + +# Create managed disk for upload +az disk create \ + --resource-group cozystack-autoscaler \ + --name talos-v1.11.6 \ + --location germanywestcentral \ + --upload-type Upload \ + --upload-size-bytes $VHD_SIZE \ + --sku Standard_LRS \ + --os-type Linux \ + --hyper-v-generation V2 + +# Get SAS URL for upload +SAS_URL=$(az disk grant-access \ + --resource-group cozystack-autoscaler \ + --name talos-v1.11.6 \ + --access-level Write \ + --duration-in-seconds 3600 \ + --query accessSAS --output tsv) + +# Upload VHD +azcopy copy azure-amd64.vhd "$SAS_URL" --blob-type PageBlob + +# Revoke access +az disk revoke-access \ + --resource-group cozystack-autoscaler \ + --name talos-v1.11.6 + +# Create managed image from disk +az image create \ + --resource-group cozystack-autoscaler \ + --name talos-v1.11.6 \ + --location germanywestcentral \ + --os-type Linux \ + --hyper-v-generation V2 \ + --source $(az disk show --resource-group cozystack-autoscaler --name talos-v1.11.6 --query id --output tsv) +``` + +## Step 3: Create Talos Machine Config for Azure + +Create a machine config similar to the Hetzner one, with these Azure-specific changes: + +```yaml +machine: + nodeLabels: + kilo.squat.ai/location: azure # <-- changed from 'hetzner-cloud' + kubelet: + nodeIP: + validSubnets: + - 10.2.0.0/24 # <-- Azure VNet subnet +``` + +All other settings (cluster tokens, control plane endpoint, extensions, etc.) remain the same as the Hetzner config. + +## Step 4: Create VMSS (Virtual Machine Scale Set) + +```bash +IMAGE_ID=$(az image show \ + --resource-group cozystack-autoscaler \ + --name talos-v1.11.6 \ + --query id --output tsv) + +az vmss create \ + --resource-group cozystack-autoscaler \ + --name workers \ + --location germanywestcentral \ + --orchestration-mode Uniform \ + --image "$IMAGE_ID" \ + --vm-sku Standard_D2s_v3 \ + --instance-count 0 \ + --vnet-name cozystack-vnet \ + --subnet workers \ + --public-ip-per-vm \ + --custom-data machineconfig-azure.yaml \ + --security-type Standard \ + --admin-username talos \ + --authentication-type ssh \ + --generate-ssh-keys \ + --upgrade-policy-mode Manual +``` + +**Important notes:** +- Must use `--orchestration-mode Uniform` (cluster-autoscaler requires Uniform mode) +- Must use `--public-ip-per-vm` for WireGuard connectivity +- Check VM quota in your region: `az vm list-usage --location germanywestcentral` +- `--custom-data` passes the Talos machine config to new instances + +### Available VM sizes in Germany West Central + +| Family | Example | vCPU | RAM | Use case | +|--------|---------|------|-----|----------| +| D-series | Standard_D2s_v3 | 2 | 8 GB | General purpose | +| D-series | Standard_D4s_v3 | 4 | 16 GB | General purpose | +| NC T4 | Standard_NC4as_T4_v3 | 4 | 28 GB | GPU (T4) | +| NC A100 | Standard_NC24ads_A100_v4 | 24 | 220 GB | GPU (A100) | +| NC H100 | Standard_NC40ads_H100_v5 | 40 | 320 GB | GPU (H100) | + +## Step 5: Create Kubernetes Secrets + +### 5.1 Azure Credentials Secret + +```bash +kubectl create namespace cozy-cluster-autoscaler-azure + +kubectl create secret generic azure-credentials \ + --namespace cozy-cluster-autoscaler-azure \ + --from-literal=ClientID="" \ + --from-literal=ClientSecret="" \ + --from-literal=TenantID="" \ + --from-literal=SubscriptionID="" \ + --from-literal=ResourceGroup="cozystack-autoscaler" \ + --from-literal=VMType="vmss" +``` + +### 5.2 Talos Machine Config Secret + +```bash +kubectl create secret generic talos-config \ + --namespace cozy-cluster-autoscaler-azure \ + --from-file=cloud-init=machineconfig-azure.yaml +``` + +## Step 6: Deploy Cluster Autoscaler + +Example Package resource: + +```yaml +apiVersion: cozystack.io/v1alpha1 +kind: Package +metadata: + name: cozystack.cluster-autoscaler-azure + namespace: cozy-cluster-autoscaler-azure +spec: + type: cluster-autoscaler + values: + cluster-autoscaler: + azureClientID: "" + azureClientSecret: "" + azureTenantID: "" + azureSubscriptionID: "" + azureResourceGroup: "cozystack-autoscaler" + azureVMType: "vmss" + autoscalingGroups: + - name: workers + minSize: 0 + maxSize: 10 +``` + +Or configure the HelmRelease directly with `secretKeyRefNameOverride` to use existing secrets: + +```yaml +cluster-autoscaler: + secretKeyRefNameOverride: azure-credentials + autoscalingGroups: + - name: workers + minSize: 0 + maxSize: 10 +``` + +## Step 7: Kilo WireGuard Endpoint Configuration + +**Important:** Azure nodes behind NAT need their public IP advertised as the WireGuard endpoint. Without this, the WireGuard tunnel between Hetzner and Azure nodes will not be established. + +Each new Azure node needs the annotation: + +```bash +kubectl annotate node \ + kilo.squat.ai/force-endpoint=:51820 +``` + +### Automated Endpoint Configuration + +For automated endpoint detection, create a DaemonSet that runs on Azure nodes (`kilo.squat.ai/location=azure`) and: + +1. Queries Azure Instance Metadata Service (IMDS) for the public IP: + ```bash + curl -s -H "Metadata: true" \ + "http://169.254.169.254/metadata/instance/network/interface/0/ipv4/ipAddress/0/publicIpAddress?api-version=2021-02-01&format=text" + ``` +2. Annotates the node with `kilo.squat.ai/force-endpoint=:51820` + +This ensures new autoscaled nodes automatically get proper WireGuard connectivity. + +## Testing + +### Manual scale test + +```bash +# Scale up +az vmss scale --resource-group cozystack-autoscaler --name workers --new-capacity 1 + +# Check node joined +kubectl get nodes -o wide + +# Check WireGuard tunnel +kubectl logs -n cozy-kilo + +# Scale down +az vmss scale --resource-group cozystack-autoscaler --name workers --new-capacity 0 +``` + +### Autoscaler test + +Deploy a workload with anti-affinity to trigger autoscaling: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: test-azure-autoscale +spec: + replicas: 3 + selector: + matchLabels: + app: test-azure + template: + metadata: + labels: + app: test-azure + spec: + nodeSelector: + kilo.squat.ai/location: azure + containers: + - name: pause + image: registry.k8s.io/pause:3.9 + resources: + requests: + cpu: "500m" + memory: "512Mi" +``` + +## Troubleshooting + +### Node doesn't join cluster +- Check that the Talos machine config control plane endpoint is reachable from Azure +- Verify NSG rules allow outbound traffic to port 6443 +- Check VMSS instance provisioning state: `az vmss list-instances --resource-group cozystack-autoscaler --name workers` + +### WireGuard tunnel not established +- Verify `kilo.squat.ai/force-endpoint` annotation is set with the public IP +- Check NSG allows inbound UDP 51820 +- Inspect kilo logs: `kubectl logs -n cozy-kilo ` + +### VM quota errors +- Check quota: `az vm list-usage --location germanywestcentral` +- Request quota increase via Azure portal +- Try a different VM family that has available quota + +### SkuNotAvailable errors +- Some VM sizes may have capacity restrictions in certain regions +- Try a different VM size: `az vm list-skus --location germanywestcentral --size ` + +## Current Infrastructure Reference + +Created in subscription `cdd9c3ff-ef22-46f5-916f-cf529408f367` (Sandbox autoscaler): + +| Resource | Name | Details | +|----------|------|---------| +| Resource Group | cozystack-autoscaler | germanywestcentral | +| VNet | cozystack-vnet | 10.2.0.0/16 | +| Subnet | workers | 10.2.0.0/24 | +| NSG | cozystack-nsg | Allow UDP 51820, TCP 50000 | +| Managed Image | talos-v1.11.6 | Talos with extensions (drbd, zfs, etc.) | +| VMSS | workers | Standard_D2s_v3, Uniform, 0 instances | +| Storage Account | cozystacktalos | Used for VHD upload | + +Service Principal: `78df2235-89b3-4b94-b6db-5573677eee57` (autoscaler-contributor) From 27f1e79e32d095ad939b7660503642b04f6249e9 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Fri, 6 Feb 2026 20:38:30 +0100 Subject: [PATCH 191/889] docs(cluster-autoscaler): add topology.kubernetes.io/zone label and improve Azure docs Add topology.kubernetes.io/zone node label alongside kilo.squat.ai/location for standard Kubernetes topology awareness across Hetzner and Azure zones. Update Azure docs with correct Package resource format (spec.components), replace hardcoded values with placeholders, and remove environment-specific infrastructure references. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- .../system/cluster-autoscaler/docs/azure.md | 188 +++++++----------- .../system/cluster-autoscaler/docs/hetzner.md | 2 + 2 files changed, 70 insertions(+), 120 deletions(-) diff --git a/packages/system/cluster-autoscaler/docs/azure.md b/packages/system/cluster-autoscaler/docs/azure.md index 6c00bea0..7bcfbedc 100644 --- a/packages/system/cluster-autoscaler/docs/azure.md +++ b/packages/system/cluster-autoscaler/docs/azure.md @@ -24,33 +24,33 @@ az login --service-principal \ ```bash az group create \ - --name cozystack-autoscaler \ - --location germanywestcentral + --name \ + --location ``` ### 1.3 Create VNet and Subnet ```bash az network vnet create \ - --resource-group cozystack-autoscaler \ + --resource-group \ --name cozystack-vnet \ --address-prefix 10.2.0.0/16 \ --subnet-name workers \ --subnet-prefix 10.2.0.0/24 \ - --location germanywestcentral + --location ``` ### 1.4 Create Network Security Group ```bash az network nsg create \ - --resource-group cozystack-autoscaler \ + --resource-group \ --name cozystack-nsg \ - --location germanywestcentral + --location # Allow WireGuard az network nsg rule create \ - --resource-group cozystack-autoscaler \ + --resource-group \ --nsg-name cozystack-nsg \ --name AllowWireGuard \ --priority 100 \ @@ -61,7 +61,7 @@ az network nsg rule create \ # Allow Talos API az network nsg rule create \ - --resource-group cozystack-autoscaler \ + --resource-group \ --nsg-name cozystack-nsg \ --name AllowTalosAPI \ --priority 110 \ @@ -72,7 +72,7 @@ az network nsg rule create \ # Associate NSG with subnet az network vnet subnet update \ - --resource-group cozystack-autoscaler \ + --resource-group \ --vnet-name cozystack-vnet \ --name workers \ --network-security-group cozystack-nsg @@ -108,19 +108,12 @@ curl -s -X POST https://factory.talos.dev/schematics \ Save the returned `id` as `SCHEMATIC_ID`. -### 2.2 Create Storage Account and Upload VHD +### 2.2 Create Managed Image from VHD ```bash -# Create storage account -az storage account create \ - --name cozystacktalos \ - --resource-group cozystack-autoscaler \ - --location germanywestcentral \ - --sku Standard_LRS - # Download Talos Azure image curl -L -o azure-amd64.raw.xz \ - "https://factory.talos.dev/image/${SCHEMATIC_ID}/v1.11.6/azure-amd64.raw.xz" + "https://factory.talos.dev/image/${SCHEMATIC_ID}//azure-amd64.raw.xz" # Decompress xz -d azure-amd64.raw.xz @@ -135,9 +128,9 @@ VHD_SIZE=$(stat -f%z azure-amd64.vhd) # macOS # Create managed disk for upload az disk create \ - --resource-group cozystack-autoscaler \ - --name talos-v1.11.6 \ - --location germanywestcentral \ + --resource-group \ + --name talos- \ + --location \ --upload-type Upload \ --upload-size-bytes $VHD_SIZE \ --sku Standard_LRS \ @@ -146,8 +139,8 @@ az disk create \ # Get SAS URL for upload SAS_URL=$(az disk grant-access \ - --resource-group cozystack-autoscaler \ - --name talos-v1.11.6 \ + --resource-group \ + --name talos- \ --access-level Write \ --duration-in-seconds 3600 \ --query accessSAS --output tsv) @@ -157,17 +150,17 @@ azcopy copy azure-amd64.vhd "$SAS_URL" --blob-type PageBlob # Revoke access az disk revoke-access \ - --resource-group cozystack-autoscaler \ - --name talos-v1.11.6 + --resource-group \ + --name talos- # Create managed image from disk az image create \ - --resource-group cozystack-autoscaler \ - --name talos-v1.11.6 \ - --location germanywestcentral \ + --resource-group \ + --name talos- \ + --location \ --os-type Linux \ --hyper-v-generation V2 \ - --source $(az disk show --resource-group cozystack-autoscaler --name talos-v1.11.6 --query id --output tsv) + --source $(az disk show --resource-group --name talos- --query id --output tsv) ``` ## Step 3: Create Talos Machine Config for Azure @@ -177,7 +170,8 @@ Create a machine config similar to the Hetzner one, with these Azure-specific ch ```yaml machine: nodeLabels: - kilo.squat.ai/location: azure # <-- changed from 'hetzner-cloud' + kilo.squat.ai/location: azure + topology.kubernetes.io/zone: azure kubelet: nodeIP: validSubnets: @@ -190,14 +184,14 @@ All other settings (cluster tokens, control plane endpoint, extensions, etc.) re ```bash IMAGE_ID=$(az image show \ - --resource-group cozystack-autoscaler \ - --name talos-v1.11.6 \ + --resource-group \ + --name talos- \ --query id --output tsv) az vmss create \ - --resource-group cozystack-autoscaler \ + --resource-group \ --name workers \ - --location germanywestcentral \ + --location \ --orchestration-mode Uniform \ --image "$IMAGE_ID" \ --vm-sku Standard_D2s_v3 \ @@ -216,82 +210,52 @@ az vmss create \ **Important notes:** - Must use `--orchestration-mode Uniform` (cluster-autoscaler requires Uniform mode) - Must use `--public-ip-per-vm` for WireGuard connectivity -- Check VM quota in your region: `az vm list-usage --location germanywestcentral` +- Check VM quota in your region: `az vm list-usage --location ` - `--custom-data` passes the Talos machine config to new instances -### Available VM sizes in Germany West Central +## Step 5: Deploy Cluster Autoscaler -| Family | Example | vCPU | RAM | Use case | -|--------|---------|------|-----|----------| -| D-series | Standard_D2s_v3 | 2 | 8 GB | General purpose | -| D-series | Standard_D4s_v3 | 4 | 16 GB | General purpose | -| NC T4 | Standard_NC4as_T4_v3 | 4 | 28 GB | GPU (T4) | -| NC A100 | Standard_NC24ads_A100_v4 | 24 | 220 GB | GPU (A100) | -| NC H100 | Standard_NC40ads_H100_v5 | 40 | 320 GB | GPU (H100) | - -## Step 5: Create Kubernetes Secrets - -### 5.1 Azure Credentials Secret - -```bash -kubectl create namespace cozy-cluster-autoscaler-azure - -kubectl create secret generic azure-credentials \ - --namespace cozy-cluster-autoscaler-azure \ - --from-literal=ClientID="" \ - --from-literal=ClientSecret="" \ - --from-literal=TenantID="" \ - --from-literal=SubscriptionID="" \ - --from-literal=ResourceGroup="cozystack-autoscaler" \ - --from-literal=VMType="vmss" -``` - -### 5.2 Talos Machine Config Secret - -```bash -kubectl create secret generic talos-config \ - --namespace cozy-cluster-autoscaler-azure \ - --from-file=cloud-init=machineconfig-azure.yaml -``` - -## Step 6: Deploy Cluster Autoscaler - -Example Package resource: +Create the Package resource: ```yaml apiVersion: cozystack.io/v1alpha1 kind: Package metadata: name: cozystack.cluster-autoscaler-azure - namespace: cozy-cluster-autoscaler-azure spec: - type: cluster-autoscaler - values: - cluster-autoscaler: - azureClientID: "" - azureClientSecret: "" - azureTenantID: "" - azureSubscriptionID: "" - azureResourceGroup: "cozystack-autoscaler" - azureVMType: "vmss" - autoscalingGroups: - - name: workers - minSize: 0 - maxSize: 10 + variant: default + components: + cluster-autoscaler-azure: + values: + cluster-autoscaler: + azureClientID: "" + azureClientSecret: "" + azureTenantID: "" + azureSubscriptionID: "" + azureResourceGroup: "" + azureVMType: "vmss" + autoscalingGroups: + - name: workers + minSize: 0 + maxSize: 10 + rbac: + additionalRules: + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - create + - get + - update ``` -Or configure the HelmRelease directly with `secretKeyRefNameOverride` to use existing secrets: - -```yaml -cluster-autoscaler: - secretKeyRefNameOverride: azure-credentials - autoscalingGroups: - - name: workers - minSize: 0 - maxSize: 10 +Apply: +```bash +kubectl apply -f package.yaml ``` -## Step 7: Kilo WireGuard Endpoint Configuration +## Step 6: Kilo WireGuard Endpoint Configuration **Important:** Azure nodes behind NAT need their public IP advertised as the WireGuard endpoint. Without this, the WireGuard tunnel between Hetzner and Azure nodes will not be established. @@ -304,7 +268,7 @@ kubectl annotate node \ ### Automated Endpoint Configuration -For automated endpoint detection, create a DaemonSet that runs on Azure nodes (`kilo.squat.ai/location=azure`) and: +For automated endpoint detection, create a DaemonSet that runs on Azure nodes (`topology.kubernetes.io/zone=azure`) and: 1. Queries Azure Instance Metadata Service (IMDS) for the public IP: ```bash @@ -321,7 +285,7 @@ This ensures new autoscaled nodes automatically get proper WireGuard connectivit ```bash # Scale up -az vmss scale --resource-group cozystack-autoscaler --name workers --new-capacity 1 +az vmss scale --resource-group --name workers --new-capacity 1 # Check node joined kubectl get nodes -o wide @@ -330,12 +294,12 @@ kubectl get nodes -o wide kubectl logs -n cozy-kilo # Scale down -az vmss scale --resource-group cozystack-autoscaler --name workers --new-capacity 0 +az vmss scale --resource-group --name workers --new-capacity 0 ``` ### Autoscaler test -Deploy a workload with anti-affinity to trigger autoscaling: +Deploy a workload to trigger autoscaling: ```yaml apiVersion: apps/v1 @@ -353,7 +317,7 @@ spec: app: test-azure spec: nodeSelector: - kilo.squat.ai/location: azure + topology.kubernetes.io/zone: azure containers: - name: pause image: registry.k8s.io/pause:3.9 @@ -368,7 +332,7 @@ spec: ### Node doesn't join cluster - Check that the Talos machine config control plane endpoint is reachable from Azure - Verify NSG rules allow outbound traffic to port 6443 -- Check VMSS instance provisioning state: `az vmss list-instances --resource-group cozystack-autoscaler --name workers` +- Check VMSS instance provisioning state: `az vmss list-instances --resource-group --name workers` ### WireGuard tunnel not established - Verify `kilo.squat.ai/force-endpoint` annotation is set with the public IP @@ -376,26 +340,10 @@ spec: - Inspect kilo logs: `kubectl logs -n cozy-kilo ` ### VM quota errors -- Check quota: `az vm list-usage --location germanywestcentral` +- Check quota: `az vm list-usage --location ` - Request quota increase via Azure portal - Try a different VM family that has available quota ### SkuNotAvailable errors - Some VM sizes may have capacity restrictions in certain regions -- Try a different VM size: `az vm list-skus --location germanywestcentral --size ` - -## Current Infrastructure Reference - -Created in subscription `cdd9c3ff-ef22-46f5-916f-cf529408f367` (Sandbox autoscaler): - -| Resource | Name | Details | -|----------|------|---------| -| Resource Group | cozystack-autoscaler | germanywestcentral | -| VNet | cozystack-vnet | 10.2.0.0/16 | -| Subnet | workers | 10.2.0.0/24 | -| NSG | cozystack-nsg | Allow UDP 51820, TCP 50000 | -| Managed Image | talos-v1.11.6 | Talos with extensions (drbd, zfs, etc.) | -| VMSS | workers | Standard_D2s_v3, Uniform, 0 instances | -| Storage Account | cozystacktalos | Used for VHD upload | - -Service Principal: `78df2235-89b3-4b94-b6db-5573677eee57` (autoscaler-contributor) +- Try a different VM size: `az vm list-skus --location --size ` diff --git a/packages/system/cluster-autoscaler/docs/hetzner.md b/packages/system/cluster-autoscaler/docs/hetzner.md index c7f0b55d..e3f1832b 100644 --- a/packages/system/cluster-autoscaler/docs/hetzner.md +++ b/packages/system/cluster-autoscaler/docs/hetzner.md @@ -100,6 +100,7 @@ machine: # Node labels (applied automatically on join) nodeLabels: kilo.squat.ai/location: hetzner-cloud + topology.kubernetes.io/zone: hetzner-cloud kubelet: image: ghcr.io/siderolabs/kubelet:v1.33.1 # Use vSwitch IP as internal IP @@ -368,6 +369,7 @@ For multi-location clusters using Kilo mesh networking, add location label to ma machine: nodeLabels: kilo.squat.ai/location: hetzner-cloud + topology.kubernetes.io/zone: hetzner-cloud ``` This allows Kilo to create proper WireGuard tunnels between your bare-metal nodes and Hetzner Cloud nodes. From ffd97e581f5fdb2de128e05a3786619f7f332611 Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Sat, 7 Feb 2026 08:02:26 +0300 Subject: [PATCH 192/889] [virtual-machine] Fix templating of backup strategy Signed-off-by: Timofei Larkin --- .../backupstrategy.yaml} | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) rename packages/system/virtual-machine-rd/{cozyrds/backup-strategy.yaml => templates/backupstrategy.yaml} (62%) diff --git a/packages/system/virtual-machine-rd/cozyrds/backup-strategy.yaml b/packages/system/virtual-machine-rd/templates/backupstrategy.yaml similarity index 62% rename from packages/system/virtual-machine-rd/cozyrds/backup-strategy.yaml rename to packages/system/virtual-machine-rd/templates/backupstrategy.yaml index b84f08e4..b9af2ef4 100644 --- a/packages/system/virtual-machine-rd/cozyrds/backup-strategy.yaml +++ b/packages/system/virtual-machine-rd/templates/backupstrategy.yaml @@ -1,3 +1,4 @@ +{{- if .Capabilities.APIVersions.Has "strategy.backups.cozystack.io/v1alpha1" }} apiVersion: strategy.backups.cozystack.io/v1alpha1 kind: Velero metadata: @@ -6,10 +7,11 @@ spec: template: spec: # see https://velero.io/docs/v1.9/api-types/backup/ includedNamespaces: - - {{ .Application.metadata.namespace }} + - '{{ printf "{{ .Application.metadata.namespace }}" }}' labelSelector: - apps.cozystack.io/application.Kind: {{ .Application.kind }} + matchLabels: + apps.cozystack.io/application.Kind: '{{ printf "{{ .Application.kind }}" }}' includedResources: - helmreleases.helm.toolkit.fluxcd.io @@ -21,12 +23,13 @@ spec: - configmaps - secrets - storageLocation: {{ .Parameters.backupStorageLocationName }} + storageLocation: '{{ printf "{{ .Parameters.backupStorageLocationName }}" }}' volumeSnapshotLocations: - - {{ .Parameters.backupStorageLocationName }} + - '{{ printf "{{ .Parameters.backupStorageLocationName }}" }}' snapshotVolumes: true snapshotMoveData: true ttl: 720h0m0s itemOperationTimeout: 24h0m0s +{{- end }} From 74a8313d6539ff4f76bf2fc0e0e509680d5867d5 Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Sat, 7 Feb 2026 08:46:39 +0300 Subject: [PATCH 193/889] [tenant,rbac] Use shared clusterroles ## What this PR does Previously a namespaced role was created per tenant and access level. Since these roles are all identical, it's sufficient to create a cluster role per access level and just create namespaced rolebindings to these cluster roles per tenant. This will enable aggregation rules. E.g. if a new API group is installed, such as backups.cozystack.io, a new clusterrole can be created for managing this API group with a label like rbac.cozystack.io/aggregate-to-admin. Smart use of aggregation rules will enable automatic granting of access rights not just to admin, but to super-admin too, and there will be no need to update every single tenant. ### Release note ```release-note [tenant,rbac] Use ClusterRoles with aggregationRules instead of roles per every tenant. ``` Signed-off-by: Timofei Larkin --- hack/e2e-apps/mongodb.bats | 2 +- packages/apps/tenant/templates/tenant.yaml | 339 +----------------- .../templates/clusterroles.yaml | 248 +++++++++++++ .../templates/dashboard-role.yaml | 15 + 4 files changed, 280 insertions(+), 324 deletions(-) create mode 100644 packages/system/cozystack-basics/templates/clusterroles.yaml create mode 100644 packages/system/cozystack-basics/templates/dashboard-role.yaml diff --git a/hack/e2e-apps/mongodb.bats b/hack/e2e-apps/mongodb.bats index 794baf63..db58b2cf 100644 --- a/hack/e2e-apps/mongodb.bats +++ b/hack/e2e-apps/mongodb.bats @@ -13,7 +13,7 @@ spec: size: 10Gi replicas: 1 storageClass: "" - resourcesPreset: "nano" + resourcesPreset: "small" users: testuser: password: xai7Wepo diff --git a/packages/apps/tenant/templates/tenant.yaml b/packages/apps/tenant/templates/tenant.yaml index 6d325a07..c26de3bd 100644 --- a/packages/apps/tenant/templates/tenant.yaml +++ b/packages/apps/tenant/templates/tenant.yaml @@ -5,38 +5,6 @@ metadata: name: {{ include "tenant.name" . }} namespace: {{ include "tenant.name" . }} --- -# == default role == -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: {{ include "tenant.name" . }} - namespace: {{ include "tenant.name" . }} -rules: -- apiGroups: [""] - resources: ["pods", "services", "persistentvolumes", "endpoints", "events", "resourcequotas"] - verbs: ["get", "list", "watch"] -- apiGroups: ["networking.k8s.io"] - resources: ["ingresses"] - verbs: ["get", "list", "watch"] -- apiGroups: ["rbac.authorization.k8s.io"] - resources: ["roles"] - verbs: ["get"] -- apiGroups: ["apps.cozystack.io"] - resources: ['*'] - verbs: ['*'] -- apiGroups: - - cozystack.io - resources: - - workloadmonitors - - workloads - verbs: ["get", "list", "watch"] -- apiGroups: - - core.cozystack.io - resources: - - tenantmodules - - tenantsecrets - verbs: ["get", "list", "watch"] ---- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: @@ -62,60 +30,11 @@ subjects: name: {{ include "tenant.name" . }} namespace: {{ include "tenant.name" . }} roleRef: - kind: Role - name: {{ include "tenant.name" . }} + kind: ClusterRole + name: cozy-tenant apiGroup: rbac.authorization.k8s.io --- -# == view role == ---- -kind: Role -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: {{ include "tenant.name" . }}-view - namespace: {{ include "tenant.name" . }} -rules: - - apiGroups: - - rbac.authorization.k8s.io - resources: - - roles - verbs: - - get - - apiGroups: - - apps.cozystack.io - resources: - - "*" - verbs: - - get - - list - - watch - - apiGroups: - - "" - resources: - - pods - - services - - persistentvolumes - - endpoints - - events - - resourcequotas - verbs: - - get - - list - - watch - - apiGroups: - - networking.k8s.io - resources: - - ingresses - verbs: - - get - - list - - watch - - apiGroups: - - cozystack.io - resources: - - workloadmonitors - - workloads - verbs: ["get", "list", "watch"] ---- +# == view role binding == kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: @@ -124,76 +43,11 @@ metadata: subjects: {{ include "cozy-lib.rbac.subjectsForTenantAndAccessLevel" (list "view" (include "tenant.name" .)) | nindent 2 }} roleRef: - kind: Role - name: {{ include "tenant.name" . }}-view + kind: ClusterRole + name: cozy-tenant-view apiGroup: rbac.authorization.k8s.io - ---- -# == use role == ---- -kind: Role -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: {{ include "tenant.name" . }}-use - namespace: {{ include "tenant.name" . }} -rules: - - apiGroups: [rbac.authorization.k8s.io] - resources: - - roles - verbs: - - get - - apiGroups: ["apps.cozystack.io"] - resources: - - "*" - verbs: - - get - - list - - watch - - apiGroups: [""] - resources: - - pods - - services - - persistentvolumes - - endpoints - - events - - resourcequotas - verbs: - - get - - list - - watch - - apiGroups: ["networking.k8s.io"] - resources: - - ingresses - verbs: - - get - - list - - watch - - apiGroups: ["subresources.kubevirt.io"] - resources: - - virtualmachineinstances/console - - virtualmachineinstances/vnc - verbs: - - get - - list - - apiGroups: ["subresources.kubevirt.io"] - resources: - - virtualmachineinstances/portforward - verbs: - - get - - update - - apiGroups: - - cozystack.io - resources: - - workloadmonitors - - workloads - verbs: ["get", "list", "watch"] - - apiGroups: - - core.cozystack.io - resources: - - tenantmodules - - tenantsecrets - verbs: ["get", "list", "watch"] --- +# == use role binding == kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: @@ -202,97 +56,11 @@ metadata: subjects: {{ include "cozy-lib.rbac.subjectsForTenantAndAccessLevel" (list "use" (include "tenant.name" .)) | nindent 2 }} roleRef: - kind: Role - name: {{ include "tenant.name" . }}-use + kind: ClusterRole + name: cozy-tenant-use apiGroup: rbac.authorization.k8s.io --- -# == admin role == ---- -kind: Role -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: {{ include "tenant.name" . }}-admin - namespace: {{ include "tenant.name" . }} -rules: - - apiGroups: [rbac.authorization.k8s.io] - resources: - - roles - verbs: - - get - - apiGroups: [""] - resources: - - pods - - services - - persistentvolumes - - endpoints - - events - - resourcequotas - verbs: - - get - - list - - watch - - delete - - apiGroups: ["kubevirt.io"] - resources: - - virtualmachines - verbs: - - get - - list - - apiGroups: ["subresources.kubevirt.io"] - resources: - - virtualmachineinstances/console - - virtualmachineinstances/vnc - verbs: - - get - - list - - apiGroups: ["subresources.kubevirt.io"] - resources: - - virtualmachineinstances/portforward - verbs: - - get - - update - - apiGroups: ["apps.cozystack.io"] - resources: - - buckets - - clickhouses - - ferretdb - - foos - - httpcaches - - kafkas - - kuberneteses - - mysqls - - natses - - postgreses - - rabbitmqs - - redises - - seaweedfses - - tcpbalancers - - virtualmachines - - vmdisks - - vminstances - - infos - - virtualprivateclouds - verbs: - - get - - list - - watch - - create - - update - - patch - - delete - - apiGroups: - - cozystack.io - resources: - - workloadmonitors - - workloads - verbs: ["get", "list", "watch"] - - apiGroups: - - core.cozystack.io - resources: - - tenantmodules - - tenantsecrets - verbs: ["get", "list", "watch"] ---- +# == admin role binding == kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: @@ -301,72 +69,11 @@ metadata: subjects: {{ include "cozy-lib.rbac.subjectsForTenantAndAccessLevel" (list "admin" (include "tenant.name" .)) | nindent 2 }} roleRef: - kind: Role - name: {{ include "tenant.name" . }}-admin + kind: ClusterRole + name: cozy-tenant-admin apiGroup: rbac.authorization.k8s.io --- -# == super admin role == ---- -kind: Role -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: {{ include "tenant.name" . }}-super-admin - namespace: {{ include "tenant.name" . }} -rules: - - apiGroups: [rbac.authorization.k8s.io] - resources: - - roles - verbs: - - get - - apiGroups: [""] - resources: - - pods - - services - - persistentvolumes - - endpoints - - events - - resourcequotas - verbs: - - get - - list - - watch - - delete - - apiGroups: ["kubevirt.io"] - resources: - - virtualmachines - verbs: - - '*' - - apiGroups: ["subresources.kubevirt.io"] - resources: - - virtualmachineinstances/console - - virtualmachineinstances/vnc - verbs: - - get - - list - - apiGroups: ["subresources.kubevirt.io"] - resources: - - virtualmachineinstances/portforward - verbs: - - get - - update - - apiGroups: ["apps.cozystack.io"] - resources: - - '*' - verbs: - - '*' - - apiGroups: - - cozystack.io - resources: - - workloadmonitors - - workloads - verbs: ["get", "list", "watch"] - - apiGroups: - - core.cozystack.io - resources: - - tenantmodules - - tenantsecrets - verbs: ["get", "list", "watch"] ---- +# == super admin role binding == kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: @@ -375,25 +82,11 @@ metadata: subjects: {{ include "cozy-lib.rbac.subjectsForTenantAndAccessLevel" (list "super-admin" (include "tenant.name" .) ) | nindent 2 }} roleRef: - kind: Role - name: {{ include "tenant.name" . }}-super-admin + kind: ClusterRole + name: cozy-tenant-super-admin apiGroup: rbac.authorization.k8s.io --- -# == dashboard role == ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: {{ include "tenant.name" . }} - namespace: cozy-public -rules: -- apiGroups: ["source.toolkit.fluxcd.io"] - resources: ["helmrepositories"] - verbs: ["get", "list"] -- apiGroups: ["source.toolkit.fluxcd.io"] - resources: ["helmcharts"] - verbs: ["get", "list"] ---- +# == dashboard role binding == apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: @@ -417,5 +110,5 @@ subjects: namespace: {{ include "tenant.name" . }} roleRef: kind: Role - name: {{ include "tenant.name" . }} + name: cozy-tenant-dashboard apiGroup: rbac.authorization.k8s.io diff --git a/packages/system/cozystack-basics/templates/clusterroles.yaml b/packages/system/cozystack-basics/templates/clusterroles.yaml new file mode 100644 index 00000000..747650fb --- /dev/null +++ b/packages/system/cozystack-basics/templates/clusterroles.yaml @@ -0,0 +1,248 @@ +--- +# == default cluster role == +# Used by tenant ServiceAccounts +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cozy-tenant +aggregationRule: + clusterRoleSelectors: + - matchLabels: + rbac.cozystack.io/aggregate-to-tenant: "true" +rules: [] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cozy-tenant-base + labels: + rbac.cozystack.io/aggregate-to-tenant: "true" +rules: +- apiGroups: [""] + resources: ["pods", "services", "persistentvolumes", "endpoints", "events", "resourcequotas"] + verbs: ["get", "list", "watch"] +- apiGroups: ["networking.k8s.io"] + resources: ["ingresses"] + verbs: ["get", "list", "watch"] +- apiGroups: ["rbac.authorization.k8s.io"] + resources: ["roles"] + verbs: ["get"] +- apiGroups: ["apps.cozystack.io"] + resources: ['*'] + verbs: ['*'] +- apiGroups: + - cozystack.io + resources: + - workloadmonitors + - workloads + verbs: ["get", "list", "watch"] +- apiGroups: + - core.cozystack.io + resources: + - tenantmodules + - tenantsecrets + verbs: ["get", "list", "watch"] +--- +# == view cluster role == +# Aggregates all roles labeled for view access +# Also aggregated into use, admin, and super-admin +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cozy-tenant-view + labels: + rbac.cozystack.io/aggregate-to-tenant-use: "true" + rbac.cozystack.io/aggregate-to-tenant-admin: "true" + rbac.cozystack.io/aggregate-to-tenant-super-admin: "true" +aggregationRule: + clusterRoleSelectors: + - matchLabels: + rbac.cozystack.io/aggregate-to-tenant-view: "true" +rules: [] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cozy-tenant-view-base + labels: + rbac.cozystack.io/aggregate-to-tenant-view: "true" +rules: +- apiGroups: + - rbac.authorization.k8s.io + resources: + - roles + verbs: + - get +- apiGroups: + - apps.cozystack.io + resources: + - "*" + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - pods + - services + - persistentvolumes + - endpoints + - events + - resourcequotas + verbs: + - get + - list + - watch +- apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - get + - list + - watch +- apiGroups: + - cozystack.io + resources: + - workloadmonitors + - workloads + verbs: ["get", "list", "watch"] +--- +# == use cluster role == +# Aggregates view + all roles labeled for use access +# Also aggregated into admin and super-admin +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cozy-tenant-use + labels: + rbac.cozystack.io/aggregate-to-tenant-admin: "true" + rbac.cozystack.io/aggregate-to-tenant-super-admin: "true" +aggregationRule: + clusterRoleSelectors: + - matchLabels: + rbac.cozystack.io/aggregate-to-tenant-use: "true" +rules: [] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cozy-tenant-use-base + labels: + rbac.cozystack.io/aggregate-to-tenant-use: "true" +rules: +- apiGroups: ["subresources.kubevirt.io"] + resources: + - virtualmachineinstances/console + - virtualmachineinstances/vnc + verbs: + - get + - list +- apiGroups: ["subresources.kubevirt.io"] + resources: + - virtualmachineinstances/portforward + verbs: + - get + - update +- apiGroups: + - core.cozystack.io + resources: + - tenantmodules + - tenantsecrets + verbs: ["get", "list", "watch"] +--- +# == admin cluster role == +# Aggregates use + all roles labeled for admin access +# Also aggregated into super-admin +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cozy-tenant-admin + labels: + rbac.cozystack.io/aggregate-to-tenant-super-admin: "true" +aggregationRule: + clusterRoleSelectors: + - matchLabels: + rbac.cozystack.io/aggregate-to-tenant-admin: "true" +rules: [] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cozy-tenant-admin-base + labels: + rbac.cozystack.io/aggregate-to-tenant-admin: "true" +rules: +- apiGroups: [""] + resources: + - pods + - services + - persistentvolumes + - endpoints + - events + - resourcequotas + verbs: + - delete +- apiGroups: ["kubevirt.io"] + resources: + - virtualmachines + verbs: + - get + - list +- apiGroups: ["apps.cozystack.io"] + resources: + - buckets + - clickhouses + - ferretdb + - foos + - httpcaches + - kafkas + - kuberneteses + - mysqls + - natses + - postgreses + - rabbitmqs + - redises + - seaweedfses + - tcpbalancers + - virtualmachines + - vmdisks + - vminstances + - infos + - virtualprivateclouds + verbs: + - create + - update + - patch + - delete +--- +# == super admin cluster role == +# Aggregates admin + all roles labeled for super-admin access +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cozy-tenant-super-admin +aggregationRule: + clusterRoleSelectors: + - matchLabels: + rbac.cozystack.io/aggregate-to-tenant-super-admin: "true" +rules: [] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cozy-tenant-super-admin-base + labels: + rbac.cozystack.io/aggregate-to-tenant-super-admin: "true" +rules: +- apiGroups: ["kubevirt.io"] + resources: + - virtualmachines + verbs: + - '*' +- apiGroups: ["apps.cozystack.io"] + resources: + - '*' + verbs: + - '*' diff --git a/packages/system/cozystack-basics/templates/dashboard-role.yaml b/packages/system/cozystack-basics/templates/dashboard-role.yaml new file mode 100644 index 00000000..8f7d3e5d --- /dev/null +++ b/packages/system/cozystack-basics/templates/dashboard-role.yaml @@ -0,0 +1,15 @@ +--- +# == dashboard role == +# Single namespaced role for tenant dashboard access to helm resources +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: cozy-tenant-dashboard + namespace: cozy-public +rules: +- apiGroups: ["source.toolkit.fluxcd.io"] + resources: ["helmrepositories"] + verbs: ["get", "list"] +- apiGroups: ["source.toolkit.fluxcd.io"] + resources: ["helmcharts"] + verbs: ["get", "list"] From 8010dc52504e56152ef7a22230003ca2dc5baf42 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Mon, 9 Feb 2026 14:21:11 +0300 Subject: [PATCH 194/889] [qdrant] Register Qdrant application in platform catalog Add PackageSource and PaaS bundle entry so the platform deploys qdrant-rd, which provides the ApplicationDefinition that registers the Qdrant Kind in the API server. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- .../platform/sources/qdrant-application.yaml | 29 +++++++++++++++++++ .../core/platform/templates/bundles/paas.yaml | 1 + 2 files changed, 30 insertions(+) create mode 100644 packages/core/platform/sources/qdrant-application.yaml diff --git a/packages/core/platform/sources/qdrant-application.yaml b/packages/core/platform/sources/qdrant-application.yaml new file mode 100644 index 00000000..6477cc37 --- /dev/null +++ b/packages/core/platform/sources/qdrant-application.yaml @@ -0,0 +1,29 @@ +--- +apiVersion: cozystack.io/v1alpha1 +kind: PackageSource +metadata: + name: cozystack.qdrant-application +spec: + sourceRef: + kind: OCIRepository + name: cozystack-packages + namespace: cozy-system + path: / + variants: + - name: default + dependsOn: + - cozystack.networking + libraries: + - name: cozy-lib + path: library/cozy-lib + components: + - name: qdrant-system + path: system/qdrant + - name: qdrant + path: apps/qdrant + libraries: ["cozy-lib"] + - name: qdrant-rd + path: system/qdrant-rd + install: + namespace: cozy-system + releaseName: qdrant-rd diff --git a/packages/core/platform/templates/bundles/paas.yaml b/packages/core/platform/templates/bundles/paas.yaml index 359f0959..8b519bf8 100644 --- a/packages/core/platform/templates/bundles/paas.yaml +++ b/packages/core/platform/templates/bundles/paas.yaml @@ -17,6 +17,7 @@ {{include "cozystack.platform.package.default" (list "cozystack.mongodb-application" $) }} {{include "cozystack.platform.package.default" (list "cozystack.nats-application" $) }} {{include "cozystack.platform.package.default" (list "cozystack.postgres-application" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.qdrant-application" $) }} {{include "cozystack.platform.package.default" (list "cozystack.rabbitmq-application" $) }} {{include "cozystack.platform.package.default" (list "cozystack.redis-application" $) }} {{- end }} From 6542ab58eb86b28691bfc55595310a3c671dd05c Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Mon, 9 Feb 2026 17:22:42 +0100 Subject: [PATCH 195/889] feat(kilo): add configurable MTU for WireGuard interface Expose the --mtu flag in values.yaml to allow overriding the default WireGuard interface MTU (1420). This is needed for environments where the underlying network has a lower MTU, such as Azure VMs (eth0 MTU 1400), to avoid packet fragmentation in the WireGuard tunnel. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- packages/system/kilo/templates/kilo.yaml | 1 + packages/system/kilo/values.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/system/kilo/templates/kilo.yaml b/packages/system/kilo/templates/kilo.yaml index 298c0400..b0561b58 100644 --- a/packages/system/kilo/templates/kilo.yaml +++ b/packages/system/kilo/templates/kilo.yaml @@ -41,6 +41,7 @@ spec: {{- with .Values.kilo.transitCIDR }} - --subnet={{ . }} {{- end }} + - --mtu={{ .Values.kilo.mtu | default 1420 }} - --internal-cidr=$(NODE_IP)/32 env: - name: NODE_NAME diff --git a/packages/system/kilo/values.yaml b/packages/system/kilo/values.yaml index 7bcfc55c..4d32420e 100644 --- a/packages/system/kilo/values.yaml +++ b/packages/system/kilo/values.yaml @@ -8,3 +8,4 @@ kilo: transitCIDR: 100.66.0.0/16 meshGranularity: location cleanUpInterface: false + mtu: 1420 From 8926283bdeb904f4227d4a06c31ff316c90779b3 Mon Sep 17 00:00:00 2001 From: Kirill Ilin Date: Mon, 9 Feb 2026 23:03:50 +0500 Subject: [PATCH 196/889] [vm] migrate to `runStrategy` instead of `running` Signed-off-by: Kirill Ilin --- packages/apps/virtual-machine/README.md | 2 +- packages/apps/virtual-machine/templates/vm.yaml | 6 +++++- packages/apps/virtual-machine/values.schema.json | 15 +++++++++++---- packages/apps/virtual-machine/values.yaml | 11 +++++++++-- .../cozyrds/virtual-machine.yaml | 4 ++-- 5 files changed, 28 insertions(+), 10 deletions(-) diff --git a/packages/apps/virtual-machine/README.md b/packages/apps/virtual-machine/README.md index dc6633f2..41f1464d 100644 --- a/packages/apps/virtual-machine/README.md +++ b/packages/apps/virtual-machine/README.md @@ -41,7 +41,7 @@ virtctl ssh @ | `external` | Enable external access from outside the cluster. | `bool` | `false` | | `externalMethod` | Method to pass through traffic to the VM. | `string` | `PortList` | | `externalPorts` | Ports to forward from outside the cluster. | `[]int` | `[22]` | -| `running` | Whether the virtual machine should be running. | `bool` | `true` | +| `runStrategy` | Requested running state of the VirtualMachineInstance | `string` | `Always` | | `instanceType` | Virtual Machine instance type. | `string` | `u1.medium` | | `instanceProfile` | Virtual Machine preferences profile. | `string` | `ubuntu` | | `systemDisk` | System disk configuration. | `object` | `{}` | diff --git a/packages/apps/virtual-machine/templates/vm.yaml b/packages/apps/virtual-machine/templates/vm.yaml index efadf3af..1a2e7a30 100644 --- a/packages/apps/virtual-machine/templates/vm.yaml +++ b/packages/apps/virtual-machine/templates/vm.yaml @@ -12,7 +12,11 @@ metadata: labels: {{- include "virtual-machine.labels" . | nindent 4 }} spec: - running: {{ .Values.running | default "true" }} + {{- if hasKey .Values "runStrategy" }} + runStrategy: {{ .Values.runStrategy }} + {{- else }} + runStrategy: {{ ternary "Always" "Halted" (.Values.running | default true) }} + {{- end }} {{- with .Values.instanceType }} instancetype: diff --git a/packages/apps/virtual-machine/values.schema.json b/packages/apps/virtual-machine/values.schema.json index e96d6449..cd2dc6e5 100644 --- a/packages/apps/virtual-machine/values.schema.json +++ b/packages/apps/virtual-machine/values.schema.json @@ -154,10 +154,17 @@ } } }, - "running": { - "description": "Whether the virtual machine should be running.", - "type": "boolean", - "default": true + "runStrategy": { + "description": "Requested running state of the VirtualMachineInstance", + "type": "string", + "default": "Always", + "enum": [ + "Always", + "Halted", + "Manual", + "RerunOnFailure", + "Once" + ] }, "sshKeys": { "description": "List of SSH public keys for authentication.", diff --git a/packages/apps/virtual-machine/values.yaml b/packages/apps/virtual-machine/values.yaml index a4bfbb53..6fa22946 100644 --- a/packages/apps/virtual-machine/values.yaml +++ b/packages/apps/virtual-machine/values.yaml @@ -39,8 +39,15 @@ externalMethod: "PortList" externalPorts: - 22 -## @param {bool} running - Whether the virtual machine should be running. -running: true +## @enum {string} RunStrategy - Requested running state of the VirtualMachineInstance +## @value Always - VMI should always be running +## @value Halted - VMI should never be running +## @value Manual - VMI can be started/stopped using API endpoints +## @value RerunOnFailure - VMI will initially be running and restarted if a failure occurs, but will not be restarted upon successful completion +## @value Once - VMI will run once and not be restarted upon completion regardless if the completion is of phase Failure or Success + +## @param {RunStrategy} runStrategy - Requested running state of the VirtualMachineInstance +runStrategy: Always ## @param {string} instanceType - Virtual Machine instance type. ## @param {string} instanceProfile - Virtual Machine preferences profile. diff --git a/packages/system/virtual-machine-rd/cozyrds/virtual-machine.yaml b/packages/system/virtual-machine-rd/cozyrds/virtual-machine.yaml index 5fdc8561..6143fa6c 100644 --- a/packages/system/virtual-machine-rd/cozyrds/virtual-machine.yaml +++ b/packages/system/virtual-machine-rd/cozyrds/virtual-machine.yaml @@ -8,7 +8,7 @@ spec: plural: virtualmachines singular: virtualmachine openAPISchema: |- - {"title":"Chart Values","type":"object","properties":{"cloudInit":{"description":"Cloud-init user data.","type":"string","default":""},"cloudInitSeed":{"description":"Seed string to generate SMBIOS UUID for the VM.","type":"string","default":""},"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"externalMethod":{"description":"Method to pass through traffic to the VM.","type":"string","default":"PortList","enum":["PortList","WholeIP"]},"externalPorts":{"description":"Ports to forward from outside the cluster.","type":"array","default":[22],"items":{"type":"integer"}},"gpus":{"description":"List of GPUs to attach.","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"name":{"description":"The name of the GPU resource to attach.","type":"string"}}}},"instanceProfile":{"description":"Virtual Machine preferences profile.","type":"string","default":"ubuntu","enum":["alpine","centos.7","centos.7.desktop","centos.stream10","centos.stream10.desktop","centos.stream8","centos.stream8.desktop","centos.stream8.dpdk","centos.stream9","centos.stream9.desktop","centos.stream9.dpdk","cirros","fedora","fedora.arm64","opensuse.leap","opensuse.tumbleweed","rhel.10","rhel.10.arm64","rhel.7","rhel.7.desktop","rhel.8","rhel.8.desktop","rhel.8.dpdk","rhel.9","rhel.9.arm64","rhel.9.desktop","rhel.9.dpdk","rhel.9.realtime","sles","ubuntu","windows.10","windows.10.virtio","windows.11","windows.11.virtio","windows.2k16","windows.2k16.virtio","windows.2k19","windows.2k19.virtio","windows.2k22","windows.2k22.virtio","windows.2k25","windows.2k25.virtio",""]},"instanceType":{"description":"Virtual Machine instance type.","type":"string","default":"u1.medium"},"resources":{"description":"Resource configuration for the virtual machine.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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},"sockets":{"description":"Number of CPU sockets (vCPU topology).","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}}},"running":{"description":"Whether the virtual machine should be running.","type":"boolean","default":true},"sshKeys":{"description":"List of SSH public keys for authentication.","type":"array","default":[],"items":{"type":"string"}},"subnets":{"description":"Additional subnets","type":"array","default":[],"items":{"type":"object","properties":{"name":{"description":"Subnet name","type":"string"}}}},"systemDisk":{"description":"System disk configuration.","type":"object","default":{},"required":["image","storage"],"properties":{"image":{"description":"The base image for the virtual machine.","type":"string","default":"ubuntu","enum":["ubuntu","cirros","alpine","fedora","talos"]},"storage":{"description":"The size of the disk allocated for the virtual machine.","type":"string","default":"5Gi"},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":"replicated"}}}}} + {"title":"Chart Values","type":"object","properties":{"cloudInit":{"description":"Cloud-init user data.","type":"string","default":""},"cloudInitSeed":{"description":"Seed string to generate SMBIOS UUID for the VM.","type":"string","default":""},"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"externalMethod":{"description":"Method to pass through traffic to the VM.","type":"string","default":"PortList","enum":["PortList","WholeIP"]},"externalPorts":{"description":"Ports to forward from outside the cluster.","type":"array","default":[22],"items":{"type":"integer"}},"gpus":{"description":"List of GPUs to attach.","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"name":{"description":"The name of the GPU resource to attach.","type":"string"}}}},"instanceProfile":{"description":"Virtual Machine preferences profile.","type":"string","default":"ubuntu","enum":["alpine","centos.7","centos.7.desktop","centos.stream10","centos.stream10.desktop","centos.stream8","centos.stream8.desktop","centos.stream8.dpdk","centos.stream9","centos.stream9.desktop","centos.stream9.dpdk","cirros","fedora","fedora.arm64","opensuse.leap","opensuse.tumbleweed","rhel.10","rhel.10.arm64","rhel.7","rhel.7.desktop","rhel.8","rhel.8.desktop","rhel.8.dpdk","rhel.9","rhel.9.arm64","rhel.9.desktop","rhel.9.dpdk","rhel.9.realtime","sles","ubuntu","windows.10","windows.10.virtio","windows.11","windows.11.virtio","windows.2k16","windows.2k16.virtio","windows.2k19","windows.2k19.virtio","windows.2k22","windows.2k22.virtio","windows.2k25","windows.2k25.virtio",""]},"instanceType":{"description":"Virtual Machine instance type.","type":"string","default":"u1.medium"},"resources":{"description":"Resource configuration for the virtual machine.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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},"sockets":{"description":"Number of CPU sockets (vCPU topology).","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}}},"runStrategy":{"description":"Requested running state of the VirtualMachineInstance","type":"string","default":"Always","enum":["Always","Halted","Manual","RerunOnFailure","Once"]},"sshKeys":{"description":"List of SSH public keys for authentication.","type":"array","default":[],"items":{"type":"string"}},"subnets":{"description":"Additional subnets","type":"array","default":[],"items":{"type":"object","properties":{"name":{"description":"Subnet name","type":"string"}}}},"systemDisk":{"description":"System disk configuration.","type":"object","default":{},"required":["image","storage"],"properties":{"image":{"description":"The base image for the virtual machine.","type":"string","default":"ubuntu","enum":["ubuntu","cirros","alpine","fedora","talos"]},"storage":{"description":"The size of the disk allocated for the virtual machine.","type":"string","default":"5Gi"},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":"replicated"}}}}} release: prefix: virtual-machine- labels: @@ -26,7 +26,7 @@ spec: tags: - compute icon: PHN2ZyB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgdmlld0JveD0iMCAwIDE0NCAxNDQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNDQiIGhlaWdodD0iMTQ0IiByeD0iMjQiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcl82ODdfMzQ1NCkiLz4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzY4N18zNDU0KSI+CjxwYXRoIGQ9Ik04OS41MDM5IDExMS43MDdINTQuNDk3QzU0LjE3MjcgMTExLjcwNyA1NC4wMTA4IDExMS4yMjEgNTQuMzM0OSAxMTEuMDU5TDU3LjI1MjIgMTA4Ljk1MkM2MC4zMzE0IDEwNi42ODMgNjEuOTUyMiAxMDIuNjMxIDYwLjk3OTcgOTguNzQxMkg4My4wMjFDODIuMDQ4NSAxMDIuNjMxIDgzLjY2OTMgMTA2LjY4MyA4Ni43NDg1IDEwOC45NTJMODkuNjY1OCAxMTEuMDU5Qzg5Ljk5IDExMS4yMjEgODkuODI3OSAxMTEuNzA3IDg5LjUwMzkgMTExLjcwN1oiIGZpbGw9IiNCMEI2QkIiLz4KPHBhdGggZD0iTTExMy4zMjggOTguNzQxSDMwLjY3MjVDMjcuNTkzMSA5OC43NDEgMjUgOTYuMTQ4IDI1IDkzLjA2ODdWMzMuMTAzMkMyNSAzMC4wMjM5IDI3LjU5MzEgMjcuNDMwNyAzMC42NzI1IDI3LjQzMDdIMTEzLjMyOEMxMTYuNDA3IDI3LjQzMDcgMTE5IDMwLjAyMzcgMTE5IDMzLjEwMzJWOTMuMDY4N0MxMTkgOTYuMTQ4IDExNi40MDcgOTguNzQxIDExMy4zMjggOTguNzQxWiIgZmlsbD0iI0U4RURFRSIvPgo8cGF0aCBkPSJNMTE5IDg0LjE1NDlIMjVWMzMuMTAzMkMyNSAzMC4wMjM5IDI3LjU5MzEgMjcuNDMwNyAzMC42NzI1IDI3LjQzMDdIMTEzLjMyOEMxMTYuNDA3IDI3LjQzMDcgMTE5IDMwLjAyMzcgMTE5IDMzLjEwMzJMMTE5IDg0LjE1NDlaIiBmaWxsPSIjMDBCM0ZGIi8+CjxwYXRoIGQ9Ik05MC42Mzc0IDExNi41NjlINTMuMzYxNkM1Mi4wNjUxIDExNi41NjkgNTAuOTMwNyAxMTUuNDM1IDUwLjkzMDcgMTE0LjEzOEM1MC45MzA3IDExMi44NDEgNTIuMDY1MSAxMTEuNzA3IDUzLjM2MTYgMTExLjcwN0g5MC42Mzc0QzkxLjkzMzkgMTExLjcwNyA5My4wNjg0IDExMi44NDEgOTMuMDY4NCAxMTQuMTM4QzkzLjA2ODQgMTE1LjQzNSA5MS45MzM5IDExNi41NjkgOTAuNjM3NCAxMTYuNTY5WiIgZmlsbD0iI0U4RURFRSIvPgo8L2c+CjxwYXRoIGQ9Ik03Mi41Mjc1IDUzLjgzNjdDNzIuNDQzMSA1My44MzUxIDcyLjM2MDUgNTMuODEyMiA3Mi4yODczIDUzLjc3MDFMNTYuNDY5OSA0NC43OTM0QzU2LjM5ODMgNDQuNzUxOSA1Ni4zMzg4IDQ0LjY5MjMgNTYuMjk3MyA0NC42MjA3QzU2LjI1NTkgNDQuNTQ5IDU2LjIzMzggNDQuNDY3OCA1Ni4yMzM0IDQ0LjM4NUM1Ni4yMzM0IDQ0LjIxNjkgNTYuMzI1OCA0NC4wNjE3IDU2LjQ2OTkgNDMuOTc4NUw3Mi4xOTEyIDM1LjA2MDlDNzIuMjYzNyAzNS4wMjEgNzIuMzQ1IDM1IDcyLjQyNzcgMzVDNzIuNTEwNSAzNSA3Mi41OTE4IDM1LjAyMSA3Mi42NjQzIDM1LjA2MDlMODguNDg3MiA0NC4wMzk1Qzg4LjU1OTEgNDQuMDgwMSA4OC42MTg4IDQ0LjEzOTIgODguNjYgNDQuMjEwN0M4OC43MDEzIDQ0LjI4MjIgODguNzIyNyA0NC4zNjM1IDg4LjcyMTkgNDQuNDQ2Qzg4LjcyMjUgNDQuNTI4NSA4OC43MDEgNDQuNjA5NyA4OC42NTk4IDQ0LjY4MTJDODguNjE4NSA0NC43NTI2IDg4LjU1ODkgNDQuODExOCA4OC40ODcyIDQ0Ljg1MjVMNzIuNzcxNCA1My43NjgzQzcyLjY5NzIgNTMuODExNCA3Mi42MTMzIDUzLjgzNDkgNzIuNTI3NSA1My44MzY3IiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBvcGFjaXR5PSIwLjciIGQ9Ik03MC4yNTUzIDc1LjY1MTdDNzAuMTcxIDc1LjY1MzUgNzAuMDg3OCA3NS42MzE3IDcwLjAxNTEgNzUuNTg4OEw1NC4yNDU4IDY2LjY0MTdDNTQuMTcxNSA2Ni42MDI0IDU0LjEwOTUgNjYuNTQzNiA1NC4wNjYxIDY2LjQ3MTZDNTQuMDIyOCA2Ni4zOTk3IDU0IDY2LjMxNzMgNTQgNjYuMjMzM1Y0OC4yNzhDNTQgNDguMTA4IDU0LjA5MjQgNDcuOTU0NiA1NC4yNDM5IDQ3Ljg2OTZDNTQuMzE3MiA0Ny44MjcxIDU0LjQwMDQgNDcuODA0NyA1NC40ODUxIDQ3LjgwNDdDNTQuNTY5NyA0Ny44MDQ3IDU0LjY1MjkgNDcuODI3MSA1NC43MjYyIDQ3Ljg2OTZMNzAuNDkzNyA1Ni44MTMxQzcwLjU2NDIgNTYuODU2NSA3MC42MjI1IDU2LjkxNyA3MC42NjMyIDU2Ljk4OTFDNzAuNzAzOSA1Ny4wNjEyIDcwLjcyNTcgNTcuMTQyNCA3MC43MjY1IDU3LjIyNTFWNzUuMTgwNUM3MC43MjU5IDc1LjI2MjggNzAuNzA0MiA3NS4zNDM2IDcwLjY2MzUgNzUuNDE1MUM3MC42MjI3IDc1LjQ4NjYgNzAuNTY0MiA3NS41NDY0IDcwLjQ5MzcgNzUuNTg4OEM3MC40MjA2IDc1LjYyOTEgNzAuMzM4NyA3NS42NTA3IDcwLjI1NTMgNzUuNjUxNyIgZmlsbD0id2hpdGUiLz4KPHBhdGggb3BhY2l0eT0iMC40IiBkPSJNNzQuNzE5OCA3NS42NTExQzc0LjYzMzMgNzUuNjUxMiA3NC41NDgyIDc1LjYyOTYgNzQuNDcyMiA3NS41ODgzQzc0LjQwMTYgNzUuNTQ2MSA3NC4zNDMyIDc1LjQ4NjIgNzQuMzAyNyA3NS40MTQ3Qzc0LjI2MjMgNzUuMzQzMSA3NC4yNDExIDc1LjI2MjIgNzQuMjQxMiA3NS4xOFY1Ny4zMzczQzc0LjI0MTIgNTcuMTcxIDc0LjMzMzYgNTcuMDE1OCA3NC40NzIyIDU2LjkyOUw5MC4yMzk3IDQ3Ljk4NTVDOTAuMzExOSA0Ny45NDM4IDkwLjM5MzggNDcuOTIxOSA5MC40NzcxIDQ3LjkyMTlDOTAuNTYwNSA0Ny45MjE5IDkwLjY0MjQgNDcuOTQzOCA5MC43MTQ2IDQ3Ljk4NTVDOTAuNzg3NiA0OC4wMjU1IDkwLjg0ODUgNDguMDg0MiA5MC44OTExIDQ4LjE1NTdDOTAuOTMzNyA0OC4yMjcyIDkwLjk1NjMgNDguMzA4OCA5MC45NTY2IDQ4LjM5MlY2Ni4yMzI4QzkwLjk1NyA2Ni4zMTY0IDkwLjkzNDcgNjYuMzk4NSA5MC44OTIxIDY2LjQ3MDRDOTAuODQ5NSA2Ni41NDI0IDkwLjc4ODEgNjYuNjAxNCA5MC43MTQ2IDY2LjY0MTFMNzQuOTUyNiA3NS41ODgzQzc0Ljg4MjUgNzUuNjMwNyA3NC44MDE4IDc1LjY1MjUgNzQuNzE5OCA3NS42NTExIiBmaWxsPSJ3aGl0ZSIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyXzY4N18zNDU0IiB4MT0iMTYxIiB5MT0iMTgwIiB4Mj0iMy41OTI4NGUtMDciIHkyPSI0Ljk5OTk4IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNTk1NjU2Ii8+CjwvbGluZWFyR3JhZGllbnQ+CjxjbGlwUGF0aCBpZD0iY2xpcDBfNjg3XzM0NTQiPgo8cmVjdCB3aWR0aD0iOTQiIGhlaWdodD0iOTQiIGZpbGw9IndoaXRlIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyNSAyNSkiLz4KPC9jbGlwUGF0aD4KPC9kZWZzPgo8L3N2Zz4K - keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "external"], ["spec", "externalMethod"], ["spec", "externalPorts"], ["spec", "running"], ["spec", "instanceType"], ["spec", "instanceProfile"], ["spec", "systemDisk"], ["spec", "systemDisk", "image"], ["spec", "systemDisk", "storage"], ["spec", "systemDisk", "storageClass"], ["spec", "subnets"], ["spec", "gpus"], ["spec", "resources"], ["spec", "sshKeys"], ["spec", "cloudInit"], ["spec", "cloudInitSeed"]] + keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "external"], ["spec", "externalMethod"], ["spec", "externalPorts"], ["spec", "runStrategy"], ["spec", "instanceType"], ["spec", "instanceProfile"], ["spec", "systemDisk"], ["spec", "systemDisk", "image"], ["spec", "systemDisk", "storage"], ["spec", "systemDisk", "storageClass"], ["spec", "subnets"], ["spec", "gpus"], ["spec", "resources"], ["spec", "sshKeys"], ["spec", "cloudInit"], ["spec", "cloudInitSeed"]] secrets: exclude: [] include: [] From 4c9b1d5263df5df22b240f7e6b9c3774614d7ad1 Mon Sep 17 00:00:00 2001 From: IvanHunters Date: Tue, 10 Feb 2026 00:41:05 +0300 Subject: [PATCH 197/889] [keycloak-configure] Use internal URL and skip TLS verification Switch ClusterKeycloak to use internal service URL instead of external ingress, and enable insecureSkipVerify for environments with self-signed certificates. Signed-off-by: IvanHunters --- packages/system/dashboard/templates/gatekeeper.yaml | 1 + packages/system/keycloak-configure/templates/configure-kk.yaml | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/system/dashboard/templates/gatekeeper.yaml b/packages/system/dashboard/templates/gatekeeper.yaml index d87c2deb..34e727f0 100644 --- a/packages/system/dashboard/templates/gatekeeper.yaml +++ b/packages/system/dashboard/templates/gatekeeper.yaml @@ -65,6 +65,7 @@ spec: - --cookie-secret=$(OAUTH2_PROXY_COOKIE_SECRET) - --skip-provider-button - --scope=openid email profile offline_access + - --ssl-insecure-skip-verify=true env: - name: OAUTH2_PROXY_CLIENT_ID value: dashboard diff --git a/packages/system/keycloak-configure/templates/configure-kk.yaml b/packages/system/keycloak-configure/templates/configure-kk.yaml index 29afc6c0..a641fd8a 100644 --- a/packages/system/keycloak-configure/templates/configure-kk.yaml +++ b/packages/system/keycloak-configure/templates/configure-kk.yaml @@ -14,7 +14,8 @@ metadata: namespace: {{ .Release.Namespace }} spec: secret: keycloak-credentials - url: https://keycloak.{{ $host }} + url: http://keycloak-http.cozy-keycloak.svc:8080 + insecureSkipVerify: true --- From d82c4d46c5da4a830d977c425f2e7c2bb35743cd Mon Sep 17 00:00:00 2001 From: IvanHunters Date: Tue, 10 Feb 2026 00:48:51 +0300 Subject: [PATCH 198/889] [keycloak-configure] Fix kubernetes client creation Remove authorizationServicesEnabled as it's incompatible with public clients (requires service account which public clients don't have). Signed-off-by: IvanHunters --- packages/system/keycloak-configure/templates/configure-kk.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/system/keycloak-configure/templates/configure-kk.yaml b/packages/system/keycloak-configure/templates/configure-kk.yaml index a641fd8a..d2ed13d5 100644 --- a/packages/system/keycloak-configure/templates/configure-kk.yaml +++ b/packages/system/keycloak-configure/templates/configure-kk.yaml @@ -73,7 +73,6 @@ metadata: name: keycloakclient spec: advancedProtocolMappers: true - authorizationServicesEnabled: true clientId: kubernetes defaultClientScopes: - groups From cf2c6bc15f138f9621b2135054a7136f84564d9e Mon Sep 17 00:00:00 2001 From: Kirill Ilin Date: Tue, 10 Feb 2026 13:00:33 +0500 Subject: [PATCH 199/889] [vm] allow switching between instancetype and custom resources Implemented by upgrade hook atomically patching VM resource Signed-off-by: Kirill Ilin --- .../virtual-machine/templates/_helpers.tpl | 23 ++++++++++++++ .../templates/vm-update-hook.yaml | 31 +++++++++++++++++-- .../apps/virtual-machine/templates/vm.yaml | 13 +++----- 3 files changed, 56 insertions(+), 11 deletions(-) diff --git a/packages/apps/virtual-machine/templates/_helpers.tpl b/packages/apps/virtual-machine/templates/_helpers.tpl index e65cad9d..ddea90fe 100644 --- a/packages/apps/virtual-machine/templates/_helpers.tpl +++ b/packages/apps/virtual-machine/templates/_helpers.tpl @@ -70,6 +70,29 @@ Generate a stable UUID for cloud-init re-initialization upon upgrade. {{- $uuid }} {{- end }} +{{/* +Domain resources (cpu, memory) as a JSON object. +Used in vm.yaml for rendering and in the update hook for merge patches. +*/}} +{{- define "virtual-machine.domainResources" -}} +{{- $result := dict -}} +{{- if or .Values.cpuModel (and .Values.resources .Values.resources.cpu .Values.resources.sockets) -}} + {{- $cpu := dict -}} + {{- if and .Values.resources .Values.resources.cpu .Values.resources.sockets -}} + {{- $_ := set $cpu "cores" (.Values.resources.cpu | int64) -}} + {{- $_ := set $cpu "sockets" (.Values.resources.sockets | int64) -}} + {{- end -}} + {{- if .Values.cpuModel -}} + {{- $_ := set $cpu "model" .Values.cpuModel -}} + {{- end -}} + {{- $_ := set $result "cpu" $cpu -}} +{{- end -}} +{{- if and .Values.resources .Values.resources.memory -}} + {{- $_ := set $result "resources" (dict "requests" (dict "memory" .Values.resources.memory)) -}} +{{- end -}} +{{- $result | toJson -}} +{{- end -}} + {{/* Node Affinity for Windows VMs */}} diff --git a/packages/apps/virtual-machine/templates/vm-update-hook.yaml b/packages/apps/virtual-machine/templates/vm-update-hook.yaml index 3c7d7d34..b1223150 100644 --- a/packages/apps/virtual-machine/templates/vm-update-hook.yaml +++ b/packages/apps/virtual-machine/templates/vm-update-hook.yaml @@ -14,14 +14,25 @@ {{- $needUpdateProfile := false -}} {{- $needResizePVC := false -}} {{- $needRecreateService := false -}} +{{- $needRemoveInstanceType := false -}} +{{- $needRemoveCustomResources := false -}} -{{- if and $existingVM $instanceType -}} +{{- $existingHasInstanceType := false -}} +{{- if and $existingVM $existingVM.spec.instancetype -}} + {{- $existingHasInstanceType = true -}} +{{- end -}} + +{{- if and $existingHasInstanceType (not $instanceType) -}} + {{- $needRemoveInstanceType = true -}} +{{- else if and $existingHasInstanceType $instanceType -}} {{- if not (eq $existingVM.spec.instancetype.name $instanceType) -}} {{- $needUpdateType = true -}} {{- end -}} +{{- else if and $existingVM (not $existingHasInstanceType) $instanceType -}} + {{- $needRemoveCustomResources = true -}} {{- end -}} -{{- if and $existingVM $instanceProfile -}} +{{- if and $existingVM $existingVM.spec.preference $instanceProfile -}} {{- if not (eq $existingVM.spec.preference.name $instanceProfile) -}} {{- $needUpdateProfile = true -}} {{- end -}} @@ -45,7 +56,7 @@ {{- end -}} {{- end -}} -{{- if or $needUpdateType $needUpdateProfile $needResizePVC $needRecreateService }} +{{- if or $needUpdateType $needUpdateProfile $needResizePVC $needRecreateService $needRemoveInstanceType $needRemoveCustomResources }} apiVersion: batch/v1 kind: Job metadata: @@ -90,6 +101,20 @@ spec: -p '{"spec":{"preference":{"name": "{{ $instanceProfile }}", "revisionName": null}}}' {{- end }} + {{- if $needRemoveInstanceType }} + echo "Removing instancetype from VM (switching to custom resources)..." + kubectl patch virtualmachines.kubevirt.io {{ $vmName }} -n {{ $namespace }} \ + --type merge \ + -p '{"spec":{"instancetype":null{{- if not $instanceProfile }},"preference":null{{- end }},"template":{"spec":{"domain":{{ include "virtual-machine.domainResources" . }}}}}}' + {{- end }} + + {{- if $needRemoveCustomResources }} + echo "Removing custom CPU/memory from domain (switching to instancetype)..." + kubectl patch virtualmachines.kubevirt.io {{ $vmName }} -n {{ $namespace }} \ + --type merge \ + -p '{"spec":{"instancetype":{"name":"{{ $instanceType }}","revisionName":null},"template":{"spec":{"domain":{"cpu":null,"resources":null}}}}}' + {{- end }} + {{- if $needResizePVC }} echo "Patching PVC for storage resize..." kubectl patch pvc {{ $vmName }} -n {{ $namespace }} \ diff --git a/packages/apps/virtual-machine/templates/vm.yaml b/packages/apps/virtual-machine/templates/vm.yaml index 1a2e7a30..35f0dae9 100644 --- a/packages/apps/virtual-machine/templates/vm.yaml +++ b/packages/apps/virtual-machine/templates/vm.yaml @@ -71,15 +71,12 @@ spec: {{- include "virtual-machine.labels" . | nindent 8 }} spec: domain: - {{- if and .Values.resources .Values.resources.cpu .Values.resources.sockets }} - cpu: - cores: {{ .Values.resources.cpu }} - sockets: {{ .Values.resources.sockets }} + {{- $domainRes := include "virtual-machine.domainResources" . | fromJson -}} + {{- with $domainRes.cpu }} + cpu: {{- . | toYaml | nindent 10 }} {{- end }} - {{- if and .Values.resources .Values.resources.memory }} - resources: - requests: - memory: {{ .Values.resources.memory | quote }} + {{- with $domainRes.resources }} + resources: {{- . | toYaml | nindent 10 }} {{- end }} firmware: uuid: {{ include "virtual-machine.stableUuid" . }} From 13d848efc3530407cf9c66c1247223e85b9b7d16 Mon Sep 17 00:00:00 2001 From: Kirill Ilin Date: Tue, 10 Feb 2026 13:36:49 +0500 Subject: [PATCH 200/889] [vm] add validation for resources Signed-off-by: Kirill Ilin --- packages/apps/virtual-machine/templates/vm-update-hook.yaml | 6 +----- packages/apps/virtual-machine/templates/vm.yaml | 3 +++ 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/apps/virtual-machine/templates/vm-update-hook.yaml b/packages/apps/virtual-machine/templates/vm-update-hook.yaml index b1223150..65ee02f0 100644 --- a/packages/apps/virtual-machine/templates/vm-update-hook.yaml +++ b/packages/apps/virtual-machine/templates/vm-update-hook.yaml @@ -17,11 +17,7 @@ {{- $needRemoveInstanceType := false -}} {{- $needRemoveCustomResources := false -}} -{{- $existingHasInstanceType := false -}} -{{- if and $existingVM $existingVM.spec.instancetype -}} - {{- $existingHasInstanceType = true -}} -{{- end -}} - +{{- $existingHasInstanceType := and $existingVM $existingVM.spec.instancetype -}} {{- if and $existingHasInstanceType (not $instanceType) -}} {{- $needRemoveInstanceType = true -}} {{- else if and $existingHasInstanceType $instanceType -}} diff --git a/packages/apps/virtual-machine/templates/vm.yaml b/packages/apps/virtual-machine/templates/vm.yaml index 35f0dae9..c1b67344 100644 --- a/packages/apps/virtual-machine/templates/vm.yaml +++ b/packages/apps/virtual-machine/templates/vm.yaml @@ -4,6 +4,9 @@ {{- if and .Values.instanceProfile (not (lookup "instancetype.kubevirt.io/v1beta1" "VirtualMachineClusterPreference" "" .Values.instanceProfile)) }} {{- fail (printf "Specified profile not exists in cluster: %s" .Values.instanceProfile) }} {{- end }} +{{- if and (not .Values.instanceType) (not (and .Values.resources .Values.resources.cpu .Values.resources.sockets .Values.resources.memory)) }} +{{- fail "Either instanceType or resources (cpu, sockets, memory) must be specified" }} +{{- end }} apiVersion: kubevirt.io/v1 kind: VirtualMachine From 8144b8232e5661b1f4fb3bb39bc3cd0d94f2e626 Mon Sep 17 00:00:00 2001 From: IvanHunters Date: Tue, 10 Feb 2026 14:37:25 +0300 Subject: [PATCH 201/889] fix(dashboard): make ssl-insecure-skip-verify configurable Add authentication.oidc.insecureSkipVerify option to platform chart with default=false. The flag is now conditionally included in oauth2-proxy args only when explicitly enabled. Signed-off-by: IvanHunters --- packages/core/platform/templates/apps.yaml | 1 + packages/core/platform/values.yaml | 1 + packages/system/dashboard/templates/gatekeeper.yaml | 3 +++ 3 files changed, 5 insertions(+) diff --git a/packages/core/platform/templates/apps.yaml b/packages/core/platform/templates/apps.yaml index ee3b35cf..8484fe4c 100644 --- a/packages/core/platform/templates/apps.yaml +++ b/packages/core/platform/templates/apps.yaml @@ -23,6 +23,7 @@ stringData: bundle-name: {{ .Values.bundles.system.variant | quote }} clusterissuer: {{ .Values.publishing.certificates.issuerType | quote }} oidc-enabled: {{ .Values.authentication.oidc.enabled | quote }} + oidc-insecure-skip-verify: {{ .Values.authentication.oidc.insecureSkipVerify | quote }} extra-keycloak-redirect-uri-for-dashboard: {{ index .Values.authentication.oidc.keycloakExtraRedirectUri | quote }} expose-services: {{ .Values.publishing.exposedServices | join "," | quote }} expose-ingress: {{ .Values.publishing.ingressName | quote }} diff --git a/packages/core/platform/values.yaml b/packages/core/platform/values.yaml index 4709b82e..f9ce672d 100644 --- a/packages/core/platform/values.yaml +++ b/packages/core/platform/values.yaml @@ -51,6 +51,7 @@ publishing: authentication: oidc: enabled: false + insecureSkipVerify: false keycloakExtraRedirectUri: "" # Pod scheduling configuration scheduling: diff --git a/packages/system/dashboard/templates/gatekeeper.yaml b/packages/system/dashboard/templates/gatekeeper.yaml index 34e727f0..23427239 100644 --- a/packages/system/dashboard/templates/gatekeeper.yaml +++ b/packages/system/dashboard/templates/gatekeeper.yaml @@ -1,5 +1,6 @@ {{- $host := index .Values._cluster "root-host" }} {{- $oidcEnabled := index .Values._cluster "oidc-enabled" }} +{{- $oidcInsecureSkipVerify := index .Values._cluster "oidc-insecure-skip-verify" }} apiVersion: apps/v1 kind: Deployment @@ -65,7 +66,9 @@ spec: - --cookie-secret=$(OAUTH2_PROXY_COOKIE_SECRET) - --skip-provider-button - --scope=openid email profile offline_access + {{- if eq $oidcInsecureSkipVerify "true" }} - --ssl-insecure-skip-verify=true + {{- end }} env: - name: OAUTH2_PROXY_CLIENT_ID value: dashboard From 0d27d3a034dca793c69381f95a172314f30bfd25 Mon Sep 17 00:00:00 2001 From: Kirill Ilin Date: Tue, 10 Feb 2026 10:10:08 +0500 Subject: [PATCH 202/889] [vm] add cpuModel field to specify cpu model without instanceType Signed-off-by: Kirill Ilin --- packages/apps/virtual-machine/README.md | 47 ++++++++++--------- .../apps/virtual-machine/values.schema.json | 5 ++ packages/apps/virtual-machine/values.yaml | 3 ++ .../cozyrds/virtual-machine.yaml | 4 +- 4 files changed, 34 insertions(+), 25 deletions(-) diff --git a/packages/apps/virtual-machine/README.md b/packages/apps/virtual-machine/README.md index 41f1464d..c7cfe788 100644 --- a/packages/apps/virtual-machine/README.md +++ b/packages/apps/virtual-machine/README.md @@ -36,29 +36,30 @@ virtctl ssh @ ### Common parameters -| Name | Description | Type | Value | -| ------------------------- | ------------------------------------------------------- | ---------- | ------------ | -| `external` | Enable external access from outside the cluster. | `bool` | `false` | -| `externalMethod` | Method to pass through traffic to the VM. | `string` | `PortList` | -| `externalPorts` | Ports to forward from outside the cluster. | `[]int` | `[22]` | -| `runStrategy` | Requested running state of the VirtualMachineInstance | `string` | `Always` | -| `instanceType` | Virtual Machine instance type. | `string` | `u1.medium` | -| `instanceProfile` | Virtual Machine preferences profile. | `string` | `ubuntu` | -| `systemDisk` | System disk configuration. | `object` | `{}` | -| `systemDisk.image` | The base image for the virtual machine. | `string` | `ubuntu` | -| `systemDisk.storage` | The size of the disk allocated for the virtual machine. | `string` | `5Gi` | -| `systemDisk.storageClass` | StorageClass used to store the data. | `string` | `replicated` | -| `subnets` | Additional subnets | `[]object` | `[]` | -| `subnets[i].name` | Subnet name | `string` | `""` | -| `gpus` | List of GPUs to attach. | `[]object` | `[]` | -| `gpus[i].name` | The name of the GPU resource to attach. | `string` | `""` | -| `resources` | Resource configuration for the virtual machine. | `object` | `{}` | -| `resources.cpu` | Number of CPU cores allocated. | `quantity` | `""` | -| `resources.sockets` | Number of CPU sockets (vCPU topology). | `quantity` | `""` | -| `resources.memory` | Amount of memory allocated. | `quantity` | `""` | -| `sshKeys` | List of SSH public keys for authentication. | `[]string` | `[]` | -| `cloudInit` | Cloud-init user data. | `string` | `""` | -| `cloudInitSeed` | Seed string to generate SMBIOS UUID for the VM. | `string` | `""` | +| Name | Description | Type | Value | +| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | ---------- | ------------ | +| `external` | Enable external access from outside the cluster. | `bool` | `false` | +| `externalMethod` | Method to pass through traffic to the VM. | `string` | `PortList` | +| `externalPorts` | Ports to forward from outside the cluster. | `[]int` | `[22]` | +| `runStrategy` | Requested running state of the VirtualMachineInstance | `string` | `Always` | +| `instanceType` | Virtual Machine instance type. | `string` | `u1.medium` | +| `instanceProfile` | Virtual Machine preferences profile. | `string` | `ubuntu` | +| `systemDisk` | System disk configuration. | `object` | `{}` | +| `systemDisk.image` | The base image for the virtual machine. | `string` | `ubuntu` | +| `systemDisk.storage` | The size of the disk allocated for the virtual machine. | `string` | `5Gi` | +| `systemDisk.storageClass` | StorageClass used to store the data. | `string` | `replicated` | +| `subnets` | Additional subnets | `[]object` | `[]` | +| `subnets[i].name` | Subnet name | `string` | `""` | +| `gpus` | List of GPUs to attach. | `[]object` | `[]` | +| `gpus[i].name` | The name of the GPU resource to attach. | `string` | `""` | +| `cpuModel` | Model specifies the CPU model inside the VMI. List of available models https://github.com/libvirt/libvirt/tree/master/src/cpu_map | `string` | `""` | +| `resources` | Resource configuration for the virtual machine. | `object` | `{}` | +| `resources.cpu` | Number of CPU cores allocated. | `quantity` | `""` | +| `resources.sockets` | Number of CPU sockets (vCPU topology). | `quantity` | `""` | +| `resources.memory` | Amount of memory allocated. | `quantity` | `""` | +| `sshKeys` | List of SSH public keys for authentication. | `[]string` | `[]` | +| `cloudInit` | Cloud-init user data. | `string` | `""` | +| `cloudInitSeed` | Seed string to generate SMBIOS UUID for the VM. | `string` | `""` | ## U Series diff --git a/packages/apps/virtual-machine/values.schema.json b/packages/apps/virtual-machine/values.schema.json index cd2dc6e5..3cd6c226 100644 --- a/packages/apps/virtual-machine/values.schema.json +++ b/packages/apps/virtual-machine/values.schema.json @@ -12,6 +12,11 @@ "type": "string", "default": "" }, + "cpuModel": { + "description": "Model specifies the CPU model inside the VMI. List of available models https://github.com/libvirt/libvirt/tree/master/src/cpu_map", + "type": "string", + "default": "" + }, "external": { "description": "Enable external access from outside the cluster.", "type": "boolean", diff --git a/packages/apps/virtual-machine/values.yaml b/packages/apps/virtual-machine/values.yaml index 6fa22946..169d7e10 100644 --- a/packages/apps/virtual-machine/values.yaml +++ b/packages/apps/virtual-machine/values.yaml @@ -74,6 +74,9 @@ gpus: [] ## gpus: ## - name: nvidia.com/GA102GL_A10 +## @param {string} cpuModel - Model specifies the CPU model inside the VMI. List of available models https://github.com/libvirt/libvirt/tree/master/src/cpu_map +cpuModel: "" + ## @param {Resources} [resources] - Resource configuration for the virtual machine. resources: {} ## Example: diff --git a/packages/system/virtual-machine-rd/cozyrds/virtual-machine.yaml b/packages/system/virtual-machine-rd/cozyrds/virtual-machine.yaml index 6143fa6c..6b52340b 100644 --- a/packages/system/virtual-machine-rd/cozyrds/virtual-machine.yaml +++ b/packages/system/virtual-machine-rd/cozyrds/virtual-machine.yaml @@ -8,7 +8,7 @@ spec: plural: virtualmachines singular: virtualmachine openAPISchema: |- - {"title":"Chart Values","type":"object","properties":{"cloudInit":{"description":"Cloud-init user data.","type":"string","default":""},"cloudInitSeed":{"description":"Seed string to generate SMBIOS UUID for the VM.","type":"string","default":""},"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"externalMethod":{"description":"Method to pass through traffic to the VM.","type":"string","default":"PortList","enum":["PortList","WholeIP"]},"externalPorts":{"description":"Ports to forward from outside the cluster.","type":"array","default":[22],"items":{"type":"integer"}},"gpus":{"description":"List of GPUs to attach.","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"name":{"description":"The name of the GPU resource to attach.","type":"string"}}}},"instanceProfile":{"description":"Virtual Machine preferences profile.","type":"string","default":"ubuntu","enum":["alpine","centos.7","centos.7.desktop","centos.stream10","centos.stream10.desktop","centos.stream8","centos.stream8.desktop","centos.stream8.dpdk","centos.stream9","centos.stream9.desktop","centos.stream9.dpdk","cirros","fedora","fedora.arm64","opensuse.leap","opensuse.tumbleweed","rhel.10","rhel.10.arm64","rhel.7","rhel.7.desktop","rhel.8","rhel.8.desktop","rhel.8.dpdk","rhel.9","rhel.9.arm64","rhel.9.desktop","rhel.9.dpdk","rhel.9.realtime","sles","ubuntu","windows.10","windows.10.virtio","windows.11","windows.11.virtio","windows.2k16","windows.2k16.virtio","windows.2k19","windows.2k19.virtio","windows.2k22","windows.2k22.virtio","windows.2k25","windows.2k25.virtio",""]},"instanceType":{"description":"Virtual Machine instance type.","type":"string","default":"u1.medium"},"resources":{"description":"Resource configuration for the virtual machine.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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},"sockets":{"description":"Number of CPU sockets (vCPU topology).","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}}},"runStrategy":{"description":"Requested running state of the VirtualMachineInstance","type":"string","default":"Always","enum":["Always","Halted","Manual","RerunOnFailure","Once"]},"sshKeys":{"description":"List of SSH public keys for authentication.","type":"array","default":[],"items":{"type":"string"}},"subnets":{"description":"Additional subnets","type":"array","default":[],"items":{"type":"object","properties":{"name":{"description":"Subnet name","type":"string"}}}},"systemDisk":{"description":"System disk configuration.","type":"object","default":{},"required":["image","storage"],"properties":{"image":{"description":"The base image for the virtual machine.","type":"string","default":"ubuntu","enum":["ubuntu","cirros","alpine","fedora","talos"]},"storage":{"description":"The size of the disk allocated for the virtual machine.","type":"string","default":"5Gi"},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":"replicated"}}}}} + {"title":"Chart Values","type":"object","properties":{"cloudInit":{"description":"Cloud-init user data.","type":"string","default":""},"cloudInitSeed":{"description":"Seed string to generate SMBIOS UUID for the VM.","type":"string","default":""},"cpuModel":{"description":"Model specifies the CPU model inside the VMI. List of available models https://github.com/libvirt/libvirt/tree/master/src/cpu_map","type":"string","default":""},"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"externalMethod":{"description":"Method to pass through traffic to the VM.","type":"string","default":"PortList","enum":["PortList","WholeIP"]},"externalPorts":{"description":"Ports to forward from outside the cluster.","type":"array","default":[22],"items":{"type":"integer"}},"gpus":{"description":"List of GPUs to attach.","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"name":{"description":"The name of the GPU resource to attach.","type":"string"}}}},"instanceProfile":{"description":"Virtual Machine preferences profile.","type":"string","default":"ubuntu","enum":["alpine","centos.7","centos.7.desktop","centos.stream10","centos.stream10.desktop","centos.stream8","centos.stream8.desktop","centos.stream8.dpdk","centos.stream9","centos.stream9.desktop","centos.stream9.dpdk","cirros","fedora","fedora.arm64","opensuse.leap","opensuse.tumbleweed","rhel.10","rhel.10.arm64","rhel.7","rhel.7.desktop","rhel.8","rhel.8.desktop","rhel.8.dpdk","rhel.9","rhel.9.arm64","rhel.9.desktop","rhel.9.dpdk","rhel.9.realtime","sles","ubuntu","windows.10","windows.10.virtio","windows.11","windows.11.virtio","windows.2k16","windows.2k16.virtio","windows.2k19","windows.2k19.virtio","windows.2k22","windows.2k22.virtio","windows.2k25","windows.2k25.virtio",""]},"instanceType":{"description":"Virtual Machine instance type.","type":"string","default":"u1.medium"},"resources":{"description":"Resource configuration for the virtual machine.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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},"sockets":{"description":"Number of CPU sockets (vCPU topology).","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}}},"runStrategy":{"description":"Requested running state of the VirtualMachineInstance","type":"string","default":"Always","enum":["Always","Halted","Manual","RerunOnFailure","Once"]},"sshKeys":{"description":"List of SSH public keys for authentication.","type":"array","default":[],"items":{"type":"string"}},"subnets":{"description":"Additional subnets","type":"array","default":[],"items":{"type":"object","properties":{"name":{"description":"Subnet name","type":"string"}}}},"systemDisk":{"description":"System disk configuration.","type":"object","default":{},"required":["image","storage"],"properties":{"image":{"description":"The base image for the virtual machine.","type":"string","default":"ubuntu","enum":["ubuntu","cirros","alpine","fedora","talos"]},"storage":{"description":"The size of the disk allocated for the virtual machine.","type":"string","default":"5Gi"},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":"replicated"}}}}} release: prefix: virtual-machine- labels: @@ -26,7 +26,7 @@ spec: tags: - compute icon: PHN2ZyB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgdmlld0JveD0iMCAwIDE0NCAxNDQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNDQiIGhlaWdodD0iMTQ0IiByeD0iMjQiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcl82ODdfMzQ1NCkiLz4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzY4N18zNDU0KSI+CjxwYXRoIGQ9Ik04OS41MDM5IDExMS43MDdINTQuNDk3QzU0LjE3MjcgMTExLjcwNyA1NC4wMTA4IDExMS4yMjEgNTQuMzM0OSAxMTEuMDU5TDU3LjI1MjIgMTA4Ljk1MkM2MC4zMzE0IDEwNi42ODMgNjEuOTUyMiAxMDIuNjMxIDYwLjk3OTcgOTguNzQxMkg4My4wMjFDODIuMDQ4NSAxMDIuNjMxIDgzLjY2OTMgMTA2LjY4MyA4Ni43NDg1IDEwOC45NTJMODkuNjY1OCAxMTEuMDU5Qzg5Ljk5IDExMS4yMjEgODkuODI3OSAxMTEuNzA3IDg5LjUwMzkgMTExLjcwN1oiIGZpbGw9IiNCMEI2QkIiLz4KPHBhdGggZD0iTTExMy4zMjggOTguNzQxSDMwLjY3MjVDMjcuNTkzMSA5OC43NDEgMjUgOTYuMTQ4IDI1IDkzLjA2ODdWMzMuMTAzMkMyNSAzMC4wMjM5IDI3LjU5MzEgMjcuNDMwNyAzMC42NzI1IDI3LjQzMDdIMTEzLjMyOEMxMTYuNDA3IDI3LjQzMDcgMTE5IDMwLjAyMzcgMTE5IDMzLjEwMzJWOTMuMDY4N0MxMTkgOTYuMTQ4IDExNi40MDcgOTguNzQxIDExMy4zMjggOTguNzQxWiIgZmlsbD0iI0U4RURFRSIvPgo8cGF0aCBkPSJNMTE5IDg0LjE1NDlIMjVWMzMuMTAzMkMyNSAzMC4wMjM5IDI3LjU5MzEgMjcuNDMwNyAzMC42NzI1IDI3LjQzMDdIMTEzLjMyOEMxMTYuNDA3IDI3LjQzMDcgMTE5IDMwLjAyMzcgMTE5IDMzLjEwMzJMMTE5IDg0LjE1NDlaIiBmaWxsPSIjMDBCM0ZGIi8+CjxwYXRoIGQ9Ik05MC42Mzc0IDExNi41NjlINTMuMzYxNkM1Mi4wNjUxIDExNi41NjkgNTAuOTMwNyAxMTUuNDM1IDUwLjkzMDcgMTE0LjEzOEM1MC45MzA3IDExMi44NDEgNTIuMDY1MSAxMTEuNzA3IDUzLjM2MTYgMTExLjcwN0g5MC42Mzc0QzkxLjkzMzkgMTExLjcwNyA5My4wNjg0IDExMi44NDEgOTMuMDY4NCAxMTQuMTM4QzkzLjA2ODQgMTE1LjQzNSA5MS45MzM5IDExNi41NjkgOTAuNjM3NCAxMTYuNTY5WiIgZmlsbD0iI0U4RURFRSIvPgo8L2c+CjxwYXRoIGQ9Ik03Mi41Mjc1IDUzLjgzNjdDNzIuNDQzMSA1My44MzUxIDcyLjM2MDUgNTMuODEyMiA3Mi4yODczIDUzLjc3MDFMNTYuNDY5OSA0NC43OTM0QzU2LjM5ODMgNDQuNzUxOSA1Ni4zMzg4IDQ0LjY5MjMgNTYuMjk3MyA0NC42MjA3QzU2LjI1NTkgNDQuNTQ5IDU2LjIzMzggNDQuNDY3OCA1Ni4yMzM0IDQ0LjM4NUM1Ni4yMzM0IDQ0LjIxNjkgNTYuMzI1OCA0NC4wNjE3IDU2LjQ2OTkgNDMuOTc4NUw3Mi4xOTEyIDM1LjA2MDlDNzIuMjYzNyAzNS4wMjEgNzIuMzQ1IDM1IDcyLjQyNzcgMzVDNzIuNTEwNSAzNSA3Mi41OTE4IDM1LjAyMSA3Mi42NjQzIDM1LjA2MDlMODguNDg3MiA0NC4wMzk1Qzg4LjU1OTEgNDQuMDgwMSA4OC42MTg4IDQ0LjEzOTIgODguNjYgNDQuMjEwN0M4OC43MDEzIDQ0LjI4MjIgODguNzIyNyA0NC4zNjM1IDg4LjcyMTkgNDQuNDQ2Qzg4LjcyMjUgNDQuNTI4NSA4OC43MDEgNDQuNjA5NyA4OC42NTk4IDQ0LjY4MTJDODguNjE4NSA0NC43NTI2IDg4LjU1ODkgNDQuODExOCA4OC40ODcyIDQ0Ljg1MjVMNzIuNzcxNCA1My43NjgzQzcyLjY5NzIgNTMuODExNCA3Mi42MTMzIDUzLjgzNDkgNzIuNTI3NSA1My44MzY3IiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBvcGFjaXR5PSIwLjciIGQ9Ik03MC4yNTUzIDc1LjY1MTdDNzAuMTcxIDc1LjY1MzUgNzAuMDg3OCA3NS42MzE3IDcwLjAxNTEgNzUuNTg4OEw1NC4yNDU4IDY2LjY0MTdDNTQuMTcxNSA2Ni42MDI0IDU0LjEwOTUgNjYuNTQzNiA1NC4wNjYxIDY2LjQ3MTZDNTQuMDIyOCA2Ni4zOTk3IDU0IDY2LjMxNzMgNTQgNjYuMjMzM1Y0OC4yNzhDNTQgNDguMTA4IDU0LjA5MjQgNDcuOTU0NiA1NC4yNDM5IDQ3Ljg2OTZDNTQuMzE3MiA0Ny44MjcxIDU0LjQwMDQgNDcuODA0NyA1NC40ODUxIDQ3LjgwNDdDNTQuNTY5NyA0Ny44MDQ3IDU0LjY1MjkgNDcuODI3MSA1NC43MjYyIDQ3Ljg2OTZMNzAuNDkzNyA1Ni44MTMxQzcwLjU2NDIgNTYuODU2NSA3MC42MjI1IDU2LjkxNyA3MC42NjMyIDU2Ljk4OTFDNzAuNzAzOSA1Ny4wNjEyIDcwLjcyNTcgNTcuMTQyNCA3MC43MjY1IDU3LjIyNTFWNzUuMTgwNUM3MC43MjU5IDc1LjI2MjggNzAuNzA0MiA3NS4zNDM2IDcwLjY2MzUgNzUuNDE1MUM3MC42MjI3IDc1LjQ4NjYgNzAuNTY0MiA3NS41NDY0IDcwLjQ5MzcgNzUuNTg4OEM3MC40MjA2IDc1LjYyOTEgNzAuMzM4NyA3NS42NTA3IDcwLjI1NTMgNzUuNjUxNyIgZmlsbD0id2hpdGUiLz4KPHBhdGggb3BhY2l0eT0iMC40IiBkPSJNNzQuNzE5OCA3NS42NTExQzc0LjYzMzMgNzUuNjUxMiA3NC41NDgyIDc1LjYyOTYgNzQuNDcyMiA3NS41ODgzQzc0LjQwMTYgNzUuNTQ2MSA3NC4zNDMyIDc1LjQ4NjIgNzQuMzAyNyA3NS40MTQ3Qzc0LjI2MjMgNzUuMzQzMSA3NC4yNDExIDc1LjI2MjIgNzQuMjQxMiA3NS4xOFY1Ny4zMzczQzc0LjI0MTIgNTcuMTcxIDc0LjMzMzYgNTcuMDE1OCA3NC40NzIyIDU2LjkyOUw5MC4yMzk3IDQ3Ljk4NTVDOTAuMzExOSA0Ny45NDM4IDkwLjM5MzggNDcuOTIxOSA5MC40NzcxIDQ3LjkyMTlDOTAuNTYwNSA0Ny45MjE5IDkwLjY0MjQgNDcuOTQzOCA5MC43MTQ2IDQ3Ljk4NTVDOTAuNzg3NiA0OC4wMjU1IDkwLjg0ODUgNDguMDg0MiA5MC44OTExIDQ4LjE1NTdDOTAuOTMzNyA0OC4yMjcyIDkwLjk1NjMgNDguMzA4OCA5MC45NTY2IDQ4LjM5MlY2Ni4yMzI4QzkwLjk1NyA2Ni4zMTY0IDkwLjkzNDcgNjYuMzk4NSA5MC44OTIxIDY2LjQ3MDRDOTAuODQ5NSA2Ni41NDI0IDkwLjc4ODEgNjYuNjAxNCA5MC43MTQ2IDY2LjY0MTFMNzQuOTUyNiA3NS41ODgzQzc0Ljg4MjUgNzUuNjMwNyA3NC44MDE4IDc1LjY1MjUgNzQuNzE5OCA3NS42NTExIiBmaWxsPSJ3aGl0ZSIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyXzY4N18zNDU0IiB4MT0iMTYxIiB5MT0iMTgwIiB4Mj0iMy41OTI4NGUtMDciIHkyPSI0Ljk5OTk4IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNTk1NjU2Ii8+CjwvbGluZWFyR3JhZGllbnQ+CjxjbGlwUGF0aCBpZD0iY2xpcDBfNjg3XzM0NTQiPgo8cmVjdCB3aWR0aD0iOTQiIGhlaWdodD0iOTQiIGZpbGw9IndoaXRlIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyNSAyNSkiLz4KPC9jbGlwUGF0aD4KPC9kZWZzPgo8L3N2Zz4K - keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "external"], ["spec", "externalMethod"], ["spec", "externalPorts"], ["spec", "runStrategy"], ["spec", "instanceType"], ["spec", "instanceProfile"], ["spec", "systemDisk"], ["spec", "systemDisk", "image"], ["spec", "systemDisk", "storage"], ["spec", "systemDisk", "storageClass"], ["spec", "subnets"], ["spec", "gpus"], ["spec", "resources"], ["spec", "sshKeys"], ["spec", "cloudInit"], ["spec", "cloudInitSeed"]] + keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "external"], ["spec", "externalMethod"], ["spec", "externalPorts"], ["spec", "runStrategy"], ["spec", "instanceType"], ["spec", "instanceProfile"], ["spec", "systemDisk"], ["spec", "systemDisk", "image"], ["spec", "systemDisk", "storage"], ["spec", "systemDisk", "storageClass"], ["spec", "subnets"], ["spec", "gpus"], ["spec", "cpuModel"], ["spec", "resources"], ["spec", "sshKeys"], ["spec", "cloudInit"], ["spec", "cloudInitSeed"]] secrets: exclude: [] include: [] From 1d3deab3f3a3ed04885a63b88669af25f86fa38a Mon Sep 17 00:00:00 2001 From: IvanHunters <49371933+IvanHunters@users.noreply.github.com> Date: Tue, 10 Feb 2026 14:47:50 +0300 Subject: [PATCH 203/889] Add @IvanHunters to CODEOWNERS Signed-off-by: IvanHunters <49371933+IvanHunters@users.noreply.github.com> --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4b70683c..716776ea 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @kvaps @lllamnyp @lexfrei @androndo +* @kvaps @lllamnyp @lexfrei @androndo @IvanHunters From 051889e761653c6f2d5eaf0fb1037131d8a38a61 Mon Sep 17 00:00:00 2001 From: IvanHunters Date: Tue, 10 Feb 2026 16:04:47 +0300 Subject: [PATCH 204/889] fix(monitoring): remove duplicate dashboards.list from extra/monitoring Dashboard files should only be in system/monitoring. The dashboards are created by system/monitoring when deployed via HelmRelease from extra/monitoring. Signed-off-by: IvanHunters --- packages/extra/monitoring/dashboards.list | 45 ----------------------- 1 file changed, 45 deletions(-) delete mode 100644 packages/extra/monitoring/dashboards.list diff --git a/packages/extra/monitoring/dashboards.list b/packages/extra/monitoring/dashboards.list deleted file mode 100644 index 1f4b2eea..00000000 --- a/packages/extra/monitoring/dashboards.list +++ /dev/null @@ -1,45 +0,0 @@ -dotdc/k8s-views-global -dotdc/k8s-views-namespaces -dotdc/k8s-system-coredns -dotdc/k8s-views-pods -cache/nginx-vts-stats -victoria-metrics/vmalert -victoria-metrics/vmagent -victoria-metrics/victoriametrics-cluster -victoria-metrics/backupmanager -victoria-metrics/victoriametrics -victoria-metrics/operator -ingress/controller-detail -ingress/controllers -ingress/namespaces -ingress/vhosts -ingress/vhost-detail -ingress/namespace-detail -db/cloudnativepg -db/maria-db -db/redis -main/controller -main/namespaces -main/capacity-planning -main/ntp -main/namespace -main/pod -main/volumes -main/node -main/nodes -control-plane/control-plane-status -control-plane/deprecated-resources -control-plane/dns-coredns -control-plane/kube-etcd -kubevirt/kubevirt-control-plane -flux/flux-control-plane -flux/flux-stats -kafka/strimzi-kafka -goldpinger/goldpinger -clickhouse/altinity-clickhouse-operator-dashboard -storage/linstor -seaweedfs/seaweedfs -hubble/overview -hubble/dns-namespace -hubble/l7-http-metrics -hubble/network-overview From e8ffbbb097225ab597d0c6af5555c6f4f9f40611 Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Tue, 10 Feb 2026 17:49:41 +0300 Subject: [PATCH 205/889] [backups] Add kubevirt plugin to velero ## What this PR does This patch installs the kubevirt-velero-plugin, so that backing up a kubevirt resource such as a VMI will automatically include related items, such as datavolumes, and, eventually, all the way down to pods and volumes. ### Release note ```release-note [backups] Include the kubevirt-velero-plugin with the Velero package. ``` Signed-off-by: Timofei Larkin --- packages/system/velero/values.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/system/velero/values.yaml b/packages/system/velero/values.yaml index 4d503543..e15240d3 100644 --- a/packages/system/velero/values.yaml +++ b/packages/system/velero/values.yaml @@ -9,6 +9,13 @@ velero: volumeMounts: - mountPath: /target name: plugins + - image: quay.io/kubevirt/kubevirt-velero-plugin:v0.8.0 + name: kubevirt-kubevirt-velero-plugin + imagePullPolicy: IfNotPresent + volumeMounts: + - mountPath: /target + name: plugins + deployNodeAgent: true configuration: # defaultVolumesToFsBackup: true From 494144fb92e287b55f879732a310de85fc318629 Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Tue, 10 Feb 2026 18:25:44 +0300 Subject: [PATCH 206/889] [backups] Install backupstrategy controller by default ## What this PR does Enables the installation of the backupstrategy controller by default. ### Release note ```release-note [backups] Install the backupstrategy controller by default. ``` Signed-off-by: Timofei Larkin --- packages/core/platform/templates/bundles/system.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/platform/templates/bundles/system.yaml b/packages/core/platform/templates/bundles/system.yaml index faab22eb..2d8b7e33 100644 --- a/packages/core/platform/templates/bundles/system.yaml +++ b/packages/core/platform/templates/bundles/system.yaml @@ -131,7 +131,7 @@ {{include "cozystack.platform.package.default" (list "cozystack.monitoring-application" $) }} {{include "cozystack.platform.package.default" (list "cozystack.etcd-application" $) }} {{include "cozystack.platform.package.default" (list "cozystack.cozystack-basics" $) }} -{{include "cozystack.platform.package.optional.default" (list "cozystack.backupstrategy-controller" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.backupstrategy-controller" $) }} {{include "cozystack.platform.package.default" (list "cozystack.backup-controller" $) }} {{include "cozystack.platform.package.optional.default" (list "cozystack.velero" $) }} {{include "cozystack.platform.package.default" (list "cozystack.vertical-pod-autoscaler" $) }} From 4b73afe13701284cd0b055e5e98386e74c8b258d Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Tue, 10 Feb 2026 19:05:05 +0300 Subject: [PATCH 207/889] [backups] Better selectors for VM strategy ## What this PR does This patch adds another label selector for resources being backed up by the default Velero strategy for virtual machines, ensuring that the actual VM and VMI are captured by selector. ### Release note ```release-note [backups] Refined the label selector in the Velero VM backup strategy to capture resources previously missed. ``` Signed-off-by: Timofei Larkin --- .../templates/backupstrategy.yaml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/system/virtual-machine-rd/templates/backupstrategy.yaml b/packages/system/virtual-machine-rd/templates/backupstrategy.yaml index b9af2ef4..ac2fe517 100644 --- a/packages/system/virtual-machine-rd/templates/backupstrategy.yaml +++ b/packages/system/virtual-machine-rd/templates/backupstrategy.yaml @@ -5,14 +5,17 @@ metadata: name: velero-backup-strategy-for-virtualmachines spec: template: - spec: # see https://velero.io/docs/v1.9/api-types/backup/ + spec: + csiSnapshotTimeout: 10m0s includedNamespaces: - '{{ printf "{{ .Application.metadata.namespace }}" }}' - - labelSelector: - matchLabels: - apps.cozystack.io/application.Kind: '{{ printf "{{ .Application.kind }}" }}' - + orLabelSelector: + - matchLabels: + apps.cozystack.io/application.group: apps.cozystack.io + apps.cozystack.io/application.kind: '{{ printf "{{ .Application.kind }}" }}' + apps.cozystack.io/application.name: '{{ printf "{{ .Application.metadata.name }}"}}' + - matchLabels: + app.kubernetes.io/instance: 'virtual-machine-{{ printf "{{ .Application.metadata.name }}"}}' includedResources: - helmreleases.helm.toolkit.fluxcd.io - virtualmachines.kubevirt.io @@ -22,14 +25,11 @@ spec: - services - configmaps - secrets - storageLocation: '{{ printf "{{ .Parameters.backupStorageLocationName }}" }}' - volumeSnapshotLocations: - '{{ printf "{{ .Parameters.backupStorageLocationName }}" }}' snapshotVolumes: true snapshotMoveData: true - ttl: 720h0m0s itemOperationTimeout: 24h0m0s {{- end }} From 698e542af53629e2bcdef7e6cf6f8c55de753382 Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Tue, 10 Feb 2026 19:21:50 +0300 Subject: [PATCH 208/889] [platform] Fix cozystack-values secret race ## What this PR does The cozystack-values secret is created as part of the cozystack-basics chart and is required by cozystack to install properly, however there is a race condition between it and the lineage-controller-webhook, where it may be blocked from being created if the mutating webhook configuration is already set up. By adding a system label to this secret it is dropped from consideration by the webhook. ### Release note ```release-note [platform] Resolve race condition between the system cozystack-values secret and the lineage webhook by adding a label that causes the webhook to ignore this secret. ``` Signed-off-by: Timofei Larkin --- .../cozystack-basics/templates/cozystack-values-secret.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/system/cozystack-basics/templates/cozystack-values-secret.yaml b/packages/system/cozystack-basics/templates/cozystack-values-secret.yaml index b625435f..8c0d1c7a 100644 --- a/packages/system/cozystack-basics/templates/cozystack-values-secret.yaml +++ b/packages/system/cozystack-basics/templates/cozystack-values-secret.yaml @@ -6,6 +6,7 @@ metadata: namespace: tenant-root labels: reconcile.fluxcd.io/watch: Enabled + internal.cozystack.io/managed-by-cozystack: "" type: Opaque stringData: values.yaml: | From 939727b93629ecc5e073ce50b16dcf8522d1fb3b Mon Sep 17 00:00:00 2001 From: Kirill Ilin Date: Tue, 10 Feb 2026 22:47:20 +0500 Subject: [PATCH 209/889] Add clustersecret-operator system package Signed-off-by: Kirill Ilin --- .../sources/clustersecret-operator.yaml | 21 +++ .../system/clustersecret-operator/.helmignore | 3 + .../system/clustersecret-operator/Chart.yaml | 3 + .../system/clustersecret-operator/Makefile | 11 ++ .../charts/clustersecret-operator/.helmignore | 23 +++ .../charts/clustersecret-operator/Chart.yaml | 6 + .../charts/clustersecret-operator/README.md | 74 ++++++++++ .../crds/clustersecrets.yaml | 106 ++++++++++++++ .../templates/_helpers-controller.tpl | 13 ++ .../templates/_helpers-webhook.tpl | 13 ++ .../templates/_helpers.tpl | 51 +++++++ .../templates/controller-deployment.yaml | 96 ++++++++++++ .../templates/controller-pdb.yaml | 13 ++ .../templates/controller-rbac.yaml | 74 ++++++++++ .../templates/tests/rbac.yaml | 58 ++++++++ .../templates/tests/test-1.yaml | 44 ++++++ .../templates/webhook-admission.yaml | 107 ++++++++++++++ .../templates/webhook-deployment.yaml | 109 ++++++++++++++ .../templates/webhook-pdb.yaml | 13 ++ .../templates/webhook-rbac.yaml | 7 + .../charts/clustersecret-operator/values.yaml | 138 ++++++++++++++++++ .../system/clustersecret-operator/values.yaml | 1 + 22 files changed, 984 insertions(+) create mode 100644 packages/core/platform/sources/clustersecret-operator.yaml create mode 100644 packages/system/clustersecret-operator/.helmignore create mode 100644 packages/system/clustersecret-operator/Chart.yaml create mode 100644 packages/system/clustersecret-operator/Makefile create mode 100644 packages/system/clustersecret-operator/charts/clustersecret-operator/.helmignore create mode 100644 packages/system/clustersecret-operator/charts/clustersecret-operator/Chart.yaml create mode 100644 packages/system/clustersecret-operator/charts/clustersecret-operator/README.md create mode 100644 packages/system/clustersecret-operator/charts/clustersecret-operator/crds/clustersecrets.yaml create mode 100644 packages/system/clustersecret-operator/charts/clustersecret-operator/templates/_helpers-controller.tpl create mode 100644 packages/system/clustersecret-operator/charts/clustersecret-operator/templates/_helpers-webhook.tpl create mode 100644 packages/system/clustersecret-operator/charts/clustersecret-operator/templates/_helpers.tpl create mode 100644 packages/system/clustersecret-operator/charts/clustersecret-operator/templates/controller-deployment.yaml create mode 100644 packages/system/clustersecret-operator/charts/clustersecret-operator/templates/controller-pdb.yaml create mode 100644 packages/system/clustersecret-operator/charts/clustersecret-operator/templates/controller-rbac.yaml create mode 100644 packages/system/clustersecret-operator/charts/clustersecret-operator/templates/tests/rbac.yaml create mode 100644 packages/system/clustersecret-operator/charts/clustersecret-operator/templates/tests/test-1.yaml create mode 100644 packages/system/clustersecret-operator/charts/clustersecret-operator/templates/webhook-admission.yaml create mode 100644 packages/system/clustersecret-operator/charts/clustersecret-operator/templates/webhook-deployment.yaml create mode 100644 packages/system/clustersecret-operator/charts/clustersecret-operator/templates/webhook-pdb.yaml create mode 100644 packages/system/clustersecret-operator/charts/clustersecret-operator/templates/webhook-rbac.yaml create mode 100644 packages/system/clustersecret-operator/charts/clustersecret-operator/values.yaml create mode 100644 packages/system/clustersecret-operator/values.yaml diff --git a/packages/core/platform/sources/clustersecret-operator.yaml b/packages/core/platform/sources/clustersecret-operator.yaml new file mode 100644 index 00000000..2dc800ab --- /dev/null +++ b/packages/core/platform/sources/clustersecret-operator.yaml @@ -0,0 +1,21 @@ +--- +apiVersion: cozystack.io/v1alpha1 +kind: PackageSource +metadata: + name: cozystack.clustersecret-operator +spec: + sourceRef: + kind: OCIRepository + name: cozystack-packages + namespace: cozy-system + path: / + variants: + - name: default + dependsOn: + - cozystack.networking + components: + - name: clustersecret-operator + path: system/clustersecret-operator + install: + namespace: cozy-clustersecret-operator + releaseName: clustersecret-operator diff --git a/packages/system/clustersecret-operator/.helmignore b/packages/system/clustersecret-operator/.helmignore new file mode 100644 index 00000000..d5c178e8 --- /dev/null +++ b/packages/system/clustersecret-operator/.helmignore @@ -0,0 +1,3 @@ +images +hack +.gitkeep diff --git a/packages/system/clustersecret-operator/Chart.yaml b/packages/system/clustersecret-operator/Chart.yaml new file mode 100644 index 00000000..bc9ef8e7 --- /dev/null +++ b/packages/system/clustersecret-operator/Chart.yaml @@ -0,0 +1,3 @@ +apiVersion: v2 +name: cozy-clustersecret-operator +version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process diff --git a/packages/system/clustersecret-operator/Makefile b/packages/system/clustersecret-operator/Makefile new file mode 100644 index 00000000..34fa1464 --- /dev/null +++ b/packages/system/clustersecret-operator/Makefile @@ -0,0 +1,11 @@ +export NAME=clustersecret-operator +export NAMESPACE=cozy-clustersecret-operator + +export REPO_NAME=clustersecret-operator +export REPO_URL=https://sap.github.io/clustersecret-operator-helm +export CHART_NAME=clustersecret-operator +export CHART_VERSION=^0.3 + +include ../../../hack/package.mk + +update: clean clustersecret-operator-update diff --git a/packages/system/clustersecret-operator/charts/clustersecret-operator/.helmignore b/packages/system/clustersecret-operator/charts/clustersecret-operator/.helmignore new file mode 100644 index 00000000..0e8a0eb3 --- /dev/null +++ b/packages/system/clustersecret-operator/charts/clustersecret-operator/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/packages/system/clustersecret-operator/charts/clustersecret-operator/Chart.yaml b/packages/system/clustersecret-operator/charts/clustersecret-operator/Chart.yaml new file mode 100644 index 00000000..447e8530 --- /dev/null +++ b/packages/system/clustersecret-operator/charts/clustersecret-operator/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +appVersion: v0.3.68 +description: A Helm chart for https://github.com/sap/clustersecret-operator +name: clustersecret-operator +type: application +version: 0.3.68 diff --git a/packages/system/clustersecret-operator/charts/clustersecret-operator/README.md b/packages/system/clustersecret-operator/charts/clustersecret-operator/README.md new file mode 100644 index 00000000..11b21f84 --- /dev/null +++ b/packages/system/clustersecret-operator/charts/clustersecret-operator/README.md @@ -0,0 +1,74 @@ +# clustersecret-operator + +![Version: 0.3.68](https://img.shields.io/badge/Version-0.3.68-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v0.3.68](https://img.shields.io/badge/AppVersion-v0.3.68-informational?style=flat-square) + +A Helm chart for https://github.com/sap/clustersecret-operator + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| fullnameOverride | string | `""` | Override full name | +| nameOverride | string | `""` | Override name | +| global.image.tag | string | `""` | Image tag (defauls to .Chart.AppVersion) | +| global.image.pullPolicy | string | `"IfNotPresent"` | Image pull policy | +| global.imagePullSecrets | list | `[]` | Image pull secrets | +| global.affinity | object | `{}` | Affinity settings | +| global.nodeSelector | object | `{}` | Node selector | +| global.topologySpreadConstraints | list | `[]` | Topology spread constraints (if unspecified, default constraints for hostname and zone will be generated) | +| global.defaultHostNameSpreadPolicy | string | `"ScheduleAnyway"` | Default topology spread policy for hostname | +| global.defaultZoneSpreadPolicy | string | `"ScheduleAnyway"` | Default topology spread policy for zone | +| global.tolerations | list | `[]` | Tolerations | +| global.priorityClassName | string | `""` | Priority class | +| global.podSecurityContext | object | `{}` | Pod security context | +| global.podAnnotations | object | `{}` | Additional pod annotations | +| global.podLabels | object | `{}` | Additional pod labels | +| global.securityContext | object | `{}` | Container security context | +| global.logLevel | int | `0` | Log level | +| controller.replicaCount | int | `1` | Replica count | +| controller.image.repository | string | `"ghcr.io/sap/clustersecret-operator/controller"` | Image repository | +| controller.image.tag | string | `""` | Image tag (defauls to .Chart.AppVersion) | +| controller.image.pullPolicy | string | `""` | Image pull policy | +| controller.imagePullSecrets | list | `[]` | Image pull secrets | +| controller.affinity | object | `{}` | Affinity settings | +| controller.nodeSelector | object | `{}` | Node selector | +| controller.topologySpreadConstraints | list | `[]` | Topology spread constraints (if unspecified, default constraints for hostname and zone will be generated) | +| controller.defaultHostNameSpreadPolicy | string | `""` | Default topology spread policy for hostname | +| controller.defaultZoneSpreadPolicy | string | `""` | Default topology spread policy for zone | +| controller.tolerations | list | `[]` | Tolerations | +| controller.priorityClassName | string | `""` | Priority class | +| controller.podSecurityContext | object | `{}` | Pod security context | +| controller.podAnnotations | object | `{}` | Additional pod annotations | +| controller.podLabels | object | `{}` | Additional pod labels | +| controller.securityContext | object | `{}` | Container security context | +| controller.resources.limits.memory | string | `"200Mi"` | Memory limit | +| controller.resources.limits.cpu | float | `0.1` | CPU limit | +| controller.resources.requests.memory | string | `"20Mi"` | Memory request | +| controller.resources.requests.cpu | float | `0.01` | CPU request | +| controller.logLevel | int | `0` | Log level | +| webhook.replicaCount | int | `1` | Replica count | +| webhook.image.repository | string | `"ghcr.io/sap/clustersecret-operator/webhook"` | Image repository | +| webhook.image.tag | string | `""` | Image tag (defauls to .Chart.AppVersion) | +| webhook.image.pullPolicy | string | `""` | Image pull policy | +| webhook.imagePullSecrets | list | `[]` | Image pull secrets | +| webhook.affinity | object | `{}` | Affinity settings | +| webhook.nodeSelector | object | `{}` | Node selector | +| webhook.topologySpreadConstraints | list | `[]` | Topology spread constraints (if unspecified, default constraints for hostname and zone will be generated) | +| webhook.defaultHostNameSpreadPolicy | string | `""` | Default topology spread policy for hostname | +| webhook.defaultZoneSpreadPolicy | string | `""` | Default topology spread policy for zone | +| webhook.tolerations | list | `[]` | Tolerations | +| webhook.priorityClassName | string | `""` | Priority class | +| webhook.podSecurityContext | object | `{}` | Pod security context | +| webhook.podAnnotations | object | `{}` | Additional pod annotations | +| webhook.podLabels | object | `{}` | Additional pod labels | +| webhook.securityContext | object | `{}` | Container security context | +| webhook.resources.limits.memory | string | `"100Mi"` | Memory limit | +| webhook.resources.limits.cpu | float | `0.1` | CPU limit | +| webhook.resources.requests.memory | string | `"20Mi"` | Memory request | +| webhook.resources.requests.cpu | float | `0.01` | CPU request | +| webhook.service.type | string | `"ClusterIP"` | Service type | +| webhook.service.port | int | `443` | Service port | +| webhook.logLevel | int | `0` | Log level | + +---------------------------------------------- +Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0) diff --git a/packages/system/clustersecret-operator/charts/clustersecret-operator/crds/clustersecrets.yaml b/packages/system/clustersecret-operator/charts/clustersecret-operator/crds/clustersecrets.yaml new file mode 100644 index 00000000..e28a99b3 --- /dev/null +++ b/packages/system/clustersecret-operator/charts/clustersecret-operator/crds/clustersecrets.yaml @@ -0,0 +1,106 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: clustersecrets.core.cs.sap.com +spec: + scope: Cluster + names: + plural: clustersecrets + singular: clustersecret + kind: ClusterSecret + group: core.cs.sap.com + versions: + - name: v1alpha1 + served: true + storage: true + additionalPrinterColumns: + - name: Age + type: date + jsonPath: .metadata.creationTimestamp + - name: State + type: string + jsonPath: .status.state + subresources: + status: {} + schema: + openAPIV3Schema: + type: object + required: ["spec"] + properties: + spec: + type: object + required: ["template"] + properties: + namespaceSelector: + type: object + anyOf: + - required: ["matchLabels"] + - required: ["matchExpressions"] + properties: + matchLabels: + type: object + additionalProperties: + type: string + nullable: true + matchExpressions: + type: array + items: + type: object + properties: + key: + type: string + operator: + type: string + enum: ["In","NotIn","Exists","DoesNotExist"] + values: + type: array + items: + type: string + template: + type: object + required: ["type"] + properties: + type: + type: string + minLength: 1 + data: + type: object + additionalProperties: + type: string + nullable: true + stringData: + type: object + additionalProperties: + type: string + nullable: true + status: + type: object + properties: + observedGeneration: + type: integer + state: + type: string + enum: ["Ready","Processing","Deleting","Error"] + conditions: + type: array + items: + type: object + required: ["type","status"] + properties: + type: + type: string + enum: ["Ready"] + status: + type: string + enum: ["True","False","Unknown"] + lastUpdateTime: + type: string + format: datetime + lastTransitionTime: + type: string + format: datetime + reason: + type: string + minLength: 1 + message: + type: string diff --git a/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/_helpers-controller.tpl b/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/_helpers-controller.tpl new file mode 100644 index 00000000..4f012e93 --- /dev/null +++ b/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/_helpers-controller.tpl @@ -0,0 +1,13 @@ +{{- define "clustersecret-operator.controller.fullname" -}} +{{ include "clustersecret-operator.fullname" . }}-controller +{{- end }} + +{{- define "clustersecret-operator.controller.labels" -}} +{{ include "clustersecret-operator.labels" . }} +app.kubernetes.io/component: controller +{{- end }} + +{{- define "clustersecret-operator.controller.selectorLabels" -}} +{{ include "clustersecret-operator.selectorLabels" . }} +app.kubernetes.io/component: controller +{{- end }} \ No newline at end of file diff --git a/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/_helpers-webhook.tpl b/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/_helpers-webhook.tpl new file mode 100644 index 00000000..77a8a372 --- /dev/null +++ b/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/_helpers-webhook.tpl @@ -0,0 +1,13 @@ +{{- define "clustersecret-operator.webhook.fullname" -}} +{{ include "clustersecret-operator.fullname" . }}-webhook +{{- end }} + +{{- define "clustersecret-operator.webhook.labels" -}} +{{ include "clustersecret-operator.labels" . }} +app.kubernetes.io/component: webhook +{{- end }} + +{{- define "clustersecret-operator.webhook.selectorLabels" -}} +{{ include "clustersecret-operator.selectorLabels" . }} +app.kubernetes.io/component: webhook +{{- end }} \ No newline at end of file diff --git a/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/_helpers.tpl b/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/_helpers.tpl new file mode 100644 index 00000000..48b65844 --- /dev/null +++ b/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/_helpers.tpl @@ -0,0 +1,51 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "clustersecret-operator.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "clustersecret-operator.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "clustersecret-operator.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "clustersecret-operator.labels" -}} +helm.sh/chart: {{ include "clustersecret-operator.chart" . }} +{{ include "clustersecret-operator.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "clustersecret-operator.selectorLabels" -}} +app.kubernetes.io/name: {{ include "clustersecret-operator.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} diff --git a/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/controller-deployment.yaml b/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/controller-deployment.yaml new file mode 100644 index 00000000..e42a33a3 --- /dev/null +++ b/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/controller-deployment.yaml @@ -0,0 +1,96 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "clustersecret-operator.controller.fullname" . }} + labels: + {{- include "clustersecret-operator.controller.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.controller.replicaCount }} + selector: + matchLabels: + {{- include "clustersecret-operator.controller.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.controller.podAnnotations | default .Values.global.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "clustersecret-operator.controller.selectorLabels" . | nindent 8 }} + {{- with .Values.controller.podLabels | default .Values.global.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.controller.imagePullSecrets | default .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 6 }} + {{- end }} + {{- with .Values.controller.podSecurityContext | default .Values.global.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.controller.nodeSelector | default .Values.global.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.controller.affinity | default .Values.global.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.controller.topologySpreadConstraints | default .Values.global.topologySpreadConstraints }} + topologySpreadConstraints: + {{- range . }} + - {{ toYaml . | nindent 8 | trim }} + {{- if not .labelSelector }} + labelSelector: + matchLabels: + {{- include "clustersecret-operator.controller.selectorLabels" $ | nindent 12 }} + {{- end }} + {{- end }} + {{- else }} + topologySpreadConstraints: + - maxSkew: 1 + topologyKey: kubernetes.io/hostname + nodeTaintsPolicy: Honor + whenUnsatisfiable: {{ .Values.controller.defaultHostNameSpreadPolicy | default .Values.global.defaultHostNameSpreadPolicy }} + labelSelector: + matchLabels: + {{- include "clustersecret-operator.controller.selectorLabels" . | nindent 12 }} + - maxSkew: 1 + topologyKey: topology.kubernetes.io/zone + nodeTaintsPolicy: Honor + whenUnsatisfiable: {{ .Values.controller.defaultZoneSpreadPolicy | default .Values.global.defaultZoneSpreadPolicy }} + labelSelector: + matchLabels: + {{- include "clustersecret-operator.controller.selectorLabels" . | nindent 12 }} + {{- end }} + {{- with .Values.controller.tolerations | default .Values.global.tolerations }} + tolerations: + {{- toYaml . | nindent 6 }} + {{- end }} + {{- with .Values.controller.priorityClassName | default .Values.global.priorityClassName }} + priorityClassName: {{ . }} + {{- end }} + serviceAccountName: {{ include "clustersecret-operator.controller.fullname" . }} + automountServiceAccountToken: true + containers: + - name: controller + image: {{ .Values.controller.image.repository }}:{{ .Values.controller.image.tag | default .Values.global.image.tag | default .Chart.AppVersion }} + imagePullPolicy: {{ .Values.controller.image.pullPolicy | default .Values.global.image.pullPolicy }} + {{- with .Values.controller.securityContext | default .Values.global.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + resources: + {{- toYaml .Values.controller.resources | nindent 12 }} + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + args: + - "--lease_namespace={{ .Release.Namespace }}" + - "--lease_name={{ include "clustersecret-operator.controller.fullname" . }}" + - "--lease_id=$(POD_NAME)" + - "--v={{ .Values.controller.logLevel | default .Values.global.logLevel | default 0 }}" diff --git a/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/controller-pdb.yaml b/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/controller-pdb.yaml new file mode 100644 index 00000000..6f1985d5 --- /dev/null +++ b/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/controller-pdb.yaml @@ -0,0 +1,13 @@ +{{- if ge (int .Values.controller.replicaCount) 2 }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "clustersecret-operator.controller.fullname" . }} + labels: + {{- include "clustersecret-operator.controller.labels" . | nindent 4 }} +spec: + minAvailable: 1 + selector: + matchLabels: + {{- include "clustersecret-operator.controller.selectorLabels" . | nindent 6 }} +{{- end }} \ No newline at end of file diff --git a/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/controller-rbac.yaml b/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/controller-rbac.yaml new file mode 100644 index 00000000..040a5559 --- /dev/null +++ b/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/controller-rbac.yaml @@ -0,0 +1,74 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "clustersecret-operator.controller.fullname" . }} + labels: + {{- include "clustersecret-operator.controller.labels" . | nindent 4 }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + namespace: {{ .Release.Namespace }} + name: {{ include "clustersecret-operator.controller.fullname" . }} + labels: + {{- include "clustersecret-operator.controller.labels" . | nindent 4 }} +rules: +- apiGroups: ["coordination.k8s.io"] + resources: ["leases"] + verbs: ["create"] +- apiGroups: ["coordination.k8s.io"] + resources: ["leases"] + resourceNames: ["{{ include "clustersecret-operator.controller.fullname" . }}"] + verbs: ["get","list","watch","update","patch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + namespace: {{ .Release.Namespace }} + name: {{ include "clustersecret-operator.controller.fullname" . }} + labels: + {{- include "clustersecret-operator.controller.labels" . | nindent 4 }} +subjects: +- kind: ServiceAccount + namespace: {{ .Release.Namespace }} + name: {{ include "clustersecret-operator.controller.fullname" . }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "clustersecret-operator.controller.fullname" . }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "clustersecret-operator.controller.fullname" . }} + labels: + {{- include "clustersecret-operator.controller.labels" . | nindent 4 }} +rules: +- apiGroups: [""] + resources: ["secrets"] + verbs: ["get","list","watch","create","update","patch","delete"] +- apiGroups: [""] + resources: ["namespaces"] + verbs: ["get","list","watch"] +- apiGroups: ["core.cs.sap.com"] + resources: ["clustersecrets","clustersecrets/status"] + verbs: ["get","list","watch","update"] +- apiGroups: ["","events.k8s.io"] + resources: ["events"] + verbs: ["*"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "clustersecret-operator.controller.fullname" . }} + labels: + {{- include "clustersecret-operator.controller.labels" . | nindent 4 }} +subjects: +- kind: ServiceAccount + namespace: {{ .Release.Namespace }} + name: {{ include "clustersecret-operator.controller.fullname" . }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "clustersecret-operator.controller.fullname" . }} diff --git a/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/tests/rbac.yaml b/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/tests/rbac.yaml new file mode 100644 index 00000000..83205ec0 --- /dev/null +++ b/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/tests/rbac.yaml @@ -0,0 +1,58 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "clustersecret-operator.fullname" . }}-test + labels: + {{- include "clustersecret-operator.labels" . | nindent 4 }} + annotations: + helm.sh/hook: test + helm.sh/hook-weight: "-1" + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "clustersecret-operator.fullname" . }}-test + labels: + {{- include "clustersecret-operator.labels" . | nindent 4 }} + annotations: + helm.sh/hook: test + helm.sh/hook-weight: "-1" + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded +rules: +- apiGroups: + - core.cs.sap.com + resources: + - clustersecrets + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "clustersecret-operator.fullname" . }}-test + labels: + {{- include "clustersecret-operator.labels" . | nindent 4 }} + annotations: + helm.sh/hook: test + helm.sh/hook-weight: "-1" + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded +subjects: +- kind: ServiceAccount + namespace: {{ .Release.Namespace }} + name: {{ include "clustersecret-operator.fullname" . }}-test +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "clustersecret-operator.fullname" . }}-test diff --git a/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/tests/test-1.yaml b/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/tests/test-1.yaml new file mode 100644 index 00000000..2c163a78 --- /dev/null +++ b/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/tests/test-1.yaml @@ -0,0 +1,44 @@ +{{- $clusterSecretName := printf "%s-test-1-%s" (include "clustersecret-operator.fullname" .) (randAlphaNum 10 | lower) }} +--- +apiVersion: core.cs.sap.com/v1alpha1 +kind: ClusterSecret +metadata: + name: {{ $clusterSecretName }} + labels: + {{- include "clustersecret-operator.labels" . | nindent 4 }} + annotations: + helm.sh/hook: test + helm.sh/hook-weight: "0" + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded +spec: + namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: default + template: + type: Opaque + data: + mykey: bXl2YWx1ZQ== +--- +apiVersion: v1 +kind: Pod +metadata: + name: {{ include "clustersecret-operator.fullname" . }}-test-1 + labels: + {{- include "clustersecret-operator.labels" . | nindent 4 }} + annotations: + helm.sh/hook: test + helm.sh/hook-weight: "1" + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded +spec: + containers: + - name: kubectl + image: ghcr.io/sap/kubectl-container:{{ .Capabilities.KubeVersion.Version }} + command: + - bash + - -ec + - | + kubectl wait clustersecrets.core.cs.sap.com/{{ $clusterSecretName }} --for condition=Ready --timeout 30s + kubectl -n default get secret {{ $clusterSecretName }} + serviceAccountName: {{ include "clustersecret-operator.fullname" . }}-test + terminationGracePeriodSeconds: 3 + restartPolicy: Never diff --git a/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/webhook-admission.yaml b/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/webhook-admission.yaml new file mode 100644 index 00000000..5e49e23c --- /dev/null +++ b/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/webhook-admission.yaml @@ -0,0 +1,107 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "clustersecret-operator.webhook.fullname" . }} + labels: + {{- include "clustersecret-operator.webhook.labels" . | nindent 4 }} +spec: + type: {{ .Values.webhook.service.type }} + ports: + - port: {{ .Values.webhook.service.port }} + targetPort: 1443 + protocol: TCP + name: https + selector: + {{- include "clustersecret-operator.webhook.selectorLabels" . | nindent 4 }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "clustersecret-operator.webhook.fullname" . }}-tls + labels: + {{- include "clustersecret-operator.webhook.labels" . | nindent 4 }} +type: Opaque +data: + {{- $data := (lookup "v1" "Secret" .Release.Namespace (printf "%s-tls" (include "clustersecret-operator.webhook.fullname" .))).data }} + {{- $caCert := "" }} + {{- if $data }} + {{ $data | toYaml | nindent 2 }} + {{- $caCert = index $data "ca.crt" }} + {{- else }} + {{- $cn := printf "%s.%s.svc" (include "clustersecret-operator.webhook.fullname" .) .Release.Namespace }} + {{- $ca := genCA (printf "%s-ca" (include "clustersecret-operator.webhook.fullname" .)) 36500 }} + {{- $cert := genSignedCert $cn (list "127.0.0.1") (list $cn "localhost") 36500 $ca }} + ca.crt: {{ $ca.Cert | b64enc }} + tls.crt: {{ $cert.Cert | b64enc }} + tls.key: {{ $cert.Key | b64enc }} + {{- $caCert = $ca.Cert | b64enc }} + {{- end }} +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: clustersecret-operator + labels: + {{- include "clustersecret-operator.webhook.labels" . | nindent 4 }} +webhooks: +- name: clustersecret-operator.cs.sap.com + admissionReviewVersions: + - v1 + clientConfig: + caBundle: {{ $caCert }} + service: + name: {{ include "clustersecret-operator.webhook.fullname" . }} + namespace: {{ .Release.Namespace }} + path: /validation + port: {{ .Values.webhook.service.port }} + rules: + - apiGroups: + - core.cs.sap.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + - DELETE + resources: + - clustersecrets + scope: Cluster + matchPolicy: Exact + sideEffects: None + timeoutSeconds: 10 + failurePolicy: Fail +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: clustersecret-operator + labels: + {{- include "clustersecret-operator.webhook.labels" . | nindent 4 }} +webhooks: +- name: clustersecret-operator.cs.sap.com + admissionReviewVersions: + - v1 + clientConfig: + caBundle: {{ $caCert }} + service: + name: {{ include "clustersecret-operator.webhook.fullname" . }} + namespace: {{ .Release.Namespace }} + path: /mutation + port: {{ .Values.webhook.service.port }} + rules: + - apiGroups: + - core.cs.sap.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + - DELETE + resources: + - clustersecrets + scope: Cluster + matchPolicy: Exact + sideEffects: None + timeoutSeconds: 10 + failurePolicy: Fail \ No newline at end of file diff --git a/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/webhook-deployment.yaml b/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/webhook-deployment.yaml new file mode 100644 index 00000000..df766a52 --- /dev/null +++ b/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/webhook-deployment.yaml @@ -0,0 +1,109 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "clustersecret-operator.webhook.fullname" . }} + labels: + {{- include "clustersecret-operator.webhook.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.webhook.replicaCount }} + selector: + matchLabels: + {{- include "clustersecret-operator.webhook.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.webhook.podAnnotations | default .Values.global.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "clustersecret-operator.webhook.selectorLabels" . | nindent 8 }} + {{- with .Values.webhook.podLabels | default .Values.global.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.webhook.imagePullSecrets | default .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 6 }} + {{- end }} + {{- with .Values.webhook.podSecurityContext | default .Values.global.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.webhook.nodeSelector | default .Values.global.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.webhook.affinity | default .Values.global.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.webhook.topologySpreadConstraints | default .Values.global.topologySpreadConstraints }} + topologySpreadConstraints: + {{- range . }} + - {{ toYaml . | nindent 8 | trim }} + {{- if not .labelSelector }} + labelSelector: + matchLabels: + {{- include "clustersecret-operator.webhook.selectorLabels" $ | nindent 12 }} + {{- end }} + {{- end }} + {{- else }} + topologySpreadConstraints: + - maxSkew: 1 + topologyKey: kubernetes.io/hostname + nodeTaintsPolicy: Honor + whenUnsatisfiable: {{ .Values.webhook.defaultHostNameSpreadPolicy | default .Values.global.defaultHostNameSpreadPolicy }} + labelSelector: + matchLabels: + {{- include "clustersecret-operator.webhook.selectorLabels" . | nindent 12 }} + - maxSkew: 1 + topologyKey: topology.kubernetes.io/zone + nodeTaintsPolicy: Honor + whenUnsatisfiable: {{ .Values.webhook.defaultZoneSpreadPolicy | default .Values.global.defaultZoneSpreadPolicy }} + labelSelector: + matchLabels: + {{- include "clustersecret-operator.webhook.selectorLabels" . | nindent 12 }} + {{- end }} + {{- with .Values.webhook.tolerations | default .Values.global.tolerations }} + tolerations: + {{- toYaml . | nindent 6 }} + {{- end }} + {{- with .Values.webhook.priorityClassName | default .Values.global.priorityClassName }} + priorityClassName: {{ . }} + {{- end }} + serviceAccountName: {{ include "clustersecret-operator.webhook.fullname" . }} + automountServiceAccountToken: true + containers: + - name: webhook + image: {{ .Values.webhook.image.repository }}:{{ .Values.webhook.image.tag | default .Values.global.image.tag | default .Chart.AppVersion }} + imagePullPolicy: {{ .Values.webhook.image.pullPolicy | default .Values.global.image.pullPolicy }} + {{- with .Values.webhook.securityContext | default .Values.global.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + resources: + {{- toYaml .Values.webhook.resources | nindent 12 }} + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + args: + - "--bind_address=:1443" + - "--tls_enabled=true" + - "--tls_key_file=/app/etc/ssl/tls.key" + - "--tls_cert_file=/app/etc/ssl/tls.crt" + - "--v={{ .Values.webhook.logLevel | default .Values.global.logLevel | default 0 }}" + volumeMounts: + - name: ssl + mountPath: /app/etc/ssl + volumes: + - name: ssl + secret: + secretName: {{ include "clustersecret-operator.webhook.fullname" . }}-tls + items: + - key: tls.key + path: tls.key + - key: tls.crt + path: tls.crt diff --git a/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/webhook-pdb.yaml b/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/webhook-pdb.yaml new file mode 100644 index 00000000..bdfc1bc1 --- /dev/null +++ b/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/webhook-pdb.yaml @@ -0,0 +1,13 @@ +{{- if ge (int .Values.webhook.replicaCount) 2 }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "clustersecret-operator.webhook.fullname" . }} + labels: + {{- include "clustersecret-operator.webhook.labels" . | nindent 4 }} +spec: + minAvailable: 1 + selector: + matchLabels: + {{- include "clustersecret-operator.webhook.selectorLabels" . | nindent 6 }} +{{- end }} \ No newline at end of file diff --git a/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/webhook-rbac.yaml b/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/webhook-rbac.yaml new file mode 100644 index 00000000..c149e28a --- /dev/null +++ b/packages/system/clustersecret-operator/charts/clustersecret-operator/templates/webhook-rbac.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "clustersecret-operator.webhook.fullname" . }} + labels: + {{- include "clustersecret-operator.webhook.labels" . | nindent 4 }} diff --git a/packages/system/clustersecret-operator/charts/clustersecret-operator/values.yaml b/packages/system/clustersecret-operator/charts/clustersecret-operator/values.yaml new file mode 100644 index 00000000..3eb14836 --- /dev/null +++ b/packages/system/clustersecret-operator/charts/clustersecret-operator/values.yaml @@ -0,0 +1,138 @@ +# -- Override full name +fullnameOverride: "" +# -- Override name +nameOverride: "" + +global: + image: + # -- Image tag (defauls to .Chart.AppVersion) + tag: "" + # -- Image pull policy + pullPolicy: IfNotPresent + # -- Image pull secrets + imagePullSecrets: [] + # -- Affinity settings + affinity: {} + # -- Node selector + nodeSelector: {} + # -- Topology spread constraints (if unspecified, default constraints for hostname and zone will be generated) + topologySpreadConstraints: [] + # -- Default topology spread policy for hostname + defaultHostNameSpreadPolicy: ScheduleAnyway + # -- Default topology spread policy for zone + defaultZoneSpreadPolicy: ScheduleAnyway + # -- Tolerations + tolerations: [] + # -- Priority class + priorityClassName: "" + # -- Pod security context + podSecurityContext: {} + # -- Additional pod annotations + podAnnotations: {} + # -- Additional pod labels + podLabels: {} + # -- Container security context + securityContext: {} + # -- Log level + logLevel: 0 + +controller: + # -- Replica count + replicaCount: 1 + image: + # -- Image repository + repository: ghcr.io/sap/clustersecret-operator/controller + # -- Image tag (defauls to .Chart.AppVersion) + tag: "" + # -- Image pull policy + pullPolicy: "" + # -- Image pull secrets + imagePullSecrets: [] + # -- Affinity settings + affinity: {} + # -- Node selector + nodeSelector: {} + # -- Topology spread constraints (if unspecified, default constraints for hostname and zone will be generated) + topologySpreadConstraints: [] + # -- Default topology spread policy for hostname + defaultHostNameSpreadPolicy: "" + # -- Default topology spread policy for zone + defaultZoneSpreadPolicy: "" + # -- Tolerations + tolerations: [] + # -- Priority class + priorityClassName: "" + # -- Pod security context + podSecurityContext: {} + # -- Additional pod annotations + podAnnotations: {} + # -- Additional pod labels + podLabels: {} + # -- Container security context + securityContext: {} + resources: + limits: + # -- Memory limit + memory: 200Mi + # -- CPU limit + cpu: 0.1 + requests: + # -- Memory request + memory: 20Mi + # -- CPU request + cpu: 0.01 + # -- Log level + logLevel: 0 + +webhook: + # -- Replica count + replicaCount: 1 + image: + # -- Image repository + repository: ghcr.io/sap/clustersecret-operator/webhook + # -- Image tag (defauls to .Chart.AppVersion) + tag: "" + # -- Image pull policy + pullPolicy: "" + # -- Image pull secrets + imagePullSecrets: [] + # -- Affinity settings + affinity: {} + # -- Node selector + nodeSelector: {} + # -- Topology spread constraints (if unspecified, default constraints for hostname and zone will be generated) + topologySpreadConstraints: [] + # -- Default topology spread policy for hostname + defaultHostNameSpreadPolicy: "" + # -- Default topology spread policy for zone + defaultZoneSpreadPolicy: "" + # -- Tolerations + tolerations: [] + # -- Priority class + priorityClassName: "" + # -- Pod security context + podSecurityContext: {} + # -- Additional pod annotations + podAnnotations: {} + # -- Additional pod labels + podLabels: {} + # -- Container security context + securityContext: {} + resources: + limits: + # -- Memory limit + memory: 100Mi + # -- CPU limit + cpu: 0.1 + requests: + # -- Memory request + memory: 20Mi + # -- CPU request + cpu: 0.01 + service: + # -- Service type + type: ClusterIP + # -- Service port + port: 443 + # -- Log level + logLevel: 0 diff --git a/packages/system/clustersecret-operator/values.yaml b/packages/system/clustersecret-operator/values.yaml new file mode 100644 index 00000000..ff7b161a --- /dev/null +++ b/packages/system/clustersecret-operator/values.yaml @@ -0,0 +1 @@ +clustersecret-operator: {} From bb39a0f73f20636b137dbe25cb1474d206a21e1a Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 10 Feb 2026 20:07:16 +0100 Subject: [PATCH 210/889] Update kilo v0.7.0 from a fork Signed-off-by: Andrei Kvapil --- Makefile | 1 - packages/system/kilo/Makefile | 22 +- packages/system/kilo/images/kilo/Dockerfile | 47 ---- .../system/kilo/images/kilo/patches/1.diff | 246 ------------------ .../system/kilo/images/kilo/patches/2.diff | 30 --- packages/system/kilo/templates/kilo.yaml | 2 +- packages/system/kilo/values.yaml | 4 +- 7 files changed, 8 insertions(+), 344 deletions(-) delete mode 100644 packages/system/kilo/images/kilo/Dockerfile delete mode 100644 packages/system/kilo/images/kilo/patches/1.diff delete mode 100644 packages/system/kilo/images/kilo/patches/2.diff diff --git a/Makefile b/Makefile index ba0c8df7..f658747f 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,6 @@ build: build-deps make -C packages/system/dashboard image make -C packages/system/metallb image make -C packages/system/kamaji image - make -C packages/system/kilo image make -C packages/system/bucket image make -C packages/system/objectstorage-controller image make -C packages/system/grafana-operator image diff --git a/packages/system/kilo/Makefile b/packages/system/kilo/Makefile index 3d278db3..fcce8bd9 100644 --- a/packages/system/kilo/Makefile +++ b/packages/system/kilo/Makefile @@ -5,21 +5,9 @@ include ../../../hack/common-envs.mk include ../../../hack/package.mk update: - wget https://raw.githubusercontent.com/squat/kilo/refs/heads/main/manifests/crds.yaml -O templates/crds.yaml - wget https://raw.githubusercontent.com/squat/kilo/refs/heads/main/manifests/kilo-typhoon-flannel.yaml -O templates/kilo.yaml - sed -i 's|kube-system|cozy-kilo|g' templates/kilo.yaml - sed -i 's|--compatibility=flannel|--compatibility=cilium|' templates/kilo.yaml - sed -i '/- --local=false/a \ - --mesh-granularity=full\n - --service-cidr=10.244.0.0/24\n - --service-cidr=10.96.0.0/24' templates/kilo.yaml - -image: - docker buildx build images/kilo \ - --tag $(REGISTRY)/kilo:$(call settag,$(TAG)) \ - --cache-from type=registry,ref=$(REGISTRY)/kilo:latest \ - --cache-to type=inline \ - --metadata-file images/kilo.json \ - $(BUILDX_ARGS) - REPOSITORY="$(REGISTRY)/kilo" \ - yq -i '.kilo.image.repository = strenv(REPOSITORY)' values.yaml - TAG=$(TAG)@$$(yq e '."containerimage.digest"' images/kilo.json -o json -r) \ + tag=$$(git ls-remote --tags --sort="v:refname" https://github.com/cozystack/kilo | awk -F'[/^]' 'END{print $$3}') && \ + digest=$$(skopeo inspect --override-os linux --format '{{.Digest}}' docker://ghcr.io/cozystack/cozystack/kilo:$${tag}) && \ + REPOSITORY="ghcr.io/cozystack/cozystack/kilo" \ + yq -i '.kilo.image.repository = strenv(REPOSITORY)' values.yaml && \ + TAG="$${tag}@$${digest}" \ yq -i '.kilo.image.tag = strenv(TAG)' values.yaml - rm -f images/kilo.json diff --git a/packages/system/kilo/images/kilo/Dockerfile b/packages/system/kilo/images/kilo/Dockerfile deleted file mode 100644 index b36a85d0..00000000 --- a/packages/system/kilo/images/kilo/Dockerfile +++ /dev/null @@ -1,47 +0,0 @@ -# Build the manager binary -ARG FROM=alpine -FROM $FROM AS cni -ARG GOARCH=amd64 -ARG CNI_PLUGINS_VERSION=v1.1.1 -RUN apk add --no-cache curl && \ - curl -Lo cni.tar.gz https://github.com/containernetworking/plugins/releases/download/$CNI_PLUGINS_VERSION/cni-plugins-linux-$GOARCH-$CNI_PLUGINS_VERSION.tgz && \ - tar -xf cni.tar.gz - -FROM golang:1.19.0 as builder - -ARG VERSION=0.6.0 -ARG TARGETOS -ARG TARGETARCH - -WORKDIR /workspace - -RUN curl -sSL https://github.com/squat/kilo/archive/refs/tags/${VERSION}.tar.gz | tar -xzvf- --strip=1 - -COPY patches /patches -RUN git apply /patches/*.diff - - -RUN set -eux; \ - GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 \ - go build -mod=vendor \ - -ldflags "-X github.com/squat/kilo/pkg/version.Version=$VERSION" \ - -o /out/kg \ - ./cmd/kg/... ; \ - GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 \ - go build -mod=vendor \ - -ldflags "-X github.com/squat/kilo/pkg/version.Version=$VERSION" \ - -o /out/kgctl \ - ./cmd/kgctl/... - -FROM alpine:3.20 -ARG GOARCH -ARG ALPINE_VERSION=v3.20 -LABEL maintainer="squat " -RUN echo -e "https://alpine.global.ssl.fastly.net/alpine/$ALPINE_VERSION/main\nhttps://alpine.global.ssl.fastly.net/alpine/$ALPINE_VERSION/community" > /etc/apk/repositories && \ - apk add --no-cache ipset iptables ip6tables graphviz font-noto -COPY --from=cni bridge host-local loopback portmap /opt/cni/bin/ -ADD https://raw.githubusercontent.com/kubernetes-sigs/iptables-wrappers/e139a115350974aac8a82ec4b815d2845f86997e/iptables-wrapper-installer.sh / -RUN chmod 700 /iptables-wrapper-installer.sh && /iptables-wrapper-installer.sh --no-sanity-check -COPY --from=builder /out/kg /opt/bin/ -COPY --from=builder /out/kgctl /opt/bin/ -ENTRYPOINT ["/opt/bin/kg"] \ No newline at end of file diff --git a/packages/system/kilo/images/kilo/patches/1.diff b/packages/system/kilo/images/kilo/patches/1.diff deleted file mode 100644 index 9597593e..00000000 --- a/packages/system/kilo/images/kilo/patches/1.diff +++ /dev/null @@ -1,246 +0,0 @@ -diff --git a/cmd/kg/main.go b/cmd/kg/main.go -index c2ad6d5..301819f 100644 ---- a/cmd/kg/main.go -+++ b/cmd/kg/main.go -@@ -120,6 +120,7 @@ var ( - topologyLabel string - port int - serviceCIDRsRaw []string -+ internalCIDRsRaw []string - subnet string - resyncPeriod time.Duration - iptablesForwardRule bool -@@ -152,6 +153,7 @@ func init() { - cmd.Flags().StringVar(&topologyLabel, "topology-label", k8s.RegionLabelKey, "Kubernetes node label used to group nodes into logical locations.") - cmd.Flags().IntVar(&port, "port", mesh.DefaultKiloPort, "The port over which WireGuard peers should communicate.") - cmd.Flags().StringSliceVar(&serviceCIDRsRaw, "service-cidr", nil, "The service CIDR for the Kubernetes cluster. Can be provided optionally to avoid masquerading packets sent to service IPs. Can be specified multiple times.") -+ cmd.Flags().StringSliceVar(&internalCIDRsRaw, "internal-cidr", nil, "CIDRs to consider for internal IP auto-detection. If specified, only IPs within these CIDRs will be used. Can be specified multiple times.") - cmd.Flags().StringVar(&subnet, "subnet", mesh.DefaultKiloSubnet.String(), "CIDR from which to allocate addresses for WireGuard interfaces.") - cmd.Flags().DurationVar(&resyncPeriod, "resync-period", 30*time.Second, "How often should the Kilo controllers reconcile?") - cmd.Flags().BoolVar(&iptablesForwardRule, "iptables-forward-rules", false, "Add default accept rules to the FORWARD chain in iptables. Warning: this may break firewalls with a deny all policy and is potentially insecure!") -@@ -266,7 +268,16 @@ func runRoot(_ *cobra.Command, _ []string) error { - serviceCIDRs = append(serviceCIDRs, s) - } - -- m, err := mesh.New(b, enc, gr, hostname, port, s, local, cni, cniPath, iface, cleanUp, cleanUpIface, createIface, mtu, resyncPeriod, prioritisePrivateAddr, iptablesForwardRule, serviceCIDRs, log.With(logger, "component", "kilo"), registry) -+ var internalCIDRs []*net.IPNet -+ for _, internalCIDR := range internalCIDRsRaw { -+ _, s, err := net.ParseCIDR(internalCIDR) -+ if err != nil { -+ return fmt.Errorf("failed to parse %q as CIDR: %v", internalCIDR, err) -+ } -+ internalCIDRs = append(internalCIDRs, s) -+ } -+ -+ m, err := mesh.New(b, enc, gr, hostname, port, s, local, cni, cniPath, iface, cleanUp, cleanUpIface, createIface, mtu, resyncPeriod, prioritisePrivateAddr, iptablesForwardRule, internalCIDRs, serviceCIDRs, log.With(logger, "component", "kilo"), registry) - if err != nil { - return fmt.Errorf("failed to create Kilo mesh: %v", err) - } -diff --git a/manifests/kilo-bootkube-flannel.yaml b/manifests/kilo-bootkube-flannel.yaml -index 64e3500..fb3d25f 100644 ---- a/manifests/kilo-bootkube-flannel.yaml -+++ b/manifests/kilo-bootkube-flannel.yaml -@@ -74,11 +74,16 @@ spec: - - --cni=false - - --compatibility=flannel - - --local=false -+ - --internal-cidr=$(NODE_IP)/32 - env: - - name: NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName -+ - name: NODE_IP -+ valueFrom: -+ fieldRef: -+ fieldPath: status.hostIP - ports: - - containerPort: 1107 - name: metrics -diff --git a/manifests/kilo-k3s-cilium.yaml b/manifests/kilo-k3s-cilium.yaml -index d75c93c..c98e518 100644 ---- a/manifests/kilo-k3s-cilium.yaml -+++ b/manifests/kilo-k3s-cilium.yaml -@@ -106,11 +106,16 @@ spec: - - --encapsulate=crosssubnet - - --clean-up-interface=true - - --log-level=all -+ - --internal-cidr=$(NODE_IP)/32 - env: - - name: NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName -+ - name: NODE_IP -+ valueFrom: -+ fieldRef: -+ fieldPath: status.hostIP - ports: - - containerPort: 1107 - name: metrics -diff --git a/manifests/kilo-k3s-flannel.yaml b/manifests/kilo-k3s-flannel.yaml -index 612cb11..07b4c87 100644 ---- a/manifests/kilo-k3s-flannel.yaml -+++ b/manifests/kilo-k3s-flannel.yaml -@@ -103,11 +103,16 @@ spec: - - --cni=false - - --compatibility=flannel - - --local=false -+ - --internal-cidr=$(NODE_IP)/32 - env: - - name: NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName -+ - name: NODE_IP -+ valueFrom: -+ fieldRef: -+ fieldPath: status.hostIP - ports: - - containerPort: 1107 - name: metrics -diff --git a/manifests/kilo-kubeadm-cilium.yaml b/manifests/kilo-kubeadm-cilium.yaml -index 5bf065a..ac0bf90 100644 ---- a/manifests/kilo-kubeadm-cilium.yaml -+++ b/manifests/kilo-kubeadm-cilium.yaml -@@ -79,11 +79,16 @@ spec: - - --clean-up-interface=true - - --subnet=172.31.254.0/24 - - --log-level=all -+ - --internal-cidr=$(NODE_IP)/32 - env: - - name: NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName -+ - name: NODE_IP -+ valueFrom: -+ fieldRef: -+ fieldPath: status.hostIP - ports: - - containerPort: 1107 - name: metrics -diff --git a/manifests/kilo-kubeadm-flannel-userspace.yaml b/manifests/kilo-kubeadm-flannel-userspace.yaml -index c4ce25b..5d1824e 100644 ---- a/manifests/kilo-kubeadm-flannel-userspace.yaml -+++ b/manifests/kilo-kubeadm-flannel-userspace.yaml -@@ -88,11 +88,16 @@ spec: - - --cni=false - - --compatibility=flannel - - --local=false -+ - --internal-cidr=$(NODE_IP)/32 - env: - - name: NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName -+ - name: NODE_IP -+ valueFrom: -+ fieldRef: -+ fieldPath: status.hostIP - ports: - - containerPort: 1107 - name: metrics -diff --git a/manifests/kilo-kubeadm-flannel.yaml b/manifests/kilo-kubeadm-flannel.yaml -index fea35dc..ff6bb25 100644 ---- a/manifests/kilo-kubeadm-flannel.yaml -+++ b/manifests/kilo-kubeadm-flannel.yaml -@@ -74,11 +74,16 @@ spec: - - --cni=false - - --compatibility=flannel - - --local=false -+ - --internal-cidr=$(NODE_IP)/32 - env: - - name: NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName -+ - name: NODE_IP -+ valueFrom: -+ fieldRef: -+ fieldPath: status.hostIP - ports: - - containerPort: 1107 - name: metrics -diff --git a/manifests/kilo-typhoon-flannel.yaml b/manifests/kilo-typhoon-flannel.yaml -index 0e4d9b2..cbd467a 100644 ---- a/manifests/kilo-typhoon-flannel.yaml -+++ b/manifests/kilo-typhoon-flannel.yaml -@@ -74,11 +74,16 @@ spec: - - --cni=false - - --compatibility=flannel - - --local=false -+ - --internal-cidr=$(NODE_IP)/32 - env: - - name: NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName -+ - name: NODE_IP -+ valueFrom: -+ fieldRef: -+ fieldPath: status.hostIP - ports: - - containerPort: 1107 - name: metrics -diff --git a/pkg/mesh/discoverips.go b/pkg/mesh/discoverips.go -index c8991d9..4598472 100644 ---- a/pkg/mesh/discoverips.go -+++ b/pkg/mesh/discoverips.go -@@ -40,7 +40,8 @@ import ( - // - private IP assigned to interface of default route - // - private IP assigned to local interface - // - if no IP was found, return nil and an error. --func getIP(hostname string, ignoreIfaces ...int) (*net.IPNet, *net.IPNet, error) { -+// If allowedCIDRs is not empty, only IPs within these CIDRs will be considered for private IP selection. -+func getIP(hostname string, allowedCIDRs []*net.IPNet, ignoreIfaces ...int) (*net.IPNet, *net.IPNet, error) { - ignore := make(map[string]struct{}) - for i := range ignoreIfaces { - if ignoreIfaces[i] == 0 { -@@ -144,6 +145,10 @@ func getIP(hostname string, ignoreIfaces ...int) (*net.IPNet, *net.IPNet, error) - if _, ok := ignore[tmpPriv[i].String()]; ok { - continue - } -+ // If allowedCIDRs is specified, filter private IPs by these CIDRs. -+ if len(allowedCIDRs) > 0 && !isInCIDRs(tmpPriv[i].IP, allowedCIDRs) { -+ continue -+ } - priv = append(priv, tmpPriv[i]) - } - for i := range tmpPub { -@@ -290,3 +295,13 @@ func defaultInterface() (*net.Interface, error) { - - return nil, errors.New("failed to find default route") - } -+ -+// isInCIDRs checks if the given IP is within any of the provided CIDRs. -+func isInCIDRs(ip net.IP, cidrs []*net.IPNet) bool { -+ for _, cidr := range cidrs { -+ if cidr.Contains(ip) { -+ return true -+ } -+ } -+ return false -+} -diff --git a/pkg/mesh/mesh.go b/pkg/mesh/mesh.go -index 3057d2a..e042467 100644 ---- a/pkg/mesh/mesh.go -+++ b/pkg/mesh/mesh.go -@@ -89,7 +89,7 @@ type Mesh struct { - } - - // New returns a new Mesh instance. --func New(backend Backend, enc encapsulation.Encapsulator, granularity Granularity, hostname string, port int, subnet *net.IPNet, local, cni bool, cniPath, iface string, cleanup bool, cleanUpIface bool, createIface bool, mtu uint, resyncPeriod time.Duration, prioritisePrivateAddr, iptablesForwardRule bool, serviceCIDRs []*net.IPNet, logger log.Logger, registerer prometheus.Registerer) (*Mesh, error) { -+func New(backend Backend, enc encapsulation.Encapsulator, granularity Granularity, hostname string, port int, subnet *net.IPNet, local, cni bool, cniPath, iface string, cleanup bool, cleanUpIface bool, createIface bool, mtu uint, resyncPeriod time.Duration, prioritisePrivateAddr, iptablesForwardRule bool, allowedInternalCIDRs []*net.IPNet, serviceCIDRs []*net.IPNet, logger log.Logger, registerer prometheus.Registerer) (*Mesh, error) { - if err := os.MkdirAll(kiloPath, 0700); err != nil { - return nil, fmt.Errorf("failed to create directory to store configuration: %v", err) - } -@@ -134,7 +134,7 @@ func New(backend Backend, enc encapsulation.Encapsulator, granularity Granularit - } - kiloIface = link.Attrs().Index - } -- privateIP, publicIP, err := getIP(hostname, kiloIface, enc.Index(), cniIndex) -+ privateIP, publicIP, err := getIP(hostname, allowedInternalCIDRs, kiloIface, enc.Index(), cniIndex) - if err != nil { - return nil, fmt.Errorf("failed to find public IP: %v", err) - } diff --git a/packages/system/kilo/images/kilo/patches/2.diff b/packages/system/kilo/images/kilo/patches/2.diff deleted file mode 100644 index 730c4469..00000000 --- a/packages/system/kilo/images/kilo/patches/2.diff +++ /dev/null @@ -1,30 +0,0 @@ -diff --git a/pkg/mesh/topology.go b/pkg/mesh/topology.go -index ca22bf6..1de8ae4 100644 ---- a/pkg/mesh/topology.go -+++ b/pkg/mesh/topology.go -@@ -263,17 +263,22 @@ CheckIPs: - } - } - // Check if allowed location IPs intersect with the allowed IPs. -+ // If the allowed location IP fully contains an allowed IP, that's fine - -+ // the more specific route will be used. Only warn if it's a partial overlap -+ // or if the allowed IP contains the allowed location IP. - for _, i := range s.allowedIPs { -- if intersect(ip, i) { -+ if intersect(ip, i) && !ip.Contains(i.IP) { - level.Warn(t.logger).Log("msg", "overlapping allowed location IPnet with allowed IPnets", "IP", ip.String(), "IP2", i.String(), "segment-location", s.location) - continue CheckIPs - } - } - // Check if allowed location IPs intersect with the private IPs of the segment. -+ // If the allowed location IP fully contains a private IP, that's fine. - for _, i := range s.privateIPs { - if ip.Contains(i) { -- level.Warn(t.logger).Log("msg", "overlapping allowed location IPnet with privateIP", "IP", ip.String(), "IP2", i.String(), "segment-location", s.location) -- continue CheckIPs -+ // This is OK - the allowed location IP contains the private IP, -+ // so the more specific route to the private IP will still work. -+ level.Debug(t.logger).Log("msg", "allowed location IPnet contains privateIP", "IP", ip.String(), "IP2", i.String(), "segment-location", s.location) - } - } - } diff --git a/packages/system/kilo/templates/kilo.yaml b/packages/system/kilo/templates/kilo.yaml index b0561b58..e209aed2 100644 --- a/packages/system/kilo/templates/kilo.yaml +++ b/packages/system/kilo/templates/kilo.yaml @@ -41,7 +41,7 @@ spec: {{- with .Values.kilo.transitCIDR }} - --subnet={{ . }} {{- end }} - - --mtu={{ .Values.kilo.mtu | default 1420 }} + - --mtu={{ .Values.kilo.mtu | default "auto" }} - --internal-cidr=$(NODE_IP)/32 env: - name: NODE_NAME diff --git a/packages/system/kilo/values.yaml b/packages/system/kilo/values.yaml index 4d32420e..279f62b0 100644 --- a/packages/system/kilo/values.yaml +++ b/packages/system/kilo/values.yaml @@ -1,11 +1,11 @@ kilo: image: pullPolicy: IfNotPresent - tag: v1.0.0-beta.2@sha256:45ad01a89ebb5311735660a0d1a1df36eda4b6f960be1b6319d2b94d2a7db701 + tag: v0.7.0@sha256:69c14b8292c0dc9045fd646d332811619a2d93bcc1f9e19e11da9a9a172c988c repository: ghcr.io/cozystack/cozystack/kilo podCIDR: 10.244.0.0/16 serviceCIDR: 10.96.0.0/16 transitCIDR: 100.66.0.0/16 meshGranularity: location cleanUpInterface: false - mtu: 1420 + mtu: auto From c1c1171c96786d6304322179d986ecfcff5973c3 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 10 Feb 2026 20:19:03 +0100 Subject: [PATCH 211/889] Update local-ccm v0.3.0 Signed-off-by: Andrei Kvapil --- .../charts/local-ccm/templates/_helpers.tpl | 32 ------- ...-clusterrole.yaml => nlc-clusterrole.yaml} | 8 +- ...nding.yaml => nlc-clusterrolebinding.yaml} | 9 +- .../local-ccm/templates/nlc-deployment.yaml | 78 +++++++++++++++ .../templates/nlc-serviceaccount.yaml | 13 +++ .../node-lifecycle-controller-deployment.yaml | 72 -------------- ...e-lifecycle-controller-serviceaccount.yaml | 8 -- .../local-ccm/charts/local-ccm/values.yaml | 95 ++++++++++++++----- 8 files changed, 171 insertions(+), 144 deletions(-) rename packages/system/local-ccm/charts/local-ccm/templates/{node-lifecycle-controller-clusterrole.yaml => nlc-clusterrole.yaml} (63%) rename packages/system/local-ccm/charts/local-ccm/templates/{node-lifecycle-controller-clusterrolebinding.yaml => nlc-clusterrolebinding.yaml} (52%) create mode 100644 packages/system/local-ccm/charts/local-ccm/templates/nlc-deployment.yaml create mode 100644 packages/system/local-ccm/charts/local-ccm/templates/nlc-serviceaccount.yaml delete mode 100644 packages/system/local-ccm/charts/local-ccm/templates/node-lifecycle-controller-deployment.yaml delete mode 100644 packages/system/local-ccm/charts/local-ccm/templates/node-lifecycle-controller-serviceaccount.yaml diff --git a/packages/system/local-ccm/charts/local-ccm/templates/_helpers.tpl b/packages/system/local-ccm/charts/local-ccm/templates/_helpers.tpl index 435ef6e4..0de841f5 100644 --- a/packages/system/local-ccm/charts/local-ccm/templates/_helpers.tpl +++ b/packages/system/local-ccm/charts/local-ccm/templates/_helpers.tpl @@ -63,35 +63,3 @@ Create the name of the service account to use {{- default "default" .Values.serviceAccount.name }} {{- end }} {{- end }} - -{{/* -Node Lifecycle Controller fullname -*/}} -{{- define "node-lifecycle-controller.fullname" -}} -{{- printf "%s-lifecycle" (include "local-ccm.fullname" .) | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Node Lifecycle Controller labels -*/}} -{{- define "node-lifecycle-controller.labels" -}} -helm.sh/chart: {{ include "local-ccm.chart" . }} -{{ include "node-lifecycle-controller.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} -{{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- with .Values.labels }} -{{ toYaml . }} -{{- end }} -{{- end }} - -{{/* -Node Lifecycle Controller selector labels -*/}} -{{- define "node-lifecycle-controller.selectorLabels" -}} -app.kubernetes.io/name: node-lifecycle-controller -app.kubernetes.io/instance: {{ .Release.Name }} -app: node-lifecycle-controller -component: node-lifecycle -{{- end }} diff --git a/packages/system/local-ccm/charts/local-ccm/templates/node-lifecycle-controller-clusterrole.yaml b/packages/system/local-ccm/charts/local-ccm/templates/nlc-clusterrole.yaml similarity index 63% rename from packages/system/local-ccm/charts/local-ccm/templates/node-lifecycle-controller-clusterrole.yaml rename to packages/system/local-ccm/charts/local-ccm/templates/nlc-clusterrole.yaml index 4d5e078d..9661261d 100644 --- a/packages/system/local-ccm/charts/local-ccm/templates/node-lifecycle-controller-clusterrole.yaml +++ b/packages/system/local-ccm/charts/local-ccm/templates/nlc-clusterrole.yaml @@ -2,16 +2,20 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: {{ include "node-lifecycle-controller.fullname" . }} + name: {{ include "local-ccm.fullname" . }}-nlc labels: - {{- include "node-lifecycle-controller.labels" . | nindent 4 }} + {{- include "local-ccm.labels" . | nindent 4 }} + app.kubernetes.io/component: node-lifecycle-controller rules: +# Node management - apiGroups: [""] resources: ["nodes"] verbs: ["get", "list", "watch", "delete"] +# Events for recording actions - apiGroups: [""] resources: ["events"] verbs: ["create", "patch"] +# Leader election - apiGroups: ["coordination.k8s.io"] resources: ["leases"] verbs: ["get", "create", "update"] diff --git a/packages/system/local-ccm/charts/local-ccm/templates/node-lifecycle-controller-clusterrolebinding.yaml b/packages/system/local-ccm/charts/local-ccm/templates/nlc-clusterrolebinding.yaml similarity index 52% rename from packages/system/local-ccm/charts/local-ccm/templates/node-lifecycle-controller-clusterrolebinding.yaml rename to packages/system/local-ccm/charts/local-ccm/templates/nlc-clusterrolebinding.yaml index dadf74b2..82194f81 100644 --- a/packages/system/local-ccm/charts/local-ccm/templates/node-lifecycle-controller-clusterrolebinding.yaml +++ b/packages/system/local-ccm/charts/local-ccm/templates/nlc-clusterrolebinding.yaml @@ -2,15 +2,16 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: - name: {{ include "node-lifecycle-controller.fullname" . }} + name: {{ include "local-ccm.fullname" . }}-nlc labels: - {{- include "node-lifecycle-controller.labels" . | nindent 4 }} + {{- include "local-ccm.labels" . | nindent 4 }} + app.kubernetes.io/component: node-lifecycle-controller roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole - name: {{ include "node-lifecycle-controller.fullname" . }} + name: {{ include "local-ccm.fullname" . }}-nlc subjects: - kind: ServiceAccount - name: {{ include "node-lifecycle-controller.fullname" . }} + name: {{ include "local-ccm.fullname" . }}-nlc namespace: {{ .Release.Namespace }} {{- end }} diff --git a/packages/system/local-ccm/charts/local-ccm/templates/nlc-deployment.yaml b/packages/system/local-ccm/charts/local-ccm/templates/nlc-deployment.yaml new file mode 100644 index 00000000..ea154d3d --- /dev/null +++ b/packages/system/local-ccm/charts/local-ccm/templates/nlc-deployment.yaml @@ -0,0 +1,78 @@ +{{- if .Values.nodeLifecycleController.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "local-ccm.fullname" . }}-nlc + labels: + {{- include "local-ccm.labels" . | nindent 4 }} + app.kubernetes.io/component: node-lifecycle-controller +spec: + replicas: {{ .Values.nodeLifecycleController.replicaCount }} + selector: + matchLabels: + {{- include "local-ccm.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: node-lifecycle-controller + template: + metadata: + labels: + {{- include "local-ccm.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: node-lifecycle-controller + {{- with .Values.nodeLifecycleController.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + serviceAccountName: {{ include "local-ccm.fullname" . }}-nlc + {{- if .Values.nodeLifecycleController.hostNetwork }} + hostNetwork: true + dnsPolicy: ClusterFirstWithHostNet + {{- end }} + {{- with .Values.nodeLifecycleController.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeLifecycleController.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeLifecycleController.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeLifecycleController.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: node-lifecycle-controller + image: "{{ .Values.nodeLifecycleController.image.repository }}:{{ .Values.nodeLifecycleController.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.nodeLifecycleController.image.pullPolicy }} + command: + - /usr/local/bin/node-lifecycle-controller + args: + - --watch-autoscaler-taint={{ .Values.nodeLifecycleController.controller.watchAutoscalerTaint }} + {{- if .Values.nodeLifecycleController.controller.nodeSelector }} + - --node-selector={{ .Values.nodeLifecycleController.controller.nodeSelector }} + {{- end }} + {{- if .Values.nodeLifecycleController.controller.protectedLabels }} + - --protected-labels={{ .Values.nodeLifecycleController.controller.protectedLabels }} + {{- end }} + - --not-ready-timeout={{ .Values.nodeLifecycleController.controller.notReadyTimeout }} + - --ping-timeout={{ .Values.nodeLifecycleController.controller.pingTimeout }} + - --ping-count={{ .Values.nodeLifecycleController.controller.pingCount }} + - --reconcile-interval={{ .Values.nodeLifecycleController.controller.reconcileInterval }} + - --leader-elect={{ .Values.nodeLifecycleController.leaderElection.enabled }} + - --leader-elect-id={{ .Values.nodeLifecycleController.leaderElection.resourceName }} + - --namespace=$(POD_NAMESPACE) + - --dry-run={{ .Values.nodeLifecycleController.controller.dryRun }} + - --v={{ .Values.nodeLifecycleController.controller.verbosity }} + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + securityContext: + {{- toYaml .Values.nodeLifecycleController.securityContext | nindent 10 }} + resources: + {{- toYaml .Values.nodeLifecycleController.resources | nindent 10 }} +{{- end }} diff --git a/packages/system/local-ccm/charts/local-ccm/templates/nlc-serviceaccount.yaml b/packages/system/local-ccm/charts/local-ccm/templates/nlc-serviceaccount.yaml new file mode 100644 index 00000000..a72ab136 --- /dev/null +++ b/packages/system/local-ccm/charts/local-ccm/templates/nlc-serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if and .Values.nodeLifecycleController.enabled .Values.nodeLifecycleController.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "local-ccm.fullname" . }}-nlc + labels: + {{- include "local-ccm.labels" . | nindent 4 }} + app.kubernetes.io/component: node-lifecycle-controller + {{- with .Values.nodeLifecycleController.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/packages/system/local-ccm/charts/local-ccm/templates/node-lifecycle-controller-deployment.yaml b/packages/system/local-ccm/charts/local-ccm/templates/node-lifecycle-controller-deployment.yaml deleted file mode 100644 index c7450ff5..00000000 --- a/packages/system/local-ccm/charts/local-ccm/templates/node-lifecycle-controller-deployment.yaml +++ /dev/null @@ -1,72 +0,0 @@ -{{- if .Values.nodeLifecycleController.enabled }} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "node-lifecycle-controller.fullname" . }} - labels: - {{- include "node-lifecycle-controller.labels" . | nindent 4 }} -spec: - replicas: {{ .Values.nodeLifecycleController.replicaCount }} - selector: - matchLabels: - {{- include "node-lifecycle-controller.selectorLabels" . | nindent 6 }} - template: - metadata: - labels: - {{- include "node-lifecycle-controller.selectorLabels" . | nindent 8 }} - spec: - serviceAccountName: {{ include "node-lifecycle-controller.fullname" . }} - hostNetwork: true - dnsPolicy: ClusterFirstWithHostNet - securityContext: - runAsNonRoot: false - tolerations: - - key: node-role.kubernetes.io/control-plane - operator: Exists - effect: NoSchedule - - key: node-role.kubernetes.io/master - operator: Exists - effect: NoSchedule - affinity: - nodeAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - weight: 100 - preference: - matchExpressions: - - key: node-role.kubernetes.io/control-plane - operator: Exists - containers: - - name: controller - image: "{{ .Values.nodeLifecycleController.image.repository }}:{{ .Values.nodeLifecycleController.image.tag }}" - imagePullPolicy: {{ .Values.nodeLifecycleController.image.pullPolicy }} - command: - - /node-lifecycle-controller - args: - {{- if .Values.nodeLifecycleController.nodeSelector }} - - --node-selector={{ .Values.nodeLifecycleController.nodeSelector }} - {{- end }} - {{- if .Values.nodeLifecycleController.protectedLabels }} - - --protected-labels={{ .Values.nodeLifecycleController.protectedLabels }} - {{- end }} - - --not-ready-timeout={{ .Values.nodeLifecycleController.notReadyTimeout }} - - --ping-timeout={{ .Values.nodeLifecycleController.pingTimeout }} - - --ping-count={{ .Values.nodeLifecycleController.pingCount }} - - --reconcile-interval={{ .Values.nodeLifecycleController.reconcileInterval }} - - --leader-elect={{ .Values.nodeLifecycleController.leaderElection.enabled }} - - --leader-elect-id={{ .Values.nodeLifecycleController.leaderElection.resourceName }} - - --namespace=$(POD_NAMESPACE) - - --dry-run={{ .Values.nodeLifecycleController.dryRun }} - - --v={{ .Values.nodeLifecycleController.verbosity }} - env: - - name: POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - securityContext: - capabilities: - add: - - NET_RAW - runAsUser: 0 - resources: - {{- toYaml .Values.nodeLifecycleController.resources | nindent 10 }} -{{- end }} diff --git a/packages/system/local-ccm/charts/local-ccm/templates/node-lifecycle-controller-serviceaccount.yaml b/packages/system/local-ccm/charts/local-ccm/templates/node-lifecycle-controller-serviceaccount.yaml deleted file mode 100644 index a7a4f72d..00000000 --- a/packages/system/local-ccm/charts/local-ccm/templates/node-lifecycle-controller-serviceaccount.yaml +++ /dev/null @@ -1,8 +0,0 @@ -{{- if .Values.nodeLifecycleController.enabled }} -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ include "node-lifecycle-controller.fullname" . }} - labels: - {{- include "node-lifecycle-controller.labels" . | nindent 4 }} -{{- end }} diff --git a/packages/system/local-ccm/charts/local-ccm/values.yaml b/packages/system/local-ccm/charts/local-ccm/values.yaml index 702bffff..d84bac3f 100644 --- a/packages/system/local-ccm/charts/local-ccm/values.yaml +++ b/packages/system/local-ccm/charts/local-ccm/values.yaml @@ -2,7 +2,7 @@ image: repository: ghcr.io/cozystack/local-ccm - tag: v0.2.1 + tag: v0.3.0 pullPolicy: Always # Service account configuration serviceAccount: @@ -55,39 +55,49 @@ affinity: labels: {} # Additional annotations for pods podAnnotations: {} - -# Node Lifecycle Controller configuration -# Automatically deletes unreachable NotReady nodes from the cluster +# Node Lifecycle Controller - deletes unreachable NotReady nodes nodeLifecycleController: - enabled: false + # Enable node lifecycle controller + enabled: true image: - repository: ghcr.io/cozystack/node-lifecycle-controller - tag: v0.1.0 + repository: ghcr.io/cozystack/local-ccm + tag: "v0.3.0" # Defaults to appVersion pullPolicy: IfNotPresent - # Label selector for nodes to manage - # Only nodes matching this selector will be considered for deletion - # Example: "node.kubernetes.io/instance-type" (nodes created by autoscaler) - nodeSelector: "" - # Comma-separated list of labels that protect nodes from deletion - protectedLabels: "" - # Duration a node must be NotReady before considering deletion - notReadyTimeout: 5m - # Timeout for ping checks - pingTimeout: 5s - # Number of ping attempts before considering node unreachable - pingCount: 3 - # Interval between reconciliation loops - reconcileInterval: 30s - # Log actions without actually deleting nodes - dryRun: false - # Verbosity level (0-5) - verbosity: 2 + # Controller configuration + controller: + # Watch only nodes with ToBeDeletedByClusterAutoscaler taint + # Ignored when nodeSelector is set + watchAutoscalerTaint: true + # Label selector for nodes to manage + # Only nodes matching this selector will be considered for deletion + # Example: "node.kubernetes.io/instance-type" (nodes created by autoscaler) + nodeSelector: "" + # Comma-separated list of labels/annotations that protect nodes from deletion + # Nodes with any of these labels will never be deleted + # Example: "kilo.squat.ai/leader,cluster-autoscaler.kubernetes.io/scale-down-disabled" + protectedLabels: "" + # Duration a node must be NotReady before considering deletion + notReadyTimeout: 5m + # Timeout for ping checks + pingTimeout: 5s + # Number of ping attempts before considering node unreachable + pingCount: 3 + # Interval between reconciliation loops + reconcileInterval: 30s + # Log actions without actually deleting nodes + dryRun: false + # Verbosity level (0-5) + verbosity: 2 # Leader election for HA leaderElection: enabled: true resourceName: node-lifecycle-controller - # Number of replicas + # Number of replicas (only 1 will be active due to leader election) replicaCount: 1 + # Service account configuration + serviceAccount: + create: true + annotations: {} # Pod resources resources: requests: @@ -96,3 +106,36 @@ nodeLifecycleController: limits: cpu: 100m memory: 64Mi + # Security context for the pod + podSecurityContext: + runAsNonRoot: false # Need root for ICMP ping + # Security context for the container + securityContext: + capabilities: + add: + - NET_RAW # Required for ICMP ping + runAsUser: 0 # Must run as root for raw sockets + # Node selector for the controller pod + nodeSelector: {} + # Tolerations for the controller pod + tolerations: + - key: node-role.kubernetes.io/control-plane + operator: Exists + effect: NoSchedule + - key: node-role.kubernetes.io/master + operator: Exists + effect: NoSchedule + # Affinity for the controller pod + affinity: + nodeAffinity: + # Prefer to run on control-plane nodes + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + preference: + matchExpressions: + - key: node-role.kubernetes.io/control-plane + operator: Exists + # Additional annotations for pods + podAnnotations: {} + # Use host network for ping to work across network boundaries + hostNetwork: true From 9213abc26085cd1cffea61fac7dd8502d824ff4c Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 10 Feb 2026 20:42:47 +0100 Subject: [PATCH 212/889] [system] Remove privileged flag from cluster-autoscaler PackageSources Cluster-autoscaler does not require privileged installation. Remove the unnecessary privileged: true setting from both Hetzner and Azure PackageSource definitions. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- packages/core/platform/sources/cluster-autoscaler-azure.yaml | 1 - packages/core/platform/sources/cluster-autoscaler-hetzner.yaml | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/core/platform/sources/cluster-autoscaler-azure.yaml b/packages/core/platform/sources/cluster-autoscaler-azure.yaml index d17b716d..7709f3f7 100644 --- a/packages/core/platform/sources/cluster-autoscaler-azure.yaml +++ b/packages/core/platform/sources/cluster-autoscaler-azure.yaml @@ -20,6 +20,5 @@ spec: - values.yaml - values-azure.yaml install: - privileged: true namespace: cozy-cluster-autoscaler-azure releaseName: cluster-autoscaler-azure diff --git a/packages/core/platform/sources/cluster-autoscaler-hetzner.yaml b/packages/core/platform/sources/cluster-autoscaler-hetzner.yaml index ca2c4557..bbb1a85b 100644 --- a/packages/core/platform/sources/cluster-autoscaler-hetzner.yaml +++ b/packages/core/platform/sources/cluster-autoscaler-hetzner.yaml @@ -20,6 +20,5 @@ spec: - values.yaml - values-hetzner.yaml install: - privileged: true namespace: cozy-cluster-autoscaler-hetzner releaseName: cluster-autoscaler-hetzner From 5c889124e748139952a3b3f8552a75137eb5c32d Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 10 Feb 2026 20:46:44 +0100 Subject: [PATCH 213/889] [system] Remove inline docs from cluster-autoscaler package Documentation moved to the website repository as a separate PR. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- .../system/cluster-autoscaler/docs/azure.md | 349 ---------------- .../system/cluster-autoscaler/docs/hetzner.md | 375 ------------------ 2 files changed, 724 deletions(-) delete mode 100644 packages/system/cluster-autoscaler/docs/azure.md delete mode 100644 packages/system/cluster-autoscaler/docs/hetzner.md diff --git a/packages/system/cluster-autoscaler/docs/azure.md b/packages/system/cluster-autoscaler/docs/azure.md deleted file mode 100644 index 7bcfbedc..00000000 --- a/packages/system/cluster-autoscaler/docs/azure.md +++ /dev/null @@ -1,349 +0,0 @@ -# Cluster Autoscaler for Azure - -This guide explains how to configure cluster-autoscaler for automatic node scaling in Azure with Talos Linux. - -## Prerequisites - -- Azure subscription with Contributor Service Principal -- `az` CLI installed -- Existing Talos Kubernetes cluster with Kilo WireGuard mesh -- Talos worker machine config - -## Step 1: Create Azure Infrastructure - -### 1.1 Login with Service Principal - -```bash -az login --service-principal \ - --username "" \ - --password "" \ - --tenant "" -``` - -### 1.2 Create Resource Group - -```bash -az group create \ - --name \ - --location -``` - -### 1.3 Create VNet and Subnet - -```bash -az network vnet create \ - --resource-group \ - --name cozystack-vnet \ - --address-prefix 10.2.0.0/16 \ - --subnet-name workers \ - --subnet-prefix 10.2.0.0/24 \ - --location -``` - -### 1.4 Create Network Security Group - -```bash -az network nsg create \ - --resource-group \ - --name cozystack-nsg \ - --location - -# Allow WireGuard -az network nsg rule create \ - --resource-group \ - --nsg-name cozystack-nsg \ - --name AllowWireGuard \ - --priority 100 \ - --direction Inbound \ - --access Allow \ - --protocol Udp \ - --destination-port-ranges 51820 - -# Allow Talos API -az network nsg rule create \ - --resource-group \ - --nsg-name cozystack-nsg \ - --name AllowTalosAPI \ - --priority 110 \ - --direction Inbound \ - --access Allow \ - --protocol Tcp \ - --destination-port-ranges 50000 - -# Associate NSG with subnet -az network vnet subnet update \ - --resource-group \ - --vnet-name cozystack-vnet \ - --name workers \ - --network-security-group cozystack-nsg -``` - -## Step 2: Create Talos Image - -### 2.1 Generate Schematic ID - -Create a schematic at [factory.talos.dev](https://factory.talos.dev) with required extensions: - -```bash -curl -s -X POST https://factory.talos.dev/schematics \ - -H "Content-Type: application/json" \ - -d '{ - "customization": { - "systemExtensions": { - "officialExtensions": [ - "siderolabs/amd-ucode", - "siderolabs/amdgpu-firmware", - "siderolabs/bnx2-bnx2x", - "siderolabs/drbd", - "siderolabs/i915-ucode", - "siderolabs/intel-ice-firmware", - "siderolabs/intel-ucode", - "siderolabs/qlogic-firmware", - "siderolabs/zfs" - ] - } - } - }' -``` - -Save the returned `id` as `SCHEMATIC_ID`. - -### 2.2 Create Managed Image from VHD - -```bash -# Download Talos Azure image -curl -L -o azure-amd64.raw.xz \ - "https://factory.talos.dev/image/${SCHEMATIC_ID}//azure-amd64.raw.xz" - -# Decompress -xz -d azure-amd64.raw.xz - -# Convert to VHD -qemu-img convert -f raw -o subformat=fixed,force_size -O vpc \ - azure-amd64.raw azure-amd64.vhd - -# Get VHD size -VHD_SIZE=$(stat -f%z azure-amd64.vhd) # macOS -# VHD_SIZE=$(stat -c%s azure-amd64.vhd) # Linux - -# Create managed disk for upload -az disk create \ - --resource-group \ - --name talos- \ - --location \ - --upload-type Upload \ - --upload-size-bytes $VHD_SIZE \ - --sku Standard_LRS \ - --os-type Linux \ - --hyper-v-generation V2 - -# Get SAS URL for upload -SAS_URL=$(az disk grant-access \ - --resource-group \ - --name talos- \ - --access-level Write \ - --duration-in-seconds 3600 \ - --query accessSAS --output tsv) - -# Upload VHD -azcopy copy azure-amd64.vhd "$SAS_URL" --blob-type PageBlob - -# Revoke access -az disk revoke-access \ - --resource-group \ - --name talos- - -# Create managed image from disk -az image create \ - --resource-group \ - --name talos- \ - --location \ - --os-type Linux \ - --hyper-v-generation V2 \ - --source $(az disk show --resource-group --name talos- --query id --output tsv) -``` - -## Step 3: Create Talos Machine Config for Azure - -Create a machine config similar to the Hetzner one, with these Azure-specific changes: - -```yaml -machine: - nodeLabels: - kilo.squat.ai/location: azure - topology.kubernetes.io/zone: azure - kubelet: - nodeIP: - validSubnets: - - 10.2.0.0/24 # <-- Azure VNet subnet -``` - -All other settings (cluster tokens, control plane endpoint, extensions, etc.) remain the same as the Hetzner config. - -## Step 4: Create VMSS (Virtual Machine Scale Set) - -```bash -IMAGE_ID=$(az image show \ - --resource-group \ - --name talos- \ - --query id --output tsv) - -az vmss create \ - --resource-group \ - --name workers \ - --location \ - --orchestration-mode Uniform \ - --image "$IMAGE_ID" \ - --vm-sku Standard_D2s_v3 \ - --instance-count 0 \ - --vnet-name cozystack-vnet \ - --subnet workers \ - --public-ip-per-vm \ - --custom-data machineconfig-azure.yaml \ - --security-type Standard \ - --admin-username talos \ - --authentication-type ssh \ - --generate-ssh-keys \ - --upgrade-policy-mode Manual -``` - -**Important notes:** -- Must use `--orchestration-mode Uniform` (cluster-autoscaler requires Uniform mode) -- Must use `--public-ip-per-vm` for WireGuard connectivity -- Check VM quota in your region: `az vm list-usage --location ` -- `--custom-data` passes the Talos machine config to new instances - -## Step 5: Deploy Cluster Autoscaler - -Create the Package resource: - -```yaml -apiVersion: cozystack.io/v1alpha1 -kind: Package -metadata: - name: cozystack.cluster-autoscaler-azure -spec: - variant: default - components: - cluster-autoscaler-azure: - values: - cluster-autoscaler: - azureClientID: "" - azureClientSecret: "" - azureTenantID: "" - azureSubscriptionID: "" - azureResourceGroup: "" - azureVMType: "vmss" - autoscalingGroups: - - name: workers - minSize: 0 - maxSize: 10 - rbac: - additionalRules: - - apiGroups: - - coordination.k8s.io - resources: - - leases - verbs: - - create - - get - - update -``` - -Apply: -```bash -kubectl apply -f package.yaml -``` - -## Step 6: Kilo WireGuard Endpoint Configuration - -**Important:** Azure nodes behind NAT need their public IP advertised as the WireGuard endpoint. Without this, the WireGuard tunnel between Hetzner and Azure nodes will not be established. - -Each new Azure node needs the annotation: - -```bash -kubectl annotate node \ - kilo.squat.ai/force-endpoint=:51820 -``` - -### Automated Endpoint Configuration - -For automated endpoint detection, create a DaemonSet that runs on Azure nodes (`topology.kubernetes.io/zone=azure`) and: - -1. Queries Azure Instance Metadata Service (IMDS) for the public IP: - ```bash - curl -s -H "Metadata: true" \ - "http://169.254.169.254/metadata/instance/network/interface/0/ipv4/ipAddress/0/publicIpAddress?api-version=2021-02-01&format=text" - ``` -2. Annotates the node with `kilo.squat.ai/force-endpoint=:51820` - -This ensures new autoscaled nodes automatically get proper WireGuard connectivity. - -## Testing - -### Manual scale test - -```bash -# Scale up -az vmss scale --resource-group --name workers --new-capacity 1 - -# Check node joined -kubectl get nodes -o wide - -# Check WireGuard tunnel -kubectl logs -n cozy-kilo - -# Scale down -az vmss scale --resource-group --name workers --new-capacity 0 -``` - -### Autoscaler test - -Deploy a workload to trigger autoscaling: - -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: test-azure-autoscale -spec: - replicas: 3 - selector: - matchLabels: - app: test-azure - template: - metadata: - labels: - app: test-azure - spec: - nodeSelector: - topology.kubernetes.io/zone: azure - containers: - - name: pause - image: registry.k8s.io/pause:3.9 - resources: - requests: - cpu: "500m" - memory: "512Mi" -``` - -## Troubleshooting - -### Node doesn't join cluster -- Check that the Talos machine config control plane endpoint is reachable from Azure -- Verify NSG rules allow outbound traffic to port 6443 -- Check VMSS instance provisioning state: `az vmss list-instances --resource-group --name workers` - -### WireGuard tunnel not established -- Verify `kilo.squat.ai/force-endpoint` annotation is set with the public IP -- Check NSG allows inbound UDP 51820 -- Inspect kilo logs: `kubectl logs -n cozy-kilo ` - -### VM quota errors -- Check quota: `az vm list-usage --location ` -- Request quota increase via Azure portal -- Try a different VM family that has available quota - -### SkuNotAvailable errors -- Some VM sizes may have capacity restrictions in certain regions -- Try a different VM size: `az vm list-skus --location --size ` diff --git a/packages/system/cluster-autoscaler/docs/hetzner.md b/packages/system/cluster-autoscaler/docs/hetzner.md deleted file mode 100644 index e3f1832b..00000000 --- a/packages/system/cluster-autoscaler/docs/hetzner.md +++ /dev/null @@ -1,375 +0,0 @@ -# Cluster Autoscaler for Hetzner Cloud - -This guide explains how to configure cluster-autoscaler for automatic node scaling in Hetzner Cloud with Talos Linux. - -## Prerequisites - -- Hetzner Cloud account with API token -- `hcloud` CLI installed -- Existing Talos Kubernetes cluster -- Talos worker machine config - -## Step 1: Create Talos Image in Hetzner Cloud - -Hetzner doesn't support direct image uploads, so we need to create a snapshot via a temporary server. - -### 1.1 Configure hcloud CLI - -```bash -export HCLOUD_TOKEN="" -``` - -### 1.2 Create temporary server in rescue mode - -```bash -# Create server (without starting) -hcloud server create \ - --name talos-image-builder \ - --type cpx22 \ - --image ubuntu-24.04 \ - --location fsn1 \ - --ssh-key \ - --start-after-create=false - -# Enable rescue mode and start -hcloud server enable-rescue --type linux64 --ssh-key talos-image-builder -hcloud server poweron talos-image-builder -``` - -### 1.3 Get server IP and write Talos image - -```bash -# Get server IP -SERVER_IP=$(hcloud server ip talos-image-builder) - -# SSH into rescue mode and write image -ssh root@$SERVER_IP - -# Inside rescue mode: -wget -O- "https://factory.talos.dev/image///hcloud-amd64.raw.xz" \ - | xz -d \ - | dd of=/dev/sda bs=4M status=progress -sync -exit -``` - -Get your schematic ID from https://factory.talos.dev with required extensions: -- `siderolabs/qemu-guest-agent` (required for Hetzner) -- Other extensions as needed (zfs, drbd, etc.) - -### 1.4 Create snapshot and cleanup - -```bash -# Power off and create snapshot -hcloud server poweroff talos-image-builder -hcloud server create-image --type snapshot --description "Talos v1.11.6" talos-image-builder - -# Get snapshot ID (save this for later) -hcloud image list --type snapshot - -# Delete temporary server -hcloud server delete talos-image-builder -``` - -## Step 2: Create Hetzner vSwitch (Optional but Recommended) - -Create a private network for communication between nodes: - -```bash -# Create network -hcloud network create --name cozystack-vswitch --ip-range 10.100.0.0/16 - -# Add subnet for your region (eu-central covers FSN1, NBG1) -hcloud network add-subnet cozystack-vswitch \ - --type cloud \ - --network-zone eu-central \ - --ip-range 10.100.0.0/24 -``` - -## Step 3: Create Talos Machine Config - -Create a worker machine config for autoscaled nodes. Important fields: - -```yaml -version: v1alpha1 -machine: - type: worker - token: - ca: - crt: - # Node labels (applied automatically on join) - nodeLabels: - kilo.squat.ai/location: hetzner-cloud - topology.kubernetes.io/zone: hetzner-cloud - kubelet: - image: ghcr.io/siderolabs/kubelet:v1.33.1 - # Use vSwitch IP as internal IP - nodeIP: - validSubnets: - - 10.100.0.0/24 - # Required for external cloud provider - extraArgs: - cloud-provider: external - extraConfig: - maxPods: 512 - defaultRuntimeSeccompProfileEnabled: true - disableManifestsDirectory: true - # Registry mirrors (recommended to avoid rate limiting) - registries: - mirrors: - docker.io: - endpoints: - - https://mirror.gcr.io -cluster: - controlPlane: - endpoint: https://:6443 - clusterName: - network: - cni: - name: none - podSubnets: - - 10.244.0.0/16 - serviceSubnets: - - 10.96.0.0/16 - token: - ca: - crt: -``` - -> **Important**: Ensure kubelet version matches your cluster version. Talos 1.11.6 doesn't support Kubernetes 1.35+. - -## Step 4: Create Kubernetes Secrets - -### 4.1 Create secret with Hetzner API token - -```bash -kubectl -n cozy-cluster-autoscaler-hetzner create secret generic hetzner-credentials \ - --from-literal=token= -``` - -### 4.2 Create secret with Talos machine config - -The machine config must be base64-encoded: - -```bash -# Encode your worker.yaml (single line base64) -base64 -i worker.yaml -o worker.b64 - -# Create secret -kubectl -n cozy-cluster-autoscaler-hetzner create secret generic talos-config \ - --from-file=cloud-init=worker.b64 -``` - -## Step 5: Deploy Cluster Autoscaler - -Create the Package resource: - -```yaml -apiVersion: cozystack.io/v1alpha1 -kind: Package -metadata: - name: cozystack.cluster-autoscaler-hetzner -spec: - variant: default - components: - cluster-autoscaler-hetzner: - values: - cluster-autoscaler: - autoscalingGroups: - - name: workers-fsn1 - minSize: 0 - maxSize: 10 - instanceType: cpx22 - region: FSN1 - extraEnv: - HCLOUD_IMAGE: "" - HCLOUD_SSH_KEY: "" - HCLOUD_NETWORK: "cozystack-vswitch" - HCLOUD_PUBLIC_IPV4: "true" - HCLOUD_PUBLIC_IPV6: "false" - extraEnvSecrets: - HCLOUD_TOKEN: - name: hetzner-credentials - key: token - HCLOUD_CLOUD_INIT: - name: talos-config - key: cloud-init - rbac: - additionalRules: - - apiGroups: - - coordination.k8s.io - resources: - - leases - verbs: - - create - - get - - update -``` - -Apply: -```bash -kubectl apply -f package.yaml -``` - -## Step 6: Test Autoscaling - -Create a deployment with pod anti-affinity to force scale-up: - -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: test-autoscaler -spec: - replicas: 5 - selector: - matchLabels: - app: test-autoscaler - template: - metadata: - labels: - app: test-autoscaler - spec: - affinity: - podAntiAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - labelSelector: - matchLabels: - app: test-autoscaler - topologyKey: kubernetes.io/hostname - containers: - - name: nginx - image: nginx - resources: - requests: - cpu: "100m" - memory: "128Mi" -``` - -If you have fewer nodes than replicas, the autoscaler will create new Hetzner servers. - -## Step 7: Verify - -```bash -# Check autoscaler logs -kubectl -n cozy-cluster-autoscaler-hetzner logs deployment/cluster-autoscaler-hetzner-hetzner-cluster-autoscaler -f - -# Check nodes -kubectl get nodes -o wide - -# Verify node labels and internal IP -kubectl get node --show-labels -``` - -Expected result for autoscaled nodes: -- Internal IP from vSwitch range (e.g., 10.100.0.2) -- Label `kilo.squat.ai/location=hetzner-cloud` - -## Configuration Reference - -### Environment Variables - -| Variable | Description | Required | -|----------|-------------|----------| -| `HCLOUD_TOKEN` | Hetzner API token | Yes | -| `HCLOUD_IMAGE` | Talos snapshot ID | Yes | -| `HCLOUD_CLOUD_INIT` | Base64-encoded machine config | Yes | -| `HCLOUD_NETWORK` | vSwitch network name/ID | No | -| `HCLOUD_SSH_KEY` | SSH key name/ID | No | -| `HCLOUD_FIREWALL` | Firewall name/ID | No | -| `HCLOUD_PUBLIC_IPV4` | Assign public IPv4 | No (default: true) | -| `HCLOUD_PUBLIC_IPV6` | Assign public IPv6 | No (default: false) | - -### Hetzner Server Types - -| Type | vCPU | RAM | Good for | -|------|------|-----|----------| -| cpx22 | 2 | 4GB | Small workloads | -| cpx32 | 4 | 8GB | General purpose | -| cpx42 | 8 | 16GB | Medium workloads | -| cpx52 | 16 | 32GB | Large workloads | -| ccx13 | 2 dedicated | 8GB | CPU-intensive | -| ccx23 | 4 dedicated | 16GB | CPU-intensive | -| ccx33 | 8 dedicated | 32GB | CPU-intensive | -| cax11 | 2 ARM | 4GB | ARM workloads | -| cax21 | 4 ARM | 8GB | ARM workloads | - -> **Note**: Some older server types (cpx11, cpx21, etc.) may be unavailable in certain regions. - -### Hetzner Regions - -| Code | Location | -|------|----------| -| FSN1 | Falkenstein, Germany | -| NBG1 | Nuremberg, Germany | -| HEL1 | Helsinki, Finland | -| ASH | Ashburn, USA | -| HIL | Hillsboro, USA | - -## Troubleshooting - -### Nodes not joining cluster - -1. Check VNC console via Hetzner Cloud Console or: - ```bash - hcloud server request-console - ``` -2. Common errors: - - **"unknown keys found during decoding"**: Check Talos config format. `nodeLabels` goes under `machine`, `nodeIP` goes under `machine.kubelet` - - **"kubelet image is not valid"**: Kubernetes version mismatch. Use kubelet version compatible with your Talos version - - **"failed to load config"**: Machine config syntax error - -### Nodes have wrong Internal IP - -Ensure `machine.kubelet.nodeIP.validSubnets` is set to your vSwitch subnet: -```yaml -machine: - kubelet: - nodeIP: - validSubnets: - - 10.100.0.0/24 -``` - -### Scale-up not triggered - -1. Check autoscaler logs for errors -2. Verify RBAC permissions (leases access required) -3. Check if pods are actually pending: - ```bash - kubectl get pods --field-selector=status.phase=Pending - ``` - -### Registry rate limiting (403 errors) - -Add registry mirrors to Talos config: -```yaml -machine: - registries: - mirrors: - docker.io: - endpoints: - - https://mirror.gcr.io - registry.k8s.io: - endpoints: - - https://registry.k8s.io -``` - -### Scale-down not working - -Talos caches absent nodes for up to 30 minutes. Wait or restart autoscaler: -```bash -kubectl -n cozy-cluster-autoscaler-hetzner rollout restart deployment cluster-autoscaler-hetzner-hetzner-cluster-autoscaler -``` - -## Integration with Kilo - -For multi-location clusters using Kilo mesh networking, add location label to machine config: - -```yaml -machine: - nodeLabels: - kilo.squat.ai/location: hetzner-cloud - topology.kubernetes.io/zone: hetzner-cloud -``` - -This allows Kilo to create proper WireGuard tunnels between your bare-metal nodes and Hetzner Cloud nodes. From fdfb8e0608566b339ea83ea99da1a42b77e47843 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 10 Feb 2026 21:04:03 +0100 Subject: [PATCH 214/889] refactor(apps): remove FerretDB application Remove the FerretDB managed application, its resource definition, platform source, RBAC entry, and e2e test. Historical migration scripts are left intact for upgrade compatibility. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- hack/e2e-apps/ferretdb.bats | 44 ---- packages/apps/ferretdb/.helmignore | 3 - packages/apps/ferretdb/Chart.yaml | 7 - packages/apps/ferretdb/Makefile | 12 -- packages/apps/ferretdb/README.md | 82 -------- packages/apps/ferretdb/charts/cozy-lib | 1 - packages/apps/ferretdb/logos/ferretdb.svg | 12 -- packages/apps/ferretdb/templates/.gitkeep | 0 .../apps/ferretdb/templates/_resources.tpl | 49 ----- packages/apps/ferretdb/templates/backup.yaml | 12 -- .../templates/dashboard-resourcemap.yaml | 37 ---- .../apps/ferretdb/templates/external-svc.yaml | 19 -- .../apps/ferretdb/templates/ferretdb.yaml | 29 --- .../apps/ferretdb/templates/postgres.yaml | 114 ----------- .../ferretdb/templates/workloadmonitor.yaml | 13 -- packages/apps/ferretdb/values.schema.json | 190 ------------------ packages/apps/ferretdb/values.yaml | 98 --------- .../sources/ferretdb-application.yaml | 27 --- .../core/platform/templates/bundles/paas.yaml | 1 - .../templates/clusterroles.yaml | 1 - packages/system/ferretdb-rd/Chart.yaml | 3 - packages/system/ferretdb-rd/Makefile | 4 - .../system/ferretdb-rd/cozyrds/ferretdb.yaml | 38 ---- .../system/ferretdb-rd/templates/cozyrd.yaml | 4 - packages/system/ferretdb-rd/values.yaml | 1 - 25 files changed, 801 deletions(-) delete mode 100644 hack/e2e-apps/ferretdb.bats delete mode 100644 packages/apps/ferretdb/.helmignore delete mode 100644 packages/apps/ferretdb/Chart.yaml delete mode 100644 packages/apps/ferretdb/Makefile delete mode 100644 packages/apps/ferretdb/README.md delete mode 120000 packages/apps/ferretdb/charts/cozy-lib delete mode 100644 packages/apps/ferretdb/logos/ferretdb.svg delete mode 100644 packages/apps/ferretdb/templates/.gitkeep delete mode 100644 packages/apps/ferretdb/templates/_resources.tpl delete mode 100644 packages/apps/ferretdb/templates/backup.yaml delete mode 100644 packages/apps/ferretdb/templates/dashboard-resourcemap.yaml delete mode 100644 packages/apps/ferretdb/templates/external-svc.yaml delete mode 100644 packages/apps/ferretdb/templates/ferretdb.yaml delete mode 100644 packages/apps/ferretdb/templates/postgres.yaml delete mode 100644 packages/apps/ferretdb/templates/workloadmonitor.yaml delete mode 100644 packages/apps/ferretdb/values.schema.json delete mode 100644 packages/apps/ferretdb/values.yaml delete mode 100644 packages/core/platform/sources/ferretdb-application.yaml delete mode 100644 packages/system/ferretdb-rd/Chart.yaml delete mode 100644 packages/system/ferretdb-rd/Makefile delete mode 100644 packages/system/ferretdb-rd/cozyrds/ferretdb.yaml delete mode 100644 packages/system/ferretdb-rd/templates/cozyrd.yaml delete mode 100644 packages/system/ferretdb-rd/values.yaml diff --git a/hack/e2e-apps/ferretdb.bats b/hack/e2e-apps/ferretdb.bats deleted file mode 100644 index a8eb7cf1..00000000 --- a/hack/e2e-apps/ferretdb.bats +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env bats - -@test "Create DB FerretDB" { - name='test' - kubectl apply -f - <" - s3SecretKey: "" - schedule: "0 2 * * * *" - bootstrap: - enabled: false - external: false - quorum: - maxSyncReplicas: 0 - minSyncReplicas: 0 - replicas: 2 - resources: {} - resourcesPreset: "micro" - size: "10Gi" - users: - testuser: - password: xai7Wepo -EOF - sleep 5 - kubectl -n tenant-test wait hr ferretdb-$name --timeout=100s --for=condition=ready - timeout 40 sh -ec "until kubectl -n tenant-test get svc ferretdb-$name-postgres-r -o jsonpath='{.spec.ports[0].port}' | grep -q '5432'; do sleep 10; done" - timeout 40 sh -ec "until kubectl -n tenant-test get svc ferretdb-$name-postgres-ro -o jsonpath='{.spec.ports[0].port}' | grep -q '5432'; do sleep 10; done" - timeout 40 sh -ec "until kubectl -n tenant-test get svc ferretdb-$name-postgres-rw -o jsonpath='{.spec.ports[0].port}' | grep -q '5432'; do sleep 10; done" - timeout 120 sh -ec "until kubectl -n tenant-test get endpoints ferretdb-$name-postgres-r -o jsonpath='{.subsets[*].addresses[*].ip}' | grep -q '[0-9]'; do sleep 10; done" - # for some reason it takes longer for the read-only endpoint to be ready - #timeout 120 sh -ec "until kubectl -n tenant-test get endpoints ferretdb-$name-postgres-ro -o jsonpath='{.subsets[*].addresses[*].ip}' | grep -q '[0-9]'; do sleep 10; done" - timeout 120 sh -ec "until kubectl -n tenant-test get endpoints ferretdb-$name-postgres-rw -o jsonpath='{.subsets[*].addresses[*].ip}' | grep -q '[0-9]'; do sleep 10; done" - kubectl -n tenant-test delete ferretdb.apps.cozystack.io $name -} diff --git a/packages/apps/ferretdb/.helmignore b/packages/apps/ferretdb/.helmignore deleted file mode 100644 index 1ea0ae84..00000000 --- a/packages/apps/ferretdb/.helmignore +++ /dev/null @@ -1,3 +0,0 @@ -.helmignore -/logos -/Makefile diff --git a/packages/apps/ferretdb/Chart.yaml b/packages/apps/ferretdb/Chart.yaml deleted file mode 100644 index 3044103a..00000000 --- a/packages/apps/ferretdb/Chart.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: v2 -name: ferretdb -description: Managed FerretDB service -icon: /logos/ferretdb.svg -type: application -version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process -appVersion: 2.4.0 diff --git a/packages/apps/ferretdb/Makefile b/packages/apps/ferretdb/Makefile deleted file mode 100644 index 40b7423f..00000000 --- a/packages/apps/ferretdb/Makefile +++ /dev/null @@ -1,12 +0,0 @@ -include ../../../hack/package.mk - -generate: - cozyvalues-gen -v values.yaml -s values.schema.json -r README.md - ../../../hack/update-crd.sh - -update: - tag=$$(git ls-remote --tags --sort="v:refname" https://github.com/FerretDB/FerretDB | awk -F'[/^]' '{sub("^v", "", $$3)} END{print $$3}') && \ - pgtag=$$(skopeo list-tags docker://ghcr.io/ferretdb/postgres-documentdb | jq -r --arg tag "$$tag" '.Tags[] | select(endswith("ferretdb-" + $$tag))' | sort -V | tail -n1) && \ - sed -i "s|\(imageName: ghcr.io/ferretdb/postgres-documentdb:\).*|\1$$pgtag|" templates/postgres.yaml && \ - sed -i "s|\(image: ghcr.io/ferretdb/ferretdb:\).*|\1$$tag|" templates/ferretdb.yaml && \ - sed -i "s|\(appVersion: \).*|\1$$tag|" Chart.yaml diff --git a/packages/apps/ferretdb/README.md b/packages/apps/ferretdb/README.md deleted file mode 100644 index 91fc078e..00000000 --- a/packages/apps/ferretdb/README.md +++ /dev/null @@ -1,82 +0,0 @@ -# Managed FerretDB Service - -FerretDB is an open source MongoDB alternative. -It translates MongoDB wire protocol queries to SQL and can be used as a direct replacement for MongoDB 5.0+. -Internally, FerretDB service is backed by Postgres. - -## Parameters - -### Common parameters - -| Name | Description | Type | Value | -| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | ---------- | ------- | -| `replicas` | Number of replicas. | `int` | `2` | -| `resources` | Explicit CPU and memory configuration for each FerretDB replica. When omitted, the preset defined in `resourcesPreset` is applied. | `object` | `{}` | -| `resources.cpu` | CPU available to each replica. | `quantity` | `""` | -| `resources.memory` | Memory (RAM) available to each replica. | `quantity` | `""` | -| `resourcesPreset` | Default sizing preset used when `resources` is omitted. | `string` | `micro` | -| `size` | Persistent Volume Claim size available for application data. | `quantity` | `10Gi` | -| `storageClass` | StorageClass used to store the data. | `string` | `""` | -| `external` | Enable external access from outside the cluster. | `bool` | `false` | - - -### Application-specific parameters - -| Name | Description | Type | Value | -| ------------------------ | ---------------------------------------------------------------------------------- | ------------------- | ----- | -| `quorum` | Configuration for quorum-based synchronous replication. | `object` | `{}` | -| `quorum.minSyncReplicas` | Minimum number of synchronous replicas required for commit. | `int` | `0` | -| `quorum.maxSyncReplicas` | Maximum number of synchronous replicas allowed (must be less than total replicas). | `int` | `0` | -| `users` | Users configuration map. | `map[string]object` | `{}` | -| `users[name].password` | Password for the user. | `string` | `""` | - - -### Backup parameters - -| Name | Description | Type | Value | -| ------------------------ | ------------------------------------------------------------ | -------- | ----------------------------------- | -| `backup` | Backup configuration. | `object` | `{}` | -| `backup.enabled` | Enable regular backups (default: false). | `bool` | `false` | -| `backup.schedule` | Cron schedule for automated backups. | `string` | `0 2 * * * *` | -| `backup.retentionPolicy` | Retention policy. | `string` | `30d` | -| `backup.endpointURL` | S3 endpoint URL for uploads. | `string` | `http://minio-gateway-service:9000` | -| `backup.destinationPath` | Path to store the backup (e.g. s3://bucket/path/to/folder/). | `string` | `s3://bucket/path/to/folder/` | -| `backup.s3AccessKey` | Access key for S3 authentication. | `string` | `` | -| `backup.s3SecretKey` | Secret key for S3 authentication. | `string` | `` | - - -### Bootstrap (recovery) parameters - -| Name | Description | Type | Value | -| ------------------------ | ------------------------------------------------------------------- | -------- | ------- | -| `bootstrap` | Bootstrap configuration. | `object` | `{}` | -| `bootstrap.enabled` | Restore database cluster from a backup. | `bool` | `false` | -| `bootstrap.recoveryTime` | Timestamp (RFC3339) for point-in-time recovery; empty means latest. | `string` | `""` | -| `bootstrap.oldName` | Name of database cluster before deletion. | `string` | `""` | - - -## Parameter examples and reference - -### resources and resourcesPreset - -`resources` sets explicit CPU and memory configurations for each replica. -When left empty, the preset defined in `resourcesPreset` is applied. - -```yaml -resources: - cpu: 4000m - memory: 4Gi -``` - -`resourcesPreset` sets named CPU and memory configurations for each replica. -This setting is ignored if the corresponding `resources` value is set. - -| Preset name | CPU | memory | -|-------------|--------|---------| -| `nano` | `250m` | `128Mi` | -| `micro` | `500m` | `256Mi` | -| `small` | `1` | `512Mi` | -| `medium` | `1` | `1Gi` | -| `large` | `2` | `2Gi` | -| `xlarge` | `4` | `4Gi` | -| `2xlarge` | `8` | `8Gi` | diff --git a/packages/apps/ferretdb/charts/cozy-lib b/packages/apps/ferretdb/charts/cozy-lib deleted file mode 120000 index e1813509..00000000 --- a/packages/apps/ferretdb/charts/cozy-lib +++ /dev/null @@ -1 +0,0 @@ -../../../library/cozy-lib \ No newline at end of file diff --git a/packages/apps/ferretdb/logos/ferretdb.svg b/packages/apps/ferretdb/logos/ferretdb.svg deleted file mode 100644 index 7d5c8b40..00000000 --- a/packages/apps/ferretdb/logos/ferretdb.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/packages/apps/ferretdb/templates/.gitkeep b/packages/apps/ferretdb/templates/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/apps/ferretdb/templates/_resources.tpl b/packages/apps/ferretdb/templates/_resources.tpl deleted file mode 100644 index 6539c99a..00000000 --- a/packages/apps/ferretdb/templates/_resources.tpl +++ /dev/null @@ -1,49 +0,0 @@ -{{/* -Copyright Broadcom, Inc. All Rights Reserved. -SPDX-License-Identifier: APACHE-2.0 -*/}} - -{{/* vim: set filetype=mustache: */}} - -{{/* -Return a resource request/limit object based on a given preset. -These presets are for basic testing and not meant to be used in production -{{ include "resources.preset" (dict "type" "nano") -}} -*/}} -{{- define "resources.preset" -}} -{{- $presets := dict - "nano" (dict - "requests" (dict "cpu" "100m" "memory" "128Mi" "ephemeral-storage" "50Mi") - "limits" (dict "memory" "128Mi" "ephemeral-storage" "2Gi") - ) - "micro" (dict - "requests" (dict "cpu" "250m" "memory" "256Mi" "ephemeral-storage" "50Mi") - "limits" (dict "memory" "256Mi" "ephemeral-storage" "2Gi") - ) - "small" (dict - "requests" (dict "cpu" "500m" "memory" "512Mi" "ephemeral-storage" "50Mi") - "limits" (dict "memory" "512Mi" "ephemeral-storage" "2Gi") - ) - "medium" (dict - "requests" (dict "cpu" "500m" "memory" "1Gi" "ephemeral-storage" "50Mi") - "limits" (dict "memory" "1Gi" "ephemeral-storage" "2Gi") - ) - "large" (dict - "requests" (dict "cpu" "1" "memory" "2Gi" "ephemeral-storage" "50Mi") - "limits" (dict "memory" "2Gi" "ephemeral-storage" "2Gi") - ) - "xlarge" (dict - "requests" (dict "cpu" "2" "memory" "4Gi" "ephemeral-storage" "50Mi") - "limits" (dict "memory" "4Gi" "ephemeral-storage" "2Gi") - ) - "2xlarge" (dict - "requests" (dict "cpu" "4" "memory" "8Gi" "ephemeral-storage" "50Mi") - "limits" (dict "memory" "8Gi" "ephemeral-storage" "2Gi") - ) - }} -{{- if hasKey $presets .type -}} -{{- index $presets .type | toYaml -}} -{{- else -}} -{{- printf "ERROR: Preset key '%s' invalid. Allowed values are %s" .type (join "," (keys $presets)) | fail -}} -{{- end -}} -{{- end -}} diff --git a/packages/apps/ferretdb/templates/backup.yaml b/packages/apps/ferretdb/templates/backup.yaml deleted file mode 100644 index 96dea599..00000000 --- a/packages/apps/ferretdb/templates/backup.yaml +++ /dev/null @@ -1,12 +0,0 @@ -{{- if .Values.backup.enabled }} ---- -apiVersion: postgresql.cnpg.io/v1 -kind: ScheduledBackup -metadata: - name: {{ .Release.Name }}-postgres -spec: - schedule: {{ .Values.backup.schedule | quote }} - backupOwnerReference: self - cluster: - name: {{ .Release.Name }}-postgres -{{- end }} diff --git a/packages/apps/ferretdb/templates/dashboard-resourcemap.yaml b/packages/apps/ferretdb/templates/dashboard-resourcemap.yaml deleted file mode 100644 index af40a6fa..00000000 --- a/packages/apps/ferretdb/templates/dashboard-resourcemap.yaml +++ /dev/null @@ -1,37 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: {{ .Release.Name }}-dashboard-resources -rules: -- apiGroups: - - "" - resources: - - services - resourceNames: - - {{ .Release.Name }} - verbs: ["get", "list", "watch"] -- apiGroups: - - "" - resources: - - secrets - resourceNames: - - {{ .Release.Name }}-credentials - verbs: ["get", "list", "watch"] -- apiGroups: - - cozystack.io - resources: - - workloadmonitors - resourceNames: - - {{ .Release.Name }} - verbs: ["get", "list", "watch"] ---- -kind: RoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: {{ .Release.Name }}-dashboard-resources -subjects: -{{ include "cozy-lib.rbac.subjectsForTenantAndAccessLevel" (list "use" .Release.Namespace) }} -roleRef: - kind: Role - name: {{ .Release.Name }}-dashboard-resources - apiGroup: rbac.authorization.k8s.io diff --git a/packages/apps/ferretdb/templates/external-svc.yaml b/packages/apps/ferretdb/templates/external-svc.yaml deleted file mode 100644 index b4550cce..00000000 --- a/packages/apps/ferretdb/templates/external-svc.yaml +++ /dev/null @@ -1,19 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ .Release.Name }} - labels: - app.kubernetes.io/instance: {{ .Release.Name }} -spec: - type: {{ ternary "LoadBalancer" "ClusterIP" .Values.external }} - {{- if .Values.external }} - externalTrafficPolicy: Local - {{- if (include "cozy-lib.network.disableLoadBalancerNodePorts" $ | fromYaml) }} - allocateLoadBalancerNodePorts: false - {{- end }} - {{- end }} - ports: - - name: ferretdb - port: 27017 - selector: - app: {{ .Release.Name }} diff --git a/packages/apps/ferretdb/templates/ferretdb.yaml b/packages/apps/ferretdb/templates/ferretdb.yaml deleted file mode 100644 index e73d42a3..00000000 --- a/packages/apps/ferretdb/templates/ferretdb.yaml +++ /dev/null @@ -1,29 +0,0 @@ ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ .Release.Name }} -spec: - replicas: {{ .Values.replicas }} - selector: - matchLabels: - app: {{ .Release.Name }} - template: - metadata: - labels: - app: {{ .Release.Name }} - app.kubernetes.io/instance: {{ .Release.Name }} - spec: - containers: - - name: ferretdb - image: ghcr.io/ferretdb/ferretdb:2.4.0 - ports: - - containerPort: 27017 - env: - - name: POSTGRESQL_PASSWORD - valueFrom: - secretKeyRef: - name: {{ .Release.Name }}-postgres-superuser - key: password - - name: FERRETDB_POSTGRESQL_URL - value: "postgresql://postgres:$(POSTGRESQL_PASSWORD)@{{ .Release.Name }}-postgres-rw:5432/postgres" diff --git a/packages/apps/ferretdb/templates/postgres.yaml b/packages/apps/ferretdb/templates/postgres.yaml deleted file mode 100644 index f1b1ce2c..00000000 --- a/packages/apps/ferretdb/templates/postgres.yaml +++ /dev/null @@ -1,114 +0,0 @@ ---- -apiVersion: postgresql.cnpg.io/v1 -kind: Cluster -metadata: - name: {{ .Release.Name }}-postgres -spec: - instances: {{ .Values.replicas }} - {{- if .Values.backup.enabled }} - backup: - barmanObjectStore: - destinationPath: {{ .Values.backup.destinationPath }} - endpointURL: {{ .Values.backup.endpointURL }} - s3Credentials: - accessKeyId: - name: {{ .Release.Name }}-s3-creds - key: AWS_ACCESS_KEY_ID - secretAccessKey: - name: {{ .Release.Name }}-s3-creds - key: AWS_SECRET_ACCESS_KEY - retentionPolicy: {{ .Values.backup.retentionPolicy }} - {{- end }} - - bootstrap: - initdb: - postInitSQL: - - 'CREATE EXTENSION IF NOT EXISTS documentdb CASCADE;' - {{- if .Values.bootstrap.enabled }} - recovery: - source: {{ .Values.bootstrap.oldName }} - {{- if .Values.bootstrap.recoveryTime }} - recoveryTarget: - targetTime: {{ .Values.bootstrap.recoveryTime }} - {{- end }} - {{- end }} - {{- if .Values.bootstrap.enabled }} - externalClusters: - - name: {{ .Values.bootstrap.oldName }} - barmanObjectStore: - destinationPath: {{ .Values.backup.destinationPath }} - endpointURL: {{ .Values.backup.endpointURL }} - s3Credentials: - accessKeyId: - name: {{ .Release.Name }}-s3-creds - key: AWS_ACCESS_KEY_ID - secretAccessKey: - name: {{ .Release.Name }}-s3-creds - key: AWS_SECRET_ACCESS_KEY - {{- end }} - imageName: ghcr.io/ferretdb/postgres-documentdb:17-0.105.0-ferretdb-2.4.0 - postgresUID: 999 - postgresGID: 999 - enableSuperuserAccess: true - {{- if .Values._cluster.scheduling }} - {{- $rawConstraints := get .Values._cluster.scheduling "globalAppTopologySpreadConstraints" }} - {{- if $rawConstraints }} - {{- $rawConstraints | fromYaml | toYaml | nindent 2 }} - labelSelector: - matchLabels: - cnpg.io/cluster: {{ .Release.Name }}-postgres - {{- end }} - {{- end }} - minSyncReplicas: {{ .Values.quorum.minSyncReplicas }} - maxSyncReplicas: {{ .Values.quorum.maxSyncReplicas }} - resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.resourcesPreset .Values.resources $) | nindent 4 }} - monitoring: - enablePodMonitor: true - - postgresql: - shared_preload_libraries: - - pg_cron - - pg_documentdb_core - - pg_documentdb - parameters: - cron.database_name: 'postgres' - pg_hba: - - host postgres postgres 127.0.0.1/32 trust - - host postgres postgres ::1/128 trust - - storage: - size: {{ required ".Values.size is required" .Values.size }} - {{- with .Values.storageClass }} - storageClass: {{ . }} - {{- end }} - - inheritedMetadata: - labels: - policy.cozystack.io/allow-to-apiserver: "true" - app.kubernetes.io/instance: {{ .Release.Name }} - - {{- if .Values.users }} - managed: - roles: - {{- range $user, $config := .Values.users }} - - name: {{ $user }} - ensure: present - passwordSecret: - name: {{ printf "%s-user-%s" $.Release.Name $user }} - login: true - {{- end }} - {{- end }} - -{{- range $user, $config := .Values.users }} ---- -apiVersion: v1 -kind: Secret -metadata: - name: {{ printf "%s-user-%s" $.Release.Name $user }} - labels: - cnpg.io/reload: "true" -type: kubernetes.io/basic-auth -data: - username: {{ $user | b64enc }} - password: {{ $config.password | b64enc }} -{{- end }} diff --git a/packages/apps/ferretdb/templates/workloadmonitor.yaml b/packages/apps/ferretdb/templates/workloadmonitor.yaml deleted file mode 100644 index a1b364d0..00000000 --- a/packages/apps/ferretdb/templates/workloadmonitor.yaml +++ /dev/null @@ -1,13 +0,0 @@ ---- -apiVersion: cozystack.io/v1alpha1 -kind: WorkloadMonitor -metadata: - name: {{ $.Release.Name }} -spec: - replicas: {{ .Values.replicas }} - minReplicas: 1 - kind: ferretdb - type: ferretdb - selector: - app.kubernetes.io/instance: {{ $.Release.Name }} - version: {{ $.Chart.Version }} diff --git a/packages/apps/ferretdb/values.schema.json b/packages/apps/ferretdb/values.schema.json deleted file mode 100644 index f22a2340..00000000 --- a/packages/apps/ferretdb/values.schema.json +++ /dev/null @@ -1,190 +0,0 @@ -{ - "title": "Chart Values", - "type": "object", - "properties": { - "backup": { - "description": "Backup configuration.", - "type": "object", - "default": {}, - "required": [ - "destinationPath", - "enabled", - "endpointURL", - "retentionPolicy", - "s3AccessKey", - "s3SecretKey", - "schedule" - ], - "properties": { - "destinationPath": { - "description": "Path to store the backup (e.g. s3://bucket/path/to/folder/).", - "type": "string", - "default": "s3://bucket/path/to/folder/" - }, - "enabled": { - "description": "Enable regular backups (default: false).", - "type": "boolean", - "default": false - }, - "endpointURL": { - "description": "S3 endpoint URL for uploads.", - "type": "string", - "default": "http://minio-gateway-service:9000" - }, - "retentionPolicy": { - "description": "Retention policy.", - "type": "string", - "default": "30d" - }, - "s3AccessKey": { - "description": "Access key for S3 authentication.", - "type": "string", - "default": "\u003cyour-access-key\u003e" - }, - "s3SecretKey": { - "description": "Secret key for S3 authentication.", - "type": "string", - "default": "\u003cyour-secret-key\u003e" - }, - "schedule": { - "description": "Cron schedule for automated backups.", - "type": "string", - "default": "0 2 * * * *" - } - } - }, - "bootstrap": { - "description": "Bootstrap configuration.", - "type": "object", - "default": {}, - "properties": { - "enabled": { - "description": "Restore database cluster from a backup.", - "type": "boolean", - "default": false - }, - "oldName": { - "description": "Name of database cluster before deletion.", - "type": "string", - "default": "" - }, - "recoveryTime": { - "description": "Timestamp (RFC3339) for point-in-time recovery; empty means latest.", - "type": "string", - "default": "" - } - } - }, - "external": { - "description": "Enable external access from outside the cluster.", - "type": "boolean", - "default": false - }, - "quorum": { - "description": "Configuration for quorum-based synchronous replication.", - "type": "object", - "default": {}, - "required": [ - "maxSyncReplicas", - "minSyncReplicas" - ], - "properties": { - "maxSyncReplicas": { - "description": "Maximum number of synchronous replicas allowed (must be less than total replicas).", - "type": "integer", - "default": 0 - }, - "minSyncReplicas": { - "description": "Minimum number of synchronous replicas required for commit.", - "type": "integer", - "default": 0 - } - } - }, - "replicas": { - "description": "Number of replicas.", - "type": "integer", - "default": 2 - }, - "resources": { - "description": "Explicit CPU and memory configuration for each FerretDB replica. When omitted, the preset defined in `resourcesPreset` is applied.", - "type": "object", - "default": {}, - "properties": { - "cpu": { - "description": "CPU available to each replica.", - "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 (RAM) available to each replica.", - "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 - } - } - }, - "resourcesPreset": { - "description": "Default sizing preset used when `resources` is omitted.", - "type": "string", - "default": "micro", - "enum": [ - "nano", - "micro", - "small", - "medium", - "large", - "xlarge", - "2xlarge" - ] - }, - "size": { - "description": "Persistent Volume Claim size available for application data.", - "default": "10Gi", - "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 - }, - "storageClass": { - "description": "StorageClass used to store the data.", - "type": "string", - "default": "" - }, - "users": { - "description": "Users configuration map.", - "type": "object", - "default": {}, - "additionalProperties": { - "type": "object", - "properties": { - "password": { - "description": "Password for the user.", - "type": "string" - } - } - } - } - } -} \ No newline at end of file diff --git a/packages/apps/ferretdb/values.yaml b/packages/apps/ferretdb/values.yaml deleted file mode 100644 index fc905466..00000000 --- a/packages/apps/ferretdb/values.yaml +++ /dev/null @@ -1,98 +0,0 @@ -## -## @section Common parameters -## - -## @typedef {struct} Resources - Explicit CPU and memory configuration for each FerretDB replica. -## @field {quantity} [cpu] - CPU available to each replica. -## @field {quantity} [memory] - Memory (RAM) available to each replica. - -## @enum {string} ResourcesPreset - Default sizing preset. -## @value nano -## @value micro -## @value small -## @value medium -## @value large -## @value xlarge -## @value 2xlarge - -## @param {int} replicas - Number of replicas. -replicas: 2 - -## @param {Resources} [resources] - Explicit CPU and memory configuration for each FerretDB replica. When omitted, the preset defined in `resourcesPreset` is applied. -resources: {} - -## @param {ResourcesPreset} resourcesPreset="micro" - Default sizing preset used when `resources` is omitted. -resourcesPreset: "micro" - -## @param {quantity} size - Persistent Volume Claim size available for application data. -size: 10Gi - -## @param {string} storageClass - StorageClass used to store the data. -storageClass: "" - -## @param {bool} external - Enable external access from outside the cluster. -external: false - -## -## @section Application-specific parameters -## - -## @typedef {struct} Quorum - Configuration for quorum-based synchronous replication. -## @field {int} minSyncReplicas - Minimum number of synchronous replicas required for commit. -## @field {int} maxSyncReplicas - Maximum number of synchronous replicas allowed (must be less than total replicas). - -## @param {Quorum} quorum - Configuration for quorum-based synchronous replication. -quorum: - minSyncReplicas: 0 - maxSyncReplicas: 0 - -## @typedef {struct} User - User configuration. -## @field {string} [password] - Password for the user. - -## @param {map[string]User} users - Users configuration map. -users: {} -## Example: -## users: -## user1: -## password: strongpassword -## user2: -## password: hackme -## - -## -## @section Backup parameters -## - -## @typedef {struct} Backup - Backup configuration. -## @field {bool} enabled - Enable regular backups (default: false). -## @field {string} schedule - Cron schedule for automated backups. -## @field {string} retentionPolicy - Retention policy. -## @field {string} endpointURL - S3 endpoint URL for uploads. -## @field {string} destinationPath - Path to store the backup (e.g. s3://bucket/path/to/folder/). -## @field {string} s3AccessKey - Access key for S3 authentication. -## @field {string} s3SecretKey - Secret key for S3 authentication. - -## @param {Backup} backup - Backup configuration. -backup: - enabled: false - schedule: "0 2 * * * *" - retentionPolicy: 30d - endpointURL: http://minio-gateway-service:9000 - destinationPath: s3://bucket/path/to/folder/ - s3AccessKey: "" - s3SecretKey: "" - -## -## @section Bootstrap (recovery) parameters -## - -## @typedef {struct} Bootstrap - Bootstrap configuration for restoring a database cluster from a backup. -## @field {bool} [enabled] - Restore database cluster from a backup. -## @field {string} [recoveryTime] - Timestamp (RFC3339) for point-in-time recovery; empty means latest. -## @field {string} [oldName] - Name of database cluster before deletion. - -## @param {Bootstrap} bootstrap - Bootstrap configuration. -bootstrap: - enabled: false - recoveryTime: "" - oldName: "" diff --git a/packages/core/platform/sources/ferretdb-application.yaml b/packages/core/platform/sources/ferretdb-application.yaml deleted file mode 100644 index 447745a5..00000000 --- a/packages/core/platform/sources/ferretdb-application.yaml +++ /dev/null @@ -1,27 +0,0 @@ ---- -apiVersion: cozystack.io/v1alpha1 -kind: PackageSource -metadata: - name: cozystack.ferretdb-application -spec: - sourceRef: - kind: OCIRepository - name: cozystack-packages - namespace: cozy-system - path: / - variants: - - name: default - dependsOn: - - cozystack.networking - libraries: - - name: cozy-lib - path: library/cozy-lib - components: - - name: ferretdb - path: apps/ferretdb - libraries: [cozy-lib] - - name: ferretdb-rd - path: system/ferretdb-rd - install: - namespace: cozy-system - releaseName: ferretdb-rd diff --git a/packages/core/platform/templates/bundles/paas.yaml b/packages/core/platform/templates/bundles/paas.yaml index 8b519bf8..1b76a74d 100644 --- a/packages/core/platform/templates/bundles/paas.yaml +++ b/packages/core/platform/templates/bundles/paas.yaml @@ -10,7 +10,6 @@ {{include "cozystack.platform.package.default" (list "cozystack.redis-operator" $) }} {{include "cozystack.platform.package.default" (list "cozystack.mongodb-operator" $) }} {{include "cozystack.platform.package.default" (list "cozystack.clickhouse-application" $) }} -{{include "cozystack.platform.package.default" (list "cozystack.ferretdb-application" $) }} {{include "cozystack.platform.package.default" (list "cozystack.foundationdb-application" $) }} {{include "cozystack.platform.package.default" (list "cozystack.kafka-application" $) }} {{include "cozystack.platform.package.default" (list "cozystack.mysql-application" $) }} diff --git a/packages/system/cozystack-basics/templates/clusterroles.yaml b/packages/system/cozystack-basics/templates/clusterroles.yaml index 747650fb..b5df0d59 100644 --- a/packages/system/cozystack-basics/templates/clusterroles.yaml +++ b/packages/system/cozystack-basics/templates/clusterroles.yaml @@ -194,7 +194,6 @@ rules: resources: - buckets - clickhouses - - ferretdb - foos - httpcaches - kafkas diff --git a/packages/system/ferretdb-rd/Chart.yaml b/packages/system/ferretdb-rd/Chart.yaml deleted file mode 100644 index 6e2730f1..00000000 --- a/packages/system/ferretdb-rd/Chart.yaml +++ /dev/null @@ -1,3 +0,0 @@ -apiVersion: v2 -name: ferretdb-rd -version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process diff --git a/packages/system/ferretdb-rd/Makefile b/packages/system/ferretdb-rd/Makefile deleted file mode 100644 index 1cc7ac30..00000000 --- a/packages/system/ferretdb-rd/Makefile +++ /dev/null @@ -1,4 +0,0 @@ -export NAME=ferretdb-rd -export NAMESPACE=cozy-system - -include ../../../hack/package.mk diff --git a/packages/system/ferretdb-rd/cozyrds/ferretdb.yaml b/packages/system/ferretdb-rd/cozyrds/ferretdb.yaml deleted file mode 100644 index c54570a2..00000000 --- a/packages/system/ferretdb-rd/cozyrds/ferretdb.yaml +++ /dev/null @@ -1,38 +0,0 @@ -apiVersion: cozystack.io/v1alpha1 -kind: ApplicationDefinition -metadata: - name: ferretdb -spec: - application: - kind: FerretDB - plural: ferretdbs - singular: ferretdb - openAPISchema: |- - {"title":"Chart Values","type":"object","properties":{"backup":{"description":"Backup configuration.","type":"object","default":{},"required":["destinationPath","enabled","endpointURL","retentionPolicy","s3AccessKey","s3SecretKey","schedule"],"properties":{"destinationPath":{"description":"Path to store the backup (e.g. s3://bucket/path/to/folder/).","type":"string","default":"s3://bucket/path/to/folder/"},"enabled":{"description":"Enable regular backups (default: false).","type":"boolean","default":false},"endpointURL":{"description":"S3 endpoint URL for uploads.","type":"string","default":"http://minio-gateway-service:9000"},"retentionPolicy":{"description":"Retention policy.","type":"string","default":"30d"},"s3AccessKey":{"description":"Access key for S3 authentication.","type":"string","default":""},"s3SecretKey":{"description":"Secret key for S3 authentication.","type":"string","default":""},"schedule":{"description":"Cron schedule for automated backups.","type":"string","default":"0 2 * * * *"}}},"bootstrap":{"description":"Bootstrap configuration.","type":"object","default":{},"properties":{"enabled":{"description":"Restore database cluster from a backup.","type":"boolean","default":false},"oldName":{"description":"Name of database cluster before deletion.","type":"string","default":""},"recoveryTime":{"description":"Timestamp (RFC3339) for point-in-time recovery; empty means latest.","type":"string","default":""}}},"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"quorum":{"description":"Configuration for quorum-based synchronous replication.","type":"object","default":{},"required":["maxSyncReplicas","minSyncReplicas"],"properties":{"maxSyncReplicas":{"description":"Maximum number of synchronous replicas allowed (must be less than total replicas).","type":"integer","default":0},"minSyncReplicas":{"description":"Minimum number of synchronous replicas required for commit.","type":"integer","default":0}}},"replicas":{"description":"Number of replicas.","type":"integer","default":2},"resources":{"description":"Explicit CPU and memory configuration for each FerretDB replica. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available to each replica.","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 (RAM) available to each replica.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"micro","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]},"size":{"description":"Persistent Volume Claim size available for application data.","default":"10Gi","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},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":""},"users":{"description":"Users configuration map.","type":"object","default":{},"additionalProperties":{"type":"object","properties":{"password":{"description":"Password for the user.","type":"string"}}}}}} - release: - prefix: ferretdb- - labels: - sharding.fluxcd.io/key: tenants - chartRef: - kind: ExternalArtifact - name: cozystack-ferretdb-application-default-ferretdb - namespace: cozy-system - dashboard: - category: PaaS - singular: FerretDB - plural: FerretDB - description: Managed FerretDB service - tags: - - database - icon: PHN2ZyB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgdmlld0JveD0iMCAwIDE0NCAxNDQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHg9Ii0wLjAwMTk1MzEyIiB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgcng9IjI0IiBmaWxsPSJ1cmwoI3BhaW50MF9saW5lYXJfNjgzXzI5NTIpIi8+CjxwYXRoIGQ9Ik02OS41OTIzIDIyLjEzMUM1OC4yNjYyIDIzLjY3ODcgNDYuOTAzNyAzMC44NzE0IDQwLjMzMDIgNDAuNjY3OUMzOS4yNzQgNDIuMjUyMSAzNy40NTMxIDQ1LjU0OCAzNy40NTMxIDQ1Ljg3NTdDMzcuNDUzMSA0NS45MTIyIDM4LjMyNzIgNDUuMzg0MSAzOS4zODMzIDQ0LjY5MjFDNTIuMzg0NyAzNi4xMTU2IDY3Ljg5ODkgMzQuNTMxNCA4MC41MTc4IDQwLjQ4NThDODMuMjY3NCA0MS43Nzg3IDg0Ljk5NzMgNDMuMDM1MSA4Ny40NTU1IDQ1LjQ5MzNDOTEuNTg5IDQ5LjY0NSA5NC42MTE3IDU1LjE5ODggOTYuNzA1OCA2Mi41MDA3Qzk3Ljc5ODMgNjYuMjUxOCA5OC43MDg4IDcxLjM2ODYgOTguOTQ1NSA3NC44NDY1Qzk5LjAwMDEgNzUuNzkzNCA5OS4xNDU4IDc2LjYzMSA5OS4yMzY5IDc2LjY4NTZDOTkuNzQ2NyA3Ni45OTUyIDEwMi4wNDEgNzMuNjYyOSAxMDMuNjYyIDcwLjI3NkMxMDYuMjI5IDY0Ljg4NjEgMTA3LjQzMSA1OS41ODcyIDEwNy40MTMgNTMuNzA1N0MxMDcuMzk1IDQ1LjM4NDEgMTA0LjUxOCAzOC4zOTE3IDk4LjcyNyAzMi41NjQ4QzkzLjU5MiAyNy4zOTM0IDg3LjEwOTUgMjMuODQyNiA4MC4zMTc1IDIyLjQ1ODdDNzguNzMzMyAyMi4xNDkyIDc3LjU2NzkgMjIuMDU4MSA3NC41OTk5IDIyLjAwMzVDNzIuNTQyMiAyMS45ODUzIDcwLjMwMjUgMjIuMDM5OSA2OS41OTIzIDIyLjEzMVoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik00NS41MiA0Ni40NDAyQzQ0LjMzNjQgNDcuMDIyOSA0Mi4zNTE2IDQ4Ljg0MzggNDAuNjAzNSA1MC45Mzc5QzM5LjgyMDUgNTEuODY2NiAzOC42MzY5IDUzLjAxMzcgMzcuNzYyOSA1My42NjkzQzM1LjcyMzQgNTUuMTk4OSAzMi4yNDU1IDU4LjYwNCAzMC40NzkyIDYwLjgwNzNDMjEuMjY1NCA3Mi4yMjQ0IDE4LjY5NzkgODUuMjQ0IDIzLjA4NjMgOTguMzE4MkMyNi42OTE3IDEwOS4wMjUgMzUuMDMxNSAxMTYuMTI3IDQ3Ljg1MDggMTE5LjM1QzUyLjg0MDEgMTIwLjYyNCA2MC4zMjQgMTIxLjMzNSA2My40NTYgMTIwLjg0M0w2NC4yNTcyIDEyMC43MTVMNjMuMDE5IDExOS45ODdDNTYuMTkwNiAxMTYuMDE4IDUxLjQxOTggMTA5LjMxNyA1MC4wOTA1IDEwMS44NjlDNDkuNjg5OSA5OS42MTEgNDkuNjcxNyA5NS42MDUgNTAuMDcyMyA5My40MDE3QzUwLjk2NDUgODguNDQ4OCA1My40NTkyIDgzLjg5NjUgNTYuODQ2MSA4MS4wNTU5QzU4LjQzMDMgNzkuNzI2NiA2MS4xOTgxIDc4LjM2MDkgNjMuNDAxNCA3Ny44MzI5QzY2LjcxNTUgNzcuMDMxNyA2OC43MzY3IDc2LjEyMTIgNzAuODMwNyA3NC40NjQyQzcyLjE3ODIgNzMuNDA4IDczLjM2MTggNzEuODA1NiA3NC4zNDUxIDY5LjcyOThDNzUuMTgyNyA2Ny45NjM1IDc2Ljk2NzIgNjIuMzU1MSA3Ni45NjcyIDYxLjQ2MjhDNzYuOTY3MiA2MC44NDM3IDc2LjMyOTkgNjAuMDA2MSA3NS40MTk1IDU5LjQ0MTZDNzQuOTQ2IDU5LjE1MDIgNzQuMTk5NCA1OC45ODY0IDcyLjI4NzUgNTguNzg2MUM2NC4wNTY5IDU3LjkzMDIgNTkuOTU5OSA1Ni40MzcxIDU1LjAwNyA1Mi41MjIxQzU0LjI5NjggNTEuOTU3NiA1My40NDEgNTEuMzIwMyA1My4wOTUgNTEuMTAxOEM1Mi43NDkgNTAuOTAxNSA1Mi4wNTcxIDUwLjEzNjcgNTEuNTgzNiA0OS40MjY1QzUwLjE0NTEgNDcuMzMyNSA0OC4zNjA2IDQ1Ljk4NSA0Ni45OTQ5IDQ1Ljk2NjhDNDYuNzAzNiA0NS45NjY4IDQ2LjAyOTggNDYuMTg1MyA0NS41MiA0Ni40NDAyWk01NC40NjA3IDU0Ljg3MTFDNTUuMDc5OCA1NS4xODA2IDU1Ljc1MzUgNTUuNTgxMiA1NS45NzIgNTUuNzQ1MUw1Ni4zNzI3IDU2LjA3MjlMNTUuNzM1MyA1OC42MjIyQzU1LjE4OTEgNjAuODQzNyA1NS4wOTggNjEuNDA4MiA1NS4xNTI2IDYyLjk5MjRDNTUuMjA3MyA2NC41NTg0IDU1LjI2MTkgNjQuOTA0MyA1NS42MjYxIDY1LjQxNDJDNTYuMjI3IDY2LjIzMzYgNTcuMjY0OSA2Ni43MjUzIDU4LjQzMDMgNjYuNzI1M0M2MC4wODczIDY2LjcyNTMgNjEuMzgwMiA2NS43Nzg0IDYzLjUyODkgNjIuOTU2QzY0LjE0OCA2Mi4xNTQ4IDY0LjYzOTYgNjEuNzE3NyA2NS4zNjggNjEuMzcxOEM2Ni40OTcgNjAuODA3MyA2Ny4yOTgyIDYwLjc1MjcgNjkuODExIDYwLjk3MTJMNzEuNDg2MyA2MS4xMzVWNjIuMTE4M0M3MS40ODYzIDYzLjY2NjEgNzIuMzA1NyA2NC41NTg0IDczLjk4MDkgNjQuODEzM0w3NC43ODIxIDY0LjkyMjZMNzQuNDkwOCA2NS41OTYzQzczLjIxNjEgNjguNjczNiA2OS45Mzg1IDcyLjE1MTYgNjYuODYxMSA3My42OTk0QzY2LjM2OTUgNzMuOTM2MSA2NS4yNTg3IDc0LjM3MzEgNjQuNDAyOSA3NC42NjQ1QzYzLjAwMDggNzUuMTE5NyA2Mi42MTg0IDc1LjE3NDMgNjAuMjE0OCA3NS4xNzQzQzU3LjgyOTQgNzUuMTc0MyA1Ny40Mjg4IDc1LjExOTcgNTYuMTE3NyA3NC42ODI3QzUyLjE2NjMgNzMuMzcxNiA0OS4yMzQ3IDcwLjQ1ODEgNDcuOTA1NCA2Ni41NDMyQzQ3LjQzMTkgNjUuMTU5MyA0Ny40MTM3IDYxLjEzNSA0Ny44ODcyIDU5LjQ1OThDNDguNTI0NSA1Ny4xNDcyIDQ5LjY1MzUgNTUuMjM1MyA1MC44MzcxIDU0LjQ4ODdDNTEuNjAxOCA1My45OTcgNTMuMDIyMiA1NC4xNjA5IDU0LjQ2MDcgNTQuODcxMVoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0xMTMuMDIyIDYxLjczNjFDMTEzLjAyMiA2Mi41NTU1IDExMi4xMTEgNjYuMzQzMSAxMTEuMzQ3IDY4LjcxMDJDMTA4LjQ3IDc3LjU3ODEgMTAzLjI2MiA4NS41MzU1IDk2LjQ2OTcgOTEuMzQ0M0M5MS42OTg5IDk1LjQ0MTMgODguMzExOSA5Ny4yNDQgODIuOTQwMiA5OC41NzMzQzc5LjQ4MDUgOTkuNDI5MSA3Ny4yMjI2IDk5LjcwMjMgNzIuODM0MSA5OS44MTE1QzY3LjM1MzIgOTkuOTU3MiA2MS45NDUxIDk5LjQ2NTUgNTcuMTAxNCA5OC40MDk0QzU2LjE3MjcgOTguMjA5MSA1NS4zODk4IDk4LjA4MTYgNTUuMzM1MSA5OC4xMzYzQzU1LjExNjYgOTguMzM2NiA1NS45NTQyIDEwMS4xMjMgNTYuNjgyNiAxMDIuNTk4QzU4LjAxMTkgMTA1LjMyOSA1OS41MjMyIDEwNy4zNjggNjIuMjE4MiAxMTAuMDYzQzY1LjA1ODggMTEyLjkwNCA2Ny4xNzExIDExNC40NyA3MC40NDg3IDExNi4xNjNDNzguNTcgMTIwLjM1MSA4Ny44OTMxIDEyMC45MTYgOTcuNDUzIDExNy43NjZDMTA3LjU0MSAxMTQuNDcgMTE0Ljk1MiAxMDguNTE2IDExOC45NCAxMDAuNTAzQzEyMS41OTggOTUuMTg2NCAxMjIuNjkxIDg5LjUwNTEgMTIyLjI5IDgzLjAyMjdDMTIxLjc5OSA3NS4wMjg4IDExOC44NDkgNjcuMTk4OSAxMTQuNTcgNjIuNTczOEMxMTMuODk2IDYxLjg0NTQgMTEzLjI3NyA2MS4yNjI3IDExMy4xODYgNjEuMjYyN0MxMTMuMDk1IDYxLjI2MjcgMTEzLjAyMiA2MS40ODEyIDExMy4wMjIgNjEuNzM2MVoiIGZpbGw9IndoaXRlIi8+CjxkZWZzPgo8bGluZWFyR3JhZGllbnQgaWQ9InBhaW50MF9saW5lYXJfNjgzXzI5NTIiIHgxPSI1LjUiIHkxPSIxMSIgeDI9IjE0MSIgeTI9IjEyNC41IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiM0NUFEQzYiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMjE2Nzc4Ii8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg== - keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "replicas"], ["spec", "resources"], ["spec", "resourcesPreset"], ["spec", "size"], ["spec", "storageClass"], ["spec", "external"], ["spec", "quorum"], ["spec", "quorum", "minSyncReplicas"], ["spec", "quorum", "maxSyncReplicas"], ["spec", "users"], ["spec", "backup"], ["spec", "backup", "enabled"], ["spec", "backup", "schedule"], ["spec", "backup", "retentionPolicy"], ["spec", "backup", "endpointURL"], ["spec", "backup", "destinationPath"], ["spec", "backup", "s3AccessKey"], ["spec", "backup", "s3SecretKey"], ["spec", "bootstrap"], ["spec", "bootstrap", "enabled"], ["spec", "bootstrap", "recoveryTime"], ["spec", "bootstrap", "oldName"]] - secrets: - exclude: [] - include: - - resourceNames: - - ferretdb-{{ .name }}-credentials - services: - exclude: [] - include: - - resourceNames: - - ferretdb-{{ .name }} diff --git a/packages/system/ferretdb-rd/templates/cozyrd.yaml b/packages/system/ferretdb-rd/templates/cozyrd.yaml deleted file mode 100644 index e079ddcf..00000000 --- a/packages/system/ferretdb-rd/templates/cozyrd.yaml +++ /dev/null @@ -1,4 +0,0 @@ -{{- range $path, $_ := .Files.Glob "cozyrds/*" }} ---- -{{ $.Files.Get $path }} -{{- end }} diff --git a/packages/system/ferretdb-rd/values.yaml b/packages/system/ferretdb-rd/values.yaml deleted file mode 100644 index 0967ef42..00000000 --- a/packages/system/ferretdb-rd/values.yaml +++ /dev/null @@ -1 +0,0 @@ -{} From 470d43b33eac1bdf71549787bd179f25e17f18e7 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 10 Feb 2026 21:01:46 +0100 Subject: [PATCH 215/889] fix(mongodb): update MongoDB logo Replace the old MongoDB logo with the official one featuring the MongoDB leaf icon on a green radial gradient background. Co-authored-by: Viktoriia Kvapil <159528100+kvapsova@users.noreply.github.com> Signed-off-by: Andrei Kvapil --- packages/apps/mongodb/logos/mongodb.svg | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/packages/apps/mongodb/logos/mongodb.svg b/packages/apps/mongodb/logos/mongodb.svg index 3c76d2d6..86bb6d40 100644 --- a/packages/apps/mongodb/logos/mongodb.svg +++ b/packages/apps/mongodb/logos/mongodb.svg @@ -1,13 +1,10 @@ - - - - - + + - - - - + + + + From 593a8b2baa9ccb43521b95312f94094f3e3f14d7 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 10 Feb 2026 16:57:26 +0100 Subject: [PATCH 216/889] Update Kube-OVN to v1.15.3 Signed-off-by: Andrei Kvapil --- packages/system/kubeovn/Makefile | 8 +- .../system/kubeovn/charts/kube-ovn/Chart.yaml | 4 +- .../charts/kube-ovn/templates/_helpers.tpl | 4 +- .../kube-ovn/templates/central-deploy.yaml | 1 + .../kube-ovn/templates/controller-deploy.yaml | 48 +- .../templates/ic-controller-deploy.yaml | 1 + .../kube-ovn/templates/kube-ovn-crd.yaml | 567 +++++++++++++++++- .../kube-ovn/templates/monitor-deploy.yaml | 3 +- .../charts/kube-ovn/templates/ovn-CR.yaml | 43 +- .../charts/kube-ovn/templates/ovn-CRB.yaml | 14 + .../kube-ovn/templates/ovn-dpdk-ds.yaml | 2 +- .../charts/kube-ovn/templates/ovncni-ds.yaml | 17 +- .../charts/kube-ovn/templates/ovsovn-ds.yaml | 3 +- .../charts/kube-ovn/templates/pinger-ds.yaml | 6 +- .../kube-ovn/templates/post-delete-hook.yaml | 8 + .../kube-ovn/templates/upgrade-ovs-ovn.yaml | 2 + .../kube-ovn/templates/vpc-nat-config.yaml | 2 +- .../kubeovn/charts/kube-ovn/values.yaml | 26 +- packages/system/kubeovn/values.yaml | 2 +- 19 files changed, 696 insertions(+), 65 deletions(-) diff --git a/packages/system/kubeovn/Makefile b/packages/system/kubeovn/Makefile index 7e6d0d34..45629042 100644 --- a/packages/system/kubeovn/Makefile +++ b/packages/system/kubeovn/Makefile @@ -1,5 +1,3 @@ -KUBEOVN_TAG=v0.40.0 - export NAME=kubeovn export NAMESPACE=cozy-$(NAME) @@ -8,6 +6,6 @@ include ../../../hack/package.mk update: 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 + tag=$$(git ls-remote --tags --sort="v:refname" https://github.com/cozystack/kubeovn-chart | awk -F'[/^]' 'END{print $$3}') && \ + curl -sSL https://github.com/cozystack/kubeovn-chart/archive/refs/tags/$${tag}.tar.gz | \ + tar xzvf - --strip 2 kubeovn-chart-$${tag#*v}/chart diff --git a/packages/system/kubeovn/charts/kube-ovn/Chart.yaml b/packages/system/kubeovn/charts/kube-ovn/Chart.yaml index 0621c7c7..f0295b87 100644 --- a/packages/system/kubeovn/charts/kube-ovn/Chart.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/Chart.yaml @@ -15,12 +15,12 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: v1.14.25 +version: v1.15.3 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "1.14.25" +appVersion: "1.15.3" kubeVersion: ">= 1.29.0-0" diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/_helpers.tpl b/packages/system/kubeovn/charts/kube-ovn/templates/_helpers.tpl index fd6db240..ce144769 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/_helpers.tpl +++ b/packages/system/kubeovn/charts/kube-ovn/templates/_helpers.tpl @@ -69,7 +69,9 @@ Number of master nodes {{- $imageVersion := (index $ds.spec.template.spec.containers 0).image | splitList ":" | last | trimPrefix "v" -}} {{- $versionRegex := `^(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)` -}} {{- if and (ne $newChartVersion $chartVersion) (regexMatch $versionRegex $imageVersion) -}} - {{- if regexFind $versionRegex $imageVersion | semverCompare ">= 1.13.0" -}} + {{- if regexFind $versionRegex $imageVersion | semverCompare ">= 1.15.0" -}} + 25.03 + {{- else if regexFind $versionRegex $imageVersion | semverCompare ">= 1.13.0" -}} 24.03 {{- else if regexFind $versionRegex $imageVersion | semverCompare ">= 1.12.0" -}} 22.12 diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/central-deploy.yaml b/packages/system/kubeovn/charts/kube-ovn/templates/central-deploy.yaml index 505e0925..1bb0ece4 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/central-deploy.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/central-deploy.yaml @@ -122,6 +122,7 @@ spec: limits: cpu: {{ index .Values "ovn-central" "limits" "cpu" }} memory: {{ index .Values "ovn-central" "limits" "memory" }} + ephemeral-storage: {{ index .Values "ovn-central" "limits" "ephemeral-storage" }} volumeMounts: - mountPath: /var/run/ovn name: host-run-ovn 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 219e4ca0..69e27b23 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/controller-deploy.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/controller-deploy.yaml @@ -76,18 +76,43 @@ spec: args: - /kube-ovn/start-controller.sh - --default-ls={{ .Values.networking.DEFAULT_SUBNET }} - - --default-cidr={{ .Values.ipv4.POD_CIDR }} - - --default-gateway={{ .Values.ipv4.POD_GATEWAY }} + - --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-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={{ .Values.ipv4.JOIN_CIDR }} - - --service-cluster-ip-range={{ .Values.ipv4.SVC_CIDR }} - {{- if .Values.global.logVerbosity }} - - --v={{ .Values.global.logVerbosity }} + - --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 }} - --network-type={{- .Values.networking.NETWORK_TYPE }} - --default-provider-name={{ .Values.networking.vlan.PROVIDER_NAME }} @@ -100,6 +125,7 @@ spec: - --pod-nic-type={{- .Values.networking.POD_NIC_TYPE }} - --enable-lb={{- .Values.func.ENABLE_LB }} - --enable-np={{- .Values.func.ENABLE_NP }} + - --np-enforcement={{- .Values.func.NP_ENFORCEMENT }} - --enable-eip-snat={{- .Values.networking.ENABLE_EIP_SNAT }} - --enable-external-vpc={{- .Values.func.ENABLE_EXTERNAL_VPC }} - --enable-ecmp={{- .Values.networking.ENABLE_ECMP }} @@ -116,11 +142,14 @@ spec: - --secure-serving={{- .Values.func.SECURE_SERVING }} - --enable-ovn-ipsec={{- .Values.func.ENABLE_OVN_IPSEC }} - --enable-anp={{- .Values.func.ENABLE_ANP }} + - --enable-dns-name-resolver={{- .Values.func.ENABLE_DNS_NAME_RESOLVER }} - --ovsdb-con-timeout={{- .Values.func.OVSDB_CON_TIMEOUT }} - --ovsdb-inactivity-timeout={{- .Values.func.OVSDB_INACTIVITY_TIMEOUT }} - --enable-live-migration-optimize={{- .Values.func.ENABLE_LIVE_MIGRATION_OPTIMIZE }} - --enable-ovn-lb-prefer-local={{- .Values.func.ENABLE_OVN_LB_PREFER_LOCAL }} - --image={{ .Values.global.registry.address }}/{{ .Values.global.images.kubeovn.repository }}:{{ .Values.global.images.kubeovn.tag }} + - --skip-conntrack-dst-cidrs={{- .Values.networking.SKIP_CONNTRACK_DST_CIDRS }} + - --non-primary-cni-mode={{- .Values.cni_conf.NON_PRIMARY_CNI }} securityContext: runAsUser: {{ include "kubeovn.runAsUser" . }} privileged: false @@ -139,11 +168,7 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - - name: KUBE_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: KUBE_NODE_NAME + - name: NODE_NAME valueFrom: fieldRef: fieldPath: spec.nodeName @@ -193,6 +218,7 @@ spec: limits: cpu: {{ index .Values "kube-ovn-controller" "limits" "cpu" }} memory: {{ index .Values "kube-ovn-controller" "limits" "memory" }} + ephemeral-storage: {{ index .Values "kube-ovn-controller" "limits" "ephemeral-storage" }} nodeSelector: kubernetes.io/os: "linux" volumes: diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/ic-controller-deploy.yaml b/packages/system/kubeovn/charts/kube-ovn/templates/ic-controller-deploy.yaml index 53ecfa24..dc932a63 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/ic-controller-deploy.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/ic-controller-deploy.yaml @@ -100,6 +100,7 @@ spec: limits: cpu: 3 memory: 1Gi + ephemeral-storage: 1Gi volumeMounts: - mountPath: /var/run/ovn name: host-run-ovn diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/kube-ovn-crd.yaml b/packages/system/kubeovn/charts/kube-ovn/templates/kube-ovn-crd.yaml index 78ac7d38..5b093be6 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/kube-ovn-crd.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/kube-ovn-crd.yaml @@ -37,10 +37,14 @@ spec: properties: vpc: type: string + description: VPC name for the DNS service. This field is immutable after creation. subnet: type: string + description: Subnet name for the DNS service. This field is immutable after creation. replicas: type: integer + description: Number of DNS server replicas (1-3) + format: int32 minimum: 1 maximum: 3 status: @@ -48,23 +52,31 @@ spec: properties: active: type: boolean + description: Whether the VPC DNS service is active conditions: type: array + description: Conditions represent the latest state of the VPC DNS items: type: object properties: type: type: string + description: Type of the condition status: type: string + description: Status of the condition (True, False, Unknown) reason: type: string + description: Reason for the condition's last transition message: type: string + description: Human-readable message indicating details about the condition lastUpdateTime: type: string + description: Last time this condition was updated lastTransitionTime: type: string + description: Last time the condition transitioned from one status to another --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition @@ -117,14 +129,20 @@ spec: properties: name: type: string + description: Port name port: type: integer + description: Service port number (1-65535) + format: int32 minimum: 1 maximum: 65535 protocol: type: string + description: Protocol (TCP or UDP) targetPort: type: integer + description: Target port number (1-65535) + format: int32 minimum: 1 maximum: 65535 type: object @@ -142,8 +160,10 @@ spec: properties: ports: type: string + description: Configured ports service: type: string + description: Associated service name --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition @@ -186,12 +206,15 @@ spec: items: type: string type: array + description: External subnets configured for the NAT gateway selector: type: array items: type: string + description: Pod selector configured for the NAT gateway qosPolicy: type: string + description: QoS policy applied to the NAT gateway tolerations: type: array items: @@ -204,6 +227,8 @@ spec: enum: - Equal - Exists + - Lt + - Gt value: type: string effect: @@ -213,6 +238,7 @@ spec: - NoSchedule - PreferNoSchedule tolerationSeconds: + format: int64 type: integer affinity: properties: @@ -258,6 +284,7 @@ spec: type: object weight: type: integer + format: int32 minimum: 1 maximum: 100 required: @@ -322,8 +349,6 @@ spec: properties: key: type: string - x-kubernetes-patch-strategy: merge - x-kubernetes-patch-merge-key: key operator: type: string values: @@ -335,6 +360,9 @@ spec: - operator type: object type: array + x-kubernetes-list-type: map + x-kubernetes-list-map-keys: + - key matchLabels: additionalProperties: type: string @@ -351,6 +379,7 @@ spec: type: object weight: type: integer + format: int32 minimum: 1 maximum: 100 required: @@ -368,8 +397,6 @@ spec: properties: key: type: string - x-kubernetes-patch-strategy: merge - x-kubernetes-patch-merge-key: key operator: type: string values: @@ -381,6 +408,9 @@ spec: - operator type: object type: array + x-kubernetes-list-type: map + x-kubernetes-list-map-keys: + - key matchLabels: additionalProperties: type: string @@ -411,8 +441,6 @@ spec: properties: key: type: string - x-kubernetes-patch-strategy: merge - x-kubernetes-patch-merge-key: key operator: type: string values: @@ -424,6 +452,9 @@ spec: - operator type: object type: array + x-kubernetes-list-type: map + x-kubernetes-list-map-keys: + - key matchLabels: additionalProperties: type: string @@ -440,6 +471,7 @@ spec: type: object weight: type: integer + format: int32 minimum: 1 maximum: 100 required: @@ -457,8 +489,6 @@ spec: properties: key: type: string - x-kubernetes-patch-strategy: merge - x-kubernetes-patch-merge-key: key operator: type: string values: @@ -470,6 +500,9 @@ spec: - operator type: object type: array + x-kubernetes-list-type: map + x-kubernetes-list-map-keys: + - key matchLabels: additionalProperties: type: string @@ -492,45 +525,79 @@ spec: properties: lanIp: type: string + description: LAN IP address for the NAT gateway. This field is immutable after creation. subnet: type: string + description: Subnet name for the NAT gateway. This field is immutable after creation. externalSubnets: items: type: string type: array + description: External subnets accessible through the NAT gateway vpc: type: string + description: VPC name for the NAT gateway. This field is immutable after creation. selector: type: array items: type: string + description: Pod selector for the NAT gateway qosPolicy: type: string + description: QoS policy name to apply to the NAT gateway + noDefaultEIP: + type: boolean + description: Disable default EIP assignment bgpSpeaker: type: object + description: BGP speaker configuration properties: enabled: type: boolean + description: Enable BGP speaker asn: type: integer + format: uint32 + description: Local AS number remoteAsn: type: integer + format: uint32 + description: Remote AS number neighbors: type: array items: type: string + description: BGP neighbor IP addresses holdTime: type: string + description: BGP hold time routerId: type: string + description: BGP router ID password: type: string + description: BGP authentication password enableGracefulRestart: type: boolean + description: Enable BGP graceful restart extraArgs: type: array items: type: string + description: Extra BGP arguments + routes: + type: array + description: Static routes for the NAT gateway + items: + type: object + properties: + cidr: + type: string + format: cidr + description: Destination CIDR for the route + nextHopIP: + type: string + description: Next hop IP address tolerations: type: array items: @@ -543,6 +610,8 @@ spec: enum: - Equal - Exists + - Lt + - Gt value: type: string effect: @@ -552,6 +621,7 @@ spec: - NoSchedule - PreferNoSchedule tolerationSeconds: + format: int64 type: integer affinity: properties: @@ -597,6 +667,7 @@ spec: type: object weight: type: integer + format: int32 minimum: 1 maximum: 100 required: @@ -661,8 +732,6 @@ spec: properties: key: type: string - x-kubernetes-patch-strategy: merge - x-kubernetes-patch-merge-key: key operator: type: string values: @@ -674,6 +743,9 @@ spec: - operator type: object type: array + x-kubernetes-list-type: map + x-kubernetes-list-map-keys: + - key matchLabels: additionalProperties: type: string @@ -690,6 +762,7 @@ spec: type: object weight: type: integer + format: int32 minimum: 1 maximum: 100 required: @@ -707,8 +780,6 @@ spec: properties: key: type: string - x-kubernetes-patch-strategy: merge - x-kubernetes-patch-merge-key: key operator: type: string values: @@ -720,6 +791,9 @@ spec: - operator type: object type: array + x-kubernetes-list-type: map + x-kubernetes-list-map-keys: + - key matchLabels: additionalProperties: type: string @@ -750,8 +824,6 @@ spec: properties: key: type: string - x-kubernetes-patch-strategy: merge - x-kubernetes-patch-merge-key: key operator: type: string values: @@ -763,6 +835,9 @@ spec: - operator type: object type: array + x-kubernetes-list-type: map + x-kubernetes-list-map-keys: + - key matchLabels: additionalProperties: type: string @@ -779,6 +854,7 @@ spec: type: object weight: type: integer + format: int32 minimum: 1 maximum: 100 required: @@ -796,8 +872,6 @@ spec: properties: key: type: string - x-kubernetes-patch-strategy: merge - x-kubernetes-patch-merge-key: key operator: type: string values: @@ -809,6 +883,9 @@ spec: - operator type: object type: array + x-kubernetes-list-type: map + x-kubernetes-list-map-keys: + - key matchLabels: additionalProperties: type: string @@ -897,10 +974,13 @@ spec: properties: replicas: type: integer + format: int32 minimum: 0 maximum: 10 + description: Number of egress gateway replicas labelSelector: type: string + description: Label selector for the egress gateway conditions: items: properties: @@ -914,6 +994,7 @@ spec: maxLength: 32768 type: string observedGeneration: + format: int64 minimum: 0 type: integer reason: @@ -940,6 +1021,7 @@ spec: - type type: object type: array + description: Conditions represent the latest available observations of the egress gateway's current state x-kubernetes-list-map-keys: - type x-kubernetes-list-type: map @@ -947,12 +1029,15 @@ spec: items: type: string type: array + description: Internal IP addresses assigned to the egress gateway externalIPs: items: type: string type: array + description: External IP addresses assigned to the egress gateway phase: type: string + description: Current phase of the egress gateway (Pending, Processing, or Completed) default: Pending enum: - Pending @@ -960,9 +1045,11 @@ spec: - Completed ready: type: boolean + description: Indicates whether the egress gateway is ready default: false workload: type: object + description: Workload information for the egress gateway properties: apiVersion: type: string @@ -994,11 +1081,14 @@ spec: properties: replicas: type: integer + format: int32 default: 1 minimum: 0 maximum: 10 + description: Number of egress gateway replicas prefix: type: string + description: Name prefix for egress gateway pods. This field is immutable after creation. anyOf: - pattern: ^$ - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*[-\.]?$ @@ -1007,10 +1097,13 @@ spec: message: "This field is immutable." vpc: type: string + description: VPC name for the egress gateway. This field is immutable after creation. internalSubnet: type: string + description: Internal subnet name for the egress gateway. This field is immutable after creation. externalSubnet: type: string + description: External subnet name for the egress gateway. This field is immutable after creation and is required. internalIPs: items: type: string @@ -1021,6 +1114,7 @@ spec: - pattern: ^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|:))),(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])$ type: array x-kubernetes-list-type: set + description: Internal IP addresses for the egress gateway externalIPs: items: type: string @@ -1031,31 +1125,38 @@ spec: - pattern: ^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|:))),(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])$ type: array x-kubernetes-list-type: set + description: External IP addresses for the egress gateway image: type: string + description: Container image for the egress gateway bfd: type: object + description: BFD (Bidirectional Forwarding Detection) configuration properties: enabled: type: boolean default: false minRX: type: integer + format: int32 default: 1000 minimum: 1 maximum: 3600000 minTX: type: integer + format: int32 default: 1000 minimum: 1 maximum: 3600000 multiplier: type: integer + format: int32 default: 3 minimum: 1 maximum: 3600000 selectors: type: array + description: Selectors for pods to use this egress gateway items: type: object properties: @@ -1113,6 +1214,7 @@ spec: message: 'Each pod selector MUST have at least one matchLabels or matchExpressions' policies: type: array + description: Egress policies for the gateway items: type: object properties: @@ -1139,12 +1241,14 @@ spec: message: 'Each policy MUST have at least one ipBlock or subnet' trafficPolicy: type: string + description: Traffic policy for the egress gateway (Local or Cluster) enum: - Local - Cluster default: Cluster nodeSelector: type: array + description: Node selector for egress gateway placement items: type: object properties: @@ -1224,13 +1328,16 @@ spec: operator: description: |- Operator represents a key's relationship to the value. - Valid operators are Exists and Equal. Defaults to Equal. + Valid operators are Exists, Equal, Lt, and Gt. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category. + Lt and Gt perform numeric comparisons (requires feature gate TaintTolerationComparisonOperators). type: string enum: - Exists - Equal + - Lt + - Gt tolerationSeconds: description: |- TolerationSeconds represents the period of time the toleration (which must be @@ -1292,46 +1399,64 @@ spec: properties: ready: type: boolean + description: Indicates whether the EIP is ready ip: type: string + description: IP address assigned to the EIP nat: type: string + description: NAT configuration status redo: type: string + description: Redo operation status qosPolicy: type: string + description: QoS policy applied to the EIP conditions: type: array + description: Conditions represent the latest available observations of the EIP's current state items: type: object properties: type: type: string + description: Type of condition status: type: string + description: Status of the condition (True, False, Unknown) reason: type: string + description: Reason for the condition's last transition message: type: string + description: Human-readable message indicating details about the condition lastUpdateTime: type: string + description: Last time this condition was updated lastTransitionTime: type: string + description: Last time the condition transitioned from one status to another spec: type: object properties: v4ip: type: string + description: IPv4 address for the EIP v6ip: type: string + description: IPv6 address for the EIP macAddress: type: string + description: MAC address for the EIP natGwDp: type: string + description: NAT gateway datapath where the EIP is assigned qosPolicy: type: string + description: QoS policy name to apply to the EIP externalSubnet: type: string + description: External subnet name. This field is immutable after creation. --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition @@ -1381,40 +1506,55 @@ spec: properties: ready: type: boolean + description: Indicates whether the FIP rule is ready v4ip: type: string + description: IPv4 address of the EIP v6ip: type: string + description: IPv6 address of the EIP natGwDp: type: string + description: NAT gateway datapath where the FIP is configured redo: type: string + description: Redo operation status internalIp: type: string + description: Internal IP address mapped to the FIP conditions: type: array + description: Conditions represent the latest available observations of the FIP rule's current state items: type: object properties: type: type: string + description: Type of condition status: type: string + description: Status of the condition (True, False, Unknown) reason: type: string + description: Reason for the condition's last transition message: type: string + description: Human-readable message indicating details about the condition lastUpdateTime: type: string + description: Last time this condition was updated lastTransitionTime: type: string + description: Last time the condition transitioned from one status to another spec: type: object properties: eip: type: string + description: EIP name to use for floating IP internalIp: type: string + description: Internal IP address to map to the floating IP --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition @@ -1473,52 +1613,73 @@ spec: properties: ready: type: boolean + description: Indicates whether the DNAT rule is ready v4ip: type: string + description: IPv4 address of the EIP v6ip: type: string + description: IPv6 address of the EIP natGwDp: type: string + description: NAT gateway datapath where the DNAT rule is configured redo: type: string + description: Redo operation status protocol: type: string + description: Protocol type of the DNAT rule internalIp: type: string + description: Internal IP address configured in the DNAT rule internalPort: type: string + description: Internal port configured in the DNAT rule externalPort: type: string + description: External port configured in the DNAT rule conditions: type: array + description: Conditions represent the latest available observations of the DNAT rule's current state items: type: object properties: type: type: string + description: Type of condition status: type: string + description: Status of the condition (True, False, Unknown) reason: type: string + description: Reason for the condition's last transition message: type: string + description: Human-readable message indicating details about the condition lastUpdateTime: type: string + description: Last time this condition was updated lastTransitionTime: type: string + description: Last time the condition transitioned from one status to another spec: type: object properties: eip: type: string + description: EIP name for DNAT rule externalPort: type: string + description: External port number protocol: type: string + description: Protocol type (TCP or UDP) internalIp: type: string + description: Internal IP address to forward traffic to internalPort: type: string + description: Internal port number to forward traffic to --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition @@ -1568,40 +1729,55 @@ spec: properties: ready: type: boolean + description: Indicates whether the SNAT rule is ready v4ip: type: string + description: IPv4 address of the EIP v6ip: type: string + description: IPv6 address of the EIP natGwDp: type: string + description: NAT gateway datapath where the SNAT rule is configured redo: type: string + description: Redo operation status internalCIDR: type: string + description: Internal CIDR configured in the SNAT rule conditions: type: array + description: Conditions represent the latest available observations of the SNAT rule's current state items: type: object properties: type: type: string + description: Type of condition status: type: string + description: Status of the condition (True, False, Unknown) reason: type: string + description: Reason for the condition's last transition message: type: string + description: Human-readable message indicating details about the condition lastUpdateTime: type: string + description: Last time this condition was updated lastTransitionTime: type: string + description: Last time the condition transitioned from one status to another spec: type: object properties: eip: type: string + description: EIP name for SNAT rule internalCIDR: type: string + description: Internal CIDR to be translated via SNAT --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition @@ -1654,46 +1830,64 @@ spec: properties: type: type: string + description: Type of the OVN EIP nat: type: string + description: NAT configuration status ready: type: boolean + description: Indicates whether the EIP is ready v4Ip: type: string + description: IPv4 address assigned to the EIP v6Ip: type: string + description: IPv6 address assigned to the EIP macAddress: type: string + description: MAC address assigned to the EIP conditions: type: array + description: Conditions represent the latest available observations of the EIP's current state items: type: object properties: type: type: string + description: Type of condition status: type: string + description: Status of the condition (True, False, Unknown) reason: type: string + description: Reason for the condition's last transition message: type: string + description: Human-readable message indicating details about the condition lastUpdateTime: type: string + description: Last time this condition was updated lastTransitionTime: type: string + description: Last time the condition transitioned from one status to another spec: type: object properties: externalSubnet: type: string + description: External subnet name. This field is immutable after creation. type: type: string + description: Type of the OVN EIP (e.g., normal, distributed) v4Ip: type: string + description: IPv4 address for the EIP v6Ip: type: string + description: IPv6 address for the EIP macAddress: type: string + description: MAC address for the EIP --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition @@ -1752,50 +1946,70 @@ spec: properties: ready: type: boolean + description: Indicates whether the FIP is ready v4Eip: type: string + description: IPv4 EIP address assigned v6Eip: type: string + description: IPv6 EIP address assigned v4Ip: type: string + description: IPv4 address mapped to the FIP v6Ip: type: string + description: IPv6 address mapped to the FIP vpc: type: string + description: VPC name where the FIP is configured conditions: type: array + description: Conditions represent the latest available observations of the FIP's current state items: type: object properties: type: type: string + description: Type of condition status: type: string + description: Status of the condition (True, False, Unknown) reason: type: string + description: Reason for the condition's last transition message: type: string + description: Human-readable message indicating details about the condition lastUpdateTime: type: string + description: Last time this condition was updated lastTransitionTime: type: string + description: Last time the condition transitioned from one status to another spec: type: object properties: ovnEip: type: string + description: OVN EIP name to use for floating IP ipType: type: string + description: IP type (e.g., ipv4, ipv6, dual) type: type: string + description: FIP type ipName: type: string + description: IP resource name vpc: type: string + description: VPC name. This field is immutable after creation. v4Ip: type: string + description: IPv4 address for the floating IP v6Ip: type: string + description: IPv6 address for the floating IP --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition @@ -1845,48 +2059,67 @@ spec: properties: ready: type: boolean + description: Indicates whether the SNAT rule is ready v4Eip: type: string + description: IPv4 EIP address assigned v6Eip: type: string + description: IPv6 EIP address assigned v4IpCidr: type: string + description: IPv4 CIDR configured in the SNAT rule v6IpCidr: type: string + description: IPv6 CIDR configured in the SNAT rule vpc: type: string + description: VPC name where the SNAT rule is configured conditions: type: array + description: Conditions represent the latest available observations of the SNAT rule's current state items: type: object properties: type: type: string + description: Type of condition status: type: string + description: Status of the condition (True, False, Unknown) reason: type: string + description: Reason for the condition's last transition message: type: string + description: Human-readable message indicating details about the condition lastUpdateTime: type: string + description: Last time this condition was updated lastTransitionTime: type: string + description: Last time the condition transitioned from one status to another spec: type: object properties: ovnEip: type: string + description: OVN EIP name for SNAT rule vpcSubnet: type: string + description: VPC subnet name for SNAT ipName: type: string + description: IP resource name vpc: type: string + description: VPC name. This field is immutable after creation. v4IpCidr: type: string + description: IPv4 CIDR for SNAT v6IpCidr: type: string + description: IPv6 CIDR for SNAT --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition @@ -1951,62 +2184,88 @@ spec: properties: ready: type: boolean + description: Indicates whether the DNAT rule is ready v4Eip: type: string + description: IPv4 EIP address assigned v6Eip: type: string + description: IPv6 EIP address assigned v4Ip: type: string + description: IPv4 address configured in the DNAT rule v6Ip: type: string + description: IPv6 address configured in the DNAT rule vpc: type: string + description: VPC name where the DNAT rule is configured externalPort: type: string + description: External port configured in the DNAT rule internalPort: type: string + description: Internal port configured in the DNAT rule protocol: type: string + description: Protocol type configured in the DNAT rule ipName: type: string + description: IP resource name conditions: type: array + description: Conditions represent the latest available observations of the DNAT rule's current state items: type: object properties: type: type: string + description: Type of condition status: type: string + description: Status of the condition (True, False, Unknown) reason: type: string + description: Reason for the condition's last transition message: type: string + description: Human-readable message indicating details about the condition lastUpdateTime: type: string + description: Last time this condition was updated lastTransitionTime: type: string + description: Last time the condition transitioned from one status to another spec: type: object properties: ovnEip: type: string + description: OVN EIP name for DNAT rule ipType: type: string + description: IP type (e.g., ipv4, ipv6, dual) ipName: type: string + description: IP resource name externalPort: type: string + description: External port number internalPort: type: string + description: Internal port number to forward traffic to protocol: type: string + description: Protocol type (TCP or UDP) vpc: type: string + description: VPC name. This field is immutable after creation. v4Ip: type: string + description: IPv4 address for DNAT v6Ip: type: string + description: IPv6 address for DNAT --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition @@ -2045,19 +2304,25 @@ spec: properties: defaultSubnet: type: string + description: The default subnet name for the VPC enableExternal: type: boolean + description: Enable external network access for the VPC enableBfd: type: boolean + description: Enable BFD (Bidirectional Forwarding Detection) for the VPC namespaces: + description: List of namespaces that can use this VPC items: type: string type: array extraExternalSubnets: + description: Extra external subnets for provider-network VLAN. Immutable after creation. items: type: string type: array staticRoutes: + description: Static routes for the VPC. items: properties: policy: @@ -2075,10 +2340,14 @@ spec: type: object type: array policyRoutes: + description: Policy routes for the VPC. items: properties: priority: type: integer + description: Priority of the policy route (0-32767) + min: 0 + max: 32767 action: type: string match: @@ -2088,6 +2357,7 @@ spec: type: object type: array vpcPeerings: + description: VPC peering configurations. items: properties: remoteVpc: @@ -2100,9 +2370,11 @@ spec: properties: enabled: type: boolean + description: Enable BFD port default: false ip: type: string + description: IP address for BFD port (IPv4, IPv6, or comma-separated pair) anyOf: - pattern: ^$ - pattern: ^(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])$ @@ -2116,8 +2388,10 @@ spec: properties: key: type: string + description: Label key operator: type: string + description: Label operator enum: - In - NotIn @@ -2162,6 +2436,7 @@ spec: type: object type: array default: + description: Whether this is the default subnet. type: boolean defaultLogicalSwitch: type: string @@ -2178,10 +2453,12 @@ spec: type: string type: array extraExternalSubnets: + description: Extra external subnets for provider-network VLAN. Immutable after creation. items: type: string type: array vpcPeerings: + description: VPC peering configurations. items: type: string type: array @@ -2202,10 +2479,13 @@ spec: properties: ip: type: string + description: BFD port IP address name: type: string + description: BFD port name nodes: type: array + description: Nodes where BFD port is deployed items: type: string type: object @@ -2258,36 +2538,49 @@ spec: properties: podName: type: string + description: Pod name that this IP belongs to namespace: type: string + description: Namespace of the pod subnet: type: string + description: Primary subnet name for the IP. This field is immutable after creation. attachSubnets: type: array + description: Additional attached subnets items: type: string nodeName: type: string + description: Node name where the pod resides ipAddress: type: string + description: IP address (deprecated, use v4IpAddress or v6IpAddress) v4IpAddress: type: string + description: IPv4 address v6IpAddress: type: string + description: IPv6 address attachIps: type: array + description: Additional IP addresses from attached subnets items: type: string macAddress: type: string + description: MAC address for the primary IP attachMacs: type: array + description: MAC addresses for attached IPs items: type: string containerID: type: string + description: Container ID podType: type: string + description: Pod type (e.g., pod, vm) scope: Cluster names: plural: ips @@ -2315,6 +2608,9 @@ spec: served: true storage: true additionalPrinterColumns: + - name: Namespace + type: string + jsonPath: .spec.namespace - name: V4IP type: string jsonPath: .status.v4ip @@ -2339,56 +2635,74 @@ spec: properties: type: type: string - ready: - type: boolean + description: Type of VIP (e.g., Layer2, HealthCheck) v4ip: type: string + description: Allocated IPv4 address v6ip: type: string + description: Allocated IPv6 address mac: type: string + description: MAC address associated with the VIP selector: type: array + description: Pod names selected by this VIP items: type: string conditions: type: array + description: Conditions represent the latest state of the VIP items: type: object properties: type: type: string + description: Type of the condition status: type: string + description: Status of the condition (True, False, Unknown) reason: type: string + description: Reason for the condition's last transition message: type: string + description: Human-readable message indicating details about the condition lastUpdateTime: type: string + description: Last time this condition was updated lastTransitionTime: type: string + description: Last time the condition transitioned from one status to another spec: type: object properties: namespace: type: string + description: Namespace where the VIP is created. This field is immutable after creation. subnet: type: string + description: Subnet name for the VIP. This field is immutable after creation. type: type: string + description: Type of VIP. This field is immutable after creation. attachSubnets: type: array + description: Additional subnets to attach items: type: string v4ip: type: string + description: Specific IPv4 address to use (optional, will be allocated if not specified) macAddress: type: string + description: MAC address for the VIP v6ip: type: string + description: Specific IPv6 address to use (optional, will be allocated if not specified) selector: type: array + description: Pod names to be selected by this VIP items: type: string --- @@ -2478,6 +2792,7 @@ spec: dhcpV6OptionsUUID: type: string u2oInterconnectionIP: + description: Underlay to overlay interconnection IP. type: string u2oInterconnectionMAC: type: string @@ -2496,6 +2811,7 @@ spec: v6availableIPrange: type: string natOutgoingPolicyRules: + description: NAT outgoing policy rules. type: array items: type: object @@ -2535,50 +2851,89 @@ spec: type: object properties: vpc: + description: VPC name for the subnet. Immutable after creation. type: string default: + description: Whether this is the default subnet. type: boolean protocol: + description: Network protocol (IPv4, IPv6, or Dual). Immutable after creation. type: string enum: - IPv4 - IPv6 - Dual cidrBlock: + description: CIDR block for the subnet. Immutable after creation. type: string namespaces: + description: List of namespaces associated with this subnet. type: array items: type: string gateway: + description: Gateway IP address for the subnet. type: string provider: + description: Provider network name. type: string excludeIps: + description: IP addresses to exclude from allocation. type: array items: type: string vips: + description: Virtual IP addresses for the subnet. type: array items: type: string gatewayType: + description: Gateway type (distributed or centralized). type: string allowSubnets: + description: Allowed subnets for east-west traffic. type: array items: type: string gatewayNode: + description: Gateway node for centralized gateway mode. type: string + gatewayNodeSelectors: + description: Node selectors for gateway placement. + type: array + items: + type: object + properties: + matchLabels: + type: object + additionalProperties: + type: string + matchExpressions: + type: array + items: + type: object + properties: + key: + type: string + operator: + type: string + values: + type: array + items: + type: string natOutgoing: + description: Enable NAT for outgoing traffic. type: boolean externalEgressGateway: + description: External egress gateway for the subnet. type: string policyRoutingPriority: + description: Policy routing priority. type: integer minimum: 1 maximum: 32765 policyRoutingTableID: + description: Policy routing table ID. type: integer minimum: 1 maximum: 2147483647 @@ -2589,32 +2944,45 @@ spec: - 254 # main - 255 # local mtu: + description: Maximum transmission unit for the subnet. type: integer minimum: 68 maximum: 65535 private: + description: Whether the subnet is private. type: boolean vlan: + description: VLAN ID for the subnet. Immutable after creation. type: string logicalGateway: + description: Whether to use logical gateway. type: boolean disableGatewayCheck: + description: Disable gateway connectivity check. type: boolean disableInterConnection: + description: Disable subnet interconnection. type: boolean enableDHCP: + description: Enable DHCP for the subnet. type: boolean dhcpV4Options: + description: DHCPv4 options for the subnet. type: string dhcpV6Options: + description: DHCPv6 options for the subnet. type: string enableIPv6RA: + description: Enable IPv6 router advertisement. type: boolean ipv6RAConfigs: + description: IPv6 router advertisement configurations. type: string allowEWTraffic: + description: Allow east-west traffic between pods. type: boolean acls: + description: Access control lists for the subnet. type: array items: type: object @@ -2639,6 +3007,7 @@ spec: - drop - reject natOutgoingPolicyRules: + description: NAT outgoing policy rules. type: array items: type: object @@ -2656,20 +3025,28 @@ spec: dstIPs: type: string u2oInterconnection: + description: Enable underlay to overlay interconnection. type: boolean u2oInterconnectionIP: + description: Underlay to overlay interconnection IP. type: string enableLb: + description: Enable load balancer for the subnet. type: boolean enableEcmp: + description: Enable ECMP for the subnet. type: boolean enableMulticastSnoop: + description: Enable multicast snooping. type: boolean enableExternalLBAddress: + description: Enable external load balancer address. type: boolean routeTable: + description: Route table for the subnet. type: string namespaceSelectors: + description: Namespace selectors for subnet association. type: array items: type: object @@ -2691,6 +3068,9 @@ spec: type: array items: type: string + nodeNetwork: + description: Node network for the subnet. + type: string scope: Cluster names: plural: subnets @@ -2715,6 +3095,9 @@ spec: - name: Subnet type: string jsonPath: .spec.subnet + - name: enableAddressSet + type: boolean + jsonPath: .spec.enableAddressSet - name: IPs type: string jsonPath: .spec.ips @@ -2739,10 +3122,12 @@ spec: properties: subnet: type: string + description: Subnet name for the IP pool. This field is immutable. x-kubernetes-validations: - rule: "self == oldSelf" message: "This field is immutable." namespaces: + description: Namespaces that can use this IP pool type: array x-kubernetes-list-type: set items: @@ -2751,6 +3136,7 @@ spec: type: array minItems: 1 x-kubernetes-list-type: set + description: IP addresses or ranges in the pool (IPv4/IPv6 addresses or CIDR ranges) items: type: string anyOf: @@ -2759,6 +3145,10 @@ spec: - format: cidr - pattern: ^(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.\.(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])$ - pattern: ^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|:)))\.\.((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|:)))$ + enableAddressSet: + type: boolean + default: false + description: EnableAddressSet to work with policy-based routing and ACL required: - subnet - ips @@ -2767,37 +3157,52 @@ spec: properties: v4AvailableIPs: type: number + description: Number of available IPv4 addresses v4UsingIPs: type: number + description: Number of using IPv4 addresses v6AvailableIPs: type: number + description: Number of available IPv6 addresses v6UsingIPs: type: number + description: Number of using IPv6 addresses v4AvailableIPRange: type: string + description: Available IPv4 address range v4UsingIPRange: type: string + description: IPv4 address range in use v6AvailableIPRange: type: string + description: Available IPv6 address range v6UsingIPRange: type: string + description: IPv6 address range in use conditions: type: array + description: Conditions represent the latest state of the IP pool items: type: object properties: type: type: string + description: Type of the condition status: type: string + description: Status of the condition (True, False, Unknown) reason: type: string + description: Reason for the condition's last transition message: type: string + description: Human-readable message indicating details about the condition lastUpdateTime: type: string + description: Last time this condition was updated lastTransitionTime: type: string + description: Last time the condition transitioned from one status to another scope: Cluster names: plural: ippools @@ -2827,10 +3232,12 @@ spec: properties: id: type: integer + description: VLAN ID (0-4095). This field is immutable after creation. minimum: 0 maximum: 4095 provider: type: string + description: Provider network name. This field is immutable after creation. vlanId: type: integer description: Deprecated in favor of id @@ -2844,10 +3251,12 @@ spec: properties: subnets: type: array + description: List of subnet names using this VLAN items: type: string conflict: type: boolean + description: Whether there is a conflict with this VLAN additionalPrinterColumns: - name: ID type: string @@ -2896,29 +3305,69 @@ spec: properties: defaultInterface: type: string + description: Default interface name for the provider network. This field is immutable after creation. maxLength: 15 pattern: '^[^/\s]+$' customInterfaces: type: array + description: Custom interface configurations for specific nodes items: type: object properties: interface: type: string + description: Interface name maxLength: 15 pattern: '^[^/\s]+$' nodes: type: array + description: Nodes that use this custom interface items: type: string exchangeLinkName: type: boolean + nodeSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: map + x-kubernetes-list-map-keys: + - key + matchLabels: + additionalProperties: + type: string + type: object + type: object excludeNodes: type: array items: type: string autoCreateVlanSubinterfaces: type: boolean + description: Automatically create VLAN subinterfaces + preserveVlanInterfaces: + type: boolean + description: Enable automatic detection and preservation of VLAN interfaces + vlanInterfaces: + type: array + items: + type: string + pattern: '^[a-zA-Z0-9_-]+\.[0-9]{1,4}$' + description: Optional explicit list of VLAN interface names to preserve (e.g., eth0.10, bond0.20) required: - defaultInterface status: @@ -2926,37 +3375,49 @@ spec: properties: ready: type: boolean + description: Whether the provider network is ready readyNodes: type: array + description: Nodes that are ready items: type: string notReadyNodes: type: array + description: Nodes that are not ready items: type: string vlans: type: array + description: VLANs in use by this provider network items: type: string conditions: type: array + description: Conditions of nodes in the provider network items: type: object properties: node: type: string + description: Node name type: type: string + description: Type of the condition status: type: string + description: Status of the condition reason: type: string + description: Reason for the condition message: type: string + description: Message about the condition lastUpdateTime: type: string + description: Last update time lastTransitionTime: type: string + description: Last transition time additionalPrinterColumns: - name: DefaultInterface type: string @@ -2998,67 +3459,106 @@ spec: properties: ingressRules: type: array + description: Ingress traffic rules for the security group items: type: object properties: ipVersion: type: string + description: IP version (IPv4 or IPv6) protocol: type: string + description: Protocol (tcp, udp, icmp, or all) priority: type: integer + description: Rule priority (1-200) + min: 1 + max: 200 remoteType: type: string + description: Type of remote (address, cidr, or securityGroup) remoteAddress: type: string + description: Remote address or CIDR remoteSecurityGroup: type: string + description: Remote security group name portRangeMin: type: integer + description: Start of port range (1-65535) + min: 1 + max: 65535 portRangeMax: type: integer + description: End of port range (1-65535) + min: 1 + max: 65535 policy: type: string + description: Policy action (allow or deny) egressRules: type: array + description: Egress traffic rules for the security group items: type: object properties: ipVersion: type: string + description: IP version (IPv4 or IPv6) protocol: type: string + description: Protocol (tcp, udp, icmp, or all) priority: type: integer + description: Rule priority (1-200) + min: 1 + max: 200 remoteType: type: string + description: Type of remote (address, cidr, or securityGroup) remoteAddress: type: string + description: Remote address or CIDR remoteSecurityGroup: type: string + description: Remote security group name portRangeMin: type: integer + description: Start of port range (1-65535) + min: 1 + max: 65535 portRangeMax: type: integer + description: End of port range (1-65535) + min: 1 + max: 65535 policy: type: string + description: Policy action (allow or deny) allowSameGroupTraffic: type: boolean + description: Allow traffic between pods in the same security group status: type: object properties: portGroup: type: string + description: OVN port group name allowSameGroupTraffic: type: boolean + description: Current allow same group traffic setting ingressMd5: type: string + description: MD5 hash of ingress rules egressMd5: type: string + description: MD5 hash of egress rules ingressLastSyncSuccess: type: boolean + description: Last ingress sync success status egressLastSyncSuccess: type: boolean + description: Last egress sync success status subresources: status: {} conversion: @@ -3100,74 +3600,103 @@ spec: properties: shared: type: boolean + description: Whether the QoS policy is shared bindingType: type: string + description: Binding type of the QoS policy bandwidthLimitRules: type: array + description: Active bandwidth limit rules items: type: object properties: name: type: string + description: Rule name interface: type: string + description: Interface name rateMax: type: string + description: Maximum rate (e.g., 100Mbps) burstMax: type: string + description: Maximum burst rate priority: type: integer + description: Rule priority direction: type: string + description: Traffic direction (ingress/egress) matchType: type: string + description: Match type matchValue: type: string + description: Match value conditions: type: array + description: Conditions of the QoS policy items: type: object properties: type: type: string + description: Type of the condition status: type: string + description: Status of the condition (True, False, Unknown) reason: type: string + description: Reason for the condition's last transition message: type: string + description: Human-readable message indicating details about the condition lastUpdateTime: type: string + description: Last time this condition was updated lastTransitionTime: type: string + description: Last time the condition transitioned from one status to another spec: type: object properties: shared: type: boolean + description: Whether the QoS policy is shared across multiple pods bindingType: type: string + description: Binding type (e.g., pod, namespace) bandwidthLimitRules: type: array + description: Bandwidth limit rules to apply items: type: object properties: name: type: string + description: Rule name interface: type: string + description: Network interface to apply the rule rateMax: type: string + description: Maximum rate (e.g., 100Mbps, 1Gbps) burstMax: type: string + description: Maximum burst rate priority: type: integer + description: Rule priority for ordering direction: type: string + description: Traffic direction (ingress or egress) matchType: type: string + description: Type of traffic matching (e.g., pod, namespace) matchValue: type: string + description: Value to match for the rule required: - name x-kubernetes-list-map-keys: diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/monitor-deploy.yaml b/packages/system/kubeovn/charts/kube-ovn/templates/monitor-deploy.yaml index dc4eac22..2d498098 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/monitor-deploy.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/monitor-deploy.yaml @@ -81,7 +81,7 @@ spec: env: - name: ENABLE_SSL value: "{{ .Values.networking.ENABLE_SSL }}" - - name: KUBE_NODE_NAME + - name: NODE_NAME valueFrom: fieldRef: fieldPath: spec.nodeName @@ -110,6 +110,7 @@ spec: limits: cpu: {{ index .Values "kube-ovn-monitor" "limits" "cpu" }} memory: {{ index .Values "kube-ovn-monitor" "limits" "memory" }} + ephemeral-storage: {{ index .Values "kube-ovn-monitor" "limits" "ephemeral-storage" }} volumeMounts: - mountPath: /var/run/ovn name: host-run-ovn diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/ovn-CR.yaml b/packages/system/kubeovn/charts/kube-ovn/templates/ovn-CR.yaml index e89d8d46..1caaecdf 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/ovn-CR.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/ovn-CR.yaml @@ -48,10 +48,18 @@ rules: - switch-lb-rules/status - vpc-dnses - vpc-dnses/status + - dnsnameresolvers + - dnsnameresolvers/status - qos-policies - qos-policies/status verbs: - - "*" + - create + - get + - list + - update + - patch + - watch + - delete - apiGroups: - "" resources: @@ -84,6 +92,8 @@ rules: - network-attachment-definitions verbs: - get + - list + - watch - apiGroups: - "" - networking.k8s.io @@ -166,7 +176,11 @@ rules: resources: - leases verbs: - - "*" + - create + - update + - patch + - get + - watch - apiGroups: - "kubevirt.io" resources: @@ -181,6 +195,7 @@ rules: resources: - adminnetworkpolicies - baselineadminnetworkpolicies + - clusternetworkpolicies verbs: - get - list @@ -276,7 +291,6 @@ rules: verbs: - get - list - --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole @@ -355,12 +369,23 @@ rules: - "list" - "watch" - "delete" - - apiGroups: - - "" - resources: - - "secrets" - verbs: - - "get" +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: secret-reader-ovn-ipsec + namespace: {{ .Values.namespace }} +rules: +- apiGroups: + - "" + resources: + - "secrets" + resourceNames: + - "ovn-ipsec-ca" + verbs: + - "get" + - "list" + - "watch" --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/ovn-CRB.yaml b/packages/system/kubeovn/charts/kube-ovn/templates/ovn-CRB.yaml index 7cc43d84..638093ba 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/ovn-CRB.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/ovn-CRB.yaml @@ -67,6 +67,20 @@ subjects: namespace: {{ .Values.namespace }} --- apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: kube-ovn-cni-secret-reader + namespace: {{ .Values.namespace }} +subjects: +- kind: ServiceAccount + name: kube-ovn-cni + namespace: {{ .Values.namespace }} +roleRef: + kind: Role + name: secret-reader-ovn-ipsec + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: kube-ovn-app diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/ovn-dpdk-ds.yaml b/packages/system/kubeovn/charts/kube-ovn/templates/ovn-dpdk-ds.yaml index 330c9b6f..5e18230e 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/ovn-dpdk-ds.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/ovn-dpdk-ds.yaml @@ -54,7 +54,7 @@ spec: value: "{{- .Values.networking.TUNNEL_TYPE }}" - name: DPDK_TUNNEL_IFACE value: "{{- .Values.networking.DPDK_TUNNEL_IFACE }}" - - name: KUBE_NODE_NAME + - name: NODE_NAME valueFrom: fieldRef: fieldPath: spec.nodeName 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 d53c06c2..8be8eba6 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/ovncni-ds.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/ovncni-ds.yaml @@ -98,9 +98,13 @@ spec: - --mirror-iface={{- .Values.debug.MIRROR_IFACE }} - --node-switch={{ .Values.networking.NODE_SUBNET }} - --encap-checksum=true - - --service-cluster-ip-range={{ .Values.ipv4.SVC_CIDR }} - {{- if .Values.global.logVerbosity }} - - --v={{ .Values.global.logVerbosity }} + - --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 }} {{- if eq .Values.networking.NETWORK_TYPE "vlan" }} - --iface= @@ -121,9 +125,7 @@ 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 }} + - --non-primary-cni-mode={{- .Values.cni_conf.NON_PRIMARY_CNI }} securityContext: runAsUser: 0 privileged: false @@ -142,7 +144,7 @@ spec: valueFrom: fieldRef: fieldPath: status.podIP - - name: KUBE_NODE_NAME + - name: NODE_NAME valueFrom: fieldRef: fieldPath: spec.nodeName @@ -226,6 +228,7 @@ spec: limits: cpu: {{ index .Values "kube-ovn-cni" "limits" "cpu" }} memory: {{ index .Values "kube-ovn-cni" "limits" "memory" }} + ephemeral-storage: {{ index .Values "kube-ovn-cni" "limits" "ephemeral-storage" }} nodeSelector: kubernetes.io/os: "linux" volumes: diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/ovsovn-ds.yaml b/packages/system/kubeovn/charts/kube-ovn/templates/ovsovn-ds.yaml index 7146ec71..b15fd24e 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/ovsovn-ds.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/ovsovn-ds.yaml @@ -115,7 +115,7 @@ spec: value: "{{- .Values.func.HW_OFFLOAD }}" - name: TUNNEL_TYPE value: "{{- .Values.networking.TUNNEL_TYPE }}" - - name: KUBE_NODE_NAME + - name: NODE_NAME valueFrom: fieldRef: fieldPath: spec.nodeName @@ -173,6 +173,7 @@ spec: limits: cpu: {{ index .Values "ovs-ovn" "limits" "cpu" }} memory: {{ index .Values "ovs-ovn" "limits" "memory" }} + ephemeral-storage: {{ index .Values "ovs-ovn" "limits" "ephemeral-storage" }} nodeSelector: kubernetes.io/os: "linux" volumes: diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/pinger-ds.yaml b/packages/system/kubeovn/charts/kube-ovn/templates/pinger-ds.yaml index fbc82171..e52cf45d 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/pinger-ds.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/pinger-ds.yaml @@ -73,7 +73,6 @@ spec: {{- else if eq .Values.networking.NET_STACK "ipv6" -}} {{ .Values.ipv6.PINGER_EXTERNAL_DOMAIN }} {{- end }} - - --ds-namespace={{ .Values.namespace }} - --logtostderr=false - --alsologtostderr=true - --log_file=/var/log/kube-ovn/kube-ovn-pinger.log @@ -102,6 +101,10 @@ spec: valueFrom: fieldRef: fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace - name: NODE_NAME valueFrom: fieldRef: @@ -133,6 +136,7 @@ spec: limits: cpu: {{ index .Values "kube-ovn-pinger" "limits" "cpu" }} memory: {{ index .Values "kube-ovn-pinger" "limits" "memory" }} + ephemeral-storage: {{ index .Values "kube-ovn-pinger" "limits" "ephemeral-storage" }} livenessProbe: httpGet: path: /metrics diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/post-delete-hook.yaml b/packages/system/kubeovn/charts/kube-ovn/templates/post-delete-hook.yaml index 682b5a96..1272257f 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/post-delete-hook.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/post-delete-hook.yaml @@ -120,6 +120,14 @@ spec: - sh - -c - /kube-ovn/remove-finalizer.sh 2>&1 | tee -a /var/log/kube-ovn/remove-finalizer.log + resources: + requests: + cpu: 100m + memory: 200Mi + limits: + cpu: 1 + memory: 500Mi + ephemeral-storage: 1Gi volumeMounts: - mountPath: /var/log/kube-ovn name: kube-ovn-log diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/upgrade-ovs-ovn.yaml b/packages/system/kubeovn/charts/kube-ovn/templates/upgrade-ovs-ovn.yaml index ab646e03..b85ce8fa 100644 --- a/packages/system/kubeovn/charts/kube-ovn/templates/upgrade-ovs-ovn.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/upgrade-ovs-ovn.yaml @@ -31,6 +31,8 @@ rules: - daemonsets verbs: - list + - get + - watch - apiGroups: - apps resources: diff --git a/packages/system/kubeovn/charts/kube-ovn/templates/vpc-nat-config.yaml b/packages/system/kubeovn/charts/kube-ovn/templates/vpc-nat-config.yaml index ae9a0ce8..92e2fd94 100755 --- a/packages/system/kubeovn/charts/kube-ovn/templates/vpc-nat-config.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/templates/vpc-nat-config.yaml @@ -7,7 +7,7 @@ metadata: kubernetes.io/description: | kube-ovn vpc-nat common config data: - image: {{ .Values.global.registry.address }}/{{ .Values.global.images.kubeovn.vpcRepository }}:{{ .Values.global.images.kubeovn.tag }} + image: {{ .Values.global.registry.address }}/{{ .Values.global.images.natgateway.repository }}:{{ or .Values.global.images.natgateway.tag .Values.global.images.kubeovn.tag }} --- kind: ConfigMap diff --git a/packages/system/kubeovn/charts/kube-ovn/values.yaml b/packages/system/kubeovn/charts/kube-ovn/values.yaml index 430ea428..3ffb7750 100644 --- a/packages/system/kubeovn/charts/kube-ovn/values.yaml +++ b/packages/system/kubeovn/charts/kube-ovn/values.yaml @@ -8,10 +8,11 @@ global: images: kubeovn: repository: kube-ovn - vpcRepository: vpc-nat-gateway - tag: v1.14.25 - support_arm: true - thirdparty: true + tag: v1.15.3 + natgateway: + repository: vpc-nat-gateway + # Falls back to the same tag as kubeovn if empty + tag: v1.15.3 image: pullPolicy: IfNotPresent @@ -46,6 +47,8 @@ networking: ENABLE_METRICS: true # comma-separated string of nodelocal DNS ip addresses NODE_LOCAL_DNS_IP: "" + # comma-separated list of destination IP CIDRs that should skip conntrack processing + SKIP_CONNTRACK_DST_CIDRS: "" PROBE_INTERVAL: 180000 OVN_NORTHD_PROBE_INTERVAL: 5000 OVN_LEADER_PROBE_INTERVAL: 5 @@ -57,6 +60,7 @@ networking: func: ENABLE_LB: true ENABLE_NP: true + NP_ENFORCEMENT: standard ENABLE_EXTERNAL_VPC: false HW_OFFLOAD: false ENABLE_LB_SVC: false @@ -73,6 +77,7 @@ func: ENABLE_NAT_GW: true ENABLE_OVN_IPSEC: false ENABLE_ANP: false + ENABLE_DNS_NAME_RESOLVER: false SET_VXLAN_TX_OFF: false OVSDB_CON_TIMEOUT: 3 OVSDB_INACTIVITY_TIMEOUT: 10 @@ -80,6 +85,10 @@ func: ENABLE_OVN_LB_PREFER_LOCAL: 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: "kube-ovn.io." @@ -116,6 +125,7 @@ cni_conf: CNI_CONF_FILE: "/kube-ovn/01-kube-ovn.conflist" LOCAL_BIN_DIR: "/usr/local/bin" MOUNT_LOCAL_BIN_DIR: false + NON_PRIMARY_CNI: false kubelet_conf: KUBELET_DIR: "/var/lib/kubelet" @@ -135,7 +145,7 @@ fullnameOverride: "" HYBRID_DPDK: false HUGEPAGE_SIZE_TYPE: hugepages-2Mi # Default HUGEPAGES: 1Gi -DPDK_IMAGE_TAG: "v1.14.0-dpdk" +DPDK_IMAGE_TAG: "v1.15.0-dpdk" DPDK_CPU: "1000m" # Default CPU configuration DPDK_MEMORY: "2Gi" # Default Memory configuration @@ -146,6 +156,7 @@ ovn-central: limits: cpu: "3" memory: "4Gi" + ephemeral-storage: 1Gi ovs-ovn: requests: cpu: "200m" @@ -153,6 +164,7 @@ ovs-ovn: limits: cpu: "2" memory: "1000Mi" + ephemeral-storage: 1Gi kube-ovn-controller: requests: cpu: "200m" @@ -160,6 +172,7 @@ kube-ovn-controller: limits: cpu: "1000m" memory: "1Gi" + ephemeral-storage: 1Gi kube-ovn-cni: requests: cpu: "100m" @@ -167,6 +180,7 @@ kube-ovn-cni: limits: cpu: "1000m" memory: "1Gi" + ephemeral-storage: 1Gi kube-ovn-pinger: requests: cpu: "100m" @@ -174,6 +188,7 @@ kube-ovn-pinger: limits: cpu: "200m" memory: "400Mi" + ephemeral-storage: 1Gi kube-ovn-monitor: requests: cpu: "200m" @@ -181,3 +196,4 @@ kube-ovn-monitor: limits: cpu: "200m" memory: "200Mi" + ephemeral-storage: 1Gi diff --git a/packages/system/kubeovn/values.yaml b/packages/system/kubeovn/values.yaml index 700960ee..6911f8aa 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.25@sha256:d0b29daaf36e81cac0f9fb15d0ea6b1b49f1abba81a14c73b88a2e60ffcc5978 + tag: v1.15.3@sha256:fa53d5f254f640cb626329ad35d9e7aad647dd8e1e645e68f3f13c3659472a30 From ad24693ca3603e61d88b17093ea2a4893a577891 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Wed, 11 Feb 2026 07:06:31 +0100 Subject: [PATCH 217/889] [mongodb] Fix pre-commint check Signed-off-by: Andrei Kvapil --- packages/system/mongodb-rd/cozyrds/mongodb.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/system/mongodb-rd/cozyrds/mongodb.yaml b/packages/system/mongodb-rd/cozyrds/mongodb.yaml index 54909131..a79398c2 100644 --- a/packages/system/mongodb-rd/cozyrds/mongodb.yaml +++ b/packages/system/mongodb-rd/cozyrds/mongodb.yaml @@ -24,7 +24,7 @@ spec: description: Managed MongoDB service tags: - database - icon: PHN2ZyB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgdmlld0JveD0iMCAwIDE0NCAxNDQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNDQiIGhlaWdodD0iMTQ0IiByeD0iMjQiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcl9tb25nb2RiKSIvPgo8cGF0aCBkPSJNNzIgMjRDNzIgMjQgNzIgMjQgNzIgMjRDNzIgMjQgNTggNDAgNTggNjJDNTggODQgNzIgMTIwIDcyIDEyMEM3MiAxMjAgODYgODQgODYgNjJDODYgNDAgNzIgMjQgNzIgMjRaIiBmaWxsPSIjMDBFRDY0Ii8+CjxwYXRoIGQ9Ik03MiAxMjBDNzIgMTIwIDg2IDg0IDg2IDYyQzg2IDQwIDcyIDI0IDcyIDI0IiBzdHJva2U9IiMwMDY4NEEiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxwYXRoIGQ9Ik03MiAyNEM3MiAyNCA1OCA0MCA1OCA2MkM1OCA4NCA3MiAxMjAgNzIgMTIwIiBzdHJva2U9IiMwMDFFMkIiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxyZWN0IHg9IjY5IiB5PSIxMDgiIHdpZHRoPSI2IiBoZWlnaHQ9IjE2IiByeD0iMiIgZmlsbD0iIzAwNjg0QSIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyX21vbmdvZGIiIHgxPSIxNDAiIHkxPSIxMzAuNSIgeDI9IjQiIHkyPSI5LjQ5OTk5IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wIHN0b3AtY29sb3I9IiMwMDFFMkIiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMDIzNDMwIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg== + icon: PHN2ZyB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgdmlld0JveD0iMCAwIDE0NCAxNDQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNDQiIGhlaWdodD0iMTQ0IiByeD0iMjQiIGZpbGw9InVybCgjcGFpbnQwX3JhZGlhbF8zMzBfNDg5ODEpIi8+CjxwYXRoIGQ9Ik05NS4xMzg3IDYxLjQwMjhDODkuNjI3NiAzNy4yMjc1IDc2LjYwMzIgMjkuMjc4NSA3NS4yMDA3IDI2LjI0MTZDNzMuNjY3IDI0LjA5OSA3Mi4xMTI5IDIwLjI4NzkgNzIuMTEyOSAyMC4yODc5QzcyLjA4NzMgMjAuMjIzNiA3Mi4wNDY0IDIwLjExMDEgNzEuOTk4NyAyMEM3MS44NDAyIDIyLjE0MjYgNzEuNzU4NCAyMi45NjkyIDY5LjcyMDMgMjUuMTMwNUM2Ni41NjQzIDI3LjU4MzEgNTAuMzcyIDQxLjA4NzYgNDkuMDU0NyA2OC41NTRDNDcuODI2MSA5NC4xNzA4IDY3LjY3MiAxMDkuNDM1IDcwLjM1NiAxMTEuMzgxTDcwLjY2MSAxMTEuNTk2VjExMS41NzhDNzAuNjc4IDExMS43MDcgNzEuNTEzIDExNy42NzUgNzIuMDk5MyAxMjRINzQuMjAyMUM3NC42OTU1IDExOS41MjkgNzUuNDM1MSAxMTUuMDg5IDc2LjQxNzUgMTEwLjY5OUw3Ni41ODc5IDExMC41ODlDNzcuNzg4NCAxMDkuNzMzIDc4LjkzMzEgMTA4LjgwMiA4MC4wMTQ4IDEwNy44MDJMODAuMTM3NSAxMDcuNjkyQzg1Ljg0MjggMTAyLjQ1MyA5Ni4wOTk4IDkwLjMzNjEgOTUuOTk5MyA3MS4wMTY4Qzk1Ljk3OCA2Ny43OTQxIDk1LjY5MDIgNjQuNTc4NiA5NS4xMzg3IDYxLjQwMjhaTTcxLjg3NiA5Ni45MTgxQzcxLjg3NiA5Ni45MTgxIDcxLjg3NiA2MC45ODk2IDczLjA2ODkgNjAuOTk2M0M3My45OTkzIDYwLjk5NjMgNzUuMjA0MSAxMDcuMzQgNzUuMjA0MSAxMDcuMzRDNzMuNTQ3NyAxMDcuMTQyIDcxLjg3NiA5OS43MTI4IDcxLjg3NiA5Ni45MTgxWiIgZmlsbD0id2hpdGUiLz4KPGRlZnM+CjxyYWRpYWxHcmFkaWVudCBpZD0icGFpbnQwX3JhZGlhbF8zMzBfNDg5ODEiIGN4PSIwIiBjeT0iMCIgcj0iMSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIGdyYWRpZW50VHJhbnNmb3JtPSJ0cmFuc2xhdGUoMS4zMjI5OGUtMDUgLTcuNTAwMDEpIHJvdGF0ZSg0NC43MTc4KSBzY2FsZSgyMTUuMzE3IDMxMi40NTUpIj4KPHN0b3Agc3RvcC1jb2xvcj0iIzI1RkY4MCIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMxM0FBNTIiLz4KPC9yYWRpYWxHcmFkaWVudD4KPC9kZWZzPgo8L3N2Zz4K keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "replicas"], ["spec", "resources"], ["spec", "resourcesPreset"], ["spec", "size"], ["spec", "storageClass"], ["spec", "external"], ["spec", "version"], ["spec", "sharding"], ["spec", "shardingConfig"], ["spec", "shardingConfig", "configServers"], ["spec", "shardingConfig", "configServerSize"], ["spec", "shardingConfig", "mongos"], ["spec", "shardingConfig", "shards"], ["spec", "users"], ["spec", "databases"], ["spec", "backup"], ["spec", "backup", "enabled"], ["spec", "backup", "schedule"], ["spec", "backup", "retentionPolicy"], ["spec", "backup", "destinationPath"], ["spec", "backup", "endpointURL"], ["spec", "backup", "s3AccessKey"], ["spec", "backup", "s3SecretKey"], ["spec", "bootstrap"], ["spec", "bootstrap", "enabled"], ["spec", "bootstrap", "recoveryTime"], ["spec", "bootstrap", "backupName"]] secrets: exclude: [] From da359d558a7c752db9b56e4eff83b24bd5012bbd Mon Sep 17 00:00:00 2001 From: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com> Date: Wed, 11 Feb 2026 06:13:35 +0000 Subject: [PATCH 218/889] Prepare release v1.0.0-beta.3 Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com> --- packages/apps/kubernetes/images/cluster-autoscaler.tag | 2 +- packages/apps/kubernetes/images/kubevirt-csi-driver.tag | 2 +- packages/core/installer/values.yaml | 4 ++-- packages/core/platform/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/backup-controller/values.yaml | 2 +- packages/system/backupstrategy-controller/values.yaml | 2 +- packages/system/bucket/images/s3manager.tag | 2 +- packages/system/cozystack-api/values.yaml | 2 +- packages/system/cozystack-controller/values.yaml | 2 +- packages/system/dashboard/templates/configmap.yaml | 2 +- packages/system/dashboard/values.yaml | 6 +++--- .../system/grafana-operator/images/grafana-dashboards.tag | 2 +- packages/system/kamaji/values.yaml | 4 ++-- packages/system/kubeovn-plunger/values.yaml | 2 +- packages/system/kubeovn-webhook/values.yaml | 2 +- packages/system/kubevirt-csi-node/values.yaml | 2 +- packages/system/lineage-controller-webhook/values.yaml | 2 +- packages/system/linstor/values.yaml | 4 ++-- packages/system/objectstorage-controller/values.yaml | 2 +- packages/system/seaweedfs/values.yaml | 2 +- 23 files changed, 28 insertions(+), 28 deletions(-) diff --git a/packages/apps/kubernetes/images/cluster-autoscaler.tag b/packages/apps/kubernetes/images/cluster-autoscaler.tag index 03a5ad61..e3436c68 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:6f2b1d6b0b2bdc66f1cbb30c59393369cbf070cb8f5fec748f176952273483cc +ghcr.io/cozystack/cozystack/cluster-autoscaler:0.0.0@sha256:598331326f0c2aac420187f0cc3a49fedcb22ed5de4afe50c6ccf8e05d9fa537 diff --git a/packages/apps/kubernetes/images/kubevirt-csi-driver.tag b/packages/apps/kubernetes/images/kubevirt-csi-driver.tag index 02f8f103..421a4084 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:726d9287e8caaea94eaf24c4f44734e3fbf4f8aa032b66b81848ebf95297cffe +ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:8f1ab4c3b2bed3a0adc40fcc823b040fa04b4722bec7735c030e79a3a2fd6c85 diff --git a/packages/core/installer/values.yaml b/packages/core/installer/values.yaml index 2325647e..1bfa2040 100644 --- a/packages/core/installer/values.yaml +++ b/packages/core/installer/values.yaml @@ -1,4 +1,4 @@ cozystackOperator: - image: ghcr.io/cozystack/cozystack/cozystack-operator:v1.0.0-beta.2@sha256:aaea9d8430187f208e6464cb6f102dcbbcdb0584b6a7a8a690ad91fc1f5d45e6 + image: ghcr.io/cozystack/cozystack/cozystack-operator:v1.0.0-beta.3@sha256:3cacecfc318e8314300bba8538b475fb8d50b2bf3106533faa8d942e4ed14448 platformSourceUrl: 'oci://ghcr.io/cozystack/cozystack/cozystack-packages' - platformSourceRef: 'digest=sha256:f59e562f2c91446117773ad457251d567706ea2964251a2b0acc65060fd1f3bc' + platformSourceRef: 'digest=sha256:e5ddcf5a02d17b17fc7ffa8c87ddeaa25a01d928a63fb297f6b25b669bb6b7c7' diff --git a/packages/core/platform/values.yaml b/packages/core/platform/values.yaml index f9ce672d..62bb9c69 100644 --- a/packages/core/platform/values.yaml +++ b/packages/core/platform/values.yaml @@ -5,7 +5,7 @@ sourceRef: path: / migrations: enabled: false - image: ghcr.io/cozystack/cozystack/platform-migrations:latest + image: ghcr.io/cozystack/cozystack/platform-migrations:v1.0.0-beta.3@sha256:1ab28def39c9cb0233a03708ce00c9694edc9728789eef59abdc1d72bbe9b10a targetVersion: 27 # Bundle deployment configuration bundles: diff --git a/packages/core/testing/values.yaml b/packages/core/testing/values.yaml index 1e77f663..26ac2e55 100644 --- a/packages/core/testing/values.yaml +++ b/packages/core/testing/values.yaml @@ -1,2 +1,2 @@ e2e: - image: ghcr.io/cozystack/cozystack/e2e-sandbox:v1.0.0-beta.2@sha256:eac71ef0de3450fce96255629e77903630c63ade62b81e7055f1a689f92ee153 + image: ghcr.io/cozystack/cozystack/e2e-sandbox:v1.0.0-beta.3@sha256:eac71ef0de3450fce96255629e77903630c63ade62b81e7055f1a689f92ee153 diff --git a/packages/extra/bootbox/images/matchbox.tag b/packages/extra/bootbox/images/matchbox.tag index 05ee31c2..133eda2a 100644 --- a/packages/extra/bootbox/images/matchbox.tag +++ b/packages/extra/bootbox/images/matchbox.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/matchbox:v1.0.0-beta.2@sha256:3d8c93822ca7b344b718a9ab0fb196d8fab92fcf852907975b39528d129811f0 +ghcr.io/cozystack/cozystack/matchbox:v1.0.0-beta.3@sha256:185010911694809fe15491efe3c7994e0ce46a4f92ef1f7edbbd0b318861e8d4 diff --git a/packages/extra/seaweedfs/images/objectstorage-sidecar.tag b/packages/extra/seaweedfs/images/objectstorage-sidecar.tag index 1db178ca..71eb27b8 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:v1.0.0-beta.2@sha256:ea035d4eff4a05d9d83f487d00438504cc27a95c0ee78c534d6eed53f4b2f04e +ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.0.0-beta.3@sha256:f845a2c3ec0f79a8873e9f3e65ee625ec3574c5e33e174a7bf60b15d1db49979 diff --git a/packages/system/backup-controller/values.yaml b/packages/system/backup-controller/values.yaml index 84038de8..3e91ea6e 100644 --- a/packages/system/backup-controller/values.yaml +++ b/packages/system/backup-controller/values.yaml @@ -1,5 +1,5 @@ backupController: - image: "ghcr.io/cozystack/cozystack/backup-controller:v1.0.0-beta.2@sha256:556cc41e4ec24c173e01c1679cf96915c7ca8c0b532b7945d461bf3797c8afbc" + image: "ghcr.io/cozystack/cozystack/backup-controller:v1.0.0-beta.3@sha256:7a8d0ea7b380196fac418e0697b4218af15025eac8128dfedfd12eb933bd258d" replicas: 2 debug: false metrics: diff --git a/packages/system/backupstrategy-controller/values.yaml b/packages/system/backupstrategy-controller/values.yaml index d4c9f253..94cc6bed 100644 --- a/packages/system/backupstrategy-controller/values.yaml +++ b/packages/system/backupstrategy-controller/values.yaml @@ -1,5 +1,5 @@ backupStrategyController: - image: "ghcr.io/cozystack/cozystack/backupstrategy-controller:v1.0.0-beta.2@sha256:5cbc85679790aa14fb55568439c669a704e29f02236df179abbb3a25c56a2aa7" + image: "ghcr.io/cozystack/cozystack/backupstrategy-controller:v1.0.0-beta.3@sha256:ae8fb6c7bf274a3812a6a9f3f87317fa4575250f7ee75e118d20d5ab0797577b" replicas: 2 debug: false metrics: diff --git a/packages/system/bucket/images/s3manager.tag b/packages/system/bucket/images/s3manager.tag index 8ef294d1..261f2041 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:3013e13ba967070948653cc5b913a920dea93a24370b10731fafcfd8fb6a21b0 +ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:291427de7db54a1d19dc9c2c807bdcc664a14caa9538786f31317e8c01a4a008 diff --git a/packages/system/cozystack-api/values.yaml b/packages/system/cozystack-api/values.yaml index b5abfaec..d9631b94 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:v1.0.0-beta.2@sha256:2815ee65d13e3bde376ca3975a1106e1e32b8b911f2004d85a824a9fc30eebbd + image: ghcr.io/cozystack/cozystack/cozystack-api:v1.0.0-beta.3@sha256:a263702859c36f85d097c300c1be462aaa45a3955d69e9ae72a37cabf6dadaa9 localK8sAPIEndpoint: enabled: true replicas: 2 diff --git a/packages/system/cozystack-controller/values.yaml b/packages/system/cozystack-controller/values.yaml index 7adbea35..2a1f9df4 100644 --- a/packages/system/cozystack-controller/values.yaml +++ b/packages/system/cozystack-controller/values.yaml @@ -1,5 +1,5 @@ cozystackController: - image: ghcr.io/cozystack/cozystack/cozystack-controller:v1.0.0-beta.2@sha256:3149c8341de741bce35de27591f72be82f22634ebc5dba8889b2bbae7892579a + image: ghcr.io/cozystack/cozystack/cozystack-controller:v1.0.0-beta.3@sha256:00b0ba15fef7c3ce8c0801ecb11b1953665a511990e18f51c2ca3ca40127c7aa debug: false disableTelemetry: false cozystackAPIKind: "DaemonSet" diff --git a/packages/system/dashboard/templates/configmap.yaml b/packages/system/dashboard/templates/configmap.yaml index c3ee0595..03cd572b 100644 --- a/packages/system/dashboard/templates/configmap.yaml +++ b/packages/system/dashboard/templates/configmap.yaml @@ -1,6 +1,6 @@ {{- $brandingConfig := .Values._cluster.branding | default dict }} -{{- $tenantText := "v1.0.0-beta.2" }} +{{- $tenantText := "v1.0.0-beta.3" }} {{- $footerText := "Cozystack" }} {{- $titleText := "Cozystack Dashboard" }} {{- $logoText := "" }} diff --git a/packages/system/dashboard/values.yaml b/packages/system/dashboard/values.yaml index b1eae64c..95d7bdf8 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:v1.0.0-beta.2@sha256:ed695cd89086df9bae6e3519bbcaeb2f26a36109f000dec4bc917bedcbd17d69 + image: ghcr.io/cozystack/cozystack/openapi-ui:v1.0.0-beta.3@sha256:adbd07c7bde083fbf3a2fb2625ec4adbe6718a0f6f643b2505cc0029c2c0f724 openapiUIK8sBff: - image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v1.0.0-beta.2@sha256:1f7827a1978bd9c81ac924dd0e78f6a3ce834a9a64af55047e220812bc15a944 + image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v1.0.0-beta.3@sha256:0ed112d44973dcff64b65f7f1438a35df98c7fdee4590343a790668f728d9024 tokenProxy: - image: ghcr.io/cozystack/cozystack/token-proxy:v1.0.0-beta.2@sha256:063d34c25333e110dd7fad999279d4a5497e918723f1341991af48632b96ada1 + image: ghcr.io/cozystack/cozystack/token-proxy:v1.0.0-beta.3@sha256:2e280991e07853ea48f97b0a42946afffa10d03d6a83d41099ed83e6ffc94fdc diff --git a/packages/system/grafana-operator/images/grafana-dashboards.tag b/packages/system/grafana-operator/images/grafana-dashboards.tag index dd4b8a60..726d78e9 100644 --- a/packages/system/grafana-operator/images/grafana-dashboards.tag +++ b/packages/system/grafana-operator/images/grafana-dashboards.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/grafana-dashboards:v1.0.0-beta.2@sha256:e866b5b3874b9d390b341183d2ee070e1387440c14cfe51af831695def6dc2ec +ghcr.io/cozystack/cozystack/grafana-dashboards:v1.0.0-beta.3@sha256:e866b5b3874b9d390b341183d2ee070e1387440c14cfe51af831695def6dc2ec diff --git a/packages/system/kamaji/values.yaml b/packages/system/kamaji/values.yaml index f8f169f9..bd7dfe63 100644 --- a/packages/system/kamaji/values.yaml +++ b/packages/system/kamaji/values.yaml @@ -3,7 +3,7 @@ kamaji: deploy: false image: pullPolicy: IfNotPresent - tag: v1.0.0-beta.2@sha256:d5f2fa2972ba33cd2ccb855256e4bda4734d7e250638811b77f2e0dc72ad6b19 + tag: v1.0.0-beta.3@sha256:fe9b6bb548edfc26be8aaac65801d598a4e2f9884ddf748083b9e509fa00259e 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:v1.0.0-beta.2@sha256:d5f2fa2972ba33cd2ccb855256e4bda4734d7e250638811b77f2e0dc72ad6b19 + - --migrate-image=ghcr.io/cozystack/cozystack/kamaji:v1.0.0-beta.3@sha256:fe9b6bb548edfc26be8aaac65801d598a4e2f9884ddf748083b9e509fa00259e diff --git a/packages/system/kubeovn-plunger/values.yaml b/packages/system/kubeovn-plunger/values.yaml index ed7c84dd..b86cc2dc 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:v1.0.0-beta.2@sha256:387fe9eca078edfb631511a091da9f2a7fcdc214867b4e2c269b55122a0f4ce7 +image: ghcr.io/cozystack/cozystack/kubeovn-plunger:v1.0.0-beta.3@sha256:c89397d6a74e6de38830513a323ade3fa99a42946b40ba596136fc08930b2348 ovnCentralName: ovn-central diff --git a/packages/system/kubeovn-webhook/values.yaml b/packages/system/kubeovn-webhook/values.yaml index 244879e7..65898067 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:v1.0.0-beta.2@sha256:e18f9fd679e38f65362a8d0042f25468272f6d081136ad47027168d8e7e07a4a +image: ghcr.io/cozystack/cozystack/kubeovn-webhook:v1.0.0-beta.3@sha256:e18f9fd679e38f65362a8d0042f25468272f6d081136ad47027168d8e7e07a4a diff --git a/packages/system/kubevirt-csi-node/values.yaml b/packages/system/kubevirt-csi-node/values.yaml index 8457b6d1..cf8b4fa8 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:726d9287e8caaea94eaf24c4f44734e3fbf4f8aa032b66b81848ebf95297cffe + image: ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:8f1ab4c3b2bed3a0adc40fcc823b040fa04b4722bec7735c030e79a3a2fd6c85 diff --git a/packages/system/lineage-controller-webhook/values.yaml b/packages/system/lineage-controller-webhook/values.yaml index 6c19c22b..10ac0be5 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:v1.0.0-beta.2@sha256:e2ffc29d244b9b5916ab048c338a8f284a47b0f4bd00e903dcf36df2a0299b72 + image: ghcr.io/cozystack/cozystack/lineage-controller-webhook:v1.0.0-beta.3@sha256:da85b064cdef4c8a798f32a731396a9b90741654dc599a5f4f381372834765db debug: false localK8sAPIEndpoint: enabled: true diff --git a/packages/system/linstor/values.yaml b/packages/system/linstor/values.yaml index 8c22ac6b..bec791ee 100644 --- a/packages/system/linstor/values.yaml +++ b/packages/system/linstor/values.yaml @@ -1,7 +1,7 @@ piraeusServer: image: repository: ghcr.io/cozystack/cozystack/piraeus-server - tag: 1.32.3@sha256:0e78fa31a3fe4ec2af43d1e59a9fc0f6d765780e32d473e18e1c495714051802 + tag: 1.32.3@sha256:66eadfc98cd809d2b3c4e6fd631bcd0c4b4cd72a7fb819ac4a0cab7904280546 # Talos-specific workarounds (disable for generic Linux like Ubuntu/Debian) talos: enabled: true @@ -13,4 +13,4 @@ linstor: linstorCSI: image: repository: ghcr.io/cozystack/cozystack/linstor-csi - tag: v1.10.5@sha256:68465f120cfeec3d7ccbb389dd9bdbf7df1675da3ab9ba91c3feff21a799bc36 + tag: v1.10.5@sha256:6e6cf48cb994f3918df946e02ec454ac64916678b3e60d78c136b431f1a26155 diff --git a/packages/system/objectstorage-controller/values.yaml b/packages/system/objectstorage-controller/values.yaml index aa3756ad..d5fe5bec 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:v1.0.0-beta.2@sha256:1f35e09bae32cd11c6ce2268556cac76b8da68b448208aea3c13071306087534" + image: "ghcr.io/cozystack/cozystack/objectstorage-controller:v1.0.0-beta.3@sha256:bb2b2b95cbc3d613b077a87a6c281a3ceff8ef8655d770fb2f8fd6b5f1d0c588" diff --git a/packages/system/seaweedfs/values.yaml b/packages/system/seaweedfs/values.yaml index 8db83fc0..cc5e8301 100644 --- a/packages/system/seaweedfs/values.yaml +++ b/packages/system/seaweedfs/values.yaml @@ -177,7 +177,7 @@ seaweedfs: bucketClassName: "seaweedfs" region: "" sidecar: - image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.0.0-beta.2@sha256:ea035d4eff4a05d9d83f487d00438504cc27a95c0ee78c534d6eed53f4b2f04e" + image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.0.0-beta.3@sha256:f845a2c3ec0f79a8873e9f3e65ee625ec3574c5e33e174a7bf60b15d1db49979" certificates: commonName: "SeaweedFS CA" ipAddresses: [] From 8f015efc93d95917db865e17f26215df801df052 Mon Sep 17 00:00:00 2001 From: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com> Date: Wed, 11 Feb 2026 06:24:30 +0000 Subject: [PATCH 219/889] docs: add changelog for v1.0.0-beta.3 Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com> --- docs/changelogs/v1.0.0-beta.3.md | 144 +++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 docs/changelogs/v1.0.0-beta.3.md diff --git a/docs/changelogs/v1.0.0-beta.3.md b/docs/changelogs/v1.0.0-beta.3.md new file mode 100644 index 00000000..45d654a7 --- /dev/null +++ b/docs/changelogs/v1.0.0-beta.3.md @@ -0,0 +1,144 @@ + + +> **⚠️ Beta Release Warning**: This is a pre-release version intended for testing and early adoption. Breaking changes may occur before the stable v1.0.0 release. + +## Major Features and Improvements + +### New Applications + +* **[qdrant] Add Qdrant vector database application**: Added Qdrant as a new managed application, providing a high-performance vector database for AI and machine learning workloads. Supports single replica or clustered mode, persistent storage, resource presets, API key authentication, and optional external LoadBalancer access ([**@lexfrei**](https://github.com/lexfrei) in #1987). + +### System Components + +* **[system] Add cluster-autoscaler package for Hetzner and Azure**: Added cluster-autoscaler system package with support for multiple cloud providers (Hetzner and Azure) to automatically scale management cluster nodes. Includes comprehensive documentation for Hetzner setup with Talos Linux, vSwitch configuration, and Kilo mesh networking integration ([**@kvaps**](https://github.com/kvaps) in #1964). + +* **[system] Add clustersecret-operator package**: Added clustersecret-operator system package for managing secrets across multiple namespaces in Kubernetes clusters ([**@sircthulhu**](https://github.com/sircthulhu) in #2025). + +### Networking + +* **[kilo] Update to v0.7.0 and add configurable MTU**: Updated Kilo WireGuard mesh networking to v0.7.0 from cozystack fork with pre-built images. Added configurable MTU parameter (default: auto) for WireGuard interface, allowing automatic MTU detection or manual override ([**@kvaps**](https://github.com/kvaps) in #2003). + +* **[local-ccm] Add node-lifecycle-controller component**: Added optional node-lifecycle-controller to local-ccm package that automatically deletes unreachable NotReady nodes from the cluster. Solves the "zombie" node problem when cluster autoscaler deletes cloud instances but node objects remain in Kubernetes. Supports configurable node selectors, protected labels, and HA deployment with leader election ([**@IvanHunters**](https://github.com/IvanHunters) in #1992). + +### Virtual Machines + +* **[vm] Add cpuModel field to specify CPU model without instanceType**: Added cpuModel field to VirtualMachine API, allowing users to specify CPU model directly without using instanceType, providing more granular control over VM CPU configuration ([**@sircthulhu**](https://github.com/sircthulhu) in #2007). + +* **[vm] Allow switching between instancetype and custom resources**: Implemented atomic upgrade hook that allows switching between instanceType-based and custom resource-based VM configuration, providing more flexibility in VM resource management ([**@sircthulhu**](https://github.com/sircthulhu) in #2008). + +* **[vm] Migrate to runStrategy instead of running**: Migrated VirtualMachine API from deprecated `running` field to `runStrategy` field, following KubeVirt upstream best practices ([**@sircthulhu**](https://github.com/sircthulhu) in #2004). + +### Backups + +* **[backups] Add comprehensive backup and restore functionality**: Major update to backup system including BackupClass for Velero, virtual machine backup strategies, RestoreJob resource with end-to-end restore workflows, Velero integration with polling and status tracking, and enhanced backup plans UI with simplified Plan/BackupJob API ([**@androndo**](https://github.com/androndo) in #1967, [**@lllamnyp**](https://github.com/lllamnyp) in #1968). + +* **[backups] Add kubevirt plugin to velero**: Added KubeVirt plugin to Velero for proper virtual machine backup support, enabling consistent snapshots of VM state and data ([**@lllamnyp**](https://github.com/lllamnyp) in #2017). + +* **[backups] Install backupstrategy controller by default**: Enabled backupstrategy controller by default to provide automatic backup scheduling and management for managed applications ([**@lllamnyp**](https://github.com/lllamnyp) in #2020). + +* **[backups] Better selectors for VM strategy**: Improved VM backup strategy selectors for more accurate and reliable backup targeting ([**@lllamnyp**](https://github.com/lllamnyp) in #2023). + +### Platform + +* **[kubernetes] Auto-enable Gateway API support in cert-manager**: Added automatic Gateway API support in cert-manager for tenant Kubernetes clusters, enabling automatic certificate management for Gateway API resources ([**@kvaps**](https://github.com/kvaps) in #1997). + +* **[tenant,rbac] Use shared clusterroles**: Refactored tenant RBAC to use shared ClusterRoles, improving maintainability and consistency across tenant namespaces ([**@lllamnyp**](https://github.com/lllamnyp) in #1999). + +* **[mongodb] Unify users and databases configuration**: Simplified MongoDB user and database configuration with a more unified API structure ([**@kvaps**](https://github.com/kvaps) in #1923). + +## Improvements + +* **[keycloak-configure,dashboard] Enable insecure TLS verification by default**: Made SSL certificate verification configurable with insecure mode enabled by default for easier local development and testing ([**@IvanHunters**](https://github.com/IvanHunters) in #2005). + +* **[dashboard] Add startupProbe to prevent container restarts on slow hardware**: Added startup probe to dashboard pods to prevent unnecessary container restarts on slow hardware or during high load ([**@kvaps**](https://github.com/kvaps) in #1996). + +* **[cilium] Change cilium-operator replicas to 1**: Reduced Cilium operator replicas from 2 to 1 to decrease resource consumption in smaller deployments ([**@IvanHunters**](https://github.com/IvanHunters) in #1784). + +* **[monitoring] Enable monitoring for core components**: Enhanced monitoring capabilities with better dashboards and metrics collection for core Cozystack components ([**@IvanHunters**](https://github.com/IvanHunters) in #1937). + +* **[branding] Separate values for Keycloak**: Separated Keycloak branding values for better customization capabilities ([**@nbykov0**](https://github.com/nbykov0) in #1947). + +* **[kubernetes] Use ingress-nginx nodeport service**: Changed Kubernetes managed clusters to use ingress-nginx NodePort service for improved compatibility and flexibility ([**@sircthulhu**](https://github.com/sircthulhu) in #1948). + +## Fixes + +* **[linstor] Extract piraeus-operator CRDs into separate package**: Fixed issue where Helm did not reliably install all CRDs from large crds.yaml files by creating dedicated piraeus-operator-crds package. This ensures the linstorsatellites.io CRD is properly installed, preventing satellite pod creation failures ([**@IvanHunters**](https://github.com/IvanHunters) in #1991). + +* **[platform] Fix cozystack-values secret race condition**: Fixed race condition in cozystack-values secret creation that could cause platform initialization failures ([**@lllamnyp**](https://github.com/lllamnyp) in #2024). + +* **[seaweedfs] Increase certificate duration to 10 years**: Increased SeaweedFS certificate validity from 1 year to 10 years to reduce certificate rotation overhead and prevent unexpected certificate expiration issues ([**@IvanHunters**](https://github.com/IvanHunters) in #1986). + +* **[monitoring] Remove cozystack-controller dependency**: Fixed monitoring package to remove unnecessary dependency on cozystack-controller, allowing monitoring to be installed independently ([**@IvanHunters**](https://github.com/IvanHunters) in #1990). + +* **[monitoring] Remove duplicate dashboards.list from extra/monitoring**: Fixed duplicate dashboards.list configuration in extra/monitoring package ([**@IvanHunters**](https://github.com/IvanHunters) in #2016). + +* **[mongodb] Fix pre-commit check**: Fixed pre-commit linting issues in MongoDB package ([**@kvaps**](https://github.com/kvaps) in #1753). + +* **[mongodb] Update MongoDB logo**: Updated MongoDB application logo in the dashboard to use the correct branding ([**@kvaps**](https://github.com/kvaps) in #2027). + +* **[bootbox] Auto-create bootbox-application as dependency**: Fixed bootbox package to automatically create required bootbox-application dependency ([**@kvaps**](https://github.com/kvaps) in #1974). + +* **[migrations] Add migration 25 for v1.0 upgrade cleanup**: Added migration script to handle cleanup during v1.0 upgrade path ([**@kvaps**](https://github.com/kvaps) in #1975). + +* **[build] Fix platform migrations image build**: Fixed Docker image build process for platform migrations ([**@kvaps**](https://github.com/kvaps) in #1976). + +* **[postgres-operator] Correct PromQL syntax in CNPGClusterOffline alert**: Fixed incorrect PromQL syntax in CNPGClusterOffline Prometheus alert for PostgreSQL clusters ([**@mattia-eleuteri**](https://github.com/mattia-eleuteri) in #1981). + +* **[coredns] Fix serviceaccount to match kubernetes bootstrap RBAC**: Fixed CoreDNS service account configuration to correctly match Kubernetes bootstrap RBAC requirements ([**@mattia-eleuteri**](https://github.com/mattia-eleuteri) in #1958). + +* **[dashboard] Verify JWT token**: Added JWT token verification to dashboard for improved security ([**@lllamnyp**](https://github.com/lllamnyp) in #1980). + +* **[talm] Skip config loading for completion subcommands**: Fixed talm CLI to skip unnecessary config loading for shell completion commands ([**@kitsunoff**](https://github.com/kitsunoff) in [cozystack/talm#109](https://github.com/cozystack/talm/pull/109)). + +## Dependencies + +* **[kube-ovn] Update Kube-OVN to v1.15.3**: Updated Kube-OVN CNI to v1.15.3 with performance improvements and bug fixes ([**@kvaps**](https://github.com/kvaps) in #2022). + +* **[local-ccm] Update to v0.3.0**: Updated local cloud controller manager to v0.3.0 with node-lifecycle-controller support ([**@kvaps**](https://github.com/kvaps) in #1992). + +* **[kilo] Update to v0.7.0**: Updated Kilo to v0.7.0 from cozystack fork with improved MTU handling ([**@kvaps**](https://github.com/kvaps) in #2003). + +## Development, Testing, and CI/CD + +* **[ci] Use GitHub Copilot CLI for changelog generation**: Automated changelog generation using GitHub Copilot CLI to improve release process efficiency ([**@androndo**](https://github.com/androndo) in #1753). + +* **[ci] Choose runner conditional on label**: Added conditional runner selection in CI based on PR labels for more flexible CI/CD workflows ([**@lllamnyp**](https://github.com/lllamnyp) in #1998). + +* **[backups] Add restore jobs controller**: Added controller for managing backup restore jobs ([**@androndo**](https://github.com/androndo) in #1811). + +* **Update CODEOWNERS**: Updated CODEOWNERS file to include new maintainers ([**@lllamnyp**](https://github.com/lllamnyp) in #1972, [**@IvanHunters**](https://github.com/IvanHunters) in #2015). + +## Documentation + +* **[website] Add LINSTOR disk preparation guide**: Added comprehensive documentation for preparing disks for LINSTOR storage system ([**@IvanHunters**](https://github.com/IvanHunters) in [cozystack/website#411](https://github.com/cozystack/website/pull/411)). + +* **[website] Add Proxmox VM migration guide**: Added detailed guide for migrating virtual machines from Proxmox to Cozystack ([**@IvanHunters**](https://github.com/IvanHunters) in [cozystack/website#410](https://github.com/cozystack/website/pull/410)). + +* **[website] Describe operator-based and HelmRelease-based package patterns**: Added development documentation explaining operator-based and HelmRelease-based package patterns for Cozystack ([**@kvaps**](https://github.com/kvaps) in [cozystack/website#413](https://github.com/cozystack/website/pull/413)). + +* **[website] Correct typo in kubeconfig reference in Kubernetes installation guide**: Fixed documentation typo in kubeconfig reference ([**@shkarface**](https://github.com/shkarface) in [cozystack/website#414](https://github.com/cozystack/website/pull/414)). + +* **[website] Check quotas before an upgrade**: Added troubleshooting documentation for checking resource quotas before performing upgrades ([**@nbykov0**](https://github.com/nbykov0) in [cozystack/website#405](https://github.com/cozystack/website/pull/405)). + +--- + +## Contributors + +We'd like to thank all contributors who made this release possible: + +* [**@IvanHunters**](https://github.com/IvanHunters) +* [**@androndo**](https://github.com/androndo) +* [**@kitsunoff**](https://github.com/kitsunoff) +* [**@kvaps**](https://github.com/kvaps) +* [**@lexfrei**](https://github.com/lexfrei) +* [**@lllamnyp**](https://github.com/lllamnyp) +* [**@mattia-eleuteri**](https://github.com/mattia-eleuteri) +* [**@nbykov0**](https://github.com/nbykov0) +* [**@shkarface**](https://github.com/shkarface) +* [**@sircthulhu**](https://github.com/sircthulhu) + +--- + +**Full Changelog**: [v1.0.0-beta.2...v1.0.0-beta.3](https://github.com/cozystack/cozystack/compare/v1.0.0-beta.2...v1.0.0-beta.3) From 87e394c0c9bd3202bae02df68ac94a2a1b41b9d0 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Mon, 29 Dec 2025 16:32:23 +0300 Subject: [PATCH 220/889] [platform] Add DNS-1035 validation for Application names Add validation to ensure Application names (including Tenants) conform to DNS-1035 format. This prevents creation of resources with names starting with digits, which would cause Kubernetes resource creation failures (e.g., Services, Namespaces). DNS-1035 requires names to: - Start with a lowercase letter [a-z] - Contain only lowercase alphanumeric or hyphens [-a-z0-9] - End with an alphanumeric character [a-z0-9] Also fixes broken validation.go that referenced non-existent internal types (apps.Application, apps.ApplicationSpec). Fixes: https://github.com/cozystack/cozystack/issues/1538 Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- pkg/apis/apps/validation/validation.go | 56 +++++++++++++++++++ pkg/apis/apps/validation/validation_test.go | 61 +++++++++++++++++++++ pkg/registry/apps/application/rest.go | 7 +++ 3 files changed, 124 insertions(+) create mode 100644 pkg/apis/apps/validation/validation.go create mode 100644 pkg/apis/apps/validation/validation_test.go diff --git a/pkg/apis/apps/validation/validation.go b/pkg/apis/apps/validation/validation.go new file mode 100644 index 00000000..ac8e6623 --- /dev/null +++ b/pkg/apis/apps/validation/validation.go @@ -0,0 +1,56 @@ +/* +Copyright 2024 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. +*/ + +package validation + +import ( + "regexp" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// dns1035LabelRegex validates DNS-1035 label format. +// DNS-1035 labels must start with a letter, contain only lowercase alphanumeric +// characters or hyphens, and end with an alphanumeric character. +var dns1035LabelRegex = regexp.MustCompile(`^[a-z]([-a-z0-9]*[a-z0-9])?$`) + +// maxDNS1035LabelLength is the maximum length of a DNS-1035 label. +const maxDNS1035LabelLength = 63 + +// ValidateApplicationName validates that an Application name conforms to DNS-1035. +// This is required because Application names are used to create Kubernetes resources +// (Services, Namespaces, etc.) that must have DNS-1035 compliant names. +func ValidateApplicationName(name string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if len(name) == 0 { + allErrs = append(allErrs, field.Required(fldPath, "name is required")) + return allErrs + } + + if len(name) > maxDNS1035LabelLength { + allErrs = append(allErrs, field.TooLongMaxLength(fldPath, name, maxDNS1035LabelLength)) + } + + if !dns1035LabelRegex.MatchString(name) { + allErrs = append(allErrs, field.Invalid(fldPath, name, + "a DNS-1035 label must consist of lower case alphanumeric characters or '-', "+ + "start with an alphabetic character, and end with an alphanumeric character "+ + "(e.g. 'my-name', regex used for validation is '[a-z]([-a-z0-9]*[a-z0-9])?')")) + } + + return allErrs +} diff --git a/pkg/apis/apps/validation/validation_test.go b/pkg/apis/apps/validation/validation_test.go new file mode 100644 index 00000000..d1e6b2f8 --- /dev/null +++ b/pkg/apis/apps/validation/validation_test.go @@ -0,0 +1,61 @@ +/* +Copyright 2024 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. +*/ + +package validation + +import ( + "testing" + + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestValidateApplicationName(t *testing.T) { + tests := []struct { + name string + appName string + wantError bool + }{ + // Valid names + {"valid simple name", "tenant-one", false}, + {"valid single letter", "a", false}, + {"valid with numbers", "abc-123", false}, + {"valid lowercase", "my-tenant", false}, + {"valid long name", "my-very-long-tenant-name-123", false}, + + // Invalid names + {"starts with digit", "1john", true}, + {"only digits", "123", true}, + {"starts with hyphen", "-tenant", true}, + {"ends with hyphen", "tenant-", true}, + {"uppercase letters", "Tenant", true}, + {"mixed case", "myTenant", true}, + {"underscore", "my_tenant", true}, + {"dot", "my.tenant", true}, + {"empty string", "", true}, + {"space", "my tenant", true}, + {"too long (64 chars)", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", true}, + {"max length (63 chars)", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + errs := ValidateApplicationName(tt.appName, field.NewPath("metadata").Child("name")) + if (len(errs) > 0) != tt.wantError { + t.Errorf("ValidateApplicationName(%q) returned %d errors, wantError = %v", tt.appName, len(errs), tt.wantError) + } + }) + } +} diff --git a/pkg/registry/apps/application/rest.go b/pkg/registry/apps/application/rest.go index 00d22486..d93f55e0 100644 --- a/pkg/registry/apps/application/rest.go +++ b/pkg/registry/apps/application/rest.go @@ -35,6 +35,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/selection" + "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/duration" "k8s.io/apimachinery/pkg/watch" "k8s.io/apiserver/pkg/endpoints/request" @@ -43,6 +44,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" appsv1alpha1 "github.com/cozystack/cozystack/pkg/apis/apps/v1alpha1" + "github.com/cozystack/cozystack/pkg/apis/apps/validation" "github.com/cozystack/cozystack/pkg/config" "github.com/cozystack/cozystack/pkg/registry" fieldfilter "github.com/cozystack/cozystack/pkg/registry/fields" @@ -152,6 +154,11 @@ func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation return nil, fmt.Errorf("expected *appsv1alpha1.Application object, got %T", obj) } + // Validate Application name conforms to DNS-1035 + if errs := validation.ValidateApplicationName(app.Name, field.NewPath("metadata").Child("name")); len(errs) > 0 { + return nil, apierrors.NewInvalid(r.gvk.GroupKind(), app.Name, errs) + } + // Validate that values don't contain reserved keys (starting with "_") if err := validateNoInternalKeys(app.Spec); err != nil { return nil, apierrors.NewBadRequest(err.Error()) From 1cbf1831641f5e695a9e7f12e0f8355832460f93 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Mon, 29 Dec 2025 16:42:05 +0300 Subject: [PATCH 221/889] fix(validation): limit name to 40 chars and add comprehensive tests - Reduce maxApplicationNameLength from 63 to 40 characters to allow room for prefixes like "tenant-" and nested namespaces - Add 27 test cases covering: - Valid names (simple, single letter, with numbers, double hyphen) - Invalid start characters (digit, hyphen) - Invalid end characters (hyphen) - Invalid characters (uppercase, underscore, dot, space, unicode) - Empty/whitespace inputs - Length boundary tests (40 valid, 41+ invalid) Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- pkg/apis/apps/validation/validation.go | 10 ++++--- pkg/apis/apps/validation/validation_test.go | 32 +++++++++++++++++---- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/pkg/apis/apps/validation/validation.go b/pkg/apis/apps/validation/validation.go index ac8e6623..c6c2e535 100644 --- a/pkg/apis/apps/validation/validation.go +++ b/pkg/apis/apps/validation/validation.go @@ -27,8 +27,10 @@ import ( // characters or hyphens, and end with an alphanumeric character. var dns1035LabelRegex = regexp.MustCompile(`^[a-z]([-a-z0-9]*[a-z0-9])?$`) -// maxDNS1035LabelLength is the maximum length of a DNS-1035 label. -const maxDNS1035LabelLength = 63 +// maxApplicationNameLength is the maximum length of an Application name. +// This is set to 40 (not 63) to allow room for prefixes like "tenant-" +// and nested tenant namespaces (e.g., "tenant-parent-child"). +const maxApplicationNameLength = 40 // ValidateApplicationName validates that an Application name conforms to DNS-1035. // This is required because Application names are used to create Kubernetes resources @@ -41,8 +43,8 @@ func ValidateApplicationName(name string, fldPath *field.Path) field.ErrorList { return allErrs } - if len(name) > maxDNS1035LabelLength { - allErrs = append(allErrs, field.TooLongMaxLength(fldPath, name, maxDNS1035LabelLength)) + if len(name) > maxApplicationNameLength { + allErrs = append(allErrs, field.TooLongMaxLength(fldPath, name, maxApplicationNameLength)) } if !dns1035LabelRegex.MatchString(name) { diff --git a/pkg/apis/apps/validation/validation_test.go b/pkg/apis/apps/validation/validation_test.go index d1e6b2f8..87e80fe0 100644 --- a/pkg/apis/apps/validation/validation_test.go +++ b/pkg/apis/apps/validation/validation_test.go @@ -17,6 +17,7 @@ limitations under the License. package validation import ( + "strings" "testing" "k8s.io/apimachinery/pkg/util/validation/field" @@ -33,28 +34,47 @@ func TestValidateApplicationName(t *testing.T) { {"valid single letter", "a", false}, {"valid with numbers", "abc-123", false}, {"valid lowercase", "my-tenant", false}, - {"valid long name", "my-very-long-tenant-name-123", false}, + {"valid long name", "my-very-long-tenant-name", false}, + {"valid double hyphen", "my--tenant", false}, + {"valid max length (40 chars)", strings.Repeat("a", 40), false}, - // Invalid names + // Invalid: starts with wrong character {"starts with digit", "1john", true}, {"only digits", "123", true}, {"starts with hyphen", "-tenant", true}, + + // Invalid: ends with wrong character {"ends with hyphen", "tenant-", true}, + + // Invalid: wrong characters {"uppercase letters", "Tenant", true}, {"mixed case", "myTenant", true}, {"underscore", "my_tenant", true}, {"dot", "my.tenant", true}, - {"empty string", "", true}, {"space", "my tenant", true}, - {"too long (64 chars)", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", true}, - {"max length (63 chars)", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", false}, + {"unicode cyrillic", "тенант", true}, + {"unicode emoji", "tenant🚀", true}, + {"special chars", "tenant@home", true}, + {"colon", "tenant:one", true}, + {"slash", "tenant/one", true}, + + // Invalid: empty or whitespace + {"empty string", "", true}, + {"only spaces", " ", true}, + {"leading space", " tenant", true}, + {"trailing space", "tenant ", true}, + + // Invalid: length + {"too long (41 chars)", strings.Repeat("a", 41), true}, + {"way too long (100 chars)", strings.Repeat("a", 100), true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { errs := ValidateApplicationName(tt.appName, field.NewPath("metadata").Child("name")) if (len(errs) > 0) != tt.wantError { - t.Errorf("ValidateApplicationName(%q) returned %d errors, wantError = %v", tt.appName, len(errs), tt.wantError) + t.Errorf("ValidateApplicationName(%q) returned %d errors, wantError = %v, errors = %v", + tt.appName, len(errs), tt.wantError, errs) } }) } From 9f20771cf8829001b041421145841b452da7ea4b Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Mon, 29 Dec 2025 16:42:57 +0300 Subject: [PATCH 222/889] docs(tenant): update naming requirements in README Clarify DNS-1035 naming rules: - Must start with lowercase letter - Allowed characters: a-z, 0-9, hyphen - Must end with letter or number - Maximum 40 characters Change wording from "not allowed" to "discouraged" for dashes since the validation technically permits them. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/apps/tenant/README.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/apps/tenant/README.md b/packages/apps/tenant/README.md index 99a973c7..811197f2 100644 --- a/packages/apps/tenant/README.md +++ b/packages/apps/tenant/README.md @@ -6,9 +6,14 @@ Tenants can be created recursively and are subject to the following rules: ### Tenant naming -Tenant names must be alphanumeric. -Using dashes (`-`) in tenant names is not allowed, unlike with other services. -This limitation exists to keep consistent naming in tenants, nested tenants, and services deployed in them. +Tenant names must follow DNS-1035 naming rules: +- Must start with a lowercase letter (`a-z`) +- Can only contain lowercase letters, numbers, and hyphens (`a-z`, `0-9`, `-`) +- Must end with a letter or number (not a hyphen) +- Maximum length: 40 characters + +**Note:** Using dashes (`-`) in tenant names is discouraged, unlike with other services. +This is to keep consistent naming in tenants, nested tenants, and services deployed in them. For example: From 7c0e99e1af2e63ee79f4668ec872c047d23cad0a Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Mon, 29 Dec 2025 20:25:02 +0300 Subject: [PATCH 223/889] [platform] Add OpenAPI schema validation for Application names Add pattern and maxLength constraints to ObjectMeta.name in OpenAPI schema. This enables UI form validation when openapi-k8s-toolkit supports it. - Pattern: ^[a-z]([-a-z0-9]*[a-z0-9])?$ (DNS-1035) - MaxLength: 40 Depends on: cozystack/openapi-k8s-toolkit#1 Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- pkg/cmd/server/openapi.go | 45 ++++++++++++++++ pkg/cmd/server/openapi_test.go | 98 ++++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 pkg/cmd/server/openapi_test.go diff --git a/pkg/cmd/server/openapi.go b/pkg/cmd/server/openapi.go index e6a659c5..e5734b9b 100644 --- a/pkg/cmd/server/openapi.go +++ b/pkg/cmd/server/openapi.go @@ -19,8 +19,47 @@ const ( baseListRef = apiPrefix + ".ApplicationList" baseStatusRef = apiPrefix + ".ApplicationStatus" smp = "application/strategic-merge-patch+json" + + // objectMetaRef is the OpenAPI reference for Kubernetes ObjectMeta. + objectMetaRef = "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta" + // applicationNamePattern is DNS-1035 regex for Application names. + applicationNamePattern = `^[a-z]([-a-z0-9]*[a-z0-9])?$` + // applicationNameMaxLength is the maximum length for Application names. + applicationNameMaxLength = 40 ) +// patchObjectMetaNameValidation adds DNS-1035 validation to metadata.name. +// This ensures UI form validation matches backend validation. +func patchObjectMetaNameValidation(schemas map[string]*spec.Schema) { + objMeta, ok := schemas[objectMetaRef] + if !ok || objMeta == nil { + return + } + + if name, ok := objMeta.Properties["name"]; ok { + name.Pattern = applicationNamePattern + maxLen := int64(applicationNameMaxLength) + name.MaxLength = &maxLen + objMeta.Properties["name"] = name + } +} + +// patchObjectMetaNameValidationV2 adds DNS-1035 validation for Swagger v2. +func patchObjectMetaNameValidationV2(defs map[string]spec.Schema) { + objMeta, ok := defs[objectMetaRef] + if !ok { + return + } + + if name, ok := objMeta.Properties["name"]; ok { + name.Pattern = applicationNamePattern + maxLen := int64(applicationNameMaxLength) + name.MaxLength = &maxLen + objMeta.Properties["name"] = name + } + defs[objectMetaRef] = objMeta +} + // deepCopySchema clones *spec.Schema via JSON-marshal/unmarshal. func deepCopySchema(in *spec.Schema) *spec.Schema { if in == nil { @@ -262,6 +301,9 @@ func buildPostProcessV3(kindSchemas map[string]string) func(*spec3.OpenAPI) (*sp } } + // Add name validation to ObjectMeta for UI form validation + patchObjectMetaNameValidation(doc.Components.Schemas) + // Rewrite all $ref in the document out, err := rewriteDocRefs(doc) if err != nil { @@ -343,6 +385,9 @@ func buildPostProcessV2(kindSchemas map[string]string) func(*spec.Swagger) (*spe return sw, fmt.Errorf("base Application* schemas not found") } + // Add name validation to ObjectMeta for UI form validation + patchObjectMetaNameValidationV2(defs) + for kind, raw := range kindSchemas { ref := apiPrefix + "." + kind statusRef := ref + "Status" diff --git a/pkg/cmd/server/openapi_test.go b/pkg/cmd/server/openapi_test.go new file mode 100644 index 00000000..921b0d30 --- /dev/null +++ b/pkg/cmd/server/openapi_test.go @@ -0,0 +1,98 @@ +/* +Copyright 2024 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. +*/ + +package server + +import ( + "testing" + + "k8s.io/kube-openapi/pkg/validation/spec" +) + +func TestPatchObjectMetaNameValidation(t *testing.T) { + tests := []struct { + name string + schemas map[string]*spec.Schema + wantPattern string + wantMaxLength *int64 + }{ + { + name: "patches ObjectMeta name field", + schemas: map[string]*spec.Schema{ + objectMetaRef: { + SchemaProps: spec.SchemaProps{ + Properties: map[string]spec.Schema{ + "name": {SchemaProps: spec.SchemaProps{Type: []string{"string"}}}, + }, + }, + }, + }, + wantPattern: applicationNamePattern, + wantMaxLength: ptr(int64(applicationNameMaxLength)), + }, + { + name: "no panic when ObjectMeta missing", + schemas: map[string]*spec.Schema{}, + wantPattern: "", + wantMaxLength: nil, + }, + { + name: "no panic when name field missing", + schemas: map[string]*spec.Schema{ + objectMetaRef: { + SchemaProps: spec.SchemaProps{ + Properties: map[string]spec.Schema{}, + }, + }, + }, + wantPattern: "", + wantMaxLength: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + patchObjectMetaNameValidation(tt.schemas) + + objMeta, ok := tt.schemas[objectMetaRef] + if !ok { + if tt.wantPattern != "" || tt.wantMaxLength != nil { + t.Error("expected ObjectMeta to exist") + } + return + } + + name, ok := objMeta.Properties["name"] + if !ok { + if tt.wantPattern != "" || tt.wantMaxLength != nil { + t.Error("expected name field to exist") + } + return + } + + if name.Pattern != tt.wantPattern { + t.Errorf("Pattern = %q, want %q", name.Pattern, tt.wantPattern) + } + if (name.MaxLength == nil) != (tt.wantMaxLength == nil) { + t.Errorf("MaxLength nil mismatch") + } else if name.MaxLength != nil && *name.MaxLength != *tt.wantMaxLength { + t.Errorf("MaxLength = %d, want %d", *name.MaxLength, *tt.wantMaxLength) + } + }) + } +} + +func ptr[T any](v T) *T { return &v } From 3685d49c4e3c38360a6ea6d5de186e9b617f4658 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 11 Feb 2026 12:34:51 +0300 Subject: [PATCH 224/889] feat(api): add dynamic name length validation based on root-host Read root-host from cozystack-values secret at API server startup and use it to compute maximum allowed name length for applications. For all apps: validates prefix + name fits within the Helm release name limit (53 chars). For Tenants: additionally checks that the host label (name + "." + rootHost) fits within the Kubernetes label value limit (63 chars). This replaces the static 40-char limit with a dynamic calculation that accounts for the actual cluster root host length. Ref: https://github.com/cozystack/cozystack/issues/2001 Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- pkg/apiserver/apiserver.go | 2 +- pkg/cmd/server/start.go | 47 ++++++ pkg/cmd/server/start_test.go | 93 ++++++++++- pkg/config/config.go | 1 + pkg/registry/apps/application/rest.go | 47 +++++- .../apps/application/rest_validation_test.go | 153 ++++++++++++++++++ 6 files changed, 339 insertions(+), 4 deletions(-) create mode 100644 pkg/registry/apps/application/rest_validation_test.go diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index bc2b4857..aba9a345 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -208,7 +208,7 @@ func (c completedConfig) New() (*CozyServer, error) { // --- dynamically-configured, per-tenant resources --- appsV1alpha1Storage := map[string]rest.Storage{} for _, resConfig := range c.ResourceConfig.Resources { - storage := applicationstorage.NewREST(cli, watchCli, &resConfig) + storage := applicationstorage.NewREST(cli, watchCli, &resConfig, c.ResourceConfig.RootHost) appsV1alpha1Storage[resConfig.Application.Plural] = cozyregistry.RESTInPeace(storage) } appsApiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(apps.GroupName, Scheme, metav1.ParameterCodec, Codecs) diff --git a/pkg/cmd/server/start.go b/pkg/cmd/server/start.go index 6ff8d2d1..90293b39 100644 --- a/pkg/cmd/server/start.go +++ b/pkg/cmd/server/start.go @@ -33,6 +33,7 @@ import ( "github.com/cozystack/cozystack/pkg/config" sampleopenapi "github.com/cozystack/cozystack/pkg/generated/openapi" "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apiserver/pkg/endpoints/openapi" @@ -44,6 +45,7 @@ import ( netutils "k8s.io/utils/net" "sigs.k8s.io/controller-runtime/pkg/client" k8sconfig "sigs.k8s.io/controller-runtime/pkg/client/config" + "sigs.k8s.io/yaml" ) // CozyServerOptions holds the state for the Cozy API server @@ -179,9 +181,54 @@ func (o *CozyServerOptions) Complete() error { o.ResourceConfig.Resources = append(o.ResourceConfig.Resources, resource) } + // Read root-host from cozystack-values secret (best-effort, non-fatal) + coreScheme := runtime.NewScheme() + _ = corev1.AddToScheme(coreScheme) + coreClient, err := client.New(cfg, client.Options{Scheme: coreScheme}) + if err != nil { + fmt.Printf("Warning: failed to create client for reading cozystack-values secret: %v\n", err) + } else { + secret := &corev1.Secret{} + err := coreClient.Get(context.Background(), client.ObjectKey{ + Namespace: "cozy-system", + Name: "cozystack-values", + }, secret) + if err != nil { + fmt.Printf("Warning: failed to read cozystack-values secret: %v\n", err) + } else { + o.ResourceConfig.RootHost = parseRootHostFromSecret(secret) + if o.ResourceConfig.RootHost != "" { + fmt.Printf("Loaded root-host: %s\n", o.ResourceConfig.RootHost) + } + } + } + return nil } +// parseRootHostFromSecret extracts _cluster.root-host from the cozystack-values secret. +func parseRootHostFromSecret(secret *corev1.Secret) string { + if secret == nil || secret.Data == nil { + return "" + } + + valuesYAML, ok := secret.Data["values.yaml"] + if !ok || len(valuesYAML) == 0 { + return "" + } + + var values struct { + Cluster struct { + RootHost string `json:"root-host"` + } `json:"_cluster"` + } + if err := yaml.Unmarshal(valuesYAML, &values); err != nil { + return "" + } + + return values.Cluster.RootHost +} + // Validate checks the correctness of the options func (o CozyServerOptions) Validate(args []string) error { var allErrors []error diff --git a/pkg/cmd/server/start_test.go b/pkg/cmd/server/start_test.go index df7c3547..6e19a719 100644 --- a/pkg/cmd/server/start_test.go +++ b/pkg/cmd/server/start_test.go @@ -16,5 +16,94 @@ limitations under the License. package server -// Note: Tests for KEP-4330 component versioning functionality have been removed -// as the functionality is not available in Kubernetes v0.34.1. +import ( + "testing" + + corev1 "k8s.io/api/core/v1" +) + +func TestParseRootHostFromSecret(t *testing.T) { + tests := []struct { + name string + secret *corev1.Secret + expected string + }{ + { + name: "valid secret with root-host", + secret: &corev1.Secret{ + Data: map[string][]byte{ + "values.yaml": []byte("_cluster:\n root-host: \"example.com\"\n bundle-name: \"paas-full\"\n"), + }, + }, + expected: "example.com", + }, + { + name: "valid secret with unquoted root-host", + secret: &corev1.Secret{ + Data: map[string][]byte{ + "values.yaml": []byte("_cluster:\n root-host: my.domain.org\n"), + }, + }, + expected: "my.domain.org", + }, + { + name: "missing values.yaml key", + secret: &corev1.Secret{ + Data: map[string][]byte{ + "other-key": []byte("some data"), + }, + }, + expected: "", + }, + { + name: "malformed YAML", + secret: &corev1.Secret{ + Data: map[string][]byte{ + "values.yaml": []byte("not: valid: yaml: [[["), + }, + }, + expected: "", + }, + { + name: "empty root-host", + secret: &corev1.Secret{ + Data: map[string][]byte{ + "values.yaml": []byte("_cluster:\n root-host: \"\"\n"), + }, + }, + expected: "", + }, + { + name: "no _cluster key", + secret: &corev1.Secret{ + Data: map[string][]byte{ + "values.yaml": []byte("other:\n key: value\n"), + }, + }, + expected: "", + }, + { + name: "_cluster without root-host", + secret: &corev1.Secret{ + Data: map[string][]byte{ + "values.yaml": []byte("_cluster:\n bundle-name: \"paas-full\"\n"), + }, + }, + expected: "", + }, + { + name: "nil data", + secret: &corev1.Secret{}, + expected: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := parseRootHostFromSecret(tt.secret) + if result != tt.expected { + t.Errorf("parseRootHostFromSecret() = %q, want %q", result, tt.expected) + } + }) + } +} diff --git a/pkg/config/config.go b/pkg/config/config.go index 1e123e2c..260d149a 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -19,6 +19,7 @@ package config // ResourceConfig represents the structure of the configuration file. type ResourceConfig struct { Resources []Resource `yaml:"resources"` + RootHost string // cluster root host from cozystack-values secret } // Resource describes an individual resource. diff --git a/pkg/registry/apps/application/rest.go b/pkg/registry/apps/application/rest.go index d93f55e0..45c54b54 100644 --- a/pkg/registry/apps/application/rest.go +++ b/pkg/registry/apps/application/rest.go @@ -91,10 +91,11 @@ type REST struct { singularName string releaseConfig config.ReleaseConfig specSchema *structuralschema.Structural + rootHost string } // NewREST creates a new REST storage for Application with specific configuration -func NewREST(c client.Client, w client.WithWatch, config *config.Resource) *REST { +func NewREST(c client.Client, w client.WithWatch, config *config.Resource, rootHost string) *REST { var specSchema *structuralschema.Structural if raw := strings.TrimSpace(config.Application.OpenAPISchema); raw != "" { @@ -133,6 +134,7 @@ func NewREST(c client.Client, w client.WithWatch, config *config.Resource) *REST singularName: config.Application.Singular, releaseConfig: config.Release, specSchema: specSchema, + rootHost: rootHost, } } @@ -159,6 +161,11 @@ func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation return nil, apierrors.NewInvalid(r.gvk.GroupKind(), app.Name, errs) } + // Validate name length against Helm release and label limits + if err := r.validateNameLength(app.Name); err != nil { + return nil, apierrors.NewBadRequest(err.Error()) + } + // Validate that values don't contain reserved keys (starting with "_") if err := validateNoInternalKeys(app.Spec); err != nil { return nil, apierrors.NewBadRequest(err.Error()) @@ -477,6 +484,11 @@ func (r *REST) Update(ctx context.Context, name string, objInfo rest.UpdatedObje return nil, false, fmt.Errorf("expected *appsv1alpha1.Application object, got %T", newObj) } + // Validate name length against Helm release and label limits + if err := r.validateNameLength(app.Name); err != nil { + return nil, false, apierrors.NewBadRequest(err.Error()) + } + // Validate that values don't contain reserved keys (starting with "_") if err := validateNoInternalKeys(app.Spec); err != nil { return nil, false, apierrors.NewBadRequest(err.Error()) @@ -1036,6 +1048,39 @@ func validateNoInternalKeys(values *apiextv1.JSON) error { return nil } +// maxHelmReleaseName is the maximum length of a Helm release name (DNS-1123 subdomain). +const maxHelmReleaseName = 53 + +// maxK8sLabelValue is the maximum length of a Kubernetes label value. +const maxK8sLabelValue = 63 + +// validateNameLength checks that the application name won't exceed Kubernetes limits. +// For all apps: prefix + name must fit within the Helm release name limit (53 chars). +// For Tenants: additionally checks that the host label (name + "." + rootHost) fits +// within the Kubernetes label value limit (63 chars). +func (r *REST) validateNameLength(name string) error { + helmMax := maxHelmReleaseName - len(r.releaseConfig.Prefix) + maxLen := helmMax + + if r.kindName == "Tenant" && r.rootHost != "" { + hostLabelMax := maxK8sLabelValue - len(r.rootHost) - 1 // -1 for the dot separator + if hostLabelMax < maxLen { + maxLen = hostLabelMax + } + } + + if maxLen <= 0 { + return fmt.Errorf("configuration error: no valid name length possible (release prefix %q, root host %q)", + r.releaseConfig.Prefix, r.rootHost) + } + + if len(name) > maxLen { + return fmt.Errorf("name %q is too long: maximum %d characters allowed (release prefix %q, root host %q)", + name, maxLen, r.releaseConfig.Prefix, r.rootHost) + } + return nil +} + // convertHelmReleaseToApplication implements the actual conversion logic func (r *REST) convertHelmReleaseToApplication(ctx context.Context, hr *helmv2.HelmRelease) (appsv1alpha1.Application, error) { // Filter out internal keys (starting with "_") from spec diff --git a/pkg/registry/apps/application/rest_validation_test.go b/pkg/registry/apps/application/rest_validation_test.go new file mode 100644 index 00000000..adecc3ad --- /dev/null +++ b/pkg/registry/apps/application/rest_validation_test.go @@ -0,0 +1,153 @@ +/* +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. +*/ + +package application + +import ( + "strings" + "testing" + + "github.com/cozystack/cozystack/pkg/config" +) + +func TestValidateNameLength(t *testing.T) { + tests := []struct { + name string + kindName string + prefix string + rootHost string + appName string + wantError bool + }{ + { + name: "non-tenant short name passes", + kindName: "MySQL", + prefix: "mysql-", + rootHost: "example.com", + appName: "mydb", + wantError: false, + }, + { + name: "non-tenant at helm boundary passes", + kindName: "MySQL", + prefix: "mysql-", + rootHost: "example.com", + appName: strings.Repeat("a", 53-len("mysql-")), // exactly 47 chars + wantError: false, + }, + { + name: "non-tenant exceeding helm limit fails", + kindName: "MySQL", + prefix: "mysql-", + rootHost: "example.com", + appName: strings.Repeat("a", 53-len("mysql-")+1), // 48 chars + wantError: true, + }, + { + name: "tenant no rootHost within helm limit passes", + kindName: "Tenant", + prefix: "tenant-", + rootHost: "", + appName: strings.Repeat("a", 53-len("tenant-")), // 46 chars + wantError: false, + }, + { + name: "tenant no rootHost exceeding helm limit fails", + kindName: "Tenant", + prefix: "tenant-", + rootHost: "", + appName: strings.Repeat("a", 53-len("tenant-")+1), // 47 chars + wantError: true, + }, + { + name: "tenant with rootHost within both limits passes", + kindName: "Tenant", + prefix: "tenant-", + rootHost: "example.com", // 11 chars → host label max = 63-11-1 = 51 + appName: "short", + wantError: false, + }, + { + name: "tenant with short rootHost helm limit is still stricter", + kindName: "Tenant", + prefix: "tenant-", + rootHost: "example.com", // 11 chars → host label max = 51, helm max = 46 + appName: strings.Repeat("a", 53-len("tenant-")), // 46 chars — at Helm boundary + wantError: false, + }, + { + name: "tenant with long rootHost at host label boundary passes", + kindName: "Tenant", + prefix: "tenant-", + rootHost: "long-subdomain.hosting.example.com", // 34 chars → host label max = 63-34-1 = 28 + appName: strings.Repeat("a", 63-len("long-subdomain.hosting.example.com")-1), // exactly 28 chars + wantError: false, + }, + { + name: "tenant with long rootHost exceeding host label limit fails", + kindName: "Tenant", + prefix: "tenant-", + rootHost: "long-subdomain.hosting.example.com", // 34 chars → host label max = 28 + appName: strings.Repeat("a", 63-len("long-subdomain.hosting.example.com")-1+1), // 29 chars + wantError: true, + }, + { + name: "tenant host label limit stricter than helm limit", + kindName: "Tenant", + prefix: "tenant-", + rootHost: "very-long-subdomain.hosting.example.com", // 39 chars → host label max = 63-39-1 = 23 + appName: strings.Repeat("a", 24), // 24 > 23 (host limit) but < 46 (helm limit) + wantError: true, + }, + { + name: "tenant short rootHost where helm limit is stricter", + kindName: "Tenant", + prefix: "tenant-", + rootHost: "x.co", // 4 chars → host label max = 63-4-1 = 58, helm max = 46 + appName: strings.Repeat("a", 53-len("tenant-")+1), // 47 > 46 (helm limit) but < 58 (host limit) + wantError: true, + }, + { + name: "non-tenant ignores rootHost for limit calculation", + kindName: "MySQL", + prefix: "mysql-", + rootHost: "very-long-subdomain.hosting.example.com", + appName: strings.Repeat("a", 53-len("mysql-")), // at helm boundary + wantError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &REST{ + kindName: tt.kindName, + releaseConfig: config.ReleaseConfig{ + Prefix: tt.prefix, + }, + rootHost: tt.rootHost, + } + + err := r.validateNameLength(tt.appName) + + if tt.wantError && err == nil { + t.Errorf("expected error for name %q (len=%d), got nil", tt.appName, len(tt.appName)) + } + if !tt.wantError && err != nil { + t.Errorf("unexpected error for name %q (len=%d): %v", tt.appName, len(tt.appName), err) + } + }) + } +} From dd34fb581ec78db333bc9dd7707d897b9615018a Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 11 Feb 2026 12:39:51 +0300 Subject: [PATCH 225/889] fix(api): handle edge case when prefix or root host exhaust name capacity Add protection against negative or zero maxLen when release prefix or root host are too long, returning a clear configuration error instead of a confusing "name too long" message. Add corresponding test cases. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- .../apps/application/rest_validation_test.go | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/pkg/registry/apps/application/rest_validation_test.go b/pkg/registry/apps/application/rest_validation_test.go index adecc3ad..8c93cf37 100644 --- a/pkg/registry/apps/application/rest_validation_test.go +++ b/pkg/registry/apps/application/rest_validation_test.go @@ -128,6 +128,38 @@ func TestValidateNameLength(t *testing.T) { appName: strings.Repeat("a", 53-len("mysql-")), // at helm boundary wantError: false, }, + { + name: "prefix consuming all helm capacity returns config error", + kindName: "MySQL", + prefix: strings.Repeat("x", 53), // prefix == maxHelmReleaseName → helmMax = 0 + rootHost: "", + appName: "a", + wantError: true, + }, + { + name: "prefix exceeding helm capacity returns config error", + kindName: "MySQL", + prefix: strings.Repeat("x", 60), // prefix > maxHelmReleaseName → helmMax < 0 + rootHost: "", + appName: "a", + wantError: true, + }, + { + name: "tenant with rootHost consuming all label capacity returns config error", + kindName: "Tenant", + prefix: "t-", + rootHost: strings.Repeat("h", 62), // 62 chars → hostLabelMax = 63-62-1 = 0, helmMax = 51 + appName: "a", + wantError: true, + }, + { + name: "tenant with rootHost exceeding label capacity returns config error", + kindName: "Tenant", + prefix: "t-", + rootHost: strings.Repeat("h", 70), // 70 chars → hostLabelMax = 63-70-1 = -8, helmMax = 51 + appName: "a", + wantError: true, + }, } for _, tt := range tests { From d4556e4c53ce430d87f5488f3d0f4005163d8342 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 11 Feb 2026 12:58:44 +0300 Subject: [PATCH 226/889] fix(api): address review feedback for name validation - Add DNS-1035 format validation to Update path (was only in Create) - Simplify Secret reading by reusing existing scheme instead of creating a separate client - Add nil secret test case for parseRootHostFromSecret Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- pkg/cmd/server/start.go | 28 ++++++++++++--------------- pkg/cmd/server/start_test.go | 5 +++++ pkg/registry/apps/application/rest.go | 5 +++++ 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/pkg/cmd/server/start.go b/pkg/cmd/server/start.go index 90293b39..3c1db606 100644 --- a/pkg/cmd/server/start.go +++ b/pkg/cmd/server/start.go @@ -115,6 +115,9 @@ func (o *CozyServerOptions) Complete() error { if err := v1alpha1.AddToScheme(scheme); err != nil { return fmt.Errorf("failed to register types: %w", err) } + if err := corev1.AddToScheme(scheme); err != nil { + return fmt.Errorf("failed to register core types: %w", err) + } cfg, err := k8sconfig.GetConfig() if err != nil { @@ -182,24 +185,17 @@ func (o *CozyServerOptions) Complete() error { } // Read root-host from cozystack-values secret (best-effort, non-fatal) - coreScheme := runtime.NewScheme() - _ = corev1.AddToScheme(coreScheme) - coreClient, err := client.New(cfg, client.Options{Scheme: coreScheme}) + secret := &corev1.Secret{} + err = o.Client.Get(context.Background(), client.ObjectKey{ + Namespace: "cozy-system", + Name: "cozystack-values", + }, secret) if err != nil { - fmt.Printf("Warning: failed to create client for reading cozystack-values secret: %v\n", err) + fmt.Printf("Warning: failed to read cozystack-values secret: %v\n", err) } else { - secret := &corev1.Secret{} - err := coreClient.Get(context.Background(), client.ObjectKey{ - Namespace: "cozy-system", - Name: "cozystack-values", - }, secret) - if err != nil { - fmt.Printf("Warning: failed to read cozystack-values secret: %v\n", err) - } else { - o.ResourceConfig.RootHost = parseRootHostFromSecret(secret) - if o.ResourceConfig.RootHost != "" { - fmt.Printf("Loaded root-host: %s\n", o.ResourceConfig.RootHost) - } + o.ResourceConfig.RootHost = parseRootHostFromSecret(secret) + if o.ResourceConfig.RootHost != "" { + fmt.Printf("Loaded root-host: %s\n", o.ResourceConfig.RootHost) } } diff --git a/pkg/cmd/server/start_test.go b/pkg/cmd/server/start_test.go index 6e19a719..3d4d7856 100644 --- a/pkg/cmd/server/start_test.go +++ b/pkg/cmd/server/start_test.go @@ -96,6 +96,11 @@ func TestParseRootHostFromSecret(t *testing.T) { secret: &corev1.Secret{}, expected: "", }, + { + name: "nil secret", + secret: nil, + expected: "", + }, } for _, tt := range tests { diff --git a/pkg/registry/apps/application/rest.go b/pkg/registry/apps/application/rest.go index 45c54b54..16f0dde4 100644 --- a/pkg/registry/apps/application/rest.go +++ b/pkg/registry/apps/application/rest.go @@ -484,6 +484,11 @@ func (r *REST) Update(ctx context.Context, name string, objInfo rest.UpdatedObje return nil, false, fmt.Errorf("expected *appsv1alpha1.Application object, got %T", newObj) } + // Validate Application name conforms to DNS-1035 + if errs := validation.ValidateApplicationName(app.Name, field.NewPath("metadata").Child("name")); len(errs) > 0 { + return nil, false, apierrors.NewInvalid(r.gvk.GroupKind(), app.Name, errs) + } + // Validate name length against Helm release and label limits if err := r.validateNameLength(app.Name); err != nil { return nil, false, apierrors.NewBadRequest(err.Error()) From 9e47669f686c5adac037eedc65662ab645cb731c Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 11 Feb 2026 13:07:50 +0300 Subject: [PATCH 227/889] fix(api): remove name validation from Update path and use klog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Skip DNS-1035 and length validation on Update since Kubernetes names are immutable — validating would block updates to pre-existing resources with non-conforming names. Replace fmt.Printf with klog for structured logging consistency. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- pkg/cmd/server/start.go | 5 +++-- pkg/registry/apps/application/rest.go | 12 +++--------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/pkg/cmd/server/start.go b/pkg/cmd/server/start.go index 3c1db606..bf0d63bd 100644 --- a/pkg/cmd/server/start.go +++ b/pkg/cmd/server/start.go @@ -42,6 +42,7 @@ import ( utilfeature "k8s.io/apiserver/pkg/util/feature" basecompatibility "k8s.io/component-base/compatibility" baseversion "k8s.io/component-base/version" + "k8s.io/klog/v2" netutils "k8s.io/utils/net" "sigs.k8s.io/controller-runtime/pkg/client" k8sconfig "sigs.k8s.io/controller-runtime/pkg/client/config" @@ -191,11 +192,11 @@ func (o *CozyServerOptions) Complete() error { Name: "cozystack-values", }, secret) if err != nil { - fmt.Printf("Warning: failed to read cozystack-values secret: %v\n", err) + klog.Warningf("failed to read cozystack-values secret: %v", err) } else { o.ResourceConfig.RootHost = parseRootHostFromSecret(secret) if o.ResourceConfig.RootHost != "" { - fmt.Printf("Loaded root-host: %s\n", o.ResourceConfig.RootHost) + klog.Infof("Loaded root-host: %s", o.ResourceConfig.RootHost) } } diff --git a/pkg/registry/apps/application/rest.go b/pkg/registry/apps/application/rest.go index 16f0dde4..42734774 100644 --- a/pkg/registry/apps/application/rest.go +++ b/pkg/registry/apps/application/rest.go @@ -484,15 +484,9 @@ func (r *REST) Update(ctx context.Context, name string, objInfo rest.UpdatedObje return nil, false, fmt.Errorf("expected *appsv1alpha1.Application object, got %T", newObj) } - // Validate Application name conforms to DNS-1035 - if errs := validation.ValidateApplicationName(app.Name, field.NewPath("metadata").Child("name")); len(errs) > 0 { - return nil, false, apierrors.NewInvalid(r.gvk.GroupKind(), app.Name, errs) - } - - // Validate name length against Helm release and label limits - if err := r.validateNameLength(app.Name); err != nil { - return nil, false, apierrors.NewBadRequest(err.Error()) - } + // Note: name validation (DNS-1035 format + length) is intentionally skipped on + // Update because Kubernetes names are immutable. Validating here would block + // updates to pre-existing resources whose names don't conform to the new rules. // Validate that values don't contain reserved keys (starting with "_") if err := validateNoInternalKeys(app.Spec); err != nil { From e978e00c7e7b0157c38047c962acb7372ff41004 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 11 Feb 2026 13:18:45 +0300 Subject: [PATCH 228/889] refactor(api): use standard IsDNS1035Label and remove static length limit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace custom DNS-1035 regex with k8s.io/apimachinery IsDNS1035Label. Remove hardcoded maxApplicationNameLength=40 from both validation and OpenAPI — length validation is now handled entirely by validateNameLength which computes dynamic limits based on Helm release prefix and root-host. Fix README to reflect that max length depends on cluster configuration. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/apps/tenant/README.md | 6 ++-- pkg/apis/apps/validation/validation.go | 26 ++++------------- pkg/apis/apps/validation/validation_test.go | 6 ++-- pkg/cmd/server/openapi.go | 6 ---- pkg/cmd/server/openapi_test.go | 31 +++++++-------------- 5 files changed, 21 insertions(+), 54 deletions(-) diff --git a/packages/apps/tenant/README.md b/packages/apps/tenant/README.md index 811197f2..472ccce2 100644 --- a/packages/apps/tenant/README.md +++ b/packages/apps/tenant/README.md @@ -10,16 +10,16 @@ Tenant names must follow DNS-1035 naming rules: - Must start with a lowercase letter (`a-z`) - Can only contain lowercase letters, numbers, and hyphens (`a-z`, `0-9`, `-`) - Must end with a letter or number (not a hyphen) -- Maximum length: 40 characters +- Maximum length depends on the cluster configuration (Helm release prefix and root domain) -**Note:** Using dashes (`-`) in tenant names is discouraged, unlike with other services. +**Note:** Using dashes (`-`) in tenant names is **allowed but discouraged**, unlike with other services. This is to keep consistent naming in tenants, nested tenants, and services deployed in them. +Names with dashes (e.g., `foo-bar`) may lead to ambiguous parsing of internal resource names like `tenant-foo-bar`. For example: - The root tenant is named `root`, but internally it's referenced as `tenant-root`. - A nested tenant could be named `foo`, which would result in `tenant-foo` in service names and URLs. -- However, a tenant can not be named `foo-bar`, because parsing names such as `tenant-foo-bar` would be ambiguous. ### Unique domains diff --git a/pkg/apis/apps/validation/validation.go b/pkg/apis/apps/validation/validation.go index c6c2e535..28810b72 100644 --- a/pkg/apis/apps/validation/validation.go +++ b/pkg/apis/apps/validation/validation.go @@ -17,24 +17,15 @@ limitations under the License. package validation import ( - "regexp" - + k8svalidation "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" ) -// dns1035LabelRegex validates DNS-1035 label format. -// DNS-1035 labels must start with a letter, contain only lowercase alphanumeric -// characters or hyphens, and end with an alphanumeric character. -var dns1035LabelRegex = regexp.MustCompile(`^[a-z]([-a-z0-9]*[a-z0-9])?$`) - -// maxApplicationNameLength is the maximum length of an Application name. -// This is set to 40 (not 63) to allow room for prefixes like "tenant-" -// and nested tenant namespaces (e.g., "tenant-parent-child"). -const maxApplicationNameLength = 40 - // ValidateApplicationName validates that an Application name conforms to DNS-1035. // This is required because Application names are used to create Kubernetes resources // (Services, Namespaces, etc.) that must have DNS-1035 compliant names. +// Note: length validation is handled separately by validateNameLength in the REST +// handler, which computes dynamic limits based on Helm release prefix and root-host. func ValidateApplicationName(name string, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} @@ -43,15 +34,8 @@ func ValidateApplicationName(name string, fldPath *field.Path) field.ErrorList { return allErrs } - if len(name) > maxApplicationNameLength { - allErrs = append(allErrs, field.TooLongMaxLength(fldPath, name, maxApplicationNameLength)) - } - - if !dns1035LabelRegex.MatchString(name) { - allErrs = append(allErrs, field.Invalid(fldPath, name, - "a DNS-1035 label must consist of lower case alphanumeric characters or '-', "+ - "start with an alphabetic character, and end with an alphanumeric character "+ - "(e.g. 'my-name', regex used for validation is '[a-z]([-a-z0-9]*[a-z0-9])?')")) + for _, msg := range k8svalidation.IsDNS1035Label(name) { + allErrs = append(allErrs, field.Invalid(fldPath, name, msg)) } return allErrs diff --git a/pkg/apis/apps/validation/validation_test.go b/pkg/apis/apps/validation/validation_test.go index 87e80fe0..7bf194d5 100644 --- a/pkg/apis/apps/validation/validation_test.go +++ b/pkg/apis/apps/validation/validation_test.go @@ -36,7 +36,7 @@ func TestValidateApplicationName(t *testing.T) { {"valid lowercase", "my-tenant", false}, {"valid long name", "my-very-long-tenant-name", false}, {"valid double hyphen", "my--tenant", false}, - {"valid max length (40 chars)", strings.Repeat("a", 40), false}, + {"valid at DNS-1035 max (63 chars)", strings.Repeat("a", 63), false}, // Invalid: starts with wrong character {"starts with digit", "1john", true}, @@ -64,8 +64,8 @@ func TestValidateApplicationName(t *testing.T) { {"leading space", " tenant", true}, {"trailing space", "tenant ", true}, - // Invalid: length - {"too long (41 chars)", strings.Repeat("a", 41), true}, + // Invalid: exceeds DNS-1035 max length (63) + {"too long (64 chars)", strings.Repeat("a", 64), true}, {"way too long (100 chars)", strings.Repeat("a", 100), true}, } diff --git a/pkg/cmd/server/openapi.go b/pkg/cmd/server/openapi.go index e5734b9b..4d89d1fd 100644 --- a/pkg/cmd/server/openapi.go +++ b/pkg/cmd/server/openapi.go @@ -24,8 +24,6 @@ const ( objectMetaRef = "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta" // applicationNamePattern is DNS-1035 regex for Application names. applicationNamePattern = `^[a-z]([-a-z0-9]*[a-z0-9])?$` - // applicationNameMaxLength is the maximum length for Application names. - applicationNameMaxLength = 40 ) // patchObjectMetaNameValidation adds DNS-1035 validation to metadata.name. @@ -38,8 +36,6 @@ func patchObjectMetaNameValidation(schemas map[string]*spec.Schema) { if name, ok := objMeta.Properties["name"]; ok { name.Pattern = applicationNamePattern - maxLen := int64(applicationNameMaxLength) - name.MaxLength = &maxLen objMeta.Properties["name"] = name } } @@ -53,8 +49,6 @@ func patchObjectMetaNameValidationV2(defs map[string]spec.Schema) { if name, ok := objMeta.Properties["name"]; ok { name.Pattern = applicationNamePattern - maxLen := int64(applicationNameMaxLength) - name.MaxLength = &maxLen objMeta.Properties["name"] = name } defs[objectMetaRef] = objMeta diff --git a/pkg/cmd/server/openapi_test.go b/pkg/cmd/server/openapi_test.go index 921b0d30..626581ca 100644 --- a/pkg/cmd/server/openapi_test.go +++ b/pkg/cmd/server/openapi_test.go @@ -24,10 +24,9 @@ import ( func TestPatchObjectMetaNameValidation(t *testing.T) { tests := []struct { - name string - schemas map[string]*spec.Schema - wantPattern string - wantMaxLength *int64 + name string + schemas map[string]*spec.Schema + wantPattern string }{ { name: "patches ObjectMeta name field", @@ -40,14 +39,12 @@ func TestPatchObjectMetaNameValidation(t *testing.T) { }, }, }, - wantPattern: applicationNamePattern, - wantMaxLength: ptr(int64(applicationNameMaxLength)), + wantPattern: applicationNamePattern, }, { - name: "no panic when ObjectMeta missing", - schemas: map[string]*spec.Schema{}, - wantPattern: "", - wantMaxLength: nil, + name: "no panic when ObjectMeta missing", + schemas: map[string]*spec.Schema{}, + wantPattern: "", }, { name: "no panic when name field missing", @@ -58,8 +55,7 @@ func TestPatchObjectMetaNameValidation(t *testing.T) { }, }, }, - wantPattern: "", - wantMaxLength: nil, + wantPattern: "", }, } @@ -69,7 +65,7 @@ func TestPatchObjectMetaNameValidation(t *testing.T) { objMeta, ok := tt.schemas[objectMetaRef] if !ok { - if tt.wantPattern != "" || tt.wantMaxLength != nil { + if tt.wantPattern != "" { t.Error("expected ObjectMeta to exist") } return @@ -77,7 +73,7 @@ func TestPatchObjectMetaNameValidation(t *testing.T) { name, ok := objMeta.Properties["name"] if !ok { - if tt.wantPattern != "" || tt.wantMaxLength != nil { + if tt.wantPattern != "" { t.Error("expected name field to exist") } return @@ -86,13 +82,6 @@ func TestPatchObjectMetaNameValidation(t *testing.T) { if name.Pattern != tt.wantPattern { t.Errorf("Pattern = %q, want %q", name.Pattern, tt.wantPattern) } - if (name.MaxLength == nil) != (tt.wantMaxLength == nil) { - t.Errorf("MaxLength nil mismatch") - } else if name.MaxLength != nil && *name.MaxLength != *tt.wantMaxLength { - t.Errorf("MaxLength = %d, want %d", *name.MaxLength, *tt.wantMaxLength) - } }) } } - -func ptr[T any](v T) *T { return &v } From c932740dc5d09d7a7c3f9d66d13044a8aa35713e Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 11 Feb 2026 13:24:16 +0300 Subject: [PATCH 229/889] refactor(api): remove global ObjectMeta name patching from OpenAPI Remove patchObjectMetaNameValidation and patchObjectMetaNameValidationV2 functions that were modifying the global ObjectMeta schema. This patching affected ALL resources served by the API server, not just Application resources. Backend validation in Create() is sufficient for enforcing name constraints. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- pkg/cmd/server/openapi.go | 38 --------------- pkg/cmd/server/openapi_test.go | 87 ---------------------------------- 2 files changed, 125 deletions(-) delete mode 100644 pkg/cmd/server/openapi_test.go diff --git a/pkg/cmd/server/openapi.go b/pkg/cmd/server/openapi.go index 4d89d1fd..b52aa989 100644 --- a/pkg/cmd/server/openapi.go +++ b/pkg/cmd/server/openapi.go @@ -20,40 +20,8 @@ const ( baseStatusRef = apiPrefix + ".ApplicationStatus" smp = "application/strategic-merge-patch+json" - // objectMetaRef is the OpenAPI reference for Kubernetes ObjectMeta. - objectMetaRef = "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta" - // applicationNamePattern is DNS-1035 regex for Application names. - applicationNamePattern = `^[a-z]([-a-z0-9]*[a-z0-9])?$` ) -// patchObjectMetaNameValidation adds DNS-1035 validation to metadata.name. -// This ensures UI form validation matches backend validation. -func patchObjectMetaNameValidation(schemas map[string]*spec.Schema) { - objMeta, ok := schemas[objectMetaRef] - if !ok || objMeta == nil { - return - } - - if name, ok := objMeta.Properties["name"]; ok { - name.Pattern = applicationNamePattern - objMeta.Properties["name"] = name - } -} - -// patchObjectMetaNameValidationV2 adds DNS-1035 validation for Swagger v2. -func patchObjectMetaNameValidationV2(defs map[string]spec.Schema) { - objMeta, ok := defs[objectMetaRef] - if !ok { - return - } - - if name, ok := objMeta.Properties["name"]; ok { - name.Pattern = applicationNamePattern - objMeta.Properties["name"] = name - } - defs[objectMetaRef] = objMeta -} - // deepCopySchema clones *spec.Schema via JSON-marshal/unmarshal. func deepCopySchema(in *spec.Schema) *spec.Schema { if in == nil { @@ -295,9 +263,6 @@ func buildPostProcessV3(kindSchemas map[string]string) func(*spec3.OpenAPI) (*sp } } - // Add name validation to ObjectMeta for UI form validation - patchObjectMetaNameValidation(doc.Components.Schemas) - // Rewrite all $ref in the document out, err := rewriteDocRefs(doc) if err != nil { @@ -379,9 +344,6 @@ func buildPostProcessV2(kindSchemas map[string]string) func(*spec.Swagger) (*spe return sw, fmt.Errorf("base Application* schemas not found") } - // Add name validation to ObjectMeta for UI form validation - patchObjectMetaNameValidationV2(defs) - for kind, raw := range kindSchemas { ref := apiPrefix + "." + kind statusRef := ref + "Status" diff --git a/pkg/cmd/server/openapi_test.go b/pkg/cmd/server/openapi_test.go deleted file mode 100644 index 626581ca..00000000 --- a/pkg/cmd/server/openapi_test.go +++ /dev/null @@ -1,87 +0,0 @@ -/* -Copyright 2024 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. -*/ - -package server - -import ( - "testing" - - "k8s.io/kube-openapi/pkg/validation/spec" -) - -func TestPatchObjectMetaNameValidation(t *testing.T) { - tests := []struct { - name string - schemas map[string]*spec.Schema - wantPattern string - }{ - { - name: "patches ObjectMeta name field", - schemas: map[string]*spec.Schema{ - objectMetaRef: { - SchemaProps: spec.SchemaProps{ - Properties: map[string]spec.Schema{ - "name": {SchemaProps: spec.SchemaProps{Type: []string{"string"}}}, - }, - }, - }, - }, - wantPattern: applicationNamePattern, - }, - { - name: "no panic when ObjectMeta missing", - schemas: map[string]*spec.Schema{}, - wantPattern: "", - }, - { - name: "no panic when name field missing", - schemas: map[string]*spec.Schema{ - objectMetaRef: { - SchemaProps: spec.SchemaProps{ - Properties: map[string]spec.Schema{}, - }, - }, - }, - wantPattern: "", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - patchObjectMetaNameValidation(tt.schemas) - - objMeta, ok := tt.schemas[objectMetaRef] - if !ok { - if tt.wantPattern != "" { - t.Error("expected ObjectMeta to exist") - } - return - } - - name, ok := objMeta.Properties["name"] - if !ok { - if tt.wantPattern != "" { - t.Error("expected name field to exist") - } - return - } - - if name.Pattern != tt.wantPattern { - t.Errorf("Pattern = %q, want %q", name.Pattern, tt.wantPattern) - } - }) - } -} From e267cfcf9dd30aef98f2fa0a4a479db5d0b305a9 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 11 Feb 2026 13:29:42 +0300 Subject: [PATCH 230/889] fix(api): address review feedback for validation consistency - Return field.ErrorList from validateNameLength for consistent apierrors.NewInvalid error shape (was NewBadRequest) - Add klog warning when YAML parsing fails in parseRootHostFromSecret - Fix maxHelmReleaseName comment to accurately describe Helm convention - Add note that root-host changes require API server restart - Replace interface{} with any throughout openapi.go and rest.go - Remove trailing blank line in const block Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- pkg/cmd/server/openapi.go | 31 ++++++++-------- pkg/cmd/server/start.go | 1 + pkg/registry/apps/application/rest.go | 36 ++++++++++++------- .../apps/application/rest_validation_test.go | 10 +++--- 4 files changed, 44 insertions(+), 34 deletions(-) diff --git a/pkg/cmd/server/openapi.go b/pkg/cmd/server/openapi.go index b52aa989..616709ca 100644 --- a/pkg/cmd/server/openapi.go +++ b/pkg/cmd/server/openapi.go @@ -19,7 +19,6 @@ const ( baseListRef = apiPrefix + ".ApplicationList" baseStatusRef = apiPrefix + ".ApplicationStatus" smp = "application/strategic-merge-patch+json" - ) // deepCopySchema clones *spec.Schema via JSON-marshal/unmarshal. @@ -102,9 +101,9 @@ func cloneKindSchemas(kind string, base, baseStatus, baseList *spec.Schema, v3 b // GVK-extensions setGVK := func(s *spec.Schema, k string) { - s.Extensions = map[string]interface{}{ - "x-kubernetes-group-version-kind": []interface{}{ - map[string]interface{}{"group": "apps.cozystack.io", "version": "v1alpha1", "kind": k}, + s.Extensions = map[string]any{ + "x-kubernetes-group-version-kind": []any{ + map[string]any{"group": "apps.cozystack.io", "version": "v1alpha1", "kind": k}, }, } } @@ -133,35 +132,35 @@ func cloneKindSchemas(kind string, base, baseStatus, baseList *spec.Schema, v3 b } // rewriteDocRefs rewrites all $ref in the OpenAPI document -func rewriteDocRefs(doc interface{}) ([]byte, error) { +func rewriteDocRefs(doc any) ([]byte, error) { raw, err := json.Marshal(doc) if err != nil { return nil, fmt.Errorf("failed to marshal OpenAPI document: %w", err) } - var any interface{} - if err := json.Unmarshal(raw, &any); err != nil { + var parsed any + if err := json.Unmarshal(raw, &parsed); err != nil { return nil, err } - walkAndRewriteRefs(any, "") - return json.Marshal(any) + walkAndRewriteRefs(parsed, "") + return json.Marshal(parsed) } // walkAndRewriteRefs walks arbitrary JSON (map/array) and // - when encountering x-kubernetes-group-version-kind, extracts kind, // updating the currentKind context; // - rewrites all $ref inside the current context from Application* → kind*. -func walkAndRewriteRefs(node interface{}, currentKind string) { +func walkAndRewriteRefs(node any, currentKind string) { switch n := node.(type) { - case map[string]interface{}: + case map[string]any: if gvk, ok := n["x-kubernetes-group-version-kind"]; ok { switch g := gvk.(type) { - case map[string]interface{}: + case map[string]any: if k, ok := g["kind"].(string); ok { currentKind = k } - case []interface{}: + case []any: if len(g) > 0 { - if mm, ok := g[0].(map[string]interface{}); ok { + if mm, ok := g[0].(map[string]any); ok { if k, ok := mm["kind"].(string); ok { currentKind = k } @@ -178,7 +177,7 @@ func walkAndRewriteRefs(node interface{}, currentKind string) { } walkAndRewriteRefs(v, currentKind) } - case []interface{}: + case []any: for _, v := range n { walkAndRewriteRefs(v, currentKind) } @@ -293,7 +292,7 @@ func sanitizeForV2(s *spec.Schema) { if hasIntAndStringAnyOf(s.AnyOf) { s.Type = spec.StringOrArray{"string"} if s.Extensions == nil { - s.Extensions = map[string]interface{}{} + s.Extensions = map[string]any{} } s.Extensions["x-kubernetes-int-or-string"] = true } diff --git a/pkg/cmd/server/start.go b/pkg/cmd/server/start.go index bf0d63bd..09c9485c 100644 --- a/pkg/cmd/server/start.go +++ b/pkg/cmd/server/start.go @@ -220,6 +220,7 @@ func parseRootHostFromSecret(secret *corev1.Secret) string { } `json:"_cluster"` } if err := yaml.Unmarshal(valuesYAML, &values); err != nil { + klog.Warningf("failed to parse values.yaml from cozystack-values secret: %v", err) return "" } diff --git a/pkg/registry/apps/application/rest.go b/pkg/registry/apps/application/rest.go index 42734774..26e8429f 100644 --- a/pkg/registry/apps/application/rest.go +++ b/pkg/registry/apps/application/rest.go @@ -162,8 +162,8 @@ func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation } // Validate name length against Helm release and label limits - if err := r.validateNameLength(app.Name); err != nil { - return nil, apierrors.NewBadRequest(err.Error()) + if nameLenErrs := r.validateNameLength(app.Name); len(nameLenErrs) > 0 { + return nil, apierrors.NewInvalid(r.gvk.GroupKind(), app.Name, nameLenErrs) } // Validate that values don't contain reserved keys (starting with "_") @@ -1014,7 +1014,7 @@ func filterInternalKeys(values *apiextv1.JSON) *apiextv1.JSON { if values == nil || len(values.Raw) == 0 { return values } - var data map[string]interface{} + var data map[string]any if err := json.Unmarshal(values.Raw, &data); err != nil { return values } @@ -1035,7 +1035,7 @@ func validateNoInternalKeys(values *apiextv1.JSON) error { if values == nil || len(values.Raw) == 0 { return nil } - var data map[string]interface{} + var data map[string]any if err := json.Unmarshal(values.Raw, &data); err != nil { return err } @@ -1047,7 +1047,8 @@ func validateNoInternalKeys(values *apiextv1.JSON) error { return nil } -// maxHelmReleaseName is the maximum length of a Helm release name (DNS-1123 subdomain). +// maxHelmReleaseName is the Helm release name limit. Helm reserves room for +// chart-generated resource suffixes within the 63-char DNS-1035 label limit. const maxHelmReleaseName = 53 // maxK8sLabelValue is the maximum length of a Kubernetes label value. @@ -1057,7 +1058,13 @@ const maxK8sLabelValue = 63 // For all apps: prefix + name must fit within the Helm release name limit (53 chars). // For Tenants: additionally checks that the host label (name + "." + rootHost) fits // within the Kubernetes label value limit (63 chars). -func (r *REST) validateNameLength(name string) error { +// +// Note: rootHost is read once at API server startup from the cozystack-values Secret. +// Changing root-host requires an API server restart for validation to reflect the new value. +func (r *REST) validateNameLength(name string) field.ErrorList { + fldPath := field.NewPath("metadata").Child("name") + allErrs := field.ErrorList{} + helmMax := maxHelmReleaseName - len(r.releaseConfig.Prefix) maxLen := helmMax @@ -1069,15 +1076,18 @@ func (r *REST) validateNameLength(name string) error { } if maxLen <= 0 { - return fmt.Errorf("configuration error: no valid name length possible (release prefix %q, root host %q)", - r.releaseConfig.Prefix, r.rootHost) + allErrs = append(allErrs, field.Invalid(fldPath, name, + fmt.Sprintf("configuration error: no valid name length possible (release prefix %q, root host %q)", + r.releaseConfig.Prefix, r.rootHost))) + return allErrs } if len(name) > maxLen { - return fmt.Errorf("name %q is too long: maximum %d characters allowed (release prefix %q, root host %q)", - name, maxLen, r.releaseConfig.Prefix, r.rootHost) + allErrs = append(allErrs, field.Invalid(fldPath, name, + fmt.Sprintf("must be no more than %d characters (release prefix %q, root host %q)", + maxLen, r.releaseConfig.Prefix, r.rootHost))) } - return nil + return allErrs } // convertHelmReleaseToApplication implements the actual conversion logic @@ -1233,7 +1243,7 @@ func (r *REST) buildTableFromApplications(apps []appsv1alpha1.Application) metav 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)}, + Cells: []any{app.GetName(), getReadyStatus(app.Status.Conditions), computeAge(app.GetCreationTimestamp().Time, now), getVersion(app.Status.Version)}, Object: runtime.RawExtension{Object: app}, } table.Rows = append(table.Rows, row) @@ -1257,7 +1267,7 @@ func (r *REST) buildTableFromApplication(app appsv1alpha1.Application) metav1.Ta a := app row := metav1.TableRow{ - Cells: []interface{}{app.GetName(), getReadyStatus(app.Status.Conditions), computeAge(app.GetCreationTimestamp().Time, now), getVersion(app.Status.Version)}, + Cells: []any{app.GetName(), getReadyStatus(app.Status.Conditions), computeAge(app.GetCreationTimestamp().Time, now), getVersion(app.Status.Version)}, Object: runtime.RawExtension{Object: &a}, } table.Rows = append(table.Rows, row) diff --git a/pkg/registry/apps/application/rest_validation_test.go b/pkg/registry/apps/application/rest_validation_test.go index 8c93cf37..ee6d66da 100644 --- a/pkg/registry/apps/application/rest_validation_test.go +++ b/pkg/registry/apps/application/rest_validation_test.go @@ -172,13 +172,13 @@ func TestValidateNameLength(t *testing.T) { rootHost: tt.rootHost, } - err := r.validateNameLength(tt.appName) + errs := r.validateNameLength(tt.appName) - if tt.wantError && err == nil { - t.Errorf("expected error for name %q (len=%d), got nil", tt.appName, len(tt.appName)) + if tt.wantError && len(errs) == 0 { + t.Errorf("expected error for name %q (len=%d), got none", tt.appName, len(tt.appName)) } - if !tt.wantError && err != nil { - t.Errorf("unexpected error for name %q (len=%d): %v", tt.appName, len(tt.appName), err) + if !tt.wantError && len(errs) > 0 { + t.Errorf("unexpected error for name %q (len=%d): %v", tt.appName, len(tt.appName), errs) } }) } From d5e713a4e79a773ae9729c47c14f86e916a582e4 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 11 Feb 2026 13:32:59 +0300 Subject: [PATCH 231/889] fix(api): fix import order and context-aware error messages - Fix goimports order: duration before validation/field - Show rootHost in error messages only for Tenant kind where it actually affects the length calculation Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- pkg/registry/apps/application/rest.go | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/pkg/registry/apps/application/rest.go b/pkg/registry/apps/application/rest.go index 26e8429f..fcbcfcef 100644 --- a/pkg/registry/apps/application/rest.go +++ b/pkg/registry/apps/application/rest.go @@ -35,8 +35,8 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/selection" - "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/duration" + "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/watch" "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" @@ -1075,17 +1075,25 @@ func (r *REST) validateNameLength(name string) field.ErrorList { } } + isTenantWithHost := r.kindName == "Tenant" && r.rootHost != "" + if maxLen <= 0 { - allErrs = append(allErrs, field.Invalid(fldPath, name, - fmt.Sprintf("configuration error: no valid name length possible (release prefix %q, root host %q)", - r.releaseConfig.Prefix, r.rootHost))) + msg := fmt.Sprintf("configuration error: no valid name length possible (release prefix %q)", r.releaseConfig.Prefix) + if isTenantWithHost { + msg = fmt.Sprintf("configuration error: no valid name length possible (release prefix %q, root host %q)", + r.releaseConfig.Prefix, r.rootHost) + } + allErrs = append(allErrs, field.Invalid(fldPath, name, msg)) return allErrs } if len(name) > maxLen { - allErrs = append(allErrs, field.Invalid(fldPath, name, - fmt.Sprintf("must be no more than %d characters (release prefix %q, root host %q)", - maxLen, r.releaseConfig.Prefix, r.rootHost))) + msg := fmt.Sprintf("must be no more than %d characters (release prefix %q)", maxLen, r.releaseConfig.Prefix) + if isTenantWithHost { + msg = fmt.Sprintf("must be no more than %d characters (release prefix %q, root host %q)", + maxLen, r.releaseConfig.Prefix, r.rootHost) + } + allErrs = append(allErrs, field.Invalid(fldPath, name, msg)) } return allErrs } From 5bf481ae4dc27c051512c99fa9926e8eb834f3dc Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 11 Feb 2026 13:35:42 +0300 Subject: [PATCH 232/889] chore: update copyright year in start_test.go Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- pkg/cmd/server/start_test.go | 2 +- pkg/registry/apps/application/rest_validation_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/cmd/server/start_test.go b/pkg/cmd/server/start_test.go index 3d4d7856..3a43df72 100644 --- a/pkg/cmd/server/start_test.go +++ b/pkg/cmd/server/start_test.go @@ -1,5 +1,5 @@ /* -Copyright 2024 The Cozystack Authors. +Copyright 2026 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. diff --git a/pkg/registry/apps/application/rest_validation_test.go b/pkg/registry/apps/application/rest_validation_test.go index ee6d66da..07b78e1a 100644 --- a/pkg/registry/apps/application/rest_validation_test.go +++ b/pkg/registry/apps/application/rest_validation_test.go @@ -1,5 +1,5 @@ /* -Copyright 2025 The Cozystack Authors. +Copyright 2026 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. From 8387ea4d08ed4c0aca65a5715f08fb5d17829a71 Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Tue, 10 Feb 2026 18:09:18 +0300 Subject: [PATCH 233/889] [backups] Create RBAC for backup resources ## What this PR does This patch adds cluster roles that get deployed with the backup controller and which follow aggregation rules to automatically let users work with resources from the backups.cozystack.io API group as soon as the CRDs and controller are installed. ### Release note ```release-note [backups] Add RBAC resources to let users work with backups. ``` Signed-off-by: Timofei Larkin --- .../templates/tenant-clusterroles.yaml | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 packages/system/backup-controller/templates/tenant-clusterroles.yaml diff --git a/packages/system/backup-controller/templates/tenant-clusterroles.yaml b/packages/system/backup-controller/templates/tenant-clusterroles.yaml new file mode 100644 index 00000000..1e9eb775 --- /dev/null +++ b/packages/system/backup-controller/templates/tenant-clusterroles.yaml @@ -0,0 +1,44 @@ +--- +# == backup view cluster role == +# Aggregated into cozy-tenant-view (and consequently use, admin, super-admin) +# Provides read-only access to all backup resources +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cozy:backups:view + labels: + rbac.cozystack.io/aggregate-to-tenant-view: "true" +rules: +- apiGroups: ["backups.cozystack.io"] + resources: + - plans + - backupjobs + - restorejobs + - backups + - backupclasses + verbs: + - get + - list + - watch +--- +# == backup admin cluster role == +# Aggregated into cozy-tenant-admin (and consequently super-admin) +# Provides write access to plans, backupjobs, restorejobs +# Backups and backupclasses remain read-only (inherited from view) +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cozy:backups:admin + labels: + rbac.cozystack.io/aggregate-to-tenant-admin: "true" +rules: +- apiGroups: ["backups.cozystack.io"] + resources: + - plans + - backupjobs + - restorejobs + verbs: + - create + - update + - patch + - delete From 8d496d0f11071d9ec8b33c61326ff8da595a5445 Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Wed, 11 Feb 2026 16:55:24 +0300 Subject: [PATCH 234/889] [ci] Cozyreport improvements ## What this PR does Previously the debug log collection script that fired when CI failed treated Packages and PackageSources as namespaced resources and as a result of incorrect parsing failed to correctly kubectl describe and kubectl get -oyaml them. Additionally, the script did not read the logs of init containers. These issues are fixed with this patch. ### Release note ```release-note [ci] Improvements to cozyreport.sh (ci log collection script): fix retrieval of Package and PackageSource details, consider initContainers as well as containers, when fetching logs of errored pods. ``` Signed-off-by: Timofei Larkin --- hack/cozyreport.sh | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/hack/cozyreport.sh b/hack/cozyreport.sh index 28fddfe9..565fd435 100755 --- a/hack/cozyreport.sh +++ b/hack/cozyreport.sh @@ -57,23 +57,23 @@ kubectl get hr -A --no-headers | awk '$4 != "True"' | \ done echo "Collecting packages..." -kubectl get packages -A > $REPORT_DIR/kubernetes/packages.txt 2>&1 -kubectl get packages -A --no-headers | awk '$4 != "True"' | \ - while read NAMESPACE NAME _; do - DIR=$REPORT_DIR/kubernetes/packages/$NAMESPACE/$NAME +kubectl get packages > $REPORT_DIR/kubernetes/packages.txt 2>&1 +kubectl get packages --no-headers | awk '$3 != "True"' | \ + while read NAME _; do + DIR=$REPORT_DIR/kubernetes/packages/$NAME mkdir -p $DIR - kubectl get package -n $NAMESPACE $NAME -o yaml > $DIR/package.yaml 2>&1 - kubectl describe package -n $NAMESPACE $NAME > $DIR/describe.txt 2>&1 + kubectl get package $NAME -o yaml > $DIR/package.yaml 2>&1 + kubectl describe package $NAME > $DIR/describe.txt 2>&1 done echo "Collecting packagesources..." -kubectl get packagesources -A > $REPORT_DIR/kubernetes/packagesources.txt 2>&1 -kubectl get packagesources -A --no-headers | awk '$4 != "True"' | \ - while read NAMESPACE NAME _; do - DIR=$REPORT_DIR/kubernetes/packagesources/$NAMESPACE/$NAME +kubectl get packagesources > $REPORT_DIR/kubernetes/packagesources.txt 2>&1 +kubectl get packagesources --no-headers | awk '$3 != "True"' | \ + while read NAME _; do + DIR=$REPORT_DIR/kubernetes/packagesources/$NAME mkdir -p $DIR - kubectl get packagesource -n $NAMESPACE $NAME -o yaml > $DIR/packagesource.yaml 2>&1 - kubectl describe packagesource -n $NAMESPACE $NAME > $DIR/describe.txt 2>&1 + kubectl get packagesource $NAME -o yaml > $DIR/packagesource.yaml 2>&1 + kubectl describe packagesource $NAME > $DIR/describe.txt 2>&1 done echo "Collecting pods..." @@ -82,7 +82,7 @@ kubectl get pod -A --no-headers | awk '$4 !~ /Running|Succeeded|Completed/' | while read NAMESPACE NAME _ STATE _; do DIR=$REPORT_DIR/kubernetes/pods/$NAMESPACE/$NAME mkdir -p $DIR - CONTAINERS=$(kubectl get pod -o jsonpath='{.spec.containers[*].name}' -n $NAMESPACE $NAME) + CONTAINERS=$(kubectl get pod -o jsonpath='{.spec.containers[*].name} {.spec.initContainers[*].name}' -n $NAMESPACE $NAME) kubectl get pod -n $NAMESPACE $NAME -o yaml > $DIR/pod.yaml 2>&1 kubectl describe pod -n $NAMESPACE $NAME > $DIR/describe.txt 2>&1 if [ "$STATE" != "Pending" ]; then From 84010a80152e28551c64a41e09394293e5472368 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 11 Feb 2026 17:33:19 +0300 Subject: [PATCH 235/889] fix(e2e): increase HelmRelease readiness timeout for kubernetes test The 1-minute timeout for waiting on HelmRelease readiness is too short for ingress-nginx after its migration from hostNetwork to NodePort Service, causing consistent E2E failures on kubernetes-latest. Increase the timeout to 5 minutes to allow sufficient time for all components to become ready. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- hack/e2e-apps/run-kubernetes.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/e2e-apps/run-kubernetes.sh b/hack/e2e-apps/run-kubernetes.sh index f8f1ce5f..8e580a5d 100644 --- a/hack/e2e-apps/run-kubernetes.sh +++ b/hack/e2e-apps/run-kubernetes.sh @@ -222,7 +222,7 @@ fi kubectl wait machinedeployment kubernetes-${test_name}-md0 -n tenant-test --timeout=10m --for=jsonpath='{.status.v1beta2.readyReplicas}'=2 for component in cilium coredns csi ingress-nginx vsnap-crd; do - kubectl wait hr kubernetes-${test_name}-${component} -n tenant-test --timeout=1m --for=condition=ready + kubectl wait hr kubernetes-${test_name}-${component} -n tenant-test --timeout=5m --for=condition=ready done # Clean up by deleting the Kubernetes resource From 26736242619176b462d56dca081f18e48331b7f1 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 11 Feb 2026 17:39:02 +0300 Subject: [PATCH 236/889] fix(e2e): apply increased timeout only to ingress-nginx Keep the 1-minute timeout for other components (cilium, coredns, csi, vsnap-crd) to preserve fast failure detection, and apply the 5-minute timeout specifically to ingress-nginx which needs it after the hostNetwork to NodePort migration. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- hack/e2e-apps/run-kubernetes.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/hack/e2e-apps/run-kubernetes.sh b/hack/e2e-apps/run-kubernetes.sh index 8e580a5d..3e2e20f9 100644 --- a/hack/e2e-apps/run-kubernetes.sh +++ b/hack/e2e-apps/run-kubernetes.sh @@ -221,9 +221,10 @@ fi # Wait for all machine deployment replicas to be ready (timeout after 10 minutes) kubectl wait machinedeployment kubernetes-${test_name}-md0 -n tenant-test --timeout=10m --for=jsonpath='{.status.v1beta2.readyReplicas}'=2 - for component in cilium coredns csi ingress-nginx vsnap-crd; do - kubectl wait hr kubernetes-${test_name}-${component} -n tenant-test --timeout=5m --for=condition=ready + for component in cilium coredns csi vsnap-crd; do + kubectl wait hr kubernetes-${test_name}-${component} -n tenant-test --timeout=1m --for=condition=ready done + kubectl wait hr kubernetes-${test_name}-ingress-nginx -n tenant-test --timeout=5m --for=condition=ready # Clean up by deleting the Kubernetes resource kubectl -n tenant-test delete kuberneteses.apps.cozystack.io $test_name From 36b2a19d3c4e4be58b9de3474c6d48ab056dd1ed Mon Sep 17 00:00:00 2001 From: Timofei Larkin Date: Tue, 10 Feb 2026 18:20:31 +0300 Subject: [PATCH 237/889] [rbac] Use hierarchical naming scheme ## What this PR does This patch improves the naming conventions used in Cozystack's RBAC resources. It follows the k8s system convention of using colons as separators in RBAC resource names (e.g. system:nodes:) and renames some default tenant clusterroles to a scheme like cozy:tenant:admin. ### Release note ```release-note [rbac] Use a more structured naming convention for Cozystack's RBAC resources. ``` Signed-off-by: Timofei Larkin --- packages/apps/tenant/templates/tenant.yaml | 24 +++++++++---------- .../templates/clusterroles.yaml | 20 ++++++++-------- .../templates/dashboard-role.yaml | 2 +- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/apps/tenant/templates/tenant.yaml b/packages/apps/tenant/templates/tenant.yaml index c26de3bd..79eba4ca 100644 --- a/packages/apps/tenant/templates/tenant.yaml +++ b/packages/apps/tenant/templates/tenant.yaml @@ -8,7 +8,7 @@ metadata: apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: - name: {{ include "tenant.name" . }} + name: cozy:tenant namespace: {{ include "tenant.name" . }} subjects: {{- if ne .Release.Namespace "tenant-root" }} @@ -31,66 +31,66 @@ subjects: namespace: {{ include "tenant.name" . }} roleRef: kind: ClusterRole - name: cozy-tenant + name: cozy:tenant apiGroup: rbac.authorization.k8s.io --- # == view role binding == kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: - name: {{ include "tenant.name" . }}-view + name: cozy:tenant:view namespace: {{ include "tenant.name" . }} subjects: {{ include "cozy-lib.rbac.subjectsForTenantAndAccessLevel" (list "view" (include "tenant.name" .)) | nindent 2 }} roleRef: kind: ClusterRole - name: cozy-tenant-view + name: cozy:tenant:view apiGroup: rbac.authorization.k8s.io --- # == use role binding == kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: - name: {{ include "tenant.name" . }}-use + name: cozy:tenant:use namespace: {{ include "tenant.name" . }} subjects: {{ include "cozy-lib.rbac.subjectsForTenantAndAccessLevel" (list "use" (include "tenant.name" .)) | nindent 2 }} roleRef: kind: ClusterRole - name: cozy-tenant-use + name: cozy:tenant:use apiGroup: rbac.authorization.k8s.io --- # == admin role binding == kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: - name: {{ include "tenant.name" . }}-admin + name: cozy:tenant:admin namespace: {{ include "tenant.name" . }} subjects: {{ include "cozy-lib.rbac.subjectsForTenantAndAccessLevel" (list "admin" (include "tenant.name" .)) | nindent 2 }} roleRef: kind: ClusterRole - name: cozy-tenant-admin + name: cozy:tenant:admin apiGroup: rbac.authorization.k8s.io --- # == super admin role binding == kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: - name: {{ include "tenant.name" . }}-super-admin + name: cozy:tenant:super-admin namespace: {{ include "tenant.name" . }} subjects: {{ include "cozy-lib.rbac.subjectsForTenantAndAccessLevel" (list "super-admin" (include "tenant.name" .) ) | nindent 2 }} roleRef: kind: ClusterRole - name: cozy-tenant-super-admin + name: cozy:tenant:super-admin apiGroup: rbac.authorization.k8s.io --- # == dashboard role binding == apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: - name: {{ include "tenant.name" . }} + name: cozy:{{ include "tenant.name" . }}:dashboard namespace: cozy-public subjects: - kind: Group @@ -110,5 +110,5 @@ subjects: namespace: {{ include "tenant.name" . }} roleRef: kind: Role - name: cozy-tenant-dashboard + name: cozy:tenant:dashboard apiGroup: rbac.authorization.k8s.io diff --git a/packages/system/cozystack-basics/templates/clusterroles.yaml b/packages/system/cozystack-basics/templates/clusterroles.yaml index 747650fb..38660a2e 100644 --- a/packages/system/cozystack-basics/templates/clusterroles.yaml +++ b/packages/system/cozystack-basics/templates/clusterroles.yaml @@ -4,7 +4,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: cozy-tenant + name: cozy:tenant aggregationRule: clusterRoleSelectors: - matchLabels: @@ -14,7 +14,7 @@ rules: [] apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: cozy-tenant-base + name: cozy:tenant:base labels: rbac.cozystack.io/aggregate-to-tenant: "true" rules: @@ -49,7 +49,7 @@ rules: apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: cozy-tenant-view + name: cozy:tenant:view labels: rbac.cozystack.io/aggregate-to-tenant-use: "true" rbac.cozystack.io/aggregate-to-tenant-admin: "true" @@ -63,7 +63,7 @@ rules: [] apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: cozy-tenant-view-base + name: cozy:tenant:view:base labels: rbac.cozystack.io/aggregate-to-tenant-view: "true" rules: @@ -115,7 +115,7 @@ rules: apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: cozy-tenant-use + name: cozy:tenant:use labels: rbac.cozystack.io/aggregate-to-tenant-admin: "true" rbac.cozystack.io/aggregate-to-tenant-super-admin: "true" @@ -128,7 +128,7 @@ rules: [] apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: cozy-tenant-use-base + name: cozy:tenant:use:base labels: rbac.cozystack.io/aggregate-to-tenant-use: "true" rules: @@ -158,7 +158,7 @@ rules: apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: cozy-tenant-admin + name: cozy:tenant:admin labels: rbac.cozystack.io/aggregate-to-tenant-super-admin: "true" aggregationRule: @@ -170,7 +170,7 @@ rules: [] apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: cozy-tenant-admin-base + name: cozy:tenant:admin:base labels: rbac.cozystack.io/aggregate-to-tenant-admin: "true" rules: @@ -222,7 +222,7 @@ rules: apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: cozy-tenant-super-admin + name: cozy:tenant:super-admin aggregationRule: clusterRoleSelectors: - matchLabels: @@ -232,7 +232,7 @@ rules: [] apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - name: cozy-tenant-super-admin-base + name: cozy:tenant:super-admin:base labels: rbac.cozystack.io/aggregate-to-tenant-super-admin: "true" rules: diff --git a/packages/system/cozystack-basics/templates/dashboard-role.yaml b/packages/system/cozystack-basics/templates/dashboard-role.yaml index 8f7d3e5d..f7656319 100644 --- a/packages/system/cozystack-basics/templates/dashboard-role.yaml +++ b/packages/system/cozystack-basics/templates/dashboard-role.yaml @@ -4,7 +4,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: - name: cozy-tenant-dashboard + name: cozy:tenant:dashboard namespace: cozy-public rules: - apiGroups: ["source.toolkit.fluxcd.io"] From ba7c729066424e0bb88e4e5baedebc63902e600c Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Wed, 11 Feb 2026 22:02:58 +0100 Subject: [PATCH 238/889] [installer] Merge operator templates into single variant-based template Consolidate three separate operator deployment templates (talos, generic, hosted) into a single template with variant selection via values.yaml parameter. This resolves conflicts when running `make apply` from the installer package, which previously rendered all three deployments. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- Makefile | 6 +- .../templates/cozystack-operator-generic.yaml | 90 ------------------- .../templates/cozystack-operator-hosted.yaml | 77 ---------------- .../templates/cozystack-operator.yaml | 21 +++++ packages/core/installer/values.yaml | 2 + 5 files changed, 27 insertions(+), 169 deletions(-) delete mode 100644 packages/core/installer/templates/cozystack-operator-generic.yaml delete mode 100644 packages/core/installer/templates/cozystack-operator-hosted.yaml diff --git a/Makefile b/Makefile index f658747f..25a9cc25 100644 --- a/Makefile +++ b/Makefile @@ -48,13 +48,15 @@ manifests: > _out/assets/cozystack-operator.yaml # Generic Kubernetes variant (k3s, kubeadm, RKE2) helm template installer packages/core/installer -n cozy-system \ - -s templates/cozystack-operator-generic.yaml \ + -s templates/cozystack-operator.yaml \ -s templates/packagesource.yaml \ + --set cozystackOperator.variant=generic \ > _out/assets/cozystack-operator-generic.yaml # Hosted variant (managed Kubernetes) helm template installer packages/core/installer -n cozy-system \ - -s templates/cozystack-operator-hosted.yaml \ + -s templates/cozystack-operator.yaml \ -s templates/packagesource.yaml \ + --set cozystackOperator.variant=hosted \ > _out/assets/cozystack-operator-hosted.yaml cozypkg: diff --git a/packages/core/installer/templates/cozystack-operator-generic.yaml b/packages/core/installer/templates/cozystack-operator-generic.yaml deleted file mode 100644 index 510a92c8..00000000 --- a/packages/core/installer/templates/cozystack-operator-generic.yaml +++ /dev/null @@ -1,90 +0,0 @@ ---- -apiVersion: v1 -kind: Namespace -metadata: - name: cozy-system - labels: - cozystack.io/system: "true" - pod-security.kubernetes.io/enforce: privileged ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: cozystack - namespace: cozy-system ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: cozystack -subjects: -- kind: ServiceAccount - name: cozystack - namespace: cozy-system -roleRef: - kind: ClusterRole - name: cluster-admin - apiGroup: rbac.authorization.k8s.io ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: cozystack-operator - namespace: cozy-system -spec: - replicas: 1 - selector: - matchLabels: - app: cozystack-operator - strategy: - type: RollingUpdate - rollingUpdate: - maxSurge: 0 - maxUnavailable: 1 - template: - metadata: - labels: - app: cozystack-operator - spec: - serviceAccountName: cozystack - containers: - - name: cozystack-operator - image: "{{ .Values.cozystackOperator.image }}" - args: - - --leader-elect=true - - --install-flux=true - - --metrics-bind-address=0 - - --health-probe-bind-address= - {{- if .Values.cozystackOperator.disableTelemetry }} - - --disable-telemetry - {{- end }} - - --platform-source-name=cozystack-platform - - --platform-source-url={{ .Values.cozystackOperator.platformSourceUrl }} - {{- if .Values.cozystackOperator.platformSourceRef }} - - --platform-source-ref={{ .Values.cozystackOperator.platformSourceRef }} - {{- end }} - env: - # Generic Kubernetes: read from ConfigMap - # Create cozystack-operator-config ConfigMap before applying this manifest - - name: KUBERNETES_SERVICE_HOST - valueFrom: - configMapKeyRef: - name: cozystack-operator-config - key: KUBERNETES_SERVICE_HOST - optional: false - - name: KUBERNETES_SERVICE_PORT - valueFrom: - configMapKeyRef: - name: cozystack-operator-config - key: KUBERNETES_SERVICE_PORT - optional: false - hostNetwork: true - tolerations: - - key: "node.kubernetes.io/not-ready" - operator: "Exists" - - key: "node.kubernetes.io/unreachable" - operator: "Exists" - - key: "node.cilium.io/agent-not-ready" - operator: "Exists" - - key: "node.cloudprovider.kubernetes.io/uninitialized" - operator: "Exists" diff --git a/packages/core/installer/templates/cozystack-operator-hosted.yaml b/packages/core/installer/templates/cozystack-operator-hosted.yaml deleted file mode 100644 index 38ccdf75..00000000 --- a/packages/core/installer/templates/cozystack-operator-hosted.yaml +++ /dev/null @@ -1,77 +0,0 @@ ---- -apiVersion: v1 -kind: Namespace -metadata: - name: cozy-system - labels: - cozystack.io/system: "true" - pod-security.kubernetes.io/enforce: privileged ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: cozystack - namespace: cozy-system ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: cozystack -subjects: -- kind: ServiceAccount - name: cozystack - namespace: cozy-system -roleRef: - kind: ClusterRole - name: cluster-admin - apiGroup: rbac.authorization.k8s.io ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: cozystack-operator - namespace: cozy-system -spec: - replicas: 1 - selector: - matchLabels: - app: cozystack-operator - strategy: - type: RollingUpdate - rollingUpdate: - maxSurge: 0 - maxUnavailable: 1 - template: - metadata: - labels: - app: cozystack-operator - spec: - serviceAccountName: cozystack - containers: - - name: cozystack-operator - image: "{{ .Values.cozystackOperator.image }}" - args: - - --leader-elect=true - - --install-flux=true - - --metrics-bind-address=0 - - --health-probe-bind-address= - {{- if .Values.cozystackOperator.disableTelemetry }} - - --disable-telemetry - {{- end }} - - --platform-source-name=cozystack-platform - - --platform-source-url={{ .Values.cozystackOperator.platformSourceUrl }} - {{- if .Values.cozystackOperator.platformSourceRef }} - - --platform-source-ref={{ .Values.cozystackOperator.platformSourceRef }} - {{- end }} - # Hosted: use in-cluster service account, no env override needed - env: [] - hostNetwork: true - tolerations: - - key: "node.kubernetes.io/not-ready" - operator: "Exists" - - key: "node.kubernetes.io/unreachable" - operator: "Exists" - - key: "node.cilium.io/agent-not-ready" - operator: "Exists" - - key: "node.cloudprovider.kubernetes.io/uninitialized" - operator: "Exists" diff --git a/packages/core/installer/templates/cozystack-operator.yaml b/packages/core/installer/templates/cozystack-operator.yaml index c40dc663..96ce9b87 100644 --- a/packages/core/installer/templates/cozystack-operator.yaml +++ b/packages/core/installer/templates/cozystack-operator.yaml @@ -63,12 +63,33 @@ spec: {{- if .Values.cozystackOperator.platformSourceRef }} - --platform-source-ref={{ .Values.cozystackOperator.platformSourceRef }} {{- end }} + {{- if eq .Values.cozystackOperator.variant "talos" }} env: # Talos KubePrism endpoint - name: KUBERNETES_SERVICE_HOST value: "localhost" - name: KUBERNETES_SERVICE_PORT value: "7445" + {{- else if eq .Values.cozystackOperator.variant "generic" }} + env: + # Generic Kubernetes: read from ConfigMap + # Create cozystack-operator-config ConfigMap before applying this manifest + - name: KUBERNETES_SERVICE_HOST + valueFrom: + configMapKeyRef: + name: cozystack-operator-config + key: KUBERNETES_SERVICE_HOST + optional: false + - name: KUBERNETES_SERVICE_PORT + valueFrom: + configMapKeyRef: + name: cozystack-operator-config + key: KUBERNETES_SERVICE_PORT + optional: false + {{- else if eq .Values.cozystackOperator.variant "hosted" }} + # Hosted: use in-cluster service account, no env override needed + env: [] + {{- end }} hostNetwork: true tolerations: - key: "node.kubernetes.io/not-ready" diff --git a/packages/core/installer/values.yaml b/packages/core/installer/values.yaml index 1bfa2040..e9de3f13 100644 --- a/packages/core/installer/values.yaml +++ b/packages/core/installer/values.yaml @@ -1,4 +1,6 @@ cozystackOperator: + # Deployment variant: talos, generic, hosted + variant: talos image: ghcr.io/cozystack/cozystack/cozystack-operator:v1.0.0-beta.3@sha256:3cacecfc318e8314300bba8538b475fb8d50b2bf3106533faa8d942e4ed14448 platformSourceUrl: 'oci://ghcr.io/cozystack/cozystack/cozystack-packages' platformSourceRef: 'digest=sha256:e5ddcf5a02d17b17fc7ffa8c87ddeaa25a01d928a63fb297f6b25b669bb6b7c7' From 3971e9cb392560f55441aad27e5c80e0c6343388 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Wed, 11 Feb 2026 22:05:37 +0100 Subject: [PATCH 239/889] [installer] Rename talos asset to cozystack-operator-talos.yaml Add -talos suffix to the default variant output file for consistency with -generic and -hosted variants. Update all references in CI workflows, e2e tests, upload scripts, and testing Makefile. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- .github/workflows/pull-requests.yaml | 6 +++--- Makefile | 2 +- hack/e2e-install-cozystack.bats | 6 +++--- hack/upload-assets.sh | 2 +- packages/core/testing/Makefile | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/pull-requests.yaml b/.github/workflows/pull-requests.yaml index 73efc4b6..edc8b923 100644 --- a/.github/workflows/pull-requests.yaml +++ b/.github/workflows/pull-requests.yaml @@ -81,7 +81,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: cozystack-operator - path: _out/assets/cozystack-operator.yaml + path: _out/assets/cozystack-operator-talos.yaml - name: Upload Talos image uses: actions/upload-artifact@v4 @@ -140,7 +140,7 @@ jobs: } const find = (n) => draft.assets.find(a => a.name === n)?.id; const crdsId = find('cozystack-crds.yaml'); - const operatorId = find('cozystack-operator.yaml'); + const operatorId = find('cozystack-operator-talos.yaml'); const diskId = find('nocloud-amd64.raw.xz'); if (!crdsId || !operatorId || !diskId) { core.setFailed('Required assets missing in draft release'); @@ -212,7 +212,7 @@ jobs: -o _out/assets/cozystack-crds.yaml \ "https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/assets/${{ needs.resolve_assets.outputs.crds_id }}" curl -sSL -H "Authorization: token ${GH_PAT}" -H "Accept: application/octet-stream" \ - -o _out/assets/cozystack-operator.yaml \ + -o _out/assets/cozystack-operator-talos.yaml \ "https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/assets/${{ needs.resolve_assets.outputs.operator_id }}" env: GH_PAT: ${{ secrets.GH_PAT }} diff --git a/Makefile b/Makefile index 25a9cc25..9b3b217c 100644 --- a/Makefile +++ b/Makefile @@ -45,7 +45,7 @@ manifests: helm template installer packages/core/installer -n cozy-system \ -s templates/cozystack-operator.yaml \ -s templates/packagesource.yaml \ - > _out/assets/cozystack-operator.yaml + > _out/assets/cozystack-operator-talos.yaml # Generic Kubernetes variant (k3s, kubeadm, RKE2) helm template installer packages/core/installer -n cozy-system \ -s templates/cozystack-operator.yaml \ diff --git a/hack/e2e-install-cozystack.bats b/hack/e2e-install-cozystack.bats index 4a549c4b..1abb691f 100644 --- a/hack/e2e-install-cozystack.bats +++ b/hack/e2e-install-cozystack.bats @@ -5,8 +5,8 @@ echo "Missing: _out/assets/cozystack-crds.yaml" >&2 exit 1 fi - if [ ! -f _out/assets/cozystack-operator.yaml ]; then - echo "Missing: _out/assets/cozystack-operator.yaml" >&2 + if [ ! -f _out/assets/cozystack-operator-talos.yaml ]; then + echo "Missing: _out/assets/cozystack-operator-talos.yaml" >&2 exit 1 fi } @@ -17,7 +17,7 @@ # Apply installer manifests (CRDs + operator) kubectl apply -f _out/assets/cozystack-crds.yaml - kubectl apply -f _out/assets/cozystack-operator.yaml + kubectl apply -f _out/assets/cozystack-operator-talos.yaml # Wait for the operator deployment to become available kubectl wait deployment/cozystack-operator -n cozy-system --timeout=1m --for=condition=Available diff --git a/hack/upload-assets.sh b/hack/upload-assets.sh index e846261c..f9bb7b25 100755 --- a/hack/upload-assets.sh +++ b/hack/upload-assets.sh @@ -4,7 +4,7 @@ set -xe version=${VERSION:-$(git describe --tags)} gh release upload --clobber $version _out/assets/cozystack-crds.yaml -gh release upload --clobber $version _out/assets/cozystack-operator.yaml +gh release upload --clobber $version _out/assets/cozystack-operator-talos.yaml gh release upload --clobber $version _out/assets/cozystack-operator-generic.yaml gh release upload --clobber $version _out/assets/cozystack-operator-hosted.yaml gh release upload --clobber $version _out/assets/metal-amd64.iso diff --git a/packages/core/testing/Makefile b/packages/core/testing/Makefile index baccbfce..2fb41eba 100644 --- a/packages/core/testing/Makefile +++ b/packages/core/testing/Makefile @@ -32,7 +32,7 @@ copy-nocloud-image: copy-installer-manifest: docker cp ../../../_out/assets/cozystack-crds.yaml "${SANDBOX_NAME}":/workspace/_out/assets/cozystack-crds.yaml - docker cp ../../../_out/assets/cozystack-operator.yaml "${SANDBOX_NAME}":/workspace/_out/assets/cozystack-operator.yaml + docker cp ../../../_out/assets/cozystack-operator-talos.yaml "${SANDBOX_NAME}":/workspace/_out/assets/cozystack-operator-talos.yaml prepare-cluster: copy-nocloud-image docker exec "${SANDBOX_NAME}" sh -c 'cd /workspace && hack/cozytest.sh hack/e2e-prepare-cluster.bats' From f61c8f9859381bcf41466c8fffa7c9c82576af7c Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Wed, 11 Feb 2026 22:39:35 +0100 Subject: [PATCH 240/889] [platform] Clean up Helm secrets for removed -rd releases in migration 23 Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- packages/core/platform/images/migrations/migrations/23 | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/core/platform/images/migrations/migrations/23 b/packages/core/platform/images/migrations/migrations/23 index 4e8c8e18..7cb9c85e 100755 --- a/packages/core/platform/images/migrations/migrations/23 +++ b/packages/core/platform/images/migrations/migrations/23 @@ -6,6 +6,13 @@ set -euo pipefail # Remove old cozystack-resource-definition-crd HelmRelease (renamed to application-definition-crd) kubectl delete hr -n cozy-system cozystack-resource-definition-crd --ignore-not-found +kubectl delete secrets -n cozy-system $( + kubectl get secrets -n cozy-system \ + -l owner=helm \ + -o jsonpath='{range .items[*]}{.metadata.labels.name}{" "}{.metadata.name}{"\n"}{end}' \ + | awk '$1 ~ /-rd$/ {print $2}' +) --ignore-not-found + # Stamp version kubectl create configmap -n cozy-system cozystack-version \ --from-literal=version=24 --dry-run=client -o yaml | kubectl apply -f- From 4e804e0f86d0cf534f9d69b910dea8c1fab5860b Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Wed, 11 Feb 2026 22:54:16 +0100 Subject: [PATCH 241/889] Move CRDs installation logic to piraeus-operator-crds chart Update piraeus-operator Makefile to move CRDs template into piraeus-operator-crds chart on update, and add installCRDs toggle. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- packages/system/piraeus-operator-crds/templates/crds.yaml | 2 ++ packages/system/piraeus-operator-crds/values.yaml | 2 +- packages/system/piraeus-operator/Makefile | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/system/piraeus-operator-crds/templates/crds.yaml b/packages/system/piraeus-operator-crds/templates/crds.yaml index 996674b3..5bd9212c 100644 --- a/packages/system/piraeus-operator-crds/templates/crds.yaml +++ b/packages/system/piraeus-operator-crds/templates/crds.yaml @@ -1,3 +1,4 @@ + {{ if .Values.installCRDs }} --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition @@ -2169,3 +2170,4 @@ spec: storage: true subresources: status: {} +{{ end }} diff --git a/packages/system/piraeus-operator-crds/values.yaml b/packages/system/piraeus-operator-crds/values.yaml index 0967ef42..1b4551cc 100644 --- a/packages/system/piraeus-operator-crds/values.yaml +++ b/packages/system/piraeus-operator-crds/values.yaml @@ -1 +1 @@ -{} +installCRDs: true diff --git a/packages/system/piraeus-operator/Makefile b/packages/system/piraeus-operator/Makefile index b46680f2..7e507f63 100644 --- a/packages/system/piraeus-operator/Makefile +++ b/packages/system/piraeus-operator/Makefile @@ -8,3 +8,4 @@ update: tag=$$(git ls-remote --tags --sort="v:refname" https://github.com/piraeusdatastore/piraeus-operator | awk -F'[/^]' 'END{print $$3}') && \ curl -sSL https://github.com/piraeusdatastore/piraeus-operator/archive/refs/tags/$${tag}.tar.gz | \ tar xzvf - --strip 1 piraeus-operator-$${tag#*v}/charts + mv charts/piraeus/templates/crds.yaml ../piraeus-operator-crds/templates/crds.yaml From 67f981837085a04d826ae3cb48f7b63d09284f16 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Wed, 11 Feb 2026 22:59:11 +0100 Subject: [PATCH 242/889] [linstor] Add migration to reassign Piraeus CRDs to piraeus-operator-crds chart Migrate Helm ownership labels and annotations on Piraeus CRDs from piraeus-operator to piraeus-operator-crds, and delete stale Helm secrets to prevent piraeus-operator from removing CRDs on upgrade. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- .../core/platform/images/migrations/migrations/27 | 15 +++++++++++++++ packages/core/platform/values.yaml | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100755 packages/core/platform/images/migrations/migrations/27 diff --git a/packages/core/platform/images/migrations/migrations/27 b/packages/core/platform/images/migrations/migrations/27 new file mode 100755 index 00000000..3e8719e9 --- /dev/null +++ b/packages/core/platform/images/migrations/migrations/27 @@ -0,0 +1,15 @@ +#!/bin/sh +# Migration 27 --> 28 + +set -euo pipefail + +# Migrate Piraeus CRDs to piraeus-operator-crds Helm release +for crd in linstorclusters.piraeus.io linstornodeconnections.piraeus.io linstorsatelliteconfigurations.piraeus.io linstorsatellites.piraeus.io; do + kubectl annotate crd "$crd" meta.helm.sh/release-namespace=cozy-linstor meta.helm.sh/release-name=piraeus-operator-crds --overwrite + kubectl label crd "$crd" app.kubernetes.io/managed-by=Helm helm.toolkit.fluxcd.io/namespace=cozy-linstor helm.toolkit.fluxcd.io/name=piraeus-operator-crds --overwrite +done +kubectl delete secret -n cozy-linstor -l name=piraeus-operator,owner=helm --ignore-not-found + +# Stamp version +kubectl create configmap -n cozy-system cozystack-version \ + --from-literal=version=28 --dry-run=client -o yaml | kubectl apply -f- diff --git a/packages/core/platform/values.yaml b/packages/core/platform/values.yaml index 62bb9c69..f923c22d 100644 --- a/packages/core/platform/values.yaml +++ b/packages/core/platform/values.yaml @@ -6,7 +6,7 @@ sourceRef: migrations: enabled: false image: ghcr.io/cozystack/cozystack/platform-migrations:v1.0.0-beta.3@sha256:1ab28def39c9cb0233a03708ce00c9694edc9728789eef59abdc1d72bbe9b10a - targetVersion: 27 + targetVersion: 28 # Bundle deployment configuration bundles: system: From b8ccdedbf8929ca1a9f38ee5178c23563d08b129 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 12 Feb 2026 00:14:37 +0100 Subject: [PATCH 243/889] [monitoring] Fix YAML parse error in monitoring-agents vmagent template Replace unrendered Helm template expressions in values.yaml with global.target parameter. Pass tenant-root from platform bundle, default to cozy-monitoring. Use tpl in vmagent template to render URLs. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- .../core/platform/templates/bundles/system.yaml | 3 ++- .../monitoring-agents/templates/vmagent.yaml | 4 ++-- packages/system/monitoring-agents/values.yaml | 16 +++++++++------- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/core/platform/templates/bundles/system.yaml b/packages/core/platform/templates/bundles/system.yaml index 2d8b7e33..c24726ef 100644 --- a/packages/core/platform/templates/bundles/system.yaml +++ b/packages/core/platform/templates/bundles/system.yaml @@ -136,7 +136,8 @@ {{include "cozystack.platform.package.optional.default" (list "cozystack.velero" $) }} {{include "cozystack.platform.package.default" (list "cozystack.vertical-pod-autoscaler" $) }} {{include "cozystack.platform.package.default" (list "cozystack.metrics-server" $) }} -{{include "cozystack.platform.package.default" (list "cozystack.monitoring-agents" $) }} +{{- $monitoringAgentsComponents := dict "monitoring-agents" (dict "values" (dict "global" (dict "target" "tenant-root"))) -}} +{{include "cozystack.platform.package" (list "cozystack.monitoring-agents" "default" $ $monitoringAgentsComponents) }} {{include "cozystack.platform.package.default" (list "cozystack.goldpinger" $) }} {{include "cozystack.platform.package.default" (list "cozystack.prometheus-operator-crds" $) }} {{include "cozystack.platform.package.default" (list "cozystack.grafana-operator" $) }} diff --git a/packages/system/monitoring-agents/templates/vmagent.yaml b/packages/system/monitoring-agents/templates/vmagent.yaml index 56b756e7..00ab63c1 100644 --- a/packages/system/monitoring-agents/templates/vmagent.yaml +++ b/packages/system/monitoring-agents/templates/vmagent.yaml @@ -10,12 +10,12 @@ spec: shardCount: 1 externalLabels: cluster: {{ .Values.vmagent.externalLabels.cluster }} - tenant: {{ .Values.vmagent.externalLabels.tenant }} + tenant: {{ .Values.global.target }} extraArgs: {{- toYaml (deepCopy .Values.vmagent.extraArgs | mergeOverwrite (fromYaml (include "monitoring-agents.vmagent.defaultExtraArgs" .))) | nindent 4 }} remoteWrite: {{- range .Values.vmagent.remoteWrite.urls }} - - url: {{ . | quote }} + - url: {{ tpl . $ | quote }} {{- end }} scrapeInterval: 30s diff --git a/packages/system/monitoring-agents/values.yaml b/packages/system/monitoring-agents/values.yaml index 3b79de95..a4d4b025 100644 --- a/packages/system/monitoring-agents/values.yaml +++ b/packages/system/monitoring-agents/values.yaml @@ -1,3 +1,6 @@ +global: + target: cozy-monitoring + kube-state-metrics: extraArgs: - --metric-labels-allowlist=pods=[*],deployments=[*] @@ -275,11 +278,10 @@ kube-state-metrics: vmagent: externalLabels: cluster: cozystack - tenant: '{{ .Release.Namespace }}' remoteWrite: urls: - - 'http://vminsert-shortterm.{{ .Release.Namespace }}.svc:8480/insert/0/prometheus' - - 'http://vminsert-longterm.{{ .Release.Namespace }}.svc:8480/insert/0/prometheus' + - http://vminsert-shortterm.{{ .Values.global.target }}.svc:8480/insert/0/prometheus + - http://vminsert-longterm.{{ .Values.global.target }}.svc:8480/insert/0/prometheus extraArgs: {} fluent-bit: @@ -342,7 +344,7 @@ fluent-bit: [OUTPUT] Name http Match kube.* - Host vlogs-generic.{{ .Release.Namespace }}.svc + Host vlogs-generic.{{ .Values.global.target }}.svc port 9428 compress gzip uri /insert/jsonline?_stream_fields=log_source,stream,kubernetes_pod_name,kubernetes_container_name,kubernetes_namespace_name&_msg_field=log&_time_field=date @@ -353,7 +355,7 @@ fluent-bit: [OUTPUT] Name http Match events.* - Host vlogs-generic.{{ .Release.Namespace }}.svc + Host vlogs-generic.{{ .Values.global.target }}.svc port 9428 compress gzip uri /insert/jsonline?_stream_fields=log_source,reason,meatdata_namespace,metadata_name&_msg_field=message&_time_field=date @@ -364,7 +366,7 @@ fluent-bit: [OUTPUT] Name http Match audit.* - Host vlogs-generic.{{ .Release.Namespace }}.svc + Host vlogs-generic.{{ .Values.global.target }}.svc port 9428 compress gzip uri /insert/jsonline?_stream_fields=log_source,stage,user_username,verb,requestUri&_msg_field=requestURI&_time_field=date @@ -416,7 +418,7 @@ fluent-bit: [FILTER] Name modify Match * - Add tenant {{ .Release.Namespace }} + Add tenant {{ .Values.global.target }} [FILTER] Name modify Match * From fb8157ef9bb07e957ab8660c78cfcf0dc857b9b7 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Thu, 12 Feb 2026 13:52:37 +0300 Subject: [PATCH 244/889] refactor(api): remove rootHost-based name length validation Root-host validation for Tenant names is no longer needed here. The underlying issue (namespace.cozystack.io/host label exceeding 63-char limit) will be addressed in #2002 by moving the label to an annotation. Name length validation now only checks the Helm release name limit (53 - prefix length), which applies uniformly to all application types. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- pkg/apis/apps/validation/validation.go | 2 +- pkg/apiserver/apiserver.go | 2 +- pkg/cmd/server/start.go | 41 -------- pkg/cmd/server/start_test.go | 97 ------------------- pkg/config/config.go | 1 - pkg/registry/apps/application/rest.go | 42 ++------ .../apps/application/rest_validation_test.go | 91 +---------------- 7 files changed, 14 insertions(+), 262 deletions(-) diff --git a/pkg/apis/apps/validation/validation.go b/pkg/apis/apps/validation/validation.go index 28810b72..273873a4 100644 --- a/pkg/apis/apps/validation/validation.go +++ b/pkg/apis/apps/validation/validation.go @@ -25,7 +25,7 @@ import ( // This is required because Application names are used to create Kubernetes resources // (Services, Namespaces, etc.) that must have DNS-1035 compliant names. // Note: length validation is handled separately by validateNameLength in the REST -// handler, which computes dynamic limits based on Helm release prefix and root-host. +// handler, which computes dynamic limits based on Helm release prefix. func ValidateApplicationName(name string, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index aba9a345..bc2b4857 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -208,7 +208,7 @@ func (c completedConfig) New() (*CozyServer, error) { // --- dynamically-configured, per-tenant resources --- appsV1alpha1Storage := map[string]rest.Storage{} for _, resConfig := range c.ResourceConfig.Resources { - storage := applicationstorage.NewREST(cli, watchCli, &resConfig, c.ResourceConfig.RootHost) + storage := applicationstorage.NewREST(cli, watchCli, &resConfig) appsV1alpha1Storage[resConfig.Application.Plural] = cozyregistry.RESTInPeace(storage) } appsApiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(apps.GroupName, Scheme, metav1.ParameterCodec, Codecs) diff --git a/pkg/cmd/server/start.go b/pkg/cmd/server/start.go index 09c9485c..9a688700 100644 --- a/pkg/cmd/server/start.go +++ b/pkg/cmd/server/start.go @@ -42,11 +42,9 @@ import ( utilfeature "k8s.io/apiserver/pkg/util/feature" basecompatibility "k8s.io/component-base/compatibility" baseversion "k8s.io/component-base/version" - "k8s.io/klog/v2" netutils "k8s.io/utils/net" "sigs.k8s.io/controller-runtime/pkg/client" k8sconfig "sigs.k8s.io/controller-runtime/pkg/client/config" - "sigs.k8s.io/yaml" ) // CozyServerOptions holds the state for the Cozy API server @@ -185,48 +183,9 @@ func (o *CozyServerOptions) Complete() error { o.ResourceConfig.Resources = append(o.ResourceConfig.Resources, resource) } - // Read root-host from cozystack-values secret (best-effort, non-fatal) - secret := &corev1.Secret{} - err = o.Client.Get(context.Background(), client.ObjectKey{ - Namespace: "cozy-system", - Name: "cozystack-values", - }, secret) - if err != nil { - klog.Warningf("failed to read cozystack-values secret: %v", err) - } else { - o.ResourceConfig.RootHost = parseRootHostFromSecret(secret) - if o.ResourceConfig.RootHost != "" { - klog.Infof("Loaded root-host: %s", o.ResourceConfig.RootHost) - } - } - return nil } -// parseRootHostFromSecret extracts _cluster.root-host from the cozystack-values secret. -func parseRootHostFromSecret(secret *corev1.Secret) string { - if secret == nil || secret.Data == nil { - return "" - } - - valuesYAML, ok := secret.Data["values.yaml"] - if !ok || len(valuesYAML) == 0 { - return "" - } - - var values struct { - Cluster struct { - RootHost string `json:"root-host"` - } `json:"_cluster"` - } - if err := yaml.Unmarshal(valuesYAML, &values); err != nil { - klog.Warningf("failed to parse values.yaml from cozystack-values secret: %v", err) - return "" - } - - return values.Cluster.RootHost -} - // Validate checks the correctness of the options func (o CozyServerOptions) Validate(args []string) error { var allErrors []error diff --git a/pkg/cmd/server/start_test.go b/pkg/cmd/server/start_test.go index 3a43df72..9dc2000c 100644 --- a/pkg/cmd/server/start_test.go +++ b/pkg/cmd/server/start_test.go @@ -15,100 +15,3 @@ limitations under the License. */ package server - -import ( - "testing" - - corev1 "k8s.io/api/core/v1" -) - -func TestParseRootHostFromSecret(t *testing.T) { - tests := []struct { - name string - secret *corev1.Secret - expected string - }{ - { - name: "valid secret with root-host", - secret: &corev1.Secret{ - Data: map[string][]byte{ - "values.yaml": []byte("_cluster:\n root-host: \"example.com\"\n bundle-name: \"paas-full\"\n"), - }, - }, - expected: "example.com", - }, - { - name: "valid secret with unquoted root-host", - secret: &corev1.Secret{ - Data: map[string][]byte{ - "values.yaml": []byte("_cluster:\n root-host: my.domain.org\n"), - }, - }, - expected: "my.domain.org", - }, - { - name: "missing values.yaml key", - secret: &corev1.Secret{ - Data: map[string][]byte{ - "other-key": []byte("some data"), - }, - }, - expected: "", - }, - { - name: "malformed YAML", - secret: &corev1.Secret{ - Data: map[string][]byte{ - "values.yaml": []byte("not: valid: yaml: [[["), - }, - }, - expected: "", - }, - { - name: "empty root-host", - secret: &corev1.Secret{ - Data: map[string][]byte{ - "values.yaml": []byte("_cluster:\n root-host: \"\"\n"), - }, - }, - expected: "", - }, - { - name: "no _cluster key", - secret: &corev1.Secret{ - Data: map[string][]byte{ - "values.yaml": []byte("other:\n key: value\n"), - }, - }, - expected: "", - }, - { - name: "_cluster without root-host", - secret: &corev1.Secret{ - Data: map[string][]byte{ - "values.yaml": []byte("_cluster:\n bundle-name: \"paas-full\"\n"), - }, - }, - expected: "", - }, - { - name: "nil data", - secret: &corev1.Secret{}, - expected: "", - }, - { - name: "nil secret", - secret: nil, - expected: "", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := parseRootHostFromSecret(tt.secret) - if result != tt.expected { - t.Errorf("parseRootHostFromSecret() = %q, want %q", result, tt.expected) - } - }) - } -} diff --git a/pkg/config/config.go b/pkg/config/config.go index 260d149a..1e123e2c 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -19,7 +19,6 @@ package config // ResourceConfig represents the structure of the configuration file. type ResourceConfig struct { Resources []Resource `yaml:"resources"` - RootHost string // cluster root host from cozystack-values secret } // Resource describes an individual resource. diff --git a/pkg/registry/apps/application/rest.go b/pkg/registry/apps/application/rest.go index fcbcfcef..7871bbe7 100644 --- a/pkg/registry/apps/application/rest.go +++ b/pkg/registry/apps/application/rest.go @@ -91,11 +91,10 @@ type REST struct { singularName string releaseConfig config.ReleaseConfig specSchema *structuralschema.Structural - rootHost string } // NewREST creates a new REST storage for Application with specific configuration -func NewREST(c client.Client, w client.WithWatch, config *config.Resource, rootHost string) *REST { +func NewREST(c client.Client, w client.WithWatch, config *config.Resource) *REST { var specSchema *structuralschema.Structural if raw := strings.TrimSpace(config.Application.OpenAPISchema); raw != "" { @@ -134,7 +133,6 @@ func NewREST(c client.Client, w client.WithWatch, config *config.Resource, rootH singularName: config.Application.Singular, releaseConfig: config.Release, specSchema: specSchema, - rootHost: rootHost, } } @@ -1051,49 +1049,23 @@ func validateNoInternalKeys(values *apiextv1.JSON) error { // chart-generated resource suffixes within the 63-char DNS-1035 label limit. const maxHelmReleaseName = 53 -// maxK8sLabelValue is the maximum length of a Kubernetes label value. -const maxK8sLabelValue = 63 - // validateNameLength checks that the application name won't exceed Kubernetes limits. -// For all apps: prefix + name must fit within the Helm release name limit (53 chars). -// For Tenants: additionally checks that the host label (name + "." + rootHost) fits -// within the Kubernetes label value limit (63 chars). -// -// Note: rootHost is read once at API server startup from the cozystack-values Secret. -// Changing root-host requires an API server restart for validation to reflect the new value. +// prefix + name must fit within the Helm release name limit (53 chars). func (r *REST) validateNameLength(name string) field.ErrorList { fldPath := field.NewPath("metadata").Child("name") allErrs := field.ErrorList{} - helmMax := maxHelmReleaseName - len(r.releaseConfig.Prefix) - maxLen := helmMax - - if r.kindName == "Tenant" && r.rootHost != "" { - hostLabelMax := maxK8sLabelValue - len(r.rootHost) - 1 // -1 for the dot separator - if hostLabelMax < maxLen { - maxLen = hostLabelMax - } - } - - isTenantWithHost := r.kindName == "Tenant" && r.rootHost != "" + maxLen := maxHelmReleaseName - len(r.releaseConfig.Prefix) if maxLen <= 0 { - msg := fmt.Sprintf("configuration error: no valid name length possible (release prefix %q)", r.releaseConfig.Prefix) - if isTenantWithHost { - msg = fmt.Sprintf("configuration error: no valid name length possible (release prefix %q, root host %q)", - r.releaseConfig.Prefix, r.rootHost) - } - allErrs = append(allErrs, field.Invalid(fldPath, name, msg)) + allErrs = append(allErrs, field.Invalid(fldPath, name, + fmt.Sprintf("configuration error: no valid name length possible (release prefix %q)", r.releaseConfig.Prefix))) return allErrs } if len(name) > maxLen { - msg := fmt.Sprintf("must be no more than %d characters (release prefix %q)", maxLen, r.releaseConfig.Prefix) - if isTenantWithHost { - msg = fmt.Sprintf("must be no more than %d characters (release prefix %q, root host %q)", - maxLen, r.releaseConfig.Prefix, r.rootHost) - } - allErrs = append(allErrs, field.Invalid(fldPath, name, msg)) + allErrs = append(allErrs, field.Invalid(fldPath, name, + fmt.Sprintf("must be no more than %d characters (release prefix %q)", maxLen, r.releaseConfig.Prefix))) } return allErrs } diff --git a/pkg/registry/apps/application/rest_validation_test.go b/pkg/registry/apps/application/rest_validation_test.go index 07b78e1a..c5d331b2 100644 --- a/pkg/registry/apps/application/rest_validation_test.go +++ b/pkg/registry/apps/application/rest_validation_test.go @@ -28,111 +28,48 @@ func TestValidateNameLength(t *testing.T) { name string kindName string prefix string - rootHost string appName string wantError bool }{ { - name: "non-tenant short name passes", + name: "short name passes", kindName: "MySQL", prefix: "mysql-", - rootHost: "example.com", appName: "mydb", wantError: false, }, { - name: "non-tenant at helm boundary passes", + name: "at helm boundary passes", kindName: "MySQL", prefix: "mysql-", - rootHost: "example.com", appName: strings.Repeat("a", 53-len("mysql-")), // exactly 47 chars wantError: false, }, { - name: "non-tenant exceeding helm limit fails", + name: "exceeding helm limit fails", kindName: "MySQL", prefix: "mysql-", - rootHost: "example.com", appName: strings.Repeat("a", 53-len("mysql-")+1), // 48 chars wantError: true, }, { - name: "tenant no rootHost within helm limit passes", + name: "tenant within helm limit passes", kindName: "Tenant", prefix: "tenant-", - rootHost: "", appName: strings.Repeat("a", 53-len("tenant-")), // 46 chars wantError: false, }, { - name: "tenant no rootHost exceeding helm limit fails", + name: "tenant exceeding helm limit fails", kindName: "Tenant", prefix: "tenant-", - rootHost: "", appName: strings.Repeat("a", 53-len("tenant-")+1), // 47 chars wantError: true, }, - { - name: "tenant with rootHost within both limits passes", - kindName: "Tenant", - prefix: "tenant-", - rootHost: "example.com", // 11 chars → host label max = 63-11-1 = 51 - appName: "short", - wantError: false, - }, - { - name: "tenant with short rootHost helm limit is still stricter", - kindName: "Tenant", - prefix: "tenant-", - rootHost: "example.com", // 11 chars → host label max = 51, helm max = 46 - appName: strings.Repeat("a", 53-len("tenant-")), // 46 chars — at Helm boundary - wantError: false, - }, - { - name: "tenant with long rootHost at host label boundary passes", - kindName: "Tenant", - prefix: "tenant-", - rootHost: "long-subdomain.hosting.example.com", // 34 chars → host label max = 63-34-1 = 28 - appName: strings.Repeat("a", 63-len("long-subdomain.hosting.example.com")-1), // exactly 28 chars - wantError: false, - }, - { - name: "tenant with long rootHost exceeding host label limit fails", - kindName: "Tenant", - prefix: "tenant-", - rootHost: "long-subdomain.hosting.example.com", // 34 chars → host label max = 28 - appName: strings.Repeat("a", 63-len("long-subdomain.hosting.example.com")-1+1), // 29 chars - wantError: true, - }, - { - name: "tenant host label limit stricter than helm limit", - kindName: "Tenant", - prefix: "tenant-", - rootHost: "very-long-subdomain.hosting.example.com", // 39 chars → host label max = 63-39-1 = 23 - appName: strings.Repeat("a", 24), // 24 > 23 (host limit) but < 46 (helm limit) - wantError: true, - }, - { - name: "tenant short rootHost where helm limit is stricter", - kindName: "Tenant", - prefix: "tenant-", - rootHost: "x.co", // 4 chars → host label max = 63-4-1 = 58, helm max = 46 - appName: strings.Repeat("a", 53-len("tenant-")+1), // 47 > 46 (helm limit) but < 58 (host limit) - wantError: true, - }, - { - name: "non-tenant ignores rootHost for limit calculation", - kindName: "MySQL", - prefix: "mysql-", - rootHost: "very-long-subdomain.hosting.example.com", - appName: strings.Repeat("a", 53-len("mysql-")), // at helm boundary - wantError: false, - }, { name: "prefix consuming all helm capacity returns config error", kindName: "MySQL", prefix: strings.Repeat("x", 53), // prefix == maxHelmReleaseName → helmMax = 0 - rootHost: "", appName: "a", wantError: true, }, @@ -140,23 +77,6 @@ func TestValidateNameLength(t *testing.T) { name: "prefix exceeding helm capacity returns config error", kindName: "MySQL", prefix: strings.Repeat("x", 60), // prefix > maxHelmReleaseName → helmMax < 0 - rootHost: "", - appName: "a", - wantError: true, - }, - { - name: "tenant with rootHost consuming all label capacity returns config error", - kindName: "Tenant", - prefix: "t-", - rootHost: strings.Repeat("h", 62), // 62 chars → hostLabelMax = 63-62-1 = 0, helmMax = 51 - appName: "a", - wantError: true, - }, - { - name: "tenant with rootHost exceeding label capacity returns config error", - kindName: "Tenant", - prefix: "t-", - rootHost: strings.Repeat("h", 70), // 70 chars → hostLabelMax = 63-70-1 = -8, helmMax = 51 appName: "a", wantError: true, }, @@ -169,7 +89,6 @@ func TestValidateNameLength(t *testing.T) { releaseConfig: config.ReleaseConfig{ Prefix: tt.prefix, }, - rootHost: tt.rootHost, } errs := r.validateNameLength(tt.appName) From bce530011699e03eaa08fefd3f0945caed55c9c3 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 10 Feb 2026 19:11:22 +0100 Subject: [PATCH 245/889] refactor: rename mysql application to mariadb The mysql chart actually deploys MariaDB via mariadb-operator, but was incorrectly named "mysql". Rename all references to use the correct "mariadb" name across the codebase. Changes: - Rename packages/apps/mysql -> packages/apps/mariadb - Rename packages/system/mysql-rd -> packages/system/mariadb-rd - Rename platform source and bundle references - Update CRD kind from MySQL to MariaDB - Update RBAC, e2e tests, backup controller tests - Keep real MySQL CLI/config tool names unchanged (mysqldump, [mysqld], etc.) Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- Makefile | 2 +- api/backups/v1alpha1/DESIGN.md | 2 +- api/backups/v1alpha1/backupclass_types.go | 2 +- docs/agents/contributing.md | 2 +- hack/e2e-apps/mariadb.bats | 46 +++++++++++++++++++ hack/e2e-apps/mysql.bats | 46 ------------------- .../backupclass_resolver_test.go | 22 ++++----- .../factory/backupjob_test.go | 4 +- packages/apps/{mysql => mariadb}/.helmignore | 0 packages/apps/{mysql => mariadb}/Chart.yaml | 2 +- packages/apps/{mysql => mariadb}/Makefile | 0 packages/apps/{mysql => mariadb}/README.md | 2 +- .../apps/{mysql => mariadb}/charts/cozy-lib | 0 .../{mysql => mariadb}/files/versions.yaml | 0 .../hack/update-versions.sh | 6 +-- .../images/mariadb-backup.tag | 0 .../images/mariadb-backup/Dockerfile | 0 .../apps/{mysql => mariadb}/logos/mariadb.svg | 0 .../{mysql => mariadb}/templates/.gitkeep | 0 .../templates/_resources.tpl | 0 .../templates/_versions.tpl | 2 +- .../templates/backup-cronjob.yaml | 0 .../templates/backup-script.yaml | 0 .../templates/backup-secret.yaml | 0 .../{mysql => mariadb}/templates/config.yaml | 0 .../templates/dashboard-resourcemap.yaml | 0 .../apps/{mysql => mariadb}/templates/db.yaml | 0 .../templates/hooks/cleanup-pvc.yaml | 0 .../{mysql => mariadb}/templates/mariadb.yaml | 2 +- .../templates/regsecret.yaml | 0 .../{mysql => mariadb}/templates/secret.yaml | 0 .../{mysql => mariadb}/templates/user.yaml | 0 .../templates/workloadmonitor.yaml | 4 +- .../{mysql => mariadb}/values.schema.json | 2 +- packages/apps/{mysql => mariadb}/values.yaml | 2 +- ...lication.yaml => mariadb-application.yaml} | 12 ++--- .../core/platform/templates/bundles/paas.yaml | 2 +- .../templates/clusterroles.yaml | 2 +- .../{mysql-rd => mariadb-rd}/Chart.yaml | 2 +- .../system/{mysql-rd => mariadb-rd}/Makefile | 2 +- .../cozyrds/mariadb.yaml} | 24 +++++----- .../templates/cozyrd.yaml | 0 .../{mysql-rd => mariadb-rd}/values.yaml | 0 43 files changed, 96 insertions(+), 96 deletions(-) create mode 100644 hack/e2e-apps/mariadb.bats delete mode 100644 hack/e2e-apps/mysql.bats rename packages/apps/{mysql => mariadb}/.helmignore (100%) rename packages/apps/{mysql => mariadb}/Chart.yaml (93%) rename packages/apps/{mysql => mariadb}/Makefile (100%) rename packages/apps/{mysql => mariadb}/README.md (98%) rename packages/apps/{mysql => mariadb}/charts/cozy-lib (100%) rename packages/apps/{mysql => mariadb}/files/versions.yaml (100%) rename packages/apps/{mysql => mariadb}/hack/update-versions.sh (97%) rename packages/apps/{mysql => mariadb}/images/mariadb-backup.tag (100%) rename packages/apps/{mysql => mariadb}/images/mariadb-backup/Dockerfile (100%) rename packages/apps/{mysql => mariadb}/logos/mariadb.svg (100%) rename packages/apps/{mysql => mariadb}/templates/.gitkeep (100%) rename packages/apps/{mysql => mariadb}/templates/_resources.tpl (100%) rename packages/apps/{mysql => mariadb}/templates/_versions.tpl (89%) rename packages/apps/{mysql => mariadb}/templates/backup-cronjob.yaml (100%) rename packages/apps/{mysql => mariadb}/templates/backup-script.yaml (100%) rename packages/apps/{mysql => mariadb}/templates/backup-secret.yaml (100%) rename packages/apps/{mysql => mariadb}/templates/config.yaml (100%) rename packages/apps/{mysql => mariadb}/templates/dashboard-resourcemap.yaml (100%) rename packages/apps/{mysql => mariadb}/templates/db.yaml (100%) rename packages/apps/{mysql => mariadb}/templates/hooks/cleanup-pvc.yaml (100%) rename packages/apps/{mysql => mariadb}/templates/mariadb.yaml (97%) rename packages/apps/{mysql => mariadb}/templates/regsecret.yaml (100%) rename packages/apps/{mysql => mariadb}/templates/secret.yaml (100%) rename packages/apps/{mysql => mariadb}/templates/user.yaml (100%) rename packages/apps/{mysql => mariadb}/templates/workloadmonitor.yaml (88%) rename packages/apps/{mysql => mariadb}/values.schema.json (99%) rename packages/apps/{mysql => mariadb}/values.yaml (98%) rename packages/core/platform/sources/{mysql-application.yaml => mariadb-application.yaml} (71%) rename packages/system/{mysql-rd => mariadb-rd}/Chart.yaml (87%) rename packages/system/{mysql-rd => mariadb-rd}/Makefile (73%) rename packages/system/{mysql-rd/cozyrds/mysql.yaml => mariadb-rd/cozyrds/mariadb.yaml} (69%) rename packages/system/{mysql-rd => mariadb-rd}/templates/cozyrd.yaml (100%) rename packages/system/{mysql-rd => mariadb-rd}/values.yaml (100%) diff --git a/Makefile b/Makefile index 9b3b217c..e6c50458 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ build-deps: build: build-deps make -C packages/apps/http-cache image - make -C packages/apps/mysql image + make -C packages/apps/mariadb image make -C packages/apps/clickhouse image make -C packages/apps/kubernetes image make -C packages/system/monitoring image diff --git a/api/backups/v1alpha1/DESIGN.md b/api/backups/v1alpha1/DESIGN.md index bab753b2..7ba06fae 100644 --- a/api/backups/v1alpha1/DESIGN.md +++ b/api/backups/v1alpha1/DESIGN.md @@ -196,7 +196,7 @@ type ApplicationSelector struct { // +optional APIGroup *string `json:"apiGroup,omitempty"` - // Kind is the kind of the application (e.g., VirtualMachine, MySQL). + // Kind is the kind of the application (e.g., VirtualMachine, MariaDB). Kind string `json:"kind"` } ``` diff --git a/api/backups/v1alpha1/backupclass_types.go b/api/backups/v1alpha1/backupclass_types.go index 19bcfd52..2b2dc7ff 100644 --- a/api/backups/v1alpha1/backupclass_types.go +++ b/api/backups/v1alpha1/backupclass_types.go @@ -73,7 +73,7 @@ type ApplicationSelector struct { // +optional APIGroup *string `json:"apiGroup,omitempty"` - // Kind is the kind of the application (e.g., VirtualMachine, MySQL). + // Kind is the kind of the application (e.g., VirtualMachine, MariaDB). Kind string `json:"kind"` } diff --git a/docs/agents/contributing.md b/docs/agents/contributing.md index 4a2a7ef9..d5a4366e 100644 --- a/docs/agents/contributing.md +++ b/docs/agents/contributing.md @@ -27,7 +27,7 @@ git commit --signoff -m "[component] Brief description of changes" **Component prefixes:** - System: `[dashboard]`, `[platform]`, `[cilium]`, `[kube-ovn]`, `[linstor]`, `[fluxcd]`, `[cluster-api]` -- Apps: `[postgres]`, `[mysql]`, `[redis]`, `[kafka]`, `[clickhouse]`, `[virtual-machine]`, `[kubernetes]` +- Apps: `[postgres]`, `[mariadb]`, `[redis]`, `[kafka]`, `[clickhouse]`, `[virtual-machine]`, `[kubernetes]` - Other: `[tests]`, `[ci]`, `[docs]`, `[maintenance]` **Examples:** diff --git a/hack/e2e-apps/mariadb.bats b/hack/e2e-apps/mariadb.bats new file mode 100644 index 00000000..50e608ac --- /dev/null +++ b/hack/e2e-apps/mariadb.bats @@ -0,0 +1,46 @@ +#!/usr/bin/env bats + +@test "Create DB MariaDB" { + name='test' + kubectl apply -f- <` | diff --git a/packages/apps/mysql/charts/cozy-lib b/packages/apps/mariadb/charts/cozy-lib similarity index 100% rename from packages/apps/mysql/charts/cozy-lib rename to packages/apps/mariadb/charts/cozy-lib diff --git a/packages/apps/mysql/files/versions.yaml b/packages/apps/mariadb/files/versions.yaml similarity index 100% rename from packages/apps/mysql/files/versions.yaml rename to packages/apps/mariadb/files/versions.yaml diff --git a/packages/apps/mysql/hack/update-versions.sh b/packages/apps/mariadb/hack/update-versions.sh similarity index 97% rename from packages/apps/mysql/hack/update-versions.sh rename to packages/apps/mariadb/hack/update-versions.sh index 1b58591a..775c0327 100755 --- a/packages/apps/mysql/hack/update-versions.sh +++ b/packages/apps/mariadb/hack/update-versions.sh @@ -5,9 +5,9 @@ set -o nounset set -o pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -MYSQL_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" -VALUES_FILE="${MYSQL_DIR}/values.yaml" -VERSIONS_FILE="${MYSQL_DIR}/files/versions.yaml" +MARIADB_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +VALUES_FILE="${MARIADB_DIR}/values.yaml" +VERSIONS_FILE="${MARIADB_DIR}/files/versions.yaml" MARIADB_API_URL="https://downloads.mariadb.org/rest-api/mariadb/" # Check if jq is installed diff --git a/packages/apps/mysql/images/mariadb-backup.tag b/packages/apps/mariadb/images/mariadb-backup.tag similarity index 100% rename from packages/apps/mysql/images/mariadb-backup.tag rename to packages/apps/mariadb/images/mariadb-backup.tag diff --git a/packages/apps/mysql/images/mariadb-backup/Dockerfile b/packages/apps/mariadb/images/mariadb-backup/Dockerfile similarity index 100% rename from packages/apps/mysql/images/mariadb-backup/Dockerfile rename to packages/apps/mariadb/images/mariadb-backup/Dockerfile diff --git a/packages/apps/mysql/logos/mariadb.svg b/packages/apps/mariadb/logos/mariadb.svg similarity index 100% rename from packages/apps/mysql/logos/mariadb.svg rename to packages/apps/mariadb/logos/mariadb.svg diff --git a/packages/apps/mysql/templates/.gitkeep b/packages/apps/mariadb/templates/.gitkeep similarity index 100% rename from packages/apps/mysql/templates/.gitkeep rename to packages/apps/mariadb/templates/.gitkeep diff --git a/packages/apps/mysql/templates/_resources.tpl b/packages/apps/mariadb/templates/_resources.tpl similarity index 100% rename from packages/apps/mysql/templates/_resources.tpl rename to packages/apps/mariadb/templates/_resources.tpl diff --git a/packages/apps/mysql/templates/_versions.tpl b/packages/apps/mariadb/templates/_versions.tpl similarity index 89% rename from packages/apps/mysql/templates/_versions.tpl rename to packages/apps/mariadb/templates/_versions.tpl index cd4ee0e1..a896ea5f 100644 --- a/packages/apps/mysql/templates/_versions.tpl +++ b/packages/apps/mariadb/templates/_versions.tpl @@ -1,4 +1,4 @@ -{{- define "mysql.versionMap" }} +{{- define "mariadb.versionMap" }} {{- $versionMap := .Files.Get "files/versions.yaml" | fromYaml }} {{- if not (hasKey $versionMap .Values.version) }} {{- printf `MariaDB version %s is not supported, allowed versions are %s` $.Values.version (keys $versionMap) | fail }} diff --git a/packages/apps/mysql/templates/backup-cronjob.yaml b/packages/apps/mariadb/templates/backup-cronjob.yaml similarity index 100% rename from packages/apps/mysql/templates/backup-cronjob.yaml rename to packages/apps/mariadb/templates/backup-cronjob.yaml diff --git a/packages/apps/mysql/templates/backup-script.yaml b/packages/apps/mariadb/templates/backup-script.yaml similarity index 100% rename from packages/apps/mysql/templates/backup-script.yaml rename to packages/apps/mariadb/templates/backup-script.yaml diff --git a/packages/apps/mysql/templates/backup-secret.yaml b/packages/apps/mariadb/templates/backup-secret.yaml similarity index 100% rename from packages/apps/mysql/templates/backup-secret.yaml rename to packages/apps/mariadb/templates/backup-secret.yaml diff --git a/packages/apps/mysql/templates/config.yaml b/packages/apps/mariadb/templates/config.yaml similarity index 100% rename from packages/apps/mysql/templates/config.yaml rename to packages/apps/mariadb/templates/config.yaml diff --git a/packages/apps/mysql/templates/dashboard-resourcemap.yaml b/packages/apps/mariadb/templates/dashboard-resourcemap.yaml similarity index 100% rename from packages/apps/mysql/templates/dashboard-resourcemap.yaml rename to packages/apps/mariadb/templates/dashboard-resourcemap.yaml diff --git a/packages/apps/mysql/templates/db.yaml b/packages/apps/mariadb/templates/db.yaml similarity index 100% rename from packages/apps/mysql/templates/db.yaml rename to packages/apps/mariadb/templates/db.yaml diff --git a/packages/apps/mysql/templates/hooks/cleanup-pvc.yaml b/packages/apps/mariadb/templates/hooks/cleanup-pvc.yaml similarity index 100% rename from packages/apps/mysql/templates/hooks/cleanup-pvc.yaml rename to packages/apps/mariadb/templates/hooks/cleanup-pvc.yaml diff --git a/packages/apps/mysql/templates/mariadb.yaml b/packages/apps/mariadb/templates/mariadb.yaml similarity index 97% rename from packages/apps/mysql/templates/mariadb.yaml rename to packages/apps/mariadb/templates/mariadb.yaml index b4a888d8..8a530134 100644 --- a/packages/apps/mysql/templates/mariadb.yaml +++ b/packages/apps/mariadb/templates/mariadb.yaml @@ -8,7 +8,7 @@ spec: name: {{ .Release.Name }}-credentials key: root - image: "mariadb:{{ include "mysql.versionMap" $ }}" + image: "mariadb:{{ include "mariadb.versionMap" $ }}" port: 3306 diff --git a/packages/apps/mysql/templates/regsecret.yaml b/packages/apps/mariadb/templates/regsecret.yaml similarity index 100% rename from packages/apps/mysql/templates/regsecret.yaml rename to packages/apps/mariadb/templates/regsecret.yaml diff --git a/packages/apps/mysql/templates/secret.yaml b/packages/apps/mariadb/templates/secret.yaml similarity index 100% rename from packages/apps/mysql/templates/secret.yaml rename to packages/apps/mariadb/templates/secret.yaml diff --git a/packages/apps/mysql/templates/user.yaml b/packages/apps/mariadb/templates/user.yaml similarity index 100% rename from packages/apps/mysql/templates/user.yaml rename to packages/apps/mariadb/templates/user.yaml diff --git a/packages/apps/mysql/templates/workloadmonitor.yaml b/packages/apps/mariadb/templates/workloadmonitor.yaml similarity index 88% rename from packages/apps/mysql/templates/workloadmonitor.yaml rename to packages/apps/mariadb/templates/workloadmonitor.yaml index 9fc6d144..b69139bf 100644 --- a/packages/apps/mysql/templates/workloadmonitor.yaml +++ b/packages/apps/mariadb/templates/workloadmonitor.yaml @@ -6,8 +6,8 @@ metadata: spec: replicas: {{ .Values.replicas }} minReplicas: 1 - kind: mysql - type: mysql + kind: mariadb + type: mariadb selector: app.kubernetes.io/instance: {{ $.Release.Name }} version: {{ $.Chart.Version }} diff --git a/packages/apps/mysql/values.schema.json b/packages/apps/mariadb/values.schema.json similarity index 99% rename from packages/apps/mysql/values.schema.json rename to packages/apps/mariadb/values.schema.json index a36754b2..31c56378 100644 --- a/packages/apps/mysql/values.schema.json +++ b/packages/apps/mariadb/values.schema.json @@ -40,7 +40,7 @@ "s3Bucket": { "description": "S3 bucket used for storing backups.", "type": "string", - "default": "s3.example.org/mysql-backups" + "default": "s3.example.org/mariadb-backups" }, "s3Region": { "description": "AWS S3 region where backups are stored.", diff --git a/packages/apps/mysql/values.yaml b/packages/apps/mariadb/values.yaml similarity index 98% rename from packages/apps/mysql/values.yaml rename to packages/apps/mariadb/values.yaml index c7726b7a..6d629a83 100644 --- a/packages/apps/mysql/values.yaml +++ b/packages/apps/mariadb/values.yaml @@ -98,7 +98,7 @@ databases: {} backup: enabled: false s3Region: us-east-1 - s3Bucket: "s3.example.org/mysql-backups" + s3Bucket: "s3.example.org/mariadb-backups" schedule: "0 2 * * *" cleanupStrategy: "--keep-last=3 --keep-daily=3 --keep-within-weekly=1m" s3AccessKey: "" diff --git a/packages/core/platform/sources/mysql-application.yaml b/packages/core/platform/sources/mariadb-application.yaml similarity index 71% rename from packages/core/platform/sources/mysql-application.yaml rename to packages/core/platform/sources/mariadb-application.yaml index 4b22e36e..34ff901b 100644 --- a/packages/core/platform/sources/mysql-application.yaml +++ b/packages/core/platform/sources/mariadb-application.yaml @@ -2,7 +2,7 @@ apiVersion: cozystack.io/v1alpha1 kind: PackageSource metadata: - name: cozystack.mysql-application + name: cozystack.mariadb-application spec: sourceRef: kind: OCIRepository @@ -18,11 +18,11 @@ spec: - name: cozy-lib path: library/cozy-lib components: - - name: mysql - path: apps/mysql + - name: mariadb + path: apps/mariadb libraries: ["cozy-lib"] - - name: mysql-rd - path: system/mysql-rd + - name: mariadb-rd + path: system/mariadb-rd install: namespace: cozy-system - releaseName: mysql-rd + releaseName: mariadb-rd diff --git a/packages/core/platform/templates/bundles/paas.yaml b/packages/core/platform/templates/bundles/paas.yaml index 1b76a74d..5a478f9d 100644 --- a/packages/core/platform/templates/bundles/paas.yaml +++ b/packages/core/platform/templates/bundles/paas.yaml @@ -12,7 +12,7 @@ {{include "cozystack.platform.package.default" (list "cozystack.clickhouse-application" $) }} {{include "cozystack.platform.package.default" (list "cozystack.foundationdb-application" $) }} {{include "cozystack.platform.package.default" (list "cozystack.kafka-application" $) }} -{{include "cozystack.platform.package.default" (list "cozystack.mysql-application" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.mariadb-application" $) }} {{include "cozystack.platform.package.default" (list "cozystack.mongodb-application" $) }} {{include "cozystack.platform.package.default" (list "cozystack.nats-application" $) }} {{include "cozystack.platform.package.default" (list "cozystack.postgres-application" $) }} diff --git a/packages/system/cozystack-basics/templates/clusterroles.yaml b/packages/system/cozystack-basics/templates/clusterroles.yaml index 2b3303ed..29395dfe 100644 --- a/packages/system/cozystack-basics/templates/clusterroles.yaml +++ b/packages/system/cozystack-basics/templates/clusterroles.yaml @@ -198,7 +198,7 @@ rules: - httpcaches - kafkas - kuberneteses - - mysqls + - mariadbs - natses - postgreses - rabbitmqs diff --git a/packages/system/mysql-rd/Chart.yaml b/packages/system/mariadb-rd/Chart.yaml similarity index 87% rename from packages/system/mysql-rd/Chart.yaml rename to packages/system/mariadb-rd/Chart.yaml index d3c6b081..091d535c 100644 --- a/packages/system/mysql-rd/Chart.yaml +++ b/packages/system/mariadb-rd/Chart.yaml @@ -1,3 +1,3 @@ apiVersion: v2 -name: mysql-rd +name: mariadb-rd version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process diff --git a/packages/system/mysql-rd/Makefile b/packages/system/mariadb-rd/Makefile similarity index 73% rename from packages/system/mysql-rd/Makefile rename to packages/system/mariadb-rd/Makefile index 2a8af787..def401d8 100644 --- a/packages/system/mysql-rd/Makefile +++ b/packages/system/mariadb-rd/Makefile @@ -1,4 +1,4 @@ -export NAME=mysql-rd +export NAME=mariadb-rd export NAMESPACE=cozy-system include ../../../hack/package.mk diff --git a/packages/system/mysql-rd/cozyrds/mysql.yaml b/packages/system/mariadb-rd/cozyrds/mariadb.yaml similarity index 69% rename from packages/system/mysql-rd/cozyrds/mysql.yaml rename to packages/system/mariadb-rd/cozyrds/mariadb.yaml index 9c2d6d32..5e8088c8 100644 --- a/packages/system/mysql-rd/cozyrds/mysql.yaml +++ b/packages/system/mariadb-rd/cozyrds/mariadb.yaml @@ -1,26 +1,26 @@ apiVersion: cozystack.io/v1alpha1 kind: ApplicationDefinition metadata: - name: mysql + name: mariadb spec: application: - kind: MySQL - plural: mysqls - singular: mysql + kind: MariaDB + plural: mariadbs + singular: mariadb openAPISchema: |- - {"title":"Chart Values","type":"object","properties":{"backup":{"description":"Backup configuration.","type":"object","default":{},"required":["cleanupStrategy","enabled","resticPassword","s3AccessKey","s3Bucket","s3Region","s3SecretKey","schedule"],"properties":{"cleanupStrategy":{"description":"Retention strategy for cleaning up old backups.","type":"string","default":"--keep-last=3 --keep-daily=3 --keep-within-weekly=1m"},"enabled":{"description":"Enable regular backups (default: false).","type":"boolean","default":false},"resticPassword":{"description":"Password for Restic backup encryption.","type":"string","default":""},"s3AccessKey":{"description":"Access key for S3 authentication.","type":"string","default":""},"s3Bucket":{"description":"S3 bucket used for storing backups.","type":"string","default":"s3.example.org/mysql-backups"},"s3Region":{"description":"AWS S3 region where backups are stored.","type":"string","default":"us-east-1"},"s3SecretKey":{"description":"Secret key for S3 authentication.","type":"string","default":""},"schedule":{"description":"Cron schedule for automated backups.","type":"string","default":"0 2 * * *"}}},"databases":{"description":"Databases configuration map.","type":"object","default":{},"additionalProperties":{"type":"object","properties":{"roles":{"description":"Roles assigned to users.","type":"object","properties":{"admin":{"description":"List of users with admin privileges.","type":"array","items":{"type":"string"}},"readonly":{"description":"List of users with read-only privileges.","type":"array","items":{"type":"string"}}}}}}},"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"replicas":{"description":"Number of MariaDB replicas.","type":"integer","default":2},"resources":{"description":"Explicit CPU and memory configuration for each MariaDB replica. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available to each replica.","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 (RAM) available to each replica.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"nano","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]},"size":{"description":"Persistent Volume Claim size available for application data.","default":"10Gi","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},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":""},"users":{"description":"Users configuration map.","type":"object","default":{},"additionalProperties":{"type":"object","required":["maxUserConnections","password"],"properties":{"maxUserConnections":{"description":"Maximum number of connections.","type":"integer"},"password":{"description":"Password for the user.","type":"string"}}}},"version":{"description":"MariaDB major.minor version to deploy","type":"string","default":"v11.8","enum":["v11.8","v11.4","v10.11","v10.6"]}}} + {"title":"Chart Values","type":"object","properties":{"backup":{"description":"Backup configuration.","type":"object","default":{},"required":["cleanupStrategy","enabled","resticPassword","s3AccessKey","s3Bucket","s3Region","s3SecretKey","schedule"],"properties":{"cleanupStrategy":{"description":"Retention strategy for cleaning up old backups.","type":"string","default":"--keep-last=3 --keep-daily=3 --keep-within-weekly=1m"},"enabled":{"description":"Enable regular backups (default: false).","type":"boolean","default":false},"resticPassword":{"description":"Password for Restic backup encryption.","type":"string","default":""},"s3AccessKey":{"description":"Access key for S3 authentication.","type":"string","default":""},"s3Bucket":{"description":"S3 bucket used for storing backups.","type":"string","default":"s3.example.org/mariadb-backups"},"s3Region":{"description":"AWS S3 region where backups are stored.","type":"string","default":"us-east-1"},"s3SecretKey":{"description":"Secret key for S3 authentication.","type":"string","default":""},"schedule":{"description":"Cron schedule for automated backups.","type":"string","default":"0 2 * * *"}}},"databases":{"description":"Databases configuration map.","type":"object","default":{},"additionalProperties":{"type":"object","properties":{"roles":{"description":"Roles assigned to users.","type":"object","properties":{"admin":{"description":"List of users with admin privileges.","type":"array","items":{"type":"string"}},"readonly":{"description":"List of users with read-only privileges.","type":"array","items":{"type":"string"}}}}}}},"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"replicas":{"description":"Number of MariaDB replicas.","type":"integer","default":2},"resources":{"description":"Explicit CPU and memory configuration for each MariaDB replica. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available to each replica.","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 (RAM) available to each replica.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"nano","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]},"size":{"description":"Persistent Volume Claim size available for application data.","default":"10Gi","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},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":""},"users":{"description":"Users configuration map.","type":"object","default":{},"additionalProperties":{"type":"object","required":["maxUserConnections","password"],"properties":{"maxUserConnections":{"description":"Maximum number of connections.","type":"integer"},"password":{"description":"Password for the user.","type":"string"}}}},"version":{"description":"MariaDB major.minor version to deploy","type":"string","default":"v11.8","enum":["v11.8","v11.4","v10.11","v10.6"]}}} release: - prefix: mysql- + prefix: mariadb- labels: sharding.fluxcd.io/key: tenants chartRef: kind: ExternalArtifact - name: cozystack-mysql-application-default-mysql + name: cozystack-mariadb-application-default-mariadb namespace: cozy-system dashboard: category: PaaS - singular: MySQL - plural: MySQL + singular: MariaDB + plural: MariaDB description: Managed MariaDB service tags: - database @@ -30,10 +30,10 @@ spec: exclude: [] include: - resourceNames: - - mysql-{{ .name }}-credentials + - mariadb-{{ .name }}-credentials services: exclude: [] include: - resourceNames: - - mysql-{{ .name }}-primary - - mysql-{{ .name }}-secondary + - mariadb-{{ .name }}-primary + - mariadb-{{ .name }}-secondary diff --git a/packages/system/mysql-rd/templates/cozyrd.yaml b/packages/system/mariadb-rd/templates/cozyrd.yaml similarity index 100% rename from packages/system/mysql-rd/templates/cozyrd.yaml rename to packages/system/mariadb-rd/templates/cozyrd.yaml diff --git a/packages/system/mysql-rd/values.yaml b/packages/system/mariadb-rd/values.yaml similarity index 100% rename from packages/system/mysql-rd/values.yaml rename to packages/system/mariadb-rd/values.yaml From 740eb7028b7cce4060f11307650caac8f7052d87 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 10 Feb 2026 19:11:31 +0100 Subject: [PATCH 246/889] feat(platform): add migration 27 to rename mysql resources to mariadb Add platform migration that auto-discovers all deployed MySQL HelmReleases and renames their resources to use the mariadb prefix. The migration handles PVC data preservation via PV claimRef rebinding, temporarily disables protection-webhook for protected resource deletion, and scales mariadb-operator once for all instances. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- packages/apps/mariadb/README.md | 2 +- .../platform/images/migrations/migrations/28 | 616 ++++++++++++++++++ packages/core/platform/values.yaml | 4 +- 3 files changed, 619 insertions(+), 3 deletions(-) create mode 100755 packages/core/platform/images/migrations/migrations/28 diff --git a/packages/apps/mariadb/README.md b/packages/apps/mariadb/README.md index 6b3b8199..bb3ea8ac 100644 --- a/packages/apps/mariadb/README.md +++ b/packages/apps/mariadb/README.md @@ -102,7 +102,7 @@ more details: | `backup` | Backup configuration. | `object` | `{}` | | `backup.enabled` | Enable regular backups (default: false). | `bool` | `false` | | `backup.s3Region` | AWS S3 region where backups are stored. | `string` | `us-east-1` | -| `backup.s3Bucket` | S3 bucket used for storing backups. | `string` | `s3.example.org/mariadb-backups` | +| `backup.s3Bucket` | S3 bucket used for storing backups. | `string` | `s3.example.org/mariadb-backups` | | `backup.schedule` | Cron schedule for automated backups. | `string` | `0 2 * * *` | | `backup.cleanupStrategy` | Retention strategy for cleaning up old backups. | `string` | `--keep-last=3 --keep-daily=3 --keep-within-weekly=1m` | | `backup.s3AccessKey` | Access key for S3 authentication. | `string` | `` | diff --git a/packages/core/platform/images/migrations/migrations/28 b/packages/core/platform/images/migrations/migrations/28 new file mode 100755 index 00000000..259d5fd8 --- /dev/null +++ b/packages/core/platform/images/migrations/migrations/28 @@ -0,0 +1,616 @@ +#!/bin/bash +# Migration 28 --> 29 +# Rename all mysql-prefixed resources to mariadb- across the cluster. +# Discovers all MySQL HelmReleases by label, migrates each instance. +# Idempotent: safe to re-run. + +set -euo pipefail + +OLD_PREFIX="mysql" +NEW_PREFIX="mariadb" +MARIADB_OPERATOR_NS="cozy-mariadb-operator" +MARIADB_WEBHOOK_NAME="mariadb-operator-webhook" +PROTECTION_WEBHOOK_NAME="protection-webhook" +PROTECTION_WEBHOOK_NS="protection-webhook" +declare -A OPERATOR_REPLICAS=() +declare -A ORIGINAL_PV_POLICIES=() + +# ============================================================ +# Helper functions +# ============================================================ + +resource_exists() { + local ns="$1" type="$2" name="$3" + kubectl -n "$ns" get "$type" "$name" --no-headers 2>/dev/null | grep -q . +} + +delete_resource() { + local ns="$1" type="$2" name="$3" + if resource_exists "$ns" "$type" "$name"; then + echo " [DELETE] ${type}/${name}" + kubectl -n "$ns" delete "$type" "$name" --wait=false + else + echo " [SKIP] ${type}/${name} already gone" + fi +} + +clone_resource() { + local ns="$1" type="$2" old_name="$3" new_name="$4" old_instance="$5" new_instance="$6" + + if resource_exists "$ns" "$type" "$new_name"; then + echo " [SKIP] ${type}/${new_name} already exists" + return 0 + fi + if ! resource_exists "$ns" "$type" "$old_name"; then + echo " [SKIP] ${type}/${old_name} not found" + return 0 + fi + + echo " [CREATE] ${type}/${new_name} from ${old_name}" + kubectl -n "$ns" get "$type" "$old_name" -o json | \ + jq --arg new_name "$new_name" --arg old "$old_instance" --arg new "$new_instance" ' + .metadata.name = $new_name | + del(.metadata.uid, .metadata.resourceVersion, .metadata.creationTimestamp, + .metadata.generation, .metadata.managedFields, .metadata.selfLink, + .metadata.ownerReferences) | + del(.status) | + .metadata.labels = ((.metadata.labels // {}) | with_entries( + if (.value | type) == "string" then .value |= gsub($old; $new) else . end)) | + .metadata.annotations = ((.metadata.annotations // {}) | with_entries( + select(.key != "kubectl.kubernetes.io/last-applied-configuration") | + if (.value | type) == "string" then .value |= gsub($old; $new) else . end)) + ' | kubectl -n "$ns" apply -f - +} + +patch_webhook_policy() { + local name="$1" policy="$2" + echo " [PATCH] Set failurePolicy=${policy} on ValidatingWebhookConfiguration/${name}" + kubectl get validatingwebhookconfiguration "$name" -o json | \ + jq --arg p "$policy" '.webhooks[].failurePolicy = $p' | \ + kubectl apply -f - +} + +# ============================================================ +# STEP 1: Discover all MySQL instances +# ============================================================ +echo "=== Discovering MySQL HelmReleases ===" +INSTANCES=() +while IFS=/ read -r ns name; do + [ -z "$ns" ] && continue + instance="${name#${OLD_PREFIX}-}" + INSTANCES+=("${ns}/${instance}") + echo " Found: ${ns}/${name} (instance=${instance})" +done < <(kubectl get hr -A -l "apps.cozystack.io/application.kind=MySQL" \ + -o jsonpath='{range .items[*]}{.metadata.namespace}/{.metadata.name}{"\n"}{end}' 2>/dev/null) + +if [ ${#INSTANCES[@]} -eq 0 ]; then + echo " No MySQL HelmReleases found. Nothing to migrate." + + # Recover operator if a prior run left it scaled down + for deploy in mariadb-operator mariadb-operator-cert-controller mariadb-operator-webhook; do + current=$(kubectl -n "$MARIADB_OPERATOR_NS" get deploy "$deploy" \ + -o jsonpath='{.spec.replicas}' 2>/dev/null || echo "1") + if [ "$current" = "0" ]; then + echo " [RECOVER] Scaling ${deploy} -> 1" + kubectl -n "$MARIADB_OPERATOR_NS" scale deploy "$deploy" --replicas=1 + fi + done + + # Restore webhook if left at Ignore + current_policy=$(kubectl get validatingwebhookconfiguration "$MARIADB_WEBHOOK_NAME" \ + -o jsonpath='{.webhooks[0].failurePolicy}' 2>/dev/null || echo "unknown") + if [ "$current_policy" = "Ignore" ]; then + patch_webhook_policy "$MARIADB_WEBHOOK_NAME" "Fail" + fi + + # Unsuspend any new HelmReleases left suspended from a prior partial run + while IFS=/ read -r ns name; do + [ -z "$ns" ] && continue + suspended=$(kubectl -n "$ns" get hr "$name" -o jsonpath='{.spec.suspend}' 2>/dev/null || echo "false") + if [ "$suspended" = "true" ]; then + echo " [UNSUSPEND] ${ns}/hr/${name}" + kubectl -n "$ns" patch hr "$name" --type=merge -p '{"spec":{"suspend":false}}' + fi + done < <(kubectl get hr -A -l "apps.cozystack.io/application.kind=MariaDB" \ + -o jsonpath='{range .items[*]}{.metadata.namespace}/{.metadata.name}{"\n"}{end}' 2>/dev/null) + + kubectl create configmap -n cozy-system cozystack-version \ + --from-literal=version=29 --dry-run=client -o yaml | kubectl apply -f- + exit 0 +fi +echo " Total: ${#INSTANCES[@]} instance(s)" + +# ============================================================ +# STEP 2: Scale down mariadb-operator and disable its webhook +# ============================================================ +echo "" +echo "--- Step 2: Scale down mariadb-operator ---" +for deploy in mariadb-operator mariadb-operator-cert-controller mariadb-operator-webhook; do + current=$(kubectl -n "$MARIADB_OPERATOR_NS" get deploy "$deploy" -o jsonpath='{.spec.replicas}' 2>/dev/null || echo "0") + OPERATOR_REPLICAS["$deploy"]="$current" + if [ "$current" != "0" ]; then + echo " [SCALE] ${deploy} -> 0 (was ${current})" + kubectl -n "$MARIADB_OPERATOR_NS" scale deploy "$deploy" --replicas=0 + else + echo " [SKIP] ${deploy} already scaled to 0" + fi +done + +echo " Waiting for operator pods to terminate..." +kubectl -n "$MARIADB_OPERATOR_NS" wait --for=delete pod \ + -l app.kubernetes.io/instance=mariadb-operator --timeout=60s 2>/dev/null || true + +current_policy=$(kubectl get validatingwebhookconfiguration "$MARIADB_WEBHOOK_NAME" \ + -o jsonpath='{.webhooks[0].failurePolicy}' 2>/dev/null || echo "unknown") +if [ "$current_policy" = "Fail" ]; then + patch_webhook_policy "$MARIADB_WEBHOOK_NAME" "Ignore" +else + echo " [SKIP] Webhook ${MARIADB_WEBHOOK_NAME} already failurePolicy=${current_policy}" +fi + +# ============================================================ +# STEP 3: Migrate each instance +# ============================================================ +ALL_PV_NAMES=() +ALL_PROTECTED_RESOURCES=() + +for entry in "${INSTANCES[@]}"; do + NAMESPACE="${entry%%/*}" + INSTANCE="${entry#*/}" + OLD_NAME="${OLD_PREFIX}-${INSTANCE}" + NEW_NAME="${NEW_PREFIX}-${INSTANCE}" + + echo "" + echo "======================================================================" + echo "=== Migrating: ${OLD_NAME} -> ${NEW_NAME} in ${NAMESPACE}" + echo "======================================================================" + + # --- 3a: Suspend old HelmRelease --- + echo " --- Suspend HelmRelease ---" + if resource_exists "$NAMESPACE" "hr" "$OLD_NAME"; then + suspended=$(kubectl -n "$NAMESPACE" get hr "$OLD_NAME" -o jsonpath='{.spec.suspend}' 2>/dev/null || echo "false") + if [ "$suspended" != "true" ]; then + echo " [SUSPEND] hr/${OLD_NAME}" + kubectl -n "$NAMESPACE" patch hr "$OLD_NAME" --type=merge -p '{"spec":{"suspend":true}}' + else + echo " [SKIP] hr/${OLD_NAME} already suspended" + fi + else + echo " [SKIP] hr/${OLD_NAME} not found" + fi + + # --- 3b: Switch PV reclaim policy to Retain --- + echo " --- Switch PV reclaim to Retain ---" + PV_NAMES=() + replicas=$(kubectl -n "$NAMESPACE" get mariadb.k8s.mariadb.com "$OLD_NAME" \ + -o jsonpath='{.spec.replicas}' 2>/dev/null || true) + if [ -z "$replicas" ]; then + # Fallback: count existing PVCs matching the old or new name pattern + replicas=$(kubectl -n "$NAMESPACE" get pvc --no-headers -o name 2>/dev/null \ + | grep -cE "storage-${OLD_NAME}-[0-9]+$|storage-${NEW_NAME}-[0-9]+$" || echo "0") + echo " [INFO] MariaDB CR not found, derived replicas=${replicas} from existing PVCs" + fi + for i in $(seq 0 $((replicas - 1))); do + pvc_name="storage-${OLD_NAME}-${i}" + if resource_exists "$NAMESPACE" "pvc" "$pvc_name"; then + pv_name=$(kubectl -n "$NAMESPACE" get pvc "$pvc_name" -o jsonpath='{.spec.volumeName}') + PV_NAMES+=("$pv_name") + ALL_PV_NAMES+=("$pv_name") + current_policy=$(kubectl get pv "$pv_name" -o jsonpath='{.spec.persistentVolumeReclaimPolicy}') + if [ "$current_policy" != "Retain" ]; then + echo " [PATCH] PV ${pv_name}: ${current_policy} -> Retain" + ORIGINAL_PV_POLICIES["$pv_name"]="$current_policy" + kubectl patch pv "$pv_name" -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}' + else + echo " [SKIP] PV ${pv_name} already Retain" + fi + else + new_pvc_name="storage-${NEW_NAME}-${i}" + if resource_exists "$NAMESPACE" "pvc" "$new_pvc_name"; then + pv_name=$(kubectl -n "$NAMESPACE" get pvc "$new_pvc_name" -o jsonpath='{.spec.volumeName}') + PV_NAMES+=("$pv_name") + ALL_PV_NAMES+=("$pv_name") + echo " [SKIP] New PVC ${new_pvc_name} already exists, PV=${pv_name}" + else + echo " [WARN] Neither old nor new PVC found for index ${i}" + PV_NAMES+=("") + fi + fi + done + + # --- 3c: Delete old StatefulSet and Deployment --- + echo " --- Delete old StatefulSet/Deployment ---" + if resource_exists "$NAMESPACE" "statefulset" "$OLD_NAME"; then + echo " [DELETE] statefulset/${OLD_NAME} (cascade=orphan)" + kubectl -n "$NAMESPACE" delete statefulset "$OLD_NAME" --cascade=orphan + else + echo " [SKIP] statefulset/${OLD_NAME} already gone" + fi + if resource_exists "$NAMESPACE" "deployment" "${OLD_NAME}-metrics"; then + echo " [DELETE] deployment/${OLD_NAME}-metrics" + kubectl -n "$NAMESPACE" delete deployment "${OLD_NAME}-metrics" + else + echo " [SKIP] deployment/${OLD_NAME}-metrics already gone" + fi + + # --- 3d: Delete old pods --- + echo " --- Delete old pods ---" + for i in $(seq 0 $((replicas - 1))); do + pod_name="${OLD_NAME}-${i}" + if resource_exists "$NAMESPACE" "pod" "$pod_name"; then + echo " [DELETE] pod/${pod_name}" + kubectl -n "$NAMESPACE" delete pod "$pod_name" --grace-period=30 + else + echo " [SKIP] pod/${pod_name} already gone" + fi + done + for pod in $(kubectl -n "$NAMESPACE" get pods \ + -l app.kubernetes.io/name=mysqld-exporter,app.kubernetes.io/instance="${OLD_NAME}" \ + -o name 2>/dev/null); do + echo " [DELETE] ${pod}" + kubectl -n "$NAMESPACE" delete "$pod" --grace-period=30 + done + + # --- 3e: Migrate PVCs --- + echo " --- Migrate PVCs ---" + for i in $(seq 0 $((replicas - 1))); do + old_pvc="storage-${OLD_NAME}-${i}" + new_pvc="storage-${NEW_NAME}-${i}" + pv_name="${PV_NAMES[$i]:-}" + + if [ -z "$pv_name" ]; then + echo " [WARN] No PV found for index ${i}, skipping" + continue + fi + + if resource_exists "$NAMESPACE" "pvc" "$new_pvc"; then + new_phase=$(kubectl -n "$NAMESPACE" get pvc "$new_pvc" \ + -o jsonpath='{.status.phase}' 2>/dev/null || echo "unknown") + if [ "$new_phase" = "Bound" ]; then + echo " [SKIP] PVC ${new_pvc} already Bound" + continue + fi + fi + + if resource_exists "$NAMESPACE" "pvc" "$old_pvc"; then + storage_size=$(kubectl -n "$NAMESPACE" get pvc "$old_pvc" \ + -o jsonpath='{.spec.resources.requests.storage}') + storage_class=$(kubectl -n "$NAMESPACE" get pvc "$old_pvc" \ + -o jsonpath='{.spec.storageClassName}') + old_labels=$(kubectl -n "$NAMESPACE" get pvc "$old_pvc" -o json | \ + jq --arg old "$OLD_NAME" --arg new "$NEW_NAME" \ + '(.metadata.labels // {}) | with_entries( + if (.value | type) == "string" and .value == $old then .value = $new else . end)') + + if ! resource_exists "$NAMESPACE" "pvc" "$new_pvc"; then + echo " [CREATE] PVC ${new_pvc} -> PV ${pv_name}" + cat < ${new_pvc}" + kubectl patch pv "$pv_name" --type=merge -p "{ + \"spec\":{\"claimRef\":{\"apiVersion\":\"v1\",\"kind\":\"PersistentVolumeClaim\", + \"name\":\"${new_pvc}\",\"namespace\":\"${NAMESPACE}\",\"uid\":\"${new_pvc_uid}\"}} + }" + + echo " Waiting for PVC ${new_pvc} to bind..." + kubectl -n "$NAMESPACE" wait pvc "$new_pvc" \ + --for=jsonpath='{.status.phase}'=Bound --timeout=60s + echo " [OK] PVC ${new_pvc} is Bound" + fi + done + + # --- 3f: Clone Services --- + echo " --- Clone Services ---" + for suffix in "" "-internal" "-primary" "-secondary" "-metrics"; do + old_svc="${OLD_NAME}${suffix}" + new_svc="${NEW_NAME}${suffix}" + if resource_exists "$NAMESPACE" "svc" "$old_svc"; then + if resource_exists "$NAMESPACE" "svc" "$new_svc"; then + echo " [SKIP] svc/${new_svc} already exists" + else + echo " [CREATE] svc/${new_svc} from ${old_svc}" + kubectl -n "$NAMESPACE" get svc "$old_svc" -o json | \ + jq --arg new_svc "$new_svc" --arg old "$OLD_NAME" --arg new "$NEW_NAME" ' + .metadata.name = $new_svc | + del(.metadata.uid, .metadata.resourceVersion, .metadata.creationTimestamp, + .metadata.generation, .metadata.managedFields, .metadata.selfLink, + .metadata.ownerReferences) | + del(.status) | + del(.spec.clusterIP, .spec.clusterIPs) | + .metadata.labels = ((.metadata.labels // {}) | with_entries( + if (.value | type) == "string" then .value |= gsub($old; $new) else . end)) | + .metadata.annotations = ((.metadata.annotations // {}) | with_entries( + select(.key != "kubectl.kubernetes.io/last-applied-configuration") | + if (.value | type) == "string" then .value |= gsub($old; $new) else . end)) | + .spec.selector = ((.spec.selector // {}) | with_entries( + if (.value | type) == "string" then .value |= gsub($old; $new) else . end)) + ' | kubectl -n "$NAMESPACE" apply -f - + fi + fi + done + + # --- 3g: Clone Secrets --- + echo " --- Clone Secrets ---" + for secret in $(kubectl -n "$NAMESPACE" get secret -o name 2>/dev/null \ + | grep "secret/${OLD_NAME}" | grep -v "sh.helm.release"); do + old_secret_name="${secret#secret/}" + new_secret_name="${NEW_NAME}${old_secret_name#${OLD_NAME}}" + clone_resource "$NAMESPACE" "secret" "$old_secret_name" "$new_secret_name" "$OLD_NAME" "$NEW_NAME" + done + + # --- 3h: Clone ConfigMaps --- + echo " --- Clone ConfigMaps ---" + for cm in $(kubectl -n "$NAMESPACE" get configmap -o name 2>/dev/null \ + | grep "configmap/${OLD_NAME}"); do + old_cm_name="${cm#configmap/}" + new_cm_name="${NEW_NAME}${old_cm_name#${OLD_NAME}}" + clone_resource "$NAMESPACE" "configmap" "$old_cm_name" "$new_cm_name" "$OLD_NAME" "$NEW_NAME" + done + + # --- 3i: Clone MariaDB CR --- + echo " --- Clone MariaDB CR ---" + if resource_exists "$NAMESPACE" "mariadb.k8s.mariadb.com" "$OLD_NAME"; then + if resource_exists "$NAMESPACE" "mariadb.k8s.mariadb.com" "$NEW_NAME"; then + echo " [SKIP] mariadb.k8s.mariadb.com/${NEW_NAME} already exists" + else + echo " [CREATE] mariadb.k8s.mariadb.com/${NEW_NAME}" + kubectl -n "$NAMESPACE" get mariadb.k8s.mariadb.com "$OLD_NAME" -o json | \ + jq --arg old "$OLD_NAME" --arg new "$NEW_NAME" ' + .metadata.name = $new | + del(.metadata.uid, .metadata.resourceVersion, .metadata.creationTimestamp, + .metadata.generation, .metadata.managedFields, .metadata.selfLink, + .metadata.finalizers, .metadata.ownerReferences) | + del(.status) | + .metadata.labels = ((.metadata.labels // {}) | with_entries( + if (.value | type) == "string" then .value |= gsub($old; $new) else . end)) | + .metadata.annotations = ((.metadata.annotations // {}) | with_entries( + select(.key != "kubectl.kubernetes.io/last-applied-configuration") | + if (.value | type) == "string" then .value |= gsub($old; $new) else . end)) | + .spec = (.spec | tostring | gsub($old; $new) | fromjson) + ' | kubectl -n "$NAMESPACE" apply -f - + fi + fi + + # --- 3j: Clone Users, Databases, Grants --- + echo " --- Clone Users, Databases, Grants ---" + for kind in "user.k8s.mariadb.com" "database.k8s.mariadb.com" "grant.k8s.mariadb.com"; do + for resource in $(kubectl -n "$NAMESPACE" get "$kind" -o name 2>/dev/null | grep "${OLD_NAME}"); do + old_res_name="${resource##*/}" + new_res_name="${NEW_NAME}${old_res_name#${OLD_NAME}}" + if resource_exists "$NAMESPACE" "$kind" "$new_res_name"; then + echo " [SKIP] ${kind}/${new_res_name} already exists" + else + echo " [CREATE] ${kind}/${new_res_name}" + kubectl -n "$NAMESPACE" get "$kind" "$old_res_name" -o json | \ + jq --arg old "$OLD_NAME" --arg new "$NEW_NAME" --arg new_name "$new_res_name" ' + .metadata.name = $new_name | + del(.metadata.uid, .metadata.resourceVersion, .metadata.creationTimestamp, + .metadata.generation, .metadata.managedFields, .metadata.selfLink, + .metadata.finalizers, .metadata.ownerReferences) | + del(.status) | + .metadata.labels = ((.metadata.labels // {}) | with_entries( + if (.value | type) == "string" then .value |= gsub($old; $new) else . end)) | + .metadata.annotations = ((.metadata.annotations // {}) | with_entries( + select(.key != "kubectl.kubernetes.io/last-applied-configuration") | + if (.value | type) == "string" then .value |= gsub($old; $new) else . end)) | + .spec = (.spec | tostring | gsub($old; $new) | fromjson) + ' | kubectl -n "$NAMESPACE" apply -f - + fi + done + done + + # --- 3k: Clone WorkloadMonitor --- + echo " --- Clone WorkloadMonitor ---" + clone_resource "$NAMESPACE" "workloadmonitor.cozystack.io" "$OLD_NAME" "$NEW_NAME" "$OLD_NAME" "$NEW_NAME" + + # --- 3l: Create new HelmRelease (suspended) --- + echo " --- Create new HelmRelease (suspended) ---" + if resource_exists "$NAMESPACE" "hr" "$NEW_NAME"; then + echo " [SKIP] hr/${NEW_NAME} already exists" + elif resource_exists "$NAMESPACE" "hr" "$OLD_NAME"; then + echo " [CREATE] hr/${NEW_NAME} from ${OLD_NAME} (suspended)" + kubectl -n "$NAMESPACE" get hr "$OLD_NAME" -o json | \ + jq --arg old "$OLD_NAME" --arg new "$NEW_NAME" \ + --arg old_prefix "$OLD_PREFIX" --arg new_prefix "$NEW_PREFIX" ' + .metadata.name = $new | + del(.metadata.uid, .metadata.resourceVersion, .metadata.creationTimestamp, + .metadata.generation, .metadata.managedFields, .metadata.selfLink, + .metadata.finalizers, .metadata.ownerReferences) | + del(.status) | + .metadata.labels = ((.metadata.labels // {}) | with_entries( + if (.value | type) == "string" then .value |= gsub($old; $new) else . end) | + if .["apps.cozystack.io/application.kind"] == "MySQL" + then .["apps.cozystack.io/application.kind"] = "MariaDB" + else . end) | + .metadata.annotations = ((.metadata.annotations // {}) | with_entries( + select(.key != "kubectl.kubernetes.io/last-applied-configuration") | + if (.value | type) == "string" then .value |= gsub($old; $new) else . end)) | + (if .spec.chart.spec.chart == $old_prefix + then .spec.chart.spec.chart = $new_prefix else . end) | + .spec.suspend = true + ' | kubectl -n "$NAMESPACE" apply -f - + else + echo " [WARN] hr/${OLD_NAME} not found, cannot clone" + fi + + # --- 3m: Delete old unprotected resources --- + echo " --- Delete old resources ---" + for kind in "grant.k8s.mariadb.com" "database.k8s.mariadb.com" "user.k8s.mariadb.com"; do + for resource in $(kubectl -n "$NAMESPACE" get "$kind" -o name 2>/dev/null | grep "${OLD_NAME}"); do + old_res_name="${resource##*/}" + kubectl -n "$NAMESPACE" patch "$kind" "$old_res_name" --type=json \ + -p='[{"op":"remove","path":"/metadata/finalizers"}]' 2>/dev/null || true + echo " [DELETE] ${kind}/${old_res_name}" + kubectl -n "$NAMESPACE" delete "$kind" "$old_res_name" --wait=false 2>/dev/null || true + done + done + + if resource_exists "$NAMESPACE" "mariadb.k8s.mariadb.com" "$OLD_NAME"; then + kubectl -n "$NAMESPACE" patch mariadb.k8s.mariadb.com "$OLD_NAME" --type=json \ + -p='[{"op":"remove","path":"/metadata/finalizers"}]' 2>/dev/null || true + delete_resource "$NAMESPACE" "mariadb.k8s.mariadb.com" "$OLD_NAME" + fi + + for secret in $(kubectl -n "$NAMESPACE" get secret -o name 2>/dev/null \ + | grep "secret/${OLD_NAME}" | grep -v "sh.helm.release"); do + old_secret_name="${secret#secret/}" + delete_resource "$NAMESPACE" "secret" "$old_secret_name" + done + + for cm in $(kubectl -n "$NAMESPACE" get configmap -o name 2>/dev/null \ + | grep "configmap/${OLD_NAME}"); do + old_cm_name="${cm#configmap/}" + delete_resource "$NAMESPACE" "configmap" "$old_cm_name" + done + + delete_resource "$NAMESPACE" "workloadmonitor.cozystack.io" "$OLD_NAME" + + if resource_exists "$NAMESPACE" "hr" "$OLD_NAME"; then + kubectl -n "$NAMESPACE" patch hr "$OLD_NAME" --type=json \ + -p='[{"op":"remove","path":"/metadata/finalizers"}]' 2>/dev/null || true + delete_resource "$NAMESPACE" "hr" "$OLD_NAME" + fi + + echo " [DELETE] secrets with label owner=helm,name=${OLD_NAME}" + kubectl -n "$NAMESPACE" delete secret -l "owner=helm,name=${OLD_NAME}" 2>/dev/null || true + + # Collect protected resources for batch deletion + for suffix in "" "-internal" "-primary" "-secondary" "-metrics"; do + svc_name="${OLD_NAME}${suffix}" + if resource_exists "$NAMESPACE" "svc" "$svc_name"; then + ALL_PROTECTED_RESOURCES+=("${NAMESPACE}:svc/${svc_name}") + fi + done + for i in $(seq 0 $((replicas - 1))); do + old_pvc="storage-${OLD_NAME}-${i}" + if resource_exists "$NAMESPACE" "pvc" "$old_pvc"; then + ALL_PROTECTED_RESOURCES+=("${NAMESPACE}:pvc/${old_pvc}") + fi + done +done + +# ============================================================ +# STEP 4: Delete protected resources (PVCs, Services) +# ============================================================ +echo "" +echo "--- Step 4: Delete protected resources ---" + +if [ ${#ALL_PROTECTED_RESOURCES[@]} -gt 0 ]; then + echo " --- Temporarily disabling protection-webhook ---" + + WEBHOOK_REPLICAS=$(kubectl -n "$PROTECTION_WEBHOOK_NS" get deploy "$PROTECTION_WEBHOOK_NAME" \ + -o jsonpath='{.spec.replicas}' 2>/dev/null || echo "1") + + echo " [SCALE] ${PROTECTION_WEBHOOK_NAME} -> 0 (was ${WEBHOOK_REPLICAS})" + kubectl -n "$PROTECTION_WEBHOOK_NS" scale deploy "$PROTECTION_WEBHOOK_NAME" --replicas=0 + + patch_webhook_policy "$PROTECTION_WEBHOOK_NAME" "Ignore" + + echo " Waiting for webhook pods to terminate..." + kubectl -n "$PROTECTION_WEBHOOK_NS" wait --for=delete pod \ + -l app.kubernetes.io/name=protection-webhook --timeout=60s 2>/dev/null || true + sleep 3 + + for entry in "${ALL_PROTECTED_RESOURCES[@]}"; do + ns="${entry%%:*}" + res="${entry#*:}" + echo " [DELETE] ${ns}/${res}" + kubectl -n "$ns" delete "$res" --wait=false 2>/dev/null || true + done + + patch_webhook_policy "$PROTECTION_WEBHOOK_NAME" "Fail" + + echo " [SCALE] ${PROTECTION_WEBHOOK_NAME} -> ${WEBHOOK_REPLICAS}" + kubectl -n "$PROTECTION_WEBHOOK_NS" scale deploy "$PROTECTION_WEBHOOK_NAME" \ + --replicas="$WEBHOOK_REPLICAS" + echo " --- protection-webhook restored ---" +else + echo " [SKIP] No protected resources to delete" +fi + +# ============================================================ +# STEP 5: Restore PV reclaim policies +# ============================================================ +echo "" +echo "--- Step 5: Restore PV reclaim policies ---" +for pv_name in "${ALL_PV_NAMES[@]}"; do + if [ -n "$pv_name" ]; then + original="${ORIGINAL_PV_POLICIES[$pv_name]:-}" + if [ -n "$original" ]; then + echo " [PATCH] PV ${pv_name}: Retain -> ${original}" + kubectl patch pv "$pv_name" -p "{\"spec\":{\"persistentVolumeReclaimPolicy\":\"${original}\"}}" + else + echo " [SKIP] PV ${pv_name} was already Retain, leaving as-is" + fi + fi +done + +# ============================================================ +# STEP 6: Restore mariadb-operator +# ============================================================ +echo "" +echo "--- Step 6: Restore mariadb-operator ---" + +current_policy=$(kubectl get validatingwebhookconfiguration "$MARIADB_WEBHOOK_NAME" \ + -o jsonpath='{.webhooks[0].failurePolicy}' 2>/dev/null || echo "unknown") +if [ "$current_policy" = "Ignore" ]; then + patch_webhook_policy "$MARIADB_WEBHOOK_NAME" "Fail" +else + echo " [SKIP] Webhook ${MARIADB_WEBHOOK_NAME} already failurePolicy=${current_policy}" +fi + +for deploy in mariadb-operator mariadb-operator-cert-controller mariadb-operator-webhook; do + current=$(kubectl -n "$MARIADB_OPERATOR_NS" get deploy "$deploy" \ + -o jsonpath='{.spec.replicas}' 2>/dev/null || echo "1") + if [ "$current" = "0" ]; then + target="${OPERATOR_REPLICAS[$deploy]:-1}" + echo " [SCALE] ${deploy} -> ${target}" + kubectl -n "$MARIADB_OPERATOR_NS" scale deploy "$deploy" --replicas="$target" + else + echo " [SKIP] ${deploy} already running (replicas=${current})" + fi +done + +# ============================================================ +# STEP 7: Unsuspend all new HelmReleases +# ============================================================ +echo "" +echo "--- Step 7: Unsuspend new HelmReleases ---" +for entry in "${INSTANCES[@]}"; do + ns="${entry%%/*}" + instance="${entry#*/}" + new_name="${NEW_PREFIX}-${instance}" + if resource_exists "$ns" "hr" "$new_name"; then + suspended=$(kubectl -n "$ns" get hr "$new_name" \ + -o jsonpath='{.spec.suspend}' 2>/dev/null || echo "false") + if [ "$suspended" = "true" ]; then + echo " [UNSUSPEND] ${ns}/hr/${new_name}" + kubectl -n "$ns" patch hr "$new_name" --type=merge -p '{"spec":{"suspend":false}}' + else + echo " [SKIP] ${ns}/hr/${new_name} already not suspended" + fi + fi +done + +echo "" +echo "=== Migration complete (${#INSTANCES[@]} instance(s)) ===" + +# Stamp version +kubectl create configmap -n cozy-system cozystack-version \ + --from-literal=version=29 --dry-run=client -o yaml | kubectl apply -f- diff --git a/packages/core/platform/values.yaml b/packages/core/platform/values.yaml index f923c22d..9bb8e2b1 100644 --- a/packages/core/platform/values.yaml +++ b/packages/core/platform/values.yaml @@ -5,8 +5,8 @@ sourceRef: path: / migrations: enabled: false - image: ghcr.io/cozystack/cozystack/platform-migrations:v1.0.0-beta.3@sha256:1ab28def39c9cb0233a03708ce00c9694edc9728789eef59abdc1d72bbe9b10a - targetVersion: 28 + image: ghcr.io/cozystack/cozystack/platform-migrations:latest + targetVersion: 29 # Bundle deployment configuration bundles: system: From 9a86551e40e2f168ce935416bb02010476795a0d Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 12 Feb 2026 15:18:06 +0100 Subject: [PATCH 247/889] fix(e2e): correct s3Bucket reference in mariadb test Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- hack/e2e-apps/mariadb.bats | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/e2e-apps/mariadb.bats b/hack/e2e-apps/mariadb.bats index 50e608ac..5b11c4c0 100644 --- a/hack/e2e-apps/mariadb.bats +++ b/hack/e2e-apps/mariadb.bats @@ -25,7 +25,7 @@ spec: backup: enabled: false s3Region: us-east-1 - s3Bucket: s3.example.org/postgres-backups + s3Bucket: s3.example.org/mariadb-backups schedule: "0 2 * * *" cleanupStrategy: "--keep-last=3 --keep-daily=3 --keep-within-weekly=1m" s3AccessKey: oobaiRus9pah8PhohL1ThaeTa4UVa7gu From 32b9a7749c9c0d19d362568a977618f0d304c25f Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 12 Feb 2026 15:28:15 +0100 Subject: [PATCH 248/889] [platform] Fix Makefile targets Signed-off-by: Andrei Kvapil --- packages/core/platform/Makefile | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/core/platform/Makefile b/packages/core/platform/Makefile index e47af225..15ed7b36 100644 --- a/packages/core/platform/Makefile +++ b/packages/core/platform/Makefile @@ -1,19 +1,18 @@ -NAME=platform +NAME=cozystack-platform NAMESPACE=cozy-system include ../../../hack/common-envs.mk show: - cozyhr show --namespace $(NAMESPACE) $(NAME) --plain + cozyhr show --namespace $(NAMESPACE) $(NAME) apply: - cozyhr show --namespace $(NAMESPACE) $(NAME) --plain | kubectl apply --filename - - kubectl delete helmreleases.helm.toolkit.fluxcd.io --selector cozystack.io/marked-for-deletion=true --all-namespaces + cozyhr apply --namespace $(NAMESPACE) $(NAME) reconcile: apply diff: - cozyhr show --namespace $(NAMESPACE) $(NAME) --plain | kubectl diff --filename - + cozyhr diff --namespace $(NAMESPACE) $(NAME) image: image-migrations From a628adeb35826ff292ecd0eeefbf8f0c0b16e493 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 12 Feb 2026 17:18:14 +0100 Subject: [PATCH 249/889] [platform] Switch cozystack-api from DaemonSet to Deployment with PreferClose Replace DaemonSet with direct host API access in favor of a regular Deployment using Service trafficDistribution: PreferClose. This provides prefer-local routing to the nearest cozystack-api pod with fallback to remote pods when local one is unavailable. - Replace DaemonSet/Deployment toggle with always-Deployment - Replace internalTrafficPolicy: Local with trafficDistribution: PreferClose - Remove KUBERNETES_SERVICE_HOST/PORT override (use default kubernetes service) - Replace hard nodeSelector with soft nodeAffinity for control-plane nodes - Simplify migration hook to always clean up DaemonSet if present Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- .../cozystack-api/templates/deployment.yaml | 30 +++++-------------- .../system/cozystack-api/templates/hook.yaml | 17 ++--------- .../cozystack-api/templates/service.yaml | 4 +-- packages/system/cozystack-api/values.yaml | 7 ----- 4 files changed, 12 insertions(+), 46 deletions(-) diff --git a/packages/system/cozystack-api/templates/deployment.yaml b/packages/system/cozystack-api/templates/deployment.yaml index aa877266..9febfe1e 100644 --- a/packages/system/cozystack-api/templates/deployment.yaml +++ b/packages/system/cozystack-api/templates/deployment.yaml @@ -1,18 +1,12 @@ apiVersion: apps/v1 -{{- if .Values.cozystackAPI.localK8sAPIEndpoint.enabled }} -kind: DaemonSet -{{- else }} kind: Deployment -{{- end }} metadata: name: cozystack-api namespace: cozy-system labels: app: cozystack-api spec: - {{- if not .Values.cozystackAPI.localK8sAPIEndpoint.enabled }} replicas: {{ .Values.cozystackAPI.replicas }} - {{- end }} selector: matchLabels: app: cozystack-api @@ -24,27 +18,19 @@ spec: tolerations: - operator: Exists serviceAccountName: cozystack-api - {{- if .Values.cozystackAPI.localK8sAPIEndpoint.enabled }} - {{- with .Values.cozystackAPI.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- end }} + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + preference: + matchExpressions: + - key: node-role.kubernetes.io/control-plane + operator: Exists containers: - name: cozystack-api args: - --tls-cert-file=/tmp/cozystack-api-certs/tls.crt - --tls-private-key-file=/tmp/cozystack-api-certs/tls.key - {{- if .Values.cozystackAPI.localK8sAPIEndpoint.enabled }} - env: - - name: KUBERNETES_SERVICE_HOST - valueFrom: - fieldRef: - apiVersion: v1 - fieldPath: status.hostIP - - name: KUBERNETES_SERVICE_PORT - value: "6443" - {{- end }} image: "{{ .Values.cozystackAPI.image }}" ports: - containerPort: 443 diff --git a/packages/system/cozystack-api/templates/hook.yaml b/packages/system/cozystack-api/templates/hook.yaml index 3c6b3080..852b2db0 100644 --- a/packages/system/cozystack-api/templates/hook.yaml +++ b/packages/system/cozystack-api/templates/hook.yaml @@ -1,16 +1,5 @@ -{{- $shouldRunUpdateHook := false }} -{{- $previousKind := "Deployment" }} -{{- $previousKindPlural := "deployments" }} -{{- if not .Values.cozystackAPI.localK8sAPIEndpoint.enabled }} - {{- $previousKind = "DaemonSet" }} - {{- $previousKindPlural = "daemonsets" }} -{{- end }} -{{- $previous := lookup "apps/v1" $previousKind .Release.Namespace "cozystack-api" }} +{{- $previous := lookup "apps/v1" "DaemonSet" .Release.Namespace "cozystack-api" }} {{- if $previous }} - {{- $shouldRunUpdateHook = true }} -{{- end }} - -{{- if $shouldRunUpdateHook }} --- apiVersion: batch/v1 kind: Job @@ -36,7 +25,7 @@ spec: - -exc - |- kubectl --namespace={{ .Release.Namespace }} delete --ignore-not-found \ - {{ $previousKindPlural }}.apps cozystack-api + daemonsets.apps cozystack-api restartPolicy: Never --- apiVersion: rbac.authorization.k8s.io/v1 @@ -51,7 +40,7 @@ rules: - apiGroups: - "apps" resources: - - "{{ $previousKindPlural }}" + - "daemonsets" verbs: - get - delete diff --git a/packages/system/cozystack-api/templates/service.yaml b/packages/system/cozystack-api/templates/service.yaml index abe67abc..64ba149d 100644 --- a/packages/system/cozystack-api/templates/service.yaml +++ b/packages/system/cozystack-api/templates/service.yaml @@ -4,9 +4,7 @@ metadata: name: cozystack-api namespace: cozy-system spec: - {{- if .Values.cozystackAPI.localK8sAPIEndpoint.enabled }} - internalTrafficPolicy: Local - {{- end }} + trafficDistribution: PreferClose ports: - port: 443 protocol: TCP diff --git a/packages/system/cozystack-api/values.yaml b/packages/system/cozystack-api/values.yaml index d9631b94..769cd4b6 100644 --- a/packages/system/cozystack-api/values.yaml +++ b/packages/system/cozystack-api/values.yaml @@ -1,10 +1,3 @@ cozystackAPI: image: ghcr.io/cozystack/cozystack/cozystack-api:v1.0.0-beta.3@sha256:a263702859c36f85d097c300c1be462aaa45a3955d69e9ae72a37cabf6dadaa9 - localK8sAPIEndpoint: - enabled: true replicas: 2 - # nodeSelector for DaemonSet mode (localK8sAPIEndpoint.enabled: true) - # Talos uses empty value: "node-role.kubernetes.io/control-plane": "" - # Generic k8s (k3s, kubeadm) uses: "node-role.kubernetes.io/control-plane": "true" - nodeSelector: - node-role.kubernetes.io/control-plane: "" From 1af999a5000dfe4475d3a8753cd1c76e378352f0 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 12 Feb 2026 23:24:43 +0100 Subject: [PATCH 250/889] feat(csi): add RWX Filesystem (NFS) support to kubevirt-csi-driver wrapper Implement a wrapper around upstream kubevirt-csi-driver that adds RWX Filesystem volume support via LINSTOR NFS exports: - CreateVolume: intercepts RWX+Filesystem requests, creates DataVolume with explicit AccessMode=RWX and VolumeMode=Filesystem - ControllerPublishVolume: waits for PVC bound, extracts NFS export URL from infra PV, creates CiliumNetworkPolicy with VMI ownerReferences - ControllerUnpublishVolume: manages CNP ownerReferences per-VMI - ControllerExpandVolume: delegates to upstream, disables node expansion for NFS volumes - NodeStageVolume/NodePublishVolume: mounts NFS at target path - NodeExpandVolume: no-op for NFS (LINSTOR handles resize) Also includes: - RBAC for CiliumNetworkPolicy management in infra cluster - Explicit --run-node-service=false for controller deployment - Explicit --run-controller-service=false for node DaemonSet - nfs-utils in container image for NFS mount support Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- .../kubernetes/images/kubevirt-csi-driver.tag | 2 +- .../images/kubevirt-csi-driver/Dockerfile | 28 +- .../images/kubevirt-csi-driver/controller.go | 525 +++++++++++++++++ .../images/kubevirt-csi-driver/go.mod | 101 ++++ .../images/kubevirt-csi-driver/go.sum | 543 ++++++++++++++++++ .../images/kubevirt-csi-driver/main.go | 219 +++++++ .../images/kubevirt-csi-driver/node.go | 106 ++++ .../apps/kubernetes/templates/csi/deploy.yaml | 1 + .../csi/infra-cluster-service-account.yaml | 3 + .../kubevirt-csi-node/templates/deploy.yaml | 1 + packages/system/kubevirt-csi-node/values.yaml | 2 +- 11 files changed, 1511 insertions(+), 20 deletions(-) create mode 100644 packages/apps/kubernetes/images/kubevirt-csi-driver/controller.go create mode 100644 packages/apps/kubernetes/images/kubevirt-csi-driver/go.mod create mode 100644 packages/apps/kubernetes/images/kubevirt-csi-driver/go.sum create mode 100644 packages/apps/kubernetes/images/kubevirt-csi-driver/main.go create mode 100644 packages/apps/kubernetes/images/kubevirt-csi-driver/node.go diff --git a/packages/apps/kubernetes/images/kubevirt-csi-driver.tag b/packages/apps/kubernetes/images/kubevirt-csi-driver.tag index 421a4084..ca67ba13 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:8f1ab4c3b2bed3a0adc40fcc823b040fa04b4722bec7735c030e79a3a2fd6c85 +ghcr.io/cozystack/cozystack/kubevirt-csi-driver:latest@sha256:cd58760c97ba50ef74ff940cdcda4b9a6d8554eec8305fc96c2e8bbea72b75a9 diff --git a/packages/apps/kubernetes/images/kubevirt-csi-driver/Dockerfile b/packages/apps/kubernetes/images/kubevirt-csi-driver/Dockerfile index 5ae5817f..be8125d7 100644 --- a/packages/apps/kubernetes/images/kubevirt-csi-driver/Dockerfile +++ b/packages/apps/kubernetes/images/kubevirt-csi-driver/Dockerfile @@ -1,31 +1,23 @@ -# Source: https://github.com/kubevirt/csi-driver/blob/main/Dockerfile ARG builder_image=docker.io/library/golang:1.22.5 FROM ${builder_image} AS builder -RUN git clone https://github.com/kubevirt/csi-driver /src/kubevirt-csi-driver \ - && cd /src/kubevirt-csi-driver \ - && git checkout a8d6605bc9997bcfda3fb9f1f82ba6445b4984cc ARG TARGETOS ARG TARGETARCH -ENV GOOS=$TARGETOS -ENV GOARCH=$TARGETARCH -WORKDIR /src/kubevirt-csi-driver +WORKDIR /src -RUN make build +COPY go.mod go.sum ./ +RUN go mod download + +COPY *.go ./ +RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build \ + -ldflags "-X kubevirt.io/csi-driver/pkg/service.VendorVersion=0.2.0" \ + -o kubevirt-csi-driver . FROM quay.io/centos/centos:stream9 -ARG git_url=https://github.com/kubevirt/csi-driver.git - -LABEL maintainers="The KubeVirt Project " \ - description="KubeVirt CSI Driver" \ - multi.GIT_URL=${git_url} ENTRYPOINT ["./kubevirt-csi-driver"] -RUN dnf install -y e2fsprogs xfsprogs && dnf clean all +RUN dnf install -y e2fsprogs xfsprogs nfs-utils && dnf clean all -ARG git_sha=NONE -LABEL multi.GIT_SHA=${git_sha} - -COPY --from=builder /src/kubevirt-csi-driver/kubevirt-csi-driver . +COPY --from=builder /src/kubevirt-csi-driver . diff --git a/packages/apps/kubernetes/images/kubevirt-csi-driver/controller.go b/packages/apps/kubernetes/images/kubevirt-csi-driver/controller.go new file mode 100644 index 00000000..524b45ea --- /dev/null +++ b/packages/apps/kubernetes/images/kubevirt-csi-driver/controller.go @@ -0,0 +1,525 @@ +package main + +import ( + "context" + "fmt" + "net/url" + "time" + + csi "github.com/container-storage-interface/spec/lib/go/csi" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/cache" + "k8s.io/klog/v2" + cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1" + + kubevirtclient "kubevirt.io/csi-driver/pkg/kubevirt" + "kubevirt.io/csi-driver/pkg/service" + "kubevirt.io/csi-driver/pkg/util" +) + +const ( + nfsVolumeKey = "nfsVolume" + nfsExportKey = "nfsExport" + busParameter = "bus" + serialParameter = "serial" +) + +var ciliumNetworkPolicyGVR = schema.GroupVersionResource{ + Group: "cilium.io", + Version: "v2", + Resource: "ciliumnetworkpolicies", +} + +var _ csi.ControllerServer = &WrappedControllerService{} + +// WrappedControllerService embeds the upstream ControllerService and adds RWX Filesystem (NFS) support. +type WrappedControllerService struct { + *service.ControllerService + infraClient kubernetes.Interface + dynamicClient dynamic.Interface + virtClient kubevirtclient.Client + infraNamespace string + infraClusterLabels map[string]string + storageClassEnforcement util.StorageClassEnforcement +} + +// isRWXFilesystem checks if the volume capabilities request RWX access with filesystem mode. +func isRWXFilesystem(caps []*csi.VolumeCapability) bool { + hasRWX := false + hasMount := false + for _, cap := range caps { + if cap == nil { + continue + } + if cap.GetMount() != nil { + hasMount = true + } + if am := cap.GetAccessMode(); am != nil && am.Mode == csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER { + hasRWX = true + } + } + return hasRWX && hasMount +} + +// CreateVolume intercepts RWX Filesystem requests and creates a DataVolume in the infra +// cluster with AccessMode=RWX and VolumeMode=Filesystem. Upstream rejects RWX+Filesystem, +// so we handle DataVolume creation ourselves. Using DataVolume (not bare PVC) preserves +// compatibility with upstream snapshot and clone operations. +// For all other requests, delegates to upstream. +func (w *WrappedControllerService) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) { + if !isRWXFilesystem(req.GetVolumeCapabilities()) { + return w.ControllerService.CreateVolume(ctx, req) + } + + if req == nil { + return nil, status.Error(codes.InvalidArgument, "missing request") + } + if len(req.GetName()) == 0 { + return nil, status.Error(codes.InvalidArgument, "name missing in request") + } + + // Storage class enforcement + storageClassName := req.Parameters[kubevirtclient.InfraStorageClassNameParameter] + if !w.storageClassEnforcement.AllowAll { + if storageClassName == "" { + if !w.storageClassEnforcement.AllowDefault { + return nil, status.Error(codes.InvalidArgument, "infraStorageclass is not in the allowed list") + } + } else if !util.Contains(w.storageClassEnforcement.AllowList, storageClassName) { + return nil, status.Error(codes.InvalidArgument, "infraStorageclass is not in the allowed list") + } + } + + storageSize := req.GetCapacityRange().GetRequiredBytes() + dvName := req.Name + + // Determine DataVolume source (blank, snapshot, or clone) + source, err := w.determineDvSource(ctx, req) + if err != nil { + return nil, err + } + + // Handle CSI clone: CDI doesn't allow cloning PVCs in use by a pod, + // so use DataSourceRef instead (same approach as upstream) + sourcePVCName := "" + if source.PVC != nil { + sourcePVCName = source.PVC.Name + source = nil + } + + volumeMode := corev1.PersistentVolumeFilesystem + dv := &cdiv1.DataVolume{ + TypeMeta: metav1.TypeMeta{ + Kind: "DataVolume", + APIVersion: cdiv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: dvName, + Namespace: w.infraNamespace, + Labels: w.infraClusterLabels, + Annotations: map[string]string{ + "cdi.kubevirt.io/storage.deleteAfterCompletion": "false", + "cdi.kubevirt.io/storage.bind.immediate.requested": "true", + }, + }, + Spec: cdiv1.DataVolumeSpec{ + Storage: &cdiv1.StorageSpec{ + AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteMany}, + VolumeMode: &volumeMode, + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceStorage: *resource.NewScaledQuantity(storageSize, 0), + }, + }, + }, + Source: source, + }, + } + + if sourcePVCName != "" { + dv.Spec.Storage.DataSourceRef = &corev1.TypedObjectReference{ + Kind: "PersistentVolumeClaim", + Name: sourcePVCName, + } + } + + if storageClassName != "" { + dv.Spec.Storage.StorageClassName = &storageClassName + } + + // Idempotency: check if DataVolume already exists + if existingDv, err := w.virtClient.GetDataVolume(ctx, w.infraNamespace, dvName); errors.IsNotFound(err) { + klog.Infof("Creating NFS DataVolume %s/%s", w.infraNamespace, dvName) + dv, err = w.virtClient.CreateDataVolume(ctx, w.infraNamespace, dv) + if err != nil { + klog.Errorf("Failed creating NFS DataVolume %s: %v", dvName, err) + return nil, err + } + } else if err != nil { + return nil, err + } else { + if existingDv != nil && existingDv.Spec.Storage != nil { + existingRequest := existingDv.Spec.Storage.Resources.Requests[corev1.ResourceStorage] + newRequest := dv.Spec.Storage.Resources.Requests[corev1.ResourceStorage] + if newRequest.Cmp(existingRequest) != 0 { + return nil, status.Error(codes.AlreadyExists, "requested storage size does not match existing size") + } + dv = existingDv + } + } + + serial := string(dv.GetUID()) + + return &csi.CreateVolumeResponse{ + Volume: &csi.Volume{ + CapacityBytes: storageSize, + VolumeId: dvName, + VolumeContext: map[string]string{ + busParameter: "scsi", + serialParameter: serial, + nfsVolumeKey: "true", + }, + ContentSource: req.GetVolumeContentSource(), + }, + }, nil +} + +// determineDvSource determines the DataVolume source from the CSI request content source. +// Mirrors upstream logic for blank, snapshot, and clone sources. +func (w *WrappedControllerService) determineDvSource(ctx context.Context, req *csi.CreateVolumeRequest) (*cdiv1.DataVolumeSource, error) { + res := &cdiv1.DataVolumeSource{} + if req.GetVolumeContentSource() != nil { + source := req.GetVolumeContentSource() + switch source.Type.(type) { + case *csi.VolumeContentSource_Snapshot: + snapshot, err := w.virtClient.GetVolumeSnapshot(ctx, w.infraNamespace, source.GetSnapshot().GetSnapshotId()) + if errors.IsNotFound(err) { + return nil, status.Errorf(codes.NotFound, "source snapshot %s not found", source.GetSnapshot().GetSnapshotId()) + } else if err != nil { + return nil, err + } + if snapshot != nil { + res.Snapshot = &cdiv1.DataVolumeSourceSnapshot{ + Name: snapshot.Name, + Namespace: w.infraNamespace, + } + } + case *csi.VolumeContentSource_Volume: + volume, err := w.virtClient.GetDataVolume(ctx, w.infraNamespace, source.GetVolume().GetVolumeId()) + if errors.IsNotFound(err) { + return nil, status.Errorf(codes.NotFound, "source volume %s not found", source.GetVolume().GetVolumeId()) + } else if err != nil { + return nil, err + } + if volume != nil { + res.PVC = &cdiv1.DataVolumeSourcePVC{ + Name: volume.Name, + Namespace: w.infraNamespace, + } + } + default: + return nil, status.Error(codes.InvalidArgument, "unknown content type") + } + } else { + res.Blank = &cdiv1.DataVolumeBlankImage{} + } + return res, nil +} + +// ControllerPublishVolume for NFS volumes: annotates infra PVC for WFFC binding, +// waits for PVC bound, extracts NFS export from PV, and creates CiliumNetworkPolicy. +// For RWO volumes, delegates to upstream (hotplug SCSI). +func (w *WrappedControllerService) ControllerPublishVolume(ctx context.Context, req *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error) { + if req.GetVolumeContext()[nfsVolumeKey] != "true" { + return w.ControllerService.ControllerPublishVolume(ctx, req) + } + + if len(req.GetVolumeId()) == 0 { + return nil, status.Error(codes.InvalidArgument, "volume id missing in request") + } + if len(req.GetNodeId()) == 0 { + return nil, status.Error(codes.InvalidArgument, "node id missing in request") + } + + dvName := req.GetVolumeId() + vmNamespace, vmName, err := cache.SplitMetaNamespaceKey(req.GetNodeId()) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to parse node ID %q: %v", req.GetNodeId(), err) + } + + klog.V(3).Infof("Publishing NFS volume %s to node %s/%s", dvName, vmNamespace, vmName) + + // Get VMI for CiliumNetworkPolicy ownerReference + vmi, err := w.virtClient.GetVirtualMachine(ctx, w.infraNamespace, vmName) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to get VMI %s: %v", vmName, err) + } + + // Wait for PVC to be bound (CDI handles immediate binding via annotation) + klog.V(3).Infof("Waiting for PVC %s to be bound", dvName) + if err := wait.PollUntilContextTimeout(ctx, time.Second, 2*time.Minute, true, func(ctx context.Context) (bool, error) { + p, err := w.infraClient.CoreV1().PersistentVolumeClaims(w.infraNamespace).Get(ctx, dvName, metav1.GetOptions{}) + if err != nil { + return false, err + } + return p.Status.Phase == corev1.ClaimBound, nil + }); err != nil { + return nil, status.Errorf(codes.Internal, "timed out waiting for PVC %s to be bound: %v", dvName, err) + } + + // Read PV to get NFS export + pvc, err := w.infraClient.CoreV1().PersistentVolumeClaims(w.infraNamespace).Get(ctx, dvName, metav1.GetOptions{}) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to re-read PVC %s: %v", dvName, err) + } + pv, err := w.infraClient.CoreV1().PersistentVolumes().Get(ctx, pvc.Spec.VolumeName, metav1.GetOptions{}) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to get PV %s: %v", pvc.Spec.VolumeName, err) + } + nfsExport, err := getNFSExport(pv) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to extract NFS export from PV %s: %v", pv.Name, err) + } + klog.V(3).Infof("NFS export for volume %s: %s", dvName, nfsExport) + + // Parse NFS URL for CiliumNetworkPolicy port + _, port, _, err := parseNFSExport(nfsExport) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to parse NFS export URL: %v", err) + } + + // Create or update CiliumNetworkPolicy allowing egress to NFS server + cnpName := fmt.Sprintf("csi-nfs-%s", dvName) + vmiOwnerRef := map[string]interface{}{ + "apiVersion": "kubevirt.io/v1", + "kind": "VirtualMachineInstance", + "name": vmName, + "uid": string(vmi.UID), + } + cnp := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": "cilium.io/v2", + "kind": "CiliumNetworkPolicy", + "metadata": map[string]interface{}{ + "name": cnpName, + "namespace": vmNamespace, + "ownerReferences": []interface{}{vmiOwnerRef}, + }, + "spec": map[string]interface{}{ + "endpointSelector": map[string]interface{}{}, + "egress": []interface{}{ + map[string]interface{}{ + "toEndpoints": []interface{}{ + map[string]interface{}{ + "matchLabels": map[string]interface{}{ + "k8s:app.kubernetes.io/component": "linstor-csi-nfs-server", + "k8s:io.kubernetes.pod.namespace": "cozy-linstor", + }, + }, + }, + "toPorts": []interface{}{ + map[string]interface{}{ + "ports": []interface{}{ + map[string]interface{}{ + "port": port, + "protocol": "TCP", + }, + }, + }, + }, + }, + }, + }, + }, + } + + if _, err := w.dynamicClient.Resource(ciliumNetworkPolicyGVR).Namespace(vmNamespace).Create(ctx, cnp, metav1.CreateOptions{}); err != nil { + if !errors.IsAlreadyExists(err) { + return nil, status.Errorf(codes.Internal, "failed to create CiliumNetworkPolicy %s: %v", cnpName, err) + } + // CNP exists — add ownerReference for this VMI + if err := w.addCNPOwnerReference(ctx, vmNamespace, cnpName, vmiOwnerRef); err != nil { + return nil, err + } + } + + klog.V(3).Infof("Successfully published NFS volume %s", dvName) + return &csi.ControllerPublishVolumeResponse{ + PublishContext: map[string]string{ + nfsExportKey: nfsExport, + }, + }, nil +} + +// ControllerUnpublishVolume for NFS volumes: deletes CiliumNetworkPolicy. +// For RWO volumes, delegates to upstream (hotplug removal). +func (w *WrappedControllerService) ControllerUnpublishVolume(ctx context.Context, req *csi.ControllerUnpublishVolumeRequest) (*csi.ControllerUnpublishVolumeResponse, error) { + dvName := req.GetVolumeId() + + // Determine if NFS by checking infra PVC access modes + pvc, err := w.infraClient.CoreV1().PersistentVolumeClaims(w.infraNamespace).Get(ctx, dvName, metav1.GetOptions{}) + if err != nil { + if errors.IsNotFound(err) { + return &csi.ControllerUnpublishVolumeResponse{}, nil + } + return nil, err + } + + if !hasRWXAccessMode(pvc) { + return w.ControllerService.ControllerUnpublishVolume(ctx, req) + } + + // NFS volume: remove VMI ownerReference from CiliumNetworkPolicy + vmNamespace, vmName, err := cache.SplitMetaNamespaceKey(req.GetNodeId()) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to parse node ID %q: %v", req.GetNodeId(), err) + } + + cnpName := fmt.Sprintf("csi-nfs-%s", dvName) + klog.V(3).Infof("Removing VMI %s ownerReference from CiliumNetworkPolicy %s/%s", vmName, vmNamespace, cnpName) + if err := w.removeCNPOwnerReference(ctx, vmNamespace, cnpName, vmName); err != nil { + return nil, err + } + + klog.V(3).Infof("Successfully unpublished NFS volume %s", dvName) + return &csi.ControllerUnpublishVolumeResponse{}, nil +} + +// ControllerExpandVolume delegates to upstream for the actual DataVolume/PVC resize. +// For NFS volumes, LINSTOR handles NFS server resize automatically, so no node expansion is needed. +func (w *WrappedControllerService) ControllerExpandVolume(ctx context.Context, req *csi.ControllerExpandVolumeRequest) (*csi.ControllerExpandVolumeResponse, error) { + resp, err := w.ControllerService.ControllerExpandVolume(ctx, req) + if err != nil { + return nil, err + } + + // For NFS volumes, no node-side expansion is needed + pvc, err := w.infraClient.CoreV1().PersistentVolumeClaims(w.infraNamespace).Get(ctx, req.GetVolumeId(), metav1.GetOptions{}) + if err == nil && hasRWXAccessMode(pvc) { + resp.NodeExpansionRequired = false + } + + return resp, nil +} + +// addCNPOwnerReference adds a VMI ownerReference to an existing CiliumNetworkPolicy. +func (w *WrappedControllerService) addCNPOwnerReference(ctx context.Context, namespace, cnpName string, ownerRef map[string]interface{}) error { + existing, err := w.dynamicClient.Resource(ciliumNetworkPolicyGVR).Namespace(namespace).Get(ctx, cnpName, metav1.GetOptions{}) + if err != nil { + return status.Errorf(codes.Internal, "failed to get CiliumNetworkPolicy %s: %v", cnpName, err) + } + + ownerRefs, _, _ := unstructured.NestedSlice(existing.Object, "metadata", "ownerReferences") + uid, _, _ := unstructured.NestedString(ownerRef, "uid") + for _, ref := range ownerRefs { + if refMap, ok := ref.(map[string]interface{}); ok { + if refMap["uid"] == uid { + return nil // already present + } + } + } + + ownerRefs = append(ownerRefs, ownerRef) + if err := unstructured.SetNestedSlice(existing.Object, ownerRefs, "metadata", "ownerReferences"); err != nil { + return status.Errorf(codes.Internal, "failed to set ownerReferences: %v", err) + } + if _, err := w.dynamicClient.Resource(ciliumNetworkPolicyGVR).Namespace(namespace).Update(ctx, existing, metav1.UpdateOptions{}); err != nil { + return status.Errorf(codes.Internal, "failed to update CiliumNetworkPolicy %s: %v", cnpName, err) + } + klog.V(3).Infof("Added ownerReference to CiliumNetworkPolicy %s", cnpName) + return nil +} + +// removeCNPOwnerReference removes a VMI ownerReference from a CiliumNetworkPolicy. +// Deletes the CNP if no ownerReferences remain. +func (w *WrappedControllerService) removeCNPOwnerReference(ctx context.Context, namespace, cnpName, vmName string) error { + existing, err := w.dynamicClient.Resource(ciliumNetworkPolicyGVR).Namespace(namespace).Get(ctx, cnpName, metav1.GetOptions{}) + if err != nil { + if errors.IsNotFound(err) { + return nil + } + return status.Errorf(codes.Internal, "failed to get CiliumNetworkPolicy %s: %v", cnpName, err) + } + + ownerRefs, _, _ := unstructured.NestedSlice(existing.Object, "metadata", "ownerReferences") + var remaining []interface{} + for _, ref := range ownerRefs { + if refMap, ok := ref.(map[string]interface{}); ok { + if refMap["name"] == vmName { + continue + } + } + remaining = append(remaining, ref) + } + + if len(remaining) == 0 { + // Last owner — delete CNP + if err := w.dynamicClient.Resource(ciliumNetworkPolicyGVR).Namespace(namespace).Delete(ctx, cnpName, metav1.DeleteOptions{}); err != nil { + if !errors.IsNotFound(err) { + return status.Errorf(codes.Internal, "failed to delete CiliumNetworkPolicy %s: %v", cnpName, err) + } + } + klog.V(3).Infof("Deleted CiliumNetworkPolicy %s (no more owners)", cnpName) + return nil + } + + if err := unstructured.SetNestedSlice(existing.Object, remaining, "metadata", "ownerReferences"); err != nil { + return status.Errorf(codes.Internal, "failed to set ownerReferences: %v", err) + } + if _, err := w.dynamicClient.Resource(ciliumNetworkPolicyGVR).Namespace(namespace).Update(ctx, existing, metav1.UpdateOptions{}); err != nil { + return status.Errorf(codes.Internal, "failed to update CiliumNetworkPolicy %s: %v", cnpName, err) + } + klog.V(3).Infof("Removed VMI %s ownerReference from CiliumNetworkPolicy %s", vmName, cnpName) + return nil +} + +func hasRWXAccessMode(pvc *corev1.PersistentVolumeClaim) bool { + for _, mode := range pvc.Spec.AccessModes { + if mode == corev1.ReadWriteMany { + return true + } + } + return false +} + +// getNFSExport extracts the NFS export URL from a PersistentVolume. +// Supports both native NFS PVs and CSI PVs with nfs-export volume attribute. +func getNFSExport(pv *corev1.PersistentVolume) (string, error) { + if pv.Spec.NFS != nil { + return fmt.Sprintf("nfs://%s:2049%s", pv.Spec.NFS.Server, pv.Spec.NFS.Path), nil + } + if pv.Spec.CSI != nil && pv.Spec.CSI.VolumeAttributes != nil { + if export, ok := pv.Spec.CSI.VolumeAttributes["linstor.csi.linbit.com/nfs-export"]; ok { + return export, nil + } + } + return "", fmt.Errorf("no NFS export info found in PV %s", pv.Name) +} + +// parseNFSExport parses an NFS URL of the form nfs://host:port/path. +func parseNFSExport(nfsURL string) (host, port, path string, err error) { + u, err := url.Parse(nfsURL) + if err != nil { + return "", "", "", fmt.Errorf("failed to parse NFS URL %q: %w", nfsURL, err) + } + host = u.Hostname() + port = u.Port() + if port == "" { + port = "2049" + } + path = u.Path + if path == "" { + path = "/" + } + return host, port, path, nil +} diff --git a/packages/apps/kubernetes/images/kubevirt-csi-driver/go.mod b/packages/apps/kubernetes/images/kubevirt-csi-driver/go.mod new file mode 100644 index 00000000..96a96107 --- /dev/null +++ b/packages/apps/kubernetes/images/kubevirt-csi-driver/go.mod @@ -0,0 +1,101 @@ +module cozystack.io/kubevirt-csi-driver + +go 1.22.0 + +toolchain go1.22.5 + +require ( + github.com/container-storage-interface/spec v1.10.0 + google.golang.org/grpc v1.65.0 + gopkg.in/yaml.v2 v2.4.0 + k8s.io/api v0.31.4 + k8s.io/apimachinery v0.31.4 + k8s.io/client-go v12.0.0+incompatible + k8s.io/klog/v2 v2.130.1 + k8s.io/mount-utils v0.33.1 + kubevirt.io/containerized-data-importer-api v1.59.0 + kubevirt.io/csi-driver v0.0.0-20250702202414-a8d6605bc999 +) + +require ( + 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/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.22.4 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/mock v1.6.0 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/imdario/mergo v0.3.15 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/kubernetes-csi/csi-lib-utils v0.18.1 // indirect + github.com/kubernetes-csi/external-snapshotter/client/v6 v6.3.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/moby/sys/mountinfo v0.7.1 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/opencontainers/runc v1.1.13 // indirect + github.com/opencontainers/runtime-spec v1.0.3-0.20220909204839-494a5a6aca78 // indirect + github.com/openshift/api v0.0.0-20230503133300-8bbcb7ca7183 // indirect + github.com/openshift/custom-resource-status v1.1.2 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/x448/float16 v0.8.4 // indirect + golang.org/x/net v0.33.0 // indirect + golang.org/x/oauth2 v0.21.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.3.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect + google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apiextensions-apiserver v0.26.4 // indirect + k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect + k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect + kubevirt.io/api v1.2.2 // indirect + kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect +) + +replace ( + k8s.io/api => k8s.io/api v0.31.4 + k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.31.4 + k8s.io/apimachinery => k8s.io/apimachinery v0.31.4 + k8s.io/apiserver => k8s.io/apiserver v0.31.4 + k8s.io/cli-runtime => k8s.io/cli-runtime v0.31.4 + k8s.io/client-go => k8s.io/client-go v0.31.4 + k8s.io/cloud-provider => k8s.io/cloud-provider v0.31.4 + k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.31.4 + k8s.io/code-generator => k8s.io/code-generator v0.31.4 + k8s.io/component-base => k8s.io/component-base v0.31.4 + k8s.io/component-helpers => k8s.io/component-helpers v0.31.4 + k8s.io/controller-manager => k8s.io/controller-manager v0.31.4 + k8s.io/cri-api => k8s.io/cri-api v0.31.4 + k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.31.4 + k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.31.4 + k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.31.4 + k8s.io/kube-proxy => k8s.io/kube-proxy v0.31.4 + k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.31.4 + k8s.io/kubectl => k8s.io/kubectl v0.31.4 + k8s.io/kubelet => k8s.io/kubelet v0.31.4 + k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.31.4 + k8s.io/metrics => k8s.io/metrics v0.31.4 + k8s.io/mount-utils => k8s.io/mount-utils v0.31.4 + k8s.io/pod-security-admission => k8s.io/pod-security-admission v0.31.4 + k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.31.4 + kubevirt.io/csi-driver => github.com/kubevirt/csi-driver v0.0.0-20250702202414-a8d6605bc999 +) diff --git a/packages/apps/kubernetes/images/kubevirt-csi-driver/go.sum b/packages/apps/kubernetes/images/kubevirt-csi-driver/go.sum new file mode 100644 index 00000000..a0c4ac81 --- /dev/null +++ b/packages/apps/kubernetes/images/kubevirt-csi-driver/go.sum @@ -0,0 +1,543 @@ +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= +github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs= +github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= +github.com/container-storage-interface/spec v1.10.0 h1:YkzWPV39x+ZMTa6Ax2czJLLwpryrQ+dPesB34mrRMXA= +github.com/container-storage-interface/spec v1.10.0/go.mod h1:DtUvaQszPml1YJfIK7c00mlv6/g4wNMLanLgiUbKFRI= +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/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= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.15.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +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/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +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-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= +github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/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/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +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/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.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-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM= +github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= +github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= +github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +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.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +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/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +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= +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/kubernetes-csi/csi-lib-utils v0.18.1 h1:vpg1kbQ6lFVCz7mY71zcqVE7W0GAQXXBoFfHvbW3gdw= +github.com/kubernetes-csi/csi-lib-utils v0.18.1/go.mod h1:PIcn27zmbY0KBue4JDdZVfDF56tjcS3jKroZPi+pMoY= +github.com/kubernetes-csi/external-snapshotter/client/v6 v6.3.0 h1:qS4r4ljINLWKJ9m9Ge3Q3sGZ/eIoDVDT2RhAdQFHb1k= +github.com/kubernetes-csi/external-snapshotter/client/v6 v6.3.0/go.mod h1:oGXx2XTEzs9ikW2V6IC1dD8trgjRsS/Mvc2JRiC618Y= +github.com/kubevirt/csi-driver v0.0.0-20250702202414-a8d6605bc999 h1:nq11swNPIRarVZm/YTOWh9rbuEqivgpA1c4RUEFSL90= +github.com/kubevirt/csi-driver v0.0.0-20250702202414-a8d6605bc999/go.mod h1:Pzl14YlqPpoK/ZdS79tUSuHbC2L/NPqOIjFTN5kTmUg= +github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/moby/spdystream v0.4.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/sys/mountinfo v0.7.1 h1:/tTvQaSJRr2FshkhXiIpux6fQ2Zvc4j7tAhMTStAG2g= +github.com/moby/sys/mountinfo v0.7.1/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= +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.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +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/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +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/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= +github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= +github.com/onsi/ginkgo/v2 v2.3.0/go.mod h1:Eew0uilEqZmIEZr8JrvYlvOM7Rr6xzTmMV8AyFNU9d0= +github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= +github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw= +github.com/onsi/ginkgo/v2 v2.7.0/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo= +github.com/onsi/ginkgo/v2 v2.8.1/go.mod h1:N1/NbDngAFcSLdyZ+/aYTYGSlq9qMCS/cNKGJjy+csc= +github.com/onsi/ginkgo/v2 v2.9.0/go.mod h1:4xkjoL/tZv4SMWeww56BU5kAt19mVB47gTWxmrTcxyk= +github.com/onsi/ginkgo/v2 v2.9.1/go.mod h1:FEcmzVcCHl+4o9bQZVab+4dC9+j+91t2FHSzmGAPfuo= +github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts= +github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= +github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0= +github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= +github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= +github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs= +github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= +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 v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/onsi/gomega v1.20.1/go.mod h1:DtrZpjmvpn2mPm4YWQa0/ALMDj9v4YxLgojwPeREyVo= +github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc= +github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM= +github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= +github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM= +github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= +github.com/onsi/gomega v1.27.1/go.mod h1:aHX5xOykVYzWOV4WqQy0sy8BQptgukenXpCXfadcIAw= +github.com/onsi/gomega v1.27.3/go.mod h1:5vG284IBtfDAmDyrK+eGyZmUgUlmi+Wngqo557cZ6Gw= +github.com/onsi/gomega v1.27.4/go.mod h1:riYq/GJKh8hhoM01HN6Vmuy93AarCXCBGpvFDK3q3fQ= +github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= +github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4= +github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= +github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/onsi/gomega v1.33.0/go.mod h1:+925n5YtiFsLzzafLUHzVMBpvvRAzrydIBiSIxjX3wY= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/opencontainers/runc v1.1.13 h1:98S2srgG9vw0zWcDpFMn5TRrh8kLxa/5OFUstuUhmRs= +github.com/opencontainers/runc v1.1.13/go.mod h1:R016aXacfp/gwQBYw2FDGa9m+n6atbLWrYY8hNMT/sA= +github.com/opencontainers/runtime-spec v1.0.3-0.20220909204839-494a5a6aca78 h1:R5M2qXZiK/mWPMT4VldCOiSL9HIAMuxQZWdG0CSM5+4= +github.com/opencontainers/runtime-spec v1.0.3-0.20220909204839-494a5a6aca78/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/openshift/api v0.0.0-20230503133300-8bbcb7ca7183 h1:t/CahSnpqY46sQR01SoS+Jt0jtjgmhgE6lFmRnO4q70= +github.com/openshift/api v0.0.0-20230503133300-8bbcb7ca7183/go.mod h1:4VWG+W22wrB4HfBL88P40DxLEpSOaiBVxUnfalfJo9k= +github.com/openshift/custom-resource-status v1.1.2 h1:C3DL44LEbvlbItfd8mT5jWrqPfHnSOQoQf/sypqA6A4= +github.com/openshift/custom-resource-status v1.1.2/go.mod h1:DB/Mf2oTeiAmVVX1gN+NEqweonAPY0TKUwADizj8+ZA= +github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +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= +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/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +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/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +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/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +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/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +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.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +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/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU= +golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/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-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +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.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= +golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +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.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +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-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +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/telemetry v0.0.0-20240208230135-b75ee8823808/go.mod h1:KG1lNk5ZFNssSZLrpVb4sMXKMpGwGXOxSG3rnu2gZQQ= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +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.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +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-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= +golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= +golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +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= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +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 v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/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= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/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.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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.4 h1:I2QNzitPVsPeLQvexMEsj945QumYraqv9m74isPDKhM= +k8s.io/api v0.31.4/go.mod h1:d+7vgXLvmcdT1BCo79VEgJxHHryww3V5np2OYTr6jdw= +k8s.io/apiextensions-apiserver v0.31.4 h1:FxbqzSvy92Ca9DIs5jqot883G0Ln/PGXfm/07t39LS0= +k8s.io/apiextensions-apiserver v0.31.4/go.mod h1:hIW9YU8UsqZqIWGG99/gsdIU0Ar45Qd3A12QOe/rvpg= +k8s.io/apimachinery v0.31.4 h1:8xjE2C4CzhYVm9DGf60yohpNUh5AEBnPxCryPBECmlM= +k8s.io/apimachinery v0.31.4/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/client-go v0.31.4 h1:t4QEXt4jgHIkKKlx06+W3+1JOwAFU/2OPiOo7H92eRQ= +k8s.io/client-go v0.31.4/go.mod h1:kvuMro4sFYIa8sulL5Gi5GFqUPvfH2O/dXuKstbaaeg= +k8s.io/code-generator v0.31.4/go.mod h1:yMDt13Kn7m4MMZ4LxB1KBzdZjEyxzdT4b4qXq+lnI90= +k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70/go.mod h1:VH3AT8AaQOqiGjMF9p0/IM1Dj+82ZwjfxUP1IxaHE+8= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.40.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20220124234850-424119656bbf/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/mount-utils v0.31.4 h1:9aWJ5BpJvs6fdIo36wWIuCC6ZMNllUT0JSFsVNJloFI= +k8s.io/mount-utils v0.31.4/go.mod h1:HV/VYBUGqYUj4vt82YltzpWvgv8FPg0G9ItyInT3NPU= +k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +kubevirt.io/api v1.2.2 h1:PeA937vsZawmKAsiiDQZJ/BbGH4OhEWsIzWrCNfmYXk= +kubevirt.io/api v1.2.2/go.mod h1:SbeR9ma4EwnaOZEUkh/lNz0kzYm5LPpEDE30vKXC5Zg= +kubevirt.io/containerized-data-importer-api v1.59.0 h1:GdDt9BlR0qHejpMaPfASbsG8JWDmBf1s7xZBj5W9qn0= +kubevirt.io/containerized-data-importer-api v1.59.0/go.mod h1:4yOGtCE7HvgKp7wftZZ3TBvDJ0x9d6N6KaRjRYcUFpE= +kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 h1:QMrd0nKP0BGbnxTqakhDZAUhGKxPiPiN5gSDqKUmGGc= +kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90/go.mod h1:018lASpFYBsYN6XwmA2TIrPCx6e0gviTd/ZNtSitKgc= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +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.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/packages/apps/kubernetes/images/kubevirt-csi-driver/main.go b/packages/apps/kubernetes/images/kubevirt-csi-driver/main.go new file mode 100644 index 00000000..3d4d8e83 --- /dev/null +++ b/packages/apps/kubernetes/images/kubevirt-csi-driver/main.go @@ -0,0 +1,219 @@ +package main + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + + csi "github.com/container-storage-interface/spec/lib/go/csi" + "gopkg.in/yaml.v2" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + klog "k8s.io/klog/v2" + mount "k8s.io/mount-utils" + + snapcli "kubevirt.io/csi-driver/pkg/generated/external-snapshotter/client-go/clientset/versioned" + "kubevirt.io/csi-driver/pkg/kubevirt" + "kubevirt.io/csi-driver/pkg/service" + "kubevirt.io/csi-driver/pkg/util" +) + +var ( + endpoint = flag.String("endpoint", "unix:/csi/csi.sock", "CSI endpoint") + nodeName = flag.String("node-name", "", "The node name - the node this pods runs on") + infraClusterNamespace = flag.String("infra-cluster-namespace", "", "The infra-cluster namespace") + infraClusterKubeconfig = flag.String("infra-cluster-kubeconfig", "", "the infra-cluster kubeconfig file. If not set, defaults to in cluster config.") + infraClusterLabels = flag.String("infra-cluster-labels", "", "The infra-cluster labels to use when creating resources in infra cluster. 'name=value' fields separated by a comma") + volumePrefix = flag.String("volume-prefix", "pvc", "The prefix expected for persistent volumes") + + infraStorageClassEnforcement = os.Getenv("INFRA_STORAGE_CLASS_ENFORCEMENT") + + tenantClusterKubeconfig = flag.String("tenant-cluster-kubeconfig", "", "the tenant cluster kubeconfig file. If not set, defaults to in cluster config.") + + runNodeService = flag.Bool("run-node-service", true, "Specifies rather or not to run the node service, the default is true") + runControllerService = flag.Bool("run-controller-service", true, "Specifies rather or not to run the controller service, the default is true") +) + +func init() { + klog.InitFlags(nil) +} + +func main() { + flag.Parse() + handle() + os.Exit(0) +} + +func handle() { + var tenantRestConfig *rest.Config + var infraRestConfig *rest.Config + var identityClientset *kubernetes.Clientset + + if service.VendorVersion == "" { + klog.Fatal("VendorVersion must be set at compile time") + } + klog.V(2).Infof("Driver vendor %v %v", service.VendorName, service.VendorVersion) + + if (infraClusterLabels == nil || *infraClusterLabels == "") && !*runNodeService { + klog.Fatal("infra-cluster-labels must be set") + } + if volumePrefix == nil || *volumePrefix == "" { + klog.Fatal("volume-prefix must be set") + } + + inClusterConfig, err := rest.InClusterConfig() + if err != nil { + klog.Fatalf("Failed to build in cluster config: %v", err) + } + + if *tenantClusterKubeconfig != "" { + tenantRestConfig, err = clientcmd.BuildConfigFromFlags("", *tenantClusterKubeconfig) + if err != nil { + klog.Fatalf("failed to build tenant cluster config: %v", err) + } + } else { + tenantRestConfig = inClusterConfig + } + + if *infraClusterKubeconfig != "" { + infraRestConfig, err = clientcmd.BuildConfigFromFlags("", *infraClusterKubeconfig) + if err != nil { + klog.Fatalf("failed to build infra cluster config: %v", err) + } + } else { + infraRestConfig = inClusterConfig + } + + tenantClientSet, err := kubernetes.NewForConfig(tenantRestConfig) + if err != nil { + klog.Fatalf("Failed to build tenant client set: %v", err) + } + tenantSnapshotClientSet, err := snapcli.NewForConfig(tenantRestConfig) + if err != nil { + klog.Fatalf("Failed to build tenant snapshot client set: %v", err) + } + + infraClusterLabelsMap := parseLabels() + klog.V(5).Infof("Storage class enforcement string: \n%s", infraStorageClassEnforcement) + storageClassEnforcement := configureStorageClassEnforcement(infraStorageClassEnforcement) + + virtClient, err := kubevirt.NewClient(infraRestConfig, infraClusterLabelsMap, tenantClientSet, tenantSnapshotClientSet, storageClassEnforcement, *volumePrefix) + if err != nil { + klog.Fatal(err) + } + + var nodeID string + if *nodeName != "" { + node, err := tenantClientSet.CoreV1().Nodes().Get(context.TODO(), *nodeName, v1.GetOptions{}) + if err != nil { + klog.Fatal(fmt.Errorf("failed to find node by name %v: %v", nodeName, err)) + } + if node.Spec.ProviderID == "" { + klog.Fatal("provider name missing from node, something's not right") + } + vmName := strings.TrimPrefix(node.Spec.ProviderID, `kubevirt://`) + vmNamespace, ok := node.Annotations["cluster.x-k8s.io/cluster-namespace"] + if !ok { + klog.Fatal("cannot infer infra vm namespace") + } + nodeID = fmt.Sprintf("%s/%s", vmNamespace, vmName) + klog.Infof("Node name: %v, Node ID: %s", *nodeName, nodeID) + } + + identityClientset = tenantClientSet + if *runControllerService { + identityClientset, err = kubernetes.NewForConfig(infraRestConfig) + if err != nil { + klog.Fatalf("Failed to build infra client set: %v", err) + } + } + + // Create upstream driver (provides Identity, Controller, Node services) + upstreamDriver := service.NewKubevirtCSIDriver(virtClient, + identityClientset, + *infraClusterNamespace, + infraClusterLabelsMap, + storageClassEnforcement, + nodeID, + *runNodeService, + *runControllerService) + + // Wrap controller and node services with NFS/RWX support + var cs csi.ControllerServer + if *runControllerService { + infraKubernetesClient, err := kubernetes.NewForConfig(infraRestConfig) + if err != nil { + klog.Fatalf("Failed to build infra kubernetes client: %v", err) + } + infraDynamicClient, err := dynamic.NewForConfig(infraRestConfig) + if err != nil { + klog.Fatalf("Failed to build infra dynamic client: %v", err) + } + cs = &WrappedControllerService{ + ControllerService: upstreamDriver.ControllerService, + infraClient: infraKubernetesClient, + dynamicClient: infraDynamicClient, + virtClient: virtClient, + infraNamespace: *infraClusterNamespace, + infraClusterLabels: infraClusterLabelsMap, + storageClassEnforcement: storageClassEnforcement, + } + } + + var ns csi.NodeServer + if *runNodeService { + ns = &WrappedNodeService{ + NodeService: upstreamDriver.NodeService, + mounter: mount.New(""), + } + } + + // Run gRPC server with upstream Identity + wrapped Controller/Node + s := service.NewNonBlockingGRPCServer() + s.Start(*endpoint, upstreamDriver.IdentityService, cs, ns) + s.Wait() +} + +func configureStorageClassEnforcement(infraStorageClassEnforcement string) util.StorageClassEnforcement { + var storageClassEnforcement util.StorageClassEnforcement + + if infraStorageClassEnforcement == "" { + storageClassEnforcement = util.StorageClassEnforcement{ + AllowAll: true, + AllowDefault: true, + } + } else { + err := yaml.Unmarshal([]byte(infraStorageClassEnforcement), &storageClassEnforcement) + if err != nil { + klog.Fatalf("Failed to parse infra-storage-class-enforcement %v", err) + } + } + return storageClassEnforcement +} + +func parseLabels() map[string]string { + infraClusterLabelsMap := map[string]string{} + + if *infraClusterLabels == "" { + return infraClusterLabelsMap + } + + labelStrings := strings.Split(*infraClusterLabels, ",") + + for _, label := range labelStrings { + labelPair := strings.SplitN(label, "=", 2) + + if len(labelPair) != 2 { + panic("Bad labels format. Should be 'key=value,key=value,...'") + } + + infraClusterLabelsMap[labelPair[0]] = labelPair[1] + } + + return infraClusterLabelsMap +} diff --git a/packages/apps/kubernetes/images/kubevirt-csi-driver/node.go b/packages/apps/kubernetes/images/kubevirt-csi-driver/node.go new file mode 100644 index 00000000..2260900b --- /dev/null +++ b/packages/apps/kubernetes/images/kubevirt-csi-driver/node.go @@ -0,0 +1,106 @@ +package main + +import ( + "context" + "fmt" + "os" + + csi "github.com/container-storage-interface/spec/lib/go/csi" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "k8s.io/klog/v2" + mount "k8s.io/mount-utils" + + "kubevirt.io/csi-driver/pkg/service" +) + +var _ csi.NodeServer = &WrappedNodeService{} + +// WrappedNodeService embeds the upstream NodeService and adds NFS mount support. +type WrappedNodeService struct { + *service.NodeService + mounter mount.Interface +} + +// NodeStageVolume for NFS volumes is a no-op (NFS doesn't need staging). +// For RWO volumes, delegates to upstream (lsblk + mkfs). +func (w *WrappedNodeService) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRequest) (*csi.NodeStageVolumeResponse, error) { + if req.GetPublishContext()[nfsExportKey] != "" { + klog.V(3).Infof("NFS volume %s: skipping stage", req.GetVolumeId()) + return &csi.NodeStageVolumeResponse{}, nil + } + return w.NodeService.NodeStageVolume(ctx, req) +} + +// NodePublishVolume for NFS volumes: mounts NFS at the target path. +// For RWO volumes, delegates to upstream (mount block device). +func (w *WrappedNodeService) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) { + nfsExport := req.GetPublishContext()[nfsExportKey] + if nfsExport == "" { + return w.NodeService.NodePublishVolume(ctx, req) + } + + klog.V(3).Infof("Publishing NFS volume %s at %s", req.GetVolumeId(), req.GetTargetPath()) + + host, port, path, err := parseNFSExport(nfsExport) + if err != nil { + return nil, status.Errorf(codes.Internal, "failed to parse NFS export: %v", err) + } + + targetPath := req.GetTargetPath() + + // Check if already mounted + notMnt, err := w.mounter.IsLikelyNotMountPoint(targetPath) + if err != nil { + if !os.IsNotExist(err) { + return nil, status.Errorf(codes.Internal, "failed to check mount point %s: %v", targetPath, err) + } + if err := os.MkdirAll(targetPath, 0750); err != nil { + return nil, status.Errorf(codes.Internal, "failed to create target path %s: %v", targetPath, err) + } + notMnt = true + } + + if !notMnt { + klog.V(3).Infof("NFS volume %s already mounted at %s", req.GetVolumeId(), targetPath) + return &csi.NodePublishVolumeResponse{}, nil + } + + source := fmt.Sprintf("%s:%s", host, path) + mountOptions := []string{ + "nfsvers=4.2", + fmt.Sprintf("port=%s", port), + } + + klog.V(3).Infof("Mounting NFS %s at %s with options %v", source, targetPath, mountOptions) + if err := w.mounter.Mount(source, targetPath, "nfs", mountOptions); err != nil { + return nil, status.Errorf(codes.Internal, "NFS mount of %s at %s failed: %v", source, targetPath, err) + } + + return &csi.NodePublishVolumeResponse{}, nil +} + +// NodeExpandVolume for NFS volumes is a no-op (LINSTOR handles NFS resize automatically). +// This should not normally be called for NFS since ControllerExpandVolume returns +// NodeExpansionRequired=false, but we handle it gracefully as a safety net. +func (w *WrappedNodeService) NodeExpandVolume(ctx context.Context, req *csi.NodeExpandVolumeRequest) (*csi.NodeExpandVolumeResponse, error) { + if isNFSMount(req.GetVolumePath(), w.mounter) { + klog.V(3).Infof("NFS volume %s: skipping node expansion", req.GetVolumeId()) + return &csi.NodeExpandVolumeResponse{}, nil + } + return w.NodeService.NodeExpandVolume(ctx, req) +} + +// isNFSMount checks if the given path is an NFS mount point. +func isNFSMount(path string, m mount.Interface) bool { + mountPoints, err := m.List() + if err != nil { + return false + } + for _, mp := range mountPoints { + if mp.Path == path && (mp.Type == "nfs" || mp.Type == "nfs4") { + return true + } + } + return false +} diff --git a/packages/apps/kubernetes/templates/csi/deploy.yaml b/packages/apps/kubernetes/templates/csi/deploy.yaml index b79a6d64..938b6d67 100644 --- a/packages/apps/kubernetes/templates/csi/deploy.yaml +++ b/packages/apps/kubernetes/templates/csi/deploy.yaml @@ -32,6 +32,7 @@ spec: - "--endpoint=$(CSI_ENDPOINT)" - "--infra-cluster-namespace=$(INFRACLUSTER_NAMESPACE)" - "--infra-cluster-labels=$(INFRACLUSTER_LABELS)" + - "--run-node-service=false" - "--v=5" ports: - name: healthz diff --git a/packages/apps/kubernetes/templates/csi/infra-cluster-service-account.yaml b/packages/apps/kubernetes/templates/csi/infra-cluster-service-account.yaml index 793871d2..fb11ab25 100644 --- a/packages/apps/kubernetes/templates/csi/infra-cluster-service-account.yaml +++ b/packages/apps/kubernetes/templates/csi/infra-cluster-service-account.yaml @@ -24,6 +24,9 @@ rules: - apiGroups: [""] resources: ["persistentvolumeclaims"] verbs: ["get", "patch"] +- apiGroups: ["cilium.io"] + resources: ["ciliumnetworkpolicies"] + verbs: ["get", "create", "update", "delete"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding diff --git a/packages/system/kubevirt-csi-node/templates/deploy.yaml b/packages/system/kubevirt-csi-node/templates/deploy.yaml index ed3b43fb..4e95bf77 100644 --- a/packages/system/kubevirt-csi-node/templates/deploy.yaml +++ b/packages/system/kubevirt-csi-node/templates/deploy.yaml @@ -167,6 +167,7 @@ spec: args: - "--endpoint=unix:/csi/csi.sock" - "--node-name=$(KUBE_NODE_NAME)" + - "--run-controller-service=false" - "--v=5" env: - name: KUBE_NODE_NAME diff --git a/packages/system/kubevirt-csi-node/values.yaml b/packages/system/kubevirt-csi-node/values.yaml index cf8b4fa8..f7fd69a1 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:8f1ab4c3b2bed3a0adc40fcc823b040fa04b4722bec7735c030e79a3a2fd6c85 + image: ghcr.io/cozystack/cozystack/kubevirt-csi-driver:latest@sha256:cd58760c97ba50ef74ff940cdcda4b9a6d8554eec8305fc96c2e8bbea72b75a9 From 46103400f2ccec25a848878d7c875c7d531e5956 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 12 Feb 2026 23:27:44 +0100 Subject: [PATCH 251/889] test(e2e): adapt kubernetes NFS test for native RWX CSI support Remove separate NFS Application dependency from e2e test. The kubevirt CSI driver wrapper now handles RWX Filesystem volumes natively - PVCs with ReadWriteMany accessMode use the standard kubevirt StorageClass. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- hack/e2e-apps/run-kubernetes.sh | 55 ++++++++++++++++++++++++++++ packages/apps/kubernetes/.helmignore | 1 + 2 files changed, 56 insertions(+) diff --git a/hack/e2e-apps/run-kubernetes.sh b/hack/e2e-apps/run-kubernetes.sh index 3e2e20f9..c62ae131 100644 --- a/hack/e2e-apps/run-kubernetes.sh +++ b/hack/e2e-apps/run-kubernetes.sh @@ -218,6 +218,61 @@ fi kubectl delete deployment --kubeconfig tenantkubeconfig-${test_name} "${test_name}-backend" -n tenant-test kubectl delete service --kubeconfig tenantkubeconfig-${test_name} "${test_name}-backend" -n tenant-test + # Test RWX NFS mount in tenant cluster (uses kubevirt CSI driver with RWX support) + kubectl --kubeconfig tenantkubeconfig-${test_name} apply -f - < /data/test.txt && cat /data/test.txt"] + volumeMounts: + - name: nfs-vol + mountPath: /data + volumes: + - name: nfs-vol + persistentVolumeClaim: + claimName: nfs-test-pvc + restartPolicy: Never +EOF + + # Wait for Pod to complete successfully + kubectl --kubeconfig tenantkubeconfig-${test_name} wait pod nfs-test-pod -n tenant-test --timeout=2m --for=jsonpath='{.status.phase}'=Succeeded + + # Verify NFS data integrity + nfs_result=$(kubectl --kubeconfig tenantkubeconfig-${test_name} logs nfs-test-pod -n tenant-test) + if [ "$nfs_result" != "nfs-mount-ok" ]; then + echo "NFS mount test failed: expected 'nfs-mount-ok', got '$nfs_result'" >&2 + exit 1 + fi + + # Cleanup NFS test resources in tenant cluster + kubectl --kubeconfig tenantkubeconfig-${test_name} delete pod nfs-test-pod -n tenant-test + kubectl --kubeconfig tenantkubeconfig-${test_name} delete pvc nfs-test-pvc -n tenant-test + # Wait for all machine deployment replicas to be ready (timeout after 10 minutes) kubectl wait machinedeployment kubernetes-${test_name}-md0 -n tenant-test --timeout=10m --for=jsonpath='{.status.v1beta2.readyReplicas}'=2 diff --git a/packages/apps/kubernetes/.helmignore b/packages/apps/kubernetes/.helmignore index 3de7d4a5..cdada667 100644 --- a/packages/apps/kubernetes/.helmignore +++ b/packages/apps/kubernetes/.helmignore @@ -2,3 +2,4 @@ /logos /Makefile /hack +/images/*/* From 168f6f2445a629e24a9e326c86063bda84e567df Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Fri, 13 Feb 2026 09:04:33 +0100 Subject: [PATCH 252/889] fix(csi): address review feedback for kubevirt-csi-driver RWX support - Move nil check before req dereference in CreateVolume - Scope CiliumNetworkPolicy endpointSelector to specific VMI - Use vmNamespace from NodeId for VMI lookup instead of infraNamespace - Log PVC lookup errors in ControllerExpandVolume - Wrap CNP ownerReference updates in retry.RetryOnConflict - Fix infraClusterLabels validation to check runControllerService flag - Dereference nodeName pointer in error message - Replace panic with klog.Fatal for consistent error handling - Honor CSI readonly flag in NFS NodePublishVolume - Log mount list errors in isNFSMount - Reorder Dockerfile ENTRYPOINT after COPY for better layer caching - Add cleanup on e2e test failure and --wait on pod deletion Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- hack/e2e-apps/run-kubernetes.sh | 4 +- .../images/kubevirt-csi-driver/Dockerfile | 4 +- .../images/kubevirt-csi-driver/controller.go | 129 ++++++++++-------- .../images/kubevirt-csi-driver/main.go | 6 +- .../images/kubevirt-csi-driver/node.go | 4 + 5 files changed, 82 insertions(+), 65 deletions(-) diff --git a/hack/e2e-apps/run-kubernetes.sh b/hack/e2e-apps/run-kubernetes.sh index c62ae131..5b8b1b4b 100644 --- a/hack/e2e-apps/run-kubernetes.sh +++ b/hack/e2e-apps/run-kubernetes.sh @@ -266,11 +266,13 @@ EOF nfs_result=$(kubectl --kubeconfig tenantkubeconfig-${test_name} logs nfs-test-pod -n tenant-test) if [ "$nfs_result" != "nfs-mount-ok" ]; then echo "NFS mount test failed: expected 'nfs-mount-ok', got '$nfs_result'" >&2 + kubectl --kubeconfig tenantkubeconfig-${test_name} delete pod nfs-test-pod -n tenant-test --wait=false 2>/dev/null || true + kubectl --kubeconfig tenantkubeconfig-${test_name} delete pvc nfs-test-pvc -n tenant-test --wait=false 2>/dev/null || true exit 1 fi # Cleanup NFS test resources in tenant cluster - kubectl --kubeconfig tenantkubeconfig-${test_name} delete pod nfs-test-pod -n tenant-test + kubectl --kubeconfig tenantkubeconfig-${test_name} delete pod nfs-test-pod -n tenant-test --wait kubectl --kubeconfig tenantkubeconfig-${test_name} delete pvc nfs-test-pvc -n tenant-test # Wait for all machine deployment replicas to be ready (timeout after 10 minutes) diff --git a/packages/apps/kubernetes/images/kubevirt-csi-driver/Dockerfile b/packages/apps/kubernetes/images/kubevirt-csi-driver/Dockerfile index be8125d7..b156d08e 100644 --- a/packages/apps/kubernetes/images/kubevirt-csi-driver/Dockerfile +++ b/packages/apps/kubernetes/images/kubevirt-csi-driver/Dockerfile @@ -16,8 +16,8 @@ RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build \ FROM quay.io/centos/centos:stream9 -ENTRYPOINT ["./kubevirt-csi-driver"] - RUN dnf install -y e2fsprogs xfsprogs nfs-utils && dnf clean all COPY --from=builder /src/kubevirt-csi-driver . + +ENTRYPOINT ["./kubevirt-csi-driver"] diff --git a/packages/apps/kubernetes/images/kubevirt-csi-driver/controller.go b/packages/apps/kubernetes/images/kubevirt-csi-driver/controller.go index 524b45ea..7192b95b 100644 --- a/packages/apps/kubernetes/images/kubevirt-csi-driver/controller.go +++ b/packages/apps/kubernetes/images/kubevirt-csi-driver/controller.go @@ -19,6 +19,7 @@ import ( "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/cache" + "k8s.io/client-go/util/retry" "k8s.io/klog/v2" cdiv1 "kubevirt.io/containerized-data-importer-api/pkg/apis/core/v1beta1" @@ -77,13 +78,13 @@ func isRWXFilesystem(caps []*csi.VolumeCapability) bool { // compatibility with upstream snapshot and clone operations. // For all other requests, delegates to upstream. func (w *WrappedControllerService) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "missing request") + } if !isRWXFilesystem(req.GetVolumeCapabilities()) { return w.ControllerService.CreateVolume(ctx, req) } - if req == nil { - return nil, status.Error(codes.InvalidArgument, "missing request") - } if len(req.GetName()) == 0 { return nil, status.Error(codes.InvalidArgument, "name missing in request") } @@ -260,9 +261,9 @@ func (w *WrappedControllerService) ControllerPublishVolume(ctx context.Context, klog.V(3).Infof("Publishing NFS volume %s to node %s/%s", dvName, vmNamespace, vmName) // Get VMI for CiliumNetworkPolicy ownerReference - vmi, err := w.virtClient.GetVirtualMachine(ctx, w.infraNamespace, vmName) + vmi, err := w.virtClient.GetVirtualMachine(ctx, vmNamespace, vmName) if err != nil { - return nil, status.Errorf(codes.Internal, "failed to get VMI %s: %v", vmName, err) + return nil, status.Errorf(codes.Internal, "failed to get VMI %s/%s: %v", vmNamespace, vmName, err) } // Wait for PVC to be bound (CDI handles immediate binding via annotation) @@ -316,7 +317,11 @@ func (w *WrappedControllerService) ControllerPublishVolume(ctx context.Context, "ownerReferences": []interface{}{vmiOwnerRef}, }, "spec": map[string]interface{}{ - "endpointSelector": map[string]interface{}{}, + "endpointSelector": map[string]interface{}{ + "matchLabels": map[string]interface{}{ + "kubevirt.io/vm": vmName, + }, + }, "egress": []interface{}{ map[string]interface{}{ "toEndpoints": []interface{}{ @@ -405,7 +410,9 @@ func (w *WrappedControllerService) ControllerExpandVolume(ctx context.Context, r // For NFS volumes, no node-side expansion is needed pvc, err := w.infraClient.CoreV1().PersistentVolumeClaims(w.infraNamespace).Get(ctx, req.GetVolumeId(), metav1.GetOptions{}) - if err == nil && hasRWXAccessMode(pvc) { + if err != nil { + klog.Warningf("Failed to check PVC access mode for %s/%s: %v", w.infraNamespace, req.GetVolumeId(), err) + } else if hasRWXAccessMode(pvc) { resp.NodeExpansionRequired = false } @@ -414,73 +421,77 @@ func (w *WrappedControllerService) ControllerExpandVolume(ctx context.Context, r // addCNPOwnerReference adds a VMI ownerReference to an existing CiliumNetworkPolicy. func (w *WrappedControllerService) addCNPOwnerReference(ctx context.Context, namespace, cnpName string, ownerRef map[string]interface{}) error { - existing, err := w.dynamicClient.Resource(ciliumNetworkPolicyGVR).Namespace(namespace).Get(ctx, cnpName, metav1.GetOptions{}) - if err != nil { - return status.Errorf(codes.Internal, "failed to get CiliumNetworkPolicy %s: %v", cnpName, err) - } + return retry.RetryOnConflict(retry.DefaultRetry, func() error { + existing, err := w.dynamicClient.Resource(ciliumNetworkPolicyGVR).Namespace(namespace).Get(ctx, cnpName, metav1.GetOptions{}) + if err != nil { + return status.Errorf(codes.Internal, "failed to get CiliumNetworkPolicy %s: %v", cnpName, err) + } - ownerRefs, _, _ := unstructured.NestedSlice(existing.Object, "metadata", "ownerReferences") - uid, _, _ := unstructured.NestedString(ownerRef, "uid") - for _, ref := range ownerRefs { - if refMap, ok := ref.(map[string]interface{}); ok { - if refMap["uid"] == uid { - return nil // already present + ownerRefs, _, _ := unstructured.NestedSlice(existing.Object, "metadata", "ownerReferences") + uid, _, _ := unstructured.NestedString(ownerRef, "uid") + for _, ref := range ownerRefs { + if refMap, ok := ref.(map[string]interface{}); ok { + if refMap["uid"] == uid { + return nil // already present + } } } - } - ownerRefs = append(ownerRefs, ownerRef) - if err := unstructured.SetNestedSlice(existing.Object, ownerRefs, "metadata", "ownerReferences"); err != nil { - return status.Errorf(codes.Internal, "failed to set ownerReferences: %v", err) - } - if _, err := w.dynamicClient.Resource(ciliumNetworkPolicyGVR).Namespace(namespace).Update(ctx, existing, metav1.UpdateOptions{}); err != nil { - return status.Errorf(codes.Internal, "failed to update CiliumNetworkPolicy %s: %v", cnpName, err) - } - klog.V(3).Infof("Added ownerReference to CiliumNetworkPolicy %s", cnpName) - return nil + ownerRefs = append(ownerRefs, ownerRef) + if err := unstructured.SetNestedSlice(existing.Object, ownerRefs, "metadata", "ownerReferences"); err != nil { + return status.Errorf(codes.Internal, "failed to set ownerReferences: %v", err) + } + if _, err := w.dynamicClient.Resource(ciliumNetworkPolicyGVR).Namespace(namespace).Update(ctx, existing, metav1.UpdateOptions{}); err != nil { + return err + } + klog.V(3).Infof("Added ownerReference to CiliumNetworkPolicy %s", cnpName) + return nil + }) } // removeCNPOwnerReference removes a VMI ownerReference from a CiliumNetworkPolicy. // Deletes the CNP if no ownerReferences remain. func (w *WrappedControllerService) removeCNPOwnerReference(ctx context.Context, namespace, cnpName, vmName string) error { - existing, err := w.dynamicClient.Resource(ciliumNetworkPolicyGVR).Namespace(namespace).Get(ctx, cnpName, metav1.GetOptions{}) - if err != nil { - if errors.IsNotFound(err) { + return retry.RetryOnConflict(retry.DefaultRetry, func() error { + existing, err := w.dynamicClient.Resource(ciliumNetworkPolicyGVR).Namespace(namespace).Get(ctx, cnpName, metav1.GetOptions{}) + if err != nil { + if errors.IsNotFound(err) { + return nil + } + return status.Errorf(codes.Internal, "failed to get CiliumNetworkPolicy %s: %v", cnpName, err) + } + + ownerRefs, _, _ := unstructured.NestedSlice(existing.Object, "metadata", "ownerReferences") + var remaining []interface{} + for _, ref := range ownerRefs { + if refMap, ok := ref.(map[string]interface{}); ok { + if refMap["name"] == vmName { + continue + } + } + remaining = append(remaining, ref) + } + + if len(remaining) == 0 { + // Last owner — delete CNP + if err := w.dynamicClient.Resource(ciliumNetworkPolicyGVR).Namespace(namespace).Delete(ctx, cnpName, metav1.DeleteOptions{}); err != nil { + if !errors.IsNotFound(err) { + return status.Errorf(codes.Internal, "failed to delete CiliumNetworkPolicy %s: %v", cnpName, err) + } + } + klog.V(3).Infof("Deleted CiliumNetworkPolicy %s (no more owners)", cnpName) return nil } - return status.Errorf(codes.Internal, "failed to get CiliumNetworkPolicy %s: %v", cnpName, err) - } - ownerRefs, _, _ := unstructured.NestedSlice(existing.Object, "metadata", "ownerReferences") - var remaining []interface{} - for _, ref := range ownerRefs { - if refMap, ok := ref.(map[string]interface{}); ok { - if refMap["name"] == vmName { - continue - } + if err := unstructured.SetNestedSlice(existing.Object, remaining, "metadata", "ownerReferences"); err != nil { + return status.Errorf(codes.Internal, "failed to set ownerReferences: %v", err) } - remaining = append(remaining, ref) - } - - if len(remaining) == 0 { - // Last owner — delete CNP - if err := w.dynamicClient.Resource(ciliumNetworkPolicyGVR).Namespace(namespace).Delete(ctx, cnpName, metav1.DeleteOptions{}); err != nil { - if !errors.IsNotFound(err) { - return status.Errorf(codes.Internal, "failed to delete CiliumNetworkPolicy %s: %v", cnpName, err) - } + if _, err := w.dynamicClient.Resource(ciliumNetworkPolicyGVR).Namespace(namespace).Update(ctx, existing, metav1.UpdateOptions{}); err != nil { + return err } - klog.V(3).Infof("Deleted CiliumNetworkPolicy %s (no more owners)", cnpName) + klog.V(3).Infof("Removed VMI %s ownerReference from CiliumNetworkPolicy %s", vmName, cnpName) return nil - } - - if err := unstructured.SetNestedSlice(existing.Object, remaining, "metadata", "ownerReferences"); err != nil { - return status.Errorf(codes.Internal, "failed to set ownerReferences: %v", err) - } - if _, err := w.dynamicClient.Resource(ciliumNetworkPolicyGVR).Namespace(namespace).Update(ctx, existing, metav1.UpdateOptions{}); err != nil { - return status.Errorf(codes.Internal, "failed to update CiliumNetworkPolicy %s: %v", cnpName, err) - } - klog.V(3).Infof("Removed VMI %s ownerReference from CiliumNetworkPolicy %s", vmName, cnpName) - return nil + }) } func hasRWXAccessMode(pvc *corev1.PersistentVolumeClaim) bool { diff --git a/packages/apps/kubernetes/images/kubevirt-csi-driver/main.go b/packages/apps/kubernetes/images/kubevirt-csi-driver/main.go index 3d4d8e83..396cee42 100644 --- a/packages/apps/kubernetes/images/kubevirt-csi-driver/main.go +++ b/packages/apps/kubernetes/images/kubevirt-csi-driver/main.go @@ -59,7 +59,7 @@ func handle() { } klog.V(2).Infof("Driver vendor %v %v", service.VendorName, service.VendorVersion) - if (infraClusterLabels == nil || *infraClusterLabels == "") && !*runNodeService { + if (infraClusterLabels == nil || *infraClusterLabels == "") && *runControllerService { klog.Fatal("infra-cluster-labels must be set") } if volumePrefix == nil || *volumePrefix == "" { @@ -111,7 +111,7 @@ func handle() { if *nodeName != "" { node, err := tenantClientSet.CoreV1().Nodes().Get(context.TODO(), *nodeName, v1.GetOptions{}) if err != nil { - klog.Fatal(fmt.Errorf("failed to find node by name %v: %v", nodeName, err)) + klog.Fatal(fmt.Errorf("failed to find node by name %v: %v", *nodeName, err)) } if node.Spec.ProviderID == "" { klog.Fatal("provider name missing from node, something's not right") @@ -209,7 +209,7 @@ func parseLabels() map[string]string { labelPair := strings.SplitN(label, "=", 2) if len(labelPair) != 2 { - panic("Bad labels format. Should be 'key=value,key=value,...'") + klog.Fatal("Bad labels format. Should be 'key=value,key=value,...'") } infraClusterLabelsMap[labelPair[0]] = labelPair[1] diff --git a/packages/apps/kubernetes/images/kubevirt-csi-driver/node.go b/packages/apps/kubernetes/images/kubevirt-csi-driver/node.go index 2260900b..5636a1d5 100644 --- a/packages/apps/kubernetes/images/kubevirt-csi-driver/node.go +++ b/packages/apps/kubernetes/images/kubevirt-csi-driver/node.go @@ -71,6 +71,9 @@ func (w *WrappedNodeService) NodePublishVolume(ctx context.Context, req *csi.Nod "nfsvers=4.2", fmt.Sprintf("port=%s", port), } + if req.GetReadonly() { + mountOptions = append(mountOptions, "ro") + } klog.V(3).Infof("Mounting NFS %s at %s with options %v", source, targetPath, mountOptions) if err := w.mounter.Mount(source, targetPath, "nfs", mountOptions); err != nil { @@ -95,6 +98,7 @@ func (w *WrappedNodeService) NodeExpandVolume(ctx context.Context, req *csi.Node func isNFSMount(path string, m mount.Interface) bool { mountPoints, err := m.List() if err != nil { + klog.Warningf("Failed to list mount points: %v", err) return false } for _, mp := range mountPoints { From 574c636761e8a990685d26c610985f17f34f57d5 Mon Sep 17 00:00:00 2001 From: Kirill Ilin Date: Fri, 13 Feb 2026 16:31:09 +0500 Subject: [PATCH 253/889] [cozystack-operator] Preserve existing suspend field in package reconciler Signed-off-by: Kirill Ilin --- internal/operator/package_reconciler.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/operator/package_reconciler.go b/internal/operator/package_reconciler.go index 79d78a87..e15f57ac 100644 --- a/internal/operator/package_reconciler.go +++ b/internal/operator/package_reconciler.go @@ -211,13 +211,13 @@ func (r *PackageReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct Namespace: "cozy-system", }, Install: &helmv2.Install{ - Timeout: &metav1.Duration{Duration: 10 * 60 * 1000000000}, // 10m + Timeout: &metav1.Duration{Duration: 10 * 60 * 1000000000}, // 10m Remediation: &helmv2.InstallRemediation{ Retries: -1, }, }, Upgrade: &helmv2.Upgrade{ - Timeout: &metav1.Duration{Duration: 10 * 60 * 1000000000}, // 10m + Timeout: &metav1.Duration{Duration: 10 * 60 * 1000000000}, // 10m Remediation: &helmv2.UpgradeRemediation{ Retries: -1, }, @@ -387,6 +387,7 @@ func (r *PackageReconciler) createOrUpdateHelmRelease(ctx context.Context, hr *h } hr.SetAnnotations(annotations) + hr.Spec.Suspend = existing.Spec.Suspend // Update Spec existing.Spec = hr.Spec existing.SetLabels(hr.GetLabels()) @@ -816,7 +817,7 @@ func (r *PackageReconciler) reconcileNamespaces(ctx context.Context, pkg *cozyv1 func (r *PackageReconciler) createOrUpdateNamespace(ctx context.Context, namespace *corev1.Namespace) error { // Ensure TypeMeta is set for server-side apply namespace.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("Namespace")) - + // Use server-side apply with field manager // This is atomic and avoids race conditions from Get/Create/Update pattern // Labels and annotations will be merged automatically by the server From 371f67276a2989e385fd28e47c4de41e39ec3bf5 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Fri, 13 Feb 2026 16:34:00 +0100 Subject: [PATCH 254/889] fix(operator): use per-Package SSA field owner for namespace reconciliation When multiple Packages share a namespace (e.g. cozystack.linstor and cozystack.linstor-scheduler both use cozy-linstor), the shared SSA field owner "cozystack-package-controller" caused a race condition: the last Package to reconcile would overwrite the namespace labels set by others. This meant that if cozystack.linstor set pod-security.kubernetes.io/enforce= privileged and then cozystack.linstor-scheduler reconciled without the privileged flag, SSA would remove the label. On Talos clusters (which default to "baseline" PodSecurity enforcement), this caused privileged satellite pods to be rejected with PodSecurity violations. Fix by using per-Package field owners (cozystack-package-{name}), so each Package independently manages its own namespace labels without interfering with other Packages sharing the same namespace. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- internal/operator/package_reconciler.go | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/internal/operator/package_reconciler.go b/internal/operator/package_reconciler.go index e15f57ac..b69539e4 100644 --- a/internal/operator/package_reconciler.go +++ b/internal/operator/package_reconciler.go @@ -803,7 +803,7 @@ func (r *PackageReconciler) reconcileNamespaces(ctx context.Context, pkg *cozyv1 namespace.Labels["pod-security.kubernetes.io/enforce"] = "privileged" } - if err := r.createOrUpdateNamespace(ctx, namespace); err != nil { + if err := r.createOrUpdateNamespace(ctx, namespace, pkg.Name); err != nil { logger.Error(err, "failed to reconcile namespace", "name", nsName, "privileged", info.privileged) return fmt.Errorf("failed to reconcile namespace %s: %w", nsName, err) } @@ -813,16 +813,19 @@ func (r *PackageReconciler) reconcileNamespaces(ctx context.Context, pkg *cozyv1 return nil } -// createOrUpdateNamespace creates or updates a namespace using server-side apply -func (r *PackageReconciler) createOrUpdateNamespace(ctx context.Context, namespace *corev1.Namespace) error { +// createOrUpdateNamespace creates or updates a namespace using server-side apply. +// Each Package uses its own field owner to prevent different Packages sharing the same +// namespace from overwriting each other's labels. This ensures that if any Package sets +// the privileged PSA label, other Packages reconciling the same namespace won't remove it. +func (r *PackageReconciler) createOrUpdateNamespace(ctx context.Context, namespace *corev1.Namespace, packageName string) error { // Ensure TypeMeta is set for server-side apply namespace.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("Namespace")) - // Use server-side apply with field manager - // This is atomic and avoids race conditions from Get/Create/Update pattern - // Labels and annotations will be merged automatically by the server - // Each label/annotation key is treated as a separate field, so existing ones are preserved - return r.Patch(ctx, namespace, client.Apply, client.FieldOwner("cozystack-package-controller")) + // Use per-Package field owner to avoid conflicts between Packages sharing a namespace. + // ForceOwnership is needed to take over fields from the previous shared field owner + // ("cozystack-package-controller") during the transition. + fieldOwner := fmt.Sprintf("cozystack-package-%s", packageName) + return r.Patch(ctx, namespace, client.Apply, client.FieldOwner(fieldOwner), client.ForceOwnership) } // cleanupOrphanedHelmReleases removes HelmReleases that are no longer needed From 6576b3bb8742a2c9ae8a238194d092ee0ddd5dd6 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Fri, 13 Feb 2026 17:08:01 +0100 Subject: [PATCH 255/889] fix(operator): resolve namespace privileged flag by checking all Packages Instead of using per-Package SSA field owners (which is a workaround relying on SSA mechanics), properly resolve whether a namespace should be privileged by iterating all PackageSources and their active Packages. A namespace gets the privileged PodSecurity label if ANY Package has a component with privileged: true installed in it. This fixes the race condition where Packages sharing a namespace (e.g. linstor and linstor-scheduler in cozy-linstor) would overwrite each other's labels depending on reconciliation order. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- internal/operator/package_reconciler.go | 126 +++++++++++++++--------- 1 file changed, 81 insertions(+), 45 deletions(-) diff --git a/internal/operator/package_reconciler.go b/internal/operator/package_reconciler.go index b69539e4..32e667e4 100644 --- a/internal/operator/package_reconciler.go +++ b/internal/operator/package_reconciler.go @@ -736,53 +736,39 @@ func (r *PackageReconciler) updateDependentPackagesDependencies(ctx context.Cont return nil } -// reconcileNamespaces creates or updates namespaces based on components in the variant +// reconcileNamespaces creates or updates namespaces based on components in the variant. +// For each namespace, it checks ALL Packages sharing that namespace to determine whether +// the namespace should be privileged — it is privileged if ANY Package has a privileged +// component installed in it. func (r *PackageReconciler) reconcileNamespaces(ctx context.Context, pkg *cozyv1alpha1.Package, variant *cozyv1alpha1.Variant) error { logger := log.FromContext(ctx) - // Collect namespaces from components - // Map: namespace -> {isPrivileged} - type namespaceInfo struct { - privileged bool - } - namespacesMap := make(map[string]namespaceInfo) - + // Collect namespaces from this Package's components + targetNamespaces := make(map[string]struct{}) for _, component := range variant.Components { - // Skip components without Install section if component.Install == nil { continue } - - // Check if component is disabled via Package spec if pkgComponent, ok := pkg.Spec.Components[component.Name]; ok { if pkgComponent.Enabled != nil && !*pkgComponent.Enabled { continue } } - - // Namespace must be set namespace := component.Install.Namespace if namespace == "" { return fmt.Errorf("component %s has empty namespace in Install section", component.Name) } + targetNamespaces[namespace] = struct{}{} + } - info, exists := namespacesMap[namespace] - if !exists { - info = namespaceInfo{ - privileged: false, - } - } - - // If component is privileged, mark namespace as privileged - if component.Install.Privileged { - info.privileged = true - } - - namespacesMap[namespace] = info + // Determine which namespaces should be privileged by checking ALL Packages + privileged, err := r.resolvePrivilegedNamespaces(ctx, targetNamespaces) + if err != nil { + return fmt.Errorf("failed to resolve privileged namespaces: %w", err) } // Create or update all namespaces - for nsName, info := range namespacesMap { + for nsName := range targetNamespaces { namespace := &corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ Name: nsName, @@ -793,39 +779,89 @@ func (r *PackageReconciler) reconcileNamespaces(ctx context.Context, pkg *cozyv1 }, } - // Add system label only for non-tenant namespaces if !strings.HasPrefix(nsName, "tenant-") { namespace.Labels["cozystack.io/system"] = "true" } - // Add privileged label if needed - if info.privileged { + if privileged[nsName] { namespace.Labels["pod-security.kubernetes.io/enforce"] = "privileged" } - if err := r.createOrUpdateNamespace(ctx, namespace, pkg.Name); err != nil { - logger.Error(err, "failed to reconcile namespace", "name", nsName, "privileged", info.privileged) + if err := r.createOrUpdateNamespace(ctx, namespace); err != nil { + logger.Error(err, "failed to reconcile namespace", "name", nsName, "privileged", privileged[nsName]) return fmt.Errorf("failed to reconcile namespace %s: %w", nsName, err) } - logger.Info("reconciled namespace", "name", nsName, "privileged", info.privileged) + logger.Info("reconciled namespace", "name", nsName, "privileged", privileged[nsName]) } return nil } -// createOrUpdateNamespace creates or updates a namespace using server-side apply. -// Each Package uses its own field owner to prevent different Packages sharing the same -// namespace from overwriting each other's labels. This ensures that if any Package sets -// the privileged PSA label, other Packages reconciling the same namespace won't remove it. -func (r *PackageReconciler) createOrUpdateNamespace(ctx context.Context, namespace *corev1.Namespace, packageName string) error { - // Ensure TypeMeta is set for server-side apply - namespace.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("Namespace")) +// resolvePrivilegedNamespaces checks all PackageSources and their corresponding Packages +// to determine which of the given namespaces require the privileged PodSecurity level. +// A namespace is privileged if ANY active Package has a component with privileged: true in it. +func (r *PackageReconciler) resolvePrivilegedNamespaces(ctx context.Context, namespaces map[string]struct{}) (map[string]bool, error) { + result := make(map[string]bool) - // Use per-Package field owner to avoid conflicts between Packages sharing a namespace. - // ForceOwnership is needed to take over fields from the previous shared field owner - // ("cozystack-package-controller") during the transition. - fieldOwner := fmt.Sprintf("cozystack-package-%s", packageName) - return r.Patch(ctx, namespace, client.Apply, client.FieldOwner(fieldOwner), client.ForceOwnership) + packageSources := &cozyv1alpha1.PackageSourceList{} + if err := r.List(ctx, packageSources); err != nil { + return nil, fmt.Errorf("failed to list PackageSources: %w", err) + } + + for i := range packageSources.Items { + ps := &packageSources.Items[i] + + // Check if a Package exists for this PackageSource + pkg := &cozyv1alpha1.Package{} + if err := r.Get(ctx, types.NamespacedName{Name: ps.Name}, pkg); err != nil { + if apierrors.IsNotFound(err) { + continue + } + return nil, fmt.Errorf("failed to get Package %s: %w", ps.Name, err) + } + + // Resolve active variant + variantName := pkg.Spec.Variant + if variantName == "" { + variantName = "default" + } + + var variant *cozyv1alpha1.Variant + for j := range ps.Spec.Variants { + if ps.Spec.Variants[j].Name == variantName { + variant = &ps.Spec.Variants[j] + break + } + } + if variant == nil { + continue + } + + for _, component := range variant.Components { + if component.Install == nil { + continue + } + if pkgComponent, ok := pkg.Spec.Components[component.Name]; ok { + if pkgComponent.Enabled != nil && !*pkgComponent.Enabled { + continue + } + } + if _, relevant := namespaces[component.Install.Namespace]; !relevant { + continue + } + if component.Install.Privileged { + result[component.Install.Namespace] = true + } + } + } + + return result, nil +} + +// createOrUpdateNamespace creates or updates a namespace using server-side apply. +func (r *PackageReconciler) createOrUpdateNamespace(ctx context.Context, namespace *corev1.Namespace) error { + namespace.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("Namespace")) + return r.Patch(ctx, namespace, client.Apply, client.FieldOwner("cozystack-package-controller"), client.ForceOwnership) } // cleanupOrphanedHelmReleases removes HelmReleases that are no longer needed From 62116030dc0bbd732e257879a2c284a49f2ed7db Mon Sep 17 00:00:00 2001 From: Kirill Ilin Date: Fri, 13 Feb 2026 23:03:48 +0500 Subject: [PATCH 256/889] [etcd-operator] fix dependencies: add vertical-pod-autoscaler Signed-off-by: Kirill Ilin --- packages/core/platform/sources/etcd-operator.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/platform/sources/etcd-operator.yaml b/packages/core/platform/sources/etcd-operator.yaml index 599ed2a3..5f42a4a0 100644 --- a/packages/core/platform/sources/etcd-operator.yaml +++ b/packages/core/platform/sources/etcd-operator.yaml @@ -14,6 +14,7 @@ spec: dependsOn: - cozystack.networking - cozystack.cert-manager + - cozystack.vertical-pod-autoscaler components: - name: etcd-operator path: system/etcd-operator From 88baceae2c3297ece860b47928814b5c567307e1 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Sat, 14 Feb 2026 01:59:40 +0100 Subject: [PATCH 257/889] [platform] Make cozystack-api reconciliation always use Deployment Signed-off-by: Andrei Kvapil --- cmd/cozystack-controller/main.go | 12 ++------- .../applicationdefinition_controller.go | 26 +++++-------------- .../templates/deployment.yaml | 3 --- .../system/cozystack-controller/values.yaml | 1 - 4 files changed, 9 insertions(+), 33 deletions(-) diff --git a/cmd/cozystack-controller/main.go b/cmd/cozystack-controller/main.go index 82fc673c..77c4f9dc 100644 --- a/cmd/cozystack-controller/main.go +++ b/cmd/cozystack-controller/main.go @@ -68,7 +68,6 @@ func main() { var disableTelemetry bool var telemetryEndpoint string var telemetryInterval string - var reconcileDeployment 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.") @@ -86,8 +85,6 @@ func main() { "Endpoint for sending telemetry data") flag.StringVar(&telemetryInterval, "telemetry-interval", "15m", "Interval between telemetry data collection (e.g. 15m, 1h)") - flag.BoolVar(&reconcileDeployment, "reconcile-deployment", false, - "If set, the Cozystack API server is assumed to run as a Deployment, else as a DaemonSet.") opts := zap.Options{ Development: false, } @@ -196,14 +193,9 @@ func main() { os.Exit(1) } - cozyAPIKind := "DaemonSet" - if reconcileDeployment { - cozyAPIKind = "Deployment" - } if err = (&controller.ApplicationDefinitionReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - CozystackAPIKind: cozyAPIKind, + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "ApplicationDefinitionReconciler") os.Exit(1) diff --git a/internal/controller/applicationdefinition_controller.go b/internal/controller/applicationdefinition_controller.go index e5920b4c..6ce25249 100644 --- a/internal/controller/applicationdefinition_controller.go +++ b/internal/controller/applicationdefinition_controller.go @@ -32,8 +32,6 @@ type ApplicationDefinitionReconciler struct { mu sync.Mutex lastEvent time.Time lastHandled time.Time - - CozystackAPIKind string } func (r *ApplicationDefinitionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { @@ -67,7 +65,7 @@ func (r *ApplicationDefinitionReconciler) SetupWithManager(mgr ctrl.Manager) err } type appDefHashView struct { - Name string `json:"name"` + Name string `json:"name"` Spec cozyv1alpha1.ApplicationDefinitionSpec `json:"spec"` } @@ -155,23 +153,13 @@ func (r *ApplicationDefinitionReconciler) getWorkload( ctx context.Context, key types.NamespacedName, ) (tpl *corev1.PodTemplateSpec, obj client.Object, patch client.Patch, err error) { - if r.CozystackAPIKind == "Deployment" { - dep := &appsv1.Deployment{} - if err := r.Get(ctx, key, dep); err != nil { - return nil, nil, nil, err - } - obj = dep - tpl = &dep.Spec.Template - patch = client.MergeFrom(dep.DeepCopy()) - } else { - ds := &appsv1.DaemonSet{} - if err := r.Get(ctx, key, ds); err != nil { - return nil, nil, nil, err - } - obj = ds - tpl = &ds.Spec.Template - patch = client.MergeFrom(ds.DeepCopy()) + dep := &appsv1.Deployment{} + if err := r.Get(ctx, key, dep); err != nil { + return nil, nil, nil, err } + obj = dep + tpl = &dep.Spec.Template + patch = client.MergeFrom(dep.DeepCopy()) if tpl.Annotations == nil { tpl.Annotations = make(map[string]string) } diff --git a/packages/system/cozystack-controller/templates/deployment.yaml b/packages/system/cozystack-controller/templates/deployment.yaml index 201aeb72..aab7bbe1 100644 --- a/packages/system/cozystack-controller/templates/deployment.yaml +++ b/packages/system/cozystack-controller/templates/deployment.yaml @@ -27,6 +27,3 @@ spec: {{- if .Values.cozystackController.disableTelemetry }} - --disable-telemetry {{- end }} - {{- if eq .Values.cozystackController.cozystackAPIKind "Deployment" }} - - --reconcile-deployment - {{- end }} diff --git a/packages/system/cozystack-controller/values.yaml b/packages/system/cozystack-controller/values.yaml index 2a1f9df4..908a955d 100644 --- a/packages/system/cozystack-controller/values.yaml +++ b/packages/system/cozystack-controller/values.yaml @@ -2,4 +2,3 @@ cozystackController: image: ghcr.io/cozystack/cozystack/cozystack-controller:v1.0.0-beta.3@sha256:00b0ba15fef7c3ce8c0801ecb11b1953665a511990e18f51c2ca3ca40127c7aa debug: false disableTelemetry: false - cozystackAPIKind: "DaemonSet" From b7e16aaa96a304bc683a083067ad72e8d1df5a4b Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Sat, 14 Feb 2026 02:49:47 +0100 Subject: [PATCH 258/889] Update kilo v0.7.1 Signed-off-by: Andrei Kvapil --- packages/system/kilo/Makefile | 2 +- packages/system/kilo/values.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/system/kilo/Makefile b/packages/system/kilo/Makefile index fcce8bd9..48dcb938 100644 --- a/packages/system/kilo/Makefile +++ b/packages/system/kilo/Makefile @@ -6,7 +6,7 @@ include ../../../hack/package.mk update: tag=$$(git ls-remote --tags --sort="v:refname" https://github.com/cozystack/kilo | awk -F'[/^]' 'END{print $$3}') && \ - digest=$$(skopeo inspect --override-os linux --format '{{.Digest}}' docker://ghcr.io/cozystack/cozystack/kilo:$${tag}) && \ + digest=$$(skopeo inspect --override-os linux --override-arch amd64 --format '{{.Digest}}' docker://ghcr.io/cozystack/cozystack/kilo:amd64-$${tag}) && \ REPOSITORY="ghcr.io/cozystack/cozystack/kilo" \ yq -i '.kilo.image.repository = strenv(REPOSITORY)' values.yaml && \ TAG="$${tag}@$${digest}" \ diff --git a/packages/system/kilo/values.yaml b/packages/system/kilo/values.yaml index 279f62b0..762f3c26 100644 --- a/packages/system/kilo/values.yaml +++ b/packages/system/kilo/values.yaml @@ -1,7 +1,7 @@ kilo: image: pullPolicy: IfNotPresent - tag: v0.7.0@sha256:69c14b8292c0dc9045fd646d332811619a2d93bcc1f9e19e11da9a9a172c988c + tag: v0.7.1@sha256:c2160097ef3aa4e9e460430665b743a14b8f19480211edab6f946264718340ff repository: ghcr.io/cozystack/cozystack/kilo podCIDR: 10.244.0.0/16 serviceCIDR: 10.96.0.0/16 From 5568c0be9f0f76b5297d0499276ef031e4df39d8 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 12 Feb 2026 01:00:05 +0100 Subject: [PATCH 259/889] feat(vm-instance): port cpuModel, instancetype switching, and runStrategy changes Port three features from virtual-machine to vm-instance: - Add cpuModel field to specify CPU model without instanceType - Allow switching between instancetype and custom resources via update hook - Migrate from running boolean to runStrategy enum Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- packages/apps/vm-instance/README.md | 45 ++++++++--------- .../apps/vm-instance/templates/_helpers.tpl | 23 +++++++++ .../vm-instance/templates/vm-update-hook.yaml | 48 +++++++++++++++++-- packages/apps/vm-instance/templates/vm.yaml | 22 +++++---- packages/apps/vm-instance/values.schema.json | 20 ++++++-- packages/apps/vm-instance/values.yaml | 14 +++++- .../vm-instance-rd/cozyrds/vm-instance.yaml | 4 +- 7 files changed, 133 insertions(+), 43 deletions(-) diff --git a/packages/apps/vm-instance/README.md b/packages/apps/vm-instance/README.md index dc63508a..11d1441d 100644 --- a/packages/apps/vm-instance/README.md +++ b/packages/apps/vm-instance/README.md @@ -36,28 +36,29 @@ virtctl ssh @ ### Common parameters -| Name | Description | Type | Value | -| ------------------- | ------------------------------------------------------------------- | ---------- | ----------- | -| `external` | Enable external access from outside the cluster. | `bool` | `false` | -| `externalMethod` | Method to pass through traffic to the VM. | `string` | `PortList` | -| `externalPorts` | Ports to forward from outside the cluster. | `[]int` | `[22]` | -| `running` | Determines if the virtual machine should be running. | `bool` | `true` | -| `instanceType` | Virtual Machine instance type. | `string` | `u1.medium` | -| `instanceProfile` | Virtual Machine preferences profile. | `string` | `ubuntu` | -| `disks` | List of disks to attach. | `[]object` | `[]` | -| `disks[i].name` | Disk name. | `string` | `""` | -| `disks[i].bus` | Disk bus type (e.g. "sata"). | `string` | `""` | -| `subnets` | Additional subnets | `[]object` | `[]` | -| `subnets[i].name` | Subnet name | `string` | `""` | -| `gpus` | List of GPUs to attach (NVIDIA driver requires at least 4 GiB RAM). | `[]object` | `[]` | -| `gpus[i].name` | The name of the GPU resource to attach. | `string` | `""` | -| `resources` | Resource configuration for the virtual machine. | `object` | `{}` | -| `resources.cpu` | Number of CPU cores allocated. | `quantity` | `""` | -| `resources.memory` | Amount of memory allocated. | `quantity` | `""` | -| `resources.sockets` | Number of CPU sockets (vCPU topology). | `quantity` | `""` | -| `sshKeys` | List of SSH public keys for authentication. | `[]string` | `[]` | -| `cloudInit` | Cloud-init user data. | `string` | `""` | -| `cloudInitSeed` | Seed string to generate SMBIOS UUID for the VM. | `string` | `""` | +| Name | Description | Type | Value | +| ------------------- | --------------------------------------------------------------------------------------------------------------------------------- | ---------- | ----------- | +| `external` | Enable external access from outside the cluster. | `bool` | `false` | +| `externalMethod` | Method to pass through traffic to the VM. | `string` | `PortList` | +| `externalPorts` | Ports to forward from outside the cluster. | `[]int` | `[22]` | +| `runStrategy` | Requested running state of the VirtualMachineInstance | `string` | `Always` | +| `instanceType` | Virtual Machine instance type. | `string` | `u1.medium` | +| `instanceProfile` | Virtual Machine preferences profile. | `string` | `ubuntu` | +| `disks` | List of disks to attach. | `[]object` | `[]` | +| `disks[i].name` | Disk name. | `string` | `""` | +| `disks[i].bus` | Disk bus type (e.g. "sata"). | `string` | `""` | +| `subnets` | Additional subnets | `[]object` | `[]` | +| `subnets[i].name` | Subnet name | `string` | `""` | +| `gpus` | List of GPUs to attach (NVIDIA driver requires at least 4 GiB RAM). | `[]object` | `[]` | +| `gpus[i].name` | The name of the GPU resource to attach. | `string` | `""` | +| `cpuModel` | Model specifies the CPU model inside the VMI. List of available models https://github.com/libvirt/libvirt/tree/master/src/cpu_map | `string` | `""` | +| `resources` | Resource configuration for the virtual machine. | `object` | `{}` | +| `resources.cpu` | Number of CPU cores allocated. | `quantity` | `""` | +| `resources.memory` | Amount of memory allocated. | `quantity` | `""` | +| `resources.sockets` | Number of CPU sockets (vCPU topology). | `quantity` | `""` | +| `sshKeys` | List of SSH public keys for authentication. | `[]string` | `[]` | +| `cloudInit` | Cloud-init user data. | `string` | `""` | +| `cloudInitSeed` | Seed string to generate SMBIOS UUID for the VM. | `string` | `""` | ## U Series diff --git a/packages/apps/vm-instance/templates/_helpers.tpl b/packages/apps/vm-instance/templates/_helpers.tpl index e65cad9d..ddea90fe 100644 --- a/packages/apps/vm-instance/templates/_helpers.tpl +++ b/packages/apps/vm-instance/templates/_helpers.tpl @@ -70,6 +70,29 @@ Generate a stable UUID for cloud-init re-initialization upon upgrade. {{- $uuid }} {{- end }} +{{/* +Domain resources (cpu, memory) as a JSON object. +Used in vm.yaml for rendering and in the update hook for merge patches. +*/}} +{{- define "virtual-machine.domainResources" -}} +{{- $result := dict -}} +{{- if or .Values.cpuModel (and .Values.resources .Values.resources.cpu .Values.resources.sockets) -}} + {{- $cpu := dict -}} + {{- if and .Values.resources .Values.resources.cpu .Values.resources.sockets -}} + {{- $_ := set $cpu "cores" (.Values.resources.cpu | int64) -}} + {{- $_ := set $cpu "sockets" (.Values.resources.sockets | int64) -}} + {{- end -}} + {{- if .Values.cpuModel -}} + {{- $_ := set $cpu "model" .Values.cpuModel -}} + {{- end -}} + {{- $_ := set $result "cpu" $cpu -}} +{{- end -}} +{{- if and .Values.resources .Values.resources.memory -}} + {{- $_ := set $result "resources" (dict "requests" (dict "memory" .Values.resources.memory)) -}} +{{- end -}} +{{- $result | toJson -}} +{{- end -}} + {{/* Node Affinity for Windows VMs */}} diff --git a/packages/apps/vm-instance/templates/vm-update-hook.yaml b/packages/apps/vm-instance/templates/vm-update-hook.yaml index a12e8583..995dafff 100644 --- a/packages/apps/vm-instance/templates/vm-update-hook.yaml +++ b/packages/apps/vm-instance/templates/vm-update-hook.yaml @@ -2,26 +2,43 @@ {{- $namespace := .Release.Namespace -}} {{- $existingVM := lookup "kubevirt.io/v1" "VirtualMachine" $namespace $vmName -}} +{{- $existingService := lookup "v1" "Service" $namespace $vmName -}} {{- $instanceType := .Values.instanceType | default "" -}} {{- $instanceProfile := .Values.instanceProfile | default "" -}} +{{- $desiredServiceType := ternary "LoadBalancer" "ClusterIP" .Values.external -}} {{- $needUpdateType := false -}} {{- $needUpdateProfile := false -}} +{{- $needRecreateService := false -}} +{{- $needRemoveInstanceType := false -}} +{{- $needRemoveCustomResources := false -}} -{{- if and $existingVM $instanceType -}} +{{- $existingHasInstanceType := and $existingVM $existingVM.spec.instancetype -}} +{{- if and $existingHasInstanceType (not $instanceType) -}} + {{- $needRemoveInstanceType = true -}} +{{- else if and $existingHasInstanceType $instanceType -}} {{- if not (eq $existingVM.spec.instancetype.name $instanceType) -}} {{- $needUpdateType = true -}} {{- end -}} +{{- else if and $existingVM (not $existingHasInstanceType) $instanceType -}} + {{- $needRemoveCustomResources = true -}} {{- end -}} -{{- if and $existingVM $instanceProfile -}} +{{- if and $existingVM $existingVM.spec.preference $instanceProfile -}} {{- if not (eq $existingVM.spec.preference.name $instanceProfile) -}} {{- $needUpdateProfile = true -}} {{- end -}} {{- end -}} -{{- if or $needUpdateType $needUpdateProfile }} +{{- if $existingService -}} + {{- $currentServiceType := $existingService.spec.type -}} + {{- if ne $currentServiceType $desiredServiceType -}} + {{- $needRecreateService = true -}} + {{- end -}} +{{- end -}} + +{{- if or $needUpdateType $needUpdateProfile $needRecreateService $needRemoveInstanceType $needRemoveCustomResources }} apiVersion: batch/v1 kind: Job metadata: @@ -58,13 +75,32 @@ spec: --type merge \ -p '{"spec":{"instancetype":{"name": "{{ $instanceType }}", "revisionName": null}}}' {{- end }} - + {{- if $needUpdateProfile }} echo "Patching VirtualMachine for preference update..." kubectl patch virtualmachines.kubevirt.io {{ $vmName }} -n {{ $namespace }} \ --type merge \ -p '{"spec":{"preference":{"name": "{{ $instanceProfile }}", "revisionName": null}}}' {{- end }} + + {{- if $needRemoveInstanceType }} + echo "Removing instancetype from VM (switching to custom resources)..." + kubectl patch virtualmachines.kubevirt.io {{ $vmName }} -n {{ $namespace }} \ + --type merge \ + -p '{"spec":{"instancetype":null{{- if not $instanceProfile }},"preference":null{{- end }},"template":{"spec":{"domain":{{ include "virtual-machine.domainResources" . }}}}}}' + {{- end }} + + {{- if $needRemoveCustomResources }} + echo "Removing custom CPU/memory from domain (switching to instancetype)..." + kubectl patch virtualmachines.kubevirt.io {{ $vmName }} -n {{ $namespace }} \ + --type merge \ + -p '{"spec":{"instancetype":{"name":"{{ $instanceType }}","revisionName":null},"template":{"spec":{"domain":{"cpu":null,"resources":null}}}}}' + {{- end }} + + {{- if $needRecreateService }} + echo "Removing Service..." + kubectl delete service --cascade=orphan -n {{ $namespace }} {{ $vmName }} + {{- end }} --- apiVersion: v1 kind: ServiceAccount @@ -87,6 +123,10 @@ rules: - apiGroups: ["kubevirt.io"] resources: ["virtualmachines"] verbs: ["patch", "get", "list", "watch"] + - apiGroups: [""] + resources: ["services"] + resourceNames: ["{{ $vmName }}"] + verbs: ["delete"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding diff --git a/packages/apps/vm-instance/templates/vm.yaml b/packages/apps/vm-instance/templates/vm.yaml index acad3475..226e5aa7 100644 --- a/packages/apps/vm-instance/templates/vm.yaml +++ b/packages/apps/vm-instance/templates/vm.yaml @@ -4,6 +4,9 @@ {{- if and .Values.instanceProfile (not (lookup "instancetype.kubevirt.io/v1beta1" "VirtualMachineClusterPreference" "" .Values.instanceProfile)) }} {{- fail (printf "Specified instanceProfile does not exist in the cluster: %s" .Values.instanceProfile) }} {{- end }} +{{- if and (not .Values.instanceType) (not (and .Values.resources .Values.resources.cpu .Values.resources.sockets .Values.resources.memory)) }} +{{- fail "Either instanceType or resources (cpu, sockets, memory) must be specified" }} +{{- end }} apiVersion: kubevirt.io/v1 kind: VirtualMachine @@ -12,7 +15,11 @@ metadata: labels: {{- include "virtual-machine.labels" . | nindent 4 }} spec: - running: {{ .Values.running }} + {{- if hasKey .Values "runStrategy" }} + runStrategy: {{ .Values.runStrategy }} + {{- else }} + runStrategy: {{ ternary "Always" "Halted" (.Values.running | default true) }} + {{- end }} {{- with .Values.instanceType }} instancetype: kind: VirtualMachineClusterInstancetype @@ -31,15 +38,12 @@ spec: {{- include "virtual-machine.labels" . | nindent 8 }} spec: domain: - {{- if and .Values.resources .Values.resources.cpu .Values.resources.sockets }} - cpu: - cores: {{ .Values.resources.cpu }} - sockets: {{ .Values.resources.sockets }} + {{- $domainRes := include "virtual-machine.domainResources" . | fromJson -}} + {{- with $domainRes.cpu }} + cpu: {{- . | toYaml | nindent 10 }} {{- end }} - {{- if and .Values.resources .Values.resources.memory }} - resources: - requests: - memory: {{ .Values.resources.memory | quote }} + {{- with $domainRes.resources }} + resources: {{- . | toYaml | nindent 10 }} {{- end }} firmware: uuid: {{ include "virtual-machine.stableUuid" . }} diff --git a/packages/apps/vm-instance/values.schema.json b/packages/apps/vm-instance/values.schema.json index ca6b34e7..0788c879 100644 --- a/packages/apps/vm-instance/values.schema.json +++ b/packages/apps/vm-instance/values.schema.json @@ -12,6 +12,11 @@ "type": "string", "default": "" }, + "cpuModel": { + "description": "Model specifies the CPU model inside the VMI. List of available models https://github.com/libvirt/libvirt/tree/master/src/cpu_map", + "type": "string", + "default": "" + }, "disks": { "description": "List of disks to attach.", "type": "array", @@ -175,10 +180,17 @@ } } }, - "running": { - "description": "Determines if the virtual machine should be running.", - "type": "boolean", - "default": true + "runStrategy": { + "description": "Requested running state of the VirtualMachineInstance", + "type": "string", + "default": "Always", + "enum": [ + "Always", + "Halted", + "Manual", + "RerunOnFailure", + "Once" + ] }, "sshKeys": { "description": "List of SSH public keys for authentication.", diff --git a/packages/apps/vm-instance/values.yaml b/packages/apps/vm-instance/values.yaml index 9d0a1522..d5ed5679 100644 --- a/packages/apps/vm-instance/values.yaml +++ b/packages/apps/vm-instance/values.yaml @@ -31,8 +31,15 @@ externalMethod: PortList externalPorts: - 22 -## @param {bool} running - Determines if the virtual machine should be running. -running: true +## @enum {string} RunStrategy - Requested running state of the VirtualMachineInstance +## @value Always - VMI should always be running +## @value Halted - VMI should never be running +## @value Manual - VMI can be started/stopped using API endpoints +## @value RerunOnFailure - VMI will initially be running and restarted if a failure occurs, but will not be restarted upon successful completion +## @value Once - VMI will run once and not be restarted upon completion regardless if the completion is of phase Failure or Success + +## @param {RunStrategy} runStrategy - Requested running state of the VirtualMachineInstance +runStrategy: Always ## @param {string} instanceType - Virtual Machine instance type. instanceType: "u1.medium" @@ -62,6 +69,9 @@ gpus: [] ## gpus: ## - name: nvidia.com/GA102GL_A10 +## @param {string} cpuModel - Model specifies the CPU model inside the VMI. List of available models https://github.com/libvirt/libvirt/tree/master/src/cpu_map +cpuModel: "" + ## @param {Resources} [resources] - Resource configuration for the virtual machine. resources: {} diff --git a/packages/system/vm-instance-rd/cozyrds/vm-instance.yaml b/packages/system/vm-instance-rd/cozyrds/vm-instance.yaml index b03d82a0..ff7c8cfe 100644 --- a/packages/system/vm-instance-rd/cozyrds/vm-instance.yaml +++ b/packages/system/vm-instance-rd/cozyrds/vm-instance.yaml @@ -8,7 +8,7 @@ spec: singular: vminstance plural: vminstances openAPISchema: |- - {"title":"Chart Values","type":"object","properties":{"cloudInit":{"description":"Cloud-init user data.","type":"string","default":""},"cloudInitSeed":{"description":"Seed string to generate SMBIOS UUID for the VM.","type":"string","default":""},"disks":{"description":"List of disks to attach.","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"bus":{"description":"Disk bus type (e.g. \"sata\").","type":"string"},"name":{"description":"Disk name.","type":"string"}}}},"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"externalMethod":{"description":"Method to pass through traffic to the VM.","type":"string","default":"PortList","enum":["PortList","WholeIP"]},"externalPorts":{"description":"Ports to forward from outside the cluster.","type":"array","default":[22],"items":{"type":"integer"}},"gpus":{"description":"List of GPUs to attach (NVIDIA driver requires at least 4 GiB RAM).","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"name":{"description":"The name of the GPU resource to attach.","type":"string"}}}},"instanceProfile":{"description":"Virtual Machine preferences profile.","type":"string","default":"ubuntu","enum":["alpine","centos.7","centos.7.desktop","centos.stream10","centos.stream10.desktop","centos.stream8","centos.stream8.desktop","centos.stream8.dpdk","centos.stream9","centos.stream9.desktop","centos.stream9.dpdk","cirros","fedora","fedora.arm64","opensuse.leap","opensuse.tumbleweed","rhel.10","rhel.10.arm64","rhel.7","rhel.7.desktop","rhel.8","rhel.8.desktop","rhel.8.dpdk","rhel.9","rhel.9.arm64","rhel.9.desktop","rhel.9.dpdk","rhel.9.realtime","sles","ubuntu","windows.10","windows.10.virtio","windows.11","windows.11.virtio","windows.2k16","windows.2k16.virtio","windows.2k19","windows.2k19.virtio","windows.2k22","windows.2k22.virtio","windows.2k25","windows.2k25.virtio",""]},"instanceType":{"description":"Virtual Machine instance type.","type":"string","default":"u1.medium"},"resources":{"description":"Resource configuration for the virtual machine.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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},"sockets":{"description":"Number of CPU sockets (vCPU topology).","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}}},"running":{"description":"Determines if the virtual machine should be running.","type":"boolean","default":true},"sshKeys":{"description":"List of SSH public keys for authentication.","type":"array","default":[],"items":{"type":"string"}},"subnets":{"description":"Additional subnets","type":"array","default":[],"items":{"type":"object","properties":{"name":{"description":"Subnet name","type":"string"}}}}}} + {"title":"Chart Values","type":"object","properties":{"cloudInit":{"description":"Cloud-init user data.","type":"string","default":""},"cloudInitSeed":{"description":"Seed string to generate SMBIOS UUID for the VM.","type":"string","default":""},"cpuModel":{"description":"Model specifies the CPU model inside the VMI. List of available models https://github.com/libvirt/libvirt/tree/master/src/cpu_map","type":"string","default":""},"disks":{"description":"List of disks to attach.","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"bus":{"description":"Disk bus type (e.g. \"sata\").","type":"string"},"name":{"description":"Disk name.","type":"string"}}}},"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"externalMethod":{"description":"Method to pass through traffic to the VM.","type":"string","default":"PortList","enum":["PortList","WholeIP"]},"externalPorts":{"description":"Ports to forward from outside the cluster.","type":"array","default":[22],"items":{"type":"integer"}},"gpus":{"description":"List of GPUs to attach (NVIDIA driver requires at least 4 GiB RAM).","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"name":{"description":"The name of the GPU resource to attach.","type":"string"}}}},"instanceProfile":{"description":"Virtual Machine preferences profile.","type":"string","default":"ubuntu","enum":["alpine","centos.7","centos.7.desktop","centos.stream10","centos.stream10.desktop","centos.stream8","centos.stream8.desktop","centos.stream8.dpdk","centos.stream9","centos.stream9.desktop","centos.stream9.dpdk","cirros","fedora","fedora.arm64","opensuse.leap","opensuse.tumbleweed","rhel.10","rhel.10.arm64","rhel.7","rhel.7.desktop","rhel.8","rhel.8.desktop","rhel.8.dpdk","rhel.9","rhel.9.arm64","rhel.9.desktop","rhel.9.dpdk","rhel.9.realtime","sles","ubuntu","windows.10","windows.10.virtio","windows.11","windows.11.virtio","windows.2k16","windows.2k16.virtio","windows.2k19","windows.2k19.virtio","windows.2k22","windows.2k22.virtio","windows.2k25","windows.2k25.virtio",""]},"instanceType":{"description":"Virtual Machine instance type.","type":"string","default":"u1.medium"},"resources":{"description":"Resource configuration for the virtual machine.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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},"sockets":{"description":"Number of CPU sockets (vCPU topology).","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}}},"runStrategy":{"description":"Requested running state of the VirtualMachineInstance","type":"string","default":"Always","enum":["Always","Halted","Manual","RerunOnFailure","Once"]},"sshKeys":{"description":"List of SSH public keys for authentication.","type":"array","default":[],"items":{"type":"string"}},"subnets":{"description":"Additional subnets","type":"array","default":[],"items":{"type":"object","properties":{"name":{"description":"Subnet name","type":"string"}}}}}} release: prefix: vm-instance- labels: @@ -26,7 +26,7 @@ spec: tags: - compute icon: PHN2ZyB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgdmlld0JveD0iMCAwIDE0NCAxNDQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNDQiIGhlaWdodD0iMTQ0IiByeD0iMjQiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcl82ODdfMzQ1NCkiLz4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzY4N18zNDU0KSI+CjxwYXRoIGQ9Ik04OS41MDM5IDExMS43MDdINTQuNDk3QzU0LjE3MjcgMTExLjcwNyA1NC4wMTA4IDExMS4yMjEgNTQuMzM0OSAxMTEuMDU5TDU3LjI1MjIgMTA4Ljk1MkM2MC4zMzE0IDEwNi42ODMgNjEuOTUyMiAxMDIuNjMxIDYwLjk3OTcgOTguNzQxMkg4My4wMjFDODIuMDQ4NSAxMDIuNjMxIDgzLjY2OTMgMTA2LjY4MyA4Ni43NDg1IDEwOC45NTJMODkuNjY1OCAxMTEuMDU5Qzg5Ljk5IDExMS4yMjEgODkuODI3OSAxMTEuNzA3IDg5LjUwMzkgMTExLjcwN1oiIGZpbGw9IiNCMEI2QkIiLz4KPHBhdGggZD0iTTExMy4zMjggOTguNzQxSDMwLjY3MjVDMjcuNTkzMSA5OC43NDEgMjUgOTYuMTQ4IDI1IDkzLjA2ODdWMzMuMTAzMkMyNSAzMC4wMjM5IDI3LjU5MzEgMjcuNDMwNyAzMC42NzI1IDI3LjQzMDdIMTEzLjMyOEMxMTYuNDA3IDI3LjQzMDcgMTE5IDMwLjAyMzcgMTE5IDMzLjEwMzJWOTMuMDY4N0MxMTkgOTYuMTQ4IDExNi40MDcgOTguNzQxIDExMy4zMjggOTguNzQxWiIgZmlsbD0iI0U4RURFRSIvPgo8cGF0aCBkPSJNMTE5IDg0LjE1NDlIMjVWMzMuMTAzMkMyNSAzMC4wMjM5IDI3LjU5MzEgMjcuNDMwNyAzMC42NzI1IDI3LjQzMDdIMTEzLjMyOEMxMTYuNDA3IDI3LjQzMDcgMTE5IDMwLjAyMzcgMTE5IDMzLjEwMzJMMTE5IDg0LjE1NDlaIiBmaWxsPSIjMDBCM0ZGIi8+CjxwYXRoIGQ9Ik05MC42Mzc0IDExNi41NjlINTMuMzYxNkM1Mi4wNjUxIDExNi41NjkgNTAuOTMwNyAxMTUuNDM1IDUwLjkzMDcgMTE0LjEzOEM1MC45MzA3IDExMi44NDEgNTIuMDY1MSAxMTEuNzA3IDUzLjM2MTYgMTExLjcwN0g5MC42Mzc0QzkxLjkzMzkgMTExLjcwNyA5My4wNjg0IDExMi44NDEgOTMuMDY4NCAxMTQuMTM4QzkzLjA2ODQgMTE1LjQzNSA5MS45MzM5IDExNi41NjkgOTAuNjM3NCAxMTYuNTY5WiIgZmlsbD0iI0U4RURFRSIvPgo8L2c+CjxwYXRoIGQ9Ik03Mi41Mjc1IDUzLjgzNjdDNzIuNDQzMSA1My44MzUxIDcyLjM2MDUgNTMuODEyMiA3Mi4yODczIDUzLjc3MDFMNTYuNDY5OSA0NC43OTM0QzU2LjM5ODMgNDQuNzUxOSA1Ni4zMzg4IDQ0LjY5MjMgNTYuMjk3MyA0NC42MjA3QzU2LjI1NTkgNDQuNTQ5IDU2LjIzMzggNDQuNDY3OCA1Ni4yMzM0IDQ0LjM4NUM1Ni4yMzM0IDQ0LjIxNjkgNTYuMzI1OCA0NC4wNjE3IDU2LjQ2OTkgNDMuOTc4NUw3Mi4xOTEyIDM1LjA2MDlDNzIuMjYzNyAzNS4wMjEgNzIuMzQ1IDM1IDcyLjQyNzcgMzVDNzIuNTEwNSAzNSA3Mi41OTE4IDM1LjAyMSA3Mi42NjQzIDM1LjA2MDlMODguNDg3MiA0NC4wMzk1Qzg4LjU1OTEgNDQuMDgwMSA4OC42MTg4IDQ0LjEzOTIgODguNjYgNDQuMjEwN0M4OC43MDEzIDQ0LjI4MjIgODguNzIyNyA0NC4zNjM1IDg4LjcyMTkgNDQuNDQ2Qzg4LjcyMjUgNDQuNTI4NSA4OC43MDEgNDQuNjA5NyA4OC42NTk4IDQ0LjY4MTJDODguNjE4NSA0NC43NTI2IDg4LjU1ODkgNDQuODExOCA4OC40ODcyIDQ0Ljg1MjVMNzIuNzcxNCA1My43NjgzQzcyLjY5NzIgNTMuODExNCA3Mi42MTMzIDUzLjgzNDkgNzIuNTI3NSA1My44MzY3IiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBvcGFjaXR5PSIwLjciIGQ9Ik03MC4yNTUzIDc1LjY1MTdDNzAuMTcxIDc1LjY1MzUgNzAuMDg3OCA3NS42MzE3IDcwLjAxNTEgNzUuNTg4OEw1NC4yNDU4IDY2LjY0MTdDNTQuMTcxNSA2Ni42MDI0IDU0LjEwOTUgNjYuNTQzNiA1NC4wNjYxIDY2LjQ3MTZDNTQuMDIyOCA2Ni4zOTk3IDU0IDY2LjMxNzMgNTQgNjYuMjMzM1Y0OC4yNzhDNTQgNDguMTA4IDU0LjA5MjQgNDcuOTU0NiA1NC4yNDM5IDQ3Ljg2OTZDNTQuMzE3MiA0Ny44MjcxIDU0LjQwMDQgNDcuODA0NyA1NC40ODUxIDQ3LjgwNDdDNTQuNTY5NyA0Ny44MDQ3IDU0LjY1MjkgNDcuODI3MSA1NC43MjYyIDQ3Ljg2OTZMNzAuNDkzNyA1Ni44MTMxQzcwLjU2NDIgNTYuODU2NSA3MC42MjI1IDU2LjkxNyA3MC42NjMyIDU2Ljk4OTFDNzAuNzAzOSA1Ny4wNjEyIDcwLjcyNTcgNTcuMTQyNCA3MC43MjY1IDU3LjIyNTFWNzUuMTgwNUM3MC43MjU5IDc1LjI2MjggNzAuNzA0MiA3NS4zNDM2IDcwLjY2MzUgNzUuNDE1MUM3MC42MjI3IDc1LjQ4NjYgNzAuNTY0MiA3NS41NDY0IDcwLjQ5MzcgNzUuNTg4OEM3MC40MjA2IDc1LjYyOTEgNzAuMzM4NyA3NS42NTA3IDcwLjI1NTMgNzUuNjUxNyIgZmlsbD0id2hpdGUiLz4KPHBhdGggb3BhY2l0eT0iMC40IiBkPSJNNzQuNzE5OCA3NS42NTExQzc0LjYzMzMgNzUuNjUxMiA3NC41NDgyIDc1LjYyOTYgNzQuNDcyMiA3NS41ODgzQzc0LjQwMTYgNzUuNTQ2MSA3NC4zNDMyIDc1LjQ4NjIgNzQuMzAyNyA3NS40MTQ3Qzc0LjI2MjMgNzUuMzQzMSA3NC4yNDExIDc1LjI2MjIgNzQuMjQxMiA3NS4xOFY1Ny4zMzczQzc0LjI0MTIgNTcuMTcxIDc0LjMzMzYgNTcuMDE1OCA3NC40NzIyIDU2LjkyOUw5MC4yMzk3IDQ3Ljk4NTVDOTAuMzExOSA0Ny45NDM4IDkwLjM5MzggNDcuOTIxOSA5MC40NzcxIDQ3LjkyMTlDOTAuNTYwNSA0Ny45MjE5IDkwLjY0MjQgNDcuOTQzOCA5MC43MTQ2IDQ3Ljk4NTVDOTAuNzg3NiA0OC4wMjU1IDkwLjg0ODUgNDguMDg0MiA5MC44OTExIDQ4LjE1NTdDOTAuOTMzNyA0OC4yMjcyIDkwLjk1NjMgNDguMzA4OCA5MC45NTY2IDQ4LjM5MlY2Ni4yMzI4QzkwLjk1NyA2Ni4zMTY0IDkwLjkzNDcgNjYuMzk4NSA5MC44OTIxIDY2LjQ3MDRDOTAuODQ5NSA2Ni41NDI0IDkwLjc4ODEgNjYuNjAxNCA5MC43MTQ2IDY2LjY0MTFMNzQuOTUyNiA3NS41ODgzQzc0Ljg4MjUgNzUuNjMwNyA3NC44MDE4IDc1LjY1MjUgNzQuNzE5OCA3NS42NTExIiBmaWxsPSJ3aGl0ZSIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyXzY4N18zNDU0IiB4MT0iMTYxIiB5MT0iMTgwIiB4Mj0iMy41OTI4NGUtMDciIHkyPSI0Ljk5OTk4IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNTk1NjU2Ii8+CjwvbGluZWFyR3JhZGllbnQ+CjxjbGlwUGF0aCBpZD0iY2xpcDBfNjg3XzM0NTQiPgo8cmVjdCB3aWR0aD0iOTQiIGhlaWdodD0iOTQiIGZpbGw9IndoaXRlIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyNSAyNSkiLz4KPC9jbGlwUGF0aD4KPC9kZWZzPgo8L3N2Zz4K - keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "external"], ["spec", "externalMethod"], ["spec", "externalPorts"], ["spec", "running"], ["spec", "instanceType"], ["spec", "instanceProfile"], ["spec", "disks"], ["spec", "subnets"], ["spec", "gpus"], ["spec", "resources"], ["spec", "sshKeys"], ["spec", "cloudInit"], ["spec", "cloudInitSeed"]] + keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "external"], ["spec", "externalMethod"], ["spec", "externalPorts"], ["spec", "runStrategy"], ["spec", "instanceType"], ["spec", "instanceProfile"], ["spec", "disks"], ["spec", "subnets"], ["spec", "gpus"], ["spec", "cpuModel"], ["spec", "resources"], ["spec", "sshKeys"], ["spec", "cloudInit"], ["spec", "cloudInitSeed"]] secrets: exclude: [] include: [] From ee54495dfb643432d70e6f3cf9157fa3ec709619 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 12 Feb 2026 01:07:19 +0100 Subject: [PATCH 260/889] feat(platform): add migration 28 to convert virtual-machine to vm-disk + vm-instance Splits virtual-machine HelmReleases into separate vm-disk and vm-instance components. Handles PV data preservation, kube-ovn IP migration, and LoadBalancer IP pinning via metallb annotation for external VMs. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- .../platform/images/migrations/migrations/29 | 691 ++++++++++++++++++ packages/core/platform/values.yaml | 2 +- 2 files changed, 692 insertions(+), 1 deletion(-) create mode 100755 packages/core/platform/images/migrations/migrations/29 diff --git a/packages/core/platform/images/migrations/migrations/29 b/packages/core/platform/images/migrations/migrations/29 new file mode 100755 index 00000000..557fad0e --- /dev/null +++ b/packages/core/platform/images/migrations/migrations/29 @@ -0,0 +1,691 @@ +#!/bin/bash +# Migration 29 --> 30 +# Convert virtual-machine HelmReleases to vm-disk + vm-instance. +# Discovers all VirtualMachine HelmReleases by label, migrates each instance. +# Idempotent: safe to re-run. + +set -euo pipefail + +OLD_PREFIX="virtual-machine" +NEW_DISK_PREFIX="vm-disk" +NEW_INSTANCE_PREFIX="vm-instance" +PROTECTION_WEBHOOK_NAME="protection-webhook" +PROTECTION_WEBHOOK_NS="protection-webhook" + +# ============================================================ +# Helper functions +# ============================================================ + +resource_exists() { + local ns="$1" type="$2" name="$3" + kubectl -n "$ns" get "$type" "$name" --no-headers 2>/dev/null | grep -q . +} + +delete_resource() { + local ns="$1" type="$2" name="$3" + if resource_exists "$ns" "$type" "$name"; then + echo " [DELETE] ${type}/${name}" + kubectl -n "$ns" delete "$type" "$name" --wait=false + else + echo " [SKIP] ${type}/${name} already gone" + fi +} + +clone_resource() { + local ns="$1" type="$2" old_name="$3" new_name="$4" old_instance="$5" new_instance="$6" + + if resource_exists "$ns" "$type" "$new_name"; then + echo " [SKIP] ${type}/${new_name} already exists" + return 0 + fi + if ! resource_exists "$ns" "$type" "$old_name"; then + echo " [SKIP] ${type}/${old_name} not found" + return 0 + fi + + echo " [CREATE] ${type}/${new_name} from ${old_name}" + kubectl -n "$ns" get "$type" "$old_name" -o json | \ + jq --arg new_name "$new_name" --arg old "$old_instance" --arg new "$new_instance" ' + .metadata.name = $new_name | + del(.metadata.uid, .metadata.resourceVersion, .metadata.creationTimestamp, + .metadata.generation, .metadata.managedFields, .metadata.selfLink, + .metadata.ownerReferences) | + del(.status) | + .metadata.labels = ((.metadata.labels // {}) | with_entries( + if (.value | type) == "string" then .value |= gsub($old; $new) else . end)) | + .metadata.annotations = ((.metadata.annotations // {}) | with_entries( + select(.key != "kubectl.kubernetes.io/last-applied-configuration") | + if (.value | type) == "string" then .value |= gsub($old; $new) else . end)) + ' | kubectl -n "$ns" apply -f - +} + +# ============================================================ +# STEP 1: Discover all VirtualMachine instances +# ============================================================ +echo "=== Discovering VirtualMachine HelmReleases ===" +INSTANCES=() +while IFS=/ read -r ns name; do + [ -z "$ns" ] && continue + instance="${name#${OLD_PREFIX}-}" + INSTANCES+=("${ns}/${instance}") + echo " Found: ${ns}/${name} (instance=${instance})" +done < <(kubectl get hr -A -l "apps.cozystack.io/application.kind=VirtualMachine" \ + -o jsonpath='{range .items[*]}{.metadata.namespace}/{.metadata.name}{"\n"}{end}' 2>/dev/null) + +if [ ${#INSTANCES[@]} -eq 0 ]; then + echo " No VirtualMachine HelmReleases found. Nothing to migrate." + kubectl create configmap -n cozy-system cozystack-version \ + --from-literal=version=30 --dry-run=client -o yaml | kubectl apply -f- + exit 0 +fi +echo " Total: ${#INSTANCES[@]} instance(s)" + +# ============================================================ +# STEP 2: Migrate each instance +# ============================================================ +ALL_PV_NAMES=() +ALL_PROTECTED_RESOURCES=() + +for entry in "${INSTANCES[@]}"; do + NAMESPACE="${entry%%/*}" + INSTANCE="${entry#*/}" + OLD_NAME="${OLD_PREFIX}-${INSTANCE}" + NEW_DISK_NAME="${NEW_DISK_PREFIX}-${INSTANCE}" + NEW_INSTANCE_NAME="${NEW_INSTANCE_PREFIX}-${INSTANCE}" + + echo "" + echo "======================================================================" + echo "=== Migrating: ${OLD_NAME} -> ${NEW_DISK_NAME} + ${NEW_INSTANCE_NAME} in ${NAMESPACE}" + echo "======================================================================" + + # --- 2a: Suspend old HelmRelease --- + echo " --- Suspend HelmRelease ---" + if resource_exists "$NAMESPACE" "hr" "$OLD_NAME"; then + suspended=$(kubectl -n "$NAMESPACE" get hr "$OLD_NAME" -o jsonpath='{.spec.suspend}' 2>/dev/null || echo "false") + if [ "$suspended" != "true" ]; then + echo " [SUSPEND] hr/${OLD_NAME}" + kubectl -n "$NAMESPACE" patch hr "$OLD_NAME" --type=merge -p '{"spec":{"suspend":true}}' + else + echo " [SKIP] hr/${OLD_NAME} already suspended" + fi + else + echo " [SKIP] hr/${OLD_NAME} not found" + fi + + # --- 2b: Extract values from HelmRelease --- + echo " --- Extract values ---" + VALUES_SECRET="" + VALUES_KEY="values.yaml" + if resource_exists "$NAMESPACE" "hr" "$OLD_NAME"; then + VALUES_SECRET=$(kubectl -n "$NAMESPACE" get hr "$OLD_NAME" -o json | \ + jq -r '.spec.valuesFrom // [] | map(select(.kind == "Secret" and (.name | test("cozystack-values") | not))) | .[0].name // ""') + if [ -n "$VALUES_SECRET" ]; then + VALUES_KEY=$(kubectl -n "$NAMESPACE" get hr "$OLD_NAME" -o json | \ + jq -r '.spec.valuesFrom // [] | map(select(.kind == "Secret" and (.name | test("cozystack-values") | not))) | .[0].valuesKey // "values.yaml"') + fi + fi + + # Extract values from secret + EXTERNAL="false" + EXTERNAL_METHOD="PortList" + EXTERNAL_PORTS='[22]' + INSTANCE_TYPE="u1.medium" + INSTANCE_PROFILE="ubuntu" + RUN_STRATEGY="Always" + SYSTEM_DISK_IMAGE="ubuntu" + SYSTEM_DISK_STORAGE="5Gi" + SYSTEM_DISK_STORAGE_CLASS="replicated" + SSH_KEYS="[]" + CLOUD_INIT="" + CLOUD_INIT_SEED="" + SUBNETS="[]" + GPUS="[]" + CPU_MODEL="" + RESOURCES="{}" + + if [ -n "$VALUES_SECRET" ] && resource_exists "$NAMESPACE" "secret" "$VALUES_SECRET"; then + echo " Reading values from secret: ${VALUES_SECRET}" + VALUES_YAML=$(kubectl -n "$NAMESPACE" get secret "$VALUES_SECRET" -o jsonpath="{.data.${VALUES_KEY}}" 2>/dev/null | base64 -d 2>/dev/null || echo "") + if [ -z "$VALUES_YAML" ]; then + VALUES_YAML=$(kubectl -n "$NAMESPACE" get secret "$VALUES_SECRET" -o jsonpath="{.stringData.${VALUES_KEY}}" 2>/dev/null || echo "") + fi + if [ -n "$VALUES_YAML" ]; then + EXTERNAL=$(echo "$VALUES_YAML" | yq -r '.external // false') + EXTERNAL_METHOD=$(echo "$VALUES_YAML" | yq -r '.externalMethod // "PortList"') + EXTERNAL_PORTS=$(echo "$VALUES_YAML" | yq -c '.externalPorts // [22]') + INSTANCE_TYPE=$(echo "$VALUES_YAML" | yq -r '.instanceType // "u1.medium"') + INSTANCE_PROFILE=$(echo "$VALUES_YAML" | yq -r '.instanceProfile // "ubuntu"') + RUN_STRATEGY=$(echo "$VALUES_YAML" | yq -r '.runStrategy // (if .running == false then "Halted" else "Always" end)') + SYSTEM_DISK_IMAGE=$(echo "$VALUES_YAML" | yq -r '.systemDisk.image // "ubuntu"') + SYSTEM_DISK_STORAGE=$(echo "$VALUES_YAML" | yq -r '.systemDisk.storage // "5Gi"') + SYSTEM_DISK_STORAGE_CLASS=$(echo "$VALUES_YAML" | yq -r '.systemDisk.storageClass // "replicated"') + SSH_KEYS=$(echo "$VALUES_YAML" | yq -c '.sshKeys // []') + CLOUD_INIT=$(echo "$VALUES_YAML" | yq -r '.cloudInit // ""') + CLOUD_INIT_SEED=$(echo "$VALUES_YAML" | yq -r '.cloudInitSeed // ""') + SUBNETS=$(echo "$VALUES_YAML" | yq -c '.subnets // []') + GPUS=$(echo "$VALUES_YAML" | yq -c '.gpus // []') + CPU_MODEL=$(echo "$VALUES_YAML" | yq -r '.cpuModel // ""') + RESOURCES=$(echo "$VALUES_YAML" | yq -c '.resources // {}') + fi + fi + + echo " external=${EXTERNAL}, instanceType=${INSTANCE_TYPE}, image=${SYSTEM_DISK_IMAGE}" + + # --- 2c: Save kube-ovn IP --- + echo " --- Save kube-ovn IP ---" + OVN_IP="" + OVN_MAC="" + OVN_SUBNET="ovn-default" + OVN_IP_NAME="${OLD_NAME}.${NAMESPACE}" + if kubectl get ip.kubeovn.io "$OVN_IP_NAME" --no-headers 2>/dev/null | grep -q .; then + OVN_IP=$(kubectl get ip.kubeovn.io "$OVN_IP_NAME" -o jsonpath='{.spec.ipAddress}') + OVN_MAC=$(kubectl get ip.kubeovn.io "$OVN_IP_NAME" -o jsonpath='{.spec.macAddress}') + OVN_SUBNET=$(kubectl get ip.kubeovn.io "$OVN_IP_NAME" -o jsonpath='{.spec.subnet}') + echo " kube-ovn IP: ${OVN_IP}, MAC: ${OVN_MAC}, subnet: ${OVN_SUBNET}" + else + echo " [SKIP] No kube-ovn IP resource found" + fi + + # --- 2d: Save LoadBalancer IP (if external=true) --- + echo " --- Save LoadBalancer IP ---" + LB_IP="" + if [ "$EXTERNAL" = "true" ] && resource_exists "$NAMESPACE" "svc" "$OLD_NAME"; then + LB_IP=$(kubectl -n "$NAMESPACE" get svc "$OLD_NAME" -o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>/dev/null || echo "") + if [ -n "$LB_IP" ]; then + echo " LoadBalancer IP: ${LB_IP}" + else + echo " [WARN] external=true but no LoadBalancer IP assigned" + fi + else + echo " [SKIP] Not external or no service" + fi + + # --- 2e: Switch PV reclaim policy to Retain --- + echo " --- Switch PV reclaim to Retain ---" + PV_NAME="" + if resource_exists "$NAMESPACE" "pvc" "$OLD_NAME"; then + PV_NAME=$(kubectl -n "$NAMESPACE" get pvc "$OLD_NAME" -o jsonpath='{.spec.volumeName}') + if [ -n "$PV_NAME" ]; then + ALL_PV_NAMES+=("$PV_NAME") + current_policy=$(kubectl get pv "$PV_NAME" -o jsonpath='{.spec.persistentVolumeReclaimPolicy}') + if [ "$current_policy" != "Retain" ]; then + echo " [PATCH] PV ${PV_NAME}: ${current_policy} -> Retain" + kubectl patch pv "$PV_NAME" -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}' + else + echo " [SKIP] PV ${PV_NAME} already Retain" + fi + fi + elif resource_exists "$NAMESPACE" "pvc" "$NEW_DISK_NAME"; then + PV_NAME=$(kubectl -n "$NAMESPACE" get pvc "$NEW_DISK_NAME" -o jsonpath='{.spec.volumeName}') + ALL_PV_NAMES+=("$PV_NAME") + echo " [SKIP] New PVC ${NEW_DISK_NAME} already exists, PV=${PV_NAME}" + else + echo " [WARN] No PVC found for ${OLD_NAME} or ${NEW_DISK_NAME}" + fi + + # --- 2f: Stop and delete the VirtualMachine --- + echo " --- Stop VirtualMachine ---" + if resource_exists "$NAMESPACE" "vm" "$OLD_NAME"; then + echo " [PATCH] Stop VM ${OLD_NAME}" + kubectl -n "$NAMESPACE" patch vm "$OLD_NAME" --type merge -p '{"spec":{"runStrategy":"Halted"}}' 2>/dev/null || \ + kubectl -n "$NAMESPACE" patch vm "$OLD_NAME" --type merge -p '{"spec":{"running":false}}' 2>/dev/null || true + + echo " Waiting for VMI to terminate..." + kubectl -n "$NAMESPACE" wait --for=delete vmi "$OLD_NAME" --timeout=120s 2>/dev/null || true + fi + + # --- 2g: Delete VirtualMachine CR (cascades to DataVolume and PVC) --- + echo " --- Delete VirtualMachine CR ---" + if resource_exists "$NAMESPACE" "vm" "$OLD_NAME"; then + echo " [DELETE] vm/${OLD_NAME}" + kubectl -n "$NAMESPACE" delete vm "$OLD_NAME" --wait=true --timeout=60s 2>/dev/null || true + fi + + echo " Waiting for old PVC to be deleted..." + for i in $(seq 1 30); do + if ! resource_exists "$NAMESPACE" "pvc" "$OLD_NAME"; then + echo " [OK] PVC ${OLD_NAME} deleted" + break + fi + sleep 2 + done + + # If PVC still exists (orphaned), delete it manually + if resource_exists "$NAMESPACE" "pvc" "$OLD_NAME"; then + echo " [DELETE] Orphaned pvc/${OLD_NAME}" + kubectl -n "$NAMESPACE" delete pvc "$OLD_NAME" --wait=false 2>/dev/null || true + fi + + # Delete orphaned DataVolume if still present + if resource_exists "$NAMESPACE" "dv" "$OLD_NAME"; then + echo " [DELETE] Orphaned dv/${OLD_NAME}" + kubectl -n "$NAMESPACE" delete dv "$OLD_NAME" --wait=false 2>/dev/null || true + fi + + # --- 2h: Create new PVC for vm-disk --- + echo " --- Create new PVC for vm-disk ---" + if [ -n "$PV_NAME" ]; then + if resource_exists "$NAMESPACE" "pvc" "$NEW_DISK_NAME"; then + new_phase=$(kubectl -n "$NAMESPACE" get pvc "$NEW_DISK_NAME" \ + -o jsonpath='{.status.phase}' 2>/dev/null || echo "unknown") + if [ "$new_phase" = "Bound" ]; then + echo " [SKIP] PVC ${NEW_DISK_NAME} already Bound" + fi + else + echo " [CREATE] PVC ${NEW_DISK_NAME} -> PV ${PV_NAME}" + cat < ${NEW_DISK_NAME}" + kubectl patch pv "$PV_NAME" --type=merge -p "{ + \"spec\":{\"claimRef\":{\"apiVersion\":\"v1\",\"kind\":\"PersistentVolumeClaim\", + \"name\":\"${NEW_DISK_NAME}\",\"namespace\":\"${NAMESPACE}\",\"uid\":\"${new_pvc_uid}\"}} + }" + + echo " Waiting for PVC ${NEW_DISK_NAME} to bind..." + kubectl -n "$NAMESPACE" wait pvc "$NEW_DISK_NAME" \ + --for=jsonpath='{.status.phase}'=Bound --timeout=60s + echo " [OK] PVC ${NEW_DISK_NAME} is Bound" + fi + fi + + # --- 2i: Clone Secrets --- + echo " --- Clone Secrets ---" + for secret in $(kubectl -n "$NAMESPACE" get secret -o name 2>/dev/null \ + | grep "secret/${OLD_NAME}" | grep -v "sh.helm.release" | grep -v "values"); do + old_secret_name="${secret#secret/}" + suffix="${old_secret_name#${OLD_NAME}}" + new_secret_name="${NEW_INSTANCE_NAME}${suffix}" + clone_resource "$NAMESPACE" "secret" "$old_secret_name" "$new_secret_name" "$OLD_NAME" "$NEW_INSTANCE_NAME" + done + + # --- 2j: Clone WorkloadMonitor --- + echo " --- Clone WorkloadMonitor ---" + clone_resource "$NAMESPACE" "workloadmonitor.cozystack.io" "$OLD_NAME" "$NEW_INSTANCE_NAME" "$OLD_NAME" "$NEW_INSTANCE_NAME" + + # --- 2k: Migrate kube-ovn IP --- + echo " --- Migrate kube-ovn IP ---" + NEW_OVN_IP_NAME="${NEW_INSTANCE_NAME}.${NAMESPACE}" + if [ -n "$OVN_IP" ]; then + if kubectl get ip.kubeovn.io "$NEW_OVN_IP_NAME" --no-headers 2>/dev/null | grep -q .; then + echo " [SKIP] ip.kubeovn.io/${NEW_OVN_IP_NAME} already exists" + else + echo " [CREATE] ip.kubeovn.io/${NEW_OVN_IP_NAME}" + cat </dev/null | grep -q .; then + echo " [DELETE] ip.kubeovn.io/${OVN_IP_NAME}" + kubectl delete ip.kubeovn.io "$OVN_IP_NAME" 2>/dev/null || true + fi + fi + + # --- 2l: Create vm-disk values secret --- + echo " --- Create vm-disk values secret ---" + DISK_VALUES_SECRET="${NEW_DISK_NAME}-values" + if ! resource_exists "$NAMESPACE" "secret" "$DISK_VALUES_SECRET"; then + echo " [CREATE] secret/${DISK_VALUES_SECRET}" + cat </dev/null \ + | grep "secret/${OLD_NAME}" | grep -v "sh.helm.release" | grep -v "values"); do + old_secret_name="${secret#secret/}" + delete_resource "$NAMESPACE" "secret" "$old_secret_name" + done + + delete_resource "$NAMESPACE" "workloadmonitor.cozystack.io" "$OLD_NAME" + + if resource_exists "$NAMESPACE" "hr" "$OLD_NAME"; then + kubectl -n "$NAMESPACE" patch hr "$OLD_NAME" --type=json \ + -p='[{"op":"remove","path":"/metadata/finalizers"}]' 2>/dev/null || true + delete_resource "$NAMESPACE" "hr" "$OLD_NAME" + fi + + echo " [DELETE] secrets with label owner=helm,name=${OLD_NAME}" + kubectl -n "$NAMESPACE" delete secret -l "owner=helm,name=${OLD_NAME}" 2>/dev/null || true + + # Delete old values secret + if [ -n "$VALUES_SECRET" ]; then + delete_resource "$NAMESPACE" "secret" "$VALUES_SECRET" + fi + + # Collect protected resources for batch deletion + if resource_exists "$NAMESPACE" "svc" "$OLD_NAME"; then + ALL_PROTECTED_RESOURCES+=("${NAMESPACE}:svc/${OLD_NAME}") + fi +done + +# ============================================================ +# STEP 3: Delete protected resources (Services) +# ============================================================ +echo "" +echo "--- Step 3: Delete protected resources ---" + +if [ ${#ALL_PROTECTED_RESOURCES[@]} -gt 0 ]; then + echo " --- Temporarily disabling protection-webhook ---" + + WEBHOOK_REPLICAS=$(kubectl -n "$PROTECTION_WEBHOOK_NS" get deploy "$PROTECTION_WEBHOOK_NAME" \ + -o jsonpath='{.spec.replicas}' 2>/dev/null || echo "1") + + echo " [SCALE] ${PROTECTION_WEBHOOK_NAME} -> 0 (was ${WEBHOOK_REPLICAS})" + kubectl -n "$PROTECTION_WEBHOOK_NS" scale deploy "$PROTECTION_WEBHOOK_NAME" --replicas=0 + + echo " [PATCH] Set failurePolicy=Ignore on ValidatingWebhookConfiguration/${PROTECTION_WEBHOOK_NAME}" + kubectl get validatingwebhookconfiguration "$PROTECTION_WEBHOOK_NAME" -o json | \ + jq '.webhooks[].failurePolicy = "Ignore"' | \ + kubectl apply -f - + + echo " Waiting for webhook pods to terminate..." + kubectl -n "$PROTECTION_WEBHOOK_NS" wait --for=delete pod \ + -l app.kubernetes.io/name=protection-webhook --timeout=60s 2>/dev/null || true + sleep 3 + + for entry in "${ALL_PROTECTED_RESOURCES[@]}"; do + ns="${entry%%:*}" + res="${entry#*:}" + echo " [DELETE] ${ns}/${res}" + kubectl -n "$ns" delete "$res" --wait=false 2>/dev/null || true + done + + echo " [PATCH] Set failurePolicy=Fail on ValidatingWebhookConfiguration/${PROTECTION_WEBHOOK_NAME}" + kubectl get validatingwebhookconfiguration "$PROTECTION_WEBHOOK_NAME" -o json | \ + jq '.webhooks[].failurePolicy = "Fail"' | \ + kubectl apply -f - + + echo " [SCALE] ${PROTECTION_WEBHOOK_NAME} -> ${WEBHOOK_REPLICAS}" + kubectl -n "$PROTECTION_WEBHOOK_NS" scale deploy "$PROTECTION_WEBHOOK_NAME" \ + --replicas="$WEBHOOK_REPLICAS" + echo " --- protection-webhook restored ---" +else + echo " [SKIP] No protected resources to delete" +fi + +# ============================================================ +# STEP 4: Restore PV reclaim policies +# ============================================================ +echo "" +echo "--- Step 4: Restore PV reclaim policies ---" +for pv_name in "${ALL_PV_NAMES[@]}"; do + if [ -n "$pv_name" ]; then + current_policy=$(kubectl get pv "$pv_name" \ + -o jsonpath='{.spec.persistentVolumeReclaimPolicy}' 2>/dev/null || echo "unknown") + if [ "$current_policy" = "Retain" ]; then + echo " [PATCH] PV ${pv_name}: Retain -> Delete" + kubectl patch pv "$pv_name" -p '{"spec":{"persistentVolumeReclaimPolicy":"Delete"}}' + else + echo " [SKIP] PV ${pv_name} already ${current_policy}" + fi + fi +done + +# ============================================================ +# STEP 5: Unsuspend vm-disk HelmReleases first +# ============================================================ +echo "" +echo "--- Step 5: Unsuspend vm-disk HelmReleases ---" +for entry in "${INSTANCES[@]}"; do + ns="${entry%%/*}" + instance="${entry#*/}" + disk_name="${NEW_DISK_PREFIX}-${instance}" + if resource_exists "$ns" "hr" "$disk_name"; then + suspended=$(kubectl -n "$ns" get hr "$disk_name" \ + -o jsonpath='{.spec.suspend}' 2>/dev/null || echo "false") + if [ "$suspended" = "true" ]; then + echo " [UNSUSPEND] ${ns}/hr/${disk_name}" + kubectl -n "$ns" patch hr "$disk_name" --type=merge -p '{"spec":{"suspend":false}}' + else + echo " [SKIP] ${ns}/hr/${disk_name} already not suspended" + fi + fi +done + +# Wait for disk HelmReleases to become ready +echo " Waiting for vm-disk HelmReleases to reconcile..." +for entry in "${INSTANCES[@]}"; do + ns="${entry%%/*}" + instance="${entry#*/}" + disk_name="${NEW_DISK_PREFIX}-${instance}" + for i in $(seq 1 30); do + ready=$(kubectl -n "$ns" get hr "$disk_name" -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || echo "") + if [ "$ready" = "True" ]; then + echo " [OK] ${ns}/hr/${disk_name} is Ready" + break + fi + sleep 5 + done +done + +# ============================================================ +# STEP 6: Unsuspend vm-instance HelmReleases +# ============================================================ +echo "" +echo "--- Step 6: Unsuspend vm-instance HelmReleases ---" +for entry in "${INSTANCES[@]}"; do + ns="${entry%%/*}" + instance="${entry#*/}" + instance_name="${NEW_INSTANCE_PREFIX}-${instance}" + if resource_exists "$ns" "hr" "$instance_name"; then + suspended=$(kubectl -n "$ns" get hr "$instance_name" \ + -o jsonpath='{.spec.suspend}' 2>/dev/null || echo "false") + if [ "$suspended" = "true" ]; then + echo " [UNSUSPEND] ${ns}/hr/${instance_name}" + kubectl -n "$ns" patch hr "$instance_name" --type=merge -p '{"spec":{"suspend":false}}' + else + echo " [SKIP] ${ns}/hr/${instance_name} already not suspended" + fi + fi +done + +echo "" +echo "=== Migration complete (${#INSTANCES[@]} instance(s)) ===" + +# Stamp version +kubectl create configmap -n cozy-system cozystack-version \ + --from-literal=version=30 --dry-run=client -o yaml | kubectl apply -f- diff --git a/packages/core/platform/values.yaml b/packages/core/platform/values.yaml index 9bb8e2b1..38452a17 100644 --- a/packages/core/platform/values.yaml +++ b/packages/core/platform/values.yaml @@ -6,7 +6,7 @@ sourceRef: migrations: enabled: false image: ghcr.io/cozystack/cozystack/platform-migrations:latest - targetVersion: 29 + targetVersion: 30 # Bundle deployment configuration bundles: system: From 783682f171ddccf6e493085c37d0859be2240050 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 12 Feb 2026 02:07:13 +0100 Subject: [PATCH 261/889] fix(platform): fix migration 28 CDI webhook and cloud-init issues Disable both cdi-operator and cdi-apiserver during DataVolume creation to prevent CDI webhook from rejecting PVC adoption. Also delete mutating webhook configurations alongside validating ones. Fix cloud-init value serialization to avoid spurious newline. Add volumeMode detection from PV. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- .../platform/images/migrations/migrations/29 | 140 ++++++++++++++---- 1 file changed, 112 insertions(+), 28 deletions(-) diff --git a/packages/core/platform/images/migrations/migrations/29 b/packages/core/platform/images/migrations/migrations/29 index 557fad0e..96ac66eb 100755 --- a/packages/core/platform/images/migrations/migrations/29 +++ b/packages/core/platform/images/migrations/migrations/29 @@ -11,6 +11,10 @@ NEW_DISK_PREFIX="vm-disk" NEW_INSTANCE_PREFIX="vm-instance" PROTECTION_WEBHOOK_NAME="protection-webhook" PROTECTION_WEBHOOK_NS="protection-webhook" +CDI_APISERVER_NS="cozy-kubevirt-cdi" +CDI_APISERVER_DEPLOY="cdi-apiserver" +CDI_VALIDATING_WEBHOOKS="cdi-api-datavolume-validate cdi-api-dataimportcron-validate cdi-api-populator-validate cdi-api-validate" +CDI_MUTATING_WEBHOOKS="cdi-api-datavolume-mutate" # ============================================================ # Helper functions @@ -272,7 +276,8 @@ for entry in "${INSTANCES[@]}"; do echo " [SKIP] PVC ${NEW_DISK_NAME} already Bound" fi else - echo " [CREATE] PVC ${NEW_DISK_NAME} -> PV ${PV_NAME}" + PV_VOLUME_MODE=$(kubectl get pv "$PV_NAME" -o jsonpath='{.spec.volumeMode}' 2>/dev/null || echo "Filesystem") + echo " [CREATE] PVC ${NEW_DISK_NAME} -> PV ${PV_NAME} (volumeMode=${PV_VOLUME_MODE})" cat </dev/null | grep -q .; then + WEBHOOK_EXISTS=true + fi - WEBHOOK_REPLICAS=$(kubectl -n "$PROTECTION_WEBHOOK_NS" get deploy "$PROTECTION_WEBHOOK_NAME" \ - -o jsonpath='{.spec.replicas}' 2>/dev/null || echo "1") + if [ "$WEBHOOK_EXISTS" = "true" ]; then + echo " --- Temporarily disabling protection-webhook ---" - echo " [SCALE] ${PROTECTION_WEBHOOK_NAME} -> 0 (was ${WEBHOOK_REPLICAS})" - kubectl -n "$PROTECTION_WEBHOOK_NS" scale deploy "$PROTECTION_WEBHOOK_NAME" --replicas=0 + WEBHOOK_REPLICAS=$(kubectl -n "$PROTECTION_WEBHOOK_NS" get deploy "$PROTECTION_WEBHOOK_NAME" \ + -o jsonpath='{.spec.replicas}' 2>/dev/null || echo "1") - echo " [PATCH] Set failurePolicy=Ignore on ValidatingWebhookConfiguration/${PROTECTION_WEBHOOK_NAME}" - kubectl get validatingwebhookconfiguration "$PROTECTION_WEBHOOK_NAME" -o json | \ - jq '.webhooks[].failurePolicy = "Ignore"' | \ - kubectl apply -f - + echo " [SCALE] ${PROTECTION_WEBHOOK_NAME} -> 0 (was ${WEBHOOK_REPLICAS})" + kubectl -n "$PROTECTION_WEBHOOK_NS" scale deploy "$PROTECTION_WEBHOOK_NAME" --replicas=0 - echo " Waiting for webhook pods to terminate..." - kubectl -n "$PROTECTION_WEBHOOK_NS" wait --for=delete pod \ - -l app.kubernetes.io/name=protection-webhook --timeout=60s 2>/dev/null || true - sleep 3 + echo " [PATCH] Set failurePolicy=Ignore on ValidatingWebhookConfiguration/${PROTECTION_WEBHOOK_NAME}" + kubectl get validatingwebhookconfiguration "$PROTECTION_WEBHOOK_NAME" -o json | \ + jq '.webhooks[].failurePolicy = "Ignore"' | \ + kubectl apply -f - 2>/dev/null || true + + echo " Waiting for webhook pods to terminate..." + kubectl -n "$PROTECTION_WEBHOOK_NS" wait --for=delete pod \ + -l app.kubernetes.io/name=protection-webhook --timeout=60s 2>/dev/null || true + sleep 3 + fi for entry in "${ALL_PROTECTED_RESOURCES[@]}"; do ns="${entry%%:*}" @@ -594,15 +607,17 @@ if [ ${#ALL_PROTECTED_RESOURCES[@]} -gt 0 ]; then kubectl -n "$ns" delete "$res" --wait=false 2>/dev/null || true done - echo " [PATCH] Set failurePolicy=Fail on ValidatingWebhookConfiguration/${PROTECTION_WEBHOOK_NAME}" - kubectl get validatingwebhookconfiguration "$PROTECTION_WEBHOOK_NAME" -o json | \ - jq '.webhooks[].failurePolicy = "Fail"' | \ - kubectl apply -f - + if [ "$WEBHOOK_EXISTS" = "true" ]; then + echo " [PATCH] Set failurePolicy=Fail on ValidatingWebhookConfiguration/${PROTECTION_WEBHOOK_NAME}" + kubectl get validatingwebhookconfiguration "$PROTECTION_WEBHOOK_NAME" -o json | \ + jq '.webhooks[].failurePolicy = "Fail"' | \ + kubectl apply -f - 2>/dev/null || true - echo " [SCALE] ${PROTECTION_WEBHOOK_NAME} -> ${WEBHOOK_REPLICAS}" - kubectl -n "$PROTECTION_WEBHOOK_NS" scale deploy "$PROTECTION_WEBHOOK_NAME" \ - --replicas="$WEBHOOK_REPLICAS" - echo " --- protection-webhook restored ---" + echo " [SCALE] ${PROTECTION_WEBHOOK_NAME} -> ${WEBHOOK_REPLICAS}" + kubectl -n "$PROTECTION_WEBHOOK_NS" scale deploy "$PROTECTION_WEBHOOK_NAME" \ + --replicas="$WEBHOOK_REPLICAS" + echo " --- protection-webhook restored ---" + fi else echo " [SKIP] No protected resources to delete" fi @@ -626,10 +641,52 @@ for pv_name in "${ALL_PV_NAMES[@]}"; do done # ============================================================ -# STEP 5: Unsuspend vm-disk HelmReleases first +# STEP 5: Temporarily disable CDI datavolume webhooks +# ============================================================ +# CDI's datavolume-validate webhook rejects DataVolume creation when a PVC +# with the same name already exists. We must disable it so that vm-disk +# HelmReleases can reconcile and adopt the pre-created PVCs. +# We scale down both cdi-operator (which recreates webhook configs) and +# cdi-apiserver (which serves the webhooks), then delete webhook configs. +# Both are restored after vm-disk HRs reconcile. +echo "" +echo "--- Step 5: Temporarily disable CDI webhooks ---" + +CDI_OPERATOR_REPLICAS=$(kubectl -n "$CDI_APISERVER_NS" get deploy cdi-operator \ + -o jsonpath='{.spec.replicas}' 2>/dev/null || echo "1") +CDI_APISERVER_REPLICAS=$(kubectl -n "$CDI_APISERVER_NS" get deploy "$CDI_APISERVER_DEPLOY" \ + -o jsonpath='{.spec.replicas}' 2>/dev/null || echo "1") + +echo " [SCALE] cdi-operator -> 0 (was ${CDI_OPERATOR_REPLICAS})" +kubectl -n "$CDI_APISERVER_NS" scale deploy cdi-operator --replicas=0 +echo " [SCALE] ${CDI_APISERVER_DEPLOY} -> 0 (was ${CDI_APISERVER_REPLICAS})" +kubectl -n "$CDI_APISERVER_NS" scale deploy "$CDI_APISERVER_DEPLOY" --replicas=0 + +echo " Waiting for cdi pods to terminate..." +kubectl -n "$CDI_APISERVER_NS" wait --for=delete pod \ + -l cdi.kubevirt.io=cdi-apiserver --timeout=60s 2>/dev/null || true +kubectl -n "$CDI_APISERVER_NS" wait --for=delete pod \ + -l name=cdi-operator --timeout=60s 2>/dev/null || true + +for wh in $CDI_VALIDATING_WEBHOOKS; do + if kubectl get validatingwebhookconfiguration "$wh" >/dev/null 2>&1; then + echo " [DELETE] validatingwebhookconfiguration/${wh}" + kubectl delete validatingwebhookconfiguration "$wh" 2>/dev/null || true + fi +done +for wh in $CDI_MUTATING_WEBHOOKS; do + if kubectl get mutatingwebhookconfiguration "$wh" >/dev/null 2>&1; then + echo " [DELETE] mutatingwebhookconfiguration/${wh}" + kubectl delete mutatingwebhookconfiguration "$wh" 2>/dev/null || true + fi +done +sleep 2 + +# ============================================================ +# STEP 6: Unsuspend vm-disk HelmReleases first # ============================================================ echo "" -echo "--- Step 5: Unsuspend vm-disk HelmReleases ---" +echo "--- Step 6: Unsuspend vm-disk HelmReleases ---" for entry in "${INSTANCES[@]}"; do ns="${entry%%/*}" instance="${entry#*/}" @@ -643,6 +700,10 @@ for entry in "${INSTANCES[@]}"; do else echo " [SKIP] ${ns}/hr/${disk_name} already not suspended" fi + # Force immediate reconciliation + echo " [TRIGGER] Reconcile ${ns}/hr/${disk_name}" + kubectl -n "$ns" annotate hr "$disk_name" --overwrite \ + "reconcile.fluxcd.io/requestedAt=$(date +%s)" 2>/dev/null || true fi done @@ -652,21 +713,44 @@ for entry in "${INSTANCES[@]}"; do ns="${entry%%/*}" instance="${entry#*/}" disk_name="${NEW_DISK_PREFIX}-${instance}" - for i in $(seq 1 30); do + for i in $(seq 1 60); do ready=$(kubectl -n "$ns" get hr "$disk_name" -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || echo "") if [ "$ready" = "True" ]; then echo " [OK] ${ns}/hr/${disk_name} is Ready" break fi + if [ "$i" = "60" ]; then + echo " [WARN] ${ns}/hr/${disk_name} did not become Ready within timeout" + fi sleep 5 done done # ============================================================ -# STEP 6: Unsuspend vm-instance HelmReleases +# STEP 7: Restore CDI webhooks +# ============================================================ +# Scale cdi-operator and cdi-apiserver back up. +# cdi-apiserver will recreate webhook configurations automatically on start. +echo "" +echo "--- Step 7: Restore CDI webhooks ---" + +echo " [SCALE] cdi-operator -> ${CDI_OPERATOR_REPLICAS}" +kubectl -n "$CDI_APISERVER_NS" scale deploy cdi-operator \ + --replicas="$CDI_OPERATOR_REPLICAS" +echo " [SCALE] ${CDI_APISERVER_DEPLOY} -> ${CDI_APISERVER_REPLICAS}" +kubectl -n "$CDI_APISERVER_NS" scale deploy "$CDI_APISERVER_DEPLOY" \ + --replicas="$CDI_APISERVER_REPLICAS" + +echo " Waiting for CDI to be ready..." +kubectl -n "$CDI_APISERVER_NS" rollout status deploy cdi-operator --timeout=120s 2>/dev/null || true +kubectl -n "$CDI_APISERVER_NS" rollout status deploy "$CDI_APISERVER_DEPLOY" --timeout=120s 2>/dev/null || true +echo " --- CDI webhooks restored ---" + +# ============================================================ +# STEP 8: Unsuspend vm-instance HelmReleases # ============================================================ echo "" -echo "--- Step 6: Unsuspend vm-instance HelmReleases ---" +echo "--- Step 8: Unsuspend vm-instance HelmReleases ---" for entry in "${INSTANCES[@]}"; do ns="${entry%%/*}" instance="${entry#*/}" From 08a5f9890e71cfd5be937ec2fd5ea7ebec531afb Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 12 Feb 2026 08:41:33 +0100 Subject: [PATCH 262/889] feat(platform): remove virtual-machine application The virtual-machine application is replaced by separate vm-disk and vm-instance applications. Migration 28 handles the conversion of existing VirtualMachine HelmReleases. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- packages/apps/virtual-machine/.helmignore | 3 - packages/apps/virtual-machine/Chart.yaml | 7 - packages/apps/virtual-machine/Makefile | 10 - packages/apps/virtual-machine/README.md | 278 ------------------ packages/apps/virtual-machine/charts/cozy-lib | 1 - .../hack/update-instance-types.sh | 1 - packages/apps/virtual-machine/logos/vm.svg | 21 -- .../virtual-machine/templates/_helpers.tpl | 126 -------- .../templates/dashboard-resourcemap.yaml | 43 --- .../templates/secret-network-config.yaml | 40 --- .../virtual-machine/templates/secret.yaml | 33 --- .../virtual-machine/templates/service.yaml | 38 --- .../templates/vm-update-hook.yaml | 170 ----------- .../apps/virtual-machine/templates/vm.yaml | 159 ---------- .../apps/virtual-machine/values.schema.json | 230 --------------- packages/apps/virtual-machine/values.yaml | 108 ------- .../sources/virtual-machine-application.yaml | 29 -- .../core/platform/templates/bundles/iaas.yaml | 1 - packages/system/virtual-machine-rd/Chart.yaml | 3 - packages/system/virtual-machine-rd/Makefile | 4 - .../cozyrds/virtual-machine.yaml | 37 --- .../templates/backupstrategy.yaml | 35 --- .../virtual-machine-rd/templates/cozyrd.yaml | 4 - .../system/virtual-machine-rd/values.yaml | 1 - 24 files changed, 1382 deletions(-) delete mode 100644 packages/apps/virtual-machine/.helmignore delete mode 100644 packages/apps/virtual-machine/Chart.yaml delete mode 100644 packages/apps/virtual-machine/Makefile delete mode 100644 packages/apps/virtual-machine/README.md delete mode 120000 packages/apps/virtual-machine/charts/cozy-lib delete mode 100755 packages/apps/virtual-machine/hack/update-instance-types.sh delete mode 100644 packages/apps/virtual-machine/logos/vm.svg delete mode 100644 packages/apps/virtual-machine/templates/_helpers.tpl delete mode 100644 packages/apps/virtual-machine/templates/dashboard-resourcemap.yaml delete mode 100644 packages/apps/virtual-machine/templates/secret-network-config.yaml delete mode 100644 packages/apps/virtual-machine/templates/secret.yaml delete mode 100644 packages/apps/virtual-machine/templates/service.yaml delete mode 100644 packages/apps/virtual-machine/templates/vm-update-hook.yaml delete mode 100644 packages/apps/virtual-machine/templates/vm.yaml delete mode 100644 packages/apps/virtual-machine/values.schema.json delete mode 100644 packages/apps/virtual-machine/values.yaml delete mode 100644 packages/core/platform/sources/virtual-machine-application.yaml delete mode 100644 packages/system/virtual-machine-rd/Chart.yaml delete mode 100644 packages/system/virtual-machine-rd/Makefile delete mode 100644 packages/system/virtual-machine-rd/cozyrds/virtual-machine.yaml delete mode 100644 packages/system/virtual-machine-rd/templates/backupstrategy.yaml delete mode 100644 packages/system/virtual-machine-rd/templates/cozyrd.yaml delete mode 100644 packages/system/virtual-machine-rd/values.yaml diff --git a/packages/apps/virtual-machine/.helmignore b/packages/apps/virtual-machine/.helmignore deleted file mode 100644 index 1ea0ae84..00000000 --- a/packages/apps/virtual-machine/.helmignore +++ /dev/null @@ -1,3 +0,0 @@ -.helmignore -/logos -/Makefile diff --git a/packages/apps/virtual-machine/Chart.yaml b/packages/apps/virtual-machine/Chart.yaml deleted file mode 100644 index 7067744b..00000000 --- a/packages/apps/virtual-machine/Chart.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: v2 -name: virtual-machine -description: Virtual Machine (simple) -icon: /logos/vm.svg -type: application -version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process -appVersion: 0.14.0 diff --git a/packages/apps/virtual-machine/Makefile b/packages/apps/virtual-machine/Makefile deleted file mode 100644 index 7c482228..00000000 --- a/packages/apps/virtual-machine/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -include ../../../hack/package.mk - -generate: - cozyvalues-gen -v values.yaml -s values.schema.json -r README.md - yq -o json -i '.properties.gpus.items.type = "object" | .properties.gpus.default = []' values.schema.json - # INSTANCE_TYPES=$$(yq e '.metadata.name' -o=json -r ../../system/kubevirt-instancetypes/templates/instancetypes.yaml | yq 'split(" ") | . + [""]' -o json) \ - # && yq -i -o json ".properties.instanceType.enum = $${INSTANCE_TYPES}" values.schema.json - PREFERENCES=$$(yq e '.metadata.name' -o=json -r ../../system/kubevirt-instancetypes/templates/preferences.yaml | yq 'split(" ") | . + [""]' -o json) \ - && yq -i -o json ".properties.instanceProfile.enum = $${PREFERENCES}" values.schema.json - ../../../hack/update-crd.sh diff --git a/packages/apps/virtual-machine/README.md b/packages/apps/virtual-machine/README.md deleted file mode 100644 index c7cfe788..00000000 --- a/packages/apps/virtual-machine/README.md +++ /dev/null @@ -1,278 +0,0 @@ -# Virtual Machine (simple) - -A Virtual Machine (VM) simulates computer hardware, enabling various operating systems and applications to run in an isolated environment. - -## Deployment Details - -The virtual machine is managed and hosted through KubeVirt, allowing you to harness the benefits of virtualization within your Kubernetes ecosystem. - -- Docs: [KubeVirt User Guide](https://kubevirt.io/user-guide/) -- GitHub: [KubeVirt Repository](https://github.com/kubevirt/kubevirt) - -## Accessing virtual machine - -You can access the virtual machine using the virtctl tool: -- [KubeVirt User Guide - Virtctl Client Tool](https://kubevirt.io/user-guide/user_workloads/virtctl_client_tool/) - -To access the serial console: - -``` -virtctl console -``` - -To access the VM using VNC: - -``` -virtctl vnc -``` - -To SSH into the VM: - -``` -virtctl ssh @ -``` - -## Parameters - -### Common parameters - -| Name | Description | Type | Value | -| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | ---------- | ------------ | -| `external` | Enable external access from outside the cluster. | `bool` | `false` | -| `externalMethod` | Method to pass through traffic to the VM. | `string` | `PortList` | -| `externalPorts` | Ports to forward from outside the cluster. | `[]int` | `[22]` | -| `runStrategy` | Requested running state of the VirtualMachineInstance | `string` | `Always` | -| `instanceType` | Virtual Machine instance type. | `string` | `u1.medium` | -| `instanceProfile` | Virtual Machine preferences profile. | `string` | `ubuntu` | -| `systemDisk` | System disk configuration. | `object` | `{}` | -| `systemDisk.image` | The base image for the virtual machine. | `string` | `ubuntu` | -| `systemDisk.storage` | The size of the disk allocated for the virtual machine. | `string` | `5Gi` | -| `systemDisk.storageClass` | StorageClass used to store the data. | `string` | `replicated` | -| `subnets` | Additional subnets | `[]object` | `[]` | -| `subnets[i].name` | Subnet name | `string` | `""` | -| `gpus` | List of GPUs to attach. | `[]object` | `[]` | -| `gpus[i].name` | The name of the GPU resource to attach. | `string` | `""` | -| `cpuModel` | Model specifies the CPU model inside the VMI. List of available models https://github.com/libvirt/libvirt/tree/master/src/cpu_map | `string` | `""` | -| `resources` | Resource configuration for the virtual machine. | `object` | `{}` | -| `resources.cpu` | Number of CPU cores allocated. | `quantity` | `""` | -| `resources.sockets` | Number of CPU sockets (vCPU topology). | `quantity` | `""` | -| `resources.memory` | Amount of memory allocated. | `quantity` | `""` | -| `sshKeys` | List of SSH public keys for authentication. | `[]string` | `[]` | -| `cloudInit` | Cloud-init user data. | `string` | `""` | -| `cloudInitSeed` | Seed string to generate SMBIOS UUID for the VM. | `string` | `""` | - - -## U Series - -The U Series is quite neutral and provides resources for -general purpose applications. - -*U* is the abbreviation for "Universal", hinting at the universal -attitude towards workloads. - -VMs of instance types will share physical CPU cores on a -time-slice basis with other VMs. - -### U Series Characteristics - -Specific characteristics of this series are: -- *Burstable CPU performance* - The workload has a baseline compute - performance but is permitted to burst beyond this baseline, if - excess compute resources are available. -- *vCPU-To-Memory Ratio (1:4)* - A vCPU-to-Memory ratio of 1:4, for less - noise per node. - -## O Series - -The O Series is based on the U Series, with the only difference -being that memory is overcommitted. - -*O* is the abbreviation for "Overcommitted". - -### UO Series Characteristics - -Specific characteristics of this series are: -- *Burstable CPU performance* - The workload has a baseline compute - performance but is permitted to burst beyond this baseline, if - excess compute resources are available. -- *Overcommitted Memory* - Memory is over-committed in order to achieve - a higher workload density. -- *vCPU-To-Memory Ratio (1:4)* - A vCPU-to-Memory ratio of 1:4, for less - noise per node. - -## CX Series - -The CX Series provides exclusive compute resources for compute -intensive applications. - -*CX* is the abbreviation of "Compute Exclusive". - -The exclusive resources are given to the compute threads of the -VM. In order to ensure this, some additional cores (depending -on the number of disks and NICs) will be requested to offload -the IO threading from cores dedicated to the workload. -In addition, in this series, the NUMA topology of the used -cores is provided to the VM. - -### CX Series Characteristics - -Specific characteristics of this series are: -- *Hugepages* - Hugepages are used in order to improve memory - performance. -- *Dedicated CPU* - Physical cores are exclusively assigned to every - vCPU in order to provide fixed and high compute guarantees to the - workload. -- *Isolated emulator threads* - Hypervisor emulator threads are isolated - from the vCPUs in order to reduce emaulation related impact on the - workload. -- *vNUMA* - Physical NUMA topology is reflected in the guest in order to - optimize guest sided cache utilization. -- *vCPU-To-Memory Ratio (1:2)* - A vCPU-to-Memory ratio of 1:2. - -## M Series - -The M Series provides resources for memory intensive -applications. - -*M* is the abbreviation of "Memory". - -### M Series Characteristics - -Specific characteristics of this series are: -- *Hugepages* - Hugepages are used in order to improve memory - performance. -- *Burstable CPU performance* - The workload has a baseline compute - performance but is permitted to burst beyond this baseline, if - excess compute resources are available. -- *vCPU-To-Memory Ratio (1:8)* - A vCPU-to-Memory ratio of 1:8, for much - less noise per node. - -## RT Series - -The RT Series provides resources for realtime applications, like Oslat. - -*RT* is the abbreviation for "realtime". - -This series of instance types requires nodes capable of running -realtime applications. - -### RT Series Characteristics - -Specific characteristics of this series are: -- *Hugepages* - Hugepages are used in order to improve memory - performance. -- *Dedicated CPU* - Physical cores are exclusively assigned to every - vCPU in order to provide fixed and high compute guarantees to the - workload. -- *Isolated emulator threads* - Hypervisor emulator threads are isolated - from the vCPUs in order to reduce emaulation related impact on the - workload. -- *vCPU-To-Memory Ratio (1:4)* - A vCPU-to-Memory ratio of 1:4 starting from - the medium size. - -## Development - -To get started with customizing or creating your own instancetypes and preferences -see [DEVELOPMENT.md](./DEVELOPMENT.md). - -## Resources - -The following instancetype resources are provided by Cozystack: - -Name | vCPUs | Memory ------|-------|------- -cx1.2xlarge | 8 | 16Gi -cx1.4xlarge | 16 | 32Gi -cx1.8xlarge | 32 | 64Gi -cx1.large | 2 | 4Gi -cx1.medium | 1 | 2Gi -cx1.xlarge | 4 | 8Gi -gn1.2xlarge | 8 | 32Gi -gn1.4xlarge | 16 | 64Gi -gn1.8xlarge | 32 | 128Gi -gn1.xlarge | 4 | 16Gi -m1.2xlarge | 8 | 64Gi -m1.4xlarge | 16 | 128Gi -m1.8xlarge | 32 | 256Gi -m1.large | 2 | 16Gi -m1.xlarge | 4 | 32Gi -n1.2xlarge | 16 | 32Gi -n1.4xlarge | 32 | 64Gi -n1.8xlarge | 64 | 128Gi -n1.large | 4 | 8Gi -n1.medium | 4 | 4Gi -n1.xlarge | 8 | 16Gi -o1.2xlarge | 8 | 32Gi -o1.4xlarge | 16 | 64Gi -o1.8xlarge | 32 | 128Gi -o1.large | 2 | 8Gi -o1.medium | 1 | 4Gi -o1.micro | 1 | 1Gi -o1.nano | 1 | 512Mi -o1.small | 1 | 2Gi -o1.xlarge | 4 | 16Gi -rt1.2xlarge | 8 | 32Gi -rt1.4xlarge | 16 | 64Gi -rt1.8xlarge | 32 | 128Gi -rt1.large | 2 | 8Gi -rt1.medium | 1 | 4Gi -rt1.micro | 1 | 1Gi -rt1.small | 1 | 2Gi -rt1.xlarge | 4 | 16Gi -u1.2xlarge | 8 | 32Gi -u1.2xmedium | 2 | 4Gi -u1.4xlarge | 16 | 64Gi -u1.8xlarge | 32 | 128Gi -u1.large | 2 | 8Gi -u1.medium | 1 | 4Gi -u1.micro | 1 | 1Gi -u1.nano | 1 | 512Mi -u1.small | 1 | 2Gi -u1.xlarge | 4 | 16Gi - -The following preference resources are provided by Cozystack: - -Name | Guest OS ------|--------- -alpine | Alpine -centos.7 | CentOS 7 -centos.7.desktop | CentOS 7 -centos.stream10 | CentOS Stream 10 -centos.stream10.desktop | CentOS Stream 10 -centos.stream8 | CentOS Stream 8 -centos.stream8.desktop | CentOS Stream 8 -centos.stream8.dpdk | CentOS Stream 8 -centos.stream9 | CentOS Stream 9 -centos.stream9.desktop | CentOS Stream 9 -centos.stream9.dpdk | CentOS Stream 9 -cirros | Cirros -fedora | Fedora (amd64) -fedora.arm64 | Fedora (arm64) -opensuse.leap | OpenSUSE Leap -opensuse.tumbleweed | OpenSUSE Tumbleweed -rhel.10 | Red Hat Enterprise Linux 10 Beta (amd64) -rhel.10.arm64 | Red Hat Enterprise Linux 10 Beta (arm64) -rhel.7 | Red Hat Enterprise Linux 7 -rhel.7.desktop | Red Hat Enterprise Linux 7 -rhel.8 | Red Hat Enterprise Linux 8 -rhel.8.desktop | Red Hat Enterprise Linux 8 -rhel.8.dpdk | Red Hat Enterprise Linux 8 -rhel.9 | Red Hat Enterprise Linux 9 (amd64) -rhel.9.arm64 | Red Hat Enterprise Linux 9 (arm64) -rhel.9.desktop | Red Hat Enterprise Linux 9 Desktop (amd64) -rhel.9.dpdk | Red Hat Enterprise Linux 9 DPDK (amd64) -rhel.9.realtime | Red Hat Enterprise Linux 9 Realtime (amd64) -sles | SUSE Linux Enterprise Server -ubuntu | Ubuntu -windows.10 | Microsoft Windows 10 -windows.10.virtio | Microsoft Windows 10 (virtio) -windows.11 | Microsoft Windows 11 -windows.11.virtio | Microsoft Windows 11 (virtio) -windows.2k16 | Microsoft Windows Server 2016 -windows.2k16.virtio | Microsoft Windows Server 2016 (virtio) -windows.2k19 | Microsoft Windows Server 2019 -windows.2k19.virtio | Microsoft Windows Server 2019 (virtio) -windows.2k22 | Microsoft Windows Server 2022 -windows.2k22.virtio | Microsoft Windows Server 2022 (virtio) -windows.2k25 | Microsoft Windows Server 2025 -windows.2k25.virtio | Microsoft Windows Server 2025 (virtio) diff --git a/packages/apps/virtual-machine/charts/cozy-lib b/packages/apps/virtual-machine/charts/cozy-lib deleted file mode 120000 index e1813509..00000000 --- a/packages/apps/virtual-machine/charts/cozy-lib +++ /dev/null @@ -1 +0,0 @@ -../../../library/cozy-lib \ No newline at end of file diff --git a/packages/apps/virtual-machine/hack/update-instance-types.sh b/packages/apps/virtual-machine/hack/update-instance-types.sh deleted file mode 100755 index 1a248525..00000000 --- a/packages/apps/virtual-machine/hack/update-instance-types.sh +++ /dev/null @@ -1 +0,0 @@ -#!/bin/sh diff --git a/packages/apps/virtual-machine/logos/vm.svg b/packages/apps/virtual-machine/logos/vm.svg deleted file mode 100644 index 9c3e34d9..00000000 --- a/packages/apps/virtual-machine/logos/vm.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/apps/virtual-machine/templates/_helpers.tpl b/packages/apps/virtual-machine/templates/_helpers.tpl deleted file mode 100644 index ddea90fe..00000000 --- a/packages/apps/virtual-machine/templates/_helpers.tpl +++ /dev/null @@ -1,126 +0,0 @@ -{{/* -Expand the name of the chart. -*/}} -{{- define "virtual-machine.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. -*/}} -{{- define "virtual-machine.fullname" -}} -{{- if .Values.fullnameOverride }} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- $name := default .Chart.Name .Values.nameOverride }} -{{- if contains $name .Release.Name }} -{{- .Release.Name | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} -{{- end }} -{{- end }} -{{- end }} - -{{/* -Create chart name and version as used by the chart label. -*/}} -{{- define "virtual-machine.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Common labels -*/}} -{{- define "virtual-machine.labels" -}} -helm.sh/chart: {{ include "virtual-machine.chart" . }} -{{ include "virtual-machine.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} -{{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- end }} - -{{/* -Selector labels -*/}} -{{- define "virtual-machine.selectorLabels" -}} -app.kubernetes.io/name: {{ include "virtual-machine.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- end }} - -{{/* -Generate a stable UUID for cloud-init re-initialization upon upgrade. -*/}} -{{- define "virtual-machine.stableUuid" -}} -{{- $source := printf "%s-%s-%s" .Release.Namespace (include "virtual-machine.fullname" .) .Values.cloudInitSeed }} -{{- $hash := sha256sum $source }} -{{- $uuid := printf "%s-%s-4%s-9%s-%s" (substr 0 8 $hash) (substr 8 12 $hash) (substr 13 16 $hash) (substr 17 20 $hash) (substr 20 32 $hash) }} -{{- if eq .Values.cloudInitSeed "" }} - {{- /* Try to save previous uuid to not trigger full cloud-init again if user decided to remove the seed. */}} - {{- $vmResource := lookup "kubevirt.io/v1" "VirtualMachine" .Release.Namespace (include "virtual-machine.fullname" .) -}} - {{- if $vmResource }} - {{- $existingUuid := $vmResource | dig "spec" "template" "spec" "domain" "firmware" "uuid" "" }} - {{- if $existingUuid }} - {{- $uuid = $existingUuid }} - {{- end }} - {{- end }} -{{- end }} -{{- $uuid }} -{{- end }} - -{{/* -Domain resources (cpu, memory) as a JSON object. -Used in vm.yaml for rendering and in the update hook for merge patches. -*/}} -{{- define "virtual-machine.domainResources" -}} -{{- $result := dict -}} -{{- if or .Values.cpuModel (and .Values.resources .Values.resources.cpu .Values.resources.sockets) -}} - {{- $cpu := dict -}} - {{- if and .Values.resources .Values.resources.cpu .Values.resources.sockets -}} - {{- $_ := set $cpu "cores" (.Values.resources.cpu | int64) -}} - {{- $_ := set $cpu "sockets" (.Values.resources.sockets | int64) -}} - {{- end -}} - {{- if .Values.cpuModel -}} - {{- $_ := set $cpu "model" .Values.cpuModel -}} - {{- end -}} - {{- $_ := set $result "cpu" $cpu -}} -{{- end -}} -{{- if and .Values.resources .Values.resources.memory -}} - {{- $_ := set $result "resources" (dict "requests" (dict "memory" .Values.resources.memory)) -}} -{{- end -}} -{{- $result | toJson -}} -{{- end -}} - -{{/* -Node Affinity for Windows VMs -*/}} -{{- define "virtual-machine.nodeAffinity" -}} -{{- if .Values._cluster.scheduling -}} -{{- $dedicatedNodesForWindowsVMs := get .Values._cluster.scheduling "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/dashboard-resourcemap.yaml b/packages/apps/virtual-machine/templates/dashboard-resourcemap.yaml deleted file mode 100644 index 4beac5dc..00000000 --- a/packages/apps/virtual-machine/templates/dashboard-resourcemap.yaml +++ /dev/null @@ -1,43 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: {{ .Release.Name }}-dashboard-resources -rules: -- apiGroups: - - "" - resources: - - services - resourceNames: - - {{ include "virtual-machine.fullname" . }} - verbs: ["get", "list", "watch"] -- apiGroups: - - cozystack.io - resources: - - workloadmonitors - resourceNames: - - {{ .Release.Name }} - verbs: ["get", "list", "watch"] ---- -kind: RoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: {{ .Release.Name }}-dashboard-resources -subjects: -{{ include "cozy-lib.rbac.subjectsForTenantAndAccessLevel" (list "use" .Release.Namespace) }} -roleRef: - kind: Role - name: {{ .Release.Name }}-dashboard-resources - apiGroup: rbac.authorization.k8s.io ---- -apiVersion: cozystack.io/v1alpha1 -kind: WorkloadMonitor -metadata: - name: {{ $.Release.Name }} -spec: - replicas: 1 - minReplicas: 1 - kind: virtual-machine - type: virtual-machine - selector: - app.kubernetes.io/instance: {{ .Release.Name }} - version: {{ $.Chart.Version }} diff --git a/packages/apps/virtual-machine/templates/secret-network-config.yaml b/packages/apps/virtual-machine/templates/secret-network-config.yaml deleted file mode 100644 index 9800ceac..00000000 --- a/packages/apps/virtual-machine/templates/secret-network-config.yaml +++ /dev/null @@ -1,40 +0,0 @@ -# if subnets are specified and image is either ubunu or alpine -{{- if and .Values.subnets (or (eq .Values.systemDisk.image "ubuntu") (eq .Values.systemDisk.image "alpine")) }} ---- -apiVersion: v1 -kind: Secret -metadata: - name: {{ include "virtual-machine.fullname" $ }}-network-config -stringData: - networkData: | - network: - version: 1 - config: - {{- if eq .Values.systemDisk.image "ubuntu" }} - # main iface - - type: physical - name: enp1s0 - subnets: - - type: dhcp - # additional ifaces attached to subnets - {{- range $i, $subnet := .Values.subnets }} - - type: physical - name: enp{{ add 2 $i }}s0 - subnets: - - type: dhcp - {{- end }} - {{- else if eq .Values.systemDisk.image "alpine" }} - #main iface - - type: physical - name: eth0 - subnets: - - type: dhcp - # additional ifaces attached to subnets - {{- range $i, $subnet := .Values.subnets }} - - type: physical - name: eth{{ add1 $i }} - subnets: - - type: dhcp - {{- end }} - {{- end }} -{{- end }} diff --git a/packages/apps/virtual-machine/templates/secret.yaml b/packages/apps/virtual-machine/templates/secret.yaml deleted file mode 100644 index 73cd92bf..00000000 --- a/packages/apps/virtual-machine/templates/secret.yaml +++ /dev/null @@ -1,33 +0,0 @@ -{{- if .Values.sshKeys }} ---- -apiVersion: v1 -kind: Secret -metadata: - name: {{ include "virtual-machine.fullname" $ }}-ssh-keys -stringData: - {{- range $k, $v := .Values.sshKeys }} - key{{ $k }}: {{ quote $v }} - {{- end }} -{{- end }} -{{- if or .Values.cloudInit .Values.sshKeys }} ---- -apiVersion: v1 -kind: Secret -metadata: - name: {{ include "virtual-machine.fullname" . }}-cloud-init -stringData: - userdata: | - {{- if .Values.cloudInit }} - {{- .Values.cloudInit | nindent 4 }} - {{- else if and (.Values.sshKeys) (not .Values.cloudInit) }} - {{- /* - We usually provide ssh keys in cloud-init metadata, because userdata it not typed and can be used for any purpose. - However, if user provides ssh keys but not cloud-init, we still need to provide a minimal cloud-init config to avoid errors. - */}} - #cloud-config - ssh_authorized_keys: - {{- range .Values.sshKeys }} - - {{ quote . }} - {{- end }} - {{- end }} -{{- end }} diff --git a/packages/apps/virtual-machine/templates/service.yaml b/packages/apps/virtual-machine/templates/service.yaml deleted file mode 100644 index b12f7612..00000000 --- a/packages/apps/virtual-machine/templates/service.yaml +++ /dev/null @@ -1,38 +0,0 @@ ---- -apiVersion: v1 -kind: Service -metadata: - name: {{ include "virtual-machine.fullname" . }} - 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 }} - {{- range .Values.externalPorts }} - - name: port-{{ . }} - port: {{ . }} - targetPort: {{ . }} - {{- end }} - {{- end }} -{{- else }} - - port: 65535 -{{- end }} diff --git a/packages/apps/virtual-machine/templates/vm-update-hook.yaml b/packages/apps/virtual-machine/templates/vm-update-hook.yaml deleted file mode 100644 index 65ee02f0..00000000 --- a/packages/apps/virtual-machine/templates/vm-update-hook.yaml +++ /dev/null @@ -1,170 +0,0 @@ -{{- $vmName := include "virtual-machine.fullname" . -}} -{{- $namespace := .Release.Namespace -}} - -{{- $existingVM := lookup "kubevirt.io/v1" "VirtualMachine" $namespace $vmName -}} -{{- $existingPVC := lookup "v1" "PersistentVolumeClaim" $namespace $vmName -}} -{{- $existingService := lookup "v1" "Service" $namespace $vmName -}} - -{{- $instanceType := .Values.instanceType | default "" -}} -{{- $instanceProfile := .Values.instanceProfile | default "" -}} -{{- $desiredStorage := .Values.systemDisk.storage | default "" -}} -{{- $desiredServiceType := ternary "LoadBalancer" "ClusterIP" .Values.external -}} - -{{- $needUpdateType := false -}} -{{- $needUpdateProfile := false -}} -{{- $needResizePVC := false -}} -{{- $needRecreateService := false -}} -{{- $needRemoveInstanceType := false -}} -{{- $needRemoveCustomResources := false -}} - -{{- $existingHasInstanceType := and $existingVM $existingVM.spec.instancetype -}} -{{- if and $existingHasInstanceType (not $instanceType) -}} - {{- $needRemoveInstanceType = true -}} -{{- else if and $existingHasInstanceType $instanceType -}} - {{- if not (eq $existingVM.spec.instancetype.name $instanceType) -}} - {{- $needUpdateType = true -}} - {{- end -}} -{{- else if and $existingVM (not $existingHasInstanceType) $instanceType -}} - {{- $needRemoveCustomResources = true -}} -{{- end -}} - -{{- if and $existingVM $existingVM.spec.preference $instanceProfile -}} - {{- if not (eq $existingVM.spec.preference.name $instanceProfile) -}} - {{- $needUpdateProfile = true -}} - {{- end -}} -{{- end -}} - -{{- if and $existingPVC $desiredStorage -}} - {{- $currentStorage := $existingPVC.spec.resources.requests.storage | toString -}} - {{- if not (eq $currentStorage $desiredStorage) -}} - {{- $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 -}} - -{{- if $existingService -}} - {{- $currentServiceType := $existingService.spec.type -}} - {{- if ne $currentServiceType $desiredServiceType -}} - {{- $needRecreateService = true -}} - {{- end -}} -{{- end -}} - -{{- if or $needUpdateType $needUpdateProfile $needResizePVC $needRecreateService $needRemoveInstanceType $needRemoveCustomResources }} -apiVersion: batch/v1 -kind: Job -metadata: - name: "{{ $.Release.Name }}-update-hook" - annotations: - helm.sh/hook: pre-install,pre-upgrade - helm.sh/hook-weight: "0" - helm.sh/hook-delete-policy: hook-succeeded,before-hook-creation -spec: - template: - metadata: - labels: - app: "{{ $.Release.Name }}-update-hook" - policy.cozystack.io/allow-to-apiserver: "true" - spec: - serviceAccountName: {{ $.Release.Name }}-update-hook - restartPolicy: Never - containers: - - name: update-resources - image: docker.io/alpine/k8s:1.33.4 - resources: - requests: - memory: "16Mi" - cpu: "10m" - limits: - memory: "128Mi" - cpu: "100m" - command: ["sh", "-exc"] - args: - - | - {{- if $needUpdateType }} - echo "Patching VirtualMachine for instancetype update..." - kubectl patch virtualmachines.kubevirt.io {{ $vmName }} -n {{ $namespace }} \ - --type merge \ - -p '{"spec":{"instancetype":{"name": "{{ $instanceType }}", "revisionName": null}}}' - {{- end }} - - {{- if $needUpdateProfile }} - echo "Patching VirtualMachine for preference update..." - kubectl patch virtualmachines.kubevirt.io {{ $vmName }} -n {{ $namespace }} \ - --type merge \ - -p '{"spec":{"preference":{"name": "{{ $instanceProfile }}", "revisionName": null}}}' - {{- end }} - - {{- if $needRemoveInstanceType }} - echo "Removing instancetype from VM (switching to custom resources)..." - kubectl patch virtualmachines.kubevirt.io {{ $vmName }} -n {{ $namespace }} \ - --type merge \ - -p '{"spec":{"instancetype":null{{- if not $instanceProfile }},"preference":null{{- end }},"template":{"spec":{"domain":{{ include "virtual-machine.domainResources" . }}}}}}' - {{- end }} - - {{- if $needRemoveCustomResources }} - echo "Removing custom CPU/memory from domain (switching to instancetype)..." - kubectl patch virtualmachines.kubevirt.io {{ $vmName }} -n {{ $namespace }} \ - --type merge \ - -p '{"spec":{"instancetype":{"name":"{{ $instanceType }}","revisionName":null},"template":{"spec":{"domain":{"cpu":null,"resources":null}}}}}' - {{- end }} - - {{- if $needResizePVC }} - echo "Patching PVC for storage resize..." - kubectl patch pvc {{ $vmName }} -n {{ $namespace }} \ - --type merge \ - -p '{"spec":{"resources":{"requests":{"storage":"{{ $desiredStorage }}"}}}}' - {{- end }} - - {{- if $needRecreateService }} - echo "Removing Service..." - kubectl delete service --cascade=orphan -n {{ $namespace }} {{ $vmName }} - {{- end }} ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ $.Release.Name }}-update-hook - annotations: - helm.sh/hook: pre-install,pre-upgrade - helm.sh/hook-weight: "-5" - helm.sh/hook-delete-policy: before-hook-creation ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: {{ $.Release.Name }}-update-hook - annotations: - helm.sh/hook: pre-install,pre-upgrade - helm.sh/hook-weight: "-5" - helm.sh/hook-delete-policy: before-hook-creation -rules: - - apiGroups: ["kubevirt.io"] - resources: ["virtualmachines"] - verbs: ["patch", "get", "list", "watch"] - - apiGroups: [""] - resources: ["persistentvolumeclaims"] - verbs: ["patch", "get", "list", "watch"] - - apiGroups: [""] - resources: ["services"] - resourceNames: ["{{ $vmName }}"] - verbs: ["delete"] ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: {{ $.Release.Name }}-update-hook - annotations: - helm.sh/hook: pre-install,pre-upgrade - helm.sh/hook-weight: "-5" - helm.sh/hook-delete-policy: before-hook-creation -subjects: - - kind: ServiceAccount - name: {{ $.Release.Name }}-update-hook -roleRef: - kind: Role - name: {{ $.Release.Name }}-update-hook - apiGroup: rbac.authorization.k8s.io -{{- end }} diff --git a/packages/apps/virtual-machine/templates/vm.yaml b/packages/apps/virtual-machine/templates/vm.yaml deleted file mode 100644 index c1b67344..00000000 --- a/packages/apps/virtual-machine/templates/vm.yaml +++ /dev/null @@ -1,159 +0,0 @@ -{{- if and .Values.instanceType (not (lookup "instancetype.kubevirt.io/v1beta1" "VirtualMachineClusterInstancetype" "" .Values.instanceType)) }} -{{- fail (printf "Specified instancetype not exists in cluster: %s" .Values.instanceType) }} -{{- end }} -{{- if and .Values.instanceProfile (not (lookup "instancetype.kubevirt.io/v1beta1" "VirtualMachineClusterPreference" "" .Values.instanceProfile)) }} -{{- fail (printf "Specified profile not exists in cluster: %s" .Values.instanceProfile) }} -{{- end }} -{{- if and (not .Values.instanceType) (not (and .Values.resources .Values.resources.cpu .Values.resources.sockets .Values.resources.memory)) }} -{{- fail "Either instanceType or resources (cpu, sockets, memory) must be specified" }} -{{- end }} - -apiVersion: kubevirt.io/v1 -kind: VirtualMachine -metadata: - name: {{ include "virtual-machine.fullname" . }} - labels: - {{- include "virtual-machine.labels" . | nindent 4 }} -spec: - {{- if hasKey .Values "runStrategy" }} - runStrategy: {{ .Values.runStrategy }} - {{- else }} - runStrategy: {{ ternary "Always" "Halted" (.Values.running | default true) }} - {{- end }} - - {{- with .Values.instanceType }} - instancetype: - kind: VirtualMachineClusterInstancetype - name: {{ . }} - {{- end }} - {{- with .Values.instanceProfile }} - preference: - kind: VirtualMachineClusterPreference - name: {{ . }} - {{- end }} - - dataVolumeTemplates: - - metadata: - name: {{ include "virtual-machine.fullname" . }} - labels: - app.kubernetes.io/instance: {{ .Release.Name }} - spec: - storage: - resources: - requests: - storage: {{ .Values.systemDisk.storage | quote }} - {{- with .Values.systemDisk.storageClass }} - storageClassName: {{ . }} - {{- end }} - source: - {{- $dv := lookup "cdi.kubevirt.io/v1beta1" "DataVolume" "cozy-public" (printf "vm-image-%s" .Values.systemDisk.image) }} - {{- if $dv }} - pvc: - name: vm-image-{{ .Values.systemDisk.image }} - namespace: cozy-public - {{- else }} - http: - {{- if eq .Values.systemDisk.image "cirros" }} - url: https://download.cirros-cloud.net/0.6.2/cirros-0.6.2-x86_64-disk.img - {{- else if eq .Values.systemDisk.image "ubuntu" }} - url: https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img - {{- else if eq .Values.systemDisk.image "fedora" }} - url: https://download.fedoraproject.org/pub/fedora/linux/releases/42/Cloud/x86_64/images/Fedora-Cloud-Base-Generic-42-1.1.x86_64.qcow2 - {{- else if eq .Values.systemDisk.image "alpine" }} - url: https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/cloud/nocloud_alpine-3.20.8-x86_64-bios-cloudinit-r0.qcow2 - {{- else if eq .Values.systemDisk.image "talos" }} - url: https://github.com/siderolabs/talos/releases/download/v1.7.6/nocloud-amd64.raw.xz - {{- end }} - {{- end }} - - template: - metadata: - annotations: - kubevirt.io/allow-pod-bridge-network-live-migration: "true" - labels: - {{- include "virtual-machine.labels" . | nindent 8 }} - spec: - domain: - {{- $domainRes := include "virtual-machine.domainResources" . | fromJson -}} - {{- with $domainRes.cpu }} - cpu: {{- . | toYaml | nindent 10 }} - {{- end }} - {{- with $domainRes.resources }} - resources: {{- . | toYaml | nindent 10 }} - {{- end }} - firmware: - uuid: {{ include "virtual-machine.stableUuid" . }} - devices: - {{- if .Values.gpus }} - gpus: - {{- range $i, $gpu := .Values.gpus }} - - name: gpu{{ add $i 1 }} - deviceName: {{ $gpu.name }} - {{- end }} - {{- end }} - - disks: - - disk: - bus: scsi - name: systemdisk - {{- if or .Values.cloudInit .Values.sshKeys (and .Values.subnets (or (eq .Values.systemDisk.image "ubuntu") (eq .Values.systemDisk.image "alpine"))) }} - - disk: - bus: virtio - name: cloudinitdisk - {{- end }} - - interfaces: - - name: default - bridge: {} - {{- if .Values.subnets }} - {{- range $i, $subnet := .Values.subnets }} - - name: {{ $subnet.name }} - bridge: {} - {{- end }} - {{- end }} - - machine: - type: "" - - {{- if .Values.sshKeys }} - accessCredentials: - - sshPublicKey: - source: - secret: - secretName: {{ include "virtual-machine.fullname" $ }}-ssh-keys - propagationMethod: - # keys will be injected into metadata part of cloud-init disk - noCloud: {} - {{- end }} - - terminationGracePeriodSeconds: 30 - - {{- include "virtual-machine.nodeAffinity" . | nindent 6 }} - - volumes: - - name: systemdisk - dataVolume: - name: {{ include "virtual-machine.fullname" . }} - {{- if or .Values.cloudInit .Values.sshKeys (and .Values.subnets (or (eq .Values.systemDisk.image "ubuntu") (eq .Values.systemDisk.image "alpine"))) }} - - name: cloudinitdisk - cloudInitNoCloud: - {{- if or .Values.cloudInit .Values.sshKeys }} - secretRef: - name: {{ include "virtual-machine.fullname" . }}-cloud-init - {{- end }} - {{- if and .Values.subnets (or (eq .Values.systemDisk.image "ubuntu") (eq .Values.systemDisk.image "alpine")) }} - networkDataSecretRef: - name: {{ include "virtual-machine.fullname" . }}-network-config - {{- end }} - {{- end }} - - networks: - - name: default - pod: {} - {{- if .Values.subnets }} - {{- range $i, $subnet := .Values.subnets }} - - name: {{ $subnet.name }} - multus: - networkName: {{ $.Release.Namespace }}/{{ $subnet.name }} - {{- end }} - {{- end }} diff --git a/packages/apps/virtual-machine/values.schema.json b/packages/apps/virtual-machine/values.schema.json deleted file mode 100644 index 3cd6c226..00000000 --- a/packages/apps/virtual-machine/values.schema.json +++ /dev/null @@ -1,230 +0,0 @@ -{ - "title": "Chart Values", - "type": "object", - "properties": { - "cloudInit": { - "description": "Cloud-init user data.", - "type": "string", - "default": "" - }, - "cloudInitSeed": { - "description": "Seed string to generate SMBIOS UUID for the VM.", - "type": "string", - "default": "" - }, - "cpuModel": { - "description": "Model specifies the CPU model inside the VMI. List of available models https://github.com/libvirt/libvirt/tree/master/src/cpu_map", - "type": "string", - "default": "" - }, - "external": { - "description": "Enable external access from outside the cluster.", - "type": "boolean", - "default": false - }, - "externalMethod": { - "description": "Method to pass through traffic to the VM.", - "type": "string", - "default": "PortList", - "enum": [ - "PortList", - "WholeIP" - ] - }, - "externalPorts": { - "description": "Ports to forward from outside the cluster.", - "type": "array", - "default": [ - 22 - ], - "items": { - "type": "integer" - } - }, - "gpus": { - "description": "List of GPUs to attach.", - "type": "array", - "default": [], - "items": { - "type": "object", - "required": [ - "name" - ], - "properties": { - "name": { - "description": "The name of the GPU resource to attach.", - "type": "string" - } - } - } - }, - "instanceProfile": { - "description": "Virtual Machine preferences profile.", - "type": "string", - "default": "ubuntu", - "enum": [ - "alpine", - "centos.7", - "centos.7.desktop", - "centos.stream10", - "centos.stream10.desktop", - "centos.stream8", - "centos.stream8.desktop", - "centos.stream8.dpdk", - "centos.stream9", - "centos.stream9.desktop", - "centos.stream9.dpdk", - "cirros", - "fedora", - "fedora.arm64", - "opensuse.leap", - "opensuse.tumbleweed", - "rhel.10", - "rhel.10.arm64", - "rhel.7", - "rhel.7.desktop", - "rhel.8", - "rhel.8.desktop", - "rhel.8.dpdk", - "rhel.9", - "rhel.9.arm64", - "rhel.9.desktop", - "rhel.9.dpdk", - "rhel.9.realtime", - "sles", - "ubuntu", - "windows.10", - "windows.10.virtio", - "windows.11", - "windows.11.virtio", - "windows.2k16", - "windows.2k16.virtio", - "windows.2k19", - "windows.2k19.virtio", - "windows.2k22", - "windows.2k22.virtio", - "windows.2k25", - "windows.2k25.virtio", - "" - ] - }, - "instanceType": { - "description": "Virtual Machine instance type.", - "type": "string", - "default": "u1.medium" - }, - "resources": { - "description": "Resource configuration for the virtual machine.", - "type": "object", - "default": {}, - "properties": { - "cpu": { - "description": "Number of CPU cores allocated.", - "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": "Amount of memory allocated.", - "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 - }, - "sockets": { - "description": "Number of CPU sockets (vCPU topology).", - "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 - } - } - }, - "runStrategy": { - "description": "Requested running state of the VirtualMachineInstance", - "type": "string", - "default": "Always", - "enum": [ - "Always", - "Halted", - "Manual", - "RerunOnFailure", - "Once" - ] - }, - "sshKeys": { - "description": "List of SSH public keys for authentication.", - "type": "array", - "default": [], - "items": { - "type": "string" - } - }, - "subnets": { - "description": "Additional subnets", - "type": "array", - "default": [], - "items": { - "type": "object", - "properties": { - "name": { - "description": "Subnet name", - "type": "string" - } - } - } - }, - "systemDisk": { - "description": "System disk configuration.", - "type": "object", - "default": {}, - "required": [ - "image", - "storage" - ], - "properties": { - "image": { - "description": "The base image for the virtual machine.", - "type": "string", - "default": "ubuntu", - "enum": [ - "ubuntu", - "cirros", - "alpine", - "fedora", - "talos" - ] - }, - "storage": { - "description": "The size of the disk allocated for the virtual machine.", - "type": "string", - "default": "5Gi" - }, - "storageClass": { - "description": "StorageClass used to store the data.", - "type": "string", - "default": "replicated" - } - } - } - } -} diff --git a/packages/apps/virtual-machine/values.yaml b/packages/apps/virtual-machine/values.yaml deleted file mode 100644 index 169d7e10..00000000 --- a/packages/apps/virtual-machine/values.yaml +++ /dev/null @@ -1,108 +0,0 @@ -## -## @section Common parameters -## - -## @enum {string} ExternalMethod - Method to pass through traffic to the VM. -## @value PortList - Forward selected ports only. -## @value WholeIP - Forward all traffic for the IP. - -## @enum {string} SystemImage - The base image for the virtual machine. -## @value ubuntu -## @value cirros -## @value alpine -## @value fedora -## @value talos - -## @typedef {struct} SystemDisk - System disk configuration. -## @field {SystemImage} image - The base image for the virtual machine. -## @field {string} storage - The size of the disk allocated for the virtual machine. -## @field {string} [storageClass] - StorageClass used to store the data. - -## @typedef {struct} Subnet - Additional subnets -## @field {string} [name] - Subnet name - -## @typedef {struct} GPU - GPU device configuration. -## @field {string} name - The name of the GPU resource to attach. - -## @typedef {struct} Resources - Resource configuration for the virtual machine. -## @field {quantity} [cpu] - Number of CPU cores allocated. -## @field {quantity} [sockets] - Number of CPU sockets (vCPU topology). -## @field {quantity} [memory] - Amount of memory allocated. - -## @param {bool} external - Enable external access from outside the cluster. -external: false - -## @param {ExternalMethod} externalMethod - Method to pass through traffic to the VM. -externalMethod: "PortList" - -## @param {[]int} externalPorts - Ports to forward from outside the cluster. -externalPorts: - - 22 - -## @enum {string} RunStrategy - Requested running state of the VirtualMachineInstance -## @value Always - VMI should always be running -## @value Halted - VMI should never be running -## @value Manual - VMI can be started/stopped using API endpoints -## @value RerunOnFailure - VMI will initially be running and restarted if a failure occurs, but will not be restarted upon successful completion -## @value Once - VMI will run once and not be restarted upon completion regardless if the completion is of phase Failure or Success - -## @param {RunStrategy} runStrategy - Requested running state of the VirtualMachineInstance -runStrategy: Always - -## @param {string} instanceType - Virtual Machine instance type. -## @param {string} instanceProfile - Virtual Machine preferences profile. -instanceType: "u1.medium" -instanceProfile: ubuntu - -## @param {SystemDisk} systemDisk - System disk configuration. -systemDisk: - image: ubuntu - storage: 5Gi - storageClass: replicated - -## @param {[]Subnet} subnets - Additional subnets -subnets: [] -## Example: -## subnets: -## - name: subnet-84dbec17 -## - name: subnet-aa8896b5 -## - name: subnet-e9b97196 - -## @param {[]GPU} gpus - List of GPUs to attach. -gpus: [] -## Example: -## gpus: -## - name: nvidia.com/GA102GL_A10 - -## @param {string} cpuModel - Model specifies the CPU model inside the VMI. List of available models https://github.com/libvirt/libvirt/tree/master/src/cpu_map -cpuModel: "" - -## @param {Resources} [resources] - Resource configuration for the virtual machine. -resources: {} -## Example: -## resources: -## cpu: "4" -## sockets: "1" -## memory: "8Gi" - -## @param {[]string} sshKeys - List of SSH public keys for authentication. -sshKeys: [] -## Example: -## sshKeys: -## - ssh-rsa ... -## - ssh-ed25519 ... -## - -## @param {string} cloudInit - Cloud-init user data. -cloudInit: "" -## Example: -## cloudInit: | -## #cloud-config -## password: ubuntu -## chpasswd: { expire: False } -## - -## @param {string} cloudInitSeed - Seed string to generate SMBIOS UUID for the VM. -cloudInitSeed: "" -## Example: -## cloudInitSeed: "upd1" diff --git a/packages/core/platform/sources/virtual-machine-application.yaml b/packages/core/platform/sources/virtual-machine-application.yaml deleted file mode 100644 index b2583e7e..00000000 --- a/packages/core/platform/sources/virtual-machine-application.yaml +++ /dev/null @@ -1,29 +0,0 @@ ---- -apiVersion: cozystack.io/v1alpha1 -kind: PackageSource -metadata: - name: cozystack.virtual-machine-application -spec: - sourceRef: - kind: OCIRepository - name: cozystack-packages - namespace: cozy-system - path: / - variants: - - name: kubevirt - dependsOn: - - cozystack.networking - - cozystack.kubevirt - - cozystack.kubevirt-cdi - libraries: - - name: cozy-lib - path: library/cozy-lib - components: - - name: virtual-machine - path: apps/virtual-machine - libraries: [cozy-lib] - - name: virtual-machine-rd - path: system/virtual-machine-rd - install: - namespace: cozy-system - releaseName: virtual-machine-rd diff --git a/packages/core/platform/templates/bundles/iaas.yaml b/packages/core/platform/templates/bundles/iaas.yaml index 7c856b8c..41dc5181 100644 --- a/packages/core/platform/templates/bundles/iaas.yaml +++ b/packages/core/platform/templates/bundles/iaas.yaml @@ -13,7 +13,6 @@ {{include "cozystack.platform.package.default" (list "cozystack.capi-provider-infra-kubevirt" $) }} {{include "cozystack.platform.package.default" (list "cozystack.bucket-application" $) }} {{include "cozystack.platform.package" (list "cozystack.kubernetes-application" "kubevirt" $) }} -{{include "cozystack.platform.package" (list "cozystack.virtual-machine-application" "kubevirt" $) }} {{include "cozystack.platform.package" (list "cozystack.virtualprivatecloud-application" "kubevirt" $) }} {{include "cozystack.platform.package" (list "cozystack.vm-disk-application" "kubevirt" $) }} {{include "cozystack.platform.package" (list "cozystack.vm-instance-application" "kubevirt" $) }} diff --git a/packages/system/virtual-machine-rd/Chart.yaml b/packages/system/virtual-machine-rd/Chart.yaml deleted file mode 100644 index 6228a221..00000000 --- a/packages/system/virtual-machine-rd/Chart.yaml +++ /dev/null @@ -1,3 +0,0 @@ -apiVersion: v2 -name: virtual-machine-rd -version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process diff --git a/packages/system/virtual-machine-rd/Makefile b/packages/system/virtual-machine-rd/Makefile deleted file mode 100644 index 79142c9b..00000000 --- a/packages/system/virtual-machine-rd/Makefile +++ /dev/null @@ -1,4 +0,0 @@ -export NAME=virtual-machine-rd -export NAMESPACE=cozy-system - -include ../../../hack/package.mk diff --git a/packages/system/virtual-machine-rd/cozyrds/virtual-machine.yaml b/packages/system/virtual-machine-rd/cozyrds/virtual-machine.yaml deleted file mode 100644 index 6b52340b..00000000 --- a/packages/system/virtual-machine-rd/cozyrds/virtual-machine.yaml +++ /dev/null @@ -1,37 +0,0 @@ -apiVersion: cozystack.io/v1alpha1 -kind: ApplicationDefinition -metadata: - name: virtual-machine -spec: - application: - kind: VirtualMachine - plural: virtualmachines - singular: virtualmachine - openAPISchema: |- - {"title":"Chart Values","type":"object","properties":{"cloudInit":{"description":"Cloud-init user data.","type":"string","default":""},"cloudInitSeed":{"description":"Seed string to generate SMBIOS UUID for the VM.","type":"string","default":""},"cpuModel":{"description":"Model specifies the CPU model inside the VMI. List of available models https://github.com/libvirt/libvirt/tree/master/src/cpu_map","type":"string","default":""},"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"externalMethod":{"description":"Method to pass through traffic to the VM.","type":"string","default":"PortList","enum":["PortList","WholeIP"]},"externalPorts":{"description":"Ports to forward from outside the cluster.","type":"array","default":[22],"items":{"type":"integer"}},"gpus":{"description":"List of GPUs to attach.","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"name":{"description":"The name of the GPU resource to attach.","type":"string"}}}},"instanceProfile":{"description":"Virtual Machine preferences profile.","type":"string","default":"ubuntu","enum":["alpine","centos.7","centos.7.desktop","centos.stream10","centos.stream10.desktop","centos.stream8","centos.stream8.desktop","centos.stream8.dpdk","centos.stream9","centos.stream9.desktop","centos.stream9.dpdk","cirros","fedora","fedora.arm64","opensuse.leap","opensuse.tumbleweed","rhel.10","rhel.10.arm64","rhel.7","rhel.7.desktop","rhel.8","rhel.8.desktop","rhel.8.dpdk","rhel.9","rhel.9.arm64","rhel.9.desktop","rhel.9.dpdk","rhel.9.realtime","sles","ubuntu","windows.10","windows.10.virtio","windows.11","windows.11.virtio","windows.2k16","windows.2k16.virtio","windows.2k19","windows.2k19.virtio","windows.2k22","windows.2k22.virtio","windows.2k25","windows.2k25.virtio",""]},"instanceType":{"description":"Virtual Machine instance type.","type":"string","default":"u1.medium"},"resources":{"description":"Resource configuration for the virtual machine.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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},"sockets":{"description":"Number of CPU sockets (vCPU topology).","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}}},"runStrategy":{"description":"Requested running state of the VirtualMachineInstance","type":"string","default":"Always","enum":["Always","Halted","Manual","RerunOnFailure","Once"]},"sshKeys":{"description":"List of SSH public keys for authentication.","type":"array","default":[],"items":{"type":"string"}},"subnets":{"description":"Additional subnets","type":"array","default":[],"items":{"type":"object","properties":{"name":{"description":"Subnet name","type":"string"}}}},"systemDisk":{"description":"System disk configuration.","type":"object","default":{},"required":["image","storage"],"properties":{"image":{"description":"The base image for the virtual machine.","type":"string","default":"ubuntu","enum":["ubuntu","cirros","alpine","fedora","talos"]},"storage":{"description":"The size of the disk allocated for the virtual machine.","type":"string","default":"5Gi"},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":"replicated"}}}}} - release: - prefix: virtual-machine- - labels: - sharding.fluxcd.io/key: tenants - chartRef: - kind: ExternalArtifact - name: cozystack-virtual-machine-application-kubevirt-virtual-machine - namespace: cozy-system - dashboard: - category: IaaS - singular: Virtual Machine - plural: Virtual Machines - description: Virtual Machine (simple) - weight: 10 - tags: - - compute - icon: PHN2ZyB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgdmlld0JveD0iMCAwIDE0NCAxNDQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNDQiIGhlaWdodD0iMTQ0IiByeD0iMjQiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcl82ODdfMzQ1NCkiLz4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzY4N18zNDU0KSI+CjxwYXRoIGQ9Ik04OS41MDM5IDExMS43MDdINTQuNDk3QzU0LjE3MjcgMTExLjcwNyA1NC4wMTA4IDExMS4yMjEgNTQuMzM0OSAxMTEuMDU5TDU3LjI1MjIgMTA4Ljk1MkM2MC4zMzE0IDEwNi42ODMgNjEuOTUyMiAxMDIuNjMxIDYwLjk3OTcgOTguNzQxMkg4My4wMjFDODIuMDQ4NSAxMDIuNjMxIDgzLjY2OTMgMTA2LjY4MyA4Ni43NDg1IDEwOC45NTJMODkuNjY1OCAxMTEuMDU5Qzg5Ljk5IDExMS4yMjEgODkuODI3OSAxMTEuNzA3IDg5LjUwMzkgMTExLjcwN1oiIGZpbGw9IiNCMEI2QkIiLz4KPHBhdGggZD0iTTExMy4zMjggOTguNzQxSDMwLjY3MjVDMjcuNTkzMSA5OC43NDEgMjUgOTYuMTQ4IDI1IDkzLjA2ODdWMzMuMTAzMkMyNSAzMC4wMjM5IDI3LjU5MzEgMjcuNDMwNyAzMC42NzI1IDI3LjQzMDdIMTEzLjMyOEMxMTYuNDA3IDI3LjQzMDcgMTE5IDMwLjAyMzcgMTE5IDMzLjEwMzJWOTMuMDY4N0MxMTkgOTYuMTQ4IDExNi40MDcgOTguNzQxIDExMy4zMjggOTguNzQxWiIgZmlsbD0iI0U4RURFRSIvPgo8cGF0aCBkPSJNMTE5IDg0LjE1NDlIMjVWMzMuMTAzMkMyNSAzMC4wMjM5IDI3LjU5MzEgMjcuNDMwNyAzMC42NzI1IDI3LjQzMDdIMTEzLjMyOEMxMTYuNDA3IDI3LjQzMDcgMTE5IDMwLjAyMzcgMTE5IDMzLjEwMzJMMTE5IDg0LjE1NDlaIiBmaWxsPSIjMDBCM0ZGIi8+CjxwYXRoIGQ9Ik05MC42Mzc0IDExNi41NjlINTMuMzYxNkM1Mi4wNjUxIDExNi41NjkgNTAuOTMwNyAxMTUuNDM1IDUwLjkzMDcgMTE0LjEzOEM1MC45MzA3IDExMi44NDEgNTIuMDY1MSAxMTEuNzA3IDUzLjM2MTYgMTExLjcwN0g5MC42Mzc0QzkxLjkzMzkgMTExLjcwNyA5My4wNjg0IDExMi44NDEgOTMuMDY4NCAxMTQuMTM4QzkzLjA2ODQgMTE1LjQzNSA5MS45MzM5IDExNi41NjkgOTAuNjM3NCAxMTYuNTY5WiIgZmlsbD0iI0U4RURFRSIvPgo8L2c+CjxwYXRoIGQ9Ik03Mi41Mjc1IDUzLjgzNjdDNzIuNDQzMSA1My44MzUxIDcyLjM2MDUgNTMuODEyMiA3Mi4yODczIDUzLjc3MDFMNTYuNDY5OSA0NC43OTM0QzU2LjM5ODMgNDQuNzUxOSA1Ni4zMzg4IDQ0LjY5MjMgNTYuMjk3MyA0NC42MjA3QzU2LjI1NTkgNDQuNTQ5IDU2LjIzMzggNDQuNDY3OCA1Ni4yMzM0IDQ0LjM4NUM1Ni4yMzM0IDQ0LjIxNjkgNTYuMzI1OCA0NC4wNjE3IDU2LjQ2OTkgNDMuOTc4NUw3Mi4xOTEyIDM1LjA2MDlDNzIuMjYzNyAzNS4wMjEgNzIuMzQ1IDM1IDcyLjQyNzcgMzVDNzIuNTEwNSAzNSA3Mi41OTE4IDM1LjAyMSA3Mi42NjQzIDM1LjA2MDlMODguNDg3MiA0NC4wMzk1Qzg4LjU1OTEgNDQuMDgwMSA4OC42MTg4IDQ0LjEzOTIgODguNjYgNDQuMjEwN0M4OC43MDEzIDQ0LjI4MjIgODguNzIyNyA0NC4zNjM1IDg4LjcyMTkgNDQuNDQ2Qzg4LjcyMjUgNDQuNTI4NSA4OC43MDEgNDQuNjA5NyA4OC42NTk4IDQ0LjY4MTJDODguNjE4NSA0NC43NTI2IDg4LjU1ODkgNDQuODExOCA4OC40ODcyIDQ0Ljg1MjVMNzIuNzcxNCA1My43NjgzQzcyLjY5NzIgNTMuODExNCA3Mi42MTMzIDUzLjgzNDkgNzIuNTI3NSA1My44MzY3IiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBvcGFjaXR5PSIwLjciIGQ9Ik03MC4yNTUzIDc1LjY1MTdDNzAuMTcxIDc1LjY1MzUgNzAuMDg3OCA3NS42MzE3IDcwLjAxNTEgNzUuNTg4OEw1NC4yNDU4IDY2LjY0MTdDNTQuMTcxNSA2Ni42MDI0IDU0LjEwOTUgNjYuNTQzNiA1NC4wNjYxIDY2LjQ3MTZDNTQuMDIyOCA2Ni4zOTk3IDU0IDY2LjMxNzMgNTQgNjYuMjMzM1Y0OC4yNzhDNTQgNDguMTA4IDU0LjA5MjQgNDcuOTU0NiA1NC4yNDM5IDQ3Ljg2OTZDNTQuMzE3MiA0Ny44MjcxIDU0LjQwMDQgNDcuODA0NyA1NC40ODUxIDQ3LjgwNDdDNTQuNTY5NyA0Ny44MDQ3IDU0LjY1MjkgNDcuODI3MSA1NC43MjYyIDQ3Ljg2OTZMNzAuNDkzNyA1Ni44MTMxQzcwLjU2NDIgNTYuODU2NSA3MC42MjI1IDU2LjkxNyA3MC42NjMyIDU2Ljk4OTFDNzAuNzAzOSA1Ny4wNjEyIDcwLjcyNTcgNTcuMTQyNCA3MC43MjY1IDU3LjIyNTFWNzUuMTgwNUM3MC43MjU5IDc1LjI2MjggNzAuNzA0MiA3NS4zNDM2IDcwLjY2MzUgNzUuNDE1MUM3MC42MjI3IDc1LjQ4NjYgNzAuNTY0MiA3NS41NDY0IDcwLjQ5MzcgNzUuNTg4OEM3MC40MjA2IDc1LjYyOTEgNzAuMzM4NyA3NS42NTA3IDcwLjI1NTMgNzUuNjUxNyIgZmlsbD0id2hpdGUiLz4KPHBhdGggb3BhY2l0eT0iMC40IiBkPSJNNzQuNzE5OCA3NS42NTExQzc0LjYzMzMgNzUuNjUxMiA3NC41NDgyIDc1LjYyOTYgNzQuNDcyMiA3NS41ODgzQzc0LjQwMTYgNzUuNTQ2MSA3NC4zNDMyIDc1LjQ4NjIgNzQuMzAyNyA3NS40MTQ3Qzc0LjI2MjMgNzUuMzQzMSA3NC4yNDExIDc1LjI2MjIgNzQuMjQxMiA3NS4xOFY1Ny4zMzczQzc0LjI0MTIgNTcuMTcxIDc0LjMzMzYgNTcuMDE1OCA3NC40NzIyIDU2LjkyOUw5MC4yMzk3IDQ3Ljk4NTVDOTAuMzExOSA0Ny45NDM4IDkwLjM5MzggNDcuOTIxOSA5MC40NzcxIDQ3LjkyMTlDOTAuNTYwNSA0Ny45MjE5IDkwLjY0MjQgNDcuOTQzOCA5MC43MTQ2IDQ3Ljk4NTVDOTAuNzg3NiA0OC4wMjU1IDkwLjg0ODUgNDguMDg0MiA5MC44OTExIDQ4LjE1NTdDOTAuOTMzNyA0OC4yMjcyIDkwLjk1NjMgNDguMzA4OCA5MC45NTY2IDQ4LjM5MlY2Ni4yMzI4QzkwLjk1NyA2Ni4zMTY0IDkwLjkzNDcgNjYuMzk4NSA5MC44OTIxIDY2LjQ3MDRDOTAuODQ5NSA2Ni41NDI0IDkwLjc4ODEgNjYuNjAxNCA5MC43MTQ2IDY2LjY0MTFMNzQuOTUyNiA3NS41ODgzQzc0Ljg4MjUgNzUuNjMwNyA3NC44MDE4IDc1LjY1MjUgNzQuNzE5OCA3NS42NTExIiBmaWxsPSJ3aGl0ZSIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyXzY4N18zNDU0IiB4MT0iMTYxIiB5MT0iMTgwIiB4Mj0iMy41OTI4NGUtMDciIHkyPSI0Ljk5OTk4IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNTk1NjU2Ii8+CjwvbGluZWFyR3JhZGllbnQ+CjxjbGlwUGF0aCBpZD0iY2xpcDBfNjg3XzM0NTQiPgo8cmVjdCB3aWR0aD0iOTQiIGhlaWdodD0iOTQiIGZpbGw9IndoaXRlIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyNSAyNSkiLz4KPC9jbGlwUGF0aD4KPC9kZWZzPgo8L3N2Zz4K - keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "external"], ["spec", "externalMethod"], ["spec", "externalPorts"], ["spec", "runStrategy"], ["spec", "instanceType"], ["spec", "instanceProfile"], ["spec", "systemDisk"], ["spec", "systemDisk", "image"], ["spec", "systemDisk", "storage"], ["spec", "systemDisk", "storageClass"], ["spec", "subnets"], ["spec", "gpus"], ["spec", "cpuModel"], ["spec", "resources"], ["spec", "sshKeys"], ["spec", "cloudInit"], ["spec", "cloudInitSeed"]] - secrets: - exclude: [] - include: [] - services: - exclude: [] - include: - - resourceNames: - - virtual-machine-{{ .name }} diff --git a/packages/system/virtual-machine-rd/templates/backupstrategy.yaml b/packages/system/virtual-machine-rd/templates/backupstrategy.yaml deleted file mode 100644 index ac2fe517..00000000 --- a/packages/system/virtual-machine-rd/templates/backupstrategy.yaml +++ /dev/null @@ -1,35 +0,0 @@ -{{- if .Capabilities.APIVersions.Has "strategy.backups.cozystack.io/v1alpha1" }} -apiVersion: strategy.backups.cozystack.io/v1alpha1 -kind: Velero -metadata: - name: velero-backup-strategy-for-virtualmachines -spec: - template: - spec: - csiSnapshotTimeout: 10m0s - includedNamespaces: - - '{{ printf "{{ .Application.metadata.namespace }}" }}' - orLabelSelector: - - matchLabels: - apps.cozystack.io/application.group: apps.cozystack.io - apps.cozystack.io/application.kind: '{{ printf "{{ .Application.kind }}" }}' - apps.cozystack.io/application.name: '{{ printf "{{ .Application.metadata.name }}"}}' - - matchLabels: - app.kubernetes.io/instance: 'virtual-machine-{{ printf "{{ .Application.metadata.name }}"}}' - includedResources: - - helmreleases.helm.toolkit.fluxcd.io - - virtualmachines.kubevirt.io - - virtualmachineinstances.kubevirt.io - - datavolumes.cdi.kubevirt.io - - persistentvolumeclaims - - services - - configmaps - - secrets - storageLocation: '{{ printf "{{ .Parameters.backupStorageLocationName }}" }}' - volumeSnapshotLocations: - - '{{ printf "{{ .Parameters.backupStorageLocationName }}" }}' - snapshotVolumes: true - snapshotMoveData: true - ttl: 720h0m0s - itemOperationTimeout: 24h0m0s -{{- end }} diff --git a/packages/system/virtual-machine-rd/templates/cozyrd.yaml b/packages/system/virtual-machine-rd/templates/cozyrd.yaml deleted file mode 100644 index e079ddcf..00000000 --- a/packages/system/virtual-machine-rd/templates/cozyrd.yaml +++ /dev/null @@ -1,4 +0,0 @@ -{{- range $path, $_ := .Files.Glob "cozyrds/*" }} ---- -{{ $.Files.Get $path }} -{{- end }} diff --git a/packages/system/virtual-machine-rd/values.yaml b/packages/system/virtual-machine-rd/values.yaml deleted file mode 100644 index 0967ef42..00000000 --- a/packages/system/virtual-machine-rd/values.yaml +++ /dev/null @@ -1 +0,0 @@ -{} From 13aa341a28403bab0a3e8ee0a8665d17f0f91554 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Thu, 12 Feb 2026 15:26:18 +0100 Subject: [PATCH 263/889] fix(platform): address review comments in vm migration script Replace `|| echo ""` with `|| true` to avoid newline bugs in variable assignments. Switch `for x in $(cmd)` loops to `while read` for safer iteration over kubectl output. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- hack/e2e-apps/virtualmachine.bats | 45 ------------------- .../platform/images/migrations/migrations/29 | 18 ++++---- 2 files changed, 10 insertions(+), 53 deletions(-) delete mode 100644 hack/e2e-apps/virtualmachine.bats diff --git a/hack/e2e-apps/virtualmachine.bats b/hack/e2e-apps/virtualmachine.bats deleted file mode 100644 index 33e87b53..00000000 --- a/hack/e2e-apps/virtualmachine.bats +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bats - -@test "Create a Virtual Machine" { - name='test' - kubectl apply -f - </dev/null | base64 -d 2>/dev/null || echo "") + VALUES_YAML=$(kubectl -n "$NAMESPACE" get secret "$VALUES_SECRET" -o jsonpath="{.data.${VALUES_KEY}}" 2>/dev/null | base64 -d 2>/dev/null || true) if [ -z "$VALUES_YAML" ]; then - VALUES_YAML=$(kubectl -n "$NAMESPACE" get secret "$VALUES_SECRET" -o jsonpath="{.stringData.${VALUES_KEY}}" 2>/dev/null || echo "") + VALUES_YAML=$(kubectl -n "$NAMESPACE" get secret "$VALUES_SECRET" -o jsonpath="{.stringData.${VALUES_KEY}}" 2>/dev/null || true) fi if [ -n "$VALUES_YAML" ]; then EXTERNAL=$(echo "$VALUES_YAML" | yq -r '.external // false') @@ -194,7 +194,7 @@ for entry in "${INSTANCES[@]}"; do echo " --- Save LoadBalancer IP ---" LB_IP="" if [ "$EXTERNAL" = "true" ] && resource_exists "$NAMESPACE" "svc" "$OLD_NAME"; then - LB_IP=$(kubectl -n "$NAMESPACE" get svc "$OLD_NAME" -o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>/dev/null || echo "") + LB_IP=$(kubectl -n "$NAMESPACE" get svc "$OLD_NAME" -o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>/dev/null || true) if [ -n "$LB_IP" ]; then echo " LoadBalancer IP: ${LB_IP}" else @@ -314,8 +314,9 @@ PVCEOF # --- 2i: Clone Secrets --- echo " --- Clone Secrets ---" - for secret in $(kubectl -n "$NAMESPACE" get secret -o name 2>/dev/null \ - | grep "secret/${OLD_NAME}" | grep -v "sh.helm.release" | grep -v "values"); do + kubectl -n "$NAMESPACE" get secret -o name 2>/dev/null \ + | grep "secret/${OLD_NAME}" | grep -v "sh.helm.release" | grep -v "values" \ + | while IFS= read -r secret; do old_secret_name="${secret#secret/}" suffix="${old_secret_name#${OLD_NAME}}" new_secret_name="${NEW_INSTANCE_NAME}${suffix}" @@ -540,8 +541,9 @@ SVCEOF # --- 2q: Delete old resources --- echo " --- Delete old resources ---" - for secret in $(kubectl -n "$NAMESPACE" get secret -o name 2>/dev/null \ - | grep "secret/${OLD_NAME}" | grep -v "sh.helm.release" | grep -v "values"); do + kubectl -n "$NAMESPACE" get secret -o name 2>/dev/null \ + | grep "secret/${OLD_NAME}" | grep -v "sh.helm.release" | grep -v "values" \ + | while IFS= read -r secret; do old_secret_name="${secret#secret/}" delete_resource "$NAMESPACE" "secret" "$old_secret_name" done @@ -714,7 +716,7 @@ for entry in "${INSTANCES[@]}"; do instance="${entry#*/}" disk_name="${NEW_DISK_PREFIX}-${instance}" for i in $(seq 1 60); do - ready=$(kubectl -n "$ns" get hr "$disk_name" -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || echo "") + ready=$(kubectl -n "$ns" get hr "$disk_name" -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || true) if [ "$ready" = "True" ]; then echo " [OK] ${ns}/hr/${disk_name} is Ready" break From dbba5c325b662f8aa2821f1984f1c251243d9b68 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Sat, 14 Feb 2026 10:13:09 +0100 Subject: [PATCH 264/889] fix kubernetes e2e test Signed-off-by: Andrei Kvapil --- hack/e2e-apps/run-kubernetes.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/e2e-apps/run-kubernetes.sh b/hack/e2e-apps/run-kubernetes.sh index 5b8b1b4b..c60fdbe4 100644 --- a/hack/e2e-apps/run-kubernetes.sh +++ b/hack/e2e-apps/run-kubernetes.sh @@ -56,7 +56,7 @@ spec: gpus: [] instanceType: u1.medium maxReplicas: 10 - minReplicas: 0 + minReplicas: 2 roles: - ingress-nginx storageClass: replicated From b6a840e8735ee2b4f039bd4d263149e8ca92fcf0 Mon Sep 17 00:00:00 2001 From: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com> Date: Sat, 14 Feb 2026 09:17:24 +0000 Subject: [PATCH 265/889] Prepare release v1.0.0-beta.4 Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com> --- packages/apps/kubernetes/images/cluster-autoscaler.tag | 2 +- packages/apps/kubernetes/images/kubevirt-csi-driver.tag | 2 +- packages/core/installer/values.yaml | 4 ++-- packages/core/platform/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/backup-controller/values.yaml | 2 +- packages/system/backupstrategy-controller/values.yaml | 2 +- packages/system/cozystack-api/values.yaml | 2 +- packages/system/cozystack-controller/values.yaml | 2 +- packages/system/dashboard/templates/configmap.yaml | 2 +- packages/system/dashboard/values.yaml | 6 +++--- .../system/grafana-operator/images/grafana-dashboards.tag | 2 +- packages/system/kamaji/values.yaml | 4 ++-- packages/system/kubeovn-plunger/values.yaml | 2 +- packages/system/kubeovn-webhook/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 +- 21 files changed, 25 insertions(+), 25 deletions(-) diff --git a/packages/apps/kubernetes/images/cluster-autoscaler.tag b/packages/apps/kubernetes/images/cluster-autoscaler.tag index e3436c68..c3d6ddef 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:598331326f0c2aac420187f0cc3a49fedcb22ed5de4afe50c6ccf8e05d9fa537 +ghcr.io/cozystack/cozystack/cluster-autoscaler:0.0.0@sha256:7deeee117e7eec599cb453836ca95eadd131dfc8c875dc457ef29dc1433395e0 diff --git a/packages/apps/kubernetes/images/kubevirt-csi-driver.tag b/packages/apps/kubernetes/images/kubevirt-csi-driver.tag index ca67ba13..a8e48958 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:latest@sha256:cd58760c97ba50ef74ff940cdcda4b9a6d8554eec8305fc96c2e8bbea72b75a9 +ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:1997d623bb60a5b540027a4e0716e7ca84f668ee09f31290e9c43324f0003137 diff --git a/packages/core/installer/values.yaml b/packages/core/installer/values.yaml index e9de3f13..64ff817b 100644 --- a/packages/core/installer/values.yaml +++ b/packages/core/installer/values.yaml @@ -1,6 +1,6 @@ cozystackOperator: # Deployment variant: talos, generic, hosted variant: talos - image: ghcr.io/cozystack/cozystack/cozystack-operator:v1.0.0-beta.3@sha256:3cacecfc318e8314300bba8538b475fb8d50b2bf3106533faa8d942e4ed14448 + image: ghcr.io/cozystack/cozystack/cozystack-operator:v1.0.0-beta.4@sha256:322dd7358df369525f76e6e43512482e38caec5315d36a878399d2d60bf2f18d platformSourceUrl: 'oci://ghcr.io/cozystack/cozystack/cozystack-packages' - platformSourceRef: 'digest=sha256:e5ddcf5a02d17b17fc7ffa8c87ddeaa25a01d928a63fb297f6b25b669bb6b7c7' + platformSourceRef: 'digest=sha256:b88502242b535a31ab33c06ffc0a96d1c67230d2db2c5e873fa23f6523592ff6' diff --git a/packages/core/platform/values.yaml b/packages/core/platform/values.yaml index 38452a17..b90c001b 100644 --- a/packages/core/platform/values.yaml +++ b/packages/core/platform/values.yaml @@ -5,7 +5,7 @@ sourceRef: path: / migrations: enabled: false - image: ghcr.io/cozystack/cozystack/platform-migrations:latest + image: ghcr.io/cozystack/cozystack/platform-migrations:v1.0.0-beta.4@sha256:f404d05834907b9b2695bbb37732bd1ae2e79b03da77dc91bb3fbc0fbc53dcbf targetVersion: 30 # Bundle deployment configuration bundles: diff --git a/packages/core/testing/values.yaml b/packages/core/testing/values.yaml index 26ac2e55..73481ab1 100644 --- a/packages/core/testing/values.yaml +++ b/packages/core/testing/values.yaml @@ -1,2 +1,2 @@ e2e: - image: ghcr.io/cozystack/cozystack/e2e-sandbox:v1.0.0-beta.3@sha256:eac71ef0de3450fce96255629e77903630c63ade62b81e7055f1a689f92ee153 + image: ghcr.io/cozystack/cozystack/e2e-sandbox:v1.0.0-beta.4@sha256:eac71ef0de3450fce96255629e77903630c63ade62b81e7055f1a689f92ee153 diff --git a/packages/extra/bootbox/images/matchbox.tag b/packages/extra/bootbox/images/matchbox.tag index 133eda2a..6c3054d5 100644 --- a/packages/extra/bootbox/images/matchbox.tag +++ b/packages/extra/bootbox/images/matchbox.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/matchbox:v1.0.0-beta.3@sha256:185010911694809fe15491efe3c7994e0ce46a4f92ef1f7edbbd0b318861e8d4 +ghcr.io/cozystack/cozystack/matchbox:v1.0.0-beta.4@sha256:cba9a2761c40ae6645e28da4cbc6d91eb86ddc886f4523cf20b3c0e40597d593 diff --git a/packages/extra/seaweedfs/images/objectstorage-sidecar.tag b/packages/extra/seaweedfs/images/objectstorage-sidecar.tag index 71eb27b8..bd622108 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:v1.0.0-beta.3@sha256:f845a2c3ec0f79a8873e9f3e65ee625ec3574c5e33e174a7bf60b15d1db49979 +ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.0.0-beta.4@sha256:235b194a531b70e266a10ef78d2955d19f5b659513f23d8b3cfbbc0dff7fc1c0 diff --git a/packages/system/backup-controller/values.yaml b/packages/system/backup-controller/values.yaml index 3e91ea6e..1bdab88b 100644 --- a/packages/system/backup-controller/values.yaml +++ b/packages/system/backup-controller/values.yaml @@ -1,5 +1,5 @@ backupController: - image: "ghcr.io/cozystack/cozystack/backup-controller:v1.0.0-beta.3@sha256:7a8d0ea7b380196fac418e0697b4218af15025eac8128dfedfd12eb933bd258d" + image: "ghcr.io/cozystack/cozystack/backup-controller:v1.0.0-beta.4@sha256:2dcd5347683ee88012b0e558b3a240dec9942230e9c673a359e0196f407a0833" replicas: 2 debug: false metrics: diff --git a/packages/system/backupstrategy-controller/values.yaml b/packages/system/backupstrategy-controller/values.yaml index 94cc6bed..21d1a9f3 100644 --- a/packages/system/backupstrategy-controller/values.yaml +++ b/packages/system/backupstrategy-controller/values.yaml @@ -1,5 +1,5 @@ backupStrategyController: - image: "ghcr.io/cozystack/cozystack/backupstrategy-controller:v1.0.0-beta.3@sha256:ae8fb6c7bf274a3812a6a9f3f87317fa4575250f7ee75e118d20d5ab0797577b" + image: "ghcr.io/cozystack/cozystack/backupstrategy-controller:v1.0.0-beta.4@sha256:c2c150918e5609f1d6663f56b6dcbdac47bee33154524230001fcd04165f4268" replicas: 2 debug: false metrics: diff --git a/packages/system/cozystack-api/values.yaml b/packages/system/cozystack-api/values.yaml index 769cd4b6..d9041546 100644 --- a/packages/system/cozystack-api/values.yaml +++ b/packages/system/cozystack-api/values.yaml @@ -1,3 +1,3 @@ cozystackAPI: - image: ghcr.io/cozystack/cozystack/cozystack-api:v1.0.0-beta.3@sha256:a263702859c36f85d097c300c1be462aaa45a3955d69e9ae72a37cabf6dadaa9 + image: ghcr.io/cozystack/cozystack/cozystack-api:v1.0.0-beta.4@sha256:42f8d8120f7fd3bfe37f114eedf5ca8df62ac69a0d922d91a2c392772f3b46ee replicas: 2 diff --git a/packages/system/cozystack-controller/values.yaml b/packages/system/cozystack-controller/values.yaml index 908a955d..53ed1033 100644 --- a/packages/system/cozystack-controller/values.yaml +++ b/packages/system/cozystack-controller/values.yaml @@ -1,4 +1,4 @@ cozystackController: - image: ghcr.io/cozystack/cozystack/cozystack-controller:v1.0.0-beta.3@sha256:00b0ba15fef7c3ce8c0801ecb11b1953665a511990e18f51c2ca3ca40127c7aa + image: ghcr.io/cozystack/cozystack/cozystack-controller:v1.0.0-beta.4@sha256:915d07ef61e1fc3bdf87e4bfc4b8ae3920e7e33d74082778c7735ba36a08cdef debug: false disableTelemetry: false diff --git a/packages/system/dashboard/templates/configmap.yaml b/packages/system/dashboard/templates/configmap.yaml index 03cd572b..1c79b2cb 100644 --- a/packages/system/dashboard/templates/configmap.yaml +++ b/packages/system/dashboard/templates/configmap.yaml @@ -1,6 +1,6 @@ {{- $brandingConfig := .Values._cluster.branding | default dict }} -{{- $tenantText := "v1.0.0-beta.3" }} +{{- $tenantText := "v1.0.0-beta.4" }} {{- $footerText := "Cozystack" }} {{- $titleText := "Cozystack Dashboard" }} {{- $logoText := "" }} diff --git a/packages/system/dashboard/values.yaml b/packages/system/dashboard/values.yaml index 95d7bdf8..0c898a91 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:v1.0.0-beta.3@sha256:adbd07c7bde083fbf3a2fb2625ec4adbe6718a0f6f643b2505cc0029c2c0f724 + image: ghcr.io/cozystack/cozystack/openapi-ui:v1.0.0-beta.4@sha256:adbd07c7bde083fbf3a2fb2625ec4adbe6718a0f6f643b2505cc0029c2c0f724 openapiUIK8sBff: - image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v1.0.0-beta.3@sha256:0ed112d44973dcff64b65f7f1438a35df98c7fdee4590343a790668f728d9024 + image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v1.0.0-beta.4@sha256:1f7827a1978bd9c81ac924dd0e78f6a3ce834a9a64af55047e220812bc15a944 tokenProxy: - image: ghcr.io/cozystack/cozystack/token-proxy:v1.0.0-beta.3@sha256:2e280991e07853ea48f97b0a42946afffa10d03d6a83d41099ed83e6ffc94fdc + image: ghcr.io/cozystack/cozystack/token-proxy:v1.0.0-beta.4@sha256:2e280991e07853ea48f97b0a42946afffa10d03d6a83d41099ed83e6ffc94fdc diff --git a/packages/system/grafana-operator/images/grafana-dashboards.tag b/packages/system/grafana-operator/images/grafana-dashboards.tag index 726d78e9..cc81b24f 100644 --- a/packages/system/grafana-operator/images/grafana-dashboards.tag +++ b/packages/system/grafana-operator/images/grafana-dashboards.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/grafana-dashboards:v1.0.0-beta.3@sha256:e866b5b3874b9d390b341183d2ee070e1387440c14cfe51af831695def6dc2ec +ghcr.io/cozystack/cozystack/grafana-dashboards:v1.0.0-beta.4@sha256:e866b5b3874b9d390b341183d2ee070e1387440c14cfe51af831695def6dc2ec diff --git a/packages/system/kamaji/values.yaml b/packages/system/kamaji/values.yaml index bd7dfe63..26e8bfcf 100644 --- a/packages/system/kamaji/values.yaml +++ b/packages/system/kamaji/values.yaml @@ -3,7 +3,7 @@ kamaji: deploy: false image: pullPolicy: IfNotPresent - tag: v1.0.0-beta.3@sha256:fe9b6bb548edfc26be8aaac65801d598a4e2f9884ddf748083b9e509fa00259e + tag: v1.0.0-beta.4@sha256:05f8e166dc94aadb74cd6448f6f96fbd2167057a559097516a41b8f53e434918 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:v1.0.0-beta.3@sha256:fe9b6bb548edfc26be8aaac65801d598a4e2f9884ddf748083b9e509fa00259e + - --migrate-image=ghcr.io/cozystack/cozystack/kamaji:v1.0.0-beta.4@sha256:05f8e166dc94aadb74cd6448f6f96fbd2167057a559097516a41b8f53e434918 diff --git a/packages/system/kubeovn-plunger/values.yaml b/packages/system/kubeovn-plunger/values.yaml index b86cc2dc..b325a7b7 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:v1.0.0-beta.3@sha256:c89397d6a74e6de38830513a323ade3fa99a42946b40ba596136fc08930b2348 +image: ghcr.io/cozystack/cozystack/kubeovn-plunger:v1.0.0-beta.4@sha256:16b362d6fa1ca30c791ea5cfc7984e085b9ea76a24c9abb3b05dad5c8b64bd0e ovnCentralName: ovn-central diff --git a/packages/system/kubeovn-webhook/values.yaml b/packages/system/kubeovn-webhook/values.yaml index 65898067..d5d8ba93 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:v1.0.0-beta.3@sha256:e18f9fd679e38f65362a8d0042f25468272f6d081136ad47027168d8e7e07a4a +image: ghcr.io/cozystack/cozystack/kubeovn-webhook:v1.0.0-beta.4@sha256:e18f9fd679e38f65362a8d0042f25468272f6d081136ad47027168d8e7e07a4a diff --git a/packages/system/kubevirt-csi-node/values.yaml b/packages/system/kubevirt-csi-node/values.yaml index f7fd69a1..521683be 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:latest@sha256:cd58760c97ba50ef74ff940cdcda4b9a6d8554eec8305fc96c2e8bbea72b75a9 + image: ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:1997d623bb60a5b540027a4e0716e7ca84f668ee09f31290e9c43324f0003137 diff --git a/packages/system/lineage-controller-webhook/values.yaml b/packages/system/lineage-controller-webhook/values.yaml index 10ac0be5..00e593eb 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:v1.0.0-beta.3@sha256:da85b064cdef4c8a798f32a731396a9b90741654dc599a5f4f381372834765db + image: ghcr.io/cozystack/cozystack/lineage-controller-webhook:v1.0.0-beta.4@sha256:e9054f9137fd73039b2d91a979f672c9258336f91e51440a77aaa09e6e0b5254 debug: false localK8sAPIEndpoint: enabled: true diff --git a/packages/system/objectstorage-controller/values.yaml b/packages/system/objectstorage-controller/values.yaml index d5fe5bec..18dda28f 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:v1.0.0-beta.3@sha256:bb2b2b95cbc3d613b077a87a6c281a3ceff8ef8655d770fb2f8fd6b5f1d0c588" + image: "ghcr.io/cozystack/cozystack/objectstorage-controller:v1.0.0-beta.4@sha256:b4c972769afda76c48b58e7acf0ac66a0abf16a622f245c60338f432872f640a" diff --git a/packages/system/seaweedfs/values.yaml b/packages/system/seaweedfs/values.yaml index cc5e8301..63199e28 100644 --- a/packages/system/seaweedfs/values.yaml +++ b/packages/system/seaweedfs/values.yaml @@ -177,7 +177,7 @@ seaweedfs: bucketClassName: "seaweedfs" region: "" sidecar: - image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.0.0-beta.3@sha256:f845a2c3ec0f79a8873e9f3e65ee625ec3574c5e33e174a7bf60b15d1db49979" + image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.0.0-beta.4@sha256:235b194a531b70e266a10ef78d2955d19f5b659513f23d8b3cfbbc0dff7fc1c0" certificates: commonName: "SeaweedFS CA" ipAddresses: [] From 6b407bd4039fd2e98805518267256761fd6e3b27 Mon Sep 17 00:00:00 2001 From: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com> Date: Sat, 14 Feb 2026 09:23:50 +0000 Subject: [PATCH 266/889] docs: add changelog for v1.0.0-beta.4 Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com> --- docs/changelogs/v1.0.0-beta.4.md | 96 ++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 docs/changelogs/v1.0.0-beta.4.md diff --git a/docs/changelogs/v1.0.0-beta.4.md b/docs/changelogs/v1.0.0-beta.4.md new file mode 100644 index 00000000..c5694a73 --- /dev/null +++ b/docs/changelogs/v1.0.0-beta.4.md @@ -0,0 +1,96 @@ + + +> **⚠️ Beta Release Warning**: This is a pre-release version intended for testing and early adoption. Breaking changes may occur before the stable v1.0.0 release. + +## Major Features and Improvements + +### Virtual Machines + +* **[vm-instance] Complete migration from virtual-machine to vm-disk and vm-instance**: Completed the architectural redesign of virtual machine management by fully migrating from the legacy `virtual-machine` application to the new `vm-disk` and `vm-instance` applications. This includes automatic migration scripts (migration 28) that convert existing virtual machines, handle CDI webhook configurations, and update cloud-init references. The new architecture provides better separation of concerns between disk management and VM lifecycle, enabling more flexible VM configuration and improved resource management ([**@kvaps**](https://github.com/kvaps) in #2040). + +* **[vm-instance] Port advanced VM features**: Ported critical VM features from the legacy virtual-machine application including cpuModel field for direct CPU model specification, support for switching between instanceType and custom resource configurations, and migration from deprecated `running` field to `runStrategy` field following KubeVirt best practices ([**@kvaps**](https://github.com/kvaps) in #2040). + +### Storage and CSI + +* **[kubevirt-csi-driver] Add RWX Filesystem (NFS) support**: Added Read-Write-Many (RWX) filesystem support to kubevirt-csi-driver, enabling multiple pods to mount the same persistent volume simultaneously via NFS. This provides native NFS support for shared storage use cases without requiring external NFS provisioners, with automatic NFS server deployment per PVC and seamless integration with KubeVirt's storage layer ([**@kvaps**](https://github.com/kvaps) in #2042). + +### Platform and Infrastructure + +* **[cozystack-api] Switch from DaemonSet to Deployment**: Migrated cozystack-api from DaemonSet to Deployment with PreferClose topology spread constraints, improving resource efficiency while maintaining high availability. The Deployment approach reduces resource consumption compared to running API pods on every node, while topology spreading ensures resilient pod placement across the cluster ([**@kvaps**](https://github.com/kvaps) in #2041, #2048). + +* **[linstor] Move CRDs installation to dedicated chart**: Refactored LINSTOR CRDs installation by moving them to a dedicated `piraeus-operator-crds` chart, solving Helm's limitation with large CRD files that could cause unreliable installations. This ensures all LINSTOR CRDs (including linstorsatellites.io) are properly installed before the operator starts, preventing satellite pod creation failures. Includes automatic migration script to reassign existing CRDs to the new chart ([**@kvaps**](https://github.com/kvaps) in #2036). + +* **[installer] Unify operator templates**: Merged separate operator templates into a single variant-based template, simplifying the installation process and reducing configuration duplication. The new template supports different deployment variants (Talos, non-Talos) through a unified configuration approach ([**@kvaps**](https://github.com/kvaps) in #2034). + +### Applications + +* **[mariadb] Rename mysql application to mariadb**: Renamed the MySQL application to MariaDB to accurately reflect the underlying database engine being used. Includes automatic migration script (migration 27) that handles resource renaming and ensures seamless upgrade path for existing MySQL deployments. All resources, including databases, users, backups, and configurations, are automatically migrated to use the mariadb naming ([**@kvaps**](https://github.com/kvaps) in #2026). + +* **[ferretdb] Remove FerretDB application**: Removed the FerretDB application from the catalog as it has been superseded by native MongoDB support with improved performance and features ([**@kvaps**](https://github.com/kvaps) in #2028). + +## Improvements + +* **[rbac] Use hierarchical naming scheme**: Refactored RBAC configuration to use hierarchical naming scheme for cluster roles and role bindings, improving organization and maintainability of permission structures across the platform ([**@lllamnyp**](https://github.com/lllamnyp) in #2019). + +* **[backups] Create RBAC for backup resources**: Added comprehensive RBAC configuration for backup resources, enabling proper permission management for backup operations and restore jobs across different user roles ([**@lllamnyp**](https://github.com/lllamnyp) in #2018). + +* **[etcd-operator] Add vertical-pod-autoscaler dependency**: Added vertical-pod-autoscaler as a dependency to etcd-operator package, ensuring proper resource scaling and optimization for etcd clusters ([**@sircthulhu**](https://github.com/sircthulhu) in #2047). + +## Fixes + +* **[cozystack-operator] Preserve existing suspend field in package reconciler**: Fixed package reconciler to properly preserve the existing suspend field state during reconciliation, preventing unintended resumption of suspended packages ([**@sircthulhu**](https://github.com/sircthulhu) in #2043). + +* **[cozystack-operator] Fix namespace privileged flag resolution**: Fixed operator to correctly resolve namespace privileged flag by checking all Packages in the namespace, not just the first one. This ensures namespaces are properly marked as privileged when any package requires elevated permissions ([**@kvaps**](https://github.com/kvaps) in #2046). + +* **[cozystack-operator] Fix namespace reconciliation field ownership**: Fixed Server-Side Apply (SSA) field ownership conflicts by using per-Package field owner for namespace reconciliation, preventing conflicts when multiple packages reconcile the same namespace ([**@kvaps**](https://github.com/kvaps) in #2046). + +* **[platform] Clean up Helm secrets for removed releases**: Added cleanup logic to migration 23 to remove orphaned Helm secrets from removed -rd releases, preventing secret accumulation and reducing cluster resource usage ([**@kvaps**](https://github.com/kvaps) in #2035). + +* **[monitoring] Fix YAML parse error in vmagent template**: Fixed YAML parsing error in monitoring-agents vmagent template that could cause monitoring stack deployment failures ([**@kvaps**](https://github.com/kvaps) in #2037). + +* **[talm] Fix metadata.id type casting in physical_links_info**: Fixed Prometheus query in physical_links_info chart to properly cast metadata.id to string for regexMatch operations, preventing query failures with numeric interface IDs ([**@kvaps**](https://github.com/kvaps) in cozystack/talm#110). + +## Dependencies + +* **[kilo] Update to v0.7.1**: Updated Kilo WireGuard mesh networking to v0.7.1 with bug fixes and improvements ([**@kvaps**](https://github.com/kvaps) in #2049). + +## Development, Testing, and CI/CD + +* **[ci] Improve cozyreport functionality**: Enhanced cozyreport tool with improved reporting capabilities for CI/CD pipelines, providing better visibility into test results and build status ([**@lllamnyp**](https://github.com/lllamnyp) in #2032). + +* **[e2e] Increase HelmRelease readiness timeout for kubernetes test**: Increased HelmRelease readiness timeout in Kubernetes end-to-end tests to prevent false failures on slower hardware or during high load conditions, specifically targeting ingress-nginx component which may take longer to become ready ([**@lexfrei**](https://github.com/lexfrei) in #2033). + +## Documentation + +* **[website] Add documentation versioning**: Implemented comprehensive documentation versioning system with separate v0 and v1 documentation trees, version selector in the UI, proper URL redirects for unversioned docs, and improved navigation for users working with different Cozystack versions ([**@IvanStukov**](https://github.com/IvanStukov) in cozystack/website#415). + +* **[website] Describe upgrade to v1.0**: Added detailed upgrade instructions for migrating from v0.x to v1.0, including prerequisites, upgrade steps, and troubleshooting guidance ([**@nbykov0**](https://github.com/nbykov0) in cozystack/website@21bbe84). + +* **[website] Update support documentation**: Updated support documentation with current contact information and support channels ([**@xrmtech-isk**](https://github.com/xrmtech-isk) in cozystack/website#420). + +--- + +## Contributors + +We'd like to thank all contributors who made this release possible: + +* [**@IvanStukov**](https://github.com/IvanStukov) +* [**@kvaps**](https://github.com/kvaps) +* [**@lexfrei**](https://github.com/lexfrei) +* [**@lllamnyp**](https://github.com/lllamnyp) +* [**@nbykov0**](https://github.com/nbykov0) +* [**@sircthulhu**](https://github.com/sircthulhu) +* [**@xrmtech-isk**](https://github.com/xrmtech-isk) + +### New Contributors + +We're excited to welcome our first-time contributors: + +* [**@IvanStukov**](https://github.com/IvanStukov) - First contribution! +* [**@xrmtech-isk**](https://github.com/xrmtech-isk) - First contribution! + +--- + +**Full Changelog**: [v1.0.0-beta.3...v1.0.0-beta.4](https://github.com/cozystack/cozystack/compare/v1.0.0-beta.3...v1.0.0-beta.4) From 2bc5e01fda74d418d0bb929dc4e356f62f10b168 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Sat, 14 Feb 2026 11:02:12 +0100 Subject: [PATCH 267/889] fix kubernetes e2e test for rwx volume Signed-off-by: Andrei Kvapil --- hack/e2e-apps/run-kubernetes.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/e2e-apps/run-kubernetes.sh b/hack/e2e-apps/run-kubernetes.sh index c60fdbe4..d0997ecb 100644 --- a/hack/e2e-apps/run-kubernetes.sh +++ b/hack/e2e-apps/run-kubernetes.sh @@ -260,7 +260,7 @@ spec: EOF # Wait for Pod to complete successfully - kubectl --kubeconfig tenantkubeconfig-${test_name} wait pod nfs-test-pod -n tenant-test --timeout=2m --for=jsonpath='{.status.phase}'=Succeeded + kubectl --kubeconfig tenantkubeconfig-${test_name} wait pod nfs-test-pod -n tenant-test --timeout=5m --for=jsonpath='{.status.phase}'=Succeeded # Verify NFS data integrity nfs_result=$(kubectl --kubeconfig tenantkubeconfig-${test_name} logs nfs-test-pod -n tenant-test) From b170a9f4f9d4f65ba677d22d0e14ae76bcdebf1a Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Sat, 14 Feb 2026 11:42:52 +0100 Subject: [PATCH 268/889] feat(cluster-autoscaler): enable enforce-node-group-min-size by default Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- packages/system/cluster-autoscaler/values.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/system/cluster-autoscaler/values.yaml b/packages/system/cluster-autoscaler/values.yaml index 85eacb2d..eaef53d9 100644 --- a/packages/system/cluster-autoscaler/values.yaml +++ b/packages/system/cluster-autoscaler/values.yaml @@ -1 +1,3 @@ -cluster-autoscaler: {} +cluster-autoscaler: + extraArgs: + enforce-node-group-min-size: true From fa55b5f41f84fc6ace9edc93648a9beb7c6f7bd2 Mon Sep 17 00:00:00 2001 From: Kirill Ilin Date: Sun, 15 Feb 2026 16:25:00 +0500 Subject: [PATCH 269/889] [dashboard] Upgrade dashboard to version 1.4.0 - Upgrade CRDs in CozyStack dashboard controller - Add Ingress proxy timeouts for WebSocket to work without terminations - Add CFOMapping custom resource Signed-off-by: Kirill Ilin --- api/dashboard/v1alpha1/dashboard_resources.go | 22 ++ api/dashboard/v1alpha1/groupversion_info.go | 3 + .../v1alpha1/zz_generated.deepcopy.go | 59 ++++++ internal/controller/dashboard/breadcrumb.go | 4 +- .../dashboard/customformsoverride.go | 47 +++++ internal/controller/dashboard/factory.go | 94 ++++----- internal/controller/dashboard/manager.go | 8 + .../controller/dashboard/marketplacepanel.go | 2 +- internal/controller/dashboard/navigation.go | 69 +++++++ internal/controller/dashboard/sidebar.go | 14 +- .../controller/dashboard/static_helpers.go | 2 +- .../controller/dashboard/static_processor.go | 2 + .../controller/dashboard/static_refactored.go | 191 ++++++++++++------ .../dashboard.cozystack.io_cfomappings.yaml | 118 +++++++++++ .../images/openapi-ui-k8s-bff/Dockerfile | 5 +- .../dashboard/images/openapi-ui/Dockerfile | 19 +- .../patches/flatmap-dynamic-key.diff | 90 --------- .../patches/tenantmodules.diff | 12 +- .../system/dashboard/templates/ingress.yaml | 2 + .../dashboard/templates/nginx-config.yaml | 15 +- packages/system/dashboard/templates/web.yaml | 73 +++++-- 21 files changed, 608 insertions(+), 243 deletions(-) create mode 100644 internal/controller/dashboard/navigation.go create mode 100644 packages/system/cozystack-controller/definitions/dashboard.cozystack.io_cfomappings.yaml delete mode 100644 packages/system/dashboard/images/openapi-ui/openapi-k8s-toolkit/patches/flatmap-dynamic-key.diff diff --git a/api/dashboard/v1alpha1/dashboard_resources.go b/api/dashboard/v1alpha1/dashboard_resources.go index 5af3b359..afac0b05 100644 --- a/api/dashboard/v1alpha1/dashboard_resources.go +++ b/api/dashboard/v1alpha1/dashboard_resources.go @@ -253,3 +253,25 @@ type FactoryList struct { metav1.ListMeta `json:"metadata,omitempty"` Items []Factory `json:"items"` } + +// ----------------------------------------------------------------------------- +// CustomFormsOverrideMapping +// ----------------------------------------------------------------------------- + +// +kubebuilder:object:root=true +// +kubebuilder:resource:path=cfomappings,scope=Cluster +// +kubebuilder:subresource:status +type CFOMapping struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ArbitrarySpec `json:"spec"` + Status CommonStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +type CFOMappingList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []CFOMapping `json:"items"` +} diff --git a/api/dashboard/v1alpha1/groupversion_info.go b/api/dashboard/v1alpha1/groupversion_info.go index b3c38e00..659401a7 100644 --- a/api/dashboard/v1alpha1/groupversion_info.go +++ b/api/dashboard/v1alpha1/groupversion_info.go @@ -69,6 +69,9 @@ func addKnownTypes(scheme *runtime.Scheme) error { &Factory{}, &FactoryList{}, + + &CFOMapping{}, + &CFOMappingList{}, ) metav1.AddToGroupVersion(scheme, GroupVersion) return nil diff --git a/api/dashboard/v1alpha1/zz_generated.deepcopy.go b/api/dashboard/v1alpha1/zz_generated.deepcopy.go index 45b1d9d7..568a1780 100644 --- a/api/dashboard/v1alpha1/zz_generated.deepcopy.go +++ b/api/dashboard/v1alpha1/zz_generated.deepcopy.go @@ -417,6 +417,65 @@ func (in *FactoryList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CFOMapping) DeepCopyInto(out *CFOMapping) { + *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 CFOMapping. +func (in *CFOMapping) DeepCopy() *CFOMapping { + if in == nil { + return nil + } + out := new(CFOMapping) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CFOMapping) 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 *CFOMappingList) DeepCopyInto(out *CFOMappingList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]CFOMapping, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CFOMappingList. +func (in *CFOMappingList) DeepCopy() *CFOMappingList { + if in == nil { + return nil + } + out := new(CFOMappingList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CFOMappingList) 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 *MarketplacePanel) DeepCopyInto(out *MarketplacePanel) { *out = *in diff --git a/internal/controller/dashboard/breadcrumb.go b/internal/controller/dashboard/breadcrumb.go index aadcacac..6d65f2db 100644 --- a/internal/controller/dashboard/breadcrumb.go +++ b/internal/controller/dashboard/breadcrumb.go @@ -33,12 +33,12 @@ func (m *Manager) ensureBreadcrumb(ctx context.Context, crd *cozyv1alpha1.Applic key := plural // e.g., "virtualmachines" label := labelPlural - link := fmt.Sprintf("/openapi-ui/{clusterName}/{namespace}/api-table/%s/%s/%s", strings.ToLower(group), strings.ToLower(version), plural) + link := fmt.Sprintf("/openapi-ui/{cluster}/{namespace}/api-table/%s/%s/%s", strings.ToLower(group), strings.ToLower(version), plural) // If this is a module, change the first breadcrumb item to "Tenant Modules" if crd.Spec.Dashboard != nil && crd.Spec.Dashboard.Module { key = "tenantmodules" label = "Tenant Modules" - link = "/openapi-ui/{clusterName}/{namespace}/api-table/core.cozystack.io/v1alpha1/tenantmodules" + link = "/openapi-ui/{cluster}/{namespace}/api-table/core.cozystack.io/v1alpha1/tenantmodules" } items := []any{ diff --git a/internal/controller/dashboard/customformsoverride.go b/internal/controller/dashboard/customformsoverride.go index 60bc82fc..dfbccf5c 100644 --- a/internal/controller/dashboard/customformsoverride.go +++ b/internal/controller/dashboard/customformsoverride.go @@ -84,6 +84,53 @@ func (m *Manager) ensureCustomFormsOverride(ctx context.Context, crd *cozyv1alph return err } +// ensureCFOMapping updates the CFOMapping resource to include a mapping for the given CRD +func (m *Manager) ensureCFOMapping(ctx context.Context, crd *cozyv1alpha1.ApplicationDefinition) error { + g, v, kind := pickGVK(crd) + plural := pickPlural(kind, crd) + + resourcePath := fmt.Sprintf("/%s/%s/%s", g, v, plural) + customizationID := fmt.Sprintf("default-%s", resourcePath) + + obj := &dashv1alpha1.CFOMapping{} + obj.SetName("cfomapping") + + _, err := controllerutil.CreateOrUpdate(ctx, m.Client, obj, func() error { + // Parse existing mappings + mappings := make(map[string]string) + if obj.Spec.JSON.Raw != nil { + var spec map[string]any + if err := json.Unmarshal(obj.Spec.JSON.Raw, &spec); err == nil { + if m, ok := spec["mappings"].(map[string]any); ok { + for k, val := range m { + if s, ok := val.(string); ok { + mappings[k] = s + } + } + } + } + } + + // Add/update the mapping for this CRD + mappings[resourcePath] = customizationID + + specData := map[string]any{ + "mappings": mappings, + } + b, err := json.Marshal(specData) + if err != nil { + return err + } + + newSpec := dashv1alpha1.ArbitrarySpec{JSON: apiextv1.JSON{Raw: b}} + if !compareArbitrarySpecs(obj.Spec, newSpec) { + obj.Spec = newSpec + } + return nil + }) + return err +} + // buildMultilineStringSchema parses OpenAPI schema and creates schema with multilineString // for all string fields inside spec that don't have enum func buildMultilineStringSchema(openAPISchema string) (map[string]any, error) { diff --git a/internal/controller/dashboard/factory.go b/internal/controller/dashboard/factory.go index e55aedc7..66ee111d 100644 --- a/internal/controller/dashboard/factory.go +++ b/internal/controller/dashboard/factory.go @@ -47,7 +47,7 @@ func (m *Manager) ensureFactory(ctx context.Context, crd *cozyv1alpha1.Applicati if prefix, ok := vncTabPrefix(kind); ok { tabs = append(tabs, vncTab(prefix)) } - tabs = append(tabs, yamlTab(plural)) + tabs = append(tabs, yamlTab(g, v, plural)) // Use unified factory creation config := UnifiedResourceConfig{ @@ -160,11 +160,11 @@ func detailsTab(kind, endpoint, schemaJSON string, keysOrder [][]string) map[str map[string]any{ "type": "EnrichedTable", "data": map[string]any{ - "id": "vpc-subnets-table", - "baseprefix": "/openapi-ui", - "clusterNamePartOfUrl": "{2}", - "customizationId": "virtualprivatecloud-subnets", - "fetchUrl": "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/configmaps", + "id": "vpc-subnets-table", + "baseprefix": "/openapi-ui", + "cluster": "{2}", + "customizationId": "virtualprivatecloud-subnets", + "fetchUrl": "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/configmaps", "fieldSelector": map[string]any{ "metadata.name": "virtualprivatecloud-{6}-subnets", }, @@ -188,12 +188,12 @@ func detailsTab(kind, endpoint, schemaJSON string, keysOrder [][]string) map[str map[string]any{ "type": "EnrichedTable", "data": map[string]any{ - "id": "resource-quotas-table", - "baseprefix": "/openapi-ui", - "clusterNamePartOfUrl": "{2}", - "customizationId": "factory-resource-quotas", - "fetchUrl": "/api/clusters/{2}/k8s/api/v1/namespaces/{reqsJsonPath[0]['.status.namespace']}/resourcequotas", - "pathToItems": []any{`items`}, + "id": "resource-quotas-table", + "baseprefix": "/openapi-ui", + "cluster": "{2}", + "customizationId": "factory-resource-quotas", + "fetchUrl": "/api/clusters/{2}/k8s/api/v1/namespaces/{reqsJsonPath[0]['.status.namespace']}/resourcequotas", + "pathToItems": []any{`items`}, }, }, }), @@ -242,13 +242,13 @@ func detailsTab(kind, endpoint, schemaJSON string, keysOrder [][]string) map[str map[string]any{ "type": "EnrichedTable", "data": map[string]any{ - "id": "conditions-table", - "fetchUrl": endpoint, - "clusterNamePartOfUrl": "{2}", - "customizationId": "factory-status-conditions", - "baseprefix": "/openapi-ui", - "withoutControls": true, - "pathToItems": []any{"status", "conditions"}, + "id": "conditions-table", + "fetchUrl": endpoint, + "cluster": "{2}", + "customizationId": "factory-status-conditions", + "baseprefix": "/openapi-ui", + "withoutControls": true, + "pathToItems": []any{"status", "conditions"}, }, }, }), @@ -264,12 +264,12 @@ func workloadsTab(kind string) map[string]any { map[string]any{ "type": "EnrichedTable", "data": map[string]any{ - "id": "workloads-table", - "fetchUrl": "/api/clusters/{2}/k8s/apis/cozystack.io/v1alpha1/namespaces/{3}/workloadmonitors", - "clusterNamePartOfUrl": "{2}", - "baseprefix": "/openapi-ui", - "customizationId": "factory-details-v1alpha1.cozystack.io.workloadmonitors", - "pathToItems": []any{"items"}, + "id": "workloads-table", + "fetchUrl": "/api/clusters/{2}/k8s/apis/cozystack.io/v1alpha1/namespaces/{3}/workloadmonitors", + "cluster": "{2}", + "baseprefix": "/openapi-ui", + "customizationId": "factory-details-v1alpha1.cozystack.io.workloadmonitors", + "pathToItems": []any{"items"}, "labelSelector": map[string]any{ "apps.cozystack.io/application.group": "apps.cozystack.io", "apps.cozystack.io/application.kind": kind, @@ -289,12 +289,12 @@ func servicesTab(kind string) map[string]any { map[string]any{ "type": "EnrichedTable", "data": map[string]any{ - "id": "services-table", - "fetchUrl": "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/services", - "clusterNamePartOfUrl": "{2}", - "baseprefix": "/openapi-ui", - "customizationId": "factory-details-v1.services", - "pathToItems": []any{"items"}, + "id": "services-table", + "fetchUrl": "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/services", + "cluster": "{2}", + "baseprefix": "/openapi-ui", + "customizationId": "factory-details-v1.services", + "pathToItems": []any{"items"}, "labelSelector": map[string]any{ "apps.cozystack.io/application.group": "apps.cozystack.io", "apps.cozystack.io/application.kind": kind, @@ -315,12 +315,12 @@ func ingressesTab(kind string) map[string]any { map[string]any{ "type": "EnrichedTable", "data": map[string]any{ - "id": "ingresses-table", - "fetchUrl": "/api/clusters/{2}/k8s/apis/networking.k8s.io/v1/namespaces/{3}/ingresses", - "clusterNamePartOfUrl": "{2}", - "baseprefix": "/openapi-ui", - "customizationId": "factory-details-networking.k8s.io.v1.ingresses", - "pathToItems": []any{"items"}, + "id": "ingresses-table", + "fetchUrl": "/api/clusters/{2}/k8s/apis/networking.k8s.io/v1/namespaces/{3}/ingresses", + "cluster": "{2}", + "baseprefix": "/openapi-ui", + "customizationId": "factory-details-networking.k8s.io.v1.ingresses", + "pathToItems": []any{"items"}, "labelSelector": map[string]any{ "apps.cozystack.io/application.group": "apps.cozystack.io", "apps.cozystack.io/application.kind": kind, @@ -341,12 +341,12 @@ func secretsTab(kind string) map[string]any { map[string]any{ "type": "EnrichedTable", "data": map[string]any{ - "id": "secrets-table", - "fetchUrl": "/api/clusters/{2}/k8s/apis/core.cozystack.io/v1alpha1/namespaces/{3}/tenantsecrets", - "clusterNamePartOfUrl": "{2}", - "baseprefix": "/openapi-ui", - "customizationId": "factory-details-v1alpha1.core.cozystack.io.tenantsecrets", - "pathToItems": []any{"items"}, + "id": "secrets-table", + "fetchUrl": "/api/clusters/{2}/k8s/apis/core.cozystack.io/v1alpha1/namespaces/{3}/tenantsecrets", + "cluster": "{2}", + "baseprefix": "/openapi-ui", + "customizationId": "factory-details-v1alpha1.core.cozystack.io.tenantsecrets", + "pathToItems": []any{"items"}, "labelSelector": map[string]any{ "apps.cozystack.io/application.group": "apps.cozystack.io", "apps.cozystack.io/application.kind": kind, @@ -358,7 +358,7 @@ func secretsTab(kind string) map[string]any { } } -func yamlTab(plural string) map[string]any { +func yamlTab(group, version, plural string) map[string]any { return map[string]any{ "key": "yaml", "label": "YAML", @@ -369,8 +369,10 @@ func yamlTab(plural string) map[string]any { "id": "yaml-editor", "cluster": "{2}", "isNameSpaced": true, - "type": "builtin", - "typeName": plural, + "type": "apis", + "apiGroup": group, + "apiVersion": version, + "plural": plural, "prefillValuesRequestIndex": float64(0), "readOnly": true, "substractHeight": float64(400), diff --git a/internal/controller/dashboard/manager.go b/internal/controller/dashboard/manager.go index 23897586..b0a4cd1b 100644 --- a/internal/controller/dashboard/manager.go +++ b/internal/controller/dashboard/manager.go @@ -132,6 +132,10 @@ func (m *Manager) EnsureForAppDef(ctx context.Context, crd *cozyv1alpha1.Applica return reconcile.Result{}, err } + if err := m.ensureCFOMapping(ctx, crd); err != nil { + return reconcile.Result{}, err + } + if err := m.ensureSidebar(ctx, crd); err != nil { return reconcile.Result{}, err } @@ -139,6 +143,10 @@ func (m *Manager) EnsureForAppDef(ctx context.Context, crd *cozyv1alpha1.Applica if err := m.ensureFactory(ctx, crd); err != nil { return reconcile.Result{}, err } + + if err := m.ensureNavigation(ctx, crd); err != nil { + return reconcile.Result{}, err + } return reconcile.Result{}, nil } diff --git a/internal/controller/dashboard/marketplacepanel.go b/internal/controller/dashboard/marketplacepanel.go index 6bfce752..fcfff799 100644 --- a/internal/controller/dashboard/marketplacepanel.go +++ b/internal/controller/dashboard/marketplacepanel.go @@ -74,7 +74,7 @@ func (m *Manager) ensureMarketplacePanel(ctx context.Context, crd *cozyv1alpha1. "type": "nonCrd", "apiGroup": "apps.cozystack.io", "apiVersion": "v1alpha1", - "typeName": app.Plural, // e.g., "buckets" + "plural": app.Plural, // e.g., "buckets" "disabled": false, "hidden": false, "tags": tags, diff --git a/internal/controller/dashboard/navigation.go b/internal/controller/dashboard/navigation.go new file mode 100644 index 00000000..5e4bad2e --- /dev/null +++ b/internal/controller/dashboard/navigation.go @@ -0,0 +1,69 @@ +package dashboard + +import ( + "context" + "encoding/json" + "fmt" + "strings" + + dashv1alpha1 "github.com/cozystack/cozystack/api/dashboard/v1alpha1" + cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1" + + apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +// ensureNavigation updates the Navigation resource to include a baseFactoriesMapping entry for the given CRD +func (m *Manager) ensureNavigation(ctx context.Context, crd *cozyv1alpha1.ApplicationDefinition) error { + g, v, kind := pickGVK(crd) + plural := pickPlural(kind, crd) + + lowerKind := strings.ToLower(kind) + factoryKey := fmt.Sprintf("%s-details", lowerKind) + + // All CRD resources are namespaced API resources + mappingKey := fmt.Sprintf("base-factory-namespaced-api-%s-%s-%s", g, v, plural) + + obj := &dashv1alpha1.Navigation{} + obj.SetName("navigation") + + _, err := controllerutil.CreateOrUpdate(ctx, m.Client, obj, func() error { + // Parse existing spec + spec := make(map[string]any) + if obj.Spec.JSON.Raw != nil { + if err := json.Unmarshal(obj.Spec.JSON.Raw, &spec); err != nil { + spec = make(map[string]any) + } + } + + // Get or create baseFactoriesMapping + var mappings map[string]string + if existing, ok := spec["baseFactoriesMapping"].(map[string]any); ok { + mappings = make(map[string]string, len(existing)) + for k, val := range existing { + if s, ok := val.(string); ok { + mappings[k] = s + } + } + } else { + mappings = make(map[string]string) + } + + // Add/update the mapping for this CRD + mappings[mappingKey] = factoryKey + + spec["baseFactoriesMapping"] = mappings + + b, err := json.Marshal(spec) + if err != nil { + return err + } + + newSpec := dashv1alpha1.ArbitrarySpec{JSON: apiextv1.JSON{Raw: b}} + if !compareArbitrarySpecs(obj.Spec, newSpec) { + obj.Spec = newSpec + } + return nil + }) + return err +} diff --git a/internal/controller/dashboard/sidebar.go b/internal/controller/dashboard/sidebar.go index a6ade387..b7a6eb70 100644 --- a/internal/controller/dashboard/sidebar.go +++ b/internal/controller/dashboard/sidebar.go @@ -22,8 +22,8 @@ import ( // // Menu rules: // - The first section is "Marketplace" with two hardcoded entries: -// - Marketplace (/openapi-ui/{clusterName}/{namespace}/factory/marketplace) -// - Tenant Info (/openapi-ui/{clusterName}/{namespace}/factory/info-details/info) +// - Marketplace (/openapi-ui/{cluster}/{namespace}/factory/marketplace) +// - Tenant Info (/openapi-ui/{cluster}/{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, Backups, Administration @@ -91,7 +91,7 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Applicati // Weight (default 0) weight := def.Spec.Dashboard.Weight - link := fmt.Sprintf("/openapi-ui/{clusterName}/{namespace}/api-table/%s/%s/%s", g, v, plural) + link := fmt.Sprintf("/openapi-ui/{cluster}/{namespace}/api-table/%s/%s/%s", g, v, plural) categories[cat] = append(categories[cat], item{ Key: plural, @@ -146,7 +146,7 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Applicati map[string]any{ "key": "marketplace", "label": "Marketplace", - "link": "/openapi-ui/{clusterName}/{namespace}/factory/marketplace", + "link": "/openapi-ui/{cluster}/{namespace}/factory/marketplace", }, }, }, @@ -205,12 +205,12 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Applicati map[string]any{ "key": "info", "label": "Info", - "link": "/openapi-ui/{clusterName}/{namespace}/factory/info-details/info", + "link": "/openapi-ui/{cluster}/{namespace}/factory/info-details/info", }, map[string]any{ "key": "modules", "label": "Modules", - "link": "/openapi-ui/{clusterName}/{namespace}/api-table/core.cozystack.io/v1alpha1/tenantmodules", + "link": "/openapi-ui/{cluster}/{namespace}/api-table/core.cozystack.io/v1alpha1/tenantmodules", }, map[string]any{ "key": "loadbalancer-services", @@ -220,7 +220,7 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Applicati map[string]any{ "key": "tenants", "label": "Tenants", - "link": "/openapi-ui/{clusterName}/{namespace}/api-table/apps.cozystack.io/v1alpha1/tenants", + "link": "/openapi-ui/{cluster}/{namespace}/api-table/apps.cozystack.io/v1alpha1/tenants", }, }, }) diff --git a/internal/controller/dashboard/static_helpers.go b/internal/controller/dashboard/static_helpers.go index af46674b..1a1018cb 100644 --- a/internal/controller/dashboard/static_helpers.go +++ b/internal/controller/dashboard/static_helpers.go @@ -1134,7 +1134,7 @@ func yamlEditor(id, cluster string, isNameSpaced bool, typeName string, prefillV "cluster": cluster, "isNameSpaced": isNameSpaced, "type": "builtin", - "typeName": typeName, + "plural": typeName, "prefillValuesRequestIndex": prefillValuesRequestIndex, "substractHeight": float64(400), }, diff --git a/internal/controller/dashboard/static_processor.go b/internal/controller/dashboard/static_processor.go index 902e3818..d4b270f4 100644 --- a/internal/controller/dashboard/static_processor.go +++ b/internal/controller/dashboard/static_processor.go @@ -49,6 +49,8 @@ func (m *Manager) ensureStaticResource(ctx context.Context, obj client.Object) e resource.(*dashv1alpha1.Navigation).Spec = o.Spec case *dashv1alpha1.TableUriMapping: resource.(*dashv1alpha1.TableUriMapping).Spec = o.Spec + case *dashv1alpha1.CFOMapping: + resource.(*dashv1alpha1.CFOMapping).Spec = o.Spec } // Ensure labels are always set m.addDashboardLabels(resource, nil, ResourceTypeStatic) diff --git a/internal/controller/dashboard/static_refactored.go b/internal/controller/dashboard/static_refactored.go index 4b244396..d47c88da 100644 --- a/internal/controller/dashboard/static_refactored.go +++ b/internal/controller/dashboard/static_refactored.go @@ -17,111 +17,111 @@ func CreateAllBreadcrumbs() []*dashboardv1alpha1.Breadcrumb { return []*dashboardv1alpha1.Breadcrumb{ // Stock project factory configmap details createBreadcrumb("stock-project-factory-configmap-details", []map[string]any{ - createBreadcrumbItem("configmaps", "v1/configmaps", "/openapi-ui/{clusterName}/{namespace}/builtin-table/configmaps"), + createBreadcrumbItem("configmaps", "v1/configmaps", "/openapi-ui/{cluster}/{namespace}/builtin-table/configmaps"), createBreadcrumbItem("configmap", "{6}"), }), // Stock cluster factory namespace details createBreadcrumb("stock-cluster-factory-namespace-details", []map[string]any{ - createBreadcrumbItem("namespaces", "v1/namespaces", "/openapi-ui/{clusterName}/builtin-table/namespaces"), + createBreadcrumbItem("namespaces", "v1/namespaces", "/openapi-ui/{cluster}/builtin-table/namespaces"), createBreadcrumbItem("namespace", "{5}"), }), // Stock cluster factory node details createBreadcrumb("stock-cluster-factory-node-details", []map[string]any{ - createBreadcrumbItem("node", "v1/nodes", "/openapi-ui/{clusterName}/builtin-table/nodes"), + createBreadcrumbItem("node", "v1/nodes", "/openapi-ui/{cluster}/builtin-table/nodes"), createBreadcrumbItem("node", "{5}"), }), // Stock project factory pod details createBreadcrumb("stock-project-factory-pod-details", []map[string]any{ - createBreadcrumbItem("pods", "v1/pods", "/openapi-ui/{clusterName}/{namespace}/builtin-table/pods"), + createBreadcrumbItem("pods", "v1/pods", "/openapi-ui/{cluster}/{namespace}/builtin-table/pods"), createBreadcrumbItem("pod", "{6}"), }), // Stock project factory secret details createBreadcrumb("stock-project-factory-kube-secret-details", []map[string]any{ - createBreadcrumbItem("secrets", "v1/secrets", "/openapi-ui/{clusterName}/{namespace}/builtin-table/secrets"), + createBreadcrumbItem("secrets", "v1/secrets", "/openapi-ui/{cluster}/{namespace}/builtin-table/secrets"), createBreadcrumbItem("secret", "{6}"), }), // Stock project factory service details createBreadcrumb("stock-project-factory-kube-service-details", []map[string]any{ - createBreadcrumbItem("services", "v1/services", "/openapi-ui/{clusterName}/{namespace}/builtin-table/services"), + createBreadcrumbItem("services", "v1/services", "/openapi-ui/{cluster}/{namespace}/builtin-table/services"), createBreadcrumbItem("service", "{6}"), }), // Stock project factory ingress details createBreadcrumb("stock-project-factory-kube-ingress-details", []map[string]any{ - createBreadcrumbItem("ingresses", "networking.k8s.io/v1/ingresses", "/openapi-ui/{clusterName}/{namespace}/builtin-table/ingresses"), + createBreadcrumbItem("ingresses", "networking.k8s.io/v1/ingresses", "/openapi-ui/{cluster}/{namespace}/builtin-table/ingresses"), createBreadcrumbItem("ingress", "{6}"), }), // Stock cluster api table createBreadcrumb("stock-cluster-api-table", []map[string]any{ - createBreadcrumbItem("api", "{apiGroup}/{apiVersion}/{typeName}"), + createBreadcrumbItem("api", "{apiGroup}/{apiVersion}/{plural}"), }), // Stock cluster api form createBreadcrumb("stock-cluster-api-form", []map[string]any{ - createBreadcrumbItem("create-api-res-namespaced-table", "{apiGroup}/{apiVersion}/{typeName}", "/openapi-ui/{clusterName}/api-table/{apiGroup}/{apiVersion}/{typeName}"), + createBreadcrumbItem("create-api-res-namespaced-table", "{apiGroup}/{apiVersion}/{plural}", "/openapi-ui/{cluster}/api-table/{apiGroup}/{apiVersion}/{plural}"), createBreadcrumbItem("create-api-res-namespaced-typename", "Create"), }), // Stock cluster api form edit createBreadcrumb("stock-cluster-api-form-edit", []map[string]any{ - createBreadcrumbItem("create-api-res-namespaced-table", "{apiGroup}/{apiVersion}/{typeName}", "/openapi-ui/{clusterName}/api-table/{apiGroup}/{apiVersion}/{typeName}"), + createBreadcrumbItem("create-api-res-namespaced-table", "{apiGroup}/{apiVersion}/{plural}", "/openapi-ui/{cluster}/api-table/{apiGroup}/{apiVersion}/{plural}"), createBreadcrumbItem("create-api-res-namespaced-typename", "Update"), }), // Stock cluster builtin table createBreadcrumb("stock-cluster-builtin-table", []map[string]any{ - createBreadcrumbItem("api", "v1/{typeName}"), + createBreadcrumbItem("api", "v1/{plural}"), }), // Stock cluster builtin form createBreadcrumb("stock-cluster-builtin-form", []map[string]any{ - createBreadcrumbItem("create-api-res-namespaced-table", "v1/{typeName}", "/openapi-ui/{clusterName}/builtin-table/{typeName}"), + createBreadcrumbItem("create-api-res-namespaced-table", "v1/{plural}", "/openapi-ui/{cluster}/builtin-table/{plural}"), createBreadcrumbItem("create-api-res-namespaced-typename", "Create"), }), // Stock cluster builtin form edit createBreadcrumb("stock-cluster-builtin-form-edit", []map[string]any{ - createBreadcrumbItem("create-api-res-namespaced-table", "v1/{typeName}", "/openapi-ui/{clusterName}/builtin-table/{typeName}"), + createBreadcrumbItem("create-api-res-namespaced-table", "v1/{plural}", "/openapi-ui/{cluster}/builtin-table/{plural}"), createBreadcrumbItem("create-api-res-namespaced-typename", "Update"), }), // Stock project api table createBreadcrumb("stock-project-api-table", []map[string]any{ - createBreadcrumbItem("api", "{apiGroup}/{apiVersion}/{typeName}"), + createBreadcrumbItem("api", "{apiGroup}/{apiVersion}/{plural}"), }), // Stock project api form createBreadcrumb("stock-project-api-form", []map[string]any{ - createBreadcrumbItem("create-api-res-namespaced-table", "{apiGroup}/{apiVersion}/{typeName}", "/openapi-ui/{clusterName}/{namespace}/api-table/{apiGroup}/{apiVersion}/{typeName}"), + createBreadcrumbItem("create-api-res-namespaced-table", "{apiGroup}/{apiVersion}/{plural}", "/openapi-ui/{cluster}/{namespace}/api-table/{apiGroup}/{apiVersion}/{plural}"), createBreadcrumbItem("create-api-res-namespaced-typename", "Create"), }), // Stock project api form edit createBreadcrumb("stock-project-api-form-edit", []map[string]any{ - createBreadcrumbItem("create-api-res-namespaced-table", "{apiGroup}/{apiVersion}/{typeName}", "/openapi-ui/{clusterName}/{namespace}/api-table/{apiGroup}/{apiVersion}/{typeName}"), + createBreadcrumbItem("create-api-res-namespaced-table", "{apiGroup}/{apiVersion}/{plural}", "/openapi-ui/{cluster}/{namespace}/api-table/{apiGroup}/{apiVersion}/{plural}"), createBreadcrumbItem("create-api-res-namespaced-typename", "Update"), }), // Stock project builtin table createBreadcrumb("stock-project-builtin-table", []map[string]any{ - createBreadcrumbItem("api", "v1/{typeName}"), + createBreadcrumbItem("api", "v1/{plural}"), }), // Stock project builtin form createBreadcrumb("stock-project-builtin-form", []map[string]any{ - createBreadcrumbItem("create-api-res-namespaced-table", "v1/{typeName}", "/openapi-ui/{clusterName}/{namespace}/builtin-table/{typeName}"), + createBreadcrumbItem("create-api-res-namespaced-table", "v1/{plural}", "/openapi-ui/{cluster}/{namespace}/builtin-table/{plural}"), createBreadcrumbItem("create-api-res-namespaced-typename", "Create"), }), // Stock project builtin form edit createBreadcrumb("stock-project-builtin-form-edit", []map[string]any{ - createBreadcrumbItem("create-api-res-namespaced-table", "v1/{typeName}", "/openapi-ui/{clusterName}/{namespace}/builtin-table/{typeName}"), + createBreadcrumbItem("create-api-res-namespaced-table", "v1/{plural}", "/openapi-ui/{cluster}/{namespace}/builtin-table/{plural}"), createBreadcrumbItem("create-api-res-namespaced-typename", "Update"), }), } @@ -535,14 +535,14 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory { }, []any{ map[string]any{ "data": map[string]any{ - "baseApiVersion": "v1alpha1", - "baseprefix": "openapi-ui", - "clusterNamePartOfUrl": "{2}", - "id": 311, - "mpResourceKind": "MarketplacePanel", - "mpResourceName": "marketplacepanels", - "namespacePartOfUrl": "{3}", - "baseApiGroup": "dashboard.cozystack.io", + "baseApiVersion": "v1alpha1", + "baseprefix": "openapi-ui", + "cluster": "{2}", + "id": 311, + "marketplaceKind": "MarketplacePanel", + "marketplacePlural": "marketplacepanels", + "namespace": "{3}", + "baseApiGroup": "dashboard.cozystack.io", }, "type": "MarketplaceCard", }, @@ -855,7 +855,7 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory { "prefillValuesRequestIndex": 0, "substractHeight": float64(400), "type": "builtin", - "typeName": "secrets", + "plural": "secrets", "readOnly": true, }, }, @@ -1085,13 +1085,13 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory { map[string]any{ "type": "EnrichedTable", "data": map[string]any{ - "id": "service-port-mapping-table", - "baseprefix": "/openapi-ui", - "clusterNamePartOfUrl": "{2}", - "customizationId": "factory-kube-service-details-port-mapping", - "fetchUrl": "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/services/{6}", - "pathToItems": ".spec.ports", - "withoutControls": true, + "id": "service-port-mapping-table", + "baseprefix": "/openapi-ui", + "cluster": "{2}", + "customizationId": "factory-kube-service-details-port-mapping", + "fetchUrl": "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/services/{6}", + "pathToItems": ".spec.ports", + "withoutControls": true, }, }, }), @@ -1111,11 +1111,11 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory { map[string]any{ "type": "EnrichedTable", "data": map[string]any{ - "id": "service-pod-serving-table", - "baseprefix": "/openapi-ui", - "clusterNamePartOfUrl": "{2}", - "customizationId": "factory-kube-service-details-endpointslice", - "fetchUrl": "/api/clusters/{2}/k8s/apis/discovery.k8s.io/v1/namespaces/{3}/endpointslices", + "id": "service-pod-serving-table", + "baseprefix": "/openapi-ui", + "cluster": "{2}", + "customizationId": "factory-kube-service-details-endpointslice", + "fetchUrl": "/api/clusters/{2}/k8s/apis/discovery.k8s.io/v1/namespaces/{3}/endpointslices", "labelSelector": map[string]any{ "kubernetes.io/service-name": "{reqsJsonPath[0]['.metadata.name']['-']}", }, @@ -1145,7 +1145,7 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory { "prefillValuesRequestIndex": 0, "substractHeight": float64(400), "type": "builtin", - "typeName": "services", + "plural": "services", }, }, }, @@ -1168,11 +1168,11 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory { map[string]any{ "type": "EnrichedTable", "data": map[string]any{ - "id": "pods-table", - "baseprefix": "/openapi-ui", - "clusterNamePartOfUrl": "{2}", - "customizationId": "factory-node-details-/v1/pods", - "fetchUrl": "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/pods", + "id": "pods-table", + "baseprefix": "/openapi-ui", + "cluster": "{2}", + "customizationId": "factory-node-details-/v1/pods", + "fetchUrl": "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/pods", "labelSelectorFull": map[string]any{ "pathToLabels": ".spec.selector", "reqIndex": 0, @@ -1300,13 +1300,13 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory { map[string]any{ "type": "EnrichedTable", "data": map[string]any{ - "id": "rules-table", - "fetchUrl": "/api/clusters/{2}/k8s/apis/networking.k8s.io/v1/namespaces/{3}/ingresses/{6}", - "clusterNamePartOfUrl": "{2}", - "customizationId": "factory-kube-ingress-details-rules", - "baseprefix": "/openapi-ui", - "withoutControls": true, - "pathToItems": []any{"spec", "rules"}, + "id": "rules-table", + "fetchUrl": "/api/clusters/{2}/k8s/apis/networking.k8s.io/v1/namespaces/{3}/ingresses/{6}", + "cluster": "{2}", + "customizationId": "factory-kube-ingress-details-rules", + "baseprefix": "/openapi-ui", + "withoutControls": true, + "pathToItems": []any{"spec", "rules"}, }, }, }), @@ -1322,8 +1322,10 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory { "id": "yaml-editor", "cluster": "{2}", "isNameSpaced": true, - "type": "builtin", - "typeName": "ingresses", + "type": "apis", + "apiGroup": "networking.k8s.io", + "apiVersion": "v1", + "plural": "ingresses", "prefillValuesRequestIndex": float64(0), "substractHeight": float64(400), }, @@ -1452,11 +1454,11 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory { map[string]any{ "type": "EnrichedTable", "data": map[string]any{ - "id": "workloads-table", - "baseprefix": "/openapi-ui", - "clusterNamePartOfUrl": "{2}", - "customizationId": "factory-details-v1alpha1.cozystack.io.workloads", - "fetchUrl": "/api/clusters/{2}/k8s/apis/cozystack.io/v1alpha1/namespaces/{3}/workloads", + "id": "workloads-table", + "baseprefix": "/openapi-ui", + "cluster": "{2}", + "customizationId": "factory-details-v1alpha1.cozystack.io.workloads", + "fetchUrl": "/api/clusters/{2}/k8s/apis/cozystack.io/v1alpha1/namespaces/{3}/workloads", "labelSelector": map[string]any{ "workloads.cozystack.io/monitor": "{reqs[0]['metadata','name']}", }, @@ -1477,8 +1479,10 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory { "isNameSpaced": true, "prefillValuesRequestIndex": 0, "substractHeight": float64(400), - "type": "builtin", - "typeName": "workloadmonitors", + "type": "apis", + "apiGroup": "cozystack.io", + "apiVersion": "v1alpha1", + "plural": "workloadmonitors", }, }, }, @@ -1960,12 +1964,27 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory { // CreateAllNavigations creates all navigation resources using helper functions func CreateAllNavigations() []*dashboardv1alpha1.Navigation { + // Build baseFactoriesMapping for static (built-in) factories + baseFactoriesMapping := map[string]string{ + // Cluster-scoped builtin resources + "base-factory-clusterscoped-builtin-v1-namespaces": "namespace-details", + "base-factory-clusterscoped-builtin-v1-nodes": "node-details", + // Namespaced builtin resources + "base-factory-namespaced-builtin-v1-pods": "pod-details", + "base-factory-namespaced-builtin-v1-secrets": "kube-secret-details", + "base-factory-namespaced-builtin-v1-services": "kube-service-details", + // Namespaced API resources + "base-factory-namespaced-api-networking.k8s.io-v1-ingresses": "kube-ingress-details", + "base-factory-namespaced-api-cozystack.io-v1alpha1-workloadmonitors": "workloadmonitor-details", + } + return []*dashboardv1alpha1.Navigation{ createNavigation("navigation", map[string]any{ "namespaces": map[string]any{ "change": "/openapi-ui/{selectedCluster}/{value}/factory/marketplace", "clear": "/openapi-ui/{selectedCluster}/api-table/core.cozystack.io/v1alpha1/tenantnamespaces", }, + "baseFactoriesMapping": baseFactoriesMapping, }), } } @@ -2342,6 +2361,51 @@ func createWorkloadmonitorHeader() map[string]any { } } +// CreateStaticCFOMapping creates the CFOMapping resource with mappings from static CustomFormsOverrides +func CreateStaticCFOMapping() *dashboardv1alpha1.CFOMapping { + // Build mappings from static CustomFormsOverrides + customFormsOverrides := CreateAllCustomFormsOverrides() + mappings := make(map[string]string, len(customFormsOverrides)) + for _, cfo := range customFormsOverrides { + var spec map[string]any + if err := json.Unmarshal(cfo.Spec.JSON.Raw, &spec); err != nil { + continue + } + customizationID, ok := spec["customizationId"].(string) + if !ok { + continue + } + // Extract the resource path from customizationId (remove "default-" prefix) + resourcePath := strings.TrimPrefix(customizationID, "default-") + mappings[resourcePath] = customizationID + } + + return createCFOMapping("cfomapping", mappings) +} + +// createCFOMapping creates a CFOMapping resource +func createCFOMapping(name string, mappings map[string]string) *dashboardv1alpha1.CFOMapping { + spec := map[string]any{ + "mappings": mappings, + } + jsonData, _ := json.Marshal(spec) + + return &dashboardv1alpha1.CFOMapping{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "dashboard.cozystack.io/v1alpha1", + Kind: "CFOMapping", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: dashboardv1alpha1.ArbitrarySpec{ + JSON: v1.JSON{ + Raw: jsonData, + }, + }, + } +} + // ---------------- Complete resource creation function ---------------- // CreateAllStaticResources creates all static dashboard resources using helper functions @@ -2378,5 +2442,8 @@ func CreateAllStaticResources() []client.Object { resources = append(resources, tableUriMapping) } + // Add CFOMapping + resources = append(resources, CreateStaticCFOMapping()) + return resources } diff --git a/packages/system/cozystack-controller/definitions/dashboard.cozystack.io_cfomappings.yaml b/packages/system/cozystack-controller/definitions/dashboard.cozystack.io_cfomappings.yaml new file mode 100644 index 00000000..a46f6a23 --- /dev/null +++ b/packages/system/cozystack-controller/definitions/dashboard.cozystack.io_cfomappings.yaml @@ -0,0 +1,118 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.4 + name: cfomappings.dashboard.cozystack.io +spec: + group: dashboard.cozystack.io + names: + kind: CFOMapping + listKind: CFOMappingList + plural: cfomappings + singular: cfomapping + scope: Cluster + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + 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: |- + ArbitrarySpec holds schemaless user data and preserves unknown fields. + We map the entire .spec to a single JSON payload to mirror the CRDs you provided. + NOTE: Using apiextensionsv1.JSON avoids losing arbitrary structure during round-trips. + type: object + x-kubernetes-preserve-unknown-fields: true + status: + description: CommonStatus is a generic Status block with Kubernetes conditions. + properties: + conditions: + description: Conditions represent the latest available observations + of an object'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 + observedGeneration: + description: ObservedGeneration reflects the most recent generation + observed by the controller. + format: int64 + type: integer + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/packages/system/dashboard/images/openapi-ui-k8s-bff/Dockerfile b/packages/system/dashboard/images/openapi-ui-k8s-bff/Dockerfile index ef698698..ea0c900d 100644 --- a/packages/system/dashboard/images/openapi-ui-k8s-bff/Dockerfile +++ b/packages/system/dashboard/images/openapi-ui-k8s-bff/Dockerfile @@ -1,9 +1,10 @@ -# imported from https://github.com/cozystack/openapi-ui-k8s-bff +# imported from https://github.com/PRO-Robotech/openapi-ui-k8s-bff ARG NODE_VERSION=20.18.1 FROM node:${NODE_VERSION}-alpine AS builder WORKDIR /src -ARG COMMIT_REF=183dc9dcbb0f8a1833dad642c35faa385c71e58d +# release/1.4.0 +ARG COMMIT_REF=92e4b618eb9ad17b19827b5a2b7ceab33e8cf534 RUN wget -O- https://github.com/PRO-Robotech/openapi-ui-k8s-bff/archive/${COMMIT_REF}.tar.gz | tar xzf - --strip-components=1 ENV PATH=/src/node_modules/.bin:$PATH diff --git a/packages/system/dashboard/images/openapi-ui/Dockerfile b/packages/system/dashboard/images/openapi-ui/Dockerfile index f430f8e6..4b80c960 100644 --- a/packages/system/dashboard/images/openapi-ui/Dockerfile +++ b/packages/system/dashboard/images/openapi-ui/Dockerfile @@ -1,12 +1,13 @@ ARG NODE_VERSION=20.18.1 # openapi-k8s-toolkit -# imported from https://github.com/cozystack/openapi-k8s-toolkit +# imported from https://github.com/PRO-Robotech/openapi-k8s-toolkit FROM node:${NODE_VERSION}-alpine AS openapi-k8s-toolkit-builder RUN apk add git WORKDIR /src -ARG COMMIT=cb2f122caafaa2fd5455750213d9e633017ec555 -RUN wget -O- https://github.com/cozystack/openapi-k8s-toolkit/archive/${COMMIT}.tar.gz | tar -xzvf- --strip-components=1 +# release/1.4.0 +ARG COMMIT=c67029cc7b7495c65ee0406033576e773a73bb01 +RUN wget -O- https://github.com/PRO-Robotech/openapi-k8s-toolkit/archive/${COMMIT}.tar.gz | tar -xzvf- --strip-components=1 COPY openapi-k8s-toolkit/patches /patches RUN git apply /patches/*.diff @@ -17,12 +18,13 @@ RUN npm run build # openapi-ui -# imported from https://github.com/cozystack/openapi-ui +# imported from https://github.com/PRO-Robotech/openapi-ui FROM node:${NODE_VERSION}-alpine AS builder #RUN apk add git WORKDIR /src -ARG COMMIT_REF=3cfbbf2156b6a5e4a1f283a032019530c0c2d37d +# release/1.4.0 +ARG COMMIT_REF=6addca6939264ef2e39801baa88c1460cc1aa53e RUN wget -O- https://github.com/PRO-Robotech/openapi-ui/archive/${COMMIT_REF}.tar.gz | tar xzf - --strip-components=1 #COPY openapi-ui/patches /patches @@ -56,5 +58,12 @@ COPY --from=builder2 /src/node_modules /app/node_modules COPY --from=builder2 /src/build /app/build EXPOSE 8080 RUN sed -i -e 's|OpenAPI UI|Cozystack|g' build/index.html +# Fix Factory component: return null while loading instead of showing "Factory Not Found" 404 +RUN APP_JS=$(find build -name "App-react.js" -type f | head -1) && \ + if [ -n "$APP_JS" ]; then \ + sed -i 's|const { data: factoryData } = useK8sSmartResource({|const { data: factoryData, isLoading: factoryIsLoading } = useK8sSmartResource({|' "$APP_JS" && \ + sed -i '/Factory Not Found/s/return /return factoryIsLoading ? null : /' "$APP_JS" && \ + echo "Factory loading patch applied to $APP_JS"; \ + fi USER 1001 CMD ["node", "/app/build/index.js"] diff --git a/packages/system/dashboard/images/openapi-ui/openapi-k8s-toolkit/patches/flatmap-dynamic-key.diff b/packages/system/dashboard/images/openapi-ui/openapi-k8s-toolkit/patches/flatmap-dynamic-key.diff deleted file mode 100644 index 03212014..00000000 --- a/packages/system/dashboard/images/openapi-ui/openapi-k8s-toolkit/patches/flatmap-dynamic-key.diff +++ /dev/null @@ -1,90 +0,0 @@ -diff --git a/src/components/molecules/EnrichedTable/organisms/EnrichedTableProvider/utils.ts b/src/components/molecules/EnrichedTable/organisms/EnrichedTableProvider/utils.ts -index 87a0f12..fb2e1cc 100644 ---- a/src/components/molecules/EnrichedTable/organisms/EnrichedTableProvider/utils.ts -+++ b/src/components/molecules/EnrichedTable/organisms/EnrichedTableProvider/utils.ts -@@ -134,22 +134,6 @@ export const prepare = ({ - // impossible in k8s - return {} - }) -- if (customFields.length > 0) { -- dataSource = dataSource.map((el: TJSON) => { -- const newFieldsForComplexJsonPath: Record = {} -- customFields.forEach(({ dataIndex, jsonPath }) => { -- const jpQueryResult = jp.query(el, `$${jsonPath}`) -- newFieldsForComplexJsonPath[dataIndex] = -- Array.isArray(jpQueryResult) && jpQueryResult.length === 1 ? jpQueryResult[0] : jpQueryResult -- }) -- if (typeof el === 'object') { -- return { ...el, ...newFieldsForComplexJsonPath } -- } -- // impossible in k8s -- return { ...newFieldsForComplexJsonPath } -- }) -- } -- - // Handle flatMap: expand rows for map objects - // Process all flatMap columns sequentially - if (flatMapColumns.length > 0 && dataSource) { -@@ -204,6 +188,62 @@ export const prepare = ({ - currentDataSource = expandedDataSource - }) - dataSource = currentDataSource -+ } -+ -+ if (customFields.length > 0) { -+ dataSource = dataSource.map((el: TJSON) => { -+ const newFieldsForComplexJsonPath: Record = {} -+ customFields.forEach(({ dataIndex, jsonPath }) => { -+ let fieldValue: TJSON = null -+ let handled = false -+ -+ const flatMapMatch = jsonPath.match(/^(.*)\[(_flatMap[^\]]+_Key)\](.*)$/) -+ if (flatMapMatch && el && typeof el === 'object' && !Array.isArray(el)) { -+ const basePath = flatMapMatch[1] -+ const keyField = flatMapMatch[2] -+ const tailPath = flatMapMatch[3] -+ const keyValue = (el as Record)[keyField] -+ if (keyValue !== null && keyValue !== undefined) { -+ const baseResult = jp.query(el, `$${basePath}`)[0] -+ if (baseResult && typeof baseResult === 'object' && !Array.isArray(baseResult)) { -+ const baseValue = (baseResult as Record)[String(keyValue)] -+ if (tailPath) { -+ const normalizedTailPath = -+ tailPath.startsWith('.') || tailPath.startsWith('[') ? tailPath : `.${tailPath}` -+ const tailResult = jp.query(baseValue, `$${normalizedTailPath}`) -+ fieldValue = Array.isArray(tailResult) && tailResult.length === 1 ? tailResult[0] : tailResult -+ } else { -+ fieldValue = baseValue as TJSON -+ } -+ handled = true -+ } -+ } -+ } -+ -+ if (!handled) { -+ let resolvedJsonPath = jsonPath -+ if (el && typeof el === 'object' && !Array.isArray(el)) { -+ resolvedJsonPath = jsonPath.replace(/\[(_flatMap[^\]]+_Key)\]/g, (match, keyField) => { -+ const keyValue = (el as Record)[keyField] -+ if (keyValue === null || keyValue === undefined) { -+ return match -+ } -+ const escaped = String(keyValue).replace(/'/g, "\\'") -+ return `['${escaped}']` -+ }) -+ } -+ const jpQueryResult = jp.query(el, `$${resolvedJsonPath}`) -+ fieldValue = Array.isArray(jpQueryResult) && jpQueryResult.length === 1 ? jpQueryResult[0] : jpQueryResult -+ } -+ -+ newFieldsForComplexJsonPath[dataIndex] = fieldValue -+ }) -+ if (typeof el === 'object') { -+ return { ...el, ...newFieldsForComplexJsonPath } -+ } -+ // impossible in k8s -+ return { ...newFieldsForComplexJsonPath } -+ }) - } - } else { - dataSource = dataItems.map((el: TJSON) => { diff --git a/packages/system/dashboard/images/openapi-ui/openapi-k8s-toolkit/patches/tenantmodules.diff b/packages/system/dashboard/images/openapi-ui/openapi-k8s-toolkit/patches/tenantmodules.diff index 2817a8e0..d028f09a 100644 --- a/packages/system/dashboard/images/openapi-ui/openapi-k8s-toolkit/patches/tenantmodules.diff +++ b/packages/system/dashboard/images/openapi-ui/openapi-k8s-toolkit/patches/tenantmodules.diff @@ -33,18 +33,18 @@ index 8bcef4d..2551e92 100644 if (key === 'edit') { + // Special case: redirect tenantmodules from core.cozystack.io to apps.cozystack.io with plural form + let apiGroupAndVersion = value.apiGroupAndVersion -+ let typeName = value.typeName -+ if (apiGroupAndVersion?.startsWith('core.cozystack.io/') && typeName === 'tenantmodules') { ++ let plural = value.plural ++ if (apiGroupAndVersion?.startsWith('core.cozystack.io/') && plural === 'tenantmodules') { + const appsApiVersion = apiGroupAndVersion.replace('core.cozystack.io/', 'apps.cozystack.io/') -+ const pluralTypeName = getPluralForm(value.entryName) ++ const pluralName = getPluralForm(value.name) + apiGroupAndVersion = appsApiVersion -+ typeName = pluralTypeName ++ plural = pluralName + } navigate( `${baseprefix}/${value.cluster}${value.namespace ? `/${value.namespace}` : ''}${ value.syntheticProject ? `/${value.syntheticProject}` : '' -- }/${value.pathPrefix}/${value.apiGroupAndVersion}/${value.typeName}/${value.entryName}?backlink=${ -+ }/${value.pathPrefix}/${apiGroupAndVersion}/${typeName}/${value.entryName}?backlink=${ +- }/${value.pathPrefix}/${value.apiGroupAndVersion}/${value.plural}/${value.name}?backlink=${ ++ }/${value.pathPrefix}/${apiGroupAndVersion}/${plural}/${value.name}?backlink=${ value.backlink }`, ) diff --git a/packages/system/dashboard/templates/ingress.yaml b/packages/system/dashboard/templates/ingress.yaml index aacc1bc1..b3079a72 100644 --- a/packages/system/dashboard/templates/ingress.yaml +++ b/packages/system/dashboard/templates/ingress.yaml @@ -18,6 +18,8 @@ metadata: nginx.ingress.kubernetes.io/proxy-body-size: 100m nginx.ingress.kubernetes.io/proxy-buffer-size: 100m nginx.ingress.kubernetes.io/proxy-buffers-number: "4" + nginx.ingress.kubernetes.io/proxy-read-timeout: "86400" + nginx.ingress.kubernetes.io/proxy-send-timeout: "86400" name: dashboard-web-ingress spec: ingressClassName: {{ $exposeIngress }} diff --git a/packages/system/dashboard/templates/nginx-config.yaml b/packages/system/dashboard/templates/nginx-config.yaml index 696e9ce9..989f1470 100644 --- a/packages/system/dashboard/templates/nginx-config.yaml +++ b/packages/system/dashboard/templates/nginx-config.yaml @@ -32,6 +32,19 @@ data: proxy_pass http://incloud-web-nginx.{{ .Release.Namespace }}.svc:8080; } + location /k8s/clusters/default/ { + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $host; + proxy_read_timeout 86400s; + proxy_send_timeout 86400s; + + rewrite /k8s/clusters/default/(.*) /$1 break; + proxy_pass https://kubernetes.default.svc:443; + } + location /k8s { proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; @@ -40,7 +53,7 @@ data: proxy_set_header Host $host; proxy_read_timeout 86400s; proxy_send_timeout 86400s; - + rewrite /k8s/(.*) /$1 break; proxy_pass https://kubernetes.default.svc:443; } diff --git a/packages/system/dashboard/templates/web.yaml b/packages/system/dashboard/templates/web.yaml index d1c789b6..defb087b 100644 --- a/packages/system/dashboard/templates/web.yaml +++ b/packages/system/dashboard/templates/web.yaml @@ -45,6 +45,24 @@ spec: value: v1alpha1 - name: BASE_NAMESPACE_FULL_PATH value: "/apis/core.cozystack.io/v1alpha1/tenantnamespaces" + - name: BASE_NAVIGATION_RESOURCE_PLURAL + value: navigations + - name: BASE_NAVIGATION_RESOURCE_NAME + value: navigation + - name: BASE_FRONTEND_PREFIX + value: /openapi-ui + - name: BASE_FACTORY_NAMESPACED_API_KEY + value: base-factory-namespaced-api + - name: BASE_FACTORY_CLUSTERSCOPED_API_KEY + value: base-factory-clusterscoped-api + - name: BASE_FACTORY_NAMESPACED_BUILTIN_KEY + value: base-factory-namespaced-builtin + - name: BASE_FACTORY_CLUSTERSCOPED_BUILTIN_KEY + value: base-factory-clusterscoped-builtin + - name: BASE_NAMESPACE_FACTORY_KEY + value: base-factory-clusterscoped-builtin + - name: BASE_ALLOWED_AUTH_HEADERS + value: user-agent,accept,content-type,origin,referer,accept-encoding,cookie,authorization - name: LOGGER value: "true" - name: LOGGER_WITH_HEADERS @@ -73,7 +91,7 @@ spec: name: bff ports: - containerPort: 64231 - name: http + name: http-bff protocol: TCP resources: limits: @@ -98,7 +116,6 @@ spec: type: RuntimeDefault terminationMessagePath: /dev/termination-log terminationMessagePolicy: File - volumeMounts: null - env: - name: BASEPREFIX value: /openapi-ui @@ -108,40 +125,60 @@ spec: value: dashboard.cozystack.io - name: CUSTOMIZATION_API_VERSION value: v1alpha1 - - name: CUSTOMIZATION_NAVIGATION_RESOURCE - value: navigation + - name: CUSTOMIZATION_CFOMAPPING_RESOURCE_NAME + value: cfomapping + - name: CUSTOMIZATION_CFOMAPPING_RESOURCE_PLURAL + value: cfomappings + - name: CUSTOMIZATION_CFO_FALLBACK_ID + value: "" - name: CUSTOMIZATION_NAVIGATION_RESOURCE_NAME + value: navigation + - name: CUSTOMIZATION_NAVIGATION_RESOURCE_PLURAL value: navigations + - name: CUSTOMIZATION_SIDEBAR_FALLBACK_ID + value: stock-project-api-table + - name: CUSTOMIZATION_BREADCRUMBS_FALLBACK_ID + value: stock-project-api-table - name: INSTANCES_API_GROUP value: dashboard.cozystack.io - - name: INSTANCES_RESOURCE_NAME - value: instances - - name: INSTANCES_VERSION + - name: INSTANCES_API_VERSION value: v1alpha1 - - name: MARKETPLACE_GROUP - value: dashboard.cozystack.io + - name: INSTANCES_PLURAL + value: instances - name: MARKETPLACE_KIND value: MarketplacePanel - - name: MARKETPLACE_RESOURCE_NAME + - name: MARKETPLACE_PLURAL value: marketplacepanels - - name: MARKETPLACE_VERSION - value: v1alpha1 - name: NAVIGATE_FROM_CLUSTERLIST value: /openapi-ui/~recordValue~/api-table/core.cozystack.io/v1alpha1/tenantnamespaces - name: PROJECTS_API_GROUP value: core.cozystack.io - - name: PROJECTS_RESOURCE_NAME - value: tenantnamespaces - - name: PROJECTS_VERSION + - name: PROJECTS_API_VERSION value: v1alpha1 + - name: PROJECTS_PLURAL + value: tenantnamespaces - name: CUSTOM_NAMESPACE_API_RESOURCE_API_GROUP value: core.cozystack.io - name: CUSTOM_NAMESPACE_API_RESOURCE_API_VERSION value: v1alpha1 - - name: CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME + - name: CUSTOM_NAMESPACE_API_RESOURCE_PLURAL value: tenantnamespaces + - name: BASE_FACTORY_NAMESPACED_API_KEY + value: base-factory-namespaced-api + - name: BASE_FACTORY_CLUSTERSCOPED_API_KEY + value: base-factory-clusterscoped-api + - name: BASE_FACTORY_NAMESPACED_BUILTIN_KEY + value: base-factory-namespaced-builtin + - name: BASE_FACTORY_CLUSTERSCOPED_BUILTIN_KEY + value: base-factory-clusterscoped-builtin + - name: BASE_NAMESPACE_FACTORY_KEY + value: base-factory-clusterscoped-builtin - name: USE_NAMESPACE_NAV value: "true" + - name: USE_NEW_NAVIGATION + value: "true" + - name: HIDE_NAVIGATION + value: "true" - name: LOGIN_URL value: "/oauth2/userinfo" - name: LOGOUT_URL @@ -225,17 +262,13 @@ spec: type: RuntimeDefault terminationMessagePath: /dev/termination-log terminationMessagePolicy: File - volumeMounts: null dnsPolicy: ClusterFirst enableServiceLinks: false hostIPC: false hostNetwork: false hostPID: false - preemptionPolicy: null priorityClassName: system-cluster-critical restartPolicy: Always - runtimeClassName: null schedulerName: default-scheduler serviceAccountName: incloud-web-web terminationGracePeriodSeconds: 30 - volumes: null From 8e8bea039f79f5b15d7358a74111df23225eb502 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Sun, 15 Feb 2026 21:34:19 +0100 Subject: [PATCH 270/889] feat(vpc): migrate subnets definition from map to array format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change VPC subnets from map[string]Subnet to []Subnet with explicit name field, aligning with the vm-instance subnet format. Map format: subnets: {mysubnet: {cidr: "x"}} Array format: subnets: [{name: mysubnet, cidr: "x"}] Subnet ID generation (sha256 of namespace/vpcId/subnetName) remains unchanged — subnetName now comes from .name field instead of map key. ConfigMap output format stays the same. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- packages/apps/vpc/templates/vpc.yaml | 16 ++++++++-------- packages/apps/vpc/values.schema.json | 13 ++++++++++--- packages/apps/vpc/values.yaml | 9 +++++---- .../cozyrds/virtualprivatecloud.yaml | 2 +- 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/packages/apps/vpc/templates/vpc.yaml b/packages/apps/vpc/templates/vpc.yaml index eb113b4f..6c0d2249 100644 --- a/packages/apps/vpc/templates/vpc.yaml +++ b/packages/apps/vpc/templates/vpc.yaml @@ -16,8 +16,8 @@ spec: namespaces: - {{ .Release.Namespace }} -{{- range $subnetName, $subnetConfig := .Values.subnets }} -{{- $subnetId := print "subnet-" (print $.Release.Namespace "/" $vpcId "/" $subnetName | sha256sum | trunc 8) }} +{{- range .Values.subnets }} +{{- $subnetId := print "subnet-" (print $.Release.Namespace "/" $vpcId "/" .name | sha256sum | trunc 8) }} --- apiVersion: k8s.cni.cncf.io/v1 kind: NetworkAttachmentDefinition @@ -25,7 +25,7 @@ metadata: name: {{ $subnetId }} namespace: {{ $.Release.Namespace }} labels: - cozystack.io/subnetName: {{ $subnetName }} + cozystack.io/subnetName: {{ .name }} cozystack.io/vpcId: {{ $vpcId }} cozystack.io/vpcName: {{ $.Release.Name }} cozystack.io/tenantName: {{ $.Release.Namespace }} @@ -42,13 +42,13 @@ kind: Subnet metadata: name: {{ $subnetId }} labels: - cozystack.io/subnetName: {{ $subnetName }} + cozystack.io/subnetName: {{ .name }} cozystack.io/vpcId: {{ $vpcId }} cozystack.io/vpcName: {{ $.Release.Name }} cozystack.io/tenantName: {{ $.Release.Namespace }} spec: vpc: {{ $vpcId }} - cidrBlock: {{ $subnetConfig.cidr }} + cidrBlock: {{ .cidr }} provider: "{{ $subnetId }}.{{ $.Release.Namespace }}.ovn" protocol: IPv4 enableLb: false @@ -66,9 +66,9 @@ metadata: cozystack.io/vpcId: {{ $vpcId }} cozystack.io/tenantName: {{ $.Release.Namespace }} data: - {{- range $subnetName, $subnetConfig := .Values.subnets }} - {{ $subnetName }}.ID: {{ print "subnet-" (print $.Release.Namespace "/" $vpcId "/" $subnetName | sha256sum | trunc 8) }} - {{ $subnetName }}.CIDR: {{ $subnetConfig.cidr }} + {{- range .Values.subnets }} + {{ .name }}.ID: {{ print "subnet-" (print $.Release.Namespace "/" $vpcId "/" .name | sha256sum | trunc 8) }} + {{ .name }}.CIDR: {{ .cidr }} {{- end }} --- apiVersion: rbac.authorization.k8s.io/v1 diff --git a/packages/apps/vpc/values.schema.json b/packages/apps/vpc/values.schema.json index 3e888de4..a975d2d7 100644 --- a/packages/apps/vpc/values.schema.json +++ b/packages/apps/vpc/values.schema.json @@ -4,14 +4,21 @@ "properties": { "subnets": { "description": "Subnets of a VPC", - "type": "object", - "default": {}, - "additionalProperties": { + "type": "array", + "default": [], + "items": { "type": "object", + "required": [ + "name" + ], "properties": { "cidr": { "description": "IP address range", "type": "string" + }, + "name": { + "description": "Subnet name", + "type": "string" } } } diff --git a/packages/apps/vpc/values.yaml b/packages/apps/vpc/values.yaml index e2bd5faa..3af8b26b 100644 --- a/packages/apps/vpc/values.yaml +++ b/packages/apps/vpc/values.yaml @@ -3,13 +3,14 @@ ## ## @typedef {struct} Subnet - Subnet of a VPC +## @field {string} name - Subnet name ## @field {string} [cidr] - IP address range -## @param {map[string]Subnet} subnets - Subnets of a VPC -subnets: {} +## @param {[]Subnet} subnets - Subnets of a VPC +subnets: [] ## Example: ## subnets: -## mysubnet0: +## - name: mysubnet0 ## cidr: "172.16.0.0/24" -## mysubnet1: +## - name: mysubnet1 ## cidr: "172.16.1.0/24" diff --git a/packages/system/virtualprivatecloud-rd/cozyrds/virtualprivatecloud.yaml b/packages/system/virtualprivatecloud-rd/cozyrds/virtualprivatecloud.yaml index 3f53f984..d5694a2c 100644 --- a/packages/system/virtualprivatecloud-rd/cozyrds/virtualprivatecloud.yaml +++ b/packages/system/virtualprivatecloud-rd/cozyrds/virtualprivatecloud.yaml @@ -8,7 +8,7 @@ spec: plural: virtualprivateclouds singular: virtualprivatecloud openAPISchema: |- - {"title":"Chart Values","type":"object","properties":{"subnets":{"description":"Subnets of a VPC","type":"object","default":{},"additionalProperties":{"type":"object","properties":{"cidr":{"description":"IP address range","type":"string"}}}}}} + {"title":"Chart Values","type":"object","properties":{"subnets":{"description":"Subnets of a VPC","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"cidr":{"description":"IP address range","type":"string"},"name":{"description":"Subnet name","type":"string"}}}}}} release: prefix: "virtualprivatecloud-" labels: From 9031de05380be6b1560dae57c8c2e79441f50c33 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Sun, 15 Feb 2026 21:34:26 +0100 Subject: [PATCH 271/889] feat(platform): add migration 30 for VPC subnets map-to-array conversion Add migration script that converts VPC HelmRelease values from map format to array format. The script discovers all VirtualPrivateCloud HelmReleases, reads their values Secrets, and converts subnets using yq. Idempotent: skips if subnets are already an array or null. Bumps migration targetVersion from 30 to 31. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- .../platform/images/migrations/migrations/30 | 116 ++++++++++++++++++ packages/core/platform/values.yaml | 2 +- 2 files changed, 117 insertions(+), 1 deletion(-) create mode 100755 packages/core/platform/images/migrations/migrations/30 diff --git a/packages/core/platform/images/migrations/migrations/30 b/packages/core/platform/images/migrations/migrations/30 new file mode 100755 index 00000000..64b89305 --- /dev/null +++ b/packages/core/platform/images/migrations/migrations/30 @@ -0,0 +1,116 @@ +#!/bin/bash +# Migration 30 --> 31 +# Convert VPC subnets from map format to array format in HelmRelease values. +# Map format: subnets: {name: {cidr: x}} +# Array format: subnets: [{name: name, cidr: x}] +# Idempotent: skips if subnets is already an array or empty/null. + +set -euo pipefail + +# ============================================================ +# STEP 1: Discover all VirtualPrivateCloud HelmReleases +# ============================================================ +echo "=== Discovering VirtualPrivateCloud HelmReleases ===" +INSTANCES=() +while IFS=/ read -r ns name; do + [ -z "$ns" ] && continue + INSTANCES+=("${ns}/${name}") + echo " Found: ${ns}/${name}" +done < <(kubectl get hr -A -l "apps.cozystack.io/application.kind=VirtualPrivateCloud" \ + -o jsonpath='{range .items[*]}{.metadata.namespace}/{.metadata.name}{"\n"}{end}' 2>/dev/null) + +if [ ${#INSTANCES[@]} -eq 0 ]; then + echo " No VirtualPrivateCloud HelmReleases found. Nothing to migrate." + kubectl create configmap -n cozy-system cozystack-version \ + --from-literal=version=31 --dry-run=client -o yaml | kubectl apply -f- + exit 0 +fi +echo " Total: ${#INSTANCES[@]} instance(s)" + +# ============================================================ +# STEP 2: Migrate each instance +# ============================================================ +for entry in "${INSTANCES[@]}"; do + NAMESPACE="${entry%%/*}" + HR_NAME="${entry#*/}" + + echo "" + echo "======================================================================" + echo "=== Processing: ${HR_NAME} in ${NAMESPACE}" + echo "======================================================================" + + # --- Find values Secret --- + VALUES_SECRET=$(kubectl -n "$NAMESPACE" get hr "$HR_NAME" -o json | \ + jq -r '.spec.valuesFrom // [] | map(select(.kind == "Secret" and (.name | test("cozystack-values") | not))) | .[0].name // ""') + VALUES_KEY=$(kubectl -n "$NAMESPACE" get hr "$HR_NAME" -o json | \ + jq -r '.spec.valuesFrom // [] | map(select(.kind == "Secret" and (.name | test("cozystack-values") | not))) | .[0].valuesKey // "values.yaml"') + + if [ -z "$VALUES_SECRET" ]; then + echo " [SKIP] No values Secret found for hr/${HR_NAME}" + continue + fi + + if ! kubectl -n "$NAMESPACE" get secret "$VALUES_SECRET" --no-headers 2>/dev/null | grep -q .; then + echo " [SKIP] Secret ${VALUES_SECRET} not found" + continue + fi + + echo " Reading values from secret: ${VALUES_SECRET} (key: ${VALUES_KEY})" + + # --- Decode current values --- + VALUES_YAML=$(kubectl -n "$NAMESPACE" get secret "$VALUES_SECRET" \ + -o jsonpath="{.data.${VALUES_KEY}}" 2>/dev/null | base64 -d 2>/dev/null || true) + if [ -z "$VALUES_YAML" ]; then + VALUES_YAML=$(kubectl -n "$NAMESPACE" get secret "$VALUES_SECRET" \ + -o jsonpath="{.stringData.${VALUES_KEY}}" 2>/dev/null || true) + fi + + if [ -z "$VALUES_YAML" ]; then + echo " [SKIP] Could not read values from secret" + continue + fi + + # --- Check subnets type --- + SUBNETS_TYPE=$(echo "$VALUES_YAML" | yq -r '.subnets | type') + + case "$SUBNETS_TYPE" in + "!!map"|"object") + echo " [CONVERT] subnets is a map, converting to array" + ;; + "!!seq"|"array") + echo " [SKIP] subnets is already an array" + continue + ;; + "!!null"|"null") + echo " [SKIP] subnets is null/empty" + continue + ;; + *) + echo " [SKIP] subnets has unexpected type: ${SUBNETS_TYPE}" + continue + ;; + esac + + # --- Convert map to array --- + # {name: {cidr: x}} -> [{name: name, cidr: x}] + NEW_VALUES=$(echo "$VALUES_YAML" | yq ' + .subnets = ([.subnets | to_entries[] | {"name": .key} + .value]) + ') + + # --- Patch the Secret --- + NEW_VALUES_B64=$(echo "$NEW_VALUES" | base64) + echo " [PATCH] Updating secret/${VALUES_SECRET}" + kubectl -n "$NAMESPACE" get secret "$VALUES_SECRET" -o json | \ + jq --arg key "$VALUES_KEY" --arg val "$NEW_VALUES_B64" \ + '.data[$key] = $val' | \ + kubectl apply -f - + + echo " [OK] Converted subnets for ${HR_NAME}" +done + +echo "" +echo "=== Migration complete (${#INSTANCES[@]} instance(s)) ===" + +# Stamp version +kubectl create configmap -n cozy-system cozystack-version \ + --from-literal=version=31 --dry-run=client -o yaml | kubectl apply -f- diff --git a/packages/core/platform/values.yaml b/packages/core/platform/values.yaml index b90c001b..74758eb0 100644 --- a/packages/core/platform/values.yaml +++ b/packages/core/platform/values.yaml @@ -6,7 +6,7 @@ sourceRef: migrations: enabled: false image: ghcr.io/cozystack/cozystack/platform-migrations:v1.0.0-beta.4@sha256:f404d05834907b9b2695bbb37732bd1ae2e79b03da77dc91bb3fbc0fbc53dcbf - targetVersion: 30 + targetVersion: 31 # Bundle deployment configuration bundles: system: From cf505c580d26ceea10b53c2df76fd2b2357a0115 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Sun, 15 Feb 2026 22:50:24 +0100 Subject: [PATCH 272/889] Update kilo v0.8.0 Signed-off-by: Andrei Kvapil --- packages/system/kilo/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/system/kilo/values.yaml b/packages/system/kilo/values.yaml index 762f3c26..e648e32e 100644 --- a/packages/system/kilo/values.yaml +++ b/packages/system/kilo/values.yaml @@ -1,7 +1,7 @@ kilo: image: pullPolicy: IfNotPresent - tag: v0.7.1@sha256:c2160097ef3aa4e9e460430665b743a14b8f19480211edab6f946264718340ff + tag: v0.8.0@sha256:d673c46ba0ff762bc31491f53b81c7e5f59b6b58560eb258df7cf55e480ebdd4 repository: ghcr.io/cozystack/cozystack/kilo podCIDR: 10.244.0.0/16 serviceCIDR: 10.96.0.0/16 From ef040c2ed283ff38022727782fef0652b50b84e8 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Mon, 16 Feb 2026 15:16:38 +0100 Subject: [PATCH 273/889] feat(kilo): add Cilium compatibility variant Add a new "cilium" variant to the kilo PackageSource that deploys kilo with --compatibility=cilium flag. This enables Cilium-aware IPIP encapsulation, routing outer packets through Cilium's VxLAN overlay instead of the host network. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- packages/core/platform/sources/kilo.yaml | 13 +++++++++++++ packages/system/kilo/templates/kilo.yaml | 3 +++ packages/system/kilo/values-cilium.yaml | 2 ++ packages/system/kilo/values.yaml | 2 +- 4 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 packages/system/kilo/values-cilium.yaml diff --git a/packages/core/platform/sources/kilo.yaml b/packages/core/platform/sources/kilo.yaml index 72602164..95770667 100644 --- a/packages/core/platform/sources/kilo.yaml +++ b/packages/core/platform/sources/kilo.yaml @@ -20,3 +20,16 @@ spec: privileged: true namespace: cozy-kilo releaseName: kilo + - name: cilium + dependsOn: + - cozystack.networking + components: + - name: kilo + path: system/kilo + valuesFiles: + - values.yaml + - values-cilium.yaml + install: + privileged: true + namespace: cozy-kilo + releaseName: kilo diff --git a/packages/system/kilo/templates/kilo.yaml b/packages/system/kilo/templates/kilo.yaml index e209aed2..fef7a718 100644 --- a/packages/system/kilo/templates/kilo.yaml +++ b/packages/system/kilo/templates/kilo.yaml @@ -29,6 +29,9 @@ spec: - --hostname=$(NODE_NAME) - --cni=false - --clean-up-interface={{ .Values.kilo.cleanUpInterface | default "false" }} + {{- with .Values.kilo.compatibility }} + - --compatibility={{ . }} + {{- end }} - --encapsulate=crosssubnet - --local=false - --mesh-granularity={{ .Values.kilo.meshGranularity | default "location" }} diff --git a/packages/system/kilo/values-cilium.yaml b/packages/system/kilo/values-cilium.yaml new file mode 100644 index 00000000..1a75fc41 --- /dev/null +++ b/packages/system/kilo/values-cilium.yaml @@ -0,0 +1,2 @@ +kilo: + compatibility: cilium diff --git a/packages/system/kilo/values.yaml b/packages/system/kilo/values.yaml index 762f3c26..4057c284 100644 --- a/packages/system/kilo/values.yaml +++ b/packages/system/kilo/values.yaml @@ -1,7 +1,7 @@ kilo: image: pullPolicy: IfNotPresent - tag: v0.7.1@sha256:c2160097ef3aa4e9e460430665b743a14b8f19480211edab6f946264718340ff + tag: v0.8.1@sha256:56602fa796eccea0d0e005c29e5c7094ae46d15bd5306b02b829672b178dcd5c repository: ghcr.io/cozystack/cozystack/kilo podCIDR: 10.244.0.0/16 serviceCIDR: 10.96.0.0/16 From 7ca6e5ce9e4ea49a73b28ae86ab46bc38dafd0fa Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Mon, 16 Feb 2026 18:47:04 +0300 Subject: [PATCH 274/889] feat(installer): add variant-aware templates for generic Kubernetes support Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- Makefile | 5 +++-- packages/core/installer/.helmignore | 11 +++++++++++ packages/core/installer/Makefile | 8 +++++++- .../installer/templates/cozystack-operator.yaml | 15 +++------------ .../core/installer/templates/packagesource.yaml | 4 ++++ packages/core/installer/values.yaml | 10 ++++++++++ 6 files changed, 38 insertions(+), 15 deletions(-) create mode 100644 packages/core/installer/.helmignore diff --git a/Makefile b/Makefile index e6c50458..12554861 100644 --- a/Makefile +++ b/Makefile @@ -48,15 +48,16 @@ manifests: > _out/assets/cozystack-operator-talos.yaml # Generic Kubernetes variant (k3s, kubeadm, RKE2) helm template installer packages/core/installer -n cozy-system \ + --set cozystackOperator.variant=generic \ + --set cozystack.apiServerHost=REPLACE_ME \ -s templates/cozystack-operator.yaml \ -s templates/packagesource.yaml \ - --set cozystackOperator.variant=generic \ > _out/assets/cozystack-operator-generic.yaml # Hosted variant (managed Kubernetes) helm template installer packages/core/installer -n cozy-system \ + --set cozystackOperator.variant=hosted \ -s templates/cozystack-operator.yaml \ -s templates/packagesource.yaml \ - --set cozystackOperator.variant=hosted \ > _out/assets/cozystack-operator-hosted.yaml cozypkg: diff --git a/packages/core/installer/.helmignore b/packages/core/installer/.helmignore new file mode 100644 index 00000000..ba88ffe8 --- /dev/null +++ b/packages/core/installer/.helmignore @@ -0,0 +1,11 @@ +# VCS and IDE +.git +.gitignore + +# Build artifacts +Makefile +images/ +example/ +*.tgz + +# NOTE: definitions/ is intentionally NOT excluded — needed by crds.yaml via .Files.Glob diff --git a/packages/core/installer/Makefile b/packages/core/installer/Makefile index ad9982b5..7038bb9c 100644 --- a/packages/core/installer/Makefile +++ b/packages/core/installer/Makefile @@ -15,7 +15,7 @@ apply: diff: cozyhr show --namespace $(NAMESPACE) $(NAME) --plain | kubectl diff -f - -image: pre-checks image-operator image-packages +image: pre-checks image-operator image-packages chart image-operator: docker buildx build -f images/cozystack-operator/Dockerfile ../../.. \ @@ -43,3 +43,9 @@ image-packages: test -n "$$DIGEST" && \ yq -i '.cozystackOperator.platformSourceUrl = strenv(REPO)' values.yaml && \ yq -i '.cozystackOperator.platformSourceRef = "digest=" + strenv(DIGEST)' values.yaml + +chart: + set -e; \ + PKG=$$(helm package . --version $(COZYSTACK_VERSION) | awk '{print $$NF}'); \ + trap 'rm -f "$$PKG"' EXIT; \ + if [ "$(PUSH)" = "1" ]; then helm push "$$PKG" oci://$(REGISTRY); fi diff --git a/packages/core/installer/templates/cozystack-operator.yaml b/packages/core/installer/templates/cozystack-operator.yaml index 96ce9b87..4568e18b 100644 --- a/packages/core/installer/templates/cozystack-operator.yaml +++ b/packages/core/installer/templates/cozystack-operator.yaml @@ -72,20 +72,11 @@ spec: value: "7445" {{- else if eq .Values.cozystackOperator.variant "generic" }} env: - # Generic Kubernetes: read from ConfigMap - # Create cozystack-operator-config ConfigMap before applying this manifest + # Generic Kubernetes: API server endpoint - name: KUBERNETES_SERVICE_HOST - valueFrom: - configMapKeyRef: - name: cozystack-operator-config - key: KUBERNETES_SERVICE_HOST - optional: false + value: {{ required "cozystack.apiServerHost is required in generic mode" .Values.cozystack.apiServerHost | quote }} - name: KUBERNETES_SERVICE_PORT - valueFrom: - configMapKeyRef: - name: cozystack-operator-config - key: KUBERNETES_SERVICE_PORT - optional: false + value: {{ .Values.cozystack.apiServerPort | quote }} {{- else if eq .Values.cozystackOperator.variant "hosted" }} # Hosted: use in-cluster service account, no env override needed env: [] diff --git a/packages/core/installer/templates/packagesource.yaml b/packages/core/installer/templates/packagesource.yaml index f4a5f6d0..65b1e4bf 100644 --- a/packages/core/installer/templates/packagesource.yaml +++ b/packages/core/installer/templates/packagesource.yaml @@ -1,3 +1,7 @@ +{{- $validVariants := list "talos" "generic" "hosted" -}} +{{- if not (has .Values.cozystackOperator.variant $validVariants) -}} +{{- fail (printf "Invalid cozystackOperator.variant %q: must be one of talos, generic, hosted" .Values.cozystackOperator.variant) -}} +{{- end -}} --- apiVersion: cozystack.io/v1alpha1 kind: PackageSource diff --git a/packages/core/installer/values.yaml b/packages/core/installer/values.yaml index 64ff817b..95633e8e 100644 --- a/packages/core/installer/values.yaml +++ b/packages/core/installer/values.yaml @@ -4,3 +4,13 @@ cozystackOperator: image: ghcr.io/cozystack/cozystack/cozystack-operator:v1.0.0-beta.4@sha256:322dd7358df369525f76e6e43512482e38caec5315d36a878399d2d60bf2f18d platformSourceUrl: 'oci://ghcr.io/cozystack/cozystack/cozystack-packages' platformSourceRef: 'digest=sha256:b88502242b535a31ab33c06ffc0a96d1c67230d2db2c5e873fa23f6523592ff6' + +# Generic variant configuration (only used when cozystackOperator.variant=generic) +cozystack: + # Kubernetes API server host (IP only, no protocol/port) + # Must be the INTERNAL IP of the control-plane node + # (the IP visible on the node's network interface, not a public/NAT IP) + # Used by the operator and networking components (cilium, kube-ovn) + apiServerHost: "" + # Kubernetes API server port + apiServerPort: "6443" From bae70596fc0e121afa31d5d9a8e35961e430cf17 Mon Sep 17 00:00:00 2001 From: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com> Date: Mon, 16 Feb 2026 16:01:30 +0000 Subject: [PATCH 275/889] Prepare release v1.0.0-beta.5 Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com> --- packages/core/installer/values.yaml | 5 ++--- packages/core/platform/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/backup-controller/values.yaml | 2 +- packages/system/backupstrategy-controller/values.yaml | 2 +- packages/system/cozystack-api/values.yaml | 2 +- packages/system/cozystack-controller/values.yaml | 2 +- packages/system/dashboard/templates/configmap.yaml | 2 +- packages/system/dashboard/values.yaml | 6 +++--- .../system/grafana-operator/images/grafana-dashboards.tag | 2 +- packages/system/kamaji/values.yaml | 4 ++-- packages/system/kubeovn-plunger/values.yaml | 2 +- packages/system/kubeovn-webhook/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(+), 23 deletions(-) diff --git a/packages/core/installer/values.yaml b/packages/core/installer/values.yaml index 95633e8e..3dd0f1d6 100644 --- a/packages/core/installer/values.yaml +++ b/packages/core/installer/values.yaml @@ -1,10 +1,9 @@ cozystackOperator: # Deployment variant: talos, generic, hosted variant: talos - image: ghcr.io/cozystack/cozystack/cozystack-operator:v1.0.0-beta.4@sha256:322dd7358df369525f76e6e43512482e38caec5315d36a878399d2d60bf2f18d + image: ghcr.io/cozystack/cozystack/cozystack-operator:v1.0.0-beta.5@sha256:9f3089cb13b3e19dab14b8edc1220efde693f6066d3474c0137953e1873d16f3 platformSourceUrl: 'oci://ghcr.io/cozystack/cozystack/cozystack-packages' - platformSourceRef: 'digest=sha256:b88502242b535a31ab33c06ffc0a96d1c67230d2db2c5e873fa23f6523592ff6' - + platformSourceRef: 'digest=sha256:86daf23f7b2ff2f448b273153733c55ac2f57c2bbe72c779ff14865d71e623cb' # Generic variant configuration (only used when cozystackOperator.variant=generic) cozystack: # Kubernetes API server host (IP only, no protocol/port) diff --git a/packages/core/platform/values.yaml b/packages/core/platform/values.yaml index 74758eb0..ca2d9d1b 100644 --- a/packages/core/platform/values.yaml +++ b/packages/core/platform/values.yaml @@ -5,7 +5,7 @@ sourceRef: path: / migrations: enabled: false - image: ghcr.io/cozystack/cozystack/platform-migrations:v1.0.0-beta.4@sha256:f404d05834907b9b2695bbb37732bd1ae2e79b03da77dc91bb3fbc0fbc53dcbf + image: ghcr.io/cozystack/cozystack/platform-migrations:v1.0.0-beta.5@sha256:e7a9e0f0adc33e0be007af42f50ed3af064aa965ad628e48cc6c7943f31f239d targetVersion: 31 # Bundle deployment configuration bundles: diff --git a/packages/core/testing/values.yaml b/packages/core/testing/values.yaml index 73481ab1..627d3096 100644 --- a/packages/core/testing/values.yaml +++ b/packages/core/testing/values.yaml @@ -1,2 +1,2 @@ e2e: - image: ghcr.io/cozystack/cozystack/e2e-sandbox:v1.0.0-beta.4@sha256:eac71ef0de3450fce96255629e77903630c63ade62b81e7055f1a689f92ee153 + image: ghcr.io/cozystack/cozystack/e2e-sandbox:v1.0.0-beta.5@sha256:eac71ef0de3450fce96255629e77903630c63ade62b81e7055f1a689f92ee153 diff --git a/packages/extra/bootbox/images/matchbox.tag b/packages/extra/bootbox/images/matchbox.tag index 6c3054d5..19014ae6 100644 --- a/packages/extra/bootbox/images/matchbox.tag +++ b/packages/extra/bootbox/images/matchbox.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/matchbox:v1.0.0-beta.4@sha256:cba9a2761c40ae6645e28da4cbc6d91eb86ddc886f4523cf20b3c0e40597d593 +ghcr.io/cozystack/cozystack/matchbox:v1.0.0-beta.5@sha256:8facb6bbbaf336ff3dd606d6bcbf65f5d1fb7f079bec09966544398632005b47 diff --git a/packages/extra/seaweedfs/images/objectstorage-sidecar.tag b/packages/extra/seaweedfs/images/objectstorage-sidecar.tag index bd622108..9f5222a7 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:v1.0.0-beta.4@sha256:235b194a531b70e266a10ef78d2955d19f5b659513f23d8b3cfbbc0dff7fc1c0 +ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.0.0-beta.5@sha256:235b194a531b70e266a10ef78d2955d19f5b659513f23d8b3cfbbc0dff7fc1c0 diff --git a/packages/system/backup-controller/values.yaml b/packages/system/backup-controller/values.yaml index 1bdab88b..63c49ed1 100644 --- a/packages/system/backup-controller/values.yaml +++ b/packages/system/backup-controller/values.yaml @@ -1,5 +1,5 @@ backupController: - image: "ghcr.io/cozystack/cozystack/backup-controller:v1.0.0-beta.4@sha256:2dcd5347683ee88012b0e558b3a240dec9942230e9c673a359e0196f407a0833" + image: "ghcr.io/cozystack/cozystack/backup-controller:v1.0.0-beta.5@sha256:c99ff8ab11c5c016841acd9dca72c7a643dba72a98b8f816ea67ba7fa3549f88" replicas: 2 debug: false metrics: diff --git a/packages/system/backupstrategy-controller/values.yaml b/packages/system/backupstrategy-controller/values.yaml index 21d1a9f3..5ff8efd5 100644 --- a/packages/system/backupstrategy-controller/values.yaml +++ b/packages/system/backupstrategy-controller/values.yaml @@ -1,5 +1,5 @@ backupStrategyController: - image: "ghcr.io/cozystack/cozystack/backupstrategy-controller:v1.0.0-beta.4@sha256:c2c150918e5609f1d6663f56b6dcbdac47bee33154524230001fcd04165f4268" + image: "ghcr.io/cozystack/cozystack/backupstrategy-controller:v1.0.0-beta.5@sha256:cd92a2620c9965512977b57c2829d931e7ecb7a8afc0693d7c8ab39bd8ff77d8" replicas: 2 debug: false metrics: diff --git a/packages/system/cozystack-api/values.yaml b/packages/system/cozystack-api/values.yaml index d9041546..511d1f33 100644 --- a/packages/system/cozystack-api/values.yaml +++ b/packages/system/cozystack-api/values.yaml @@ -1,3 +1,3 @@ cozystackAPI: - image: ghcr.io/cozystack/cozystack/cozystack-api:v1.0.0-beta.4@sha256:42f8d8120f7fd3bfe37f114eedf5ca8df62ac69a0d922d91a2c392772f3b46ee + image: ghcr.io/cozystack/cozystack/cozystack-api:v1.0.0-beta.5@sha256:b217c3d1cca7e35b9e6ee32297ebe923fee12ca48d94062484a9dfcffda0e2e3 replicas: 2 diff --git a/packages/system/cozystack-controller/values.yaml b/packages/system/cozystack-controller/values.yaml index 53ed1033..5ead5eae 100644 --- a/packages/system/cozystack-controller/values.yaml +++ b/packages/system/cozystack-controller/values.yaml @@ -1,4 +1,4 @@ cozystackController: - image: ghcr.io/cozystack/cozystack/cozystack-controller:v1.0.0-beta.4@sha256:915d07ef61e1fc3bdf87e4bfc4b8ae3920e7e33d74082778c7735ba36a08cdef + image: ghcr.io/cozystack/cozystack/cozystack-controller:v1.0.0-beta.5@sha256:edeb1788395d650f5f14a935c8f6b95ede7ca548cba198c18c75cc5eafd89104 debug: false disableTelemetry: false diff --git a/packages/system/dashboard/templates/configmap.yaml b/packages/system/dashboard/templates/configmap.yaml index 1c79b2cb..c32fc638 100644 --- a/packages/system/dashboard/templates/configmap.yaml +++ b/packages/system/dashboard/templates/configmap.yaml @@ -1,6 +1,6 @@ {{- $brandingConfig := .Values._cluster.branding | default dict }} -{{- $tenantText := "v1.0.0-beta.4" }} +{{- $tenantText := "v1.0.0-beta.5" }} {{- $footerText := "Cozystack" }} {{- $titleText := "Cozystack Dashboard" }} {{- $logoText := "" }} diff --git a/packages/system/dashboard/values.yaml b/packages/system/dashboard/values.yaml index 0c898a91..a2f9c642 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:v1.0.0-beta.4@sha256:adbd07c7bde083fbf3a2fb2625ec4adbe6718a0f6f643b2505cc0029c2c0f724 + image: ghcr.io/cozystack/cozystack/openapi-ui:v1.0.0-beta.5@sha256:0d331986ac06de1fa5b660d69670cba0f2a59fcdab6cf43cb136827ecb8fcfe8 openapiUIK8sBff: - image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v1.0.0-beta.4@sha256:1f7827a1978bd9c81ac924dd0e78f6a3ce834a9a64af55047e220812bc15a944 + image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v1.0.0-beta.5@sha256:1b3ea6d4c7dbbe6a8def3b2807fffdfab2ac4afc39d7a846e57dd491fa168f92 tokenProxy: - image: ghcr.io/cozystack/cozystack/token-proxy:v1.0.0-beta.4@sha256:2e280991e07853ea48f97b0a42946afffa10d03d6a83d41099ed83e6ffc94fdc + image: ghcr.io/cozystack/cozystack/token-proxy:v1.0.0-beta.5@sha256:2e280991e07853ea48f97b0a42946afffa10d03d6a83d41099ed83e6ffc94fdc diff --git a/packages/system/grafana-operator/images/grafana-dashboards.tag b/packages/system/grafana-operator/images/grafana-dashboards.tag index cc81b24f..34111c9f 100644 --- a/packages/system/grafana-operator/images/grafana-dashboards.tag +++ b/packages/system/grafana-operator/images/grafana-dashboards.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/grafana-dashboards:v1.0.0-beta.4@sha256:e866b5b3874b9d390b341183d2ee070e1387440c14cfe51af831695def6dc2ec +ghcr.io/cozystack/cozystack/grafana-dashboards:v1.0.0-beta.5@sha256:e866b5b3874b9d390b341183d2ee070e1387440c14cfe51af831695def6dc2ec diff --git a/packages/system/kamaji/values.yaml b/packages/system/kamaji/values.yaml index 26e8bfcf..cbf71457 100644 --- a/packages/system/kamaji/values.yaml +++ b/packages/system/kamaji/values.yaml @@ -3,7 +3,7 @@ kamaji: deploy: false image: pullPolicy: IfNotPresent - tag: v1.0.0-beta.4@sha256:05f8e166dc94aadb74cd6448f6f96fbd2167057a559097516a41b8f53e434918 + tag: v1.0.0-beta.5@sha256:05f8e166dc94aadb74cd6448f6f96fbd2167057a559097516a41b8f53e434918 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:v1.0.0-beta.4@sha256:05f8e166dc94aadb74cd6448f6f96fbd2167057a559097516a41b8f53e434918 + - --migrate-image=ghcr.io/cozystack/cozystack/kamaji:v1.0.0-beta.5@sha256:05f8e166dc94aadb74cd6448f6f96fbd2167057a559097516a41b8f53e434918 diff --git a/packages/system/kubeovn-plunger/values.yaml b/packages/system/kubeovn-plunger/values.yaml index b325a7b7..0abcc7db 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:v1.0.0-beta.4@sha256:16b362d6fa1ca30c791ea5cfc7984e085b9ea76a24c9abb3b05dad5c8b64bd0e +image: ghcr.io/cozystack/cozystack/kubeovn-plunger:v1.0.0-beta.5@sha256:67d615f86c3230e8c643b6bd347093cdb1e575c99f7c63599fd43e0c4ffc249a ovnCentralName: ovn-central diff --git a/packages/system/kubeovn-webhook/values.yaml b/packages/system/kubeovn-webhook/values.yaml index d5d8ba93..26613989 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:v1.0.0-beta.4@sha256:e18f9fd679e38f65362a8d0042f25468272f6d081136ad47027168d8e7e07a4a +image: ghcr.io/cozystack/cozystack/kubeovn-webhook:v1.0.0-beta.5@sha256:e6334c29d3aaf0dea766c88e3e05b53ad623d1bb497b3c836e6f76adade45b29 diff --git a/packages/system/lineage-controller-webhook/values.yaml b/packages/system/lineage-controller-webhook/values.yaml index 00e593eb..d5f28f89 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:v1.0.0-beta.4@sha256:e9054f9137fd73039b2d91a979f672c9258336f91e51440a77aaa09e6e0b5254 + image: ghcr.io/cozystack/cozystack/lineage-controller-webhook:v1.0.0-beta.5@sha256:9dd5411d222fe7d7b0583a8e6807bf7745eb1412bf180827c5f1957e6ebbfeb4 debug: false localK8sAPIEndpoint: enabled: true diff --git a/packages/system/objectstorage-controller/values.yaml b/packages/system/objectstorage-controller/values.yaml index 18dda28f..b3c9d4c6 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:v1.0.0-beta.4@sha256:b4c972769afda76c48b58e7acf0ac66a0abf16a622f245c60338f432872f640a" + image: "ghcr.io/cozystack/cozystack/objectstorage-controller:v1.0.0-beta.5@sha256:b4c972769afda76c48b58e7acf0ac66a0abf16a622f245c60338f432872f640a" diff --git a/packages/system/seaweedfs/values.yaml b/packages/system/seaweedfs/values.yaml index 63199e28..94aba339 100644 --- a/packages/system/seaweedfs/values.yaml +++ b/packages/system/seaweedfs/values.yaml @@ -177,7 +177,7 @@ seaweedfs: bucketClassName: "seaweedfs" region: "" sidecar: - image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.0.0-beta.4@sha256:235b194a531b70e266a10ef78d2955d19f5b659513f23d8b3cfbbc0dff7fc1c0" + image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.0.0-beta.5@sha256:235b194a531b70e266a10ef78d2955d19f5b659513f23d8b3cfbbc0dff7fc1c0" certificates: commonName: "SeaweedFS CA" ipAddresses: [] From bfba9fb5e70fa2cb33f2ceae1d318ea5780a3f5e Mon Sep 17 00:00:00 2001 From: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com> Date: Mon, 16 Feb 2026 16:08:18 +0000 Subject: [PATCH 276/889] docs: add changelog for v1.0.0-beta.5 Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com> --- docs/changelogs/v1.0.0-beta.5.md | 36 ++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 docs/changelogs/v1.0.0-beta.5.md diff --git a/docs/changelogs/v1.0.0-beta.5.md b/docs/changelogs/v1.0.0-beta.5.md new file mode 100644 index 00000000..b8bc306c --- /dev/null +++ b/docs/changelogs/v1.0.0-beta.5.md @@ -0,0 +1,36 @@ + + +> **⚠️ Beta Release Warning**: This is a pre-release version intended for testing and early adoption. Breaking changes may occur before the stable v1.0.0 release. + +## Features and Improvements + +* **[installer] Add variant-aware templates for generic Kubernetes support**: Extended the installer chart to support generic and hosted Kubernetes deployments via the existing `cozystackOperator.variant` parameter. When using `variant=generic`, the installer now renders separate templates for the Cozystack operator, skipping Talos-specific components. This enables users to deploy Cozystack on standard Kubernetes distributions and hosted Kubernetes services, expanding platform compatibility beyond Talos Linux ([**@lexfrei**](https://github.com/lexfrei) in #2010). + +* **[kilo] Add Cilium compatibility variant**: Added a new `cilium` variant to the kilo PackageSource that deploys kilo with the `--compatibility=cilium` flag. This enables Cilium-aware IPIP encapsulation where the outer packet IP matches the inner packet source, allowing Cilium's network policies to function correctly with kilo's WireGuard mesh networking. Users can now run kilo alongside Cilium CNI while maintaining full network policy enforcement capabilities ([**@kvaps**](https://github.com/kvaps) in #2055). + +* **[cluster-autoscaler] Enable enforce-node-group-min-size by default**: Enabled the `enforce-node-group-min-size` option for the system cluster-autoscaler chart. This ensures node groups are always scaled up to their configured minimum size, even when current workload demands are lower, preventing unexpected scale-down below minimum thresholds and improving cluster stability for production workloads ([**@kvaps**](https://github.com/kvaps) in #2050). + +* **[dashboard] Upgrade dashboard to version 1.4.0**: Updated the Cozystack dashboard to version 1.4.0 with new features and improvements for better user experience and cluster management capabilities ([**@sircthulhu**](https://github.com/sircthulhu) in #2051). + +## Breaking Changes & Upgrade Notes + +* **[vpc] Migrate subnets definition from map to array format**: Migrated VPC subnets definition from map format (`map[string]Subnet`) to array format (`[]Subnet`) with an explicit `name` field. This aligns VPC subnet definitions with the vm-instance `networks` field pattern and provides more intuitive configuration. Existing VPC deployments are automatically migrated via migration 30, which converts the subnet map to an array while preserving all existing subnet configurations and network connectivity ([**@kvaps**](https://github.com/kvaps) in #2052). + +## Dependencies + +* **[kilo] Update to v0.8.0**: Updated Kilo WireGuard mesh networking to v0.8.0 with performance improvements, bug fixes, and new compatibility features ([**@kvaps**](https://github.com/kvaps) in #2053). + +* **[talm] Skip config loading for __complete command**: Fixed CLI completion behavior by skipping config loading for the `__complete` command, preventing errors during shell completion when configuration files are not available or misconfigured ([**@kitsunoff**](https://github.com/kitsunoff) in cozystack/talm#109). + +## Contributors + +We'd like to thank all contributors who made this release possible: + +* [**@kitsunoff**](https://github.com/kitsunoff) +* [**@kvaps**](https://github.com/kvaps) +* [**@lexfrei**](https://github.com/lexfrei) +* [**@sircthulhu**](https://github.com/sircthulhu) + +**Full Changelog**: https://github.com/cozystack/cozystack/compare/v1.0.0-beta.4...v1.0.0-beta.5 From 73b8946a7e2c398f22bb02342788ee6cf64831e6 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Mon, 16 Feb 2026 23:01:22 +0300 Subject: [PATCH 277/889] chore(codegen): regenerate stale deepcopy and CRD definitions Run make generate to bring generated files up to date with current API types. This was pre-existing staleness unrelated to any code change. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- .../v1alpha1/zz_generated.deepcopy.go | 118 +++++++++--------- .../backups.cozystack.io_backupclasses.yaml | 2 +- 2 files changed, 60 insertions(+), 60 deletions(-) diff --git a/api/dashboard/v1alpha1/zz_generated.deepcopy.go b/api/dashboard/v1alpha1/zz_generated.deepcopy.go index 568a1780..25d97a79 100644 --- a/api/dashboard/v1alpha1/zz_generated.deepcopy.go +++ b/api/dashboard/v1alpha1/zz_generated.deepcopy.go @@ -159,6 +159,65 @@ func (in *BreadcrumbList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CFOMapping) DeepCopyInto(out *CFOMapping) { + *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 CFOMapping. +func (in *CFOMapping) DeepCopy() *CFOMapping { + if in == nil { + return nil + } + out := new(CFOMapping) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CFOMapping) 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 *CFOMappingList) DeepCopyInto(out *CFOMappingList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]CFOMapping, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CFOMappingList. +func (in *CFOMappingList) DeepCopy() *CFOMappingList { + if in == nil { + return nil + } + out := new(CFOMappingList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CFOMappingList) 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 *CommonStatus) DeepCopyInto(out *CommonStatus) { *out = *in @@ -417,65 +476,6 @@ func (in *FactoryList) DeepCopyObject() runtime.Object { return nil } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CFOMapping) DeepCopyInto(out *CFOMapping) { - *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 CFOMapping. -func (in *CFOMapping) DeepCopy() *CFOMapping { - if in == nil { - return nil - } - out := new(CFOMapping) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *CFOMapping) 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 *CFOMappingList) DeepCopyInto(out *CFOMappingList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]CFOMapping, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CFOMappingList. -func (in *CFOMappingList) DeepCopy() *CFOMappingList { - if in == nil { - return nil - } - out := new(CFOMappingList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *CFOMappingList) 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 *MarketplacePanel) DeepCopyInto(out *MarketplacePanel) { *out = *in diff --git a/packages/system/backup-controller/definitions/backups.cozystack.io_backupclasses.yaml b/packages/system/backup-controller/definitions/backups.cozystack.io_backupclasses.yaml index b0f0fec2..4af8cf7d 100644 --- a/packages/system/backup-controller/definitions/backups.cozystack.io_backupclasses.yaml +++ b/packages/system/backup-controller/definitions/backups.cozystack.io_backupclasses.yaml @@ -60,7 +60,7 @@ spec: type: string kind: description: Kind is the kind of the application (e.g., - VirtualMachine, MySQL). + VirtualMachine, MariaDB). type: string required: - kind From 75e25fa9779b36bbd219f9318d072c6c525a0d1f Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Mon, 16 Feb 2026 23:01:38 +0300 Subject: [PATCH 278/889] fix(codegen): add gen_client to update-codegen.sh and regenerate applyconfiguration The applyconfiguration code referenced testing.TypeConverter from k8s.io/client-go/testing, which was removed in client-go v0.34.1. Root cause: hack/update-codegen.sh called gen_helpers and gen_openapi but not gen_client, so applyconfiguration was never regenerated after the client-go upgrade. Changes: - Fix THIS_PKG from sample-apiserver template leftover to correct module path - Add kube::codegen::gen_client call with --with-applyconfig flag - Regenerate applyconfiguration (now uses managedfields.TypeConverter) - Add tests for ForKind and NewTypeConverter functions Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- go.mod | 3 +- go.sum | 5 - hack/update-codegen.sh | 9 +- .../apps/v1alpha1/application.go | 42 ++++-- .../apps/v1alpha1/applicationspec.go | 48 ------- .../apps/v1alpha1/applicationstatus.go | 75 ++++++++++ .../applyconfiguration/internal/internal.go | 4 +- pkg/generated/applyconfiguration/utils.go | 18 +-- .../applyconfiguration/utils_test.go | 64 +++++++++ .../clientset/versioned/clientset.go | 120 ++++++++++++++++ .../versioned/fake/clientset_generated.go | 131 ++++++++++++++++++ pkg/generated/clientset/versioned/fake/doc.go | 20 +++ .../clientset/versioned/fake/register.go | 56 ++++++++ .../clientset/versioned/scheme/doc.go | 20 +++ .../clientset/versioned/scheme/register.go | 56 ++++++++ .../typed/apps/v1alpha1/application.go | 74 ++++++++++ .../typed/apps/v1alpha1/apps_client.go | 101 ++++++++++++++ .../versioned/typed/apps/v1alpha1/doc.go | 20 +++ .../versioned/typed/apps/v1alpha1/fake/doc.go | 20 +++ .../apps/v1alpha1/fake/fake_application.go | 53 +++++++ .../apps/v1alpha1/fake/fake_apps_client.go | 40 ++++++ .../apps/v1alpha1/generated_expansion.go | 21 +++ 22 files changed, 925 insertions(+), 75 deletions(-) delete mode 100644 pkg/generated/applyconfiguration/apps/v1alpha1/applicationspec.go create mode 100644 pkg/generated/applyconfiguration/apps/v1alpha1/applicationstatus.go create mode 100644 pkg/generated/applyconfiguration/utils_test.go create mode 100644 pkg/generated/clientset/versioned/clientset.go create mode 100644 pkg/generated/clientset/versioned/fake/clientset_generated.go create mode 100644 pkg/generated/clientset/versioned/fake/doc.go create mode 100644 pkg/generated/clientset/versioned/fake/register.go create mode 100644 pkg/generated/clientset/versioned/scheme/doc.go create mode 100644 pkg/generated/clientset/versioned/scheme/register.go create mode 100644 pkg/generated/clientset/versioned/typed/apps/v1alpha1/application.go create mode 100644 pkg/generated/clientset/versioned/typed/apps/v1alpha1/apps_client.go create mode 100644 pkg/generated/clientset/versioned/typed/apps/v1alpha1/doc.go create mode 100644 pkg/generated/clientset/versioned/typed/apps/v1alpha1/fake/doc.go create mode 100644 pkg/generated/clientset/versioned/typed/apps/v1alpha1/fake/fake_application.go create mode 100644 pkg/generated/clientset/versioned/typed/apps/v1alpha1/fake/fake_apps_client.go create mode 100644 pkg/generated/clientset/versioned/typed/apps/v1alpha1/generated_expansion.go diff --git a/go.mod b/go.mod index 9944df16..3ecda336 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d sigs.k8s.io/controller-runtime v0.22.4 - sigs.k8s.io/structured-merge-diff/v4 v4.7.0 + sigs.k8s.io/structured-merge-diff/v6 v6.3.0 ) require ( @@ -125,7 +125,6 @@ require ( sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // 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 ) diff --git a/go.sum b/go.sum index d2cbc779..0a69ac87 100644 --- a/go.sum +++ b/go.sum @@ -81,7 +81,6 @@ 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.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= @@ -324,13 +323,9 @@ sigs.k8s.io/controller-runtime v0.22.4 h1:GEjV7KV3TY8e+tJ2LCTxUTanW4z/FmNB7l327U sigs.k8s.io/controller-runtime v0.22.4/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/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.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI= -sigs.k8s.io/structured-merge-diff/v4 v4.7.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/hack/update-codegen.sh b/hack/update-codegen.sh index b97c2ade..293f229b 100755 --- a/hack/update-codegen.sh +++ b/hack/update-codegen.sh @@ -34,7 +34,7 @@ trap 'rm -rf ${TMPDIR}' EXIT source "${CODEGEN_PKG}/kube_codegen.sh" -THIS_PKG="k8s.io/sample-apiserver" +THIS_PKG="github.com/cozystack/cozystack" kube::codegen::gen_helpers \ --boilerplate "${SCRIPT_ROOT}/hack/boilerplate.go.txt" \ @@ -60,6 +60,13 @@ kube::codegen::gen_openapi \ --boilerplate "${SCRIPT_ROOT}/hack/boilerplate.go.txt" \ "${SCRIPT_ROOT}/pkg/apis" +kube::codegen::gen_client \ + --with-applyconfig \ + --output-dir "${SCRIPT_ROOT}/pkg/generated" \ + --output-pkg "${THIS_PKG}/pkg/generated" \ + --boilerplate "${SCRIPT_ROOT}/hack/boilerplate.go.txt" \ + "${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=${TMPDIR} diff --git a/pkg/generated/applyconfiguration/apps/v1alpha1/application.go b/pkg/generated/applyconfiguration/apps/v1alpha1/application.go index 908e28fd..9c5e2ab5 100644 --- a/pkg/generated/applyconfiguration/apps/v1alpha1/application.go +++ b/pkg/generated/applyconfiguration/apps/v1alpha1/application.go @@ -1,5 +1,5 @@ /* -Copyright 2024 The Cozystack Authors. +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. @@ -19,10 +19,10 @@ limitations under the License. package v1alpha1 import ( + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" types "k8s.io/apimachinery/pkg/types" v1 "k8s.io/client-go/applyconfigurations/meta/v1" - appsv1alpha1 "github.com/cozystack/cozystack/pkg/apis/apps/v1alpha1" ) // ApplicationApplyConfiguration represents a declarative configuration of the Application type for use @@ -30,8 +30,9 @@ import ( type ApplicationApplyConfiguration struct { v1.TypeMetaApplyConfiguration `json:",inline"` *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` - Spec *ApplicationSpecApplyConfiguration `json:"spec,omitempty"` - Status *appsv1alpha1.ApplicationStatus `json:"status,omitempty"` + AppVersion *string `json:"appVersion,omitempty"` + Spec *apiextensionsv1.JSON `json:"spec,omitempty"` + Status *ApplicationStatusApplyConfiguration `json:"status,omitempty"` } // Application constructs a declarative configuration of the Application type for use with @@ -44,6 +45,7 @@ func Application(name, namespace string) *ApplicationApplyConfiguration { b.WithAPIVersion("apps.cozystack.io/v1alpha1") return b } +func (b ApplicationApplyConfiguration) IsApplyConfiguration() {} // WithKind sets the Kind field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. @@ -203,24 +205,48 @@ func (b *ApplicationApplyConfiguration) ensureObjectMetaApplyConfigurationExists } } +// WithAppVersion sets the AppVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AppVersion field is set to the value of the last call. +func (b *ApplicationApplyConfiguration) WithAppVersion(value string) *ApplicationApplyConfiguration { + b.AppVersion = &value + return b +} + // WithSpec sets the Spec field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the Spec field is set to the value of the last call. -func (b *ApplicationApplyConfiguration) WithSpec(value *ApplicationSpecApplyConfiguration) *ApplicationApplyConfiguration { - b.Spec = value +func (b *ApplicationApplyConfiguration) WithSpec(value apiextensionsv1.JSON) *ApplicationApplyConfiguration { + b.Spec = &value return b } // WithStatus sets the Status field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the Status field is set to the value of the last call. -func (b *ApplicationApplyConfiguration) WithStatus(value appsv1alpha1.ApplicationStatus) *ApplicationApplyConfiguration { - b.Status = &value +func (b *ApplicationApplyConfiguration) WithStatus(value *ApplicationStatusApplyConfiguration) *ApplicationApplyConfiguration { + b.Status = value return b } +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *ApplicationApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *ApplicationApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + // GetName retrieves the value of the Name field in the declarative configuration. func (b *ApplicationApplyConfiguration) GetName() *string { b.ensureObjectMetaApplyConfigurationExists() return b.ObjectMetaApplyConfiguration.Name } + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *ApplicationApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/pkg/generated/applyconfiguration/apps/v1alpha1/applicationspec.go b/pkg/generated/applyconfiguration/apps/v1alpha1/applicationspec.go deleted file mode 100644 index 3924ebd4..00000000 --- a/pkg/generated/applyconfiguration/apps/v1alpha1/applicationspec.go +++ /dev/null @@ -1,48 +0,0 @@ -/* -Copyright 2024 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 applyconfiguration-gen. DO NOT EDIT. - -package v1alpha1 - -// ApplicationSpecApplyConfiguration represents a declarative configuration of the ApplicationSpec type for use -// with apply. -type ApplicationSpecApplyConfiguration struct { - Version *string `json:"version,omitempty"` - Values *string `json:"values,omitempty"` -} - -// ApplicationSpecApplyConfiguration constructs a declarative configuration of the ApplicationSpec type for use with -// apply. -func ApplicationSpec() *ApplicationSpecApplyConfiguration { - return &ApplicationSpecApplyConfiguration{} -} - -// WithVersion sets the Version field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Version field is set to the value of the last call. -func (b *ApplicationSpecApplyConfiguration) WithVersion(value string) *ApplicationSpecApplyConfiguration { - b.Version = &value - return b -} - -// WithValues sets the Values field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Values field is set to the value of the last call. -func (b *ApplicationSpecApplyConfiguration) WithValues(value string) *ApplicationSpecApplyConfiguration { - b.Values = &value - return b -} diff --git a/pkg/generated/applyconfiguration/apps/v1alpha1/applicationstatus.go b/pkg/generated/applyconfiguration/apps/v1alpha1/applicationstatus.go new file mode 100644 index 00000000..2a990861 --- /dev/null +++ b/pkg/generated/applyconfiguration/apps/v1alpha1/applicationstatus.go @@ -0,0 +1,75 @@ +/* +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 applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// ApplicationStatusApplyConfiguration represents a declarative configuration of the ApplicationStatus type for use +// with apply. +type ApplicationStatusApplyConfiguration struct { + Version *string `json:"version,omitempty"` + Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"` + Namespace *string `json:"namespace,omitempty"` + ExternalIPsCount *int32 `json:"externalIPsCount,omitempty"` +} + +// ApplicationStatusApplyConfiguration constructs a declarative configuration of the ApplicationStatus type for use with +// apply. +func ApplicationStatus() *ApplicationStatusApplyConfiguration { + return &ApplicationStatusApplyConfiguration{} +} + +// WithVersion sets the Version field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Version field is set to the value of the last call. +func (b *ApplicationStatusApplyConfiguration) WithVersion(value string) *ApplicationStatusApplyConfiguration { + b.Version = &value + return b +} + +// WithConditions adds the given value to the Conditions field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Conditions field. +func (b *ApplicationStatusApplyConfiguration) WithConditions(values ...*v1.ConditionApplyConfiguration) *ApplicationStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithConditions") + } + b.Conditions = append(b.Conditions, *values[i]) + } + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *ApplicationStatusApplyConfiguration) WithNamespace(value string) *ApplicationStatusApplyConfiguration { + b.Namespace = &value + return b +} + +// WithExternalIPsCount sets the ExternalIPsCount field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ExternalIPsCount field is set to the value of the last call. +func (b *ApplicationStatusApplyConfiguration) WithExternalIPsCount(value int32) *ApplicationStatusApplyConfiguration { + b.ExternalIPsCount = &value + return b +} diff --git a/pkg/generated/applyconfiguration/internal/internal.go b/pkg/generated/applyconfiguration/internal/internal.go index 760f1229..97f12849 100644 --- a/pkg/generated/applyconfiguration/internal/internal.go +++ b/pkg/generated/applyconfiguration/internal/internal.go @@ -1,5 +1,5 @@ /* -Copyright 2024 The Cozystack Authors. +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. @@ -22,7 +22,7 @@ import ( fmt "fmt" sync "sync" - typed "sigs.k8s.io/structured-merge-diff/v4/typed" + typed "sigs.k8s.io/structured-merge-diff/v6/typed" ) func Parser() *typed.Parser { diff --git a/pkg/generated/applyconfiguration/utils.go b/pkg/generated/applyconfiguration/utils.go index a27b3d20..ab73a5f1 100644 --- a/pkg/generated/applyconfiguration/utils.go +++ b/pkg/generated/applyconfiguration/utils.go @@ -1,5 +1,5 @@ /* -Copyright 2024 The Cozystack Authors. +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. @@ -19,12 +19,12 @@ limitations under the License. package applyconfiguration import ( + v1alpha1 "github.com/cozystack/cozystack/pkg/apis/apps/v1alpha1" + appsv1alpha1 "github.com/cozystack/cozystack/pkg/generated/applyconfiguration/apps/v1alpha1" + internal "github.com/cozystack/cozystack/pkg/generated/applyconfiguration/internal" runtime "k8s.io/apimachinery/pkg/runtime" schema "k8s.io/apimachinery/pkg/runtime/schema" - testing "k8s.io/client-go/testing" - v1alpha1 "github.com/cozystack/cozystack/pkg/apis/apps/v1alpha1" - internal "github.com/cozystack/cozystack/pkg/generated/applyconfiguration/internal" - appsv1alpha1 "github.com/cozystack/cozystack/pkg/generated/applyconfiguration/apps/v1alpha1" + managedfields "k8s.io/apimachinery/pkg/util/managedfields" ) // ForKind returns an apply configuration type for the given GroupVersionKind, or nil if no @@ -34,13 +34,13 @@ func ForKind(kind schema.GroupVersionKind) interface{} { // Group=apps.cozystack.io, Version=v1alpha1 case v1alpha1.SchemeGroupVersion.WithKind("Application"): return &appsv1alpha1.ApplicationApplyConfiguration{} - case v1alpha1.SchemeGroupVersion.WithKind("ApplicationSpec"): - return &appsv1alpha1.ApplicationSpecApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("ApplicationStatus"): + return &appsv1alpha1.ApplicationStatusApplyConfiguration{} } return nil } -func NewTypeConverter(scheme *runtime.Scheme) *testing.TypeConverter { - return &testing.TypeConverter{Scheme: scheme, TypeResolver: internal.Parser()} +func NewTypeConverter(scheme *runtime.Scheme) managedfields.TypeConverter { + return managedfields.NewSchemeTypeConverter(scheme, internal.Parser()) } diff --git a/pkg/generated/applyconfiguration/utils_test.go b/pkg/generated/applyconfiguration/utils_test.go new file mode 100644 index 00000000..dd6440b6 --- /dev/null +++ b/pkg/generated/applyconfiguration/utils_test.go @@ -0,0 +1,64 @@ +/* +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. +*/ + +package applyconfiguration + +import ( + "testing" + + v1alpha1 "github.com/cozystack/cozystack/pkg/apis/apps/v1alpha1" + appsv1alpha1 "github.com/cozystack/cozystack/pkg/generated/applyconfiguration/apps/v1alpha1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func TestForKind_Application(t *testing.T) { + gvk := v1alpha1.SchemeGroupVersion.WithKind("Application") + got := ForKind(gvk) + if got == nil { + t.Fatal("ForKind returned nil for Application GVK") + } + if _, ok := got.(*appsv1alpha1.ApplicationApplyConfiguration); !ok { + t.Fatalf("ForKind returned %T, want *ApplicationApplyConfiguration", got) + } +} + +func TestForKind_ApplicationStatus(t *testing.T) { + gvk := v1alpha1.SchemeGroupVersion.WithKind("ApplicationStatus") + got := ForKind(gvk) + if got == nil { + t.Fatal("ForKind returned nil for ApplicationStatus GVK") + } + if _, ok := got.(*appsv1alpha1.ApplicationStatusApplyConfiguration); !ok { + t.Fatalf("ForKind returned %T, want *ApplicationStatusApplyConfiguration", got) + } +} + +func TestForKind_Unknown(t *testing.T) { + gvk := schema.GroupVersionKind{Group: "unknown.io", Version: "v1", Kind: "Unknown"} + got := ForKind(gvk) + if got != nil { + t.Fatalf("ForKind returned %T for unknown GVK, want nil", got) + } +} + +func TestNewTypeConverter(t *testing.T) { + scheme := runtime.NewScheme() + tc := NewTypeConverter(scheme) + if tc == nil { + t.Fatal("NewTypeConverter returned nil") + } +} diff --git a/pkg/generated/clientset/versioned/clientset.go b/pkg/generated/clientset/versioned/clientset.go new file mode 100644 index 00000000..127a0032 --- /dev/null +++ b/pkg/generated/clientset/versioned/clientset.go @@ -0,0 +1,120 @@ +/* +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 client-gen. DO NOT EDIT. + +package versioned + +import ( + fmt "fmt" + http "net/http" + + appsv1alpha1 "github.com/cozystack/cozystack/pkg/generated/clientset/versioned/typed/apps/v1alpha1" + discovery "k8s.io/client-go/discovery" + rest "k8s.io/client-go/rest" + flowcontrol "k8s.io/client-go/util/flowcontrol" +) + +type Interface interface { + Discovery() discovery.DiscoveryInterface + AppsV1alpha1() appsv1alpha1.AppsV1alpha1Interface +} + +// Clientset contains the clients for groups. +type Clientset struct { + *discovery.DiscoveryClient + appsV1alpha1 *appsv1alpha1.AppsV1alpha1Client +} + +// AppsV1alpha1 retrieves the AppsV1alpha1Client +func (c *Clientset) AppsV1alpha1() appsv1alpha1.AppsV1alpha1Interface { + return c.appsV1alpha1 +} + +// Discovery retrieves the DiscoveryClient +func (c *Clientset) Discovery() discovery.DiscoveryInterface { + if c == nil { + return nil + } + return c.DiscoveryClient +} + +// NewForConfig creates a new Clientset for the given config. +// If config's RateLimiter is not set and QPS and Burst are acceptable, +// NewForConfig will generate a rate-limiter in configShallowCopy. +// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), +// where httpClient was generated with rest.HTTPClientFor(c). +func NewForConfig(c *rest.Config) (*Clientset, error) { + configShallowCopy := *c + + if configShallowCopy.UserAgent == "" { + configShallowCopy.UserAgent = rest.DefaultKubernetesUserAgent() + } + + // share the transport between all clients + httpClient, err := rest.HTTPClientFor(&configShallowCopy) + if err != nil { + return nil, err + } + + return NewForConfigAndClient(&configShallowCopy, httpClient) +} + +// NewForConfigAndClient creates a new Clientset for the given config and http client. +// Note the http client provided takes precedence over the configured transport values. +// If config's RateLimiter is not set and QPS and Burst are acceptable, +// NewForConfigAndClient will generate a rate-limiter in configShallowCopy. +func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset, error) { + configShallowCopy := *c + if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 { + if configShallowCopy.Burst <= 0 { + return nil, fmt.Errorf("burst is required to be greater than 0 when RateLimiter is not set and QPS is set to greater than 0") + } + configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst) + } + + var cs Clientset + var err error + cs.appsV1alpha1, err = appsv1alpha1.NewForConfigAndClient(&configShallowCopy, httpClient) + if err != nil { + return nil, err + } + + cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfigAndClient(&configShallowCopy, httpClient) + if err != nil { + return nil, err + } + return &cs, nil +} + +// NewForConfigOrDie creates a new Clientset for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *Clientset { + cs, err := NewForConfig(c) + if err != nil { + panic(err) + } + return cs +} + +// New creates a new Clientset for the given RESTClient. +func New(c rest.Interface) *Clientset { + var cs Clientset + cs.appsV1alpha1 = appsv1alpha1.New(c) + + cs.DiscoveryClient = discovery.NewDiscoveryClient(c) + return &cs +} diff --git a/pkg/generated/clientset/versioned/fake/clientset_generated.go b/pkg/generated/clientset/versioned/fake/clientset_generated.go new file mode 100644 index 00000000..3ceb93c7 --- /dev/null +++ b/pkg/generated/clientset/versioned/fake/clientset_generated.go @@ -0,0 +1,131 @@ +/* +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 client-gen. DO NOT EDIT. + +package fake + +import ( + applyconfiguration "github.com/cozystack/cozystack/pkg/generated/applyconfiguration" + clientset "github.com/cozystack/cozystack/pkg/generated/clientset/versioned" + appsv1alpha1 "github.com/cozystack/cozystack/pkg/generated/clientset/versioned/typed/apps/v1alpha1" + fakeappsv1alpha1 "github.com/cozystack/cozystack/pkg/generated/clientset/versioned/typed/apps/v1alpha1/fake" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/discovery" + fakediscovery "k8s.io/client-go/discovery/fake" + "k8s.io/client-go/testing" +) + +// NewSimpleClientset returns a clientset that will respond with the provided objects. +// It's backed by a very simple object tracker that processes creates, updates and deletions as-is, +// without applying any field management, validations and/or defaults. It shouldn't be considered a replacement +// for a real clientset and is mostly useful in simple unit tests. +// +// DEPRECATED: NewClientset replaces this with support for field management, which significantly improves +// server side apply testing. NewClientset is only available when apply configurations are generated (e.g. +// via --with-applyconfig). +func NewSimpleClientset(objects ...runtime.Object) *Clientset { + o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) + for _, obj := range objects { + if err := o.Add(obj); err != nil { + panic(err) + } + } + + cs := &Clientset{tracker: o} + cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} + cs.AddReactor("*", "*", testing.ObjectReaction(o)) + cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { + var opts metav1.ListOptions + if watchActcion, ok := action.(testing.WatchActionImpl); ok { + opts = watchActcion.ListOptions + } + gvr := action.GetResource() + ns := action.GetNamespace() + watch, err := o.Watch(gvr, ns, opts) + if err != nil { + return false, nil, err + } + return true, watch, nil + }) + + return cs +} + +// Clientset implements clientset.Interface. Meant to be embedded into a +// struct to get a default implementation. This makes faking out just the method +// you want to test easier. +type Clientset struct { + testing.Fake + discovery *fakediscovery.FakeDiscovery + tracker testing.ObjectTracker +} + +func (c *Clientset) Discovery() discovery.DiscoveryInterface { + return c.discovery +} + +func (c *Clientset) Tracker() testing.ObjectTracker { + return c.tracker +} + +// NewClientset returns a clientset that will respond with the provided objects. +// It's backed by a very simple object tracker that processes creates, updates and deletions as-is, +// without applying any validations and/or defaults. It shouldn't be considered a replacement +// for a real clientset and is mostly useful in simple unit tests. +func NewClientset(objects ...runtime.Object) *Clientset { + o := testing.NewFieldManagedObjectTracker( + scheme, + codecs.UniversalDecoder(), + applyconfiguration.NewTypeConverter(scheme), + ) + for _, obj := range objects { + if err := o.Add(obj); err != nil { + panic(err) + } + } + + cs := &Clientset{tracker: o} + cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} + cs.AddReactor("*", "*", testing.ObjectReaction(o)) + cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { + var opts metav1.ListOptions + if watchAction, ok := action.(testing.WatchActionImpl); ok { + opts = watchAction.ListOptions + } + gvr := action.GetResource() + ns := action.GetNamespace() + watch, err := o.Watch(gvr, ns, opts) + if err != nil { + return false, nil, err + } + return true, watch, nil + }) + + return cs +} + +var ( + _ clientset.Interface = &Clientset{} + _ testing.FakeClient = &Clientset{} +) + +// AppsV1alpha1 retrieves the AppsV1alpha1Client +func (c *Clientset) AppsV1alpha1() appsv1alpha1.AppsV1alpha1Interface { + return &fakeappsv1alpha1.FakeAppsV1alpha1{Fake: &c.Fake} +} diff --git a/pkg/generated/clientset/versioned/fake/doc.go b/pkg/generated/clientset/versioned/fake/doc.go new file mode 100644 index 00000000..2d395096 --- /dev/null +++ b/pkg/generated/clientset/versioned/fake/doc.go @@ -0,0 +1,20 @@ +/* +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 client-gen. DO NOT EDIT. + +// This package has the automatically generated fake clientset. +package fake diff --git a/pkg/generated/clientset/versioned/fake/register.go b/pkg/generated/clientset/versioned/fake/register.go new file mode 100644 index 00000000..a05b3ada --- /dev/null +++ b/pkg/generated/clientset/versioned/fake/register.go @@ -0,0 +1,56 @@ +/* +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 client-gen. DO NOT EDIT. + +package fake + +import ( + appsv1alpha1 "github.com/cozystack/cozystack/pkg/apis/apps/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + serializer "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" +) + +var scheme = runtime.NewScheme() +var codecs = serializer.NewCodecFactory(scheme) + +var localSchemeBuilder = runtime.SchemeBuilder{ + appsv1alpha1.AddToScheme, +} + +// AddToScheme adds all types of this clientset into the given scheme. This allows composition +// of clientsets, like in: +// +// import ( +// "k8s.io/client-go/kubernetes" +// clientsetscheme "k8s.io/client-go/kubernetes/scheme" +// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" +// ) +// +// kclientset, _ := kubernetes.NewForConfig(c) +// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) +// +// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types +// correctly. +var AddToScheme = localSchemeBuilder.AddToScheme + +func init() { + v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"}) + utilruntime.Must(AddToScheme(scheme)) +} diff --git a/pkg/generated/clientset/versioned/scheme/doc.go b/pkg/generated/clientset/versioned/scheme/doc.go new file mode 100644 index 00000000..4b11a5d0 --- /dev/null +++ b/pkg/generated/clientset/versioned/scheme/doc.go @@ -0,0 +1,20 @@ +/* +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 client-gen. DO NOT EDIT. + +// This package contains the scheme of the automatically generated clientset. +package scheme diff --git a/pkg/generated/clientset/versioned/scheme/register.go b/pkg/generated/clientset/versioned/scheme/register.go new file mode 100644 index 00000000..65b15149 --- /dev/null +++ b/pkg/generated/clientset/versioned/scheme/register.go @@ -0,0 +1,56 @@ +/* +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 client-gen. DO NOT EDIT. + +package scheme + +import ( + appsv1alpha1 "github.com/cozystack/cozystack/pkg/apis/apps/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + serializer "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" +) + +var Scheme = runtime.NewScheme() +var Codecs = serializer.NewCodecFactory(Scheme) +var ParameterCodec = runtime.NewParameterCodec(Scheme) +var localSchemeBuilder = runtime.SchemeBuilder{ + appsv1alpha1.AddToScheme, +} + +// AddToScheme adds all types of this clientset into the given scheme. This allows composition +// of clientsets, like in: +// +// import ( +// "k8s.io/client-go/kubernetes" +// clientsetscheme "k8s.io/client-go/kubernetes/scheme" +// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" +// ) +// +// kclientset, _ := kubernetes.NewForConfig(c) +// _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) +// +// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types +// correctly. +var AddToScheme = localSchemeBuilder.AddToScheme + +func init() { + v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) + utilruntime.Must(AddToScheme(Scheme)) +} diff --git a/pkg/generated/clientset/versioned/typed/apps/v1alpha1/application.go b/pkg/generated/clientset/versioned/typed/apps/v1alpha1/application.go new file mode 100644 index 00000000..afc81aad --- /dev/null +++ b/pkg/generated/clientset/versioned/typed/apps/v1alpha1/application.go @@ -0,0 +1,74 @@ +/* +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 client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + context "context" + + appsv1alpha1 "github.com/cozystack/cozystack/pkg/apis/apps/v1alpha1" + applyconfigurationappsv1alpha1 "github.com/cozystack/cozystack/pkg/generated/applyconfiguration/apps/v1alpha1" + scheme "github.com/cozystack/cozystack/pkg/generated/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + gentype "k8s.io/client-go/gentype" +) + +// ApplicationsGetter has a method to return a ApplicationInterface. +// A group's client should implement this interface. +type ApplicationsGetter interface { + Applications(namespace string) ApplicationInterface +} + +// ApplicationInterface has methods to work with Application resources. +type ApplicationInterface interface { + Create(ctx context.Context, application *appsv1alpha1.Application, opts v1.CreateOptions) (*appsv1alpha1.Application, error) + Update(ctx context.Context, application *appsv1alpha1.Application, opts v1.UpdateOptions) (*appsv1alpha1.Application, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, application *appsv1alpha1.Application, opts v1.UpdateOptions) (*appsv1alpha1.Application, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*appsv1alpha1.Application, error) + List(ctx context.Context, opts v1.ListOptions) (*appsv1alpha1.ApplicationList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *appsv1alpha1.Application, err error) + Apply(ctx context.Context, application *applyconfigurationappsv1alpha1.ApplicationApplyConfiguration, opts v1.ApplyOptions) (result *appsv1alpha1.Application, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). + ApplyStatus(ctx context.Context, application *applyconfigurationappsv1alpha1.ApplicationApplyConfiguration, opts v1.ApplyOptions) (result *appsv1alpha1.Application, err error) + ApplicationExpansion +} + +// applications implements ApplicationInterface +type applications struct { + *gentype.ClientWithListAndApply[*appsv1alpha1.Application, *appsv1alpha1.ApplicationList, *applyconfigurationappsv1alpha1.ApplicationApplyConfiguration] +} + +// newApplications returns a Applications +func newApplications(c *AppsV1alpha1Client, namespace string) *applications { + return &applications{ + gentype.NewClientWithListAndApply[*appsv1alpha1.Application, *appsv1alpha1.ApplicationList, *applyconfigurationappsv1alpha1.ApplicationApplyConfiguration]( + "applications", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *appsv1alpha1.Application { return &appsv1alpha1.Application{} }, + func() *appsv1alpha1.ApplicationList { return &appsv1alpha1.ApplicationList{} }, + ), + } +} diff --git a/pkg/generated/clientset/versioned/typed/apps/v1alpha1/apps_client.go b/pkg/generated/clientset/versioned/typed/apps/v1alpha1/apps_client.go new file mode 100644 index 00000000..40ff64b9 --- /dev/null +++ b/pkg/generated/clientset/versioned/typed/apps/v1alpha1/apps_client.go @@ -0,0 +1,101 @@ +/* +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 client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + http "net/http" + + appsv1alpha1 "github.com/cozystack/cozystack/pkg/apis/apps/v1alpha1" + scheme "github.com/cozystack/cozystack/pkg/generated/clientset/versioned/scheme" + rest "k8s.io/client-go/rest" +) + +type AppsV1alpha1Interface interface { + RESTClient() rest.Interface + ApplicationsGetter +} + +// AppsV1alpha1Client is used to interact with features provided by the apps.cozystack.io group. +type AppsV1alpha1Client struct { + restClient rest.Interface +} + +func (c *AppsV1alpha1Client) Applications(namespace string) ApplicationInterface { + return newApplications(c, namespace) +} + +// NewForConfig creates a new AppsV1alpha1Client for the given config. +// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient), +// where httpClient was generated with rest.HTTPClientFor(c). +func NewForConfig(c *rest.Config) (*AppsV1alpha1Client, error) { + config := *c + setConfigDefaults(&config) + httpClient, err := rest.HTTPClientFor(&config) + if err != nil { + return nil, err + } + return NewForConfigAndClient(&config, httpClient) +} + +// NewForConfigAndClient creates a new AppsV1alpha1Client for the given config and http client. +// Note the http client provided takes precedence over the configured transport values. +func NewForConfigAndClient(c *rest.Config, h *http.Client) (*AppsV1alpha1Client, error) { + config := *c + setConfigDefaults(&config) + client, err := rest.RESTClientForConfigAndClient(&config, h) + if err != nil { + return nil, err + } + return &AppsV1alpha1Client{client}, nil +} + +// NewForConfigOrDie creates a new AppsV1alpha1Client for the given config and +// panics if there is an error in the config. +func NewForConfigOrDie(c *rest.Config) *AppsV1alpha1Client { + client, err := NewForConfig(c) + if err != nil { + panic(err) + } + return client +} + +// New creates a new AppsV1alpha1Client for the given RESTClient. +func New(c rest.Interface) *AppsV1alpha1Client { + return &AppsV1alpha1Client{c} +} + +func setConfigDefaults(config *rest.Config) { + gv := appsv1alpha1.SchemeGroupVersion + config.GroupVersion = &gv + config.APIPath = "/apis" + config.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(scheme.Scheme, scheme.Codecs).WithoutConversion() + + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *AppsV1alpha1Client) RESTClient() rest.Interface { + if c == nil { + return nil + } + return c.restClient +} diff --git a/pkg/generated/clientset/versioned/typed/apps/v1alpha1/doc.go b/pkg/generated/clientset/versioned/typed/apps/v1alpha1/doc.go new file mode 100644 index 00000000..b37cc8b0 --- /dev/null +++ b/pkg/generated/clientset/versioned/typed/apps/v1alpha1/doc.go @@ -0,0 +1,20 @@ +/* +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 client-gen. DO NOT EDIT. + +// This package has the automatically generated typed clients. +package v1alpha1 diff --git a/pkg/generated/clientset/versioned/typed/apps/v1alpha1/fake/doc.go b/pkg/generated/clientset/versioned/typed/apps/v1alpha1/fake/doc.go new file mode 100644 index 00000000..592956cf --- /dev/null +++ b/pkg/generated/clientset/versioned/typed/apps/v1alpha1/fake/doc.go @@ -0,0 +1,20 @@ +/* +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 client-gen. DO NOT EDIT. + +// Package fake has the automatically generated clients. +package fake diff --git a/pkg/generated/clientset/versioned/typed/apps/v1alpha1/fake/fake_application.go b/pkg/generated/clientset/versioned/typed/apps/v1alpha1/fake/fake_application.go new file mode 100644 index 00000000..0760721d --- /dev/null +++ b/pkg/generated/clientset/versioned/typed/apps/v1alpha1/fake/fake_application.go @@ -0,0 +1,53 @@ +/* +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 client-gen. DO NOT EDIT. + +package fake + +import ( + v1alpha1 "github.com/cozystack/cozystack/pkg/apis/apps/v1alpha1" + appsv1alpha1 "github.com/cozystack/cozystack/pkg/generated/applyconfiguration/apps/v1alpha1" + typedappsv1alpha1 "github.com/cozystack/cozystack/pkg/generated/clientset/versioned/typed/apps/v1alpha1" + gentype "k8s.io/client-go/gentype" +) + +// fakeApplications implements ApplicationInterface +type fakeApplications struct { + *gentype.FakeClientWithListAndApply[*v1alpha1.Application, *v1alpha1.ApplicationList, *appsv1alpha1.ApplicationApplyConfiguration] + Fake *FakeAppsV1alpha1 +} + +func newFakeApplications(fake *FakeAppsV1alpha1, namespace string) typedappsv1alpha1.ApplicationInterface { + return &fakeApplications{ + gentype.NewFakeClientWithListAndApply[*v1alpha1.Application, *v1alpha1.ApplicationList, *appsv1alpha1.ApplicationApplyConfiguration]( + fake.Fake, + namespace, + v1alpha1.SchemeGroupVersion.WithResource("applications"), + v1alpha1.SchemeGroupVersion.WithKind("Application"), + func() *v1alpha1.Application { return &v1alpha1.Application{} }, + func() *v1alpha1.ApplicationList { return &v1alpha1.ApplicationList{} }, + func(dst, src *v1alpha1.ApplicationList) { dst.ListMeta = src.ListMeta }, + func(list *v1alpha1.ApplicationList) []*v1alpha1.Application { + return gentype.ToPointerSlice(list.Items) + }, + func(list *v1alpha1.ApplicationList, items []*v1alpha1.Application) { + list.Items = gentype.FromPointerSlice(items) + }, + ), + fake, + } +} diff --git a/pkg/generated/clientset/versioned/typed/apps/v1alpha1/fake/fake_apps_client.go b/pkg/generated/clientset/versioned/typed/apps/v1alpha1/fake/fake_apps_client.go new file mode 100644 index 00000000..40981a9d --- /dev/null +++ b/pkg/generated/clientset/versioned/typed/apps/v1alpha1/fake/fake_apps_client.go @@ -0,0 +1,40 @@ +/* +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 client-gen. DO NOT EDIT. + +package fake + +import ( + v1alpha1 "github.com/cozystack/cozystack/pkg/generated/clientset/versioned/typed/apps/v1alpha1" + rest "k8s.io/client-go/rest" + testing "k8s.io/client-go/testing" +) + +type FakeAppsV1alpha1 struct { + *testing.Fake +} + +func (c *FakeAppsV1alpha1) Applications(namespace string) v1alpha1.ApplicationInterface { + return newFakeApplications(c, namespace) +} + +// RESTClient returns a RESTClient that is used to communicate +// with API server by this client implementation. +func (c *FakeAppsV1alpha1) RESTClient() rest.Interface { + var ret *rest.RESTClient + return ret +} diff --git a/pkg/generated/clientset/versioned/typed/apps/v1alpha1/generated_expansion.go b/pkg/generated/clientset/versioned/typed/apps/v1alpha1/generated_expansion.go new file mode 100644 index 00000000..6a0aab6a --- /dev/null +++ b/pkg/generated/clientset/versioned/typed/apps/v1alpha1/generated_expansion.go @@ -0,0 +1,21 @@ +/* +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 client-gen. DO NOT EDIT. + +package v1alpha1 + +type ApplicationExpansion interface{} From 25ff583f3407b76fbae6f32e7da01beb32b113f0 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Mon, 2 Feb 2026 13:11:18 +0100 Subject: [PATCH 279/889] [apps] Add managed OpenSearch service Add OpenSearch application with operator and resource definition: - App chart with multi-version support (v1/v2/v3), TLS, auth, dashboards - OpenSearch operator wrapper (opster v2.8.0) with sysctl daemonset - ApplicationDefinition for Cozystack platform integration Co-Authored-By: Claude Opus 4.5 Signed-off-by: Matthieu --- packages/apps/opensearch/.helmignore | 23 + packages/apps/opensearch/Chart.yaml | 7 + packages/apps/opensearch/Makefile | 11 + packages/apps/opensearch/README.md | 60 + packages/apps/opensearch/charts/cozy-lib | 1 + packages/apps/opensearch/files/versions.yaml | 5 + .../apps/opensearch/hack/update-versions.sh | 53 + packages/apps/opensearch/logos/opensearch.svg | 11 + packages/apps/opensearch/templates/.gitkeep | 0 .../apps/opensearch/templates/_versions.tpl | 13 + .../templates/dashboard-resourcemap.yaml | 42 + .../opensearch/templates/external-svc.yaml | 39 + .../apps/opensearch/templates/opensearch.yaml | 99 + .../apps/opensearch/templates/security.yaml | 120 + packages/apps/opensearch/templates/users.yaml | 21 + .../opensearch/tests/opensearch_test.yaml | 516 ++ .../apps/opensearch/tests/security_test.yaml | 207 + .../apps/opensearch/tests/users_test.yaml | 176 + packages/apps/opensearch/values.schema.json | 239 + packages/apps/opensearch/values.yaml | 111 + .../system/opensearch-operator/.helmignore | 23 + .../system/opensearch-operator/Chart.yaml | 3 + packages/system/opensearch-operator/Makefile | 10 + .../charts/opensearch-operator/CHANGELOG.md | 61 + .../charts/opensearch-operator/Chart.yaml | 6 + .../charts/opensearch-operator/README.md | 29 + ...arch.opster.io_opensearchactiongroups.yaml | 94 + ...ensearch.opster.io_opensearchclusters.yaml | 6372 +++++++++++++++++ ...pster.io_opensearchcomponenttemplates.yaml | 136 + ...ch.opster.io_opensearchindextemplates.yaml | 163 + ...earch.opster.io_opensearchismpolicies.yaml | 461 ++ .../opensearch.opster.io_opensearchroles.yaml | 123 + ....opster.io_opensearchsnapshotpolicies.yaml | 203 + ...pensearch.opster.io_opensearchtenants.yaml | 85 + ....opster.io_opensearchuserrolebindings.yaml | 109 + .../opensearch.opster.io_opensearchusers.yaml | 117 + .../templates/_helpers.tpl | 62 + ...perator-controller-manager-deployment.yaml | 94 + ...ontroller-manager-metrics-service-svc.yaml | 13 + ...search-operator-controller-manager-sa.yaml | 6 + .../templates/opensearch-operator-crds.yaml | 5 + ...ch-operator-leader-election-role-role.yaml | 48 + ...erator-leader-election-rolebinding-rb.yaml | 11 + ...opensearch-operator-manager-config-cm.yaml | 17 + .../opensearch-operator-manager-role-cr.yaml | 414 ++ ...ensearch-operator-manager-rolebinding.yaml | 27 + ...opensearch-operator-metrics-reader-cr.yaml | 9 + .../opensearch-operator-proxy-role-cr.yaml | 17 + ...opensearch-operator-proxy-rolebinding.yaml | 27 + .../charts/opensearch-operator/values.yaml | 123 + .../templates/sysctl-daemonset.yaml | 64 + .../system/opensearch-operator/values.yaml | 4 + packages/system/opensearch-rd/Chart.yaml | 3 + packages/system/opensearch-rd/Makefile | 4 + .../opensearch-rd/cozyrds/opensearch.yaml | 42 + .../opensearch-rd/templates/cozyrd.yaml | 4 + packages/system/opensearch-rd/values.yaml | 1 + 57 files changed, 10744 insertions(+) create mode 100644 packages/apps/opensearch/.helmignore create mode 100644 packages/apps/opensearch/Chart.yaml create mode 100644 packages/apps/opensearch/Makefile create mode 100644 packages/apps/opensearch/README.md create mode 120000 packages/apps/opensearch/charts/cozy-lib create mode 100644 packages/apps/opensearch/files/versions.yaml create mode 100755 packages/apps/opensearch/hack/update-versions.sh create mode 100644 packages/apps/opensearch/logos/opensearch.svg create mode 100644 packages/apps/opensearch/templates/.gitkeep create mode 100644 packages/apps/opensearch/templates/_versions.tpl create mode 100644 packages/apps/opensearch/templates/dashboard-resourcemap.yaml create mode 100644 packages/apps/opensearch/templates/external-svc.yaml create mode 100644 packages/apps/opensearch/templates/opensearch.yaml create mode 100644 packages/apps/opensearch/templates/security.yaml create mode 100644 packages/apps/opensearch/templates/users.yaml create mode 100644 packages/apps/opensearch/tests/opensearch_test.yaml create mode 100644 packages/apps/opensearch/tests/security_test.yaml create mode 100644 packages/apps/opensearch/tests/users_test.yaml create mode 100644 packages/apps/opensearch/values.schema.json create mode 100644 packages/apps/opensearch/values.yaml create mode 100644 packages/system/opensearch-operator/.helmignore create mode 100644 packages/system/opensearch-operator/Chart.yaml create mode 100644 packages/system/opensearch-operator/Makefile create mode 100644 packages/system/opensearch-operator/charts/opensearch-operator/CHANGELOG.md create mode 100644 packages/system/opensearch-operator/charts/opensearch-operator/Chart.yaml create mode 100644 packages/system/opensearch-operator/charts/opensearch-operator/README.md create mode 100644 packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchactiongroups.yaml create mode 100644 packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchclusters.yaml create mode 100644 packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchcomponenttemplates.yaml create mode 100644 packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchindextemplates.yaml create mode 100644 packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchismpolicies.yaml create mode 100644 packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchroles.yaml create mode 100644 packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchsnapshotpolicies.yaml create mode 100644 packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchtenants.yaml create mode 100644 packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchuserrolebindings.yaml create mode 100644 packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchusers.yaml create mode 100644 packages/system/opensearch-operator/charts/opensearch-operator/templates/_helpers.tpl create mode 100644 packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-controller-manager-deployment.yaml create mode 100644 packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-controller-manager-metrics-service-svc.yaml create mode 100644 packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-controller-manager-sa.yaml create mode 100644 packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-crds.yaml create mode 100644 packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-leader-election-role-role.yaml create mode 100644 packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-leader-election-rolebinding-rb.yaml create mode 100644 packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-manager-config-cm.yaml create mode 100644 packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-manager-role-cr.yaml create mode 100644 packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-manager-rolebinding.yaml create mode 100644 packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-metrics-reader-cr.yaml create mode 100644 packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-proxy-role-cr.yaml create mode 100644 packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-proxy-rolebinding.yaml create mode 100644 packages/system/opensearch-operator/charts/opensearch-operator/values.yaml create mode 100644 packages/system/opensearch-operator/templates/sysctl-daemonset.yaml create mode 100644 packages/system/opensearch-operator/values.yaml create mode 100644 packages/system/opensearch-rd/Chart.yaml create mode 100644 packages/system/opensearch-rd/Makefile create mode 100644 packages/system/opensearch-rd/cozyrds/opensearch.yaml create mode 100644 packages/system/opensearch-rd/templates/cozyrd.yaml create mode 100644 packages/system/opensearch-rd/values.yaml diff --git a/packages/apps/opensearch/.helmignore b/packages/apps/opensearch/.helmignore new file mode 100644 index 00000000..0e8a0eb3 --- /dev/null +++ b/packages/apps/opensearch/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/packages/apps/opensearch/Chart.yaml b/packages/apps/opensearch/Chart.yaml new file mode 100644 index 00000000..8abefadf --- /dev/null +++ b/packages/apps/opensearch/Chart.yaml @@ -0,0 +1,7 @@ +apiVersion: v2 +name: opensearch +description: Managed OpenSearch service +icon: /logos/opensearch.svg +type: application +version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process +appVersion: "2.11.1" diff --git a/packages/apps/opensearch/Makefile b/packages/apps/opensearch/Makefile new file mode 100644 index 00000000..9440c3fd --- /dev/null +++ b/packages/apps/opensearch/Makefile @@ -0,0 +1,11 @@ +include ../../../hack/package.mk + +.PHONY: generate update + +generate: + cozyvalues-gen -v values.yaml -s values.schema.json -r README.md + ../../../hack/update-crd.sh + +update: + hack/update-versions.sh + make generate diff --git a/packages/apps/opensearch/README.md b/packages/apps/opensearch/README.md new file mode 100644 index 00000000..d968c665 --- /dev/null +++ b/packages/apps/opensearch/README.md @@ -0,0 +1,60 @@ +# Managed OpenSearch Service + +## Parameters + +### Common parameters + +| Name | Description | Type | Value | +| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------- | ---------- | ------- | +| `replicas` | Number of OpenSearch nodes in the cluster. | `int` | `3` | +| `resources` | Explicit CPU and memory configuration for each OpenSearch node. When omitted, the preset defined in `resourcesPreset` is applied. | `object` | `{}` | +| `resources.cpu` | CPU available to each node. | `quantity` | `""` | +| `resources.memory` | Memory (RAM) available to each node. | `quantity` | `""` | +| `resourcesPreset` | Default sizing preset used when `resources` is omitted. OpenSearch requires minimum 2Gi memory. | `string` | `large` | +| `size` | Persistent Volume Claim size available for application data. | `quantity` | `10Gi` | +| `storageClass` | StorageClass used to store the data. | `string` | `""` | +| `external` | Enable external access from outside the cluster. | `bool` | `false` | +| `topologySpreadPolicy` | How strictly to enforce pod distribution across nodes and zones. | `string` | `soft` | +| `version` | OpenSearch major version to deploy. | `string` | `v2` | + + +### Image configuration + +| Name | Description | Type | Value | +| ------------------- | -------------------------------------- | -------- | ----- | +| `images` | Container images used by the operator. | `object` | `{}` | +| `images.opensearch` | OpenSearch image. | `string` | `""` | + + +### Node roles configuration + +| Name | Description | Type | Value | +| ------------------ | ----------------------------- | -------- | ------- | +| `nodeRoles` | Node roles configuration. | `object` | `{}` | +| `nodeRoles.master` | Enable cluster_manager role. | `bool` | `true` | +| `nodeRoles.data` | Enable data role. | `bool` | `true` | +| `nodeRoles.ingest` | Enable ingest role. | `bool` | `true` | +| `nodeRoles.ml` | Enable machine learning role. | `bool` | `false` | + + +### Users configuration + +| Name | Description | Type | Value | +| ---------------------- | -------------------------------------------------- | ------------------- | ----- | +| `users` | Custom OpenSearch users configuration map. | `map[string]object` | `{}` | +| `users[name].password` | Password for the user (auto-generated if omitted). | `string` | `""` | +| `users[name].roles` | List of OpenSearch roles. | `[]string` | `[]` | + + +### OpenSearch Dashboards configuration + +| Name | Description | Type | Value | +| ----------------------------- | ----------------------------------------------------- | ---------- | -------- | +| `dashboards` | OpenSearch Dashboards configuration. | `object` | `{}` | +| `dashboards.enabled` | Enable OpenSearch Dashboards deployment. | `bool` | `false` | +| `dashboards.replicas` | Number of Dashboards replicas. | `int` | `1` | +| `dashboards.resources` | Explicit CPU and memory configuration for Dashboards. | `object` | `{}` | +| `dashboards.resources.cpu` | CPU available to each node. | `quantity` | `""` | +| `dashboards.resources.memory` | Memory (RAM) available to each node. | `quantity` | `""` | +| `dashboards.resourcesPreset` | Default sizing preset for Dashboards. | `string` | `medium` | + diff --git a/packages/apps/opensearch/charts/cozy-lib b/packages/apps/opensearch/charts/cozy-lib new file mode 120000 index 00000000..e1813509 --- /dev/null +++ b/packages/apps/opensearch/charts/cozy-lib @@ -0,0 +1 @@ +../../../library/cozy-lib \ No newline at end of file diff --git a/packages/apps/opensearch/files/versions.yaml b/packages/apps/opensearch/files/versions.yaml new file mode 100644 index 00000000..5e88df94 --- /dev/null +++ b/packages/apps/opensearch/files/versions.yaml @@ -0,0 +1,5 @@ +# OpenSearch version mapping (major version -> image tag) +# Auto-generated by hack/update-versions.sh - do not edit manually +"v3": "3.0.0" +"v2": "2.11.1" +"v1": "1.3.20" diff --git a/packages/apps/opensearch/hack/update-versions.sh b/packages/apps/opensearch/hack/update-versions.sh new file mode 100755 index 00000000..19d9e7b2 --- /dev/null +++ b/packages/apps/opensearch/hack/update-versions.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash + +set -o errexit +set -o nounset +set -o pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +OPENSEARCH_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +VERSIONS_FILE="${OPENSEARCH_DIR}/files/versions.yaml" + +# Supported major versions (newest first) +SUPPORTED_MAJOR_VERSIONS="3 2 1" + +echo "Supported major versions: $SUPPORTED_MAJOR_VERSIONS" + +# Check if skopeo is installed +if ! command -v skopeo &> /dev/null; then + echo "Error: skopeo is not installed. Please install skopeo and try again." >&2 + exit 1 +fi + +# Check if jq is installed +if ! command -v jq &> /dev/null; then + echo "Error: jq is not installed. Please install jq and try again." >&2 + exit 1 +fi + +# Get available image tags from Docker Hub +IMAGE="docker.io/opensearchproject/opensearch" +echo "Fetching available image tags from registry..." +TAGS=$(skopeo list-tags "docker://${IMAGE}" | jq -r '.Tags[]') + +echo "# OpenSearch version mapping (major version -> image tag)" > "${VERSIONS_FILE}" +echo "# Auto-generated by hack/update-versions.sh - do not edit manually" >> "${VERSIONS_FILE}" + +for MAJOR in $SUPPORTED_MAJOR_VERSIONS; do + # Find the latest stable release for this major version + LATEST=$(echo "$TAGS" \ + | grep -E "^${MAJOR}\.[0-9]+\.[0-9]+$" \ + | sort -t. -k1,1n -k2,2n -k3,3n \ + | tail -1) + + if [ -n "$LATEST" ]; then + echo "v${MAJOR}: latest tag is ${LATEST}" + echo "\"v${MAJOR}\": \"${LATEST}\"" >> "${VERSIONS_FILE}" + else + echo "WARNING: No stable release found for major version ${MAJOR}" >&2 + fi +done + +echo "" +echo "Updated ${VERSIONS_FILE}:" +cat "${VERSIONS_FILE}" diff --git a/packages/apps/opensearch/logos/opensearch.svg b/packages/apps/opensearch/logos/opensearch.svg new file mode 100644 index 00000000..345fdd0a --- /dev/null +++ b/packages/apps/opensearch/logos/opensearch.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/packages/apps/opensearch/templates/.gitkeep b/packages/apps/opensearch/templates/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/packages/apps/opensearch/templates/_versions.tpl b/packages/apps/opensearch/templates/_versions.tpl new file mode 100644 index 00000000..4eae4163 --- /dev/null +++ b/packages/apps/opensearch/templates/_versions.tpl @@ -0,0 +1,13 @@ +{{/* +Version mapping helper +Loads version mapping from files/versions.yaml and returns the full version for a given major version +*/}} +{{- define "opensearch.versionMap" -}} +{{- $versions := .Files.Get "files/versions.yaml" | fromYaml -}} +{{- $version := .Values.version | default "v2" -}} +{{- if hasKey $versions $version -}} +{{- index $versions $version -}} +{{- else -}} +{{- fail (printf "Invalid version '%s'. Available versions: %s" $version (keys $versions | join ", ")) -}} +{{- end -}} +{{- end -}} diff --git a/packages/apps/opensearch/templates/dashboard-resourcemap.yaml b/packages/apps/opensearch/templates/dashboard-resourcemap.yaml new file mode 100644 index 00000000..91dbf8cb --- /dev/null +++ b/packages/apps/opensearch/templates/dashboard-resourcemap.yaml @@ -0,0 +1,42 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ .Release.Name }}-dashboard-resources +rules: +- apiGroups: + - "" + resources: + - services + resourceNames: + - {{ .Release.Name }} + - {{ .Release.Name }}-external + {{- if .Values.dashboards.enabled }} + - {{ .Release.Name }}-dashboards + - {{ .Release.Name }}-dashboards-external + {{- end }} + verbs: ["get", "list", "watch"] +- apiGroups: + - "" + resources: + - secrets + resourceNames: + - {{ .Release.Name }}-credentials + verbs: ["get", "list", "watch"] +- apiGroups: + - cozystack.io + resources: + - workloadmonitors + resourceNames: + - {{ .Release.Name }} + verbs: ["get", "list", "watch"] +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ .Release.Name }}-dashboard-resources +subjects: +{{ include "cozy-lib.rbac.subjectsForTenantAndAccessLevel" (list "use" .Release.Namespace) }} +roleRef: + kind: Role + name: {{ .Release.Name }}-dashboard-resources + apiGroup: rbac.authorization.k8s.io diff --git a/packages/apps/opensearch/templates/external-svc.yaml b/packages/apps/opensearch/templates/external-svc.yaml new file mode 100644 index 00000000..f28626ab --- /dev/null +++ b/packages/apps/opensearch/templates/external-svc.yaml @@ -0,0 +1,39 @@ +{{- if .Values.external }} +{{- $clusterDomain := (index .Values._cluster "cluster-domain") | default "cozy.local" }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-external + annotations: + external-dns.alpha.kubernetes.io/hostname: {{ .Release.Name }}.{{ .Release.Namespace }}.{{ $clusterDomain }} +spec: + type: LoadBalancer + selector: + opster.io/opensearch-cluster: {{ .Release.Name }} + opster.io/opensearch-nodepool: nodes + ports: + - name: https + port: 9200 + targetPort: 9200 + protocol: TCP +{{- if .Values.dashboards.enabled }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-dashboards-external + annotations: + external-dns.alpha.kubernetes.io/hostname: {{ .Release.Name }}-dashboards.{{ .Release.Namespace }}.{{ $clusterDomain }} +spec: + type: LoadBalancer + selector: + opster.io/opensearch-cluster: {{ .Release.Name }} + app.kubernetes.io/component: dashboards + ports: + - name: https + port: 5601 + targetPort: 5601 + protocol: TCP +{{- end }} +{{- end }} diff --git a/packages/apps/opensearch/templates/opensearch.yaml b/packages/apps/opensearch/templates/opensearch.yaml new file mode 100644 index 00000000..9917cf5f --- /dev/null +++ b/packages/apps/opensearch/templates/opensearch.yaml @@ -0,0 +1,99 @@ +{{- $topologyMode := .Values.topologySpreadPolicy | default "soft" }} +{{- $whenUnsatisfiable := "ScheduleAnyway" }} +{{- if eq $topologyMode "hard" }} +{{- $whenUnsatisfiable = "DoNotSchedule" }} +{{- end }} +--- +apiVersion: opensearch.opster.io/v1 +kind: OpenSearchCluster +metadata: + name: {{ .Release.Name }} +spec: + general: + serviceName: {{ .Release.Name }} + version: {{ include "opensearch.versionMap" $ }} + httpPort: 9200 + drainDataNodes: true + {{- if gt (len .Values.images.opensearch) 0 }} + image: {{ .Values.images.opensearch }} + {{- end }} + bootstrap: + resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.resourcesPreset .Values.resources $) | nindent 6 }} + security: + tls: + transport: + generate: true + perNode: true + http: + generate: true + config: + securityConfigSecret: + name: {{ .Release.Name }}-security-config + adminCredentialsSecret: + name: {{ .Release.Name }}-admin-credentials + {{- if .Values.dashboards.enabled }} + dashboards: + enable: true + version: {{ include "opensearch.versionMap" $ }} + replicas: {{ .Values.dashboards.replicas | default 1 }} + tls: + enable: true + generate: true + resources: {{- include "cozy-lib.resources.defaultingSanitize" (list (.Values.dashboards.resourcesPreset | default "medium") .Values.dashboards.resources $) | nindent 6 }} + {{- end }} + nodePools: + - component: nodes + replicas: {{ .Values.replicas }} + diskSize: {{ .Values.size }} + persistence: + pvc: + accessModes: + - ReadWriteOnce + {{- if .Values.storageClass }} + storageClass: {{ .Values.storageClass }} + {{- else if eq (int .Values.replicas) 1 }} + storageClass: replicated + {{- else }} + storageClass: local + {{- end }} + resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.resourcesPreset .Values.resources $) | nindent 8 }} + roles: + {{- if .Values.nodeRoles.master }} + - cluster_manager + {{- end }} + {{- if .Values.nodeRoles.data }} + - data + {{- end }} + {{- if .Values.nodeRoles.ingest }} + - ingest + {{- end }} + {{- if .Values.nodeRoles.ml }} + - ml + {{- end }} + topologySpreadConstraints: + - maxSkew: 1 + topologyKey: kubernetes.io/hostname + whenUnsatisfiable: {{ $whenUnsatisfiable }} + labelSelector: + matchLabels: + opster.io/opensearch-cluster: {{ .Release.Name }} + - maxSkew: 1 + topologyKey: topology.kubernetes.io/zone + whenUnsatisfiable: {{ $whenUnsatisfiable }} + labelSelector: + matchLabels: + opster.io/opensearch-cluster: {{ .Release.Name }} +--- +# WorkloadMonitor tracks OpenSearch pods +apiVersion: cozystack.io/v1alpha1 +kind: WorkloadMonitor +metadata: + name: {{ .Release.Name }} +spec: + replicas: {{ .Values.replicas }} + minReplicas: 1 + kind: opensearch + type: opensearch + selector: + opster.io/opensearch-cluster: {{ .Release.Name }} + version: {{ .Chart.Version }} diff --git a/packages/apps/opensearch/templates/security.yaml b/packages/apps/opensearch/templates/security.yaml new file mode 100644 index 00000000..d10d353a --- /dev/null +++ b/packages/apps/opensearch/templates/security.yaml @@ -0,0 +1,120 @@ +{{- $clusterDomain := (index .Values._cluster "cluster-domain") | default "cozy.local" }} +{{- $existingAdminCreds := lookup "v1" "Secret" .Release.Namespace (printf "%s-admin-credentials" .Release.Name) }} +{{- $existingSecurityConfig := lookup "v1" "Secret" .Release.Namespace (printf "%s-security-config" .Release.Name) }} +{{- $password := randAlphaNum 32 }} +{{- if and $existingAdminCreds (hasKey $existingAdminCreds.data "password") }} +{{- $password = index $existingAdminCreds.data "password" | b64dec }} +{{- end }} +--- +# Admin credentials for the OpenSearch operator (referenced by adminCredentialsSecret) +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-admin-credentials + labels: + app.kubernetes.io/name: opensearch + app.kubernetes.io/instance: {{ .Release.Name }} +type: Opaque +stringData: + username: admin + password: {{ $password | quote }} +--- +# Security plugin configuration (referenced by securityConfigSecret) +# On upgrades, the existing secret is preserved to avoid bcrypt hash regeneration +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-security-config + labels: + app.kubernetes.io/name: opensearch + app.kubernetes.io/instance: {{ .Release.Name }} +type: Opaque +{{- if and $existingSecurityConfig (hasKey $existingSecurityConfig.data "internal_users.yml") }} +data: + {{- range $key, $val := $existingSecurityConfig.data }} + {{ $key }}: {{ $val }} + {{- end }} +{{- else }} +stringData: + config.yml: | + --- + _meta: + type: "config" + config_version: 2 + config: + dynamic: + http: + anonymous_auth_enabled: false + authc: + basic_internal_auth_domain: + description: "Authenticate via HTTP Basic against internal users database" + http_enabled: true + transport_enabled: true + order: 0 + http_authenticator: + type: basic + challenge: true + authentication_backend: + type: internal + + internal_users.yml: | + --- + _meta: + type: "internalusers" + config_version: 2 + admin: + hash: {{ htpasswd "admin" $password | trimPrefix "admin:" | quote }} + reserved: true + backend_roles: + - "admin" + description: "Admin user" + + roles.yml: | + --- + _meta: + type: "roles" + config_version: 2 + + roles_mapping.yml: | + --- + _meta: + type: "rolesmapping" + config_version: 2 + all_access: + reserved: false + backend_roles: + - "admin" + + action_groups.yml: | + --- + _meta: + type: "actiongroups" + config_version: 2 + + tenants.yml: | + --- + _meta: + type: "tenants" + config_version: 2 +{{- end }} +--- +# User-facing credentials with connection URI +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-credentials + labels: + app.kubernetes.io/name: opensearch + app.kubernetes.io/instance: {{ .Release.Name }} +type: Opaque +stringData: + username: admin + password: {{ $password | quote }} + host: {{ .Release.Name }}.{{ .Release.Namespace }}.svc.{{ $clusterDomain }} + port: "9200" + uri: https://admin:{{ $password | urlquery }}@{{ .Release.Name }}.{{ .Release.Namespace }}.svc.{{ $clusterDomain }}:9200 + {{- if .Values.dashboards.enabled }} + dashboards-host: {{ .Release.Name }}-dashboards.{{ .Release.Namespace }}.svc.{{ $clusterDomain }} + dashboards-port: "5601" + dashboards-uri: https://admin:{{ $password | urlquery }}@{{ .Release.Name }}-dashboards.{{ .Release.Namespace }}.svc.{{ $clusterDomain }}:5601 + {{- end }} diff --git a/packages/apps/opensearch/templates/users.yaml b/packages/apps/opensearch/templates/users.yaml new file mode 100644 index 00000000..1d326206 --- /dev/null +++ b/packages/apps/opensearch/templates/users.yaml @@ -0,0 +1,21 @@ +{{- range $username, $user := .Values.users }} +{{- $existingSecret := lookup "v1" "Secret" $.Release.Namespace (printf "%s-user-%s" $.Release.Name $username) }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ $.Release.Name }}-user-{{ $username }} + labels: + opensearch.opster.io/credentials: "true" +type: kubernetes.io/basic-auth +stringData: + username: {{ $username | quote }} + {{- if $user.password }} + password: {{ $user.password | quote }} + {{- else if and $existingSecret (hasKey $existingSecret.data "password") }} + password: {{ index $existingSecret.data "password" | b64dec | quote }} + {{- else }} + password: {{ randAlphaNum 16 | quote }} + {{- end }} + roles: {{ $user.roles | default (list "readall") | join "," | quote }} +{{- end }} diff --git a/packages/apps/opensearch/tests/opensearch_test.yaml b/packages/apps/opensearch/tests/opensearch_test.yaml new file mode 100644 index 00000000..78850134 --- /dev/null +++ b/packages/apps/opensearch/tests/opensearch_test.yaml @@ -0,0 +1,516 @@ +suite: opensearch CR tests + +templates: + - templates/opensearch.yaml + +tests: + ################### + # Basic rendering # + ################### + + - it: renders OpenSearchCluster and WorkloadMonitor + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + asserts: + - hasDocuments: + count: 2 + - isKind: + of: OpenSearchCluster + documentIndex: 0 + - isKind: + of: WorkloadMonitor + documentIndex: 1 + + - it: sets correct CR name + release: + name: my-opensearch + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + asserts: + - equal: + path: metadata.name + value: my-opensearch + documentIndex: 0 + + ################## + # Version # + ################## + + - it: defaults to version 2.11.1 + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + asserts: + - equal: + path: spec.general.version + value: "2.11.1" + documentIndex: 0 + + - it: sets version 3.0.0 when v3 selected + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + version: v3 + asserts: + - equal: + path: spec.general.version + value: "3.0.0" + documentIndex: 0 + + - it: sets version 1.3.20 when v1 selected + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + version: v1 + asserts: + - equal: + path: spec.general.version + value: "1.3.20" + documentIndex: 0 + + ##################### + # General # + ##################### + + - it: sets drainDataNodes to true + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + asserts: + - equal: + path: spec.general.drainDataNodes + value: true + documentIndex: 0 + + - it: sets httpPort to 9200 + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + asserts: + - equal: + path: spec.general.httpPort + value: 9200 + documentIndex: 0 + + ##################### + # Security # + ##################### + + - it: always includes security section with TLS + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + asserts: + - equal: + path: spec.security.tls.transport.generate + value: true + documentIndex: 0 + - equal: + path: spec.security.tls.transport.perNode + value: true + documentIndex: 0 + - equal: + path: spec.security.tls.http.generate + value: true + documentIndex: 0 + + - it: references security config secret + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + asserts: + - equal: + path: spec.security.config.securityConfigSecret.name + value: test-os-security-config + documentIndex: 0 + + - it: references admin credentials secret + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + asserts: + - equal: + path: spec.security.config.adminCredentialsSecret.name + value: test-os-admin-credentials + documentIndex: 0 + + - it: does not disable security plugin + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + asserts: + - notExists: + path: spec.general.additionalConfig + documentIndex: 0 + + ##################### + # Node Pools # + ##################### + + - it: sets default replica count to 3 + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + asserts: + - equal: + path: spec.nodePools[0].replicas + value: 3 + documentIndex: 0 + + - it: sets replica count from values + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + replicas: 5 + asserts: + - equal: + path: spec.nodePools[0].replicas + value: 5 + documentIndex: 0 + + ##################### + # Node Roles # + ##################### + + - it: enables default node roles + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + asserts: + - contains: + path: spec.nodePools[0].roles + content: cluster_manager + documentIndex: 0 + - contains: + path: spec.nodePools[0].roles + content: data + documentIndex: 0 + - contains: + path: spec.nodePools[0].roles + content: ingest + documentIndex: 0 + + - it: disables master role when configured + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + nodeRoles: + master: false + data: true + ingest: true + ml: false + asserts: + - notContains: + path: spec.nodePools[0].roles + content: cluster_manager + documentIndex: 0 + + - it: enables ml role when configured + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + nodeRoles: + master: true + data: true + ingest: true + ml: true + asserts: + - contains: + path: spec.nodePools[0].roles + content: ml + documentIndex: 0 + + ########################### + # Topology Spread Policy # + ########################### + + - it: uses soft topology spread by default (ScheduleAnyway) + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + asserts: + - equal: + path: spec.nodePools[0].topologySpreadConstraints[0].whenUnsatisfiable + value: ScheduleAnyway + documentIndex: 0 + - equal: + path: spec.nodePools[0].topologySpreadConstraints[1].whenUnsatisfiable + value: ScheduleAnyway + documentIndex: 0 + + - it: uses hard topology spread when configured (DoNotSchedule) + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + topologySpreadPolicy: hard + asserts: + - equal: + path: spec.nodePools[0].topologySpreadConstraints[0].whenUnsatisfiable + value: DoNotSchedule + documentIndex: 0 + - equal: + path: spec.nodePools[0].topologySpreadConstraints[1].whenUnsatisfiable + value: DoNotSchedule + documentIndex: 0 + + - it: sets correct topology keys + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + asserts: + - equal: + path: spec.nodePools[0].topologySpreadConstraints[0].topologyKey + value: kubernetes.io/hostname + documentIndex: 0 + - equal: + path: spec.nodePools[0].topologySpreadConstraints[1].topologyKey + value: topology.kubernetes.io/zone + documentIndex: 0 + + ##################### + # Storage # + ##################### + + - it: sets accessModes to ReadWriteOnce + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + asserts: + - contains: + path: spec.nodePools[0].persistence.pvc.accessModes + content: ReadWriteOnce + documentIndex: 0 + + - it: sets storage size from values + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + size: 50Gi + asserts: + - equal: + path: spec.nodePools[0].diskSize + value: 50Gi + documentIndex: 0 + + - it: uses local storageClass when replicas > 1 and no storageClass + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + storageClass: "" + replicas: 3 + asserts: + - equal: + path: spec.nodePools[0].persistence.pvc.storageClass + value: local + documentIndex: 0 + + - it: uses replicated storageClass when single replica and no storageClass + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + storageClass: "" + replicas: 1 + asserts: + - equal: + path: spec.nodePools[0].persistence.pvc.storageClass + value: replicated + documentIndex: 0 + + - it: uses custom storageClass when provided + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + storageClass: fast-ssd + asserts: + - equal: + path: spec.nodePools[0].persistence.pvc.storageClass + value: fast-ssd + documentIndex: 0 + + ##################### + # Dashboards # + ##################### + + - it: does not render dashboards when disabled + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + dashboards: + enabled: false + replicas: 1 + resourcesPreset: "medium" + resources: {} + asserts: + - notExists: + path: spec.dashboards + documentIndex: 0 + + - it: renders dashboards when enabled + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + dashboards: + enabled: true + replicas: 1 + resourcesPreset: "medium" + resources: {} + asserts: + - equal: + path: spec.dashboards.enable + value: true + documentIndex: 0 + - equal: + path: spec.dashboards.replicas + value: 1 + documentIndex: 0 + - equal: + path: spec.dashboards.tls.enable + value: true + documentIndex: 0 + - equal: + path: spec.dashboards.tls.generate + value: true + documentIndex: 0 + + ########################### + # WorkloadMonitor # + ########################### + + - it: creates WorkloadMonitor with correct metadata + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + asserts: + - equal: + path: metadata.name + value: test-os + documentIndex: 1 + - equal: + path: spec.kind + value: opensearch + documentIndex: 1 + - equal: + path: spec.type + value: opensearch + documentIndex: 1 + + - it: sets replicas in WorkloadMonitor + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + replicas: 5 + asserts: + - equal: + path: spec.replicas + value: 5 + documentIndex: 1 + + - it: sets minReplicas to 1 + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + asserts: + - equal: + path: spec.minReplicas + value: 1 + documentIndex: 1 + + - it: sets correct selector labels + release: + name: mydb + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + asserts: + - equal: + path: spec.selector["opster.io/opensearch-cluster"] + value: mydb + documentIndex: 1 diff --git a/packages/apps/opensearch/tests/security_test.yaml b/packages/apps/opensearch/tests/security_test.yaml new file mode 100644 index 00000000..cd8740d9 --- /dev/null +++ b/packages/apps/opensearch/tests/security_test.yaml @@ -0,0 +1,207 @@ +suite: security secrets tests + +templates: + - templates/security.yaml + +tests: + ################### + # Basic rendering # + ################### + + - it: renders three secrets (admin-credentials, security-config, credentials) + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + asserts: + - hasDocuments: + count: 3 + + ########################### + # Admin credentials # + ########################### + + - it: sets admin-credentials secret name + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + asserts: + - equal: + path: metadata.name + value: test-os-admin-credentials + documentIndex: 0 + + - it: sets admin username + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + asserts: + - equal: + path: stringData.username + value: admin + documentIndex: 0 + + - it: generates a 32-char alphanumeric password + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + asserts: + - matchRegex: + path: stringData.password + pattern: "^[a-zA-Z0-9]{32}$" + documentIndex: 0 + + ########################### + # Security config # + ########################### + + - it: sets security-config secret name + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + asserts: + - equal: + path: metadata.name + value: test-os-security-config + documentIndex: 1 + + - it: includes config.yml with basic auth + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + asserts: + - exists: + path: stringData["config.yml"] + documentIndex: 1 + + - it: includes internal_users.yml with bcrypt hash + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + asserts: + - matchRegex: + path: stringData["internal_users.yml"] + pattern: "hash:.*\\$2a\\$" + documentIndex: 1 + + - it: includes roles_mapping.yml + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + asserts: + - exists: + path: stringData["roles_mapping.yml"] + documentIndex: 1 + + ########################### + # User-facing credentials # + ########################### + + - it: sets credentials secret name + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + asserts: + - equal: + path: metadata.name + value: test-os-credentials + documentIndex: 2 + + - it: sets correct host and port + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + asserts: + - equal: + path: stringData.host + value: test-os.tenant-test.svc.cozy.local + documentIndex: 2 + - equal: + path: stringData.port + value: "9200" + documentIndex: 2 + + - it: sets https URI with credentials + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + asserts: + - matchRegex: + path: stringData.uri + pattern: "^https://admin:[a-zA-Z0-9]{32}@test-os\\.tenant-test\\.svc\\.cozy\\.local:9200$" + documentIndex: 2 + + - it: does not include dashboards fields when dashboards disabled + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + dashboards: + enabled: false + replicas: 1 + resourcesPreset: "medium" + resources: {} + asserts: + - notExists: + path: stringData.dashboards-host + documentIndex: 2 + + - it: includes dashboards fields when dashboards enabled + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + dashboards: + enabled: true + replicas: 1 + resourcesPreset: "medium" + resources: {} + asserts: + - equal: + path: stringData.dashboards-host + value: test-os-dashboards.tenant-test.svc.cozy.local + documentIndex: 2 + - equal: + path: stringData.dashboards-port + value: "5601" + documentIndex: 2 + - matchRegex: + path: stringData.dashboards-uri + pattern: "^https://admin:[a-zA-Z0-9]{32}@test-os-dashboards\\.tenant-test\\.svc\\.cozy\\.local:5601$" + documentIndex: 2 diff --git a/packages/apps/opensearch/tests/users_test.yaml b/packages/apps/opensearch/tests/users_test.yaml new file mode 100644 index 00000000..f6b4990e --- /dev/null +++ b/packages/apps/opensearch/tests/users_test.yaml @@ -0,0 +1,176 @@ +suite: users secrets tests + +templates: + - templates/users.yaml + +tests: + ################### + # Basic rendering # + ################### + + - it: does not render secrets when no users defined + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + users: {} + asserts: + - hasDocuments: + count: 0 + + - it: renders one secret per user + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + users: + user1: + roles: + - readall + user2: + roles: + - all_access + asserts: + - hasDocuments: + count: 2 + + ##################### + # Secret metadata # + ##################### + + - it: sets correct secret name + release: + name: my-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + users: + myuser: + roles: + - readall + asserts: + - equal: + path: metadata.name + value: my-os-user-myuser + + - it: sets opensearch credentials label + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + users: + myuser: + roles: + - readall + asserts: + - equal: + path: metadata.labels["opensearch.opster.io/credentials"] + value: "true" + + - it: uses basic-auth secret type + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + users: + myuser: + roles: + - readall + asserts: + - equal: + path: type + value: kubernetes.io/basic-auth + + ##################### + # Secret data # + ##################### + + - it: sets username in secret + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + users: + testuser: + roles: + - readall + asserts: + - equal: + path: stringData.username + value: testuser + + - it: uses provided password + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + users: + myuser: + password: mysecretpassword + roles: + - readall + asserts: + - equal: + path: stringData.password + value: mysecretpassword + + - it: generates password when not provided + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + users: + myuser: + roles: + - readall + asserts: + - matchRegex: + path: stringData.password + pattern: "^[a-zA-Z0-9]{16}$" + + - it: sets roles as comma-separated string + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + users: + myuser: + roles: + - readall + - reporting_user + - monitoring_user + asserts: + - equal: + path: stringData.roles + value: "readall,reporting_user,monitoring_user" + + - it: defaults to readall role when no roles specified + release: + name: test-os + namespace: tenant-test + set: + _cluster: + cluster-domain: cozy.local + users: + myuser: {} + asserts: + - equal: + path: stringData.roles + value: "readall" diff --git a/packages/apps/opensearch/values.schema.json b/packages/apps/opensearch/values.schema.json new file mode 100644 index 00000000..54a6bb00 --- /dev/null +++ b/packages/apps/opensearch/values.schema.json @@ -0,0 +1,239 @@ +{ + "title": "Chart Values", + "type": "object", + "properties": { + "dashboards": { + "description": "OpenSearch Dashboards configuration.", + "type": "object", + "default": {}, + "required": [ + "enabled", + "replicas", + "resourcesPreset" + ], + "properties": { + "enabled": { + "description": "Enable OpenSearch Dashboards deployment.", + "type": "boolean", + "default": false + }, + "replicas": { + "description": "Number of Dashboards replicas.", + "type": "integer", + "default": 1 + }, + "resources": { + "description": "Explicit CPU and memory configuration for Dashboards.", + "type": "object", + "default": {}, + "properties": { + "cpu": { + "description": "CPU available to each node.", + "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 (RAM) available to each node.", + "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 + } + } + }, + "resourcesPreset": { + "description": "Default sizing preset for Dashboards.", + "type": "string", + "default": "medium", + "enum": [ + "nano", + "micro", + "small", + "medium", + "large", + "xlarge", + "2xlarge" + ] + } + } + }, + "external": { + "description": "Enable external access from outside the cluster.", + "type": "boolean", + "default": false + }, + "images": { + "description": "Container images used by the operator.", + "type": "object", + "default": {}, + "required": [ + "opensearch" + ], + "properties": { + "opensearch": { + "description": "OpenSearch image.", + "type": "string", + "default": "" + } + } + }, + "nodeRoles": { + "description": "Node roles configuration.", + "type": "object", + "default": {}, + "required": [ + "data", + "ingest", + "master", + "ml" + ], + "properties": { + "data": { + "description": "Enable data role.", + "type": "boolean", + "default": true + }, + "ingest": { + "description": "Enable ingest role.", + "type": "boolean", + "default": true + }, + "master": { + "description": "Enable cluster_manager role.", + "type": "boolean", + "default": true + }, + "ml": { + "description": "Enable machine learning role.", + "type": "boolean", + "default": false + } + } + }, + "replicas": { + "description": "Number of OpenSearch nodes in the cluster.", + "type": "integer", + "default": 3 + }, + "resources": { + "description": "Explicit CPU and memory configuration for each OpenSearch node. When omitted, the preset defined in `resourcesPreset` is applied.", + "type": "object", + "default": {}, + "properties": { + "cpu": { + "description": "CPU available to each node.", + "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 (RAM) available to each node.", + "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 + } + } + }, + "resourcesPreset": { + "description": "Default sizing preset used when `resources` is omitted. OpenSearch requires minimum 2Gi memory.", + "type": "string", + "default": "large", + "enum": [ + "nano", + "micro", + "small", + "medium", + "large", + "xlarge", + "2xlarge" + ] + }, + "size": { + "description": "Persistent Volume Claim size available for application data.", + "default": "10Gi", + "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 + }, + "storageClass": { + "description": "StorageClass used to store the data.", + "type": "string", + "default": "" + }, + "topologySpreadPolicy": { + "description": "How strictly to enforce pod distribution across nodes and zones.", + "type": "string", + "default": "soft", + "enum": [ + "soft", + "hard" + ] + }, + "users": { + "description": "Custom OpenSearch users configuration map.", + "type": "object", + "default": {}, + "additionalProperties": { + "type": "object", + "properties": { + "password": { + "description": "Password for the user (auto-generated if omitted).", + "type": "string" + }, + "roles": { + "description": "List of OpenSearch roles.", + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "version": { + "description": "OpenSearch major version to deploy.", + "type": "string", + "default": "v2", + "enum": [ + "v3", + "v2", + "v1" + ] + } + } +} \ No newline at end of file diff --git a/packages/apps/opensearch/values.yaml b/packages/apps/opensearch/values.yaml new file mode 100644 index 00000000..8de805e3 --- /dev/null +++ b/packages/apps/opensearch/values.yaml @@ -0,0 +1,111 @@ +## +## @section Common parameters +## + +## @typedef {struct} Resources - Explicit CPU and memory configuration for each OpenSearch node. +## @field {quantity} [cpu] - CPU available to each node. +## @field {quantity} [memory] - Memory (RAM) available to each node. + +## @enum {string} ResourcesPreset - Default sizing preset. +## @value nano +## @value micro +## @value small +## @value medium +## @value large +## @value xlarge +## @value 2xlarge + +## @param {int} replicas - Number of OpenSearch nodes in the cluster. +replicas: 3 + +## @param {Resources} [resources] - Explicit CPU and memory configuration for each OpenSearch node. When omitted, the preset defined in `resourcesPreset` is applied. +resources: {} + +## @param {ResourcesPreset} resourcesPreset="large" - Default sizing preset used when `resources` is omitted. OpenSearch requires minimum 2Gi memory. +resourcesPreset: "large" + +## @param {quantity} size - Persistent Volume Claim size available for application data. +size: 10Gi + +## @param {string} storageClass - StorageClass used to store the data. +storageClass: "" + +## @param {bool} external - Enable external access from outside the cluster. +external: false + +## @enum {string} TopologySpreadPolicy - Pod distribution policy across nodes/zones. +## @value soft - Best-effort distribution (ScheduleAnyway) - pods may be scheduled on same node if needed +## @value hard - Strict distribution (DoNotSchedule) - pods will not schedule if spread cannot be achieved + +## @param {TopologySpreadPolicy} topologySpreadPolicy="soft" - How strictly to enforce pod distribution across nodes and zones. +topologySpreadPolicy: "soft" + +## +## @enum {string} Version +## @value v3 +## @value v2 +## @value v1 + +## @param {Version} version - OpenSearch major version to deploy. +version: v2 + +## +## @section Image configuration +## + +## @typedef {struct} Images - Container image configuration. +## @field {string} opensearch - OpenSearch image. + +## @param {Images} images - Container images used by the operator. +images: + opensearch: "" + +## +## @section Node roles configuration +## + +## @typedef {struct} NodeRoles - OpenSearch node roles. +## @field {bool} master - Enable cluster_manager role. +## @field {bool} data - Enable data role. +## @field {bool} ingest - Enable ingest role. +## @field {bool} ml - Enable machine learning role. + +## @param {NodeRoles} nodeRoles - Node roles configuration. +nodeRoles: + master: true + data: true + ingest: true + ml: false + +## +## @section Users configuration +## + +## @typedef {struct} User - User configuration. +## @field {string} [password] - Password for the user (auto-generated if omitted). +## @field {[]string} roles - List of OpenSearch roles. + +## @param {map[string]User} users - Custom OpenSearch users configuration map. +users: {} +## Example: +## users: +## myuser: +## roles: +## - all_access + +## +## @section OpenSearch Dashboards configuration +## + +## @typedef {struct} Dashboards - OpenSearch Dashboards deployment configuration. +## @field {bool} enabled - Enable OpenSearch Dashboards deployment. +## @field {int} replicas - Number of Dashboards replicas. +## @field {Resources} [resources] - Explicit CPU and memory configuration for Dashboards. +## @field {ResourcesPreset} resourcesPreset - Default sizing preset for Dashboards. + +## @param {Dashboards} dashboards - OpenSearch Dashboards configuration. +dashboards: + enabled: false + replicas: 1 + resources: {} + resourcesPreset: "medium" diff --git a/packages/system/opensearch-operator/.helmignore b/packages/system/opensearch-operator/.helmignore new file mode 100644 index 00000000..0e8a0eb3 --- /dev/null +++ b/packages/system/opensearch-operator/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/packages/system/opensearch-operator/Chart.yaml b/packages/system/opensearch-operator/Chart.yaml new file mode 100644 index 00000000..a991bdca --- /dev/null +++ b/packages/system/opensearch-operator/Chart.yaml @@ -0,0 +1,3 @@ +apiVersion: v2 +name: cozy-opensearch-operator +version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process diff --git a/packages/system/opensearch-operator/Makefile b/packages/system/opensearch-operator/Makefile new file mode 100644 index 00000000..71b94549 --- /dev/null +++ b/packages/system/opensearch-operator/Makefile @@ -0,0 +1,10 @@ +export NAME=opensearch-operator +export NAMESPACE=cozy-$(NAME) + +include ../../../hack/package.mk + +update: + rm -rf charts + helm repo add opensearch-operator https://opensearch-project.github.io/opensearch-k8s-operator/ + helm repo update opensearch-operator + helm pull opensearch-operator/opensearch-operator --untar --untardir charts diff --git a/packages/system/opensearch-operator/charts/opensearch-operator/CHANGELOG.md b/packages/system/opensearch-operator/charts/opensearch-operator/CHANGELOG.md new file mode 100644 index 00000000..1866ab55 --- /dev/null +++ b/packages/system/opensearch-operator/charts/opensearch-operator/CHANGELOG.md @@ -0,0 +1,61 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +--- +## [Unreleased] +### Added +- Added support for custom image used by `kubeRbacProxy`. +### Changed +### Deprecated +### Removed +### Fixed +### Security + +--- +## [2.0.0] +### Added +### Changed +- Modified `version` to `2.0.0` and `appVersion` to `v2.0`. +- Allow chart image tag to pick from `appVersion`, unless explicitly passed `tag` values in `values.yaml` file. +### Deprecated +### Removed +### Fixed +### Security + +--- +## [1.0.3] +### Added +### Changed +- Added missing spec `dashboards.additionalConfig` +### Deprecated +### Removed +### Fixed +### Security + +--- +## [1.0.2] +### Added +### Changed +- Added README.md file to charts/ folder. +### Deprecated +### Removed +### Fixed +### Security + +--- +## [1.0.1] +### Added +### Changed +- Updated version to 1.0.1 +### Deprecated +### Removed +### Fixed +### Security + +[Unreleased]: https://github.com/opensearch-project/opensearch-k8s-operator/compare/opensearch-operator-2.0.0...HEAD +[2.0.0]: https://github.com/opensearch-project/opensearch-k8s-operator/compare/opensearch-operator-1.0.3...opensearch-operator-2.0.0 +[1.0.3]: https://github.com/opensearch-project/opensearch-k8s-operator/compare/opensearch-operator-1.0.2...opensearch-operator-1.0.3 +[1.0.2]: https://github.com/opensearch-project/opensearch-k8s-operator/compare/opensearch-operator-1.0.1...opensearch-operator-1.0.2 diff --git a/packages/system/opensearch-operator/charts/opensearch-operator/Chart.yaml b/packages/system/opensearch-operator/charts/opensearch-operator/Chart.yaml new file mode 100644 index 00000000..331cf1e2 --- /dev/null +++ b/packages/system/opensearch-operator/charts/opensearch-operator/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +appVersion: 2.8.0 +description: The OpenSearch Operator Helm chart for Kubernetes +name: opensearch-operator +type: application +version: 2.8.0 diff --git a/packages/system/opensearch-operator/charts/opensearch-operator/README.md b/packages/system/opensearch-operator/charts/opensearch-operator/README.md new file mode 100644 index 00000000..dc63c250 --- /dev/null +++ b/packages/system/opensearch-operator/charts/opensearch-operator/README.md @@ -0,0 +1,29 @@ +# OpenSearch-k8s-operator + +The Kubernetes [OpenSearch Operator](https://github.com/opensearch-project/opensearch-k8s-operator) is used for automating the deployment, provisioning, management, and orchestration of OpenSearch clusters and OpenSearch dashboards. + +## Getting started + +The Operator can be easily installed using helm on any CNCF-certified Kubernetes cluster. Please refer to the [User Guide](https://github.com/opensearch-project/opensearch-k8s-operator/blob/main/docs/userguide/main.md) for more information. + +### Installation Using Helm + +#### Get Repo Info +``` +helm repo add opensearch-operator https://opensearch-project.github.io/opensearch-k8s-operator/ +helm repo update +``` +#### Install Chart +``` +helm install [RELEASE_NAME] opensearch-operator/opensearch-operator +``` +#### Uninstall Chart +``` +helm uninstall [RELEASE_NAME] +``` +#### Upgrade Chart +``` +helm repo update +helm upgrade [RELEASE_NAME] opensearch-operator/opensearch-operator +``` + diff --git a/packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchactiongroups.yaml b/packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchactiongroups.yaml new file mode 100644 index 00000000..8ba4c7b2 --- /dev/null +++ b/packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchactiongroups.yaml @@ -0,0 +1,94 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.0 + name: opensearchactiongroups.opensearch.opster.io +spec: + group: opensearch.opster.io + names: + kind: OpensearchActionGroup + listKind: OpensearchActionGroupList + plural: opensearchactiongroups + shortNames: + - opensearchactiongroup + singular: opensearchactiongroup + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: OpensearchActionGroup is the Schema for the opensearchactiongroups + API + 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: OpensearchActionGroupSpec defines the desired state of OpensearchActionGroup + properties: + allowedActions: + items: + type: string + type: array + description: + type: string + opensearchCluster: + 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: + type: string + required: + - allowedActions + - opensearchCluster + type: object + status: + description: OpensearchActionGroupStatus defines the observed state of + OpensearchActionGroup + properties: + existingActionGroup: + type: boolean + managedCluster: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + type: string + reason: + type: string + state: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchclusters.yaml b/packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchclusters.yaml new file mode 100644 index 00000000..063bbd00 --- /dev/null +++ b/packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchclusters.yaml @@ -0,0 +1,6372 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.0 + name: opensearchclusters.opensearch.opster.io +spec: + group: opensearch.opster.io + names: + kind: OpenSearchCluster + listKind: OpenSearchClusterList + plural: opensearchclusters + shortNames: + - os + - opensearch + singular: opensearchcluster + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.health + name: health + type: string + - description: Available nodes + jsonPath: .status.availableNodes + name: nodes + type: integer + - description: Opensearch version + jsonPath: .status.version + name: version + type: string + - jsonPath: .status.phase + name: phase + type: string + - jsonPath: .metadata.creationTimestamp + name: age + type: date + name: v1 + schema: + openAPIV3Schema: + description: Es is the Schema for the es API + 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: ClusterSpec defines the desired state of OpenSearchCluster + properties: + bootstrap: + properties: + additionalConfig: + additionalProperties: + type: string + description: Extra items to add to the opensearch.yml, defaults + to General.AdditionalConfig + type: object + affinity: + description: Affinity is a group of affinity scheduling rules. + 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. + 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. + 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. + 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. + 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. + 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. + 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. + 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. + 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 + jvm: + type: string + keystore: + items: + properties: + keyMappings: + additionalProperties: + type: string + description: Key mappings from secret to keystore keys + type: object + secret: + description: Secret containing key value pairs + 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: object + type: array + nodeSelector: + additionalProperties: + type: string + type: object + pluginsList: + items: + type: string + type: array + resources: + description: ResourceRequirements describes the compute resource + requirements. + 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 + 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 + type: object + confMgmt: + description: ConfMgmt defines which additional services will be deployed + properties: + VerUpdate: + type: boolean + autoScaler: + type: boolean + smartScaler: + type: boolean + type: object + dashboards: + properties: + additionalConfig: + additionalProperties: + type: string + description: Additional properties for opensearch_dashboards.yaml + type: object + additionalVolumes: + items: + properties: + configMap: + description: ConfigMap to use to populate the 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 object to use to populate the volume + 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 + emptyDir: + description: EmptyDir to use to populate the volume + 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 + name: + description: Name to use for the volume. Required. + type: string + path: + description: Path in the container to mount the volume at. + Required. + type: string + projected: + description: Projected object to use to populate the volume + 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 + restartPods: + description: Whether to restart the pods on content change + type: boolean + secret: + description: Secret to use populate the 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 + 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 + subPath: + description: SubPath of the referenced volume to mount. + type: string + required: + - name + - path + type: object + type: array + affinity: + description: Affinity is a group of affinity scheduling rules. + 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. + 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. + 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. + 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. + 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. + 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. + 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. + 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. + 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 + annotations: + additionalProperties: + type: string + type: object + basePath: + description: Base Path for Opensearch Clusters running behind + a reverse proxy + type: string + enable: + type: boolean + env: + 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 + image: + type: string + imagePullPolicy: + description: PullPolicy describes a policy for if/when to pull + a container image + type: string + imagePullSecrets: + 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 + labels: + additionalProperties: + type: string + type: object + nodeSelector: + additionalProperties: + type: string + type: object + opensearchCredentialsSecret: + description: Secret that contains fields username and password + for dashboards to use to login to opensearch, must only be supplied + if a custom securityconfig is provided + 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 + pluginsList: + items: + type: string + type: array + podSecurityContext: + description: Set security context for the dashboards pods + 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 + 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. + 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 + replicas: + format: int32 + type: integer + resources: + description: ResourceRequirements describes the compute resource + requirements. + 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 + securityContext: + description: Set security context for the dashboards pods' container + 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 + service: + properties: + labels: + additionalProperties: + type: string + type: object + loadBalancerSourceRanges: + items: + type: string + type: array + type: + default: ClusterIP + description: Service Type string describes ingress methods + for a service + enum: + - ClusterIP + - NodePort + - LoadBalancer + type: string + type: object + tls: + properties: + caSecret: + description: Optional, secret that contains the ca certificate + as ca.crt. If this and generate=true is set the existing + CA cert from that secret is used to generate the node certs. + In this case must contain ca.crt and ca.key fields + 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 + enable: + description: Enable HTTPS for Dashboards + type: boolean + generate: + description: Generate certificate, if false secret must be + provided + type: boolean + secret: + description: Optional, name of a TLS secret that contains + ca.crt, tls.key and tls.crt data. If ca.crt is in a different + secret provide it via the caSecret field + 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: object + 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 + version: + type: string + required: + - replicas + - version + type: object + general: + description: |- + INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + Important: Run "make" to regenerate code after modifying this file + properties: + additionalConfig: + additionalProperties: + type: string + description: Extra items to add to the opensearch.yml + type: object + additionalVolumes: + description: Additional volumes to mount to all pods in the cluster + items: + properties: + configMap: + description: ConfigMap to use to populate the 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 object to use to populate the volume + 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 + emptyDir: + description: EmptyDir to use to populate the volume + 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 + name: + description: Name to use for the volume. Required. + type: string + path: + description: Path in the container to mount the volume at. + Required. + type: string + projected: + description: Projected object to use to populate the volume + 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 + restartPods: + description: Whether to restart the pods on content change + type: boolean + secret: + description: Secret to use populate the 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 + 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 + subPath: + description: SubPath of the referenced volume to mount. + type: string + required: + - name + - path + type: object + type: array + annotations: + additionalProperties: + type: string + description: Adds support for annotations in services + type: object + command: + type: string + defaultRepo: + type: string + drainDataNodes: + description: Drain data nodes controls whether to drain data notes + on rolling restart operations + type: boolean + httpPort: + default: 9200 + format: int32 + type: integer + image: + type: string + imagePullPolicy: + description: PullPolicy describes a policy for if/when to pull + a container image + type: string + imagePullSecrets: + 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 + keystore: + description: Populate opensearch keystore before startup + items: + properties: + keyMappings: + additionalProperties: + type: string + description: Key mappings from secret to keystore keys + type: object + secret: + description: Secret containing key value pairs + 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: object + type: array + monitoring: + properties: + enable: + type: boolean + labels: + additionalProperties: + type: string + type: object + monitoringUserSecret: + type: string + pluginUrl: + type: string + scrapeInterval: + type: string + tlsConfig: + properties: + insecureSkipVerify: + type: boolean + serverName: + type: string + type: object + type: object + pluginsList: + items: + type: string + type: array + podSecurityContext: + description: Set security context for the cluster pods + 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 + 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. + 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 + securityContext: + description: Set security context for the cluster pods' container + 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 + serviceAccount: + type: string + serviceName: + type: string + setVMMaxMapCount: + type: boolean + snapshotRepositories: + items: + properties: + name: + type: string + settings: + additionalProperties: + type: string + type: object + type: + type: string + required: + - name + - type + type: object + type: array + vendor: + enum: + - Opensearch + - Op + - OP + - os + - opensearch + type: string + version: + type: string + required: + - serviceName + type: object + initHelper: + properties: + image: + type: string + imagePullPolicy: + description: PullPolicy describes a policy for if/when to pull + a container image + type: string + imagePullSecrets: + 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 + resources: + description: ResourceRequirements describes the compute resource + requirements. + 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 + version: + type: string + type: object + nodePools: + items: + properties: + additionalConfig: + additionalProperties: + type: string + type: object + affinity: + description: Affinity is a group of affinity scheduling rules. + 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. + 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. + 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. + 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. + 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. + 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. + 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. + 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. + 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 + annotations: + additionalProperties: + type: string + type: object + component: + type: string + diskSize: + type: string + env: + 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 + jvm: + type: string + labels: + additionalProperties: + type: string + type: object + nodeSelector: + additionalProperties: + type: string + type: object + pdb: + properties: + enable: + type: boolean + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + minAvailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + persistence: + description: PersistencConfig defines options for data persistence + properties: + emptyDir: + description: |- + Represents an empty directory for a pod. + Empty directory volumes support ownership management and SELinux relabeling. + 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 + hostPath: + description: |- + Represents a host path mapped into a pod. + Host path volumes do not support ownership management or SELinux relabeling. + 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 + pvc: + properties: + accessModes: + items: + type: string + type: array + storageClass: + type: string + type: object + type: object + priorityClassName: + type: string + probes: + properties: + liveness: + properties: + failureThreshold: + format: int32 + type: integer + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + readiness: + properties: + failureThreshold: + format: int32 + type: integer + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + startup: + properties: + failureThreshold: + format: int32 + type: integer + initialDelaySeconds: + format: int32 + type: integer + periodSeconds: + format: int32 + type: integer + successThreshold: + format: int32 + type: integer + timeoutSeconds: + format: int32 + type: integer + type: object + type: object + replicas: + format: int32 + type: integer + resources: + description: ResourceRequirements describes the compute resource + requirements. + 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 + roles: + items: + type: string + type: array + 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 + topologySpreadConstraints: + 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. + 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. + 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 + required: + - component + - replicas + - roles + type: object + type: array + security: + description: Security defines options for managing the opensearch-security + plugin + properties: + config: + properties: + adminCredentialsSecret: + description: Secret that contains fields username and password + to be used by the operator to access the opensearch cluster + for node draining. Must be set if custom securityconfig + is provided. + 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 + adminSecret: + description: TLS Secret that contains a client certificate + (tls.key, tls.crt, ca.crt) with admin rights in the opensearch + cluster. Must be set if transport certificates are provided + by user and not generated + 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 + securityConfigSecret: + description: Secret that contains the differnt yml files of + the opensearch-security config (config.yml, internal_users.yml, + ...) + 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 + updateJob: + description: Specific configs for the SecurityConfig update + job + properties: + resources: + description: ResourceRequirements describes the compute + resource requirements. + 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 + type: object + type: object + tls: + description: Configure tls usage for transport and http interface + properties: + http: + properties: + caSecret: + description: Optional, secret that contains the ca certificate + as ca.crt. If this and generate=true is set the existing + CA cert from that secret is used to generate the node + certs. In this case must contain ca.crt and ca.key fields + 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 + generate: + description: If set to true the operator will generate + a CA and certificates for the cluster to use, if false + secrets with existing certificates must be supplied + type: boolean + secret: + description: Optional, name of a TLS secret that contains + ca.crt, tls.key and tls.crt data. If ca.crt is in a + different secret provide it via the caSecret field + 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: object + transport: + properties: + adminDn: + description: DNs of certificates that should have admin + access, mainly used for securityconfig updates via securityadmin.sh, + only used when existing certificates are provided + items: + type: string + type: array + caSecret: + description: Optional, secret that contains the ca certificate + as ca.crt. If this and generate=true is set the existing + CA cert from that secret is used to generate the node + certs. In this case must contain ca.crt and ca.key fields + 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 + generate: + description: If set to true the operator will generate + a CA and certificates for the cluster to use, if false + secrets with existing certificates must be supplied + type: boolean + nodesDn: + description: Allowed Certificate DNs for nodes, only used + when existing certificates are provided + items: + type: string + type: array + perNode: + description: Configure transport node certificate + type: boolean + secret: + description: Optional, name of a TLS secret that contains + ca.crt, tls.key and tls.crt data. If ca.crt is in a + different secret provide it via the caSecret field + 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: object + type: object + type: object + required: + - nodePools + type: object + status: + description: ClusterStatus defines the observed state of Es + properties: + availableNodes: + description: AvailableNodes is the number of available instances. + format: int32 + type: integer + componentsStatus: + items: + properties: + component: + type: string + conditions: + items: + type: string + type: array + description: + type: string + status: + type: string + type: object + type: array + health: + description: OpenSearchHealth is the health of the cluster as returned + by the health API. + type: string + initialized: + type: boolean + phase: + description: |- + INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + Important: Run "make" to regenerate code after modifying this file + type: string + version: + type: string + required: + - componentsStatus + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchcomponenttemplates.yaml b/packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchcomponenttemplates.yaml new file mode 100644 index 00000000..7d4fd549 --- /dev/null +++ b/packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchcomponenttemplates.yaml @@ -0,0 +1,136 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.0 + name: opensearchcomponenttemplates.opensearch.opster.io +spec: + group: opensearch.opster.io + names: + kind: OpensearchComponentTemplate + listKind: OpensearchComponentTemplateList + plural: opensearchcomponenttemplates + shortNames: + - opensearchcomponenttemplate + singular: opensearchcomponenttemplate + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: OpensearchComponentTemplate is the schema for the OpenSearch + component templates API + 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: + properties: + _meta: + description: Optional user metadata about the component template + x-kubernetes-preserve-unknown-fields: true + allowAutoCreate: + description: If true, then indices can be automatically created using + this template + type: boolean + name: + description: The name of the component template. Defaults to metadata.name + type: string + opensearchCluster: + 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 + template: + description: The template that should be applied + properties: + aliases: + additionalProperties: + description: Describes the specs of an index alias + properties: + alias: + description: The name of the alias. + type: string + filter: + description: Query used to limit documents the alias can + access. + x-kubernetes-preserve-unknown-fields: true + index: + description: The name of the index that the alias points + to. + type: string + isWriteIndex: + description: If true, the index is the write index for the + alias + type: boolean + routing: + description: Value used to route indexing and search operations + to a specific shard. + type: string + type: object + description: Aliases to add + type: object + mappings: + description: Mapping for fields in the index + x-kubernetes-preserve-unknown-fields: true + settings: + description: Configuration options for the index + x-kubernetes-preserve-unknown-fields: true + type: object + version: + description: Version number used to manage the component template + externally + type: integer + required: + - opensearchCluster + - template + type: object + status: + properties: + componentTemplateName: + description: Name of the currently managed component template + type: string + existingComponentTemplate: + type: boolean + managedCluster: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + type: string + reason: + type: string + state: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchindextemplates.yaml b/packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchindextemplates.yaml new file mode 100644 index 00000000..37e517ee --- /dev/null +++ b/packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchindextemplates.yaml @@ -0,0 +1,163 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.0 + name: opensearchindextemplates.opensearch.opster.io +spec: + group: opensearch.opster.io + names: + kind: OpensearchIndexTemplate + listKind: OpensearchIndexTemplateList + plural: opensearchindextemplates + shortNames: + - opensearchindextemplate + singular: opensearchindextemplate + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: OpensearchIndexTemplate is the schema for the OpenSearch index + templates API + 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: + properties: + _meta: + description: Optional user metadata about the index template + x-kubernetes-preserve-unknown-fields: true + composedOf: + description: |- + An ordered list of component template names. Component templates are merged in the order specified, + meaning that the last component template specified has the highest precedence + items: + type: string + type: array + dataStream: + description: The dataStream config that should be applied + properties: + timestamp_field: + description: TimestampField for dataStream + properties: + name: + description: Name of the field that are used for the DataStream + type: string + required: + - name + type: object + type: object + indexPatterns: + description: Array of wildcard expressions used to match the names + of indices during creation + items: + type: string + type: array + name: + description: The name of the index template. Defaults to metadata.name + type: string + opensearchCluster: + 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 + priority: + description: |- + Priority to determine index template precedence when a new data stream or index is created. + The index template with the highest priority is chosen + type: integer + template: + description: The template that should be applied + properties: + aliases: + additionalProperties: + description: Describes the specs of an index alias + properties: + alias: + description: The name of the alias. + type: string + filter: + description: Query used to limit documents the alias can + access. + x-kubernetes-preserve-unknown-fields: true + index: + description: The name of the index that the alias points + to. + type: string + isWriteIndex: + description: If true, the index is the write index for the + alias + type: boolean + routing: + description: Value used to route indexing and search operations + to a specific shard. + type: string + type: object + description: Aliases to add + type: object + mappings: + description: Mapping for fields in the index + x-kubernetes-preserve-unknown-fields: true + settings: + description: Configuration options for the index + x-kubernetes-preserve-unknown-fields: true + type: object + version: + description: Version number used to manage the component template + externally + type: integer + required: + - indexPatterns + - opensearchCluster + type: object + status: + properties: + existingIndexTemplate: + type: boolean + indexTemplateName: + description: Name of the currently managed index template + type: string + managedCluster: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + type: string + reason: + type: string + state: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchismpolicies.yaml b/packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchismpolicies.yaml new file mode 100644 index 00000000..2f4b261d --- /dev/null +++ b/packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchismpolicies.yaml @@ -0,0 +1,461 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.0 + name: opensearchismpolicies.opensearch.opster.io +spec: + group: opensearch.opster.io + names: + kind: OpenSearchISMPolicy + listKind: OpenSearchISMPolicyList + plural: opensearchismpolicies + shortNames: + - ismp + - ismpolicy + singular: opensearchismpolicy + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + 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: ISMPolicySpec is the specification for the ISM policy for + OS. + properties: + applyToExistingIndices: + description: If true, apply the policy to existing indices that match + the index patterns in the ISM template. + type: boolean + defaultState: + description: The default starting state for each index that uses this + policy. + type: string + description: + description: A human-readable description of the policy. + type: string + errorNotification: + properties: + channel: + type: string + destination: + description: The destination URL. + properties: + amazon: + properties: + url: + type: string + type: object + chime: + properties: + url: + type: string + type: object + customWebhook: + properties: + url: + type: string + type: object + slack: + properties: + url: + type: string + type: object + type: object + messageTemplate: + description: The text of the message + properties: + source: + type: string + type: object + type: object + ismTemplate: + description: Specify an ISM template pattern that matches the index + to apply the policy. + properties: + indexPatterns: + description: Index patterns on which this policy has to be applied + items: + type: string + type: array + priority: + description: Priority of the template, defaults to 0 + type: integer + required: + - indexPatterns + type: object + opensearchCluster: + 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 + policyId: + type: string + states: + description: The states that you define in the policy. + items: + properties: + actions: + description: The actions to execute after entering a state. + items: + description: Actions are the steps that the policy sequentially + executes on entering a specific state. + properties: + alias: + properties: + actions: + description: Allocate the index to a node with a specified + attribute. + items: + properties: + add: + properties: + aliases: + description: The name of the alias. + items: + type: string + type: array + index: + description: The name of the index that + the alias points to. + type: string + isWriteIndex: + description: Specify the index that accepts + any write operations to the alias. + type: boolean + routing: + description: Limit search to an associated + shard value + type: string + type: object + remove: + properties: + aliases: + description: The name of the alias. + items: + type: string + type: array + index: + description: The name of the index that + the alias points to. + type: string + isWriteIndex: + description: Specify the index that accepts + any write operations to the alias. + type: boolean + routing: + description: Limit search to an associated + shard value + type: string + type: object + type: object + type: array + required: + - actions + type: object + allocation: + description: Allocate the index to a node with a specific + attribute set + properties: + exclude: + description: Allocate the index to a node with a specified + attribute. + type: string + include: + description: Allocate the index to a node with any + of the specified attributes. + type: string + require: + description: Don’t allocate the index to a node with + any of the specified attributes. + type: string + waitFor: + description: Wait for the policy to execute before + allocating the index to a node with a specified + attribute. + type: string + required: + - exclude + - include + - require + - waitFor + type: object + close: + description: Closes the managed index. + type: object + delete: + description: Deletes a managed index. + type: object + forceMerge: + description: Reduces the number of Lucene segments by + merging the segments of individual shards. + properties: + maxNumSegments: + description: The number of segments to reduce the + shard to. + format: int64 + type: integer + required: + - maxNumSegments + type: object + indexPriority: + description: Set the priority for the index in a specific + state. + properties: + priority: + description: The priority for the index as soon as + it enters a state. + format: int64 + type: integer + required: + - priority + type: object + notification: + description: Name string `json:"name,omitempty"` + properties: + destination: + type: string + messageTemplate: + properties: + source: + type: string + type: object + required: + - destination + - messageTemplate + type: object + open: + description: Opens a managed index. + type: object + readOnly: + description: Sets a managed index to be read only. + type: object + readWrite: + description: Sets a managed index to be writeable. + type: object + replicaCount: + description: Sets the number of replicas to assign to + an index. + properties: + numberOfReplicas: + format: int64 + type: integer + required: + - numberOfReplicas + type: object + retry: + description: The retry configuration for the action. + properties: + backoff: + description: The backoff policy type to use when retrying. + type: string + count: + description: The number of retry counts. + format: int64 + type: integer + delay: + description: The time to wait between retries. + type: string + required: + - count + type: object + rollover: + description: Rolls an alias over to a new index when the + managed index meets one of the rollover conditions. + properties: + minDocCount: + description: The minimum number of documents required + to roll over the index. + format: int64 + type: integer + minIndexAge: + description: The minimum age required to roll over + the index. + type: string + minPrimaryShardSize: + description: The minimum storage size of a single + primary shard required to roll over the index. + type: string + minSize: + description: The minimum size of the total primary + shard storage (not counting replicas) required to + roll over the index. + type: string + type: object + rollup: + description: Periodically reduce data granularity by rolling + up old data into summarized indexes. + type: object + shrink: + description: Allows you to reduce the number of primary + shards in your indexes + properties: + forceUnsafe: + description: If true, executes the shrink action even + if there are no replicas. + type: boolean + maxShardSize: + description: The maximum size in bytes of a shard + for the target index. + type: string + numNewShards: + description: The maximum number of primary shards + in the shrunken index. + type: integer + percentageOfSourceShards: + description: Percentage of the number of original + primary shards to shrink. + format: int64 + type: integer + targetIndexNameTemplate: + description: The name of the shrunken index. + type: string + type: object + snapshot: + description: Back up your cluster’s indexes and state + properties: + repository: + description: The repository name that you register + through the native snapshot API operations. + type: string + snapshot: + description: The name of the snapshot. + type: string + required: + - repository + - snapshot + type: object + timeout: + description: The timeout period for the action. Accepts + time units for minutes, hours, and days. + type: string + type: object + type: array + name: + description: The name of the state. + type: string + transitions: + description: The next states and the conditions required to + transition to those states. If no transitions exist, the policy + assumes that it’s complete and can now stop managing the index + items: + properties: + conditions: + description: conditions for the transition. + properties: + cron: + description: The cron job that triggers the transition + if no other transition happens first. + properties: + cron: + description: A wrapper for the cron job that triggers + the transition if no other transition happens + first. This wrapper is here to adhere to the + OpenSearch API. + properties: + expression: + description: The cron expression that triggers + the transition. + type: string + timezone: + description: The timezone that triggers the + transition. + type: string + required: + - expression + - timezone + type: object + required: + - cron + type: object + minDocCount: + description: The minimum document count of the index + required to transition. + format: int64 + type: integer + minIndexAge: + description: The minimum age of the index required + to transition. + type: string + minRolloverAge: + description: The minimum age required after a rollover + has occurred to transition to the next state. + type: string + minSize: + description: The minimum size of the total primary + shard storage (not counting replicas) required to + transition. + type: string + type: object + stateName: + description: The name of the state to transition to if + the conditions are met. + type: string + required: + - conditions + - stateName + type: object + type: array + required: + - actions + - name + type: object + type: array + required: + - defaultState + - description + - states + type: object + status: + description: OpensearchISMPolicyStatus defines the observed state of OpensearchISMPolicy + properties: + existingISMPolicy: + type: boolean + managedCluster: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + type: string + policyId: + type: string + reason: + type: string + state: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchroles.yaml b/packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchroles.yaml new file mode 100644 index 00000000..36ae514a --- /dev/null +++ b/packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchroles.yaml @@ -0,0 +1,123 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.0 + name: opensearchroles.opensearch.opster.io +spec: + group: opensearch.opster.io + names: + kind: OpensearchRole + listKind: OpensearchRoleList + plural: opensearchroles + shortNames: + - opensearchrole + singular: opensearchrole + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: OpensearchRole is the Schema for the opensearchroles API + 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: OpensearchRoleSpec defines the desired state of OpensearchRole + properties: + clusterPermissions: + items: + type: string + type: array + indexPermissions: + items: + properties: + allowedActions: + items: + type: string + type: array + dls: + type: string + fls: + items: + type: string + type: array + indexPatterns: + items: + type: string + type: array + maskedFields: + items: + type: string + type: array + type: object + type: array + opensearchCluster: + 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 + tenantPermissions: + items: + properties: + allowedActions: + items: + type: string + type: array + tenantPatterns: + items: + type: string + type: array + type: object + type: array + required: + - opensearchCluster + type: object + status: + description: OpensearchRoleStatus defines the observed state of OpensearchRole + properties: + existingRole: + type: boolean + managedCluster: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + type: string + reason: + type: string + state: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchsnapshotpolicies.yaml b/packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchsnapshotpolicies.yaml new file mode 100644 index 00000000..2c32048d --- /dev/null +++ b/packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchsnapshotpolicies.yaml @@ -0,0 +1,203 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.0 + name: opensearchsnapshotpolicies.opensearch.opster.io +spec: + group: opensearch.opster.io + names: + kind: OpensearchSnapshotPolicy + listKind: OpensearchSnapshotPolicyList + plural: opensearchsnapshotpolicies + singular: opensearchsnapshotpolicy + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Existing policy state + jsonPath: .status.existingSnapshotPolicy + name: existingpolicy + type: boolean + - description: Snapshot policy name + jsonPath: .status.snapshotPolicyName + name: policyName + type: string + - jsonPath: .status.state + name: state + type: string + - jsonPath: .metadata.creationTimestamp + name: age + type: date + name: v1 + schema: + openAPIV3Schema: + description: OpensearchSnapshotPolicy is the Schema for the opensearchsnapshotpolicies + API + 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: + properties: + creation: + properties: + schedule: + properties: + cron: + properties: + expression: + type: string + timezone: + type: string + required: + - expression + - timezone + type: object + required: + - cron + type: object + timeLimit: + type: string + required: + - schedule + type: object + deletion: + properties: + deleteCondition: + properties: + maxAge: + type: string + maxCount: + type: integer + minCount: + type: integer + type: object + schedule: + properties: + cron: + properties: + expression: + type: string + timezone: + type: string + required: + - expression + - timezone + type: object + required: + - cron + type: object + timeLimit: + type: string + type: object + description: + type: string + enabled: + type: boolean + notification: + properties: + channel: + properties: + id: + type: string + required: + - id + type: object + conditions: + properties: + creation: + type: boolean + deletion: + type: boolean + failure: + type: boolean + type: object + required: + - channel + type: object + opensearchCluster: + 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 + policyName: + type: string + snapshotConfig: + properties: + dateFormat: + type: string + dateFormatTimezone: + type: string + ignoreUnavailable: + type: boolean + includeGlobalState: + type: boolean + indices: + type: string + metadata: + additionalProperties: + type: string + type: object + partial: + type: boolean + repository: + type: string + required: + - repository + type: object + required: + - creation + - opensearchCluster + - policyName + - snapshotConfig + type: object + status: + description: OpensearchSnapshotPolicyStatus defines the observed state + of OpensearchSnapshotPolicy + properties: + existingSnapshotPolicy: + type: boolean + managedCluster: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + type: string + reason: + type: string + snapshotPolicyName: + type: string + state: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchtenants.yaml b/packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchtenants.yaml new file mode 100644 index 00000000..d085f3ca --- /dev/null +++ b/packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchtenants.yaml @@ -0,0 +1,85 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.0 + name: opensearchtenants.opensearch.opster.io +spec: + group: opensearch.opster.io + names: + kind: OpensearchTenant + listKind: OpensearchTenantList + plural: opensearchtenants + shortNames: + - opensearchtenant + singular: opensearchtenant + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: OpensearchTenant is the Schema for the opensearchtenants API + 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: OpensearchTenantSpec defines the desired state of OpensearchTenant + properties: + description: + type: string + opensearchCluster: + 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 + required: + - opensearchCluster + type: object + status: + description: OpensearchTenantStatus defines the observed state of OpensearchTenant + properties: + existingTenant: + type: boolean + managedCluster: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + type: string + reason: + type: string + state: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchuserrolebindings.yaml b/packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchuserrolebindings.yaml new file mode 100644 index 00000000..2e9453c6 --- /dev/null +++ b/packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchuserrolebindings.yaml @@ -0,0 +1,109 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.0 + name: opensearchuserrolebindings.opensearch.opster.io +spec: + group: opensearch.opster.io + names: + kind: OpensearchUserRoleBinding + listKind: OpensearchUserRoleBindingList + plural: opensearchuserrolebindings + shortNames: + - opensearchuserrolebinding + singular: opensearchuserrolebinding + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: OpensearchUserRoleBinding is the Schema for the opensearchuserrolebindings + API + 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: OpensearchUserRoleBindingSpec defines the desired state of + OpensearchUserRoleBinding + properties: + backendRoles: + items: + type: string + type: array + opensearchCluster: + 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 + roles: + items: + type: string + type: array + users: + items: + type: string + type: array + required: + - opensearchCluster + - roles + type: object + status: + description: OpensearchUserRoleBindingStatus defines the observed state + of OpensearchUserRoleBinding + properties: + managedCluster: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + type: string + provisionedBackendRoles: + items: + type: string + type: array + provisionedRoles: + items: + type: string + type: array + provisionedUsers: + items: + type: string + type: array + reason: + type: string + state: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchusers.yaml b/packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchusers.yaml new file mode 100644 index 00000000..8d573ee7 --- /dev/null +++ b/packages/system/opensearch-operator/charts/opensearch-operator/files/opensearch.opster.io_opensearchusers.yaml @@ -0,0 +1,117 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.0 + name: opensearchusers.opensearch.opster.io +spec: + group: opensearch.opster.io + names: + kind: OpensearchUser + listKind: OpensearchUserList + plural: opensearchusers + shortNames: + - opensearchuser + singular: opensearchuser + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: OpensearchUser is the Schema for the opensearchusers API + 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: OpensearchUserSpec defines the desired state of OpensearchUser + properties: + attributes: + additionalProperties: + type: string + type: object + backendRoles: + items: + type: string + type: array + opendistroSecurityRoles: + items: + type: string + type: array + opensearchCluster: + 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 + passwordFrom: + description: SecretKeySelector selects a key of a Secret. + 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 + required: + - opensearchCluster + - passwordFrom + type: object + status: + description: OpensearchUserStatus defines the observed state of OpensearchUser + properties: + managedCluster: + description: |- + UID is a type that holds unique ID values, including UUIDs. Because we + don't ONLY use UUIDs, this is an alias to string. Being a type captures + intent and helps make sure that UIDs and names do not get conflated. + type: string + reason: + type: string + state: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/packages/system/opensearch-operator/charts/opensearch-operator/templates/_helpers.tpl b/packages/system/opensearch-operator/charts/opensearch-operator/templates/_helpers.tpl new file mode 100644 index 00000000..32f313e3 --- /dev/null +++ b/packages/system/opensearch-operator/charts/opensearch-operator/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "opensearch-operator.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "opensearch-operator.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "opensearch-operator.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "opensearch-operator.labels" -}} +helm.sh/chart: {{ include "opensearch-operator.chart" . }} +{{ include "opensearch-operator.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "opensearch-operator.selectorLabels" -}} +app.kubernetes.io/name: {{ include "opensearch-operator.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "opensearch-operator.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (printf "%s-%s" (include "opensearch-operator.fullname" .) "controller-manager") .Values.serviceAccount.name }} +{{- else }} +{{- default "opensearch-operator-controller-manager" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-controller-manager-deployment.yaml b/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-controller-manager-deployment.yaml new file mode 100644 index 00000000..4b5cb194 --- /dev/null +++ b/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-controller-manager-deployment.yaml @@ -0,0 +1,94 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + control-plane: controller-manager + name: {{ include "opensearch-operator.fullname" . }}-controller-manager +spec: + replicas: 1 + selector: + matchLabels: + control-plane: controller-manager + template: + metadata: + labels: + control-plane: controller-manager + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + containers: + {{- if or (.Values.kubeRbacProxy.enable) (eq (.Values.kubeRbacProxy.enable | toString) "") }} + - args: + - --secure-listen-address=0.0.0.0:8443 + - --upstream=http://127.0.0.1:8080/ + - --proxy-endpoints-port=10443 + - --logtostderr=true + - --v=10 + image: "{{ .Values.kubeRbacProxy.image.repository }}:{{ .Values.kubeRbacProxy.image.tag}}" + name: kube-rbac-proxy + resources: +{{- toYaml .Values.kubeRbacProxy.resources | nindent 10 }} + readinessProbe: +{{- toYaml .Values.kubeRbacProxy.readinessProbe | nindent 10 }} + livenessProbe: +{{- toYaml .Values.kubeRbacProxy.livenessProbe | nindent 10 }} + securityContext: +{{- toYaml .Values.kubeRbacProxy.securityContext | nindent 10 }} + ports: + - containerPort: 8443 + name: https + - containerPort: 10443 + name: https-proxy + protocol: TCP + {{- end}} + - args: + - --health-probe-bind-address=:8081 + - --metrics-bind-address=127.0.0.1:8080 + - --leader-elect + {{- if .Values.manager.watchNamespace }} + - --watch-namespace={{ .Values.manager.watchNamespace }} + {{- end }} + - --loglevel={{ .Values.manager.loglevel }} + command: + - /manager + image: "{{ .Values.manager.image.repository }}:{{ .Values.manager.image.tag | default .Chart.AppVersion }}" + name: operator-controller-manager + imagePullPolicy: "{{ .Values.manager.image.pullPolicy }}" + resources: +{{- toYaml .Values.manager.resources | nindent 10 }} + readinessProbe: +{{- toYaml .Values.manager.readinessProbe | nindent 10 }} + livenessProbe: +{{- toYaml .Values.manager.livenessProbe | nindent 10 }} + env: + - name: DNS_BASE + value: {{ .Values.manager.dnsBase }} + - name: PARALLEL_RECOVERY_ENABLED + value: "{{ .Values.manager.parallelRecoveryEnabled }}" + - name: PPROF_ENDPOINTS_ENABLED + value: "{{ .Values.manager.pprofEndpointsEnabled }}" + {{- if .Values.manager.extraEnv }} + {{- toYaml .Values.manager.extraEnv | nindent 8 }} + {{- end }} + securityContext: +{{- toYaml .Values.manager.securityContext | nindent 10 }} + nodeSelector: +{{- toYaml .Values.nodeSelector | nindent 8 }} + tolerations: +{{- toYaml .Values.tolerations | nindent 8 }} + securityContext: +{{- toYaml .Values.securityContext | nindent 8}} + {{- if .Values.manager.imagePullSecrets }} + imagePullSecrets: + {{- toYaml .Values.manager.imagePullSecrets | nindent 6 }} + {{- end }} + serviceAccountName: {{ include "opensearch-operator.serviceAccountName" . }} + terminationGracePeriodSeconds: 10 + {{- if .Values.priorityClassName }} + priorityClassName: {{ .Values.priorityClassName }} + {{- end }} diff --git a/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-controller-manager-metrics-service-svc.yaml b/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-controller-manager-metrics-service-svc.yaml new file mode 100644 index 00000000..e4c6e820 --- /dev/null +++ b/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-controller-manager-metrics-service-svc.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + control-plane: controller-manager + name: {{ include "opensearch-operator.fullname" . }}-controller-manager-metrics-service +spec: + ports: + - name: https + port: 8443 + targetPort: https + selector: + control-plane: controller-manager diff --git a/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-controller-manager-sa.yaml b/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-controller-manager-sa.yaml new file mode 100644 index 00000000..ee5a1ca2 --- /dev/null +++ b/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-controller-manager-sa.yaml @@ -0,0 +1,6 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "opensearch-operator.serviceAccountName" . }} +{{- end -}} diff --git a/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-crds.yaml b/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-crds.yaml new file mode 100644 index 00000000..21fdeedb --- /dev/null +++ b/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-crds.yaml @@ -0,0 +1,5 @@ +{{- if .Values.installCRDs -}} +{{- range $path, $bytes := .Files.Glob "files/*.yaml" }} +{{ $.Files.Get $path }} +{{- end }} +{{- end }} diff --git a/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-leader-election-role-role.yaml b/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-leader-election-role-role.yaml new file mode 100644 index 00000000..aa8853e0 --- /dev/null +++ b/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-leader-election-role-role.yaml @@ -0,0 +1,48 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "opensearch-operator.fullname" . }}-leader-election-role +rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +- apiGroups: + - monitoring.coreos.com + resources: + - servicemonitors + verbs: + - create + - delete + - get + - list + - patch + - update + - watch diff --git a/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-leader-election-rolebinding-rb.yaml b/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-leader-election-rolebinding-rb.yaml new file mode 100644 index 00000000..654a0a7d --- /dev/null +++ b/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-leader-election-rolebinding-rb.yaml @@ -0,0 +1,11 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "opensearch-operator.fullname" . }}-leader-election-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "opensearch-operator.fullname" . }}-leader-election-role +subjects: +- kind: ServiceAccount + name: {{ include "opensearch-operator.serviceAccountName" . }} diff --git a/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-manager-config-cm.yaml b/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-manager-config-cm.yaml new file mode 100644 index 00000000..c2e9a13f --- /dev/null +++ b/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-manager-config-cm.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +data: + controller_manager_config.yaml: | + apiVersion: controller-runtime.sigs.k8s.io/v1alpha1 + kind: ControllerManagerConfig + health: + healthProbeBindAddress: :8081 + metrics: + bindAddress: 127.0.0.1:8080 + webhook: + port: 9443 + leaderElection: + leaderElect: true + resourceName: a867c7dc.opensearch.opster.io +kind: ConfigMap +metadata: + name: {{ include "opensearch-operator.fullname" . }}-manager-config diff --git a/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-manager-role-cr.yaml b/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-manager-role-cr.yaml new file mode 100644 index 00000000..3daf70d4 --- /dev/null +++ b/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-manager-role-cr.yaml @@ -0,0 +1,414 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + creationTimestamp: null + name: {{ include "opensearch-operator.fullname" . }}-{{ .Release.Namespace }}-manager-role +rules: +- apiGroups: + - apps + resources: + - deployments + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - apps + resources: + - statefulsets + - statefulsets/status + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - batch + resources: + - jobs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - configmaps + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - update +- apiGroups: + - "" + resources: + - namespaces + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - pods + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - secrets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - services + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - "policy" + resources: + - poddisruptionbudgets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - opensearch.opster.io + resources: + - events + verbs: + - create + - patch +- apiGroups: + - opensearch.opster.io + resources: + - opensearchactiongroups + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - opensearch.opster.io + resources: + - opensearchactiongroups/finalizers + verbs: + - update +- apiGroups: + - opensearch.opster.io + resources: + - opensearchactiongroups/status + verbs: + - get + - patch + - update +- apiGroups: + - opensearch.opster.io + resources: + - opensearchclusters + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - opensearch.opster.io + resources: + - opensearchclusters/finalizers + verbs: + - update +- apiGroups: + - opensearch.opster.io + resources: + - opensearchclusters/status + verbs: + - get + - patch + - update +- apiGroups: + - opensearch.opster.io + resources: + - opensearchcomponenttemplates + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - opensearch.opster.io + resources: + - opensearchcomponenttemplates/finalizers + verbs: + - update +- apiGroups: + - opensearch.opster.io + resources: + - opensearchcomponenttemplates/status + verbs: + - get + - patch + - update +- apiGroups: + - opensearch.opster.io + resources: + - opensearchindextemplates + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - opensearch.opster.io + resources: + - opensearchindextemplates/finalizers + verbs: + - update +- apiGroups: + - opensearch.opster.io + resources: + - opensearchindextemplates/status + verbs: + - get + - patch + - update +- apiGroups: + - opensearch.opster.io + resources: + - opensearchroles + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - opensearch.opster.io + resources: + - opensearchroles/finalizers + verbs: + - update +- apiGroups: + - opensearch.opster.io + resources: + - opensearchroles/status + verbs: + - get + - patch + - update +- apiGroups: + - opensearch.opster.io + resources: + - opensearchtenants + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - opensearch.opster.io + resources: + - opensearchtenants/finalizers + verbs: + - update +- apiGroups: + - opensearch.opster.io + resources: + - opensearchtenants/status + verbs: + - get + - patch + - update +- apiGroups: + - opensearch.opster.io + resources: + - opensearchuserrolebindings + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - opensearch.opster.io + resources: + - opensearchuserrolebindings/finalizers + verbs: + - update +- apiGroups: + - opensearch.opster.io + resources: + - opensearchuserrolebindings/status + verbs: + - get + - patch + - update +- apiGroups: + - opensearch.opster.io + resources: + - opensearchismpolicies/status + verbs: + - get + - patch + - update +- apiGroups: + - opensearch.opster.io + resources: + - opensearchismpolicies/finalizers + verbs: + - update +- apiGroups: + - opensearch.opster.io + resources: + - opensearchusers + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - opensearch.opster.io + resources: + - opensearchismpolicies + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - opensearch.opster.io + resources: + - opensearchusers/finalizers + verbs: + - update +- apiGroups: + - opensearch.opster.io + resources: + - opensearchusers/status + verbs: + - get + - patch + - update +- apiGroups: + - "" + resources: + - persistentvolumeclaims + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - monitoring.coreos.com + resources: + - servicemonitors + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - opensearch.opster.io + resources: + - opensearchsnapshotpolicies + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - opensearch.opster.io + resources: + - opensearchsnapshotpolicies/status + verbs: + - get + - patch + - update +- apiGroups: + - opensearch.opster.io + resources: + - opensearchsnapshotpolicies/finalizers + verbs: + - update \ No newline at end of file diff --git a/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-manager-rolebinding.yaml b/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-manager-rolebinding.yaml new file mode 100644 index 00000000..a528ffdf --- /dev/null +++ b/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-manager-rolebinding.yaml @@ -0,0 +1,27 @@ +{{- if .Values.useRoleBindings }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "opensearch-operator.fullname" . }}-{{ .Release.Namespace }}-manager-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "opensearch-operator.fullname" . }}-{{ .Release.Namespace }}-manager-role +subjects: +- kind: ServiceAccount + name: {{ include "opensearch-operator.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +{{- else }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "opensearch-operator.fullname" . }}-{{ .Release.Namespace }}-manager-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "opensearch-operator.fullname" . }}-{{ .Release.Namespace }}-manager-role +subjects: +- kind: ServiceAccount + name: {{ include "opensearch-operator.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-metrics-reader-cr.yaml b/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-metrics-reader-cr.yaml new file mode 100644 index 00000000..c8f9d89c --- /dev/null +++ b/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-metrics-reader-cr.yaml @@ -0,0 +1,9 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "opensearch-operator.fullname" . }}-{{ .Release.Namespace }}-metrics-reader +rules: +- nonResourceURLs: + - /metrics + verbs: + - get diff --git a/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-proxy-role-cr.yaml b/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-proxy-role-cr.yaml new file mode 100644 index 00000000..cbd6cb77 --- /dev/null +++ b/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-proxy-role-cr.yaml @@ -0,0 +1,17 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "opensearch-operator.fullname" . }}-{{ .Release.Namespace }}-proxy-role +rules: +- apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create +- apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create diff --git a/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-proxy-rolebinding.yaml b/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-proxy-rolebinding.yaml new file mode 100644 index 00000000..d9a5d339 --- /dev/null +++ b/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-proxy-rolebinding.yaml @@ -0,0 +1,27 @@ +{{- if .Values.useRoleBindings }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "opensearch-operator.fullname" . }}-{{ .Release.Namespace }}-proxy-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "opensearch-operator.fullname" . }}-{{ .Release.Namespace }}-proxy-role +subjects: +- kind: ServiceAccount + name: {{ include "opensearch-operator.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +{{- else }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "opensearch-operator.fullname" . }}-{{ .Release.Namespace }}-proxy-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "opensearch-operator.fullname" . }}-{{ .Release.Namespace }}-proxy-role +subjects: +- kind: ServiceAccount + name: {{ include "opensearch-operator.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/packages/system/opensearch-operator/charts/opensearch-operator/values.yaml b/packages/system/opensearch-operator/charts/opensearch-operator/values.yaml new file mode 100644 index 00000000..1ee839bb --- /dev/null +++ b/packages/system/opensearch-operator/charts/opensearch-operator/values.yaml @@ -0,0 +1,123 @@ +nameOverride: "" +fullnameOverride: "" + +podAnnotations: {} +podLabels: {} +nodeSelector: {} +tolerations: [] +securityContext: + runAsNonRoot: true +priorityClassName: "" +manager: + securityContext: + allowPrivilegeEscalation: false + extraEnv: [] + resources: + limits: + cpu: 200m + memory: 500Mi + requests: + cpu: 100m + memory: 350Mi + + livenessProbe: + failureThreshold: 3 + httpGet: + path: /healthz + port: 8081 + periodSeconds: 15 + successThreshold: 1 + timeoutSeconds: 3 + initialDelaySeconds: 10 + + readinessProbe: + failureThreshold: 3 + httpGet: + path: /readyz + port: 8081 + periodSeconds: 15 + successThreshold: 1 + timeoutSeconds: 3 + initialDelaySeconds: 10 + + # Set this to false to disable the experimental parallel recovery in case you are experiencing problems + parallelRecoveryEnabled: true + # Set this to true to enable the standard go pprof endpoints on port 6060 (https://pkg.go.dev/net/http/pprof) + # Should only be used for debugging purposes + pprofEndpointsEnabled: false + + image: + repository: opensearchproject/opensearch-operator + ## tag default uses appVersion from Chart.yaml, to override specify tag tag: "v1.1" + tag: "" + pullPolicy: "Always" + + ## Optional array of imagePullSecrets containing private registry credentials + imagePullSecrets: [] + # - name: secretName + + dnsBase: cluster.local + + # Log level of the operator. Possible values: debug, info, warn, error + loglevel: info + + # If a watchNamespace is specified, the manager's cache will be restricted to + # watch objects in the desired namespace. Defaults is to watch all namespaces. + watchNamespace: + +# Install the Custom Resource Definitions with Helm +installCRDs: true + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Override the service account name. Defaults to opensearch-operator-controller-manager + name: "" + +kubeRbacProxy: + enable: true + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + resources: + limits: + cpu: 50m + memory: 50Mi + requests: + cpu: 25m + memory: 25Mi + + livenessProbe: + failureThreshold: 3 + httpGet: + path: /healthz + port: 10443 + scheme: HTTPS + periodSeconds: 15 + successThreshold: 1 + timeoutSeconds: 3 + initialDelaySeconds: 10 + + readinessProbe: + failureThreshold: 3 + httpGet: + path: /healthz + port: 10443 + scheme: HTTPS + periodSeconds: 15 + successThreshold: 1 + timeoutSeconds: 3 + initialDelaySeconds: 10 + + image: + repository: "gcr.io/kubebuilder/kube-rbac-proxy" + tag: "v0.15.0" + +## If this is set to true, RoleBindings will be used instead of ClusterRoleBindings, inorder to restrict ClusterRoles +## to the namespace where the operator and OpenSearch cluster are in. In that case, specify the namespace where they +## are in in manager.watchNamespace field. +## If false, ClusterRoleBindings will be used +useRoleBindings: false diff --git a/packages/system/opensearch-operator/templates/sysctl-daemonset.yaml b/packages/system/opensearch-operator/templates/sysctl-daemonset.yaml new file mode 100644 index 00000000..76c3569e --- /dev/null +++ b/packages/system/opensearch-operator/templates/sysctl-daemonset.yaml @@ -0,0 +1,64 @@ +--- +# DaemonSet to configure vm.max_map_count on all nodes for OpenSearch +# This runs once on each node to ensure the kernel parameter is set +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: opensearch-sysctl-setter + labels: + app.kubernetes.io/name: opensearch-sysctl-setter + app.kubernetes.io/component: sysctl +spec: + selector: + matchLabels: + app.kubernetes.io/name: opensearch-sysctl-setter + template: + metadata: + labels: + app.kubernetes.io/name: opensearch-sysctl-setter + spec: + initContainers: + - name: sysctl + image: busybox:1.36 + securityContext: + privileged: true + resources: + requests: + cpu: 1m + memory: 1Mi + limits: + cpu: 50m + memory: 16Mi + command: + - sh + - -c + - | + sysctl -w vm.max_map_count=262144 + # Keep the value persistent by writing to sysctl.conf if writable + if [ -w /host-etc/sysctl.conf ]; then + grep -q "vm.max_map_count" /host-etc/sysctl.conf || echo "vm.max_map_count=262144" >> /host-etc/sysctl.conf + fi + volumeMounts: + - name: host-etc + mountPath: /host-etc + readOnly: false + containers: + - name: pause + image: registry.k8s.io/pause:3.9 + resources: + requests: + cpu: 1m + memory: 1Mi + limits: + cpu: 10m + memory: 10Mi + volumes: + - name: host-etc + hostPath: + path: /etc + type: Directory + tolerations: + - operator: Exists + effect: NoSchedule + - operator: Exists + effect: NoExecute diff --git a/packages/system/opensearch-operator/values.yaml b/packages/system/opensearch-operator/values.yaml new file mode 100644 index 00000000..619aa99b --- /dev/null +++ b/packages/system/opensearch-operator/values.yaml @@ -0,0 +1,4 @@ +opensearch-operator: + manager: + # Enable cluster-wide mode to watch all namespaces + watchNamespace: "" diff --git a/packages/system/opensearch-rd/Chart.yaml b/packages/system/opensearch-rd/Chart.yaml new file mode 100644 index 00000000..c0ca3b86 --- /dev/null +++ b/packages/system/opensearch-rd/Chart.yaml @@ -0,0 +1,3 @@ +apiVersion: v2 +name: opensearch-rd +version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process diff --git a/packages/system/opensearch-rd/Makefile b/packages/system/opensearch-rd/Makefile new file mode 100644 index 00000000..7d3c0311 --- /dev/null +++ b/packages/system/opensearch-rd/Makefile @@ -0,0 +1,4 @@ +export NAME=opensearch-rd +export NAMESPACE=cozy-system + +include ../../../hack/package.mk diff --git a/packages/system/opensearch-rd/cozyrds/opensearch.yaml b/packages/system/opensearch-rd/cozyrds/opensearch.yaml new file mode 100644 index 00000000..be15d8a1 --- /dev/null +++ b/packages/system/opensearch-rd/cozyrds/opensearch.yaml @@ -0,0 +1,42 @@ +apiVersion: cozystack.io/v1alpha1 +kind: ApplicationDefinition +metadata: + name: opensearch +spec: + application: + kind: OpenSearch + singular: opensearch + plural: opensearches + openAPISchema: |- + {"title":"Chart Values","type":"object","properties":{"dashboards":{"description":"OpenSearch Dashboards configuration.","type":"object","default":{},"required":["enabled","replicas","resourcesPreset"],"properties":{"enabled":{"description":"Enable OpenSearch Dashboards deployment.","type":"boolean","default":false},"replicas":{"description":"Number of Dashboards replicas.","type":"integer","default":1},"resources":{"description":"Explicit CPU and memory configuration for Dashboards.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available to each node.","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 (RAM) available to each node.","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}}},"resourcesPreset":{"description":"Default sizing preset for Dashboards.","type":"string","default":"medium","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}},"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"images":{"description":"Container images used by the operator.","type":"object","default":{},"required":["opensearch"],"properties":{"opensearch":{"description":"OpenSearch image.","type":"string","default":""}}},"nodeRoles":{"description":"Node roles configuration.","type":"object","default":{},"required":["data","ingest","master","ml"],"properties":{"data":{"description":"Enable data role.","type":"boolean","default":true},"ingest":{"description":"Enable ingest role.","type":"boolean","default":true},"master":{"description":"Enable cluster_manager role.","type":"boolean","default":true},"ml":{"description":"Enable machine learning role.","type":"boolean","default":false}}},"replicas":{"description":"Number of OpenSearch nodes in the cluster.","type":"integer","default":3},"resources":{"description":"Explicit CPU and memory configuration for each OpenSearch node. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available to each node.","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 (RAM) available to each node.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted. OpenSearch requires minimum 2Gi memory.","type":"string","default":"large","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]},"size":{"description":"Persistent Volume Claim size available for application data.","default":"10Gi","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},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":""},"topologySpreadPolicy":{"description":"How strictly to enforce pod distribution across nodes and zones.","type":"string","default":"soft","enum":["soft","hard"]},"users":{"description":"Custom OpenSearch users configuration map.","type":"object","default":{},"additionalProperties":{"type":"object","properties":{"password":{"description":"Password for the user (auto-generated if omitted).","type":"string"},"roles":{"description":"List of OpenSearch roles.","type":"array","items":{"type":"string"}}}}},"version":{"description":"OpenSearch major version to deploy.","type":"string","default":"v2","enum":["v3","v2","v1"]}}} + release: + prefix: opensearch- + labels: + sharding.fluxcd.io/key: tenants + chartRef: + kind: ExternalArtifact + name: cozystack-opensearch-application-default-opensearch + namespace: cozy-system + dashboard: + category: PaaS + singular: OpenSearch + plural: OpenSearch Clusters + description: Managed OpenSearch service + tags: + - database + - search + icon: PHN2ZyB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgdmlld0JveD0iMCAwIDE0NCAxNDQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNDQiIGhlaWdodD0iMTQ0IiByeD0iMjQiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcl9vcGVuc2VhcmNoKSIvPgo8cGF0aCBkPSJNNzIgMzZDNDQgMzYgMjggNTIgMjggNzJDMjggOTIgNDQgMTA4IDcyIDEwOEMxMDAgMTA4IDExNiA5MiAxMTYgNzIiIHN0cm9rZT0iIzAwNUVCOCIgc3Ryb2tlLXdpZHRoPSI4IiBzdHJva2UtbGluZWNhcD0icm91bmQiIGZpbGw9Im5vbmUiLz4KPGNpcmNsZSBjeD0iMTE2IiBjeT0iNzIiIHI9IjgiIGZpbGw9IiMwMDVFQjgiLz4KPGRlZnM+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQwX2xpbmVhcl9vcGVuc2VhcmNoIiB4MT0iMTQwIiB5MT0iMTMwLjUiIHgyPSI0IiB5Mj0iOS40OTk5OSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgo8c3RvcCBzdG9wLWNvbG9yPSIjMDAzQjVDIi8+CjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzAwNUVCOCIvPgo8L2xpbmVhckdyYWRpZW50Pgo8L2RlZnM+Cjwvc3ZnPgo= + keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "replicas"], ["spec", "resources"], ["spec", "resourcesPreset"], ["spec", "size"], ["spec", "storageClass"], ["spec", "external"], ["spec", "topologySpreadPolicy"], ["spec", "version"], ["spec", "images"], ["spec", "images", "opensearch"], ["spec", "nodeRoles"], ["spec", "nodeRoles", "master"], ["spec", "nodeRoles", "data"], ["spec", "nodeRoles", "ingest"], ["spec", "nodeRoles", "ml"], ["spec", "users"], ["spec", "dashboards"], ["spec", "dashboards", "enabled"], ["spec", "dashboards", "replicas"], ["spec", "dashboards", "resources"], ["spec", "dashboards", "resourcesPreset"]] + secrets: + exclude: [] + include: + - resourceNames: + - opensearch-{{ .name }}-credentials + services: + exclude: [] + include: + - resourceNames: + - opensearch-{{ .name }} + - opensearch-{{ .name }}-external + - opensearch-{{ .name }}-dashboards + - opensearch-{{ .name }}-dashboards-external diff --git a/packages/system/opensearch-rd/templates/cozyrd.yaml b/packages/system/opensearch-rd/templates/cozyrd.yaml new file mode 100644 index 00000000..e079ddcf --- /dev/null +++ b/packages/system/opensearch-rd/templates/cozyrd.yaml @@ -0,0 +1,4 @@ +{{- range $path, $_ := .Files.Glob "cozyrds/*" }} +--- +{{ $.Files.Get $path }} +{{- end }} diff --git a/packages/system/opensearch-rd/values.yaml b/packages/system/opensearch-rd/values.yaml new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/packages/system/opensearch-rd/values.yaml @@ -0,0 +1 @@ +{} From 4e59e5e656e59ca9b9055ae04cc1ea836d132756 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Mon, 16 Feb 2026 22:28:35 +0100 Subject: [PATCH 280/889] Add PackageSource definitions for OpenSearch Add operator and application PackageSource CRs to expose OpenSearch in the Cozystack platform: - opensearch-operator: operator deployment in cozy-opensearch-operator namespace - opensearch-application: app + resource definition with cozy-lib integration This enables OpenSearch to appear in the platform dashboard and be deployed by tenants. Co-Authored-By: Claude Sonnet 4.5 Signed-off-by: Matthieu --- .../sources/opensearch-application.yaml | 28 +++++++++++++++++++ .../platform/sources/opensearch-operator.yaml | 22 +++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 packages/core/platform/sources/opensearch-application.yaml create mode 100644 packages/core/platform/sources/opensearch-operator.yaml diff --git a/packages/core/platform/sources/opensearch-application.yaml b/packages/core/platform/sources/opensearch-application.yaml new file mode 100644 index 00000000..2b499cd0 --- /dev/null +++ b/packages/core/platform/sources/opensearch-application.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: cozystack.io/v1alpha1 +kind: PackageSource +metadata: + name: cozystack.opensearch-application +spec: + sourceRef: + kind: OCIRepository + name: cozystack-packages + namespace: cozy-system + path: / + variants: + - name: default + dependsOn: + - cozystack.networking + - cozystack.opensearch-operator + libraries: + - name: cozy-lib + path: library/cozy-lib + components: + - name: opensearch + path: apps/opensearch + libraries: ["cozy-lib"] + - name: opensearch-rd + path: system/opensearch-rd + install: + namespace: cozy-system + releaseName: opensearch-rd diff --git a/packages/core/platform/sources/opensearch-operator.yaml b/packages/core/platform/sources/opensearch-operator.yaml new file mode 100644 index 00000000..21dd7a43 --- /dev/null +++ b/packages/core/platform/sources/opensearch-operator.yaml @@ -0,0 +1,22 @@ +--- +apiVersion: cozystack.io/v1alpha1 +kind: PackageSource +metadata: + name: cozystack.opensearch-operator +spec: + sourceRef: + kind: OCIRepository + name: cozystack-packages + namespace: cozy-system + path: / + variants: + - name: default + dependsOn: + - cozystack.networking + - cozystack.prometheus-operator-crds + components: + - name: opensearch-operator + path: system/opensearch-operator + install: + namespace: cozy-opensearch-operator + releaseName: opensearch-operator From 7181fd1c937a05e94073519c21135cc2d3dbbd8b Mon Sep 17 00:00:00 2001 From: Matthieu Date: Mon, 16 Feb 2026 22:40:01 +0100 Subject: [PATCH 281/889] Fix critical issues in OpenSearch operator templates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address CodeRabbit review feedback: - Fix extraEnv indentation (nindent 8 → 10) in controller-manager deployment - Fix imagePullSecrets indentation (nindent 6 → 8) for proper YAML alignment - Change proxy-rolebinding from conditional RoleBinding to always ClusterRoleBinding (required for cluster-scoped tokenreviews/subjectaccessreviews permissions) - Pin operator chart version to 2.8.0 in Makefile for reproducible builds Co-Authored-By: Claude Sonnet 4.5 Signed-off-by: Matthieu --- packages/system/opensearch-operator/Makefile | 2 +- ...ch-operator-controller-manager-deployment.yaml | 4 ++-- .../opensearch-operator-proxy-rolebinding.yaml | 15 --------------- 3 files changed, 3 insertions(+), 18 deletions(-) diff --git a/packages/system/opensearch-operator/Makefile b/packages/system/opensearch-operator/Makefile index 71b94549..36522495 100644 --- a/packages/system/opensearch-operator/Makefile +++ b/packages/system/opensearch-operator/Makefile @@ -7,4 +7,4 @@ update: rm -rf charts helm repo add opensearch-operator https://opensearch-project.github.io/opensearch-k8s-operator/ helm repo update opensearch-operator - helm pull opensearch-operator/opensearch-operator --untar --untardir charts + helm pull opensearch-operator/opensearch-operator --version 2.8.0 --untar --untardir charts diff --git a/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-controller-manager-deployment.yaml b/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-controller-manager-deployment.yaml index 4b5cb194..43a70d65 100644 --- a/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-controller-manager-deployment.yaml +++ b/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-controller-manager-deployment.yaml @@ -73,7 +73,7 @@ spec: - name: PPROF_ENDPOINTS_ENABLED value: "{{ .Values.manager.pprofEndpointsEnabled }}" {{- if .Values.manager.extraEnv }} - {{- toYaml .Values.manager.extraEnv | nindent 8 }} + {{- toYaml .Values.manager.extraEnv | nindent 10 }} {{- end }} securityContext: {{- toYaml .Values.manager.securityContext | nindent 10 }} @@ -85,7 +85,7 @@ spec: {{- toYaml .Values.securityContext | nindent 8}} {{- if .Values.manager.imagePullSecrets }} imagePullSecrets: - {{- toYaml .Values.manager.imagePullSecrets | nindent 6 }} + {{- toYaml .Values.manager.imagePullSecrets | nindent 8 }} {{- end }} serviceAccountName: {{ include "opensearch-operator.serviceAccountName" . }} terminationGracePeriodSeconds: 10 diff --git a/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-proxy-rolebinding.yaml b/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-proxy-rolebinding.yaml index d9a5d339..5cba0693 100644 --- a/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-proxy-rolebinding.yaml +++ b/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-proxy-rolebinding.yaml @@ -1,17 +1,3 @@ -{{- if .Values.useRoleBindings }} -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: {{ include "opensearch-operator.fullname" . }}-{{ .Release.Namespace }}-proxy-rolebinding -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: {{ include "opensearch-operator.fullname" . }}-{{ .Release.Namespace }}-proxy-role -subjects: -- kind: ServiceAccount - name: {{ include "opensearch-operator.serviceAccountName" . }} - namespace: {{ .Release.Namespace }} -{{- else }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: @@ -24,4 +10,3 @@ subjects: - kind: ServiceAccount name: {{ include "opensearch-operator.serviceAccountName" . }} namespace: {{ .Release.Namespace }} -{{- end }} From f7767d1ee695d9ef25e8a79d0e46617a1c018ab6 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Mon, 16 Feb 2026 22:48:28 +0100 Subject: [PATCH 282/889] Fix YAML style issues in OpenSearch packages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address CodeRabbit nitpick feedback: - Fix inconsistent indentation in opensearch-application.yaml (4 spaces → 2 spaces) to match opensearch-operator.yaml style - Add missing space in controller-manager-deployment.yaml template (nindent 8}} → nindent 8 }}) for consistency Co-Authored-By: Claude Sonnet 4.5 Signed-off-by: Matthieu --- .../sources/opensearch-application.yaml | 32 +++++++++---------- ...perator-controller-manager-deployment.yaml | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/core/platform/sources/opensearch-application.yaml b/packages/core/platform/sources/opensearch-application.yaml index 2b499cd0..19358c21 100644 --- a/packages/core/platform/sources/opensearch-application.yaml +++ b/packages/core/platform/sources/opensearch-application.yaml @@ -10,19 +10,19 @@ spec: namespace: cozy-system path: / variants: - - name: default - dependsOn: - - cozystack.networking - - cozystack.opensearch-operator - libraries: - - name: cozy-lib - path: library/cozy-lib - components: - - name: opensearch - path: apps/opensearch - libraries: ["cozy-lib"] - - name: opensearch-rd - path: system/opensearch-rd - install: - namespace: cozy-system - releaseName: opensearch-rd + - name: default + dependsOn: + - cozystack.networking + - cozystack.opensearch-operator + libraries: + - name: cozy-lib + path: library/cozy-lib + components: + - name: opensearch + path: apps/opensearch + libraries: ["cozy-lib"] + - name: opensearch-rd + path: system/opensearch-rd + install: + namespace: cozy-system + releaseName: opensearch-rd diff --git a/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-controller-manager-deployment.yaml b/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-controller-manager-deployment.yaml index 43a70d65..114e6b20 100644 --- a/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-controller-manager-deployment.yaml +++ b/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-controller-manager-deployment.yaml @@ -82,7 +82,7 @@ spec: tolerations: {{- toYaml .Values.tolerations | nindent 8 }} securityContext: -{{- toYaml .Values.securityContext | nindent 8}} +{{- toYaml .Values.securityContext | nindent 8 }} {{- if .Values.manager.imagePullSecrets }} imagePullSecrets: {{- toYaml .Values.manager.imagePullSecrets | nindent 8 }} From 315e5dc0bd9a532eaf83f7aaf3fd3edc16c17d07 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Tue, 17 Feb 2026 01:32:05 +0300 Subject: [PATCH 283/889] fix(e2e): make kubernetes test retries effective by cleaning up stale resources When the kubernetes E2E test fails at the deployment wait step, set -eu causes immediate exit before cleanup. On retry, kubectl apply outputs "unchanged" for the stuck deployment, making retries 2 and 3 guaranteed to fail against the same stuck pod. Add pre-creation cleanup of backend deployment/service and NFS test resources using --ignore-not-found, so retries start fresh. Also increase the deployment wait timeout from 90s to 300s to handle CI resource pressure, aligning with other timeouts in the same function. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- hack/e2e-apps/run-kubernetes.sh | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/hack/e2e-apps/run-kubernetes.sh b/hack/e2e-apps/run-kubernetes.sh index d0997ecb..09e50924 100644 --- a/hack/e2e-apps/run-kubernetes.sh +++ b/hack/e2e-apps/run-kubernetes.sh @@ -132,8 +132,14 @@ metadata: name: tenant-test EOF + # Clean up backend resources from any previous failed attempt + kubectl delete deployment --kubeconfig "tenantkubeconfig-${test_name}" "${test_name}-backend" \ + -n tenant-test --ignore-not-found --timeout=60s || true + kubectl delete service --kubeconfig "tenantkubeconfig-${test_name}" "${test_name}-backend" \ + -n tenant-test --ignore-not-found --timeout=60s || true + # Backend 1 - kubectl apply --kubeconfig tenantkubeconfig-${test_name} -f- < Date: Tue, 17 Feb 2026 02:00:44 +0300 Subject: [PATCH 284/889] style(e2e): consistently quote kubeconfig variable references Quote all tenantkubeconfig-${test_name} references in run-kubernetes.sh for consistent shell scripting style. The only exception is line 195 inside a sh -ec "..." double-quoted string where inner quotes would break the outer quoting. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- hack/e2e-apps/run-kubernetes.sh | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/hack/e2e-apps/run-kubernetes.sh b/hack/e2e-apps/run-kubernetes.sh index 09e50924..511fcc17 100644 --- a/hack/e2e-apps/run-kubernetes.sh +++ b/hack/e2e-apps/run-kubernetes.sh @@ -80,10 +80,10 @@ EOF # Wait for the machine deployment to scale to 2 replicas (timeout after 1 minute) kubectl wait machinedeployment kubernetes-${test_name}-md0 -n tenant-test --timeout=1m --for=jsonpath='{.status.replicas}'=2 # Get the admin kubeconfig and save it to a file - kubectl get secret kubernetes-${test_name}-admin-kubeconfig -ojsonpath='{.data.super-admin\.conf}' -n tenant-test | base64 -d > tenantkubeconfig-${test_name} + kubectl get secret kubernetes-${test_name}-admin-kubeconfig -ojsonpath='{.data.super-admin\.conf}' -n tenant-test | base64 -d > "tenantkubeconfig-${test_name}" # Update the kubeconfig to use localhost for the API server - yq -i ".clusters[0].cluster.server = \"https://localhost:${port}\"" tenantkubeconfig-${test_name} + yq -i ".clusters[0].cluster.server = \"https://localhost:${port}\"" "tenantkubeconfig-${test_name}" # Set up port forwarding to the Kubernetes API server for a 200 second timeout @@ -98,8 +98,8 @@ EOF done ' # Verify the nodes are ready - kubectl --kubeconfig tenantkubeconfig-${test_name} wait node --all --timeout=2m --for=condition=Ready - kubectl --kubeconfig tenantkubeconfig-${test_name} get nodes -o wide + kubectl --kubeconfig "tenantkubeconfig-${test_name}" wait node --all --timeout=2m --for=condition=Ready + kubectl --kubeconfig "tenantkubeconfig-${test_name}" get nodes -o wide # Verify the kubelet version matches what we expect versions=$(kubectl --kubeconfig "tenantkubeconfig-${test_name}" \ @@ -125,7 +125,7 @@ EOF fi - kubectl --kubeconfig tenantkubeconfig-${test_name} apply -f - <&2 - kubectl --kubeconfig tenantkubeconfig-${test_name} delete pod nfs-test-pod -n tenant-test --wait=false 2>/dev/null || true - kubectl --kubeconfig tenantkubeconfig-${test_name} delete pvc nfs-test-pvc -n tenant-test --wait=false 2>/dev/null || true + kubectl --kubeconfig "tenantkubeconfig-${test_name}" delete pod nfs-test-pod -n tenant-test --wait=false 2>/dev/null || true + kubectl --kubeconfig "tenantkubeconfig-${test_name}" delete pvc nfs-test-pvc -n tenant-test --wait=false 2>/dev/null || true exit 1 fi # Cleanup NFS test resources in tenant cluster - kubectl --kubeconfig tenantkubeconfig-${test_name} delete pod nfs-test-pod -n tenant-test --wait - kubectl --kubeconfig tenantkubeconfig-${test_name} delete pvc nfs-test-pvc -n tenant-test + kubectl --kubeconfig "tenantkubeconfig-${test_name}" delete pod nfs-test-pod -n tenant-test --wait + kubectl --kubeconfig "tenantkubeconfig-${test_name}" delete pvc nfs-test-pvc -n tenant-test # Wait for all machine deployment replicas to be ready (timeout after 10 minutes) kubectl wait machinedeployment kubernetes-${test_name}-md0 -n tenant-test --timeout=10m --for=jsonpath='{.status.v1beta2.readyReplicas}'=2 From 32aff887eb8b43b4160582b6b61f434a3ceac2c2 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Tue, 17 Feb 2026 20:03:01 +0300 Subject: [PATCH 285/889] feat(openbao): add system chart with vendored upstream Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/system/openbao/Chart.yaml | 3 + packages/system/openbao/Makefile | 10 + .../system/openbao/charts/openbao/.helmignore | 28 + .../system/openbao/charts/openbao/Chart.yaml | 30 + .../system/openbao/charts/openbao/README.md | 365 +++ .../openbao/grafana/dashboards/dashboard.json | 2559 +++++++++++++++++ .../charts/openbao/templates/NOTES.txt | 18 + .../charts/openbao/templates/_helpers.tpl | 1212 ++++++++ .../templates/csi-agent-configmap.yaml | 34 + .../openbao/templates/csi-clusterrole.yaml | 23 + .../templates/csi-clusterrolebinding.yaml | 24 + .../openbao/templates/csi-daemonset.yaml | 157 + .../charts/openbao/templates/csi-role.yaml | 32 + .../openbao/templates/csi-rolebinding.yaml | 25 + .../openbao/templates/csi-serviceaccount.yaml | 21 + .../openbao/templates/extra-objects.yaml | 8 + .../grafana/configmap-dashboard.yaml | 30 + .../templates/injector-certs-secret.yaml | 19 + .../templates/injector-clusterrole.yaml | 30 + .../injector-clusterrolebinding.yaml | 24 + .../templates/injector-deployment.yaml | 179 ++ .../templates/injector-disruptionbudget.yaml | 25 + .../templates/injector-mutating-webhook.yaml | 44 + .../templates/injector-network-policy.yaml | 30 + .../openbao/templates/injector-psp-role.yaml | 25 + .../templates/injector-psp-rolebinding.yaml | 26 + .../openbao/templates/injector-psp.yaml | 51 + .../openbao/templates/injector-role.yaml | 34 + .../templates/injector-rolebinding.yaml | 27 + .../openbao/templates/injector-service.yaml | 30 + .../templates/injector-serviceaccount.yaml | 18 + .../templates/prometheus-prometheusrules.yaml | 32 + .../templates/prometheus-servicemonitor.yaml | 62 + .../templates/server-backendtlspolicy.yaml | 43 + .../templates/server-clusterrolebinding.yaml | 29 + .../templates/server-config-configmap.yaml | 31 + .../templates/server-discovery-role.yaml | 26 + .../server-discovery-rolebinding.yaml | 34 + .../templates/server-disruptionbudget.yaml | 31 + .../templates/server-ha-active-service.yaml | 68 + .../templates/server-ha-standby-service.yaml | 67 + .../templates/server-headless-service.yaml | 46 + .../openbao/templates/server-httproute.yaml | 50 + .../openbao/templates/server-ingress.yaml | 67 + .../templates/server-network-policy.yaml | 24 + .../openbao/templates/server-psp-role.yaml | 25 + .../templates/server-psp-rolebinding.yaml | 26 + .../charts/openbao/templates/server-psp.yaml | 54 + .../openbao/templates/server-route.yaml | 39 + .../openbao/templates/server-service.yaml | 64 + .../server-serviceaccount-secret.yaml | 21 + .../templates/server-serviceaccount.yaml | 22 + .../openbao/templates/server-statefulset.yaml | 228 ++ .../openbao/templates/server-tlsroute.yaml | 41 + .../templates/snapshotagent-configmap.yaml | 31 + .../templates/snapshotagent-cronjob.yaml | 66 + .../snapshotagent-serviceaccount.yaml | 16 + .../openbao/templates/tests/server-test.yaml | 56 + .../charts/openbao/templates/ui-service.yaml | 53 + .../charts/openbao/values.openshift.yaml | 30 + .../openbao/charts/openbao/values.schema.json | 1207 ++++++++ .../system/openbao/charts/openbao/values.yaml | 1583 ++++++++++ packages/system/openbao/values.yaml | 5 + 63 files changed, 9318 insertions(+) create mode 100644 packages/system/openbao/Chart.yaml create mode 100644 packages/system/openbao/Makefile create mode 100644 packages/system/openbao/charts/openbao/.helmignore create mode 100644 packages/system/openbao/charts/openbao/Chart.yaml create mode 100644 packages/system/openbao/charts/openbao/README.md create mode 100644 packages/system/openbao/charts/openbao/grafana/dashboards/dashboard.json create mode 100644 packages/system/openbao/charts/openbao/templates/NOTES.txt create mode 100644 packages/system/openbao/charts/openbao/templates/_helpers.tpl create mode 100644 packages/system/openbao/charts/openbao/templates/csi-agent-configmap.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/csi-clusterrole.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/csi-clusterrolebinding.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/csi-daemonset.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/csi-role.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/csi-rolebinding.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/csi-serviceaccount.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/extra-objects.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/grafana/configmap-dashboard.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/injector-certs-secret.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/injector-clusterrole.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/injector-clusterrolebinding.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/injector-deployment.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/injector-disruptionbudget.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/injector-mutating-webhook.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/injector-network-policy.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/injector-psp-role.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/injector-psp-rolebinding.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/injector-psp.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/injector-role.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/injector-rolebinding.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/injector-service.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/injector-serviceaccount.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/prometheus-prometheusrules.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/prometheus-servicemonitor.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/server-backendtlspolicy.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/server-clusterrolebinding.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/server-config-configmap.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/server-discovery-role.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/server-discovery-rolebinding.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/server-disruptionbudget.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/server-ha-active-service.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/server-ha-standby-service.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/server-headless-service.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/server-httproute.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/server-ingress.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/server-network-policy.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/server-psp-role.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/server-psp-rolebinding.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/server-psp.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/server-route.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/server-service.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/server-serviceaccount-secret.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/server-serviceaccount.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/server-statefulset.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/server-tlsroute.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/snapshotagent-configmap.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/snapshotagent-cronjob.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/snapshotagent-serviceaccount.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/tests/server-test.yaml create mode 100644 packages/system/openbao/charts/openbao/templates/ui-service.yaml create mode 100644 packages/system/openbao/charts/openbao/values.openshift.yaml create mode 100644 packages/system/openbao/charts/openbao/values.schema.json create mode 100644 packages/system/openbao/charts/openbao/values.yaml create mode 100644 packages/system/openbao/values.yaml diff --git a/packages/system/openbao/Chart.yaml b/packages/system/openbao/Chart.yaml new file mode 100644 index 00000000..104a5e95 --- /dev/null +++ b/packages/system/openbao/Chart.yaml @@ -0,0 +1,3 @@ +apiVersion: v2 +name: cozy-openbao +version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process diff --git a/packages/system/openbao/Makefile b/packages/system/openbao/Makefile new file mode 100644 index 00000000..4ae6190e --- /dev/null +++ b/packages/system/openbao/Makefile @@ -0,0 +1,10 @@ +export NAME=openbao +export NAMESPACE=cozy-openbao +export REPO_NAME=openbao +export REPO_URL=https://openbao.github.io/openbao-helm +export CHART_NAME=openbao +export CHART_VERSION=^0.25 + +include ../../../hack/package.mk + +update: clean openbao-update diff --git a/packages/system/openbao/charts/openbao/.helmignore b/packages/system/openbao/charts/openbao/.helmignore new file mode 100644 index 00000000..4007e243 --- /dev/null +++ b/packages/system/openbao/charts/openbao/.helmignore @@ -0,0 +1,28 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.terraform/ +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj + +# CI and test +.circleci/ +.github/ +.gitlab-ci.yml +test/ diff --git a/packages/system/openbao/charts/openbao/Chart.yaml b/packages/system/openbao/charts/openbao/Chart.yaml new file mode 100644 index 00000000..4914439e --- /dev/null +++ b/packages/system/openbao/charts/openbao/Chart.yaml @@ -0,0 +1,30 @@ +annotations: + artifacthub.io/changes: | + - kind: changed + description: | + fix: Add extraPorts to server Service in ha + artifacthub.io/containsSecurityUpdates: "false" + charts.openshift.io/name: Openbao +apiVersion: v2 +appVersion: v2.5.0 +description: Official OpenBao Chart +home: https://github.com/openbao/openbao-helm +icon: https://raw.githubusercontent.com/openbao/artwork/refs/heads/main/color/openbao-color.svg +keywords: +- vault +- openbao +- security +- encryption +- secrets +- management +- automation +- infrastructure +kubeVersion: '>= 1.30.0-0' +maintainers: +- email: openbao-security@lists.openssf.org + name: OpenBao + url: https://openbao.org +name: openbao +sources: +- https://github.com/openbao/openbao-helm +version: 0.25.3 diff --git a/packages/system/openbao/charts/openbao/README.md b/packages/system/openbao/charts/openbao/README.md new file mode 100644 index 00000000..bcdf92b1 --- /dev/null +++ b/packages/system/openbao/charts/openbao/README.md @@ -0,0 +1,365 @@ +# openbao + +![Version: 0.25.3](https://img.shields.io/badge/Version-0.25.3-informational?style=flat-square) ![AppVersion: v2.5.0](https://img.shields.io/badge/AppVersion-v2.5.0-informational?style=flat-square) + +Official OpenBao Chart + +**Homepage:** + +## Maintainers + +| Name | Email | Url | +| ---- | ------ | --- | +| OpenBao | | | + +## Source Code + +* + +## Requirements + +Kubernetes: `>= 1.30.0-0` + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| csi.agent.enabled | bool | `true` | | +| csi.agent.extraArgs | list | `[]` | | +| csi.agent.image.pullPolicy | string | `"IfNotPresent"` | image pull policy to use for agent image. if tag is "latest", set to "Always" | +| csi.agent.image.registry | string | `"quay.io"` | image registry to use for agent image | +| csi.agent.image.repository | string | `"openbao/openbao"` | image repo to use for agent image | +| csi.agent.image.tag | string | `""` | image tag to use for agent image - defaults to chart appVersion | +| csi.agent.logFormat | string | `"standard"` | | +| csi.agent.logLevel | string | `"info"` | | +| csi.agent.resources | object | `{}` | | +| csi.daemonSet.annotations | object | `{}` | | +| csi.daemonSet.endpoint | string | `"/provider/openbao.sock"` | | +| csi.daemonSet.extraLabels | object | `{}` | | +| csi.daemonSet.kubeletRootDir | string | `"/var/lib/kubelet"` | | +| csi.daemonSet.providersDir | string | `"/etc/kubernetes/secrets-store-csi-providers"` | | +| csi.daemonSet.securityContext.container | object | `{}` | | +| csi.daemonSet.securityContext.pod | object | `{}` | | +| csi.daemonSet.updateStrategy.maxUnavailable | string | `""` | | +| csi.daemonSet.updateStrategy.type | string | `"RollingUpdate"` | | +| csi.debug | bool | `false` | | +| csi.enabled | bool | `false` | True if you want to install a openbao-csi-provider daemonset. Requires installing the secrets-store-csi-driver separately, see: https://secrets-store-csi-driver.sigs.k8s.io/getting-started/installation With the driver and provider installed, you can mount OpenBao secrets into volumes similar to the OpenBao Agent injector, and you can also sync those secrets into Kubernetes secrets. | +| csi.extraArgs | list | `[]` | | +| csi.hmacSecretName | string | `""` | | +| csi.image.pullPolicy | string | `"IfNotPresent"` | image pull policy to use for csi image. if tag is "latest", set to "Always" | +| csi.image.registry | string | `"quay.io"` | image registry to use for csi image | +| csi.image.repository | string | `"openbao/openbao-csi-provider"` | image repo to use for csi image | +| csi.image.tag | string | `"2.0.0"` | image tag to use for csi image | +| csi.livenessProbe.failureThreshold | int | `2` | | +| csi.livenessProbe.initialDelaySeconds | int | `5` | | +| csi.livenessProbe.periodSeconds | int | `5` | | +| csi.livenessProbe.successThreshold | int | `1` | | +| csi.livenessProbe.timeoutSeconds | int | `3` | | +| csi.pod.affinity | object | `{}` | | +| csi.pod.annotations | object | `{}` | | +| csi.pod.extraLabels | object | `{}` | | +| csi.pod.nodeSelector | object | `{}` | | +| csi.pod.tolerations | list | `[]` | | +| csi.priorityClassName | string | `""` | | +| csi.readinessProbe.failureThreshold | int | `2` | | +| csi.readinessProbe.initialDelaySeconds | int | `5` | | +| csi.readinessProbe.periodSeconds | int | `5` | | +| csi.readinessProbe.successThreshold | int | `1` | | +| csi.readinessProbe.timeoutSeconds | int | `3` | | +| csi.resources | object | `{}` | | +| csi.serviceAccount.annotations | object | `{}` | | +| csi.serviceAccount.extraLabels | object | `{}` | | +| csi.volumeMounts | list | `[]` | volumeMounts is a list of volumeMounts for the main server container. These are rendered via toYaml rather than pre-processed like the extraVolumes value. The purpose is to make it easy to share volumes between containers. | +| csi.volumes | list | `[]` | volumes is a list of volumes made available to all containers. These are rendered via toYaml rather than pre-processed like the extraVolumes value. The purpose is to make it easy to share volumes between containers. | +| extraObjects | list | `[]` | | +| global.enabled | bool | `true` | enabled is the master enabled switch. Setting this to true or false will enable or disable all the components within this chart by default. | +| global.externalBaoAddr | string | `""` | External openbao server address for the injector and CSI provider to use. Setting this will disable deployment of a openbao server. | +| global.externalVaultAddr | string | `""` | Deprecated: Please use global.externalBaoAddr instead. | +| global.imagePullSecrets | list | `[]` | Image pull secret to use for registry authentication. Alternatively, the value may be specified as an array of strings. | +| global.namespace | string | `""` | The namespace to deploy to. Defaults to the `helm` installation namespace. | +| global.openshift | bool | `false` | If deploying to OpenShift | +| global.psp | object | `{"annotations":"seccomp.security.alpha.kubernetes.io/allowedProfileNames: docker/default,runtime/default\napparmor.security.beta.kubernetes.io/allowedProfileNames: runtime/default\nseccomp.security.alpha.kubernetes.io/defaultProfileName: runtime/default\napparmor.security.beta.kubernetes.io/defaultProfileName: runtime/default\n","enable":false}` | Create PodSecurityPolicy for pods | +| global.psp.annotations | string | `"seccomp.security.alpha.kubernetes.io/allowedProfileNames: docker/default,runtime/default\napparmor.security.beta.kubernetes.io/allowedProfileNames: runtime/default\nseccomp.security.alpha.kubernetes.io/defaultProfileName: runtime/default\napparmor.security.beta.kubernetes.io/defaultProfileName: runtime/default\n"` | Annotation for PodSecurityPolicy. This is a multi-line templated string map, and can also be set as YAML. | +| global.serverTelemetry.prometheusOperator | bool | `false` | Enable integration with the Prometheus Operator See the top level serverTelemetry section below before enabling this feature. | +| global.tlsDisable | bool | `true` | TLS for end-to-end encrypted transport | +| injector.affinity | string | `"podAntiAffinity:\n requiredDuringSchedulingIgnoredDuringExecution:\n - labelSelector:\n matchLabels:\n app.kubernetes.io/name: {{ template \"openbao.name\" . }}-agent-injector\n app.kubernetes.io/instance: \"{{ .Release.Name }}\"\n component: webhook\n topologyKey: kubernetes.io/hostname\n"` | | +| injector.agentDefaults.cpuLimit | string | `"500m"` | | +| injector.agentDefaults.cpuRequest | string | `"250m"` | | +| injector.agentDefaults.memLimit | string | `"128Mi"` | | +| injector.agentDefaults.memRequest | string | `"64Mi"` | | +| injector.agentDefaults.template | string | `"map"` | | +| injector.agentDefaults.templateConfig.exitOnRetryFailure | bool | `true` | | +| injector.agentDefaults.templateConfig.staticSecretRenderInterval | string | `""` | | +| injector.agentImage | object | `{"pullPolicy":"IfNotPresent","registry":"quay.io","repository":"openbao/openbao","tag":""}` | agentImage sets the repo and tag of the OpenBao image to use for the OpenBao Agent containers. This should be set to the official OpenBao image. OpenBao 1.3.1+ is required. | +| injector.agentImage.pullPolicy | string | `"IfNotPresent"` | image pull policy to use for agent image. if tag is "latest", set to "Always" | +| injector.agentImage.registry | string | `"quay.io"` | image registry to use for agent image | +| injector.agentImage.repository | string | `"openbao/openbao"` | image repo to use for agent image | +| injector.agentImage.tag | string | `""` | image tag to use for agent image - defaults to chart appVersion | +| injector.annotations | object | `{}` | | +| injector.authPath | string | `"auth/kubernetes"` | | +| injector.certs.caBundle | string | `""` | | +| injector.certs.certName | string | `"tls.crt"` | | +| injector.certs.keyName | string | `"tls.key"` | | +| injector.certs.secretName | string | `nil` | | +| injector.enabled | string | `"-"` | True if you want to enable openbao agent injection. @default: global.enabled | +| injector.externalVaultAddr | string | `""` | Deprecated: Please use global.externalBaoAddr instead. | +| injector.extraEnvironmentVars | object | `{}` | | +| injector.extraLabels | object | `{}` | | +| injector.failurePolicy | string | `"Ignore"` | | +| injector.hostNetwork | bool | `false` | | +| injector.image.pullPolicy | string | `"IfNotPresent"` | image pull policy to use for k8s image. if tag is "latest", set to "Always" | +| injector.image.registry | string | `"docker.io"` | image registry to use for k8s image | +| injector.image.repository | string | `"hashicorp/vault-k8s"` | image repo to use for k8s image | +| injector.image.tag | string | `"1.7.2"` | image tag to use for k8s image | +| injector.leaderElector | object | `{"enabled":true}` | If multiple replicas are specified, by default a leader will be determined so that only one injector attempts to create TLS certificates. | +| injector.livenessProbe.failureThreshold | int | `2` | When a probe fails, Kubernetes will try failureThreshold times before giving up | +| injector.livenessProbe.initialDelaySeconds | int | `5` | Number of seconds after the container has started before probe initiates | +| injector.livenessProbe.periodSeconds | int | `2` | How often (in seconds) to perform the probe | +| injector.livenessProbe.successThreshold | int | `1` | Minimum consecutive successes for the probe to be considered successful after having failed | +| injector.livenessProbe.timeoutSeconds | int | `5` | Number of seconds after which the probe times out. | +| injector.logFormat | string | `"standard"` | Configures the log format of the injector. Supported log formats: "standard", "json". | +| injector.logLevel | string | `"info"` | Configures the log verbosity of the injector. Supported log levels include: trace, debug, info, warn, error | +| injector.metrics | object | `{"enabled":false}` | If true, will enable a node exporter metrics endpoint at /metrics. | +| injector.namespaceSelector | object | `{}` | | +| injector.nodeSelector | object | `{}` | | +| injector.objectSelector | object | `{}` | | +| injector.podDisruptionBudget | object | `{}` | | +| injector.port | int | `8080` | Configures the port the injector should listen on | +| injector.priorityClassName | string | `""` | | +| injector.readinessProbe.failureThreshold | int | `2` | When a probe fails, Kubernetes will try failureThreshold times before giving up | +| injector.readinessProbe.initialDelaySeconds | int | `5` | Number of seconds after the container has started before probe initiates | +| injector.readinessProbe.periodSeconds | int | `2` | How often (in seconds) to perform the probe | +| injector.readinessProbe.successThreshold | int | `1` | Minimum consecutive successes for the probe to be considered successful after having failed | +| injector.readinessProbe.timeoutSeconds | int | `5` | Number of seconds after which the probe times out. | +| injector.replicas | int | `1` | | +| injector.resources | object | `{}` | | +| injector.revokeOnShutdown | bool | `false` | | +| injector.securityContext.container | object | `{}` | | +| injector.securityContext.pod | object | `{}` | | +| injector.service.annotations | object | `{}` | | +| injector.service.extraLabels | object | `{}` | | +| injector.serviceAccount.annotations | object | `{}` | | +| injector.startupProbe.failureThreshold | int | `12` | When a probe fails, Kubernetes will try failureThreshold times before giving up | +| injector.startupProbe.initialDelaySeconds | int | `5` | Number of seconds after the container has started before probe initiates | +| injector.startupProbe.periodSeconds | int | `5` | How often (in seconds) to perform the probe | +| injector.startupProbe.successThreshold | int | `1` | Minimum consecutive successes for the probe to be considered successful after having failed | +| injector.startupProbe.timeoutSeconds | int | `5` | Number of seconds after which the probe times out. | +| injector.strategy | object | `{}` | | +| injector.tolerations | list | `[]` | | +| injector.topologySpreadConstraints | list | `[]` | | +| injector.webhook.annotations | object | `{}` | | +| injector.webhook.failurePolicy | string | `"Ignore"` | | +| injector.webhook.matchPolicy | string | `"Exact"` | | +| injector.webhook.namespaceSelector | object | `{}` | | +| injector.webhook.objectSelector | string | `"matchExpressions:\n- key: app.kubernetes.io/name\n operator: NotIn\n values:\n - {{ template \"openbao.name\" . }}-agent-injector\n"` | | +| injector.webhook.timeoutSeconds | int | `30` | | +| injector.webhookAnnotations | object | `{}` | | +| server.affinity | string | `"podAntiAffinity:\n requiredDuringSchedulingIgnoredDuringExecution:\n - labelSelector:\n matchLabels:\n app.kubernetes.io/name: {{ template \"openbao.name\" . }}\n app.kubernetes.io/instance: \"{{ .Release.Name }}\"\n component: server\n topologyKey: kubernetes.io/hostname\n"` | | +| server.annotations | object | `{}` | | +| server.auditStorage.accessMode | string | `"ReadWriteOnce"` | | +| server.auditStorage.annotations | object | `{}` | | +| server.auditStorage.enabled | bool | `false` | | +| server.auditStorage.labels | object | `{}` | | +| server.auditStorage.mountPath | string | `"/openbao/audit"` | | +| server.auditStorage.size | string | `"10Gi"` | | +| server.auditStorage.storageClass | string | `nil` | | +| server.authDelegator.enabled | bool | `true` | | +| server.configAnnotation | bool | `false` | | +| server.dataStorage.accessMode | string | `"ReadWriteOnce"` | | +| server.dataStorage.annotations | object | `{}` | | +| server.dataStorage.enabled | bool | `true` | | +| server.dataStorage.labels | object | `{}` | | +| server.dataStorage.mountPath | string | `"/openbao/data"` | | +| server.dataStorage.size | string | `"10Gi"` | | +| server.dataStorage.storageClass | string | `nil` | | +| server.dev.devRootToken | string | `"root"` | | +| server.dev.enabled | bool | `false` | | +| server.enabled | string | `"-"` | | +| server.extraArgs | string | `""` | extraArgs is a string containing additional OpenBao server arguments. | +| server.extraContainers | string | `nil` | | +| server.extraEnvironmentVars | object | `{}` | | +| server.extraInitContainers | list | `[]` | extraInitContainers is a list of init containers. Specified as a YAML list. This is useful if you need to run a script to provision TLS certificates or write out configuration files in a dynamic way. | +| server.extraLabels | object | `{}` | | +| server.extraPorts | list | `[]` | extraPorts is a list of extra ports. Specified as a YAML list. This is useful if you need to add additional ports to the statefulset in dynamic way. | +| server.extraSecretEnvironmentVars | list | `[]` | | +| server.extraVolumes | list | `[]` | | +| server.gateway.httpRoute.activeService | bool | `true` | | +| server.gateway.httpRoute.annotations | object | `{}` | | +| server.gateway.httpRoute.apiVersion | string | `"gateway.networking.k8s.io/v1"` | | +| server.gateway.httpRoute.enabled | bool | `false` | | +| server.gateway.httpRoute.filters | list | `[]` | | +| server.gateway.httpRoute.hosts[0] | string | `"chart-example.local"` | | +| server.gateway.httpRoute.labels | object | `{}` | | +| server.gateway.httpRoute.matches.path.type | string | `"PathPrefix"` | | +| server.gateway.httpRoute.matches.path.value | string | `"/"` | | +| server.gateway.httpRoute.matches.timeouts | object | `{}` | | +| server.gateway.httpRoute.parentRefs | list | `[]` | | +| server.gateway.tlsPolicy.activeService | bool | `true` | | +| server.gateway.tlsPolicy.annotations | object | `{}` | | +| server.gateway.tlsPolicy.apiVersion | string | `"gateway.networking.k8s.io/v1"` | | +| server.gateway.tlsPolicy.enabled | bool | `false` | | +| server.gateway.tlsPolicy.labels | object | `{}` | | +| server.gateway.tlsPolicy.targetRefs | list | `[]` | | +| server.gateway.tlsPolicy.validation | object | `{}` | | +| server.gateway.tlsRoute.activeService | bool | `true` | | +| server.gateway.tlsRoute.annotations | object | `{}` | | +| server.gateway.tlsRoute.apiVersion | string | `"gateway.networking.k8s.io/v1alpha3"` | | +| server.gateway.tlsRoute.enabled | bool | `false` | | +| server.gateway.tlsRoute.hosts | list | `[]` | | +| server.gateway.tlsRoute.labels | object | `{}` | | +| server.gateway.tlsRoute.parentRefs | list | `[]` | | +| server.ha.apiAddr | string | `nil` | | +| server.ha.clusterAddr | string | `nil` | | +| server.ha.config | string | `"ui = true\n\nlistener \"tcp\" {\n tls_disable = 1\n address = \"[::]:8200\"\n cluster_address = \"[::]:8201\"\n}\nstorage \"consul\" {\n path = \"openbao\"\n address = \"HOST_IP:8500\"\n}\n\nservice_registration \"kubernetes\" {}\n\n# Example configuration for using auto-unseal, using Google Cloud KMS. The\n# GKMS keys must already exist, and the cluster must have a service account\n# that is authorized to access GCP KMS.\n#seal \"gcpckms\" {\n# project = \"openbao-helm-dev-246514\"\n# region = \"global\"\n# key_ring = \"openbao-helm-unseal-kr\"\n# crypto_key = \"openbao-helm-unseal-key\"\n#}\n\n# Example configuration for enabling Prometheus metrics.\n# If you are using Prometheus Operator you can enable a ServiceMonitor resource below.\n# You may wish to enable unauthenticated metrics in the listener block above.\n#telemetry {\n# prometheus_retention_time = \"30s\"\n# disable_hostname = true\n#}\n"` | | +| server.ha.disruptionBudget.enabled | bool | `true` | | +| server.ha.disruptionBudget.maxUnavailable | string | `nil` | | +| server.ha.enabled | bool | `false` | | +| server.ha.raft.config | string | `"ui = true\n\nlistener \"tcp\" {\n tls_disable = 1\n address = \"[::]:8200\"\n cluster_address = \"[::]:8201\"\n # Enable unauthenticated metrics access (necessary for Prometheus Operator)\n #telemetry {\n # unauthenticated_metrics_access = \"true\"\n #}\n}\n\nstorage \"raft\" {\n path = \"/openbao/data\"\n}\n\nservice_registration \"kubernetes\" {}\n"` | | +| server.ha.raft.enabled | bool | `false` | | +| server.ha.raft.setNodeId | bool | `false` | | +| server.ha.replicas | int | `3` | | +| server.hostAliases | list | `[]` | | +| server.hostNetwork | bool | `false` | | +| server.image.pullPolicy | string | `"IfNotPresent"` | image pull policy to use for server image. if tag is "latest", set to "Always" | +| server.image.registry | string | `"quay.io"` | image registry to use for server image | +| server.image.repository | string | `"openbao/openbao"` | image repo to use for server image | +| server.image.tag | string | `""` | image tag to use for server image - defaults to chart appVersion | +| server.ingress.activeService | bool | `true` | | +| server.ingress.annotations | object | `{}` | | +| server.ingress.enabled | bool | `false` | | +| server.ingress.extraPaths | list | `[]` | | +| server.ingress.hosts[0].host | string | `"chart-example.local"` | | +| server.ingress.hosts[0].paths | list | `[]` | | +| server.ingress.ingressClassName | string | `""` | | +| server.ingress.labels | object | `{}` | | +| server.ingress.pathType | string | `"Prefix"` | | +| server.ingress.tls | list | `[]` | | +| server.livenessProbe.enabled | bool | `false` | | +| server.livenessProbe.execCommand | list | `[]` | | +| server.livenessProbe.failureThreshold | int | `2` | | +| server.livenessProbe.initialDelaySeconds | int | `60` | | +| server.livenessProbe.path | string | `"/v1/sys/health?standbyok=true"` | | +| server.livenessProbe.periodSeconds | int | `5` | | +| server.livenessProbe.port | int | `8200` | | +| server.livenessProbe.successThreshold | int | `1` | | +| server.livenessProbe.timeoutSeconds | int | `3` | | +| server.logFormat | string | `""` | | +| server.logLevel | string | `""` | | +| server.networkPolicy.egress | list | `[]` | | +| server.networkPolicy.enabled | bool | `false` | | +| server.networkPolicy.ingress[0].from[0].namespaceSelector | object | `{}` | | +| server.networkPolicy.ingress[0].ports[0].port | int | `8200` | | +| server.networkPolicy.ingress[0].ports[0].protocol | string | `"TCP"` | | +| server.networkPolicy.ingress[0].ports[1].port | int | `8201` | | +| server.networkPolicy.ingress[0].ports[1].protocol | string | `"TCP"` | | +| server.nodeSelector | object | `{}` | | +| server.persistentVolumeClaimRetentionPolicy | object | `{}` | | +| server.podManagementPolicy | string | `"OrderedReady"` | | +| server.postStart | list | `[]` | | +| server.preStopSleepSeconds | int | `5` | | +| server.priorityClassName | string | `""` | | +| server.readinessProbe.enabled | bool | `true` | | +| server.readinessProbe.failureThreshold | int | `2` | | +| server.readinessProbe.initialDelaySeconds | int | `5` | | +| server.readinessProbe.periodSeconds | int | `5` | | +| server.readinessProbe.port | int | `8200` | | +| server.readinessProbe.successThreshold | int | `1` | | +| server.readinessProbe.timeoutSeconds | int | `3` | | +| server.resources | object | `{}` | | +| server.route.activeService | bool | `true` | | +| server.route.annotations | object | `{}` | | +| server.route.enabled | bool | `false` | | +| server.route.host | string | `"chart-example.local"` | | +| server.route.labels | object | `{}` | | +| server.route.tls.termination | string | `"passthrough"` | | +| server.service.active.annotations | object | `{}` | | +| server.service.active.enabled | bool | `true` | | +| server.service.active.extraLabels | object | `{}` | | +| server.service.annotations | object | `{}` | | +| server.service.enabled | bool | `true` | | +| server.service.externalTrafficPolicy | string | `"Cluster"` | | +| server.service.extraLabels | object | `{}` | | +| server.service.extraPorts | list | `[]` | extraPorts is a list of extra ports. Specified as a YAML list. This is useful if you need to add additional ports to the server service in dynamic way. | +| server.service.instanceSelector.enabled | bool | `true` | | +| server.service.ipFamilies | list | `[]` | | +| server.service.ipFamilyPolicy | string | `""` | | +| server.service.port | int | `8200` | | +| server.service.publishNotReadyAddresses | bool | `true` | | +| server.service.standby.annotations | object | `{}` | | +| server.service.standby.enabled | bool | `true` | | +| server.service.standby.extraLabels | object | `{}` | | +| server.service.targetPort | int | `8200` | | +| server.serviceAccount.annotations | object | `{}` | | +| server.serviceAccount.create | bool | `true` | | +| server.serviceAccount.createSecret | bool | `false` | | +| server.serviceAccount.extraLabels | object | `{}` | | +| server.serviceAccount.name | string | `""` | | +| server.serviceAccount.serviceDiscovery.enabled | bool | `true` | | +| server.shareProcessNamespace | bool | `false` | shareProcessNamespace enables process namespace sharing between OpenBao and the extraContainers This is useful if OpenBao must be signaled, e.g. to send a SIGHUP for a log rotation | +| server.standalone.config | string | `"ui = true\n\nlistener \"tcp\" {\n tls_disable = 1\n address = \"[::]:8200\"\n cluster_address = \"[::]:8201\"\n # Enable unauthenticated metrics access (necessary for Prometheus Operator)\n #telemetry {\n # unauthenticated_metrics_access = \"true\"\n #}\n}\nstorage \"file\" {\n path = \"/openbao/data\"\n}\n\n# Example configuration for using auto-unseal, using Google Cloud KMS. The\n# GKMS keys must already exist, and the cluster must have a service account\n# that is authorized to access GCP KMS.\n#seal \"gcpckms\" {\n# project = \"openbao-helm-dev\"\n# region = \"global\"\n# key_ring = \"openbao-helm-unseal-kr\"\n# crypto_key = \"openbao-helm-unseal-key\"\n#}\n\n# Example configuration for enabling Prometheus metrics in your config.\n#telemetry {\n# prometheus_retention_time = \"30s\"\n# disable_hostname = true\n#}\n"` | | +| server.standalone.enabled | string | `"-"` | | +| server.statefulSet.annotations | object | `{}` | | +| server.statefulSet.securityContext.container | object | `{}` | | +| server.statefulSet.securityContext.pod | object | `{}` | | +| server.terminationGracePeriodSeconds | int | `10` | | +| server.tolerations | list | `[]` | | +| server.topologySpreadConstraints | list | `[]` | | +| server.updateStrategyType | string | `"OnDelete"` | | +| server.volumeMounts | string | `nil` | | +| server.volumes | string | `nil` | | +| serverTelemetry.grafanaDashboard.defaultLabel | bool | `true` | | +| serverTelemetry.grafanaDashboard.enabled | bool | `false` | | +| serverTelemetry.grafanaDashboard.extraAnnotations | object | `{}` | | +| serverTelemetry.grafanaDashboard.extraLabel | object | `{}` | | +| serverTelemetry.prometheusRules.enabled | bool | `false` | | +| serverTelemetry.prometheusRules.rules | list | `[]` | | +| serverTelemetry.prometheusRules.selectors | object | `{}` | | +| serverTelemetry.serviceMonitor.authorization | object | `{}` | | +| serverTelemetry.serviceMonitor.enabled | bool | `false` | | +| serverTelemetry.serviceMonitor.interval | string | `"30s"` | | +| serverTelemetry.serviceMonitor.port | string | `""` | Port which Prometheus uses when scraping metrics. If empty will use `openbao.scheme` helper for its value | +| serverTelemetry.serviceMonitor.scheme | string | `""` | scheme to use when Prometheus scrapes metrics. If empty will use `openbao.scheme` helper for its value | +| serverTelemetry.serviceMonitor.scrapeClass | string | `""` | | +| serverTelemetry.serviceMonitor.scrapeTimeout | string | `"10s"` | | +| serverTelemetry.serviceMonitor.selectors | object | `{}` | | +| serverTelemetry.serviceMonitor.tlsConfig | object | `{}` | | +| snapshotAgent.annotations | object | `{}` | | +| snapshotAgent.config.baoAuthPath | string | `"kubernetes"` | | +| snapshotAgent.config.baoRole | string | `"snapshot"` | | +| snapshotAgent.config.s3Bucket | string | `"openbao-snapshots"` | | +| snapshotAgent.config.s3ExpireDays | string | `"14"` | | +| snapshotAgent.config.s3Host | string | `"s3.eu-east-1.amazonaws.com"` | | +| snapshotAgent.config.s3Uri | string | `"s3://openbao-snapshots"` | | +| snapshotAgent.config.s3cmdExtraFlag | string | `"-v"` | | +| snapshotAgent.enabled | bool | `false` | | +| snapshotAgent.extraEnvironmentVars | object | `{}` | Map of extra environment variables to set in the snapshot-agent cronjob | +| snapshotAgent.extraSecretEnvironmentVars | list | `[]` | List of extra environment variables to set in the snapshot-agent cronjob These variables take value from existing Secret objects. | +| snapshotAgent.extraVolumeMounts | list | `[]` | List of additional volumeMounts for the snapshot cronjob container. | +| snapshotAgent.extraVolumes | list | `[]` | List of extraVolumes made available to the snapshot cronjob container. | +| snapshotAgent.image.repository | string | `"ghcr.io/openbao/openbao-snapshot-agent"` | | +| snapshotAgent.image.tag | string | `"0.2.4"` | | +| snapshotAgent.resources | object | `{}` | | +| snapshotAgent.restartPolicy | string | `"OnFailure"` | | +| snapshotAgent.s3CredentialsSecret | string | `"my-s3-credentials"` | | +| snapshotAgent.schedule | string | `"*/15 * * * *"` | | +| snapshotAgent.securityContext.container | object | `{}` | | +| snapshotAgent.securityContext.pod | object | `{}` | | +| snapshotAgent.serviceAccount.annotations | object | `{}` | | +| snapshotAgent.serviceAccount.create | bool | `true` | | +| snapshotAgent.serviceAccount.extraLabels | object | `{}` | | +| snapshotAgent.serviceAccount.name | string | `""` | | +| ui.activeOpenbaoPodOnly | bool | `false` | | +| ui.annotations | object | `{}` | | +| ui.enabled | bool | `false` | | +| ui.externalPort | int | `8200` | | +| ui.externalTrafficPolicy | string | `"Cluster"` | | +| ui.extraLabels | object | `{}` | | +| ui.publishNotReadyAddresses | bool | `true` | | +| ui.serviceIPFamilies | list | `[]` | | +| ui.serviceIPFamilyPolicy | string | `""` | | +| ui.serviceNodePort | string | `nil` | | +| ui.serviceType | string | `"ClusterIP"` | | +| ui.targetPort | int | `8200` | | + +---------------------------------------------- +Autogenerated from chart metadata using [helm-docs v1.14.2](https://github.com/norwoodj/helm-docs/releases/v1.14.2) diff --git a/packages/system/openbao/charts/openbao/grafana/dashboards/dashboard.json b/packages/system/openbao/charts/openbao/grafana/dashboards/dashboard.json new file mode 100644 index 00000000..b4eb93b9 --- /dev/null +++ b/packages/system/openbao/charts/openbao/grafana/dashboards/dashboard.json @@ -0,0 +1,2559 @@ +{ + "__inputs": [ + { + "name": "DS_PROMXY", + "label": "Prometheus", + "description": "", + "type": "datasource", + "pluginId": "prometheus", + "pluginName": "Prometheus" + } + ], + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "7.0.3" + }, + { + "type": "panel", + "id": "grafana-piechart-panel", + "name": "Pie Chart", + "version": "1.5.0" + }, + { + "type": "panel", + "id": "graph", + "name": "Graph", + "version": "" + }, + { + "type": "datasource", + "id": "prometheus", + "name": "Prometheus", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "stat", + "name": "Stat", + "version": "" + }, + { + "type": "panel", + "id": "table", + "name": "Table", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "${DS_PROMXY}", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": " OpenBao Metrics", + "editable": true, + "graphTooltip": 1, + "id": null, + "iteration": 1602255075192, + "links": [], + "panels": [ + { + "datasource": "${DS_PROMXY}", + "fieldConfig": { + "defaults": { + "custom": { + "align": null + }, + "mappings": [ + { + "from": "", + "id": 0, + "operator": "", + "text": "Standby", + "to": "", + "type": 1, + "value": "0" + }, + { + "from": "", + "id": 1, + "operator": "", + "text": "Active", + "to": "", + "type": 1, + "value": "1" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 1 + } + ] + }, + "unit": "Misc" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 9, + "x": 0, + "y": 0 + }, + "id": 39, + "maxDataPoints": 100, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["last"], + "fields": "", + "values": false + } + }, + "pluginVersion": "7.0.3", + "targets": [ + { + "expr": "up{job=\"openbao-internal\"}", + "format": "time_series", + "interval": "", + "legendFormat": "{{ instance }}", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Healthy Status", + "type": "stat" + }, + { + "datasource": "${DS_PROMXY}", + "fieldConfig": { + "defaults": { + "custom": { + "align": null, + "displayMode": "auto" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Mount Path" + }, + "properties": [ + { + "id": "custom.width", + "value": 166 + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 5, + "x": 9, + "y": 0 + }, + "id": 59, + "maxDataPoints": 100, + "options": { + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "Number of Entries" + } + ] + }, + "pluginVersion": "7.0.3", + "targets": [ + { + "expr": "sum by (exported_namespace,mount_point) (${metrics_prefix}_secret_kv_count)", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "{{ mount_point }}", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Secrets", + "transformations": [ + { + "id": "seriesToColumns", + "options": { + "byField": "mount_point" + } + }, + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true, + "__name__": true, + "cluster": true, + "env": true, + "instance": true, + "job": true, + "namespace": true, + "project": true + }, + "indexByName": {}, + "renameByName": { + "Value": "Number of Entries", + "mount_point": "Mount Path", + "exported_namespace": "Namespace" + } + } + } + ], + "type": "table" + }, + { + "datasource": "${DS_PROMXY}", + "fieldConfig": { + "defaults": { + "custom": {}, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 5, + "x": 14, + "y": 0 + }, + "id": 78, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + } + }, + "pluginVersion": "7.0.3", + "targets": [ + { + "expr": "avg(${metrics_prefix}_identity_num_entities)", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Number of Identity Entities", + "type": "stat" + }, + { + "aliasColors": {}, + "breakPoint": "50%", + "cacheTimeout": null, + "combine": { + "label": "Others", + "threshold": 0 + }, + "datasource": "${DS_PROMXY}", + "decimals": null, + "fieldConfig": { + "defaults": { + "custom": { + "align": null + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "fontSize": "80%", + "format": "short", + "gridPos": { + "h": 8, + "w": 5, + "x": 19, + "y": 0 + }, + "id": 49, + "interval": null, + "legend": { + "header": "count", + "percentage": false, + "show": true, + "sideWidth": null, + "values": true + }, + "legendType": "Right side", + "links": [], + "maxDataPoints": 1, + "nullPointMode": "connected", + "pieType": "pie", + "pluginVersion": "7.0.3", + "strokeWidth": "3", + "targets": [ + { + "expr": "avg without(instance) (${metrics_prefix}_identity_entity_alias_count)", + "format": "time_series", + "interval": "", + "legendFormat": "{{ auth_method }}", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Identity Entities Aliases by Method", + "type": "grafana-piechart-panel", + "valueName": "current" + }, + { + "datasource": "${DS_PROMXY}", + "fieldConfig": { + "defaults": { + "custom": {}, + "mappings": [ + { + "from": "", + "id": 0, + "operator": "", + "text": "UNSEALED", + "to": "", + "type": 1, + "value": "2" + }, + { + "from": "", + "id": 1, + "operator": "", + "text": "SEALED", + "to": "", + "type": 1, + "value": "1" + } + ], + "noValue": "N/A", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "yellow", + "value": 1 + }, + { + "color": "green", + "value": 2 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 9, + "x": 0, + "y": 4 + }, + "id": 47, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["last"], + "fields": "", + "values": false + } + }, + "pluginVersion": "7.0.3", + "targets": [ + { + "expr": "max(1 + ${metrics_prefix}_core_unsealed{})", + "format": "time_series", + "interval": "", + "legendFormat": "{{ instance }}", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Sealed Status", + "type": "stat" + }, + { + "datasource": "${DS_PROMXY}", + "fieldConfig": { + "defaults": { + "custom": {}, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 100 + }, + { + "color": "#EF843C", + "value": 200 + }, + { + "color": "red", + "value": 400 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 5, + "x": 14, + "y": 4 + }, + "id": 95, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + } + }, + "pluginVersion": "7.0.3", + "targets": [ + { + "expr": "avg(${metrics_prefix}_expire_num_leases)", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Number of Leases", + "type": "stat" + }, + { + "collapsed": true, + "datasource": "${DS_PROMXY}", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 8 + }, + "id": 74, + "panels": [ + { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_PROMXY}", + "decimals": 0, + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 9 + }, + "hiddenSeries": false, + "id": 24, + "legend": { + "alignAsTable": true, + "avg": false, + "current": false, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": false, + "rightSide": false, + "show": true, + "sort": null, + "sortDesc": null, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "links": [], + "nullPointMode": "null as zero", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "scopedVars": { + "mountpoint": { + "selected": true, + "text": "secret", + "value": "secret" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "avg(increase(${metrics_prefix}_route_create_${mountpoint}__count[5m]))", + "format": "time_series", + "hide": false, + "interval": "5m", + "legendFormat": "Create", + "refId": "A" + }, + { + "expr": "avg(increase(${metrics_prefix}_route_delete_${mountpoint}__count[5m]))", + "hide": false, + "interval": "5m", + "legendFormat": "Delete", + "refId": "B" + }, + { + "expr": "avg(increase(${metrics_prefix}_route_read_${mountpoint}__count[5m]))", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "5m", + "intervalFactor": 1, + "legendFormat": "Read", + "refId": "C" + }, + { + "expr": "avg(increase(${metrics_prefix}_route_list_${mountpoint}__count[5m]))", + "format": "time_series", + "hide": false, + "interval": "5m", + "legendFormat": "List", + "refId": "D" + }, + { + "expr": "avg(increase(${metrics_prefix}_route_rollback_${mountpoint}__count[5m]))", + "hide": true, + "interval": "5m", + "legendFormat": "Rollback", + "refId": "E" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Number of Operations in \"$mountpoint\"", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:182", + "decimals": 0, + "format": "short", + "label": "Operations in 5 minute", + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:183", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_PROMXY}", + "decimals": 0, + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 9 + }, + "hiddenSeries": false, + "id": 35, + "legend": { + "alignAsTable": true, + "avg": true, + "current": false, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": false, + "rightSide": false, + "show": true, + "sort": null, + "sortDesc": null, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null as zero", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "scopedVars": { + "mountpoint": { + "selected": true, + "text": "secret", + "value": "secret" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "avg(rate(${metrics_prefix}_route_create_${mountpoint}__sum[1m]) / rate(${metrics_prefix}_route_create_${mountpoint}__count[1m]) * 1000)", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "Create", + "refId": "A" + }, + { + "expr": "avg(rate(${metrics_prefix}_route_delete_${mountpoint}__sum[1m]) / rate(${metrics_prefix}_route_delete_${mountpoint}__count[1m]) * 1000)", + "interval": "", + "legendFormat": "Delete", + "refId": "B" + }, + { + "expr": "avg(rate(${metrics_prefix}_route_read_${mountpoint}__sum[1m]) / rate(${metrics_prefix}_route_read_${mountpoint}__count[1m]) * 1000)", + "hide": false, + "interval": "", + "legendFormat": "Read", + "refId": "C" + }, + { + "expr": "avg(rate(${metrics_prefix}_route_list_${mountpoint}__sum[1m]) / rate(${metrics_prefix}_route_list_${mountpoint}__count[1m]) * 1000)", + "hide": false, + "interval": "", + "legendFormat": "List", + "refId": "D" + }, + { + "expr": "avg(rate(${metrics_prefix}_route_rollback_${mountpoint}__sum[1m]) / rate(${metrics_prefix}_route_rollback_${mountpoint}__count[1m]) * 1000)", + "hide": true, + "interval": "", + "legendFormat": "Rollback", + "refId": "E" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Time of Operations in \"$mountpoint\"", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:82", + "decimals": 0, + "format": "µs", + "label": "Time of one operation", + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "$$hashKey": "object:83", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "repeat": "mountpoint", + "title": "Path Info: $mountpoint", + "type": "row" + }, + { + "collapsed": false, + "datasource": "${DS_PROMXY}", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 9 + }, + "id": 45, + "panels": [], + "repeat": null, + "title": "CPU/Mem Info: $node", + "type": "row" + }, + { + "datasource": "${DS_PROMXY}", + "description": "", + "fieldConfig": { + "defaults": { + "custom": {}, + "mappings": [], + "noValue": "N/A", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 0.2 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 3, + "x": 0, + "y": 10 + }, + "id": 41, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["last"], + "fields": "", + "values": false + } + }, + "pluginVersion": "7.0.3", + "targets": [ + { + "expr": "avg(${metrics_prefix}_runtime_heap_objects{} / ${metrics_prefix}_runtime_malloc_count{})", + "interval": "", + "intervalFactor": 10, + "legendFormat": "", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Heap Objects Used", + "type": "stat" + }, + { + "datasource": "${DS_PROMXY}", + "fieldConfig": { + "defaults": { + "custom": {}, + "mappings": [], + "noValue": "N/A", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 70 + }, + { + "color": "#EF843C", + "value": 100 + }, + { + "color": "#E24D42", + "value": 150 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 3, + "x": 3, + "y": 10 + }, + "id": 76, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["last"], + "fields": "", + "values": false + } + }, + "pluginVersion": "7.0.3", + "targets": [ + { + "expr": "avg(${metrics_prefix}_runtime_num_goroutines{})", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Number of Goroutines", + "type": "stat" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_PROMXY}", + "decimals": 3, + "fieldConfig": { + "defaults": { + "custom": {}, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 5, + "w": 18, + "x": 6, + "y": 10 + }, + "hiddenSeries": false, + "id": 43, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pluginVersion": "7.0.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "avg(${metrics_prefix}_runtime_alloc_bytes{})", + "interval": "", + "intervalFactor": 5, + "legendFormat": "$node", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Allocated MB", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:373", + "format": "decbytes", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:374", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "datasource": "${DS_PROMXY}", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 15 + }, + "id": 16, + "panels": [], + "title": "Token", + "type": "row" + }, + { + "datasource": "${DS_PROMXY}", + "fieldConfig": { + "defaults": { + "custom": {}, + "mappings": [], + "noValue": "N/A", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 0, + "y": 16 + }, + "id": 53, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + } + }, + "pluginVersion": "7.0.3", + "targets": [ + { + "expr": "avg(${metrics_prefix}_token_count)", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Available Tokens", + "type": "stat" + }, + { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_PROMXY}", + "decimals": 0, + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 21, + "x": 3, + "y": 16 + }, + "hiddenSeries": false, + "id": 104, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "total": false, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "connected", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "avg without(instance) (${metrics_prefix}_token_count_by_policy)", + "interval": "", + "legendFormat": "{{ policy }}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Tokens by Policy", + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "series", + "name": null, + "show": true, + "values": ["current"] + }, + "yaxes": [ + { + "$$hashKey": "object:575", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:576", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "cacheTimeout": null, + "datasource": "${DS_PROMXY}", + "fieldConfig": { + "defaults": { + "custom": {}, + "mappings": [], + "noValue": "0", + "nullValueMode": "connected", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "orange", + "value": 1 + }, + { + "color": "red", + "value": 3 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 3, + "x": 0, + "y": 20 + }, + "id": 8, + "interval": null, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "value", + "fieldOptions": { + "calcs": ["lastNotNull"] + }, + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": ["last"], + "fields": "", + "values": false + } + }, + "pluginVersion": "7.0.3", + "targets": [ + { + "expr": "avg(${metrics_prefix}_token_create_count - ${metrics_prefix}_token_store_count)", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Pending Tokens", + "type": "stat" + }, + { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_PROMXY}", + "decimals": 0, + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 24 + }, + "hiddenSeries": false, + "id": 102, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "total": false, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "connected", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "avg without(instance) (${metrics_prefix}_token_count_by_ttl)", + "format": "time_series", + "interval": "", + "legendFormat": "{{ creation_ttl }}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Tokens by TTL", + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "series", + "name": null, + "show": true, + "values": ["current"] + }, + "yaxes": [ + { + "$$hashKey": "object:390", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:391", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_PROMXY}", + "decimals": 0, + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 24 + }, + "hiddenSeries": false, + "id": 100, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "max": true, + "min": false, + "rightSide": true, + "show": true, + "total": false, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "connected", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "avg without(instance) (${metrics_prefix}_token_count_by_auth)", + "interval": "", + "legendFormat": "{{ auth_method }}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Tokens by Auth Method", + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "series", + "name": null, + "show": true, + "values": ["current"] + }, + "yaxes": [ + { + "$$hashKey": "object:136", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:137", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_PROMXY}", + "decimals": 0, + "fieldConfig": { + "defaults": { + "custom": { + "align": null + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 12, + "x": 0, + "y": 32 + }, + "hiddenSeries": false, + "id": 65, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 1, + "maxDataPoints": 100, + "nullPointMode": "null as zero", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pluginVersion": "7.0.3", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "avg by(auth_method, creation_ttl) (${metrics_prefix}_token_creation)", + "format": "time_series", + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{ auth_method }} - {{ creation_ttl }}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Tokens Creation by Method & TTL", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:1956", + "format": "short", + "label": "", + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:1957", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Create": "rgb(84, 183, 90)", + "Store": "#0a437c" + }, + "bars": true, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_PROMXY}", + "decimals": 0, + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 12, + "x": 12, + "y": 32 + }, + "hiddenSeries": false, + "id": 6, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "expr": "avg without(instance) (${metrics_prefix}_token_create_count)", + "format": "time_series", + "instant": false, + "interval": "", + "intervalFactor": 10, + "legendFormat": "Create", + "refId": "A" + }, + { + "expr": "avg without(instance) (${metrics_prefix}_token_store_count)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 10, + "legendFormat": "Store", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Token Creation/Storage", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:877", + "decimals": 0, + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:878", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Lookup": "#0a50a1" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_PROMXY}", + "decimals": 3, + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 38 + }, + "hiddenSeries": false, + "id": 14, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "avg(irate(${metrics_prefix}_token_lookup_count[1m]))", + "hide": false, + "interval": "", + "legendFormat": "Lookups", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Token Lookups", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:330", + "decimals": 0, + "format": "short", + "label": "Lookups per second", + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "$$hashKey": "object:331", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "datasource": "${DS_PROMXY}", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 44 + }, + "id": 20, + "panels": [], + "title": "Audit", + "type": "row" + }, + { + "datasource": "${DS_PROMXY}", + "description": "", + "fieldConfig": { + "defaults": { + "custom": {}, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 1 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 3, + "x": 0, + "y": 45 + }, + "id": 97, + "maxDataPoints": 100, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["max"], + "fields": "", + "values": false + } + }, + "pluginVersion": "7.0.3", + "targets": [ + { + "expr": "max(idelta(${metrics_prefix}_audit_log_request_failure[1m]))", + "format": "time_series", + "interval": "", + "legendFormat": "Request", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Log Request Failures", + "type": "stat" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_PROMXY}", + "decimals": 3, + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 10, + "w": 11, + "x": 3, + "y": 45 + }, + "hiddenSeries": false, + "id": 4, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": true, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "avg(irate(${metrics_prefix}_audit_log_request_count[1m]))", + "format": "time_series", + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "Request ", + "refId": "A" + }, + { + "expr": "avg(irate(${metrics_prefix}_audit_log_response_count[1m]))", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "Response", + "refId": "B" + }, + { + "expr": "avg(irate(${metrics_prefix}_core_handle_request_count[1m]))", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "Handled", + "refId": "C" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Log Requests", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:109", + "decimals": 0, + "format": "short", + "label": "Requests per second", + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "$$hashKey": "object:110", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_PROMXY}", + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 10, + "w": 10, + "x": 14, + "y": 45 + }, + "hiddenSeries": false, + "id": 61, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null as zero", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "avg(irate(${metrics_prefix}_consul_get_count[1m]))", + "interval": "", + "intervalFactor": 1, + "legendFormat": "GET", + "refId": "A" + }, + { + "expr": "avg(irate(${metrics_prefix}_consul_put_count[1m]))", + "interval": "", + "legendFormat": "PUT", + "refId": "B" + }, + { + "expr": "avg(irate(${metrics_prefix}_consul_delete_count[1m]))", + "interval": "", + "legendFormat": "DELETE", + "refId": "C" + }, + { + "expr": "irate(${metrics_prefix}_consul_list_count{instance=\"$node:$port\"}[1m])", + "interval": "", + "legendFormat": "LIST", + "refId": "D" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Consul Requests", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:949", + "format": "short", + "label": "Requests per second", + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:950", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "datasource": "${DS_PROMXY}", + "description": "", + "fieldConfig": { + "defaults": { + "custom": {}, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 1 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 3, + "x": 0, + "y": 50 + }, + "id": 98, + "maxDataPoints": 100, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["max"], + "fields": "", + "values": false + } + }, + "pluginVersion": "7.0.3", + "targets": [ + { + "expr": "max(idelta(${metrics_prefix}_audit_log_response_failure[1m]))", + "format": "time_series", + "interval": "", + "legendFormat": "Request", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Log Response Failures", + "type": "stat" + }, + { + "collapsed": false, + "datasource": "${DS_PROMXY}", + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 55 + }, + "id": 18, + "panels": [], + "title": "Policy", + "type": "row" + }, + { + "aliasColors": { + "set": "#629e51" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_PROMXY}", + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 12, + "x": 0, + "y": 56 + }, + "hiddenSeries": false, + "id": 10, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "avg(irate(${metrics_prefix}_policy_set_policy_count[1m]))", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "SET", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Policy Set", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:1834", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "$$hashKey": "object:1835", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "GET": "#1f78c1" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_PROMXY}", + "fieldConfig": { + "defaults": { + "custom": {} + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 6, + "w": 12, + "x": 12, + "y": 56 + }, + "hiddenSeries": false, + "id": 12, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "avg(irate(${metrics_prefix}_policy_get_policy_count[1m]))", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "GET", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Policy Get", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:2132", + "decimals": 0, + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "$$hashKey": "object:2133", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": false, + "schemaVersion": 25, + "style": "dark", + "tags": ["openbao"], + "templating": { + "list": [ + { + "current": {}, + "hide": 0, + "includeAll": false, + "multi": false, + "name": "DS_PROMXY", + "label": "datasource", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "allValue": null, + "current": {}, + "datasource": "${DS_PROMXY}", + "definition": "label_values(up{job=\"openbao-internal\"}, instance)", + "hide": 2, + "includeAll": false, + "label": "Host:", + "multi": false, + "name": "node", + "options": [], + "query": "label_values(up{job=\"openbao-internal\"}, instance)", + "refresh": 1, + "regex": "/([^:]+):.*/", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": null, + "current": {}, + "datasource": "${DS_PROMXY}", + "definition": "label_values(up{job=\"openbao-internal\",instance=~\"$node:(.*)\"}, instance)", + "hide": 2, + "includeAll": false, + "label": null, + "multi": false, + "name": "port", + "options": [], + "query": "label_values(up{job=\"openbao-internal\",instance=~\"$node:(.*)\"}, instance)", + "refresh": 1, + "regex": "/[^:]+:(.*)/", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": "", + "current": {}, + "datasource": "${DS_PROMXY}", + "definition": "label_values(${metrics_prefix}_secret_kv_count, mount_point)", + "hide": 0, + "includeAll": true, + "label": "Mount Point:", + "multi": true, + "name": "mountpoint", + "options": [], + "query": "label_values(${metrics_prefix}_secret_kv_count, mount_point)", + "refresh": 2, + "regex": "/(.*)//", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": "", + "datasource": "${DS_PROMXY}", + "hide": 0, + "includeAll": true, + "label": "Metrics Prefix", + "current": { + "text": "vault", + "value": "vault" + }, + "description": "Metrics Prefix defined in the OpenBao configuration with `metrics_prefix`", + "name": "metrics_prefix", + "refresh": 2, + "options": [ + { + "selected": true, + "text": "vault", + "value": "vault" + } + ], + "query": "vault", + "type": "textbox" + } + ] + }, + "time": { + "from": "now-30m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + }, + "timezone": "", + "title": "OpenBao", + "uid": "openbao", + "version": 1 +} diff --git a/packages/system/openbao/charts/openbao/templates/NOTES.txt b/packages/system/openbao/charts/openbao/templates/NOTES.txt new file mode 100644 index 00000000..fce87c26 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/NOTES.txt @@ -0,0 +1,18 @@ + +Thank you for installing OpenBao! + +Now that you have deployed OpenBao, you should look over the docs on using +OpenBao with Kubernetes available here: + +https://openbao.org/docs/ + + +Your release is named {{ .Release.Name }}. To learn more about the release, try: + + $ helm status {{ .Release.Name }} + $ helm get manifest {{ .Release.Name }} + +{{ if and (not .Values.global.tlsDisable) .Values.server.gateway.httpRoute.enabled }} +WARNING: Terminating TLS before reaching the OpenBao Server is not recommended +and may break things like certificate authentication. Prefer usage of `TLSRoute`. +{{- end }} diff --git a/packages/system/openbao/charts/openbao/templates/_helpers.tpl b/packages/system/openbao/charts/openbao/templates/_helpers.tpl new file mode 100644 index 00000000..3597697b --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/_helpers.tpl @@ -0,0 +1,1212 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to +this (by the DNS naming spec). If release name contains chart name it will +be used as a full name. +*/}} +{{- define "openbao.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "openbao.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Expand the name of the chart. +*/}} +{{- define "openbao.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Allow the release namespace to be overridden +*/}} +{{- define "openbao.namespace" -}} +{{- default .Release.Namespace .Values.global.namespace -}} +{{- end -}} + +{{/* +Compute if the csi driver is enabled. +*/}} +{{- define "openbao.csiEnabled" -}} +{{- $_ := set . "csiEnabled" (or + (eq (.Values.csi.enabled | toString) "true") + (and (eq (.Values.csi.enabled | toString) "-") (eq (.Values.global.enabled | toString) "true"))) -}} +{{- end -}} + +{{/* +Compute if the injector is enabled. +*/}} +{{- define "openbao.injectorEnabled" -}} +{{- $_ := set . "injectorEnabled" (or + (eq (.Values.injector.enabled | toString) "true") + (and (eq (.Values.injector.enabled | toString) "-") (eq (.Values.global.enabled | toString) "true"))) -}} +{{- end -}} + +{{/* +Compute if the server is enabled. +*/}} +{{- define "openbao.serverEnabled" -}} +{{- $_ := set . "serverEnabled" (or + (eq (.Values.server.enabled | toString) "true") + (and (eq (.Values.server.enabled | toString) "-") (eq (.Values.global.enabled | toString) "true"))) -}} +{{- end -}} + +{{/* +Compute if the server serviceaccount is enabled. +*/}} +{{- define "openbao.serverServiceAccountEnabled" -}} +{{- $_ := set . "serverServiceAccountEnabled" + (and + (eq (.Values.server.serviceAccount.create | toString) "true" ) + (or + (eq (.Values.server.enabled | toString) "true") + (eq (.Values.global.enabled | toString) "true"))) -}} +{{- end -}} + +{{/* +Compute if the server serviceaccount should have a token created and mounted to the serviceaccount. +*/}} +{{- define "openbao.serverServiceAccountSecretCreationEnabled" -}} +{{- $_ := set . "serverServiceAccountSecretCreationEnabled" + (and + (eq (.Values.server.serviceAccount.create | toString) "true") + (eq (.Values.server.serviceAccount.createSecret | toString) "true")) -}} +{{- end -}} + + +{{/* +Compute if the server auth delegator serviceaccount is enabled. +*/}} +{{- define "openbao.serverAuthDelegator" -}} +{{- $_ := set . "serverAuthDelegator" + (and + (eq (.Values.server.authDelegator.enabled | toString) "true" ) + (or (eq (.Values.server.serviceAccount.create | toString) "true") + (not (eq .Values.server.serviceAccount.name ""))) + (or + (eq (.Values.server.enabled | toString) "true") + (eq (.Values.global.enabled | toString) "true"))) -}} +{{- end -}} + +{{/* +Compute if the server service is enabled. +*/}} +{{- define "openbao.serverServiceEnabled" -}} +{{- template "openbao.serverEnabled" . -}} +{{- $_ := set . "serverServiceEnabled" (and .serverEnabled (eq (.Values.server.service.enabled | toString) "true")) -}} +{{- end -}} + +{{/* +Compute if the ui is enabled. +*/}} +{{- define "openbao.uiEnabled" -}} +{{- $_ := set . "uiEnabled" (or + (eq (.Values.ui.enabled | toString) "true") + (and (eq (.Values.ui.enabled | toString) "-") (eq (.Values.global.enabled | toString) "true"))) -}} +{{- end -}} + +{{/* +Compute the maximum number of unavailable replicas for the PodDisruptionBudget. +This defaults to (n/2)-1 where n is the number of members of the server cluster. +Add a special case for replicas=1, where it should default to 0 as well. +*/}} +{{- define "openbao.pdb.maxUnavailable" -}} +{{- if eq (int .Values.server.ha.replicas) 1 -}} +{{ 0 }} +{{- else if .Values.server.ha.disruptionBudget.maxUnavailable -}} +{{ .Values.server.ha.disruptionBudget.maxUnavailable -}} +{{- else -}} +{{- div (sub (div (mul (int .Values.server.ha.replicas) 10) 2) 1) 10 -}} +{{- end -}} +{{- end -}} + +{{/* +Resolve the external OpenBao/Vault address by checking global and injector values in order of precedence: +1. global.externalBaoAddr +2. global.externalVaultAddr +*/}} + +{{- define "openbao.externalAddr" -}} + {{- if .Values.global.externalBaoAddr -}} + {{- .Values.global.externalBaoAddr -}} + {{- else -}} + {{- .Values.global.externalVaultAddr -}} + {{- end -}} +{{- end -}} + +{{/* +Set the variable 'mode' to the server mode requested by the user to simplify +template logic. +*/}} +{{- define "openbao.mode" -}} + {{- template "openbao.serverEnabled" . -}} + {{- if or (.Values.injector.externalVaultAddr) (.Values.global.externalVaultAddr) (.Values.global.externalBaoAddr) -}} + {{- $_ := set . "mode" "external" -}} + {{- else if not .serverEnabled -}} + {{- $_ := set . "mode" "external" -}} + {{- else if eq (.Values.server.dev.enabled | toString) "true" -}} + {{- $_ := set . "mode" "dev" -}} + {{- else if eq (.Values.server.ha.enabled | toString) "true" -}} + {{- $_ := set . "mode" "ha" -}} + {{- else if or (eq (.Values.server.standalone.enabled | toString) "true") (eq (.Values.server.standalone.enabled | toString) "-") -}} + {{- $_ := set . "mode" "standalone" -}} + {{- else -}} + {{- $_ := set . "mode" "" -}} + {{- end -}} +{{- end -}} + +{{/* +Set's the replica count based on the different modes configured by user +*/}} +{{- define "openbao.replicas" -}} + {{ if eq .mode "standalone" }} + {{- default 1 -}} + {{ else if eq .mode "ha" }} + {{- if or (kindIs "int64" .Values.server.ha.replicas) (kindIs "float64" .Values.server.ha.replicas) -}} + {{- .Values.server.ha.replicas -}} + {{ else }} + {{- 3 -}} + {{- end -}} + {{ else }} + {{- default 1 -}} + {{ end }} +{{- end -}} + +{{/* +Set's up configmap mounts if this isn't a dev deployment and the user +defined a custom configuration. Additionally iterates over any +extra volumes the user may have specified (such as a secret with TLS). +*/}} +{{- define "openbao.volumes" -}} + {{- if and (ne .mode "dev") (or (.Values.server.standalone.config) (.Values.server.ha.config)) }} + - name: config + configMap: + name: {{ template "openbao.fullname" . }}-config + {{ end }} + {{- range .Values.server.extraVolumes }} + - name: userconfig-{{ .name }} + {{ .type }}: + {{- if (eq .type "configMap") }} + name: {{ .name }} + {{- else if (eq .type "secret") }} + secretName: {{ .name }} + {{- end }} + defaultMode: {{ .defaultMode | default 420 }} + {{- end }} + {{- if .Values.server.volumes }} + {{- toYaml .Values.server.volumes | nindent 8}} + {{- end }} +{{- end -}} + +{{/* +Set's the args for custom command to render the OpenBao configuration +file with IP addresses to make the out of box experience easier +for users looking to use this chart with Consul Helm. +*/}} +{{- define "openbao.args" -}} + {{ if or (eq .mode "standalone") (eq .mode "ha") }} + - | + cp /openbao/config/extraconfig-from-values.hcl /tmp/storageconfig.hcl; + [ -n "${HOST_IP}" ] && sed -Ei "s|HOST_IP|${HOST_IP?}|g" /tmp/storageconfig.hcl; + [ -n "${POD_IP}" ] && sed -Ei "s|POD_IP|${POD_IP?}|g" /tmp/storageconfig.hcl; + [ -n "${HOSTNAME}" ] && sed -Ei "s|HOSTNAME|${HOSTNAME?}|g" /tmp/storageconfig.hcl; + [ -n "${API_ADDR}" ] && sed -Ei "s|API_ADDR|${API_ADDR?}|g" /tmp/storageconfig.hcl; + [ -n "${TRANSIT_ADDR}" ] && sed -Ei "s|TRANSIT_ADDR|${TRANSIT_ADDR?}|g" /tmp/storageconfig.hcl; + [ -n "${RAFT_ADDR}" ] && sed -Ei "s|RAFT_ADDR|${RAFT_ADDR?}|g" /tmp/storageconfig.hcl; + /usr/local/bin/docker-entrypoint.sh bao server -config=/tmp/storageconfig.hcl {{ .Values.server.extraArgs }} + {{ else if eq .mode "dev" }} + - | + /usr/local/bin/docker-entrypoint.sh bao server -dev {{ .Values.server.extraArgs }} + {{ end }} +{{- end -}} + +{{/* +Set's additional environment variables based on the mode. +*/}} +{{- define "openbao.envs" -}} + {{ if eq .mode "dev" }} + - name: VAULT_DEV_ROOT_TOKEN_ID + value: {{ .Values.server.dev.devRootToken }} + - name: VAULT_DEV_LISTEN_ADDRESS + value: "[::]:8200" + {{ end }} +{{- end -}} + +{{/* +Set's which additional volumes should be mounted to the container +based on the mode configured. +*/}} +{{- define "openbao.mounts" -}} + {{ if eq (.Values.server.auditStorage.enabled | toString) "true" }} + - name: audit + mountPath: {{ .Values.server.auditStorage.mountPath }} + {{ end }} + {{ if or (eq .mode "standalone") (and (eq .mode "ha") (eq (.Values.server.ha.raft.enabled | toString) "true")) }} + {{ if eq (.Values.server.dataStorage.enabled | toString) "true" }} + - name: data + mountPath: {{ .Values.server.dataStorage.mountPath }} + {{ end }} + {{ end }} + {{ if and (ne .mode "dev") (or (.Values.server.standalone.config) (.Values.server.ha.config)) }} + - name: config + mountPath: /openbao/config + {{ end }} + {{- range .Values.server.extraVolumes }} + - name: userconfig-{{ .name }} + readOnly: true + mountPath: {{ .path | default "/openbao/userconfig" }}/{{ .name }} + {{- end }} + {{- if .Values.server.volumeMounts }} + {{- toYaml .Values.server.volumeMounts | nindent 12}} + {{- end }} +{{- end -}} + +{{/* +Set's up the volumeClaimTemplates when data or audit storage is required. HA +might not use data storage since Consul is likely it's backend, however, audit +storage might be desired by the user. +*/}} +{{- define "openbao.volumeclaims" -}} + {{- if and (ne .mode "dev") (or .Values.server.dataStorage.enabled .Values.server.auditStorage.enabled) }} + volumeClaimTemplates: + {{- if and (eq (.Values.server.dataStorage.enabled | toString) "true") (or (eq .mode "standalone") (eq (.Values.server.ha.raft.enabled | toString ) "true" )) }} + - apiVersion: v1 + kind: PersistentVolumeClaim + metadata: + name: data + {{- include "openbao.dataVolumeClaim.annotations" . | nindent 6 }} + {{- include "openbao.dataVolumeClaim.labels" . | nindent 6 }} + spec: + accessModes: + - {{ .Values.server.dataStorage.accessMode | default "ReadWriteOnce" }} + resources: + requests: + storage: {{ .Values.server.dataStorage.size }} + {{- if .Values.server.dataStorage.storageClass }} + storageClassName: {{ .Values.server.dataStorage.storageClass }} + {{- end }} + {{ end }} + {{- if eq (.Values.server.auditStorage.enabled | toString) "true" }} + - apiVersion: v1 + kind: PersistentVolumeClaim + metadata: + name: audit + {{- include "openbao.auditVolumeClaim.annotations" . | nindent 6 }} + {{- include "openbao.auditVolumeClaim.labels" . | nindent 6 }} + spec: + accessModes: + - {{ .Values.server.auditStorage.accessMode | default "ReadWriteOnce" }} + resources: + requests: + storage: {{ .Values.server.auditStorage.size }} + {{- if .Values.server.auditStorage.storageClass }} + storageClassName: {{ .Values.server.auditStorage.storageClass }} + {{- end }} + {{ end }} + {{ end }} +{{- end -}} + +{{/* +Set's the affinity for pod placement when running in standalone and HA modes. +*/}} +{{- define "openbao.affinity" -}} + {{- if and (ne .mode "dev") .Values.server.affinity }} + affinity: + {{ $tp := typeOf .Values.server.affinity }} + {{- if eq $tp "string" }} + {{- tpl .Values.server.affinity . | nindent 8 | trim }} + {{- else }} + {{- toYaml .Values.server.affinity | nindent 8 }} + {{- end }} + {{ end }} +{{- end -}} + +{{/* +Sets the injector affinity for pod placement +*/}} +{{- define "injector.affinity" -}} + {{- if .Values.injector.affinity }} + affinity: + {{ $tp := typeOf .Values.injector.affinity }} + {{- if eq $tp "string" }} + {{- tpl .Values.injector.affinity . | nindent 8 | trim }} + {{- else }} + {{- toYaml .Values.injector.affinity | nindent 8 }} + {{- end }} + {{ end }} +{{- end -}} + +{{/* +Sets the topologySpreadConstraints when running in standalone and HA modes. +*/}} +{{- define "openbao.topologySpreadConstraints" -}} + {{- if and (ne .mode "dev") .Values.server.topologySpreadConstraints }} + topologySpreadConstraints: + {{ $tp := typeOf .Values.server.topologySpreadConstraints }} + {{- if eq $tp "string" }} + {{- tpl .Values.server.topologySpreadConstraints . | nindent 8 | trim }} + {{- else }} + {{- toYaml .Values.server.topologySpreadConstraints | nindent 8 }} + {{- end }} + {{ end }} +{{- end -}} + +{{/* +Sets the injector topologySpreadConstraints for pod placement +*/}} +{{- define "injector.topologySpreadConstraints" -}} + {{- if .Values.injector.topologySpreadConstraints }} + topologySpreadConstraints: + {{ $tp := typeOf .Values.injector.topologySpreadConstraints }} + {{- if eq $tp "string" }} + {{- tpl .Values.injector.topologySpreadConstraints . | nindent 8 | trim }} + {{- else }} + {{- toYaml .Values.injector.topologySpreadConstraints | nindent 8 }} + {{- end }} + {{ end }} +{{- end -}} + +{{/* +Sets the toleration for pod placement when running in standalone and HA modes. +*/}} +{{- define "openbao.tolerations" -}} + {{- if and (ne .mode "dev") .Values.server.tolerations }} + tolerations: + {{- $tp := typeOf .Values.server.tolerations }} + {{- if eq $tp "string" }} + {{ tpl .Values.server.tolerations . | nindent 8 | trim }} + {{- else }} + {{- toYaml .Values.server.tolerations | nindent 8 }} + {{- end }} + {{- end }} +{{- end -}} + +{{/* +Sets the injector toleration for pod placement +*/}} +{{- define "injector.tolerations" -}} + {{- if .Values.injector.tolerations }} + tolerations: + {{- $tp := typeOf .Values.injector.tolerations }} + {{- if eq $tp "string" }} + {{ tpl .Values.injector.tolerations . | nindent 8 | trim }} + {{- else }} + {{- toYaml .Values.injector.tolerations | nindent 8 }} + {{- end }} + {{- end }} +{{- end -}} + +{{/* +Set's the node selector for pod placement when running in standalone and HA modes. +*/}} +{{- define "openbao.nodeselector" -}} + {{- if and (ne .mode "dev") .Values.server.nodeSelector }} + nodeSelector: + {{- $tp := typeOf .Values.server.nodeSelector }} + {{- if eq $tp "string" }} + {{ tpl .Values.server.nodeSelector . | nindent 8 | trim }} + {{- else }} + {{- toYaml .Values.server.nodeSelector | nindent 8 }} + {{- end }} + {{- end }} +{{- end -}} + +{{/* +Sets the injector node selector for pod placement +*/}} +{{- define "injector.nodeselector" -}} + {{- if .Values.injector.nodeSelector }} + nodeSelector: + {{- $tp := typeOf .Values.injector.nodeSelector }} + {{- if eq $tp "string" }} + {{ tpl .Values.injector.nodeSelector . | nindent 8 | trim }} + {{- else }} + {{- toYaml .Values.injector.nodeSelector | nindent 8 }} + {{- end }} + {{- end }} +{{- end -}} + +{{/* +Sets the injector deployment update strategy +*/}} +{{- define "injector.strategy" -}} + {{- if .Values.injector.strategy }} + strategy: + {{- $tp := typeOf .Values.injector.strategy }} + {{- if eq $tp "string" }} + {{ tpl .Values.injector.strategy . | nindent 4 | trim }} + {{- else }} + {{- toYaml .Values.injector.strategy | nindent 4 }} + {{- end }} + {{- end }} +{{- end -}} + +{{/* +Renders service annotations from either a string (templated) or a map with indent 4. +Usage: {{ include "openbao.annotations.render.4" (list . ) }} +*/}} +{{- define "openbao.annotations.render.4" -}} + {{- $ctx := index . 0 -}} + {{- $annotations := index . 1 -}} + {{- $annotationsType := typeOf $annotations -}} + {{- if eq $annotationsType "string" -}} + {{- tpl $annotations $ctx | nindent 4 -}} + {{- else -}} + {{- toYaml $annotations | nindent 4 -}} + {{- end -}} +{{- end -}} + +{{/* +Renders service annotations from either a string (templated) or a map with indent 8. +Usage: {{ include "openbao.annotations.render.4" (list . ) }} +*/}} +{{- define "openbao.annotations.render.8" -}} + {{- $ctx := index . 0 -}} + {{- $annotations := index . 1 -}} + {{- $annotationsType := typeOf $annotations -}} + {{- if eq $annotationsType "string" -}} + {{- tpl $annotations $ctx | nindent 8 -}} + {{- else -}} + {{- toYaml $annotations | nindent 8 -}} + {{- end -}} +{{- end -}} + +{{/* +Sets extra pod annotations +*/}} +{{- define "openbao.annotations" }} + {{- if or .Values.server.configAnnotation .Values.server.annotations }} + annotations: + {{- if .Values.server.configAnnotation }} + openbao.hashicorp.com/config-checksum: {{ include "openbao.config" . | sha256sum }} + {{- end }} + {{- $generic := .Values.server.annotations -}} + {{- if $generic }} + {{- include "openbao.annotations.render.8" (list . $generic) -}} + {{- end }} +{{- end -}} +{{- end -}} + +{{/* +Sets extra injector pod annotations +*/}} +{{- define "injector.annotations" -}} + {{- $generic := .Values.injector.annotations -}} + {{- if $generic }} + annotations: + {{- include "openbao.annotations.render.8" (list . $generic) -}} + {{- end }} +{{- end -}} + +{{/* +Sets extra injector service annotations +*/}} +{{- define "injector.service.annotations" -}} + {{- $generic := .Values.injector.service.annotations -}} + {{- if $generic }} + annotations: + {{- include "openbao.annotations.render.4" (list . $generic) -}} + {{- end }} +{{- end -}} + +{{/* +securityContext for the injector pod level. +*/}} +{{- define "injector.securityContext.pod" -}} + {{- if .Values.injector.securityContext.pod }} + securityContext: + {{- $tp := typeOf .Values.injector.securityContext.pod }} + {{- if eq $tp "string" }} + {{- tpl .Values.injector.securityContext.pod . | nindent 8 }} + {{- else }} + {{- toYaml .Values.injector.securityContext.pod | nindent 8 }} + {{- end }} + {{- else if not .Values.global.openshift }} + securityContext: + runAsNonRoot: true + runAsGroup: {{ .Values.injector.gid | default 1000 }} + runAsUser: {{ .Values.injector.uid | default 100 }} + fsGroup: {{ .Values.injector.gid | default 1000 }} + {{- end }} +{{- end -}} + +{{/* +securityContext for the injector container level. +*/}} +{{- define "injector.securityContext.container" -}} + {{- if .Values.injector.securityContext.container}} + securityContext: + {{- $tp := typeOf .Values.injector.securityContext.container }} + {{- if eq $tp "string" }} + {{- tpl .Values.injector.securityContext.container . | nindent 12 }} + {{- else }} + {{- toYaml .Values.injector.securityContext.container | nindent 12 }} + {{- end }} + {{- else if not .Values.global.openshift }} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + {{- end }} +{{- end -}} + +{{/* +securityContext for the statefulset pod template. +*/}} +{{- define "server.statefulSet.securityContext.pod" -}} + {{- if .Values.server.statefulSet.securityContext.pod }} + securityContext: + {{- $tp := typeOf .Values.server.statefulSet.securityContext.pod }} + {{- if eq $tp "string" }} + {{- tpl .Values.server.statefulSet.securityContext.pod . | nindent 8 }} + {{- else }} + {{- toYaml .Values.server.statefulSet.securityContext.pod | nindent 8 }} + {{- end }} + {{- else if not .Values.global.openshift }} + securityContext: + runAsNonRoot: true + runAsGroup: {{ .Values.server.gid | default 1000 }} + runAsUser: {{ .Values.server.uid | default 100 }} + fsGroup: {{ .Values.server.gid | default 1000 }} + {{- end }} +{{- end -}} + +{{/* +securityContext for the statefulset openbao container +*/}} +{{- define "server.statefulSet.securityContext.container" -}} + {{- if .Values.server.statefulSet.securityContext.container }} + securityContext: + {{- $tp := typeOf .Values.server.statefulSet.securityContext.container }} + {{- if eq $tp "string" }} + {{- tpl .Values.server.statefulSet.securityContext.container . | nindent 12 }} + {{- else }} + {{- toYaml .Values.server.statefulSet.securityContext.container | nindent 12 }} + {{- end }} + {{- else if not .Values.global.openshift }} + securityContext: + allowPrivilegeEscalation: false + {{- end }} +{{- end -}} + +{{/* +Sets extra injector service account annotations +*/}} +{{- define "injector.serviceAccount.annotations" -}} + {{- $generic := .Values.injector.serviceAccount.annotations -}} + {{- if and (ne .mode "dev") $generic }} + annotations: + {{- include "openbao.annotations.render.4" (list . $generic) -}} + {{- end }} +{{- end -}} + +{{/* +Sets extra injector webhook annotations +*/}} +{{- define "injector.webhookAnnotations" -}} + {{- $wa1 := ((.Values.injector.webhook)).annotations -}} + {{- $wa2 := .Values.injector.webhookAnnotations -}} + {{- if or $wa1 $wa2 }} + annotations: + {{- if $wa1 }} + {{- include "openbao.annotations.render.4" (list . $wa1) -}} + {{- end }} + {{- if $wa2 }} + {{- include "openbao.annotations.render.4" (list . $wa2) -}} + {{- end }} + {{- end }} +{{- end -}} + +{{/* +Set's the injector webhook objectSelector +*/}} +{{- define "injector.objectSelector" -}} + {{- $v := or (((.Values.injector.webhook)).objectSelector) (.Values.injector.objectSelector) -}} + {{ if $v }} + objectSelector: + {{- $tp := typeOf $v -}} + {{ if eq $tp "string" }} + {{ tpl $v . | indent 6 | trim }} + {{ else }} + {{ toYaml $v | indent 6 | trim }} + {{ end }} + {{ end }} +{{ end }} + +{{/* +Sets extra ui service annotations +*/}} +{{- define "openbao.ui.annotations" -}} + {{- $generic := .Values.ui.annotations -}} + {{- if $generic }} + annotations: + {{- include "openbao.annotations.render.4" (list . $generic) -}} + {{- end }} +{{- end -}} + +{{/* +Create the name of the service account to use +*/}} +{{- define "openbao.serviceAccount.name" -}} +{{- if .Values.server.serviceAccount.create -}} + {{ default (include "openbao.fullname" .) .Values.server.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.server.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{/* +Sets extra service account annotations +*/}} +{{- define "openbao.serviceAccount.annotations" -}} + {{- $generic := .Values.server.serviceAccount.annotations -}} + {{- if and (ne .mode "dev") $generic }} + annotations: + {{- include "openbao.annotations.render.4" (list . $generic) -}} + {{- end }} +{{- end -}} + +{{/* +Sets extra ingress annotations +*/}} +{{- define "openbao.ingress.annotations" -}} + {{- $generic := .Values.server.ingress.annotations -}} + {{- if $generic }} + annotations: + {{- include "openbao.annotations.render.4" (list . $generic) -}} + {{- end }} +{{- end -}} + +{{/* +Sets extra TLSRoute annotations +*/}} +{{- define "openbao.gateway.tlsRoute.annotations" -}} + {{- $generic := .Values.server.gateway.tlsRoute.annotations -}} + {{- if $generic }} + annotations: + {{- include "openbao.annotations.render.4" (list . $generic) -}} + {{- end }} +{{- end -}} + +{{/* +Sets extra HTTPRoute annotations +*/}} +{{- define "openbao.gateway.httpRoute.annotations" -}} + {{- $generic := .Values.server.gateway.httpRoute.annotations -}} + {{- if $generic }} + annotations: + {{- include "openbao.annotations.render.4" (list . $generic) -}} + {{- end }} +{{- end -}} + +{{/* +Sets extra BackendTLSPolicy annotations +*/}} +{{- define "openbao.gateway.tlsPolicy.annotations" -}} + {{- $generic := .Values.server.gateway.tlsPolicy.annotations -}} + {{- if $generic }} + annotations: + {{- include "openbao.annotations.render.4" (list . $generic) -}} + {{- end }} +{{- end -}} + +{{/* +Sets extra route annotations +*/}} +{{- define "openbao.route.annotations" -}} + {{- $generic := .Values.server.route.annotations -}} + {{- if $generic }} + annotations: + {{- include "openbao.annotations.render.4" (list . $generic) -}} + {{- end }} +{{- end -}} + +{{/* +Sets extra openbao server Service annotations +*/}} +{{- define "openbao.service.annotations" -}} + {{- $generic := .Values.server.service.annotations -}} + {{- if $generic }} + annotations: + {{- include "openbao.annotations.render.4" (list . $generic) -}} + {{- end }} +{{- end -}} + +{{/* +Sets extra openbao server Service (active) annotations +*/}} +{{- define "openbao.service.active.annotations" -}} + {{- $active := .Values.server.service.active.annotations -}} + {{- $generic := .Values.server.service.annotations -}} + {{- if or $active $generic }} + annotations: + {{- if $active }} + {{- include "openbao.annotations.render.4" (list . $active) -}} + {{- end }} + {{- if $generic }} + {{- include "openbao.annotations.render.4" (list . $generic) -}} + {{- end }} + {{- end }} +{{- end -}} + +{{/* +Sets extra openbao server Service (standby) annotations +*/}} +{{- define "openbao.service.standby.annotations" -}} + {{- $standby := .Values.server.service.standby.annotations -}} + {{- $generic := .Values.server.service.annotations -}} + {{- if or $standby $generic }} + annotations: + {{- if $standby }} + {{- include "openbao.annotations.render.4" (list . $standby) -}} + {{- end }} + {{- if $generic }} + {{- include "openbao.annotations.render.4" (list . $generic) -}} + {{- end }} + {{- end }} +{{- end -}} + +{{/* +Sets PodSecurityPolicy annotations +*/}} +{{- define "openbao.psp.annotations" -}} + {{- $generic := .Values.global.psp.annotations -}} + {{- if $generic }} + annotations: + {{- include "openbao.annotations.render.4" (list . $generic) -}} + {{- end }} +{{- end -}} + +{{/* +Sets extra statefulset annotations +*/}} +{{- define "openbao.statefulSet.annotations" -}} + {{- $generic := .Values.server.statefulSet.annotations -}} + {{- if $generic }} + annotations: + {{- include "openbao.annotations.render.4" (list . $generic) -}} + {{- end }} +{{- end -}} + +{{/* +Sets VolumeClaim annotations for data volume +*/}} +{{- define "openbao.dataVolumeClaim.annotations" -}} + {{- $generic := .Values.server.dataStorage.annotations -}} + {{- if and (ne .mode "dev") (.Values.server.dataStorage.enabled) $generic }} + annotations: + {{- include "openbao.annotations.render.4" (list . $generic) -}} + {{- end }} +{{- end -}} + +{{/* +Sets VolumeClaim labels for data volume +*/}} +{{- define "openbao.dataVolumeClaim.labels" -}} + {{- if and (ne .mode "dev") (.Values.server.dataStorage.enabled) (.Values.server.dataStorage.labels) }} + labels: + {{- $tp := typeOf .Values.server.dataStorage.labels }} + {{- if eq $tp "string" }} + {{- tpl .Values.server.dataStorage.labels . | nindent 4 }} + {{- else }} + {{- toYaml .Values.server.dataStorage.labels | nindent 4 }} + {{- end }} + {{- end }} +{{- end -}} + +{{/* +Sets VolumeClaim annotations for audit volume +*/}} +{{- define "openbao.auditVolumeClaim.annotations" -}} + {{- $generic := .Values.server.auditStorage.annotations -}} + {{- if and (ne .mode "dev") (.Values.server.auditStorage.enabled) $generic }} + annotations: + {{- include "openbao.annotations.render.4" (list . $generic) -}} + {{- end }} +{{- end -}} + +{{/* +Sets VolumeClaim labels for audit volume +*/}} +{{- define "openbao.auditVolumeClaim.labels" -}} + {{- if and (ne .mode "dev") (.Values.server.auditStorage.enabled) (.Values.server.auditStorage.labels) }} + labels: + {{- $tp := typeOf .Values.server.auditStorage.labels }} + {{- if eq $tp "string" }} + {{- tpl .Values.server.auditStorage.labels . | nindent 4 }} + {{- else }} + {{- toYaml .Values.server.auditStorage.labels | nindent 4 }} + {{- end }} + {{- end }} +{{- end -}} + +{{/* +Set's the container resources if the user has set any. +*/}} +{{- define "openbao.resources" -}} + {{- if .Values.server.resources -}} + resources: +{{ toYaml .Values.server.resources | indent 12}} + {{ end }} +{{- end -}} + +{{/* +Sets the container resources if the user has set any. +*/}} +{{- define "injector.resources" -}} + {{- if .Values.injector.resources -}} + resources: +{{ toYaml .Values.injector.resources | indent 12}} + {{ end }} +{{- end -}} + +{{/* +Sets the container resources if the user has set any. +*/}} +{{- define "csi.resources" -}} + {{- if .Values.csi.resources -}} + resources: +{{ toYaml .Values.csi.resources | indent 12}} + {{ end }} +{{- end -}} + +{{/* +Sets the container resources for CSI's Agent sidecar if the user has set any. +*/}} +{{- define "csi.agent.resources" -}} + {{- if .Values.csi.agent.resources -}} + resources: +{{ toYaml .Values.csi.agent.resources | indent 12}} + {{ end }} +{{- end -}} + +{{/* +Set's the container resources for the SnapshotAgent if the user has set any. +*/}} +{{- define "openbao.snapshotAgent.resources" -}} + {{- if .Values.snapshotAgent.resources -}} + resources: +{{ toYaml .Values.snapshotAgent.resources | indent 14}} + {{ end }} +{{- end -}} + +{{/* +Sets extra CSI daemonset annotations +*/}} +{{- define "csi.daemonSet.annotations" -}} + {{- $generic := .Values.csi.daemonSet.annotations -}} + {{- if $generic }} + annotations: + {{- include "openbao.annotations.render.4" (list . $generic) -}} + {{- end }} +{{- end -}} + +{{/* +Sets CSI daemonset securityContext for pod template +*/}} +{{- define "csi.daemonSet.securityContext.pod" -}} + {{- if .Values.csi.daemonSet.securityContext.pod }} + securityContext: + {{- $tp := typeOf .Values.csi.daemonSet.securityContext.pod }} + {{- if eq $tp "string" }} + {{- tpl .Values.csi.daemonSet.securityContext.pod . | nindent 8 }} + {{- else }} + {{- toYaml .Values.csi.daemonSet.securityContext.pod | nindent 8 }} + {{- end }} + {{- end }} +{{- end -}} + +{{/* +Sets CSI daemonset securityContext for container +*/}} +{{- define "csi.daemonSet.securityContext.container" -}} + {{- if .Values.csi.daemonSet.securityContext.container }} + securityContext: + {{- $tp := typeOf .Values.csi.daemonSet.securityContext.container }} + {{- if eq $tp "string" }} + {{- tpl .Values.csi.daemonSet.securityContext.container . | nindent 12 }} + {{- else }} + {{- toYaml .Values.csi.daemonSet.securityContext.container | nindent 12 }} + {{- end }} + {{- end }} +{{- end -}} + +{{/* +Sets the injector toleration for pod placement +*/}} +{{- define "csi.pod.tolerations" -}} + {{- if .Values.csi.pod.tolerations }} + tolerations: + {{- $tp := typeOf .Values.csi.pod.tolerations }} + {{- if eq $tp "string" }} + {{ tpl .Values.csi.pod.tolerations . | nindent 8 | trim }} + {{- else }} + {{- toYaml .Values.csi.pod.tolerations | nindent 8 }} + {{- end }} + {{- end }} +{{- end -}} + +{{/* +Sets the CSI provider nodeSelector for pod placement +*/}} +{{- define "csi.pod.nodeselector" -}} + {{- if .Values.csi.pod.nodeSelector }} + nodeSelector: + {{- $tp := typeOf .Values.csi.pod.nodeSelector }} + {{- if eq $tp "string" }} + {{ tpl .Values.csi.pod.nodeSelector . | nindent 8 | trim }} + {{- else }} + {{- toYaml .Values.csi.pod.nodeSelector | nindent 8 }} + {{- end }} + {{- end }} +{{- end -}} +{{/* +Sets the CSI provider affinity for pod placement. +*/}} +{{- define "csi.pod.affinity" -}} + {{- if .Values.csi.pod.affinity }} + affinity: + {{ $tp := typeOf .Values.csi.pod.affinity }} + {{- if eq $tp "string" }} + {{- tpl .Values.csi.pod.affinity . | nindent 8 | trim }} + {{- else }} + {{- toYaml .Values.csi.pod.affinity | nindent 8 }} + {{- end }} + {{ end }} +{{- end -}} +{{/* +Sets extra CSI provider pod annotations +*/}} +{{- define "csi.pod.annotations" -}} + {{- $generic := .Values.csi.pod.annotations -}} + {{- if $generic }} + annotations: + {{- include "openbao.annotations.render.8" (list . $generic) -}} + {{- end }} +{{- end -}} + +{{/* +Sets extra CSI service account annotations +*/}} +{{- define "csi.serviceAccount.annotations" -}} + {{- $generic := .Values.csi.serviceAccount.annotations -}} + {{- if $generic }} + annotations: + {{- include "openbao.annotations.render.4" (list . $generic) -}} + {{- end }} +{{- end -}} + +{{/* +Inject extra environment vars in the format key:value, if populated +*/}} +{{- define "openbao.extraEnvironmentVars" -}} +{{- if .extraEnvironmentVars -}} +{{- range $key, $value := .extraEnvironmentVars }} +- name: {{ printf "%s" $key | replace "." "_" | upper | quote }} + value: {{ $value | quote }} +{{- end }} +{{- end -}} +{{- end -}} + +{{/* +Inject extra environment populated by secrets, if populated +*/}} +{{- define "openbao.extraSecretEnvironmentVars" -}} +{{- if .extraSecretEnvironmentVars -}} +{{- range .extraSecretEnvironmentVars }} +- name: {{ .envName }} + valueFrom: + secretKeyRef: + name: {{ .secretName }} + key: {{ .secretKey }} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* Scheme for health check and local endpoint */}} +{{- define "openbao.scheme" -}} +{{- if .Values.global.tlsDisable -}} +{{ "http" }} +{{- else -}} +{{ "https" }} +{{- end -}} +{{- end -}} + +{{/* +imagePullSecrets generates pull secrets from either string or map values. +A map value must be indexable by the key 'name'. +*/}} +{{- define "imagePullSecrets" -}} +{{- with .Values.global.imagePullSecrets -}} +imagePullSecrets: +{{- range . -}} +{{- if typeIs "string" . }} + - name: {{ . }} +{{- else if index . "name" }} + - name: {{ .name }} +{{- end }} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +externalTrafficPolicy sets a Service's externalTrafficPolicy if applicable. +Supported inputs are Values.server.service and Values.ui +*/}} +{{- define "service.externalTrafficPolicy" -}} +{{- $type := "" -}} +{{- if .serviceType -}} +{{- $type = .serviceType -}} +{{- else if .type -}} +{{- $type = .type -}} +{{- end -}} +{{- if and .externalTrafficPolicy (or (eq $type "LoadBalancer") (eq $type "NodePort")) }} + externalTrafficPolicy: {{ .externalTrafficPolicy }} +{{- else }} +{{- end }} +{{- end -}} + +{{/* +loadBalancer configuration for the the UI service. +Supported inputs are Values.ui +*/}} +{{- define "service.loadBalancer" -}} +{{- if eq (.serviceType | toString) "LoadBalancer" }} +{{- if .loadBalancerIP }} + loadBalancerIP: {{ .loadBalancerIP }} +{{- end }} +{{- with .loadBalancerSourceRanges }} + loadBalancerSourceRanges: +{{- range . }} + - {{ . }} +{{- end }} +{{- end -}} +{{- end }} +{{- end -}} + +{{/* +config file from values +*/}} +{{- define "openbao.config" -}} + {{- if or (eq .mode "ha") (eq .mode "standalone") }} + {{- $type := typeOf (index .Values.server .mode).config }} + {{- if eq $type "string" }} + {{- if eq .mode "standalone" }} + {{ tpl .Values.server.standalone.config . | nindent 4 | trim }} + {{- else if and (eq .mode "ha") (eq (.Values.server.ha.raft.enabled | toString) "false") }} + {{ tpl .Values.server.ha.config . | nindent 4 | trim }} + {{- else if and (eq .mode "ha") (eq (.Values.server.ha.raft.enabled | toString) "true") }} + {{ tpl .Values.server.ha.raft.config . | nindent 4 | trim }} + {{ end }} + {{- else }} + {{- if and (eq .mode "ha") (eq (.Values.server.ha.raft.enabled | toString) "true") }} +{{ (index .Values.server .mode).raft.config | toPrettyJson | indent 4 }} + {{- else }} +{{ (index .Values.server .mode).config | toPrettyJson | indent 4 }} + {{- end }} + {{- end }} + {{- end }} +{{- end -}} + +{{/* +Create the name of the service account to use for the snasphot-agent +*/}} +{{- define "openbao.snapshotAgent.serviceAccount.name" -}} +{{- if .Values.snapshotAgent.serviceAccount.create -}} + {{ default (printf "%s-%s" (include "openbao.fullname" .) "snapshot") .Values.snapshotAgent.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.snapshotAgent.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{/* +Sets extra service account annotations for the snapshot-agent +*/}} +{{- define "openbao.snapshotAgent.serviceAccount.annotations" -}} + {{- if and (ne .mode "dev") .Values.snapshotAgent.serviceAccount.annotations }} + annotations: + {{- $tp := typeOf .Values.snapshotAgent.serviceAccount.annotations }} + {{- if eq $tp "string" }} + {{- tpl .Values.snapshotAgent.serviceAccount.annotations . | nindent 4 }} + {{- else }} + {{- toYaml .Values.snapshotAgent.serviceAccount.annotations | nindent 4 }} + {{- end }} + {{- end }} +{{- end -}} + +{{/* +Sets extra snapshotAgent job annotations +*/}} +{{- define "openbao.snapshotAgent.annotations" -}} + {{- if .Values.snapshotAgent.annotations }} + annotations: + {{- $tp := typeOf .Values.snapshotAgent.annotations }} + {{- if eq $tp "string" }} + {{- tpl .Values.snapshotAgent.annotations . | nindent 4 }} + {{- else }} + {{- toYaml .Values.snapshotAgent.annotations | nindent 4 }} + {{- end }} + {{- end }} +{{- end -}} + +{{/* +securityContext for the snapshotAgent pod level. +*/}} +{{- define "snapshotAgent.securityContext.pod" -}} + {{- if .Values.snapshotAgent.securityContext.pod }} + securityContext: + {{- $tp := typeOf .Values.snapshotAgent.securityContext.pod }} + {{- if eq $tp "string" }} + {{- tpl .Values.snapshotAgent.securityContext.pod . | nindent 12 }} + {{- else }} + {{- toYaml .Values.snapshotAgent.securityContext.pod | nindent 12 }} + {{- end }} + {{- else if not .Values.global.openshift }} + securityContext: + runAsNonRoot: true + runAsGroup: {{ .Values.snapshotAgent.gid | default 1000 }} + runAsUser: {{ .Values.snapshotAgent.uid | default 100 }} + fsGroup: {{ .Values.snapshotAgent.gid | default 1000 }} + {{- end }} +{{- end -}} + +{{/* +securityContext for the snapshotAgent container level. +*/}} +{{- define "snapshotAgent.securityContext.container" -}} + {{- if .Values.snapshotAgent.securityContext.container }} + securityContext: + {{- $tp := typeOf .Values.snapshotAgent.securityContext.container }} + {{- if eq $tp "string" }} + {{- tpl .Values.snapshotAgent.securityContext.container . | nindent 14 }} + {{- else }} + {{- toYaml .Values.snapshotAgent.securityContext.container | nindent 14 }} + {{- end }} + {{- else if not .Values.global.openshift }} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + {{- end }} +{{- end -}} diff --git a/packages/system/openbao/charts/openbao/templates/csi-agent-configmap.yaml b/packages/system/openbao/charts/openbao/templates/csi-agent-configmap.yaml new file mode 100644 index 00000000..cf9dded9 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/csi-agent-configmap.yaml @@ -0,0 +1,34 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{- template "openbao.csiEnabled" . -}} +{{- if and (.csiEnabled) (eq (.Values.csi.agent.enabled | toString) "true") -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "openbao.fullname" . }}-csi-provider-agent-config + namespace: {{ include "openbao.namespace" . }} + labels: + helm.sh/chart: {{ include "openbao.chart" . }} + app.kubernetes.io/name: {{ include "openbao.name" . }}-csi-provider + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +data: + config.hcl: | + vault { + {{- if include "openbao.externalAddr" . }} + "address" = "{{ include "openbao.externalAddr" . }}" + {{- else }} + "address" = "{{ include "openbao.scheme" . }}://{{ template "openbao.fullname" . }}.{{ include "openbao.namespace" . }}.svc:{{ .Values.server.service.port }}" + {{- end }} + } + + cache {} + + listener "unix" { + address = "/var/run/vault/agent.sock" + tls_disable = true + } +{{- end }} diff --git a/packages/system/openbao/charts/openbao/templates/csi-clusterrole.yaml b/packages/system/openbao/charts/openbao/templates/csi-clusterrole.yaml new file mode 100644 index 00000000..a3fbb612 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/csi-clusterrole.yaml @@ -0,0 +1,23 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{- template "openbao.csiEnabled" . -}} +{{- if .csiEnabled -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "openbao.fullname" . }}-csi-provider-clusterrole + labels: + app.kubernetes.io/name: {{ include "openbao.name" . }}-csi-provider + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +rules: +- apiGroups: + - "" + resources: + - serviceaccounts/token + verbs: + - create +{{- end }} diff --git a/packages/system/openbao/charts/openbao/templates/csi-clusterrolebinding.yaml b/packages/system/openbao/charts/openbao/templates/csi-clusterrolebinding.yaml new file mode 100644 index 00000000..3c7847af --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/csi-clusterrolebinding.yaml @@ -0,0 +1,24 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{- template "openbao.csiEnabled" . -}} +{{- if .csiEnabled -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ template "openbao.fullname" . }}-csi-provider-clusterrolebinding + labels: + app.kubernetes.io/name: {{ include "openbao.name" . }}-csi-provider + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ template "openbao.fullname" . }}-csi-provider-clusterrole +subjects: +- kind: ServiceAccount + name: {{ template "openbao.fullname" . }}-csi-provider + namespace: {{ include "openbao.namespace" . }} +{{- end }} diff --git a/packages/system/openbao/charts/openbao/templates/csi-daemonset.yaml b/packages/system/openbao/charts/openbao/templates/csi-daemonset.yaml new file mode 100644 index 00000000..73168ed0 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/csi-daemonset.yaml @@ -0,0 +1,157 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{- template "openbao.csiEnabled" . -}} +{{- if .csiEnabled -}} +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: {{ template "openbao.fullname" . }}-csi-provider + namespace: {{ include "openbao.namespace" . }} + labels: + app.kubernetes.io/name: {{ include "openbao.name" . }}-csi-provider + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + {{- if .Values.csi.daemonSet.extraLabels -}} + {{- toYaml .Values.csi.daemonSet.extraLabels | nindent 4 -}} + {{- end -}} + {{ template "csi.daemonSet.annotations" . }} +spec: + updateStrategy: + type: {{ .Values.csi.daemonSet.updateStrategy.type }} + {{- if .Values.csi.daemonSet.updateStrategy.maxUnavailable }} + rollingUpdate: + maxUnavailable: {{ .Values.csi.daemonSet.updateStrategy.maxUnavailable }} + {{- end }} + selector: + matchLabels: + app.kubernetes.io/name: {{ include "openbao.name" . }}-csi-provider + app.kubernetes.io/instance: {{ .Release.Name }} + template: + metadata: + labels: + app.kubernetes.io/name: {{ template "openbao.name" . }}-csi-provider + app.kubernetes.io/instance: {{ .Release.Name }} + {{- if .Values.csi.pod.extraLabels -}} + {{- toYaml .Values.csi.pod.extraLabels | nindent 8 -}} + {{- end -}} + {{ template "csi.pod.annotations" . }} + spec: + {{ template "csi.daemonSet.securityContext.pod" . }} + {{- if .Values.csi.priorityClassName }} + priorityClassName: {{ .Values.csi.priorityClassName }} + {{- end }} + serviceAccountName: {{ template "openbao.fullname" . }}-csi-provider + {{- template "csi.pod.tolerations" . }} + {{- template "csi.pod.nodeselector" . }} + {{- template "csi.pod.affinity" . }} + containers: + - name: {{ include "openbao.name" . }}-csi-provider + {{ template "csi.resources" . }} + {{ template "csi.daemonSet.securityContext.container" . }} + image: "{{ .Values.csi.image.registry | default "docker.io" }}/{{ .Values.csi.image.repository }}:{{ .Values.csi.image.tag }}" + imagePullPolicy: {{ .Values.csi.image.pullPolicy }} + args: + - --endpoint={{ .Values.csi.daemonSet.endpoint }} + - --debug={{ .Values.csi.debug }} + {{- if .Values.csi.hmacSecretName }} + - --hmac-secret-name={{ .Values.csi.hmacSecretName }} + {{- else }} + - --hmac-secret-name={{- include "openbao.name" . }}-csi-provider-hmac-key + {{- end }} + {{- if .Values.csi.extraArgs }} + {{- toYaml .Values.csi.extraArgs | nindent 12 }} + {{- end }} + env: + - name: VAULT_ADDR + {{- if eq (.Values.csi.agent.enabled | toString) "true" }} + value: "unix:///var/run/vault/agent.sock" + {{- else if include "openbao.externalAddr" . }} + value: "{{ include "openbao.externalAddr" . }}" + {{- else }} + value: {{ include "openbao.scheme" . }}://{{ template "openbao.fullname" . }}.{{ include "openbao.namespace" . }}.svc:{{ .Values.server.service.port }} + {{- end }} + volumeMounts: + - name: providervol + mountPath: "/provider" + {{- if eq (.Values.csi.agent.enabled | toString) "true" }} + - name: agent-unix-socket + mountPath: /var/run/vault + {{- end }} + {{- if .Values.csi.volumeMounts }} + {{- toYaml .Values.csi.volumeMounts | nindent 12}} + {{- end }} + livenessProbe: + httpGet: + path: /health/ready + port: 8080 + failureThreshold: {{ .Values.csi.livenessProbe.failureThreshold }} + initialDelaySeconds: {{ .Values.csi.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.csi.livenessProbe.periodSeconds }} + successThreshold: {{ .Values.csi.livenessProbe.successThreshold }} + timeoutSeconds: {{ .Values.csi.livenessProbe.timeoutSeconds }} + readinessProbe: + httpGet: + path: /health/ready + port: 8080 + failureThreshold: {{ .Values.csi.readinessProbe.failureThreshold }} + initialDelaySeconds: {{ .Values.csi.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.csi.readinessProbe.periodSeconds }} + successThreshold: {{ .Values.csi.readinessProbe.successThreshold }} + timeoutSeconds: {{ .Values.csi.readinessProbe.timeoutSeconds }} + {{- if eq (.Values.csi.agent.enabled | toString) "true" }} + - name: {{ include "openbao.name" . }}-agent + image: "{{ .Values.csi.agent.image.registry | default "docker.io" }}/{{ .Values.csi.agent.image.repository }}:{{ .Values.csi.agent.image.tag | default (trimPrefix "v" .Chart.AppVersion) }}" + imagePullPolicy: {{ .Values.csi.agent.image.pullPolicy }} + {{ template "csi.agent.resources" . }} + command: + - bao + args: + - agent + - -config=/etc/vault/config.hcl + {{- if .Values.csi.agent.extraArgs }} + {{- toYaml .Values.csi.agent.extraArgs | nindent 12 }} + {{- end }} + ports: + - containerPort: 8200 + env: + - name: BAO_LOG_LEVEL + value: "{{ .Values.csi.agent.logLevel }}" + - name: BAO_LOG_FORMAT + value: "{{ .Values.csi.agent.logFormat }}" + securityContext: + runAsNonRoot: true + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsUser: 100 + runAsGroup: 1000 + volumeMounts: + - name: agent-config + mountPath: /etc/vault/config.hcl + subPath: config.hcl + readOnly: true + - name: agent-unix-socket + mountPath: /var/run/vault + {{- if .Values.csi.volumeMounts }} + {{- toYaml .Values.csi.volumeMounts | nindent 12 }} + {{- end }} + {{- end }} + volumes: + - name: providervol + hostPath: + path: {{ .Values.csi.daemonSet.providersDir }} + {{- if eq (.Values.csi.agent.enabled | toString) "true" }} + - name: agent-config + configMap: + name: {{ template "openbao.fullname" . }}-csi-provider-agent-config + - name: agent-unix-socket + emptyDir: + medium: Memory + {{- end }} + {{- if .Values.csi.volumes }} + {{- toYaml .Values.csi.volumes | nindent 8}} + {{- end }} + {{- include "imagePullSecrets" . | nindent 6 }} +{{- end }} diff --git a/packages/system/openbao/charts/openbao/templates/csi-role.yaml b/packages/system/openbao/charts/openbao/templates/csi-role.yaml new file mode 100644 index 00000000..a7554a65 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/csi-role.yaml @@ -0,0 +1,32 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{- template "openbao.csiEnabled" . -}} +{{- if .csiEnabled -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ template "openbao.fullname" . }}-csi-provider-role + namespace: {{ include "openbao.namespace" . }} + labels: + app.kubernetes.io/name: {{ include "openbao.name" . }}-csi-provider + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +rules: +- apiGroups: [""] + resources: ["secrets"] + verbs: ["get"] + resourceNames: + {{- if .Values.csi.hmacSecretName }} + - {{ .Values.csi.hmacSecretName }} + {{- else }} + - {{ include "openbao.name" . }}-csi-provider-hmac-key + {{- end }} +# 'create' permissions cannot be restricted by resource name: +# https://kubernetes.io/docs/reference/access-authn-authz/rbac/#referring-to-resources +- apiGroups: [""] + resources: ["secrets"] + verbs: ["create"] +{{- end }} diff --git a/packages/system/openbao/charts/openbao/templates/csi-rolebinding.yaml b/packages/system/openbao/charts/openbao/templates/csi-rolebinding.yaml new file mode 100644 index 00000000..c46096e1 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/csi-rolebinding.yaml @@ -0,0 +1,25 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{- template "openbao.csiEnabled" . -}} +{{- if .csiEnabled -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ template "openbao.fullname" . }}-csi-provider-rolebinding + namespace: {{ include "openbao.namespace" . }} + labels: + app.kubernetes.io/name: {{ include "openbao.name" . }}-csi-provider + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ template "openbao.fullname" . }}-csi-provider-role +subjects: +- kind: ServiceAccount + name: {{ template "openbao.fullname" . }}-csi-provider + namespace: {{ include "openbao.namespace" . }} +{{- end }} diff --git a/packages/system/openbao/charts/openbao/templates/csi-serviceaccount.yaml b/packages/system/openbao/charts/openbao/templates/csi-serviceaccount.yaml new file mode 100644 index 00000000..2f5d346b --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/csi-serviceaccount.yaml @@ -0,0 +1,21 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{- template "openbao.csiEnabled" . -}} +{{- if .csiEnabled -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "openbao.fullname" . }}-csi-provider + namespace: {{ include "openbao.namespace" . }} + labels: + app.kubernetes.io/name: {{ include "openbao.name" . }}-csi-provider + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + {{- if .Values.csi.serviceAccount.extraLabels -}} + {{- toYaml .Values.csi.serviceAccount.extraLabels | nindent 4 -}} + {{- end -}} + {{ template "csi.serviceAccount.annotations" . }} +{{- end }} diff --git a/packages/system/openbao/charts/openbao/templates/extra-objects.yaml b/packages/system/openbao/charts/openbao/templates/extra-objects.yaml new file mode 100644 index 00000000..b408fb07 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/extra-objects.yaml @@ -0,0 +1,8 @@ +{{- range .Values.extraObjects }} +--- +{{- if typeIs "string" . }} +{{ tpl . $ }} +{{- else }} +{{ tpl (. | toYaml) $ }} +{{- end }} +{{- end }} diff --git a/packages/system/openbao/charts/openbao/templates/grafana/configmap-dashboard.yaml b/packages/system/openbao/charts/openbao/templates/grafana/configmap-dashboard.yaml new file mode 100644 index 00000000..970369f0 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/grafana/configmap-dashboard.yaml @@ -0,0 +1,30 @@ +{{- if .Values.serverTelemetry.grafanaDashboard.enabled }} +{{- $files := .Files.Glob "grafana/dashboards/*.json" }} +{{- if $files }} +{{- range $path, $fileContents := $files }} +{{- $dashboardName := regexReplaceAll "(^.*/)(.*)\\.json$" $path "${2}" }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ printf "%s-%s" (include "openbao.fullname" $) $dashboardName | trunc 63 | trimSuffix "-" }} + namespace: {{ include "openbao.namespace" $ }} + labels: + app.kubernetes.io/name: {{ include "openbao.name" $ }}-grafana-dashboard + app.kubernetes.io/instance: {{ $.Release.Name }} + app.kubernetes.io/managed-by: {{ $.Release.Service }} + {{- if $.Values.serverTelemetry.grafanaDashboard.defaultLabel }} + grafana_dashboard: "1" + {{- end }} + {{- if $.Values.serverTelemetry.grafanaDashboard.extraLabels }} + {{- $.Values.serverTelemetry.grafanaDashboard.extraLabels | toYaml | nindent 4 }} + {{- end }} + {{- if $.Values.serverTelemetry.grafanaDashboard.extraAnnotations }} + annotations: + {{- $.Values.serverTelemetry.grafanaDashboard.extraAnnotations | toYaml | nindent 4 }} + {{- end }} +data: + {{ $dashboardName }}.json: {{ $.Files.Get $path | toJson }} +{{- end }} +{{- end }} +{{- end }} diff --git a/packages/system/openbao/charts/openbao/templates/injector-certs-secret.yaml b/packages/system/openbao/charts/openbao/templates/injector-certs-secret.yaml new file mode 100644 index 00000000..b5de48bf --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/injector-certs-secret.yaml @@ -0,0 +1,19 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{- template "openbao.injectorEnabled" . -}} +{{- if .injectorEnabled -}} +{{- if and (eq (.Values.injector.leaderElector.enabled | toString) "true") (gt (.Values.injector.replicas | int) 1) }} +apiVersion: v1 +kind: Secret +metadata: + name: openbao-injector-certs + namespace: {{ include "openbao.namespace" . }} + labels: + app.kubernetes.io/name: {{ include "openbao.name" . }}-agent-injector + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} +{{- end }} diff --git a/packages/system/openbao/charts/openbao/templates/injector-clusterrole.yaml b/packages/system/openbao/charts/openbao/templates/injector-clusterrole.yaml new file mode 100644 index 00000000..10ea35c1 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/injector-clusterrole.yaml @@ -0,0 +1,30 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{- template "openbao.injectorEnabled" . -}} +{{- if .injectorEnabled -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "openbao.fullname" . }}-agent-injector-clusterrole + labels: + app.kubernetes.io/name: {{ include "openbao.name" . }}-agent-injector + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +rules: +- apiGroups: ["admissionregistration.k8s.io"] + resources: ["mutatingwebhookconfigurations"] + verbs: + - "get" + - "list" + - "watch" + - "patch" +{{- if and (eq (.Values.injector.leaderElector.enabled | toString) "true") (gt (.Values.injector.replicas | int) 1) }} +- apiGroups: [""] + resources: ["nodes"] + verbs: + - "get" +{{ end }} +{{ end }} diff --git a/packages/system/openbao/charts/openbao/templates/injector-clusterrolebinding.yaml b/packages/system/openbao/charts/openbao/templates/injector-clusterrolebinding.yaml new file mode 100644 index 00000000..353ee8ac --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/injector-clusterrolebinding.yaml @@ -0,0 +1,24 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{- template "openbao.injectorEnabled" . -}} +{{- if .injectorEnabled -}} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ template "openbao.fullname" . }}-agent-injector-binding + labels: + app.kubernetes.io/name: {{ include "openbao.name" . }}-agent-injector + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ template "openbao.fullname" . }}-agent-injector-clusterrole +subjects: +- kind: ServiceAccount + name: {{ template "openbao.fullname" . }}-agent-injector + namespace: {{ include "openbao.namespace" . }} +{{ end }} diff --git a/packages/system/openbao/charts/openbao/templates/injector-deployment.yaml b/packages/system/openbao/charts/openbao/templates/injector-deployment.yaml new file mode 100644 index 00000000..ac0fc915 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/injector-deployment.yaml @@ -0,0 +1,179 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{- template "openbao.injectorEnabled" . -}} +{{- if .injectorEnabled -}} +# Deployment for the injector +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "openbao.fullname" . }}-agent-injector + namespace: {{ include "openbao.namespace" . }} + labels: + app.kubernetes.io/name: {{ include "openbao.name" . }}-agent-injector + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + component: webhook +spec: + replicas: {{ .Values.injector.replicas }} + selector: + matchLabels: + app.kubernetes.io/name: {{ template "openbao.name" . }}-agent-injector + app.kubernetes.io/instance: {{ .Release.Name }} + component: webhook + {{ template "injector.strategy" . }} + template: + metadata: + labels: + app.kubernetes.io/name: {{ template "openbao.name" . }}-agent-injector + app.kubernetes.io/instance: {{ .Release.Name }} + component: webhook + {{- if .Values.injector.extraLabels -}} + {{- toYaml .Values.injector.extraLabels | nindent 8 -}} + {{- end -}} + {{ template "injector.annotations" . }} + spec: + {{ template "injector.affinity" . }} + {{ template "injector.topologySpreadConstraints" . }} + {{ template "injector.tolerations" . }} + {{ template "injector.nodeselector" . }} + {{- if .Values.injector.priorityClassName }} + priorityClassName: {{ .Values.injector.priorityClassName }} + {{- end }} + serviceAccountName: "{{ template "openbao.fullname" . }}-agent-injector" + {{ template "injector.securityContext.pod" . -}} + {{- if not .Values.global.openshift }} + hostNetwork: {{ .Values.injector.hostNetwork }} + {{- end }} + containers: + - name: sidecar-injector + {{ template "injector.resources" . }} + image: "{{ .Values.injector.image.registry | default "docker.io" }}/{{ .Values.injector.image.repository }}:{{ .Values.injector.image.tag }}" + imagePullPolicy: "{{ .Values.injector.image.pullPolicy }}" + {{- template "injector.securityContext.container" . }} + env: + - name: AGENT_INJECT_LISTEN + value: {{ printf ":%v" .Values.injector.port }} + - name: AGENT_INJECT_LOG_LEVEL + value: {{ .Values.injector.logLevel | default "info" }} + - name: AGENT_INJECT_VAULT_ADDR + {{- if include "openbao.externalAddr" . }} + value: "{{ include "openbao.externalAddr" . }}" + {{- else if .Values.injector.externalVaultAddr }} + value: "{{ .Values.injector.externalVaultAddr }}" + {{- else }} + value: {{ include "openbao.scheme" . }}://{{ template "openbao.fullname" . }}.{{ include "openbao.namespace" . }}.svc:{{ .Values.server.service.port }} + {{- end }} + - name: AGENT_INJECT_VAULT_AUTH_PATH + value: {{ .Values.injector.authPath }} + - name: AGENT_INJECT_VAULT_IMAGE + value: "{{ .Values.injector.agentImage.registry | default "quay.io" }}/{{ .Values.injector.agentImage.repository }}:{{ .Values.injector.agentImage.tag | default (trimPrefix "v" .Chart.AppVersion) }}" + {{- if .Values.injector.certs.secretName }} + - name: AGENT_INJECT_TLS_CERT_FILE + value: "/etc/webhook/certs/{{ .Values.injector.certs.certName }}" + - name: AGENT_INJECT_TLS_KEY_FILE + value: "/etc/webhook/certs/{{ .Values.injector.certs.keyName }}" + {{- else }} + - name: AGENT_INJECT_TLS_AUTO + value: {{ template "openbao.fullname" . }}-agent-injector-cfg + - name: AGENT_INJECT_TLS_AUTO_HOSTS + value: {{ template "openbao.fullname" . }}-agent-injector-svc,{{ template "openbao.fullname" . }}-agent-injector-svc.{{ include "openbao.namespace" . }},{{ template "openbao.fullname" . }}-agent-injector-svc.{{ include "openbao.namespace" . }}.svc + {{- end }} + - name: AGENT_INJECT_LOG_FORMAT + value: {{ .Values.injector.logFormat | default "standard" }} + - name: AGENT_INJECT_REVOKE_ON_SHUTDOWN + value: "{{ .Values.injector.revokeOnShutdown | default false }}" + {{- if .Values.global.openshift }} + - name: AGENT_INJECT_SET_SECURITY_CONTEXT + value: "false" + {{- end }} + {{- if .Values.injector.metrics.enabled }} + - name: AGENT_INJECT_TELEMETRY_PATH + value: "/metrics" + {{- end }} + {{- if and (eq (.Values.injector.leaderElector.enabled | toString) "true") (gt (.Values.injector.replicas | int) 1) }} + - name: AGENT_INJECT_USE_LEADER_ELECTOR + value: "true" + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + {{- end }} + - name: AGENT_INJECT_CPU_REQUEST + value: "{{ .Values.injector.agentDefaults.cpuRequest }}" + - name: AGENT_INJECT_CPU_LIMIT + value: "{{ .Values.injector.agentDefaults.cpuLimit }}" + - name: AGENT_INJECT_MEM_REQUEST + value: "{{ .Values.injector.agentDefaults.memRequest }}" + - name: AGENT_INJECT_MEM_LIMIT + value: "{{ .Values.injector.agentDefaults.memLimit }}" + {{- if .Values.injector.agentDefaults.ephemeralRequest }} + - name: AGENT_INJECT_EPHEMERAL_REQUEST + value: "{{ .Values.injector.agentDefaults.ephemeralRequest }}" + {{- end }} + {{- if .Values.injector.agentDefaults.ephemeralLimit }} + - name: AGENT_INJECT_EPHEMERAL_LIMIT + value: "{{ .Values.injector.agentDefaults.ephemeralLimit }}" + {{- end }} + - name: AGENT_INJECT_DEFAULT_TEMPLATE + value: "{{ .Values.injector.agentDefaults.template }}" + - name: AGENT_INJECT_TEMPLATE_CONFIG_EXIT_ON_RETRY_FAILURE + value: "{{ .Values.injector.agentDefaults.templateConfig.exitOnRetryFailure }}" + {{- if .Values.injector.agentDefaults.templateConfig.staticSecretRenderInterval }} + - name: AGENT_INJECT_TEMPLATE_STATIC_SECRET_RENDER_INTERVAL + value: "{{ .Values.injector.agentDefaults.templateConfig.staticSecretRenderInterval }}" + {{- end }} + {{- include "openbao.extraEnvironmentVars" .Values.injector | nindent 12 }} + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + args: + - agent-inject + - 2>&1 + livenessProbe: + httpGet: + path: /health/ready + port: {{ .Values.injector.port }} + scheme: HTTPS + failureThreshold: {{ .Values.injector.livenessProbe.failureThreshold }} + initialDelaySeconds: {{ .Values.injector.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.injector.livenessProbe.periodSeconds }} + successThreshold: {{ .Values.injector.livenessProbe.successThreshold }} + timeoutSeconds: {{ .Values.injector.livenessProbe.timeoutSeconds }} + readinessProbe: + httpGet: + path: /health/ready + port: {{ .Values.injector.port }} + scheme: HTTPS + failureThreshold: {{ .Values.injector.readinessProbe.failureThreshold }} + initialDelaySeconds: {{ .Values.injector.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.injector.readinessProbe.periodSeconds }} + successThreshold: {{ .Values.injector.readinessProbe.successThreshold }} + timeoutSeconds: {{ .Values.injector.readinessProbe.timeoutSeconds }} + startupProbe: + httpGet: + path: /health/ready + port: {{ .Values.injector.port }} + scheme: HTTPS + failureThreshold: {{ .Values.injector.startupProbe.failureThreshold }} + initialDelaySeconds: {{ .Values.injector.startupProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.injector.startupProbe.periodSeconds }} + successThreshold: {{ .Values.injector.startupProbe.successThreshold }} + timeoutSeconds: {{ .Values.injector.startupProbe.timeoutSeconds }} +{{- if .Values.injector.certs.secretName }} + volumeMounts: + - name: webhook-certs + mountPath: /etc/webhook/certs + readOnly: true +{{- end }} +{{- if .Values.injector.certs.secretName }} + volumes: + - name: webhook-certs + secret: + secretName: "{{ .Values.injector.certs.secretName }}" +{{- end }} + {{- include "imagePullSecrets" . | nindent 6 }} +{{ end }} diff --git a/packages/system/openbao/charts/openbao/templates/injector-disruptionbudget.yaml b/packages/system/openbao/charts/openbao/templates/injector-disruptionbudget.yaml new file mode 100644 index 00000000..08749bd2 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/injector-disruptionbudget.yaml @@ -0,0 +1,25 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{- if .Values.injector.podDisruptionBudget }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ template "openbao.fullname" . }}-agent-injector + namespace: {{ include "openbao.namespace" . }} + labels: + helm.sh/chart: {{ include "openbao.chart" . }} + app.kubernetes.io/name: {{ include "openbao.name" . }}-agent-injector + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + component: webhook +spec: + selector: + matchLabels: + app.kubernetes.io/name: {{ template "openbao.name" . }}-agent-injector + app.kubernetes.io/instance: {{ .Release.Name }} + component: webhook + {{- toYaml .Values.injector.podDisruptionBudget | nindent 2 }} +{{- end -}} diff --git a/packages/system/openbao/charts/openbao/templates/injector-mutating-webhook.yaml b/packages/system/openbao/charts/openbao/templates/injector-mutating-webhook.yaml new file mode 100644 index 00000000..8ffd2671 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/injector-mutating-webhook.yaml @@ -0,0 +1,44 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{- template "openbao.injectorEnabled" . -}} +{{- if .injectorEnabled -}} +{{- if .Capabilities.APIVersions.Has "admissionregistration.k8s.io/v1" }} +apiVersion: admissionregistration.k8s.io/v1 +{{- else }} +apiVersion: admissionregistration.k8s.io/v1beta1 +{{- end }} +kind: MutatingWebhookConfiguration +metadata: + name: {{ template "openbao.fullname" . }}-agent-injector-cfg + labels: + app.kubernetes.io/name: {{ include "openbao.name" . }}-agent-injector + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + {{- template "injector.webhookAnnotations" . }} +webhooks: + - name: vault.hashicorp.com + failurePolicy: {{ ((.Values.injector.webhook)).failurePolicy | default .Values.injector.failurePolicy }} + matchPolicy: {{ ((.Values.injector.webhook)).matchPolicy | default "Exact" }} + sideEffects: None + timeoutSeconds: {{ ((.Values.injector.webhook)).timeoutSeconds | default "30" }} + admissionReviewVersions: ["v1", "v1beta1"] + clientConfig: + service: + name: {{ template "openbao.fullname" . }}-agent-injector-svc + namespace: {{ include "openbao.namespace" . }} + path: "/mutate" + caBundle: {{ .Values.injector.certs.caBundle | quote }} + rules: + - operations: ["CREATE", "UPDATE"] + apiGroups: [""] + apiVersions: ["v1"] + resources: ["pods"] +{{- if or (.Values.injector.namespaceSelector) (((.Values.injector.webhook)).namespaceSelector) }} + namespaceSelector: +{{ toYaml (((.Values.injector.webhook)).namespaceSelector | default .Values.injector.namespaceSelector) | indent 6}} +{{ end }} +{{- template "injector.objectSelector" . -}} +{{ end }} diff --git a/packages/system/openbao/charts/openbao/templates/injector-network-policy.yaml b/packages/system/openbao/charts/openbao/templates/injector-network-policy.yaml new file mode 100644 index 00000000..68a89754 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/injector-network-policy.yaml @@ -0,0 +1,30 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{- template "openbao.injectorEnabled" . -}} +{{- if .injectorEnabled -}} +{{- if eq (.Values.global.openshift | toString) "true" }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ template "openbao.fullname" . }}-agent-injector + namespace: {{ include "openbao.namespace" . }} + labels: + app.kubernetes.io/name: {{ template "openbao.name" . }}-agent-injector + app.kubernetes.io/instance: {{ .Release.Name }} +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: {{ template "openbao.name" . }}-agent-injector + app.kubernetes.io/instance: {{ .Release.Name }} + component: webhook + ingress: + - from: + - namespaceSelector: {} + ports: + - port: 8080 + protocol: TCP +{{ end }} +{{ end }} diff --git a/packages/system/openbao/charts/openbao/templates/injector-psp-role.yaml b/packages/system/openbao/charts/openbao/templates/injector-psp-role.yaml new file mode 100644 index 00000000..3f42450c --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/injector-psp-role.yaml @@ -0,0 +1,25 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{- template "openbao.injectorEnabled" . -}} +{{- if .injectorEnabled -}} +{{- if eq (.Values.global.psp.enable | toString) "true" }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ template "openbao.fullname" . }}-agent-injector-psp + namespace: {{ include "openbao.namespace" . }} + labels: + app.kubernetes.io/name: {{ include "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +rules: +- apiGroups: ['policy'] + resources: ['podsecuritypolicies'] + verbs: ['use'] + resourceNames: + - {{ template "openbao.fullname" . }}-agent-injector +{{- end }} +{{- end }} diff --git a/packages/system/openbao/charts/openbao/templates/injector-psp-rolebinding.yaml b/packages/system/openbao/charts/openbao/templates/injector-psp-rolebinding.yaml new file mode 100644 index 00000000..62a609c7 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/injector-psp-rolebinding.yaml @@ -0,0 +1,26 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{- template "openbao.injectorEnabled" . -}} +{{- if .injectorEnabled -}} +{{- if eq (.Values.global.psp.enable | toString) "true" }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ template "openbao.fullname" . }}-agent-injector-psp + namespace: {{ include "openbao.namespace" . }} + labels: + app.kubernetes.io/name: {{ include "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +roleRef: + kind: Role + name: {{ template "openbao.fullname" . }}-agent-injector-psp + apiGroup: rbac.authorization.k8s.io +subjects: + - kind: ServiceAccount + name: {{ template "openbao.fullname" . }}-agent-injector +{{- end }} +{{- end }} \ No newline at end of file diff --git a/packages/system/openbao/charts/openbao/templates/injector-psp.yaml b/packages/system/openbao/charts/openbao/templates/injector-psp.yaml new file mode 100644 index 00000000..5c1c58f7 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/injector-psp.yaml @@ -0,0 +1,51 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{- template "openbao.injectorEnabled" . -}} +{{- if .injectorEnabled -}} +{{- if eq (.Values.global.psp.enable | toString) "true" }} +apiVersion: policy/v1beta1 +kind: PodSecurityPolicy +metadata: + name: {{ template "openbao.fullname" . }}-agent-injector + labels: + app.kubernetes.io/name: {{ include "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- template "openbao.psp.annotations" . }} +spec: + privileged: false + # Required to prevent escalations to root. + allowPrivilegeEscalation: false + volumes: + - configMap + - emptyDir + - projected + - secret + - downwardAPI + hostNetwork: false + hostIPC: false + hostPID: false + runAsUser: + # Require the container to run without root privileges. + rule: MustRunAsNonRoot + seLinux: + # This policy assumes the nodes are using AppArmor rather than SELinux. + rule: RunAsAny + supplementalGroups: + rule: MustRunAs + ranges: + # Forbid adding the root group. + - min: 1 + max: 65535 + fsGroup: + rule: MustRunAs + ranges: + # Forbid adding the root group. + - min: 1 + max: 65535 + readOnlyRootFilesystem: false +{{- end }} +{{- end }} \ No newline at end of file diff --git a/packages/system/openbao/charts/openbao/templates/injector-role.yaml b/packages/system/openbao/charts/openbao/templates/injector-role.yaml new file mode 100644 index 00000000..2e29aa7b --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/injector-role.yaml @@ -0,0 +1,34 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{- template "openbao.injectorEnabled" . -}} +{{- if .injectorEnabled -}} +{{- if and (eq (.Values.injector.leaderElector.enabled | toString) "true") (gt (.Values.injector.replicas | int) 1) }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ template "openbao.fullname" . }}-agent-injector-leader-elector-role + namespace: {{ include "openbao.namespace" . }} + labels: + app.kubernetes.io/name: {{ include "openbao.name" . }}-agent-injector + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +rules: + - apiGroups: [""] + resources: ["secrets", "configmaps"] + verbs: + - "create" + - "get" + - "watch" + - "list" + - "update" + - apiGroups: [""] + resources: ["pods"] + verbs: + - "get" + - "patch" + - "delete" +{{- end }} +{{- end }} \ No newline at end of file diff --git a/packages/system/openbao/charts/openbao/templates/injector-rolebinding.yaml b/packages/system/openbao/charts/openbao/templates/injector-rolebinding.yaml new file mode 100644 index 00000000..8e460c4b --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/injector-rolebinding.yaml @@ -0,0 +1,27 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{- template "openbao.injectorEnabled" . -}} +{{- if .injectorEnabled -}} +{{- if and (eq (.Values.injector.leaderElector.enabled | toString) "true") (gt (.Values.injector.replicas | int) 1) }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ template "openbao.fullname" . }}-agent-injector-leader-elector-binding + namespace: {{ include "openbao.namespace" . }} + labels: + app.kubernetes.io/name: {{ include "openbao.name" . }}-agent-injector + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ template "openbao.fullname" . }}-agent-injector-leader-elector-role +subjects: + - kind: ServiceAccount + name: {{ template "openbao.fullname" . }}-agent-injector + namespace: {{ include "openbao.namespace" . }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/packages/system/openbao/charts/openbao/templates/injector-service.yaml b/packages/system/openbao/charts/openbao/templates/injector-service.yaml new file mode 100644 index 00000000..f9db469f --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/injector-service.yaml @@ -0,0 +1,30 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{- template "openbao.injectorEnabled" . -}} +{{- if .injectorEnabled -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "openbao.fullname" . }}-agent-injector-svc + namespace: {{ include "openbao.namespace" . }} + labels: + app.kubernetes.io/name: {{ include "openbao.name" . }}-agent-injector + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + {{- if .Values.injector.service.extraLabels -}} + {{- toYaml .Values.injector.service.extraLabels | nindent 4 -}} + {{- end -}} + {{ template "injector.service.annotations" . }} +spec: + ports: + - name: https + port: 443 + targetPort: {{ .Values.injector.port }} + selector: + app.kubernetes.io/name: {{ include "openbao.name" . }}-agent-injector + app.kubernetes.io/instance: {{ .Release.Name }} + component: webhook +{{- end }} diff --git a/packages/system/openbao/charts/openbao/templates/injector-serviceaccount.yaml b/packages/system/openbao/charts/openbao/templates/injector-serviceaccount.yaml new file mode 100644 index 00000000..a411788c --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/injector-serviceaccount.yaml @@ -0,0 +1,18 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{- template "openbao.injectorEnabled" . -}} +{{- if .injectorEnabled -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "openbao.fullname" . }}-agent-injector + namespace: {{ include "openbao.namespace" . }} + labels: + app.kubernetes.io/name: {{ include "openbao.name" . }}-agent-injector + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + {{ template "injector.serviceAccount.annotations" . }} +{{ end }} diff --git a/packages/system/openbao/charts/openbao/templates/prometheus-prometheusrules.yaml b/packages/system/openbao/charts/openbao/templates/prometheus-prometheusrules.yaml new file mode 100644 index 00000000..d371da37 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/prometheus-prometheusrules.yaml @@ -0,0 +1,32 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{ if and (.Values.serverTelemetry.prometheusRules.rules) + (or (.Values.global.serverTelemetry.prometheusOperator) (.Values.serverTelemetry.prometheusRules.enabled) ) +}} +--- +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: {{ template "openbao.fullname" . }} + namespace: {{ include "openbao.namespace" . }} + labels: + helm.sh/chart: {{ include "openbao.chart" . }} + app.kubernetes.io/name: {{ include "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + {{- /* update the selectors docs in values.yaml whenever the defaults below change. */ -}} + {{- $selectors := .Values.serverTelemetry.prometheusRules.selectors }} + {{- if $selectors }} + {{- toYaml $selectors | nindent 4 }} + {{- else }} + release: prometheus + {{- end }} +spec: + groups: + - name: {{ include "openbao.fullname" . }} + rules: + {{- toYaml .Values.serverTelemetry.prometheusRules.rules | nindent 6 }} +{{- end }} diff --git a/packages/system/openbao/charts/openbao/templates/prometheus-servicemonitor.yaml b/packages/system/openbao/charts/openbao/templates/prometheus-servicemonitor.yaml new file mode 100644 index 00000000..950c6194 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/prometheus-servicemonitor.yaml @@ -0,0 +1,62 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{ template "openbao.mode" . }} +{{ if or (.Values.global.serverTelemetry.prometheusOperator) (.Values.serverTelemetry.serviceMonitor.enabled) }} +--- +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ template "openbao.fullname" . }} + namespace: {{ include "openbao.namespace" . }} + labels: + helm.sh/chart: {{ include "openbao.chart" . }} + app.kubernetes.io/name: {{ include "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + {{- /* update the selectors docs in values.yaml whenever the defaults below change. */ -}} + {{- $selectors := .Values.serverTelemetry.serviceMonitor.selectors }} + {{- if $selectors }} + {{- toYaml $selectors | nindent 4 }} + {{- else }} + release: prometheus + {{- end }} +spec: + {{- if .Values.serverTelemetry.serviceMonitor.scrapeClass }} + scrapeClass: {{ .Values.serverTelemetry.serviceMonitor.scrapeClass }} + {{- end }} + selector: + matchLabels: + app.kubernetes.io/name: {{ template "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + {{- if eq .mode "ha" }} + openbao-active: "true" + {{- else }} + openbao-internal: "true" + {{- end }} + endpoints: + - port: {{ .Values.serverTelemetry.serviceMonitor.port | default (include "openbao.scheme" .) }} + interval: {{ .Values.serverTelemetry.serviceMonitor.interval }} + scrapeTimeout: {{ .Values.serverTelemetry.serviceMonitor.scrapeTimeout }} + scheme: {{ .Values.serverTelemetry.serviceMonitor.scheme | default (include "openbao.scheme" .) | lower }} + path: /v1/sys/metrics + params: + format: + - prometheus + {{- with .Values.serverTelemetry.serviceMonitor.tlsConfig }} + tlsConfig: + {{- toYaml . | nindent 6 }} + {{- else }} + tlsConfig: + insecureSkipVerify: true + {{- end }} + {{- with .Values.serverTelemetry.serviceMonitor.authorization }} + authorization: + {{- toYaml . | nindent 6 }} + {{- end }} + namespaceSelector: + matchNames: + - {{ include "openbao.namespace" . }} +{{ end }} diff --git a/packages/system/openbao/charts/openbao/templates/server-backendtlspolicy.yaml b/packages/system/openbao/charts/openbao/templates/server-backendtlspolicy.yaml new file mode 100644 index 00000000..9b492d26 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/server-backendtlspolicy.yaml @@ -0,0 +1,43 @@ +{{ template "openbao.mode" . }} +{{- if ne .mode "external" }} +{{- if and (not .Values.global.tlsDisable) .Values.server.gateway.tlsPolicy.enabled -}} +{{- $serviceName := include "openbao.fullname" . -}} +{{- template "openbao.serverServiceEnabled" . -}} +{{- if .serverServiceEnabled -}} +{{- if and (eq .mode "ha" ) (eq (.Values.server.gateway.tlsPolicy.activeService | toString) "true") }} +{{- $serviceName = printf "%s-%s" $serviceName "active" -}} +{{- end }} +apiVersion: {{ .Values.server.gateway.tlsPolicy.apiVersion }} +kind: BackendTLSPolicy +metadata: + name: {{ template "openbao.fullname" . }} + namespace: {{ include "openbao.namespace" . }} + labels: + helm.sh/chart: {{ include "openbao.chart" . }} + app.kubernetes.io/name: {{ include "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + {{- with .Values.server.gateway.tlsPolicy.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- template "openbao.gateway.tlsPolicy.annotations" . }} +spec: + targetRefs: + {{- if .Values.server.gateway.tlsPolicy.targetRefs }} + {{- with .Values.server.gateway.tlsPolicy.targetRefs }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- else }} + - group: '' + kind: Service + name: {{ $serviceName }} + sectionName: {{ include "openbao.scheme" . }} + {{- end }} + validation: + {{- with .Values.server.gateway.tlsPolicy.validation }} + {{- toYaml . | nindent 4 }} + {{- end }} + hostname: {{ include "openbao.fullname" . }} +{{- end }} +{{- end }} +{{- end }} diff --git a/packages/system/openbao/charts/openbao/templates/server-clusterrolebinding.yaml b/packages/system/openbao/charts/openbao/templates/server-clusterrolebinding.yaml new file mode 100644 index 00000000..0f851ec1 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/server-clusterrolebinding.yaml @@ -0,0 +1,29 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{ template "openbao.serverAuthDelegator" . }} +{{- if .serverAuthDelegator -}} +{{- if .Capabilities.APIVersions.Has "rbac.authorization.k8s.io/v1" -}} +apiVersion: rbac.authorization.k8s.io/v1 +{{- else }} +apiVersion: rbac.authorization.k8s.io/v1beta1 +{{- end }} +kind: ClusterRoleBinding +metadata: + name: {{ template "openbao.fullname" . }}-server-binding + labels: + helm.sh/chart: {{ include "openbao.chart" . }} + app.kubernetes.io/name: {{ include "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:auth-delegator +subjects: +- kind: ServiceAccount + name: {{ template "openbao.serviceAccount.name" . }} + namespace: {{ include "openbao.namespace" . }} +{{ end }} diff --git a/packages/system/openbao/charts/openbao/templates/server-config-configmap.yaml b/packages/system/openbao/charts/openbao/templates/server-config-configmap.yaml new file mode 100644 index 00000000..57c4b0e6 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/server-config-configmap.yaml @@ -0,0 +1,31 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{ template "openbao.mode" . }} +{{- if ne .mode "external" }} +{{- if .serverEnabled -}} +{{- if ne .mode "dev" -}} +{{ if or (.Values.server.standalone.config) (.Values.server.ha.config) -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "openbao.fullname" . }}-config + namespace: {{ include "openbao.namespace" . }} + labels: + helm.sh/chart: {{ include "openbao.chart" . }} + app.kubernetes.io/name: {{ include "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- if .Values.server.configAnnotation }} + annotations: + vault.hashicorp.com/config-checksum: {{ include "openbao.config" . | sha256sum }} +{{- end }} +data: + extraconfig-from-values.hcl: |- + {{ template "openbao.config" . }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/packages/system/openbao/charts/openbao/templates/server-discovery-role.yaml b/packages/system/openbao/charts/openbao/templates/server-discovery-role.yaml new file mode 100644 index 00000000..082ff996 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/server-discovery-role.yaml @@ -0,0 +1,26 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{ template "openbao.mode" . }} +{{- if .serverEnabled -}} +{{- if eq .mode "ha" }} +{{- if eq (.Values.server.serviceAccount.serviceDiscovery.enabled | toString) "true" }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + namespace: {{ include "openbao.namespace" . }} + name: {{ template "openbao.fullname" . }}-discovery-role + labels: + helm.sh/chart: {{ include "openbao.chart" . }} + app.kubernetes.io/name: {{ include "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +rules: +- apiGroups: [""] + resources: ["pods"] + verbs: ["get", "watch", "list", "update", "patch"] +{{ end }} +{{ end }} +{{ end }} diff --git a/packages/system/openbao/charts/openbao/templates/server-discovery-rolebinding.yaml b/packages/system/openbao/charts/openbao/templates/server-discovery-rolebinding.yaml new file mode 100644 index 00000000..5d3f95e3 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/server-discovery-rolebinding.yaml @@ -0,0 +1,34 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{ template "openbao.mode" . }} +{{- if .serverEnabled -}} +{{- if eq .mode "ha" }} +{{- if eq (.Values.server.serviceAccount.serviceDiscovery.enabled | toString) "true" }} +{{- if .Capabilities.APIVersions.Has "rbac.authorization.k8s.io/v1" -}} +apiVersion: rbac.authorization.k8s.io/v1 +{{- else }} +apiVersion: rbac.authorization.k8s.io/v1beta1 +{{- end }} +kind: RoleBinding +metadata: + name: {{ template "openbao.fullname" . }}-discovery-rolebinding + namespace: {{ include "openbao.namespace" . }} + labels: + helm.sh/chart: {{ include "openbao.chart" . }} + app.kubernetes.io/name: {{ include "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ template "openbao.fullname" . }}-discovery-role +subjects: +- kind: ServiceAccount + name: {{ template "openbao.serviceAccount.name" . }} + namespace: {{ include "openbao.namespace" . }} +{{ end }} +{{ end }} +{{ end }} diff --git a/packages/system/openbao/charts/openbao/templates/server-disruptionbudget.yaml b/packages/system/openbao/charts/openbao/templates/server-disruptionbudget.yaml new file mode 100644 index 00000000..7e6660a1 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/server-disruptionbudget.yaml @@ -0,0 +1,31 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{ template "openbao.mode" . }} +{{- if ne .mode "external" -}} +{{- if .serverEnabled -}} +{{- if and (eq .mode "ha") (eq (.Values.server.ha.disruptionBudget.enabled | toString) "true") -}} +# PodDisruptionBudget to prevent degrading the server cluster through +# voluntary cluster changes. +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ template "openbao.fullname" . }} + namespace: {{ include "openbao.namespace" . }} + labels: + helm.sh/chart: {{ include "openbao.chart" . }} + app.kubernetes.io/name: {{ include "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +spec: + maxUnavailable: {{ template "openbao.pdb.maxUnavailable" . }} + selector: + matchLabels: + app.kubernetes.io/name: {{ include "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + component: server +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/packages/system/openbao/charts/openbao/templates/server-ha-active-service.yaml b/packages/system/openbao/charts/openbao/templates/server-ha-active-service.yaml new file mode 100644 index 00000000..19f5afbd --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/server-ha-active-service.yaml @@ -0,0 +1,68 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{ template "openbao.mode" . }} +{{- if ne .mode "external" }} +{{- template "openbao.serverServiceEnabled" . -}} +{{- if .serverServiceEnabled -}} +{{- if eq .mode "ha" }} +{{- if eq (.Values.server.service.active.enabled | toString) "true" }} +# Service for active OpenBao pod +apiVersion: v1 +kind: Service +metadata: + name: {{ template "openbao.fullname" . }}-active + namespace: {{ include "openbao.namespace" . }} + labels: + helm.sh/chart: {{ include "openbao.chart" . }} + app.kubernetes.io/name: {{ include "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + openbao-active: "true" + {{- if .Values.server.service.active.extraLabels -}} + {{- toYaml .Values.server.service.active.extraLabels | nindent 4 -}} + {{- end -}} +{{- template "openbao.service.active.annotations" . }} +spec: + {{- if .Values.server.service.type}} + type: {{ .Values.server.service.type }} + {{- end}} + {{- if (semverCompare ">= 1.23-0" .Capabilities.KubeVersion.Version) }} + {{- if .Values.server.service.ipFamilyPolicy }} + ipFamilyPolicy: {{ .Values.server.service.ipFamilyPolicy }} + {{- end }} + {{- if .Values.server.service.ipFamilies }} + ipFamilies: {{ .Values.server.service.ipFamilies | toYaml | nindent 2 }} + {{- end }} + {{- end }} + {{- if .Values.server.service.clusterIP }} + clusterIP: {{ .Values.server.service.clusterIP }} + {{- end }} + {{- include "service.externalTrafficPolicy" .Values.server.service }} + publishNotReadyAddresses: {{ .Values.server.service.publishNotReadyAddresses }} + ports: + - name: {{ include "openbao.scheme" . }} + port: {{ .Values.server.service.port }} + targetPort: {{ .Values.server.service.targetPort }} + {{- if and (.Values.server.service.activeNodePort) (eq (.Values.server.service.type | toString) "NodePort") }} + nodePort: {{ .Values.server.service.activeNodePort }} + {{- end }} + - name: https-internal + port: 8201 + targetPort: 8201 + {{- if .Values.server.service.extraPorts -}} + {{ toYaml .Values.server.service.extraPorts | nindent 4}} + {{- end }} + selector: + app.kubernetes.io/name: {{ include "openbao.name" . }} + {{- if eq (.Values.server.service.instanceSelector.enabled | toString) "true" }} + app.kubernetes.io/instance: {{ .Release.Name }} + {{- end }} + component: server + openbao-active: "true" +{{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/packages/system/openbao/charts/openbao/templates/server-ha-standby-service.yaml b/packages/system/openbao/charts/openbao/templates/server-ha-standby-service.yaml new file mode 100644 index 00000000..f9ac4d42 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/server-ha-standby-service.yaml @@ -0,0 +1,67 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{ template "openbao.mode" . }} +{{- if ne .mode "external" }} +{{- template "openbao.serverServiceEnabled" . -}} +{{- if .serverServiceEnabled -}} +{{- if eq .mode "ha" }} +{{- if eq (.Values.server.service.standby.enabled | toString) "true" }} +# Service for standby OpenBao pod +apiVersion: v1 +kind: Service +metadata: + name: {{ template "openbao.fullname" . }}-standby + namespace: {{ include "openbao.namespace" . }} + labels: + helm.sh/chart: {{ include "openbao.chart" . }} + app.kubernetes.io/name: {{ include "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + {{- if .Values.server.service.standby.extraLabels -}} + {{- toYaml .Values.server.service.standby.extraLabels | nindent 4 -}} + {{- end -}} +{{- template "openbao.service.standby.annotations" . }} +spec: + {{- if .Values.server.service.type}} + type: {{ .Values.server.service.type }} + {{- end}} + {{- if (semverCompare ">= 1.23-0" .Capabilities.KubeVersion.Version) }} + {{- if .Values.server.service.ipFamilyPolicy }} + ipFamilyPolicy: {{ .Values.server.service.ipFamilyPolicy }} + {{- end }} + {{- if .Values.server.service.ipFamilies }} + ipFamilies: {{ .Values.server.service.ipFamilies | toYaml | nindent 2 }} + {{- end }} + {{- end }} + {{- if .Values.server.service.clusterIP }} + clusterIP: {{ .Values.server.service.clusterIP }} + {{- end }} + {{- include "service.externalTrafficPolicy" .Values.server.service }} + publishNotReadyAddresses: {{ .Values.server.service.publishNotReadyAddresses }} + ports: + - name: {{ include "openbao.scheme" . }} + port: {{ .Values.server.service.port }} + targetPort: {{ .Values.server.service.targetPort }} + {{- if and (.Values.server.service.standbyNodePort) (eq (.Values.server.service.type | toString) "NodePort") }} + nodePort: {{ .Values.server.service.standbyNodePort }} + {{- end }} + - name: https-internal + port: 8201 + targetPort: 8201 + {{- if .Values.server.service.extraPorts -}} + {{ toYaml .Values.server.service.extraPorts | nindent 4}} + {{- end }} + selector: + app.kubernetes.io/name: {{ include "openbao.name" . }} + {{- if eq (.Values.server.service.instanceSelector.enabled | toString) "true" }} + app.kubernetes.io/instance: {{ .Release.Name }} + {{- end }} + component: server + openbao-active: "false" +{{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/packages/system/openbao/charts/openbao/templates/server-headless-service.yaml b/packages/system/openbao/charts/openbao/templates/server-headless-service.yaml new file mode 100644 index 00000000..b3441c8f --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/server-headless-service.yaml @@ -0,0 +1,46 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{ template "openbao.mode" . }} +{{- if ne .mode "external" }} +{{- template "openbao.serverServiceEnabled" . -}} +{{- if .serverServiceEnabled -}} +# Service for OpenBao cluster +apiVersion: v1 +kind: Service +metadata: + name: {{ template "openbao.fullname" . }}-internal + namespace: {{ include "openbao.namespace" . }} + labels: + helm.sh/chart: {{ include "openbao.chart" . }} + app.kubernetes.io/name: {{ include "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + openbao-internal: "true" +{{ template "openbao.service.annotations" .}} +spec: + {{- if (semverCompare ">= 1.23-0" .Capabilities.KubeVersion.Version) }} + {{- if .Values.server.service.ipFamilyPolicy }} + ipFamilyPolicy: {{ .Values.server.service.ipFamilyPolicy }} + {{- end }} + {{- if .Values.server.service.ipFamilies }} + ipFamilies: {{ .Values.server.service.ipFamilies | toYaml | nindent 2 }} + {{- end }} + {{- end }} + clusterIP: None + publishNotReadyAddresses: true + ports: + - name: "{{ include "openbao.scheme" . }}" + port: {{ .Values.server.service.port }} + targetPort: {{ .Values.server.service.targetPort }} + - name: https-internal + port: 8201 + targetPort: 8201 + selector: + app.kubernetes.io/name: {{ include "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + component: server +{{- end }} +{{- end }} diff --git a/packages/system/openbao/charts/openbao/templates/server-httproute.yaml b/packages/system/openbao/charts/openbao/templates/server-httproute.yaml new file mode 100644 index 00000000..ab840b42 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/server-httproute.yaml @@ -0,0 +1,50 @@ +{{ template "openbao.mode" . }} +{{- if ne .mode "external" }} +{{- if .Values.server.gateway.httpRoute.enabled -}} +{{- $serviceName := include "openbao.fullname" . -}} +{{- template "openbao.serverServiceEnabled" . -}} +{{- if .serverServiceEnabled -}} +{{- if and (eq .mode "ha" ) (eq (.Values.server.gateway.httpRoute.activeService | toString) "true") }} +{{- $serviceName = printf "%s-%s" $serviceName "active" -}} +{{- end }} +{{- $servicePort := .Values.server.service.port -}} +apiVersion: {{ .Values.server.gateway.httpRoute.apiVersion }} +kind: HTTPRoute +metadata: + name: {{ template "openbao.fullname" . }} + namespace: {{ include "openbao.namespace" . }} + labels: + helm.sh/chart: {{ include "openbao.chart" . }} + app.kubernetes.io/name: {{ include "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + {{- with .Values.server.gateway.httpRoute.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- template "openbao.gateway.httpRoute.annotations" . }} +spec: + hostnames: + {{- range .Values.server.gateway.httpRoute.hosts }} + - {{ . | quote }} + {{- end }} + parentRefs: + {{- with .Values.server.gateway.httpRoute.parentRefs }} + {{- toYaml . | nindent 4 }} + {{- end }} + rules: + - backendRefs: + - kind: Service + name: {{ $serviceName }} + port: {{ $servicePort }} + matches: + {{- with .Values.server.gateway.httpRoute.matches.path }} + - path: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.server.gateway.httpRoute.filters }} + filters: + {{- toYaml . | nindent 6 }} + {{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/packages/system/openbao/charts/openbao/templates/server-ingress.yaml b/packages/system/openbao/charts/openbao/templates/server-ingress.yaml new file mode 100644 index 00000000..de33c66a --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/server-ingress.yaml @@ -0,0 +1,67 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{ template "openbao.mode" . }} +{{- if ne .mode "external" }} +{{- if .Values.server.ingress.enabled -}} +{{- $extraPaths := .Values.server.ingress.extraPaths -}} +{{- $serviceName := include "openbao.fullname" . -}} +{{- template "openbao.serverServiceEnabled" . -}} +{{- if .serverServiceEnabled -}} +{{- if and (eq .mode "ha" ) (eq (.Values.server.ingress.activeService | toString) "true") }} +{{- $serviceName = printf "%s-%s" $serviceName "active" -}} +{{- end }} +{{- $servicePort := .Values.server.service.port -}} +{{- $pathType := .Values.server.ingress.pathType -}} +{{- $kubeVersion := .Capabilities.KubeVersion.Version }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ template "openbao.fullname" . }} + namespace: {{ include "openbao.namespace" . }} + labels: + helm.sh/chart: {{ include "openbao.chart" . }} + app.kubernetes.io/name: {{ include "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + {{- with .Values.server.ingress.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- template "openbao.ingress.annotations" . }} +spec: +{{- if .Values.server.ingress.tls }} + tls: + {{- range .Values.server.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} +{{- end }} +{{- if .Values.server.ingress.ingressClassName }} + ingressClassName: {{ .Values.server.ingress.ingressClassName }} +{{- end }} + rules: + {{- range .Values.server.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: +{{ if $extraPaths }} +{{ toYaml $extraPaths | indent 10 }} +{{- end }} + {{- range (.paths | default (list "/")) }} + - path: {{ . }} + pathType: {{ $pathType }} + backend: + service: + name: {{ $serviceName }} + port: + number: {{ $servicePort }} + {{- end }} + {{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/packages/system/openbao/charts/openbao/templates/server-network-policy.yaml b/packages/system/openbao/charts/openbao/templates/server-network-policy.yaml new file mode 100644 index 00000000..0891a508 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/server-network-policy.yaml @@ -0,0 +1,24 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{- if eq (.Values.server.networkPolicy.enabled | toString) "true" }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ template "openbao.fullname" . }} + labels: + app.kubernetes.io/name: {{ template "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: {{ template "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + ingress: {{- toYaml .Values.server.networkPolicy.ingress | nindent 4 }} + {{- if .Values.server.networkPolicy.egress }} + egress: + {{- toYaml .Values.server.networkPolicy.egress | nindent 4 }} + {{ end }} +{{ end }} diff --git a/packages/system/openbao/charts/openbao/templates/server-psp-role.yaml b/packages/system/openbao/charts/openbao/templates/server-psp-role.yaml new file mode 100644 index 00000000..bfb71612 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/server-psp-role.yaml @@ -0,0 +1,25 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{ template "openbao.mode" . }} +{{- if .serverEnabled -}} +{{- if and (ne .mode "") (eq (.Values.global.psp.enable | toString) "true") }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ template "openbao.fullname" . }}-psp + namespace: {{ include "openbao.namespace" . }} + labels: + app.kubernetes.io/name: {{ include "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +rules: +- apiGroups: ['policy'] + resources: ['podsecuritypolicies'] + verbs: ['use'] + resourceNames: + - {{ template "openbao.fullname" . }} +{{- end }} +{{- end }} diff --git a/packages/system/openbao/charts/openbao/templates/server-psp-rolebinding.yaml b/packages/system/openbao/charts/openbao/templates/server-psp-rolebinding.yaml new file mode 100644 index 00000000..7f8bb975 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/server-psp-rolebinding.yaml @@ -0,0 +1,26 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{ template "openbao.mode" . }} +{{- if .serverEnabled -}} +{{- if and (ne .mode "") (eq (.Values.global.psp.enable | toString) "true") }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ template "openbao.fullname" . }}-psp + namespace: {{ include "openbao.namespace" . }} + labels: + app.kubernetes.io/name: {{ include "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +roleRef: + kind: Role + name: {{ template "openbao.fullname" . }}-psp + apiGroup: rbac.authorization.k8s.io +subjects: + - kind: ServiceAccount + name: {{ template "openbao.fullname" . }} +{{- end }} +{{- end }} diff --git a/packages/system/openbao/charts/openbao/templates/server-psp.yaml b/packages/system/openbao/charts/openbao/templates/server-psp.yaml new file mode 100644 index 00000000..d7c396a7 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/server-psp.yaml @@ -0,0 +1,54 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{ template "openbao.mode" . }} +{{- if .serverEnabled -}} +{{- if and (ne .mode "") (eq (.Values.global.psp.enable | toString) "true") }} +apiVersion: policy/v1beta1 +kind: PodSecurityPolicy +metadata: + name: {{ template "openbao.fullname" . }} + labels: + app.kubernetes.io/name: {{ include "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- template "openbao.psp.annotations" . }} +spec: + privileged: false + # Required to prevent escalations to root. + allowPrivilegeEscalation: false + volumes: + - configMap + - emptyDir + - projected + - secret + - downwardAPI + {{- if eq (.Values.server.dataStorage.enabled | toString) "true" }} + - persistentVolumeClaim + {{- end }} + hostNetwork: false + hostIPC: false + hostPID: false + runAsUser: + # Require the container to run without root privileges. + rule: MustRunAsNonRoot + seLinux: + # This policy assumes the nodes are using AppArmor rather than SELinux. + rule: RunAsAny + supplementalGroups: + rule: MustRunAs + ranges: + # Forbid adding the root group. + - min: 1 + max: 65535 + fsGroup: + rule: MustRunAs + ranges: + # Forbid adding the root group. + - min: 1 + max: 65535 + readOnlyRootFilesystem: false +{{- end }} +{{- end }} diff --git a/packages/system/openbao/charts/openbao/templates/server-route.yaml b/packages/system/openbao/charts/openbao/templates/server-route.yaml new file mode 100644 index 00000000..4c350d7d --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/server-route.yaml @@ -0,0 +1,39 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{- if .Values.global.openshift }} +{{- if ne .mode "external" }} +{{- if .Values.server.route.enabled -}} +{{- $serviceName := include "openbao.fullname" . -}} +{{- if and (eq .mode "ha" ) (eq (.Values.server.route.activeService | toString) "true") }} +{{- $serviceName = printf "%s-%s" $serviceName "active" -}} +{{- end }} +kind: Route +apiVersion: route.openshift.io/v1 +metadata: + name: {{ template "openbao.fullname" . }} + namespace: {{ include "openbao.namespace" . }} + labels: + helm.sh/chart: {{ include "openbao.chart" . }} + app.kubernetes.io/name: {{ include "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + {{- with .Values.server.route.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- template "openbao.route.annotations" . }} +spec: + host: {{ .Values.server.route.host }} + to: + kind: Service + name: {{ $serviceName }} + weight: 100 + port: + targetPort: 8200 + tls: + {{- toYaml .Values.server.route.tls | nindent 4 }} +{{- end }} +{{- end }} +{{- end }} diff --git a/packages/system/openbao/charts/openbao/templates/server-service.yaml b/packages/system/openbao/charts/openbao/templates/server-service.yaml new file mode 100644 index 00000000..7f82db8e --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/server-service.yaml @@ -0,0 +1,64 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{ template "openbao.mode" . }} +{{- if ne .mode "external" }} +{{- template "openbao.serverServiceEnabled" . -}} +{{- if .serverServiceEnabled -}} +# Service for OpenBao cluster +apiVersion: v1 +kind: Service +metadata: + name: {{ template "openbao.fullname" . }} + namespace: {{ include "openbao.namespace" . }} + labels: + helm.sh/chart: {{ include "openbao.chart" . }} + app.kubernetes.io/name: {{ include "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + {{- if .Values.server.service.extraLabels -}} + {{- toYaml .Values.server.service.extraLabels | nindent 4 -}} + {{- end -}} +{{ template "openbao.service.annotations" .}} +spec: + {{- if .Values.server.service.type}} + type: {{ .Values.server.service.type }} + {{- end}} + {{- if (semverCompare ">= 1.23-0" .Capabilities.KubeVersion.Version) }} + {{- if .Values.server.service.ipFamilyPolicy }} + ipFamilyPolicy: {{ .Values.server.service.ipFamilyPolicy }} + {{- end }} + {{- if .Values.server.service.ipFamilies }} + ipFamilies: {{ .Values.server.service.ipFamilies | toYaml | nindent 2 }} + {{- end }} + {{- end }} + {{- if .Values.server.service.clusterIP }} + clusterIP: {{ .Values.server.service.clusterIP }} + {{- end }} + {{- include "service.externalTrafficPolicy" .Values.server.service }} + # We want the servers to become available even if they're not ready + # since this DNS is also used for join operations. + publishNotReadyAddresses: {{ .Values.server.service.publishNotReadyAddresses }} + ports: + - name: {{ include "openbao.scheme" . }} + port: {{ .Values.server.service.port }} + targetPort: {{ .Values.server.service.targetPort }} + {{- if and (.Values.server.service.nodePort) (eq (.Values.server.service.type | toString) "NodePort") }} + nodePort: {{ .Values.server.service.nodePort }} + {{- end }} + - name: https-internal + port: 8201 + targetPort: 8201 + {{- if .Values.server.service.extraPorts -}} + {{ toYaml .Values.server.service.extraPorts | nindent 4}} + {{- end }} + selector: + app.kubernetes.io/name: {{ include "openbao.name" . }} + {{- if eq (.Values.server.service.instanceSelector.enabled | toString) "true" }} + app.kubernetes.io/instance: {{ .Release.Name }} + {{- end }} + component: server +{{- end }} +{{- end }} diff --git a/packages/system/openbao/charts/openbao/templates/server-serviceaccount-secret.yaml b/packages/system/openbao/charts/openbao/templates/server-serviceaccount-secret.yaml new file mode 100644 index 00000000..e9ab3575 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/server-serviceaccount-secret.yaml @@ -0,0 +1,21 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{ template "openbao.serverServiceAccountSecretCreationEnabled" . }} +{{- if .serverServiceAccountSecretCreationEnabled -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "openbao.serviceAccount.name" . }}-token + namespace: {{ include "openbao.namespace" . }} + annotations: + kubernetes.io/service-account.name: {{ template "openbao.serviceAccount.name" . }} + labels: + helm.sh/chart: {{ include "openbao.chart" . }} + app.kubernetes.io/name: {{ include "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +type: kubernetes.io/service-account-token +{{ end }} diff --git a/packages/system/openbao/charts/openbao/templates/server-serviceaccount.yaml b/packages/system/openbao/charts/openbao/templates/server-serviceaccount.yaml new file mode 100644 index 00000000..aa615200 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/server-serviceaccount.yaml @@ -0,0 +1,22 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{ template "openbao.serverServiceAccountEnabled" . }} +{{- if .serverServiceAccountEnabled -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "openbao.serviceAccount.name" . }} + namespace: {{ include "openbao.namespace" . }} + labels: + helm.sh/chart: {{ include "openbao.chart" . }} + app.kubernetes.io/name: {{ include "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + {{- if .Values.server.serviceAccount.extraLabels -}} + {{- toYaml .Values.server.serviceAccount.extraLabels | nindent 4 -}} + {{- end -}} + {{ template "openbao.serviceAccount.annotations" . }} +{{ end }} diff --git a/packages/system/openbao/charts/openbao/templates/server-statefulset.yaml b/packages/system/openbao/charts/openbao/templates/server-statefulset.yaml new file mode 100644 index 00000000..1628d139 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/server-statefulset.yaml @@ -0,0 +1,228 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{ template "openbao.mode" . }} +{{- if ne .mode "external" }} +{{- if ne .mode "" }} +{{- if .serverEnabled -}} +# StatefulSet to run the actual openbao server cluster. +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ template "openbao.fullname" . }} + namespace: {{ include "openbao.namespace" . }} + labels: + app.kubernetes.io/name: {{ include "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + {{- template "openbao.statefulSet.annotations" . }} +spec: + serviceName: {{ template "openbao.fullname" . }}-internal + podManagementPolicy: {{ .Values.server.podManagementPolicy }} + replicas: {{ template "openbao.replicas" . }} + updateStrategy: + type: {{ .Values.server.updateStrategyType }} + {{- if and (semverCompare ">= 1.23-0" .Capabilities.KubeVersion.Version) (.Values.server.persistentVolumeClaimRetentionPolicy) }} + persistentVolumeClaimRetentionPolicy: {{ toYaml .Values.server.persistentVolumeClaimRetentionPolicy | nindent 4 }} + {{- end }} + selector: + matchLabels: + app.kubernetes.io/name: {{ template "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + component: server + template: + metadata: + labels: + helm.sh/chart: {{ template "openbao.chart" . }} + app.kubernetes.io/name: {{ template "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + component: server + {{- if .Values.server.extraLabels -}} + {{- toYaml .Values.server.extraLabels | nindent 8 -}} + {{- end -}} + {{ template "openbao.annotations" . }} + spec: + {{ template "openbao.affinity" . }} + {{ template "openbao.topologySpreadConstraints" . }} + {{ template "openbao.tolerations" . }} + {{ template "openbao.nodeselector" . }} + {{- if .Values.server.priorityClassName }} + priorityClassName: {{ .Values.server.priorityClassName }} + {{- end }} + terminationGracePeriodSeconds: {{ .Values.server.terminationGracePeriodSeconds }} + serviceAccountName: {{ template "openbao.serviceAccount.name" . }} + {{ if .Values.server.shareProcessNamespace }} + shareProcessNamespace: true + {{ end }} + {{- template "server.statefulSet.securityContext.pod" . }} + {{- if not .Values.global.openshift }} + hostNetwork: {{ .Values.server.hostNetwork }} + {{- end }} + volumes: + {{ template "openbao.volumes" . }} + - name: home + emptyDir: {} + {{- if .Values.server.hostAliases }} + hostAliases: + {{ toYaml .Values.server.hostAliases | nindent 8}} + {{- end }} + {{- if .Values.server.extraInitContainers }} + initContainers: + {{ toYaml .Values.server.extraInitContainers | nindent 8}} + {{- end }} + containers: + - name: openbao + {{ template "openbao.resources" . }} + image: "{{ .Values.server.image.registry | default "docker.io" }}/{{ .Values.server.image.repository }}:{{ .Values.server.image.tag | default (trimPrefix "v" .Chart.AppVersion) }}" + imagePullPolicy: {{ .Values.server.image.pullPolicy }} + command: + - "/bin/sh" + - "-ec" + args: {{ template "openbao.args" . }} + {{- template "server.statefulSet.securityContext.container" . }} + env: + - name: HOST_IP + valueFrom: + fieldRef: + fieldPath: status.hostIP + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + - name: BAO_K8S_POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: BAO_K8S_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: BAO_ADDR + value: "{{ include "openbao.scheme" . }}://127.0.0.1:8200" + - name: BAO_API_ADDR + {{- if .Values.server.ha.apiAddr }} + value: {{ .Values.server.ha.apiAddr }} + {{- else }} + value: "{{ include "openbao.scheme" . }}://$(POD_IP):8200" + {{- end }} + - name: SKIP_CHOWN + value: "true" + - name: SKIP_SETCAP + value: "true" + - name: HOSTNAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: BAO_CLUSTER_ADDR + {{- if .Values.server.ha.clusterAddr }} + value: {{ .Values.server.ha.clusterAddr | quote }} + {{- else }} + value: "https://$(HOSTNAME).{{ template "openbao.fullname" . }}-internal:8201" + {{- end }} + {{- if and (eq (.Values.server.ha.raft.enabled | toString) "true") (eq (.Values.server.ha.raft.setNodeId | toString) "true") }} + - name: BAO_RAFT_NODE_ID + valueFrom: + fieldRef: + fieldPath: metadata.name + {{- end }} + - name: HOME + value: "/home/openbao" + {{- if .Values.server.logLevel }} + - name: BAO_LOG_LEVEL + value: "{{ .Values.server.logLevel }}" + {{- end }} + {{- if .Values.server.logFormat }} + - name: BAO_LOG_FORMAT + value: "{{ .Values.server.logFormat }}" + {{- end }} + {{ template "openbao.envs" . }} + {{- include "openbao.extraSecretEnvironmentVars" .Values.server | nindent 12 }} + {{- include "openbao.extraEnvironmentVars" .Values.server | nindent 12 }} + volumeMounts: + {{ template "openbao.mounts" . }} + - name: home + mountPath: /home/openbao + ports: + - containerPort: 8200 + name: {{ include "openbao.scheme" . }} + - containerPort: 8201 + name: https-internal + - containerPort: 8202 + name: {{ include "openbao.scheme" . }}-rep + {{- if .Values.server.extraPorts -}} + {{ toYaml .Values.server.extraPorts | nindent 12}} + {{- end }} + {{- if .Values.server.readinessProbe.enabled }} + readinessProbe: + {{- if .Values.server.readinessProbe.path }} + httpGet: + path: {{ .Values.server.readinessProbe.path | quote }} + port: {{ .Values.server.readinessProbe.port }} + scheme: {{ include "openbao.scheme" . | upper }} + {{- else }} + # Check status; unsealed openbao servers return 0 + # The exit code reflects the seal status: + # 0 - unsealed + # 1 - error + # 2 - sealed + exec: + command: ["/bin/sh", "-ec", "bao status -tls-skip-verify"] + {{- end }} + failureThreshold: {{ .Values.server.readinessProbe.failureThreshold }} + initialDelaySeconds: {{ .Values.server.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.server.readinessProbe.periodSeconds }} + successThreshold: {{ .Values.server.readinessProbe.successThreshold }} + timeoutSeconds: {{ .Values.server.readinessProbe.timeoutSeconds }} + {{- end }} + {{- if .Values.server.livenessProbe.enabled }} + livenessProbe: + {{- if .Values.server.livenessProbe.execCommand }} + exec: + command: + {{- range (.Values.server.livenessProbe.execCommand) }} + - {{ . | quote }} + {{- end }} + {{- else }} + httpGet: + path: {{ .Values.server.livenessProbe.path | quote }} + port: {{ .Values.server.livenessProbe.port }} + scheme: {{ include "openbao.scheme" . | upper }} + {{- end }} + failureThreshold: {{ .Values.server.livenessProbe.failureThreshold }} + initialDelaySeconds: {{ .Values.server.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.server.livenessProbe.periodSeconds }} + successThreshold: {{ .Values.server.livenessProbe.successThreshold }} + timeoutSeconds: {{ .Values.server.livenessProbe.timeoutSeconds }} + {{- end }} + lifecycle: + # openbao container doesn't receive SIGTERM from Kubernetes + # and after the grace period ends, Kube sends SIGKILL. This + # causes issues with graceful shutdowns such as deregistering itself + # from Consul (zombie services). + preStop: + exec: + command: [ + "/bin/sh", "-c", + # Adding a sleep here to give the pod eviction a + # chance to propagate, so requests will not be made + # to this pod while it's terminating + "sleep {{ .Values.server.preStopSleepSeconds }} && kill -SIGTERM $(pidof bao)", + ] + {{- if .Values.server.postStart }} + postStart: + exec: + command: + {{- range (.Values.server.postStart) }} + - {{ . | quote }} + {{- end }} + {{- end }} + {{- if .Values.server.extraContainers }} + {{ toYaml .Values.server.extraContainers | nindent 8}} + {{- end }} + {{- include "imagePullSecrets" . | nindent 6 }} + {{ template "openbao.volumeclaims" . }} +{{ end }} +{{ end }} +{{ end }} diff --git a/packages/system/openbao/charts/openbao/templates/server-tlsroute.yaml b/packages/system/openbao/charts/openbao/templates/server-tlsroute.yaml new file mode 100644 index 00000000..14bd0d34 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/server-tlsroute.yaml @@ -0,0 +1,41 @@ +{{ template "openbao.mode" . }} +{{- if ne .mode "external" }} +{{- if .Values.server.gateway.tlsRoute.enabled -}} +{{- $serviceName := include "openbao.fullname" . -}} +{{- template "openbao.serverServiceEnabled" . -}} +{{- if .serverServiceEnabled -}} +{{- if and (eq .mode "ha" ) (eq (.Values.server.gateway.tlsRoute.activeService | toString) "true") }} +{{- $serviceName = printf "%s-%s" $serviceName "active" -}} +{{- end }} +{{- $servicePort := .Values.server.service.port -}} +{{- $kubeVersion := .Capabilities.KubeVersion.Version }} +apiVersion: {{ .Values.server.gateway.tlsRoute.apiVersion }} +kind: TLSRoute +metadata: + name: {{ template "openbao.fullname" . }} + namespace: {{ include "openbao.namespace" . }} + labels: + helm.sh/chart: {{ include "openbao.chart" . }} + app.kubernetes.io/name: {{ include "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + {{- with .Values.server.gateway.tlsRoute.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- template "openbao.gateway.tlsRoute.annotations" . }} +spec: + hostnames: + {{- range .Values.server.gateway.tlsRoute.hosts }} + - {{ . | quote }} + {{- end }} + parentRefs: + {{- with .Values.server.gateway.tlsRoute.parentRefs }} + {{- toYaml . | nindent 4 }} + {{- end }} + rules: + - backendRefs: + - name: {{ $serviceName }} + port: {{ $servicePort }} +{{- end }} +{{- end }} +{{- end }} diff --git a/packages/system/openbao/charts/openbao/templates/snapshotagent-configmap.yaml b/packages/system/openbao/charts/openbao/templates/snapshotagent-configmap.yaml new file mode 100644 index 00000000..60be5505 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/snapshotagent-configmap.yaml @@ -0,0 +1,31 @@ + +--- +{{- if .Values.snapshotAgent.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "openbao.fullname" . }}-snapshot + namespace: {{ include "openbao.namespace" . }} + labels: + helm.sh/chart: {{ include "openbao.chart" . }} + app.kubernetes.io/name: {{ include "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +data: + S3_HOST: {{ .Values.snapshotAgent.config.s3Host }} + S3_BUCKET: {{ .Values.snapshotAgent.config.s3Bucket }} + {{- if .Values.snapshotAgent.config.s3cmdExtraFlag }} + S3CMD_EXTRA_FLAG: {{ .Values.snapshotAgent.config.s3cmdExtraFlag }} + {{- end }} + S3_URI: {{ .Values.snapshotAgent.config.s3Uri }} + S3_EXPIRE_DAYS: {{ .Values.snapshotAgent.config.s3ExpireDays | quote }} + BAO_AUTH_PATH: {{ .Values.snapshotAgent.config.baoAuthPath }} + BAO_ROLE: {{ .Values.snapshotAgent.config.baoRole }} + {{- if include "openbao.externalAddr" . }} + BAO_ADDR: "{{ include "openbao.externalAddr" . }}" + {{- else if and (eq .mode "ha") (eq (.Values.server.service.active.enabled | toString) "true")}} + BAO_ADDR: {{ include "openbao.scheme" . }}://{{ template "openbao.fullname" . }}-active.{{ include "openbao.namespace" . }}.svc:{{ .Values.server.service.port }} + {{- else }} + BAO_ADDR: {{ include "openbao.scheme" . }}://{{ template "openbao.fullname" . }}.{{ include "openbao.namespace" . }}.svc:{{ .Values.server.service.port }} + {{- end }} +{{ end }} diff --git a/packages/system/openbao/charts/openbao/templates/snapshotagent-cronjob.yaml b/packages/system/openbao/charts/openbao/templates/snapshotagent-cronjob.yaml new file mode 100644 index 00000000..e1efadc0 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/snapshotagent-cronjob.yaml @@ -0,0 +1,66 @@ +{{- if .Values.snapshotAgent.enabled }} +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + labels: + app.kubernetes.io/name: {{ include "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + {{- template "openbao.snapshotAgent.annotations" . }} + name: {{ template "openbao.fullname" . }}-snapshot + namespace: {{ include "openbao.namespace" . }} +spec: + schedule: {{ .Values.snapshotAgent.schedule | quote }} + jobTemplate: + metadata: + labels: + app.kubernetes.io/name: {{ include "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + component: snapshot-agent + spec: + template: + metadata: + labels: + app.kubernetes.io/name: {{ include "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + component: snapshot-agent + spec: + restartPolicy: {{ .Values.snapshotAgent.restartPolicy }} + serviceAccountName: {{ template "openbao.snapshotAgent.serviceAccount.name" . }} + {{ template "snapshotAgent.securityContext.pod" .}} + containers: + - name: bao-snapshot + envFrom: + - configMapRef: + name: {{ template "openbao.fullname" . }}-snapshot + env: + - name: AWS_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + key: AWS_SECRET_ACCESS_KEY + name: {{ .Values.snapshotAgent.s3CredentialsSecret }} + - name: AWS_ACCESS_KEY_ID + valueFrom: + secretKeyRef: + key: AWS_ACCESS_KEY_ID + name: {{ .Values.snapshotAgent.s3CredentialsSecret }} + {{- include "openbao.extraSecretEnvironmentVars" .Values.snapshotAgent | nindent 12 }} + {{- include "openbao.extraEnvironmentVars" .Values.snapshotAgent | nindent 12 }} + image: {{ .Values.snapshotAgent.image.repository }}:{{ .Values.snapshotAgent.image.tag }} + {{ template "openbao.snapshotAgent.resources". }} + {{ template "snapshotAgent.securityContext.container" .}} + volumeMounts: + - name: snapshot-dir + mountPath: /bao-snapshots + {{- with .Values.snapshotAgent.extraVolumeMounts }} + {{- toYaml . | nindent 14 }} + {{- end }} + imagePullPolicy: IfNotPresent + volumes: + - name: snapshot-dir + emptyDir: {} + {{- if .Values.snapshotAgent.extraVolumes }} + {{- toYaml .Values.snapshotAgent.extraVolumes | nindent 10 }} + {{- end }} +{{ end }} diff --git a/packages/system/openbao/charts/openbao/templates/snapshotagent-serviceaccount.yaml b/packages/system/openbao/charts/openbao/templates/snapshotagent-serviceaccount.yaml new file mode 100644 index 00000000..25fa3cb9 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/snapshotagent-serviceaccount.yaml @@ -0,0 +1,16 @@ +{{- if and .Values.snapshotAgent.enabled .Values.snapshotAgent.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "openbao.snapshotAgent.serviceAccount.name" . }} + namespace: {{ include "openbao.namespace" . }} + labels: + helm.sh/chart: {{ include "openbao.chart" . }} + app.kubernetes.io/name: {{ include "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + {{- if .Values.snapshotAgent.serviceAccount.extraLabels -}} + {{- toYaml .Values.snapshotAgent.serviceAccount.extraLabels | nindent 4 -}} + {{- end -}} + {{ template "openbao.snapshotAgent.serviceAccount.annotations" . }} +{{ end }} diff --git a/packages/system/openbao/charts/openbao/templates/tests/server-test.yaml b/packages/system/openbao/charts/openbao/templates/tests/server-test.yaml new file mode 100644 index 00000000..8c42752e --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/tests/server-test.yaml @@ -0,0 +1,56 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{ template "openbao.mode" . }} +{{- if ne .mode "external" }} +{{- if .serverEnabled -}} +apiVersion: v1 +kind: Pod +metadata: + name: {{ template "openbao.fullname" . }}-server-test + namespace: {{ include "openbao.namespace" . }} + annotations: + "helm.sh/hook": test +spec: + {{- include "imagePullSecrets" . | nindent 2 }} + containers: + - name: {{ .Release.Name }}-server-test + image: {{ .Values.server.image.registry | default "docker.io" }}/{{ .Values.server.image.repository }}:{{ .Values.server.image.tag | default (trimPrefix "v" .Chart.AppVersion) }} + imagePullPolicy: {{ .Values.server.image.pullPolicy }} + env: + - name: VAULT_ADDR + value: {{ include "openbao.scheme" . }}://{{ template "openbao.fullname" . }}.{{ include "openbao.namespace" . }}.svc:{{ .Values.server.service.port }} + {{- include "openbao.extraEnvironmentVars" .Values.server | nindent 8 }} + command: + - /bin/sh + - -c + - | + echo "Checking for sealed info in 'bao status' output" + ATTEMPTS=10 + n=0 + until [ "$n" -ge $ATTEMPTS ] + do + echo "Attempt" $n... + bao status -format yaml | grep -E '^sealed: (true|false)' && break + n=$((n+1)) + sleep 5 + done + if [ $n -ge $ATTEMPTS ]; then + echo "timed out looking for sealed info in 'bao status' output" + exit 1 + fi + + exit 0 + volumeMounts: + {{- if .Values.server.volumeMounts }} + {{- toYaml .Values.server.volumeMounts | nindent 8}} + {{- end }} + volumes: + {{- if .Values.server.volumes }} + {{- toYaml .Values.server.volumes | nindent 4}} + {{- end }} + restartPolicy: Never +{{- end }} +{{- end }} diff --git a/packages/system/openbao/charts/openbao/templates/ui-service.yaml b/packages/system/openbao/charts/openbao/templates/ui-service.yaml new file mode 100644 index 00000000..17ec0012 --- /dev/null +++ b/packages/system/openbao/charts/openbao/templates/ui-service.yaml @@ -0,0 +1,53 @@ +{{/* +Copyright (c) HashiCorp, Inc. +SPDX-License-Identifier: MPL-2.0 +*/}} + +{{ template "openbao.mode" . }} +{{- if ne .mode "external" }} +{{- template "openbao.uiEnabled" . -}} +{{- if .uiEnabled -}} + +apiVersion: v1 +kind: Service +metadata: + name: {{ template "openbao.fullname" . }}-ui + namespace: {{ include "openbao.namespace" . }} + labels: + helm.sh/chart: {{ include "openbao.chart" . }} + app.kubernetes.io/name: {{ include "openbao.name" . }}-ui + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + {{- if .Values.ui.extraLabels -}} + {{- toYaml .Values.ui.extraLabels | nindent 4 -}} + {{- end -}} + {{- template "openbao.ui.annotations" . }} +spec: + {{- if (semverCompare ">= 1.23-0" .Capabilities.KubeVersion.Version) }} + {{- if .Values.ui.serviceIPFamilyPolicy }} + ipFamilyPolicy: {{ .Values.ui.serviceIPFamilyPolicy }} + {{- end }} + {{- if .Values.ui.serviceIPFamilies }} + ipFamilies: {{ .Values.ui.serviceIPFamilies | toYaml | nindent 2 }} + {{- end }} + {{- end }} + selector: + app.kubernetes.io/name: {{ include "openbao.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + component: server + {{- if and (.Values.ui.activeOpenbaoPodOnly) (eq .mode "ha") }} + openbao-active: "true" + {{- end }} + publishNotReadyAddresses: {{ .Values.ui.publishNotReadyAddresses }} + ports: + - name: {{ include "openbao.scheme" . }} + port: {{ .Values.ui.externalPort }} + targetPort: {{ .Values.ui.targetPort }} + {{- if .Values.ui.serviceNodePort }} + nodePort: {{ .Values.ui.serviceNodePort }} + {{- end }} + type: {{ .Values.ui.serviceType }} + {{- include "service.externalTrafficPolicy" .Values.ui }} + {{- include "service.loadBalancer" .Values.ui }} +{{- end -}} +{{- end }} diff --git a/packages/system/openbao/charts/openbao/values.openshift.yaml b/packages/system/openbao/charts/openbao/values.openshift.yaml new file mode 100644 index 00000000..964a81b0 --- /dev/null +++ b/packages/system/openbao/charts/openbao/values.openshift.yaml @@ -0,0 +1,30 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +# These overrides are appropriate defaults for deploying this chart on OpenShift + +global: + openshift: true + +injector: + image: + repository: "registry.connect.redhat.com/hashicorp/vault-k8s" + tag: "1.3.1-ubi" + + agentImage: + registry: "quay.io" + repository: "openbao/openbao-ubi" + tag: "2.3.2" + # Use UBI-based OpenBao image for better OpenShift compatibility + # See: https://openbao.org/docs/install/#container-registries + +server: + image: + registry: "quay.io" + repository: "openbao/openbao-ubi" + tag: "2.3.2" + # Use UBI-based OpenBao image for better OpenShift compatibility + # See: https://openbao.org/docs/install/#container-registries + + readinessProbe: + path: "/v1/sys/health?uninitcode=204" diff --git a/packages/system/openbao/charts/openbao/values.schema.json b/packages/system/openbao/charts/openbao/values.schema.json new file mode 100644 index 00000000..75298366 --- /dev/null +++ b/packages/system/openbao/charts/openbao/values.schema.json @@ -0,0 +1,1207 @@ +{ + "$schema": "http://json-schema.org/schema#", + "type": "object", + "properties": { + "csi": { + "type": "object", + "properties": { + "agent": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "extraArgs": { + "type": "array" + }, + "image": { + "type": "object", + "properties": { + "pullPolicy": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + }, + "logFormat": { + "type": "string" + }, + "logLevel": { + "type": "string" + }, + "resources": { + "type": "object" + } + } + }, + "daemonSet": { + "type": "object", + "properties": { + "annotations": { + "type": [ + "object", + "string" + ] + }, + "extraLabels": { + "type": "object" + }, + "kubeletRootDir": { + "type": "string" + }, + "providersDir": { + "type": "string" + }, + "securityContext": { + "type": "object", + "properties": { + "container": { + "type": [ + "object", + "string" + ] + }, + "pod": { + "type": [ + "object", + "string" + ] + } + } + }, + "updateStrategy": { + "type": "object", + "properties": { + "maxUnavailable": { + "type": "string" + }, + "type": { + "type": "string" + } + } + } + } + }, + "debug": { + "type": "boolean" + }, + "enabled": { + "type": [ + "boolean", + "string" + ] + }, + "extraArgs": { + "type": "array" + }, + "image": { + "type": "object", + "properties": { + "pullPolicy": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + }, + "livenessProbe": { + "type": "object", + "properties": { + "failureThreshold": { + "type": "integer" + }, + "initialDelaySeconds": { + "type": "integer" + }, + "periodSeconds": { + "type": "integer" + }, + "successThreshold": { + "type": "integer" + }, + "timeoutSeconds": { + "type": "integer" + } + } + }, + "pod": { + "type": "object", + "properties": { + "affinity": { + "type": [ + "null", + "object", + "string" + ] + }, + "annotations": { + "type": [ + "object", + "string" + ] + }, + "extraLabels": { + "type": "object" + }, + "nodeSelector": { + "type": [ + "null", + "object", + "string" + ] + }, + "tolerations": { + "type": [ + "null", + "array", + "string" + ] + } + } + }, + "priorityClassName": { + "type": "string" + }, + "readinessProbe": { + "type": "object", + "properties": { + "failureThreshold": { + "type": "integer" + }, + "initialDelaySeconds": { + "type": "integer" + }, + "periodSeconds": { + "type": "integer" + }, + "successThreshold": { + "type": "integer" + }, + "timeoutSeconds": { + "type": "integer" + } + } + }, + "resources": { + "type": "object" + }, + "serviceAccount": { + "type": "object", + "properties": { + "annotations": { + "type": [ + "object", + "string" + ] + }, + "extraLabels": { + "type": "object" + } + } + }, + "volumeMounts": { + "type": [ + "null", + "array" + ] + }, + "volumes": { + "type": [ + "null", + "array" + ] + } + } + }, + "global": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "namespace": { + "type": "string" + }, + "externalVaultAddr": { + "type": "string" + }, + "externalBaoAddr": { + "type": "string" + }, + "imagePullSecrets": { + "type": "array" + }, + "openshift": { + "type": "boolean" + }, + "psp": { + "type": "object", + "properties": { + "annotations": { + "type": [ + "object", + "string" + ] + }, + "enable": { + "type": "boolean" + } + } + }, + "tlsDisable": { + "type": "boolean" + } + } + }, + "injector": { + "type": "object", + "properties": { + "affinity": { + "type": [ + "object", + "string" + ] + }, + "agentDefaults": { + "type": "object", + "properties": { + "cpuLimit": { + "type": "string" + }, + "cpuRequest": { + "type": "string" + }, + "memLimit": { + "type": "string" + }, + "memRequest": { + "type": "string" + }, + "ephemeralLimit": { + "type": "string" + }, + "ephemeralRequest": { + "type": "string" + }, + "template": { + "type": "string" + }, + "templateConfig": { + "type": "object", + "properties": { + "exitOnRetryFailure": { + "type": "boolean" + }, + "staticSecretRenderInterval": { + "type": "string" + } + } + } + } + }, + "agentImage": { + "type": "object", + "properties": { + "repository": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + }, + "annotations": { + "type": [ + "object", + "string" + ] + }, + "authPath": { + "type": "string" + }, + "certs": { + "type": "object", + "properties": { + "caBundle": { + "type": "string" + }, + "certName": { + "type": "string" + }, + "keyName": { + "type": "string" + }, + "secretName": { + "type": [ + "null", + "string" + ] + } + } + }, + "enabled": { + "type": [ + "boolean", + "string" + ] + }, + "externalVaultAddr": { + "type": "string" + }, + "extraEnvironmentVars": { + "type": "object" + }, + "extraLabels": { + "type": "object" + }, + "failurePolicy": { + "type": "string" + }, + "hostNetwork": { + "type": "boolean" + }, + "image": { + "type": "object", + "properties": { + "pullPolicy": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + }, + "leaderElector": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + } + }, + "logFormat": { + "type": "string" + }, + "logLevel": { + "type": "string" + }, + "metrics": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + } + }, + "namespaceSelector": { + "type": "object" + }, + "nodeSelector": { + "type": [ + "null", + "object", + "string" + ] + }, + "objectSelector": { + "type": [ + "object", + "string" + ] + }, + "podDisruptionBudget": { + "type": "object" + }, + "port": { + "type": "integer" + }, + "priorityClassName": { + "type": "string" + }, + "replicas": { + "type": "integer" + }, + "resources": { + "type": "object" + }, + "revokeOnShutdown": { + "type": "boolean" + }, + "securityContext": { + "type": "object", + "properties": { + "container": { + "type": [ + "object", + "string" + ] + }, + "pod": { + "type": [ + "object", + "string" + ] + } + } + }, + "service": { + "type": "object", + "properties": { + "annotations": { + "type": [ + "object", + "string" + ] + } + } + }, + "serviceAccount": { + "type": "object", + "properties": { + "annotations": { + "type": [ + "object", + "string" + ] + } + } + }, + "strategy": { + "type": [ + "object", + "string" + ] + }, + "tolerations": { + "type": [ + "null", + "array", + "string" + ] + }, + "topologySpreadConstraints": { + "type": [ + "null", + "array", + "string" + ] + }, + "webhook": { + "type": "object", + "properties": { + "annotations": { + "type": [ + "object", + "string" + ] + }, + "failurePolicy": { + "type": "string" + }, + "matchPolicy": { + "type": "string" + }, + "namespaceSelector": { + "type": "object" + }, + "objectSelector": { + "type": [ + "object", + "string" + ] + }, + "timeoutSeconds": { + "type": "integer" + } + } + }, + "webhookAnnotations": { + "type": [ + "object", + "string" + ] + } + } + }, + "server": { + "type": "object", + "properties": { + "affinity": { + "type": [ + "object", + "string" + ] + }, + "annotations": { + "type": [ + "object", + "string" + ] + }, + "auditStorage": { + "type": "object", + "properties": { + "accessMode": { + "type": "string" + }, + "annotations": { + "type": [ + "object", + "string" + ] + }, + "labels": { + "type": [ + "object", + "string" + ] + }, + "enabled": { + "type": [ + "boolean", + "string" + ] + }, + "mountPath": { + "type": "string" + }, + "size": { + "type": "string" + }, + "storageClass": { + "type": [ + "null", + "string" + ] + } + } + }, + "authDelegator": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + } + }, + "dataStorage": { + "type": "object", + "properties": { + "accessMode": { + "type": "string" + }, + "annotations": { + "type": [ + "object", + "string" + ] + }, + "labels": { + "type": [ + "object", + "string" + ] + }, + "enabled": { + "type": [ + "boolean", + "string" + ] + }, + "mountPath": { + "type": "string" + }, + "size": { + "type": "string" + }, + "storageClass": { + "type": [ + "null", + "string" + ] + } + } + }, + "persistentVolumeClaimRetentionPolicy": { + "type": "object", + "properties": { + "whenDeleted": { + "type": "string" + }, + "whenScaled": { + "type": "string" + } + } + }, + "dev": { + "type": "object", + "properties": { + "devRootToken": { + "type": "string" + }, + "enabled": { + "type": "boolean" + } + } + }, + "enabled": { + "type": [ + "boolean", + "string" + ] + }, + "extraArgs": { + "type": "string" + }, + "extraPorts": { + "type": [ + "null", + "array" + ] + }, + "extraContainers": { + "type": [ + "null", + "array" + ] + }, + "extraEnvironmentVars": { + "type": "object" + }, + "extraInitContainers": { + "type": [ + "null", + "array" + ] + }, + "extraLabels": { + "type": "object" + }, + "extraSecretEnvironmentVars": { + "type": "array" + }, + "extraVolumes": { + "type": "array" + }, + "ha": { + "type": "object", + "properties": { + "apiAddr": { + "type": [ + "null", + "string" + ] + }, + "clusterAddr": { + "type": [ + "null", + "string" + ] + }, + "config": { + "type": [ + "string", + "object" + ] + }, + "disruptionBudget": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "maxUnavailable": { + "type": [ + "null", + "integer" + ] + } + } + }, + "enabled": { + "type": "boolean" + }, + "raft": { + "type": "object", + "properties": { + "config": { + "type": [ + "string", + "object" + ] + }, + "enabled": { + "type": "boolean" + }, + "setNodeId": { + "type": "boolean" + } + } + }, + "replicas": { + "type": "integer" + } + } + }, + "hostAliases": { + "type": "array" + }, + "image": { + "type": "object", + "properties": { + "pullPolicy": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + }, + "ingress": { + "type": "object", + "properties": { + "activeService": { + "type": "boolean" + }, + "annotations": { + "type": [ + "object", + "string" + ] + }, + "enabled": { + "type": "boolean" + }, + "extraPaths": { + "type": "array" + }, + "hosts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "host": { + "type": "string" + }, + "paths": { + "type": "array" + } + } + } + }, + "ingressClassName": { + "type": "string" + }, + "labels": { + "type": "object" + }, + "pathType": { + "type": "string" + }, + "tls": { + "type": "array" + } + } + }, + "livenessProbe": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "failureThreshold": { + "type": "integer" + }, + "initialDelaySeconds": { + "type": "integer" + }, + "path": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "execCommand": { + "type": "array" + }, + "periodSeconds": { + "type": "integer" + }, + "successThreshold": { + "type": "integer" + }, + "timeoutSeconds": { + "type": "integer" + } + } + }, + "logFormat": { + "type": "string" + }, + "logLevel": { + "type": "string" + }, + "networkPolicy": { + "type": "object", + "properties": { + "egress": { + "type": "array" + }, + "enabled": { + "type": "boolean" + }, + "ingress": { + "type": "array" + } + } + }, + "nodeSelector": { + "type": [ + "null", + "object", + "string" + ] + }, + "postStart": { + "type": "array" + }, + "preStopSleepSeconds": { + "type": "integer" + }, + "priorityClassName": { + "type": "string" + }, + "readinessProbe": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "failureThreshold": { + "type": "integer" + }, + "initialDelaySeconds": { + "type": "integer" + }, + "periodSeconds": { + "type": "integer" + }, + "successThreshold": { + "type": "integer" + }, + "timeoutSeconds": { + "type": "integer" + } + } + }, + "resources": { + "type": "object" + }, + "route": { + "type": "object", + "properties": { + "activeService": { + "type": "boolean" + }, + "annotations": { + "type": [ + "object", + "string" + ] + }, + "enabled": { + "type": "boolean" + }, + "host": { + "type": "string" + }, + "labels": { + "type": "object" + }, + "tls": { + "type": "object" + } + } + }, + "service": { + "type": "object", + "properties": { + "active": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "annotations": { + "type": [ + "object", + "string" + ] + } + } + }, + "annotations": { + "type": [ + "object", + "string" + ] + }, + "enabled": { + "type": "boolean" + }, + "externalTrafficPolicy": { + "type": "string" + }, + "instanceSelector": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + } + }, + "port": { + "type": "integer" + }, + "publishNotReadyAddresses": { + "type": "boolean" + }, + "standby": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "annotations": { + "type": [ + "object", + "string" + ] + } + } + }, + "targetPort": { + "type": "integer" + }, + "nodePort": { + "type": "integer" + }, + "activeNodePort": { + "type": "integer" + }, + "standbyNodePort": { + "type": "integer" + }, + "ipFamilyPolicy": { + "type": "string" + }, + "ipFamilies": { + "type": [ + "array" + ] + } + } + }, + "serviceAccount": { + "type": "object", + "properties": { + "annotations": { + "type": [ + "object", + "string" + ] + }, + "create": { + "type": "boolean" + }, + "extraLabels": { + "type": "object" + }, + "createSecret": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "serviceDiscovery": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + } + } + } + } + }, + "shareProcessNamespace": { + "type": "boolean" + }, + "standalone": { + "type": "object", + "properties": { + "config": { + "type": [ + "string", + "object" + ] + }, + "enabled": { + "type": [ + "string", + "boolean" + ] + } + } + }, + "statefulSet": { + "type": "object", + "properties": { + "annotations": { + "type": [ + "object", + "string" + ] + }, + "securityContext": { + "type": "object", + "properties": { + "container": { + "type": [ + "object", + "string" + ] + }, + "pod": { + "type": [ + "object", + "string" + ] + } + } + } + } + }, + "terminationGracePeriodSeconds": { + "type": "integer" + }, + "tolerations": { + "type": [ + "null", + "array", + "string" + ] + }, + "topologySpreadConstraints": { + "type": [ + "null", + "array", + "string" + ] + }, + "updateStrategyType": { + "type": "string" + }, + "volumeMounts": { + "type": [ + "null", + "array" + ] + }, + "volumes": { + "type": [ + "null", + "array" + ] + }, + "hostNetwork": { + "type": "boolean" + } + } + }, + "serverTelemetry": { + "type": "object", + "properties": { + "prometheusRules": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "rules": { + "type": "array" + }, + "selectors": { + "type": "object" + } + } + } + } + }, + "ui": { + "type": "object", + "properties": { + "activeOpenbaoPodOnly": { + "type": "boolean" + }, + "annotations": { + "type": [ + "object", + "string" + ] + }, + "enabled": { + "type": [ + "boolean", + "string" + ] + }, + "externalPort": { + "type": "integer" + }, + "externalTrafficPolicy": { + "type": "string" + }, + "publishNotReadyAddresses": { + "type": "boolean" + }, + "serviceNodePort": { + "type": [ + "null", + "integer" + ] + }, + "serviceType": { + "type": "string" + }, + "targetPort": { + "type": "integer" + }, + "serviceIPFamilyPolicy": { + "type": [ + "string" + ] + }, + "serviceIPFamilies": { + "type": [ + "array" + ] + } + } + } + } +} diff --git a/packages/system/openbao/charts/openbao/values.yaml b/packages/system/openbao/charts/openbao/values.yaml new file mode 100644 index 00000000..7e2f604f --- /dev/null +++ b/packages/system/openbao/charts/openbao/values.yaml @@ -0,0 +1,1583 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +# Available parameters and their default values for the OpenBao chart. + +global: + # -- enabled is the master enabled switch. Setting this to true or false + # will enable or disable all the components within this chart by default. + enabled: true + + # -- The namespace to deploy to. Defaults to the `helm` installation namespace. + namespace: "" + + # -- Image pull secret to use for registry authentication. + # Alternatively, the value may be specified as an array of strings. + imagePullSecrets: [] + # imagePullSecrets: + # - name: image-pull-secret + + # -- TLS for end-to-end encrypted transport + tlsDisable: true + + # -- External openbao server address for the injector and CSI provider to use. + # Setting this will disable deployment of a openbao server. + externalBaoAddr: "" + # -- Deprecated: Please use global.externalBaoAddr instead. + externalVaultAddr: "" + + # -- If deploying to OpenShift + openshift: false + + # -- Create PodSecurityPolicy for pods + psp: + enable: false + # -- Annotation for PodSecurityPolicy. + # This is a multi-line templated string map, and can also be set as YAML. + annotations: | + seccomp.security.alpha.kubernetes.io/allowedProfileNames: docker/default,runtime/default + apparmor.security.beta.kubernetes.io/allowedProfileNames: runtime/default + seccomp.security.alpha.kubernetes.io/defaultProfileName: runtime/default + apparmor.security.beta.kubernetes.io/defaultProfileName: runtime/default + + serverTelemetry: + # -- Enable integration with the Prometheus Operator + # See the top level serverTelemetry section below before enabling this feature. + prometheusOperator: false + +injector: + # -- True if you want to enable openbao agent injection. @default: global.enabled + enabled: "-" + + replicas: 1 + + # -- Configures the port the injector should listen on + port: 8080 + + # -- If multiple replicas are specified, by default a leader will be determined + # so that only one injector attempts to create TLS certificates. + leaderElector: + enabled: true + + # -- If true, will enable a node exporter metrics endpoint at /metrics. + metrics: + enabled: false + + # -- Deprecated: Please use global.externalBaoAddr instead. + externalVaultAddr: "" + + # image sets the repo and tag of the vault-k8s image to use for the injector. + image: + # -- image registry to use for k8s image + registry: "docker.io" + # -- image repo to use for k8s image + repository: "hashicorp/vault-k8s" + # -- image tag to use for k8s image + tag: "1.7.2" + # -- image pull policy to use for k8s image. if tag is "latest", set to "Always" + pullPolicy: IfNotPresent + + # -- agentImage sets the repo and tag of the OpenBao image to use for the OpenBao Agent + # containers. This should be set to the official OpenBao image. OpenBao 1.3.1+ is + # required. + agentImage: + # -- image registry to use for agent image + registry: "quay.io" + # -- image repo to use for agent image + repository: "openbao/openbao" + # -- image tag to use for agent image - defaults to chart appVersion + tag: "" + # -- image pull policy to use for agent image. if tag is "latest", set to "Always" + pullPolicy: IfNotPresent + + # The default values for the injected OpenBao Agent containers. + agentDefaults: + # For more information on configuring resources, see the K8s documentation: + # https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + cpuLimit: "500m" + cpuRequest: "250m" + memLimit: "128Mi" + memRequest: "64Mi" + # ephemeralLimit: "128Mi" + # ephemeralRequest: "64Mi" + + # Default template type for secrets when no custom template is specified. + # Possible values include: "json" and "map". + template: "map" + + # Default values within Agent's template_config stanza. + templateConfig: + exitOnRetryFailure: true + staticSecretRenderInterval: "" + + # Used to define custom livenessProbe settings + livenessProbe: + # -- When a probe fails, Kubernetes will try failureThreshold times before giving up + failureThreshold: 2 + # -- Number of seconds after the container has started before probe initiates + initialDelaySeconds: 5 + # -- How often (in seconds) to perform the probe + periodSeconds: 2 + # -- Minimum consecutive successes for the probe to be considered successful after having failed + successThreshold: 1 + # -- Number of seconds after which the probe times out. + timeoutSeconds: 5 + # Used to define custom readinessProbe settings + readinessProbe: + # -- When a probe fails, Kubernetes will try failureThreshold times before giving up + failureThreshold: 2 + # -- Number of seconds after the container has started before probe initiates + initialDelaySeconds: 5 + # -- How often (in seconds) to perform the probe + periodSeconds: 2 + # -- Minimum consecutive successes for the probe to be considered successful after having failed + successThreshold: 1 + # -- Number of seconds after which the probe times out. + timeoutSeconds: 5 + # Used to define custom startupProbe settings + startupProbe: + # -- When a probe fails, Kubernetes will try failureThreshold times before giving up + failureThreshold: 12 + # -- Number of seconds after the container has started before probe initiates + initialDelaySeconds: 5 + # -- How often (in seconds) to perform the probe + periodSeconds: 5 + # -- Minimum consecutive successes for the probe to be considered successful after having failed + successThreshold: 1 + # -- Number of seconds after which the probe times out. + timeoutSeconds: 5 + + # Mount Path of the OpenBao Kubernetes Auth Method. + authPath: "auth/kubernetes" + + # -- Configures the log verbosity of the injector. + # Supported log levels include: trace, debug, info, warn, error + logLevel: "info" + + # -- Configures the log format of the injector. Supported log formats: "standard", "json". + logFormat: "standard" + + # Configures all OpenBao Agent sidecars to revoke their token when shutting down + revokeOnShutdown: false + + webhook: + # Configures failurePolicy of the webhook. The "unspecified" default behaviour depends on the + # API Version of the WebHook. + # To block pod creation while the webhook is unavailable, set the policy to `Fail` below. + # See https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#failure-policy + # + failurePolicy: Ignore + + # matchPolicy specifies the approach to accepting changes based on the rules of + # the MutatingWebhookConfiguration. + # See https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-matchpolicy + # for more details. + # + matchPolicy: Exact + + # timeoutSeconds is the amount of seconds before the webhook request will be ignored + # or fails. + # If it is ignored or fails depends on the failurePolicy + # See https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#timeouts + # for more details. + # + timeoutSeconds: 30 + + # namespaceSelector is the selector for restricting the webhook to only + # specific namespaces. + # See https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-namespaceselector + # for more details. + # Example: + # namespaceSelector: + # matchLabels: + # sidecar-injector: enabled + namespaceSelector: {} + + # objectSelector is the selector for restricting the webhook to only + # specific labels. + # See https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-objectselector + # for more details. + # Example: + # objectSelector: + # matchLabels: + # vault-sidecar-injector: enabled + objectSelector: | + matchExpressions: + - key: app.kubernetes.io/name + operator: NotIn + values: + - {{ template "openbao.name" . }}-agent-injector + + # Extra annotations to attach to the webhook + annotations: {} + + # Deprecated: please use 'webhook.failurePolicy' instead + # Configures failurePolicy of the webhook. The "unspecified" default behaviour depends on the + # API Version of the WebHook. + # To block pod creation while webhook is unavailable, set the policy to `Fail` below. + # See https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#failure-policy + # + failurePolicy: Ignore + + # Deprecated: please use 'webhook.namespaceSelector' instead + # namespaceSelector is the selector for restricting the webhook to only + # specific namespaces. + # See https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-namespaceselector + # for more details. + # Example: + # namespaceSelector: + # matchLabels: + # sidecar-injector: enabled + namespaceSelector: {} + + # Deprecated: please use 'webhook.objectSelector' instead + # objectSelector is the selector for restricting the webhook to only + # specific labels. + # See https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-objectselector + # for more details. + # Example: + # objectSelector: + # matchLabels: + # vault-sidecar-injector: enabled + objectSelector: {} + + # Deprecated: please use 'webhook.annotations' instead + # Extra annotations to attach to the webhook + webhookAnnotations: {} + + certs: + # secretName is the name of the secret that has the TLS certificate and + # private key to serve the injector webhook. If this is null, then the + # injector will default to its automatic management mode that will assign + # a service account to the injector to generate its own certificates. + secretName: null + + # caBundle is a base64-encoded PEM-encoded certificate bundle for the CA + # that signed the TLS certificate that the webhook serves. This must be set + # if secretName is non-null unless an external service like cert-manager is + # keeping the caBundle updated. + caBundle: "" + + # certName and keyName are the names of the files within the secret for + # the TLS cert and private key, respectively. These have reasonable + # defaults but can be customized if necessary. + certName: tls.crt + keyName: tls.key + + # Security context for the pod template and the injector container + # The default pod securityContext is: + # runAsNonRoot: true + # runAsGroup: {{ .Values.injector.gid | default 1000 }} + # runAsUser: {{ .Values.injector.uid | default 100 }} + # fsGroup: {{ .Values.injector.gid | default 1000 }} + # and for container is + # allowPrivilegeEscalation: false + # capabilities: + # drop: + # - ALL + securityContext: + pod: {} + container: {} + + resources: {} + # resources: + # requests: + # memory: 256Mi + # cpu: 250m + # limits: + # memory: 256Mi + # cpu: 250m + + # extraEnvironmentVars is a list of extra environment variables to set in the + # injector deployment. + extraEnvironmentVars: {} + # KUBERNETES_SERVICE_HOST: kubernetes.default.svc + + # Affinity Settings for injector pods + # This can either be a multi-line string or YAML matching the PodSpec's affinity field. + # Commenting out or setting as empty the affinity variable, will allow + # deployment of multiple replicas to single node services such as Minikube. + affinity: | + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: + app.kubernetes.io/name: {{ template "openbao.name" . }}-agent-injector + app.kubernetes.io/instance: "{{ .Release.Name }}" + component: webhook + topologyKey: kubernetes.io/hostname + + # Topology settings for injector pods + # ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ + # This should be either a multi-line string or YAML matching the topologySpreadConstraints array + # in a PodSpec. + topologySpreadConstraints: [] + + # Toleration Settings for injector pods + # This should be either a multi-line string or YAML matching the Toleration array + # in a PodSpec. + tolerations: [] + + # nodeSelector labels for server pod assignment, formatted as a multi-line string or YAML map. + # ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector + # Example: + # nodeSelector: + # beta.kubernetes.io/arch: amd64 + nodeSelector: {} + + # Priority class for injector pods + priorityClassName: "" + + # Extra annotations to attach to the injector pods + # This can either be YAML or a YAML-formatted multi-line templated string map + # of the annotations to apply to the injector pods + annotations: {} + + # Extra labels to attach to the agent-injector + # This should be a YAML map of the labels to apply to the injector + extraLabels: {} + + # Should the injector pods run on the host network (useful when using + # an alternate CNI in EKS) + hostNetwork: false + + # Injector service specific config + service: + # Extra annotations to attach to the injector service + annotations: {} + # Extra labels for the service definition. + # This should be a YAML map of the labels to apply to the injector service + extraLabels: {} + + # Injector serviceAccount specific config + serviceAccount: + # Extra annotations to attach to the injector serviceAccount + annotations: {} + + # A disruption budget limits the number of pods of a replicated application + # that are down simultaneously from voluntary disruptions + podDisruptionBudget: {} + # podDisruptionBudget: + # maxUnavailable: 1 + + # strategy for updating the deployment. This can be a multi-line string or a + # YAML map. + strategy: {} + # strategy: | + # rollingUpdate: + # maxSurge: 25% + # maxUnavailable: 25% + # type: RollingUpdate + +server: + # If true, or "-" with global.enabled true, OpenBao server will be installed. + # See openbao.mode in _helpers.tpl for implementation details. + enabled: "-" + + # Resource requests, limits, etc. for the server cluster placement. This + # should map directly to the value of the resources field for a PodSpec. + # By default no direct resource request is made. + + image: + # -- image registry to use for server image + registry: "quay.io" + # -- image repo to use for server image + repository: "openbao/openbao" + # -- image tag to use for server image - defaults to chart appVersion + tag: "" + # -- image pull policy to use for server image. if tag is "latest", set to "Always" + pullPolicy: IfNotPresent + + # Configure the Update Strategy Type for the StatefulSet + # See https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#update-strategies + updateStrategyType: "OnDelete" + + # Configure the pod management policy for the StatefulSet + # See https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#pod-management-policies + podManagementPolicy: "OrderedReady" + + # Configure the logging verbosity for the OpenBao server. + # Supported log levels include: trace, debug, info, warn, error + logLevel: "" + + # Configure the logging format for the OpenBao server. + # Supported log formats include: standard, json + logFormat: "" + + resources: {} + # resources: + # requests: + # memory: 256Mi + # cpu: 250m + # limits: + # memory: 256Mi + # cpu: 250m + + # Ingress allows ingress services to be created to allow external access + # from Kubernetes to access OpenBao pods. + # If deployment is on OpenShift, the following block is ignored. + # In order to expose the service, use the route section below + ingress: + enabled: false + labels: {} + # traffic: external + annotations: {} + # | + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + # or + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + + # Optionally use ingressClassName instead of deprecated annotation. + # See: https://kubernetes.io/docs/concepts/services-networking/ingress/#deprecated-annotation + ingressClassName: "" + + # As of Kubernetes 1.19, all Ingress Paths must have a pathType configured. The default value below should be sufficient in most cases. + # See: https://kubernetes.io/docs/concepts/services-networking/ingress/#path-types for other possible values. + pathType: Prefix + + # When HA mode is enabled and K8s service registration is being used, + # configure the ingress to point to the OpenBao active service. + activeService: true + hosts: + - host: chart-example.local + paths: [] + ## Extra paths to prepend to the host configuration. This is useful when working with annotation based services. + extraPaths: [] + # - path: /* + # backend: + # service: + # name: ssl-redirect + # port: + # number: use-annotation + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + + # Gateway consolidates configuration related to the Kubernetes Gateway API + # Currently, only creating a TLSRoute is supported + # See: https://gateway-api.sigs.k8s.io/ + gateway: + # Configures a TLSRoute for the OpenBao server. This can be enabled independently of the ingress configuration to allow for side-by-side scenarios for migration. + tlsRoute: + enabled: false + labels: {} + # traffic: external + annotations: {} + # external-dns.alpha.kubernetes.io/hostname: chart-example.local + + hosts: [] + # - chart-example.local + + # Allows overriding the TLSRoutes apiVersion in case a different version of Gateway API is installed on the cluster. + apiVersion: gateway.networking.k8s.io/v1alpha3 + + # When HA mode is enabled and K8s service registration is being used, + # configure the ingress to point to the OpenBao active service. + activeService: true + + # List of ParentRefs. As the helm chart configures no gateways itself, + # this should be set to at least one gateway with one or more TLS listeners + parentRefs: [] + # - name: my-gw + # namespace: gateway-namespace + # # sectionName is optional to fix to a specific listener + # sectionName: listener-name + + # Configures a HTTPRoute for the OpenBao server. This can be enabled independently of the ingress configuration to allow for side-by-side scenarios for migration. + # WARNING: Terminating TLS before reaching the OpenBao Server is not recommended and may break things like certificate authentication. Prefer usage of `TLSRoute`. + httpRoute: + enabled: false + labels: {} + # traffic: external + annotations: {} + # external-dns.alpha.kubernetes.io/hostname: chart-example.local + + hosts: + - chart-example.local + + # Allows overriding the HTTPRoute apiVersion in case a different version of Gateway API is installed on the cluster. + apiVersion: gateway.networking.k8s.io/v1 + + # When HA mode is enabled and K8s service registration is being used, + # configure the ingress to point to the OpenBao active service. + activeService: true + + # List of ParentRefs. As the helm chart configures no gateways itself, + # this should be set to at least one gateway with one or more HTTP listeners + parentRefs: [] + # - name: my-gw + # namespace: gateway-namespace + # # sectionName is optional to fix to a specific listener + # sectionName: listener-name + + matches: + path: + type: PathPrefix + value: '/' + timeouts: {} + # request: 10s #Maximum time the Gateway waits to complete the full client request and response cycle. + # backendRequest: 10s # Maximum time the Gateway waits for a response from the backend service. + filters: [] + # - type: RequestHeaderModifier + # requestHeaderModifier: + # set: + # - name: X-Forwarded-Proto + # value: https + + # If TLS is enable on server (see global.tlsDisable) the gateway must be configured + # with BackendTLSPolicy to correctly handles TLS connection with server in case TLS termination happens at gateway + tlsPolicy: + enabled: false + labels: {} + # traffic: external + annotations: {} + # external-dns.alpha.kubernetes.io/hostname: chart-example.local + + # Allows overriding the BackendTLSPolicy apiVersion in case a different version of Gateway API is installed on the cluster. + apiVersion: gateway.networking.k8s.io/v1 + + # When HA mode is enabled and K8s service registration is being used, + # configure the ingress to point to the OpenBao active service. + activeService: true + + # Identifies an API object to apply the policy to. + # If no one is specified the default is to target the OpenBao service + targetRefs: [] + + validation: {} + # caCertificateRefs: + # - kind: ConfigMap + # name: vault-ca + + # hostAliases is a list of aliases to be added to /etc/hosts. Specified as a YAML list. + hostAliases: [] + # - ip: 127.0.0.1 + # hostnames: + # - chart-example.local + + # OpenShift only - create a route to expose the service + # By default the created route will be of type passthrough + route: + enabled: false + + # When HA mode is enabled and K8s service registration is being used, + # configure the route to point to the OpenBao active service. + activeService: true + + labels: {} + annotations: {} + host: chart-example.local + # tls will be passed directly to the route's TLS config, which + # can be used to configure other termination methods that terminate + # TLS at the router + tls: + termination: passthrough + + # authDelegator enables a cluster role binding to be attached to the service + # account. This cluster role binding can be used to setup Kubernetes auth + # method. See https://openbao.org/docs/auth/kubernetes + authDelegator: + enabled: true + + # -- extraInitContainers is a list of init containers. Specified as a YAML list. + # This is useful if you need to run a script to provision TLS certificates or + # write out configuration files in a dynamic way. + extraInitContainers: [] + # # This example installs a plugin pulled from github into the /usr/local/libexec/vault/oauthapp folder, + # # which is defined in the volumes value. + # - name: oauthapp + # image: "alpine" + # command: [sh, -c] + # args: + # - cd /tmp && + # wget https://github.com/puppetlabs/vault-plugin-secrets-oauthapp/releases/download/v1.2.0/vault-plugin-secrets-oauthapp-v1.2.0-linux-amd64.tar.xz -O oauthapp.xz && + # tar -xf oauthapp.xz && + # mv vault-plugin-secrets-oauthapp-v1.2.0-linux-amd64 /usr/local/libexec/vault/oauthapp && + # chmod +x /usr/local/libexec/vault/oauthapp + # volumeMounts: + # - name: plugins + # mountPath: /usr/local/libexec/vault + + # extraContainers is a list of sidecar containers. Specified as a YAML list. + extraContainers: null + + # -- shareProcessNamespace enables process namespace sharing between OpenBao and the extraContainers + # This is useful if OpenBao must be signaled, e.g. to send a SIGHUP for a log rotation + shareProcessNamespace: false + + # -- extraArgs is a string containing additional OpenBao server arguments. + extraArgs: "" + + # -- extraPorts is a list of extra ports. Specified as a YAML list. + # This is useful if you need to add additional ports to the statefulset in dynamic way. + extraPorts: [] + # - containerPort: 8300 + # name: http-monitoring + + # Used to define custom readinessProbe settings + readinessProbe: + enabled: true + # If you need to use a http path instead of the default exec + # path: /v1/sys/health?standbyok=true + + # Port number on which readinessProbe will be checked. + port: 8200 + # When a probe fails, Kubernetes will try failureThreshold times before giving up + failureThreshold: 2 + # Number of seconds after the container has started before probe initiates + initialDelaySeconds: 5 + # How often (in seconds) to perform the probe + periodSeconds: 5 + # Minimum consecutive successes for the probe to be considered successful after having failed + successThreshold: 1 + # Number of seconds after which the probe times out. + timeoutSeconds: 3 + # Used to enable a livenessProbe for the pods + livenessProbe: + enabled: false + # Used to define a liveness exec command. If provided, exec is preferred to httpGet (path) as the livenessProbe handler. + execCommand: [] + # - /bin/sh + # - -c + # - /openbao/userconfig/mylivenessscript/run.sh + # Path for the livenessProbe to use httpGet as the livenessProbe handler + path: "/v1/sys/health?standbyok=true" + # Port number on which livenessProbe will be checked if httpGet is used as the livenessProbe handler + port: 8200 + # When a probe fails, Kubernetes will try failureThreshold times before giving up + failureThreshold: 2 + # Number of seconds after the container has started before probe initiates + initialDelaySeconds: 60 + # How often (in seconds) to perform the probe + periodSeconds: 5 + # Minimum consecutive successes for the probe to be considered successful after having failed + successThreshold: 1 + # Number of seconds after which the probe times out. + timeoutSeconds: 3 + + # Optional duration in seconds the pod needs to terminate gracefully. + # See: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/ + terminationGracePeriodSeconds: 10 + + # Used to set the sleep time during the preStop step + preStopSleepSeconds: 5 + + # Used to define commands to run after the pod is ready. + # This can be used to automate processes such as initialization + # or bootstrapping auth methods. + postStart: [] + # - /bin/sh + # - -c + # - /openbao/userconfig/myscript/run.sh + + # extraEnvironmentVars is a list of extra environment variables to set with the stateful set. These could be + # used to include variables required for auto-unseal. + extraEnvironmentVars: {} + # GOOGLE_REGION: global + # GOOGLE_PROJECT: myproject + # GOOGLE_APPLICATION_CREDENTIALS: /openbao/userconfig/myproject/myproject-creds.json + + # extraSecretEnvironmentVars is a list of extra environment variables to set with the stateful set. + # These variables take value from existing Secret objects. + extraSecretEnvironmentVars: [] + # - envName: AWS_SECRET_ACCESS_KEY + # secretName: openbao + # secretKey: AWS_SECRET_ACCESS_KEY + + # Deprecated: please use 'volumes' instead. + # extraVolumes is a list of extra volumes to mount. These will be exposed + # to OpenBao in the path `/openbao/userconfig//`. The value below is + # an array of objects, examples are shown below. + extraVolumes: [] + # - type: secret (or "configMap") + # name: my-secret + # path: null # default is `/openbao/userconfig` + + # volumes is a list of volumes made available to all containers. These are rendered + # via toYaml rather than pre-processed like the extraVolumes value. + # The purpose is to make it easy to share volumes between containers. + volumes: null + # - name: plugins + # emptyDir: {} + + # volumeMounts is a list of volumeMounts for the main server container. These are rendered + # via toYaml rather than pre-processed like the extraVolumes value. + # The purpose is to make it easy to share volumes between containers. + volumeMounts: null + # - mountPath: /usr/local/libexec/vault + # name: plugins + # readOnly: true + + # Affinity Settings + # Commenting out or setting as empty the affinity variable, will allow + # deployment to single node services such as Minikube + # This should be either a multi-line string or YAML matching the PodSpec's affinity field. + affinity: | + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: + app.kubernetes.io/name: {{ template "openbao.name" . }} + app.kubernetes.io/instance: "{{ .Release.Name }}" + component: server + topologyKey: kubernetes.io/hostname + + # Topology settings for server pods + # ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/ + # This should be either a multi-line string or YAML matching the topologySpreadConstraints array + # in a PodSpec. + topologySpreadConstraints: [] + + # Toleration Settings for server pods + # This should be either a multi-line string or YAML matching the Toleration array + # in a PodSpec. + tolerations: [] + + # nodeSelector labels for server pod assignment, formatted as a multi-line string or YAML map. + # ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector + # Example: + # nodeSelector: + # beta.kubernetes.io/arch: amd64 + nodeSelector: {} + + # Enables network policy for server pods + networkPolicy: + enabled: false + egress: [] + # egress: + # - to: + # - ipBlock: + # cidr: 10.0.0.0/24 + # ports: + # - protocol: TCP + # port: 443 + ingress: + - from: + - namespaceSelector: {} + ports: + - port: 8200 + protocol: TCP + - port: 8201 + protocol: TCP + + # Priority class for server pods + priorityClassName: "" + + # Extra labels to attach to the server pods + # This should be a YAML map of the labels to apply to the server pods + extraLabels: {} + + # Extra annotations to attach to the server pods + # This can either be YAML or a YAML-formatted multi-line templated string map + # of the annotations to apply to the server pods + annotations: {} + + # Add an annotation to the server configmap and the statefulset pods, + # vaultproject.io/config-checksum, that is a hash of the OpenBao configuration. + # This can be used together with an OnDelete deployment strategy to help + # identify which pods still need to be deleted during a deployment to pick up + # any configuration changes. + configAnnotation: false + + # Enables a headless service to be used by the OpenBao Statefulset + service: + enabled: true + # Enable or disable the openbao-active service, which selects OpenBao pods that + # have labeled themselves as the cluster leader with `openbao-active: "true"`. + active: + enabled: true + # Extra annotations for the service definition. This can either be YAML or a + # YAML-formatted multi-line templated string map of the annotations to apply + # to the active service. + annotations: {} + # Extra labels for the service definition. + # This should be a YAML map of the labels to apply to the active service + extraLabels: {} + # Enable or disable the openbao-standby service, which selects OpenBao pods that + # have labeled themselves as a cluster follower with `openbao-active: "false"`. + standby: + enabled: true + # Extra annotations for the service definition. This can either be YAML or a + # YAML-formatted multi-line templated string map of the annotations to apply + # to the standby service. + annotations: {} + # Extra labels for the service definition. + # This should be a YAML map of the labels to apply to the standby service + extraLabels: {} + # If enabled, the service selectors will include `app.kubernetes.io/instance: {{ .Release.Name }}` + # When disabled, services may select OpenBao pods not deployed from the chart. + # Does not affect the headless openbao-internal service with `ClusterIP: None` + instanceSelector: + enabled: true + # clusterIP controls whether a Cluster IP address is attached to the + # OpenBao service within Kubernetes. By default, the OpenBao service will + # be given a Cluster IP address, set to None to disable. When disabled + # Kubernetes will create a "headless" service. Headless services can be + # used to communicate with pods directly through DNS instead of a round-robin + # load balancer. + # clusterIP: None + + # Configures the service type for the main OpenBao service. Can be ClusterIP + # or NodePort. + # type: ClusterIP + + # The IP family and IP families options are to set the behaviour in a dual-stack environment. + # Omitting these values will let the service fall back to whatever the CNI dictates the defaults + # should be. + # These are only supported for kubernetes versions >=1.23.0 + # + # Configures the service's supported IP family policy, can be either: + # SingleStack: Single-stack service. The control plane allocates a cluster IP for the Service, using the first configured service cluster IP range. + # PreferDualStack: Allocates IPv4 and IPv6 cluster IPs for the Service. + # RequireDualStack: Allocates Service .spec.ClusterIPs from both IPv4 and IPv6 address ranges. + ipFamilyPolicy: "" + + # Sets the families that should be supported and the order in which they should be applied to ClusterIP as well. + # Can be IPv4 and/or IPv6. + ipFamilies: [] + + # Do not wait for pods to be ready before including them in the services' + # targets. Does not apply to the headless service, which is used for + # cluster-internal communication. + publishNotReadyAddresses: true + + # The externalTrafficPolicy can be set to either Cluster or Local + # and is only valid for LoadBalancer and NodePort service types. + # The default value is Cluster. + # ref: https://kubernetes.io/docs/concepts/services-networking/service/#external-traffic-policy + externalTrafficPolicy: Cluster + + # If type is set to "NodePort", a specific nodePort value can be configured, + # will be random if left blank. + # nodePort: 30000 + + # When HA mode is enabled + # If type is set to "NodePort", a specific nodePort value can be configured, + # will be random if left blank. + # activeNodePort: 30001 + + # When HA mode is enabled + # If type is set to "NodePort", a specific nodePort value can be configured, + # will be random if left blank. + # standbyNodePort: 30002 + + # Port on which OpenBao server is listening + port: 8200 + # Target port to which the service should be mapped to + targetPort: 8200 + + # -- extraPorts is a list of extra ports. Specified as a YAML list. + # This is useful if you need to add additional ports to the server service in dynamic way. + extraPorts: [] + # - name: metrics + # port: 9101 + # targetPort: 9101 + + # Extra annotations for the service definition. This can either be YAML or a + # YAML-formatted multi-line templated string map of the annotations to apply + # to the service. + annotations: {} + # Extra labels for the service definition. + # This should be a YAML map of the labels to apply to the service + extraLabels: {} + + # This configures the OpenBao Statefulset to create a PVC for data + # storage when using the file or raft backend storage engines. + # See https://openbao.org/docs/configuration/storage to know more + dataStorage: + enabled: true + # Size of the PVC created + size: 10Gi + # Location where the PVC will be mounted. + mountPath: "/openbao/data" + # Name of the storage class to use. If null it will use the + # configured default Storage Class. + storageClass: null + # Access Mode of the storage device being used for the PVC + accessMode: ReadWriteOnce + # Annotations to apply to the PVC + annotations: {} + # Labels to apply to the PVC + labels: {} + + # Persistent Volume Claim (PVC) retention policy + # ref: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#persistentvolumeclaim-retention + # Example: + # persistentVolumeClaimRetentionPolicy: + # whenDeleted: Retain + # whenScaled: Retain + persistentVolumeClaimRetentionPolicy: {} + + # This configures the OpenBao Statefulset to create a PVC for audit + # logs. Once OpenBao is deployed, initialized, and unsealed, OpenBao must + # be configured to use this for audit logs. This will be mounted to + # /openbao/audit + # See https://openbao.org/docs/audit to know more + auditStorage: + enabled: false + # Size of the PVC created + size: 10Gi + # Location where the PVC will be mounted. + mountPath: "/openbao/audit" + # Name of the storage class to use. If null it will use the + # configured default Storage Class. + storageClass: null + # Access Mode of the storage device being used for the PVC + accessMode: ReadWriteOnce + # Annotations to apply to the PVC + annotations: {} + # Labels to apply to the PVC + labels: {} + + # Run OpenBao in "dev" mode. This requires no further setup, no state management, + # and no initialization. This is useful for experimenting with OpenBao without + # needing to unseal, store keys, et. al. All data is lost on restart - do not + # use dev mode for anything other than experimenting. + # See https://openbao.org/docs/concepts/dev-server to know more + dev: + enabled: false + + # Set VAULT_DEV_ROOT_TOKEN_ID value + devRootToken: "root" + + # Run OpenBao in "standalone" mode. This is the default mode that will deploy if + # no arguments are given to helm. This requires a PVC for data storage to use + # the "file" backend. This mode is not highly available and should not be scaled + # past a single replica. + standalone: + enabled: "-" + + # config is a raw string of default configuration when using a Stateful + # deployment. Default is to use a PersistentVolumeClaim mounted at /openbao/data + # and store data there. This is only used when using a Replica count of 1, and + # using a stateful set. This should be HCL. + + # Note: Configuration files are stored in ConfigMaps so sensitive data + # such as passwords should be either mounted through extraSecretEnvironmentVars + # or through a Kube secret. For more information see: + # https://openbao.org/docs/platform/k8s/helm/run/#protecting-sensitive-openbao-configurations + config: | + ui = true + + listener "tcp" { + tls_disable = 1 + address = "[::]:8200" + cluster_address = "[::]:8201" + # Enable unauthenticated metrics access (necessary for Prometheus Operator) + #telemetry { + # unauthenticated_metrics_access = "true" + #} + } + storage "file" { + path = "/openbao/data" + } + + # Example configuration for using auto-unseal, using Google Cloud KMS. The + # GKMS keys must already exist, and the cluster must have a service account + # that is authorized to access GCP KMS. + #seal "gcpckms" { + # project = "openbao-helm-dev" + # region = "global" + # key_ring = "openbao-helm-unseal-kr" + # crypto_key = "openbao-helm-unseal-key" + #} + + # Example configuration for enabling Prometheus metrics in your config. + #telemetry { + # prometheus_retention_time = "30s" + # disable_hostname = true + #} + + # Run OpenBao in "HA" mode. There are no storage requirements unless the audit log + # persistence is required. In HA mode OpenBao will configure itself to use Consul + # for its storage backend. The default configuration provided will work the Consul + # Helm project by default. It is possible to manually configure OpenBao to use a + # different HA backend. + ha: + enabled: false + replicas: 3 + + # Set the api_addr configuration for OpenBao HA + # See https://openbao.org/docs/configuration/#high-availability-parameters + # If set to null, this will be set to the Pod IP Address + apiAddr: null + + # Set the cluster_addr configuration for OpenBao HA + # See https://openbao.org/docs/configuration/#high-availability-parameters + # If set to null, this will be set to https://$(HOSTNAME).{{ template "openbao.fullname" . }}-internal:8201 + clusterAddr: null + + # Enables OpenBao's integrated Raft storage. Unlike the typical HA modes where + # OpenBao's persistence is external (such as Consul), enabling Raft mode will create + # persistent volumes for OpenBao to store data according to the configuration under server.dataStorage. + # The OpenBao cluster will coordinate leader elections and failovers internally. + raft: + # Enables Raft integrated storage + enabled: false + # Set the Node Raft ID to the name of the pod + setNodeId: false + + # config is a raw string of default configuration when using a Stateful + # deployment. + # This should be HCL. + + # Note: Configuration files are stored in ConfigMaps so sensitive data + # such as passwords should be either mounted through extraSecretEnvironmentVars + # or through a Kube secret. For more information see: + # https://openbao.org/docs/platform/k8s/helm/run/#protecting-sensitive-openbao-configurations + config: | + ui = true + + listener "tcp" { + tls_disable = 1 + address = "[::]:8200" + cluster_address = "[::]:8201" + # Enable unauthenticated metrics access (necessary for Prometheus Operator) + #telemetry { + # unauthenticated_metrics_access = "true" + #} + } + + storage "raft" { + path = "/openbao/data" + } + + service_registration "kubernetes" {} + + # config is a raw string of default configuration when using a Stateful + # deployment. Default is to use a Consul for its HA storage backend. + # This should be HCL. + + # Note: Configuration files are stored in ConfigMaps so sensitive data + # such as passwords should be either mounted through extraSecretEnvironmentVars + # or through a Kube secret. For more information see: + # https://openbao.org/docs/platform/k8s/helm/run/#protecting-sensitive-openbao-configurations + config: | + ui = true + + listener "tcp" { + tls_disable = 1 + address = "[::]:8200" + cluster_address = "[::]:8201" + } + storage "consul" { + path = "openbao" + address = "HOST_IP:8500" + } + + service_registration "kubernetes" {} + + # Example configuration for using auto-unseal, using Google Cloud KMS. The + # GKMS keys must already exist, and the cluster must have a service account + # that is authorized to access GCP KMS. + #seal "gcpckms" { + # project = "openbao-helm-dev-246514" + # region = "global" + # key_ring = "openbao-helm-unseal-kr" + # crypto_key = "openbao-helm-unseal-key" + #} + + # Example configuration for enabling Prometheus metrics. + # If you are using Prometheus Operator you can enable a ServiceMonitor resource below. + # You may wish to enable unauthenticated metrics in the listener block above. + #telemetry { + # prometheus_retention_time = "30s" + # disable_hostname = true + #} + + # A disruption budget limits the number of pods of a replicated application + # that are down simultaneously from voluntary disruptions + disruptionBudget: + enabled: true + + # maxUnavailable will default to (n/2)-1 where n is the number of + # replicas. If you'd like a custom value, you can specify an override here. + maxUnavailable: null + + # Definition of the serviceAccount used to run Vault. + # These options are also used when using an external OpenBao server to validate + # Kubernetes tokens. + serviceAccount: + # Specifies whether a service account should be created + create: true + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + # Create a Secret API object to store a non-expiring token for the service account. + # Prior to v1.24.0, Kubernetes used to generate this secret for each service account by default. + # Kubernetes now recommends using short-lived tokens from the TokenRequest API or projected volumes instead if possible. + # For more details, see https://kubernetes.io/docs/concepts/configuration/secret/#serviceaccount-token-secrets + # serviceAccount.create must be equal to 'true' in order to use this feature. + createSecret: false + # Extra annotations for the serviceAccount definition. This can either be + # YAML or a YAML-formatted multi-line templated string map of the + # annotations to apply to the serviceAccount. + annotations: {} + # Extra labels to attach to the serviceAccount + # This should be a YAML map of the labels to apply to the serviceAccount + extraLabels: {} + # Enable or disable a service account role binding with the permissions required for + # OpenBao's Kubernetes service_registration config option. + # See https://openbao.org/docs/configuration/service-registration/kubernetes + serviceDiscovery: + enabled: true + + # Settings for the statefulSet used to run OpenBao. + statefulSet: + # Extra annotations for the statefulSet. This can either be YAML or a + # YAML-formatted multi-line templated string map of the annotations to apply + # to the statefulSet. + annotations: {} + + # Set the pod and container security contexts. + # If not set, these will default to, and for *not* OpenShift: + # pod: + # runAsNonRoot: true + # runAsGroup: {{ .Values.server.gid | default 1000 }} + # runAsUser: {{ .Values.server.uid | default 100 }} + # fsGroup: {{ .Values.server.gid | default 1000 }} + # container: + # allowPrivilegeEscalation: false + # + # If not set, these will default to, and for OpenShift: + # pod: {} + # container: {} + securityContext: + pod: {} + container: {} + + # Should the server pods run on the host network + hostNetwork: false + +# OpenBao UI +ui: + # True if you want to create a Service entry for the OpenBao UI. + # + # serviceType can be used to control the type of service created. For + # example, setting this to "LoadBalancer" will create an external load + # balancer (for supported K8S installations) to access the UI. + enabled: false + publishNotReadyAddresses: true + # The service should only contain selectors for active OpenBao pod + activeOpenbaoPodOnly: false + serviceType: "ClusterIP" + serviceNodePort: null + externalPort: 8200 + targetPort: 8200 + + # The IP family and IP families options are to set the behaviour in a dual-stack environment. + # Omitting these values will let the service fall back to whatever the CNI dictates the defaults + # should be. + # These are only supported for kubernetes versions >=1.23.0 + # + # Configures the service's supported IP family, can be either: + # SingleStack: Single-stack service. The control plane allocates a cluster IP for the Service, using the first configured service cluster IP range. + # PreferDualStack: Allocates IPv4 and IPv6 cluster IPs for the Service. + # RequireDualStack: Allocates Service .spec.ClusterIPs from both IPv4 and IPv6 address ranges. + serviceIPFamilyPolicy: "" + + # Sets the families that should be supported and the order in which they should be applied to ClusterIP as well + # Can be IPv4 and/or IPv6. + serviceIPFamilies: [] + + # The externalTrafficPolicy can be set to either Cluster or Local + # and is only valid for LoadBalancer and NodePort service types. + # The default value is Cluster. + # ref: https://kubernetes.io/docs/concepts/services-networking/service/#external-traffic-policy + externalTrafficPolicy: Cluster + + # loadBalancerSourceRanges: + # - 10.0.0.0/16 + # - 1.78.23.3/32 + + # loadBalancerIP: + + # Extra annotations to attach to the ui service + # This can either be YAML or a YAML-formatted multi-line templated string map + # of the annotations to apply to the ui service + annotations: {} + + # Extra labels for the service definition. + # This should be a YAML map of the labels to apply to the ui service + extraLabels: {} + +# openbao-csi-provider +csi: + # -- True if you want to install a openbao-csi-provider daemonset. + # + # Requires installing the secrets-store-csi-driver separately, see: + # https://secrets-store-csi-driver.sigs.k8s.io/getting-started/installation + # + # With the driver and provider installed, you can mount OpenBao secrets into volumes + # similar to the OpenBao Agent injector, and you can also sync those secrets into + # Kubernetes secrets. + enabled: false + + image: + # -- image registry to use for csi image + registry: "quay.io" + # -- image repo to use for csi image + repository: "openbao/openbao-csi-provider" + # -- image tag to use for csi image + tag: "2.0.0" + # -- image pull policy to use for csi image. if tag is "latest", set to "Always" + pullPolicy: IfNotPresent + + # -- volumes is a list of volumes made available to all containers. These are rendered + # via toYaml rather than pre-processed like the extraVolumes value. + # The purpose is to make it easy to share volumes between containers. + volumes: [] + # - name: tls + # secret: + # secretName: openbao-tls + + # -- volumeMounts is a list of volumeMounts for the main server container. These are rendered + # via toYaml rather than pre-processed like the extraVolumes value. + # The purpose is to make it easy to share volumes between containers. + volumeMounts: [] + # - name: tls + # mountPath: "/openbao/tls" + # readOnly: true + + resources: {} + # resources: + # requests: + # cpu: 50m + # memory: 128Mi + # limits: + # cpu: 50m + # memory: 128Mi + + # Override the default secret name for the CSI Provider's HMAC key used for + # generating secret versions. + hmacSecretName: "" + + # Settings for the daemonSet used to run the provider. + daemonSet: + updateStrategy: + type: RollingUpdate + maxUnavailable: "" + # Extra annotations for the daemonSet. This can either be YAML or a + # YAML-formatted multi-line templated string map of the annotations to apply + # to the daemonSet. + annotations: {} + # Provider host path (must match the CSI provider's path) + providersDir: "/etc/kubernetes/secrets-store-csi-providers" + # Kubelet host path + kubeletRootDir: "/var/lib/kubelet" + # endpoint path for the provider + endpoint: "/provider/openbao.sock" + + # Extra labels to attach to the openbao-csi-provider daemonSet + # This should be a YAML map of the labels to apply to the csi provider daemonSet + extraLabels: {} + # security context for the pod template and container in the csi provider daemonSet + securityContext: + pod: {} + container: {} + + pod: + # Extra annotations for the provider pods. This can either be YAML or a + # YAML-formatted multi-line templated string map of the annotations to apply + # to the pod. + annotations: {} + + # Toleration Settings for provider pods + # This should be either a multi-line string or YAML matching the Toleration array + # in a PodSpec. + tolerations: [] + + # nodeSelector labels for csi pod assignment, formatted as a multi-line string or YAML map. + # ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector + # Example: + # nodeSelector: + # beta.kubernetes.io/arch: amd64 + nodeSelector: {} + + # Affinity Settings + # This should be either a multi-line string or YAML matching the PodSpec's affinity field. + affinity: {} + + # Extra labels to attach to the openbao-csi-provider pod + # This should be a YAML map of the labels to apply to the csi provider pod + extraLabels: {} + + agent: + enabled: true + extraArgs: [] + + image: + # -- image registry to use for agent image + registry: "quay.io" + # -- image repo to use for agent image + repository: "openbao/openbao" + # -- image tag to use for agent image - defaults to chart appVersion + tag: "" + # -- image pull policy to use for agent image. if tag is "latest", set to "Always" + pullPolicy: IfNotPresent + + logFormat: standard + logLevel: info + + resources: {} + # resources: + # requests: + # memory: 256Mi + # cpu: 250m + # limits: + # memory: 256Mi + # cpu: 250m + + # Priority class for csi pods + priorityClassName: "" + + serviceAccount: + # Extra annotations for the serviceAccount definition. This can either be + # YAML or a YAML-formatted multi-line templated string map of the + # annotations to apply to the serviceAccount. + annotations: {} + + # Extra labels to attach to the openbao-csi-provider serviceAccount + # This should be a YAML map of the labels to apply to the csi provider serviceAccount + extraLabels: {} + + # Used to configure readinessProbe for the pods. + readinessProbe: + # When a probe fails, Kubernetes will try failureThreshold times before giving up + failureThreshold: 2 + # Number of seconds after the container has started before probe initiates + initialDelaySeconds: 5 + # How often (in seconds) to perform the probe + periodSeconds: 5 + # Minimum consecutive successes for the probe to be considered successful after having failed + successThreshold: 1 + # Number of seconds after which the probe times out. + timeoutSeconds: 3 + # Used to configure livenessProbe for the pods. + livenessProbe: + # When a probe fails, Kubernetes will try failureThreshold times before giving up + failureThreshold: 2 + # Number of seconds after the container has started before probe initiates + initialDelaySeconds: 5 + # How often (in seconds) to perform the probe + periodSeconds: 5 + # Minimum consecutive successes for the probe to be considered successful after having failed + successThreshold: 1 + # Number of seconds after which the probe times out. + timeoutSeconds: 3 + + # Enables debug logging. + debug: false + + # Pass arbitrary additional arguments to openbao-csi-provider. + # See https://openbao.org/docs/platform/k8s/csi/configurations + # for the available command line flags. + extraArgs: [] + +# OpenBao is able to collect and publish various runtime metrics. +# Enabling this feature requires setting adding `telemetry{}` stanza to +# the OpenBao configuration. There are a few examples included in the `config` sections above. +# +# For more information see: +# https://openbao.org/docs/configuration/telemetry +# https://openbao.org/docs/internals/telemetry +serverTelemetry: + # Enable support for the Prometheus Operator. If authorization is not required for + # OpenBao's metrics endpoint, the following OpenBao server `telemetry{}` config must be included + # in the `listener "tcp"{}` stanza + # telemetry { + # unauthenticated_metrics_access = "true" + # } + # + # See the `standalone.config` for a more complete example of this. + # + # In addition, a top level `telemetry{}` stanza must also be included in the OpenBao configuration: + # + # example: + # telemetry { + # prometheus_retention_time = "30s" + # disable_hostname = true + # } + # + # Configuration for monitoring the OpenBao server. + serviceMonitor: + # The Prometheus operator *must* be installed before enabling this feature, + # if not the chart will fail to install due to missing CustomResourceDefinitions + # provided by the operator. + # + # Instructions on how to install the Helm chart can be found here: + # https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack + # More information can be found here: + # https://github.com/prometheus-operator/prometheus-operator + # https://github.com/prometheus-operator/kube-prometheus + + # Enable deployment of the OpenBao Server ServiceMonitor CustomResource. + enabled: false + + # Selector labels to add to the ServiceMonitor. + # When empty, defaults to: + # release: prometheus + selectors: {} + + # -- Port which Prometheus uses when scraping metrics. If empty will use `openbao.scheme` helper for its value + port: "" + + # -- scheme to use when Prometheus scrapes metrics. If empty will use `openbao.scheme` helper for its value + scheme: "" + + # Interval at which Prometheus scrapes metrics + interval: 30s + + # Timeout for Prometheus scrapes + scrapeTimeout: 10s + + # tlsConfig used for scraping the Vault metrics API. + tlsConfig: {} + + # authorization used for scraping the Vault metrics API. + authorization: {} + + # scrapeClass to be used by the serviceMonitor + scrapeClass: "" + + prometheusRules: + # The Prometheus operator *must* be installed before enabling this feature, + # if not the chart will fail to install due to missing CustomResourceDefinitions + # provided by the operator. + + # Deploy the PrometheusRule custom resource for AlertManager based alerts. + # Requires that AlertManager is properly deployed. + enabled: false + + # Selector labels to add to the PrometheusRules. + # When empty, defaults to: + # release: prometheus + selectors: {} + + # Some example rules. + rules: [] + # - alert: vault-HighResponseTime + # annotations: + # message: The response time of OpenBao is over 500ms on average over the last 5 minutes. + # expr: vault_core_handle_request{quantile="0.5", namespace="mynamespace"} > 500 + # for: 5m + # labels: + # severity: warning + # - alert: vault-HighResponseTime + # annotations: + # message: The response time of OpenBao is over 1s on average over the last 10 minutes. + # expr: vault_core_handle_request{quantile="0.5", namespace="mynamespace"} > 1000 + # for: 10m + # labels: + # severity: critical + + grafanaDashboard: + # Enable deployment of the OpenBao Grafana dashboard. + # https://grafana.com/grafana/dashboards/23725-openbao + enabled: false + + # Add `grafana_dashboard: "1"` default label + defaultLabel: true + + # Extra labels for dashboard ConfigMap + extraLabel: {} + + # Extra annotations for dashboard ConfigMap + extraAnnotations: {} + +# extraObjects allows you to add any extra Kubernetes manifests to this chart +extraObjects: [] +# Examples: +# Defining as a Structured YAML Object Example: +# extraObjects: +# - apiVersion: v1 +# kind: ConfigMap +# metadata: +# name: cert-manager-configmap-{{ .Release.Name }} +# +# Using a String for Advanced Templating Example: +# extraObjects: +# - | +# apiVersion: v1 +# kind: ConfigMap +# metadata: +# name: cert-manager-configmap-{{ include "some-other-template" }} + +# Snapshot Agent Configuration +snapshotAgent: + # whether or not to enable the snapshot agent cronjob + enabled: false + # extra Annotations for the job + annotations: {} + # schedule of the cronjob + schedule: "*/15 * * * *" + restartPolicy: OnFailure + # service account settings for the snapshot agent + serviceAccount: + create: true + name: "" + annotations: {} + extraLabels: {} + # The image settings for the snapshot agent + image: + repository: ghcr.io/openbao/openbao-snapshot-agent + tag: 0.2.4 + + # -- List of extraVolumes made available to the snapshot cronjob container. + extraVolumes: [] + # - name: openbao-tls + # secret: + # defaultMode: 420 + # secretName: openbao-tls + + # -- List of additional volumeMounts for the snapshot cronjob container. + extraVolumeMounts: [] + # - mountPath: /openbao/tls/ca.crt + # name: openbao-tls + # readOnly: true + # subPath: ca.crt + + # s3CredentialsSecret to use + s3CredentialsSecret: "my-s3-credentials" + + # configuration for the snapshot agent + config: + s3Host: "s3.eu-east-1.amazonaws.com" + s3Bucket: "openbao-snapshots" + s3Uri: "s3://openbao-snapshots" + s3ExpireDays: "14" + s3cmdExtraFlag: "-v" + baoAuthPath: "kubernetes" + baoRole: "snapshot" + + # configuration of the CronJobs resources + resources: {} + + # -- Map of extra environment variables to set in the snapshot-agent cronjob + extraEnvironmentVars: {} + # BAO_CACERT: /openbao/tls/ca.crt + + # -- List of extra environment variables to set in the snapshot-agent cronjob + # These variables take value from existing Secret objects. + extraSecretEnvironmentVars: [] + # - envName: AWS_SECRET_ACCESS_KEY + # secretName: openbao + # secretKey: AWS_SECRET_ACCESS_KEY + + # Security context for the pod template and the snapshotAgent container + # The default pod securityContext is: + # runAsNonRoot: true + # runAsGroup: {{ .Values.snapshotAgent.gid | default 1000 }} + # runAsUser: {{ .Values.snapshotAgent.uid | default 100 }} + # fsGroup: {{ .Values.snapshotAgent.gid | default 1000 }} + # and for container is + # allowPrivilegeEscalation: false + # capabilities: + # drop: + # - ALL + securityContext: + pod: {} + container: {} diff --git a/packages/system/openbao/values.yaml b/packages/system/openbao/values.yaml new file mode 100644 index 00000000..957f37a0 --- /dev/null +++ b/packages/system/openbao/values.yaml @@ -0,0 +1,5 @@ +openbao: + injector: + enabled: false + csi: + enabled: false From da59efec21b479bc541e88274eac8c4a9a62d411 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Tue, 17 Feb 2026 20:03:01 +0300 Subject: [PATCH 286/889] feat(openbao): add application chart with standalone and HA modes Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/apps/openbao/.helmignore | 3 + packages/apps/openbao/Chart.yaml | 7 ++ packages/apps/openbao/Makefile | 5 + packages/apps/openbao/README.md | 27 +++++ packages/apps/openbao/charts/cozy-lib | 1 + packages/apps/openbao/logos/openbao.svg | 11 +++ .../apps/openbao/templates/_resources.tpl | 49 +++++++++ .../templates/dashboard-resourcemap.yaml | 31 ++++++ packages/apps/openbao/templates/openbao.yaml | 99 +++++++++++++++++++ .../openbao/templates/workloadmonitor.yaml | 13 +++ packages/apps/openbao/values.schema.json | 87 ++++++++++++++++ packages/apps/openbao/values.yaml | 41 ++++++++ 12 files changed, 374 insertions(+) create mode 100644 packages/apps/openbao/.helmignore create mode 100644 packages/apps/openbao/Chart.yaml create mode 100644 packages/apps/openbao/Makefile create mode 100644 packages/apps/openbao/README.md create mode 120000 packages/apps/openbao/charts/cozy-lib create mode 100644 packages/apps/openbao/logos/openbao.svg create mode 100644 packages/apps/openbao/templates/_resources.tpl create mode 100644 packages/apps/openbao/templates/dashboard-resourcemap.yaml create mode 100644 packages/apps/openbao/templates/openbao.yaml create mode 100644 packages/apps/openbao/templates/workloadmonitor.yaml create mode 100644 packages/apps/openbao/values.schema.json create mode 100644 packages/apps/openbao/values.yaml diff --git a/packages/apps/openbao/.helmignore b/packages/apps/openbao/.helmignore new file mode 100644 index 00000000..1ea0ae84 --- /dev/null +++ b/packages/apps/openbao/.helmignore @@ -0,0 +1,3 @@ +.helmignore +/logos +/Makefile diff --git a/packages/apps/openbao/Chart.yaml b/packages/apps/openbao/Chart.yaml new file mode 100644 index 00000000..f33fc756 --- /dev/null +++ b/packages/apps/openbao/Chart.yaml @@ -0,0 +1,7 @@ +apiVersion: v2 +name: openbao +description: Managed OpenBAO secrets management service +icon: /logos/openbao.svg +type: application +version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process +appVersion: "2.5.0" diff --git a/packages/apps/openbao/Makefile b/packages/apps/openbao/Makefile new file mode 100644 index 00000000..d1cfda8e --- /dev/null +++ b/packages/apps/openbao/Makefile @@ -0,0 +1,5 @@ +include ../../../hack/package.mk + +generate: + cozyvalues-gen -v values.yaml -s values.schema.json -r README.md + ../../../hack/update-crd.sh diff --git a/packages/apps/openbao/README.md b/packages/apps/openbao/README.md new file mode 100644 index 00000000..c53c9d28 --- /dev/null +++ b/packages/apps/openbao/README.md @@ -0,0 +1,27 @@ +# Managed OpenBAO Service + +OpenBAO is an open-source secrets management solution forked from HashiCorp Vault. +It provides identity-based secrets and encryption management for cloud infrastructure. + +## Parameters + +### Common parameters + +| Name | Description | Type | Value | +| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ------- | +| `replicas` | Number of OpenBAO replicas. HA with Raft is automatically enabled when replicas > 1. Switching between standalone (file storage) and HA (Raft storage) modes requires data migration. | `int` | `1` | +| `resources` | Explicit CPU and memory configuration for each OpenBAO replica. When omitted, the preset defined in `resourcesPreset` is applied. | `object` | `{}` | +| `resources.cpu` | CPU available to each replica. | `quantity` | `""` | +| `resources.memory` | Memory (RAM) available to each replica. | `quantity` | `""` | +| `resourcesPreset` | Default sizing preset used when `resources` is omitted. | `string` | `small` | +| `size` | Persistent Volume Claim size for data storage. | `quantity` | `10Gi` | +| `storageClass` | StorageClass used to store the data. | `string` | `""` | +| `external` | Enable external access from outside the cluster. | `bool` | `false` | + + +### Application-specific parameters + +| Name | Description | Type | Value | +| ---- | -------------------------- | ------ | ------ | +| `ui` | Enable the OpenBAO web UI. | `bool` | `true` | + diff --git a/packages/apps/openbao/charts/cozy-lib b/packages/apps/openbao/charts/cozy-lib new file mode 120000 index 00000000..e1813509 --- /dev/null +++ b/packages/apps/openbao/charts/cozy-lib @@ -0,0 +1 @@ +../../../library/cozy-lib \ No newline at end of file diff --git a/packages/apps/openbao/logos/openbao.svg b/packages/apps/openbao/logos/openbao.svg new file mode 100644 index 00000000..5ce73b79 --- /dev/null +++ b/packages/apps/openbao/logos/openbao.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/packages/apps/openbao/templates/_resources.tpl b/packages/apps/openbao/templates/_resources.tpl new file mode 100644 index 00000000..7aeb976e --- /dev/null +++ b/packages/apps/openbao/templates/_resources.tpl @@ -0,0 +1,49 @@ +{{/* +Copyright Broadcom, Inc. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{/* vim: set filetype=mustache: */}} + +{{/* +Return a resource request/limit object based on a given preset. +These presets are for basic testing and not meant to be used in production +{{ include "resources.preset" (dict "type" "nano") -}} +*/}} +{{- define "resources.preset" -}} +{{- $presets := dict + "nano" (dict + "requests" (dict "cpu" "100m" "memory" "128Mi" "ephemeral-storage" "50Mi") + "limits" (dict "memory" "128Mi" "ephemeral-storage" "2Gi") + ) + "micro" (dict + "requests" (dict "cpu" "250m" "memory" "256Mi" "ephemeral-storage" "50Mi") + "limits" (dict "memory" "256Mi" "ephemeral-storage" "2Gi") + ) + "small" (dict + "requests" (dict "cpu" "500m" "memory" "512Mi" "ephemeral-storage" "50Mi") + "limits" (dict "memory" "512Mi" "ephemeral-storage" "2Gi") + ) + "medium" (dict + "requests" (dict "cpu" "500m" "memory" "1Gi" "ephemeral-storage" "50Mi") + "limits" (dict "memory" "1Gi" "ephemeral-storage" "2Gi") + ) + "large" (dict + "requests" (dict "cpu" "1" "memory" "2Gi" "ephemeral-storage" "50Mi") + "limits" (dict "memory" "2Gi" "ephemeral-storage" "2Gi") + ) + "xlarge" (dict + "requests" (dict "cpu" "2" "memory" "4Gi" "ephemeral-storage" "50Mi") + "limits" (dict "memory" "4Gi" "ephemeral-storage" "2Gi") + ) + "2xlarge" (dict + "requests" (dict "cpu" "4" "memory" "8Gi" "ephemeral-storage" "50Mi") + "limits" (dict "memory" "8Gi" "ephemeral-storage" "2Gi") + ) + }} +{{- if hasKey $presets .type -}} +{{- index $presets .type | toYaml -}} +{{- else -}} +{{- printf "ERROR: Preset key '%s' invalid. Allowed values are %s" .type (join "," (keys $presets)) | fail -}} +{{- end -}} +{{- end -}} diff --git a/packages/apps/openbao/templates/dashboard-resourcemap.yaml b/packages/apps/openbao/templates/dashboard-resourcemap.yaml new file mode 100644 index 00000000..452a07db --- /dev/null +++ b/packages/apps/openbao/templates/dashboard-resourcemap.yaml @@ -0,0 +1,31 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ .Release.Name }}-dashboard-resources +rules: +- apiGroups: + - "" + resources: + - services + resourceNames: + - {{ .Release.Name }} + - {{ .Release.Name }}-internal + verbs: ["get", "list", "watch"] +- apiGroups: + - cozystack.io + resources: + - workloadmonitors + resourceNames: + - {{ .Release.Name }} + verbs: ["get", "list", "watch"] +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ .Release.Name }}-dashboard-resources +subjects: +{{ include "cozy-lib.rbac.subjectsForTenantAndAccessLevel" (list "use" .Release.Namespace) }} +roleRef: + kind: Role + name: {{ .Release.Name }}-dashboard-resources + apiGroup: rbac.authorization.k8s.io diff --git a/packages/apps/openbao/templates/openbao.yaml b/packages/apps/openbao/templates/openbao.yaml new file mode 100644 index 00000000..daa6f9c4 --- /dev/null +++ b/packages/apps/openbao/templates/openbao.yaml @@ -0,0 +1,99 @@ +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: {{ .Release.Name }}-system + labels: + sharding.fluxcd.io/key: tenants +spec: + chartRef: + kind: ExternalArtifact + name: cozystack-openbao-application-default-openbao-system + namespace: cozy-system + interval: 5m + timeout: 10m + install: + remediation: + retries: -1 + upgrade: + force: true + remediation: + retries: -1 + valuesFrom: + - kind: Secret + name: cozystack-values + values: + openbao: + fullnameOverride: {{ .Release.Name }} + global: + tlsDisable: true + server: + podManagementPolicy: Parallel + resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.resourcesPreset .Values.resources $) | nindent 10 }} + dataStorage: + enabled: true + size: {{ .Values.size }} + {{- with .Values.storageClass }} + storageClass: {{ . }} + {{- end }} + {{- if gt (int .Values.replicas) 1 }} + standalone: + enabled: false + ha: + enabled: true + replicas: {{ .Values.replicas }} + raft: + enabled: true + setNodeId: true + config: | + ui = {{ .Values.ui }} + + listener "tcp" { + address = "[::]:8200" + cluster_address = "[::]:8201" + tls_disable = true + } + + storage "raft" { + path = "/openbao/data" + {{- range $i := until (int $.Values.replicas) }} + retry_join { + leader_api_addr = "http://{{ $.Release.Name }}-{{ $i }}.{{ $.Release.Name }}-internal:8200" + } + {{- end }} + } + + service_registration "kubernetes" {} + {{- else }} + standalone: + enabled: true + config: | + ui = {{ .Values.ui }} + + listener "tcp" { + address = "[::]:8200" + cluster_address = "[::]:8201" + tls_disable = true + } + + storage "file" { + path = "/openbao/data" + } + # Note: service_registration "kubernetes" {} is intentionally omitted + # in standalone mode — it requires an HA-capable storage backend and + # causes a fatal error with storage "file". + ha: + enabled: false + {{- end }} + {{- if .Values.external }} + service: + type: LoadBalancer + {{- end }} + ui: + enabled: {{ .Values.ui }} + {{- if .Values.external }} + serviceType: LoadBalancer + {{- end }} + injector: + enabled: false + csi: + enabled: false diff --git a/packages/apps/openbao/templates/workloadmonitor.yaml b/packages/apps/openbao/templates/workloadmonitor.yaml new file mode 100644 index 00000000..0a9acf76 --- /dev/null +++ b/packages/apps/openbao/templates/workloadmonitor.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: cozystack.io/v1alpha1 +kind: WorkloadMonitor +metadata: + name: {{ $.Release.Name }} +spec: + replicas: {{ .Values.replicas }} + minReplicas: 1 + kind: openbao + type: openbao + selector: + app.kubernetes.io/instance: {{ $.Release.Name }}-system + version: {{ $.Chart.Version }} diff --git a/packages/apps/openbao/values.schema.json b/packages/apps/openbao/values.schema.json new file mode 100644 index 00000000..b48f28a6 --- /dev/null +++ b/packages/apps/openbao/values.schema.json @@ -0,0 +1,87 @@ +{ + "title": "Chart Values", + "type": "object", + "properties": { + "external": { + "description": "Enable external access from outside the cluster.", + "type": "boolean", + "default": false + }, + "replicas": { + "description": "Number of OpenBAO replicas. HA with Raft is automatically enabled when replicas \u003e 1. Switching between standalone (file storage) and HA (Raft storage) modes requires data migration.", + "type": "integer", + "default": 1 + }, + "resources": { + "description": "Explicit CPU and memory configuration for each OpenBAO replica. When omitted, the preset defined in `resourcesPreset` is applied.", + "type": "object", + "default": {}, + "properties": { + "cpu": { + "description": "CPU available to each replica.", + "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 (RAM) available to each replica.", + "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 + } + } + }, + "resourcesPreset": { + "description": "Default sizing preset used when `resources` is omitted.", + "type": "string", + "default": "small", + "enum": [ + "nano", + "micro", + "small", + "medium", + "large", + "xlarge", + "2xlarge" + ] + }, + "size": { + "description": "Persistent Volume Claim size for data storage.", + "default": "10Gi", + "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 + }, + "storageClass": { + "description": "StorageClass used to store the data.", + "type": "string", + "default": "" + }, + "ui": { + "description": "Enable the OpenBAO web UI.", + "type": "boolean", + "default": true + } + } +} \ No newline at end of file diff --git a/packages/apps/openbao/values.yaml b/packages/apps/openbao/values.yaml new file mode 100644 index 00000000..fc8b75cc --- /dev/null +++ b/packages/apps/openbao/values.yaml @@ -0,0 +1,41 @@ +## +## @section Common parameters +## + +## @typedef {struct} Resources - Explicit CPU and memory configuration for each OpenBAO replica. +## @field {quantity} [cpu] - CPU available to each replica. +## @field {quantity} [memory] - Memory (RAM) available to each replica. + +## @enum {string} ResourcesPreset - Default sizing preset. +## @value nano +## @value micro +## @value small +## @value medium +## @value large +## @value xlarge +## @value 2xlarge + +## @param {int} replicas - Number of OpenBAO replicas. HA with Raft is automatically enabled when replicas > 1. Switching between standalone (file storage) and HA (Raft storage) modes requires data migration. +replicas: 1 + +## @param {Resources} [resources] - Explicit CPU and memory configuration for each OpenBAO replica. When omitted, the preset defined in `resourcesPreset` is applied. +resources: {} + +## @param {ResourcesPreset} resourcesPreset="small" - Default sizing preset used when `resources` is omitted. +resourcesPreset: "small" + +## @param {quantity} size - Persistent Volume Claim size for data storage. +size: 10Gi + +## @param {string} storageClass - StorageClass used to store the data. +storageClass: "" + +## @param {bool} external - Enable external access from outside the cluster. +external: false + +## +## @section Application-specific parameters +## + +## @param {bool} ui - Enable the OpenBAO web UI. +ui: true From 088bc0ffe2285bdbf783862d602baa9498fd15b8 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Tue, 17 Feb 2026 20:03:02 +0300 Subject: [PATCH 287/889] feat(openbao): add resource definition, PackageSource, and PaaS bundle entry Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- .../platform/sources/openbao-application.yaml | 29 +++++++++++++++ .../core/platform/templates/bundles/paas.yaml | 1 + packages/system/openbao-rd/Chart.yaml | 3 ++ packages/system/openbao-rd/Makefile | 4 ++ .../system/openbao-rd/cozyrds/openbao.yaml | 37 +++++++++++++++++++ .../system/openbao-rd/templates/cozyrd.yaml | 4 ++ packages/system/openbao-rd/values.yaml | 1 + 7 files changed, 79 insertions(+) create mode 100644 packages/core/platform/sources/openbao-application.yaml create mode 100644 packages/system/openbao-rd/Chart.yaml create mode 100644 packages/system/openbao-rd/Makefile create mode 100644 packages/system/openbao-rd/cozyrds/openbao.yaml create mode 100644 packages/system/openbao-rd/templates/cozyrd.yaml create mode 100644 packages/system/openbao-rd/values.yaml diff --git a/packages/core/platform/sources/openbao-application.yaml b/packages/core/platform/sources/openbao-application.yaml new file mode 100644 index 00000000..438148af --- /dev/null +++ b/packages/core/platform/sources/openbao-application.yaml @@ -0,0 +1,29 @@ +--- +apiVersion: cozystack.io/v1alpha1 +kind: PackageSource +metadata: + name: cozystack.openbao-application +spec: + sourceRef: + kind: OCIRepository + name: cozystack-packages + namespace: cozy-system + path: / + variants: + - name: default + dependsOn: + - cozystack.networking + libraries: + - name: cozy-lib + path: library/cozy-lib + components: + - name: openbao-system + path: system/openbao + - name: openbao + path: apps/openbao + libraries: ["cozy-lib"] + - name: openbao-rd + path: system/openbao-rd + install: + namespace: cozy-system + releaseName: openbao-rd diff --git a/packages/core/platform/templates/bundles/paas.yaml b/packages/core/platform/templates/bundles/paas.yaml index 5a478f9d..80eb9824 100644 --- a/packages/core/platform/templates/bundles/paas.yaml +++ b/packages/core/platform/templates/bundles/paas.yaml @@ -15,6 +15,7 @@ {{include "cozystack.platform.package.default" (list "cozystack.mariadb-application" $) }} {{include "cozystack.platform.package.default" (list "cozystack.mongodb-application" $) }} {{include "cozystack.platform.package.default" (list "cozystack.nats-application" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.openbao-application" $) }} {{include "cozystack.platform.package.default" (list "cozystack.postgres-application" $) }} {{include "cozystack.platform.package.default" (list "cozystack.qdrant-application" $) }} {{include "cozystack.platform.package.default" (list "cozystack.rabbitmq-application" $) }} diff --git a/packages/system/openbao-rd/Chart.yaml b/packages/system/openbao-rd/Chart.yaml new file mode 100644 index 00000000..f93484c3 --- /dev/null +++ b/packages/system/openbao-rd/Chart.yaml @@ -0,0 +1,3 @@ +apiVersion: v2 +name: openbao-rd +version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process diff --git a/packages/system/openbao-rd/Makefile b/packages/system/openbao-rd/Makefile new file mode 100644 index 00000000..3ad0f7ba --- /dev/null +++ b/packages/system/openbao-rd/Makefile @@ -0,0 +1,4 @@ +export NAME=openbao-rd +export NAMESPACE=cozy-system + +include ../../../hack/package.mk diff --git a/packages/system/openbao-rd/cozyrds/openbao.yaml b/packages/system/openbao-rd/cozyrds/openbao.yaml new file mode 100644 index 00000000..9c20776e --- /dev/null +++ b/packages/system/openbao-rd/cozyrds/openbao.yaml @@ -0,0 +1,37 @@ +apiVersion: cozystack.io/v1alpha1 +kind: ApplicationDefinition +metadata: + name: openbao +spec: + application: + kind: OpenBAO + plural: openbaos + singular: openbao + openAPISchema: |- + {"title":"Chart Values","type":"object","properties":{"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"replicas":{"description":"Number of OpenBAO replicas. HA with Raft is automatically enabled when replicas > 1. Switching between standalone (file storage) and HA (Raft storage) modes requires data migration.","type":"integer","default":1},"resources":{"description":"Explicit CPU and memory configuration for each OpenBAO replica. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available to each replica.","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 (RAM) available to each replica.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"small","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]},"size":{"description":"Persistent Volume Claim size for data storage.","default":"10Gi","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},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":""},"ui":{"description":"Enable the OpenBAO web UI.","type":"boolean","default":true}}} + release: + prefix: openbao- + labels: + sharding.fluxcd.io/key: tenants + chartRef: + kind: ExternalArtifact + name: cozystack-openbao-application-default-openbao + namespace: cozy-system + dashboard: + category: PaaS + singular: OpenBAO + plural: OpenBAO + description: Managed OpenBAO secrets management service + tags: + - secrets + icon: PHN2ZyB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgdmlld0JveD0iMCAwIDE0NCAxNDQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNDQiIGhlaWdodD0iMTQ0IiByeD0iMjQiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcikiLz4KPHJlY3Qgd2lkdGg9IjE0NCIgaGVpZ2h0PSIxNDQiIHJ4PSIyNCIgZmlsbD0iYmxhY2siIGZpbGwtb3BhY2l0eT0iMC4zIi8+CjxwYXRoIGZpbGwtcnVsZT0iZXZlbm9kZCIgY2xpcC1ydWxlPSJldmVub2RkIiBkPSJNNzIgMzBDNTMuMjIyIDMwIDM4IDQ1LjIyMiAzOCA2NHY4Yy0zLjMxNCAwLTYgMi42ODYtNiA2djMwYzAgMy4zMTQgMi42ODYgNiA2IDZoNjhjMy4zMTQgMCA2LTIuNjg2IDYtNlY3OGMwLTMuMzE0LTIuNjg2LTYtNi02di04QzEwNiA0NS4yMjIgOTAuNzc4IDMwIDcyIDMwem0tOCA0MnYtOGMwLTQuNDE4IDMuNTgyLTggOC04czggMy41ODIgOCA4djhINjR6bTI2IDB2LThjMC04LjgzNy03LjE2My0xNi0xNi0xNnMtMTYgNy4xNjMtMTYgMTZ2OGgtMnYyOGg2MFY3Mkg5MHptLTIyIDE0YTQgNCAwIDExOCAwIDQgNCAwIDAxLTggMHptNC04YTggOCAwIDEwMCAxNiA4IDggMCAwMDAtMTZ6IiBmaWxsPSJ3aGl0ZSIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyIiB4MT0iMTAiIHkxPSIxNS41IiB4Mj0iMTQ0IiB5Mj0iMTMxLjUiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iIzg3ZDZiZSIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiM3OWMwYWIiLz4KPC9saW5lYXJHcmFkaWVudD4KPC9kZWZzPgo8L3N2Zz4K + keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "replicas"], ["spec", "resources"], ["spec", "resourcesPreset"], ["spec", "size"], ["spec", "storageClass"], ["spec", "external"], ["spec", "ui"]] + secrets: + exclude: [] + include: [] + services: + exclude: [] + include: + - resourceNames: + - openbao-{{ .name }} + - openbao-{{ .name }}-internal diff --git a/packages/system/openbao-rd/templates/cozyrd.yaml b/packages/system/openbao-rd/templates/cozyrd.yaml new file mode 100644 index 00000000..e079ddcf --- /dev/null +++ b/packages/system/openbao-rd/templates/cozyrd.yaml @@ -0,0 +1,4 @@ +{{- range $path, $_ := .Files.Glob "cozyrds/*" }} +--- +{{ $.Files.Get $path }} +{{- end }} diff --git a/packages/system/openbao-rd/values.yaml b/packages/system/openbao-rd/values.yaml new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/packages/system/openbao-rd/values.yaml @@ -0,0 +1 @@ +{} From dd4723386fcbb4041d6bb6bef144c5c8597e55ac Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Tue, 17 Feb 2026 21:23:27 +0300 Subject: [PATCH 288/889] test(openbao): add E2E test for standalone mode Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- hack/e2e-apps/openbao.bats | 59 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 hack/e2e-apps/openbao.bats diff --git a/hack/e2e-apps/openbao.bats b/hack/e2e-apps/openbao.bats new file mode 100644 index 00000000..cd9d47d3 --- /dev/null +++ b/hack/e2e-apps/openbao.bats @@ -0,0 +1,59 @@ +#!/usr/bin/env bats + +@test "Create OpenBAO (standalone)" { + name='test' + kubectl apply -f- </dev/null | grep -q true; do sleep 5; done"; then + echo "=== DEBUG: Container did not start in time ===" >&2 + kubectl -n tenant-test describe pod openbao-$name-0 >&2 || true + kubectl -n tenant-test logs openbao-$name-0 --previous >&2 || true + kubectl -n tenant-test logs openbao-$name-0 >&2 || true + return 1 + fi + + # Wait for OpenBAO API to accept connections + # bao status exit codes: 0 = unsealed, 1 = error/not ready, 2 = sealed but responsive + if ! timeout 60 sh -ec "until kubectl -n tenant-test exec openbao-$name-0 -- bao status >/dev/null 2>&1; rc=\$?; test \$rc -eq 0 -o \$rc -eq 2; do sleep 3; done"; then + echo "=== DEBUG: OpenBAO API did not become responsive ===" >&2 + kubectl -n tenant-test describe pod openbao-$name-0 >&2 || true + kubectl -n tenant-test logs openbao-$name-0 --previous >&2 || true + kubectl -n tenant-test logs openbao-$name-0 >&2 || true + return 1 + fi + + # Initialize OpenBAO (single key share for testing simplicity) + init_output=$(kubectl -n tenant-test exec openbao-$name-0 -- bao operator init -key-shares=1 -key-threshold=1 -format=json) + unseal_key=$(echo "$init_output" | jq -r '.unseal_keys_b64[0]') + if [ -z "$unseal_key" ] || [ "$unseal_key" = "null" ]; then + echo "Failed to extract unseal key. Init output: $init_output" >&2 + return 1 + fi + + # Unseal OpenBAO + kubectl -n tenant-test exec openbao-$name-0 -- bao operator unseal "$unseal_key" + + # Now wait for pod to become ready (readiness probe checks seal status) + kubectl -n tenant-test wait sts openbao-$name --timeout=90s --for=jsonpath='{.status.readyReplicas}'=1 + kubectl -n tenant-test wait pvc data-openbao-$name-0 --timeout=50s --for=jsonpath='{.status.phase}'=Bound + kubectl -n tenant-test delete openbao.apps.cozystack.io $name + kubectl -n tenant-test delete pvc data-openbao-$name-0 --ignore-not-found +} From 8cc8e52d157f9b9116b2f4c84650000b77f9904b Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 17 Feb 2026 19:31:57 +0100 Subject: [PATCH 289/889] chore(platform): remove standalone kilo PackageSource Kilo is now integrated into the cilium-kilo networking variant instead of being a separate package that users install manually. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- packages/core/platform/sources/kilo.yaml | 35 ------------------------ 1 file changed, 35 deletions(-) delete mode 100644 packages/core/platform/sources/kilo.yaml diff --git a/packages/core/platform/sources/kilo.yaml b/packages/core/platform/sources/kilo.yaml deleted file mode 100644 index 95770667..00000000 --- a/packages/core/platform/sources/kilo.yaml +++ /dev/null @@ -1,35 +0,0 @@ ---- -apiVersion: cozystack.io/v1alpha1 -kind: PackageSource -metadata: - name: cozystack.kilo -spec: - sourceRef: - kind: OCIRepository - name: cozystack-packages - namespace: cozy-system - path: / - variants: - - name: default - dependsOn: - - cozystack.networking - components: - - name: kilo - path: system/kilo - install: - privileged: true - namespace: cozy-kilo - releaseName: kilo - - name: cilium - dependsOn: - - cozystack.networking - components: - - name: kilo - path: system/kilo - valuesFiles: - - values.yaml - - values-cilium.yaml - install: - privileged: true - namespace: cozy-kilo - releaseName: kilo From 536766cffcf4fcebad5ef28e4ad21991ff5c2c3d Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 17 Feb 2026 19:32:19 +0100 Subject: [PATCH 290/889] feat(platform): add cilium-kilo networking variant Add a new networking variant that integrates Kilo with Cilium pre-configured. Cilium is deployed with host firewall disabled and enable-ipip-termination enabled, which are required for correct IPIP encapsulation through Cilium's overlay. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- .../core/platform/sources/networking.yaml | 25 +++++++++++++++++++ packages/system/cilium/values-kilo.yaml | 5 ++++ 2 files changed, 30 insertions(+) create mode 100644 packages/system/cilium/values-kilo.yaml diff --git a/packages/core/platform/sources/networking.yaml b/packages/core/platform/sources/networking.yaml index 9694dcc3..8e8485b3 100644 --- a/packages/core/platform/sources/networking.yaml +++ b/packages/core/platform/sources/networking.yaml @@ -33,6 +33,31 @@ spec: releaseName: cilium-networkpolicy dependsOn: - cilium + - name: cilium-kilo + dependsOn: [] + components: + - name: cilium + path: system/cilium + valuesFiles: + - values.yaml + - values-talos.yaml + - values-kilo.yaml + install: + privileged: true + namespace: cozy-cilium + releaseName: cilium + dependsOn: [] + - name: kilo + path: system/kilo + valuesFiles: + - values.yaml + - values-cilium.yaml + install: + privileged: true + namespace: cozy-kilo + releaseName: kilo + dependsOn: + - cilium # Generic Cilium variant for non-Talos clusters (kubeadm, k3s, RKE2, etc.) - name: cilium-generic dependsOn: [] diff --git a/packages/system/cilium/values-kilo.yaml b/packages/system/cilium/values-kilo.yaml new file mode 100644 index 00000000..c7e98d2a --- /dev/null +++ b/packages/system/cilium/values-kilo.yaml @@ -0,0 +1,5 @@ +cilium: + hostFirewall: + enabled: false + extraConfig: + enable-ipip-termination: "true" From 96ba3b9ca5df595d3dbc2f499e29ef763db4f2ef Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 17 Feb 2026 20:14:34 +0100 Subject: [PATCH 291/889] fix(kilo): remove service-cidr option from chart The --service-cidr flag prevents masquerading for service IPs, but service CIDRs are cluster-local and not useful for multi-location mesh routing. Remove the serviceCIDR value and its template usage. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- packages/system/kilo/templates/kilo.yaml | 3 --- packages/system/kilo/values.yaml | 5 ++--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/system/kilo/templates/kilo.yaml b/packages/system/kilo/templates/kilo.yaml index fef7a718..a0af620a 100644 --- a/packages/system/kilo/templates/kilo.yaml +++ b/packages/system/kilo/templates/kilo.yaml @@ -38,9 +38,6 @@ spec: {{- with .Values.kilo.podCIDR }} - --service-cidr={{ . }} {{- end }} - {{- with .Values.kilo.serviceCIDR }} - - --service-cidr={{ . }} - {{- end }} {{- with .Values.kilo.transitCIDR }} - --subnet={{ . }} {{- end }} diff --git a/packages/system/kilo/values.yaml b/packages/system/kilo/values.yaml index 4057c284..939fd222 100644 --- a/packages/system/kilo/values.yaml +++ b/packages/system/kilo/values.yaml @@ -1,10 +1,9 @@ kilo: image: pullPolicy: IfNotPresent - tag: v0.8.1@sha256:56602fa796eccea0d0e005c29e5c7094ae46d15bd5306b02b829672b178dcd5c - repository: ghcr.io/cozystack/cozystack/kilo + tag: kilo-require-cilium-tunl + repository: ghcr.io/kvaps/test podCIDR: 10.244.0.0/16 - serviceCIDR: 10.96.0.0/16 transitCIDR: 100.66.0.0/16 meshGranularity: location cleanUpInterface: false From bf1e49d34bad47afeb6d25ff9350472aa1fbd6e6 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 17 Feb 2026 20:16:01 +0100 Subject: [PATCH 292/889] fix(kilo): remove podCIDR passed as --service-cidr The podCIDR was incorrectly passed as --service-cidr to prevent masquerading on pod traffic. This is unnecessary for multi-location mesh and was a leftover from single-cluster assumptions. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- packages/system/kilo/templates/kilo.yaml | 3 --- packages/system/kilo/values.yaml | 1 - 2 files changed, 4 deletions(-) diff --git a/packages/system/kilo/templates/kilo.yaml b/packages/system/kilo/templates/kilo.yaml index a0af620a..ceac643d 100644 --- a/packages/system/kilo/templates/kilo.yaml +++ b/packages/system/kilo/templates/kilo.yaml @@ -35,9 +35,6 @@ spec: - --encapsulate=crosssubnet - --local=false - --mesh-granularity={{ .Values.kilo.meshGranularity | default "location" }} - {{- with .Values.kilo.podCIDR }} - - --service-cidr={{ . }} - {{- end }} {{- with .Values.kilo.transitCIDR }} - --subnet={{ . }} {{- end }} diff --git a/packages/system/kilo/values.yaml b/packages/system/kilo/values.yaml index 939fd222..5ffd5da0 100644 --- a/packages/system/kilo/values.yaml +++ b/packages/system/kilo/values.yaml @@ -3,7 +3,6 @@ kilo: pullPolicy: IfNotPresent tag: kilo-require-cilium-tunl repository: ghcr.io/kvaps/test - podCIDR: 10.244.0.0/16 transitCIDR: 100.66.0.0/16 meshGranularity: location cleanUpInterface: false From b3b73071057d3dce856f21b60eca44e9c1b12123 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 17 Feb 2026 22:14:52 +0100 Subject: [PATCH 293/889] fix(kilo): use official kilo image and clean up cilium-kilo config Switch kilo image to official ghcr.io/cozystack/cozystack/kilo:v0.8.2, remove unnecessary enable-ipip-termination from cilium-kilo values, and update platform source digest. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- packages/system/cilium/values-kilo.yaml | 2 -- packages/system/kilo/values.yaml | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/system/cilium/values-kilo.yaml b/packages/system/cilium/values-kilo.yaml index c7e98d2a..c303ef0a 100644 --- a/packages/system/cilium/values-kilo.yaml +++ b/packages/system/cilium/values-kilo.yaml @@ -1,5 +1,3 @@ cilium: hostFirewall: enabled: false - extraConfig: - enable-ipip-termination: "true" diff --git a/packages/system/kilo/values.yaml b/packages/system/kilo/values.yaml index 5ffd5da0..d7c00af4 100644 --- a/packages/system/kilo/values.yaml +++ b/packages/system/kilo/values.yaml @@ -1,8 +1,8 @@ kilo: image: pullPolicy: IfNotPresent - tag: kilo-require-cilium-tunl - repository: ghcr.io/kvaps/test + tag: v0.8.2@sha256:9f27000ffd0bbf9adf3aefd017b7835dfae57b6ea38bf7488fb636536c2467da + repository: ghcr.io/cozystack/cozystack/kilo transitCIDR: 100.66.0.0/16 meshGranularity: location cleanUpInterface: false From 153d2c48ae7215eb3b793b5ef7b2027f39edc5a9 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Mon, 16 Feb 2026 22:24:29 +0300 Subject: [PATCH 294/889] refactor(e2e): use helm install instead of kubectl apply for cozystack installation Replace pre-rendered static YAML application with direct helm chart installation in e2e tests. The chart directory with correct values is already present in the sandbox after pr.patch application. - Remove CRD/operator artifact upload/download from CI workflow - Remove copy-installer-manifest target from testing Makefile - Use helm upgrade --install from local chart in e2e-install-cozystack.bats Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- .github/workflows/pull-requests.yaml | 40 +--------------------------- hack/e2e-install-cozystack.bats | 25 ++++++++--------- packages/core/testing/Makefile | 8 ++---- 3 files changed, 14 insertions(+), 59 deletions(-) diff --git a/.github/workflows/pull-requests.yaml b/.github/workflows/pull-requests.yaml index edc8b923..b9b7b0ac 100644 --- a/.github/workflows/pull-requests.yaml +++ b/.github/workflows/pull-requests.yaml @@ -71,18 +71,6 @@ jobs: name: pr-patch path: _out/assets/pr.patch - - name: Upload CRDs - uses: actions/upload-artifact@v4 - with: - name: cozystack-crds - path: _out/assets/cozystack-crds.yaml - - - name: Upload operator - uses: actions/upload-artifact@v4 - with: - name: cozystack-operator - path: _out/assets/cozystack-operator-talos.yaml - - name: Upload Talos image uses: actions/upload-artifact@v4 with: @@ -94,8 +82,6 @@ jobs: runs-on: ubuntu-latest if: contains(github.event.pull_request.labels.*.name, 'release') outputs: - crds_id: ${{ steps.fetch_assets.outputs.crds_id }} - operator_id: ${{ steps.fetch_assets.outputs.operator_id }} disk_id: ${{ steps.fetch_assets.outputs.disk_id }} steps: @@ -139,15 +125,11 @@ jobs: return; } const find = (n) => draft.assets.find(a => a.name === n)?.id; - const crdsId = find('cozystack-crds.yaml'); - const operatorId = find('cozystack-operator-talos.yaml'); const diskId = find('nocloud-amd64.raw.xz'); - if (!crdsId || !operatorId || !diskId) { + if (!diskId) { core.setFailed('Required assets missing in draft release'); return; } - core.setOutput('crds_id', crdsId); - core.setOutput('operator_id', operatorId); core.setOutput('disk_id', diskId); @@ -174,20 +156,6 @@ jobs: name: talos-image path: _out/assets - - name: "Download CRDs (regular PR)" - if: "!contains(github.event.pull_request.labels.*.name, 'release')" - uses: actions/download-artifact@v4 - with: - name: cozystack-crds - path: _out/assets - - - name: "Download operator (regular PR)" - if: "!contains(github.event.pull_request.labels.*.name, 'release')" - uses: actions/download-artifact@v4 - with: - name: cozystack-operator - path: _out/assets - - name: Download PR patch if: "!contains(github.event.pull_request.labels.*.name, 'release')" uses: actions/download-artifact@v4 @@ -208,12 +176,6 @@ jobs: curl -sSL -H "Authorization: token ${GH_PAT}" -H "Accept: application/octet-stream" \ -o _out/assets/nocloud-amd64.raw.xz \ "https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/assets/${{ needs.resolve_assets.outputs.disk_id }}" - curl -sSL -H "Authorization: token ${GH_PAT}" -H "Accept: application/octet-stream" \ - -o _out/assets/cozystack-crds.yaml \ - "https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/assets/${{ needs.resolve_assets.outputs.crds_id }}" - curl -sSL -H "Authorization: token ${GH_PAT}" -H "Accept: application/octet-stream" \ - -o _out/assets/cozystack-operator-talos.yaml \ - "https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/assets/${{ needs.resolve_assets.outputs.operator_id }}" env: GH_PAT: ${{ secrets.GH_PAT }} diff --git a/hack/e2e-install-cozystack.bats b/hack/e2e-install-cozystack.bats index 1abb691f..36329e93 100644 --- a/hack/e2e-install-cozystack.bats +++ b/hack/e2e-install-cozystack.bats @@ -1,25 +1,22 @@ #!/usr/bin/env bats -@test "Required installer assets exist" { - if [ ! -f _out/assets/cozystack-crds.yaml ]; then - echo "Missing: _out/assets/cozystack-crds.yaml" >&2 - exit 1 - fi - if [ ! -f _out/assets/cozystack-operator-talos.yaml ]; then - echo "Missing: _out/assets/cozystack-operator-talos.yaml" >&2 +@test "Required installer chart exists" { + if [ ! -f packages/core/installer/Chart.yaml ]; then + echo "Missing: packages/core/installer/Chart.yaml" >&2 exit 1 fi } @test "Install Cozystack" { - # Create namespace - kubectl create namespace cozy-system --dry-run=client -o yaml | kubectl apply -f - + # Install cozy-installer chart + helm upgrade installer packages/core/installer \ + --install \ + --namespace cozy-system \ + --create-namespace \ + --wait \ + --timeout 2m - # Apply installer manifests (CRDs + operator) - kubectl apply -f _out/assets/cozystack-crds.yaml - kubectl apply -f _out/assets/cozystack-operator-talos.yaml - - # Wait for the operator deployment to become available + # Verify the operator deployment is available kubectl wait deployment/cozystack-operator -n cozy-system --timeout=1m --for=condition=Available # Create platform Package with isp-full variant diff --git a/packages/core/testing/Makefile b/packages/core/testing/Makefile index 2fb41eba..35897909 100644 --- a/packages/core/testing/Makefile +++ b/packages/core/testing/Makefile @@ -30,17 +30,13 @@ test: test-cluster test-openapi test-apps ## Run the end-to-end tests in existin copy-nocloud-image: docker cp ../../../_out/assets/nocloud-amd64.raw.xz "${SANDBOX_NAME}":/workspace/_out/assets/nocloud-amd64.raw.xz -copy-installer-manifest: - docker cp ../../../_out/assets/cozystack-crds.yaml "${SANDBOX_NAME}":/workspace/_out/assets/cozystack-crds.yaml - docker cp ../../../_out/assets/cozystack-operator-talos.yaml "${SANDBOX_NAME}":/workspace/_out/assets/cozystack-operator-talos.yaml - prepare-cluster: copy-nocloud-image docker exec "${SANDBOX_NAME}" sh -c 'cd /workspace && hack/cozytest.sh hack/e2e-prepare-cluster.bats' -install-cozystack: copy-installer-manifest +install-cozystack: docker exec "${SANDBOX_NAME}" sh -c 'cd /workspace && hack/cozytest.sh hack/e2e-install-cozystack.bats' -test-cluster: copy-nocloud-image copy-installer-manifest ## Run the end-to-end for creating a cluster +test-cluster: copy-nocloud-image ## Run the end-to-end for creating a cluster docker exec "${SANDBOX_NAME}" sh -c 'cd /workspace && hack/cozytest.sh hack/e2e-cluster.bats' test-openapi: From 58dfc972019ee9db5593eb8ed3161733b8d3775f Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Mon, 16 Feb 2026 22:52:17 +0300 Subject: [PATCH 295/889] fix(e2e): apply CRDs before helm install to resolve dependency ordering Helm cannot validate PackageSource CR during install because the CRD is part of the same chart. Pre-apply CRDs via helm template + kubectl apply --server-side before running helm upgrade --install. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- hack/e2e-install-cozystack.bats | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/hack/e2e-install-cozystack.bats b/hack/e2e-install-cozystack.bats index 36329e93..f98c1266 100644 --- a/hack/e2e-install-cozystack.bats +++ b/hack/e2e-install-cozystack.bats @@ -8,6 +8,12 @@ } @test "Install Cozystack" { + # Install CRDs first (PackageSource CR in the chart depends on them) + helm template installer packages/core/installer \ + --namespace cozy-system \ + --show-only templates/crds.yaml \ + | kubectl apply --server-side -f - + # Install cozy-installer chart helm upgrade installer packages/core/installer \ --install \ From 55cd8fc0e1ddc945d0d2523956646688c6804ecb Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Mon, 16 Feb 2026 23:04:03 +0300 Subject: [PATCH 296/889] refactor(installer): move CRDs to crds/ directory for proper Helm install ordering Helm installs crds/ contents before processing templates, resolving the chicken-and-egg problem where PackageSource CR validation fails because its CRD hasn't been registered yet. - Move definitions/ to crds/ in the installer chart - Remove templates/crds.yaml (Helm auto-installs from crds/) - Update codegen script to write CRDs to crds/ - Replace helm template with cat for static CRD manifest generation - Remove pre-apply CRD workaround from e2e test Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- Makefile | 4 +--- hack/e2e-install-cozystack.bats | 8 +------- hack/update-codegen.sh | 2 +- packages/core/installer/.helmignore | 2 -- .../core/installer/{definitions => crds}/.gitattributes | 0 .../{definitions => crds}/cozystack.io_packages.yaml | 0 .../cozystack.io_packagesources.yaml | 0 packages/core/installer/templates/crds.yaml | 3 --- 8 files changed, 3 insertions(+), 16 deletions(-) rename packages/core/installer/{definitions => crds}/.gitattributes (100%) rename packages/core/installer/{definitions => crds}/cozystack.io_packages.yaml (100%) rename packages/core/installer/{definitions => crds}/cozystack.io_packagesources.yaml (100%) delete mode 100644 packages/core/installer/templates/crds.yaml diff --git a/Makefile b/Makefile index 12554861..dd6a249d 100644 --- a/Makefile +++ b/Makefile @@ -38,9 +38,7 @@ build: build-deps manifests: mkdir -p _out/assets - helm template installer packages/core/installer -n cozy-system \ - -s templates/crds.yaml \ - > _out/assets/cozystack-crds.yaml + cat packages/core/installer/crds/*.yaml > _out/assets/cozystack-crds.yaml # Talos variant (default) helm template installer packages/core/installer -n cozy-system \ -s templates/cozystack-operator.yaml \ diff --git a/hack/e2e-install-cozystack.bats b/hack/e2e-install-cozystack.bats index f98c1266..5e518cad 100644 --- a/hack/e2e-install-cozystack.bats +++ b/hack/e2e-install-cozystack.bats @@ -8,13 +8,7 @@ } @test "Install Cozystack" { - # Install CRDs first (PackageSource CR in the chart depends on them) - helm template installer packages/core/installer \ - --namespace cozy-system \ - --show-only templates/crds.yaml \ - | kubectl apply --server-side -f - - - # Install cozy-installer chart + # Install cozy-installer chart (CRDs from crds/ are applied automatically) helm upgrade installer packages/core/installer \ --install \ --namespace cozy-system \ diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh index 293f229b..d0711cad 100755 --- a/hack/update-codegen.sh +++ b/hack/update-codegen.sh @@ -24,7 +24,7 @@ API_KNOWN_VIOLATIONS_DIR="${API_KNOWN_VIOLATIONS_DIR:-"${SCRIPT_ROOT}/api/api-ru 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) -OPERATOR_CRDDIR=packages/core/installer/definitions +OPERATOR_CRDDIR=packages/core/installer/crds COZY_CONTROLLER_CRDDIR=packages/system/cozystack-controller/definitions COZY_RD_CRDDIR=packages/system/application-definition-crd/definition BACKUPS_CORE_CRDDIR=packages/system/backup-controller/definitions diff --git a/packages/core/installer/.helmignore b/packages/core/installer/.helmignore index ba88ffe8..6901ff3d 100644 --- a/packages/core/installer/.helmignore +++ b/packages/core/installer/.helmignore @@ -7,5 +7,3 @@ Makefile images/ example/ *.tgz - -# NOTE: definitions/ is intentionally NOT excluded — needed by crds.yaml via .Files.Glob diff --git a/packages/core/installer/definitions/.gitattributes b/packages/core/installer/crds/.gitattributes similarity index 100% rename from packages/core/installer/definitions/.gitattributes rename to packages/core/installer/crds/.gitattributes diff --git a/packages/core/installer/definitions/cozystack.io_packages.yaml b/packages/core/installer/crds/cozystack.io_packages.yaml similarity index 100% rename from packages/core/installer/definitions/cozystack.io_packages.yaml rename to packages/core/installer/crds/cozystack.io_packages.yaml diff --git a/packages/core/installer/definitions/cozystack.io_packagesources.yaml b/packages/core/installer/crds/cozystack.io_packagesources.yaml similarity index 100% rename from packages/core/installer/definitions/cozystack.io_packagesources.yaml rename to packages/core/installer/crds/cozystack.io_packagesources.yaml diff --git a/packages/core/installer/templates/crds.yaml b/packages/core/installer/templates/crds.yaml deleted file mode 100644 index 7c7ea584..00000000 --- a/packages/core/installer/templates/crds.yaml +++ /dev/null @@ -1,3 +0,0 @@ -{{- range $path, $_ := .Files.Glob "definitions/*.yaml" }} -{{ $.Files.Get $path }} -{{- end }} From d8dd5adbe020e5e35da3ab5c6950c8ad03e3701e Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Mon, 16 Feb 2026 23:13:33 +0300 Subject: [PATCH 297/889] fix(testing): remove broken test-cluster target The test-cluster target references non-existent hack/e2e-cluster.bats file. Remove it and its dependency from the test target. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/core/testing/Makefile | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/core/testing/Makefile b/packages/core/testing/Makefile index 35897909..ce21a8e1 100644 --- a/packages/core/testing/Makefile +++ b/packages/core/testing/Makefile @@ -25,7 +25,7 @@ image-e2e-sandbox: yq -i '.e2e.image = strenv(IMAGE)' values.yaml rm -f images/e2e-sandbox.json -test: test-cluster test-openapi test-apps ## Run the end-to-end tests in existing sandbox +test: test-openapi test-apps ## Run the end-to-end tests in existing sandbox copy-nocloud-image: docker cp ../../../_out/assets/nocloud-amd64.raw.xz "${SANDBOX_NAME}":/workspace/_out/assets/nocloud-amd64.raw.xz @@ -36,9 +36,6 @@ prepare-cluster: copy-nocloud-image install-cozystack: docker exec "${SANDBOX_NAME}" sh -c 'cd /workspace && hack/cozytest.sh hack/e2e-install-cozystack.bats' -test-cluster: copy-nocloud-image ## Run the end-to-end for creating a cluster - docker exec "${SANDBOX_NAME}" sh -c 'cd /workspace && hack/cozytest.sh hack/e2e-cluster.bats' - test-openapi: docker exec "${SANDBOX_NAME}" sh -c 'cd /workspace && hack/cozytest.sh hack/e2e-test-openapi.bats' From 1ddbe68bc2c28c3990af8d8d22d6309731437130 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Tue, 17 Feb 2026 23:23:10 +0300 Subject: [PATCH 298/889] feat(operator): add --install-crds flag with embedded CRD manifests Embed Package and PackageSource CRDs in the operator binary using Go embed, following the same pattern as --install-flux. The operator applies CRDs at startup using server-side apply, ensuring they are updated on every operator restart/upgrade. This addresses the CRD lifecycle concern: Helm crds/ directory handles initial install, while the operator manages updates on subsequent deployments. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- cmd/cozystack-operator/main.go | 16 ++ internal/crdinstall/install.go | 144 ++++++++++ internal/crdinstall/manifests.embed.go | 50 ++++ internal/crdinstall/manifests/.gitattributes | 1 + .../manifests/cozystack.io_packages.yaml | 171 ++++++++++++ .../cozystack.io_packagesources.yaml | 250 ++++++++++++++++++ 6 files changed, 632 insertions(+) create mode 100644 internal/crdinstall/install.go create mode 100644 internal/crdinstall/manifests.embed.go create mode 100644 internal/crdinstall/manifests/.gitattributes create mode 100644 internal/crdinstall/manifests/cozystack.io_packages.yaml create mode 100644 internal/crdinstall/manifests/cozystack.io_packagesources.yaml diff --git a/cmd/cozystack-operator/main.go b/cmd/cozystack-operator/main.go index 188ce160..f365a076 100644 --- a/cmd/cozystack-operator/main.go +++ b/cmd/cozystack-operator/main.go @@ -50,6 +50,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook" "github.com/cozystack/cozystack/internal/cozyvaluesreplicator" + "github.com/cozystack/cozystack/internal/crdinstall" "github.com/cozystack/cozystack/internal/fluxinstall" "github.com/cozystack/cozystack/internal/operator" "github.com/cozystack/cozystack/internal/telemetry" @@ -77,6 +78,7 @@ func main() { var probeAddr string var secureMetrics bool var enableHTTP2 bool + var installCRDs bool var installFlux bool var disableTelemetry bool var telemetryEndpoint string @@ -97,6 +99,7 @@ func main() { "If set the metrics endpoint is served securely") flag.BoolVar(&enableHTTP2, "enable-http2", false, "If set, HTTP/2 will be enabled for the metrics and webhook servers") + flag.BoolVar(&installCRDs, "install-crds", false, "Install Cozystack CRDs before starting reconcile loop") flag.BoolVar(&installFlux, "install-flux", false, "Install Flux components before starting reconcile loop") flag.BoolVar(&disableTelemetry, "disable-telemetry", false, "Disable telemetry collection") @@ -177,6 +180,19 @@ func main() { os.Exit(1) } + // Install Cozystack CRDs before starting reconcile loop + if installCRDs { + setupLog.Info("Installing Cozystack CRDs before starting reconcile loop") + installCtx, installCancel := context.WithTimeout(context.Background(), 2*time.Minute) + defer installCancel() + + if err := crdinstall.Install(installCtx, directClient, crdinstall.WriteEmbeddedManifests); err != nil { + setupLog.Error(err, "failed to install CRDs") + os.Exit(1) + } + setupLog.Info("CRD installation completed successfully") + } + // Install Flux before starting reconcile loop if installFlux { setupLog.Info("Installing Flux components before starting reconcile loop") diff --git a/internal/crdinstall/install.go b/internal/crdinstall/install.go new file mode 100644 index 00000000..fb6e4588 --- /dev/null +++ b/internal/crdinstall/install.go @@ -0,0 +1,144 @@ +/* +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. +*/ + +package crdinstall + +import ( + "bufio" + "bytes" + "context" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + k8syaml "k8s.io/apimachinery/pkg/util/yaml" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" +) + +// Install applies Cozystack CRDs using embedded manifests. +// It extracts the manifests and applies them to the cluster using server-side apply. +func Install(ctx context.Context, k8sClient client.Client, writeEmbeddedManifests func(string) error) error { + logger := log.FromContext(ctx) + + tmpDir, err := os.MkdirTemp("", "crd-install-*") + if err != nil { + return fmt.Errorf("failed to create temp directory: %w", err) + } + defer os.RemoveAll(tmpDir) + + manifestsDir := filepath.Join(tmpDir, "manifests") + if err := os.MkdirAll(manifestsDir, 0755); err != nil { + return fmt.Errorf("failed to create manifests directory: %w", err) + } + + if err := writeEmbeddedManifests(manifestsDir); err != nil { + return fmt.Errorf("failed to extract embedded manifests: %w", err) + } + + entries, err := os.ReadDir(manifestsDir) + if err != nil { + return fmt.Errorf("failed to read manifests directory: %w", err) + } + + var manifestFiles []string + for _, entry := range entries { + if strings.HasSuffix(entry.Name(), ".yaml") { + manifestFiles = append(manifestFiles, filepath.Join(manifestsDir, entry.Name())) + } + } + + if len(manifestFiles) == 0 { + return fmt.Errorf("no YAML manifest files found in directory") + } + + var objects []*unstructured.Unstructured + for _, manifestPath := range manifestFiles { + objs, err := parseManifests(manifestPath) + if err != nil { + return fmt.Errorf("failed to parse manifests from %s: %w", manifestPath, err) + } + objects = append(objects, objs...) + } + + if len(objects) == 0 { + return fmt.Errorf("no objects found in manifests") + } + + logger.Info("Applying Cozystack CRDs", "count", len(objects)) + for _, obj := range objects { + patchOptions := &client.PatchOptions{ + FieldManager: "cozystack-operator", + Force: func() *bool { b := true; return &b }(), + } + + if err := k8sClient.Patch(ctx, obj, client.Apply, patchOptions); err != nil { + return fmt.Errorf("failed to apply CRD %s: %w", obj.GetName(), err) + } + logger.Info("Applied CRD", "name", obj.GetName()) + } + + logger.Info("CRD installation completed successfully") + return nil +} + +func parseManifests(manifestPath string) ([]*unstructured.Unstructured, error) { + data, err := os.ReadFile(manifestPath) + if err != nil { + return nil, fmt.Errorf("failed to read manifest file: %w", err) + } + + return readYAMLObjects(bytes.NewReader(data)) +} + +func readYAMLObjects(reader io.Reader) ([]*unstructured.Unstructured, error) { + var objects []*unstructured.Unstructured + yamlReader := k8syaml.NewYAMLReader(bufio.NewReader(reader)) + + for { + doc, err := yamlReader.Read() + if err != nil { + if err == io.EOF { + break + } + return nil, fmt.Errorf("failed to read YAML document: %w", err) + } + + if len(bytes.TrimSpace(doc)) == 0 { + continue + } + + obj := &unstructured.Unstructured{} + decoder := k8syaml.NewYAMLOrJSONDecoder(bytes.NewReader(doc), len(doc)) + if err := decoder.Decode(obj); err != nil { + if err == io.EOF { + continue + } + return nil, fmt.Errorf("failed to decode YAML document: %w", err) + } + + if obj.GetKind() == "" { + continue + } + + objects = append(objects, obj) + } + + return objects, nil +} diff --git a/internal/crdinstall/manifests.embed.go b/internal/crdinstall/manifests.embed.go new file mode 100644 index 00000000..dd578db6 --- /dev/null +++ b/internal/crdinstall/manifests.embed.go @@ -0,0 +1,50 @@ +/* +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. +*/ + +package crdinstall + +import ( + "embed" + "fmt" + "io/fs" + "os" + "path" +) + +//go:embed manifests/*.yaml +var embeddedCRDManifests embed.FS + +// WriteEmbeddedManifests extracts embedded CRD manifests to a directory. +func WriteEmbeddedManifests(dir string) error { + manifests, err := fs.ReadDir(embeddedCRDManifests, "manifests") + if err != nil { + return fmt.Errorf("failed to read embedded manifests: %w", err) + } + + for _, manifest := range manifests { + data, err := fs.ReadFile(embeddedCRDManifests, path.Join("manifests", manifest.Name())) + if err != nil { + return fmt.Errorf("failed to read file %s: %w", manifest.Name(), err) + } + + outputPath := path.Join(dir, manifest.Name()) + if err := os.WriteFile(outputPath, data, 0666); err != nil { + return fmt.Errorf("failed to write file %s: %w", outputPath, err) + } + } + + return nil +} diff --git a/internal/crdinstall/manifests/.gitattributes b/internal/crdinstall/manifests/.gitattributes new file mode 100644 index 00000000..f581feca --- /dev/null +++ b/internal/crdinstall/manifests/.gitattributes @@ -0,0 +1 @@ +*.yaml linguist-generated diff --git a/internal/crdinstall/manifests/cozystack.io_packages.yaml b/internal/crdinstall/manifests/cozystack.io_packages.yaml new file mode 100644 index 00000000..cb7e2763 --- /dev/null +++ b/internal/crdinstall/manifests/cozystack.io_packages.yaml @@ -0,0 +1,171 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.4 + name: packages.cozystack.io +spec: + group: cozystack.io + names: + kind: Package + listKind: PackageList + plural: packages + shortNames: + - pkg + - pkgs + singular: package + scope: Cluster + versions: + - additionalPrinterColumns: + - description: Selected variant + jsonPath: .spec.variant + name: Variant + type: string + - description: Ready status + jsonPath: .status.conditions[?(@.type=='Ready')].status + name: Ready + type: string + - description: Ready message + jsonPath: .status.conditions[?(@.type=='Ready')].message + name: Status + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: Package is the Schema for the packages API + 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: PackageSpec defines the desired state of Package + properties: + components: + additionalProperties: + description: PackageComponent defines overrides for a specific component + properties: + enabled: + description: |- + Enabled indicates whether this component should be installed + If false, the component will be disabled even if it's defined in the PackageSource + type: boolean + values: + description: |- + Values contains Helm chart values as a JSON object + These values will be merged with the default values from the PackageSource + x-kubernetes-preserve-unknown-fields: true + type: object + description: |- + Components is a map of release name to component overrides + Allows overriding values and enabling/disabling specific components from the PackageSource + type: object + ignoreDependencies: + description: |- + IgnoreDependencies is a list of package source dependencies to ignore + Dependencies listed here will not be installed even if they are specified in the PackageSource + items: + type: string + type: array + variant: + description: |- + Variant is the name of the variant to use from the PackageSource + If not specified, defaults to "default" + type: string + type: object + status: + description: PackageStatus defines the observed state of Package + properties: + conditions: + description: Conditions represents the latest available observations + of a Package'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 + dependencies: + additionalProperties: + description: DependencyStatus represents the readiness status of + a dependency + properties: + ready: + description: Ready indicates whether the dependency is ready + type: boolean + required: + - ready + type: object + description: |- + Dependencies tracks the readiness status of each dependency + Key is the dependency package name, value indicates if the dependency is ready + type: object + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/internal/crdinstall/manifests/cozystack.io_packagesources.yaml b/internal/crdinstall/manifests/cozystack.io_packagesources.yaml new file mode 100644 index 00000000..0acfcdd2 --- /dev/null +++ b/internal/crdinstall/manifests/cozystack.io_packagesources.yaml @@ -0,0 +1,250 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.4 + name: packagesources.cozystack.io +spec: + group: cozystack.io + names: + kind: PackageSource + listKind: PackageSourceList + plural: packagesources + shortNames: + - pks + singular: packagesource + scope: Cluster + versions: + - additionalPrinterColumns: + - description: Package variants (comma-separated) + jsonPath: .status.variants + name: Variants + type: string + - description: Ready status + jsonPath: .status.conditions[?(@.type=='Ready')].status + name: Ready + type: string + - description: Ready message + jsonPath: .status.conditions[?(@.type=='Ready')].message + name: Status + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: PackageSource is the Schema for the packagesources API + 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: PackageSourceSpec defines the desired state of PackageSource + properties: + sourceRef: + description: SourceRef is the source reference for the package source + charts + properties: + kind: + description: Kind of the source reference + enum: + - GitRepository + - OCIRepository + type: string + name: + description: Name of the source reference + type: string + namespace: + description: Namespace of the source reference + type: string + path: + description: |- + Path is the base path where packages are located in the source. + For GitRepository, defaults to "packages" if not specified. + For OCIRepository, defaults to empty string (root) if not specified. + type: string + required: + - kind + - name + - namespace + type: object + variants: + description: |- + Variants is a list of package source variants + Each variant defines components, applications, dependencies, and libraries for a specific configuration + items: + description: Variant defines a single variant configuration + properties: + components: + description: Components is a list of Helm releases to be installed + as part of this variant + items: + description: Component defines a single Helm release component + within a package source + properties: + install: + description: Install defines installation parameters for + this component + properties: + dependsOn: + description: DependsOn is a list of component names + that must be installed before this component + items: + type: string + type: array + namespace: + description: Namespace is the Kubernetes namespace + where the release will be installed + type: string + privileged: + description: Privileged indicates whether this release + requires privileged access + type: boolean + releaseName: + description: |- + ReleaseName is the name of the HelmRelease resource that will be created + If not specified, defaults to the component Name field + type: string + type: object + libraries: + description: |- + Libraries is a list of library names that this component depends on + These libraries must be defined at the variant level + items: + type: string + type: array + name: + description: Name is the unique identifier for this component + within the package source + type: string + path: + description: Path is the path to the Helm chart directory + type: string + valuesFiles: + description: ValuesFiles is a list of values file names + to use + items: + type: string + type: array + required: + - name + - path + type: object + type: array + dependsOn: + description: |- + DependsOn is a list of package source dependencies + For example: "cozystack.networking" + items: + type: string + type: array + libraries: + description: Libraries is a list of Helm library charts used + by components in this variant + items: + description: Library defines a Helm library chart + properties: + name: + description: Name is the optional name for library placed + in charts + type: string + path: + description: Path is the path to the library chart directory + type: string + required: + - path + type: object + type: array + name: + description: Name is the unique identifier for this variant + type: string + required: + - name + type: object + type: array + type: object + status: + description: PackageSourceStatus defines the observed state of PackageSource + properties: + conditions: + description: Conditions represents the latest available observations + of a PackageSource'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 + variants: + description: |- + Variants is a comma-separated list of package variant names + This field is populated by the controller based on spec.variants keys + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} From 879b10b777d90ba5a7335d77c095eb0896846f87 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Tue, 17 Feb 2026 23:24:36 +0300 Subject: [PATCH 299/889] feat(installer): enable --install-crds in operator deployment Add --install-crds=true to cozystack-operator container args so the operator applies embedded CRD manifests on startup via server-side apply. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/core/installer/templates/cozystack-operator.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/installer/templates/cozystack-operator.yaml b/packages/core/installer/templates/cozystack-operator.yaml index 4568e18b..359fb40c 100644 --- a/packages/core/installer/templates/cozystack-operator.yaml +++ b/packages/core/installer/templates/cozystack-operator.yaml @@ -53,6 +53,7 @@ spec: args: - --leader-elect=true - --install-flux=true + - --install-crds=true - --metrics-bind-address=0 - --health-probe-bind-address= {{- if .Values.cozystackOperator.disableTelemetry }} From 1558fb428aad0cfaef0cffbbd60d30834e52586a Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Tue, 17 Feb 2026 23:24:51 +0300 Subject: [PATCH 300/889] build(codegen): sync CRDs to operator embed directory After generating CRDs to packages/core/installer/crds/, copy them to internal/crdinstall/manifests/ so the operator binary embeds the latest CRD definitions. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- hack/update-codegen.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh index d0711cad..b7f96691 100755 --- a/hack/update-codegen.sh +++ b/hack/update-codegen.sh @@ -25,6 +25,7 @@ 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) OPERATOR_CRDDIR=packages/core/installer/crds +OPERATOR_EMBEDDIR=internal/crdinstall/manifests COZY_CONTROLLER_CRDDIR=packages/system/cozystack-controller/definitions COZY_RD_CRDDIR=packages/system/application-definition-crd/definition BACKUPS_CORE_CRDDIR=packages/system/backup-controller/definitions @@ -73,6 +74,9 @@ $CONTROLLER_GEN rbac:roleName=manager-role crd paths="./api/..." output:crd:arti mv ${TMPDIR}/cozystack.io_packages.yaml ${OPERATOR_CRDDIR}/cozystack.io_packages.yaml mv ${TMPDIR}/cozystack.io_packagesources.yaml ${OPERATOR_CRDDIR}/cozystack.io_packagesources.yaml +cp ${OPERATOR_CRDDIR}/cozystack.io_packages.yaml ${OPERATOR_EMBEDDIR}/cozystack.io_packages.yaml +cp ${OPERATOR_CRDDIR}/cozystack.io_packagesources.yaml ${OPERATOR_EMBEDDIR}/cozystack.io_packagesources.yaml + mv ${TMPDIR}/cozystack.io_applicationdefinitions.yaml \ ${COZY_RD_CRDDIR}/cozystack.io_applicationdefinitions.yaml From 4187b5ed9460d48860736004bf07969c9e46bed3 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Tue, 17 Feb 2026 23:31:29 +0300 Subject: [PATCH 301/889] fix(crdinstall): use filepath for OS paths, restrict permissions, add tests - Use filepath.Join instead of path.Join for OS file paths - Restrict extracted manifest permissions from 0666 to 0600 - Add unit tests for readYAMLObjects, parseManifests, and WriteEmbeddedManifests including permission verification Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- internal/crdinstall/install_test.go | 242 +++++++++++++++++++++++++ internal/crdinstall/manifests.embed.go | 5 +- 2 files changed, 245 insertions(+), 2 deletions(-) create mode 100644 internal/crdinstall/install_test.go diff --git a/internal/crdinstall/install_test.go b/internal/crdinstall/install_test.go new file mode 100644 index 00000000..fc1f1fd9 --- /dev/null +++ b/internal/crdinstall/install_test.go @@ -0,0 +1,242 @@ +/* +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. +*/ + +package crdinstall + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func TestReadYAMLObjects(t *testing.T) { + tests := []struct { + name string + input string + wantCount int + wantErr bool + }{ + { + name: "single document", + input: `apiVersion: v1 +kind: ConfigMap +metadata: + name: test +`, + wantCount: 1, + }, + { + name: "multiple documents", + input: `apiVersion: v1 +kind: ConfigMap +metadata: + name: test1 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: test2 +`, + wantCount: 2, + }, + { + name: "empty input", + input: "", + wantCount: 0, + }, + { + name: "document without kind returns error", + input: `apiVersion: v1 +metadata: + name: test +`, + wantErr: true, + }, + { + name: "whitespace-only document between separators is skipped", + input: `apiVersion: v1 +kind: ConfigMap +metadata: + name: test1 +--- + +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: test2 +`, + wantCount: 2, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + objects, err := readYAMLObjects(strings.NewReader(tt.input)) + if (err != nil) != tt.wantErr { + t.Errorf("readYAMLObjects() error = %v, wantErr %v", err, tt.wantErr) + return + } + if len(objects) != tt.wantCount { + t.Errorf("readYAMLObjects() returned %d objects, want %d", len(objects), tt.wantCount) + } + }) + } +} + +func TestReadYAMLObjects_preservesFields(t *testing.T) { + input := `apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: packages.cozystack.io +spec: + group: cozystack.io +` + objects, err := readYAMLObjects(strings.NewReader(input)) + if err != nil { + t.Fatalf("readYAMLObjects() error = %v", err) + } + if len(objects) != 1 { + t.Fatalf("expected 1 object, got %d", len(objects)) + } + + obj := objects[0] + if obj.GetKind() != "CustomResourceDefinition" { + t.Errorf("kind = %q, want %q", obj.GetKind(), "CustomResourceDefinition") + } + if obj.GetName() != "packages.cozystack.io" { + t.Errorf("name = %q, want %q", obj.GetName(), "packages.cozystack.io") + } + if obj.GetAPIVersion() != "apiextensions.k8s.io/v1" { + t.Errorf("apiVersion = %q, want %q", obj.GetAPIVersion(), "apiextensions.k8s.io/v1") + } +} + +func TestParseManifests(t *testing.T) { + tmpDir := t.TempDir() + manifestPath := filepath.Join(tmpDir, "test.yaml") + + content := `apiVersion: v1 +kind: ConfigMap +metadata: + name: cm1 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm2 +` + if err := os.WriteFile(manifestPath, []byte(content), 0600); err != nil { + t.Fatalf("failed to write test manifest: %v", err) + } + + objects, err := parseManifests(manifestPath) + if err != nil { + t.Fatalf("parseManifests() error = %v", err) + } + if len(objects) != 2 { + t.Errorf("parseManifests() returned %d objects, want 2", len(objects)) + } +} + +func TestParseManifests_fileNotFound(t *testing.T) { + _, err := parseManifests("/nonexistent/path/test.yaml") + if err == nil { + t.Error("parseManifests() expected error for nonexistent file, got nil") + } +} + +func TestWriteEmbeddedManifests(t *testing.T) { + tmpDir := t.TempDir() + + if err := WriteEmbeddedManifests(tmpDir); err != nil { + t.Fatalf("WriteEmbeddedManifests() error = %v", err) + } + + entries, err := os.ReadDir(tmpDir) + if err != nil { + t.Fatalf("failed to read output dir: %v", err) + } + + var yamlFiles []string + for _, e := range entries { + if strings.HasSuffix(e.Name(), ".yaml") { + yamlFiles = append(yamlFiles, e.Name()) + } + } + + if len(yamlFiles) == 0 { + t.Error("WriteEmbeddedManifests() produced no YAML files") + } + + expectedFiles := []string{ + "cozystack.io_packages.yaml", + "cozystack.io_packagesources.yaml", + } + for _, expected := range expectedFiles { + found := false + for _, actual := range yamlFiles { + if actual == expected { + found = true + break + } + } + if !found { + t.Errorf("expected file %q not found in output, got %v", expected, yamlFiles) + } + } + + // Verify files are non-empty and contain valid YAML + for _, f := range yamlFiles { + data, err := os.ReadFile(filepath.Join(tmpDir, f)) + if err != nil { + t.Errorf("failed to read %s: %v", f, err) + continue + } + if len(data) == 0 { + t.Errorf("file %s is empty", f) + } + } +} + +func TestWriteEmbeddedManifests_filePermissions(t *testing.T) { + tmpDir := t.TempDir() + + if err := WriteEmbeddedManifests(tmpDir); err != nil { + t.Fatalf("WriteEmbeddedManifests() error = %v", err) + } + + entries, err := os.ReadDir(tmpDir) + if err != nil { + t.Fatalf("failed to read output dir: %v", err) + } + + for _, e := range entries { + if !strings.HasSuffix(e.Name(), ".yaml") { + continue + } + info, err := e.Info() + if err != nil { + t.Errorf("failed to get info for %s: %v", e.Name(), err) + continue + } + perm := info.Mode().Perm() + if perm&0o077 != 0 { + t.Errorf("file %s has overly permissive mode %o, expected no group/other access", e.Name(), perm) + } + } +} diff --git a/internal/crdinstall/manifests.embed.go b/internal/crdinstall/manifests.embed.go index dd578db6..7464e54e 100644 --- a/internal/crdinstall/manifests.embed.go +++ b/internal/crdinstall/manifests.embed.go @@ -22,6 +22,7 @@ import ( "io/fs" "os" "path" + "path/filepath" ) //go:embed manifests/*.yaml @@ -40,8 +41,8 @@ func WriteEmbeddedManifests(dir string) error { return fmt.Errorf("failed to read file %s: %w", manifest.Name(), err) } - outputPath := path.Join(dir, manifest.Name()) - if err := os.WriteFile(outputPath, data, 0666); err != nil { + outputPath := filepath.Join(dir, manifest.Name()) + if err := os.WriteFile(outputPath, data, 0600); err != nil { return fmt.Errorf("failed to write file %s: %w", outputPath, err) } } From 20d122445dfc34aea556bb2f9e33d899cf5413f6 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Tue, 17 Feb 2026 23:42:16 +0300 Subject: [PATCH 302/889] fix(crdinstall): add CRD readiness check, Install tests, fix fluxinstall - Wait for CRDs to have Established condition after server-side apply, instead of returning immediately - Add TestInstall with fake client and interceptor to simulate CRD establishment - Add TestInstall_noManifests and TestInstall_writeManifestsFails for error paths - Fix fluxinstall/manifests.embed.go: use filepath.Join for OS paths and restrict permissions from 0666 to 0600 (same fix as crdinstall) Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- internal/crdinstall/install.go | 68 ++++++++++++ internal/crdinstall/install_test.go | 141 ++++++++++++++++++++++++ internal/fluxinstall/manifests.embed.go | 5 +- 3 files changed, 212 insertions(+), 2 deletions(-) diff --git a/internal/crdinstall/install.go b/internal/crdinstall/install.go index fb6e4588..62615f68 100644 --- a/internal/crdinstall/install.go +++ b/internal/crdinstall/install.go @@ -25,8 +25,10 @@ import ( "os" "path/filepath" "strings" + "time" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/types" k8syaml "k8s.io/apimachinery/pkg/util/yaml" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" @@ -94,10 +96,76 @@ func Install(ctx context.Context, k8sClient client.Client, writeEmbeddedManifest logger.Info("Applied CRD", "name", obj.GetName()) } + if err := waitForCRDsEstablished(ctx, k8sClient, objects, logger); err != nil { + return fmt.Errorf("CRDs not established after apply: %w", err) + } + logger.Info("CRD installation completed successfully") return nil } +// waitForCRDsEstablished polls applied CRDs until all have the Established condition. +func waitForCRDsEstablished(ctx context.Context, k8sClient client.Client, objects []*unstructured.Unstructured, logger interface{ Info(string, ...interface{}) }) error { + var crdNames []string + for _, obj := range objects { + if obj.GetKind() == "CustomResourceDefinition" { + crdNames = append(crdNames, obj.GetName()) + } + } + + if len(crdNames) == 0 { + return nil + } + + ticker := time.NewTicker(500 * time.Millisecond) + defer ticker.Stop() + + for { + allEstablished := true + for _, name := range crdNames { + crd := &unstructured.Unstructured{} + crd.SetGroupVersionKind(objects[0].GroupVersionKind()) + if err := k8sClient.Get(ctx, types.NamespacedName{Name: name}, crd); err != nil { + allEstablished = false + break + } + + conditions, found, err := unstructured.NestedSlice(crd.Object, "status", "conditions") + if err != nil || !found { + allEstablished = false + break + } + + established := false + for _, c := range conditions { + cond, ok := c.(map[string]interface{}) + if !ok { + continue + } + if cond["type"] == "Established" && cond["status"] == "True" { + established = true + break + } + } + if !established { + allEstablished = false + break + } + } + + if allEstablished { + logger.Info("All CRDs established", "count", len(crdNames)) + return nil + } + + select { + case <-ctx.Done(): + return fmt.Errorf("context cancelled while waiting for CRDs to be established: %w", ctx.Err()) + case <-ticker.C: + } + } +} + func parseManifests(manifestPath string) ([]*unstructured.Unstructured, error) { data, err := os.ReadFile(manifestPath) if err != nil { diff --git a/internal/crdinstall/install_test.go b/internal/crdinstall/install_test.go index fc1f1fd9..ca49db51 100644 --- a/internal/crdinstall/install_test.go +++ b/internal/crdinstall/install_test.go @@ -17,10 +17,21 @@ limitations under the License. package crdinstall import ( + "context" "os" "path/filepath" "strings" "testing" + "time" + + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/client/interceptor" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" ) func TestReadYAMLObjects(t *testing.T) { @@ -213,6 +224,136 @@ func TestWriteEmbeddedManifests(t *testing.T) { } } +func TestInstall_appliesAllCRDs(t *testing.T) { + log.SetLogger(zap.New(zap.UseDevMode(true))) + + scheme := runtime.NewScheme() + if err := apiextensionsv1.AddToScheme(scheme); err != nil { + t.Fatalf("failed to add apiextensions to scheme: %v", err) + } + + // Intercept Get calls to simulate CRDs becoming Established + fakeClient := fake.NewClientBuilder(). + WithScheme(scheme). + WithInterceptorFuncs(interceptor.Funcs{ + Get: func(ctx context.Context, c client.WithWatch, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { + if err := c.Get(ctx, key, obj, opts...); err != nil { + return err + } + u, ok := obj.(*unstructured.Unstructured) + if !ok { + return nil + } + if u.GetKind() == "CustomResourceDefinition" { + _ = unstructured.SetNestedSlice(u.Object, []interface{}{ + map[string]interface{}{ + "type": "Established", + "status": "True", + }, + }, "status", "conditions") + } + return nil + }, + }). + Build() + + // Write two CRD manifests + writeManifests := func(dir string) error { + crd1 := `apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: packages.cozystack.io +spec: + group: cozystack.io + names: + kind: Package + plural: packages + scope: Namespaced + versions: + - name: v1alpha1 + served: true + storage: true + schema: + openAPIV3Schema: + type: object +` + crd2 := `apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: packagesources.cozystack.io +spec: + group: cozystack.io + names: + kind: PackageSource + plural: packagesources + scope: Namespaced + versions: + - name: v1alpha1 + served: true + storage: true + schema: + openAPIV3Schema: + type: object +` + if err := os.WriteFile(filepath.Join(dir, "crd1.yaml"), []byte(crd1), 0600); err != nil { + return err + } + return os.WriteFile(filepath.Join(dir, "crd2.yaml"), []byte(crd2), 0600) + } + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + ctx = log.IntoContext(ctx, log.FromContext(context.Background())) + + err := Install(ctx, fakeClient, writeManifests) + if err != nil { + t.Fatalf("Install() error = %v", err) + } +} + +func TestInstall_noManifests(t *testing.T) { + log.SetLogger(zap.New(zap.UseDevMode(true))) + + scheme := runtime.NewScheme() + fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build() + + writeEmpty := func(dir string) error { + return nil + } + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + ctx = log.IntoContext(ctx, log.FromContext(context.Background())) + + err := Install(ctx, fakeClient, writeEmpty) + if err == nil { + t.Error("Install() expected error for empty manifests, got nil") + } + if !strings.Contains(err.Error(), "no YAML manifest files found") { + t.Errorf("Install() error = %v, want error containing 'no YAML manifest files found'", err) + } +} + +func TestInstall_writeManifestsFails(t *testing.T) { + log.SetLogger(zap.New(zap.UseDevMode(true))) + + scheme := runtime.NewScheme() + fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build() + + writeFail := func(dir string) error { + return os.ErrPermission + } + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + ctx = log.IntoContext(ctx, log.FromContext(context.Background())) + + err := Install(ctx, fakeClient, writeFail) + if err == nil { + t.Error("Install() expected error when writeManifests fails, got nil") + } +} + func TestWriteEmbeddedManifests_filePermissions(t *testing.T) { tmpDir := t.TempDir() diff --git a/internal/fluxinstall/manifests.embed.go b/internal/fluxinstall/manifests.embed.go index e6a13a99..6ff48d2d 100644 --- a/internal/fluxinstall/manifests.embed.go +++ b/internal/fluxinstall/manifests.embed.go @@ -22,6 +22,7 @@ import ( "io/fs" "os" "path" + "path/filepath" ) //go:embed manifests/*.yaml @@ -40,8 +41,8 @@ func WriteEmbeddedManifests(dir string) error { return fmt.Errorf("failed to read file %s: %w", manifest.Name(), err) } - outputPath := path.Join(dir, manifest.Name()) - if err := os.WriteFile(outputPath, data, 0666); err != nil { + outputPath := filepath.Join(dir, manifest.Name()) + if err := os.WriteFile(outputPath, data, 0600); err != nil { return fmt.Errorf("failed to write file %s: %w", outputPath, err) } } From 962f8e96f4f458f27641ca13248683b8d0c5f4f7 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Tue, 17 Feb 2026 23:42:59 +0300 Subject: [PATCH 303/889] ci(makefile): add CRD sync verification between Helm crds/ and operator embed Add verify-crds target that diffs packages/core/installer/crds/ and internal/crdinstall/manifests/ to catch accidental divergence. Include it in unit-tests target so it runs in CI. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- Makefile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index dd6a249d..85b48659 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: manifests assets unit-tests helm-unit-tests +.PHONY: manifests assets unit-tests helm-unit-tests verify-crds include hack/common-envs.mk @@ -80,7 +80,11 @@ test: make -C packages/core/testing apply make -C packages/core/testing test -unit-tests: helm-unit-tests +verify-crds: + @diff packages/core/installer/crds/ internal/crdinstall/manifests/ --exclude=.gitattributes \ + || (echo "ERROR: CRD manifests out of sync. Run 'make generate' to fix." && exit 1) + +unit-tests: helm-unit-tests verify-crds helm-unit-tests: hack/helm-unit-tests.sh From b3fe6a8c4a2af1d92e580bc17fcd136c3ba4e87e Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Tue, 17 Feb 2026 23:49:20 +0300 Subject: [PATCH 304/889] fix(crdinstall): hardcode CRD GVK, add timeout test, document dual install - Use explicit apiextensions.k8s.io/v1 CRD GVK in waitForCRDsEstablished instead of fragile objects[0].GroupVersionKind() - Add TestInstall_crdNotEstablished for context timeout path - Add --recursive to diff in verify-crds Makefile target - Document why both crds/ and --install-crds exist in deployment template Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- Makefile | 2 +- internal/crdinstall/install.go | 7 ++- internal/crdinstall/install_test.go | 46 +++++++++++++++++++ .../templates/cozystack-operator.yaml | 3 ++ 4 files changed, 56 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 85b48659..9707ade4 100644 --- a/Makefile +++ b/Makefile @@ -81,7 +81,7 @@ test: make -C packages/core/testing test verify-crds: - @diff packages/core/installer/crds/ internal/crdinstall/manifests/ --exclude=.gitattributes \ + @diff --recursive packages/core/installer/crds/ internal/crdinstall/manifests/ --exclude=.gitattributes \ || (echo "ERROR: CRD manifests out of sync. Run 'make generate' to fix." && exit 1) unit-tests: helm-unit-tests verify-crds diff --git a/internal/crdinstall/install.go b/internal/crdinstall/install.go index 62615f68..315de612 100644 --- a/internal/crdinstall/install.go +++ b/internal/crdinstall/install.go @@ -28,6 +28,7 @@ import ( "time" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" k8syaml "k8s.io/apimachinery/pkg/util/yaml" "sigs.k8s.io/controller-runtime/pkg/client" @@ -124,7 +125,11 @@ func waitForCRDsEstablished(ctx context.Context, k8sClient client.Client, object allEstablished := true for _, name := range crdNames { crd := &unstructured.Unstructured{} - crd.SetGroupVersionKind(objects[0].GroupVersionKind()) + crd.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "apiextensions.k8s.io", + Version: "v1", + Kind: "CustomResourceDefinition", + }) if err := k8sClient.Get(ctx, types.NamespacedName{Name: name}, crd); err != nil { allEstablished = false break diff --git a/internal/crdinstall/install_test.go b/internal/crdinstall/install_test.go index ca49db51..2fd200bc 100644 --- a/internal/crdinstall/install_test.go +++ b/internal/crdinstall/install_test.go @@ -354,6 +354,52 @@ func TestInstall_writeManifestsFails(t *testing.T) { } } +func TestInstall_crdNotEstablished(t *testing.T) { + log.SetLogger(zap.New(zap.UseDevMode(true))) + + scheme := runtime.NewScheme() + if err := apiextensionsv1.AddToScheme(scheme); err != nil { + t.Fatalf("failed to add apiextensions to scheme: %v", err) + } + + // No interceptor: CRDs will never get Established condition + fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build() + + writeManifests := func(dir string) error { + crd := `apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: packages.cozystack.io +spec: + group: cozystack.io + names: + kind: Package + plural: packages + scope: Namespaced + versions: + - name: v1alpha1 + served: true + storage: true + schema: + openAPIV3Schema: + type: object +` + return os.WriteFile(filepath.Join(dir, "crd.yaml"), []byte(crd), 0600) + } + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + ctx = log.IntoContext(ctx, log.FromContext(context.Background())) + + err := Install(ctx, fakeClient, writeManifests) + if err == nil { + t.Fatal("Install() expected error when CRDs never become established, got nil") + } + if !strings.Contains(err.Error(), "CRDs not established") { + t.Errorf("Install() error = %v, want error containing 'CRDs not established'", err) + } +} + func TestWriteEmbeddedManifests_filePermissions(t *testing.T) { tmpDir := t.TempDir() diff --git a/packages/core/installer/templates/cozystack-operator.yaml b/packages/core/installer/templates/cozystack-operator.yaml index 359fb40c..1153ec4d 100644 --- a/packages/core/installer/templates/cozystack-operator.yaml +++ b/packages/core/installer/templates/cozystack-operator.yaml @@ -53,6 +53,9 @@ spec: args: - --leader-elect=true - --install-flux=true + # CRDs are also in crds/ for initial helm install, but Helm never updates + # them on upgrade. The operator applies embedded CRDs via server-side apply + # on every startup, ensuring they stay up to date. - --install-crds=true - --metrics-bind-address=0 - --health-probe-bind-address= From 3fbce0dba5dbe2a41e1672f6386a07f41a3e59a8 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 18 Feb 2026 00:01:46 +0300 Subject: [PATCH 305/889] refactor(operator): extract shared manifest utils from crdinstall and fluxinstall Move duplicated YAML parsing (ReadYAMLObjects, ParseManifestFile) and CRD readiness check (WaitForCRDsEstablished, CollectCRDNames) into a shared internal/manifestutil package. Both crdinstall and fluxinstall now import from manifestutil instead of maintaining identical copies. Replace fluxinstall's time.Sleep(2s) after CRD apply with proper WaitForCRDsEstablished polling, matching the crdinstall behavior. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- internal/crdinstall/install.go | 128 +------- internal/crdinstall/install_test.go | 473 ++++++++++------------------ internal/fluxinstall/install.go | 68 +--- internal/manifestutil/crd.go | 116 +++++++ internal/manifestutil/crd_test.go | 190 +++++++++++ internal/manifestutil/parse.go | 76 +++++ internal/manifestutil/parse_test.go | 161 ++++++++++ 7 files changed, 717 insertions(+), 495 deletions(-) create mode 100644 internal/manifestutil/crd.go create mode 100644 internal/manifestutil/crd_test.go create mode 100644 internal/manifestutil/parse.go create mode 100644 internal/manifestutil/parse_test.go diff --git a/internal/crdinstall/install.go b/internal/crdinstall/install.go index 315de612..4cd93597 100644 --- a/internal/crdinstall/install.go +++ b/internal/crdinstall/install.go @@ -17,26 +17,22 @@ limitations under the License. package crdinstall import ( - "bufio" - "bytes" "context" "fmt" - "io" "os" "path/filepath" "strings" - "time" + + "github.com/cozystack/cozystack/internal/manifestutil" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" - k8syaml "k8s.io/apimachinery/pkg/util/yaml" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" ) // Install applies Cozystack CRDs using embedded manifests. -// It extracts the manifests and applies them to the cluster using server-side apply. +// It extracts the manifests and applies them to the cluster using server-side apply, +// then waits for all CRDs to have the Established condition. func Install(ctx context.Context, k8sClient client.Client, writeEmbeddedManifests func(string) error) error { logger := log.FromContext(ctx) @@ -73,7 +69,7 @@ func Install(ctx context.Context, k8sClient client.Client, writeEmbeddedManifest var objects []*unstructured.Unstructured for _, manifestPath := range manifestFiles { - objs, err := parseManifests(manifestPath) + objs, err := manifestutil.ParseManifestFile(manifestPath) if err != nil { return fmt.Errorf("failed to parse manifests from %s: %w", manifestPath, err) } @@ -97,121 +93,11 @@ func Install(ctx context.Context, k8sClient client.Client, writeEmbeddedManifest logger.Info("Applied CRD", "name", obj.GetName()) } - if err := waitForCRDsEstablished(ctx, k8sClient, objects, logger); err != nil { + crdNames := manifestutil.CollectCRDNames(objects) + if err := manifestutil.WaitForCRDsEstablished(ctx, k8sClient, crdNames); err != nil { return fmt.Errorf("CRDs not established after apply: %w", err) } logger.Info("CRD installation completed successfully") return nil } - -// waitForCRDsEstablished polls applied CRDs until all have the Established condition. -func waitForCRDsEstablished(ctx context.Context, k8sClient client.Client, objects []*unstructured.Unstructured, logger interface{ Info(string, ...interface{}) }) error { - var crdNames []string - for _, obj := range objects { - if obj.GetKind() == "CustomResourceDefinition" { - crdNames = append(crdNames, obj.GetName()) - } - } - - if len(crdNames) == 0 { - return nil - } - - ticker := time.NewTicker(500 * time.Millisecond) - defer ticker.Stop() - - for { - allEstablished := true - for _, name := range crdNames { - crd := &unstructured.Unstructured{} - crd.SetGroupVersionKind(schema.GroupVersionKind{ - Group: "apiextensions.k8s.io", - Version: "v1", - Kind: "CustomResourceDefinition", - }) - if err := k8sClient.Get(ctx, types.NamespacedName{Name: name}, crd); err != nil { - allEstablished = false - break - } - - conditions, found, err := unstructured.NestedSlice(crd.Object, "status", "conditions") - if err != nil || !found { - allEstablished = false - break - } - - established := false - for _, c := range conditions { - cond, ok := c.(map[string]interface{}) - if !ok { - continue - } - if cond["type"] == "Established" && cond["status"] == "True" { - established = true - break - } - } - if !established { - allEstablished = false - break - } - } - - if allEstablished { - logger.Info("All CRDs established", "count", len(crdNames)) - return nil - } - - select { - case <-ctx.Done(): - return fmt.Errorf("context cancelled while waiting for CRDs to be established: %w", ctx.Err()) - case <-ticker.C: - } - } -} - -func parseManifests(manifestPath string) ([]*unstructured.Unstructured, error) { - data, err := os.ReadFile(manifestPath) - if err != nil { - return nil, fmt.Errorf("failed to read manifest file: %w", err) - } - - return readYAMLObjects(bytes.NewReader(data)) -} - -func readYAMLObjects(reader io.Reader) ([]*unstructured.Unstructured, error) { - var objects []*unstructured.Unstructured - yamlReader := k8syaml.NewYAMLReader(bufio.NewReader(reader)) - - for { - doc, err := yamlReader.Read() - if err != nil { - if err == io.EOF { - break - } - return nil, fmt.Errorf("failed to read YAML document: %w", err) - } - - if len(bytes.TrimSpace(doc)) == 0 { - continue - } - - obj := &unstructured.Unstructured{} - decoder := k8syaml.NewYAMLOrJSONDecoder(bytes.NewReader(doc), len(doc)) - if err := decoder.Decode(obj); err != nil { - if err == io.EOF { - continue - } - return nil, fmt.Errorf("failed to decode YAML document: %w", err) - } - - if obj.GetKind() == "" { - continue - } - - objects = append(objects, obj) - } - - return objects, nil -} diff --git a/internal/crdinstall/install_test.go b/internal/crdinstall/install_test.go index 2fd200bc..a4de0da2 100644 --- a/internal/crdinstall/install_test.go +++ b/internal/crdinstall/install_test.go @@ -18,6 +18,7 @@ package crdinstall import ( "context" + "fmt" "os" "path/filepath" "strings" @@ -34,143 +35,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log/zap" ) -func TestReadYAMLObjects(t *testing.T) { - tests := []struct { - name string - input string - wantCount int - wantErr bool - }{ - { - name: "single document", - input: `apiVersion: v1 -kind: ConfigMap -metadata: - name: test -`, - wantCount: 1, - }, - { - name: "multiple documents", - input: `apiVersion: v1 -kind: ConfigMap -metadata: - name: test1 ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: test2 -`, - wantCount: 2, - }, - { - name: "empty input", - input: "", - wantCount: 0, - }, - { - name: "document without kind returns error", - input: `apiVersion: v1 -metadata: - name: test -`, - wantErr: true, - }, - { - name: "whitespace-only document between separators is skipped", - input: `apiVersion: v1 -kind: ConfigMap -metadata: - name: test1 ---- - ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: test2 -`, - wantCount: 2, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - objects, err := readYAMLObjects(strings.NewReader(tt.input)) - if (err != nil) != tt.wantErr { - t.Errorf("readYAMLObjects() error = %v, wantErr %v", err, tt.wantErr) - return - } - if len(objects) != tt.wantCount { - t.Errorf("readYAMLObjects() returned %d objects, want %d", len(objects), tt.wantCount) - } - }) - } -} - -func TestReadYAMLObjects_preservesFields(t *testing.T) { - input := `apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: packages.cozystack.io -spec: - group: cozystack.io -` - objects, err := readYAMLObjects(strings.NewReader(input)) - if err != nil { - t.Fatalf("readYAMLObjects() error = %v", err) - } - if len(objects) != 1 { - t.Fatalf("expected 1 object, got %d", len(objects)) - } - - obj := objects[0] - if obj.GetKind() != "CustomResourceDefinition" { - t.Errorf("kind = %q, want %q", obj.GetKind(), "CustomResourceDefinition") - } - if obj.GetName() != "packages.cozystack.io" { - t.Errorf("name = %q, want %q", obj.GetName(), "packages.cozystack.io") - } - if obj.GetAPIVersion() != "apiextensions.k8s.io/v1" { - t.Errorf("apiVersion = %q, want %q", obj.GetAPIVersion(), "apiextensions.k8s.io/v1") - } -} - -func TestParseManifests(t *testing.T) { - tmpDir := t.TempDir() - manifestPath := filepath.Join(tmpDir, "test.yaml") - - content := `apiVersion: v1 -kind: ConfigMap -metadata: - name: cm1 ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: cm2 -` - if err := os.WriteFile(manifestPath, []byte(content), 0600); err != nil { - t.Fatalf("failed to write test manifest: %v", err) - } - - objects, err := parseManifests(manifestPath) - if err != nil { - t.Fatalf("parseManifests() error = %v", err) - } - if len(objects) != 2 { - t.Errorf("parseManifests() returned %d objects, want 2", len(objects)) - } -} - -func TestParseManifests_fileNotFound(t *testing.T) { - _, err := parseManifests("/nonexistent/path/test.yaml") - if err == nil { - t.Error("parseManifests() expected error for nonexistent file, got nil") - } -} - func TestWriteEmbeddedManifests(t *testing.T) { tmpDir := t.TempDir() @@ -211,7 +75,7 @@ func TestWriteEmbeddedManifests(t *testing.T) { } } - // Verify files are non-empty and contain valid YAML + // Verify files are non-empty for _, f := range yamlFiles { data, err := os.ReadFile(filepath.Join(tmpDir, f)) if err != nil { @@ -224,182 +88,6 @@ func TestWriteEmbeddedManifests(t *testing.T) { } } -func TestInstall_appliesAllCRDs(t *testing.T) { - log.SetLogger(zap.New(zap.UseDevMode(true))) - - scheme := runtime.NewScheme() - if err := apiextensionsv1.AddToScheme(scheme); err != nil { - t.Fatalf("failed to add apiextensions to scheme: %v", err) - } - - // Intercept Get calls to simulate CRDs becoming Established - fakeClient := fake.NewClientBuilder(). - WithScheme(scheme). - WithInterceptorFuncs(interceptor.Funcs{ - Get: func(ctx context.Context, c client.WithWatch, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { - if err := c.Get(ctx, key, obj, opts...); err != nil { - return err - } - u, ok := obj.(*unstructured.Unstructured) - if !ok { - return nil - } - if u.GetKind() == "CustomResourceDefinition" { - _ = unstructured.SetNestedSlice(u.Object, []interface{}{ - map[string]interface{}{ - "type": "Established", - "status": "True", - }, - }, "status", "conditions") - } - return nil - }, - }). - Build() - - // Write two CRD manifests - writeManifests := func(dir string) error { - crd1 := `apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: packages.cozystack.io -spec: - group: cozystack.io - names: - kind: Package - plural: packages - scope: Namespaced - versions: - - name: v1alpha1 - served: true - storage: true - schema: - openAPIV3Schema: - type: object -` - crd2 := `apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: packagesources.cozystack.io -spec: - group: cozystack.io - names: - kind: PackageSource - plural: packagesources - scope: Namespaced - versions: - - name: v1alpha1 - served: true - storage: true - schema: - openAPIV3Schema: - type: object -` - if err := os.WriteFile(filepath.Join(dir, "crd1.yaml"), []byte(crd1), 0600); err != nil { - return err - } - return os.WriteFile(filepath.Join(dir, "crd2.yaml"), []byte(crd2), 0600) - } - - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - ctx = log.IntoContext(ctx, log.FromContext(context.Background())) - - err := Install(ctx, fakeClient, writeManifests) - if err != nil { - t.Fatalf("Install() error = %v", err) - } -} - -func TestInstall_noManifests(t *testing.T) { - log.SetLogger(zap.New(zap.UseDevMode(true))) - - scheme := runtime.NewScheme() - fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build() - - writeEmpty := func(dir string) error { - return nil - } - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - ctx = log.IntoContext(ctx, log.FromContext(context.Background())) - - err := Install(ctx, fakeClient, writeEmpty) - if err == nil { - t.Error("Install() expected error for empty manifests, got nil") - } - if !strings.Contains(err.Error(), "no YAML manifest files found") { - t.Errorf("Install() error = %v, want error containing 'no YAML manifest files found'", err) - } -} - -func TestInstall_writeManifestsFails(t *testing.T) { - log.SetLogger(zap.New(zap.UseDevMode(true))) - - scheme := runtime.NewScheme() - fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build() - - writeFail := func(dir string) error { - return os.ErrPermission - } - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - ctx = log.IntoContext(ctx, log.FromContext(context.Background())) - - err := Install(ctx, fakeClient, writeFail) - if err == nil { - t.Error("Install() expected error when writeManifests fails, got nil") - } -} - -func TestInstall_crdNotEstablished(t *testing.T) { - log.SetLogger(zap.New(zap.UseDevMode(true))) - - scheme := runtime.NewScheme() - if err := apiextensionsv1.AddToScheme(scheme); err != nil { - t.Fatalf("failed to add apiextensions to scheme: %v", err) - } - - // No interceptor: CRDs will never get Established condition - fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build() - - writeManifests := func(dir string) error { - crd := `apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: packages.cozystack.io -spec: - group: cozystack.io - names: - kind: Package - plural: packages - scope: Namespaced - versions: - - name: v1alpha1 - served: true - storage: true - schema: - openAPIV3Schema: - type: object -` - return os.WriteFile(filepath.Join(dir, "crd.yaml"), []byte(crd), 0600) - } - - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) - defer cancel() - ctx = log.IntoContext(ctx, log.FromContext(context.Background())) - - err := Install(ctx, fakeClient, writeManifests) - if err == nil { - t.Fatal("Install() expected error when CRDs never become established, got nil") - } - if !strings.Contains(err.Error(), "CRDs not established") { - t.Errorf("Install() error = %v, want error containing 'CRDs not established'", err) - } -} - func TestWriteEmbeddedManifests_filePermissions(t *testing.T) { tmpDir := t.TempDir() @@ -427,3 +115,160 @@ func TestWriteEmbeddedManifests_filePermissions(t *testing.T) { } } } + +// newCRDManifestWriter returns a function that writes test CRD YAML files. +func newCRDManifestWriter(crds ...string) func(string) error { + return func(dir string) error { + for i, crd := range crds { + filename := filepath.Join(dir, fmt.Sprintf("crd%d.yaml", i+1)) + if err := os.WriteFile(filename, []byte(crd), 0600); err != nil { + return err + } + } + return nil + } +} + +var testCRD1 = `apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: packages.cozystack.io +spec: + group: cozystack.io + names: + kind: Package + plural: packages + scope: Namespaced + versions: + - name: v1alpha1 + served: true + storage: true + schema: + openAPIV3Schema: + type: object +` + +var testCRD2 = `apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: packagesources.cozystack.io +spec: + group: cozystack.io + names: + kind: PackageSource + plural: packagesources + scope: Namespaced + versions: + - name: v1alpha1 + served: true + storage: true + schema: + openAPIV3Schema: + type: object +` + +// establishedInterceptor simulates CRDs becoming Established in the API server. +func establishedInterceptor() interceptor.Funcs { + return interceptor.Funcs{ + Get: func(ctx context.Context, c client.WithWatch, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { + if err := c.Get(ctx, key, obj, opts...); err != nil { + return err + } + u, ok := obj.(*unstructured.Unstructured) + if !ok { + return nil + } + if u.GetKind() == "CustomResourceDefinition" { + _ = unstructured.SetNestedSlice(u.Object, []interface{}{ + map[string]interface{}{ + "type": "Established", + "status": "True", + }, + }, "status", "conditions") + } + return nil + }, + } +} + +func TestInstall_appliesAllCRDs(t *testing.T) { + log.SetLogger(zap.New(zap.UseDevMode(true))) + + scheme := runtime.NewScheme() + if err := apiextensionsv1.AddToScheme(scheme); err != nil { + t.Fatalf("failed to add apiextensions to scheme: %v", err) + } + + fakeClient := fake.NewClientBuilder(). + WithScheme(scheme). + WithInterceptorFuncs(establishedInterceptor()). + Build() + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + ctx = log.IntoContext(ctx, log.FromContext(context.Background())) + + err := Install(ctx, fakeClient, newCRDManifestWriter(testCRD1, testCRD2)) + if err != nil { + t.Fatalf("Install() error = %v", err) + } +} + +func TestInstall_noManifests(t *testing.T) { + log.SetLogger(zap.New(zap.UseDevMode(true))) + + scheme := runtime.NewScheme() + fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build() + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + ctx = log.IntoContext(ctx, log.FromContext(context.Background())) + + err := Install(ctx, fakeClient, func(string) error { return nil }) + if err == nil { + t.Error("Install() expected error for empty manifests, got nil") + } + if !strings.Contains(err.Error(), "no YAML manifest files found") { + t.Errorf("Install() error = %v, want error containing 'no YAML manifest files found'", err) + } +} + +func TestInstall_writeManifestsFails(t *testing.T) { + log.SetLogger(zap.New(zap.UseDevMode(true))) + + scheme := runtime.NewScheme() + fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build() + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + ctx = log.IntoContext(ctx, log.FromContext(context.Background())) + + err := Install(ctx, fakeClient, func(string) error { return os.ErrPermission }) + if err == nil { + t.Error("Install() expected error when writeManifests fails, got nil") + } +} + +func TestInstall_crdNotEstablished(t *testing.T) { + log.SetLogger(zap.New(zap.UseDevMode(true))) + + scheme := runtime.NewScheme() + if err := apiextensionsv1.AddToScheme(scheme); err != nil { + t.Fatalf("failed to add apiextensions to scheme: %v", err) + } + + // No interceptor: CRDs will never get Established condition + fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build() + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + ctx = log.IntoContext(ctx, log.FromContext(context.Background())) + + err := Install(ctx, fakeClient, newCRDManifestWriter(testCRD1)) + if err == nil { + t.Fatal("Install() expected error when CRDs never become established, got nil") + } + if !strings.Contains(err.Error(), "CRDs not established") { + t.Errorf("Install() error = %v, want error containing 'CRDs not established'", err) + } +} diff --git a/internal/fluxinstall/install.go b/internal/fluxinstall/install.go index 2097ecfb..4ae6f4cb 100644 --- a/internal/fluxinstall/install.go +++ b/internal/fluxinstall/install.go @@ -17,18 +17,15 @@ limitations under the License. package fluxinstall import ( - "bufio" - "bytes" "context" "fmt" - "io" "os" "path/filepath" "strings" - "time" + + "github.com/cozystack/cozystack/internal/manifestutil" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - k8syaml "k8s.io/apimachinery/pkg/util/yaml" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" ) @@ -76,7 +73,7 @@ func Install(ctx context.Context, k8sClient client.Client, writeEmbeddedManifest // Parse all manifest files var objects []*unstructured.Unstructured for _, manifestPath := range manifestFiles { - objs, err := parseManifests(manifestPath) + objs, err := manifestutil.ParseManifestFile(manifestPath) if err != nil { return fmt.Errorf("failed to parse manifests from %s: %w", manifestPath, err) } @@ -110,56 +107,6 @@ func Install(ctx context.Context, k8sClient client.Client, writeEmbeddedManifest return nil } -// parseManifests parses YAML manifests into unstructured objects. -func parseManifests(manifestPath string) ([]*unstructured.Unstructured, error) { - data, err := os.ReadFile(manifestPath) - if err != nil { - return nil, fmt.Errorf("failed to read manifest file: %w", err) - } - - return readYAMLObjects(bytes.NewReader(data)) -} - -// readYAMLObjects parses multi-document YAML into unstructured objects. -func readYAMLObjects(reader io.Reader) ([]*unstructured.Unstructured, error) { - var objects []*unstructured.Unstructured - yamlReader := k8syaml.NewYAMLReader(bufio.NewReader(reader)) - - for { - doc, err := yamlReader.Read() - if err != nil { - if err == io.EOF { - break - } - return nil, fmt.Errorf("failed to read YAML document: %w", err) - } - - // Skip empty documents - if len(bytes.TrimSpace(doc)) == 0 { - continue - } - - obj := &unstructured.Unstructured{} - decoder := k8syaml.NewYAMLOrJSONDecoder(bytes.NewReader(doc), len(doc)) - if err := decoder.Decode(obj); err != nil { - // Skip documents that can't be decoded (might be comments or empty) - if err == io.EOF { - continue - } - return nil, fmt.Errorf("failed to decode YAML document: %w", err) - } - - // Skip empty objects (no kind) - if obj.GetKind() == "" { - continue - } - - objects = append(objects, obj) - } - - return objects, nil -} - // applyManifests applies Kubernetes objects using server-side apply. func applyManifests(ctx context.Context, k8sClient client.Client, objects []*unstructured.Unstructured) error { logger := log.FromContext(ctx) @@ -183,8 +130,11 @@ func applyManifests(ctx context.Context, k8sClient client.Client, objects []*uns return fmt.Errorf("failed to apply cluster definitions: %w", err) } - // Wait a bit for CRDs to be registered - time.Sleep(2 * time.Second) + // Wait for CRDs to be established before applying dependent resources + crdNames := manifestutil.CollectCRDNames(stageOne) + if err := manifestutil.WaitForCRDsEstablished(ctx, k8sClient, crdNames); err != nil { + return fmt.Errorf("CRDs not established after apply: %w", err) + } } // Apply stage two (everything else) @@ -215,7 +165,6 @@ func applyObjects(ctx context.Context, k8sClient client.Client, objects []*unstr return nil } - // extractNamespace extracts the namespace name from the Namespace object in the manifests. func extractNamespace(objects []*unstructured.Unstructured) (string, error) { for _, obj := range objects { @@ -386,4 +335,3 @@ func setEnvVar(env []interface{}, name, value string) []interface{} { return env } - diff --git a/internal/manifestutil/crd.go b/internal/manifestutil/crd.go new file mode 100644 index 00000000..00421c4c --- /dev/null +++ b/internal/manifestutil/crd.go @@ -0,0 +1,116 @@ +/* +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. +*/ + +package manifestutil + +import ( + "context" + "fmt" + "time" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" +) + +var crdGVK = schema.GroupVersionKind{ + Group: "apiextensions.k8s.io", + Version: "v1", + Kind: "CustomResourceDefinition", +} + +// WaitForCRDsEstablished polls the API server until all named CRDs have the +// Established condition set to True, or the context is cancelled. +func WaitForCRDsEstablished(ctx context.Context, k8sClient client.Client, crdNames []string) error { + if len(crdNames) == 0 { + return nil + } + + logger := log.FromContext(ctx) + ticker := time.NewTicker(500 * time.Millisecond) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return fmt.Errorf("context cancelled while waiting for CRDs to be established: %w", ctx.Err()) + default: + } + + allEstablished := true + var pendingCRD string + for _, name := range crdNames { + crd := &unstructured.Unstructured{} + crd.SetGroupVersionKind(crdGVK) + if err := k8sClient.Get(ctx, types.NamespacedName{Name: name}, crd); err != nil { + allEstablished = false + pendingCRD = name + break + } + + conditions, found, err := unstructured.NestedSlice(crd.Object, "status", "conditions") + if err != nil || !found { + allEstablished = false + pendingCRD = name + break + } + + established := false + for _, c := range conditions { + cond, ok := c.(map[string]interface{}) + if !ok { + continue + } + if cond["type"] == "Established" && cond["status"] == "True" { + established = true + break + } + } + if !established { + allEstablished = false + pendingCRD = name + break + } + } + + if allEstablished { + logger.Info("All CRDs established", "count", len(crdNames)) + return nil + } + + logger.V(1).Info("Waiting for CRD to be established", "crd", pendingCRD) + + select { + case <-ctx.Done(): + return fmt.Errorf("context cancelled while waiting for CRD %q to be established: %w", pendingCRD, ctx.Err()) + case <-ticker.C: + } + } +} + +// CollectCRDNames returns the names of all CustomResourceDefinition objects +// from the given list of unstructured objects. +func CollectCRDNames(objects []*unstructured.Unstructured) []string { + var names []string + for _, obj := range objects { + if obj.GetKind() == "CustomResourceDefinition" { + names = append(names, obj.GetName()) + } + } + return names +} diff --git a/internal/manifestutil/crd_test.go b/internal/manifestutil/crd_test.go new file mode 100644 index 00000000..874377b9 --- /dev/null +++ b/internal/manifestutil/crd_test.go @@ -0,0 +1,190 @@ +/* +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. +*/ + +package manifestutil + +import ( + "context" + "testing" + "time" + + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/client/interceptor" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +func TestCollectCRDNames(t *testing.T) { + objects := []*unstructured.Unstructured{ + {Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Namespace", + "metadata": map[string]interface{}{"name": "test-ns"}, + }}, + {Object: map[string]interface{}{ + "apiVersion": "apiextensions.k8s.io/v1", + "kind": "CustomResourceDefinition", + "metadata": map[string]interface{}{"name": "packages.cozystack.io"}, + }}, + {Object: map[string]interface{}{ + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": map[string]interface{}{"name": "test-deploy"}, + }}, + {Object: map[string]interface{}{ + "apiVersion": "apiextensions.k8s.io/v1", + "kind": "CustomResourceDefinition", + "metadata": map[string]interface{}{"name": "packagesources.cozystack.io"}, + }}, + } + + names := CollectCRDNames(objects) + if len(names) != 2 { + t.Fatalf("CollectCRDNames() returned %d names, want 2", len(names)) + } + if names[0] != "packages.cozystack.io" { + t.Errorf("names[0] = %q, want %q", names[0], "packages.cozystack.io") + } + if names[1] != "packagesources.cozystack.io" { + t.Errorf("names[1] = %q, want %q", names[1], "packagesources.cozystack.io") + } +} + +func TestCollectCRDNames_noCRDs(t *testing.T) { + objects := []*unstructured.Unstructured{ + {Object: map[string]interface{}{ + "apiVersion": "v1", + "kind": "Namespace", + "metadata": map[string]interface{}{"name": "test"}, + }}, + } + + names := CollectCRDNames(objects) + if len(names) != 0 { + t.Errorf("CollectCRDNames() returned %d names, want 0", len(names)) + } +} + +func TestWaitForCRDsEstablished_success(t *testing.T) { + log.SetLogger(zap.New(zap.UseDevMode(true))) + + scheme := runtime.NewScheme() + if err := apiextensionsv1.AddToScheme(scheme); err != nil { + t.Fatalf("failed to add apiextensions to scheme: %v", err) + } + + // Create a CRD object in the fake client + crd := &unstructured.Unstructured{Object: map[string]interface{}{ + "apiVersion": "apiextensions.k8s.io/v1", + "kind": "CustomResourceDefinition", + "metadata": map[string]interface{}{"name": "packages.cozystack.io"}, + }} + + fakeClient := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(crd). + WithInterceptorFuncs(interceptor.Funcs{ + Get: func(ctx context.Context, c client.WithWatch, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { + if err := c.Get(ctx, key, obj, opts...); err != nil { + return err + } + u, ok := obj.(*unstructured.Unstructured) + if !ok { + return nil + } + if u.GetKind() == "CustomResourceDefinition" { + _ = unstructured.SetNestedSlice(u.Object, []interface{}{ + map[string]interface{}{ + "type": "Established", + "status": "True", + }, + }, "status", "conditions") + } + return nil + }, + }). + Build() + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + ctx = log.IntoContext(ctx, log.FromContext(context.Background())) + + err := WaitForCRDsEstablished(ctx, fakeClient, []string{"packages.cozystack.io"}) + if err != nil { + t.Fatalf("WaitForCRDsEstablished() error = %v", err) + } +} + +func TestWaitForCRDsEstablished_timeout(t *testing.T) { + log.SetLogger(zap.New(zap.UseDevMode(true))) + + scheme := runtime.NewScheme() + if err := apiextensionsv1.AddToScheme(scheme); err != nil { + t.Fatalf("failed to add apiextensions to scheme: %v", err) + } + + // CRD exists but never gets Established condition + crd := &unstructured.Unstructured{Object: map[string]interface{}{ + "apiVersion": "apiextensions.k8s.io/v1", + "kind": "CustomResourceDefinition", + "metadata": map[string]interface{}{"name": "packages.cozystack.io"}, + }} + + fakeClient := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(crd). + Build() + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + ctx = log.IntoContext(ctx, log.FromContext(context.Background())) + + err := WaitForCRDsEstablished(ctx, fakeClient, []string{"packages.cozystack.io"}) + if err == nil { + t.Fatal("WaitForCRDsEstablished() expected error on timeout, got nil") + } + if !contains(err.Error(), "packages.cozystack.io") { + t.Errorf("error should mention stuck CRD name, got: %v", err) + } +} + +func TestWaitForCRDsEstablished_empty(t *testing.T) { + scheme := runtime.NewScheme() + fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build() + + ctx := context.Background() + err := WaitForCRDsEstablished(ctx, fakeClient, nil) + if err != nil { + t.Fatalf("WaitForCRDsEstablished() with empty names should return nil, got: %v", err) + } +} + +func contains(s, substr string) bool { + return len(s) >= len(substr) && searchString(s, substr) +} + +func searchString(s, substr string) bool { + for i := 0; i <= len(s)-len(substr); i++ { + if s[i:i+len(substr)] == substr { + return true + } + } + return false +} diff --git a/internal/manifestutil/parse.go b/internal/manifestutil/parse.go new file mode 100644 index 00000000..009e4a96 --- /dev/null +++ b/internal/manifestutil/parse.go @@ -0,0 +1,76 @@ +/* +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. +*/ + +package manifestutil + +import ( + "bufio" + "bytes" + "fmt" + "io" + "os" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + k8syaml "k8s.io/apimachinery/pkg/util/yaml" +) + +// ParseManifestFile reads a YAML file and parses it into unstructured objects. +func ParseManifestFile(manifestPath string) ([]*unstructured.Unstructured, error) { + data, err := os.ReadFile(manifestPath) + if err != nil { + return nil, fmt.Errorf("failed to read manifest file: %w", err) + } + + return ReadYAMLObjects(bytes.NewReader(data)) +} + +// ReadYAMLObjects parses multi-document YAML from a reader into unstructured objects. +// Empty documents and documents without a kind are skipped. +func ReadYAMLObjects(reader io.Reader) ([]*unstructured.Unstructured, error) { + var objects []*unstructured.Unstructured + yamlReader := k8syaml.NewYAMLReader(bufio.NewReader(reader)) + + for { + doc, err := yamlReader.Read() + if err != nil { + if err == io.EOF { + break + } + return nil, fmt.Errorf("failed to read YAML document: %w", err) + } + + if len(bytes.TrimSpace(doc)) == 0 { + continue + } + + obj := &unstructured.Unstructured{} + decoder := k8syaml.NewYAMLOrJSONDecoder(bytes.NewReader(doc), len(doc)) + if err := decoder.Decode(obj); err != nil { + if err == io.EOF { + continue + } + return nil, fmt.Errorf("failed to decode YAML document: %w", err) + } + + if obj.GetKind() == "" { + continue + } + + objects = append(objects, obj) + } + + return objects, nil +} diff --git a/internal/manifestutil/parse_test.go b/internal/manifestutil/parse_test.go new file mode 100644 index 00000000..30bf7030 --- /dev/null +++ b/internal/manifestutil/parse_test.go @@ -0,0 +1,161 @@ +/* +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. +*/ + +package manifestutil + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func TestReadYAMLObjects(t *testing.T) { + tests := []struct { + name string + input string + wantCount int + wantErr bool + }{ + { + name: "single document", + input: `apiVersion: v1 +kind: ConfigMap +metadata: + name: test +`, + wantCount: 1, + }, + { + name: "multiple documents", + input: `apiVersion: v1 +kind: ConfigMap +metadata: + name: test1 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: test2 +`, + wantCount: 2, + }, + { + name: "empty input", + input: "", + wantCount: 0, + }, + { + name: "document without kind returns error", + input: `apiVersion: v1 +metadata: + name: test +`, + wantErr: true, + }, + { + name: "whitespace-only document between separators is skipped", + input: `apiVersion: v1 +kind: ConfigMap +metadata: + name: test1 +--- + +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: test2 +`, + wantCount: 2, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + objects, err := ReadYAMLObjects(strings.NewReader(tt.input)) + if (err != nil) != tt.wantErr { + t.Errorf("ReadYAMLObjects() error = %v, wantErr %v", err, tt.wantErr) + return + } + if len(objects) != tt.wantCount { + t.Errorf("ReadYAMLObjects() returned %d objects, want %d", len(objects), tt.wantCount) + } + }) + } +} + +func TestReadYAMLObjects_preservesFields(t *testing.T) { + input := `apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: packages.cozystack.io +spec: + group: cozystack.io +` + objects, err := ReadYAMLObjects(strings.NewReader(input)) + if err != nil { + t.Fatalf("ReadYAMLObjects() error = %v", err) + } + if len(objects) != 1 { + t.Fatalf("expected 1 object, got %d", len(objects)) + } + + obj := objects[0] + if obj.GetKind() != "CustomResourceDefinition" { + t.Errorf("kind = %q, want %q", obj.GetKind(), "CustomResourceDefinition") + } + if obj.GetName() != "packages.cozystack.io" { + t.Errorf("name = %q, want %q", obj.GetName(), "packages.cozystack.io") + } + if obj.GetAPIVersion() != "apiextensions.k8s.io/v1" { + t.Errorf("apiVersion = %q, want %q", obj.GetAPIVersion(), "apiextensions.k8s.io/v1") + } +} + +func TestParseManifestFile(t *testing.T) { + tmpDir := t.TempDir() + manifestPath := filepath.Join(tmpDir, "test.yaml") + + content := `apiVersion: v1 +kind: ConfigMap +metadata: + name: cm1 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm2 +` + if err := os.WriteFile(manifestPath, []byte(content), 0600); err != nil { + t.Fatalf("failed to write test manifest: %v", err) + } + + objects, err := ParseManifestFile(manifestPath) + if err != nil { + t.Fatalf("ParseManifestFile() error = %v", err) + } + if len(objects) != 2 { + t.Errorf("ParseManifestFile() returned %d objects, want 2", len(objects)) + } +} + +func TestParseManifestFile_notFound(t *testing.T) { + _, err := ParseManifestFile("/nonexistent/path/test.yaml") + if err == nil { + t.Error("ParseManifestFile() expected error for nonexistent file, got nil") + } +} From abd644122f3ed93e8efc84a43a868c0383bd64ce Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 18 Feb 2026 00:07:09 +0300 Subject: [PATCH 306/889] fix(crdinstall): reject non-CRD objects in embedded manifests Validate that all parsed objects are CustomResourceDefinition before applying with force server-side apply. This prevents accidental application of arbitrary resources if a non-CRD file is placed in the manifests directory. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- internal/crdinstall/install.go | 9 +++++++++ internal/crdinstall/install_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/internal/crdinstall/install.go b/internal/crdinstall/install.go index 4cd93597..f65abf63 100644 --- a/internal/crdinstall/install.go +++ b/internal/crdinstall/install.go @@ -80,6 +80,15 @@ func Install(ctx context.Context, k8sClient client.Client, writeEmbeddedManifest return fmt.Errorf("no objects found in manifests") } + // Validate all objects are CRDs — reject anything else to prevent + // accidental force-apply of arbitrary resources. + for _, obj := range objects { + if obj.GetKind() != "CustomResourceDefinition" { + return fmt.Errorf("unexpected object %s/%s in CRD manifests, only CustomResourceDefinition is allowed", + obj.GetKind(), obj.GetName()) + } + } + logger.Info("Applying Cozystack CRDs", "count", len(objects)) for _, obj := range objects { patchOptions := &client.PatchOptions{ diff --git a/internal/crdinstall/install_test.go b/internal/crdinstall/install_test.go index a4de0da2..870a146c 100644 --- a/internal/crdinstall/install_test.go +++ b/internal/crdinstall/install_test.go @@ -249,6 +249,34 @@ func TestInstall_writeManifestsFails(t *testing.T) { } } +func TestInstall_rejectsNonCRDObjects(t *testing.T) { + log.SetLogger(zap.New(zap.UseDevMode(true))) + + scheme := runtime.NewScheme() + if err := apiextensionsv1.AddToScheme(scheme); err != nil { + t.Fatalf("failed to add apiextensions to scheme: %v", err) + } + + fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build() + + nonCRD := `apiVersion: v1 +kind: Namespace +metadata: + name: should-not-be-applied +` + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + ctx = log.IntoContext(ctx, log.FromContext(context.Background())) + + err := Install(ctx, fakeClient, newCRDManifestWriter(nonCRD)) + if err == nil { + t.Fatal("Install() expected error for non-CRD object, got nil") + } + if !strings.Contains(err.Error(), "unexpected object") { + t.Errorf("Install() error = %v, want error containing 'unexpected object'", err) + } +} + func TestInstall_crdNotEstablished(t *testing.T) { log.SetLogger(zap.New(zap.UseDevMode(true))) From cecc5861af1e8eb7aab966b11de835fa4be3b0c6 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 18 Feb 2026 00:13:25 +0300 Subject: [PATCH 307/889] fix(operator): validate CRD apiVersion, respect SIGTERM during install - Check both apiVersion and kind when validating embedded CRD manifests to prevent applying objects with wrong API group - Move ctrl.SetupSignalHandler() before install phases so CRD and Flux installs respect SIGTERM instead of blocking for up to 2 minutes - Replace custom contains/searchString helpers with strings.Contains Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- cmd/cozystack-operator/main.go | 10 ++++++---- internal/crdinstall/install.go | 6 +++--- internal/manifestutil/crd_test.go | 15 ++------------- 3 files changed, 11 insertions(+), 20 deletions(-) diff --git a/cmd/cozystack-operator/main.go b/cmd/cozystack-operator/main.go index f365a076..a0efdd84 100644 --- a/cmd/cozystack-operator/main.go +++ b/cmd/cozystack-operator/main.go @@ -180,10 +180,13 @@ func main() { os.Exit(1) } + // Set up signal handler early so install phases respect SIGTERM + mgrCtx := ctrl.SetupSignalHandler() + // Install Cozystack CRDs before starting reconcile loop if installCRDs { setupLog.Info("Installing Cozystack CRDs before starting reconcile loop") - installCtx, installCancel := context.WithTimeout(context.Background(), 2*time.Minute) + installCtx, installCancel := context.WithTimeout(mgrCtx, 2*time.Minute) defer installCancel() if err := crdinstall.Install(installCtx, directClient, crdinstall.WriteEmbeddedManifests); err != nil { @@ -196,7 +199,7 @@ func main() { // Install Flux before starting reconcile loop if installFlux { setupLog.Info("Installing Flux components before starting reconcile loop") - installCtx, installCancel := context.WithTimeout(context.Background(), 5*time.Minute) + installCtx, installCancel := context.WithTimeout(mgrCtx, 5*time.Minute) defer installCancel() // Use direct client for pre-start operations (cache is not ready yet) @@ -210,7 +213,7 @@ func main() { // Generate and install platform source resource if specified if platformSourceURL != "" { setupLog.Info("Generating platform source resource", "url", platformSourceURL, "name", platformSourceName, "ref", platformSourceRef) - installCtx, installCancel := context.WithTimeout(context.Background(), 2*time.Minute) + installCtx, installCancel := context.WithTimeout(mgrCtx, 2*time.Minute) defer installCancel() // Use direct client for pre-start operations (cache is not ready yet) @@ -292,7 +295,6 @@ func main() { } setupLog.Info("Starting controller manager") - mgrCtx := ctrl.SetupSignalHandler() if err := mgr.Start(mgrCtx); err != nil { setupLog.Error(err, "problem running manager") os.Exit(1) diff --git a/internal/crdinstall/install.go b/internal/crdinstall/install.go index f65abf63..5143f2e6 100644 --- a/internal/crdinstall/install.go +++ b/internal/crdinstall/install.go @@ -83,9 +83,9 @@ func Install(ctx context.Context, k8sClient client.Client, writeEmbeddedManifest // Validate all objects are CRDs — reject anything else to prevent // accidental force-apply of arbitrary resources. for _, obj := range objects { - if obj.GetKind() != "CustomResourceDefinition" { - return fmt.Errorf("unexpected object %s/%s in CRD manifests, only CustomResourceDefinition is allowed", - obj.GetKind(), obj.GetName()) + if obj.GetAPIVersion() != "apiextensions.k8s.io/v1" || obj.GetKind() != "CustomResourceDefinition" { + return fmt.Errorf("unexpected object %s %s/%s in CRD manifests, only apiextensions.k8s.io/v1 CustomResourceDefinition is allowed", + obj.GetAPIVersion(), obj.GetKind(), obj.GetName()) } } diff --git a/internal/manifestutil/crd_test.go b/internal/manifestutil/crd_test.go index 874377b9..c67b2efa 100644 --- a/internal/manifestutil/crd_test.go +++ b/internal/manifestutil/crd_test.go @@ -18,6 +18,7 @@ package manifestutil import ( "context" + "strings" "testing" "time" @@ -160,7 +161,7 @@ func TestWaitForCRDsEstablished_timeout(t *testing.T) { if err == nil { t.Fatal("WaitForCRDsEstablished() expected error on timeout, got nil") } - if !contains(err.Error(), "packages.cozystack.io") { + if !strings.Contains(err.Error(), "packages.cozystack.io") { t.Errorf("error should mention stuck CRD name, got: %v", err) } } @@ -176,15 +177,3 @@ func TestWaitForCRDsEstablished_empty(t *testing.T) { } } -func contains(s, substr string) bool { - return len(s) >= len(substr) && searchString(s, substr) -} - -func searchString(s, substr string) bool { - for i := 0; i <= len(s)-len(substr); i++ { - if s[i:i+len(substr)] == substr { - return true - } - } - return false -} From 9eb13fdafe50f9970899a9f0a1e59664be2d034a Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 18 Feb 2026 00:15:08 +0300 Subject: [PATCH 308/889] fix(controller): update workload test to use current label name The workload reconciler was refactored to use the label workloads.cozystack.io/monitor but the test still used the old workloadmonitor.cozystack.io/name label, causing the reconciler to delete the workload instead of keeping it. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- internal/controller/workload_controller_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/controller/workload_controller_test.go b/internal/controller/workload_controller_test.go index b454adc3..194a20d3 100644 --- a/internal/controller/workload_controller_test.go +++ b/internal/controller/workload_controller_test.go @@ -43,7 +43,7 @@ func TestWorkloadReconciler_DeletesOnMissingMonitor(t *testing.T) { Name: "pod-foo", Namespace: "default", Labels: map[string]string{ - "workloadmonitor.cozystack.io/name": "missing-monitor", + "workloads.cozystack.io/monitor": "missing-monitor", }, }, } @@ -89,7 +89,7 @@ func TestWorkloadReconciler_KeepsWhenAllExist(t *testing.T) { Name: "pod-foo", Namespace: "default", Labels: map[string]string{ - "workloadmonitor.cozystack.io/name": "mon", + "workloads.cozystack.io/monitor": "mon", }, }, } From 92d261fc1e64a87b3b0fee5a87c5d010f86cb1fb Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 18 Feb 2026 00:25:06 +0300 Subject: [PATCH 309/889] fix: address review findings in operator and tests - Remove duplicate "Starting controller manager" log before install phases, keep only the one before mgr.Start() - Rename misleading test "document without kind returns error" to "decoder rejects document without kind" to match actual behavior - Document Helm uninstall CRD behavior in deployment template comment - Use --health-probe-bind-address=0 consistently with metrics-bind - Exclude all dotfiles in verify-crds diff, not just .gitattributes Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- Makefile | 2 +- cmd/cozystack-operator/main.go | 3 +-- internal/manifestutil/parse_test.go | 2 +- packages/core/installer/templates/cozystack-operator.yaml | 7 ++++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 9707ade4..73bd0bea 100644 --- a/Makefile +++ b/Makefile @@ -81,7 +81,7 @@ test: make -C packages/core/testing test verify-crds: - @diff --recursive packages/core/installer/crds/ internal/crdinstall/manifests/ --exclude=.gitattributes \ + @diff --recursive packages/core/installer/crds/ internal/crdinstall/manifests/ --exclude='.*' \ || (echo "ERROR: CRD manifests out of sync. Run 'make generate' to fix." && exit 1) unit-tests: helm-unit-tests verify-crds diff --git a/cmd/cozystack-operator/main.go b/cmd/cozystack-operator/main.go index a0efdd84..d3c40f7e 100644 --- a/cmd/cozystack-operator/main.go +++ b/cmd/cozystack-operator/main.go @@ -137,8 +137,7 @@ func main() { os.Exit(1) } - // Start the controller manager - setupLog.Info("Starting controller manager") + // Initialize the controller manager mgr, err := ctrl.NewManager(config, ctrl.Options{ Scheme: scheme, Cache: cache.Options{ diff --git a/internal/manifestutil/parse_test.go b/internal/manifestutil/parse_test.go index 30bf7030..860405c7 100644 --- a/internal/manifestutil/parse_test.go +++ b/internal/manifestutil/parse_test.go @@ -59,7 +59,7 @@ metadata: wantCount: 0, }, { - name: "document without kind returns error", + name: "decoder rejects document without kind", input: `apiVersion: v1 metadata: name: test diff --git a/packages/core/installer/templates/cozystack-operator.yaml b/packages/core/installer/templates/cozystack-operator.yaml index 1153ec4d..5cd471cf 100644 --- a/packages/core/installer/templates/cozystack-operator.yaml +++ b/packages/core/installer/templates/cozystack-operator.yaml @@ -54,11 +54,12 @@ spec: - --leader-elect=true - --install-flux=true # CRDs are also in crds/ for initial helm install, but Helm never updates - # them on upgrade. The operator applies embedded CRDs via server-side apply - # on every startup, ensuring they stay up to date. + # them on upgrade and never deletes them on uninstall. The operator applies + # embedded CRDs via server-side apply on every startup, ensuring they stay + # up to date. To fully remove CRDs, delete them manually after helm uninstall. - --install-crds=true - --metrics-bind-address=0 - - --health-probe-bind-address= + - --health-probe-bind-address=0 {{- if .Values.cozystackOperator.disableTelemetry }} - --disable-telemetry {{- end }} From 09805ff382a69ca5891566f7c0f6916bd78a908f Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 18 Feb 2026 00:29:52 +0300 Subject: [PATCH 310/889] fix(manifestutil): check apiVersion in CollectCRDNames for consistent GVK matching CollectCRDNames now requires both apiVersion "apiextensions.k8s.io/v1" and kind "CustomResourceDefinition", consistent with the validation in crdinstall.Install. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- internal/manifestutil/crd.go | 6 ++++-- internal/manifestutil/crd_test.go | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/internal/manifestutil/crd.go b/internal/manifestutil/crd.go index 00421c4c..08468d1b 100644 --- a/internal/manifestutil/crd.go +++ b/internal/manifestutil/crd.go @@ -104,11 +104,13 @@ func WaitForCRDsEstablished(ctx context.Context, k8sClient client.Client, crdNam } // CollectCRDNames returns the names of all CustomResourceDefinition objects -// from the given list of unstructured objects. +// from the given list of unstructured objects. Only objects with +// apiVersion "apiextensions.k8s.io/v1" and kind "CustomResourceDefinition" +// are matched. func CollectCRDNames(objects []*unstructured.Unstructured) []string { var names []string for _, obj := range objects { - if obj.GetKind() == "CustomResourceDefinition" { + if obj.GetAPIVersion() == "apiextensions.k8s.io/v1" && obj.GetKind() == "CustomResourceDefinition" { names = append(names, obj.GetName()) } } diff --git a/internal/manifestutil/crd_test.go b/internal/manifestutil/crd_test.go index c67b2efa..5d046353 100644 --- a/internal/manifestutil/crd_test.go +++ b/internal/manifestutil/crd_test.go @@ -68,6 +68,29 @@ func TestCollectCRDNames(t *testing.T) { } } +func TestCollectCRDNames_ignoresWrongAPIVersion(t *testing.T) { + objects := []*unstructured.Unstructured{ + {Object: map[string]interface{}{ + "apiVersion": "apiextensions.k8s.io/v1", + "kind": "CustomResourceDefinition", + "metadata": map[string]interface{}{"name": "real.crd.io"}, + }}, + {Object: map[string]interface{}{ + "apiVersion": "apiextensions.k8s.io/v1beta1", + "kind": "CustomResourceDefinition", + "metadata": map[string]interface{}{"name": "legacy.crd.io"}, + }}, + } + + names := CollectCRDNames(objects) + if len(names) != 1 { + t.Fatalf("CollectCRDNames() returned %d names, want 1", len(names)) + } + if names[0] != "real.crd.io" { + t.Errorf("names[0] = %q, want %q", names[0], "real.crd.io") + } +} + func TestCollectCRDNames_noCRDs(t *testing.T) { objects := []*unstructured.Unstructured{ {Object: map[string]interface{}{ From 543ce6e5fd040a33bdf1825b29c79cad9953a2b6 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Mon, 16 Feb 2026 19:48:32 +0300 Subject: [PATCH 311/889] [harbor] Add managed Harbor container registry application Add Harbor v2.14.2 as a tenant-level managed service with per-component resource configuration, ingress with TLS termination, and internal PostgreSQL/Redis. Includes: - extra/harbor wrapper chart with HelmRelease, WorkloadMonitors, Ingress - system/harbor with vendored upstream chart (helm.goharbor.io v1.18.2) - harbor-rd ApplicationDefinition for dynamic CRD registration - PackageSource and system.yaml bundle entry - E2E test with Secret and Service verification Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- hack/e2e-apps/harbor.bats | 48 + .../platform/sources/harbor-application.yaml | 29 + .../platform/templates/bundles/system.yaml | 1 + packages/extra/harbor/Chart.yaml | 7 + packages/extra/harbor/Makefile | 7 + packages/extra/harbor/README.md | 54 + packages/extra/harbor/charts/cozy-lib | 1 + packages/extra/harbor/logos/harbor.svg | 1 + .../templates/dashboard-resourcemap.yaml | 46 + packages/extra/harbor/templates/harbor.yaml | 153 +++ packages/extra/harbor/templates/ingress.yaml | 33 + packages/extra/harbor/values.schema.json | 414 +++++++ packages/extra/harbor/values.yaml | 90 ++ packages/system/harbor-rd/Chart.yaml | 3 + packages/system/harbor-rd/Makefile | 4 + packages/system/harbor-rd/cozyrds/harbor.yaml | 47 + .../system/harbor-rd/templates/cozyrd.yaml | 4 + packages/system/harbor-rd/values.yaml | 1 + packages/system/harbor/Chart.yaml | 3 + packages/system/harbor/Makefile | 10 + .../system/harbor/charts/harbor/.helmignore | 6 + .../system/harbor/charts/harbor/Chart.yaml | 22 + packages/system/harbor/charts/harbor/LICENSE | 201 +++ .../system/harbor/charts/harbor/README.md | 775 ++++++++++++ .../harbor/charts/harbor/templates/NOTES.txt | 3 + .../charts/harbor/templates/_helpers.tpl | 606 +++++++++ .../charts/harbor/templates/core/core-cm.yaml | 92 ++ .../harbor/templates/core/core-dpl.yaml | 258 ++++ .../templates/core/core-pre-upgrade-job.yaml | 78 ++ .../harbor/templates/core/core-secret.yaml | 37 + .../harbor/templates/core/core-svc.yaml | 32 + .../harbor/templates/core/core-tls.yaml | 16 + .../templates/database/database-secret.yaml | 12 + .../templates/database/database-ss.yaml | 165 +++ .../templates/database/database-svc.yaml | 21 + .../templates/exporter/exporter-cm-env.yaml | 36 + .../templates/exporter/exporter-dpl.yaml | 146 +++ .../templates/exporter/exporter-secret.yaml | 17 + .../templates/exporter/exporter-svc.yaml | 22 + .../harbor/templates/gateway-apis/route.yaml | 55 + .../harbor/templates/ingress/ingress.yaml | 132 ++ .../harbor/templates/ingress/secret.yaml | 18 + .../harbor/templates/internal/auto-tls.yaml | 86 ++ .../jobservice/jobservice-cm-env.yaml | 37 + .../templates/jobservice/jobservice-cm.yaml | 58 + .../templates/jobservice/jobservice-dpl.yaml | 183 +++ .../templates/jobservice/jobservice-pvc.yaml | 32 + .../jobservice/jobservice-secrets.yaml | 17 + .../templates/jobservice/jobservice-svc.yaml | 25 + .../templates/jobservice/jobservice-tls.yaml | 16 + .../templates/metrics/metrics-svcmon.yaml | 29 + .../templates/nginx/configmap-http.yaml | 136 ++ .../templates/nginx/configmap-https.yaml | 173 +++ .../harbor/templates/nginx/deployment.yaml | 133 ++ .../charts/harbor/templates/nginx/secret.yaml | 26 + .../harbor/templates/nginx/service.yaml | 101 ++ .../harbor/templates/portal/configmap.yaml | 68 + .../harbor/templates/portal/deployment.yaml | 124 ++ .../harbor/templates/portal/service.yaml | 27 + .../charts/harbor/templates/portal/tls.yaml | 16 + .../harbor/templates/redis/service.yaml | 21 + .../harbor/templates/redis/statefulset.yaml | 128 ++ .../templates/registry/registry-cm.yaml | 248 ++++ .../templates/registry/registry-dpl.yaml | 431 +++++++ .../templates/registry/registry-pvc.yaml | 34 + .../templates/registry/registry-secret.yaml | 57 + .../templates/registry/registry-svc.yaml | 27 + .../templates/registry/registry-tls.yaml | 16 + .../templates/registry/registryctl-cm.yaml | 9 + .../registry/registryctl-secret.yaml | 10 + .../harbor/templates/trivy/trivy-secret.yaml | 13 + .../harbor/templates/trivy/trivy-sts.yaml | 237 ++++ .../harbor/templates/trivy/trivy-svc.yaml | 23 + .../harbor/templates/trivy/trivy-tls.yaml | 16 + .../system/harbor/charts/harbor/values.yaml | 1095 +++++++++++++++++ packages/system/harbor/values.yaml | 1 + 76 files changed, 7359 insertions(+) create mode 100644 hack/e2e-apps/harbor.bats create mode 100644 packages/core/platform/sources/harbor-application.yaml create mode 100644 packages/extra/harbor/Chart.yaml create mode 100644 packages/extra/harbor/Makefile create mode 100644 packages/extra/harbor/README.md create mode 120000 packages/extra/harbor/charts/cozy-lib create mode 100644 packages/extra/harbor/logos/harbor.svg create mode 100644 packages/extra/harbor/templates/dashboard-resourcemap.yaml create mode 100644 packages/extra/harbor/templates/harbor.yaml create mode 100644 packages/extra/harbor/templates/ingress.yaml create mode 100644 packages/extra/harbor/values.schema.json create mode 100644 packages/extra/harbor/values.yaml create mode 100644 packages/system/harbor-rd/Chart.yaml create mode 100644 packages/system/harbor-rd/Makefile create mode 100644 packages/system/harbor-rd/cozyrds/harbor.yaml create mode 100644 packages/system/harbor-rd/templates/cozyrd.yaml create mode 100644 packages/system/harbor-rd/values.yaml create mode 100644 packages/system/harbor/Chart.yaml create mode 100644 packages/system/harbor/Makefile create mode 100644 packages/system/harbor/charts/harbor/.helmignore create mode 100644 packages/system/harbor/charts/harbor/Chart.yaml create mode 100644 packages/system/harbor/charts/harbor/LICENSE create mode 100644 packages/system/harbor/charts/harbor/README.md create mode 100644 packages/system/harbor/charts/harbor/templates/NOTES.txt create mode 100644 packages/system/harbor/charts/harbor/templates/_helpers.tpl create mode 100644 packages/system/harbor/charts/harbor/templates/core/core-cm.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/core/core-dpl.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/core/core-pre-upgrade-job.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/core/core-secret.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/core/core-svc.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/core/core-tls.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/database/database-secret.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/database/database-ss.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/database/database-svc.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/exporter/exporter-cm-env.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/exporter/exporter-dpl.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/exporter/exporter-secret.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/exporter/exporter-svc.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/gateway-apis/route.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/ingress/ingress.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/ingress/secret.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/internal/auto-tls.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/jobservice/jobservice-cm-env.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/jobservice/jobservice-cm.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/jobservice/jobservice-dpl.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/jobservice/jobservice-pvc.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/jobservice/jobservice-secrets.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/jobservice/jobservice-svc.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/jobservice/jobservice-tls.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/metrics/metrics-svcmon.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/nginx/configmap-http.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/nginx/configmap-https.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/nginx/deployment.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/nginx/secret.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/nginx/service.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/portal/configmap.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/portal/deployment.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/portal/service.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/portal/tls.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/redis/service.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/redis/statefulset.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/registry/registry-cm.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/registry/registry-dpl.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/registry/registry-pvc.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/registry/registry-secret.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/registry/registry-svc.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/registry/registry-tls.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/registry/registryctl-cm.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/registry/registryctl-secret.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/trivy/trivy-secret.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/trivy/trivy-sts.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/trivy/trivy-svc.yaml create mode 100644 packages/system/harbor/charts/harbor/templates/trivy/trivy-tls.yaml create mode 100644 packages/system/harbor/charts/harbor/values.yaml create mode 100644 packages/system/harbor/values.yaml diff --git a/hack/e2e-apps/harbor.bats b/hack/e2e-apps/harbor.bats new file mode 100644 index 00000000..8d1daecb --- /dev/null +++ b/hack/e2e-apps/harbor.bats @@ -0,0 +1,48 @@ +#!/usr/bin/env bats + +@test "Create Harbor" { + name='harbor' + kubectl apply -f- < \ No newline at end of file diff --git a/packages/extra/harbor/templates/dashboard-resourcemap.yaml b/packages/extra/harbor/templates/dashboard-resourcemap.yaml new file mode 100644 index 00000000..f8d91a61 --- /dev/null +++ b/packages/extra/harbor/templates/dashboard-resourcemap.yaml @@ -0,0 +1,46 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ .Release.Name }}-dashboard-resources +rules: +- apiGroups: + - "" + resources: + - services + resourceNames: + - {{ .Release.Name }} + verbs: ["get", "list", "watch"] +- apiGroups: + - "" + resources: + - secrets + resourceNames: + - {{ .Release.Name }}-credentials + verbs: ["get", "list", "watch"] +- apiGroups: + - networking.k8s.io + resources: + - ingresses + resourceNames: + - {{ .Release.Name }}-ingress + verbs: ["get", "list", "watch"] +- apiGroups: + - cozystack.io + resources: + - workloadmonitors + resourceNames: + - {{ .Release.Name }}-core + - {{ .Release.Name }}-registry + - {{ .Release.Name }}-portal + verbs: ["get", "list", "watch"] +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ .Release.Name }}-dashboard-resources +subjects: +{{ include "cozy-lib.rbac.subjectsForTenantAndAccessLevel" (list "super-admin" .Release.Namespace) }} +roleRef: + kind: Role + name: {{ .Release.Name }}-dashboard-resources + apiGroup: rbac.authorization.k8s.io diff --git a/packages/extra/harbor/templates/harbor.yaml b/packages/extra/harbor/templates/harbor.yaml new file mode 100644 index 00000000..23c06641 --- /dev/null +++ b/packages/extra/harbor/templates/harbor.yaml @@ -0,0 +1,153 @@ +{{- $ingress := .Values._namespace.ingress }} +{{- $host := .Values._namespace.host }} +{{- $harborHost := .Values.host | default (printf "harbor.%s" $host) }} + +{{- $existingSecret := lookup "v1" "Secret" .Release.Namespace (printf "%s-credentials" .Release.Name) }} +{{- $adminPassword := randAlphaNum 16 }} +{{- if $existingSecret }} + {{- $adminPassword = index $existingSecret.data "admin-password" | b64dec }} +{{- end }} + +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-credentials +stringData: + admin-password: {{ $adminPassword | quote }} + url: https://{{ $harborHost }} + +--- + +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: {{ .Release.Name }}-system + labels: + sharding.fluxcd.io/key: tenants +spec: + chartRef: + kind: ExternalArtifact + name: cozystack-harbor-application-default-harbor-system + namespace: cozy-system + interval: 5m + timeout: 15m + install: + remediation: + retries: -1 + upgrade: + force: true + remediation: + retries: -1 + valuesFrom: + - kind: Secret + name: cozystack-values + values: + harbor: + fullnameOverride: {{ .Release.Name }} + harborAdminPassword: {{ $adminPassword | quote }} + externalURL: https://{{ $harborHost }} + expose: + type: clusterIP + clusterIP: + name: {{ .Release.Name }} + tls: + enabled: false + persistence: + enabled: true + resourcePolicy: "keep" + persistentVolumeClaim: + registry: + size: {{ .Values.registry.size }} + {{- with .Values.storageClass }} + storageClass: {{ . }} + {{- end }} + database: + size: {{ .Values.database.size }} + {{- with .Values.storageClass }} + storageClass: {{ . }} + {{- end }} + redis: + size: {{ .Values.redis.size }} + {{- with .Values.storageClass }} + storageClass: {{ . }} + {{- end }} + {{- if .Values.trivy.enabled }} + trivy: + size: {{ .Values.trivy.size }} + {{- with .Values.storageClass }} + storageClass: {{ . }} + {{- end }} + {{- end }} + portal: + resources: {{- include "cozy-lib.resources.defaultingSanitize" (list "nano" (dict) $) | nindent 10 }} + core: + resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.core.resourcesPreset .Values.core.resources $) | nindent 10 }} + registry: + resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.registry.resourcesPreset .Values.registry.resources $) | nindent 10 }} + jobservice: + resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.jobservice.resourcesPreset .Values.jobservice.resources $) | nindent 10 }} + trivy: + enabled: {{ .Values.trivy.enabled }} + {{- if .Values.trivy.enabled }} + resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.trivy.resourcesPreset .Values.trivy.resources $) | nindent 10 }} + {{- end }} + database: + type: internal + internal: + resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.database.resourcesPreset .Values.database.resources $) | nindent 12 }} + redis: + type: internal + internal: + resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.redis.resourcesPreset .Values.redis.resources $) | nindent 12 }} + metrics: + enabled: true + serviceMonitor: + enabled: true + +--- + +apiVersion: cozystack.io/v1alpha1 +kind: WorkloadMonitor +metadata: + name: {{ $.Release.Name }}-core +spec: + replicas: 1 + minReplicas: 1 + kind: harbor + type: core + selector: + release: {{ $.Release.Name }}-system + component: core + version: {{ $.Chart.Version }} + +--- + +apiVersion: cozystack.io/v1alpha1 +kind: WorkloadMonitor +metadata: + name: {{ $.Release.Name }}-registry +spec: + replicas: 1 + minReplicas: 1 + kind: harbor + type: registry + selector: + release: {{ $.Release.Name }}-system + component: registry + version: {{ $.Chart.Version }} + +--- + +apiVersion: cozystack.io/v1alpha1 +kind: WorkloadMonitor +metadata: + name: {{ $.Release.Name }}-portal +spec: + replicas: 1 + minReplicas: 1 + kind: harbor + type: portal + selector: + release: {{ $.Release.Name }}-system + component: portal + version: {{ $.Chart.Version }} diff --git a/packages/extra/harbor/templates/ingress.yaml b/packages/extra/harbor/templates/ingress.yaml new file mode 100644 index 00000000..785b4a79 --- /dev/null +++ b/packages/extra/harbor/templates/ingress.yaml @@ -0,0 +1,33 @@ +{{- $ingress := .Values._namespace.ingress }} +{{- $host := .Values._namespace.host }} +{{- $harborHost := .Values.host | default (printf "harbor.%s" $host) }} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ .Release.Name }}-ingress + annotations: + nginx.ingress.kubernetes.io/proxy-body-size: "0" + nginx.ingress.kubernetes.io/proxy-read-timeout: "900" + nginx.ingress.kubernetes.io/proxy-send-timeout: "900" + nginx.ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/backend-protocol: "HTTP" + acme.cert-manager.io/http01-ingress-class: {{ $ingress }} + cert-manager.io/cluster-issuer: letsencrypt-prod +spec: + ingressClassName: {{ $ingress }} + tls: + - hosts: + - {{ $harborHost }} + secretName: {{ .Release.Name }}-ingress-tls + rules: + - host: {{ $harborHost }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ .Release.Name }} + port: + number: 80 diff --git a/packages/extra/harbor/values.schema.json b/packages/extra/harbor/values.schema.json new file mode 100644 index 00000000..a4632de7 --- /dev/null +++ b/packages/extra/harbor/values.schema.json @@ -0,0 +1,414 @@ +{ + "title": "Chart Values", + "type": "object", + "properties": { + "core": { + "description": "Core API server configuration.", + "type": "object", + "default": {}, + "properties": { + "resources": { + "description": "Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.", + "type": "object", + "default": {}, + "properties": { + "cpu": { + "description": "Number of CPU cores allocated.", + "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": "Amount of memory allocated.", + "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 + } + } + }, + "resourcesPreset": { + "description": "Default sizing preset used when `resources` is omitted.", + "type": "string", + "default": "small", + "enum": [ + "nano", + "micro", + "small", + "medium", + "large", + "xlarge", + "2xlarge" + ] + } + } + }, + "database": { + "description": "Internal PostgreSQL database configuration.", + "type": "object", + "default": {}, + "required": [ + "size" + ], + "properties": { + "resources": { + "description": "Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.", + "type": "object", + "default": {}, + "properties": { + "cpu": { + "description": "Number of CPU cores allocated.", + "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": "Amount of memory allocated.", + "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 + } + } + }, + "resourcesPreset": { + "description": "Default sizing preset used when `resources` is omitted.", + "type": "string", + "default": "nano", + "enum": [ + "nano", + "micro", + "small", + "medium", + "large", + "xlarge", + "2xlarge" + ] + }, + "size": { + "description": "Persistent Volume size for database storage.", + "default": "5Gi", + "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": "Hostname for external access to Harbor (defaults to 'harbor' subdomain for the tenant host).", + "type": "string", + "default": "" + }, + "jobservice": { + "description": "Background job service configuration.", + "type": "object", + "default": {}, + "properties": { + "resources": { + "description": "Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.", + "type": "object", + "default": {}, + "properties": { + "cpu": { + "description": "Number of CPU cores allocated.", + "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": "Amount of memory allocated.", + "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 + } + } + }, + "resourcesPreset": { + "description": "Default sizing preset used when `resources` is omitted.", + "type": "string", + "default": "nano", + "enum": [ + "nano", + "micro", + "small", + "medium", + "large", + "xlarge", + "2xlarge" + ] + } + } + }, + "redis": { + "description": "Internal Redis cache configuration.", + "type": "object", + "default": {}, + "required": [ + "size" + ], + "properties": { + "resources": { + "description": "Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.", + "type": "object", + "default": {}, + "properties": { + "cpu": { + "description": "Number of CPU cores allocated.", + "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": "Amount of memory allocated.", + "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 + } + } + }, + "resourcesPreset": { + "description": "Default sizing preset used when `resources` is omitted.", + "type": "string", + "default": "nano", + "enum": [ + "nano", + "micro", + "small", + "medium", + "large", + "xlarge", + "2xlarge" + ] + }, + "size": { + "description": "Persistent Volume size for cache storage.", + "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 + } + } + }, + "registry": { + "description": "Container image registry configuration.", + "type": "object", + "default": {}, + "required": [ + "size" + ], + "properties": { + "resources": { + "description": "Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.", + "type": "object", + "default": {}, + "properties": { + "cpu": { + "description": "Number of CPU cores allocated.", + "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": "Amount of memory allocated.", + "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 + } + } + }, + "resourcesPreset": { + "description": "Default sizing preset used when `resources` is omitted.", + "type": "string", + "default": "small", + "enum": [ + "nano", + "micro", + "small", + "medium", + "large", + "xlarge", + "2xlarge" + ] + }, + "size": { + "description": "Persistent Volume size for container image storage.", + "default": "50Gi", + "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 + } + } + }, + "storageClass": { + "description": "StorageClass used to store the data.", + "type": "string", + "default": "" + }, + "trivy": { + "description": "Trivy vulnerability scanner configuration.", + "type": "object", + "default": {}, + "required": [ + "enabled", + "size" + ], + "properties": { + "enabled": { + "description": "Enable or disable the vulnerability scanner.", + "type": "boolean", + "default": true + }, + "resources": { + "description": "Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.", + "type": "object", + "default": {}, + "properties": { + "cpu": { + "description": "Number of CPU cores allocated.", + "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": "Amount of memory allocated.", + "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 + } + } + }, + "resourcesPreset": { + "description": "Default sizing preset used when `resources` is omitted.", + "type": "string", + "default": "nano", + "enum": [ + "nano", + "micro", + "small", + "medium", + "large", + "xlarge", + "2xlarge" + ] + }, + "size": { + "description": "Persistent Volume size for vulnerability database cache.", + "default": "5Gi", + "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 + } + } + } + } +} \ No newline at end of file diff --git a/packages/extra/harbor/values.yaml b/packages/extra/harbor/values.yaml new file mode 100644 index 00000000..50d2ff5f --- /dev/null +++ b/packages/extra/harbor/values.yaml @@ -0,0 +1,90 @@ +## +## @section Common parameters +## + +## @param {string} [host] - Hostname for external access to Harbor (defaults to 'harbor' subdomain for the tenant host). +host: "" + +## @param {string} storageClass - StorageClass used to store the data. +storageClass: "" + +## +## @section Component configuration +## + +## @typedef {struct} Resources - Resource configuration. +## @field {quantity} [cpu] - Number of CPU cores allocated. +## @field {quantity} [memory] - Amount of memory allocated. + +## @enum {string} ResourcesPreset - Default sizing preset. +## @value nano +## @value micro +## @value small +## @value medium +## @value large +## @value xlarge +## @value 2xlarge + +## @typedef {struct} Core - Core API server configuration. +## @field {Resources} [resources] - Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied. +## @field {ResourcesPreset} [resourcesPreset] - Default sizing preset used when `resources` is omitted. + +## @param {Core} core - Core API server configuration. +core: + resources: {} + resourcesPreset: "small" + +## @typedef {struct} Registry - Container image registry configuration. +## @field {quantity} size - Persistent Volume size for container image storage. +## @field {Resources} [resources] - Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied. +## @field {ResourcesPreset} [resourcesPreset] - Default sizing preset used when `resources` is omitted. + +## @param {Registry} registry - Container image registry configuration. +registry: + size: 50Gi + resources: {} + resourcesPreset: "small" + +## @typedef {struct} Jobservice - Background job service configuration. +## @field {Resources} [resources] - Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied. +## @field {ResourcesPreset} [resourcesPreset] - Default sizing preset used when `resources` is omitted. + +## @param {Jobservice} jobservice - Background job service configuration. +jobservice: + resources: {} + resourcesPreset: "nano" + +## @typedef {struct} Trivy - Trivy vulnerability scanner configuration. +## @field {bool} enabled - Enable or disable the vulnerability scanner. +## @field {quantity} size - Persistent Volume size for vulnerability database cache. +## @field {Resources} [resources] - Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied. +## @field {ResourcesPreset} [resourcesPreset] - Default sizing preset used when `resources` is omitted. + +## @param {Trivy} trivy - Trivy vulnerability scanner configuration. +trivy: + enabled: true + size: 5Gi + resources: {} + resourcesPreset: "nano" + +## @typedef {struct} Database - Internal PostgreSQL database configuration. +## @field {quantity} size - Persistent Volume size for database storage. +## @field {Resources} [resources] - Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied. +## @field {ResourcesPreset} [resourcesPreset] - Default sizing preset used when `resources` is omitted. + +## @param {Database} database - Internal PostgreSQL database configuration. +database: + size: 5Gi + resources: {} + resourcesPreset: "nano" + +## @typedef {struct} Redis - Internal Redis cache configuration. +## @field {quantity} size - Persistent Volume size for cache storage. +## @field {Resources} [resources] - Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied. +## @field {ResourcesPreset} [resourcesPreset] - Default sizing preset used when `resources` is omitted. + +## @param {Redis} redis - Internal Redis cache configuration. +redis: + size: 1Gi + resources: {} + resourcesPreset: "nano" diff --git a/packages/system/harbor-rd/Chart.yaml b/packages/system/harbor-rd/Chart.yaml new file mode 100644 index 00000000..c27f19d0 --- /dev/null +++ b/packages/system/harbor-rd/Chart.yaml @@ -0,0 +1,3 @@ +apiVersion: v2 +name: harbor-rd +version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process diff --git a/packages/system/harbor-rd/Makefile b/packages/system/harbor-rd/Makefile new file mode 100644 index 00000000..ed36644a --- /dev/null +++ b/packages/system/harbor-rd/Makefile @@ -0,0 +1,4 @@ +export NAME=harbor-rd +export NAMESPACE=cozy-system + +include ../../../hack/package.mk diff --git a/packages/system/harbor-rd/cozyrds/harbor.yaml b/packages/system/harbor-rd/cozyrds/harbor.yaml new file mode 100644 index 00000000..da8cfb62 --- /dev/null +++ b/packages/system/harbor-rd/cozyrds/harbor.yaml @@ -0,0 +1,47 @@ +apiVersion: cozystack.io/v1alpha1 +kind: ApplicationDefinition +metadata: + name: harbor +spec: + application: + kind: Harbor + singular: harbor + plural: harbors + openAPISchema: |- + {"title":"Chart Values","type":"object","properties":{"core":{"description":"Core API server configuration.","type":"object","default":{},"properties":{"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"small","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}},"database":{"description":"Internal PostgreSQL database configuration.","type":"object","default":{},"required":["size"],"properties":{"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"nano","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]},"size":{"description":"Persistent Volume size for database storage.","default":"5Gi","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":"Hostname for external access to Harbor (defaults to 'harbor' subdomain for the tenant host).","type":"string","default":""},"jobservice":{"description":"Background job service configuration.","type":"object","default":{},"properties":{"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"nano","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}},"redis":{"description":"Internal Redis cache configuration.","type":"object","default":{},"required":["size"],"properties":{"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"nano","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]},"size":{"description":"Persistent Volume size for cache storage.","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}}},"registry":{"description":"Container image registry configuration.","type":"object","default":{},"required":["size"],"properties":{"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"small","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]},"size":{"description":"Persistent Volume size for container image storage.","default":"50Gi","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}}},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":""},"trivy":{"description":"Trivy vulnerability scanner configuration.","type":"object","default":{},"required":["enabled","size"],"properties":{"enabled":{"description":"Enable or disable the vulnerability scanner.","type":"boolean","default":true},"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"nano","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]},"size":{"description":"Persistent Volume size for vulnerability database cache.","default":"5Gi","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: + sharding.fluxcd.io/key: tenants + internal.cozystack.io/tenantmodule: "true" + chartRef: + kind: ExternalArtifact + name: cozystack-harbor-application-default-harbor + namespace: cozy-system + dashboard: + category: PaaS + singular: Harbor + plural: Harbor + name: harbor + description: Managed Harbor container registry + module: true + tags: + - registry + - container + icon: "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHJvbGU9ImltZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHZpZXdCb3g9IjEuMDUgLTEuOTUgMzU5LjQxIDM2MS42NiI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQiIHgxPSIyNjQuNzkiIHgyPSIyNjcuMjciIHkxPSI5NTIuMzkiIHkyPSI5NTIuMzkiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMzAuNDMgMCAwIC0zMC40MyAtNzk1NS4yMiAyOTI4NS43NSkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiM2MGI5MzIiLz48c3RvcCBvZmZzZXQ9Ii4yOCIgc3RvcC1jb2xvcj0iIzYwYjkzMiIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzM2N2MzNCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQtMiIgeDE9IjI2My43NyIgeDI9IjI2Ni4yNiIgeTE9Ijk1NS42NSIgeTI9Ijk1NS42NSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgyNy4yMSAwIDAgLTI3LjIxIC03MDczLjg1IDI2MTY5LjQxKSIgeGxpbms6aHJlZj0iI2xpbmVhci1ncmFkaWVudCIvPjxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50LTMiIHgxPSIyNjMuMjgiIHgyPSIyNjUuNzYiIHkxPSI5NTMuNzQiIHkyPSI5NTMuNzQiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMjUuNzUgMCAwIC0yNS43NSAtNjY3MS4xMyAyNDgxMi4yMykiIHhsaW5rOmhyZWY9IiNsaW5lYXItZ3JhZGllbnQiLz48bGluZWFyR3JhZGllbnQgaWQ9ImxpbmVhci1ncmFkaWVudC00IiB4MT0iMjYzLjc3IiB4Mj0iMjY2LjI1IiB5MT0iOTUzLjIiIHkyPSI5NTMuMiIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgyNy4xIDAgMCAtMjcuMSAtNzA0MC45IDI2MTAyLjQ5KSIgeGxpbms6aHJlZj0iI2xpbmVhci1ncmFkaWVudCIvPjxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50LTUiIHgxPSIyNjIuNzMiIHgyPSIyNjUuMjEiIHkxPSI5NTQuMzQiIHkyPSI5NTQuMzQiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMjQuNCAwIDAgLTI0LjQgLTYzMDEuMzYgMjM1MjEuOTcpIiB4bGluazpocmVmPSIjbGluZWFyLWdyYWRpZW50Ii8+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQtNiIgeDE9IjI3Mi4xNCIgeDI9IjI3NC42MiIgeTE9Ijk1NS4xNSIgeTI9Ijk1NS4xNSIgZ3JhZGllbnRUcmFuc2Zvcm09InNjYWxlKDY2LjA5IC02Ni4wOSkgcm90YXRlKDM2LjUyIDE1ODguMTUzIDY4LjE0OCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiM0NTk2ZDgiLz48c3RvcCBvZmZzZXQ9Ii4yIiBzdG9wLWNvbG9yPSIjNDU5NmQ4Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjZmZmIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9ImxpbmVhci1ncmFkaWVudC03IiB4MT0iMjcwLjY1IiB4Mj0iMjczLjEzIiB5MT0iOTUyLjM4IiB5Mj0iOTUyLjM4IiBncmFkaWVudFRyYW5zZm9ybT0ic2NhbGUoNzcuOCAtNzcuOCkgcm90YXRlKC0xMS41NCAtNDU4Ny4yMDkgMTgwMy4zMjMpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjNDE5NGQ3Ii8+PHN0b3Agb2Zmc2V0PSIuMiIgc3RvcC1jb2xvcj0iIzQxOTRkNyIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI2ZmZiIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQtOCIgeDE9IjI3MC45NyIgeDI9IjI3My40NSIgeTE9Ijk1My43NSIgeTI9Ijk1My43NSIgZ3JhZGllbnRUcmFuc2Zvcm09InNjYWxlKDcxLjM1IC03MS4zNSkgcm90YXRlKDEwLjIzIDU0NzcuMzcgLTEwMjQuNjAyKSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iIzQxOTRkNyIvPjxzdG9wIG9mZnNldD0iLjMzIiBzdG9wLWNvbG9yPSIjNDQ5NmQ4Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjZmZmIi8+PC9saW5lYXJHcmFkaWVudD48Y2xpcFBhdGggaWQ9ImNsaXAtcGF0aCI+PHBhdGggZD0iTTE3NC40IDMwMi41MmwtNjguNjUtMzAuMjYtMy44MyA0My4yMSA3NS41IDIzLjk4LTMuMDItMzYuOTN6IiBjbGFzcz0iY2xzLTEiLz48L2NsaXBQYXRoPjxjbGlwUGF0aCBpZD0iY2xpcC1wYXRoLTIiPjxwYXRoIGQ9Ik0xMTkuMTkgMTM1LjM4aDM4LjU1djMyLjg5aC05LjE1di0xNGExMC4xMyAxMC4xMyAwIDEgMC0yMC4yNiAwdjE0aC05LjE1em0tNy43IDcybDU3LjIgMjUuMjEtMy45NC00OC4yNGg3LjQ5di0xNi4wOGgtNS41NXYtMzIuODloNS41NXYtOS40NWwtMzAuODYtMzAuMTl2LTIuMTJhMi45MSAyLjkxIDAgMCAwLTUuODIgMHYyLjEybC0zMC44NiAzMC4xOXY5LjQ1aDUuNTZ2MzIuODloLTUuNTZ2MTYuMTJoOC44MnoiIGNsYXNzPSJjbHMtMSIvPjwvY2xpcFBhdGg+PGNsaXBQYXRoIGlkPSJjbGlwLXBhdGgtMyI+PHBhdGggZD0iTTEwOC4xNCAyNDUuMjhsNjMuODggMjguMTYtLjk2LTExLjczLTYxLjk2LTI3LjMtLjk2IDEwLjg3eiIgY2xhc3M9ImNscy0xIi8+PC9jbGlwUGF0aD48Y2xpcFBhdGggaWQ9ImNsaXAtcGF0aC00Ij48cGF0aCBkPSJNMTA2LjQ2IDI2NC4yMWw2Ny4yMyAyOS42My0uOTYtMTEuNzItNjUuMzEtMjguNzgtLjk2IDEwLjg3eiIgY2xhc3M9ImNscy0xIi8+PC9jbGlwUGF0aD48Y2xpcFBhdGggaWQ9ImNsaXAtcGF0aC01Ij48cGF0aCBkPSJNMTEwLjc3IDIxNS40OGwtLjk2IDEwLjg3IDYwLjU0IDI2LjY4LS45NS0xMS43Mi01OC42My0yNS44M3oiIGNsYXNzPSJjbHMtMSIvPjwvY2xpcFBhdGg+PGNsaXBQYXRoIGlkPSJjbGlwLXBhdGgtNiI+PHBhdGggZD0iTTMxMy4xMyA2Ny41OWExNzUuMzEgMTc1LjMxIDAgMCAwLTI5Ljc1LTI4LjEzYy0xLjU3LTEuMTctMy4xOC0yLjMtNC43OS0zLjQyTDI1NiA1OS41bC04Mi4zNCA4NS42MyAxMTMuNDEtNTguNjggMjkuMDctMTVjLTEuMDEtMS4zMS0xLjk4LTIuNjItMy4wMS0zLjg2eiIgY2xhc3M9ImNscy0xIi8+PC9jbGlwUGF0aD48Y2xpcFBhdGggaWQ9ImNsaXAtcGF0aC03Ij48cGF0aCBkPSJNMzUzLjU5IDE3Ny42MWMwLTItLjE0LTQtLjIyLTUuOTNsLTMyLjIxLTIuMzEtMTQ3LjQ3LTEwLjU4TDMxOC4zNiAyMDlsMzAuNDEgMTAuNTVjLjA5LS4zNi4xOS0uNzEuMjgtMS4wOGExNzMuNjUgMTczLjY1IDAgMCAwIDQuNTctMzkuNDd2LTEuMzd6IiBjbGFzcz0iY2xzLTEiLz48L2NsaXBQYXRoPjxjbGlwUGF0aCBpZD0iY2xpcC1wYXRoLTgiPjxwYXRoIGQ9Ik0zNDguODQgMTM4LjYxYTE3Mi41NSAxNzIuNTUgMCAwIDAtMTMuODQtMzcuN2wtMzAuOTQgOS45NEwxNzUuOTIgMTUybDE0Mi00LjgzIDMyLjUtMS4xMWMtLjUtMi40OC0uOTktNC45OC0xLjU4LTcuNDV6IiBjbGFzcz0iY2xzLTEiLz48L2NsaXBQYXRoPjxzdHlsZT4uY2xzLTF7ZmlsbDpub25lfS5jbHMtMTN7ZmlsbDojNjk2NTY2fTwvc3R5bGU+PC9kZWZzPjxnIGlkPSJnMTIiPjxwYXRoIGlkPSJwYXRoMTQiIGZpbGw9IiNmZmYiIGQ9Ik0zMC44OSAxNzlhMTQ4Ljg3IDE0OC44NyAwIDEgMSAxNDguODcgMTQ4Ljg1QTE0OC44NyAxNDguODcgMCAwIDEgMzAuODkgMTc5Ii8+PGcgaWQ9ImczMCIgY2xpcC1wYXRoPSJ1cmwoI2NsaXAtcGF0aCkiPjxnIGlkPSJnMzIiPjxwYXRoIGlkPSJwYXRoNDYiIGZpbGw9InVybCgjbGluZWFyLWdyYWRpZW50KSIgZD0iTTE3NC40IDMwMi41MmwtNjguNjUtMzAuMjUtMy44MiA0My4yIDc1LjUgMjQtMy0zNi45MyIvPjwvZz48L2c+PGcgaWQ9Imc0OCIgY2xpcC1wYXRoPSJ1cmwoI2NsaXAtcGF0aC0yKSI+PGcgaWQ9Imc1MCI+PHBhdGggaWQ9InBhdGg2NCIgZmlsbD0idXJsKCNsaW5lYXItZ3JhZGllbnQtMikiIGQ9Ik0xMTkuMTkgMTM1LjM4aDM4LjU1djMyLjg5aC05LjE1di0xNGExMC4xMyAxMC4xMyAwIDEgMC0yMC4yNiAwdjE0aC05LjE1em0tNy43IDcybDU3LjIgMjUuMjEtMy45NC00OC4yNGg3LjQ5di0xNi4wOGgtNS41NXYtMzIuODloNS41NXYtOS40NWwtMzAuODYtMzAuMTl2LTIuMTJhMi45MSAyLjkxIDAgMCAwLTUuODIgMHYyLjEybC0zMC44NiAzMC4xOXY5LjQ1aDUuNTZ2MzIuODloLTUuNTZ2MTYuMTJoOC44MmwtMiAyMyIvPjwvZz48L2c+PGcgaWQ9Imc2NiIgY2xpcC1wYXRoPSJ1cmwoI2NsaXAtcGF0aC0zKSI+PGcgaWQ9Imc2OCI+PHBhdGggaWQ9InBhdGg4MiIgZmlsbD0idXJsKCNsaW5lYXItZ3JhZGllbnQtMykiIGQ9Ik0xMDguMTMgMjQ1LjI4TDE3MiAyNzMuNDRsLTEtMTEuNzItNjItMjcuMzEtMSAxMC44NyIvPjwvZz48L2c+PGcgaWQ9Imc4NCIgY2xpcC1wYXRoPSJ1cmwoI2NsaXAtcGF0aC00KSI+PGcgaWQ9Imc4NiI+PHBhdGggaWQ9InBhdGgxMDAiIGZpbGw9InVybCgjbGluZWFyLWdyYWRpZW50LTQpIiBkPSJNMTA2LjQ2IDI2NC4yMWw2Ny4yMyAyOS42My0xLTExLjcyLTY1LjMxLTI4Ljc4LTEgMTAuODciLz48L2c+PC9nPjxnIGlkPSJnMTAyIiBjbGlwLXBhdGg9InVybCgjY2xpcC1wYXRoLTUpIj48ZyBpZD0iZzEwNCI+PHBhdGggaWQ9InBhdGgxMTgiIGZpbGw9InVybCgjbGluZWFyLWdyYWRpZW50LTUpIiBkPSJNMTEwLjc3IDIxNS40OGwtMSAxMC44OEwxNzAuMzUgMjUzbC0xLTExLjcyLTU4LjYyLTI1LjgzIi8+PC9nPjwvZz48cGF0aCBpZD0icGF0aDEyMCIgZD0iTTMwNC4wNyAxMTAuODVsMzAuOTMtOS45NGMtLjExLS4yMi0uMjEtLjQ1LS4zMi0uNjZhMTc0LjQxIDE3NC40MSAwIDAgMC0xOC41NS0yOC44M2wtMjkuMDcgMTVhMTQyLjcxIDE0Mi43MSAwIDAgMSAxNi43MyAyMy44N2MuMS4xNy4xOC4zNS4yNy41MyIgY2xhc3M9ImNscy0xMyIvPjxwYXRoIGlkPSJwYXRoMTIyIiBkPSJNMzIxLjE1IDE2OS4zN2wzMi4yMSAyLjMxYTE3Mi44NiAxNzIuODYgMCAwIDAtMy0yNS41OWwtMzIuNSAxLjExYTE0MSAxNDEgMCAwIDEgMy4yNSAyMi4xNyIgY2xhc3M9ImNscy0xMyIvPjxwYXRoIGlkPSJwYXRoMTI0IiBkPSJNMTgyIDMyMC44MWMtNzguMiAwLTE0MS44My02My42Mi0xNDEuODMtMTQxLjgyUzEwMy44MiAzNy4xNiAxODIgMzcuMTZhMTQwLjkzIDE0MC45MyAwIDAgMSA3Ni4zIDIyLjM0TDI4MC44NSAzNkExNzIuODYgMTcyLjg2IDAgMCAwIDE4MiA1LjExQzg2LjE1IDUuMTEgOC4xNSA4My4xMSA4LjE1IDE3OXM3OCAxNzMuODUgMTczLjg1IDE3My44NWM4MS45IDAgMTUwLjY5LTU3IDE2OS0xMzMuMzJMMzIwLjYyIDIwOWMtMTMuOCA2My44NC03MC42OSAxMTEuODMtMTM4LjYgMTExLjgzIiBjbGFzcz0iY2xzLTEzIi8+PGcgaWQ9ImcxMjYiIGNsaXAtcGF0aD0idXJsKCNjbGlwLXBhdGgtNikiPjxnIGlkPSJnMTI4Ij48cGF0aCBpZD0icGF0aDE0MiIgZmlsbD0idXJsKCNsaW5lYXItZ3JhZGllbnQtNikiIGQ9Ik0zMTMuMTMgNjcuNTlhMTc1LjMxIDE3NS4zMSAwIDAgMC0yOS43NS0yOC4xM2MtMS41Ny0xLjE3LTMuMTgtMi4zLTQuNzktMy40MkwyNTYgNTkuNWwtODIuMzQgODUuNjMgMTEzLjQxLTU4LjY4IDI5LjA3LTE1Yy0xLTEuMjctMi0yLjU4LTMtMy44MiIvPjwvZz48L2c+PGcgaWQ9ImcxNDQiIGNsaXAtcGF0aD0idXJsKCNjbGlwLXBhdGgtNykiPjxnIGlkPSJnMTQ2Ij48cGF0aCBpZD0icGF0aDE2MCIgZmlsbD0idXJsKCNsaW5lYXItZ3JhZGllbnQtNykiIGQ9Ik0zNTMuNTkgMTc3LjYxYzAtMi0uMTQtNC0uMjItNS45M2wtMzIuMjEtMi4zMS0xNDcuNDctMTAuNThMMzE4LjM2IDIwOWwzMC40MSAxMC41NWMuMDktLjM2LjE5LS43MS4yOC0xLjA4YTE3My42NSAxNzMuNjUgMCAwIDAgNC41Ny0zOS40N3YtMS4zNyIvPjwvZz48L2c+PGcgaWQ9ImcxNjIiIGNsaXAtcGF0aD0idXJsKCNjbGlwLXBhdGgtOCkiPjxnIGlkPSJnMTY0Ij48cGF0aCBpZD0icGF0aDE3OCIgZmlsbD0idXJsKCNsaW5lYXItZ3JhZGllbnQtOCkiIGQ9Ik0zNDguODQgMTM4LjYxYTE3Mi41NSAxNzIuNTUgMCAwIDAtMTMuODQtMzcuN2wtMzAuOTQgOS45NEwxNzUuOTIgMTUybDE0Mi00LjgzIDMyLjUtMS4xMWMtLjQ4LTIuNTEtMS01LTEuNTYtNy40OCIvPjwvZz48L2c+PC9nPjwvc3ZnPg==" + keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "host"], ["spec", "storageClass"], ["spec", "core"], ["spec", "core", "resources"], ["spec", "core", "resourcesPreset"], ["spec", "registry"], ["spec", "registry", "size"], ["spec", "registry", "resources"], ["spec", "registry", "resourcesPreset"], ["spec", "jobservice"], ["spec", "jobservice", "resources"], ["spec", "jobservice", "resourcesPreset"], ["spec", "trivy"], ["spec", "trivy", "enabled"], ["spec", "trivy", "size"], ["spec", "trivy", "resources"], ["spec", "trivy", "resourcesPreset"], ["spec", "database"], ["spec", "database", "size"], ["spec", "database", "resources"], ["spec", "database", "resourcesPreset"], ["spec", "redis"], ["spec", "redis", "size"], ["spec", "redis", "resources"], ["spec", "redis", "resourcesPreset"]] + secrets: + exclude: [] + include: + - resourceNames: + - {{ .name }}-credentials + services: + exclude: [] + include: + - resourceNames: + - {{ .name }} + ingresses: + exclude: [] + include: + - resourceNames: + - {{ .name }}-ingress diff --git a/packages/system/harbor-rd/templates/cozyrd.yaml b/packages/system/harbor-rd/templates/cozyrd.yaml new file mode 100644 index 00000000..e079ddcf --- /dev/null +++ b/packages/system/harbor-rd/templates/cozyrd.yaml @@ -0,0 +1,4 @@ +{{- range $path, $_ := .Files.Glob "cozyrds/*" }} +--- +{{ $.Files.Get $path }} +{{- end }} diff --git a/packages/system/harbor-rd/values.yaml b/packages/system/harbor-rd/values.yaml new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/packages/system/harbor-rd/values.yaml @@ -0,0 +1 @@ +{} diff --git a/packages/system/harbor/Chart.yaml b/packages/system/harbor/Chart.yaml new file mode 100644 index 00000000..5e7007b1 --- /dev/null +++ b/packages/system/harbor/Chart.yaml @@ -0,0 +1,3 @@ +apiVersion: v2 +name: cozy-harbor +version: 0.0.0 # Placeholder, the actual version will be automatically set during the build process diff --git a/packages/system/harbor/Makefile b/packages/system/harbor/Makefile new file mode 100644 index 00000000..5cae60ed --- /dev/null +++ b/packages/system/harbor/Makefile @@ -0,0 +1,10 @@ +export NAME=harbor-system +export NAMESPACE=cozy-system + +include ../../../hack/package.mk + +update: + rm -rf charts + helm repo add harbor https://helm.goharbor.io + helm repo update harbor + helm pull harbor/harbor --untar --untardir charts diff --git a/packages/system/harbor/charts/harbor/.helmignore b/packages/system/harbor/charts/harbor/.helmignore new file mode 100644 index 00000000..b4424fd5 --- /dev/null +++ b/packages/system/harbor/charts/harbor/.helmignore @@ -0,0 +1,6 @@ +.github/* +docs/* +.git/* +.gitignore +CONTRIBUTING.md +test/* \ No newline at end of file diff --git a/packages/system/harbor/charts/harbor/Chart.yaml b/packages/system/harbor/charts/harbor/Chart.yaml new file mode 100644 index 00000000..e30a1b58 --- /dev/null +++ b/packages/system/harbor/charts/harbor/Chart.yaml @@ -0,0 +1,22 @@ +apiVersion: v1 +appVersion: 2.14.2 +description: An open source trusted cloud native registry that stores, signs, and + scans content +home: https://goharbor.io +icon: https://raw.githubusercontent.com/goharbor/website/main/static/img/logos/harbor-icon-color.png +keywords: +- docker +- registry +- harbor +maintainers: +- email: yan-yw.wang@broadcom.com + name: Yan Wang +- email: stone.zhang@broadcom.com + name: Stone Zhang +- email: miner.yang@broadcom.com + name: Miner Yang +name: harbor +sources: +- https://github.com/goharbor/harbor +- https://github.com/goharbor/harbor-helm +version: 1.18.2 diff --git a/packages/system/harbor/charts/harbor/LICENSE b/packages/system/harbor/charts/harbor/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/packages/system/harbor/charts/harbor/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/packages/system/harbor/charts/harbor/README.md b/packages/system/harbor/charts/harbor/README.md new file mode 100644 index 00000000..658150d6 --- /dev/null +++ b/packages/system/harbor/charts/harbor/README.md @@ -0,0 +1,775 @@ +# Helm Chart for Harbor + +**Notes:** The master branch is in heavy development, please use the other stable versions instead. A highly available solution for Harbor based on chart can be found [here](docs/High%20Availability.md). And refer to the [guide](docs/Upgrade.md) to upgrade the existing deployment. + +This repository, including the issues, focuses on deploying Harbor chart via helm. For functionality issues or Harbor questions, please open issues on [goharbor/harbor](https://github.com/goharbor/harbor) + +## Introduction + +This [Helm](https://github.com/kubernetes/helm) chart installs [Harbor](https://github.com/goharbor/harbor) in a Kubernetes cluster. Welcome to [contribute](CONTRIBUTING.md) to Helm Chart for Harbor. + +## Prerequisites + +- Kubernetes cluster 1.20+ +- Helm v3.2.0+ + +## Installation + +### Add Helm repository + +```bash +helm repo add harbor https://helm.goharbor.io +``` + +### Configure the chart + +The following items can be set via `--set` flag during installation or configured by editing the `values.yaml` directly (need to download the chart first). + +#### Configure how to expose Harbor service + +- **Ingress**: The ingress controller must be installed in the Kubernetes cluster. + **Notes:** if TLS is disabled, the port must be included in the command when pulling/pushing images. Refer to issue [#5291](https://github.com/goharbor/harbor/issues/5291) for details. +- **ClusterIP**: Exposes the service on a cluster-internal IP. Choosing this value makes the service only reachable from within the cluster. +- **NodePort**: Exposes the service on each Node’s IP at a static port (the NodePort). You’ll be able to contact the NodePort service, from outside the cluster, by requesting `NodeIP:NodePort`. +- **LoadBalancer**: Exposes the service externally using a cloud provider’s load balancer. +- **Gateway APIs**: Exposes the service using gateway-api CRDs using HTTPRoute. Requires v1.0.0+ + +#### Configure the external URL + +The external URL for Harbor core service is used to: + +1. populate the docker/helm commands showed on portal +2. populate the token service URL returned to docker client + +Format: `protocol://domain[:port]`. Usually: + +- if service exposed via `Ingress`, the `domain` should be the value of `expose.ingress.hosts.core` +- if service exposed via `ClusterIP`, the `domain` should be the value of `expose.clusterIP.name` +- if service exposed via `NodePort`, the `domain` should be the IP address of one Kubernetes node +- if service exposed via `LoadBalancer`, set the `domain` as your own domain name and add a CNAME record to map the domain name to the one you got from the cloud provider + +If Harbor is deployed behind the proxy, set it as the URL of proxy. + +#### Configure how to persist data + +- **Disable**: The data does not survive the termination of a pod. +- **Persistent Volume Claim(default)**: A default `StorageClass` is needed in the Kubernetes cluster to dynamically provision the volumes. Specify another StorageClass in the `storageClass` or set `existingClaim` if you already have existing persistent volumes to use. +- **External Storage(only for images and charts)**: For images and charts, the external storages are supported: `azure`, `gcs`, `s3` `swift` and `oss`. + +#### Configure the other items listed in [configuration](#configuration) section + +### Install the chart + +Install the Harbor helm chart with a release name `my-release`: + +```bash +helm install my-release harbor/harbor +``` + +## Uninstallation + +To uninstall/delete the `my-release` deployment: + +```bash +helm uninstall my-release +``` + +## Configuration + +The following table lists the configurable parameters of the Harbor chart and the default values. + + +| Parameter | Description | Default | +|----------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------| +| **Expose** | | | +| `expose.type` | How to expose the service: `ingress`, `clusterIP`, `nodePort`, `loadBalancer` or `route` other values will be ignored and the creation of service will be skipped. | `ingress` | +| `expose.tls.enabled` | Enable TLS or not. Delete the `ssl-redirect` annotations in `expose.ingress.annotations` when TLS is disabled and `expose.type` is `ingress`. Note: if the `expose.type` is `ingress` and TLS is disabled, the port must be included in the command when pulling/pushing images. Refer to https://github.com/goharbor/harbor/issues/5291 for details. | `true` | +| `expose.tls.certSource` | The source of the TLS certificate. Set as `auto`, `secret` or `none` and fill the information in the corresponding section: 1) auto: generate the TLS certificate automatically 2) secret: read the TLS certificate from the specified secret. The TLS certificate can be generated manually or by cert manager 3) none: configure no TLS certificate for the ingress. If the default TLS certificate is configured in the ingress controller, choose this option | `auto` | +| `expose.tls.auto.commonName` | The common name used to generate the certificate, it's necessary when the type isn't `ingress` | | +| `expose.tls.secret.secretName` | The name of secret which contains keys named: `tls.crt` - the certificate; `tls.key` - the private key | | +| `expose.ingress.hosts.core` | The host of Harbor core service in ingress rule | `core.harbor.domain` | +| `expose.ingress.controller` | The ingress controller type. Currently supports `default`, `gce`, `alb`, `f5-bigip` and `ncp` | `default` | +| `expose.ingress.kubeVersionOverride` | Allows the ability to override the kubernetes version used while templating the ingress | | +| `expose.ingress.className` | Specify the `ingressClassName` used to implement the Ingress (Kubernetes 1.18+) | | +| `expose.ingress.annotations` | The annotations used commonly for ingresses | | +| `expose.ingress.labels` | The labels specific to ingress | {} | +| `expose.clusterIP.name` | The name of ClusterIP service | `harbor` | +| `expose.clusterIP.annotations` | The annotations attached to the ClusterIP service | {} | +| `expose.clusterIP.ports.httpPort` | The service port Harbor listens on when serving HTTP | `80` | +| `expose.clusterIP.ports.httpsPort` | The service port Harbor listens on when serving HTTPS | `443` | +| `expose.clusterIP.annotations` | The annotations used commonly for clusterIP | | +| `expose.clusterIP.labels` | The labels specific to clusterIP | {} | +| `expose.nodePort.name` | The name of NodePort service | `harbor` | +| `expose.nodePort.ports.http.port` | The service port Harbor listens on when serving HTTP | `80` | +| `expose.nodePort.ports.http.nodePort` | The node port Harbor listens on when serving HTTP | `30002` | +| `expose.nodePort.ports.https.port` | The service port Harbor listens on when serving HTTPS | `443` | +| `expose.nodePort.ports.https.nodePort` | The node port Harbor listens on when serving HTTPS | `30003` | +| `expose.nodePort.annotations` | The annotations used commonly for nodePort | | +| `expose.nodePort.labels` | The labels specific to nodePort | {} | +| `expose.loadBalancer.name` | The name of service | `harbor` | +| `expose.loadBalancer.IP` | The IP of the loadBalancer. It only works when loadBalancer supports assigning IP | `""` | +| `expose.loadBalancer.ports.httpPort` | The service port Harbor listens on when serving HTTP | `80` | +| `expose.loadBalancer.ports.httpsPort` | The service port Harbor listens on when serving HTTPS | `30002` | +| `expose.loadBalancer.annotations` | The annotations attached to the loadBalancer service | {} | +| `expose.loadBalancer.labels` | The labels specific to loadBalancer | {} | +| `expose.loadBalancer.sourceRanges` | List of IP address ranges to assign to loadBalancerSourceRanges | [] | +| `expose.route.labels` | The labels to attach to the HTTPRoute | `{}` | +| `expose.route.annotations` | The annotations to attach to the HTTPRoute | `{}` | +| `expose.route.hosts` | The hosts that the HTTPRoute will request to the Gateway | `[]` | +| `expose.route.parentRefs` | The Gateways to attach to the HTTPRoute | `{}` | +| **Internal TLS** | | | +| `internalTLS.enabled` | Enable TLS for the components (core, jobservice, portal, registry, trivy) | `false` | +| `internalTLS.strong_ssl_ciphers` | Enable strong ssl ciphers for nginx and portal | `false` | +| `internalTLS.certSource` | Method to provide TLS for the components, options are `auto`, `manual`, `secret`. | `auto` | +| `internalTLS.trustCa` | The content of trust CA, only available when `certSource` is `manual`. **Note**: all the internal certificates of the components must be issued by this CA | | +| `internalTLS.core.secretName` | The secret name for core component, only available when `certSource` is `secret`. The secret must contain keys named: `ca.crt` - the CA certificate which is used to issue internal key and crt pair for components and all Harbor components must be issued by the same CA, `tls.crt` - the content of the TLS cert file, `tls.key` - the content of the TLS key file. | | +| `internalTLS.core.crt` | Content of core's TLS cert file, only available when `certSource` is `manual` | | +| `internalTLS.core.key` | Content of core's TLS key file, only available when `certSource` is `manual` | | +| `internalTLS.jobservice.secretName` | The secret name for jobservice component, only available when `certSource` is `secret`. The secret must contain keys named: `ca.crt` - the CA certificate which is used to issue internal key and crt pair for components and all Harbor components must be issued by the same CA, `tls.crt` - the content of the TLS cert file, `tls.key` - the content of the TLS key file. | | +| `internalTLS.jobservice.crt` | Content of jobservice's TLS cert file, only available when `certSource` is `manual` | | +| `internalTLS.jobservice.key` | Content of jobservice's TLS key file, only available when `certSource` is `manual` | | +| `internalTLS.registry.secretName` | The secret name for registry component, only available when `certSource` is `secret`. The secret must contain keys named: `ca.crt` - the CA certificate which is used to issue internal key and crt pair for components and all Harbor components must be issued by the same CA, `tls.crt` - the content of the TLS cert file, `tls.key` - the content of the TLS key file. | | +| `internalTLS.registry.crt` | Content of registry's TLS cert file, only available when `certSource` is `manual` | | +| `internalTLS.registry.key` | Content of registry's TLS key file, only available when `certSource` is `manual` | | +| `internalTLS.portal.secretName` | The secret name for portal component, only available when `certSource` is `secret`. The secret must contain keys named: `ca.crt` - the CA certificate which is used to issue internal key and crt pair for components and all Harbor components must be issued by the same CA, `tls.crt` - the content of the TLS cert file, `tls.key` - the content of the TLS key file. | | +| `internalTLS.portal.crt` | Content of portal's TLS cert file, only available when `certSource` is `manual` | | +| `internalTLS.portal.key` | Content of portal's TLS key file, only available when `certSource` is `manual` | | +| `internalTLS.trivy.secretName` | The secret name for trivy component, only available when `certSource` is `secret`. The secret must contain keys named: `ca.crt` - the CA certificate which is used to issue internal key and crt pair for components and all Harbor components must be issued by the same CA, `tls.crt` - the content of the TLS cert file, `tls.key` - the content of the TLS key file. | | +| `internalTLS.trivy.crt` | Content of trivy's TLS cert file, only available when `certSource` is `manual` | | +| `internalTLS.trivy.key` | Content of trivy's TLS key file, only available when `certSource` is `manual` | | +| **IPFamily** | | | +| `ipFamily.ipv4.enabled` | if cluster is ipv4 enabled, all ipv4 related configs will set correspondingly, but currently it only affects the nginx related components | `true` | +| `ipFamily.ipv6.enabled` | if cluster is ipv6 enabled, all ipv6 related configs will set correspondingly, but currently it only affects the nginx related components | `true` | +| `ipFamily.policy` | Sets the IP family policy for services to be able to configure dual-stack; see [Configure dual-stack](https://kubernetes.io/docs/concepts/services-networking/dual-stack/#services). | `""` | +| `ipFamily.families` | A list of IP families for services that should be supported, in the order in which they should be applied. Can be "IPv4" and/or "IPv6". | [] | +| **Persistence** | | | +| `persistence.enabled` | Enable the data persistence or not | `true` | +| `persistence.resourcePolicy` | Setting it to `keep` to avoid removing PVCs during a helm delete operation. Leaving it empty will delete PVCs after the chart deleted. Does not affect PVCs created for internal database and redis components. | `keep` | +| `persistence.persistentVolumeClaim.registry.existingClaim` | Use the existing PVC which must be created manually before bound, and specify the `subPath` if the PVC is shared with other components | | +| `persistence.persistentVolumeClaim.registry.storageClass` | Specify the `storageClass` used to provision the volume. Or the default StorageClass will be used (the default). Set it to `-` to disable dynamic provisioning | | +| `persistence.persistentVolumeClaim.registry.subPath` | The sub path used in the volume | | +| `persistence.persistentVolumeClaim.registry.accessMode` | The access mode of the volume | `ReadWriteOnce` | +| `persistence.persistentVolumeClaim.registry.size` | The size of the volume | `5Gi` | +| `persistence.persistentVolumeClaim.registry.annotations` | The annotations of the volume | | +| `persistence.persistentVolumeClaim.jobservice.jobLog.existingClaim` | Use the existing PVC which must be created manually before bound, and specify the `subPath` if the PVC is shared with other components. | | +| `persistence.persistentVolumeClaim.jobservice.jobLog.storageClass` | Specify the `storageClass` used to provision the volume. Or the default StorageClass will be used (the default). Set it to `-` to disable dynamic provisioning | | +| `persistence.persistentVolumeClaim.jobservice.jobLog.subPath` | The sub path used in the volume | | +| `persistence.persistentVolumeClaim.jobservice.jobLog.accessMode` | The access mode of the volume | `ReadWriteOnce` | +| `persistence.persistentVolumeClaim.jobservice.jobLog.size` | The size of the volume | `1Gi` | +| `persistence.persistentVolumeClaim.jobservice.jobLog.annotations` | The annotations of the volume | | +| `persistence.persistentVolumeClaim.database.existingClaim` | Use the existing PVC which must be created manually before bound, and specify the `subPath` if the PVC is shared with other components. If external database is used, the setting will be ignored | | +| `persistence.persistentVolumeClaim.database.storageClass` | Specify the `storageClass` used to provision the volume. Or the default StorageClass will be used (the default). Set it to `-` to disable dynamic provisioning. If external database is used, the setting will be ignored | | +| `persistence.persistentVolumeClaim.database.subPath` | The sub path used in the volume. If external database is used, the setting will be ignored | | +| `persistence.persistentVolumeClaim.database.accessMode` | The access mode of the volume. If external database is used, the setting will be ignored | `ReadWriteOnce` | +| `persistence.persistentVolumeClaim.database.size` | The size of the volume. If external database is used, the setting will be ignored | `1Gi` | +| `persistence.persistentVolumeClaim.database.annotations` | The annotations of the volume | | +| `persistence.persistentVolumeClaim.redis.existingClaim` | Use the existing PVC which must be created manually before bound, and specify the `subPath` if the PVC is shared with other components. If external Redis is used, the setting will be ignored | | +| `persistence.persistentVolumeClaim.redis.storageClass` | Specify the `storageClass` used to provision the volume. Or the default StorageClass will be used (the default). Set it to `-` to disable dynamic provisioning. If external Redis is used, the setting will be ignored | | +| `persistence.persistentVolumeClaim.redis.subPath` | The sub path used in the volume. If external Redis is used, the setting will be ignored | | +| `persistence.persistentVolumeClaim.redis.accessMode` | The access mode of the volume. If external Redis is used, the setting will be ignored | `ReadWriteOnce` | +| `persistence.persistentVolumeClaim.redis.size` | The size of the volume. If external Redis is used, the setting will be ignored | `1Gi` | +| `persistence.persistentVolumeClaim.redis.annotations` | The annotations of the volume | | +| `persistence.persistentVolumeClaim.trivy.existingClaim` | Use the existing PVC which must be created manually before bound, and specify the `subPath` if the PVC is shared with other components | | +| `persistence.persistentVolumeClaim.trivy.storageClass` | Specify the `storageClass` used to provision the volume. Or the default StorageClass will be used (the default). Set it to `-` to disable dynamic provisioning | | +| `persistence.persistentVolumeClaim.trivy.subPath` | The sub path used in the volume | | +| `persistence.persistentVolumeClaim.trivy.accessMode` | The access mode of the volume | `ReadWriteOnce` | +| `persistence.persistentVolumeClaim.trivy.size` | The size of the volume | `1Gi` | +| `persistence.persistentVolumeClaim.trivy.annotations` | The annotations of the volume | | +| `persistence.imageChartStorage.disableredirect` | The configuration for managing redirects from content backends. For backends which not supported it (such as using minio for `s3` storage type), please set it to `true` to disable redirects. Refer to the [guide](https://github.com/docker/distribution/blob/master/docs/configuration.md#redirect) for more details | `false` | +| `persistence.imageChartStorage.caBundleSecretName` | Specify the `caBundleSecretName` if the storage service uses a self-signed certificate. The secret must contain keys named `ca.crt` which will be injected into the trust store of registry's and containers. | | +| `persistence.imageChartStorage.type` | The type of storage for images and charts: `filesystem`, `azure`, `gcs`, `s3`, `swift` or `oss`. The type must be `filesystem` if you want to use persistent volumes for registry. Refer to the [guide](https://github.com/docker/distribution/blob/master/docs/configuration.md#storage) for more details | `filesystem` | +| `persistence.imageChartStorage.gcs.existingSecret` | An existing secret containing the gcs service account json key. The key must be gcs-key.json. | `""` | +| `persistence.imageChartStorage.gcs.useWorkloadIdentity` | A boolean to allow the use of workloadidentity in a GKE cluster. To use it, create a kubernetes service account and set the name in the key `serviceAccountName` of each component, then allow automounting the service account. | `false` | +| **General** | | | +| `externalURL` | The external URL for Harbor core service | `https://core.harbor.domain` | +| `caBundleSecretName` | The custom CA bundle secret name, the secret must contain key named "ca.crt" which will be injected into the trust store for core, jobservice, registry, trivy components. | | +| `uaaSecretName` | If using external UAA auth which has a self signed cert, you can provide a pre-created secret containing it under the key `ca.crt`. | | +| `imagePullPolicy` | The image pull policy | | +| `imagePullSecrets` | The imagePullSecrets names for all deployments | | +| `updateStrategy.type` | The update strategy for deployments with persistent volumes(jobservice, registry): `RollingUpdate` or `Recreate`. Set it as `Recreate` when `RWM` for volumes isn't supported | `RollingUpdate` | +| `logLevel` | The log level: `debug`, `info`, `warning`, `error` or `fatal` | `info` | +| `harborAdminPassword` | The initial password of Harbor admin. Change it from portal after launching Harbor | `Harbor12345` | +| `existingSecretAdminPassword` | The name of secret where admin password can be found. | | +| `existingSecretAdminPasswordKey` | The name of the key in the secret where to find harbor admin password Harbor | `HARBOR_ADMIN_PASSWORD` | +| `caSecretName` | The name of the secret which contains key named `ca.crt`. Setting this enables the download link on portal to download the CA certificate when the certificate isn't generated automatically | | +| `secretKey` | The key used for encryption. Must be a string of 16 chars | `not-a-secure-key` | +| `existingSecretSecretKey` | An existing secret containing the encoding secretKey | `""` | +| `proxy.httpProxy` | The URL of the HTTP proxy server | | +| `proxy.httpsProxy` | The URL of the HTTPS proxy server | | +| `proxy.noProxy` | The URLs that the proxy settings not apply to | 127.0.0.1,localhost,.local,.internal | +| `proxy.components` | The component list that the proxy settings apply to | core, jobservice, trivy | +| `enableMigrateHelmHook` | Run the migration job via helm hook, if it is true, the database migration will be separated from harbor-core, run with a preupgrade job migration-job | `false` | +| **Nginx** (if service exposed via `ingress`, Nginx will not be used) | | | +| `nginx.image.repository` | Image repository | `goharbor/nginx-photon` | +| `nginx.image.tag` | Image tag | `dev` | +| `nginx.replicas` | The replica count | `1` | +| `nginx.revisionHistoryLimit` | The revision history limit | `10` | +| `nginx.resources` | The [resources] to allocate for container | undefined | +| `nginx.automountServiceAccountToken` | Mount serviceAccountToken? | `false` | +| `nginx.nodeSelector` | Node labels for pod assignment | `{}` | +| `nginx.tolerations` | Tolerations for pod assignment | `[]` | +| `nginx.affinity` | Node/Pod affinities | `{}` | +| `nginx.topologySpreadConstraints` | Constraints that define how Pods are spread across failure-domains like regions or availability zones | `[]` | +| `nginx.podAnnotations` | Annotations to add to the nginx pod | `{}` | +| `nginx.priorityClassName` | The priority class to run the pod as | | +| **Portal** | | | +| `portal.image.repository` | Repository for portal image | `goharbor/harbor-portal` | +| `portal.image.tag` | Tag for portal image | `dev` | +| `portal.replicas` | The replica count | `1` | +| `portal.revisionHistoryLimit` | The revision history limit | `10` | +| `portal.resources` | The [resources] to allocate for container | undefined | +| `portal.automountServiceAccountToken` | Mount serviceAccountToken? | `false` | +| `portal.nodeSelector` | Node labels for pod assignment | `{}` | +| `portal.tolerations` | Tolerations for pod assignment | `[]` | +| `portal.affinity` | Node/Pod affinities | `{}` | +| `portal.topologySpreadConstraints` | Constraints that define how Pods are spread across failure-domains like regions or availability zones | `[]` | +| `portal.podAnnotations` | Annotations to add to the portal pod | `{}` | +| `portal.serviceAnnotations` | Annotations to add to the portal service | `{}` | +| `portal.priorityClassName` | The priority class to run the pod as | | +| `portal.initContainers` | Init containers to be run before the controller's container starts. | `[]` | +| **Core** | | | +| `core.image.repository` | Repository for Harbor core image | `goharbor/harbor-core` | +| `core.image.tag` | Tag for Harbor core image | `dev` | +| `core.replicas` | The replica count | `1` | +| `core.revisionHistoryLimit` | The revision history limit | `10` | +| `core.startupProbe.initialDelaySeconds` | The initial delay in seconds for the startup probe | `10` | +| `core.resources` | The [resources] to allocate for container | undefined | +| `core.automountServiceAccountToken` | Mount serviceAccountToken? | `false` | +| `core.nodeSelector` | Node labels for pod assignment | `{}` | +| `core.tolerations` | Tolerations for pod assignment | `[]` | +| `core.affinity` | Node/Pod affinities | `{}` | +| `core.topologySpreadConstraints` | Constraints that define how Pods are spread across failure-domains like regions or availability zones | `[]` | +| `core.podAnnotations` | Annotations to add to the core pod | `{}` | +| `core.serviceAnnotations` | Annotations to add to the core service | `{}` | +| `core.configureUserSettings` | A JSON string to set in the environment variable `CONFIG_OVERWRITE_JSON` to configure user settings. See the [official docs](https://goharbor.io/docs/latest/install-config/configure-user-settings-cli/#configure-users-settings-using-an-environment-variable). | | +| `core.quotaUpdateProvider` | The provider for updating project quota(usage), there are 2 options, redis or db. By default it is implemented by db but you can configure it to redis which can improve the performance of high concurrent pushing to the same project, and reduce the database connections spike and occupies. Using redis will bring up some delay for quota usage updation for display, so only suggest switch provider to redis if you were ran into the db connections spike around the scenario of high concurrent pushing to same project, no improvment for other scenes. | `db` | +| `core.secret` | Secret is used when core server communicates with other components. If a secret key is not specified, Helm will generate one. Must be a string of 16 chars. | | +| `core.secretName` | Fill the name of a kubernetes secret if you want to use your own TLS certificate and private key for token encryption/decryption. The secret must contain keys named: `tls.crt` - the certificate and `tls.key` - the private key. The default key pair will be used if it isn't set | | +| `core.tokenKey` | PEM-formatted RSA private key used to sign service tokens. Only used if `core.secretName` is unset. If set, `core.tokenCert` MUST also be set. | | +| `core.tokenCert` | PEM-formatted certificate signed by `core.tokenKey` used to validate service tokens. Only used if `core.secretName` is unset. If set, `core.tokenKey` MUST also be set. | | +| `core.xsrfKey` | The XSRF key. Will be generated automatically if it isn't specified | | +| `core.priorityClassName` | The priority class to run the pod as | | +| `core.artifactPullAsyncFlushDuration` | The time duration for async update artifact pull_time and repository pull_count | | +| `core.gdpr.deleteUser` | Enable GDPR compliant user delete | `false` | +| `core.gdpr.auditLogsCompliant` | Enable GDPR compliant for audit logs by changing username to its CRC32 value if that user was deleted from the system | `false` | +| `core.initContainers` | Init containers to be run before the controller's container starts. | `[]` | +| **Jobservice** | | | +| `jobservice.image.repository` | Repository for jobservice image | `goharbor/harbor-jobservice` | +| `jobservice.image.tag` | Tag for jobservice image | `dev` | +| `jobservice.replicas` | The replica count | `1` | +| `jobservice.revisionHistoryLimit` | The revision history limit | `10` | +| `jobservice.maxJobWorkers` | The max job workers | `10` | +| `jobservice.jobLoggers` | The loggers for jobs: `file`, `database` or `stdout` | `[file]` | +| `jobservice.loggerSweeperDuration` | The jobLogger sweeper duration in days (ignored if `jobLoggers` is set to `stdout`) | `14` | +| `jobservice.notification.webhook_job_max_retry` | The maximum retry of webhook sending notifications | `3` | +| `jobservice.notification.webhook_job_http_client_timeout` | The http client timeout value of webhook sending notifications | `3` | +| `jobservice.reaper.max_update_hours` | the max time to wait for a task to finish, if unfinished after max_update_hours, the task will be mark as error, but the task will continue to run, default value is 24 | `24` | +| `jobservice.reaper.max_dangling_hours` | the max time for execution in running state without new task created | `168` | +| `jobservice.resources` | The [resources] to allocate for container | undefined | +| `jobservice.automountServiceAccountToken` | Mount serviceAccountToken? | `false` | +| `jobservice.nodeSelector` | Node labels for pod assignment | `{}` | +| `jobservice.tolerations` | Tolerations for pod assignment | `[]` | +| `jobservice.affinity` | Node/Pod affinities | `{}` | +| `jobservice.topologySpreadConstraints` | Constraints that define how Pods are spread across failure-domains like regions or availability zones | `[]` | +| `jobservice.podAnnotations` | Annotations to add to the jobservice pod | `{}` | +| `jobservice.priorityClassName` | The priority class to run the pod as | | +| `jobservice.secret` | Secret is used when job service communicates with other components. If a secret key is not specified, Helm will generate one. Must be a string of 16 chars. | | +| `jobservice.initContainers` | Init containers to be run before the controller's container starts. | `[]` | +| **Registry** | | | +| `registry.registry.image.repository` | Repository for registry image | `goharbor/registry-photon` | +| `registry.registry.image.tag` | Tag for registry image | `dev` | +| `registry.registry.resources` | The [resources] to allocate for container | undefined | +| `registry.controller.image.repository` | Repository for registry controller image | `goharbor/harbor-registryctl` | +| `registry.controller.image.tag` | Tag for registry controller image | `dev` | +| `registry.controller.resources` | The [resources] to allocate for container | undefined | +| `registry.replicas` | The replica count | `1` | +| `registry.revisionHistoryLimit` | The revision history limit | `10` | +| `registry.nodeSelector` | Node labels for pod assignment | `{}` | +| `registry.automountServiceAccountToken` | Mount serviceAccountToken? | `false` | +| `registry.tolerations` | Tolerations for pod assignment | `[]` | +| `registry.affinity` | Node/Pod affinities | `{}` | +| `registry.topologySpreadConstraints` | Constraints that define how Pods are spread across failure-domains like regions or availability zones | `[]` | +| `registry.middleware` | Middleware is used to add support for a CDN between backend storage and `docker pull` recipient. See [official docs](https://github.com/docker/distribution/blob/master/docs/configuration.md#middleware). | | +| `registry.podAnnotations` | Annotations to add to the registry pod | `{}` | +| `registry.priorityClassName` | The priority class to run the pod as | | +| `registry.secret` | Secret is used to secure the upload state from client and registry storage backend. See [official docs](https://github.com/docker/distribution/blob/master/docs/configuration.md#http). If a secret key is not specified, Helm will generate one. Must be a string of 16 chars. | | +| `registry.credentials.username` | The username that harbor core uses internally to access the registry instance. Together with the `registry.credentials.password`, a htpasswd is created. This is an alternative to providing `registry.credentials.htpasswdString`. For more details see [official docs](https://github.com/docker/distribution/blob/master/docs/configuration.md#htpasswd). | `harbor_registry_user` | +| `registry.credentials.password` | The password that harbor core uses internally to access the registry instance. Together with the `registry.credentials.username`, a htpasswd is created. This is an alternative to providing `registry.credentials.htpasswdString`. For more details see [official docs](https://github.com/docker/distribution/blob/master/docs/configuration.md#htpasswd). It is suggested you update this value before installation. | `harbor_registry_password` | +| `registry.credentials.existingSecret` | An existing secret containing the password for accessing the registry instance, which is hosted by htpasswd auth mode. More details see [official docs](https://github.com/docker/distribution/blob/master/docs/configuration.md#htpasswd). The key must be `REGISTRY_PASSWD` | `""` | +| `registry.credentials.htpasswdString` | Login and password in htpasswd string format. Excludes `registry.credentials.username` and `registry.credentials.password`. May come in handy when integrating with tools like argocd or flux. This allows the same line to be generated each time the template is rendered, instead of the `htpasswd` function from helm, which generates different lines each time because of the salt. | undefined | +| `registry.relativeurls` | If true, the registry returns relative URLs in Location headers. The client is responsible for resolving the correct URL. Needed if harbor is behind a reverse proxy | `false` | +| `registry.upload_purging.enabled` | If true, enable purge _upload directories | `true` | +| `registry.upload_purging.age` | Remove files in _upload directories which exist for a period of time, default is one week. | `168h` | +| `registry.upload_purging.interval` | The interval of the purge operations | `24h` | +| `registry.upload_purging.dryrun` | If true, enable dryrun for purging _upload, default false | `false` | +| `registry.initContainers` | Init containers to be run before the controller's container starts. | `[]` | +| **[Trivy][trivy]** | | | +| `trivy.enabled` | The flag to enable Trivy scanner | `true` | +| `trivy.image.repository` | Repository for Trivy adapter image | `goharbor/trivy-adapter-photon` | +| `trivy.image.tag` | Tag for Trivy adapter image | `dev` | +| `trivy.resources` | The [resources] to allocate for Trivy adapter container | | +| `trivy.automountServiceAccountToken` | Mount serviceAccountToken? | `false` | +| `trivy.replicas` | The number of Pod replicas | `1` | +| `trivy.debugMode` | The flag to enable Trivy debug mode | `false` | +| `trivy.vulnType` | Comma-separated list of vulnerability types. Possible values `os` and `library`. | `os,library` | +| `trivy.severity` | Comma-separated list of severities to be checked | `UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL` | +| `trivy.ignoreUnfixed` | The flag to display only fixed vulnerabilities | `false` | +| `trivy.insecure` | The flag to skip verifying registry certificate | `false` | +| `trivy.skipUpdate` | The flag to disable [Trivy DB][trivy-db] downloads from GitHub | `false` | +| `trivy.skipJavaDBUpdate` | If the flag is enabled you have to manually download the `trivy-java.db` file [Trivy Java DB][trivy-java-db] and mount it in the `/home/scanner/.cache/trivy/java-db/trivy-java.db` path | `false` | +| `trivy.offlineScan` | The flag prevents Trivy from sending API requests to identify dependencies. | `false` | +| `trivy.securityCheck` | Comma-separated list of what security issues to detect. | `vuln` | +| `trivy.timeout` | The duration to wait for scan completion | `5m0s` | +| `trivy.gitHubToken` | The GitHub access token to download [Trivy DB][trivy-db] (see [GitHub rate limiting][trivy-rate-limiting]) | | +| `trivy.priorityClassName` | The priority class to run the pod as | | +| `trivy.topologySpreadConstraints` | The priority class to run the pod as | | +| `trivy.initContainers` | Init containers to be run before the controller's container starts. | `[]` | +| **Database** | | | +| `database.type` | If external database is used, set it to `external` | `internal` | +| `database.internal.image.repository` | Repository for database image | `goharbor/harbor-db` | +| `database.internal.image.tag` | Tag for database image | `dev` | +| `database.internal.password` | The password for database | `changeit` | +| `database.internal.shmSizeLimit` | The limit for the size of shared memory for internal PostgreSQL, conventionally it's around 50% of the memory limit of the container | `512Mi` | +| `database.internal.resources` | The [resources] to allocate for container | undefined | +| `database.internal.automountServiceAccountToken` | Mount serviceAccountToken? | `false` | +| `database.internal.initContainer.migrator.resources` | The [resources] to allocate for the database migrator initContainer | undefined | +| `database.internal.initContainer.permissions.resources` | The [resources] to allocate for the database permissions initContainer | undefined | +| `database.internal.nodeSelector` | Node labels for pod assignment | `{}` | +| `database.internal.tolerations` | Tolerations for pod assignment | `[]` | +| `database.internal.affinity` | Node/Pod affinities | `{}` | +| `database.internal.priorityClassName` | The priority class to run the pod as | | +| `database.internal.livenessProbe.timeoutSeconds` | The timeout used in liveness probe; 1 to 5 seconds | 1 | +| `database.internal.readinessProbe.timeoutSeconds` | The timeout used in readiness probe; 1 to 5 seconds | 1 | +| `database.internal.extrInitContainers` | Extra init containers to be run before the database's container starts. | `[]` | +| `database.external.host` | The hostname of external database | `192.168.0.1` | +| `database.external.port` | The port of external database | `5432` | +| `database.external.username` | The username of external database | `user` | +| `database.external.password` | The password of external database | `password` | +| `database.external.coreDatabase` | The database used by core service | `registry` | +| `database.external.existingSecret` | An existing password containing the database password. the key must be `password`. | `""` | +| `database.external.sslmode` | Connection method of external database (require, verify-full, verify-ca, disable) | `disable` | +| `database.maxIdleConns` | The maximum number of connections in the idle connection pool. If it <=0, no idle connections are retained. | `50` | +| `database.maxOpenConns` | The maximum number of open connections to the database. If it <= 0, then there is no limit on the number of open connections. | `100` | +| `database.podAnnotations` | Annotations to add to the database pod | `{}` | +| **Redis** | | | +| `redis.type` | If external redis is used, set it to `external` | `internal` | +| `redis.internal.image.repository` | Repository for redis image | `goharbor/redis-photon` | +| `redis.internal.image.tag` | Tag for redis image | `dev` | +| `redis.internal.resources` | The [resources] to allocate for container | undefined | +| `redis.internal.automountServiceAccountToken` | Mount serviceAccountToken? | `false` | +| `redis.internal.nodeSelector` | Node labels for pod assignment | `{}` | +| `redis.internal.tolerations` | Tolerations for pod assignment | `[]` | +| `redis.internal.affinity` | Node/Pod affinities | `{}` | +| `redis.internal.priorityClassName` | The priority class to run the pod as | | +| `redis.internal.jobserviceDatabaseIndex` | The database index for jobservice | `1` | +| `redis.internal.registryDatabaseIndex` | The database index for registry | `2` | +| `redis.internal.trivyAdapterIndex` | The database index for trivy adapter | `5` | +| `redis.internal.harborDatabaseIndex` | The database index for harbor miscellaneous business logic | `0` | +| `redis.internal.cacheLayerDatabaseIndex` | The database index for harbor cache layer | `0` | +| `redis.internal.initContainers` | Init containers to be run before the redis's container starts. | `[]` | +| `redis.external.addr` | The addr of external Redis: :. When using sentinel, it should be :,:,: | `192.168.0.2:6379` | +| `redis.external.sentinelMasterSet` | The name of the set of Redis instances to monitor | | +| `redis.external.coreDatabaseIndex` | The database index for core | `0` | +| `redis.external.jobserviceDatabaseIndex` | The database index for jobservice | `1` | +| `redis.external.registryDatabaseIndex` | The database index for registry | `2` | +| `redis.external.trivyAdapterIndex` | The database index for trivy adapter | `5` | +| `redis.external.harborDatabaseIndex` | The database index for harbor miscellaneous business logic | `0` | +| `redis.external.cacheLayerDatabaseIndex` | The database index for harbor cache layer | `0` | +| `redis.external.username` | The username of external Redis | | +| `redis.external.password` | The password of external Redis | | +| `redis.external.existingSecret` | Use an existing secret to connect to redis. The key must be `REDIS_PASSWORD`. | `""` | +| `redis.podAnnotations` | Annotations to add to the redis pod | `{}` | +| **Exporter** | | | +| `exporter.replicas` | The replica count | `1` | +| `exporter.revisionHistoryLimit` | The revision history limit | `10` | +| `exporter.podAnnotations` | Annotations to add to the exporter pod | `{}` | +| `exporter.image.repository` | Repository for redis image | `goharbor/harbor-exporter` | +| `exporter.image.tag` | Tag for exporter image | `dev` | +| `exporter.nodeSelector` | Node labels for pod assignment | `{}` | +| `exporter.tolerations` | Tolerations for pod assignment | `[]` | +| `exporter.affinity` | Node/Pod affinities | `{}` | +| `exporter.topologySpreadConstraints` | Constraints that define how Pods are spread across failure-domains like regions or availability zones | `[]` | +| `exporter.automountServiceAccountToken` | Mount serviceAccountToken? | `false` | +| `exporter.cacheDuration` | the cache duration for information that exporter collected from Harbor | `30` | +| `exporter.cacheCleanInterval` | cache clean interval for information that exporter collected from Harbor | `14400` | +| `exporter.priorityClassName` | The priority class to run the pod as | | +| **Metrics** | | | +| `metrics.enabled` | if enable harbor metrics | `false` | +| `metrics.core.path` | the url path for core metrics | `/metrics` | +| `metrics.core.port` | the port for core metrics | `8001` | +| `metrics.registry.path` | the url path for registry metrics | `/metrics` | +| `metrics.registry.port` | the port for registry metrics | `8001` | +| `metrics.exporter.path` | the url path for exporter metrics | `/metrics` | +| `metrics.exporter.port` | the port for exporter metrics | `8001` | +| `metrics.serviceMonitor.enabled` | create prometheus serviceMonitor. Requires prometheus CRD's | `false` | +| `metrics.serviceMonitor.additionalLabels` | additional labels to upsert to the manifest | `""` | +| `metrics.serviceMonitor.interval` | scrape period for harbor metrics | `""` | +| `metrics.serviceMonitor.metricRelabelings` | metrics relabel to add/mod/del before ingestion | `[]` | +| `metrics.serviceMonitor.relabelings` | relabels to add/mod/del to sample before scrape | `[]` | +| **Trace** | | | +| `trace.enabled` | Enable tracing or not | `false` | +| `trace.provider` | The tracing provider: `jaeger` or `otel`. `jaeger` should be 1.26+ | `jaeger` | +| `trace.sample_rate` | Set `sample_rate` to 1 if you want sampling 100% of trace data; set 0.5 if you want sampling 50% of trace data, and so forth | `1` | +| `trace.namespace` | Namespace used to differentiate different harbor services | | +| `trace.attributes` | `attributes` is a key value dict contains user defined attributes used to initialize trace provider | | +| `trace.jaeger.endpoint` | The endpoint of jaeger | `http://hostname:14268/api/traces` | +| `trace.jaeger.username` | The username of jaeger | | +| `trace.jaeger.password` | The password of jaeger | | +| `trace.jaeger.agent_host` | The agent host of jaeger | | +| `trace.jaeger.agent_port` | The agent port of jaeger | `6831` | +| `trace.otel.endpoint` | The endpoint of otel | `hostname:4318` | +| `trace.otel.url_path` | The URL path of otel | `/v1/traces` | +| `trace.otel.compression` | Whether enable compression or not for otel | `false` | +| `trace.otel.insecure` | Whether establish insecure connection or not for otel | `true` | +| `trace.otel.timeout` | The timeout in seconds of otel | `10` | +| **Cache** | | | +| `cache.enabled` | Enable cache layer or not | `false` | +| `cache.expireHours` | The expire hours of cache layer | `24` | +| Parameter | Description | Default | +|----------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------| +| **Expose** | | | +| `expose.type` | How to expose the service: `ingress`, `clusterIP`, `nodePort` or `loadBalancer`, other values will be ignored and the creation of service will be skipped. | `ingress` | +| `expose.tls.enabled` | Enable TLS or not. Delete the `ssl-redirect` annotations in `expose.ingress.annotations` when TLS is disabled and `expose.type` is `ingress`. Note: if the `expose.type` is `ingress` and TLS is disabled, the port must be included in the command when pulling/pushing images. Refer to https://github.com/goharbor/harbor/issues/5291 for details. | `true` | +| `expose.tls.certSource` | The source of the TLS certificate. Set as `auto`, `secret` or `none` and fill the information in the corresponding section: 1) auto: generate the TLS certificate automatically 2) secret: read the TLS certificate from the specified secret. The TLS certificate can be generated manually or by cert manager 3) none: configure no TLS certificate for the ingress. If the default TLS certificate is configured in the ingress controller, choose this option | `auto` | +| `expose.tls.auto.commonName` | The common name used to generate the certificate, it's necessary when the type isn't `ingress` | | +| `expose.tls.secret.secretName` | The name of secret which contains keys named: `tls.crt` - the certificate; `tls.key` - the private key | | +| `expose.ingress.hosts.core` | The host of Harbor core service in ingress rule | `core.harbor.domain` | +| `expose.ingress.controller` | The ingress controller type. Currently supports `default`, `gce`, `alb`, `f5-bigip` and `ncp` | `default` | +| `expose.ingress.kubeVersionOverride` | Allows the ability to override the kubernetes version used while templating the ingress | | +| `expose.ingress.className` | Specify the `ingressClassName` used to implement the Ingress (Kubernetes 1.18+) | | +| `expose.ingress.annotations` | The annotations used commonly for ingresses | | +| `expose.ingress.labels` | The labels specific to ingress | {} | +| `expose.clusterIP.name` | The name of ClusterIP service | `harbor` | +| `expose.clusterIP.annotations` | The annotations attached to the ClusterIP service | {} | +| `expose.clusterIP.ports.httpPort` | The service port Harbor listens on when serving HTTP | `80` | +| `expose.clusterIP.ports.httpsPort` | The service port Harbor listens on when serving HTTPS | `443` | +| `expose.clusterIP.annotations` | The annotations used commonly for clusterIP | | +| `expose.clusterIP.labels` | The labels specific to clusterIP | {} | +| `expose.nodePort.name` | The name of NodePort service | `harbor` | +| `expose.nodePort.ports.http.port` | The service port Harbor listens on when serving HTTP | `80` | +| `expose.nodePort.ports.http.nodePort` | The node port Harbor listens on when serving HTTP | `30002` | +| `expose.nodePort.ports.https.port` | The service port Harbor listens on when serving HTTPS | `443` | +| `expose.nodePort.ports.https.nodePort` | The node port Harbor listens on when serving HTTPS | `30003` | +| `expose.nodePort.annotations` | The annotations used commonly for nodePort | | +| `expose.nodePort.labels` | The labels specific to nodePort | {} | +| `expose.loadBalancer.name` | The name of service | `harbor` | +| `expose.loadBalancer.IP` | The IP of the loadBalancer. It only works when loadBalancer supports assigning IP | `""` | +| `expose.loadBalancer.ports.httpPort` | The service port Harbor listens on when serving HTTP | `80` | +| `expose.loadBalancer.ports.httpsPort` | The service port Harbor listens on when serving HTTPS | `30002` | +| `expose.loadBalancer.annotations` | The annotations attached to the loadBalancer service | {} | +| `expose.loadBalancer.labels` | The labels specific to loadBalancer | {} | +| `expose.loadBalancer.sourceRanges` | List of IP address ranges to assign to loadBalancerSourceRanges | [] | +| **Internal TLS** | | | +| `internalTLS.enabled` | Enable TLS for the components (core, jobservice, portal, registry, trivy) | `false` | +| `internalTLS.strong_ssl_ciphers` | Enable strong ssl ciphers for nginx and portal | `false` | +| `internalTLS.certSource` | Method to provide TLS for the components, options are `auto`, `manual`, `secret`. | `auto` | +| `internalTLS.trustCa` | The content of trust CA, only available when `certSource` is `manual`. **Note**: all the internal certificates of the components must be issued by this CA | | +| `internalTLS.core.secretName` | The secret name for core component, only available when `certSource` is `secret`. The secret must contain keys named: `ca.crt` - the CA certificate which is used to issue internal key and crt pair for components and all Harbor components must be issued by the same CA, `tls.crt` - the content of the TLS cert file, `tls.key` - the content of the TLS key file. | | +| `internalTLS.core.crt` | Content of core's TLS cert file, only available when `certSource` is `manual` | | +| `internalTLS.core.key` | Content of core's TLS key file, only available when `certSource` is `manual` | | +| `internalTLS.jobservice.secretName` | The secret name for jobservice component, only available when `certSource` is `secret`. The secret must contain keys named: `ca.crt` - the CA certificate which is used to issue internal key and crt pair for components and all Harbor components must be issued by the same CA, `tls.crt` - the content of the TLS cert file, `tls.key` - the content of the TLS key file. | | +| `internalTLS.jobservice.crt` | Content of jobservice's TLS cert file, only available when `certSource` is `manual` | | +| `internalTLS.jobservice.key` | Content of jobservice's TLS key file, only available when `certSource` is `manual` | | +| `internalTLS.registry.secretName` | The secret name for registry component, only available when `certSource` is `secret`. The secret must contain keys named: `ca.crt` - the CA certificate which is used to issue internal key and crt pair for components and all Harbor components must be issued by the same CA, `tls.crt` - the content of the TLS cert file, `tls.key` - the content of the TLS key file. | | +| `internalTLS.registry.crt` | Content of registry's TLS cert file, only available when `certSource` is `manual` | | +| `internalTLS.registry.key` | Content of registry's TLS key file, only available when `certSource` is `manual` | | +| `internalTLS.portal.secretName` | The secret name for portal component, only available when `certSource` is `secret`. The secret must contain keys named: `ca.crt` - the CA certificate which is used to issue internal key and crt pair for components and all Harbor components must be issued by the same CA, `tls.crt` - the content of the TLS cert file, `tls.key` - the content of the TLS key file. | | +| `internalTLS.portal.crt` | Content of portal's TLS cert file, only available when `certSource` is `manual` | | +| `internalTLS.portal.key` | Content of portal's TLS key file, only available when `certSource` is `manual` | | +| `internalTLS.trivy.secretName` | The secret name for trivy component, only available when `certSource` is `secret`. The secret must contain keys named: `ca.crt` - the CA certificate which is used to issue internal key and crt pair for components and all Harbor components must be issued by the same CA, `tls.crt` - the content of the TLS cert file, `tls.key` - the content of the TLS key file. | | +| `internalTLS.trivy.crt` | Content of trivy's TLS cert file, only available when `certSource` is `manual` | | +| `internalTLS.trivy.key` | Content of trivy's TLS key file, only available when `certSource` is `manual` | | +| **IPFamily** | | | +| `ipFamily.ipv4.enabled` | if cluster is ipv4 enabled, all ipv4 related configs will set correspondingly, but currently it only affects the nginx related components | `true` | +| `ipFamily.ipv6.enabled` | if cluster is ipv6 enabled, all ipv6 related configs will set correspondingly, but currently it only affects the nginx related components | `true` | +| **Persistence** | | | +| `persistence.enabled` | Enable the data persistence or not | `true` | +| `persistence.resourcePolicy` | Setting it to `keep` to avoid removing PVCs during a helm delete operation. Leaving it empty will delete PVCs after the chart deleted. Does not affect PVCs created for internal database and redis components. | `keep` | +| `persistence.persistentVolumeClaim.registry.existingClaim` | Use the existing PVC which must be created manually before bound, and specify the `subPath` if the PVC is shared with other components | | +| `persistence.persistentVolumeClaim.registry.storageClass` | Specify the `storageClass` used to provision the volume. Or the default StorageClass will be used (the default). Set it to `-` to disable dynamic provisioning | | +| `persistence.persistentVolumeClaim.registry.subPath` | The sub path used in the volume | | +| `persistence.persistentVolumeClaim.registry.accessMode` | The access mode of the volume | `ReadWriteOnce` | +| `persistence.persistentVolumeClaim.registry.size` | The size of the volume | `5Gi` | +| `persistence.persistentVolumeClaim.registry.annotations` | The annotations of the volume | | +| `persistence.persistentVolumeClaim.jobservice.jobLog.existingClaim` | Use the existing PVC which must be created manually before bound, and specify the `subPath` if the PVC is shared with other components. | | +| `persistence.persistentVolumeClaim.jobservice.jobLog.storageClass` | Specify the `storageClass` used to provision the volume. Or the default StorageClass will be used (the default). Set it to `-` to disable dynamic provisioning | | +| `persistence.persistentVolumeClaim.jobservice.jobLog.subPath` | The sub path used in the volume | | +| `persistence.persistentVolumeClaim.jobservice.jobLog.accessMode` | The access mode of the volume | `ReadWriteOnce` | +| `persistence.persistentVolumeClaim.jobservice.jobLog.size` | The size of the volume | `1Gi` | +| `persistence.persistentVolumeClaim.jobservice.jobLog.annotations` | The annotations of the volume | | +| `persistence.persistentVolumeClaim.database.existingClaim` | Use the existing PVC which must be created manually before bound, and specify the `subPath` if the PVC is shared with other components. If external database is used, the setting will be ignored | | +| `persistence.persistentVolumeClaim.database.storageClass` | Specify the `storageClass` used to provision the volume. Or the default StorageClass will be used (the default). Set it to `-` to disable dynamic provisioning. If external database is used, the setting will be ignored | | +| `persistence.persistentVolumeClaim.database.subPath` | The sub path used in the volume. If external database is used, the setting will be ignored | | +| `persistence.persistentVolumeClaim.database.accessMode` | The access mode of the volume. If external database is used, the setting will be ignored | `ReadWriteOnce` | +| `persistence.persistentVolumeClaim.database.size` | The size of the volume. If external database is used, the setting will be ignored | `1Gi` | +| `persistence.persistentVolumeClaim.database.annotations` | The annotations of the volume | | +| `persistence.persistentVolumeClaim.redis.existingClaim` | Use the existing PVC which must be created manually before bound, and specify the `subPath` if the PVC is shared with other components. If external Redis is used, the setting will be ignored | | +| `persistence.persistentVolumeClaim.redis.storageClass` | Specify the `storageClass` used to provision the volume. Or the default StorageClass will be used (the default). Set it to `-` to disable dynamic provisioning. If external Redis is used, the setting will be ignored | | +| `persistence.persistentVolumeClaim.redis.subPath` | The sub path used in the volume. If external Redis is used, the setting will be ignored | | +| `persistence.persistentVolumeClaim.redis.accessMode` | The access mode of the volume. If external Redis is used, the setting will be ignored | `ReadWriteOnce` | +| `persistence.persistentVolumeClaim.redis.size` | The size of the volume. If external Redis is used, the setting will be ignored | `1Gi` | +| `persistence.persistentVolumeClaim.redis.annotations` | The annotations of the volume | | +| `persistence.persistentVolumeClaim.trivy.existingClaim` | Use the existing PVC which must be created manually before bound, and specify the `subPath` if the PVC is shared with other components | | +| `persistence.persistentVolumeClaim.trivy.storageClass` | Specify the `storageClass` used to provision the volume. Or the default StorageClass will be used (the default). Set it to `-` to disable dynamic provisioning | | +| `persistence.persistentVolumeClaim.trivy.subPath` | The sub path used in the volume | | +| `persistence.persistentVolumeClaim.trivy.accessMode` | The access mode of the volume | `ReadWriteOnce` | +| `persistence.persistentVolumeClaim.trivy.size` | The size of the volume | `1Gi` | +| `persistence.persistentVolumeClaim.trivy.annotations` | The annotations of the volume | | +| `persistence.imageChartStorage.disableredirect` | The configuration for managing redirects from content backends. For backends which not supported it (such as using minio for `s3` storage type), please set it to `true` to disable redirects. Refer to the [guide](https://github.com/docker/distribution/blob/master/docs/configuration.md#redirect) for more details | `false` | +| `persistence.imageChartStorage.caBundleSecretName` | Specify the `caBundleSecretName` if the storage service uses a self-signed certificate. The secret must contain keys named `ca.crt` which will be injected into the trust store of registry's and containers. | | +| `persistence.imageChartStorage.type` | The type of storage for images and charts: `filesystem`, `azure`, `gcs`, `s3`, `swift` or `oss`. The type must be `filesystem` if you want to use persistent volumes for registry. Refer to the [guide](https://github.com/docker/distribution/blob/master/docs/configuration.md#storage) for more details | `filesystem` | +| `persistence.imageChartStorage.gcs.existingSecret` | An existing secret containing the gcs service account json key. The key must be gcs-key.json. | `""` | +| `persistence.imageChartStorage.gcs.useWorkloadIdentity` | A boolean to allow the use of workloadidentity in a GKE cluster. To use it, create a kubernetes service account and set the name in the key `serviceAccountName` of each component, then allow automounting the service account. | `false` | +| **General** | | | +| `externalURL` | The external URL for Harbor core service | `https://core.harbor.domain` | +| `caBundleSecretName` | The custom CA bundle secret name, the secret must contain key named "ca.crt" which will be injected into the trust store for core, jobservice, registry, trivy components. | | +| `uaaSecretName` | If using external UAA auth which has a self signed cert, you can provide a pre-created secret containing it under the key `ca.crt`. | | +| `imagePullPolicy` | The image pull policy | | +| `imagePullSecrets` | The imagePullSecrets names for all deployments | | +| `updateStrategy.type` | The update strategy for deployments with persistent volumes(jobservice, registry): `RollingUpdate` or `Recreate`. Set it as `Recreate` when `RWM` for volumes isn't supported | `RollingUpdate` | +| `logLevel` | The log level: `debug`, `info`, `warning`, `error` or `fatal` | `info` | +| `harborAdminPassword` | The initial password of Harbor admin. Change it from portal after launching Harbor | `Harbor12345` | +| `existingSecretAdminPassword` | The name of secret where admin password can be found. | | +| `existingSecretAdminPasswordKey` | The name of the key in the secret where to find harbor admin password Harbor | `HARBOR_ADMIN_PASSWORD` | +| `caSecretName` | The name of the secret which contains key named `ca.crt`. Setting this enables the download link on portal to download the CA certificate when the certificate isn't generated automatically | | +| `secretKey` | The key used for encryption. Must be a string of 16 chars | `not-a-secure-key` | +| `existingSecretSecretKey` | An existing secret containing the encoding secretKey | `""` | +| `proxy.httpProxy` | The URL of the HTTP proxy server | | +| `proxy.httpsProxy` | The URL of the HTTPS proxy server | | +| `proxy.noProxy` | The URLs that the proxy settings not apply to | 127.0.0.1,localhost,.local,.internal | +| `proxy.components` | The component list that the proxy settings apply to | core, jobservice, trivy | +| `enableMigrateHelmHook` | Run the migration job via helm hook, if it is true, the database migration will be separated from harbor-core, run with a preupgrade job migration-job | `false` | +| **Nginx** (if service exposed via `ingress`, Nginx will not be used) | | | +| `nginx.image.repository` | Image repository | `goharbor/nginx-photon` | +| `nginx.image.tag` | Image tag | `dev` | +| `nginx.replicas` | The replica count | `1` | +| `nginx.revisionHistoryLimit` | The revision history limit | `10` | +| `nginx.resources` | The [resources] to allocate for container | undefined | +| `nginx.automountServiceAccountToken` | Mount serviceAccountToken? | `false` | +| `nginx.nodeSelector` | Node labels for pod assignment | `{}` | +| `nginx.tolerations` | Tolerations for pod assignment | `[]` | +| `nginx.affinity` | Node/Pod affinities | `{}` | +| `nginx.topologySpreadConstraints` | Constraints that define how Pods are spread across failure-domains like regions or availability zones | `[]` | +| `nginx.podAnnotations` | Annotations to add to the nginx pod | `{}` | +| `nginx.priorityClassName` | The priority class to run the pod as | | +| **Portal** | | | +| `portal.image.repository` | Repository for portal image | `goharbor/harbor-portal` | +| `portal.image.tag` | Tag for portal image | `dev` | +| `portal.replicas` | The replica count | `1` | +| `portal.revisionHistoryLimit` | The revision history limit | `10` | +| `portal.resources` | The [resources] to allocate for container | undefined | +| `portal.automountServiceAccountToken` | Mount serviceAccountToken? | `false` | +| `portal.nodeSelector` | Node labels for pod assignment | `{}` | +| `portal.tolerations` | Tolerations for pod assignment | `[]` | +| `portal.affinity` | Node/Pod affinities | `{}` | +| `portal.topologySpreadConstraints` | Constraints that define how Pods are spread across failure-domains like regions or availability zones | `[]` | +| `portal.podAnnotations` | Annotations to add to the portal pod | `{}` | +| `portal.serviceAnnotations` | Annotations to add to the portal service | `{}` | +| `portal.priorityClassName` | The priority class to run the pod as | | +| `portal.initContainers` | Init containers to be run before the controller's container starts. | `[]` | +| **Core** | | | +| `core.image.repository` | Repository for Harbor core image | `goharbor/harbor-core` | +| `core.image.tag` | Tag for Harbor core image | `dev` | +| `core.replicas` | The replica count | `1` | +| `core.revisionHistoryLimit` | The revision history limit | `10` | +| `core.startupProbe.initialDelaySeconds` | The initial delay in seconds for the startup probe | `10` | +| `core.resources` | The [resources] to allocate for container | undefined | +| `core.automountServiceAccountToken` | Mount serviceAccountToken? | `false` | +| `core.nodeSelector` | Node labels for pod assignment | `{}` | +| `core.tolerations` | Tolerations for pod assignment | `[]` | +| `core.affinity` | Node/Pod affinities | `{}` | +| `core.topologySpreadConstraints` | Constraints that define how Pods are spread across failure-domains like regions or availability zones | `[]` | +| `core.podAnnotations` | Annotations to add to the core pod | `{}` | +| `core.serviceAnnotations` | Annotations to add to the core service | `{}` | +| `core.configureUserSettings` | A JSON string to set in the environment variable `CONFIG_OVERWRITE_JSON` to configure user settings. See the [official docs](https://goharbor.io/docs/latest/install-config/configure-user-settings-cli/#configure-users-settings-using-an-environment-variable). | | +| `core.quotaUpdateProvider` | The provider for updating project quota(usage), there are 2 options, `redis` or `db`. You can set it to be implemented by `redis` which can improve the performance of high concurrent pushing to the same project, and reduce database connection spikes and occupies. Using redis will bring up some delay for quota usage update for display, so only suggest switch provider to redis if you ran into the db connections spike around the scenario of high concurrent pushing to same project, no improvement for other scenes. | `db` | +| `core.secret` | Secret is used when core server communicates with other components. If a secret key is not specified, Helm will generate one. Must be a string of 16 chars. | | +| `core.secretName` | Fill the name of a kubernetes secret if you want to use your own TLS certificate and private key for token encryption/decryption. The secret must contain keys named: `tls.crt` - the certificate and `tls.key` - the private key. The default key pair will be used if it isn't set | | +| `core.tokenKey` | PEM-formatted RSA private key used to sign service tokens. Only used if `core.secretName` is unset. If set, `core.tokenCert` MUST also be set. | | +| `core.tokenCert` | PEM-formatted certificate signed by `core.tokenKey` used to validate service tokens. Only used if `core.secretName` is unset. If set, `core.tokenKey` MUST also be set. | | +| `core.xsrfKey` | The XSRF key. Will be generated automatically if it isn't specified | | +| `core.priorityClassName` | The priority class to run the pod as | | +| `core.artifactPullAsyncFlushDuration` | The time duration for async update artifact pull_time and repository pull_count | | +| `core.gdpr.deleteUser` | Enable GDPR compliant user delete | `false` | +| `core.gdpr.auditLogsCompliant` | Enable GDPR compliant for audit logs by changing username to its CRC32 value if that user was deleted from the system | `false` | +| `core.initContainers` | Init containers to be run before the controller's container starts. | `[]` | +| **Jobservice** | | | +| `jobservice.image.repository` | Repository for jobservice image | `goharbor/harbor-jobservice` | +| `jobservice.image.tag` | Tag for jobservice image | `dev` | +| `jobservice.replicas` | The replica count | `1` | +| `jobservice.revisionHistoryLimit` | The revision history limit | `10` | +| `jobservice.maxJobWorkers` | The max job workers | `10` | +| `jobservice.jobLoggers` | The loggers for jobs: `file`, `database` or `stdout` | `[file]` | +| `jobservice.loggerSweeperDuration` | The jobLogger sweeper duration in days (ignored if `jobLoggers` is set to `stdout`) | `14` | +| `jobservice.notification.webhook_job_max_retry` | The maximum retry of webhook sending notifications | `3` | +| `jobservice.notification.webhook_job_http_client_timeout` | The http client timeout value of webhook sending notifications | `3` | +| `jobservice.reaper.max_update_hours` | the max time to wait for a task to finish, if unfinished after max_update_hours, the task will be mark as error, but the task will continue to run | `24` | +| `jobservice.reaper.max_dangling_hours` | the max time for execution in running state without new task created | `168` | +| `jobservice.resources` | The [resources] to allocate for container | undefined | +| `jobservice.automountServiceAccountToken` | Mount serviceAccountToken? | `false` | +| `jobservice.nodeSelector` | Node labels for pod assignment | `{}` | +| `jobservice.tolerations` | Tolerations for pod assignment | `[]` | +| `jobservice.affinity` | Node/Pod affinities | `{}` | +| `jobservice.topologySpreadConstraints` | Constraints that define how Pods are spread across failure-domains like regions or availability zones | `[]` | +| `jobservice.podAnnotations` | Annotations to add to the jobservice pod | `{}` | +| `jobservice.priorityClassName` | The priority class to run the pod as | | +| `jobservice.secret` | Secret is used when job service communicates with other components. If a secret key is not specified, Helm will generate one. Must be a string of 16 chars. | | +| `jobservice.initContainers` | Init containers to be run before the controller's container starts. | `[]` | +| **Registry** | | | +| `registry.registry.image.repository` | Repository for registry image | `goharbor/registry-photon` | +| `registry.registry.image.tag` | Tag for registry image | `dev` | +| `registry.registry.resources` | The [resources] to allocate for container | undefined | +| `registry.controller.image.repository` | Repository for registry controller image | `goharbor/harbor-registryctl` | +| `registry.controller.image.tag` | Tag for registry controller image | `dev` | +| `registry.controller.resources` | The [resources] to allocate for container | undefined | +| `registry.replicas` | The replica count | `1` | +| `registry.revisionHistoryLimit` | The revision history limit | `10` | +| `registry.nodeSelector` | Node labels for pod assignment | `{}` | +| `registry.automountServiceAccountToken` | Mount serviceAccountToken? | `false` | +| `registry.tolerations` | Tolerations for pod assignment | `[]` | +| `registry.affinity` | Node/Pod affinities | `{}` | +| `registry.topologySpreadConstraints` | Constraints that define how Pods are spread across failure-domains like regions or availability zones | `[]` | +| `registry.middleware` | Middleware is used to add support for a CDN between backend storage and `docker pull` recipient. See [official docs](https://github.com/docker/distribution/blob/master/docs/configuration.md#middleware). | | +| `registry.podAnnotations` | Annotations to add to the registry pod | `{}` | +| `registry.priorityClassName` | The priority class to run the pod as | | +| `registry.secret` | Secret is used to secure the upload state from client and registry storage backend. See [official docs](https://github.com/docker/distribution/blob/master/docs/configuration.md#http). If a secret key is not specified, Helm will generate one. Must be a string of 16 chars. | | +| `registry.credentials.username` | The username that harbor core uses internally to access the registry instance. Together with the `registry.credentials.password`, a htpasswd is created. This is an alternative to providing `registry.credentials.htpasswdString`. For more details see [official docs](https://github.com/docker/distribution/blob/master/docs/configuration.md#htpasswd). | `harbor_registry_user` | +| `registry.credentials.password` | The password that harbor core uses internally to access the registry instance. Together with the `registry.credentials.username`, a htpasswd is created. This is an alternative to providing `registry.credentials.htpasswdString`. For more details see [official docs](https://github.com/docker/distribution/blob/master/docs/configuration.md#htpasswd). It is suggested you update this value before installation. | `harbor_registry_password` | +| `registry.credentials.existingSecret` | An existing secret containing the password for accessing the registry instance, which is hosted by htpasswd auth mode. More details see [official docs](https://github.com/docker/distribution/blob/master/docs/configuration.md#htpasswd). The key must be `REGISTRY_PASSWD` | `""` | +| `registry.credentials.htpasswdString` | Login and password in htpasswd string format. Excludes `registry.credentials.username` and `registry.credentials.password`. May come in handy when integrating with tools like argocd or flux. This allows the same line to be generated each time the template is rendered, instead of the `htpasswd` function from helm, which generates different lines each time because of the salt. | undefined | +| `registry.relativeurls` | If true, the registry returns relative URLs in Location headers. The client is responsible for resolving the correct URL. Needed if harbor is behind a reverse proxy | `false` | +| `registry.upload_purging.enabled` | If true, enable purge _upload directories | `true` | +| `registry.upload_purging.age` | Remove files in _upload directories which exist for a period of time, default is one week. | `168h` | +| `registry.upload_purging.interval` | The interval of the purge operations | `24h` | +| `registry.upload_purging.dryrun` | If true, enable dryrun for purging _upload | `false` | +| `registry.initContainers` | Init containers to be run before the controller's container starts. | `[]` | +| **[Trivy][trivy]** | | | +| `trivy.enabled` | The flag to enable Trivy scanner | `true` | +| `trivy.image.repository` | Repository for Trivy adapter image | `goharbor/trivy-adapter-photon` | +| `trivy.image.tag` | Tag for Trivy adapter image | `dev` | +| `trivy.resources` | The [resources] to allocate for Trivy adapter container | | +| `trivy.automountServiceAccountToken` | Mount serviceAccountToken? | `false` | +| `trivy.replicas` | The number of Pod replicas | `1` | +| `trivy.debugMode` | The flag to enable Trivy debug mode | `false` | +| `trivy.vulnType` | Comma-separated list of vulnerability types. Possible values `os` and `library`. | `os,library` | +| `trivy.severity` | Comma-separated list of severities to be checked | `UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL` | +| `trivy.ignoreUnfixed` | The flag to display only fixed vulnerabilities | `false` | +| `trivy.insecure` | The flag to skip verifying registry certificate | `false` | +| `trivy.skipUpdate` | The flag to disable [Trivy DB][trivy-db] downloads from GitHub | `false` | +| `trivy.skipJavaDBUpdate` | If the flag is enabled you have to manually download the `trivy-java.db` file [Trivy Java DB][trivy-java-db] and mount it in the `/home/scanner/.cache/trivy/java-db/trivy-java.db` path | `false` | +| `trivy.dbRepository` | OCI repository(ies) to retrieve the trivy vulnerability database in order of priority | `mirror.gcr.io/aquasec/trivy-db,ghcr.io/aquasecurity/trivy-db` | +| `trivy.javaDBRepository` | OCI repository(ies) to retrieve the Java trivy vulnerability database in order of priority | `mirror.gcr.io/aquasec/trivy-java-db,ghcr.io/aquasecurity/trivy-java-db` | +| `trivy.offlineScan` | The flag prevents Trivy from sending API requests to identify dependencies. | `false` | +| `trivy.securityCheck` | Comma-separated list of what security issues to detect. | `vuln` | +| `trivy.timeout` | The duration to wait for scan completion | `5m0s` | +| `trivy.gitHubToken` | The GitHub access token to download [Trivy DB][trivy-db] (see [GitHub rate limiting][trivy-rate-limiting]) | | +| `trivy.priorityClassName` | The priority class to run the pod as | | +| `trivy.topologySpreadConstraints` | The priority class to run the pod as | | +| `trivy.initContainers` | Init containers to be run before the controller's container starts. | `[]` | +| **Database** | | | +| `database.type` | If external database is used, set it to `external` | `internal` | +| `database.internal.image.repository` | Repository for database image | `goharbor/harbor-db` | +| `database.internal.image.tag` | Tag for database image | `dev` | +| `database.internal.password` | The password for database | `changeit` | +| `database.internal.shmSizeLimit` | The limit for the size of shared memory for internal PostgreSQL, conventionally it's around 50% of the memory limit of the container | `512Mi` | +| `database.internal.resources` | The [resources] to allocate for container | undefined | +| `database.internal.automountServiceAccountToken` | Mount serviceAccountToken? | `false` | +| `database.internal.initContainer.migrator.resources` | The [resources] to allocate for the database migrator initContainer | undefined | +| `database.internal.initContainer.permissions.resources` | The [resources] to allocate for the database permissions initContainer | undefined | +| `database.internal.nodeSelector` | Node labels for pod assignment | `{}` | +| `database.internal.tolerations` | Tolerations for pod assignment | `[]` | +| `database.internal.affinity` | Node/Pod affinities | `{}` | +| `database.internal.priorityClassName` | The priority class to run the pod as | | +| `database.internal.livenessProbe.timeoutSeconds` | The timeout used in liveness probe; 1 to 5 seconds | 1 | +| `database.internal.readinessProbe.timeoutSeconds` | The timeout used in readiness probe; 1 to 5 seconds | 1 | +| `database.internal.extrInitContainers` | Extra init containers to be run before the database's container starts. | `[]` | +| `database.external.host` | The hostname of external database | `192.168.0.1` | +| `database.external.port` | The port of external database | `5432` | +| `database.external.username` | The username of external database | `user` | +| `database.external.password` | The password of external database | `password` | +| `database.external.coreDatabase` | The database used by core service | `registry` | +| `database.external.existingSecret` | An existing password containing the database password. the key must be `password`. | `""` | +| `database.external.sslmode` | Connection method of external database (require, verify-full, verify-ca, disable) | `disable` | +| `database.maxIdleConns` | The maximum number of connections in the idle connection pool. If it <=0, no idle connections are retained. | `50` | +| `database.maxOpenConns` | The maximum number of open connections to the database. If it <= 0, then there is no limit on the number of open connections. | `100` | +| `database.podAnnotations` | Annotations to add to the database pod | `{}` | +| **Redis** | | | +| `redis.type` | If external redis is used, set it to `external` | `internal` | +| `redis.internal.image.repository` | Repository for redis image | `goharbor/redis-photon` | +| `redis.internal.image.tag` | Tag for redis image | `dev` | +| `redis.internal.resources` | The [resources] to allocate for container | undefined | +| `redis.internal.automountServiceAccountToken` | Mount serviceAccountToken? | `false` | +| `redis.internal.nodeSelector` | Node labels for pod assignment | `{}` | +| `redis.internal.tolerations` | Tolerations for pod assignment | `[]` | +| `redis.internal.affinity` | Node/Pod affinities | `{}` | +| `redis.internal.priorityClassName` | The priority class to run the pod as | | +| `redis.internal.jobserviceDatabaseIndex` | The database index for jobservice | `1` | +| `redis.internal.registryDatabaseIndex` | The database index for registry | `2` | +| `redis.internal.trivyAdapterIndex` | The database index for trivy adapter | `5` | +| `redis.internal.harborDatabaseIndex` | The database index for harbor miscellaneous business logic | `0` | +| `redis.internal.cacheLayerDatabaseIndex` | The database index for harbor cache layer | `0` | +| `redis.internal.initContainers` | Init containers to be run before the redis's container starts. | `[]` | +| `redis.external.addr` | The addr of external Redis: :. When using sentinel, it should be :,:,: | `192.168.0.2:6379` | +| `redis.external.sentinelMasterSet` | The name of the set of Redis instances to monitor | | +| `redis.external.coreDatabaseIndex` | The database index for core | `0` | +| `redis.external.jobserviceDatabaseIndex` | The database index for jobservice | `1` | +| `redis.external.registryDatabaseIndex` | The database index for registry | `2` | +| `redis.external.trivyAdapterIndex` | The database index for trivy adapter | `5` | +| `redis.external.harborDatabaseIndex` | The database index for harbor miscellaneous business logic | `0` | +| `redis.external.cacheLayerDatabaseIndex` | The database index for harbor cache layer | `0` | +| `redis.external.username` | The username of external Redis | | +| `redis.external.password` | The password of external Redis | | +| `redis.external.existingSecret` | Use an existing secret to connect to redis. The key must be `REDIS_PASSWORD`. | `""` | +| `redis.podAnnotations` | Annotations to add to the redis pod | `{}` | +| **Exporter** | | | +| `exporter.replicas` | The replica count | `1` | +| `exporter.revisionHistoryLimit` | The revision history limit | `10` | +| `exporter.podAnnotations` | Annotations to add to the exporter pod | `{}` | +| `exporter.image.repository` | Repository for redis image | `goharbor/harbor-exporter` | +| `exporter.image.tag` | Tag for exporter image | `dev` | +| `exporter.nodeSelector` | Node labels for pod assignment | `{}` | +| `exporter.tolerations` | Tolerations for pod assignment | `[]` | +| `exporter.affinity` | Node/Pod affinities | `{}` | +| `exporter.topologySpreadConstraints` | Constraints that define how Pods are spread across failure-domains like regions or availability zones | `[]` | +| `exporter.automountServiceAccountToken` | Mount serviceAccountToken? | `false` | +| `exporter.cacheDuration` | the cache duration for information that exporter collected from Harbor | `30` | +| `exporter.cacheCleanInterval` | cache clean interval for information that exporter collected from Harbor | `14400` | +| `exporter.priorityClassName` | The priority class to run the pod as | | +| **Metrics** | | | +| `metrics.enabled` | if enable harbor metrics | `false` | +| `metrics.core.path` | the url path for core metrics | `/metrics` | +| `metrics.core.port` | the port for core metrics | `8001` | +| `metrics.registry.path` | the url path for registry metrics | `/metrics` | +| `metrics.registry.port` | the port for registry metrics | `8001` | +| `metrics.exporter.path` | the url path for exporter metrics | `/metrics` | +| `metrics.exporter.port` | the port for exporter metrics | `8001` | +| `metrics.serviceMonitor.enabled` | create prometheus serviceMonitor. Requires prometheus CRD's | `false` | +| `metrics.serviceMonitor.additionalLabels` | additional labels to upsert to the manifest | `""` | +| `metrics.serviceMonitor.interval` | scrape period for harbor metrics | `""` | +| `metrics.serviceMonitor.metricRelabelings` | metrics relabel to add/mod/del before ingestion | `[]` | +| `metrics.serviceMonitor.relabelings` | relabels to add/mod/del to sample before scrape | `[]` | +| **Trace** | | | +| `trace.enabled` | Enable tracing or not | `false` | +| `trace.provider` | The tracing provider: `jaeger` or `otel`. `jaeger` should be 1.26+ | `jaeger` | +| `trace.sample_rate` | Set `sample_rate` to 1 if you want sampling 100% of trace data; set 0.5 if you want sampling 50% of trace data, and so forth | `1` | +| `trace.namespace` | Namespace used to differentiate different harbor services | | +| `trace.attributes` | `attributes` is a key value dict contains user defined attributes used to initialize trace provider | | +| `trace.jaeger.endpoint` | The endpoint of jaeger | `http://hostname:14268/api/traces` | +| `trace.jaeger.username` | The username of jaeger | | +| `trace.jaeger.password` | The password of jaeger | | +| `trace.jaeger.agent_host` | The agent host of jaeger | | +| `trace.jaeger.agent_port` | The agent port of jaeger | `6831` | +| `trace.otel.endpoint` | The endpoint of otel | `hostname:4318` | +| `trace.otel.url_path` | The URL path of otel | `/v1/traces` | +| `trace.otel.compression` | Whether enable compression or not for otel | `false` | +| `trace.otel.insecure` | Whether establish insecure connection or not for otel | `true` | +| `trace.otel.timeout` | The timeout in seconds of otel | `10` | +| **Cache** | | | +| `cache.enabled` | Enable cache layer or not | `false` | +| `cache.expireHours` | The expire hours of cache layer | `24` | + +[resources]: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/ +[trivy]: https://github.com/aquasecurity/trivy +[trivy-db]: https://github.com/aquasecurity/trivy-db +[trivy-java-db]: https://github.com/aquasecurity/trivy-java-db +[trivy-rate-limiting]: https://github.com/aquasecurity/trivy#github-rate-limiting diff --git a/packages/system/harbor/charts/harbor/templates/NOTES.txt b/packages/system/harbor/charts/harbor/templates/NOTES.txt new file mode 100644 index 00000000..0980c08a --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/NOTES.txt @@ -0,0 +1,3 @@ +Please wait for several minutes for Harbor deployment to complete. +Then you should be able to visit the Harbor portal at {{ .Values.externalURL }} +For more details, please visit https://github.com/goharbor/harbor diff --git a/packages/system/harbor/charts/harbor/templates/_helpers.tpl b/packages/system/harbor/charts/harbor/templates/_helpers.tpl new file mode 100644 index 00000000..95643c49 --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/_helpers.tpl @@ -0,0 +1,606 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "harbor.name" -}} +{{- default "harbor" .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "harbor.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default "harbor" .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* Helm required labels: legacy */}} +{{- define "harbor.legacy.labels" -}} +heritage: {{ .Release.Service }} +release: {{ .Release.Name }} +chart: {{ .Chart.Name }} +app: "{{ template "harbor.name" . }}" +{{- end -}} + +{{/* Helm required labels */}} +{{- define "harbor.labels" -}} +heritage: {{ .Release.Service }} +release: {{ .Release.Name }} +chart: {{ .Chart.Name }} +app: "{{ template "harbor.name" . }}" +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/name: {{ include "harbor.name" . }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +app.kubernetes.io/part-of: {{ include "harbor.name" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +{{- end -}} + +{{/* matchLabels */}} +{{- define "harbor.matchLabels" -}} +release: {{ .Release.Name }} +app: "{{ template "harbor.name" . }}" +{{- end -}} + +{{/* Helper for printing values from existing secrets*/}} +{{- define "harbor.secretKeyHelper" -}} + {{- if and (not (empty .data)) (hasKey .data .key) }} + {{- index .data .key | b64dec -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.autoGenCert" -}} + {{- if and .Values.expose.tls.enabled (eq .Values.expose.tls.certSource "auto") -}} + {{- printf "true" -}} + {{- else -}} + {{- printf "false" -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.autoGenCertForIngress" -}} + {{- if and (eq (include "harbor.autoGenCert" .) "true") (eq .Values.expose.type "ingress") -}} + {{- printf "true" -}} + {{- else -}} + {{- printf "false" -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.autoGenCertForNginx" -}} + {{- if and (eq (include "harbor.autoGenCert" .) "true") (ne .Values.expose.type "ingress") -}} + {{- printf "true" -}} + {{- else -}} + {{- printf "false" -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.database.host" -}} + {{- if eq .Values.database.type "internal" -}} + {{- template "harbor.database" . }} + {{- else -}} + {{- .Values.database.external.host -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.database.port" -}} + {{- if eq .Values.database.type "internal" -}} + {{- printf "%s" "5432" -}} + {{- else -}} + {{- .Values.database.external.port -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.database.username" -}} + {{- if eq .Values.database.type "internal" -}} + {{- printf "%s" "postgres" -}} + {{- else -}} + {{- .Values.database.external.username -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.database.rawPassword" -}} + {{- if eq .Values.database.type "internal" -}} + {{- $existingSecret := lookup "v1" "Secret" .Release.Namespace (include "harbor.database" .) -}} + {{- if and (not (empty $existingSecret)) (hasKey $existingSecret.data "POSTGRES_PASSWORD") -}} + {{- .Values.database.internal.password | default (index $existingSecret.data "POSTGRES_PASSWORD" | b64dec) -}} + {{- else -}} + {{- .Values.database.internal.password -}} + {{- end -}} + {{- else -}} + {{- .Values.database.external.password -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.database.escapedRawPassword" -}} + {{- include "harbor.database.rawPassword" . | urlquery | replace "+" "%20" -}} +{{- end -}} + +{{- define "harbor.database.encryptedPassword" -}} + {{- include "harbor.database.rawPassword" . | b64enc | quote -}} +{{- end -}} + +{{- define "harbor.database.coreDatabase" -}} + {{- if eq .Values.database.type "internal" -}} + {{- printf "%s" "registry" -}} + {{- else -}} + {{- .Values.database.external.coreDatabase -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.database.sslmode" -}} + {{- if eq .Values.database.type "internal" -}} + {{- printf "%s" "disable" -}} + {{- else -}} + {{- .Values.database.external.sslmode -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.redis.scheme" -}} + {{- with .Values.redis }} + {{- if eq .type "external" -}} + {{- if not (not .external.sentinelMasterSet) -}} + {{- ternary "rediss+sentinel" "redis+sentinel" (.external.tlsOptions.enable) }} + {{- else -}} + {{- ternary "rediss" "redis" (.external.tlsOptions.enable) }} + {{- end -}} + {{- else -}} + {{ print "redis" }} + {{- end -}} + {{- end }} +{{- end -}} + +{{- define "harbor.redis.enableTLS" -}} + {{- with .Values.redis }} + {{- ternary "true" "false" (and ( eq .type "external") (.external.tlsOptions.enable)) }} + {{- end }} +{{- end -}} + +/*host:port*/ +{{- define "harbor.redis.addr" -}} + {{- with .Values.redis }} + {{- ternary (printf "%s:6379" (include "harbor.redis" $ )) .external.addr (eq .type "internal") }} + {{- end }} +{{- end -}} + +{{- define "harbor.redis.masterSet" -}} + {{- with .Values.redis }} + {{- ternary .external.sentinelMasterSet "" (contains "+sentinel" (include "harbor.redis.scheme" $)) }} + {{- end }} +{{- end -}} + +{{- define "harbor.redis.password" -}} + {{- with .Values.redis }} + {{- ternary "" .external.password (eq .type "internal") }} + {{- end }} +{{- end -}} + + +{{- define "harbor.redis.usernamefromsecret" -}} + {{- $existingSecret := (lookup "v1" "Secret" .Release.Namespace (.Values.redis.external.existingSecret)) -}} + {{- if and (not (empty $existingSecret)) (hasKey $existingSecret.data "REDIS_USERNAME") -}} + {{- printf "%s" ($existingSecret.data.REDIS_USERNAME | b64dec | trim ) }} + {{- end -}} +{{- end -}} + +{{- define "harbor.redis.pwdfromsecret" -}} + {{- (lookup "v1" "Secret" .Release.Namespace (.Values.redis.external.existingSecret)).data.REDIS_PASSWORD | b64dec }} +{{- end -}} + +{{- define "harbor.redis.cred" -}} + {{- with .Values.redis }} + {{- if (and (eq .type "external" ) (.external.existingSecret)) }} + {{- printf "%s:%s@" (include "harbor.redis.usernamefromsecret" $) (include "harbor.redis.pwdfromsecret" $) -}} + {{- else }} + {{- ternary (printf "%s:%s@" (.external.username | urlquery) (.external.password | urlquery)) "" (and (eq .type "external" ) (not (not .external.password))) }} + {{- end }} + {{- end }} +{{- end -}} + +/*scheme://[:password@]host:port[/master_set]*/ +{{- define "harbor.redis.url" -}} + {{- with .Values.redis }} + {{- $path := ternary "" (printf "/%s" (include "harbor.redis.masterSet" $)) (not (include "harbor.redis.masterSet" $)) }} + {{- printf "%s://%s%s%s" (include "harbor.redis.scheme" $) (include "harbor.redis.cred" $) (include "harbor.redis.addr" $) $path -}} + {{- end }} +{{- end -}} + +/*scheme://[:password@]addr/db_index?idle_timeout_seconds=30*/ +{{- define "harbor.redis.urlForCore" -}} + {{- with .Values.redis }} + {{- $index := ternary "0" .external.coreDatabaseIndex (eq .type "internal") }} + {{- printf "%s/%s?idle_timeout_seconds=30" (include "harbor.redis.url" $) $index -}} + {{- end }} +{{- end -}} + +/*scheme://[:password@]addr/db_index*/ +{{- define "harbor.redis.urlForJobservice" -}} + {{- with .Values.redis }} + {{- $index := ternary .internal.jobserviceDatabaseIndex .external.jobserviceDatabaseIndex (eq .type "internal") }} + {{- printf "%s/%s" (include "harbor.redis.url" $) $index -}} + {{- end }} +{{- end -}} + +/*scheme://[:password@]addr/db_index?idle_timeout_seconds=30*/ +{{- define "harbor.redis.urlForRegistry" -}} + {{- with .Values.redis }} + {{- $index := ternary .internal.registryDatabaseIndex .external.registryDatabaseIndex (eq .type "internal") }} + {{- printf "%s/%s?idle_timeout_seconds=30" (include "harbor.redis.url" $) $index -}} + {{- end }} +{{- end -}} + +/*scheme://[:password@]addr/db_index?idle_timeout_seconds=30*/ +{{- define "harbor.redis.urlForTrivy" -}} + {{- with .Values.redis }} + {{- $index := ternary .internal.trivyAdapterIndex .external.trivyAdapterIndex (eq .type "internal") }} + {{- printf "%s/%s?idle_timeout_seconds=30" (include "harbor.redis.url" $) $index -}} + {{- end }} +{{- end -}} + +/*scheme://[:password@]addr/db_index?idle_timeout_seconds=30*/ +{{- define "harbor.redis.urlForHarbor" -}} + {{- with .Values.redis }} + {{- $index := ternary .internal.harborDatabaseIndex .external.harborDatabaseIndex (eq .type "internal") }} + {{- printf "%s/%s?idle_timeout_seconds=30" (include "harbor.redis.url" $) $index -}} + {{- end }} +{{- end -}} + +/*scheme://[:password@]addr/db_index?idle_timeout_seconds=30*/ +{{- define "harbor.redis.urlForCache" -}} + {{- with .Values.redis }} + {{- $index := ternary .internal.cacheLayerDatabaseIndex .external.cacheLayerDatabaseIndex (eq .type "internal") }} + {{- printf "%s/%s?idle_timeout_seconds=30" (include "harbor.redis.url" $) $index -}} + {{- end }} +{{- end -}} + +{{- define "harbor.redis.dbForRegistry" -}} + {{- with .Values.redis }} + {{- ternary .internal.registryDatabaseIndex .external.registryDatabaseIndex (eq .type "internal") }} + {{- end }} +{{- end -}} + +{{- define "harbor.portal" -}} + {{- printf "%s-portal" (include "harbor.fullname" .) -}} +{{- end -}} + +{{- define "harbor.core" -}} + {{- printf "%s-core" (include "harbor.fullname" .) -}} +{{- end -}} + +{{- define "harbor.redis" -}} + {{- printf "%s-redis" (include "harbor.fullname" .) -}} +{{- end -}} + +{{- define "harbor.jobservice" -}} + {{- printf "%s-jobservice" (include "harbor.fullname" .) -}} +{{- end -}} + +{{- define "harbor.registry" -}} + {{- printf "%s-registry" (include "harbor.fullname" .) -}} +{{- end -}} + +{{- define "harbor.registryCtl" -}} + {{- printf "%s-registryctl" (include "harbor.fullname" .) -}} +{{- end -}} + +{{- define "harbor.database" -}} + {{- printf "%s-database" (include "harbor.fullname" .) -}} +{{- end -}} + +{{- define "harbor.trivy" -}} + {{- printf "%s-trivy" (include "harbor.fullname" .) -}} +{{- end -}} + +{{- define "harbor.nginx" -}} + {{- printf "%s-nginx" (include "harbor.fullname" .) -}} +{{- end -}} + +{{- define "harbor.exporter" -}} + {{- printf "%s-exporter" (include "harbor.fullname" .) -}} +{{- end -}} + +{{- define "harbor.ingress" -}} + {{- printf "%s-ingress" (include "harbor.fullname" .) -}} +{{- end -}} + +{{- define "harbor.route" -}} + {{- printf "%s-route" (include "harbor.fullname" .) -}} +{{- end -}} + +{{- define "harbor.noProxy" -}} + {{- printf "%s,%s,%s,%s,%s,%s,%s,%s" (include "harbor.core" .) (include "harbor.jobservice" .) (include "harbor.database" .) (include "harbor.registry" .) (include "harbor.portal" .) (include "harbor.trivy" .) (include "harbor.exporter" .) .Values.proxy.noProxy -}} +{{- end -}} + +{{- define "harbor.caBundleVolume" -}} +- name: ca-bundle-certs + secret: + secretName: {{ .Values.caBundleSecretName }} +{{- end -}} + +{{- define "harbor.caBundleVolumeMount" -}} +- name: ca-bundle-certs + mountPath: /harbor_cust_cert/custom-ca.crt + subPath: ca.crt +{{- end -}} + +{{/* scheme for all components because it only support http mode */}} +{{- define "harbor.component.scheme" -}} + {{- if .Values.internalTLS.enabled -}} + {{- printf "https" -}} + {{- else -}} + {{- printf "http" -}} + {{- end -}} +{{- end -}} + +{{/* core component container port */}} +{{- define "harbor.core.containerPort" -}} + {{- if .Values.internalTLS.enabled -}} + {{- printf "8443" -}} + {{- else -}} + {{- printf "8080" -}} + {{- end -}} +{{- end -}} + +{{/* core component service port */}} +{{- define "harbor.core.servicePort" -}} + {{- if .Values.internalTLS.enabled -}} + {{- printf "443" -}} + {{- else -}} + {{- printf "80" -}} + {{- end -}} +{{- end -}} + +{{/* jobservice component container port */}} +{{- define "harbor.jobservice.containerPort" -}} + {{- if .Values.internalTLS.enabled -}} + {{- printf "8443" -}} + {{- else -}} + {{- printf "8080" -}} + {{- end -}} +{{- end -}} + +{{/* jobservice component service port */}} +{{- define "harbor.jobservice.servicePort" -}} + {{- if .Values.internalTLS.enabled -}} + {{- printf "443" -}} + {{- else -}} + {{- printf "80" -}} + {{- end -}} +{{- end -}} + +{{/* portal component container port */}} +{{- define "harbor.portal.containerPort" -}} + {{- if .Values.internalTLS.enabled -}} + {{- printf "8443" -}} + {{- else -}} + {{- printf "8080" -}} + {{- end -}} +{{- end -}} + +{{/* portal component service port */}} +{{- define "harbor.portal.servicePort" -}} + {{- if .Values.internalTLS.enabled -}} + {{- printf "443" -}} + {{- else -}} + {{- printf "80" -}} + {{- end -}} +{{- end -}} + +{{/* registry component container port */}} +{{- define "harbor.registry.containerPort" -}} + {{- if .Values.internalTLS.enabled -}} + {{- printf "5443" -}} + {{- else -}} + {{- printf "5000" -}} + {{- end -}} +{{- end -}} + +{{/* registry component service port */}} +{{- define "harbor.registry.servicePort" -}} + {{- if .Values.internalTLS.enabled -}} + {{- printf "5443" -}} + {{- else -}} + {{- printf "5000" -}} + {{- end -}} +{{- end -}} + +{{/* registryctl component container port */}} +{{- define "harbor.registryctl.containerPort" -}} + {{- if .Values.internalTLS.enabled -}} + {{- printf "8443" -}} + {{- else -}} + {{- printf "8080" -}} + {{- end -}} +{{- end -}} + +{{/* registryctl component service port */}} +{{- define "harbor.registryctl.servicePort" -}} + {{- if .Values.internalTLS.enabled -}} + {{- printf "8443" -}} + {{- else -}} + {{- printf "8080" -}} + {{- end -}} +{{- end -}} + +{{/* trivy component container port */}} +{{- define "harbor.trivy.containerPort" -}} + {{- if .Values.internalTLS.enabled -}} + {{- printf "8443" -}} + {{- else -}} + {{- printf "8080" -}} + {{- end -}} +{{- end -}} + +{{/* trivy component service port */}} +{{- define "harbor.trivy.servicePort" -}} + {{- if .Values.internalTLS.enabled -}} + {{- printf "8443" -}} + {{- else -}} + {{- printf "8080" -}} + {{- end -}} +{{- end -}} + +{{/* CORE_URL */}} +{{/* port is included in this url as a workaround for issue https://github.com/aquasecurity/harbor-scanner-trivy/issues/108 */}} +{{- define "harbor.coreURL" -}} + {{- printf "%s://%s:%s" (include "harbor.component.scheme" .) (include "harbor.core" .) (include "harbor.core.servicePort" .) -}} +{{- end -}} + +{{/* JOBSERVICE_URL */}} +{{- define "harbor.jobserviceURL" -}} + {{- printf "%s://%s-jobservice" (include "harbor.component.scheme" .) (include "harbor.fullname" .) -}} +{{- end -}} + +{{/* PORTAL_URL */}} +{{- define "harbor.portalURL" -}} + {{- printf "%s://%s" (include "harbor.component.scheme" .) (include "harbor.portal" .) -}} +{{- end -}} + +{{/* REGISTRY_URL */}} +{{- define "harbor.registryURL" -}} + {{- printf "%s://%s:%s" (include "harbor.component.scheme" .) (include "harbor.registry" .) (include "harbor.registry.servicePort" .) -}} +{{- end -}} + +{{/* REGISTRY_CONTROLLER_URL */}} +{{- define "harbor.registryControllerURL" -}} + {{- printf "%s://%s:%s" (include "harbor.component.scheme" .) (include "harbor.registry" .) (include "harbor.registryctl.servicePort" .) -}} +{{- end -}} + +{{/* TOKEN_SERVICE_URL */}} +{{- define "harbor.tokenServiceURL" -}} + {{- printf "%s/service/token" (include "harbor.coreURL" .) -}} +{{- end -}} + +{{/* TRIVY_ADAPTER_URL */}} +{{- define "harbor.trivyAdapterURL" -}} + {{- printf "%s://%s:%s" (include "harbor.component.scheme" .) (include "harbor.trivy" .) (include "harbor.trivy.servicePort" .) -}} +{{- end -}} + +{{- define "harbor.internalTLS.core.secretName" -}} + {{- if eq .Values.internalTLS.certSource "secret" -}} + {{- .Values.internalTLS.core.secretName -}} + {{- else -}} + {{- printf "%s-core-internal-tls" (include "harbor.fullname" .) -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.internalTLS.jobservice.secretName" -}} + {{- if eq .Values.internalTLS.certSource "secret" -}} + {{- .Values.internalTLS.jobservice.secretName -}} + {{- else -}} + {{- printf "%s-jobservice-internal-tls" (include "harbor.fullname" .) -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.internalTLS.portal.secretName" -}} + {{- if eq .Values.internalTLS.certSource "secret" -}} + {{- .Values.internalTLS.portal.secretName -}} + {{- else -}} + {{- printf "%s-portal-internal-tls" (include "harbor.fullname" .) -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.internalTLS.registry.secretName" -}} + {{- if eq .Values.internalTLS.certSource "secret" -}} + {{- .Values.internalTLS.registry.secretName -}} + {{- else -}} + {{- printf "%s-registry-internal-tls" (include "harbor.fullname" .) -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.internalTLS.trivy.secretName" -}} + {{- if eq .Values.internalTLS.certSource "secret" -}} + {{- .Values.internalTLS.trivy.secretName -}} + {{- else -}} + {{- printf "%s-trivy-internal-tls" (include "harbor.fullname" .) -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.tlsCoreSecretForIngress" -}} + {{- if eq .Values.expose.tls.certSource "none" -}} + {{- printf "" -}} + {{- else if eq .Values.expose.tls.certSource "secret" -}} + {{- .Values.expose.tls.secret.secretName -}} + {{- else -}} + {{- include "harbor.ingress" . -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.tlsSecretForNginx" -}} + {{- if eq .Values.expose.tls.certSource "secret" -}} + {{- .Values.expose.tls.secret.secretName -}} + {{- else -}} + {{- include "harbor.nginx" . -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.metricsPortName" -}} + {{- if .Values.internalTLS.enabled }} + {{- printf "https-metrics" -}} + {{- else -}} + {{- printf "http-metrics" -}} + {{- end -}} +{{- end -}} + +{{- define "harbor.traceEnvs" -}} + TRACE_ENABLED: "{{ .Values.trace.enabled }}" + TRACE_SAMPLE_RATE: "{{ .Values.trace.sample_rate }}" + TRACE_NAMESPACE: "{{ .Values.trace.namespace }}" + {{- if .Values.trace.attributes }} + TRACE_ATTRIBUTES: {{ .Values.trace.attributes | toJson | squote }} + {{- end }} + {{- if eq .Values.trace.provider "jaeger" }} + TRACE_JAEGER_ENDPOINT: "{{ .Values.trace.jaeger.endpoint }}" + TRACE_JAEGER_USERNAME: "{{ .Values.trace.jaeger.username }}" + TRACE_JAEGER_AGENT_HOSTNAME: "{{ .Values.trace.jaeger.agent_host }}" + TRACE_JAEGER_AGENT_PORT: "{{ .Values.trace.jaeger.agent_port }}" + {{- else }} + TRACE_OTEL_ENDPOINT: "{{ .Values.trace.otel.endpoint }}" + TRACE_OTEL_URL_PATH: "{{ .Values.trace.otel.url_path }}" + TRACE_OTEL_COMPRESSION: "{{ .Values.trace.otel.compression }}" + TRACE_OTEL_INSECURE: "{{ .Values.trace.otel.insecure }}" + TRACE_OTEL_TIMEOUT: "{{ .Values.trace.otel.timeout }}" + {{- end }} +{{- end -}} + +{{- define "harbor.traceEnvsForCore" -}} + {{- if .Values.trace.enabled }} + TRACE_SERVICE_NAME: "harbor-core" + {{ include "harbor.traceEnvs" . }} + {{- end }} +{{- end -}} + +{{- define "harbor.traceEnvsForJobservice" -}} + {{- if .Values.trace.enabled }} + TRACE_SERVICE_NAME: "harbor-jobservice" + {{ include "harbor.traceEnvs" . }} + {{- end }} +{{- end -}} + +{{- define "harbor.traceEnvsForRegistryCtl" -}} + {{- if .Values.trace.enabled }} + TRACE_SERVICE_NAME: "harbor-registryctl" + {{ include "harbor.traceEnvs" . }} + {{- end }} +{{- end -}} + +{{- define "harbor.traceJaegerPassword" -}} + {{- if and .Values.trace.enabled (eq .Values.trace.provider "jaeger") }} + TRACE_JAEGER_PASSWORD: "{{ .Values.trace.jaeger.password | default "" | b64enc }}" + {{- end }} +{{- end -}} + +{{/* Allow KubeVersion to be overridden. */}} +{{- define "harbor.ingress.kubeVersion" -}} + {{- default .Capabilities.KubeVersion.Version .Values.expose.ingress.kubeVersionOverride -}} +{{- end -}} diff --git a/packages/system/harbor/charts/harbor/templates/core/core-cm.yaml b/packages/system/harbor/charts/harbor/templates/core/core-cm.yaml new file mode 100644 index 00000000..17a82078 --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/core/core-cm.yaml @@ -0,0 +1,92 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "harbor.core" . }} + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +data: + app.conf: |+ + appname = Harbor + runmode = prod + enablegzip = true + + [prod] + httpport = {{ ternary "8443" "8080" .Values.internalTLS.enabled }} + PORT: "{{ ternary "8443" "8080" .Values.internalTLS.enabled }}" + DATABASE_TYPE: "postgresql" + POSTGRESQL_HOST: "{{ template "harbor.database.host" . }}" + POSTGRESQL_PORT: "{{ template "harbor.database.port" . }}" + POSTGRESQL_USERNAME: "{{ template "harbor.database.username" . }}" + POSTGRESQL_DATABASE: "{{ template "harbor.database.coreDatabase" . }}" + POSTGRESQL_SSLMODE: "{{ template "harbor.database.sslmode" . }}" + POSTGRESQL_MAX_IDLE_CONNS: "{{ .Values.database.maxIdleConns }}" + POSTGRESQL_MAX_OPEN_CONNS: "{{ .Values.database.maxOpenConns }}" + EXT_ENDPOINT: "{{ .Values.externalURL }}" + CORE_URL: "{{ template "harbor.coreURL" . }}" + JOBSERVICE_URL: "{{ template "harbor.jobserviceURL" . }}" + REGISTRY_URL: "{{ template "harbor.registryURL" . }}" + TOKEN_SERVICE_URL: "{{ template "harbor.tokenServiceURL" . }}" + CORE_LOCAL_URL: "{{ ternary "https://127.0.0.1:8443" "http://127.0.0.1:8080" .Values.internalTLS.enabled }}" + WITH_TRIVY: {{ .Values.trivy.enabled | quote }} + TRIVY_ADAPTER_URL: "{{ template "harbor.trivyAdapterURL" . }}" + REGISTRY_STORAGE_PROVIDER_NAME: "{{ .Values.persistence.imageChartStorage.type }}" + LOG_LEVEL: "{{ .Values.logLevel }}" + CONFIG_PATH: "/etc/core/app.conf" + CHART_CACHE_DRIVER: "redis" + _REDIS_URL_CORE: "{{ template "harbor.redis.urlForCore" . }}" + _REDIS_URL_REG: "{{ template "harbor.redis.urlForRegistry" . }}" + {{- if or (and (eq .Values.redis.type "internal") .Values.redis.internal.harborDatabaseIndex) (and (eq .Values.redis.type "external") .Values.redis.external.harborDatabaseIndex) }} + _REDIS_URL_HARBOR: "{{ template "harbor.redis.urlForHarbor" . }}" + {{- end }} + {{- if or (and (eq .Values.redis.type "internal") .Values.redis.internal.cacheLayerDatabaseIndex) (and (eq .Values.redis.type "external") .Values.redis.external.cacheLayerDatabaseIndex) }} + _REDIS_URL_CACHE_LAYER: "{{ template "harbor.redis.urlForCache" . }}" + {{- end }} + PORTAL_URL: "{{ template "harbor.portalURL" . }}" + REGISTRY_CONTROLLER_URL: "{{ template "harbor.registryControllerURL" . }}" + REGISTRY_CREDENTIAL_USERNAME: "{{ .Values.registry.credentials.username }}" + {{- if .Values.uaaSecretName }} + UAA_CA_ROOT: "/etc/core/auth-ca/auth-ca.crt" + {{- end }} + {{- if has "core" .Values.proxy.components }} + HTTP_PROXY: "{{ .Values.proxy.httpProxy }}" + HTTPS_PROXY: "{{ .Values.proxy.httpsProxy }}" + NO_PROXY: "{{ template "harbor.noProxy" . }}" + {{- end }} + PERMITTED_REGISTRY_TYPES_FOR_PROXY_CACHE: "docker-hub,harbor,azure-acr,ali-acr,aws-ecr,google-gcr,docker-registry,github-ghcr,jfrog-artifactory" + REPLICATION_ADAPTER_WHITELIST: "ali-acr,aws-ecr,azure-acr,docker-hub,docker-registry,github-ghcr,google-gcr,harbor,huawei-SWR,jfrog-artifactory,tencent-tcr,volcengine-cr" + {{- if .Values.metrics.enabled}} + METRIC_ENABLE: "true" + METRIC_PATH: "{{ .Values.metrics.core.path }}" + METRIC_PORT: "{{ .Values.metrics.core.port }}" + METRIC_NAMESPACE: harbor + METRIC_SUBSYSTEM: core + {{- end }} + + {{- if hasKey .Values.core "gcTimeWindowHours" }} + #make the GC time window configurable for testing + GC_TIME_WINDOW_HOURS: "{{ .Values.core.gcTimeWindowHours }}" + {{- end }} + {{- template "harbor.traceEnvsForCore" . }} + + {{- if .Values.core.artifactPullAsyncFlushDuration }} + ARTIFACT_PULL_ASYNC_FLUSH_DURATION: {{ .Values.core.artifactPullAsyncFlushDuration | quote }} + {{- end }} + + {{- if .Values.core.gdpr}} + {{- if .Values.core.gdpr.deleteUser}} + GDPR_DELETE_USER: "true" + {{- end }} + {{- if .Values.core.gdpr.auditLogsCompliant}} + GDPR_AUDIT_LOGS: "true" + {{- end }} + {{- end }} + + {{- if .Values.cache.enabled }} + CACHE_ENABLED: "true" + CACHE_EXPIRE_HOURS: "{{ .Values.cache.expireHours }}" + {{- end }} + + {{- if .Values.core.quotaUpdateProvider }} + QUOTA_UPDATE_PROVIDER: "{{ .Values.core.quotaUpdateProvider }}" + {{- end }} diff --git a/packages/system/harbor/charts/harbor/templates/core/core-dpl.yaml b/packages/system/harbor/charts/harbor/templates/core/core-dpl.yaml new file mode 100644 index 00000000..4705c5f6 --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/core/core-dpl.yaml @@ -0,0 +1,258 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "harbor.core" . }} + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} + component: core + app.kubernetes.io/component: core +spec: + replicas: {{ .Values.core.replicas }} + revisionHistoryLimit: {{ .Values.core.revisionHistoryLimit }} + selector: + matchLabels: +{{ include "harbor.matchLabels" . | indent 6 }} + component: core + template: + metadata: + labels: +{{ include "harbor.labels" . | indent 8 }} + component: core + app.kubernetes.io/component: core +{{- if .Values.core.podLabels }} +{{ toYaml .Values.core.podLabels | indent 8 }} +{{- end }} + annotations: + checksum/configmap: {{ include (print $.Template.BasePath "/core/core-cm.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/core/core-secret.yaml") . | sha256sum }} + checksum/secret-jobservice: {{ include (print $.Template.BasePath "/jobservice/jobservice-secrets.yaml") . | sha256sum }} +{{- if and .Values.internalTLS.enabled (eq .Values.internalTLS.certSource "auto") }} + checksum/tls: {{ include (print $.Template.BasePath "/internal/auto-tls.yaml") . | sha256sum }} +{{- else if and .Values.internalTLS.enabled (eq .Values.internalTLS.certSource "manual") }} + checksum/tls: {{ include (print $.Template.BasePath "/core/core-tls.yaml") . | sha256sum }} +{{- end }} +{{- if .Values.core.podAnnotations }} +{{ toYaml .Values.core.podAnnotations | indent 8 }} +{{- end }} + spec: + securityContext: + runAsUser: 10000 + fsGroup: 10000 +{{- if .Values.core.serviceAccountName }} + serviceAccountName: {{ .Values.core.serviceAccountName }} +{{- end -}} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + automountServiceAccountToken: {{ .Values.core.automountServiceAccountToken | default false }} + terminationGracePeriodSeconds: 120 +{{- with .Values.core.topologySpreadConstraints}} + topologySpreadConstraints: +{{- range . }} + - {{ . | toYaml | indent 8 | trim }} + labelSelector: + matchLabels: +{{ include "harbor.matchLabels" $ | indent 12 }} + component: core +{{- end }} +{{- end }} + {{- with .Values.core.initContainers }} + initContainers: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: core + image: {{ .Values.core.image.repository }}:{{ .Values.core.image.tag }} + imagePullPolicy: {{ .Values.imagePullPolicy }} + {{- if .Values.core.startupProbe.enabled }} + startupProbe: + httpGet: + path: /api/v2.0/ping + scheme: {{ include "harbor.component.scheme" . | upper }} + port: {{ template "harbor.core.containerPort" . }} + failureThreshold: 360 + initialDelaySeconds: {{ .Values.core.startupProbe.initialDelaySeconds }} + periodSeconds: 10 + {{- end }} + livenessProbe: + httpGet: + path: /api/v2.0/ping + scheme: {{ include "harbor.component.scheme" . | upper }} + port: {{ template "harbor.core.containerPort" . }} + failureThreshold: 2 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /api/v2.0/ping + scheme: {{ include "harbor.component.scheme" . | upper }} + port: {{ template "harbor.core.containerPort" . }} + failureThreshold: 2 + periodSeconds: 10 + envFrom: + - configMapRef: + name: "{{ template "harbor.core" . }}" + - secretRef: + name: "{{ template "harbor.core" . }}" + env: + - name: CORE_SECRET + valueFrom: + secretKeyRef: + name: {{ default (include "harbor.core" .) .Values.core.existingSecret }} + key: secret + - name: JOBSERVICE_SECRET + valueFrom: + secretKeyRef: + name: {{ default (include "harbor.jobservice" .) .Values.jobservice.existingSecret }} + {{- if .Values.jobservice.existingSecret }} + key: {{ .Values.jobservice.existingSecretKey }} + {{- else }} + key: JOBSERVICE_SECRET + {{- end }} + {{- if .Values.existingSecretAdminPassword }} + - name: HARBOR_ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.existingSecretAdminPassword }} + key: {{ .Values.existingSecretAdminPasswordKey }} + {{- end }} + {{- if .Values.internalTLS.enabled }} + - name: INTERNAL_TLS_ENABLED + value: "true" + - name: INTERNAL_TLS_KEY_PATH + value: /etc/harbor/ssl/core/tls.key + - name: INTERNAL_TLS_CERT_PATH + value: /etc/harbor/ssl/core/tls.crt + - name: INTERNAL_TLS_TRUST_CA_PATH + value: /etc/harbor/ssl/core/ca.crt + {{- end }} + {{- if .Values.database.external.existingSecret }} + - name: POSTGRESQL_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.database.external.existingSecret }} + key: password + {{- end }} + {{- if .Values.registry.credentials.existingSecret }} + - name: REGISTRY_CREDENTIAL_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.registry.credentials.existingSecret }} + key: REGISTRY_PASSWD + {{- end }} + {{- if .Values.core.existingXsrfSecret }} + - name: CSRF_KEY + valueFrom: + secretKeyRef: + name: {{ .Values.core.existingXsrfSecret }} + key: {{ .Values.core.existingXsrfSecretKey }} + {{- end }} +{{- with .Values.core.extraEnvVars }} +{{- toYaml . | nindent 10 }} +{{- end }} + {{- if not (empty .Values.containerSecurityContext) }} + securityContext: {{ .Values.containerSecurityContext | toYaml | nindent 10 }} + {{- end }} + ports: + - containerPort: {{ template "harbor.core.containerPort" . }} + volumeMounts: + - name: config + mountPath: /etc/core/app.conf + subPath: app.conf + - name: secret-key + mountPath: /etc/core/key + subPath: key + - name: token-service-private-key + mountPath: /etc/core/private_key.pem + subPath: tls.key + {{- if .Values.expose.tls.enabled }} + - name: ca-download + mountPath: /etc/core/ca + {{- end }} + {{- if .Values.uaaSecretName }} + - name: auth-ca-cert + mountPath: /etc/core/auth-ca/auth-ca.crt + subPath: auth-ca.crt + {{- end }} + {{- if .Values.internalTLS.enabled }} + - name: core-internal-certs + mountPath: /etc/harbor/ssl/core + {{- end }} + - name: psc + mountPath: /etc/core/token + {{- if .Values.caBundleSecretName }} +{{ include "harbor.caBundleVolumeMount" . | indent 8 }} + {{- end }} +{{- if .Values.core.resources }} + resources: +{{ toYaml .Values.core.resources | indent 10 }} +{{- end }} + volumes: + - name: config + configMap: + name: {{ template "harbor.core" . }} + items: + - key: app.conf + path: app.conf + - name: secret-key + secret: + {{- if .Values.existingSecretSecretKey }} + secretName: {{ .Values.existingSecretSecretKey }} + {{- else }} + secretName: {{ template "harbor.core" . }} + {{- end }} + items: + - key: secretKey + path: key + - name: token-service-private-key + secret: + {{- if .Values.core.secretName }} + secretName: {{ .Values.core.secretName }} + {{- else }} + secretName: {{ template "harbor.core" . }} + {{- end }} + {{- if .Values.expose.tls.enabled }} + - name: ca-download + secret: + {{- if .Values.caSecretName }} + secretName: {{ .Values.caSecretName }} + {{- else if eq (include "harbor.autoGenCertForIngress" .) "true" }} + secretName: "{{ template "harbor.ingress" . }}" + {{- else if eq (include "harbor.autoGenCertForNginx" .) "true" }} + secretName: {{ template "harbor.tlsSecretForNginx" . }} + {{- end }} + {{- end }} + {{- if .Values.uaaSecretName }} + - name: auth-ca-cert + secret: + secretName: {{ .Values.uaaSecretName }} + items: + - key: ca.crt + path: auth-ca.crt + {{- end }} + {{- if .Values.internalTLS.enabled }} + - name: core-internal-certs + secret: + secretName: {{ template "harbor.internalTLS.core.secretName" . }} + {{- end }} + - name: psc + emptyDir: {} + {{- if .Values.caBundleSecretName }} +{{ include "harbor.caBundleVolume" . | indent 6 }} + {{- end }} + {{- with .Values.core.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.core.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.core.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} + {{- if .Values.core.priorityClassName }} + priorityClassName: {{ .Values.core.priorityClassName }} + {{- end }} diff --git a/packages/system/harbor/charts/harbor/templates/core/core-pre-upgrade-job.yaml b/packages/system/harbor/charts/harbor/templates/core/core-pre-upgrade-job.yaml new file mode 100644 index 00000000..87271569 --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/core/core-pre-upgrade-job.yaml @@ -0,0 +1,78 @@ +{{- if .Values.enableMigrateHelmHook }} +apiVersion: batch/v1 +kind: Job +metadata: + name: migration-job + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} + component: migrator + annotations: + # This is what defines this resource as a hook. Without this line, the + # job is considered part of the release. + "helm.sh/hook": pre-upgrade + "helm.sh/hook-weight": "-5" +spec: + template: + metadata: + labels: +{{ include "harbor.matchLabels" . | indent 8 }} + component: migrator + spec: + restartPolicy: Never + securityContext: + runAsUser: 10000 + fsGroup: 10000 +{{- if .Values.core.serviceAccountName }} + serviceAccountName: {{ .Values.core.serviceAccountName }} +{{- end -}} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + terminationGracePeriodSeconds: 120 + containers: + - name: core-job + image: {{ .Values.core.image.repository }}:{{ .Values.core.image.tag }} + imagePullPolicy: {{ .Values.imagePullPolicy }} + command: ["/harbor/harbor_core", "-mode=migrate"] + envFrom: + - configMapRef: + name: "{{ template "harbor.core" . }}" + - secretRef: + name: "{{ template "harbor.core" . }}" + {{- if .Values.database.external.existingSecret }} + env: + - name: POSTGRESQL_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.database.external.existingSecret }} + key: password + {{- end }} + {{- if not (empty .Values.containerSecurityContext) }} + securityContext: {{ .Values.containerSecurityContext | toYaml | nindent 10 }} + {{- end }} + volumeMounts: + - name: config + mountPath: /etc/core/app.conf + subPath: app.conf + volumes: + - name: config + configMap: + name: {{ template "harbor.core" . }} + items: + - key: app.conf + path: app.conf + {{- with .Values.core.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.core.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.core.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} +{{- end }} diff --git a/packages/system/harbor/charts/harbor/templates/core/core-secret.yaml b/packages/system/harbor/charts/harbor/templates/core/core-secret.yaml new file mode 100644 index 00000000..ea9d4cfa --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/core/core-secret.yaml @@ -0,0 +1,37 @@ +{{- $existingSecret := lookup "v1" "Secret" .Release.Namespace (include "harbor.core" .) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "harbor.core" . }} + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +type: Opaque +data: + {{- if not .Values.existingSecretSecretKey }} + secretKey: {{ .Values.secretKey | b64enc | quote }} + {{- end }} + {{- if not .Values.core.existingSecret }} + secret: {{ .Values.core.secret | default (include "harbor.secretKeyHelper" (dict "key" "secret" "data" $existingSecret.data)) | default (randAlphaNum 16) | b64enc | quote }} + {{- end }} + {{- if not .Values.core.secretName }} + {{- $ca := genCA "harbor-token-ca" 365 }} + tls.key: {{ .Values.core.tokenKey | default $ca.Key | b64enc | quote }} + tls.crt: {{ .Values.core.tokenCert | default $ca.Cert | b64enc | quote }} + {{- end }} + {{- if not .Values.existingSecretAdminPassword }} + HARBOR_ADMIN_PASSWORD: {{ .Values.harborAdminPassword | b64enc | quote }} + {{- end }} + {{- if not .Values.database.external.existingSecret }} + POSTGRESQL_PASSWORD: {{ template "harbor.database.encryptedPassword" . }} + {{- end }} + {{- if not .Values.registry.credentials.existingSecret }} + REGISTRY_CREDENTIAL_PASSWORD: {{ .Values.registry.credentials.password | b64enc | quote }} + {{- end }} + {{- if not .Values.core.existingXsrfSecret }} + CSRF_KEY: {{ .Values.core.xsrfKey | default (include "harbor.secretKeyHelper" (dict "key" "CSRF_KEY" "data" $existingSecret.data)) | default (randAlphaNum 32) | b64enc | quote }} + {{- end }} +{{- if .Values.core.configureUserSettings }} + CONFIG_OVERWRITE_JSON: {{ .Values.core.configureUserSettings | b64enc | quote }} +{{- end }} + {{- template "harbor.traceJaegerPassword" . }} diff --git a/packages/system/harbor/charts/harbor/templates/core/core-svc.yaml b/packages/system/harbor/charts/harbor/templates/core/core-svc.yaml new file mode 100644 index 00000000..a1d2368d --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/core/core-svc.yaml @@ -0,0 +1,32 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ template "harbor.core" . }} + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +{{- with .Values.core.serviceAnnotations }} + annotations: + {{- toYaml . | nindent 4 }} +{{- end }} +spec: +{{- if or (eq .Values.expose.ingress.controller "gce") (eq .Values.expose.ingress.controller "alb") (eq .Values.expose.ingress.controller "f5-bigip") }} + type: NodePort +{{- end }} +{{- if .Values.ipFamily.policy }} + ipFamilyPolicy: {{ .Values.ipFamily.policy }} +{{- end }} +{{- if .Values.ipFamily.families }} + ipFamilies: {{ toYaml .Values.ipFamily.families | nindent 4 }} +{{- end }} + ports: + - name: {{ ternary "https-web" "http-web" .Values.internalTLS.enabled }} + port: {{ template "harbor.core.servicePort" . }} + targetPort: {{ template "harbor.core.containerPort" . }} +{{- if .Values.metrics.enabled}} + - name: {{ template "harbor.metricsPortName" . }} + port: {{ .Values.metrics.core.port }} +{{- end }} + selector: +{{ include "harbor.matchLabels" . | indent 4 }} + component: core diff --git a/packages/system/harbor/charts/harbor/templates/core/core-tls.yaml b/packages/system/harbor/charts/harbor/templates/core/core-tls.yaml new file mode 100644 index 00000000..d90d30c8 --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/core/core-tls.yaml @@ -0,0 +1,16 @@ +{{- if and .Values.internalTLS.enabled }} +{{- if eq .Values.internalTLS.certSource "manual" }} +apiVersion: v1 +kind: Secret +metadata: + name: "{{ template "harbor.internalTLS.core.secretName" . }}" + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +type: kubernetes.io/tls +data: + ca.crt: {{ (required "The \"internalTLS.trustCa\" is required!" .Values.internalTLS.trustCa) | b64enc | quote }} + tls.crt: {{ (required "The \"internalTLS.core.crt\" is required!" .Values.internalTLS.core.crt) | b64enc | quote }} + tls.key: {{ (required "The \"internalTLS.core.key\" is required!" .Values.internalTLS.core.key) | b64enc | quote }} +{{- end }} +{{- end }} diff --git a/packages/system/harbor/charts/harbor/templates/database/database-secret.yaml b/packages/system/harbor/charts/harbor/templates/database/database-secret.yaml new file mode 100644 index 00000000..0d07ec26 --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/database/database-secret.yaml @@ -0,0 +1,12 @@ +{{- if eq .Values.database.type "internal" -}} +apiVersion: v1 +kind: Secret +metadata: + name: "{{ template "harbor.database" . }}" + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +type: Opaque +data: + POSTGRES_PASSWORD: {{ template "harbor.database.encryptedPassword" . }} +{{- end -}} diff --git a/packages/system/harbor/charts/harbor/templates/database/database-ss.yaml b/packages/system/harbor/charts/harbor/templates/database/database-ss.yaml new file mode 100644 index 00000000..8bddd291 --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/database/database-ss.yaml @@ -0,0 +1,165 @@ +{{- if eq .Values.database.type "internal" -}} +{{- $database := .Values.persistence.persistentVolumeClaim.database -}} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: "{{ template "harbor.database" . }}" + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} + component: database + app.kubernetes.io/component: database +spec: + replicas: 1 + serviceName: "{{ template "harbor.database" . }}" + selector: + matchLabels: +{{ include "harbor.matchLabels" . | indent 6 }} + component: database + template: + metadata: + labels: +{{ include "harbor.labels" . | indent 8 }} + component: database + app.kubernetes.io/component: database +{{- if .Values.database.podLabels }} +{{ toYaml .Values.database.podLabels | indent 8 }} +{{- end }} + annotations: + checksum/secret: {{ include (print $.Template.BasePath "/database/database-secret.yaml") . | sha256sum }} +{{- if .Values.database.podAnnotations }} +{{ toYaml .Values.database.podAnnotations | indent 8 }} +{{- end }} + spec: + securityContext: + runAsUser: 999 + fsGroup: 999 +{{- if .Values.database.internal.serviceAccountName }} + serviceAccountName: {{ .Values.database.internal.serviceAccountName }} +{{- end -}} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + automountServiceAccountToken: {{ .Values.database.internal.automountServiceAccountToken | default false }} + terminationGracePeriodSeconds: 120 + initContainers: + # with "fsGroup" set, each time a volume is mounted, Kubernetes must recursively chown() and chmod() all the files and directories inside the volume + # this causes the postgresql reports the "data directory /var/lib/postgresql/data/pgdata has group or world access" issue when using some CSIs e.g. Ceph + # use this init container to correct the permission + # as "fsGroup" applied before the init container running, the container has enough permission to execute the command + - name: "data-permissions-ensurer" + image: {{ .Values.database.internal.image.repository }}:{{ .Values.database.internal.image.tag }} + imagePullPolicy: {{ .Values.imagePullPolicy }} + {{- if not (empty .Values.containerSecurityContext) }} + securityContext: {{ .Values.containerSecurityContext | toYaml | nindent 10 }} + {{- end }} + command: ["/bin/sh"] + args: ["-c", "chmod -R 700 /var/lib/postgresql/data/pgdata || true"] +{{- if .Values.database.internal.initContainer.permissions.resources }} + resources: +{{ toYaml .Values.database.internal.initContainer.permissions.resources | indent 10 }} +{{- end }} + volumeMounts: + - name: database-data + mountPath: /var/lib/postgresql/data + subPath: {{ $database.subPath }} + {{- with .Values.database.internal.extrInitContainers }} + {{- toYaml . | nindent 6 }} + {{- end }} + containers: + - name: database + image: {{ .Values.database.internal.image.repository }}:{{ .Values.database.internal.image.tag }} + imagePullPolicy: {{ .Values.imagePullPolicy }} + {{- if not (empty .Values.containerSecurityContext) }} + securityContext: {{ .Values.containerSecurityContext | toYaml | nindent 10 }} + {{- end }} + livenessProbe: + exec: + command: + - /docker-healthcheck.sh + initialDelaySeconds: 300 + periodSeconds: 10 + timeoutSeconds: {{ .Values.database.internal.livenessProbe.timeoutSeconds }} + readinessProbe: + exec: + command: + - /docker-healthcheck.sh + initialDelaySeconds: 1 + periodSeconds: 10 + timeoutSeconds: {{ .Values.database.internal.readinessProbe.timeoutSeconds }} +{{- if .Values.database.internal.resources }} + resources: +{{ toYaml .Values.database.internal.resources | indent 10 }} +{{- end }} + envFrom: + - secretRef: + name: "{{ template "harbor.database" . }}" + env: + # put the data into a sub directory to avoid the permission issue in k8s with restricted psp enabled + # more detail refer to https://github.com/goharbor/harbor-helm/issues/756 + - name: PGDATA + value: "/var/lib/postgresql/data/pgdata" +{{- with .Values.database.internal.extraEnvVars }} +{{- toYaml . | nindent 10 }} +{{- end }} + volumeMounts: + - name: database-data + mountPath: /var/lib/postgresql/data + subPath: {{ $database.subPath }} + - name: shm-volume + mountPath: /dev/shm + volumes: + - name: shm-volume + emptyDir: + medium: Memory + sizeLimit: {{ .Values.database.internal.shmSizeLimit }} + {{- if not .Values.persistence.enabled }} + - name: "database-data" + emptyDir: {} + {{- else if $database.existingClaim }} + - name: "database-data" + persistentVolumeClaim: + claimName: {{ $database.existingClaim }} + {{- end -}} + {{- with .Values.database.internal.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.database.internal.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.database.internal.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} + {{- if .Values.database.internal.priorityClassName }} + priorityClassName: {{ .Values.database.internal.priorityClassName }} + {{- end }} + {{- if and .Values.persistence.enabled (not $database.existingClaim) }} + volumeClaimTemplates: + - apiVersion: v1 + kind: PersistentVolumeClaim + metadata: + name: "database-data" + labels: +{{ include "harbor.legacy.labels" . | indent 8 }} + annotations: + {{- range $key, $value := $database.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} + spec: + accessModes: [{{ $database.accessMode | quote }}] + {{- if $database.storageClass }} + {{- if (eq "-" $database.storageClass) }} + storageClassName: "" + {{- else }} + storageClassName: "{{ $database.storageClass }}" + {{- end }} + {{- end }} + resources: + requests: + storage: {{ $database.size | quote }} + {{- end -}} + {{- end -}} diff --git a/packages/system/harbor/charts/harbor/templates/database/database-svc.yaml b/packages/system/harbor/charts/harbor/templates/database/database-svc.yaml new file mode 100644 index 00000000..aef4c6df --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/database/database-svc.yaml @@ -0,0 +1,21 @@ +{{- if eq .Values.database.type "internal" -}} +apiVersion: v1 +kind: Service +metadata: + name: "{{ template "harbor.database" . }}" + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +spec: +{{- if .Values.ipFamily.policy }} + ipFamilyPolicy: {{ .Values.ipFamily.policy }} +{{- end }} +{{- if .Values.ipFamily.families }} + ipFamilies: {{ toYaml .Values.ipFamily.families | nindent 4 }} +{{- end }} + ports: + - port: 5432 + selector: +{{ include "harbor.matchLabels" . | indent 4 }} + component: database +{{- end -}} diff --git a/packages/system/harbor/charts/harbor/templates/exporter/exporter-cm-env.yaml b/packages/system/harbor/charts/harbor/templates/exporter/exporter-cm-env.yaml new file mode 100644 index 00000000..3f911032 --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/exporter/exporter-cm-env.yaml @@ -0,0 +1,36 @@ +{{- if .Values.metrics.enabled}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: "{{ template "harbor.exporter" . }}-env" + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +data: + {{- if has "jobservice" .Values.proxy.components }} + HTTP_PROXY: "{{ .Values.proxy.httpProxy }}" + HTTPS_PROXY: "{{ .Values.proxy.httpsProxy }}" + NO_PROXY: "{{ template "harbor.noProxy" . }}" + {{- end }} + LOG_LEVEL: "{{ .Values.logLevel }}" + HARBOR_EXPORTER_PORT: "{{ .Values.metrics.exporter.port }}" + HARBOR_EXPORTER_METRICS_PATH: "{{ .Values.metrics.exporter.path }}" + HARBOR_EXPORTER_METRICS_ENABLED: "{{ .Values.metrics.enabled }}" + HARBOR_EXPORTER_CACHE_TIME: "{{ .Values.exporter.cacheDuration }}" + HARBOR_EXPORTER_CACHE_CLEAN_INTERVAL: "{{ .Values.exporter.cacheCleanInterval }}" + HARBOR_METRIC_NAMESPACE: harbor + HARBOR_METRIC_SUBSYSTEM: exporter + HARBOR_REDIS_URL: "{{ template "harbor.redis.urlForJobservice" . }}" + HARBOR_REDIS_NAMESPACE: harbor_job_service_namespace + HARBOR_REDIS_TIMEOUT: "3600" + HARBOR_SERVICE_SCHEME: "{{ template "harbor.component.scheme" . }}" + HARBOR_SERVICE_HOST: "{{ template "harbor.core" . }}" + HARBOR_SERVICE_PORT: "{{ template "harbor.core.servicePort" . }}" + HARBOR_DATABASE_HOST: "{{ template "harbor.database.host" . }}" + HARBOR_DATABASE_PORT: "{{ template "harbor.database.port" . }}" + HARBOR_DATABASE_USERNAME: "{{ template "harbor.database.username" . }}" + HARBOR_DATABASE_DBNAME: "{{ template "harbor.database.coreDatabase" . }}" + HARBOR_DATABASE_SSLMODE: "{{ template "harbor.database.sslmode" . }}" + HARBOR_DATABASE_MAX_IDLE_CONNS: "{{ .Values.database.maxIdleConns }}" + HARBOR_DATABASE_MAX_OPEN_CONNS: "{{ .Values.database.maxOpenConns }}" +{{- end}} diff --git a/packages/system/harbor/charts/harbor/templates/exporter/exporter-dpl.yaml b/packages/system/harbor/charts/harbor/templates/exporter/exporter-dpl.yaml new file mode 100644 index 00000000..30894a6d --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/exporter/exporter-dpl.yaml @@ -0,0 +1,146 @@ +{{- if .Values.metrics.enabled}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "harbor.exporter" . }} + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} + component: exporter + app.kubernetes.io/component: exporter +spec: + replicas: {{ .Values.exporter.replicas }} + revisionHistoryLimit: {{ .Values.exporter.revisionHistoryLimit }} + selector: + matchLabels: +{{ include "harbor.matchLabels" . | indent 6 }} + component: exporter + template: + metadata: + labels: +{{ include "harbor.labels" . | indent 8 }} + component: exporter + app.kubernetes.io/component: exporter +{{- if .Values.exporter.podLabels }} +{{ toYaml .Values.exporter.podLabels | indent 8 }} +{{- end }} + annotations: + checksum/configmap: {{ include (print $.Template.BasePath "/exporter/exporter-cm-env.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/exporter/exporter-secret.yaml") . | sha256sum }} +{{- if and .Values.internalTLS.enabled (eq .Values.internalTLS.certSource "auto") }} + checksum/tls: {{ include (print $.Template.BasePath "/internal/auto-tls.yaml") . | sha256sum }} +{{- else if and .Values.internalTLS.enabled (eq .Values.internalTLS.certSource "manual") }} + checksum/tls: {{ include (print $.Template.BasePath "/core/core-tls.yaml") . | sha256sum }} +{{- end }} +{{- if .Values.exporter.podAnnotations }} +{{ toYaml .Values.exporter.podAnnotations | indent 8 }} +{{- end }} + spec: + securityContext: + runAsUser: 10000 + fsGroup: 10000 +{{- if .Values.exporter.serviceAccountName }} + serviceAccountName: {{ .Values.exporter.serviceAccountName }} +{{- end -}} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + automountServiceAccountToken: {{ .Values.exporter.automountServiceAccountToken | default false }} +{{- with .Values.exporter.topologySpreadConstraints }} + topologySpreadConstraints: +{{- range . }} + - {{ . | toYaml | indent 8 | trim }} + labelSelector: + matchLabels: +{{ include "harbor.matchLabels" $ | indent 12 }} + component: exporter +{{- end }} +{{- end }} + containers: + - name: exporter + image: {{ .Values.exporter.image.repository }}:{{ .Values.exporter.image.tag }} + imagePullPolicy: {{ .Values.imagePullPolicy }} + livenessProbe: + httpGet: + path: / + port: {{ .Values.metrics.exporter.port }} + initialDelaySeconds: 300 + periodSeconds: 10 + readinessProbe: + httpGet: + path: / + port: {{ .Values.metrics.exporter.port }} + initialDelaySeconds: 30 + periodSeconds: 10 + args: ["-log-level", "{{ .Values.logLevel }}"] + envFrom: + - configMapRef: + name: "{{ template "harbor.exporter" . }}-env" + - secretRef: + name: "{{ template "harbor.exporter" . }}" + env: + {{- if .Values.database.external.existingSecret }} + - name: HARBOR_DATABASE_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.database.external.existingSecret }} + key: password + {{- end }} + {{- if .Values.existingSecretAdminPassword }} + - name: HARBOR_ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.existingSecretAdminPassword }} + key: {{ .Values.existingSecretAdminPasswordKey }} + {{- end }} + {{- with .Values.exporter.extraEnvVars }} + {{- toYaml . | nindent 8 }} + {{- end }} +{{- if .Values.exporter.resources }} + resources: +{{ toYaml .Values.exporter.resources | indent 10 }} +{{- end }} + {{- if not (empty .Values.containerSecurityContext) }} + securityContext: {{ .Values.containerSecurityContext | toYaml | nindent 10 }} + {{- end }} + ports: + - containerPort: {{ .Values.metrics.exporter.port }} + volumeMounts: + {{- if .Values.caBundleSecretName }} +{{ include "harbor.caBundleVolumeMount" . | indent 8 }} + {{- end }} + {{- if .Values.internalTLS.enabled }} + - name: core-internal-certs + mountPath: /etc/harbor/ssl/core + # There are some metric data are collectd from harbor core. + # When internal TLS is enabled, the Exporter need the CA file to collect these data. + {{- end }} + volumes: + - name: config + secret: + secretName: "{{ template "harbor.exporter" . }}" + {{- if .Values.internalTLS.enabled }} + - name: core-internal-certs + secret: + secretName: {{ template "harbor.internalTLS.core.secretName" . }} + {{- end }} + {{- if .Values.caBundleSecretName }} +{{ include "harbor.caBundleVolume" . | indent 6 }} + {{- end }} + {{- with .Values.exporter.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.exporter.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.exporter.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} + {{- if .Values.exporter.priorityClassName }} + priorityClassName: {{ .Values.exporter.priorityClassName }} + {{- end }} +{{ end }} diff --git a/packages/system/harbor/charts/harbor/templates/exporter/exporter-secret.yaml b/packages/system/harbor/charts/harbor/templates/exporter/exporter-secret.yaml new file mode 100644 index 00000000..02c74d03 --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/exporter/exporter-secret.yaml @@ -0,0 +1,17 @@ +{{- if .Values.metrics.enabled}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "harbor.exporter" . }} + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +type: Opaque +data: +{{- if not .Values.existingSecretAdminPassword }} + HARBOR_ADMIN_PASSWORD: {{ .Values.harborAdminPassword | b64enc | quote }} +{{- end }} +{{- if not .Values.database.external.existingSecret }} + HARBOR_DATABASE_PASSWORD: {{ template "harbor.database.encryptedPassword" . }} +{{- end }} +{{- end }} diff --git a/packages/system/harbor/charts/harbor/templates/exporter/exporter-svc.yaml b/packages/system/harbor/charts/harbor/templates/exporter/exporter-svc.yaml new file mode 100644 index 00000000..11306e27 --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/exporter/exporter-svc.yaml @@ -0,0 +1,22 @@ +{{- if .Values.metrics.enabled}} +apiVersion: v1 +kind: Service +metadata: + name: "{{ template "harbor.exporter" . }}" + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +spec: +{{- if .Values.ipFamily.policy }} + ipFamilyPolicy: {{ .Values.ipFamily.policy }} +{{- end }} +{{- if .Values.ipFamily.families }} + ipFamilies: {{ toYaml .Values.ipFamily.families | nindent 4 }} +{{- end }} + ports: + - name: {{ template "harbor.metricsPortName" . }} + port: {{ .Values.metrics.exporter.port }} + selector: +{{ include "harbor.matchLabels" . | indent 4 }} + component: exporter +{{ end }} diff --git a/packages/system/harbor/charts/harbor/templates/gateway-apis/route.yaml b/packages/system/harbor/charts/harbor/templates/gateway-apis/route.yaml new file mode 100644 index 00000000..0999a5ff --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/gateway-apis/route.yaml @@ -0,0 +1,55 @@ +{{- if eq .Values.expose.type "route" }} +{{- $route := .Values.expose.route -}} +{{- $_ := set . "path_type" "PathPrefix" -}} +{{- $_ := set . "portal_path" "/" -}} +{{- $_ := set . "api_path" "/api/" -}} +{{- $_ := set . "service_path" "/service/" -}} +{{- $_ := set . "v2_path" "/v2/" -}} +{{- $_ := set . "controller_path" "/c/" -}} +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: "{{ template "harbor.route" . }}" + namespace: {{ .Release.Namespace | quote }} +{{- if $route.labels }} + labels: +{{ include "harbor.labels" . | indent 4 }} +{{ toYaml $route.labels | indent 4 }} +{{- end }} +{{- if $route.annotations }} + annotations: +{{ toYaml $route.annotations | indent 4 }} +{{- end }} +spec: + parentRefs: + {{- toYaml $route.parentRefs | nindent 2 }} + hostnames: + {{- toYaml $route.hosts | nindent 2 }} + rules: + - matches: + - path: + type: {{ .path_type }} + value: {{ .api_path }} + - path: + type: {{ .path_type }} + value: {{ .service_path }} + - path: + type: {{ .path_type }} + value: {{ .v2_path }} + - path: + type: {{ .path_type }} + value: {{ .controller_path }} + backendRefs: + - name: {{ template "harbor.core" . }} + namespace: {{ .Release.Namespace | quote }} + port: {{ template "harbor.core.servicePort" . }} + - matches: + - path: + type: {{ .path_type }} + value: {{ .portal_path }} + backendRefs: + - name: {{ template "harbor.portal" . }} + namespace: {{ .Release.Namespace | quote }} + port: {{ template "harbor.portal.servicePort" . }} +{{- end }} diff --git a/packages/system/harbor/charts/harbor/templates/ingress/ingress.yaml b/packages/system/harbor/charts/harbor/templates/ingress/ingress.yaml new file mode 100644 index 00000000..06096b86 --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/ingress/ingress.yaml @@ -0,0 +1,132 @@ +{{- if eq .Values.expose.type "ingress" }} +{{- $ingress := .Values.expose.ingress -}} +{{- $tls := .Values.expose.tls -}} +{{- if eq .Values.expose.ingress.controller "gce" }} + {{- $_ := set . "path_type" "ImplementationSpecific" -}} + {{- $_ := set . "portal_path" "/*" -}} + {{- $_ := set . "api_path" "/api/*" -}} + {{- $_ := set . "service_path" "/service/*" -}} + {{- $_ := set . "v2_path" "/v2/*" -}} + {{- $_ := set . "controller_path" "/c/*" -}} +{{- else if eq .Values.expose.ingress.controller "ncp" }} + {{- $_ := set . "path_type" "Prefix" -}} + {{- $_ := set . "portal_path" "/.*" -}} + {{- $_ := set . "api_path" "/api/.*" -}} + {{- $_ := set . "service_path" "/service/.*" -}} + {{- $_ := set . "v2_path" "/v2/.*" -}} + {{- $_ := set . "controller_path" "/c/.*" -}} +{{- else }} + {{- $_ := set . "path_type" "Prefix" -}} + {{- $_ := set . "portal_path" "/" -}} + {{- $_ := set . "api_path" "/api/" -}} + {{- $_ := set . "service_path" "/service/" -}} + {{- $_ := set . "v2_path" "/v2/" -}} + {{- $_ := set . "controller_path" "/c/" -}} +{{- end }} + +--- +{{- if semverCompare "<1.14-0" (include "harbor.ingress.kubeVersion" .) }} +apiVersion: extensions/v1beta1 +{{- else if semverCompare "<1.19-0" (include "harbor.ingress.kubeVersion" .) }} +apiVersion: networking.k8s.io/v1beta1 +{{- else }} +apiVersion: networking.k8s.io/v1 +{{- end }} +kind: Ingress +metadata: + name: "{{ template "harbor.ingress" . }}" + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +{{- if $ingress.labels }} +{{ toYaml $ingress.labels | indent 4 }} +{{- end }} + annotations: +{{ toYaml $ingress.annotations | indent 4 }} +{{- if .Values.internalTLS.enabled }} + nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" +{{- end }} +{{- if eq .Values.expose.ingress.controller "ncp" }} + ncp/use-regex: "true" + {{- if $tls.enabled }} + ncp/http-redirect: "true" + {{- end }} +{{- end }} +spec: + {{- if $ingress.className }} + ingressClassName: {{ $ingress.className }} + {{- end }} + {{- if $tls.enabled }} + tls: + - secretName: {{ template "harbor.tlsCoreSecretForIngress" . }} + {{- if $ingress.hosts.core }} + hosts: + - {{ $ingress.hosts.core }} + {{- end }} + {{- end }} + rules: + - http: + paths: +{{- if semverCompare "<1.19-0" (include "harbor.ingress.kubeVersion" .) }} + - path: {{ .api_path }} + backend: + serviceName: {{ template "harbor.core" . }} + servicePort: {{ template "harbor.core.servicePort" . }} + - path: {{ .service_path }} + backend: + serviceName: {{ template "harbor.core" . }} + servicePort: {{ template "harbor.core.servicePort" . }} + - path: {{ .v2_path }} + backend: + serviceName: {{ template "harbor.core" . }} + servicePort: {{ template "harbor.core.servicePort" . }} + - path: {{ .controller_path }} + backend: + serviceName: {{ template "harbor.core" . }} + servicePort: {{ template "harbor.core.servicePort" . }} + - path: {{ .portal_path }} + backend: + serviceName: {{ template "harbor.portal" . }} + servicePort: {{ template "harbor.portal.servicePort" . }} +{{- else }} + - path: {{ .api_path }} + pathType: {{ .path_type }} + backend: + service: + name: {{ template "harbor.core" . }} + port: + number: {{ template "harbor.core.servicePort" . }} + - path: {{ .service_path }} + pathType: {{ .path_type }} + backend: + service: + name: {{ template "harbor.core" . }} + port: + number: {{ template "harbor.core.servicePort" . }} + - path: {{ .v2_path }} + pathType: {{ .path_type }} + backend: + service: + name: {{ template "harbor.core" . }} + port: + number: {{ template "harbor.core.servicePort" . }} + - path: {{ .controller_path }} + pathType: {{ .path_type }} + backend: + service: + name: {{ template "harbor.core" . }} + port: + number: {{ template "harbor.core.servicePort" . }} + - path: {{ .portal_path }} + pathType: {{ .path_type }} + backend: + service: + name: {{ template "harbor.portal" . }} + port: + number: {{ template "harbor.portal.servicePort" . }} +{{- end }} + {{- if $ingress.hosts.core }} + host: {{ $ingress.hosts.core }} + {{- end }} + +{{- end }} diff --git a/packages/system/harbor/charts/harbor/templates/ingress/secret.yaml b/packages/system/harbor/charts/harbor/templates/ingress/secret.yaml new file mode 100644 index 00000000..48343b0f --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/ingress/secret.yaml @@ -0,0 +1,18 @@ +{{- if eq .Values.expose.type "ingress" }} +{{- if eq (include "harbor.autoGenCertForIngress" .) "true" }} +{{- $ca := genCA "harbor-ca" 365 }} +{{- $cert := genSignedCert .Values.expose.ingress.hosts.core nil (list .Values.expose.ingress.hosts.core) 365 $ca }} +apiVersion: v1 +kind: Secret +metadata: + name: "{{ template "harbor.ingress" . }}" + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +type: kubernetes.io/tls +data: + tls.crt: {{ $cert.Cert | b64enc | quote }} + tls.key: {{ $cert.Key | b64enc | quote }} + ca.crt: {{ $ca.Cert | b64enc | quote }} +{{- end }} +{{- end }} diff --git a/packages/system/harbor/charts/harbor/templates/internal/auto-tls.yaml b/packages/system/harbor/charts/harbor/templates/internal/auto-tls.yaml new file mode 100644 index 00000000..32807cfd --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/internal/auto-tls.yaml @@ -0,0 +1,86 @@ +{{- if and .Values.internalTLS.enabled (eq .Values.internalTLS.certSource "auto") }} +{{- $ca := genCA "harbor-internal-ca" 365 }} +{{- $coreCN := (include "harbor.core" .) }} +{{- $coreCrt := genSignedCert $coreCN (list "127.0.0.1") (list "localhost" $coreCN) 365 $ca }} +{{- $jsCN := (include "harbor.jobservice" .) }} +{{- $jsCrt := genSignedCert $jsCN nil (list $jsCN) 365 $ca }} +{{- $regCN := (include "harbor.registry" .) }} +{{- $regCrt := genSignedCert $regCN nil (list $regCN) 365 $ca }} +{{- $portalCN := (include "harbor.portal" .) }} +{{- $portalCrt := genSignedCert $portalCN nil (list $portalCN) 365 $ca }} + +--- +apiVersion: v1 +kind: Secret +metadata: + name: "{{ template "harbor.internalTLS.core.secretName" . }}" + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +type: kubernetes.io/tls +data: + ca.crt: {{ $ca.Cert | b64enc | quote }} + tls.crt: {{ $coreCrt.Cert | b64enc | quote }} + tls.key: {{ $coreCrt.Key | b64enc | quote }} + +--- +apiVersion: v1 +kind: Secret +metadata: + name: "{{ template "harbor.internalTLS.jobservice.secretName" . }}" + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +type: kubernetes.io/tls +data: + ca.crt: {{ $ca.Cert | b64enc | quote }} + tls.crt: {{ $jsCrt.Cert | b64enc | quote }} + tls.key: {{ $jsCrt.Key | b64enc | quote }} + +--- +apiVersion: v1 +kind: Secret +metadata: + name: "{{ template "harbor.internalTLS.registry.secretName" . }}" + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +type: kubernetes.io/tls +data: + ca.crt: {{ $ca.Cert | b64enc | quote }} + tls.crt: {{ $regCrt.Cert | b64enc | quote }} + tls.key: {{ $regCrt.Key | b64enc | quote }} + +--- +apiVersion: v1 +kind: Secret +metadata: + name: "{{ template "harbor.internalTLS.portal.secretName" . }}" + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +type: kubernetes.io/tls +data: + ca.crt: {{ $ca.Cert | b64enc | quote }} + tls.crt: {{ $portalCrt.Cert | b64enc | quote }} + tls.key: {{ $portalCrt.Key | b64enc | quote }} + +{{- if and .Values.trivy.enabled}} +--- +{{- $trivyCN := (include "harbor.trivy" .) }} +{{- $trivyCrt := genSignedCert $trivyCN nil (list $trivyCN) 365 $ca }} +apiVersion: v1 +kind: Secret +metadata: + name: "{{ template "harbor.internalTLS.trivy.secretName" . }}" + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +type: kubernetes.io/tls +data: + ca.crt: {{ $ca.Cert | b64enc | quote }} + tls.crt: {{ $trivyCrt.Cert | b64enc | quote }} + tls.key: {{ $trivyCrt.Key | b64enc | quote }} +{{- end }} + +{{- end }} diff --git a/packages/system/harbor/charts/harbor/templates/jobservice/jobservice-cm-env.yaml b/packages/system/harbor/charts/harbor/templates/jobservice/jobservice-cm-env.yaml new file mode 100644 index 00000000..f1359131 --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/jobservice/jobservice-cm-env.yaml @@ -0,0 +1,37 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: "{{ template "harbor.jobservice" . }}-env" + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +data: + CORE_URL: "{{ template "harbor.coreURL" . }}" + TOKEN_SERVICE_URL: "{{ template "harbor.tokenServiceURL" . }}" + REGISTRY_URL: "{{ template "harbor.registryURL" . }}" + REGISTRY_CONTROLLER_URL: "{{ template "harbor.registryControllerURL" . }}" + REGISTRY_CREDENTIAL_USERNAME: "{{ .Values.registry.credentials.username }}" + + JOBSERVICE_WEBHOOK_JOB_MAX_RETRY: "{{ .Values.jobservice.notification.webhook_job_max_retry }}" + JOBSERVICE_WEBHOOK_JOB_HTTP_CLIENT_TIMEOUT: "{{ .Values.jobservice.notification.webhook_job_http_client_timeout }}" + + LOG_LEVEL: "{{ .Values.logLevel }}" + + {{- if has "jobservice" .Values.proxy.components }} + HTTP_PROXY: "{{ .Values.proxy.httpProxy }}" + HTTPS_PROXY: "{{ .Values.proxy.httpsProxy }}" + NO_PROXY: "{{ template "harbor.noProxy" . }}" + {{- end }} + {{- if .Values.metrics.enabled}} + METRIC_NAMESPACE: harbor + METRIC_SUBSYSTEM: jobservice + {{- end }} + {{- template "harbor.traceEnvsForJobservice" . }} + {{- if .Values.cache.enabled }} + _REDIS_URL_CORE: "{{ template "harbor.redis.urlForCore" . }}" + CACHE_ENABLED: "true" + CACHE_EXPIRE_HOURS: "{{ .Values.cache.expireHours }}" + {{- end }} + {{- if or (and (eq .Values.redis.type "internal") .Values.redis.internal.cacheLayerDatabaseIndex) (and (eq .Values.redis.type "external") .Values.redis.external.cacheLayerDatabaseIndex) }} + _REDIS_URL_CACHE_LAYER: "{{ template "harbor.redis.urlForCache" . }}" + {{- end }} diff --git a/packages/system/harbor/charts/harbor/templates/jobservice/jobservice-cm.yaml b/packages/system/harbor/charts/harbor/templates/jobservice/jobservice-cm.yaml new file mode 100644 index 00000000..c950e678 --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/jobservice/jobservice-cm.yaml @@ -0,0 +1,58 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: "{{ template "harbor.jobservice" . }}" + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +data: + config.yml: |+ + #Server listening port + protocol: "{{ template "harbor.component.scheme" . }}" + port: {{ template "harbor.jobservice.containerPort". }} + {{- if .Values.internalTLS.enabled }} + https_config: + cert: "/etc/harbor/ssl/jobservice/tls.crt" + key: "/etc/harbor/ssl/jobservice/tls.key" + {{- end }} + worker_pool: + workers: {{ .Values.jobservice.maxJobWorkers }} + backend: "redis" + redis_pool: + redis_url: "{{ template "harbor.redis.urlForJobservice" . }}" + namespace: "harbor_job_service_namespace" + idle_timeout_second: 3600 + job_loggers: + {{- if has "file" .Values.jobservice.jobLoggers }} + - name: "FILE" + level: {{ .Values.logLevel | upper }} + settings: # Customized settings of logger + base_dir: "/var/log/jobs" + sweeper: + duration: {{ .Values.jobservice.loggerSweeperDuration }} #days + settings: # Customized settings of sweeper + work_dir: "/var/log/jobs" + {{- end }} + {{- if has "database" .Values.jobservice.jobLoggers }} + - name: "DB" + level: {{ .Values.logLevel | upper }} + sweeper: + duration: {{ .Values.jobservice.loggerSweeperDuration }} #days + {{- end }} + {{- if has "stdout" .Values.jobservice.jobLoggers }} + - name: "STD_OUTPUT" + level: {{ .Values.logLevel | upper }} + {{- end }} + metric: + enabled: {{ .Values.metrics.enabled }} + path: {{ .Values.metrics.jobservice.path }} + port: {{ .Values.metrics.jobservice.port }} + #Loggers for the job service + loggers: + - name: "STD_OUTPUT" + level: {{ .Values.logLevel | upper }} + reaper: + # the max time to wait for a task to finish, if unfinished after max_update_hours, the task will be mark as error, but the task will continue to run, default value is 24 + max_update_hours: {{ .Values.jobservice.reaper.max_update_hours }} + # the max time for execution in running state without new task created + max_dangling_hours: {{ .Values.jobservice.reaper.max_dangling_hours }} diff --git a/packages/system/harbor/charts/harbor/templates/jobservice/jobservice-dpl.yaml b/packages/system/harbor/charts/harbor/templates/jobservice/jobservice-dpl.yaml new file mode 100644 index 00000000..3e426694 --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/jobservice/jobservice-dpl.yaml @@ -0,0 +1,183 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: "{{ template "harbor.jobservice" . }}" + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} + component: jobservice + app.kubernetes.io/component: jobservice +spec: + replicas: {{ .Values.jobservice.replicas }} + revisionHistoryLimit: {{ .Values.jobservice.revisionHistoryLimit }} + strategy: + type: {{ .Values.updateStrategy.type }} + {{- if eq .Values.updateStrategy.type "Recreate" }} + rollingUpdate: null + {{- end }} + selector: + matchLabels: +{{ include "harbor.matchLabels" . | indent 6 }} + component: jobservice + template: + metadata: + labels: +{{ include "harbor.labels" . | indent 8 }} + component: jobservice + app.kubernetes.io/component: jobservice +{{- if .Values.jobservice.podLabels }} +{{ toYaml .Values.jobservice.podLabels | indent 8 }} +{{- end }} + annotations: + checksum/configmap: {{ include (print $.Template.BasePath "/jobservice/jobservice-cm.yaml") . | sha256sum }} + checksum/configmap-env: {{ include (print $.Template.BasePath "/jobservice/jobservice-cm-env.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/jobservice/jobservice-secrets.yaml") . | sha256sum }} + checksum/secret-core: {{ include (print $.Template.BasePath "/core/core-secret.yaml") . | sha256sum }} +{{- if and .Values.internalTLS.enabled (eq .Values.internalTLS.certSource "auto") }} + checksum/tls: {{ include (print $.Template.BasePath "/internal/auto-tls.yaml") . | sha256sum }} +{{- else if and .Values.internalTLS.enabled (eq .Values.internalTLS.certSource "manual") }} + checksum/tls: {{ include (print $.Template.BasePath "/jobservice/jobservice-tls.yaml") . | sha256sum }} +{{- end }} +{{- if .Values.jobservice.podAnnotations }} +{{ toYaml .Values.jobservice.podAnnotations | indent 8 }} +{{- end }} + spec: + securityContext: + runAsUser: 10000 + fsGroup: 10000 +{{- if .Values.jobservice.serviceAccountName }} + serviceAccountName: {{ .Values.jobservice.serviceAccountName }} +{{- end -}} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + automountServiceAccountToken: {{ .Values.jobservice.automountServiceAccountToken | default false }} + terminationGracePeriodSeconds: 120 +{{- with .Values.jobservice.topologySpreadConstraints}} + topologySpreadConstraints: +{{- range . }} + - {{ . | toYaml | indent 8 | trim }} + labelSelector: + matchLabels: +{{ include "harbor.matchLabels" $ | indent 12 }} + component: jobservice +{{- end }} +{{- end }} + {{- with .Values.jobservice.initContainers }} + initContainers: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: jobservice + image: {{ .Values.jobservice.image.repository }}:{{ .Values.jobservice.image.tag }} + imagePullPolicy: {{ .Values.imagePullPolicy }} + livenessProbe: + httpGet: + path: /api/v1/stats + scheme: {{ include "harbor.component.scheme" . | upper }} + port: {{ template "harbor.jobservice.containerPort" . }} + initialDelaySeconds: 300 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /api/v1/stats + scheme: {{ include "harbor.component.scheme" . | upper }} + port: {{ template "harbor.jobservice.containerPort" . }} + initialDelaySeconds: 20 + periodSeconds: 10 +{{- if .Values.jobservice.resources }} + resources: +{{ toYaml .Values.jobservice.resources | indent 10 }} +{{- end }} + env: + - name: CORE_SECRET + valueFrom: + secretKeyRef: + name: {{ default (include "harbor.core" .) .Values.core.existingSecret }} + key: secret + {{- if .Values.jobservice.existingSecret }} + - name: JOBSERVICE_SECRET + valueFrom: + secretKeyRef: + name: {{ .Values.jobservice.existingSecret }} + key: {{ .Values.jobservice.existingSecretKey }} + {{- end }} + {{- if .Values.internalTLS.enabled }} + - name: INTERNAL_TLS_ENABLED + value: "true" + - name: INTERNAL_TLS_KEY_PATH + value: /etc/harbor/ssl/jobservice/tls.key + - name: INTERNAL_TLS_CERT_PATH + value: /etc/harbor/ssl/jobservice/tls.crt + - name: INTERNAL_TLS_TRUST_CA_PATH + value: /etc/harbor/ssl/jobservice/ca.crt + {{- end }} + {{- if .Values.registry.credentials.existingSecret }} + - name: REGISTRY_CREDENTIAL_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.registry.credentials.existingSecret }} + key: REGISTRY_PASSWD + {{- end }} +{{- with .Values.jobservice.extraEnvVars }} +{{- toYaml . | nindent 10 }} +{{- end }} + {{- if not (empty .Values.containerSecurityContext) }} + securityContext: {{ .Values.containerSecurityContext | toYaml | nindent 10 }} + {{- end }} + envFrom: + - configMapRef: + name: "{{ template "harbor.jobservice" . }}-env" + - secretRef: + name: "{{ template "harbor.jobservice" . }}" + ports: + - containerPort: {{ template "harbor.jobservice.containerPort" . }} + volumeMounts: + - name: jobservice-config + mountPath: /etc/jobservice/config.yml + subPath: config.yml + - name: job-logs + mountPath: /var/log/jobs + subPath: {{ .Values.persistence.persistentVolumeClaim.jobservice.jobLog.subPath }} + {{- if .Values.internalTLS.enabled }} + - name: jobservice-internal-certs + mountPath: /etc/harbor/ssl/jobservice + {{- end }} + {{- if .Values.caBundleSecretName }} +{{ include "harbor.caBundleVolumeMount" . | indent 8 }} + {{- end }} + volumes: + - name: jobservice-config + configMap: + name: "{{ template "harbor.jobservice" . }}" + - name: job-logs + {{- if and .Values.persistence.enabled (has "file" .Values.jobservice.jobLoggers) }} + persistentVolumeClaim: + claimName: {{ .Values.persistence.persistentVolumeClaim.jobservice.jobLog.existingClaim | default (include "harbor.jobservice" .) }} + {{- else }} + emptyDir: {} + {{- end }} + {{- if .Values.internalTLS.enabled }} + - name: jobservice-internal-certs + secret: + secretName: {{ template "harbor.internalTLS.jobservice.secretName" . }} + {{- end }} + {{- if .Values.caBundleSecretName }} +{{ include "harbor.caBundleVolume" . | indent 6 }} + {{- end }} + {{- with .Values.jobservice.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.jobservice.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.jobservice.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} + {{- if .Values.jobservice.priorityClassName }} + priorityClassName: {{ .Values.jobservice.priorityClassName }} + {{- end }} diff --git a/packages/system/harbor/charts/harbor/templates/jobservice/jobservice-pvc.yaml b/packages/system/harbor/charts/harbor/templates/jobservice/jobservice-pvc.yaml new file mode 100644 index 00000000..eb781eed --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/jobservice/jobservice-pvc.yaml @@ -0,0 +1,32 @@ +{{- $jobLog := .Values.persistence.persistentVolumeClaim.jobservice.jobLog -}} +{{- if and .Values.persistence.enabled (not $jobLog.existingClaim) (has "file" .Values.jobservice.jobLoggers) }} +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ template "harbor.jobservice" . }} + namespace: {{ .Release.Namespace | quote }} + annotations: + {{- range $key, $value := $jobLog.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} + {{- if eq .Values.persistence.resourcePolicy "keep" }} + helm.sh/resource-policy: keep + {{- end }} + labels: +{{ include "harbor.labels" . | indent 4 }} + component: jobservice + app.kubernetes.io/component: jobservice +spec: + accessModes: + - {{ $jobLog.accessMode }} + resources: + requests: + storage: {{ $jobLog.size }} + {{- if $jobLog.storageClass }} + {{- if eq "-" $jobLog.storageClass }} + storageClassName: "" + {{- else }} + storageClassName: {{ $jobLog.storageClass }} + {{- end }} + {{- end }} +{{- end }} diff --git a/packages/system/harbor/charts/harbor/templates/jobservice/jobservice-secrets.yaml b/packages/system/harbor/charts/harbor/templates/jobservice/jobservice-secrets.yaml new file mode 100644 index 00000000..7706c351 --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/jobservice/jobservice-secrets.yaml @@ -0,0 +1,17 @@ +{{- $existingSecret := lookup "v1" "Secret" .Release.Namespace (include "harbor.jobservice" .) }} +apiVersion: v1 +kind: Secret +metadata: + name: "{{ template "harbor.jobservice" . }}" + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +type: Opaque +data: + {{- if not .Values.jobservice.existingSecret }} + JOBSERVICE_SECRET: {{ .Values.jobservice.secret | default (include "harbor.secretKeyHelper" (dict "key" "JOBSERVICE_SECRET" "data" $existingSecret.data)) | default (randAlphaNum 16) | b64enc | quote }} + {{- end }} + {{- if not .Values.registry.credentials.existingSecret }} + REGISTRY_CREDENTIAL_PASSWORD: {{ .Values.registry.credentials.password | b64enc | quote }} + {{- end }} + {{- template "harbor.traceJaegerPassword" . }} diff --git a/packages/system/harbor/charts/harbor/templates/jobservice/jobservice-svc.yaml b/packages/system/harbor/charts/harbor/templates/jobservice/jobservice-svc.yaml new file mode 100644 index 00000000..9bddc43c --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/jobservice/jobservice-svc.yaml @@ -0,0 +1,25 @@ +apiVersion: v1 +kind: Service +metadata: + name: "{{ template "harbor.jobservice" . }}" + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +spec: +{{- if .Values.ipFamily.policy }} + ipFamilyPolicy: {{ .Values.ipFamily.policy }} +{{- end }} +{{- if .Values.ipFamily.families }} + ipFamilies: {{ toYaml .Values.ipFamily.families | nindent 4 }} +{{- end }} + ports: + - name: {{ ternary "https-jobservice" "http-jobservice" .Values.internalTLS.enabled }} + port: {{ template "harbor.jobservice.servicePort" . }} + targetPort: {{ template "harbor.jobservice.containerPort" . }} +{{- if .Values.metrics.enabled }} + - name: {{ template "harbor.metricsPortName" . }} + port: {{ .Values.metrics.jobservice.port }} +{{- end }} + selector: +{{ include "harbor.matchLabels" . | indent 4 }} + component: jobservice diff --git a/packages/system/harbor/charts/harbor/templates/jobservice/jobservice-tls.yaml b/packages/system/harbor/charts/harbor/templates/jobservice/jobservice-tls.yaml new file mode 100644 index 00000000..58809ec4 --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/jobservice/jobservice-tls.yaml @@ -0,0 +1,16 @@ +{{- if and .Values.internalTLS.enabled }} +{{- if eq .Values.internalTLS.certSource "manual" }} +apiVersion: v1 +kind: Secret +metadata: + name: "{{ template "harbor.internalTLS.jobservice.secretName" . }}" + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +type: kubernetes.io/tls +data: + ca.crt: {{ (required "The \"internalTLS.trustCa\" is required!" .Values.internalTLS.trustCa) | b64enc | quote }} + tls.crt: {{ (required "The \"internalTLS.jobservice.crt\" is required!" .Values.internalTLS.jobservice.crt) | b64enc | quote }} + tls.key: {{ (required "The \"internalTLS.jobservice.key\" is required!" .Values.internalTLS.jobservice.key) | b64enc | quote }} +{{- end }} +{{- end }} diff --git a/packages/system/harbor/charts/harbor/templates/metrics/metrics-svcmon.yaml b/packages/system/harbor/charts/harbor/templates/metrics/metrics-svcmon.yaml new file mode 100644 index 00000000..d566285e --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/metrics/metrics-svcmon.yaml @@ -0,0 +1,29 @@ +{{- if and .Values.metrics.enabled .Values.metrics.serviceMonitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ template "harbor.fullname" . }} + namespace: {{ .Release.Namespace | quote }} + labels: {{ include "harbor.labels" . | nindent 4 }} +{{- if .Values.metrics.serviceMonitor.additionalLabels }} +{{ toYaml .Values.metrics.serviceMonitor.additionalLabels | indent 4 }} +{{- end }} +spec: + jobLabel: app.kubernetes.io/name + endpoints: + - port: {{ template "harbor.metricsPortName" . }} + {{- if .Values.metrics.serviceMonitor.interval }} + interval: {{ .Values.metrics.serviceMonitor.interval }} + {{- end }} + honorLabels: true +{{- if .Values.metrics.serviceMonitor.metricRelabelings }} + metricRelabelings: +{{ tpl (toYaml .Values.metrics.serviceMonitor.metricRelabelings | indent 4) . }} +{{- end }} +{{- if .Values.metrics.serviceMonitor.relabelings }} + relabelings: +{{ toYaml .Values.metrics.serviceMonitor.relabelings | indent 4 }} +{{- end }} + selector: + matchLabels: {{ include "harbor.matchLabels" . | nindent 6 }} +{{- end }} diff --git a/packages/system/harbor/charts/harbor/templates/nginx/configmap-http.yaml b/packages/system/harbor/charts/harbor/templates/nginx/configmap-http.yaml new file mode 100644 index 00000000..78efa187 --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/nginx/configmap-http.yaml @@ -0,0 +1,136 @@ +{{- if and (ne .Values.expose.type "ingress") (ne .Values.expose.type "route") (not .Values.expose.tls.enabled) }} +{{- $scheme := (include "harbor.component.scheme" .) -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "harbor.nginx" . }} + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +data: + nginx.conf: |+ + worker_processes auto; + pid /tmp/nginx.pid; + + events { + worker_connections 3096; + use epoll; + multi_accept on; + } + + http { + client_body_temp_path /tmp/client_body_temp; + proxy_temp_path /tmp/proxy_temp; + fastcgi_temp_path /tmp/fastcgi_temp; + uwsgi_temp_path /tmp/uwsgi_temp; + scgi_temp_path /tmp/scgi_temp; + tcp_nodelay on; + + # this is necessary for us to be able to disable request buffering in all cases + proxy_http_version 1.1; + + upstream core { + server "{{ template "harbor.core" . }}:{{ template "harbor.core.servicePort" . }}"; + } + + upstream portal { + server {{ template "harbor.portal" . }}:{{ template "harbor.portal.servicePort" . }}; + } + + log_format timed_combined '[$time_local]:$remote_addr - ' + '"$request" $status $body_bytes_sent ' + '"$http_referer" "$http_user_agent" ' + '$request_time $upstream_response_time $pipe'; + + access_log /dev/stdout timed_combined; + + map $http_x_forwarded_proto $x_forwarded_proto { + default $http_x_forwarded_proto; + "" $scheme; + } + + server { + {{- if .Values.ipFamily.ipv4.enabled}} + listen 8080; + {{- end}} + {{- if .Values.ipFamily.ipv6.enabled }} + listen [::]:8080; + {{- end }} + server_tokens off; + # disable any limits to avoid HTTP 413 for large image uploads + client_max_body_size 0; + + # Add extra headers + add_header X-Frame-Options DENY; + add_header Content-Security-Policy "frame-ancestors 'none'"; + + location / { + proxy_pass {{ $scheme }}://portal/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $x_forwarded_proto; + + proxy_buffering off; + proxy_request_buffering off; + } + + location /api/ { + proxy_pass {{ $scheme }}://core/api/; + {{- if and .Values.internalTLS.enabled }} + proxy_ssl_verify off; + proxy_ssl_session_reuse on; + {{- end }} + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $x_forwarded_proto; + + proxy_buffering off; + proxy_request_buffering off; + } + + location /c/ { + proxy_pass {{ $scheme }}://core/c/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $x_forwarded_proto; + + proxy_buffering off; + proxy_request_buffering off; + } + + location /v1/ { + return 404; + } + + location /v2/ { + proxy_pass {{ $scheme }}://core/v2/; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $x_forwarded_proto; + proxy_buffering off; + proxy_request_buffering off; + proxy_send_timeout 900; + proxy_read_timeout 900; + } + + location /service/ { + proxy_pass {{ $scheme }}://core/service/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $x_forwarded_proto; + + proxy_buffering off; + proxy_request_buffering off; + } + + location /service/notifications { + return 404; + } + } + } +{{- end }} diff --git a/packages/system/harbor/charts/harbor/templates/nginx/configmap-https.yaml b/packages/system/harbor/charts/harbor/templates/nginx/configmap-https.yaml new file mode 100644 index 00000000..3a80e292 --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/nginx/configmap-https.yaml @@ -0,0 +1,173 @@ +{{- if and (ne .Values.expose.type "ingress") (ne .Values.expose.type "route") (.Values.expose.tls.enabled) }} +{{- $scheme := (include "harbor.component.scheme" .) -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "harbor.nginx" . }} + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +data: + nginx.conf: |+ + worker_processes auto; + pid /tmp/nginx.pid; + + events { + worker_connections 3096; + use epoll; + multi_accept on; + } + + http { + client_body_temp_path /tmp/client_body_temp; + proxy_temp_path /tmp/proxy_temp; + fastcgi_temp_path /tmp/fastcgi_temp; + uwsgi_temp_path /tmp/uwsgi_temp; + scgi_temp_path /tmp/scgi_temp; + tcp_nodelay on; + + # this is necessary for us to be able to disable request buffering in all cases + proxy_http_version 1.1; + + upstream core { + server "{{ template "harbor.core" . }}:{{ template "harbor.core.servicePort" . }}"; + } + + upstream portal { + server "{{ template "harbor.portal" . }}:{{ template "harbor.portal.servicePort" . }}"; + } + + log_format timed_combined '[$time_local]:$remote_addr - ' + '"$request" $status $body_bytes_sent ' + '"$http_referer" "$http_user_agent" ' + '$request_time $upstream_response_time $pipe'; + + access_log /dev/stdout timed_combined; + + map $http_x_forwarded_proto $x_forwarded_proto { + default $http_x_forwarded_proto; + "" $scheme; + } + + server { + {{- if .Values.ipFamily.ipv4.enabled }} + listen 8443 ssl; + {{- end}} + {{- if .Values.ipFamily.ipv6.enabled }} + listen [::]:8443 ssl; + {{- end }} + # server_name harbordomain.com; + server_tokens off; + # SSL + ssl_certificate /etc/nginx/cert/tls.crt; + ssl_certificate_key /etc/nginx/cert/tls.key; + + # Recommendations from https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html + ssl_protocols TLSv1.2 TLSv1.3; + {{- if .Values.internalTLS.strong_ssl_ciphers }} + ssl_ciphers ECDHE+AESGCM:DHE+AESGCM:ECDHE+RSA+SHA256:DHE+RSA+SHA256:!AES128; + {{ else }} + ssl_ciphers '!aNULL:kECDH+AESGCM:ECDH+AESGCM:RSA+AESGCM:kECDH+AES:ECDH+AES:RSA+AES:'; + {{- end }} + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + + # disable any limits to avoid HTTP 413 for large image uploads + client_max_body_size 0; + + # required to avoid HTTP 411: see Issue #1486 (https://github.com/docker/docker/issues/1486) + chunked_transfer_encoding on; + + # Add extra headers + add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload"; + add_header X-Frame-Options DENY; + add_header Content-Security-Policy "frame-ancestors 'none'"; + + location / { + proxy_pass {{ $scheme }}://portal/; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $x_forwarded_proto; + + proxy_cookie_path / "/; HttpOnly; Secure"; + + proxy_buffering off; + proxy_request_buffering off; + } + + location /api/ { + proxy_pass {{ $scheme }}://core/api/; + {{- if and .Values.internalTLS.enabled }} + proxy_ssl_verify off; + proxy_ssl_session_reuse on; + {{- end }} + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $x_forwarded_proto; + + proxy_cookie_path / "/; Secure"; + + proxy_buffering off; + proxy_request_buffering off; + } + + location /c/ { + proxy_pass {{ $scheme }}://core/c/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $x_forwarded_proto; + + proxy_cookie_path / "/; Secure"; + + proxy_buffering off; + proxy_request_buffering off; + } + + location /v1/ { + return 404; + } + + location /v2/ { + proxy_pass {{ $scheme }}://core/v2/; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $x_forwarded_proto; + proxy_buffering off; + proxy_request_buffering off; + proxy_send_timeout 900; + proxy_read_timeout 900; + } + + location /service/ { + proxy_pass {{ $scheme }}://core/service/; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $x_forwarded_proto; + + proxy_cookie_path / "/; Secure"; + + proxy_buffering off; + proxy_request_buffering off; + } + + location /service/notifications { + return 404; + } + } + server { + {{- if .Values.ipFamily.ipv4.enabled }} + listen 8080; + {{- end}} + {{- if .Values.ipFamily.ipv6.enabled }} + listen [::]:8080; + {{- end}} + #server_name harbordomain.com; + return 301 https://$host$request_uri; + } + } +{{- end }} diff --git a/packages/system/harbor/charts/harbor/templates/nginx/deployment.yaml b/packages/system/harbor/charts/harbor/templates/nginx/deployment.yaml new file mode 100644 index 00000000..0c8bc40f --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/nginx/deployment.yaml @@ -0,0 +1,133 @@ +{{- if and (ne .Values.expose.type "ingress") (ne .Values.expose.type "route") }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "harbor.nginx" . }} + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} + component: nginx + app.kubernetes.io/component: nginx +spec: + replicas: {{ .Values.nginx.replicas }} + revisionHistoryLimit: {{ .Values.nginx.revisionHistoryLimit }} + selector: + matchLabels: +{{ include "harbor.matchLabels" . | indent 6 }} + component: nginx + template: + metadata: + labels: +{{ include "harbor.labels" . | indent 8 }} + component: nginx + app.kubernetes.io/component: nginx +{{- if .Values.nginx.podLabels }} +{{ toYaml .Values.nginx.podLabels | indent 8 }} +{{- end }} + annotations: + {{- if not .Values.expose.tls.enabled }} + checksum/configmap: {{ include (print $.Template.BasePath "/nginx/configmap-http.yaml") . | sha256sum }} + {{- else }} + checksum/configmap: {{ include (print $.Template.BasePath "/nginx/configmap-https.yaml") . | sha256sum }} + {{- end }} + {{- if eq (include "harbor.autoGenCertForNginx" .) "true" }} + checksum/secret: {{ include (print $.Template.BasePath "/nginx/secret.yaml") . | sha256sum }} + {{- end }} +{{- if .Values.nginx.podAnnotations }} +{{ toYaml .Values.nginx.podAnnotations | indent 8 }} +{{- end }} + spec: +{{- if .Values.nginx.serviceAccountName }} + serviceAccountName: {{ .Values.nginx.serviceAccountName }} +{{- end }} + securityContext: + runAsUser: 10000 + fsGroup: 10000 + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + automountServiceAccountToken: {{ .Values.nginx.automountServiceAccountToken | default false }} +{{- with .Values.nginx.topologySpreadConstraints}} + topologySpreadConstraints: +{{- range . }} + - {{ . | toYaml | indent 8 | trim }} + labelSelector: + matchLabels: +{{ include "harbor.matchLabels" $ | indent 12 }} + component: nginx +{{- end }} +{{- end }} + containers: + - name: nginx + image: "{{ .Values.nginx.image.repository }}:{{ .Values.nginx.image.tag }}" + imagePullPolicy: "{{ .Values.imagePullPolicy }}" + {{- $_ := set . "scheme" "HTTP" -}} + {{- $_ := set . "port" "8080" -}} + {{- if .Values.expose.tls.enabled }} + {{- $_ := set . "scheme" "HTTPS" -}} + {{- $_ := set . "port" "8443" -}} + {{- end }} + livenessProbe: + httpGet: + scheme: {{ .scheme }} + path: / + port: {{ .port }} + initialDelaySeconds: 300 + periodSeconds: 10 + readinessProbe: + httpGet: + scheme: {{ .scheme }} + path: / + port: {{ .port }} + initialDelaySeconds: 1 + periodSeconds: 10 +{{- if .Values.nginx.resources }} + resources: +{{ toYaml .Values.nginx.resources | indent 10 }} +{{- end }} +{{- with .Values.nginx.extraEnvVars }} + env: +{{- toYaml . | nindent 10 }} +{{- end }} + {{- if not (empty .Values.containerSecurityContext) }} + securityContext: {{ .Values.containerSecurityContext | toYaml | nindent 10 }} + {{- end }} + ports: + - containerPort: 8080 + {{- if .Values.expose.tls.enabled }} + - containerPort: 8443 + {{- end }} + volumeMounts: + - name: config + mountPath: /etc/nginx/nginx.conf + subPath: nginx.conf + {{- if .Values.expose.tls.enabled }} + - name: certificate + mountPath: /etc/nginx/cert + {{- end }} + volumes: + - name: config + configMap: + name: {{ template "harbor.nginx" . }} + {{- if .Values.expose.tls.enabled }} + - name: certificate + secret: + secretName: {{ template "harbor.tlsSecretForNginx" . }} + {{- end }} + {{- with .Values.nginx.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.nginx.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.nginx.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} + {{- if .Values.nginx.priorityClassName }} + priorityClassName: {{ .Values.nginx.priorityClassName }} + {{- end }} +{{- end }} diff --git a/packages/system/harbor/charts/harbor/templates/nginx/secret.yaml b/packages/system/harbor/charts/harbor/templates/nginx/secret.yaml new file mode 100644 index 00000000..b855e7e9 --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/nginx/secret.yaml @@ -0,0 +1,26 @@ +{{- if and (ne .Values.expose.type "ingress") (ne .Values.expose.type "route") (.Values.expose.tls.enabled) }} +{{- if eq (include "harbor.autoGenCertForNginx" .) "true" }} +{{- $ca := genCA "harbor-ca" 365 }} +{{- $cn := (required "The \"expose.tls.auto.commonName\" is required!" .Values.expose.tls.auto.commonName) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "harbor.nginx" . }} + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +type: Opaque +data: + {{- if regexMatch `^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$` $cn }} + {{- $cert := genSignedCert $cn (list $cn) nil 365 $ca }} + tls.crt: {{ $cert.Cert | b64enc | quote }} + tls.key: {{ $cert.Key | b64enc | quote }} + ca.crt: {{ $ca.Cert | b64enc | quote }} + {{- else }} + {{- $cert := genSignedCert $cn nil (list $cn) 365 $ca }} + tls.crt: {{ $cert.Cert | b64enc | quote }} + tls.key: {{ $cert.Key | b64enc | quote }} + ca.crt: {{ $ca.Cert | b64enc | quote }} + {{- end }} +{{- end }} +{{- end }} diff --git a/packages/system/harbor/charts/harbor/templates/nginx/service.yaml b/packages/system/harbor/charts/harbor/templates/nginx/service.yaml new file mode 100644 index 00000000..9554cd43 --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/nginx/service.yaml @@ -0,0 +1,101 @@ +{{- if or (eq .Values.expose.type "clusterIP") (eq .Values.expose.type "nodePort") (eq .Values.expose.type "loadBalancer") }} +apiVersion: v1 +kind: Service +metadata: +{{- if eq .Values.expose.type "clusterIP" }} +{{- $clusterIP := .Values.expose.clusterIP }} + name: {{ $clusterIP.name }} + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +{{- if .Values.expose.clusterIP.labels }} +{{ toYaml $clusterIP.labels | indent 4 }} +{{- end }} +{{- with $clusterIP.annotations }} + annotations: + {{- toYaml . | nindent 4 }} +{{- end }} +spec: + type: ClusterIP + {{- if .Values.expose.clusterIP.staticClusterIP }} + clusterIP: {{ .Values.expose.clusterIP.staticClusterIP }} + {{- end }} + {{- if .Values.ipFamily.policy }} + ipFamilyPolicy: {{ .Values.ipFamily.policy }} + {{- end }} + {{- if .Values.ipFamily.families }} + ipFamilies: {{ toYaml .Values.ipFamily.families }} + {{- end }} + ports: + - name: http + port: {{ $clusterIP.ports.httpPort }} + targetPort: 8080 + {{- if .Values.expose.tls.enabled }} + - name: https + port: {{ $clusterIP.ports.httpsPort }} + targetPort: 8443 + {{- end }} +{{- else if eq .Values.expose.type "nodePort" }} +{{- $nodePort := .Values.expose.nodePort }} + name: {{ $nodePort.name }} + labels: +{{ include "harbor.labels" . | indent 4 }} +{{- if .Values.expose.nodePort.labels }} +{{ toYaml $nodePort.labels | indent 4 }} +{{- end }} +{{- with $nodePort.annotations }} + annotations: + {{- toYaml . | nindent 4 }} +{{- end }} +spec: + type: NodePort + ports: + - name: http + port: {{ $nodePort.ports.http.port }} + targetPort: 8080 + {{- if $nodePort.ports.http.nodePort }} + nodePort: {{ $nodePort.ports.http.nodePort }} + {{- end }} + {{- if .Values.expose.tls.enabled }} + - name: https + port: {{ $nodePort.ports.https.port }} + targetPort: 8443 + {{- if $nodePort.ports.https.nodePort }} + nodePort: {{ $nodePort.ports.https.nodePort }} + {{- end }} + {{- end }} +{{- else if eq .Values.expose.type "loadBalancer" }} +{{- $loadBalancer := .Values.expose.loadBalancer }} + name: {{ $loadBalancer.name }} + labels: +{{ include "harbor.labels" . | indent 4 }} +{{- if .Values.expose.loadBalancer.labels }} +{{ toYaml $loadBalancer.labels | indent 4 }} +{{- end }} +{{- with $loadBalancer.annotations }} + annotations: + {{- toYaml . | nindent 4 }} +{{- end }} +spec: + type: LoadBalancer + {{- with $loadBalancer.sourceRanges }} + loadBalancerSourceRanges: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- if $loadBalancer.IP }} + loadBalancerIP: {{ $loadBalancer.IP }} + {{- end }} + ports: + - name: http + port: {{ $loadBalancer.ports.httpPort }} + targetPort: 8080 + {{- if .Values.expose.tls.enabled }} + - name: https + port: {{ $loadBalancer.ports.httpsPort }} + targetPort: 8443 + {{- end }} +{{- end }} + selector: +{{ include "harbor.matchLabels" . | indent 4 }} + component: nginx +{{- end }} diff --git a/packages/system/harbor/charts/harbor/templates/portal/configmap.yaml b/packages/system/harbor/charts/harbor/templates/portal/configmap.yaml new file mode 100644 index 00000000..af56783a --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/portal/configmap.yaml @@ -0,0 +1,68 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: "{{ template "harbor.portal" . }}" + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +data: + nginx.conf: |+ + worker_processes auto; + pid /tmp/nginx.pid; + events { + worker_connections 1024; + } + http { + client_body_temp_path /tmp/client_body_temp; + proxy_temp_path /tmp/proxy_temp; + fastcgi_temp_path /tmp/fastcgi_temp; + uwsgi_temp_path /tmp/uwsgi_temp; + scgi_temp_path /tmp/scgi_temp; + server { + {{- if .Values.internalTLS.enabled }} + {{- if .Values.ipFamily.ipv4.enabled}} + listen {{ template "harbor.portal.containerPort" . }} ssl; + {{- end }} + {{- if .Values.ipFamily.ipv6.enabled}} + listen [::]:{{ template "harbor.portal.containerPort" . }} ssl; + {{- end }} + # SSL + ssl_certificate /etc/harbor/ssl/portal/tls.crt; + ssl_certificate_key /etc/harbor/ssl/portal/tls.key; + + # Recommendations from https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html + ssl_protocols TLSv1.2 TLSv1.3; + {{- if .Values.internalTLS.strong_ssl_ciphers }} + ssl_ciphers ECDHE+AESGCM:DHE+AESGCM:ECDHE+RSA+SHA256:DHE+RSA+SHA256:!AES128; + {{ else }} + ssl_ciphers '!aNULL:kECDH+AESGCM:ECDH+AESGCM:RSA+AESGCM:kECDH+AES:ECDH+AES:RSA+AES:'; + {{- end }} + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + {{- else }} + {{- if .Values.ipFamily.ipv4.enabled }} + listen {{ template "harbor.portal.containerPort" . }}; + {{- end }} + {{- if .Values.ipFamily.ipv6.enabled}} + listen [::]:{{ template "harbor.portal.containerPort" . }}; + {{- end }} + {{- end }} + server_name localhost; + root /usr/share/nginx/html; + index index.html index.htm; + include /etc/nginx/mime.types; + gzip on; + gzip_min_length 1000; + gzip_proxied expired no-cache no-store private auth; + gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; + location /devcenter-api-2.0 { + try_files $uri $uri/ /swagger-ui-index.html; + } + location / { + try_files $uri $uri/ /index.html; + } + location = /index.html { + add_header Cache-Control "no-store, no-cache, must-revalidate"; + } + } + } diff --git a/packages/system/harbor/charts/harbor/templates/portal/deployment.yaml b/packages/system/harbor/charts/harbor/templates/portal/deployment.yaml new file mode 100644 index 00000000..88bcd497 --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/portal/deployment.yaml @@ -0,0 +1,124 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: "{{ template "harbor.portal" . }}" + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} + component: portal + app.kubernetes.io/component: portal +spec: + replicas: {{ .Values.portal.replicas }} + revisionHistoryLimit: {{ .Values.portal.revisionHistoryLimit }} + selector: + matchLabels: +{{ include "harbor.matchLabels" . | indent 6 }} + component: portal + template: + metadata: + labels: +{{ include "harbor.labels" . | indent 8 }} + component: portal + app.kubernetes.io/component: portal +{{- if .Values.portal.podLabels }} +{{ toYaml .Values.portal.podLabels | indent 8 }} +{{- end }} + annotations: +{{- if and .Values.internalTLS.enabled (eq .Values.internalTLS.certSource "auto") }} + checksum/tls: {{ include (print $.Template.BasePath "/internal/auto-tls.yaml") . | sha256sum }} +{{- else if and .Values.internalTLS.enabled (eq .Values.internalTLS.certSource "manual") }} + checksum/tls: {{ include (print $.Template.BasePath "/portal/tls.yaml") . | sha256sum }} +{{- end }} + checksum/configmap: {{ include (print $.Template.BasePath "/portal/configmap.yaml") . | sha256sum }} +{{- if .Values.portal.podAnnotations }} +{{ toYaml .Values.portal.podAnnotations | indent 8 }} +{{- end }} + spec: + securityContext: + runAsUser: 10000 + fsGroup: 10000 + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- if .Values.portal.serviceAccountName }} + serviceAccountName: {{ .Values.portal.serviceAccountName }} +{{- end }} + automountServiceAccountToken: {{ .Values.portal.automountServiceAccountToken | default false }} +{{- with .Values.portal.topologySpreadConstraints}} + topologySpreadConstraints: +{{- range . }} + - {{ . | toYaml | indent 8 | trim }} + labelSelector: + matchLabels: +{{ include "harbor.matchLabels" $ | indent 12 }} + component: portal +{{- end }} +{{- end }} + {{- with .Values.portal.initContainers }} + initContainers: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: portal + image: {{ .Values.portal.image.repository }}:{{ .Values.portal.image.tag }} + imagePullPolicy: {{ .Values.imagePullPolicy }} +{{- if .Values.portal.resources }} + resources: +{{ toYaml .Values.portal.resources | indent 10 }} +{{- end }} +{{- with .Values.portal.extraEnvVars }} + env: +{{- toYaml . | nindent 10 }} +{{- end }} + {{- if not (empty .Values.containerSecurityContext) }} + securityContext: {{ .Values.containerSecurityContext | toYaml | nindent 10 }} + {{- end }} + livenessProbe: + httpGet: + path: / + scheme: {{ include "harbor.component.scheme" . | upper }} + port: {{ template "harbor.portal.containerPort" . }} + initialDelaySeconds: 300 + periodSeconds: 10 + readinessProbe: + httpGet: + path: / + scheme: {{ include "harbor.component.scheme" . | upper }} + port: {{ template "harbor.portal.containerPort" . }} + initialDelaySeconds: 1 + periodSeconds: 10 + ports: + - containerPort: {{ template "harbor.portal.containerPort" . }} + volumeMounts: + - name: portal-config + mountPath: /etc/nginx/nginx.conf + subPath: nginx.conf + {{- if .Values.internalTLS.enabled }} + - name: portal-internal-certs + mountPath: /etc/harbor/ssl/portal + {{- end }} + volumes: + - name: portal-config + configMap: + name: "{{ template "harbor.portal" . }}" + {{- if .Values.internalTLS.enabled }} + - name: portal-internal-certs + secret: + secretName: {{ template "harbor.internalTLS.portal.secretName" . }} + {{- end }} + {{- with .Values.portal.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.portal.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.portal.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} + {{- if .Values.portal.priorityClassName }} + priorityClassName: {{ .Values.portal.priorityClassName }} + {{- end }} diff --git a/packages/system/harbor/charts/harbor/templates/portal/service.yaml b/packages/system/harbor/charts/harbor/templates/portal/service.yaml new file mode 100644 index 00000000..2ce2482b --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/portal/service.yaml @@ -0,0 +1,27 @@ +apiVersion: v1 +kind: Service +metadata: + name: "{{ template "harbor.portal" . }}" + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +{{- with .Values.portal.serviceAnnotations }} + annotations: + {{- toYaml . | nindent 4 }} +{{- end }} +spec: +{{- if or (eq .Values.expose.ingress.controller "gce") (eq .Values.expose.ingress.controller "alb") (eq .Values.expose.ingress.controller "f5-bigip") }} + type: NodePort +{{- end }} +{{- if .Values.ipFamily.policy }} + ipFamilyPolicy: {{ .Values.ipFamily.policy }} +{{- end }} +{{- if .Values.ipFamily.families }} + ipFamilies: {{ toYaml .Values.ipFamily.families | nindent 4 }} +{{- end }} + ports: + - port: {{ template "harbor.portal.servicePort" . }} + targetPort: {{ template "harbor.portal.containerPort" . }} + selector: +{{ include "harbor.matchLabels" . | indent 4 }} + component: portal diff --git a/packages/system/harbor/charts/harbor/templates/portal/tls.yaml b/packages/system/harbor/charts/harbor/templates/portal/tls.yaml new file mode 100644 index 00000000..e61a7d3a --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/portal/tls.yaml @@ -0,0 +1,16 @@ +{{- if and .Values.internalTLS.enabled }} +{{- if eq .Values.internalTLS.certSource "manual" }} +apiVersion: v1 +kind: Secret +metadata: + name: "{{ template "harbor.internalTLS.portal.secretName" . }}" + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +type: kubernetes.io/tls +data: + ca.crt: {{ (required "The \"internalTLS.trustCa\" is required!" .Values.internalTLS.trustCa) | b64enc | quote }} + tls.crt: {{ (required "The \"internalTLS.portal.crt\" is required!" .Values.internalTLS.portal.crt) | b64enc | quote }} + tls.key: {{ (required "The \"internalTLS.portal.key\" is required!" .Values.internalTLS.portal.key) | b64enc | quote }} +{{- end }} +{{- end }} diff --git a/packages/system/harbor/charts/harbor/templates/redis/service.yaml b/packages/system/harbor/charts/harbor/templates/redis/service.yaml new file mode 100644 index 00000000..8bb8e85b --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/redis/service.yaml @@ -0,0 +1,21 @@ +{{- if eq .Values.redis.type "internal" -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "harbor.redis" . }} + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +spec: + {{- if .Values.ipFamily.policy }} + ipFamilyPolicy: {{ .Values.ipFamily.policy }} + {{- end }} + {{- if .Values.ipFamily.families }} + ipFamilies: {{ toYaml .Values.ipFamily.families }} + {{- end }} + ports: + - port: 6379 + selector: +{{ include "harbor.matchLabels" . | indent 4 }} + component: redis +{{- end -}} diff --git a/packages/system/harbor/charts/harbor/templates/redis/statefulset.yaml b/packages/system/harbor/charts/harbor/templates/redis/statefulset.yaml new file mode 100644 index 00000000..2316f69b --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/redis/statefulset.yaml @@ -0,0 +1,128 @@ +{{- if eq .Values.redis.type "internal" -}} +{{- $redis := .Values.persistence.persistentVolumeClaim.redis -}} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ template "harbor.redis" . }} + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} + component: redis + app.kubernetes.io/component: redis +spec: + replicas: 1 + serviceName: {{ template "harbor.redis" . }} + selector: + matchLabels: +{{ include "harbor.matchLabels" . | indent 6 }} + component: redis + template: + metadata: + labels: +{{ include "harbor.labels" . | indent 8 }} + component: redis + app.kubernetes.io/component: redis +{{- if .Values.redis.podLabels }} +{{ toYaml .Values.redis.podLabels | indent 8 }} +{{- end }} +{{- if .Values.redis.podAnnotations }} + annotations: +{{ toYaml .Values.redis.podAnnotations | indent 8 }} +{{- end }} + spec: + securityContext: + runAsUser: 999 + fsGroup: 999 +{{- if .Values.redis.internal.serviceAccountName }} + serviceAccountName: {{ .Values.redis.internal.serviceAccountName }} +{{- end -}} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + automountServiceAccountToken: {{ .Values.redis.internal.automountServiceAccountToken | default false }} + terminationGracePeriodSeconds: 120 + {{- with .Values.redis.internal.initContainers }} + initContainers: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: redis + image: {{ .Values.redis.internal.image.repository }}:{{ .Values.redis.internal.image.tag }} + imagePullPolicy: {{ .Values.imagePullPolicy }} + {{- if not (empty .Values.containerSecurityContext) }} + securityContext: {{ .Values.containerSecurityContext | toYaml | nindent 10 }} + {{- end }} + livenessProbe: + tcpSocket: + port: 6379 + initialDelaySeconds: 300 + periodSeconds: 10 + readinessProbe: + tcpSocket: + port: 6379 + initialDelaySeconds: 1 + periodSeconds: 10 +{{- if .Values.redis.internal.resources }} + resources: +{{ toYaml .Values.redis.internal.resources | indent 10 }} +{{- end }} +{{- with .Values.redis.internal.extraEnvVars }} + env: +{{- toYaml . | nindent 10 }} +{{- end }} + volumeMounts: + - name: data + mountPath: /var/lib/redis + subPath: {{ $redis.subPath }} + {{- if not .Values.persistence.enabled }} + volumes: + - name: data + emptyDir: {} + {{- else if $redis.existingClaim }} + volumes: + - name: data + persistentVolumeClaim: + claimName: {{ $redis.existingClaim }} + {{- end -}} + {{- with .Values.redis.internal.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.redis.internal.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.redis.internal.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} + {{- if .Values.redis.internal.priorityClassName }} + priorityClassName: {{ .Values.redis.internal.priorityClassName }} + {{- end }} + {{- if and .Values.persistence.enabled (not $redis.existingClaim) }} + volumeClaimTemplates: + - apiVersion: v1 + kind: PersistentVolumeClaim + metadata: + name: data + labels: +{{ include "harbor.legacy.labels" . | indent 8 }} + annotations: + {{- range $key, $value := $redis.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} + spec: + accessModes: [{{ $redis.accessMode | quote }}] + {{- if $redis.storageClass }} + {{- if (eq "-" $redis.storageClass) }} + storageClassName: "" + {{- else }} + storageClassName: "{{ $redis.storageClass }}" + {{- end }} + {{- end }} + resources: + requests: + storage: {{ $redis.size | quote }} + {{- end -}} + {{- end -}} diff --git a/packages/system/harbor/charts/harbor/templates/registry/registry-cm.yaml b/packages/system/harbor/charts/harbor/templates/registry/registry-cm.yaml new file mode 100644 index 00000000..2ef398ed --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/registry/registry-cm.yaml @@ -0,0 +1,248 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: "{{ template "harbor.registry" . }}" + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +data: + config.yml: |+ + version: 0.1 + log: + {{- if eq .Values.logLevel "warning" }} + level: warn + {{- else if eq .Values.logLevel "fatal" }} + level: error + {{- else }} + level: {{ .Values.logLevel }} + {{- end }} + fields: + service: registry + storage: + {{- $storage := .Values.persistence.imageChartStorage }} + {{- $type := $storage.type }} + {{- if eq $type "filesystem" }} + filesystem: + rootdirectory: {{ $storage.filesystem.rootdirectory }} + {{- if $storage.filesystem.maxthreads }} + maxthreads: {{ $storage.filesystem.maxthreads }} + {{- end }} + {{- else if eq $type "azure" }} + azure: + accountname: {{ $storage.azure.accountname }} + container: {{ $storage.azure.container }} + {{- if $storage.azure.realm }} + realm: {{ $storage.azure.realm }} + {{- end }} + {{- else if eq $type "gcs" }} + gcs: + bucket: {{ $storage.gcs.bucket }} + {{- if not .Values.persistence.imageChartStorage.gcs.useWorkloadIdentity }} + keyfile: /etc/registry/gcs-key.json + {{- end }} + {{- if $storage.gcs.rootdirectory }} + rootdirectory: {{ $storage.gcs.rootdirectory }} + {{- end }} + {{- if $storage.gcs.chunksize }} + chunksize: {{ $storage.gcs.chunksize }} + {{- end }} + {{- else if eq $type "s3" }} + s3: + region: {{ $storage.s3.region }} + bucket: {{ $storage.s3.bucket }} + {{- if $storage.s3.regionendpoint }} + regionendpoint: {{ $storage.s3.regionendpoint }} + {{- end }} + {{- if $storage.s3.encrypt }} + encrypt: {{ $storage.s3.encrypt }} + {{- end }} + {{- if $storage.s3.keyid }} + keyid: {{ $storage.s3.keyid }} + {{- end }} + {{- if $storage.s3.secure }} + secure: {{ $storage.s3.secure }} + {{- end }} + {{- if and $storage.s3.secure $storage.s3.skipverify }} + skipverify: {{ $storage.s3.skipverify }} + {{- end }} + {{- if $storage.s3.v4auth }} + v4auth: {{ $storage.s3.v4auth }} + {{- end }} + {{- if $storage.s3.chunksize }} + chunksize: {{ $storage.s3.chunksize }} + {{- end }} + {{- if $storage.s3.rootdirectory }} + rootdirectory: {{ $storage.s3.rootdirectory }} + {{- end }} + {{- if $storage.s3.storageclass }} + storageclass: {{ $storage.s3.storageclass }} + {{- end }} + {{- if $storage.s3.multipartcopychunksize }} + multipartcopychunksize: {{ $storage.s3.multipartcopychunksize }} + {{- end }} + {{- if $storage.s3.multipartcopymaxconcurrency }} + multipartcopymaxconcurrency: {{ $storage.s3.multipartcopymaxconcurrency }} + {{- end }} + {{- if $storage.s3.multipartcopythresholdsize }} + multipartcopythresholdsize: {{ $storage.s3.multipartcopythresholdsize }} + {{- end }} + {{- else if eq $type "swift" }} + swift: + authurl: {{ $storage.swift.authurl }} + username: {{ $storage.swift.username }} + container: {{ $storage.swift.container }} + {{- if $storage.swift.region }} + region: {{ $storage.swift.region }} + {{- end }} + {{- if $storage.swift.tenant }} + tenant: {{ $storage.swift.tenant }} + {{- end }} + {{- if $storage.swift.tenantid }} + tenantid: {{ $storage.swift.tenantid }} + {{- end }} + {{- if $storage.swift.domain }} + domain: {{ $storage.swift.domain }} + {{- end }} + {{- if $storage.swift.domainid }} + domainid: {{ $storage.swift.domainid }} + {{- end }} + {{- if $storage.swift.trustid }} + trustid: {{ $storage.swift.trustid }} + {{- end }} + {{- if $storage.swift.insecureskipverify }} + insecureskipverify: {{ $storage.swift.insecureskipverify }} + {{- end }} + {{- if $storage.swift.chunksize }} + chunksize: {{ $storage.swift.chunksize }} + {{- end }} + {{- if $storage.swift.prefix }} + prefix: {{ $storage.swift.prefix }} + {{- end }} + {{- if $storage.swift.authversion }} + authversion: {{ $storage.swift.authversion }} + {{- end }} + {{- if $storage.swift.endpointtype }} + endpointtype: {{ $storage.swift.endpointtype }} + {{- end }} + {{- if $storage.swift.tempurlcontainerkey }} + tempurlcontainerkey: {{ $storage.swift.tempurlcontainerkey }} + {{- end }} + {{- if $storage.swift.tempurlmethods }} + tempurlmethods: {{ $storage.swift.tempurlmethods }} + {{- end }} + {{- else if eq $type "oss" }} + oss: + accesskeyid: {{ $storage.oss.accesskeyid }} + region: {{ $storage.oss.region }} + bucket: {{ $storage.oss.bucket }} + {{- if $storage.oss.endpoint }} + endpoint: {{ $storage.oss.bucket }}.{{ $storage.oss.endpoint }} + {{- end }} + {{- if $storage.oss.internal }} + internal: {{ $storage.oss.internal }} + {{- end }} + {{- if $storage.oss.encrypt }} + encrypt: {{ $storage.oss.encrypt }} + {{- end }} + {{- if $storage.oss.secure }} + secure: {{ $storage.oss.secure }} + {{- end }} + {{- if $storage.oss.chunksize }} + chunksize: {{ $storage.oss.chunksize }} + {{- end }} + {{- if $storage.oss.rootdirectory }} + rootdirectory: {{ $storage.oss.rootdirectory }} + {{- end }} + {{- end }} + cache: + layerinfo: redis + maintenance: + uploadpurging: + {{- if .Values.registry.upload_purging.enabled }} + enabled: true + age: {{ .Values.registry.upload_purging.age }} + interval: {{ .Values.registry.upload_purging.interval }} + dryrun: {{ .Values.registry.upload_purging.dryrun }} + {{- else }} + enabled: false + {{- end }} + delete: + enabled: true + redirect: + disable: {{ $storage.disableredirect }} + redis: + addr: {{ template "harbor.redis.addr" . }} + {{- if eq "redis+sentinel" (include "harbor.redis.scheme" .) }} + sentinelMasterSet: {{ template "harbor.redis.masterSet" . }} + {{- end }} + db: {{ template "harbor.redis.dbForRegistry" . }} + {{- if not (eq (include "harbor.redis.password" .) "") }} + password: {{ template "harbor.redis.password" . }} + {{- end }} + readtimeout: 10s + writetimeout: 10s + dialtimeout: 10s + enableTLS: {{ template "harbor.redis.enableTLS" . }} + pool: + maxidle: 100 + maxactive: 500 + idletimeout: 60s + http: + addr: :{{ template "harbor.registry.containerPort" . }} + relativeurls: {{ .Values.registry.relativeurls }} + {{- if .Values.internalTLS.enabled }} + tls: + certificate: /etc/harbor/ssl/registry/tls.crt + key: /etc/harbor/ssl/registry/tls.key + minimumtls: tls1.2 + {{- end }} + # set via environment variable + # secret: placeholder + debug: + {{- if .Values.metrics.enabled}} + addr: :{{ .Values.metrics.registry.port }} + prometheus: + enabled: true + path: {{ .Values.metrics.registry.path }} + {{- else }} + addr: localhost:5001 + {{- end }} + auth: + htpasswd: + realm: harbor-registry-basic-realm + path: /etc/registry/passwd + validation: + disabled: true + compatibility: + schema1: + enabled: true + + {{- if .Values.registry.middleware.enabled }} + {{- $middleware := .Values.registry.middleware }} + {{- $middlewareType := $middleware.type }} + {{- if eq $middlewareType "cloudFront" }} + middleware: + storage: + - name: cloudfront + options: + baseurl: {{ $middleware.cloudFront.baseurl }} + privatekey: /etc/registry/pk.pem + keypairid: {{ $middleware.cloudFront.keypairid }} + duration: {{ $middleware.cloudFront.duration }} + ipfilteredby: {{ $middleware.cloudFront.ipfilteredby }} + {{- end }} + {{- end }} + ctl-config.yml: |+ + --- + {{- if .Values.internalTLS.enabled }} + protocol: "https" + port: 8443 + https_config: + cert: "/etc/harbor/ssl/registry/tls.crt" + key: "/etc/harbor/ssl/registry/tls.key" + {{- else }} + protocol: "http" + port: 8080 + {{- end }} + log_level: {{ .Values.logLevel }} + registry_config: "/etc/registry/config.yml" diff --git a/packages/system/harbor/charts/harbor/templates/registry/registry-dpl.yaml b/packages/system/harbor/charts/harbor/templates/registry/registry-dpl.yaml new file mode 100644 index 00000000..a86e2eee --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/registry/registry-dpl.yaml @@ -0,0 +1,431 @@ +{{- $storage := .Values.persistence.imageChartStorage }} +{{- $type := $storage.type }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: "{{ template "harbor.registry" . }}" + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} + component: registry + app.kubernetes.io/component: registry +spec: + replicas: {{ .Values.registry.replicas }} + revisionHistoryLimit: {{ .Values.registry.revisionHistoryLimit }} + strategy: + type: {{ .Values.updateStrategy.type }} + {{- if eq .Values.updateStrategy.type "Recreate" }} + rollingUpdate: null + {{- end }} + selector: + matchLabels: +{{ include "harbor.matchLabels" . | indent 6 }} + component: registry + template: + metadata: + labels: +{{ include "harbor.labels" . | indent 8 }} + component: registry + app.kubernetes.io/component: registry +{{- if .Values.registry.podLabels }} +{{ toYaml .Values.registry.podLabels | indent 8 }} +{{- end }} + annotations: + checksum/configmap: {{ include (print $.Template.BasePath "/registry/registry-cm.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/registry/registry-secret.yaml") . | sha256sum }} + checksum/secret-jobservice: {{ include (print $.Template.BasePath "/jobservice/jobservice-secrets.yaml") . | sha256sum }} + checksum/secret-core: {{ include (print $.Template.BasePath "/core/core-secret.yaml") . | sha256sum }} +{{- if and .Values.internalTLS.enabled (eq .Values.internalTLS.certSource "auto") }} + checksum/tls: {{ include (print $.Template.BasePath "/internal/auto-tls.yaml") . | sha256sum }} +{{- else if and .Values.internalTLS.enabled (eq .Values.internalTLS.certSource "manual") }} + checksum/tls: {{ include (print $.Template.BasePath "/registry/registry-tls.yaml") . | sha256sum }} +{{- end }} +{{- if .Values.registry.podAnnotations }} +{{ toYaml .Values.registry.podAnnotations | indent 8 }} +{{- end }} + spec: + securityContext: + runAsUser: 10000 + fsGroup: 10000 + fsGroupChangePolicy: OnRootMismatch +{{- if .Values.registry.serviceAccountName }} + serviceAccountName: {{ .Values.registry.serviceAccountName }} +{{- end -}} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + automountServiceAccountToken: {{ .Values.registry.automountServiceAccountToken | default false }} + terminationGracePeriodSeconds: 120 +{{- with .Values.registry.topologySpreadConstraints}} + topologySpreadConstraints: +{{- range . }} + - {{ . | toYaml | indent 8 | trim }} + labelSelector: + matchLabels: +{{ include "harbor.matchLabels" $ | indent 12 }} + component: registry +{{- end }} +{{- end }} + {{- with .Values.registry.initContainers }} + initContainers: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: registry + image: {{ .Values.registry.registry.image.repository }}:{{ .Values.registry.registry.image.tag }} + imagePullPolicy: {{ .Values.imagePullPolicy }} + livenessProbe: + httpGet: + path: / + scheme: {{ include "harbor.component.scheme" . | upper }} + port: {{ template "harbor.registry.containerPort" . }} + initialDelaySeconds: 300 + periodSeconds: 10 + readinessProbe: + httpGet: + path: / + scheme: {{ include "harbor.component.scheme" . | upper }} + port: {{ template "harbor.registry.containerPort" . }} + initialDelaySeconds: 1 + periodSeconds: 10 +{{- if .Values.registry.registry.resources }} + resources: +{{ toYaml .Values.registry.registry.resources | indent 10 }} +{{- end }} + {{- if not (empty .Values.containerSecurityContext) }} + securityContext: {{ .Values.containerSecurityContext | toYaml | nindent 10 }} + {{- end }} + envFrom: + - secretRef: + name: "{{ template "harbor.registry" . }}" + {{- if .Values.persistence.imageChartStorage.s3.existingSecret }} + - secretRef: + name: {{ .Values.persistence.imageChartStorage.s3.existingSecret }} + {{- end }} + env: + {{- if .Values.registry.existingSecret }} + - name: REGISTRY_HTTP_SECRET + valueFrom: + secretKeyRef: + name: {{ .Values.registry.existingSecret }} + key: {{ .Values.registry.existingSecretKey }} + {{- end }} + {{- if has "registry" .Values.proxy.components }} + - name: HTTP_PROXY + value: "{{ .Values.proxy.httpProxy }}" + - name: HTTPS_PROXY + value: "{{ .Values.proxy.httpsProxy }}" + - name: NO_PROXY + value: "{{ template "harbor.noProxy" . }}" + {{- end }} + {{- if .Values.internalTLS.enabled }} + - name: INTERNAL_TLS_ENABLED + value: "true" + - name: INTERNAL_TLS_KEY_PATH + value: /etc/harbor/ssl/registry/tls.key + - name: INTERNAL_TLS_CERT_PATH + value: /etc/harbor/ssl/registry/tls.crt + - name: INTERNAL_TLS_TRUST_CA_PATH + value: /etc/harbor/ssl/registry/ca.crt + {{- end }} + {{- if .Values.redis.external.existingSecret }} + - name: REGISTRY_REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.redis.external.existingSecret }} + key: REDIS_PASSWORD + {{- end }} + {{- if .Values.persistence.imageChartStorage.azure.existingSecret }} + - name: REGISTRY_STORAGE_AZURE_ACCOUNTKEY + valueFrom: + secretKeyRef: + name: {{ .Values.persistence.imageChartStorage.azure.existingSecret }} + key: AZURE_STORAGE_ACCESS_KEY + {{- end }} + {{- if .Values.persistence.imageChartStorage.swift.existingSecret }} + - name: REGISTRY_STORAGE_SWIFT_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.persistence.imageChartStorage.swift.existingSecret }} + key: REGISTRY_STORAGE_SWIFT_PASSWORD + - name: REGISTRY_STORAGE_SWIFT_SECRETKEY + valueFrom: + secretKeyRef: + name: {{ .Values.persistence.imageChartStorage.swift.existingSecret }} + key: REGISTRY_STORAGE_SWIFT_SECRETKEY + optional: true + - name: REGISTRY_STORAGE_SWIFT_ACCESSKEY + valueFrom: + secretKeyRef: + name: {{ .Values.persistence.imageChartStorage.swift.existingSecret }} + key: REGISTRY_STORAGE_SWIFT_ACCESSKEY + optional: true + {{- end }} + {{- if .Values.persistence.imageChartStorage.oss.existingSecret }} + - name: REGISTRY_STORAGE_OSS_ACCESSKEYSECRET + valueFrom: + secretKeyRef: + name: {{ .Values.persistence.imageChartStorage.oss.existingSecret }} + key: REGISTRY_STORAGE_OSS_ACCESSKEYSECRET + optional: true + {{- end}} +{{- with .Values.registry.registry.extraEnvVars }} +{{- toYaml . | nindent 8 }} +{{- end }} + ports: + - containerPort: {{ template "harbor.registry.containerPort" . }} + - containerPort: {{ ternary .Values.metrics.registry.port 5001 .Values.metrics.enabled }} + volumeMounts: + - name: registry-data + mountPath: {{ .Values.persistence.imageChartStorage.filesystem.rootdirectory }} + subPath: {{ .Values.persistence.persistentVolumeClaim.registry.subPath }} + - name: registry-htpasswd + mountPath: /etc/registry/passwd + subPath: passwd + - name: registry-config + mountPath: /etc/registry/config.yml + subPath: config.yml + {{- if .Values.internalTLS.enabled }} + - name: registry-internal-certs + mountPath: /etc/harbor/ssl/registry + {{- end }} + {{- if and (and .Values.persistence.enabled (eq .Values.persistence.imageChartStorage.type "gcs")) (not .Values.persistence.imageChartStorage.gcs.useWorkloadIdentity) }} + - name: gcs-key + mountPath: /etc/registry/gcs-key.json + subPath: gcs-key.json + {{- end }} + {{- if .Values.persistence.imageChartStorage.caBundleSecretName }} + - name: storage-service-ca + mountPath: /harbor_cust_cert/custom-ca-bundle.crt + subPath: ca.crt + {{- end }} + {{- if .Values.registry.middleware.enabled }} + {{- if eq .Values.registry.middleware.type "cloudFront" }} + - name: cloudfront-key + mountPath: /etc/registry/pk.pem + subPath: pk.pem + {{- end }} + {{- end }} + {{- if .Values.caBundleSecretName }} +{{ include "harbor.caBundleVolumeMount" . | indent 8 }} + {{- end }} + - name: registryctl + image: {{ .Values.registry.controller.image.repository }}:{{ .Values.registry.controller.image.tag }} + imagePullPolicy: {{ .Values.imagePullPolicy }} + livenessProbe: + httpGet: + path: /api/health + scheme: {{ include "harbor.component.scheme" . | upper }} + port: {{ template "harbor.registryctl.containerPort" . }} + initialDelaySeconds: 300 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /api/health + scheme: {{ include "harbor.component.scheme" . | upper }} + port: {{ template "harbor.registryctl.containerPort" . }} + initialDelaySeconds: 1 + periodSeconds: 10 +{{- if .Values.registry.controller.resources }} + resources: +{{ toYaml .Values.registry.controller.resources | indent 10 }} +{{- end }} + {{- if not (empty .Values.containerSecurityContext) }} + securityContext: {{ .Values.containerSecurityContext | toYaml | nindent 10 }} + {{- end }} + envFrom: + - configMapRef: + name: "{{ template "harbor.registryCtl" . }}" + - secretRef: + name: "{{ template "harbor.registry" . }}" + - secretRef: + name: "{{ template "harbor.registryCtl" . }}" + {{- if .Values.persistence.imageChartStorage.s3.existingSecret }} + - secretRef: + name: {{ .Values.persistence.imageChartStorage.s3.existingSecret }} + {{- end }} + env: + {{- if .Values.registry.existingSecret }} + - name: REGISTRY_HTTP_SECRET + valueFrom: + secretKeyRef: + name: {{ .Values.registry.existingSecret }} + key: {{ .Values.registry.existingSecretKey }} + {{- end }} + - name: CORE_SECRET + valueFrom: + secretKeyRef: + name: {{ default (include "harbor.core" .) .Values.core.existingSecret }} + key: secret + - name: JOBSERVICE_SECRET + valueFrom: + secretKeyRef: + name: {{ default (include "harbor.jobservice" .) .Values.jobservice.existingSecret }} + {{- if .Values.jobservice.existingSecret }} + key: {{ .Values.jobservice.existingSecretKey }} + {{- else }} + key: JOBSERVICE_SECRET + {{- end }} + {{- if has "registry" .Values.proxy.components }} + - name: HTTP_PROXY + value: "{{ .Values.proxy.httpProxy }}" + - name: HTTPS_PROXY + value: "{{ .Values.proxy.httpsProxy }}" + - name: NO_PROXY + value: "{{ template "harbor.noProxy" . }}" + {{- end }} + {{- if .Values.internalTLS.enabled }} + - name: INTERNAL_TLS_ENABLED + value: "true" + - name: INTERNAL_TLS_KEY_PATH + value: /etc/harbor/ssl/registry/tls.key + - name: INTERNAL_TLS_CERT_PATH + value: /etc/harbor/ssl/registry/tls.crt + - name: INTERNAL_TLS_TRUST_CA_PATH + value: /etc/harbor/ssl/registry/ca.crt + {{- end }} + {{- if .Values.redis.external.existingSecret }} + - name: REGISTRY_REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.redis.external.existingSecret }} + key: REDIS_PASSWORD + {{- end }} + {{- if .Values.persistence.imageChartStorage.azure.existingSecret }} + - name: REGISTRY_STORAGE_AZURE_ACCOUNTKEY + valueFrom: + secretKeyRef: + name: {{ .Values.persistence.imageChartStorage.azure.existingSecret }} + key: AZURE_STORAGE_ACCESS_KEY + {{- end }} + {{- if .Values.persistence.imageChartStorage.swift.existingSecret }} + - name: REGISTRY_STORAGE_SWIFT_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.persistence.imageChartStorage.swift.existingSecret }} + key: REGISTRY_STORAGE_SWIFT_PASSWORD + - name: REGISTRY_STORAGE_SWIFT_SECRETKEY + valueFrom: + secretKeyRef: + name: {{ .Values.persistence.imageChartStorage.swift.existingSecret }} + key: REGISTRY_STORAGE_SWIFT_SECRETKEY + optional: true + - name: REGISTRY_STORAGE_SWIFT_ACCESSKEY + valueFrom: + secretKeyRef: + name: {{ .Values.persistence.imageChartStorage.swift.existingSecret }} + key: REGISTRY_STORAGE_SWIFT_ACCESSKEY + optional: true + {{- end }} + {{- if .Values.persistence.imageChartStorage.oss.existingSecret }} + - name: REGISTRY_STORAGE_OSS_ACCESSKEYSECRET + valueFrom: + secretKeyRef: + name: {{ .Values.persistence.imageChartStorage.oss.existingSecret }} + key: REGISTRY_STORAGE_OSS_ACCESSKEYSECRET + optional: true + {{- end}} +{{- with .Values.registry.controller.extraEnvVars }} +{{- toYaml . | nindent 8 }} +{{- end }} + ports: + - containerPort: {{ template "harbor.registryctl.containerPort" . }} + volumeMounts: + - name: registry-data + mountPath: {{ .Values.persistence.imageChartStorage.filesystem.rootdirectory }} + subPath: {{ .Values.persistence.persistentVolumeClaim.registry.subPath }} + - name: registry-config + mountPath: /etc/registry/config.yml + subPath: config.yml + - name: registry-config + mountPath: /etc/registryctl/config.yml + subPath: ctl-config.yml + {{- if .Values.internalTLS.enabled }} + - name: registry-internal-certs + mountPath: /etc/harbor/ssl/registry + {{- end }} + {{- if .Values.persistence.imageChartStorage.caBundleSecretName }} + - name: storage-service-ca + mountPath: /harbor_cust_cert/custom-ca-bundle.crt + subPath: ca.crt + {{- end }} + {{- if and (and .Values.persistence.enabled (eq .Values.persistence.imageChartStorage.type "gcs")) (not .Values.persistence.imageChartStorage.gcs.useWorkloadIdentity ) }} + - name: gcs-key + mountPath: /etc/registry/gcs-key.json + subPath: gcs-key.json + {{- end }} + {{- if .Values.caBundleSecretName }} +{{ include "harbor.caBundleVolumeMount" . | indent 8 }} + {{- end }} + volumes: + - name: registry-htpasswd + secret: + {{- if not .Values.registry.credentials.existingSecret }} + secretName: {{ template "harbor.registry" . }}-htpasswd + {{ else }} + secretName: {{ .Values.registry.credentials.existingSecret }} + {{- end }} + items: + - key: REGISTRY_HTPASSWD + path: passwd + - name: registry-config + configMap: + name: "{{ template "harbor.registry" . }}" + - name: registry-data + {{- if and .Values.persistence.enabled (eq .Values.persistence.imageChartStorage.type "filesystem") }} + persistentVolumeClaim: + claimName: {{ .Values.persistence.persistentVolumeClaim.registry.existingClaim | default (include "harbor.registry" .) }} + {{- else }} + emptyDir: {} + {{- end }} + {{- if .Values.internalTLS.enabled }} + - name: registry-internal-certs + secret: + secretName: {{ template "harbor.internalTLS.registry.secretName" . }} + {{- end }} + {{- if and (and .Values.persistence.enabled (eq .Values.persistence.imageChartStorage.type "gcs")) (not .Values.persistence.imageChartStorage.gcs.useWorkloadIdentity ) }} + - name: gcs-key + secret: + {{- if and (eq $type "gcs") $storage.gcs.existingSecret }} + secretName: {{ $storage.gcs.existingSecret }} + {{- else }} + secretName: {{ template "harbor.registry" . }} + {{- end }} + items: + - key: GCS_KEY_DATA + path: gcs-key.json + {{- end }} + {{- if .Values.persistence.imageChartStorage.caBundleSecretName }} + - name: storage-service-ca + secret: + secretName: {{ .Values.persistence.imageChartStorage.caBundleSecretName }} + {{- end }} + {{- if .Values.registry.middleware.enabled }} + {{- if eq .Values.registry.middleware.type "cloudFront" }} + - name: cloudfront-key + secret: + secretName: {{ .Values.registry.middleware.cloudFront.privateKeySecret }} + items: + - key: CLOUDFRONT_KEY_DATA + path: pk.pem + {{- end }} + {{- end }} + {{- if .Values.caBundleSecretName }} +{{ include "harbor.caBundleVolume" . | indent 6 }} + {{- end }} + {{- with .Values.registry.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.registry.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.registry.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} + {{- if .Values.registry.priorityClassName }} + priorityClassName: {{ .Values.registry.priorityClassName }} + {{- end }} diff --git a/packages/system/harbor/charts/harbor/templates/registry/registry-pvc.yaml b/packages/system/harbor/charts/harbor/templates/registry/registry-pvc.yaml new file mode 100644 index 00000000..712c2117 --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/registry/registry-pvc.yaml @@ -0,0 +1,34 @@ +{{- if .Values.persistence.enabled }} +{{- $registry := .Values.persistence.persistentVolumeClaim.registry -}} +{{- if and (not $registry.existingClaim) (eq .Values.persistence.imageChartStorage.type "filesystem") }} +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ template "harbor.registry" . }} + namespace: {{ .Release.Namespace | quote }} + annotations: + {{- range $key, $value := $registry.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} + {{- if eq .Values.persistence.resourcePolicy "keep" }} + helm.sh/resource-policy: keep + {{- end }} + labels: +{{ include "harbor.labels" . | indent 4 }} + component: registry + app.kubernetes.io/component: registry +spec: + accessModes: + - {{ $registry.accessMode }} + resources: + requests: + storage: {{ $registry.size }} + {{- if $registry.storageClass }} + {{- if eq "-" $registry.storageClass }} + storageClassName: "" + {{- else }} + storageClassName: {{ $registry.storageClass }} + {{- end }} + {{- end }} +{{- end }} +{{- end }} diff --git a/packages/system/harbor/charts/harbor/templates/registry/registry-secret.yaml b/packages/system/harbor/charts/harbor/templates/registry/registry-secret.yaml new file mode 100644 index 00000000..11ada3b7 --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/registry/registry-secret.yaml @@ -0,0 +1,57 @@ +{{- $existingSecret := lookup "v1" "Secret" .Release.Namespace (include "harbor.registry" .) }} +apiVersion: v1 +kind: Secret +metadata: + name: "{{ template "harbor.registry" . }}" + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +type: Opaque +data: + {{- if not .Values.registry.existingSecret }} + REGISTRY_HTTP_SECRET: {{ .Values.registry.secret | default (include "harbor.secretKeyHelper" (dict "key" "REGISTRY_HTTP_SECRET" "data" $existingSecret.data)) | default (randAlphaNum 16) | b64enc | quote }} + {{- end }} + {{- if not .Values.redis.external.existingSecret }} + REGISTRY_REDIS_PASSWORD: {{ include "harbor.redis.password" . | b64enc | quote }} + {{- end }} + {{- $storage := .Values.persistence.imageChartStorage }} + {{- $type := $storage.type }} + {{- if and (eq $type "azure") (not $storage.azure.existingSecret) }} + REGISTRY_STORAGE_AZURE_ACCOUNTKEY: {{ $storage.azure.accountkey | b64enc | quote }} + {{- else if and (and (eq $type "gcs") (not $storage.gcs.existingSecret)) (not $storage.gcs.useWorkloadIdentity) }} + GCS_KEY_DATA: {{ $storage.gcs.encodedkey | quote }} + {{- else if eq $type "s3" }} + {{- if and (not $storage.s3.existingSecret) ($storage.s3.accesskey) }} + REGISTRY_STORAGE_S3_ACCESSKEY: {{ $storage.s3.accesskey | b64enc | quote }} + {{- end }} + {{- if and (not $storage.s3.existingSecret) ($storage.s3.secretkey) }} + REGISTRY_STORAGE_S3_SECRETKEY: {{ $storage.s3.secretkey | b64enc | quote }} + {{- end }} + {{- else if and (eq $type "swift") (not ($storage.swift.existingSecret)) }} + REGISTRY_STORAGE_SWIFT_PASSWORD: {{ $storage.swift.password | b64enc | quote }} + {{- if $storage.swift.secretkey }} + REGISTRY_STORAGE_SWIFT_SECRETKEY: {{ $storage.swift.secretkey | b64enc | quote }} + {{- end }} + {{- if $storage.swift.accesskey }} + REGISTRY_STORAGE_SWIFT_ACCESSKEY: {{ $storage.swift.accesskey | b64enc | quote }} + {{- end }} + {{- else if and (eq $type "oss") ((not ($storage.oss.existingSecret))) }} + REGISTRY_STORAGE_OSS_ACCESSKEYSECRET: {{ $storage.oss.accesskeysecret | b64enc | quote }} + {{- end }} +{{- if not .Values.registry.credentials.existingSecret }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: "{{ template "harbor.registry" . }}-htpasswd" + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +type: Opaque +data: + {{- if .Values.registry.credentials.htpasswdString }} + REGISTRY_HTPASSWD: {{ .Values.registry.credentials.htpasswdString | b64enc | quote }} + {{- else }} + REGISTRY_HTPASSWD: {{ htpasswd .Values.registry.credentials.username .Values.registry.credentials.password | b64enc | quote }} + {{- end }} +{{- end }} diff --git a/packages/system/harbor/charts/harbor/templates/registry/registry-svc.yaml b/packages/system/harbor/charts/harbor/templates/registry/registry-svc.yaml new file mode 100644 index 00000000..7e30c478 --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/registry/registry-svc.yaml @@ -0,0 +1,27 @@ +apiVersion: v1 +kind: Service +metadata: + name: "{{ template "harbor.registry" . }}" + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +spec: +{{- if .Values.ipFamily.policy }} + ipFamilyPolicy: {{ .Values.ipFamily.policy }} +{{- end }} +{{- if .Values.ipFamily.families }} + ipFamilies: {{ toYaml .Values.ipFamily.families | nindent 4 }} +{{- end }} + ports: + - name: {{ ternary "https-registry" "http-registry" .Values.internalTLS.enabled }} + port: {{ template "harbor.registry.servicePort" . }} + + - name: {{ ternary "https-controller" "http-controller" .Values.internalTLS.enabled }} + port: {{ template "harbor.registryctl.servicePort" . }} +{{- if .Values.metrics.enabled}} + - name: {{ template "harbor.metricsPortName" . }} + port: {{ .Values.metrics.registry.port }} +{{- end }} + selector: +{{ include "harbor.matchLabels" . | indent 4 }} + component: registry diff --git a/packages/system/harbor/charts/harbor/templates/registry/registry-tls.yaml b/packages/system/harbor/charts/harbor/templates/registry/registry-tls.yaml new file mode 100644 index 00000000..ec4540c2 --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/registry/registry-tls.yaml @@ -0,0 +1,16 @@ +{{- if and .Values.internalTLS.enabled }} +{{- if eq .Values.internalTLS.certSource "manual" }} +apiVersion: v1 +kind: Secret +metadata: + name: "{{ template "harbor.internalTLS.registry.secretName" . }}" + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +type: kubernetes.io/tls +data: + ca.crt: {{ (required "The \"internalTLS.trustCa\" is required!" .Values.internalTLS.trustCa) | b64enc | quote }} + tls.crt: {{ (required "The \"internalTLS.registry.crt\" is required!" .Values.internalTLS.registry.crt) | b64enc | quote }} + tls.key: {{ (required "The \"internalTLS.registry.key\" is required!" .Values.internalTLS.registry.key) | b64enc | quote }} +{{- end }} +{{- end }} diff --git a/packages/system/harbor/charts/harbor/templates/registry/registryctl-cm.yaml b/packages/system/harbor/charts/harbor/templates/registry/registryctl-cm.yaml new file mode 100644 index 00000000..61b2c5e1 --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/registry/registryctl-cm.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: "{{ template "harbor.registryCtl" . }}" + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +data: + {{- template "harbor.traceEnvsForRegistryCtl" . }} diff --git a/packages/system/harbor/charts/harbor/templates/registry/registryctl-secret.yaml b/packages/system/harbor/charts/harbor/templates/registry/registryctl-secret.yaml new file mode 100644 index 00000000..324a2e03 --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/registry/registryctl-secret.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Secret +metadata: + name: "{{ template "harbor.registryCtl" . }}" + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +type: Opaque +data: + {{- template "harbor.traceJaegerPassword" . }} diff --git a/packages/system/harbor/charts/harbor/templates/trivy/trivy-secret.yaml b/packages/system/harbor/charts/harbor/templates/trivy/trivy-secret.yaml new file mode 100644 index 00000000..b13f8800 --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/trivy/trivy-secret.yaml @@ -0,0 +1,13 @@ +{{- if .Values.trivy.enabled }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "harbor.trivy" . }} + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +type: Opaque +data: + redisURL: {{ include "harbor.redis.urlForTrivy" . | b64enc }} + gitHubToken: {{ .Values.trivy.gitHubToken | default "" | b64enc | quote }} +{{- end }} diff --git a/packages/system/harbor/charts/harbor/templates/trivy/trivy-sts.yaml b/packages/system/harbor/charts/harbor/templates/trivy/trivy-sts.yaml new file mode 100644 index 00000000..19b01a2e --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/trivy/trivy-sts.yaml @@ -0,0 +1,237 @@ +{{- if .Values.trivy.enabled }} +{{- $trivy := .Values.persistence.persistentVolumeClaim.trivy }} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ template "harbor.trivy" . }} + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} + component: trivy + app.kubernetes.io/component: trivy +spec: + replicas: {{ .Values.trivy.replicas }} + serviceName: {{ template "harbor.trivy" . }} + selector: + matchLabels: +{{ include "harbor.matchLabels" . | indent 6 }} + component: trivy + template: + metadata: + labels: +{{ include "harbor.labels" . | indent 8 }} + component: trivy + app.kubernetes.io/component: trivy +{{- if .Values.trivy.podLabels }} +{{ toYaml .Values.trivy.podLabels | indent 8 }} +{{- end }} + annotations: + checksum/secret: {{ include (print $.Template.BasePath "/trivy/trivy-secret.yaml") . | sha256sum }} +{{- if and .Values.internalTLS.enabled (eq .Values.internalTLS.certSource "auto") }} + checksum/tls: {{ include (print $.Template.BasePath "/internal/auto-tls.yaml") . | sha256sum }} +{{- else if and .Values.internalTLS.enabled (eq .Values.internalTLS.certSource "manual") }} + checksum/tls: {{ include (print $.Template.BasePath "/trivy/trivy-tls.yaml") . | sha256sum }} +{{- end }} +{{- if .Values.trivy.podAnnotations }} +{{ toYaml .Values.trivy.podAnnotations | indent 8 }} +{{- end }} + spec: +{{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} +{{- end }} +{{- if .Values.trivy.serviceAccountName }} + serviceAccountName: {{ .Values.trivy.serviceAccountName }} +{{- end }} + securityContext: + runAsUser: 10000 + fsGroup: 10000 + automountServiceAccountToken: {{ .Values.trivy.automountServiceAccountToken | default false }} +{{- with .Values.trivy.topologySpreadConstraints}} + topologySpreadConstraints: +{{- range . }} + - {{ . | toYaml | indent 8 | trim }} + labelSelector: + matchLabels: +{{ include "harbor.matchLabels" $ | indent 12 }} + component: trivy +{{- end }} +{{- end }} + {{- with .Values.trivy.initContainers }} + initContainers: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: trivy + image: {{ .Values.trivy.image.repository }}:{{ .Values.trivy.image.tag }} + imagePullPolicy: {{ .Values.imagePullPolicy }} + {{- if not (empty .Values.containerSecurityContext) }} + securityContext: {{ .Values.containerSecurityContext | toYaml | nindent 12 }} + {{- end }} + env: + {{- if has "trivy" .Values.proxy.components }} + - name: HTTP_PROXY + value: "{{ .Values.proxy.httpProxy }}" + - name: HTTPS_PROXY + value: "{{ .Values.proxy.httpsProxy }}" + - name: NO_PROXY + value: "{{ template "harbor.noProxy" . }}" + {{- end }} + - name: "SCANNER_LOG_LEVEL" + value: {{ .Values.logLevel | quote }} + - name: "SCANNER_TRIVY_CACHE_DIR" + value: "/home/scanner/.cache/trivy" + - name: "SCANNER_TRIVY_REPORTS_DIR" + value: "/home/scanner/.cache/reports" + - name: "SCANNER_TRIVY_DEBUG_MODE" + value: {{ .Values.trivy.debugMode | quote }} + - name: "SCANNER_TRIVY_VULN_TYPE" + value: {{ .Values.trivy.vulnType | quote }} + - name: "SCANNER_TRIVY_TIMEOUT" + value: {{ .Values.trivy.timeout | quote }} + - name: "SCANNER_TRIVY_GITHUB_TOKEN" + valueFrom: + secretKeyRef: + name: {{ template "harbor.trivy" . }} + key: gitHubToken + - name: "SCANNER_TRIVY_SEVERITY" + value: {{ .Values.trivy.severity | quote }} + - name: "SCANNER_TRIVY_IGNORE_UNFIXED" + value: {{ .Values.trivy.ignoreUnfixed | default false | quote }} + - name: "SCANNER_TRIVY_SKIP_UPDATE" + value: {{ .Values.trivy.skipUpdate | default false | quote }} + - name: "SCANNER_TRIVY_SKIP_JAVA_DB_UPDATE" + value: {{ .Values.trivy.skipJavaDBUpdate | default false | quote }} + - name: "SCANNER_TRIVY_DB_REPOSITORY" + value: {{ .Values.trivy.dbRepository | join "," | quote }} + - name: "SCANNER_TRIVY_JAVA_DB_REPOSITORY" + value: {{ .Values.trivy.javaDBRepository | join "," | quote }} + - name: "SCANNER_TRIVY_OFFLINE_SCAN" + value: {{ .Values.trivy.offlineScan | default false | quote }} + - name: "SCANNER_TRIVY_SECURITY_CHECKS" + value: {{ .Values.trivy.securityCheck | quote }} + - name: "SCANNER_TRIVY_INSECURE" + value: {{ .Values.trivy.insecure | default false | quote }} + - name: SCANNER_API_SERVER_ADDR + value: ":{{ template "harbor.trivy.containerPort" . }}" + {{- if .Values.internalTLS.enabled }} + - name: INTERNAL_TLS_ENABLED + value: "true" + - name: SCANNER_API_SERVER_TLS_KEY + value: /etc/harbor/ssl/trivy/tls.key + - name: SCANNER_API_SERVER_TLS_CERTIFICATE + value: /etc/harbor/ssl/trivy/tls.crt + {{- end }} + - name: "SCANNER_REDIS_URL" + valueFrom: + secretKeyRef: + name: {{ template "harbor.trivy" . }} + key: redisURL + - name: "SCANNER_STORE_REDIS_URL" + valueFrom: + secretKeyRef: + name: {{ template "harbor.trivy" . }} + key: redisURL + - name: "SCANNER_JOB_QUEUE_REDIS_URL" + valueFrom: + secretKeyRef: + name: {{ template "harbor.trivy" . }} + key: redisURL +{{- with .Values.trivy.extraEnvVars }} +{{- toYaml . | nindent 12 }} +{{- end }} + ports: + - name: api-server + containerPort: {{ template "harbor.trivy.containerPort" . }} + volumeMounts: + - name: data + mountPath: /home/scanner/.cache + subPath: {{ .Values.persistence.persistentVolumeClaim.trivy.subPath }} + readOnly: false + {{- if .Values.internalTLS.enabled }} + - name: trivy-internal-certs + mountPath: /etc/harbor/ssl/trivy + {{- end }} + {{- if .Values.caBundleSecretName }} +{{ include "harbor.caBundleVolumeMount" . | indent 10 }} + {{- end }} + livenessProbe: + httpGet: + scheme: {{ include "harbor.component.scheme" . | upper }} + path: /probe/healthy + port: api-server + initialDelaySeconds: 5 + periodSeconds: 10 + successThreshold: 1 + failureThreshold: 10 + readinessProbe: + httpGet: + scheme: {{ include "harbor.component.scheme" . | upper }} + path: /probe/ready + port: api-server + initialDelaySeconds: 5 + periodSeconds: 10 + successThreshold: 1 + failureThreshold: 3 + resources: +{{ toYaml .Values.trivy.resources | indent 12 }} + {{- if or (or .Values.internalTLS.enabled .Values.caBundleSecretName) (or (not .Values.persistence.enabled) $trivy.existingClaim) }} + volumes: + {{- if .Values.internalTLS.enabled }} + - name: trivy-internal-certs + secret: + secretName: {{ template "harbor.internalTLS.trivy.secretName" . }} + {{- end }} + {{- if .Values.caBundleSecretName }} +{{ include "harbor.caBundleVolume" . | indent 6 }} + {{- end }} + {{- if not .Values.persistence.enabled }} + - name: "data" + emptyDir: {} + {{- else if $trivy.existingClaim }} + - name: "data" + persistentVolumeClaim: + claimName: {{ $trivy.existingClaim }} + {{- end }} + {{- end }} + {{- with .Values.trivy.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.trivy.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.trivy.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} + {{- if .Values.trivy.priorityClassName }} + priorityClassName: {{ .Values.trivy.priorityClassName }} + {{- end }} +{{- if and .Values.persistence.enabled (not $trivy.existingClaim) }} + volumeClaimTemplates: + - apiVersion: v1 + kind: PersistentVolumeClaim + metadata: + name: data + labels: +{{ include "harbor.legacy.labels" . | indent 8 }} + annotations: + {{- range $key, $value := $trivy.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} + spec: + accessModes: [{{ $trivy.accessMode | quote }}] + {{- if $trivy.storageClass }} + {{- if (eq "-" $trivy.storageClass) }} + storageClassName: "" + {{- else }} + storageClassName: "{{ $trivy.storageClass }}" + {{- end }} + {{- end }} + resources: + requests: + storage: {{ $trivy.size | quote }} +{{- end }} +{{- end }} diff --git a/packages/system/harbor/charts/harbor/templates/trivy/trivy-svc.yaml b/packages/system/harbor/charts/harbor/templates/trivy/trivy-svc.yaml new file mode 100644 index 00000000..a9fb9bf4 --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/trivy/trivy-svc.yaml @@ -0,0 +1,23 @@ +{{ if .Values.trivy.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: "{{ template "harbor.trivy" . }}" + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +spec: +{{- if .Values.ipFamily.policy }} + ipFamilyPolicy: {{ .Values.ipFamily.policy }} +{{- end }} +{{- if .Values.ipFamily.families }} + ipFamilies: {{ toYaml .Values.ipFamily.families | nindent 4 }} +{{- end }} + ports: + - name: {{ ternary "https-trivy" "http-trivy" .Values.internalTLS.enabled }} + protocol: TCP + port: {{ template "harbor.trivy.servicePort" . }} + selector: +{{ include "harbor.matchLabels" . | indent 4 }} + component: trivy +{{ end }} diff --git a/packages/system/harbor/charts/harbor/templates/trivy/trivy-tls.yaml b/packages/system/harbor/charts/harbor/templates/trivy/trivy-tls.yaml new file mode 100644 index 00000000..58bce4ec --- /dev/null +++ b/packages/system/harbor/charts/harbor/templates/trivy/trivy-tls.yaml @@ -0,0 +1,16 @@ +{{- if and .Values.trivy.enabled .Values.internalTLS.enabled }} +{{- if eq .Values.internalTLS.certSource "manual" }} +apiVersion: v1 +kind: Secret +metadata: + name: "{{ template "harbor.internalTLS.trivy.secretName" . }}" + namespace: {{ .Release.Namespace | quote }} + labels: +{{ include "harbor.labels" . | indent 4 }} +type: kubernetes.io/tls +data: + ca.crt: {{ (required "The \"internalTLS.trustCa\" is required!" .Values.internalTLS.trustCa) | b64enc | quote }} + tls.crt: {{ (required "The \"internalTLS.trivy.crt\" is required!" .Values.internalTLS.trivy.crt) | b64enc | quote }} + tls.key: {{ (required "The \"internalTLS.trivy.key\" is required!" .Values.internalTLS.trivy.key) | b64enc | quote }} +{{- end }} +{{- end }} diff --git a/packages/system/harbor/charts/harbor/values.yaml b/packages/system/harbor/charts/harbor/values.yaml new file mode 100644 index 00000000..e00fafc3 --- /dev/null +++ b/packages/system/harbor/charts/harbor/values.yaml @@ -0,0 +1,1095 @@ +expose: + # Set how to expose the service. Set the type as "ingress", "clusterIP", "nodePort" or "loadBalancer" + # and fill the information in the corresponding section + type: ingress + tls: + # Enable TLS or not. + # Delete the "ssl-redirect" annotations in "expose.ingress.annotations" when TLS is disabled and "expose.type" is "ingress" + # Note: if the "expose.type" is "ingress" and TLS is disabled, + # the port must be included in the command when pulling/pushing images. + # Refer to https://github.com/goharbor/harbor/issues/5291 for details. + enabled: true + # The source of the tls certificate. Set as "auto", "secret" + # or "none" and fill the information in the corresponding section + # 1) auto: generate the tls certificate automatically + # 2) secret: read the tls certificate from the specified secret. + # The tls certificate can be generated manually or by cert manager + # 3) none: configure no tls certificate for the ingress. If the default + # tls certificate is configured in the ingress controller, choose this option + certSource: auto + auto: + # The common name used to generate the certificate, it's necessary + # when the type isn't "ingress" + commonName: "" + secret: + # The name of secret which contains keys named: + # "tls.crt" - the certificate + # "tls.key" - the private key + secretName: "" + ingress: + hosts: + core: core.harbor.domain + # set to the type of ingress controller if it has specific requirements. + # leave as `default` for most ingress controllers. + # set to `gce` if using the GCE ingress controller + # set to `ncp` if using the NCP (NSX-T Container Plugin) ingress controller + # set to `alb` if using the ALB ingress controller + # set to `f5-bigip` if using the F5 BIG-IP ingress controller + controller: default + ## Allow .Capabilities.KubeVersion.Version to be overridden while creating ingress + kubeVersionOverride: "" + className: "" + annotations: + # note different ingress controllers may require a different ssl-redirect annotation + # for Envoy, use ingress.kubernetes.io/force-ssl-redirect: "true" and remove the nginx lines below + ingress.kubernetes.io/ssl-redirect: "true" + ingress.kubernetes.io/proxy-body-size: "0" + nginx.ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/proxy-body-size: "0" + # ingress-specific labels + labels: {} + route: + labels: {} + annotations: {} + # - name: envoy-internal + # namespace: networking + # sectionName: https + # group: gateway.networking.k8s.io + # kind: Gateway + parentRefs: {} + # - "harbor.example.com" + hosts: [] + clusterIP: + # The name of ClusterIP service + name: harbor + # The ip address of the ClusterIP service (leave empty for acquiring dynamic ip) + staticClusterIP: "" + ports: + # The service port Harbor listens on when serving HTTP + httpPort: 80 + # The service port Harbor listens on when serving HTTPS + httpsPort: 443 + # Annotations on the ClusterIP service + annotations: {} + # ClusterIP-specific labels + labels: {} + nodePort: + # The name of NodePort service + name: harbor + ports: + http: + # The service port Harbor listens on when serving HTTP + port: 80 + # The node port Harbor listens on when serving HTTP + nodePort: 30002 + https: + # The service port Harbor listens on when serving HTTPS + port: 443 + # The node port Harbor listens on when serving HTTPS + nodePort: 30003 + # Annotations on the nodePort service + annotations: {} + # nodePort-specific labels + labels: {} + loadBalancer: + # The name of LoadBalancer service + name: harbor + # Set the IP if the LoadBalancer supports assigning IP + IP: "" + ports: + # The service port Harbor listens on when serving HTTP + httpPort: 80 + # The service port Harbor listens on when serving HTTPS + httpsPort: 443 + # Annotations on the loadBalancer service + annotations: {} + # loadBalancer-specific labels + labels: {} + sourceRanges: [] + +# The external URL for Harbor core service. It is used to +# 1) populate the docker/helm commands showed on portal +# 2) populate the token service URL returned to docker client +# +# Format: protocol://domain[:port]. Usually: +# 1) if "expose.type" is "ingress", the "domain" should be +# the value of "expose.ingress.hosts.core" +# 2) if "expose.type" is "clusterIP", the "domain" should be +# the value of "expose.clusterIP.name" +# 3) if "expose.type" is "nodePort", the "domain" should be +# the IP address of k8s node +# +# If Harbor is deployed behind the proxy, set it as the URL of proxy +externalURL: https://core.harbor.domain + +# The persistence is enabled by default and a default StorageClass +# is needed in the k8s cluster to provision volumes dynamically. +# Specify another StorageClass in the "storageClass" or set "existingClaim" +# if you already have existing persistent volumes to use +# +# For storing images and charts, you can also use "azure", "gcs", "s3", +# "swift" or "oss". Set it in the "imageChartStorage" section +persistence: + enabled: true + # Setting it to "keep" to avoid removing PVCs during a helm delete + # operation. Leaving it empty will delete PVCs after the chart deleted + # (this does not apply for PVCs that are created for internal database + # and redis components, i.e. they are never deleted automatically) + resourcePolicy: "keep" + persistentVolumeClaim: + registry: + # Use the existing PVC which must be created manually before bound, + # and specify the "subPath" if the PVC is shared with other components + existingClaim: "" + # Specify the "storageClass" used to provision the volume. Or the default + # StorageClass will be used (the default). + # Set it to "-" to disable dynamic provisioning + storageClass: "" + subPath: "" + accessMode: ReadWriteOnce + size: 5Gi + annotations: {} + jobservice: + jobLog: + existingClaim: "" + storageClass: "" + subPath: "" + accessMode: ReadWriteOnce + size: 1Gi + annotations: {} + # If external database is used, the following settings for database will + # be ignored + database: + existingClaim: "" + storageClass: "" + subPath: "" + accessMode: ReadWriteOnce + size: 1Gi + annotations: {} + # If external Redis is used, the following settings for Redis will + # be ignored + redis: + existingClaim: "" + storageClass: "" + subPath: "" + accessMode: ReadWriteOnce + size: 1Gi + annotations: {} + trivy: + existingClaim: "" + storageClass: "" + subPath: "" + accessMode: ReadWriteOnce + size: 5Gi + annotations: {} + # Define which storage backend is used for registry to store + # images and charts. Refer to + # https://github.com/distribution/distribution/blob/release/2.8/docs/configuration.md#storage + # for the detail. + imageChartStorage: + # Specify whether to disable `redirect` for images and chart storage, for + # backends which not supported it (such as using minio for `s3` storage type), please disable + # it. To disable redirects, simply set `disableredirect` to `true` instead. + # Refer to + # https://github.com/distribution/distribution/blob/release/2.8/docs/configuration.md#redirect + # for the detail. + disableredirect: false + # Specify the "caBundleSecretName" if the storage service uses a self-signed certificate. + # The secret must contain keys named "ca.crt" which will be injected into the trust store + # of registry's containers. + # caBundleSecretName: + + # Specify the type of storage: "filesystem", "azure", "gcs", "s3", "swift", + # "oss" and fill the information needed in the corresponding section. The type + # must be "filesystem" if you want to use persistent volumes for registry + type: filesystem + filesystem: + rootdirectory: /storage + #maxthreads: 100 + azure: + accountname: accountname + accountkey: base64encodedaccountkey + container: containername + #realm: core.windows.net + # To use existing secret, the key must be AZURE_STORAGE_ACCESS_KEY + existingSecret: "" + gcs: + bucket: bucketname + # The base64 encoded json file which contains the key + encodedkey: base64-encoded-json-key-file + #rootdirectory: /gcs/object/name/prefix + #chunksize: "5242880" + # To use existing secret, the key must be GCS_KEY_DATA + existingSecret: "" + useWorkloadIdentity: false + s3: + # Set an existing secret for S3 accesskey and secretkey + # keys in the secret should be REGISTRY_STORAGE_S3_ACCESSKEY and REGISTRY_STORAGE_S3_SECRETKEY for registry + #existingSecret: "" + region: us-west-1 + bucket: bucketname + #accesskey: awsaccesskey + #secretkey: awssecretkey + #regionendpoint: http://myobjects.local + #encrypt: false + #keyid: mykeyid + #secure: true + #skipverify: false + #v4auth: true + #chunksize: "5242880" + #rootdirectory: /s3/object/name/prefix + #storageclass: STANDARD + #multipartcopychunksize: "33554432" + #multipartcopymaxconcurrency: 100 + #multipartcopythresholdsize: "33554432" + swift: + authurl: https://storage.myprovider.com/v3/auth + username: username + password: password + container: containername + # keys in existing secret must be REGISTRY_STORAGE_SWIFT_PASSWORD, REGISTRY_STORAGE_SWIFT_SECRETKEY, REGISTRY_STORAGE_SWIFT_ACCESSKEY + existingSecret: "" + #region: fr + #tenant: tenantname + #tenantid: tenantid + #domain: domainname + #domainid: domainid + #trustid: trustid + #insecureskipverify: false + #chunksize: 5M + #prefix: + #secretkey: secretkey + #accesskey: accesskey + #authversion: 3 + #endpointtype: public + #tempurlcontainerkey: false + #tempurlmethods: + oss: + accesskeyid: accesskeyid + accesskeysecret: accesskeysecret + region: regionname + bucket: bucketname + # key in existingSecret must be REGISTRY_STORAGE_OSS_ACCESSKEYSECRET + existingSecret: "" + #endpoint: endpoint + #internal: false + #encrypt: false + #secure: true + #chunksize: 10M + #rootdirectory: rootdirectory + +# The initial password of Harbor admin. Change it from portal after launching Harbor +# or give an existing secret for it +# key in secret is given via (default to HARBOR_ADMIN_PASSWORD) +existingSecretAdminPassword: "" +existingSecretAdminPasswordKey: HARBOR_ADMIN_PASSWORD +harborAdminPassword: "Harbor12345" + +# The internal TLS used for harbor components secure communicating. In order to enable https +# in each component tls cert files need to provided in advance. +internalTLS: + # If internal TLS enabled + enabled: false + # enable strong ssl ciphers (default: false) + strong_ssl_ciphers: false + # There are three ways to provide tls + # 1) "auto" will generate cert automatically + # 2) "manual" need provide cert file manually in following value + # 3) "secret" internal certificates from secret + certSource: "auto" + # The content of trust ca, only available when `certSource` is "manual" + trustCa: "" + # core related cert configuration + core: + # secret name for core's tls certs + secretName: "" + # Content of core's TLS cert file, only available when `certSource` is "manual" + crt: "" + # Content of core's TLS key file, only available when `certSource` is "manual" + key: "" + # jobservice related cert configuration + jobservice: + # secret name for jobservice's tls certs + secretName: "" + # Content of jobservice's TLS key file, only available when `certSource` is "manual" + crt: "" + # Content of jobservice's TLS key file, only available when `certSource` is "manual" + key: "" + # registry related cert configuration + registry: + # secret name for registry's tls certs + secretName: "" + # Content of registry's TLS key file, only available when `certSource` is "manual" + crt: "" + # Content of registry's TLS key file, only available when `certSource` is "manual" + key: "" + # portal related cert configuration + portal: + # secret name for portal's tls certs + secretName: "" + # Content of portal's TLS key file, only available when `certSource` is "manual" + crt: "" + # Content of portal's TLS key file, only available when `certSource` is "manual" + key: "" + # trivy related cert configuration + trivy: + # secret name for trivy's tls certs + secretName: "" + # Content of trivy's TLS key file, only available when `certSource` is "manual" + crt: "" + # Content of trivy's TLS key file, only available when `certSource` is "manual" + key: "" + +ipFamily: + # ipv6Enabled set to true if ipv6 is enabled in cluster, currently it affected the nginx related component + ipv6: + enabled: true + # ipv4Enabled set to true if ipv4 is enabled in cluster, currently it affected the nginx related component + ipv4: + enabled: true + + # Sets the IP family policy for services to be able to configure dual-stack; see [Configure dual-stack](https://kubernetes.io/docs/concepts/services-networking/dual-stack/#services). + policy: "" + # A list of IP families for services that should be supported, in the order in which they should be applied to ClusterIP. Can be "IPv4" and/or "IPv6". + families: [] + +imagePullPolicy: IfNotPresent + +# Use this set to assign a list of default pullSecrets +imagePullSecrets: +# - name: docker-registry-secret +# - name: internal-registry-secret + +# The update strategy for deployments with persistent volumes(jobservice, registry): "RollingUpdate" or "Recreate" +# Set it as "Recreate" when "RWM" for volumes isn't supported +updateStrategy: + type: RollingUpdate + +# debug, info, warning, error or fatal +logLevel: info + +# The name of the secret which contains key named "ca.crt". Setting this enables the +# download link on portal to download the CA certificate when the certificate isn't +# generated automatically +caSecretName: "" + +# The secret key used for encryption. Must be a string of 16 chars. +secretKey: "not-a-secure-key" +# If using existingSecretSecretKey, the key must be secretKey +existingSecretSecretKey: "" + +# The proxy settings for updating trivy vulnerabilities from the Internet and replicating +# artifacts from/to the registries that cannot be reached directly +proxy: + httpProxy: + httpsProxy: + noProxy: 127.0.0.1,localhost,.local,.internal + components: + - core + - jobservice + - trivy + +# Run the migration job via helm hook +enableMigrateHelmHook: false + +# The custom ca bundle secret, the secret must contain key named "ca.crt" +# which will be injected into the trust store for core, jobservice, registry, trivy components +# caBundleSecretName: "" + +## UAA Authentication Options +# If you're using UAA for authentication behind a self-signed +# certificate you will need to provide the CA Cert. +# Set uaaSecretName below to provide a pre-created secret that +# contains a base64 encoded CA Certificate named `ca.crt`. +# uaaSecretName: + +metrics: + enabled: false + core: + path: /metrics + port: 8001 + registry: + path: /metrics + port: 8001 + jobservice: + path: /metrics + port: 8001 + exporter: + path: /metrics + port: 8001 + ## Create prometheus serviceMonitor to scrape harbor metrics. + ## This requires the monitoring.coreos.com/v1 CRD. Please see + ## https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/user-guides/getting-started.md + ## + serviceMonitor: + enabled: false + additionalLabels: {} + # Scrape interval. If not set, the Prometheus default scrape interval is used. + interval: "" + # Metric relabel configs to apply to samples before ingestion. + metricRelabelings: + [] + # - action: keep + # regex: 'kube_(daemonset|deployment|pod|namespace|node|statefulset).+' + # sourceLabels: [__name__] + # Relabel configs to apply to samples before ingestion. + relabelings: + [] + # - sourceLabels: [__meta_kubernetes_pod_node_name] + # separator: ; + # regex: ^(.*)$ + # targetLabel: nodename + # replacement: $1 + # action: replace + +trace: + enabled: false + # trace provider: jaeger or otel + # jaeger should be 1.26+ + provider: jaeger + # set sample_rate to 1 if you wanna sampling 100% of trace data; set 0.5 if you wanna sampling 50% of trace data, and so forth + sample_rate: 1 + # namespace used to differentiate different harbor services + # namespace: + # attributes is a key value dict contains user defined attributes used to initialize trace provider + # attributes: + # application: harbor + jaeger: + # jaeger supports two modes: + # collector mode(uncomment endpoint and uncomment username, password if needed) + # agent mode(uncomment agent_host and agent_port) + endpoint: http://hostname:14268/api/traces + # username: + # password: + # agent_host: hostname + # export trace data by jaeger.thrift in compact mode + # agent_port: 6831 + otel: + endpoint: hostname:4318 + url_path: /v1/traces + compression: false + insecure: true + # timeout is in seconds + timeout: 10 + +# cache layer configurations +# if this feature enabled, harbor will cache the resource +# `project/project_metadata/repository/artifact/manifest` in the redis +# which help to improve the performance of high concurrent pulling manifest. +cache: + # default is not enabled. + enabled: false + # default keep cache for one day. + expireHours: 24 + +## set Container Security Context to comply with PSP restricted policy if necessary +## each of the conatiner will apply the same security context +## containerSecurityContext:{} is initially an empty yaml that you could edit it on demand, we just filled with a common template for convenience +containerSecurityContext: + privileged: false + allowPrivilegeEscalation: false + seccompProfile: + type: RuntimeDefault + runAsNonRoot: true + capabilities: + drop: + - ALL + +# If service exposed via "ingress", the Nginx will not be used +nginx: + image: + repository: goharbor/nginx-photon + tag: v2.14.2 + # set the service account to be used, default if left empty + serviceAccountName: "" + # mount the service account token + automountServiceAccountToken: false + replicas: 1 + revisionHistoryLimit: 10 + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + extraEnvVars: [] + nodeSelector: {} + tolerations: [] + affinity: {} + # Spread Pods across failure-domains like regions, availability zones or nodes + topologySpreadConstraints: [] + # - maxSkew: 1 + # topologyKey: topology.kubernetes.io/zone + # nodeTaintsPolicy: Honor + # whenUnsatisfiable: DoNotSchedule + ## Additional deployment annotations + podAnnotations: {} + ## Additional deployment labels + podLabels: {} + ## The priority class to run the pod as + priorityClassName: + +portal: + image: + repository: goharbor/harbor-portal + tag: v2.14.2 + # set the service account to be used, default if left empty + serviceAccountName: "" + # mount the service account token + automountServiceAccountToken: false + replicas: 1 + revisionHistoryLimit: 10 + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + extraEnvVars: [] + nodeSelector: {} + tolerations: [] + affinity: {} + # Spread Pods across failure-domains like regions, availability zones or nodes + topologySpreadConstraints: [] + # - maxSkew: 1 + # topologyKey: topology.kubernetes.io/zone + # nodeTaintsPolicy: Honor + # whenUnsatisfiable: DoNotSchedule + ## Additional deployment annotations + podAnnotations: {} + ## Additional deployment labels + podLabels: {} + ## Additional service annotations + serviceAnnotations: {} + ## The priority class to run the pod as + priorityClassName: + # containers to be run before the controller's container starts. + initContainers: [] + # Example: + # + # - name: wait + # image: busybox + # command: [ 'sh', '-c', "sleep 20" ] + +core: + image: + repository: goharbor/harbor-core + tag: v2.14.2 + # set the service account to be used, default if left empty + serviceAccountName: "" + # mount the service account token + automountServiceAccountToken: false + replicas: 1 + revisionHistoryLimit: 10 + ## Startup probe values + startupProbe: + enabled: true + initialDelaySeconds: 10 + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + extraEnvVars: [] + nodeSelector: {} + tolerations: [] + affinity: {} + # Spread Pods across failure-domains like regions, availability zones or nodes + topologySpreadConstraints: [] + # - maxSkew: 1 + # topologyKey: topology.kubernetes.io/zone + # nodeTaintsPolicy: Honor + # whenUnsatisfiable: DoNotSchedule + ## Additional deployment annotations + podAnnotations: {} + ## Additional deployment labels + podLabels: {} + ## Additional service annotations + serviceAnnotations: {} + ## The priority class to run the pod as + priorityClassName: + # containers to be run before the controller's container starts. + initContainers: [] + # Example: + # + # - name: wait + # image: busybox + # command: [ 'sh', '-c', "sleep 20" ] + ## User settings configuration json string + configureUserSettings: + # The provider for updating project quota(usage), there are 2 options, redis or db. + # By default it is implemented by db but you can configure it to redis which + # can improve the performance of high concurrent pushing to the same project, + # and reduce the database connections spike and occupies. + # Using redis will bring up some delay for quota usage updation for display, so only + # suggest switch provider to redis if you were ran into the db connections spike around + # the scenario of high concurrent pushing to same project, no improvment for other scenes. + quotaUpdateProvider: db # Or redis + # Secret is used when core server communicates with other components. + # If a secret key is not specified, Helm will generate one. Alternatively set existingSecret to use an existing secret + # Must be a string of 16 chars. + secret: "" + # Fill in the name of a kubernetes secret if you want to use your own + # If using existingSecret, the key must be secret + existingSecret: "" + # Fill the name of a kubernetes secret if you want to use your own + # TLS certificate and private key for token encryption/decryption. + # The secret must contain keys named: + # "tls.key" - the private key + # "tls.crt" - the certificate + secretName: "" + # If not specifying a preexisting secret, a secret can be created from tokenKey and tokenCert and used instead. + # If none of secretName, tokenKey, and tokenCert are specified, an ephemeral key and certificate will be autogenerated. + # tokenKey and tokenCert must BOTH be set or BOTH unset. + # The tokenKey value is formatted as a multiline string containing a PEM-encoded RSA key, indented one more than tokenKey on the following line. + tokenKey: | + # If tokenKey is set, the value of tokenCert must be set as a PEM-encoded certificate signed by tokenKey, and supplied as a multiline string, indented one more than tokenCert on the following line. + tokenCert: | + # The XSRF key. Will be generated automatically if it isn't specified + # While you specified, Please make sure it is 32 characters, otherwise would have validation issue at the harbor-core runtime + # https://github.com/goharbor/harbor/pull/21154 + xsrfKey: "" + # If using existingSecret, the key is defined by core.existingXsrfSecretKey + existingXsrfSecret: "" + # If using existingSecret, the key + existingXsrfSecretKey: CSRF_KEY + # The time duration for async update artifact pull_time and repository + # pull_count, the unit is second. Will be 10 seconds if it isn't set. + # eg. artifactPullAsyncFlushDuration: 10 + artifactPullAsyncFlushDuration: + gdpr: + deleteUser: false + auditLogsCompliant: false + +jobservice: + image: + repository: goharbor/harbor-jobservice + tag: v2.14.2 + # set the service account to be used, default if left empty + serviceAccountName: "" + # mount the service account token + automountServiceAccountToken: false + replicas: 1 + revisionHistoryLimit: 10 + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + extraEnvVars: [] + nodeSelector: {} + tolerations: [] + affinity: {} + # Spread Pods across failure-domains like regions, availability zones or nodes + topologySpreadConstraints: + # - maxSkew: 1 + # topologyKey: topology.kubernetes.io/zone + # nodeTaintsPolicy: Honor + # whenUnsatisfiable: DoNotSchedule + ## Additional deployment annotations + podAnnotations: {} + ## Additional deployment labels + podLabels: {} + ## The priority class to run the pod as + priorityClassName: + # containers to be run before the controller's container starts. + initContainers: [] + # Example: + # + # - name: wait + # image: busybox + # command: [ 'sh', '-c', "sleep 20" ] + maxJobWorkers: 10 + # The logger for jobs: "file", "database" or "stdout" + jobLoggers: + - file + # - database + # - stdout + # The jobLogger sweeper duration (ignored if `jobLogger` is `stdout`) + loggerSweeperDuration: 14 #days + notification: + webhook_job_max_retry: 3 + webhook_job_http_client_timeout: 3 # in seconds + reaper: + # the max time to wait for a task to finish, if unfinished after max_update_hours, the task will be mark as error, but the task will continue to run, default value is 24 + max_update_hours: 24 + # the max time for execution in running state without new task created + max_dangling_hours: 168 + # Secret is used when job service communicates with other components. + # If a secret key is not specified, Helm will generate one. + # Must be a string of 16 chars. + secret: "" + # Use an existing secret resource + existingSecret: "" + # Key within the existing secret for the job service secret + existingSecretKey: JOBSERVICE_SECRET + +registry: + registry: + image: + repository: goharbor/registry-photon + tag: v2.14.2 + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + extraEnvVars: [] + controller: + image: + repository: goharbor/harbor-registryctl + tag: v2.14.2 + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + extraEnvVars: [] + # set the service account to be used, default if left empty + serviceAccountName: "" + # mount the service account token + automountServiceAccountToken: false + replicas: 1 + revisionHistoryLimit: 10 + nodeSelector: {} + tolerations: [] + affinity: {} + # Spread Pods across failure-domains like regions, availability zones or nodes + topologySpreadConstraints: [] + # - maxSkew: 1 + # topologyKey: topology.kubernetes.io/zone + # nodeTaintsPolicy: Honor + # whenUnsatisfiable: DoNotSchedule + ## Additional deployment annotations + podAnnotations: {} + ## Additional deployment labels + podLabels: {} + ## The priority class to run the pod as + priorityClassName: + # containers to be run before the controller's container starts. + initContainers: [] + # Example: + # + # - name: wait + # image: busybox + # command: [ 'sh', '-c', "sleep 20" ] + # Secret is used to secure the upload state from client + # and registry storage backend. + # See: https://github.com/distribution/distribution/blob/release/2.8/docs/configuration.md#http + # If a secret key is not specified, Helm will generate one. + # Must be a string of 16 chars. + secret: "" + # Use an existing secret resource + existingSecret: "" + # Key within the existing secret for the registry service secret + existingSecretKey: REGISTRY_HTTP_SECRET + # If true, the registry returns relative URLs in Location headers. The client is responsible for resolving the correct URL. + relativeurls: false + credentials: + username: "harbor_registry_user" + password: "harbor_registry_password" + # If using existingSecret, the key must be REGISTRY_PASSWD and REGISTRY_HTPASSWD + existingSecret: "" + # Login and password in htpasswd string format. Excludes `registry.credentials.username` and `registry.credentials.password`. May come in handy when integrating with tools like argocd or flux. This allows the same line to be generated each time the template is rendered, instead of the `htpasswd` function from helm, which generates different lines each time because of the salt. + # htpasswdString: $apr1$XLefHzeG$Xl4.s00sMSCCcMyJljSZb0 # example string + htpasswdString: "" + middleware: + enabled: false + type: cloudFront + cloudFront: + baseurl: example.cloudfront.net + keypairid: KEYPAIRID + duration: 3000s + ipfilteredby: none + # The secret key that should be present is CLOUDFRONT_KEY_DATA, which should be the encoded private key + # that allows access to CloudFront + privateKeySecret: "my-secret" + # enable purge _upload directories + upload_purging: + enabled: true + # remove files in _upload directories which exist for a period of time, default is one week. + age: 168h + # the interval of the purge operations + interval: 24h + dryrun: false + +trivy: + # enabled the flag to enable Trivy scanner + enabled: true + image: + # repository the repository for Trivy adapter image + repository: goharbor/trivy-adapter-photon + # tag the tag for Trivy adapter image + tag: v2.14.2 + # set the service account to be used, default if left empty + serviceAccountName: "" + # mount the service account token + automountServiceAccountToken: false + # replicas the number of Pod replicas + replicas: 1 + resources: + requests: + cpu: 200m + memory: 512Mi + limits: + cpu: 1 + memory: 1Gi + extraEnvVars: [] + nodeSelector: {} + tolerations: [] + affinity: {} + # Spread Pods across failure-domains like regions, availability zones or nodes + topologySpreadConstraints: [] + # - maxSkew: 1 + # topologyKey: topology.kubernetes.io/zone + # nodeTaintsPolicy: Honor + # whenUnsatisfiable: DoNotSchedule + ## Additional deployment annotations + podAnnotations: {} + ## Additional deployment labels + podLabels: {} + ## The priority class to run the pod as + priorityClassName: + # containers to be run before the controller's container starts. + initContainers: [] + # Example: + # + # - name: wait + # image: busybox + # command: [ 'sh', '-c', "sleep 20" ] + # debugMode the flag to enable Trivy debug mode with more verbose scanning log + debugMode: false + # vulnType a comma-separated list of vulnerability types. Possible values are `os` and `library`. + vulnType: "os,library" + # severity a comma-separated list of severities to be checked + severity: "UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL" + # ignoreUnfixed the flag to display only fixed vulnerabilities + ignoreUnfixed: false + # insecure the flag to skip verifying registry certificate + insecure: false + # gitHubToken the GitHub access token to download Trivy DB + # + # Trivy DB contains vulnerability information from NVD, Red Hat, and many other upstream vulnerability databases. + # It is downloaded by Trivy from the GitHub release page https://github.com/aquasecurity/trivy-db/releases and cached + # in the local file system (`/home/scanner/.cache/trivy/db/trivy.db`). In addition, the database contains the update + # timestamp so Trivy can detect whether it should download a newer version from the Internet or use the cached one. + # Currently, the database is updated every 12 hours and published as a new release to GitHub. + # + # Anonymous downloads from GitHub are subject to the limit of 60 requests per hour. Normally such rate limit is enough + # for production operations. If, for any reason, it's not enough, you could increase the rate limit to 5000 + # requests per hour by specifying the GitHub access token. For more details on GitHub rate limiting please consult + # https://developer.github.com/v3/#rate-limiting + # + # You can create a GitHub token by following the instructions in + # https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line + gitHubToken: "" + # skipUpdate the flag to disable Trivy DB downloads from GitHub + # + # You might want to set the value of this flag to `true` in test or CI/CD environments to avoid GitHub rate limiting issues. + # If the value is set to `true` you have to manually download the `trivy.db` file and mount it in the + # `/home/scanner/.cache/trivy/db/trivy.db` path. + skipUpdate: false + # skipJavaDBUpdate If the flag is enabled you have to manually download the `trivy-java.db` file and mount it in the + # `/home/scanner/.cache/trivy/java-db/trivy-java.db` path + skipJavaDBUpdate: false + # The dbRepository and javaDBRepository flags can take multiple values, improving reliability when downloading databases. + # Databases are downloaded in priority order until one is successful. + # An attempt to download from the next repository is only made if a temporary error is received (e.g. status 429 or 5xx). + # + # OCI repository(ies) to retrieve the trivy vulnerability database in order of priority + dbRepository: + - "mirror.gcr.io/aquasec/trivy-db" + - "ghcr.io/aquasecurity/trivy-db" + # OCI repository(ies) to retrieve the Java trivy vulnerability database in order of priority + javaDBRepository: + - "mirror.gcr.io/aquasec/trivy-java-db" + - "ghcr.io/aquasecurity/trivy-java-db" + # The offlineScan option prevents Trivy from sending API requests to identify dependencies. + # + # Scanning JAR files and pom.xml may require Internet access for better detection, but this option tries to avoid it. + # For example, the offline mode will not try to resolve transitive dependencies in pom.xml when the dependency doesn't + # exist in the local repositories. It means a number of detected vulnerabilities might be fewer in offline mode. + # It would work if all the dependencies are in local. + # This option doesn’t affect DB download. You need to specify skipUpdate as well as offlineScan in an air-gapped environment. + offlineScan: false + # Comma-separated list of what security issues to detect. Defaults to `vuln`. + securityCheck: "vuln" + # The duration to wait for scan completion + timeout: 5m0s + +database: + # if external database is used, set "type" to "external" + # and fill the connection information in "external" section + type: internal + internal: + image: + repository: goharbor/harbor-db + tag: v2.14.2 + # set the service account to be used, default if left empty + serviceAccountName: "" + # mount the service account token + automountServiceAccountToken: false + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + # The timeout used in livenessProbe; 1 to 5 seconds + livenessProbe: + timeoutSeconds: 1 + # The timeout used in readinessProbe; 1 to 5 seconds + readinessProbe: + timeoutSeconds: 1 + extraEnvVars: [] + nodeSelector: {} + tolerations: [] + affinity: {} + ## The priority class to run the pod as + priorityClassName: + # containers to be run before the controller's container starts. + extrInitContainers: [] + # Example: + # + # - name: wait + # image: busybox + # command: [ 'sh', '-c', "sleep 20" ] + # The initial superuser password for internal database + password: "changeit" + # The size limit for Shared memory, pgSQL use it for shared_buffer + # More details see: + # https://github.com/goharbor/harbor/issues/15034 + shmSizeLimit: 512Mi + initContainer: + migrator: {} + # resources: + # requests: + # memory: 128Mi + # cpu: 100m + permissions: {} + # resources: + # requests: + # memory: 128Mi + # cpu: 100m + external: + host: "192.168.0.1" + port: "5432" + username: "user" + password: "password" + coreDatabase: "registry" + # if using existing secret, the key must be "password" + existingSecret: "" + # "disable" - No SSL + # "require" - Always SSL (skip verification) + # "verify-ca" - Always SSL (verify that the certificate presented by the + # server was signed by a trusted CA) + # "verify-full" - Always SSL (verify that the certification presented by the + # server was signed by a trusted CA and the server host name matches the one + # in the certificate) + sslmode: "disable" + # The maximum number of connections in the idle connection pool per pod (core+exporter). + # If it <=0, no idle connections are retained. + maxIdleConns: 100 + # The maximum number of open connections to the database per pod (core+exporter). + # If it <= 0, then there is no limit on the number of open connections. + # Note: the default number of connections is 1024 for harbor's postgres. + maxOpenConns: 900 + ## Additional deployment annotations + podAnnotations: {} + ## Additional deployment labels + podLabels: {} + +redis: + # if external Redis is used, set "type" to "external" + # and fill the connection information in "external" section + type: internal + internal: + image: + repository: goharbor/redis-photon + tag: v2.14.2 + # set the service account to be used, default if left empty + serviceAccountName: "" + # mount the service account token + automountServiceAccountToken: false + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + extraEnvVars: [] + nodeSelector: {} + tolerations: [] + affinity: {} + ## The priority class to run the pod as + priorityClassName: + # containers to be run before the controller's container starts. + initContainers: [] + # Example: + # + # - name: wait + # image: busybox + # command: [ 'sh', '-c', "sleep 20" ] + # # jobserviceDatabaseIndex defaults to "1" + # # registryDatabaseIndex defaults to "2" + # # trivyAdapterIndex defaults to "5" + # # harborDatabaseIndex defaults to "0", but it can be configured to "6", this config is optional + # # cacheLayerDatabaseIndex defaults to "0", but it can be configured to "7", this config is optional + jobserviceDatabaseIndex: "1" + registryDatabaseIndex: "2" + trivyAdapterIndex: "5" + # harborDatabaseIndex: "6" + # cacheLayerDatabaseIndex: "7" + external: + # support redis, redis+sentinel + # addr for redis: : + # addr for redis+sentinel: :,:,: + addr: "192.168.0.2:6379" + # The name of the set of Redis instances to monitor, it must be set to support redis+sentinel + sentinelMasterSet: "" + # TLS configuration for redis connection + # only server-authentication is supported, mTLS for redis connection is not supported + # tls connection will be disable by default + # Once `tlsOptions.enable` set as true, tls/ssl connection will be used for redis + # Please set the `caBundleSecretName` in this configuration file which conatins redis server rootCA if it is self-signed. + # The secret must contain keys named "ca.crt" which will be injected into the trust store + tlsOptions: + enable: false + # The "coreDatabaseIndex" must be "0" as the library Harbor + # used doesn't support configuring it + # harborDatabaseIndex defaults to "0", but it can be configured to "6", this config is optional + # cacheLayerDatabaseIndex defaults to "0", but it can be configured to "7", this config is optional + coreDatabaseIndex: "0" + jobserviceDatabaseIndex: "1" + registryDatabaseIndex: "2" + trivyAdapterIndex: "5" + # harborDatabaseIndex: "6" + # cacheLayerDatabaseIndex: "7" + # username field can be an empty string, and it will be authenticated against the default user + username: "" + password: "" + # If using existingSecret, the key must be REDIS_PASSWORD, if ACL mode enabled, also inlcudes data of username, the keys must be REDIS_USERNAME + existingSecret: "" + ## Additional deployment annotations + podAnnotations: {} + ## Additional deployment labels + podLabels: {} + +exporter: + image: + repository: goharbor/harbor-exporter + tag: v2.14.2 + serviceAccountName: "" + # mount the service account token + automountServiceAccountToken: false + replicas: 1 + revisionHistoryLimit: 10 + # resources: + # requests: + # memory: 256Mi + # cpu: 100m + extraEnvVars: [] + podAnnotations: {} + ## Additional deployment labels + podLabels: {} + nodeSelector: {} + tolerations: [] + affinity: {} + # Spread Pods across failure-domains like regions, availability zones or nodes + topologySpreadConstraints: [] + ## The priority class to run the pod as + priorityClassName: + # - maxSkew: 1 + # topologyKey: topology.kubernetes.io/zone + # nodeTaintsPolicy: Honor + # whenUnsatisfiable: DoNotSchedule + cacheDuration: 23 + cacheCleanInterval: 14400 diff --git a/packages/system/harbor/values.yaml b/packages/system/harbor/values.yaml new file mode 100644 index 00000000..4c8388fa --- /dev/null +++ b/packages/system/harbor/values.yaml @@ -0,0 +1 @@ +harbor: {} From 305495d023c652455ce63cf415ab49edaea2678e Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Mon, 16 Feb 2026 19:51:12 +0300 Subject: [PATCH 312/889] [harbor] Fix YAML quoting for Go template values in ApplicationDefinition Quote resourceNames values starting with {{ to prevent YAML parser from interpreting them as flow mappings. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/system/harbor-rd/cozyrds/harbor.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/system/harbor-rd/cozyrds/harbor.yaml b/packages/system/harbor-rd/cozyrds/harbor.yaml index da8cfb62..33c0eaab 100644 --- a/packages/system/harbor-rd/cozyrds/harbor.yaml +++ b/packages/system/harbor-rd/cozyrds/harbor.yaml @@ -34,14 +34,14 @@ spec: exclude: [] include: - resourceNames: - - {{ .name }}-credentials + - "{{ .name }}-credentials" services: exclude: [] include: - resourceNames: - - {{ .name }} + - "{{ .name }}" ingresses: exclude: [] include: - resourceNames: - - {{ .name }}-ingress + - "{{ .name }}-ingress" From 2dd3c03279c47470a6c3014071b46cc36dd6831d Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Mon, 16 Feb 2026 20:36:52 +0300 Subject: [PATCH 313/889] [harbor] Use CPNG and redis-operator instead of internal databases Replace Harbor's internal PostgreSQL with CloudNativePG operator and internal Redis with redis-operator (RedisFailover), following established Cozystack patterns from seaweedfs and redis apps. Additional fixes from code review: - Fix registry resources nesting level (registry.registry/controller) - Persist token CA across upgrades to prevent JWT invalidation - Update values schema and ApplicationDefinition Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- hack/e2e-apps/harbor.bats | 6 +- packages/extra/harbor/README.md | 14 +-- packages/extra/harbor/templates/harbor.yaml | 66 ++++++++--- packages/extra/harbor/values.schema.json | 106 ++---------------- packages/extra/harbor/values.yaml | 20 ++-- packages/system/harbor-rd/cozyrds/harbor.yaml | 4 +- .../system/harbor/templates/database.yaml | 24 ++++ packages/system/harbor/templates/redis.yaml | 66 +++++++++++ packages/system/harbor/values.yaml | 8 ++ 9 files changed, 175 insertions(+), 139 deletions(-) create mode 100644 packages/system/harbor/templates/database.yaml create mode 100644 packages/system/harbor/templates/redis.yaml diff --git a/hack/e2e-apps/harbor.bats b/hack/e2e-apps/harbor.bats index 8d1daecb..c28eee3a 100644 --- a/hack/e2e-apps/harbor.bats +++ b/hack/e2e-apps/harbor.bats @@ -28,12 +28,10 @@ spec: resourcesPreset: "nano" database: size: 2Gi - resources: {} - resourcesPreset: "nano" + replicas: 1 redis: size: 1Gi - resources: {} - resourcesPreset: "nano" + replicas: 1 EOF sleep 5 kubectl -n tenant-test wait hr $name --timeout=60s --for=condition=ready diff --git a/packages/extra/harbor/README.md b/packages/extra/harbor/README.md index 62bf072e..6425e6fe 100644 --- a/packages/extra/harbor/README.md +++ b/packages/extra/harbor/README.md @@ -39,16 +39,10 @@ Harbor is an open source trusted cloud native registry project that stores, sign | `trivy.resources.cpu` | Number of CPU cores allocated. | `quantity` | `""` | | `trivy.resources.memory` | Amount of memory allocated. | `quantity` | `""` | | `trivy.resourcesPreset` | Default sizing preset used when `resources` is omitted. | `string` | `nano` | -| `database` | Internal PostgreSQL database configuration. | `object` | `{}` | +| `database` | PostgreSQL database configuration. | `object` | `{}` | | `database.size` | Persistent Volume size for database storage. | `quantity` | `5Gi` | -| `database.resources` | Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied. | `object` | `{}` | -| `database.resources.cpu` | Number of CPU cores allocated. | `quantity` | `""` | -| `database.resources.memory` | Amount of memory allocated. | `quantity` | `""` | -| `database.resourcesPreset` | Default sizing preset used when `resources` is omitted. | `string` | `nano` | -| `redis` | Internal Redis cache configuration. | `object` | `{}` | +| `database.replicas` | Number of database instances. | `int` | `2` | +| `redis` | Redis cache configuration. | `object` | `{}` | | `redis.size` | Persistent Volume size for cache storage. | `quantity` | `1Gi` | -| `redis.resources` | Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied. | `object` | `{}` | -| `redis.resources.cpu` | Number of CPU cores allocated. | `quantity` | `""` | -| `redis.resources.memory` | Amount of memory allocated. | `quantity` | `""` | -| `redis.resourcesPreset` | Default sizing preset used when `resources` is omitted. | `string` | `nano` | +| `redis.replicas` | Number of Redis replicas. | `int` | `2` | diff --git a/packages/extra/harbor/templates/harbor.yaml b/packages/extra/harbor/templates/harbor.yaml index 23c06641..ceef4f1f 100644 --- a/packages/extra/harbor/templates/harbor.yaml +++ b/packages/extra/harbor/templates/harbor.yaml @@ -8,6 +8,18 @@ {{- $adminPassword = index $existingSecret.data "admin-password" | b64dec }} {{- end }} +{{- $existingCoreSecret := lookup "v1" "Secret" .Release.Namespace (printf "%s-core" .Release.Name) }} +{{- $tokenKey := "" }} +{{- $tokenCert := "" }} +{{- if $existingCoreSecret }} + {{- if hasKey $existingCoreSecret.data "tls.key" }} + {{- $tokenKey = index $existingCoreSecret.data "tls.key" | b64dec }} + {{- end }} + {{- if hasKey $existingCoreSecret.data "tls.crt" }} + {{- $tokenCert = index $existingCoreSecret.data "tls.crt" | b64dec }} + {{- end }} +{{- end }} + apiVersion: v1 kind: Secret metadata: @@ -42,6 +54,18 @@ spec: - kind: Secret name: cozystack-values values: + db: + replicas: {{ .Values.database.replicas }} + size: {{ .Values.database.size }} + {{- with .Values.storageClass }} + storageClass: {{ . }} + {{- end }} + redis: + replicas: {{ .Values.redis.replicas }} + size: {{ .Values.redis.size }} + {{- with .Values.storageClass }} + storageClass: {{ . }} + {{- end }} harbor: fullnameOverride: {{ .Release.Name }} harborAdminPassword: {{ $adminPassword | quote }} @@ -61,16 +85,6 @@ spec: {{- with .Values.storageClass }} storageClass: {{ . }} {{- end }} - database: - size: {{ .Values.database.size }} - {{- with .Values.storageClass }} - storageClass: {{ . }} - {{- end }} - redis: - size: {{ .Values.redis.size }} - {{- with .Values.storageClass }} - storageClass: {{ . }} - {{- end }} {{- if .Values.trivy.enabled }} trivy: size: {{ .Values.trivy.size }} @@ -81,9 +95,16 @@ spec: portal: resources: {{- include "cozy-lib.resources.defaultingSanitize" (list "nano" (dict) $) | nindent 10 }} core: + {{- if $tokenKey }} + tokenKey: {{ $tokenKey | quote }} + tokenCert: {{ $tokenCert | quote }} + {{- end }} resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.core.resourcesPreset .Values.core.resources $) | nindent 10 }} registry: - resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.registry.resourcesPreset .Values.registry.resources $) | nindent 10 }} + registry: + resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.registry.resourcesPreset .Values.registry.resources $) | nindent 12 }} + controller: + resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.registry.resourcesPreset .Values.registry.resources $) | nindent 12 }} jobservice: resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.jobservice.resourcesPreset .Values.jobservice.resources $) | nindent 10 }} trivy: @@ -92,13 +113,24 @@ spec: resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.trivy.resourcesPreset .Values.trivy.resources $) | nindent 10 }} {{- end }} database: - type: internal - internal: - resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.database.resourcesPreset .Values.database.resources $) | nindent 12 }} + type: external + external: + host: "{{ .Release.Name }}-db-rw" + port: "5432" + username: app + coreDatabase: app + sslmode: disable + existingSecret: "{{ .Release.Name }}-db-app" redis: - type: internal - internal: - resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.redis.resourcesPreset .Values.redis.resources $) | nindent 12 }} + type: external + external: + addr: "rfs-{{ .Release.Name }}-redis:26379" + sentinelMasterSet: "mymaster" + existingSecret: "{{ .Release.Name }}-redis-auth" + coreDatabaseIndex: "0" + jobserviceDatabaseIndex: "1" + registryDatabaseIndex: "2" + trivyAdapterIndex: "5" metrics: enabled: true serviceMonitor: diff --git a/packages/extra/harbor/values.schema.json b/packages/extra/harbor/values.schema.json index a4632de7..2fa0beba 100644 --- a/packages/extra/harbor/values.schema.json +++ b/packages/extra/harbor/values.schema.json @@ -57,59 +57,18 @@ } }, "database": { - "description": "Internal PostgreSQL database configuration.", + "description": "PostgreSQL database configuration.", "type": "object", "default": {}, "required": [ + "replicas", "size" ], "properties": { - "resources": { - "description": "Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.", - "type": "object", - "default": {}, - "properties": { - "cpu": { - "description": "Number of CPU cores allocated.", - "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": "Amount of memory allocated.", - "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 - } - } - }, - "resourcesPreset": { - "description": "Default sizing preset used when `resources` is omitted.", - "type": "string", - "default": "nano", - "enum": [ - "nano", - "micro", - "small", - "medium", - "large", - "xlarge", - "2xlarge" - ] + "replicas": { + "description": "Number of database instances.", + "type": "integer", + "default": 2 }, "size": { "description": "Persistent Volume size for database storage.", @@ -187,59 +146,18 @@ } }, "redis": { - "description": "Internal Redis cache configuration.", + "description": "Redis cache configuration.", "type": "object", "default": {}, "required": [ + "replicas", "size" ], "properties": { - "resources": { - "description": "Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.", - "type": "object", - "default": {}, - "properties": { - "cpu": { - "description": "Number of CPU cores allocated.", - "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": "Amount of memory allocated.", - "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 - } - } - }, - "resourcesPreset": { - "description": "Default sizing preset used when `resources` is omitted.", - "type": "string", - "default": "nano", - "enum": [ - "nano", - "micro", - "small", - "medium", - "large", - "xlarge", - "2xlarge" - ] + "replicas": { + "description": "Number of Redis replicas.", + "type": "integer", + "default": 2 }, "size": { "description": "Persistent Volume size for cache storage.", diff --git a/packages/extra/harbor/values.yaml b/packages/extra/harbor/values.yaml index 50d2ff5f..c5d89b1d 100644 --- a/packages/extra/harbor/values.yaml +++ b/packages/extra/harbor/values.yaml @@ -67,24 +67,20 @@ trivy: resources: {} resourcesPreset: "nano" -## @typedef {struct} Database - Internal PostgreSQL database configuration. +## @typedef {struct} Database - PostgreSQL database configuration (provisioned via CloudNativePG). ## @field {quantity} size - Persistent Volume size for database storage. -## @field {Resources} [resources] - Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied. -## @field {ResourcesPreset} [resourcesPreset] - Default sizing preset used when `resources` is omitted. +## @field {int} replicas - Number of database instances. -## @param {Database} database - Internal PostgreSQL database configuration. +## @param {Database} database - PostgreSQL database configuration. database: size: 5Gi - resources: {} - resourcesPreset: "nano" + replicas: 2 -## @typedef {struct} Redis - Internal Redis cache configuration. +## @typedef {struct} Redis - Redis cache configuration (provisioned via redis-operator). ## @field {quantity} size - Persistent Volume size for cache storage. -## @field {Resources} [resources] - Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied. -## @field {ResourcesPreset} [resourcesPreset] - Default sizing preset used when `resources` is omitted. +## @field {int} replicas - Number of Redis replicas. -## @param {Redis} redis - Internal Redis cache configuration. +## @param {Redis} redis - Redis cache configuration. redis: size: 1Gi - resources: {} - resourcesPreset: "nano" + replicas: 2 diff --git a/packages/system/harbor-rd/cozyrds/harbor.yaml b/packages/system/harbor-rd/cozyrds/harbor.yaml index 33c0eaab..8f159a45 100644 --- a/packages/system/harbor-rd/cozyrds/harbor.yaml +++ b/packages/system/harbor-rd/cozyrds/harbor.yaml @@ -8,7 +8,7 @@ spec: singular: harbor plural: harbors openAPISchema: |- - {"title":"Chart Values","type":"object","properties":{"core":{"description":"Core API server configuration.","type":"object","default":{},"properties":{"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"small","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}},"database":{"description":"Internal PostgreSQL database configuration.","type":"object","default":{},"required":["size"],"properties":{"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"nano","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]},"size":{"description":"Persistent Volume size for database storage.","default":"5Gi","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":"Hostname for external access to Harbor (defaults to 'harbor' subdomain for the tenant host).","type":"string","default":""},"jobservice":{"description":"Background job service configuration.","type":"object","default":{},"properties":{"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"nano","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}},"redis":{"description":"Internal Redis cache configuration.","type":"object","default":{},"required":["size"],"properties":{"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"nano","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]},"size":{"description":"Persistent Volume size for cache storage.","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}}},"registry":{"description":"Container image registry configuration.","type":"object","default":{},"required":["size"],"properties":{"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"small","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]},"size":{"description":"Persistent Volume size for container image storage.","default":"50Gi","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}}},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":""},"trivy":{"description":"Trivy vulnerability scanner configuration.","type":"object","default":{},"required":["enabled","size"],"properties":{"enabled":{"description":"Enable or disable the vulnerability scanner.","type":"boolean","default":true},"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"nano","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]},"size":{"description":"Persistent Volume size for vulnerability database cache.","default":"5Gi","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":{"core":{"description":"Core API server configuration.","type":"object","default":{},"properties":{"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"small","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}},"database":{"description":"PostgreSQL database configuration.","type":"object","default":{},"required":["replicas","size"],"properties":{"replicas":{"description":"Number of database instances.","type":"integer","default":2},"size":{"description":"Persistent Volume size for database storage.","default":"5Gi","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":"Hostname for external access to Harbor (defaults to 'harbor' subdomain for the tenant host).","type":"string","default":""},"jobservice":{"description":"Background job service configuration.","type":"object","default":{},"properties":{"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"nano","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}},"redis":{"description":"Redis cache configuration.","type":"object","default":{},"required":["replicas","size"],"properties":{"replicas":{"description":"Number of Redis replicas.","type":"integer","default":2},"size":{"description":"Persistent Volume size for cache storage.","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}}},"registry":{"description":"Container image registry configuration.","type":"object","default":{},"required":["size"],"properties":{"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"small","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]},"size":{"description":"Persistent Volume size for container image storage.","default":"50Gi","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}}},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":""},"trivy":{"description":"Trivy vulnerability scanner configuration.","type":"object","default":{},"required":["enabled","size"],"properties":{"enabled":{"description":"Enable or disable the vulnerability scanner.","type":"boolean","default":true},"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"nano","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]},"size":{"description":"Persistent Volume size for vulnerability database cache.","default":"5Gi","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: @@ -29,7 +29,7 @@ spec: - registry - container icon: "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHJvbGU9ImltZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHZpZXdCb3g9IjEuMDUgLTEuOTUgMzU5LjQxIDM2MS42NiI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQiIHgxPSIyNjQuNzkiIHgyPSIyNjcuMjciIHkxPSI5NTIuMzkiIHkyPSI5NTIuMzkiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMzAuNDMgMCAwIC0zMC40MyAtNzk1NS4yMiAyOTI4NS43NSkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiM2MGI5MzIiLz48c3RvcCBvZmZzZXQ9Ii4yOCIgc3RvcC1jb2xvcj0iIzYwYjkzMiIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzM2N2MzNCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQtMiIgeDE9IjI2My43NyIgeDI9IjI2Ni4yNiIgeTE9Ijk1NS42NSIgeTI9Ijk1NS42NSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgyNy4yMSAwIDAgLTI3LjIxIC03MDczLjg1IDI2MTY5LjQxKSIgeGxpbms6aHJlZj0iI2xpbmVhci1ncmFkaWVudCIvPjxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50LTMiIHgxPSIyNjMuMjgiIHgyPSIyNjUuNzYiIHkxPSI5NTMuNzQiIHkyPSI5NTMuNzQiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMjUuNzUgMCAwIC0yNS43NSAtNjY3MS4xMyAyNDgxMi4yMykiIHhsaW5rOmhyZWY9IiNsaW5lYXItZ3JhZGllbnQiLz48bGluZWFyR3JhZGllbnQgaWQ9ImxpbmVhci1ncmFkaWVudC00IiB4MT0iMjYzLjc3IiB4Mj0iMjY2LjI1IiB5MT0iOTUzLjIiIHkyPSI5NTMuMiIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgyNy4xIDAgMCAtMjcuMSAtNzA0MC45IDI2MTAyLjQ5KSIgeGxpbms6aHJlZj0iI2xpbmVhci1ncmFkaWVudCIvPjxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50LTUiIHgxPSIyNjIuNzMiIHgyPSIyNjUuMjEiIHkxPSI5NTQuMzQiIHkyPSI5NTQuMzQiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMjQuNCAwIDAgLTI0LjQgLTYzMDEuMzYgMjM1MjEuOTcpIiB4bGluazpocmVmPSIjbGluZWFyLWdyYWRpZW50Ii8+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQtNiIgeDE9IjI3Mi4xNCIgeDI9IjI3NC42MiIgeTE9Ijk1NS4xNSIgeTI9Ijk1NS4xNSIgZ3JhZGllbnRUcmFuc2Zvcm09InNjYWxlKDY2LjA5IC02Ni4wOSkgcm90YXRlKDM2LjUyIDE1ODguMTUzIDY4LjE0OCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiM0NTk2ZDgiLz48c3RvcCBvZmZzZXQ9Ii4yIiBzdG9wLWNvbG9yPSIjNDU5NmQ4Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjZmZmIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9ImxpbmVhci1ncmFkaWVudC03IiB4MT0iMjcwLjY1IiB4Mj0iMjczLjEzIiB5MT0iOTUyLjM4IiB5Mj0iOTUyLjM4IiBncmFkaWVudFRyYW5zZm9ybT0ic2NhbGUoNzcuOCAtNzcuOCkgcm90YXRlKC0xMS41NCAtNDU4Ny4yMDkgMTgwMy4zMjMpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjNDE5NGQ3Ii8+PHN0b3Agb2Zmc2V0PSIuMiIgc3RvcC1jb2xvcj0iIzQxOTRkNyIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI2ZmZiIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQtOCIgeDE9IjI3MC45NyIgeDI9IjI3My40NSIgeTE9Ijk1My43NSIgeTI9Ijk1My43NSIgZ3JhZGllbnRUcmFuc2Zvcm09InNjYWxlKDcxLjM1IC03MS4zNSkgcm90YXRlKDEwLjIzIDU0NzcuMzcgLTEwMjQuNjAyKSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iIzQxOTRkNyIvPjxzdG9wIG9mZnNldD0iLjMzIiBzdG9wLWNvbG9yPSIjNDQ5NmQ4Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjZmZmIi8+PC9saW5lYXJHcmFkaWVudD48Y2xpcFBhdGggaWQ9ImNsaXAtcGF0aCI+PHBhdGggZD0iTTE3NC40IDMwMi41MmwtNjguNjUtMzAuMjYtMy44MyA0My4yMSA3NS41IDIzLjk4LTMuMDItMzYuOTN6IiBjbGFzcz0iY2xzLTEiLz48L2NsaXBQYXRoPjxjbGlwUGF0aCBpZD0iY2xpcC1wYXRoLTIiPjxwYXRoIGQ9Ik0xMTkuMTkgMTM1LjM4aDM4LjU1djMyLjg5aC05LjE1di0xNGExMC4xMyAxMC4xMyAwIDEgMC0yMC4yNiAwdjE0aC05LjE1em0tNy43IDcybDU3LjIgMjUuMjEtMy45NC00OC4yNGg3LjQ5di0xNi4wOGgtNS41NXYtMzIuODloNS41NXYtOS40NWwtMzAuODYtMzAuMTl2LTIuMTJhMi45MSAyLjkxIDAgMCAwLTUuODIgMHYyLjEybC0zMC44NiAzMC4xOXY5LjQ1aDUuNTZ2MzIuODloLTUuNTZ2MTYuMTJoOC44MnoiIGNsYXNzPSJjbHMtMSIvPjwvY2xpcFBhdGg+PGNsaXBQYXRoIGlkPSJjbGlwLXBhdGgtMyI+PHBhdGggZD0iTTEwOC4xNCAyNDUuMjhsNjMuODggMjguMTYtLjk2LTExLjczLTYxLjk2LTI3LjMtLjk2IDEwLjg3eiIgY2xhc3M9ImNscy0xIi8+PC9jbGlwUGF0aD48Y2xpcFBhdGggaWQ9ImNsaXAtcGF0aC00Ij48cGF0aCBkPSJNMTA2LjQ2IDI2NC4yMWw2Ny4yMyAyOS42My0uOTYtMTEuNzItNjUuMzEtMjguNzgtLjk2IDEwLjg3eiIgY2xhc3M9ImNscy0xIi8+PC9jbGlwUGF0aD48Y2xpcFBhdGggaWQ9ImNsaXAtcGF0aC01Ij48cGF0aCBkPSJNMTEwLjc3IDIxNS40OGwtLjk2IDEwLjg3IDYwLjU0IDI2LjY4LS45NS0xMS43Mi01OC42My0yNS44M3oiIGNsYXNzPSJjbHMtMSIvPjwvY2xpcFBhdGg+PGNsaXBQYXRoIGlkPSJjbGlwLXBhdGgtNiI+PHBhdGggZD0iTTMxMy4xMyA2Ny41OWExNzUuMzEgMTc1LjMxIDAgMCAwLTI5Ljc1LTI4LjEzYy0xLjU3LTEuMTctMy4xOC0yLjMtNC43OS0zLjQyTDI1NiA1OS41bC04Mi4zNCA4NS42MyAxMTMuNDEtNTguNjggMjkuMDctMTVjLTEuMDEtMS4zMS0xLjk4LTIuNjItMy4wMS0zLjg2eiIgY2xhc3M9ImNscy0xIi8+PC9jbGlwUGF0aD48Y2xpcFBhdGggaWQ9ImNsaXAtcGF0aC03Ij48cGF0aCBkPSJNMzUzLjU5IDE3Ny42MWMwLTItLjE0LTQtLjIyLTUuOTNsLTMyLjIxLTIuMzEtMTQ3LjQ3LTEwLjU4TDMxOC4zNiAyMDlsMzAuNDEgMTAuNTVjLjA5LS4zNi4xOS0uNzEuMjgtMS4wOGExNzMuNjUgMTczLjY1IDAgMCAwIDQuNTctMzkuNDd2LTEuMzd6IiBjbGFzcz0iY2xzLTEiLz48L2NsaXBQYXRoPjxjbGlwUGF0aCBpZD0iY2xpcC1wYXRoLTgiPjxwYXRoIGQ9Ik0zNDguODQgMTM4LjYxYTE3Mi41NSAxNzIuNTUgMCAwIDAtMTMuODQtMzcuN2wtMzAuOTQgOS45NEwxNzUuOTIgMTUybDE0Mi00LjgzIDMyLjUtMS4xMWMtLjUtMi40OC0uOTktNC45OC0xLjU4LTcuNDV6IiBjbGFzcz0iY2xzLTEiLz48L2NsaXBQYXRoPjxzdHlsZT4uY2xzLTF7ZmlsbDpub25lfS5jbHMtMTN7ZmlsbDojNjk2NTY2fTwvc3R5bGU+PC9kZWZzPjxnIGlkPSJnMTIiPjxwYXRoIGlkPSJwYXRoMTQiIGZpbGw9IiNmZmYiIGQ9Ik0zMC44OSAxNzlhMTQ4Ljg3IDE0OC44NyAwIDEgMSAxNDguODcgMTQ4Ljg1QTE0OC44NyAxNDguODcgMCAwIDEgMzAuODkgMTc5Ii8+PGcgaWQ9ImczMCIgY2xpcC1wYXRoPSJ1cmwoI2NsaXAtcGF0aCkiPjxnIGlkPSJnMzIiPjxwYXRoIGlkPSJwYXRoNDYiIGZpbGw9InVybCgjbGluZWFyLWdyYWRpZW50KSIgZD0iTTE3NC40IDMwMi41MmwtNjguNjUtMzAuMjUtMy44MiA0My4yIDc1LjUgMjQtMy0zNi45MyIvPjwvZz48L2c+PGcgaWQ9Imc0OCIgY2xpcC1wYXRoPSJ1cmwoI2NsaXAtcGF0aC0yKSI+PGcgaWQ9Imc1MCI+PHBhdGggaWQ9InBhdGg2NCIgZmlsbD0idXJsKCNsaW5lYXItZ3JhZGllbnQtMikiIGQ9Ik0xMTkuMTkgMTM1LjM4aDM4LjU1djMyLjg5aC05LjE1di0xNGExMC4xMyAxMC4xMyAwIDEgMC0yMC4yNiAwdjE0aC05LjE1em0tNy43IDcybDU3LjIgMjUuMjEtMy45NC00OC4yNGg3LjQ5di0xNi4wOGgtNS41NXYtMzIuODloNS41NXYtOS40NWwtMzAuODYtMzAuMTl2LTIuMTJhMi45MSAyLjkxIDAgMCAwLTUuODIgMHYyLjEybC0zMC44NiAzMC4xOXY5LjQ1aDUuNTZ2MzIuODloLTUuNTZ2MTYuMTJoOC44MmwtMiAyMyIvPjwvZz48L2c+PGcgaWQ9Imc2NiIgY2xpcC1wYXRoPSJ1cmwoI2NsaXAtcGF0aC0zKSI+PGcgaWQ9Imc2OCI+PHBhdGggaWQ9InBhdGg4MiIgZmlsbD0idXJsKCNsaW5lYXItZ3JhZGllbnQtMykiIGQ9Ik0xMDguMTMgMjQ1LjI4TDE3MiAyNzMuNDRsLTEtMTEuNzItNjItMjcuMzEtMSAxMC44NyIvPjwvZz48L2c+PGcgaWQ9Imc4NCIgY2xpcC1wYXRoPSJ1cmwoI2NsaXAtcGF0aC00KSI+PGcgaWQ9Imc4NiI+PHBhdGggaWQ9InBhdGgxMDAiIGZpbGw9InVybCgjbGluZWFyLWdyYWRpZW50LTQpIiBkPSJNMTA2LjQ2IDI2NC4yMWw2Ny4yMyAyOS42My0xLTExLjcyLTY1LjMxLTI4Ljc4LTEgMTAuODciLz48L2c+PC9nPjxnIGlkPSJnMTAyIiBjbGlwLXBhdGg9InVybCgjY2xpcC1wYXRoLTUpIj48ZyBpZD0iZzEwNCI+PHBhdGggaWQ9InBhdGgxMTgiIGZpbGw9InVybCgjbGluZWFyLWdyYWRpZW50LTUpIiBkPSJNMTEwLjc3IDIxNS40OGwtMSAxMC44OEwxNzAuMzUgMjUzbC0xLTExLjcyLTU4LjYyLTI1LjgzIi8+PC9nPjwvZz48cGF0aCBpZD0icGF0aDEyMCIgZD0iTTMwNC4wNyAxMTAuODVsMzAuOTMtOS45NGMtLjExLS4yMi0uMjEtLjQ1LS4zMi0uNjZhMTc0LjQxIDE3NC40MSAwIDAgMC0xOC41NS0yOC44M2wtMjkuMDcgMTVhMTQyLjcxIDE0Mi43MSAwIDAgMSAxNi43MyAyMy44N2MuMS4xNy4xOC4zNS4yNy41MyIgY2xhc3M9ImNscy0xMyIvPjxwYXRoIGlkPSJwYXRoMTIyIiBkPSJNMzIxLjE1IDE2OS4zN2wzMi4yMSAyLjMxYTE3Mi44NiAxNzIuODYgMCAwIDAtMy0yNS41OWwtMzIuNSAxLjExYTE0MSAxNDEgMCAwIDEgMy4yNSAyMi4xNyIgY2xhc3M9ImNscy0xMyIvPjxwYXRoIGlkPSJwYXRoMTI0IiBkPSJNMTgyIDMyMC44MWMtNzguMiAwLTE0MS44My02My42Mi0xNDEuODMtMTQxLjgyUzEwMy44MiAzNy4xNiAxODIgMzcuMTZhMTQwLjkzIDE0MC45MyAwIDAgMSA3Ni4zIDIyLjM0TDI4MC44NSAzNkExNzIuODYgMTcyLjg2IDAgMCAwIDE4MiA1LjExQzg2LjE1IDUuMTEgOC4xNSA4My4xMSA4LjE1IDE3OXM3OCAxNzMuODUgMTczLjg1IDE3My44NWM4MS45IDAgMTUwLjY5LTU3IDE2OS0xMzMuMzJMMzIwLjYyIDIwOWMtMTMuOCA2My44NC03MC42OSAxMTEuODMtMTM4LjYgMTExLjgzIiBjbGFzcz0iY2xzLTEzIi8+PGcgaWQ9ImcxMjYiIGNsaXAtcGF0aD0idXJsKCNjbGlwLXBhdGgtNikiPjxnIGlkPSJnMTI4Ij48cGF0aCBpZD0icGF0aDE0MiIgZmlsbD0idXJsKCNsaW5lYXItZ3JhZGllbnQtNikiIGQ9Ik0zMTMuMTMgNjcuNTlhMTc1LjMxIDE3NS4zMSAwIDAgMC0yOS43NS0yOC4xM2MtMS41Ny0xLjE3LTMuMTgtMi4zLTQuNzktMy40MkwyNTYgNTkuNWwtODIuMzQgODUuNjMgMTEzLjQxLTU4LjY4IDI5LjA3LTE1Yy0xLTEuMjctMi0yLjU4LTMtMy44MiIvPjwvZz48L2c+PGcgaWQ9ImcxNDQiIGNsaXAtcGF0aD0idXJsKCNjbGlwLXBhdGgtNykiPjxnIGlkPSJnMTQ2Ij48cGF0aCBpZD0icGF0aDE2MCIgZmlsbD0idXJsKCNsaW5lYXItZ3JhZGllbnQtNykiIGQ9Ik0zNTMuNTkgMTc3LjYxYzAtMi0uMTQtNC0uMjItNS45M2wtMzIuMjEtMi4zMS0xNDcuNDctMTAuNThMMzE4LjM2IDIwOWwzMC40MSAxMC41NWMuMDktLjM2LjE5LS43MS4yOC0xLjA4YTE3My42NSAxNzMuNjUgMCAwIDAgNC41Ny0zOS40N3YtMS4zNyIvPjwvZz48L2c+PGcgaWQ9ImcxNjIiIGNsaXAtcGF0aD0idXJsKCNjbGlwLXBhdGgtOCkiPjxnIGlkPSJnMTY0Ij48cGF0aCBpZD0icGF0aDE3OCIgZmlsbD0idXJsKCNsaW5lYXItZ3JhZGllbnQtOCkiIGQ9Ik0zNDguODQgMTM4LjYxYTE3Mi41NSAxNzIuNTUgMCAwIDAtMTMuODQtMzcuN2wtMzAuOTQgOS45NEwxNzUuOTIgMTUybDE0Mi00LjgzIDMyLjUtMS4xMWMtLjQ4LTIuNTEtMS01LTEuNTYtNy40OCIvPjwvZz48L2c+PC9nPjwvc3ZnPg==" - keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "host"], ["spec", "storageClass"], ["spec", "core"], ["spec", "core", "resources"], ["spec", "core", "resourcesPreset"], ["spec", "registry"], ["spec", "registry", "size"], ["spec", "registry", "resources"], ["spec", "registry", "resourcesPreset"], ["spec", "jobservice"], ["spec", "jobservice", "resources"], ["spec", "jobservice", "resourcesPreset"], ["spec", "trivy"], ["spec", "trivy", "enabled"], ["spec", "trivy", "size"], ["spec", "trivy", "resources"], ["spec", "trivy", "resourcesPreset"], ["spec", "database"], ["spec", "database", "size"], ["spec", "database", "resources"], ["spec", "database", "resourcesPreset"], ["spec", "redis"], ["spec", "redis", "size"], ["spec", "redis", "resources"], ["spec", "redis", "resourcesPreset"]] + keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "host"], ["spec", "storageClass"], ["spec", "core"], ["spec", "core", "resources"], ["spec", "core", "resourcesPreset"], ["spec", "registry"], ["spec", "registry", "size"], ["spec", "registry", "resources"], ["spec", "registry", "resourcesPreset"], ["spec", "jobservice"], ["spec", "jobservice", "resources"], ["spec", "jobservice", "resourcesPreset"], ["spec", "trivy"], ["spec", "trivy", "enabled"], ["spec", "trivy", "size"], ["spec", "trivy", "resources"], ["spec", "trivy", "resourcesPreset"], ["spec", "database"], ["spec", "database", "size"], ["spec", "database", "replicas"], ["spec", "redis"], ["spec", "redis", "size"], ["spec", "redis", "replicas"]] secrets: exclude: [] include: diff --git a/packages/system/harbor/templates/database.yaml b/packages/system/harbor/templates/database.yaml new file mode 100644 index 00000000..5d1827aa --- /dev/null +++ b/packages/system/harbor/templates/database.yaml @@ -0,0 +1,24 @@ +--- +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: {{ .Values.harbor.fullnameOverride }}-db +spec: + instances: {{ .Values.db.replicas }} + storage: + size: {{ .Values.db.size }} + {{- with .Values.db.storageClass }} + storageClass: {{ . }} + {{- end }} + monitoring: + enablePodMonitor: true + resources: + limits: + cpu: "1" + memory: 2048Mi + requests: + cpu: 100m + memory: 512Mi + inheritedMetadata: + labels: + policy.cozystack.io/allow-to-apiserver: "true" diff --git a/packages/system/harbor/templates/redis.yaml b/packages/system/harbor/templates/redis.yaml new file mode 100644 index 00000000..7a71f1ce --- /dev/null +++ b/packages/system/harbor/templates/redis.yaml @@ -0,0 +1,66 @@ +{{- $existingPassword := lookup "v1" "Secret" .Release.Namespace (printf "%s-redis-auth" .Values.harbor.fullnameOverride) }} +{{- $password := randAlphaNum 32 | b64enc }} +{{- if $existingPassword }} + {{- $password = index $existingPassword.data "password" }} +{{- end }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.harbor.fullnameOverride }}-redis-auth +data: + password: {{ $password }} +--- +apiVersion: databases.spotahome.com/v1 +kind: RedisFailover +metadata: + name: {{ .Values.harbor.fullnameOverride }}-redis + labels: + app.kubernetes.io/instance: {{ .Values.harbor.fullnameOverride }}-redis + app.kubernetes.io/managed-by: {{ .Release.Service }} +spec: + sentinel: + replicas: 3 + resources: + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 50m + memory: 64Mi + redis: + replicas: {{ .Values.redis.replicas }} + resources: + limits: + cpu: "1" + memory: 1024Mi + requests: + cpu: 100m + memory: 256Mi + storage: + persistentVolumeClaim: + metadata: + name: redisfailover-persistent-data + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .Values.redis.size }} + {{- with .Values.redis.storageClass }} + storageClassName: {{ . }} + {{- end }} + exporter: + enabled: true + image: oliver006/redis_exporter:v1.55.0-alpine + args: + - --web.telemetry-path + - /metrics + env: + - name: REDIS_EXPORTER_LOG_FORMAT + value: txt + customConfig: + - tcp-keepalive 0 + - loglevel notice + auth: + secretPath: {{ .Values.harbor.fullnameOverride }}-redis-auth diff --git a/packages/system/harbor/values.yaml b/packages/system/harbor/values.yaml index 4c8388fa..40e924e0 100644 --- a/packages/system/harbor/values.yaml +++ b/packages/system/harbor/values.yaml @@ -1 +1,9 @@ harbor: {} +db: + replicas: 2 + size: 5Gi + storageClass: "" +redis: + replicas: 2 + size: 1Gi + storageClass: "" From 0c85639fed98637a598e019387e4be8f680c43ef Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Mon, 16 Feb 2026 21:17:10 +0300 Subject: [PATCH 314/889] [harbor] Move to apps/, use S3 via BucketClaim for registry storage Move Harbor from packages/extra/ to packages/apps/ as it is a self-sufficient end-user application, not a singleton tenant module. Update bundle entry from system to paas accordingly. Replace registry PVC storage with S3 via COSI BucketClaim/BucketAccess, provisioned from the namespace's SeaweedFS instance. S3 credentials are injected into the HelmRelease via valuesFrom with targetPath. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- hack/e2e-apps/harbor.bats | 1 - packages/{extra => apps}/harbor/Chart.yaml | 0 packages/{extra => apps}/harbor/Makefile | 0 packages/{extra => apps}/harbor/README.md | 1 - .../{extra => apps}/harbor/charts/cozy-lib | 0 .../{extra => apps}/harbor/logos/harbor.svg | 0 packages/apps/harbor/templates/bucket.yaml | 19 +++++++++++++ .../templates/dashboard-resourcemap.yaml | 0 .../harbor/templates/harbor.yaml | 28 ++++++++++++++----- .../harbor/templates/ingress.yaml | 0 .../{extra => apps}/harbor/values.schema.json | 17 ----------- packages/{extra => apps}/harbor/values.yaml | 2 -- .../platform/sources/harbor-application.yaml | 2 +- .../core/platform/templates/bundles/paas.yaml | 1 + .../platform/templates/bundles/system.yaml | 1 - packages/system/harbor-rd/cozyrds/harbor.yaml | 6 ++-- 16 files changed, 45 insertions(+), 33 deletions(-) rename packages/{extra => apps}/harbor/Chart.yaml (100%) rename packages/{extra => apps}/harbor/Makefile (100%) rename packages/{extra => apps}/harbor/README.md (97%) rename packages/{extra => apps}/harbor/charts/cozy-lib (100%) rename packages/{extra => apps}/harbor/logos/harbor.svg (100%) create mode 100644 packages/apps/harbor/templates/bucket.yaml rename packages/{extra => apps}/harbor/templates/dashboard-resourcemap.yaml (100%) rename packages/{extra => apps}/harbor/templates/harbor.yaml (87%) rename packages/{extra => apps}/harbor/templates/ingress.yaml (100%) rename packages/{extra => apps}/harbor/values.schema.json (94%) rename packages/{extra => apps}/harbor/values.yaml (97%) diff --git a/hack/e2e-apps/harbor.bats b/hack/e2e-apps/harbor.bats index c28eee3a..d4a0055f 100644 --- a/hack/e2e-apps/harbor.bats +++ b/hack/e2e-apps/harbor.bats @@ -15,7 +15,6 @@ spec: resources: {} resourcesPreset: "nano" registry: - size: 5Gi resources: {} resourcesPreset: "nano" jobservice: diff --git a/packages/extra/harbor/Chart.yaml b/packages/apps/harbor/Chart.yaml similarity index 100% rename from packages/extra/harbor/Chart.yaml rename to packages/apps/harbor/Chart.yaml diff --git a/packages/extra/harbor/Makefile b/packages/apps/harbor/Makefile similarity index 100% rename from packages/extra/harbor/Makefile rename to packages/apps/harbor/Makefile diff --git a/packages/extra/harbor/README.md b/packages/apps/harbor/README.md similarity index 97% rename from packages/extra/harbor/README.md rename to packages/apps/harbor/README.md index 6425e6fe..df44ca8d 100644 --- a/packages/extra/harbor/README.md +++ b/packages/apps/harbor/README.md @@ -22,7 +22,6 @@ Harbor is an open source trusted cloud native registry project that stores, sign | `core.resources.memory` | Amount of memory allocated. | `quantity` | `""` | | `core.resourcesPreset` | Default sizing preset used when `resources` is omitted. | `string` | `small` | | `registry` | Container image registry configuration. | `object` | `{}` | -| `registry.size` | Persistent Volume size for container image storage. | `quantity` | `50Gi` | | `registry.resources` | Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied. | `object` | `{}` | | `registry.resources.cpu` | Number of CPU cores allocated. | `quantity` | `""` | | `registry.resources.memory` | Amount of memory allocated. | `quantity` | `""` | diff --git a/packages/extra/harbor/charts/cozy-lib b/packages/apps/harbor/charts/cozy-lib similarity index 100% rename from packages/extra/harbor/charts/cozy-lib rename to packages/apps/harbor/charts/cozy-lib diff --git a/packages/extra/harbor/logos/harbor.svg b/packages/apps/harbor/logos/harbor.svg similarity index 100% rename from packages/extra/harbor/logos/harbor.svg rename to packages/apps/harbor/logos/harbor.svg diff --git a/packages/apps/harbor/templates/bucket.yaml b/packages/apps/harbor/templates/bucket.yaml new file mode 100644 index 00000000..a9e60988 --- /dev/null +++ b/packages/apps/harbor/templates/bucket.yaml @@ -0,0 +1,19 @@ +{{- $seaweedfs := .Values._namespace.seaweedfs }} +apiVersion: objectstorage.k8s.io/v1alpha1 +kind: BucketClaim +metadata: + name: {{ .Release.Name }}-registry +spec: + bucketClassName: {{ $seaweedfs }} + protocols: + - s3 +--- +apiVersion: objectstorage.k8s.io/v1alpha1 +kind: BucketAccess +metadata: + name: {{ .Release.Name }}-registry +spec: + bucketAccessClassName: {{ $seaweedfs }} + bucketClaimName: {{ .Release.Name }}-registry + credentialsSecretName: {{ .Release.Name }}-registry-bucket + protocol: s3 diff --git a/packages/extra/harbor/templates/dashboard-resourcemap.yaml b/packages/apps/harbor/templates/dashboard-resourcemap.yaml similarity index 100% rename from packages/extra/harbor/templates/dashboard-resourcemap.yaml rename to packages/apps/harbor/templates/dashboard-resourcemap.yaml diff --git a/packages/extra/harbor/templates/harbor.yaml b/packages/apps/harbor/templates/harbor.yaml similarity index 87% rename from packages/extra/harbor/templates/harbor.yaml rename to packages/apps/harbor/templates/harbor.yaml index ceef4f1f..59758950 100644 --- a/packages/extra/harbor/templates/harbor.yaml +++ b/packages/apps/harbor/templates/harbor.yaml @@ -53,6 +53,18 @@ spec: valuesFrom: - kind: Secret name: cozystack-values + - kind: Secret + name: {{ .Release.Name }}-registry-bucket + valuesKey: accessKey + targetPath: harbor.persistence.imageChartStorage.s3.accesskey + - kind: Secret + name: {{ .Release.Name }}-registry-bucket + valuesKey: secretKey + targetPath: harbor.persistence.imageChartStorage.s3.secretkey + - kind: Secret + name: {{ .Release.Name }}-registry-bucket + valuesKey: endpoint + targetPath: harbor.persistence.imageChartStorage.s3.regionendpoint values: db: replicas: {{ .Values.database.replicas }} @@ -79,19 +91,21 @@ spec: persistence: enabled: true resourcePolicy: "keep" + imageChartStorage: + type: s3 + s3: + region: us-east-1 + bucket: {{ .Release.Name }}-registry + secure: false + v4auth: true + {{- if .Values.trivy.enabled }} persistentVolumeClaim: - registry: - size: {{ .Values.registry.size }} - {{- with .Values.storageClass }} - storageClass: {{ . }} - {{- end }} - {{- if .Values.trivy.enabled }} trivy: size: {{ .Values.trivy.size }} {{- with .Values.storageClass }} storageClass: {{ . }} {{- end }} - {{- end }} + {{- end }} portal: resources: {{- include "cozy-lib.resources.defaultingSanitize" (list "nano" (dict) $) | nindent 10 }} core: diff --git a/packages/extra/harbor/templates/ingress.yaml b/packages/apps/harbor/templates/ingress.yaml similarity index 100% rename from packages/extra/harbor/templates/ingress.yaml rename to packages/apps/harbor/templates/ingress.yaml diff --git a/packages/extra/harbor/values.schema.json b/packages/apps/harbor/values.schema.json similarity index 94% rename from packages/extra/harbor/values.schema.json rename to packages/apps/harbor/values.schema.json index 2fa0beba..1352f5dc 100644 --- a/packages/extra/harbor/values.schema.json +++ b/packages/apps/harbor/values.schema.json @@ -179,9 +179,6 @@ "description": "Container image registry configuration.", "type": "object", "default": {}, - "required": [ - "size" - ], "properties": { "resources": { "description": "Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.", @@ -229,20 +226,6 @@ "xlarge", "2xlarge" ] - }, - "size": { - "description": "Persistent Volume size for container image storage.", - "default": "50Gi", - "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 } } }, diff --git a/packages/extra/harbor/values.yaml b/packages/apps/harbor/values.yaml similarity index 97% rename from packages/extra/harbor/values.yaml rename to packages/apps/harbor/values.yaml index c5d89b1d..de44eaff 100644 --- a/packages/extra/harbor/values.yaml +++ b/packages/apps/harbor/values.yaml @@ -35,13 +35,11 @@ core: resourcesPreset: "small" ## @typedef {struct} Registry - Container image registry configuration. -## @field {quantity} size - Persistent Volume size for container image storage. ## @field {Resources} [resources] - Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied. ## @field {ResourcesPreset} [resourcesPreset] - Default sizing preset used when `resources` is omitted. ## @param {Registry} registry - Container image registry configuration. registry: - size: 50Gi resources: {} resourcesPreset: "small" diff --git a/packages/core/platform/sources/harbor-application.yaml b/packages/core/platform/sources/harbor-application.yaml index 31aa70ee..b05d7a01 100644 --- a/packages/core/platform/sources/harbor-application.yaml +++ b/packages/core/platform/sources/harbor-application.yaml @@ -20,7 +20,7 @@ spec: - name: harbor-system path: system/harbor - name: harbor - path: extra/harbor + path: apps/harbor libraries: ["cozy-lib"] - name: harbor-rd path: system/harbor-rd diff --git a/packages/core/platform/templates/bundles/paas.yaml b/packages/core/platform/templates/bundles/paas.yaml index 5a478f9d..6a126304 100644 --- a/packages/core/platform/templates/bundles/paas.yaml +++ b/packages/core/platform/templates/bundles/paas.yaml @@ -11,6 +11,7 @@ {{include "cozystack.platform.package.default" (list "cozystack.mongodb-operator" $) }} {{include "cozystack.platform.package.default" (list "cozystack.clickhouse-application" $) }} {{include "cozystack.platform.package.default" (list "cozystack.foundationdb-application" $) }} +{{include "cozystack.platform.package.default" (list "cozystack.harbor-application" $) }} {{include "cozystack.platform.package.default" (list "cozystack.kafka-application" $) }} {{include "cozystack.platform.package.default" (list "cozystack.mariadb-application" $) }} {{include "cozystack.platform.package.default" (list "cozystack.mongodb-application" $) }} diff --git a/packages/core/platform/templates/bundles/system.yaml b/packages/core/platform/templates/bundles/system.yaml index 33b882c6..c24726ef 100644 --- a/packages/core/platform/templates/bundles/system.yaml +++ b/packages/core/platform/templates/bundles/system.yaml @@ -126,7 +126,6 @@ {{- $_ := set $tenantComponents "tenant" (dict "values" $tenantClusterValues) }} {{include "cozystack.platform.package" (list "cozystack.tenant-application" "default" $ $tenantComponents) }} {{include "cozystack.platform.package.default" (list "cozystack.ingress-application" $) }} -{{include "cozystack.platform.package.default" (list "cozystack.harbor-application" $) }} {{include "cozystack.platform.package.default" (list "cozystack.seaweedfs-application" $) }} {{include "cozystack.platform.package.default" (list "cozystack.info-application" $) }} {{include "cozystack.platform.package.default" (list "cozystack.monitoring-application" $) }} diff --git a/packages/system/harbor-rd/cozyrds/harbor.yaml b/packages/system/harbor-rd/cozyrds/harbor.yaml index 8f159a45..0112dc11 100644 --- a/packages/system/harbor-rd/cozyrds/harbor.yaml +++ b/packages/system/harbor-rd/cozyrds/harbor.yaml @@ -8,9 +8,9 @@ spec: singular: harbor plural: harbors openAPISchema: |- - {"title":"Chart Values","type":"object","properties":{"core":{"description":"Core API server configuration.","type":"object","default":{},"properties":{"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"small","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}},"database":{"description":"PostgreSQL database configuration.","type":"object","default":{},"required":["replicas","size"],"properties":{"replicas":{"description":"Number of database instances.","type":"integer","default":2},"size":{"description":"Persistent Volume size for database storage.","default":"5Gi","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":"Hostname for external access to Harbor (defaults to 'harbor' subdomain for the tenant host).","type":"string","default":""},"jobservice":{"description":"Background job service configuration.","type":"object","default":{},"properties":{"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"nano","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}},"redis":{"description":"Redis cache configuration.","type":"object","default":{},"required":["replicas","size"],"properties":{"replicas":{"description":"Number of Redis replicas.","type":"integer","default":2},"size":{"description":"Persistent Volume size for cache storage.","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}}},"registry":{"description":"Container image registry configuration.","type":"object","default":{},"required":["size"],"properties":{"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"small","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]},"size":{"description":"Persistent Volume size for container image storage.","default":"50Gi","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}}},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":""},"trivy":{"description":"Trivy vulnerability scanner configuration.","type":"object","default":{},"required":["enabled","size"],"properties":{"enabled":{"description":"Enable or disable the vulnerability scanner.","type":"boolean","default":true},"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"nano","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]},"size":{"description":"Persistent Volume size for vulnerability database cache.","default":"5Gi","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":{"core":{"description":"Core API server configuration.","type":"object","default":{},"properties":{"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"small","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}},"database":{"description":"PostgreSQL database configuration.","type":"object","default":{},"required":["replicas","size"],"properties":{"replicas":{"description":"Number of database instances.","type":"integer","default":2},"size":{"description":"Persistent Volume size for database storage.","default":"5Gi","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":"Hostname for external access to Harbor (defaults to 'harbor' subdomain for the tenant host).","type":"string","default":""},"jobservice":{"description":"Background job service configuration.","type":"object","default":{},"properties":{"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"nano","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}},"redis":{"description":"Redis cache configuration.","type":"object","default":{},"required":["replicas","size"],"properties":{"replicas":{"description":"Number of Redis replicas.","type":"integer","default":2},"size":{"description":"Persistent Volume size for cache storage.","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}}},"registry":{"description":"Container image registry configuration.","type":"object","default":{},"properties":{"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"small","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":""},"trivy":{"description":"Trivy vulnerability scanner configuration.","type":"object","default":{},"required":["enabled","size"],"properties":{"enabled":{"description":"Enable or disable the vulnerability scanner.","type":"boolean","default":true},"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"nano","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]},"size":{"description":"Persistent Volume size for vulnerability database cache.","default":"5Gi","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: "" + prefix: "harbor-" labels: sharding.fluxcd.io/key: tenants internal.cozystack.io/tenantmodule: "true" @@ -29,7 +29,7 @@ spec: - registry - container icon: "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHJvbGU9ImltZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHZpZXdCb3g9IjEuMDUgLTEuOTUgMzU5LjQxIDM2MS42NiI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQiIHgxPSIyNjQuNzkiIHgyPSIyNjcuMjciIHkxPSI5NTIuMzkiIHkyPSI5NTIuMzkiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMzAuNDMgMCAwIC0zMC40MyAtNzk1NS4yMiAyOTI4NS43NSkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiM2MGI5MzIiLz48c3RvcCBvZmZzZXQ9Ii4yOCIgc3RvcC1jb2xvcj0iIzYwYjkzMiIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzM2N2MzNCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQtMiIgeDE9IjI2My43NyIgeDI9IjI2Ni4yNiIgeTE9Ijk1NS42NSIgeTI9Ijk1NS42NSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgyNy4yMSAwIDAgLTI3LjIxIC03MDczLjg1IDI2MTY5LjQxKSIgeGxpbms6aHJlZj0iI2xpbmVhci1ncmFkaWVudCIvPjxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50LTMiIHgxPSIyNjMuMjgiIHgyPSIyNjUuNzYiIHkxPSI5NTMuNzQiIHkyPSI5NTMuNzQiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMjUuNzUgMCAwIC0yNS43NSAtNjY3MS4xMyAyNDgxMi4yMykiIHhsaW5rOmhyZWY9IiNsaW5lYXItZ3JhZGllbnQiLz48bGluZWFyR3JhZGllbnQgaWQ9ImxpbmVhci1ncmFkaWVudC00IiB4MT0iMjYzLjc3IiB4Mj0iMjY2LjI1IiB5MT0iOTUzLjIiIHkyPSI5NTMuMiIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgyNy4xIDAgMCAtMjcuMSAtNzA0MC45IDI2MTAyLjQ5KSIgeGxpbms6aHJlZj0iI2xpbmVhci1ncmFkaWVudCIvPjxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50LTUiIHgxPSIyNjIuNzMiIHgyPSIyNjUuMjEiIHkxPSI5NTQuMzQiIHkyPSI5NTQuMzQiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMjQuNCAwIDAgLTI0LjQgLTYzMDEuMzYgMjM1MjEuOTcpIiB4bGluazpocmVmPSIjbGluZWFyLWdyYWRpZW50Ii8+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQtNiIgeDE9IjI3Mi4xNCIgeDI9IjI3NC42MiIgeTE9Ijk1NS4xNSIgeTI9Ijk1NS4xNSIgZ3JhZGllbnRUcmFuc2Zvcm09InNjYWxlKDY2LjA5IC02Ni4wOSkgcm90YXRlKDM2LjUyIDE1ODguMTUzIDY4LjE0OCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiM0NTk2ZDgiLz48c3RvcCBvZmZzZXQ9Ii4yIiBzdG9wLWNvbG9yPSIjNDU5NmQ4Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjZmZmIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9ImxpbmVhci1ncmFkaWVudC03IiB4MT0iMjcwLjY1IiB4Mj0iMjczLjEzIiB5MT0iOTUyLjM4IiB5Mj0iOTUyLjM4IiBncmFkaWVudFRyYW5zZm9ybT0ic2NhbGUoNzcuOCAtNzcuOCkgcm90YXRlKC0xMS41NCAtNDU4Ny4yMDkgMTgwMy4zMjMpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjNDE5NGQ3Ii8+PHN0b3Agb2Zmc2V0PSIuMiIgc3RvcC1jb2xvcj0iIzQxOTRkNyIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI2ZmZiIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQtOCIgeDE9IjI3MC45NyIgeDI9IjI3My40NSIgeTE9Ijk1My43NSIgeTI9Ijk1My43NSIgZ3JhZGllbnRUcmFuc2Zvcm09InNjYWxlKDcxLjM1IC03MS4zNSkgcm90YXRlKDEwLjIzIDU0NzcuMzcgLTEwMjQuNjAyKSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iIzQxOTRkNyIvPjxzdG9wIG9mZnNldD0iLjMzIiBzdG9wLWNvbG9yPSIjNDQ5NmQ4Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjZmZmIi8+PC9saW5lYXJHcmFkaWVudD48Y2xpcFBhdGggaWQ9ImNsaXAtcGF0aCI+PHBhdGggZD0iTTE3NC40IDMwMi41MmwtNjguNjUtMzAuMjYtMy44MyA0My4yMSA3NS41IDIzLjk4LTMuMDItMzYuOTN6IiBjbGFzcz0iY2xzLTEiLz48L2NsaXBQYXRoPjxjbGlwUGF0aCBpZD0iY2xpcC1wYXRoLTIiPjxwYXRoIGQ9Ik0xMTkuMTkgMTM1LjM4aDM4LjU1djMyLjg5aC05LjE1di0xNGExMC4xMyAxMC4xMyAwIDEgMC0yMC4yNiAwdjE0aC05LjE1em0tNy43IDcybDU3LjIgMjUuMjEtMy45NC00OC4yNGg3LjQ5di0xNi4wOGgtNS41NXYtMzIuODloNS41NXYtOS40NWwtMzAuODYtMzAuMTl2LTIuMTJhMi45MSAyLjkxIDAgMCAwLTUuODIgMHYyLjEybC0zMC44NiAzMC4xOXY5LjQ1aDUuNTZ2MzIuODloLTUuNTZ2MTYuMTJoOC44MnoiIGNsYXNzPSJjbHMtMSIvPjwvY2xpcFBhdGg+PGNsaXBQYXRoIGlkPSJjbGlwLXBhdGgtMyI+PHBhdGggZD0iTTEwOC4xNCAyNDUuMjhsNjMuODggMjguMTYtLjk2LTExLjczLTYxLjk2LTI3LjMtLjk2IDEwLjg3eiIgY2xhc3M9ImNscy0xIi8+PC9jbGlwUGF0aD48Y2xpcFBhdGggaWQ9ImNsaXAtcGF0aC00Ij48cGF0aCBkPSJNMTA2LjQ2IDI2NC4yMWw2Ny4yMyAyOS42My0uOTYtMTEuNzItNjUuMzEtMjguNzgtLjk2IDEwLjg3eiIgY2xhc3M9ImNscy0xIi8+PC9jbGlwUGF0aD48Y2xpcFBhdGggaWQ9ImNsaXAtcGF0aC01Ij48cGF0aCBkPSJNMTEwLjc3IDIxNS40OGwtLjk2IDEwLjg3IDYwLjU0IDI2LjY4LS45NS0xMS43Mi01OC42My0yNS44M3oiIGNsYXNzPSJjbHMtMSIvPjwvY2xpcFBhdGg+PGNsaXBQYXRoIGlkPSJjbGlwLXBhdGgtNiI+PHBhdGggZD0iTTMxMy4xMyA2Ny41OWExNzUuMzEgMTc1LjMxIDAgMCAwLTI5Ljc1LTI4LjEzYy0xLjU3LTEuMTctMy4xOC0yLjMtNC43OS0zLjQyTDI1NiA1OS41bC04Mi4zNCA4NS42MyAxMTMuNDEtNTguNjggMjkuMDctMTVjLTEuMDEtMS4zMS0xLjk4LTIuNjItMy4wMS0zLjg2eiIgY2xhc3M9ImNscy0xIi8+PC9jbGlwUGF0aD48Y2xpcFBhdGggaWQ9ImNsaXAtcGF0aC03Ij48cGF0aCBkPSJNMzUzLjU5IDE3Ny42MWMwLTItLjE0LTQtLjIyLTUuOTNsLTMyLjIxLTIuMzEtMTQ3LjQ3LTEwLjU4TDMxOC4zNiAyMDlsMzAuNDEgMTAuNTVjLjA5LS4zNi4xOS0uNzEuMjgtMS4wOGExNzMuNjUgMTczLjY1IDAgMCAwIDQuNTctMzkuNDd2LTEuMzd6IiBjbGFzcz0iY2xzLTEiLz48L2NsaXBQYXRoPjxjbGlwUGF0aCBpZD0iY2xpcC1wYXRoLTgiPjxwYXRoIGQ9Ik0zNDguODQgMTM4LjYxYTE3Mi41NSAxNzIuNTUgMCAwIDAtMTMuODQtMzcuN2wtMzAuOTQgOS45NEwxNzUuOTIgMTUybDE0Mi00LjgzIDMyLjUtMS4xMWMtLjUtMi40OC0uOTktNC45OC0xLjU4LTcuNDV6IiBjbGFzcz0iY2xzLTEiLz48L2NsaXBQYXRoPjxzdHlsZT4uY2xzLTF7ZmlsbDpub25lfS5jbHMtMTN7ZmlsbDojNjk2NTY2fTwvc3R5bGU+PC9kZWZzPjxnIGlkPSJnMTIiPjxwYXRoIGlkPSJwYXRoMTQiIGZpbGw9IiNmZmYiIGQ9Ik0zMC44OSAxNzlhMTQ4Ljg3IDE0OC44NyAwIDEgMSAxNDguODcgMTQ4Ljg1QTE0OC44NyAxNDguODcgMCAwIDEgMzAuODkgMTc5Ii8+PGcgaWQ9ImczMCIgY2xpcC1wYXRoPSJ1cmwoI2NsaXAtcGF0aCkiPjxnIGlkPSJnMzIiPjxwYXRoIGlkPSJwYXRoNDYiIGZpbGw9InVybCgjbGluZWFyLWdyYWRpZW50KSIgZD0iTTE3NC40IDMwMi41MmwtNjguNjUtMzAuMjUtMy44MiA0My4yIDc1LjUgMjQtMy0zNi45MyIvPjwvZz48L2c+PGcgaWQ9Imc0OCIgY2xpcC1wYXRoPSJ1cmwoI2NsaXAtcGF0aC0yKSI+PGcgaWQ9Imc1MCI+PHBhdGggaWQ9InBhdGg2NCIgZmlsbD0idXJsKCNsaW5lYXItZ3JhZGllbnQtMikiIGQ9Ik0xMTkuMTkgMTM1LjM4aDM4LjU1djMyLjg5aC05LjE1di0xNGExMC4xMyAxMC4xMyAwIDEgMC0yMC4yNiAwdjE0aC05LjE1em0tNy43IDcybDU3LjIgMjUuMjEtMy45NC00OC4yNGg3LjQ5di0xNi4wOGgtNS41NXYtMzIuODloNS41NXYtOS40NWwtMzAuODYtMzAuMTl2LTIuMTJhMi45MSAyLjkxIDAgMCAwLTUuODIgMHYyLjEybC0zMC44NiAzMC4xOXY5LjQ1aDUuNTZ2MzIuODloLTUuNTZ2MTYuMTJoOC44MmwtMiAyMyIvPjwvZz48L2c+PGcgaWQ9Imc2NiIgY2xpcC1wYXRoPSJ1cmwoI2NsaXAtcGF0aC0zKSI+PGcgaWQ9Imc2OCI+PHBhdGggaWQ9InBhdGg4MiIgZmlsbD0idXJsKCNsaW5lYXItZ3JhZGllbnQtMykiIGQ9Ik0xMDguMTMgMjQ1LjI4TDE3MiAyNzMuNDRsLTEtMTEuNzItNjItMjcuMzEtMSAxMC44NyIvPjwvZz48L2c+PGcgaWQ9Imc4NCIgY2xpcC1wYXRoPSJ1cmwoI2NsaXAtcGF0aC00KSI+PGcgaWQ9Imc4NiI+PHBhdGggaWQ9InBhdGgxMDAiIGZpbGw9InVybCgjbGluZWFyLWdyYWRpZW50LTQpIiBkPSJNMTA2LjQ2IDI2NC4yMWw2Ny4yMyAyOS42My0xLTExLjcyLTY1LjMxLTI4Ljc4LTEgMTAuODciLz48L2c+PC9nPjxnIGlkPSJnMTAyIiBjbGlwLXBhdGg9InVybCgjY2xpcC1wYXRoLTUpIj48ZyBpZD0iZzEwNCI+PHBhdGggaWQ9InBhdGgxMTgiIGZpbGw9InVybCgjbGluZWFyLWdyYWRpZW50LTUpIiBkPSJNMTEwLjc3IDIxNS40OGwtMSAxMC44OEwxNzAuMzUgMjUzbC0xLTExLjcyLTU4LjYyLTI1LjgzIi8+PC9nPjwvZz48cGF0aCBpZD0icGF0aDEyMCIgZD0iTTMwNC4wNyAxMTAuODVsMzAuOTMtOS45NGMtLjExLS4yMi0uMjEtLjQ1LS4zMi0uNjZhMTc0LjQxIDE3NC40MSAwIDAgMC0xOC41NS0yOC44M2wtMjkuMDcgMTVhMTQyLjcxIDE0Mi43MSAwIDAgMSAxNi43MyAyMy44N2MuMS4xNy4xOC4zNS4yNy41MyIgY2xhc3M9ImNscy0xMyIvPjxwYXRoIGlkPSJwYXRoMTIyIiBkPSJNMzIxLjE1IDE2OS4zN2wzMi4yMSAyLjMxYTE3Mi44NiAxNzIuODYgMCAwIDAtMy0yNS41OWwtMzIuNSAxLjExYTE0MSAxNDEgMCAwIDEgMy4yNSAyMi4xNyIgY2xhc3M9ImNscy0xMyIvPjxwYXRoIGlkPSJwYXRoMTI0IiBkPSJNMTgyIDMyMC44MWMtNzguMiAwLTE0MS44My02My42Mi0xNDEuODMtMTQxLjgyUzEwMy44MiAzNy4xNiAxODIgMzcuMTZhMTQwLjkzIDE0MC45MyAwIDAgMSA3Ni4zIDIyLjM0TDI4MC44NSAzNkExNzIuODYgMTcyLjg2IDAgMCAwIDE4MiA1LjExQzg2LjE1IDUuMTEgOC4xNSA4My4xMSA4LjE1IDE3OXM3OCAxNzMuODUgMTczLjg1IDE3My44NWM4MS45IDAgMTUwLjY5LTU3IDE2OS0xMzMuMzJMMzIwLjYyIDIwOWMtMTMuOCA2My44NC03MC42OSAxMTEuODMtMTM4LjYgMTExLjgzIiBjbGFzcz0iY2xzLTEzIi8+PGcgaWQ9ImcxMjYiIGNsaXAtcGF0aD0idXJsKCNjbGlwLXBhdGgtNikiPjxnIGlkPSJnMTI4Ij48cGF0aCBpZD0icGF0aDE0MiIgZmlsbD0idXJsKCNsaW5lYXItZ3JhZGllbnQtNikiIGQ9Ik0zMTMuMTMgNjcuNTlhMTc1LjMxIDE3NS4zMSAwIDAgMC0yOS43NS0yOC4xM2MtMS41Ny0xLjE3LTMuMTgtMi4zLTQuNzktMy40MkwyNTYgNTkuNWwtODIuMzQgODUuNjMgMTEzLjQxLTU4LjY4IDI5LjA3LTE1Yy0xLTEuMjctMi0yLjU4LTMtMy44MiIvPjwvZz48L2c+PGcgaWQ9ImcxNDQiIGNsaXAtcGF0aD0idXJsKCNjbGlwLXBhdGgtNykiPjxnIGlkPSJnMTQ2Ij48cGF0aCBpZD0icGF0aDE2MCIgZmlsbD0idXJsKCNsaW5lYXItZ3JhZGllbnQtNykiIGQ9Ik0zNTMuNTkgMTc3LjYxYzAtMi0uMTQtNC0uMjItNS45M2wtMzIuMjEtMi4zMS0xNDcuNDctMTAuNThMMzE4LjM2IDIwOWwzMC40MSAxMC41NWMuMDktLjM2LjE5LS43MS4yOC0xLjA4YTE3My42NSAxNzMuNjUgMCAwIDAgNC41Ny0zOS40N3YtMS4zNyIvPjwvZz48L2c+PGcgaWQ9ImcxNjIiIGNsaXAtcGF0aD0idXJsKCNjbGlwLXBhdGgtOCkiPjxnIGlkPSJnMTY0Ij48cGF0aCBpZD0icGF0aDE3OCIgZmlsbD0idXJsKCNsaW5lYXItZ3JhZGllbnQtOCkiIGQ9Ik0zNDguODQgMTM4LjYxYTE3Mi41NSAxNzIuNTUgMCAwIDAtMTMuODQtMzcuN2wtMzAuOTQgOS45NEwxNzUuOTIgMTUybDE0Mi00LjgzIDMyLjUtMS4xMWMtLjQ4LTIuNTEtMS01LTEuNTYtNy40OCIvPjwvZz48L2c+PC9nPjwvc3ZnPg==" - keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "host"], ["spec", "storageClass"], ["spec", "core"], ["spec", "core", "resources"], ["spec", "core", "resourcesPreset"], ["spec", "registry"], ["spec", "registry", "size"], ["spec", "registry", "resources"], ["spec", "registry", "resourcesPreset"], ["spec", "jobservice"], ["spec", "jobservice", "resources"], ["spec", "jobservice", "resourcesPreset"], ["spec", "trivy"], ["spec", "trivy", "enabled"], ["spec", "trivy", "size"], ["spec", "trivy", "resources"], ["spec", "trivy", "resourcesPreset"], ["spec", "database"], ["spec", "database", "size"], ["spec", "database", "replicas"], ["spec", "redis"], ["spec", "redis", "size"], ["spec", "redis", "replicas"]] + keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "host"], ["spec", "storageClass"], ["spec", "core"], ["spec", "core", "resources"], ["spec", "core", "resourcesPreset"], ["spec", "registry"], ["spec", "registry", "resources"], ["spec", "registry", "resourcesPreset"], ["spec", "jobservice"], ["spec", "jobservice", "resources"], ["spec", "jobservice", "resourcesPreset"], ["spec", "trivy"], ["spec", "trivy", "enabled"], ["spec", "trivy", "size"], ["spec", "trivy", "resources"], ["spec", "trivy", "resourcesPreset"], ["spec", "database"], ["spec", "database", "size"], ["spec", "database", "replicas"], ["spec", "redis"], ["spec", "redis", "size"], ["spec", "redis", "replicas"]] secrets: exclude: [] include: From 6c447b2fcb87c10ecfc388ce487d6125b3245182 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Mon, 16 Feb 2026 21:47:49 +0300 Subject: [PATCH 315/889] [harbor] Improve ingress template: quote hosts, handle cloudflare issuer Add | quote to host values in ingress for proper YAML escaping. Add cloudflare issuer type handling following bucket/dashboard pattern. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/apps/harbor/templates/ingress.yaml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/apps/harbor/templates/ingress.yaml b/packages/apps/harbor/templates/ingress.yaml index 785b4a79..d2e3c204 100644 --- a/packages/apps/harbor/templates/ingress.yaml +++ b/packages/apps/harbor/templates/ingress.yaml @@ -1,6 +1,7 @@ {{- $ingress := .Values._namespace.ingress }} {{- $host := .Values._namespace.host }} {{- $harborHost := .Values.host | default (printf "harbor.%s" $host) }} +{{- $issuerType := (index .Values._cluster "clusterissuer") | default "http01" }} --- apiVersion: networking.k8s.io/v1 kind: Ingress @@ -12,16 +13,18 @@ metadata: nginx.ingress.kubernetes.io/proxy-send-timeout: "900" nginx.ingress.kubernetes.io/ssl-redirect: "true" nginx.ingress.kubernetes.io/backend-protocol: "HTTP" + {{- if ne $issuerType "cloudflare" }} acme.cert-manager.io/http01-ingress-class: {{ $ingress }} + {{- end }} cert-manager.io/cluster-issuer: letsencrypt-prod spec: ingressClassName: {{ $ingress }} tls: - hosts: - - {{ $harborHost }} + - {{ $harborHost | quote }} secretName: {{ .Release.Name }}-ingress-tls rules: - - host: {{ $harborHost }} + - host: {{ $harborHost | quote }} http: paths: - path: / From c815725bcfd2976cfa275030fb9688a108cebd6e Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Mon, 16 Feb 2026 23:09:35 +0300 Subject: [PATCH 316/889] [harbor] Fix E2E test: use correct HelmRelease name with prefix ApplicationDefinition has prefix "harbor-", so CR name "harbor" produces HelmRelease "harbor-harbor". Use name="test" and release="harbor-test" to correctly reference all resources. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- hack/e2e-apps/harbor.bats | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/hack/e2e-apps/harbor.bats b/hack/e2e-apps/harbor.bats index d4a0055f..7109ad4f 100644 --- a/hack/e2e-apps/harbor.bats +++ b/hack/e2e-apps/harbor.bats @@ -1,7 +1,8 @@ #!/usr/bin/env bats @test "Create Harbor" { - name='harbor' + name='test' + release="harbor-$name" kubectl apply -f- < Date: Tue, 17 Feb 2026 00:36:53 +0300 Subject: [PATCH 317/889] [harbor] Make registry storage configurable: S3 or PVC Add registry.storageType parameter (pvc/s3) to let users choose between PVC storage and S3 via COSI BucketClaim. Default is pvc, which works without SeaweedFS in the tenant namespace. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- hack/e2e-apps/harbor.bats | 2 ++ packages/apps/harbor/README.md | 2 ++ packages/apps/harbor/templates/bucket.yaml | 2 ++ packages/apps/harbor/templates/harbor.yaml | 15 +++++++++-- packages/apps/harbor/values.schema.json | 26 +++++++++++++++++++ packages/apps/harbor/values.yaml | 8 ++++++ packages/system/harbor-rd/cozyrds/harbor.yaml | 4 +-- 7 files changed, 55 insertions(+), 4 deletions(-) diff --git a/hack/e2e-apps/harbor.bats b/hack/e2e-apps/harbor.bats index 7109ad4f..8c734565 100644 --- a/hack/e2e-apps/harbor.bats +++ b/hack/e2e-apps/harbor.bats @@ -16,6 +16,8 @@ spec: resources: {} resourcesPreset: "nano" registry: + storageType: "pvc" + size: 5Gi resources: {} resourcesPreset: "nano" jobservice: diff --git a/packages/apps/harbor/README.md b/packages/apps/harbor/README.md index df44ca8d..3c870be3 100644 --- a/packages/apps/harbor/README.md +++ b/packages/apps/harbor/README.md @@ -22,6 +22,8 @@ Harbor is an open source trusted cloud native registry project that stores, sign | `core.resources.memory` | Amount of memory allocated. | `quantity` | `""` | | `core.resourcesPreset` | Default sizing preset used when `resources` is omitted. | `string` | `small` | | `registry` | Container image registry configuration. | `object` | `{}` | +| `registry.storageType` | Storage backend type for images. | `string` | `pvc` | +| `registry.size` | Persistent Volume size (only used when storageType is pvc). | `quantity` | `10Gi` | | `registry.resources` | Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied. | `object` | `{}` | | `registry.resources.cpu` | Number of CPU cores allocated. | `quantity` | `""` | | `registry.resources.memory` | Amount of memory allocated. | `quantity` | `""` | diff --git a/packages/apps/harbor/templates/bucket.yaml b/packages/apps/harbor/templates/bucket.yaml index a9e60988..885cbfc8 100644 --- a/packages/apps/harbor/templates/bucket.yaml +++ b/packages/apps/harbor/templates/bucket.yaml @@ -1,3 +1,4 @@ +{{- if eq .Values.registry.storageType "s3" }} {{- $seaweedfs := .Values._namespace.seaweedfs }} apiVersion: objectstorage.k8s.io/v1alpha1 kind: BucketClaim @@ -17,3 +18,4 @@ spec: bucketClaimName: {{ .Release.Name }}-registry credentialsSecretName: {{ .Release.Name }}-registry-bucket protocol: s3 +{{- end }} diff --git a/packages/apps/harbor/templates/harbor.yaml b/packages/apps/harbor/templates/harbor.yaml index 59758950..97f28942 100644 --- a/packages/apps/harbor/templates/harbor.yaml +++ b/packages/apps/harbor/templates/harbor.yaml @@ -53,6 +53,7 @@ spec: valuesFrom: - kind: Secret name: cozystack-values + {{- if eq .Values.registry.storageType "s3" }} - kind: Secret name: {{ .Release.Name }}-registry-bucket valuesKey: accessKey @@ -65,6 +66,7 @@ spec: name: {{ .Release.Name }}-registry-bucket valuesKey: endpoint targetPath: harbor.persistence.imageChartStorage.s3.regionendpoint + {{- end }} values: db: replicas: {{ .Values.database.replicas }} @@ -91,6 +93,7 @@ spec: persistence: enabled: true resourcePolicy: "keep" + {{- if eq .Values.registry.storageType "s3" }} imageChartStorage: type: s3 s3: @@ -98,14 +101,22 @@ spec: bucket: {{ .Release.Name }}-registry secure: false v4auth: true - {{- if .Values.trivy.enabled }} + {{- end }} persistentVolumeClaim: + {{- if eq .Values.registry.storageType "pvc" }} + registry: + size: {{ .Values.registry.size }} + {{- with .Values.storageClass }} + storageClass: {{ . }} + {{- end }} + {{- end }} + {{- if .Values.trivy.enabled }} trivy: size: {{ .Values.trivy.size }} {{- with .Values.storageClass }} storageClass: {{ . }} {{- end }} - {{- end }} + {{- end }} portal: resources: {{- include "cozy-lib.resources.defaultingSanitize" (list "nano" (dict) $) | nindent 10 }} core: diff --git a/packages/apps/harbor/values.schema.json b/packages/apps/harbor/values.schema.json index 1352f5dc..71cc1d98 100644 --- a/packages/apps/harbor/values.schema.json +++ b/packages/apps/harbor/values.schema.json @@ -179,6 +179,9 @@ "description": "Container image registry configuration.", "type": "object", "default": {}, + "required": [ + "storageType" + ], "properties": { "resources": { "description": "Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.", @@ -226,6 +229,29 @@ "xlarge", "2xlarge" ] + }, + "size": { + "description": "Persistent Volume size (only used when storageType is pvc).", + "default": "10Gi", + "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 + }, + "storageType": { + "description": "Storage backend type for images.", + "type": "string", + "default": "pvc", + "enum": [ + "s3", + "pvc" + ] } } }, diff --git a/packages/apps/harbor/values.yaml b/packages/apps/harbor/values.yaml index de44eaff..f1131bd1 100644 --- a/packages/apps/harbor/values.yaml +++ b/packages/apps/harbor/values.yaml @@ -34,12 +34,20 @@ core: resources: {} resourcesPreset: "small" +## @enum {string} StorageType - Storage backend for container image registry. +## @value s3 - S3-compatible object storage via COSI BucketClaim (requires SeaweedFS in tenant). +## @value pvc - Persistent Volume Claim. + ## @typedef {struct} Registry - Container image registry configuration. +## @field {StorageType} storageType - Storage backend type for images. +## @field {quantity} [size] - Persistent Volume size (only used when storageType is pvc). ## @field {Resources} [resources] - Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied. ## @field {ResourcesPreset} [resourcesPreset] - Default sizing preset used when `resources` is omitted. ## @param {Registry} registry - Container image registry configuration. registry: + storageType: "pvc" + size: 10Gi resources: {} resourcesPreset: "small" diff --git a/packages/system/harbor-rd/cozyrds/harbor.yaml b/packages/system/harbor-rd/cozyrds/harbor.yaml index 0112dc11..80924530 100644 --- a/packages/system/harbor-rd/cozyrds/harbor.yaml +++ b/packages/system/harbor-rd/cozyrds/harbor.yaml @@ -8,7 +8,7 @@ spec: singular: harbor plural: harbors openAPISchema: |- - {"title":"Chart Values","type":"object","properties":{"core":{"description":"Core API server configuration.","type":"object","default":{},"properties":{"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"small","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}},"database":{"description":"PostgreSQL database configuration.","type":"object","default":{},"required":["replicas","size"],"properties":{"replicas":{"description":"Number of database instances.","type":"integer","default":2},"size":{"description":"Persistent Volume size for database storage.","default":"5Gi","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":"Hostname for external access to Harbor (defaults to 'harbor' subdomain for the tenant host).","type":"string","default":""},"jobservice":{"description":"Background job service configuration.","type":"object","default":{},"properties":{"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"nano","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}},"redis":{"description":"Redis cache configuration.","type":"object","default":{},"required":["replicas","size"],"properties":{"replicas":{"description":"Number of Redis replicas.","type":"integer","default":2},"size":{"description":"Persistent Volume size for cache storage.","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}}},"registry":{"description":"Container image registry configuration.","type":"object","default":{},"properties":{"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"small","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":""},"trivy":{"description":"Trivy vulnerability scanner configuration.","type":"object","default":{},"required":["enabled","size"],"properties":{"enabled":{"description":"Enable or disable the vulnerability scanner.","type":"boolean","default":true},"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"nano","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]},"size":{"description":"Persistent Volume size for vulnerability database cache.","default":"5Gi","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":{"core":{"description":"Core API server configuration.","type":"object","default":{},"properties":{"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"small","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}},"database":{"description":"PostgreSQL database configuration.","type":"object","default":{},"required":["replicas","size"],"properties":{"replicas":{"description":"Number of database instances.","type":"integer","default":2},"size":{"description":"Persistent Volume size for database storage.","default":"5Gi","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":"Hostname for external access to Harbor (defaults to 'harbor' subdomain for the tenant host).","type":"string","default":""},"jobservice":{"description":"Background job service configuration.","type":"object","default":{},"properties":{"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"nano","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}},"redis":{"description":"Redis cache configuration.","type":"object","default":{},"required":["replicas","size"],"properties":{"replicas":{"description":"Number of Redis replicas.","type":"integer","default":2},"size":{"description":"Persistent Volume size for cache storage.","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}}},"registry":{"description":"Container image registry configuration.","type":"object","default":{},"required":["storageType"],"properties":{"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"small","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]},"size":{"description":"Persistent Volume size (only used when storageType is pvc).","default":"10Gi","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},"storageType":{"description":"Storage backend type for images.","type":"string","default":"pvc","enum":["s3","pvc"]}}},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":""},"trivy":{"description":"Trivy vulnerability scanner configuration.","type":"object","default":{},"required":["enabled","size"],"properties":{"enabled":{"description":"Enable or disable the vulnerability scanner.","type":"boolean","default":true},"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"nano","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]},"size":{"description":"Persistent Volume size for vulnerability database cache.","default":"5Gi","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: "harbor-" labels: @@ -29,7 +29,7 @@ spec: - registry - container icon: "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHJvbGU9ImltZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHZpZXdCb3g9IjEuMDUgLTEuOTUgMzU5LjQxIDM2MS42NiI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQiIHgxPSIyNjQuNzkiIHgyPSIyNjcuMjciIHkxPSI5NTIuMzkiIHkyPSI5NTIuMzkiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMzAuNDMgMCAwIC0zMC40MyAtNzk1NS4yMiAyOTI4NS43NSkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiM2MGI5MzIiLz48c3RvcCBvZmZzZXQ9Ii4yOCIgc3RvcC1jb2xvcj0iIzYwYjkzMiIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzM2N2MzNCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQtMiIgeDE9IjI2My43NyIgeDI9IjI2Ni4yNiIgeTE9Ijk1NS42NSIgeTI9Ijk1NS42NSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgyNy4yMSAwIDAgLTI3LjIxIC03MDczLjg1IDI2MTY5LjQxKSIgeGxpbms6aHJlZj0iI2xpbmVhci1ncmFkaWVudCIvPjxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50LTMiIHgxPSIyNjMuMjgiIHgyPSIyNjUuNzYiIHkxPSI5NTMuNzQiIHkyPSI5NTMuNzQiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMjUuNzUgMCAwIC0yNS43NSAtNjY3MS4xMyAyNDgxMi4yMykiIHhsaW5rOmhyZWY9IiNsaW5lYXItZ3JhZGllbnQiLz48bGluZWFyR3JhZGllbnQgaWQ9ImxpbmVhci1ncmFkaWVudC00IiB4MT0iMjYzLjc3IiB4Mj0iMjY2LjI1IiB5MT0iOTUzLjIiIHkyPSI5NTMuMiIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgyNy4xIDAgMCAtMjcuMSAtNzA0MC45IDI2MTAyLjQ5KSIgeGxpbms6aHJlZj0iI2xpbmVhci1ncmFkaWVudCIvPjxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50LTUiIHgxPSIyNjIuNzMiIHgyPSIyNjUuMjEiIHkxPSI5NTQuMzQiIHkyPSI5NTQuMzQiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMjQuNCAwIDAgLTI0LjQgLTYzMDEuMzYgMjM1MjEuOTcpIiB4bGluazpocmVmPSIjbGluZWFyLWdyYWRpZW50Ii8+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQtNiIgeDE9IjI3Mi4xNCIgeDI9IjI3NC42MiIgeTE9Ijk1NS4xNSIgeTI9Ijk1NS4xNSIgZ3JhZGllbnRUcmFuc2Zvcm09InNjYWxlKDY2LjA5IC02Ni4wOSkgcm90YXRlKDM2LjUyIDE1ODguMTUzIDY4LjE0OCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiM0NTk2ZDgiLz48c3RvcCBvZmZzZXQ9Ii4yIiBzdG9wLWNvbG9yPSIjNDU5NmQ4Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjZmZmIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9ImxpbmVhci1ncmFkaWVudC03IiB4MT0iMjcwLjY1IiB4Mj0iMjczLjEzIiB5MT0iOTUyLjM4IiB5Mj0iOTUyLjM4IiBncmFkaWVudFRyYW5zZm9ybT0ic2NhbGUoNzcuOCAtNzcuOCkgcm90YXRlKC0xMS41NCAtNDU4Ny4yMDkgMTgwMy4zMjMpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjNDE5NGQ3Ii8+PHN0b3Agb2Zmc2V0PSIuMiIgc3RvcC1jb2xvcj0iIzQxOTRkNyIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI2ZmZiIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQtOCIgeDE9IjI3MC45NyIgeDI9IjI3My40NSIgeTE9Ijk1My43NSIgeTI9Ijk1My43NSIgZ3JhZGllbnRUcmFuc2Zvcm09InNjYWxlKDcxLjM1IC03MS4zNSkgcm90YXRlKDEwLjIzIDU0NzcuMzcgLTEwMjQuNjAyKSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iIzQxOTRkNyIvPjxzdG9wIG9mZnNldD0iLjMzIiBzdG9wLWNvbG9yPSIjNDQ5NmQ4Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjZmZmIi8+PC9saW5lYXJHcmFkaWVudD48Y2xpcFBhdGggaWQ9ImNsaXAtcGF0aCI+PHBhdGggZD0iTTE3NC40IDMwMi41MmwtNjguNjUtMzAuMjYtMy44MyA0My4yMSA3NS41IDIzLjk4LTMuMDItMzYuOTN6IiBjbGFzcz0iY2xzLTEiLz48L2NsaXBQYXRoPjxjbGlwUGF0aCBpZD0iY2xpcC1wYXRoLTIiPjxwYXRoIGQ9Ik0xMTkuMTkgMTM1LjM4aDM4LjU1djMyLjg5aC05LjE1di0xNGExMC4xMyAxMC4xMyAwIDEgMC0yMC4yNiAwdjE0aC05LjE1em0tNy43IDcybDU3LjIgMjUuMjEtMy45NC00OC4yNGg3LjQ5di0xNi4wOGgtNS41NXYtMzIuODloNS41NXYtOS40NWwtMzAuODYtMzAuMTl2LTIuMTJhMi45MSAyLjkxIDAgMCAwLTUuODIgMHYyLjEybC0zMC44NiAzMC4xOXY5LjQ1aDUuNTZ2MzIuODloLTUuNTZ2MTYuMTJoOC44MnoiIGNsYXNzPSJjbHMtMSIvPjwvY2xpcFBhdGg+PGNsaXBQYXRoIGlkPSJjbGlwLXBhdGgtMyI+PHBhdGggZD0iTTEwOC4xNCAyNDUuMjhsNjMuODggMjguMTYtLjk2LTExLjczLTYxLjk2LTI3LjMtLjk2IDEwLjg3eiIgY2xhc3M9ImNscy0xIi8+PC9jbGlwUGF0aD48Y2xpcFBhdGggaWQ9ImNsaXAtcGF0aC00Ij48cGF0aCBkPSJNMTA2LjQ2IDI2NC4yMWw2Ny4yMyAyOS42My0uOTYtMTEuNzItNjUuMzEtMjguNzgtLjk2IDEwLjg3eiIgY2xhc3M9ImNscy0xIi8+PC9jbGlwUGF0aD48Y2xpcFBhdGggaWQ9ImNsaXAtcGF0aC01Ij48cGF0aCBkPSJNMTEwLjc3IDIxNS40OGwtLjk2IDEwLjg3IDYwLjU0IDI2LjY4LS45NS0xMS43Mi01OC42My0yNS44M3oiIGNsYXNzPSJjbHMtMSIvPjwvY2xpcFBhdGg+PGNsaXBQYXRoIGlkPSJjbGlwLXBhdGgtNiI+PHBhdGggZD0iTTMxMy4xMyA2Ny41OWExNzUuMzEgMTc1LjMxIDAgMCAwLTI5Ljc1LTI4LjEzYy0xLjU3LTEuMTctMy4xOC0yLjMtNC43OS0zLjQyTDI1NiA1OS41bC04Mi4zNCA4NS42MyAxMTMuNDEtNTguNjggMjkuMDctMTVjLTEuMDEtMS4zMS0xLjk4LTIuNjItMy4wMS0zLjg2eiIgY2xhc3M9ImNscy0xIi8+PC9jbGlwUGF0aD48Y2xpcFBhdGggaWQ9ImNsaXAtcGF0aC03Ij48cGF0aCBkPSJNMzUzLjU5IDE3Ny42MWMwLTItLjE0LTQtLjIyLTUuOTNsLTMyLjIxLTIuMzEtMTQ3LjQ3LTEwLjU4TDMxOC4zNiAyMDlsMzAuNDEgMTAuNTVjLjA5LS4zNi4xOS0uNzEuMjgtMS4wOGExNzMuNjUgMTczLjY1IDAgMCAwIDQuNTctMzkuNDd2LTEuMzd6IiBjbGFzcz0iY2xzLTEiLz48L2NsaXBQYXRoPjxjbGlwUGF0aCBpZD0iY2xpcC1wYXRoLTgiPjxwYXRoIGQ9Ik0zNDguODQgMTM4LjYxYTE3Mi41NSAxNzIuNTUgMCAwIDAtMTMuODQtMzcuN2wtMzAuOTQgOS45NEwxNzUuOTIgMTUybDE0Mi00LjgzIDMyLjUtMS4xMWMtLjUtMi40OC0uOTktNC45OC0xLjU4LTcuNDV6IiBjbGFzcz0iY2xzLTEiLz48L2NsaXBQYXRoPjxzdHlsZT4uY2xzLTF7ZmlsbDpub25lfS5jbHMtMTN7ZmlsbDojNjk2NTY2fTwvc3R5bGU+PC9kZWZzPjxnIGlkPSJnMTIiPjxwYXRoIGlkPSJwYXRoMTQiIGZpbGw9IiNmZmYiIGQ9Ik0zMC44OSAxNzlhMTQ4Ljg3IDE0OC44NyAwIDEgMSAxNDguODcgMTQ4Ljg1QTE0OC44NyAxNDguODcgMCAwIDEgMzAuODkgMTc5Ii8+PGcgaWQ9ImczMCIgY2xpcC1wYXRoPSJ1cmwoI2NsaXAtcGF0aCkiPjxnIGlkPSJnMzIiPjxwYXRoIGlkPSJwYXRoNDYiIGZpbGw9InVybCgjbGluZWFyLWdyYWRpZW50KSIgZD0iTTE3NC40IDMwMi41MmwtNjguNjUtMzAuMjUtMy44MiA0My4yIDc1LjUgMjQtMy0zNi45MyIvPjwvZz48L2c+PGcgaWQ9Imc0OCIgY2xpcC1wYXRoPSJ1cmwoI2NsaXAtcGF0aC0yKSI+PGcgaWQ9Imc1MCI+PHBhdGggaWQ9InBhdGg2NCIgZmlsbD0idXJsKCNsaW5lYXItZ3JhZGllbnQtMikiIGQ9Ik0xMTkuMTkgMTM1LjM4aDM4LjU1djMyLjg5aC05LjE1di0xNGExMC4xMyAxMC4xMyAwIDEgMC0yMC4yNiAwdjE0aC05LjE1em0tNy43IDcybDU3LjIgMjUuMjEtMy45NC00OC4yNGg3LjQ5di0xNi4wOGgtNS41NXYtMzIuODloNS41NXYtOS40NWwtMzAuODYtMzAuMTl2LTIuMTJhMi45MSAyLjkxIDAgMCAwLTUuODIgMHYyLjEybC0zMC44NiAzMC4xOXY5LjQ1aDUuNTZ2MzIuODloLTUuNTZ2MTYuMTJoOC44MmwtMiAyMyIvPjwvZz48L2c+PGcgaWQ9Imc2NiIgY2xpcC1wYXRoPSJ1cmwoI2NsaXAtcGF0aC0zKSI+PGcgaWQ9Imc2OCI+PHBhdGggaWQ9InBhdGg4MiIgZmlsbD0idXJsKCNsaW5lYXItZ3JhZGllbnQtMykiIGQ9Ik0xMDguMTMgMjQ1LjI4TDE3MiAyNzMuNDRsLTEtMTEuNzItNjItMjcuMzEtMSAxMC44NyIvPjwvZz48L2c+PGcgaWQ9Imc4NCIgY2xpcC1wYXRoPSJ1cmwoI2NsaXAtcGF0aC00KSI+PGcgaWQ9Imc4NiI+PHBhdGggaWQ9InBhdGgxMDAiIGZpbGw9InVybCgjbGluZWFyLWdyYWRpZW50LTQpIiBkPSJNMTA2LjQ2IDI2NC4yMWw2Ny4yMyAyOS42My0xLTExLjcyLTY1LjMxLTI4Ljc4LTEgMTAuODciLz48L2c+PC9nPjxnIGlkPSJnMTAyIiBjbGlwLXBhdGg9InVybCgjY2xpcC1wYXRoLTUpIj48ZyBpZD0iZzEwNCI+PHBhdGggaWQ9InBhdGgxMTgiIGZpbGw9InVybCgjbGluZWFyLWdyYWRpZW50LTUpIiBkPSJNMTEwLjc3IDIxNS40OGwtMSAxMC44OEwxNzAuMzUgMjUzbC0xLTExLjcyLTU4LjYyLTI1LjgzIi8+PC9nPjwvZz48cGF0aCBpZD0icGF0aDEyMCIgZD0iTTMwNC4wNyAxMTAuODVsMzAuOTMtOS45NGMtLjExLS4yMi0uMjEtLjQ1LS4zMi0uNjZhMTc0LjQxIDE3NC40MSAwIDAgMC0xOC41NS0yOC44M2wtMjkuMDcgMTVhMTQyLjcxIDE0Mi43MSAwIDAgMSAxNi43MyAyMy44N2MuMS4xNy4xOC4zNS4yNy41MyIgY2xhc3M9ImNscy0xMyIvPjxwYXRoIGlkPSJwYXRoMTIyIiBkPSJNMzIxLjE1IDE2OS4zN2wzMi4yMSAyLjMxYTE3Mi44NiAxNzIuODYgMCAwIDAtMy0yNS41OWwtMzIuNSAxLjExYTE0MSAxNDEgMCAwIDEgMy4yNSAyMi4xNyIgY2xhc3M9ImNscy0xMyIvPjxwYXRoIGlkPSJwYXRoMTI0IiBkPSJNMTgyIDMyMC44MWMtNzguMiAwLTE0MS44My02My42Mi0xNDEuODMtMTQxLjgyUzEwMy44MiAzNy4xNiAxODIgMzcuMTZhMTQwLjkzIDE0MC45MyAwIDAgMSA3Ni4zIDIyLjM0TDI4MC44NSAzNkExNzIuODYgMTcyLjg2IDAgMCAwIDE4MiA1LjExQzg2LjE1IDUuMTEgOC4xNSA4My4xMSA4LjE1IDE3OXM3OCAxNzMuODUgMTczLjg1IDE3My44NWM4MS45IDAgMTUwLjY5LTU3IDE2OS0xMzMuMzJMMzIwLjYyIDIwOWMtMTMuOCA2My44NC03MC42OSAxMTEuODMtMTM4LjYgMTExLjgzIiBjbGFzcz0iY2xzLTEzIi8+PGcgaWQ9ImcxMjYiIGNsaXAtcGF0aD0idXJsKCNjbGlwLXBhdGgtNikiPjxnIGlkPSJnMTI4Ij48cGF0aCBpZD0icGF0aDE0MiIgZmlsbD0idXJsKCNsaW5lYXItZ3JhZGllbnQtNikiIGQ9Ik0zMTMuMTMgNjcuNTlhMTc1LjMxIDE3NS4zMSAwIDAgMC0yOS43NS0yOC4xM2MtMS41Ny0xLjE3LTMuMTgtMi4zLTQuNzktMy40MkwyNTYgNTkuNWwtODIuMzQgODUuNjMgMTEzLjQxLTU4LjY4IDI5LjA3LTE1Yy0xLTEuMjctMi0yLjU4LTMtMy44MiIvPjwvZz48L2c+PGcgaWQ9ImcxNDQiIGNsaXAtcGF0aD0idXJsKCNjbGlwLXBhdGgtNykiPjxnIGlkPSJnMTQ2Ij48cGF0aCBpZD0icGF0aDE2MCIgZmlsbD0idXJsKCNsaW5lYXItZ3JhZGllbnQtNykiIGQ9Ik0zNTMuNTkgMTc3LjYxYzAtMi0uMTQtNC0uMjItNS45M2wtMzIuMjEtMi4zMS0xNDcuNDctMTAuNThMMzE4LjM2IDIwOWwzMC40MSAxMC41NWMuMDktLjM2LjE5LS43MS4yOC0xLjA4YTE3My42NSAxNzMuNjUgMCAwIDAgNC41Ny0zOS40N3YtMS4zNyIvPjwvZz48L2c+PGcgaWQ9ImcxNjIiIGNsaXAtcGF0aD0idXJsKCNjbGlwLXBhdGgtOCkiPjxnIGlkPSJnMTY0Ij48cGF0aCBpZD0icGF0aDE3OCIgZmlsbD0idXJsKCNsaW5lYXItZ3JhZGllbnQtOCkiIGQ9Ik0zNDguODQgMTM4LjYxYTE3Mi41NSAxNzIuNTUgMCAwIDAtMTMuODQtMzcuN2wtMzAuOTQgOS45NEwxNzUuOTIgMTUybDE0Mi00LjgzIDMyLjUtMS4xMWMtLjQ4LTIuNTEtMS01LTEuNTYtNy40OCIvPjwvZz48L2c+PC9nPjwvc3ZnPg==" - keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "host"], ["spec", "storageClass"], ["spec", "core"], ["spec", "core", "resources"], ["spec", "core", "resourcesPreset"], ["spec", "registry"], ["spec", "registry", "resources"], ["spec", "registry", "resourcesPreset"], ["spec", "jobservice"], ["spec", "jobservice", "resources"], ["spec", "jobservice", "resourcesPreset"], ["spec", "trivy"], ["spec", "trivy", "enabled"], ["spec", "trivy", "size"], ["spec", "trivy", "resources"], ["spec", "trivy", "resourcesPreset"], ["spec", "database"], ["spec", "database", "size"], ["spec", "database", "replicas"], ["spec", "redis"], ["spec", "redis", "size"], ["spec", "redis", "replicas"]] + keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "host"], ["spec", "storageClass"], ["spec", "core"], ["spec", "core", "resources"], ["spec", "core", "resourcesPreset"], ["spec", "registry"], ["spec", "registry", "storageType"], ["spec", "registry", "size"], ["spec", "registry", "resources"], ["spec", "registry", "resourcesPreset"], ["spec", "jobservice"], ["spec", "jobservice", "resources"], ["spec", "jobservice", "resourcesPreset"], ["spec", "trivy"], ["spec", "trivy", "enabled"], ["spec", "trivy", "size"], ["spec", "trivy", "resources"], ["spec", "trivy", "resourcesPreset"], ["spec", "database"], ["spec", "database", "size"], ["spec", "database", "replicas"], ["spec", "redis"], ["spec", "redis", "size"], ["spec", "redis", "replicas"]] secrets: exclude: [] include: From 490faaf2920c2a01622c4eeaf43b739c4072c90e Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Tue, 17 Feb 2026 02:01:12 +0300 Subject: [PATCH 318/889] fix(harbor): add operator dependencies, fix persistence rendering, increase E2E timeout Add postgres-operator and redis-operator to PackageSource dependsOn to ensure CRDs are available before Harbor system chart deploys. Make persistentVolumeClaim conditional to avoid empty YAML mapping when using S3 storage without Trivy. Increase E2E system HelmRelease timeout from 300s to 600s to account for CPNG + Redis + Harbor bootstrap time on QEMU. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- hack/e2e-apps/harbor.bats | 2 +- packages/apps/harbor/templates/harbor.yaml | 2 ++ packages/core/platform/sources/harbor-application.yaml | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/hack/e2e-apps/harbor.bats b/hack/e2e-apps/harbor.bats index 8c734565..e1b6ab2b 100644 --- a/hack/e2e-apps/harbor.bats +++ b/hack/e2e-apps/harbor.bats @@ -37,7 +37,7 @@ spec: EOF sleep 5 kubectl -n tenant-test wait hr $release --timeout=60s --for=condition=ready - kubectl -n tenant-test wait hr $release-system --timeout=300s --for=condition=ready + kubectl -n tenant-test wait hr $release-system --timeout=600s --for=condition=ready kubectl -n tenant-test wait deploy $release-core --timeout=120s --for=condition=available kubectl -n tenant-test wait deploy $release-registry --timeout=120s --for=condition=available kubectl -n tenant-test wait deploy $release-portal --timeout=120s --for=condition=available diff --git a/packages/apps/harbor/templates/harbor.yaml b/packages/apps/harbor/templates/harbor.yaml index 97f28942..4196ebcd 100644 --- a/packages/apps/harbor/templates/harbor.yaml +++ b/packages/apps/harbor/templates/harbor.yaml @@ -102,6 +102,7 @@ spec: secure: false v4auth: true {{- end }} + {{- if or (eq .Values.registry.storageType "pvc") .Values.trivy.enabled }} persistentVolumeClaim: {{- if eq .Values.registry.storageType "pvc" }} registry: @@ -117,6 +118,7 @@ spec: storageClass: {{ . }} {{- end }} {{- end }} + {{- end }} portal: resources: {{- include "cozy-lib.resources.defaultingSanitize" (list "nano" (dict) $) | nindent 10 }} core: diff --git a/packages/core/platform/sources/harbor-application.yaml b/packages/core/platform/sources/harbor-application.yaml index b05d7a01..b9bc8dc3 100644 --- a/packages/core/platform/sources/harbor-application.yaml +++ b/packages/core/platform/sources/harbor-application.yaml @@ -13,6 +13,8 @@ spec: - name: default dependsOn: - cozystack.networking + - cozystack.postgres-operator + - cozystack.redis-operator libraries: - name: cozy-lib path: library/cozy-lib From 0f2ba5aba25af436c7a80a2f1b676cfc340f38f5 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Tue, 17 Feb 2026 12:59:23 +0300 Subject: [PATCH 319/889] fix(harbor): add diagnostic output on E2E system HelmRelease timeout Dump HelmRelease status, pods, events, and ExternalArtifact info when harbor-test-system fails to become ready, to diagnose the root cause of the persistent timeout. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- hack/e2e-apps/harbor.bats | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/hack/e2e-apps/harbor.bats b/hack/e2e-apps/harbor.bats index e1b6ab2b..076a1841 100644 --- a/hack/e2e-apps/harbor.bats +++ b/hack/e2e-apps/harbor.bats @@ -37,7 +37,17 @@ spec: EOF sleep 5 kubectl -n tenant-test wait hr $release --timeout=60s --for=condition=ready - kubectl -n tenant-test wait hr $release-system --timeout=600s --for=condition=ready + kubectl -n tenant-test wait hr $release-system --timeout=600s --for=condition=ready || { + echo "=== HelmRelease status ===" + kubectl -n tenant-test get hr $release-system -o yaml 2>&1 || true + echo "=== Pods ===" + kubectl -n tenant-test get pods 2>&1 || true + echo "=== Events ===" + kubectl -n tenant-test get events --sort-by='.lastTimestamp' 2>&1 | tail -30 || true + echo "=== ExternalArtifact ===" + kubectl -n cozy-system get externalartifact cozystack-harbor-application-default-harbor-system -o yaml 2>&1 || true + false + } kubectl -n tenant-test wait deploy $release-core --timeout=120s --for=condition=available kubectl -n tenant-test wait deploy $release-registry --timeout=120s --for=condition=available kubectl -n tenant-test wait deploy $release-portal --timeout=120s --for=condition=available From 0e6ae28bb8597f7ff3af4d78c1ab6ce288784782 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Tue, 17 Feb 2026 15:15:15 +0300 Subject: [PATCH 320/889] fix(harbor): resolve Redis nil pointer on first install MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The vendored Harbor chart does an unsafe `lookup` of the Redis auth Secret at template rendering time to extract the password. On first install, the Secret doesn't exist yet (created by the same chart), causing a nil pointer error. Failed installs are rolled back, deleting the Secret, so retries also fail — creating an infinite failure loop. Fix by generating the Redis password in the wrapper chart (same pattern as admin password), storing it in the credentials Secret, and injecting it via HelmRelease valuesFrom with targetPath. This bypasses the vendored chart's lookup entirely — it uses the password value directly instead of looking up the Secret. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/apps/harbor/templates/harbor.yaml | 14 +++++++++++++- packages/system/harbor/templates/redis.yaml | 9 ++------- packages/system/harbor/values.yaml | 1 + 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/apps/harbor/templates/harbor.yaml b/packages/apps/harbor/templates/harbor.yaml index 4196ebcd..ff59b45f 100644 --- a/packages/apps/harbor/templates/harbor.yaml +++ b/packages/apps/harbor/templates/harbor.yaml @@ -4,8 +4,12 @@ {{- $existingSecret := lookup "v1" "Secret" .Release.Namespace (printf "%s-credentials" .Release.Name) }} {{- $adminPassword := randAlphaNum 16 }} +{{- $redisPassword := randAlphaNum 32 }} {{- if $existingSecret }} {{- $adminPassword = index $existingSecret.data "admin-password" | b64dec }} + {{- if hasKey $existingSecret.data "redis-password" }} + {{- $redisPassword = index $existingSecret.data "redis-password" | b64dec }} + {{- end }} {{- end }} {{- $existingCoreSecret := lookup "v1" "Secret" .Release.Namespace (printf "%s-core" .Release.Name) }} @@ -26,6 +30,7 @@ metadata: name: {{ .Release.Name }}-credentials stringData: admin-password: {{ $adminPassword | quote }} + redis-password: {{ $redisPassword | quote }} url: https://{{ $harborHost }} --- @@ -53,6 +58,14 @@ spec: valuesFrom: - kind: Secret name: cozystack-values + - kind: Secret + name: {{ .Release.Name }}-credentials + valuesKey: redis-password + targetPath: redis.password + - kind: Secret + name: {{ .Release.Name }}-credentials + valuesKey: redis-password + targetPath: harbor.redis.external.password {{- if eq .Values.registry.storageType "s3" }} - kind: Secret name: {{ .Release.Name }}-registry-bucket @@ -153,7 +166,6 @@ spec: external: addr: "rfs-{{ .Release.Name }}-redis:26379" sentinelMasterSet: "mymaster" - existingSecret: "{{ .Release.Name }}-redis-auth" coreDatabaseIndex: "0" jobserviceDatabaseIndex: "1" registryDatabaseIndex: "2" diff --git a/packages/system/harbor/templates/redis.yaml b/packages/system/harbor/templates/redis.yaml index 7a71f1ce..b39c2b04 100644 --- a/packages/system/harbor/templates/redis.yaml +++ b/packages/system/harbor/templates/redis.yaml @@ -1,15 +1,10 @@ -{{- $existingPassword := lookup "v1" "Secret" .Release.Namespace (printf "%s-redis-auth" .Values.harbor.fullnameOverride) }} -{{- $password := randAlphaNum 32 | b64enc }} -{{- if $existingPassword }} - {{- $password = index $existingPassword.data "password" }} -{{- end }} --- apiVersion: v1 kind: Secret metadata: name: {{ .Values.harbor.fullnameOverride }}-redis-auth -data: - password: {{ $password }} +stringData: + password: {{ .Values.redis.password | quote }} --- apiVersion: databases.spotahome.com/v1 kind: RedisFailover diff --git a/packages/system/harbor/values.yaml b/packages/system/harbor/values.yaml index 40e924e0..9038bd38 100644 --- a/packages/system/harbor/values.yaml +++ b/packages/system/harbor/values.yaml @@ -4,6 +4,7 @@ db: size: 5Gi storageClass: "" redis: + password: "" replicas: 2 size: 1Gi storageClass: "" From efb9bc70b3d09f22006f027f6b556c194b302469 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 18 Feb 2026 00:49:34 +0300 Subject: [PATCH 321/889] fix(harbor): use Release.Name for default host to avoid conflicts Multiple Harbor instances in the same namespace would get the same default hostname when derived from namespace host. Use Release.Name instead for unique hostnames per instance. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/apps/harbor/templates/harbor.yaml | 2 +- packages/apps/harbor/templates/ingress.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/apps/harbor/templates/harbor.yaml b/packages/apps/harbor/templates/harbor.yaml index ff59b45f..0e9f4fd5 100644 --- a/packages/apps/harbor/templates/harbor.yaml +++ b/packages/apps/harbor/templates/harbor.yaml @@ -1,6 +1,6 @@ {{- $ingress := .Values._namespace.ingress }} {{- $host := .Values._namespace.host }} -{{- $harborHost := .Values.host | default (printf "harbor.%s" $host) }} +{{- $harborHost := .Values.host | default (printf "harbor.%s" .Release.Name) }} {{- $existingSecret := lookup "v1" "Secret" .Release.Namespace (printf "%s-credentials" .Release.Name) }} {{- $adminPassword := randAlphaNum 16 }} diff --git a/packages/apps/harbor/templates/ingress.yaml b/packages/apps/harbor/templates/ingress.yaml index d2e3c204..3f2b8f78 100644 --- a/packages/apps/harbor/templates/ingress.yaml +++ b/packages/apps/harbor/templates/ingress.yaml @@ -1,6 +1,6 @@ {{- $ingress := .Values._namespace.ingress }} {{- $host := .Values._namespace.host }} -{{- $harborHost := .Values.host | default (printf "harbor.%s" $host) }} +{{- $harborHost := .Values.host | default (printf "harbor.%s" .Release.Name) }} {{- $issuerType := (index .Values._cluster "clusterissuer") | default "http01" }} --- apiVersion: networking.k8s.io/v1 From 26178d97be54c60cb43f432e2b623e12417e4ff8 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Tue, 17 Feb 2026 22:51:00 +0100 Subject: [PATCH 322/889] fix(platform): adopt tenant-root into cozystack-basics during migration In v0.41.x the tenant-root Namespace and HelmRelease were applied via kubectl apply with no Helm release tracking. In v1.0 these resources are managed by the cozystack-basics Helm release. Without proper Helm ownership annotations the install of cozystack-basics fails because the resources already exist. Add migration 31 that annotates and labels both the Namespace and HelmRelease so Helm can adopt them, matching the pattern established in migrations 22 and 27. Co-Authored-By: Claude Signed-off-by: Andrei Kvapil --- .../platform/images/migrations/migrations/31 | 45 +++++++++++++++++++ packages/core/platform/values.yaml | 2 +- 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100755 packages/core/platform/images/migrations/migrations/31 diff --git a/packages/core/platform/images/migrations/migrations/31 b/packages/core/platform/images/migrations/migrations/31 new file mode 100755 index 00000000..2a261d9b --- /dev/null +++ b/packages/core/platform/images/migrations/migrations/31 @@ -0,0 +1,45 @@ +#!/bin/sh +# Migration 31 --> 32 +# Adopt tenant-root resources into cozystack-basics Helm release. +# +# In v0.41.x tenant-root Namespace and HelmRelease were applied via +# kubectl apply (no Helm tracking). In v1.0 they are managed by the +# cozystack-basics Helm release. Without Helm ownership annotations +# the install of cozystack-basics fails because the resources already +# exist. This migration adds the required annotations and labels so +# Helm can adopt them. + +set -euo pipefail + +RELEASE_NAME="cozystack-basics" +RELEASE_NS="cozy-system" + +# Adopt Namespace tenant-root +if kubectl get namespace tenant-root >/dev/null 2>&1; then + echo "Adopting Namespace tenant-root into $RELEASE_NAME" + kubectl annotate namespace tenant-root \ + meta.helm.sh/release-name="$RELEASE_NAME" \ + meta.helm.sh/release-namespace="$RELEASE_NS" \ + --overwrite + kubectl label namespace tenant-root \ + app.kubernetes.io/managed-by=Helm \ + --overwrite +fi + +# Adopt HelmRelease tenant-root +if kubectl get helmrelease -n tenant-root tenant-root >/dev/null 2>&1; then + echo "Adopting HelmRelease tenant-root into $RELEASE_NAME" + kubectl annotate helmrelease -n tenant-root tenant-root \ + meta.helm.sh/release-name="$RELEASE_NAME" \ + meta.helm.sh/release-namespace="$RELEASE_NS" \ + helm.sh/resource-policy=keep \ + --overwrite + kubectl label helmrelease -n tenant-root tenant-root \ + app.kubernetes.io/managed-by=Helm \ + sharding.fluxcd.io/key=tenants \ + --overwrite +fi + +# Stamp version +kubectl create configmap -n cozy-system cozystack-version \ + --from-literal=version=32 --dry-run=client -o yaml | kubectl apply -f- diff --git a/packages/core/platform/values.yaml b/packages/core/platform/values.yaml index ca2d9d1b..f9d4c8d3 100644 --- a/packages/core/platform/values.yaml +++ b/packages/core/platform/values.yaml @@ -6,7 +6,7 @@ sourceRef: migrations: enabled: false image: ghcr.io/cozystack/cozystack/platform-migrations:v1.0.0-beta.5@sha256:e7a9e0f0adc33e0be007af42f50ed3af064aa965ad628e48cc6c7943f31f239d - targetVersion: 31 + targetVersion: 32 # Bundle deployment configuration bundles: system: From 7ac989923ddadcda85b4e758014f9a15716eb6c2 Mon Sep 17 00:00:00 2001 From: kklinch0 Date: Wed, 3 Sep 2025 15:27:38 +0300 Subject: [PATCH 323/889] Add monitoring for NATs Co-authored-by: Andrei Kvapil Signed-off-by: kklinch0 Signed-off-by: Andrei Kvapil --- dashboards/nats/nats-jetstream.json | 1541 +++++++++++++++++ dashboards/nats/nats-server.json | 1463 ++++++++++++++++ hack/download-dashboards.sh | 2 + packages/apps/nats/templates/nats.yaml | 4 - packages/system/monitoring/dashboards.list | 2 + packages/system/nats/charts/nats/Chart.yaml | 4 +- packages/system/nats/charts/nats/README.md | 46 +- .../nats/files/stateful-set/pod-template.yaml | 4 + .../stateful-set/prom-exporter-container.yaml | 3 +- .../nats/charts/nats/templates/_helpers.tpl | 14 +- packages/system/nats/charts/nats/values.yaml | 29 +- packages/system/nats/values.yaml | 4 + 12 files changed, 3086 insertions(+), 30 deletions(-) create mode 100644 dashboards/nats/nats-jetstream.json create mode 100644 dashboards/nats/nats-server.json diff --git a/dashboards/nats/nats-jetstream.json b/dashboards/nats/nats-jetstream.json new file mode 100644 index 00000000..d2312e7e --- /dev/null +++ b/dashboards/nats/nats-jetstream.json @@ -0,0 +1,1541 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "description": "NATS JetStream Dashboard", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "id": 90, + "links": [], + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 13, + "x": 0, + "y": 0 + }, + "id": 34, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "irate(nats_stream_total_messages{server_id=~\"$server\"}[5m])", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "messages per second", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "mappings": [], + "min": 0, + "thresholds": { + "mode": "percentage", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 5, + "x": 13, + "y": 0 + }, + "id": 32, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(nats_varz_jetstream_stats_memory{server_id=\"$server\"})", + "interval": "", + "legendFormat": "", + "range": true, + "refId": "A" + } + ], + "title": "Memory Used", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "mappings": [], + "min": 0, + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 18, + "y": 0 + }, + "id": 14, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(nats_varz_connections{server_id=~\"$server\"})", + "interval": "", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Connections", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "mappings": [], + "min": 0, + "thresholds": { + "mode": "percentage", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 5, + "x": 13, + "y": 3 + }, + "id": 33, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(nats_varz_jetstream_config_max_memory{server_id=~\"$server\"})", + "interval": "", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Total Memory", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 18, + "y": 3 + }, + "id": 29, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(nats_server_total_consumers{server_id=~\"$server\"})", + "interval": "", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Total Consumers", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "decimals": 3, + "mappings": [], + "max": 1, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 0.75 + }, + { + "color": "red", + "value": 0.9 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 0, + "y": 8 + }, + "id": 28, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(nats_varz_jetstream_stats_storage{server_id=~\"$server\"})/sum(nats_varz_jetstream_config_max_storage{server_id=~\"$server\"})", + "interval": "", + "legendFormat": "", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "", + "interval": "", + "legendFormat": "", + "refId": "B" + } + ], + "title": "Storage Used", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "mappings": [], + "min": 0, + "thresholds": { + "mode": "percentage", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 5, + "x": 4, + "y": 8 + }, + "id": 15, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(nats_varz_jetstream_stats_storage{server_id=~\"$server\"})", + "interval": "", + "legendFormat": "", + "range": true, + "refId": "A" + } + ], + "title": "Total Storage Used", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "decimals": 3, + "mappings": [], + "max": 1, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 0.75 + }, + { + "color": "red", + "value": 0.9 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 9, + "y": 8 + }, + "id": 31, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(nats_varz_jetstream_stats_memory{server_id=~\"$server\"})/sum(nats_varz_jetstream_config_max_memory{server_id=~\"$server\"})", + "interval": "", + "legendFormat": "", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "expr": "", + "interval": "", + "legendFormat": "", + "refId": "B" + } + ], + "title": "Memory Used", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "mappings": [], + "min": 0, + "thresholds": { + "mode": "percentage", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 5, + "x": 4, + "y": 11 + }, + "id": 30, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(nats_varz_jetstream_config_max_storage{server_id=~\"$server\"})", + "interval": "", + "legendFormat": "", + "range": true, + "refId": "A" + } + ], + "title": "Max Storage", + "type": "stat" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 14 + }, + "id": 19, + "panels": [], + "title": "Stream metrics", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "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": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 0, + "y": 15 + }, + "id": 17, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(nats_stream_total_bytes) by (stream_name)", + "interval": "", + "legendFormat": "{{stream_name}}", + "range": true, + "refId": "A" + } + ], + "title": "Stream data 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, + "barWidthFactor": 0.6, + "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": true, + "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": 6, + "w": 8, + "x": 8, + "y": 15 + }, + "id": 24, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(nats_stream_total_messages) by (stream_name)", + "interval": "", + "legendFormat": "{{stream_name}}", + "range": true, + "refId": "A" + } + ], + "title": "Stream message count", + "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, + "barWidthFactor": 0.6, + "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": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "mps" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 16, + "y": 15 + }, + "id": 20, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(rate(nats_stream_total_messages{server_id=~\"$server\",stream_name=~\"$stream\"}[$__rate_interval])) by (stream_name)", + "hide": false, + "interval": "", + "legendFormat": "{{stream_name}}", + "range": true, + "refId": "A" + } + ], + "title": "Message Rate (per second)", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 21 + }, + "id": 23, + "panels": [], + "title": "Consumer Metrics", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Messages added & processed per minute per consumer", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "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": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "mps" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 22 + }, + "id": 25, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(rate(nats_consumer_num_pending{server_id=~\"$server\",stream_name=~\"$stream\",consumer_name=~\"$consumer\"}[$__rate_interval])+rate(nats_consumer_delivered_consumer_seq{server_id=~\"$server\",stream_name=~\"$stream\",consumer_name=~\"$consumer\"}[$__rate_interval])) by (consumer_name)", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{consumer_name}} +", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "- sum(rate(nats_consumer_delivered_consumer_seq{server_id=~\"$server\",stream_name=~\"$stream\",consumer_name=~\"$consumer\",consumer_name=~\"$consumer\"}[$__rate_interval])) by (consumer_name)", + "hide": false, + "interval": "", + "legendFormat": "{{consumer_name}} -", + "range": true, + "refId": "A" + } + ], + "title": "Messages per second (++/--)", + "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, + "barWidthFactor": 0.6, + "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": true, + "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": 6, + "w": 8, + "x": 0, + "y": 28 + }, + "id": 21, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(nats_consumer_delivered_consumer_seq{server_id=~\"$server\",stream_name=~\"$stream\",consumer_name=~\"$consumer\"}) by (consumer_name)", + "hide": false, + "interval": "", + "legendFormat": "{{consumer_name}}", + "range": true, + "refId": "A" + } + ], + "title": "Total delivered messages", + "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, + "barWidthFactor": 0.6, + "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": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 8, + "y": 28 + }, + "id": 26, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(nats_consumer_num_pending{server_id=~\"$server\",stream_name=~\"$stream\",consumer_name=~\"$consumer\"}) by (consumer_name)", + "hide": false, + "interval": "", + "legendFormat": "{{consumer_name}}", + "range": true, + "refId": "A" + } + ], + "title": "Pending messages", + "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, + "barWidthFactor": 0.6, + "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": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 8, + "x": 16, + "y": 28 + }, + "id": 27, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "sum(nats_consumer_num_ack_pending{server_id=~\"$server\",stream_name=~\"$stream\",consumer_name=~\"$consumer\"}) by (consumer_name)", + "hide": false, + "interval": "", + "legendFormat": "{{consumer_name}}", + "range": true, + "refId": "A" + } + ], + "title": "Message Acks Pending", + "type": "timeseries" + } + ], + "preload": false, + "refresh": "10s", + "schemaVersion": 40, + "tags": [], + "templating": { + "list": [ + { + "current": { + "text": "vm-shortterm", + "value": "59e01639-a99e-4945-bb94-10821e415ad5" + }, + "description": "", + "label": "datasource", + "name": "DS_PROMETHEUS", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "type": "datasource" + }, + { + "current": { + "text": "", + "value": "" + }, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "definition": "label_values(nats_varz_jetstream_stats_memory,namespace)", + "includeAll": false, + "name": "namespace", + "options": [], + "query": { + "query": "label_values(nats_varz_jetstream_stats_memory,namespace)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "type": "query" + }, + { + "current": { + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "definition": "label_values(nats_server_total_streams{namespace=\"$namespace\"},server_id)", + "hide": 2, + "includeAll": true, + "label": "Server", + "name": "server", + "options": [], + "query": { + "query": "label_values(nats_server_total_streams{namespace=\"$namespace\"},server_id)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 2, + "regex": "", + "type": "query" + }, + { + "current": { + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "definition": "label_values(nats_stream_last_seq{server_id=~\"$server\"},stream_name)", + "includeAll": true, + "label": "Stream", + "multi": true, + "name": "stream", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(nats_stream_last_seq{server_id=~\"$server\"},stream_name)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 2, + "regex": "", + "type": "query" + }, + { + "current": { + "text": "All", + "value": "$__all" + }, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "definition": "label_values(nats_consumer_num_pending,consumer_name)", + "includeAll": true, + "label": "Consumer", + "multi": true, + "name": "consumer", + "options": [], + "query": { + "query": "label_values(nats_consumer_num_pending,consumer_name)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 2, + "regex": "", + "type": "query" + } + ] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "NATS JetStream", + "uid": "yQUo5l17k", + "version": 6, + "weekStart": "" +} diff --git a/dashboards/nats/nats-server.json b/dashboards/nats/nats-server.json new file mode 100644 index 00000000..02aa39b2 --- /dev/null +++ b/dashboards/nats/nats-server.json @@ -0,0 +1,1463 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "description": "NATS Server Dashboard for use with built-in Prometheus NATS Exporter into nats official helm charts", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 91, + "links": [], + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 10, + "panels": [], + "title": "OS Metrics", + "type": "row" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 1 + }, + "id": 1, + "options": { + "alertThreshold": true, + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "datasource": "PBFA97CFB590B2093", + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": true, + "expr": "nats_varz_cpu", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{server_id}}", + "metric": "nats_varz_cpu", + "refId": "A", + "step": 4 + } + ], + "title": "Server CPU", + "type": "timeseries" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 1 + }, + "id": 3, + "options": { + "alertThreshold": true, + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "datasource": "PBFA97CFB590B2093", + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": true, + "expr": "nats_varz_mem", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{server_id}}", + "metric": "nats_varz_mem", + "refId": "A", + "step": 4 + } + ], + "title": "Server Memory", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 8 + }, + "id": 11, + "panels": [], + "title": "Throughput", + "type": "row" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 0, + "y": 9 + }, + "id": 7, + "options": { + "alertThreshold": true, + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "datasource": "PBFA97CFB590B2093", + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": true, + "expr": "nats_varz_in_bytes", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{server_id}}", + "metric": "nats_varz_in_bytes", + "refId": "A", + "step": 10 + } + ], + "title": "Bytes In", + "type": "timeseries" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "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": 6, + "x": 6, + "y": 9 + }, + "id": 8, + "options": { + "alertThreshold": true, + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "datasource": "PBFA97CFB590B2093", + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": true, + "expr": "nats_varz_in_msgs", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{server_id}}", + "metric": "nats_varz_in_msgs", + "refId": "A", + "step": 10 + } + ], + "title": "NATS Msgs In", + "type": "timeseries" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 12, + "y": 9 + }, + "id": 5, + "options": { + "alertThreshold": true, + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "datasource": "PBFA97CFB590B2093", + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": true, + "expr": "nats_varz_out_bytes", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{server_id}}", + "metric": "nats_varz_out_bytes", + "refId": "A", + "step": 10 + } + ], + "title": "Bytes Out", + "type": "timeseries" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "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": 6, + "x": 18, + "y": 9 + }, + "id": 6, + "options": { + "alertThreshold": true, + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "datasource": "PBFA97CFB590B2093", + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": true, + "expr": "nats_varz_out_msgs", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{server_id}}", + "metric": "nats_varz_out_msgs", + "refId": "A", + "step": 10 + } + ], + "title": "NATS Msgs Out", + "type": "timeseries" + }, + { + "datasource": { + "datasource": "PBFA97CFB590B2093", + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 0, + "y": 16 + }, + "id": 15, + "options": { + "alertThreshold": true, + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "datasource": "PBFA97CFB590B2093", + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": true, + "expr": "sum(rate(nats_varz_in_bytes[1m]))", + "interval": "", + "intervalFactor": 2, + "legendFormat": "", + "metric": "nats_varz_in_bytes", + "refId": "A", + "step": 10 + } + ], + "title": "rate(Bytes In)", + "type": "timeseries" + }, + { + "datasource": { + "datasource": "PBFA97CFB590B2093", + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "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": 6, + "x": 6, + "y": 16 + }, + "id": 13, + "options": { + "alertThreshold": true, + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "datasource": "PBFA97CFB590B2093", + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": true, + "expr": "sum(rate(nats_varz_in_msgs[1m]))", + "interval": "", + "intervalFactor": 2, + "legendFormat": "", + "metric": "nats_varz_in_msgs", + "refId": "A", + "step": 10 + } + ], + "title": "rate(NATS Msgs In)", + "type": "timeseries" + }, + { + "datasource": { + "datasource": "PBFA97CFB590B2093", + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 12, + "y": 16 + }, + "id": 16, + "options": { + "alertThreshold": true, + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "datasource": "PBFA97CFB590B2093", + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": true, + "expr": "sum(rate(nats_varz_out_bytes[1m]))", + "interval": "", + "intervalFactor": 2, + "legendFormat": "", + "metric": "nats_varz_out_bytes", + "refId": "A", + "step": 10 + } + ], + "title": "rate(Bytes Out)", + "type": "timeseries" + }, + { + "datasource": { + "datasource": "PBFA97CFB590B2093", + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "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": 6, + "x": 18, + "y": 16 + }, + "id": 14, + "options": { + "alertThreshold": true, + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "datasource": "PBFA97CFB590B2093", + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": true, + "expr": "sum(rate(nats_varz_out_msgs[1m]))", + "interval": "", + "intervalFactor": 2, + "legendFormat": "", + "metric": "nats_varz_out_msgs", + "refId": "A", + "step": 10 + } + ], + "title": "rate(NATS Msgs Out)", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 23 + }, + "id": 12, + "panels": [], + "title": "Client Metrics", + "type": "row" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 6, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "always", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 24 + }, + "id": 2, + "options": { + "alertThreshold": true, + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "datasource": "PBFA97CFB590B2093", + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": true, + "expr": "nats_varz_connections", + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{server_id}}", + "metric": "nats_varz_connections", + "refId": "A", + "step": 2 + } + ], + "title": "Connections", + "type": "timeseries" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 6, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "always", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 24 + }, + "id": 4, + "options": { + "alertThreshold": true, + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "datasource": "PBFA97CFB590B2093", + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": true, + "expr": "nats_varz_subscriptions", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{server_id}}", + "metric": "nats_varz_subscriptions", + "refId": "A", + "step": 4 + } + ], + "title": "Subscriptions", + "type": "timeseries" + }, + { + "datasource": { + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "points", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 6, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "always", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 24 + }, + "id": 9, + "options": { + "alertThreshold": true, + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "datasource": "PBFA97CFB590B2093", + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "exemplar": true, + "expr": "nats_varz_slow_consumers", + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{server_id}}", + "metric": "nats_varz_slow_consumers", + "refId": "A", + "step": 2 + } + ], + "title": "Slow Consumers", + "type": "timeseries" + } + ], + "preload": false, + "refresh": "", + "schemaVersion": 40, + "tags": [], + "templating": { + "list": [ + { + "current": { + "text": "vm-shortterm", + "value": "59e01639-a99e-4945-bb94-10821e415ad5" + }, + "label": "datasource", + "name": "DS_PROMETHEUS", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "type": "datasource" + } + ] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "NATS Server Dashboard", + "uid": "4_zbf287k", + "version": 2, + "weekStart": "" +} diff --git a/hack/download-dashboards.sh b/hack/download-dashboards.sh index a1a6082a..d761f71c 100755 --- a/hack/download-dashboards.sh +++ b/hack/download-dashboards.sh @@ -83,6 +83,8 @@ modules/340-monitoring-kubernetes/monitoring/grafana-dashboards//flux/flux-stats modules/340-monitoring-kubernetes/monitoring/grafana-dashboards//kafka/strimzi-kafka.json modules/340-monitoring-kubernetes/monitoring/grafana-dashboards//seaweedfs/seaweedfs.json modules/340-monitoring-kubernetes/monitoring/grafana-dashboards//goldpinger/goldpinger.json +modules/340-monitoring-kubernetes/monitoring/grafana-dashboards//nats/nats-jetstream.json +modules/340-monitoring-kubernetes/monitoring/grafana-dashboards//nats/nats-server.json EOT diff --git a/packages/apps/nats/templates/nats.yaml b/packages/apps/nats/templates/nats.yaml index 4f52ff11..e5f5cf5d 100644 --- a/packages/apps/nats/templates/nats.yaml +++ b/packages/apps/nats/templates/nats.yaml @@ -95,10 +95,6 @@ spec: {{- with .Values.storageClass }} storageClassName: {{ . }} {{- end }} - promExporter: - enabled: true - podMonitor: - enabled: true {{- if .Values.external }} service: merge: diff --git a/packages/system/monitoring/dashboards.list b/packages/system/monitoring/dashboards.list index 1f4b2eea..6ad8630f 100644 --- a/packages/system/monitoring/dashboards.list +++ b/packages/system/monitoring/dashboards.list @@ -43,3 +43,5 @@ hubble/overview hubble/dns-namespace hubble/l7-http-metrics hubble/network-overview +nats/nats-jetstream +nats/nats-server diff --git a/packages/system/nats/charts/nats/Chart.yaml b/packages/system/nats/charts/nats/Chart.yaml index e59601a9..2f0edf48 100644 --- a/packages/system/nats/charts/nats/Chart.yaml +++ b/packages/system/nats/charts/nats/Chart.yaml @@ -1,5 +1,5 @@ apiVersion: v2 -appVersion: 2.10.17 +appVersion: 2.11.8 description: A Helm chart for the NATS.io High Speed Cloud Native Distributed Communications Technology. home: http://github.com/nats-io/k8s @@ -13,4 +13,4 @@ maintainers: name: The NATS Authors url: https://github.com/nats-io name: nats -version: 1.2.1 +version: 1.3.13 diff --git a/packages/system/nats/charts/nats/README.md b/packages/system/nats/charts/nats/README.md index 0916999d..0096a1bb 100644 --- a/packages/system/nats/charts/nats/README.md +++ b/packages/system/nats/charts/nats/README.md @@ -44,7 +44,7 @@ Everything in the NATS Config or Kubernetes Resources can be overridden by `merg | `container` | nats [k8s Container](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#container-v1-core) | yes | | `reloader` | config reloader [k8s Container](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#container-v1-core) | yes | | `promExporter` | prometheus exporter [k8s Container](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#container-v1-core) | no | -| `promExporter.podMonitor` | [prometheus PodMonitor](https://prometheus-operator.dev/docs/operator/api/#monitoring.coreos.com/v1.PodMonitor) | no | +| `promExporter.podMonitor` | [prometheus PodMonitor](https://prometheus-operator.dev/docs/api-reference/api/#monitoring.coreos.com/v1.PodMonitor) | no | | `service` | [k8s Service](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#service-v1-core) | yes | | `statefulSet` | [k8s StatefulSet](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#statefulset-v1-apps) | yes | | `podTemplate` | [k8s PodTemplate](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#pod-v1-core) | yes | @@ -60,7 +60,7 @@ Everything in the NATS Config or Kubernetes Resources can be overridden by `merg ### Merge -Merging is performed using the Helm `merge` function. Example - add NATS accounts and container resources: +Merging is performed using the Helm [`merge` function](https://helm.sh/docs/chart_template_guide/function_list/#merge-mustmerge). Example - add NATS accounts and container resources: ```yaml config: @@ -119,14 +119,22 @@ podTemplate: ### NATS Container Resources +We recommend setting both **requests and limits** - for both **CPU and memory** - **to the same value** for the following reasons: + +* It ensures your NATS pod has [predictable performance](https://www.datadoghq.com/blog/kubernetes-cpu-requests-limits/#predictability:~:text=If%20containers%20are,available%20capacity%20decreases.). +* The NATS server [automatically sets](https://github.com/nats-io/nats-server/blob/v2.11.0/main.go#L131-L132) [GOMAXPROCS](https://github.com/golang/go/blob/go1.24.1/src/runtime/extern.go#L230-L234) to the number of CPU cores defined in the `limits` section. If `limits` are not set, GOMAXPROCS defaults to the node's physical core count, which can lead to [poor performance](https://github.com/golang/go/issues/33803). +* The pod will be assigned to the ["Guaranteed" QoS class](https://kubernetes.io/docs/concepts/workloads/pods/pod-qos/#guaranteed), making it less likely to be evicted when node resources are constrained. + +Deviate from this recommendation only if you fully understand the implications of your settings. + ```yaml container: env: - # different from k8s units, suffix must be B, KiB, MiB, GiB, or TiB - # should be ~90% of memory limit - GOMEMLIMIT: 7GiB + # Different from k8s units, suffix must be B, KiB, MiB, GiB, or TiB + # Should be ~80% of memory limit + GOMEMLIMIT: 6GiB merge: - # recommended limit is at least 2 CPU cores and 8Gi Memory for production JetStream clusters + # Recommended minimum: at least 2 CPU cores and 8Gi memory for production JetStream clusters resources: requests: cpu: "2" @@ -138,11 +146,27 @@ container: ### Specify Image Version -```yaml -container: - image: - tag: x.y.z-alpine -``` +The container image can now be overridden by specifying either the image tag, an image digest, or a full image name. Examples below illustrate the options: + +- To set the tag: + ```yaml + container: + image: + tag: x.y.z-alpine + ``` +- To use an image digest, which overrides the tag: + ```yaml + container: + image: + repository: nats + digest: sha256:abcdef1234567890... + ``` +- To override the registry, repository, tag, and digest all at once, specify a full image name: + ```yaml + container: + image: + fullImageName: custom-reg.io/myimage@sha256:abcdef1234567890... + ``` ### Operator Mode with NATS Resolver diff --git a/packages/system/nats/charts/nats/files/stateful-set/pod-template.yaml b/packages/system/nats/charts/nats/files/stateful-set/pod-template.yaml index bb1d8d7b..9832ba34 100644 --- a/packages/system/nats/charts/nats/files/stateful-set/pod-template.yaml +++ b/packages/system/nats/charts/nats/files/stateful-set/pod-template.yaml @@ -69,3 +69,7 @@ spec: - {{ merge (dict "topologyKey" $k "labelSelector" (dict "matchLabels" (include "nats.selectorLabels" $ | fromYaml))) $v | toYaml | nindent 4 }} {{- end }} {{- end}} + + # terminationGracePeriodSeconds determines how long to wait for graceful shutdown + # this should be at least `lameDuckGracePeriod` + 20s shutdown overhead + terminationGracePeriodSeconds: 60 diff --git a/packages/system/nats/charts/nats/files/stateful-set/prom-exporter-container.yaml b/packages/system/nats/charts/nats/files/stateful-set/prom-exporter-container.yaml index c3e1b6fb..75f8a77c 100644 --- a/packages/system/nats/charts/nats/files/stateful-set/prom-exporter-container.yaml +++ b/packages/system/nats/charts/nats/files/stateful-set/prom-exporter-container.yaml @@ -27,4 +27,5 @@ args: {{- if .Values.config.gateway.enabled }} - -gatewayz {{- end }} -- http://localhost:{{ .Values.config.monitor.port }}/ +{{- $monitorProto := ternary "https" "http" .Values.config.monitor.tls.enabled }} +- {{ $monitorProto }}://{{ .Values.promExporter.monitorDomain }}:{{ .Values.config.monitor.port }}/ diff --git a/packages/system/nats/charts/nats/templates/_helpers.tpl b/packages/system/nats/charts/nats/templates/_helpers.tpl index ba831397..d8485943 100644 --- a/packages/system/nats/charts/nats/templates/_helpers.tpl +++ b/packages/system/nats/charts/nats/templates/_helpers.tpl @@ -147,10 +147,18 @@ app.kubernetes.io/component: nats-box Print the image */}} {{- define "nats.image" }} -{{- $image := printf "%s:%s" .repository .tag }} +{{- $image := "" }} +{{- if .digest }} +{{- $image = printf "%s@%s" .repository .digest }} +{{- else }} +{{- $image = printf "%s:%s" .repository .tag }} +{{- end }} {{- if or .registry .global.image.registry }} {{- $image = printf "%s/%s" (.registry | default .global.image.registry) $image }} -{{- end -}} +{{- end }} +{{- if .fullImageName }} +{{- $image = .fullImageName }} +{{- end }} image: {{ $image }} {{- if or .pullPolicy .global.image.pullPolicy }} imagePullPolicy: {{ .pullPolicy | default .global.image.pullPolicy }} @@ -274,7 +282,7 @@ output: string with following format rules */}} {{- define "nats.formatConfig" -}} {{- - (regexReplaceAll "\"<<\\s+(.*)\\s+>>\"" + (regexReplaceAll "\"<<\\s+(.*?)\\s+>>\"" (regexReplaceAll "\".*\\$include\": \"(.*)\",?" (include "toPrettyRawJson" .) "include ${1};") "${1}") -}} diff --git a/packages/system/nats/charts/nats/values.yaml b/packages/system/nats/charts/nats/values.yaml index 0b14ebd4..fa402f18 100644 --- a/packages/system/nats/charts/nats/values.yaml +++ b/packages/system/nats/charts/nats/values.yaml @@ -238,6 +238,7 @@ config: tls: # config.nats.tls must be enabled also # when enabled, monitoring port will use HTTPS with the options from config.nats.tls + # if promExporter is also enabled, consider setting promExporter.monitorDomain enabled: false profiling: @@ -312,9 +313,13 @@ config: container: image: repository: nats - tag: 2.10.17-alpine + tag: 2.11.8-alpine pullPolicy: registry: + # if digest is provided, it overrides tag (example: "sha256:abcdef1234567890") + digest: + # if fullImageName is provided, it overrides registry, repository, tag, and digest + fullImageName: # container port options # must be enabled in the config section also @@ -353,9 +358,11 @@ reloader: enabled: true image: repository: natsio/nats-server-config-reloader - tag: 0.15.0 + tag: 0.19.1 pullPolicy: registry: + digest: + fullImageName: # env var map, see nats.env for an example env: {} @@ -363,7 +370,7 @@ reloader: # all nats container volume mounts with the following prefixes # will be mounted into the reloader container natsVolumeMountPrefixes: - - /etc/ + - /etc/ # merge or patch the container # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#container-v1-core @@ -378,11 +385,16 @@ promExporter: enabled: false image: repository: natsio/prometheus-nats-exporter - tag: 0.15.0 + tag: 0.17.3 pullPolicy: registry: + digest: + fullImageName: port: 7777 + # if config.monitor.tls.enabled is set to true, monitorDomain must be set to the common name + # or a SAN used in the tls certificate + monitorDomain: localhost # env var map, see nats.env for an example env: {} @@ -398,13 +410,12 @@ promExporter: enabled: false # merge or patch the pod monitor - # https://prometheus-operator.dev/docs/operator/api/#monitoring.coreos.com/v1.PodMonitor + # https://prometheus-operator.dev/docs/api-reference/api/#monitoring.coreos.com/v1.PodMonitor merge: {} patch: [] # defaults to "{{ include "nats.fullname" $ }}" name: - ############################################################ # service ############################################################ @@ -511,7 +522,6 @@ serviceAccount: # defaults to "{{ include "nats.fullname" $ }}" name: - ############################################################ # natsBox # @@ -564,9 +574,11 @@ natsBox: container: image: repository: natsio/nats-box - tag: 0.14.3 + tag: 0.18.0 pullPolicy: registry: + digest: + fullImageName: # env var map, see nats.env for an example env: {} @@ -624,7 +636,6 @@ natsBox: # defaults to "{{ include "nats.fullname" $ }}-box" name: - ################################################################################ # Extra user-defined resources ################################################################################ diff --git a/packages/system/nats/values.yaml b/packages/system/nats/values.yaml index a28cadbe..87f730dd 100644 --- a/packages/system/nats/values.yaml +++ b/packages/system/nats/values.yaml @@ -9,3 +9,7 @@ nats: cluster: routeURLs: k8sClusterDomain: cozy.local + promExporter: + enabled: true + podMonitor: + enabled: true From e7ffc21743148afb55b991b0194128163bfa114b Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 18 Feb 2026 01:23:02 +0300 Subject: [PATCH 325/889] feat(harbor): switch registry storage to S3 via COSI BucketClaim Replace PVC-based registry storage with S3 via COSI BucketClaim/BucketAccess. The system chart parses BucketInfo secret and creates a registry-s3 Secret with REGISTRY_STORAGE_S3_* env vars that override Harbor's ConfigMap values. - Add bucket-secret.yaml to system chart (BucketInfo parser) - Remove storageType/size from registry config (S3 is now the only option) - Use Harbor's existingSecret support for S3 credentials injection - Add objectstorage-controller to PackageSource dependencies - Update E2E test with COSI bucket provisioning waits and diagnostics Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- hack/e2e-apps/harbor.bats | 15 ++++++++-- packages/apps/harbor/README.md | 2 -- packages/apps/harbor/templates/bucket.yaml | 2 -- packages/apps/harbor/templates/harbor.yaml | 30 +++---------------- packages/apps/harbor/values.schema.json | 26 ---------------- packages/apps/harbor/values.yaml | 8 ----- .../platform/sources/harbor-application.yaml | 1 + packages/system/harbor-rd/cozyrds/harbor.yaml | 4 +-- .../harbor/templates/bucket-secret.yaml | 20 +++++++++++++ packages/system/harbor/values.yaml | 2 ++ 10 files changed, 42 insertions(+), 68 deletions(-) create mode 100644 packages/system/harbor/templates/bucket-secret.yaml diff --git a/hack/e2e-apps/harbor.bats b/hack/e2e-apps/harbor.bats index 076a1841..09215546 100644 --- a/hack/e2e-apps/harbor.bats +++ b/hack/e2e-apps/harbor.bats @@ -16,8 +16,6 @@ spec: resources: {} resourcesPreset: "nano" registry: - storageType: "pvc" - size: 5Gi resources: {} resourcesPreset: "nano" jobservice: @@ -37,6 +35,13 @@ spec: EOF sleep 5 kubectl -n tenant-test wait hr $release --timeout=60s --for=condition=ready + + # Wait for COSI to provision bucket + kubectl -n tenant-test wait bucketclaims.objectstorage.k8s.io $release-registry \ + --timeout=300s --for=jsonpath='{.status.bucketReady}'=true + kubectl -n tenant-test wait bucketaccesses.objectstorage.k8s.io $release-registry \ + --timeout=60s --for=jsonpath='{.status.accessGranted}'=true + kubectl -n tenant-test wait hr $release-system --timeout=600s --for=condition=ready || { echo "=== HelmRelease status ===" kubectl -n tenant-test get hr $release-system -o yaml 2>&1 || true @@ -46,6 +51,12 @@ EOF kubectl -n tenant-test get events --sort-by='.lastTimestamp' 2>&1 | tail -30 || true echo "=== ExternalArtifact ===" kubectl -n cozy-system get externalartifact cozystack-harbor-application-default-harbor-system -o yaml 2>&1 || true + echo "=== BucketClaim status ===" + kubectl -n tenant-test get bucketclaims.objectstorage.k8s.io $release-registry -o yaml 2>&1 || true + echo "=== BucketAccess status ===" + kubectl -n tenant-test get bucketaccesses.objectstorage.k8s.io $release-registry -o yaml 2>&1 || true + echo "=== BucketAccess Secret ===" + kubectl -n tenant-test get secret $release-registry-bucket -o jsonpath='{.data.BucketInfo}' 2>&1 | base64 -d 2>&1 || true false } kubectl -n tenant-test wait deploy $release-core --timeout=120s --for=condition=available diff --git a/packages/apps/harbor/README.md b/packages/apps/harbor/README.md index 3c870be3..df44ca8d 100644 --- a/packages/apps/harbor/README.md +++ b/packages/apps/harbor/README.md @@ -22,8 +22,6 @@ Harbor is an open source trusted cloud native registry project that stores, sign | `core.resources.memory` | Amount of memory allocated. | `quantity` | `""` | | `core.resourcesPreset` | Default sizing preset used when `resources` is omitted. | `string` | `small` | | `registry` | Container image registry configuration. | `object` | `{}` | -| `registry.storageType` | Storage backend type for images. | `string` | `pvc` | -| `registry.size` | Persistent Volume size (only used when storageType is pvc). | `quantity` | `10Gi` | | `registry.resources` | Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied. | `object` | `{}` | | `registry.resources.cpu` | Number of CPU cores allocated. | `quantity` | `""` | | `registry.resources.memory` | Amount of memory allocated. | `quantity` | `""` | diff --git a/packages/apps/harbor/templates/bucket.yaml b/packages/apps/harbor/templates/bucket.yaml index 885cbfc8..a9e60988 100644 --- a/packages/apps/harbor/templates/bucket.yaml +++ b/packages/apps/harbor/templates/bucket.yaml @@ -1,4 +1,3 @@ -{{- if eq .Values.registry.storageType "s3" }} {{- $seaweedfs := .Values._namespace.seaweedfs }} apiVersion: objectstorage.k8s.io/v1alpha1 kind: BucketClaim @@ -18,4 +17,3 @@ spec: bucketClaimName: {{ .Release.Name }}-registry credentialsSecretName: {{ .Release.Name }}-registry-bucket protocol: s3 -{{- end }} diff --git a/packages/apps/harbor/templates/harbor.yaml b/packages/apps/harbor/templates/harbor.yaml index 0e9f4fd5..3ba09bfb 100644 --- a/packages/apps/harbor/templates/harbor.yaml +++ b/packages/apps/harbor/templates/harbor.yaml @@ -66,21 +66,9 @@ spec: name: {{ .Release.Name }}-credentials valuesKey: redis-password targetPath: harbor.redis.external.password - {{- if eq .Values.registry.storageType "s3" }} - - kind: Secret - name: {{ .Release.Name }}-registry-bucket - valuesKey: accessKey - targetPath: harbor.persistence.imageChartStorage.s3.accesskey - - kind: Secret - name: {{ .Release.Name }}-registry-bucket - valuesKey: secretKey - targetPath: harbor.persistence.imageChartStorage.s3.secretkey - - kind: Secret - name: {{ .Release.Name }}-registry-bucket - valuesKey: endpoint - targetPath: harbor.persistence.imageChartStorage.s3.regionendpoint - {{- end }} values: + bucket: + secretName: {{ .Release.Name }}-registry-bucket db: replicas: {{ .Values.database.replicas }} size: {{ .Values.database.size }} @@ -106,31 +94,21 @@ spec: persistence: enabled: true resourcePolicy: "keep" - {{- if eq .Values.registry.storageType "s3" }} imageChartStorage: type: s3 s3: + existingSecret: {{ .Release.Name }}-registry-s3 region: us-east-1 bucket: {{ .Release.Name }}-registry secure: false v4auth: true - {{- end }} - {{- if or (eq .Values.registry.storageType "pvc") .Values.trivy.enabled }} + {{- if .Values.trivy.enabled }} persistentVolumeClaim: - {{- if eq .Values.registry.storageType "pvc" }} - registry: - size: {{ .Values.registry.size }} - {{- with .Values.storageClass }} - storageClass: {{ . }} - {{- end }} - {{- end }} - {{- if .Values.trivy.enabled }} trivy: size: {{ .Values.trivy.size }} {{- with .Values.storageClass }} storageClass: {{ . }} {{- end }} - {{- end }} {{- end }} portal: resources: {{- include "cozy-lib.resources.defaultingSanitize" (list "nano" (dict) $) | nindent 10 }} diff --git a/packages/apps/harbor/values.schema.json b/packages/apps/harbor/values.schema.json index 71cc1d98..1352f5dc 100644 --- a/packages/apps/harbor/values.schema.json +++ b/packages/apps/harbor/values.schema.json @@ -179,9 +179,6 @@ "description": "Container image registry configuration.", "type": "object", "default": {}, - "required": [ - "storageType" - ], "properties": { "resources": { "description": "Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.", @@ -229,29 +226,6 @@ "xlarge", "2xlarge" ] - }, - "size": { - "description": "Persistent Volume size (only used when storageType is pvc).", - "default": "10Gi", - "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 - }, - "storageType": { - "description": "Storage backend type for images.", - "type": "string", - "default": "pvc", - "enum": [ - "s3", - "pvc" - ] } } }, diff --git a/packages/apps/harbor/values.yaml b/packages/apps/harbor/values.yaml index f1131bd1..de44eaff 100644 --- a/packages/apps/harbor/values.yaml +++ b/packages/apps/harbor/values.yaml @@ -34,20 +34,12 @@ core: resources: {} resourcesPreset: "small" -## @enum {string} StorageType - Storage backend for container image registry. -## @value s3 - S3-compatible object storage via COSI BucketClaim (requires SeaweedFS in tenant). -## @value pvc - Persistent Volume Claim. - ## @typedef {struct} Registry - Container image registry configuration. -## @field {StorageType} storageType - Storage backend type for images. -## @field {quantity} [size] - Persistent Volume size (only used when storageType is pvc). ## @field {Resources} [resources] - Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied. ## @field {ResourcesPreset} [resourcesPreset] - Default sizing preset used when `resources` is omitted. ## @param {Registry} registry - Container image registry configuration. registry: - storageType: "pvc" - size: 10Gi resources: {} resourcesPreset: "small" diff --git a/packages/core/platform/sources/harbor-application.yaml b/packages/core/platform/sources/harbor-application.yaml index b9bc8dc3..c2773bdd 100644 --- a/packages/core/platform/sources/harbor-application.yaml +++ b/packages/core/platform/sources/harbor-application.yaml @@ -15,6 +15,7 @@ spec: - cozystack.networking - cozystack.postgres-operator - cozystack.redis-operator + - cozystack.objectstorage-controller libraries: - name: cozy-lib path: library/cozy-lib diff --git a/packages/system/harbor-rd/cozyrds/harbor.yaml b/packages/system/harbor-rd/cozyrds/harbor.yaml index 80924530..0112dc11 100644 --- a/packages/system/harbor-rd/cozyrds/harbor.yaml +++ b/packages/system/harbor-rd/cozyrds/harbor.yaml @@ -8,7 +8,7 @@ spec: singular: harbor plural: harbors openAPISchema: |- - {"title":"Chart Values","type":"object","properties":{"core":{"description":"Core API server configuration.","type":"object","default":{},"properties":{"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"small","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}},"database":{"description":"PostgreSQL database configuration.","type":"object","default":{},"required":["replicas","size"],"properties":{"replicas":{"description":"Number of database instances.","type":"integer","default":2},"size":{"description":"Persistent Volume size for database storage.","default":"5Gi","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":"Hostname for external access to Harbor (defaults to 'harbor' subdomain for the tenant host).","type":"string","default":""},"jobservice":{"description":"Background job service configuration.","type":"object","default":{},"properties":{"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"nano","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}},"redis":{"description":"Redis cache configuration.","type":"object","default":{},"required":["replicas","size"],"properties":{"replicas":{"description":"Number of Redis replicas.","type":"integer","default":2},"size":{"description":"Persistent Volume size for cache storage.","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}}},"registry":{"description":"Container image registry configuration.","type":"object","default":{},"required":["storageType"],"properties":{"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"small","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]},"size":{"description":"Persistent Volume size (only used when storageType is pvc).","default":"10Gi","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},"storageType":{"description":"Storage backend type for images.","type":"string","default":"pvc","enum":["s3","pvc"]}}},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":""},"trivy":{"description":"Trivy vulnerability scanner configuration.","type":"object","default":{},"required":["enabled","size"],"properties":{"enabled":{"description":"Enable or disable the vulnerability scanner.","type":"boolean","default":true},"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"nano","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]},"size":{"description":"Persistent Volume size for vulnerability database cache.","default":"5Gi","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":{"core":{"description":"Core API server configuration.","type":"object","default":{},"properties":{"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"small","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}},"database":{"description":"PostgreSQL database configuration.","type":"object","default":{},"required":["replicas","size"],"properties":{"replicas":{"description":"Number of database instances.","type":"integer","default":2},"size":{"description":"Persistent Volume size for database storage.","default":"5Gi","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":"Hostname for external access to Harbor (defaults to 'harbor' subdomain for the tenant host).","type":"string","default":""},"jobservice":{"description":"Background job service configuration.","type":"object","default":{},"properties":{"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"nano","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}},"redis":{"description":"Redis cache configuration.","type":"object","default":{},"required":["replicas","size"],"properties":{"replicas":{"description":"Number of Redis replicas.","type":"integer","default":2},"size":{"description":"Persistent Volume size for cache storage.","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}}},"registry":{"description":"Container image registry configuration.","type":"object","default":{},"properties":{"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"small","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":""},"trivy":{"description":"Trivy vulnerability scanner configuration.","type":"object","default":{},"required":["enabled","size"],"properties":{"enabled":{"description":"Enable or disable the vulnerability scanner.","type":"boolean","default":true},"resources":{"description":"Explicit CPU and memory configuration. When omitted, the preset defined in `resourcesPreset` is applied.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","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":"Amount of memory allocated.","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}}},"resourcesPreset":{"description":"Default sizing preset used when `resources` is omitted.","type":"string","default":"nano","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]},"size":{"description":"Persistent Volume size for vulnerability database cache.","default":"5Gi","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: "harbor-" labels: @@ -29,7 +29,7 @@ spec: - registry - container icon: "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHJvbGU9ImltZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHZpZXdCb3g9IjEuMDUgLTEuOTUgMzU5LjQxIDM2MS42NiI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQiIHgxPSIyNjQuNzkiIHgyPSIyNjcuMjciIHkxPSI5NTIuMzkiIHkyPSI5NTIuMzkiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMzAuNDMgMCAwIC0zMC40MyAtNzk1NS4yMiAyOTI4NS43NSkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiM2MGI5MzIiLz48c3RvcCBvZmZzZXQ9Ii4yOCIgc3RvcC1jb2xvcj0iIzYwYjkzMiIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzM2N2MzNCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQtMiIgeDE9IjI2My43NyIgeDI9IjI2Ni4yNiIgeTE9Ijk1NS42NSIgeTI9Ijk1NS42NSIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgyNy4yMSAwIDAgLTI3LjIxIC03MDczLjg1IDI2MTY5LjQxKSIgeGxpbms6aHJlZj0iI2xpbmVhci1ncmFkaWVudCIvPjxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50LTMiIHgxPSIyNjMuMjgiIHgyPSIyNjUuNzYiIHkxPSI5NTMuNzQiIHkyPSI5NTMuNzQiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMjUuNzUgMCAwIC0yNS43NSAtNjY3MS4xMyAyNDgxMi4yMykiIHhsaW5rOmhyZWY9IiNsaW5lYXItZ3JhZGllbnQiLz48bGluZWFyR3JhZGllbnQgaWQ9ImxpbmVhci1ncmFkaWVudC00IiB4MT0iMjYzLjc3IiB4Mj0iMjY2LjI1IiB5MT0iOTUzLjIiIHkyPSI5NTMuMiIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgyNy4xIDAgMCAtMjcuMSAtNzA0MC45IDI2MTAyLjQ5KSIgeGxpbms6aHJlZj0iI2xpbmVhci1ncmFkaWVudCIvPjxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50LTUiIHgxPSIyNjIuNzMiIHgyPSIyNjUuMjEiIHkxPSI5NTQuMzQiIHkyPSI5NTQuMzQiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMjQuNCAwIDAgLTI0LjQgLTYzMDEuMzYgMjM1MjEuOTcpIiB4bGluazpocmVmPSIjbGluZWFyLWdyYWRpZW50Ii8+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQtNiIgeDE9IjI3Mi4xNCIgeDI9IjI3NC42MiIgeTE9Ijk1NS4xNSIgeTI9Ijk1NS4xNSIgZ3JhZGllbnRUcmFuc2Zvcm09InNjYWxlKDY2LjA5IC02Ni4wOSkgcm90YXRlKDM2LjUyIDE1ODguMTUzIDY4LjE0OCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiM0NTk2ZDgiLz48c3RvcCBvZmZzZXQ9Ii4yIiBzdG9wLWNvbG9yPSIjNDU5NmQ4Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjZmZmIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9ImxpbmVhci1ncmFkaWVudC03IiB4MT0iMjcwLjY1IiB4Mj0iMjczLjEzIiB5MT0iOTUyLjM4IiB5Mj0iOTUyLjM4IiBncmFkaWVudFRyYW5zZm9ybT0ic2NhbGUoNzcuOCAtNzcuOCkgcm90YXRlKC0xMS41NCAtNDU4Ny4yMDkgMTgwMy4zMjMpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjNDE5NGQ3Ii8+PHN0b3Agb2Zmc2V0PSIuMiIgc3RvcC1jb2xvcj0iIzQxOTRkNyIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI2ZmZiIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQtOCIgeDE9IjI3MC45NyIgeDI9IjI3My40NSIgeTE9Ijk1My43NSIgeTI9Ijk1My43NSIgZ3JhZGllbnRUcmFuc2Zvcm09InNjYWxlKDcxLjM1IC03MS4zNSkgcm90YXRlKDEwLjIzIDU0NzcuMzcgLTEwMjQuNjAyKSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iIzQxOTRkNyIvPjxzdG9wIG9mZnNldD0iLjMzIiBzdG9wLWNvbG9yPSIjNDQ5NmQ4Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjZmZmIi8+PC9saW5lYXJHcmFkaWVudD48Y2xpcFBhdGggaWQ9ImNsaXAtcGF0aCI+PHBhdGggZD0iTTE3NC40IDMwMi41MmwtNjguNjUtMzAuMjYtMy44MyA0My4yMSA3NS41IDIzLjk4LTMuMDItMzYuOTN6IiBjbGFzcz0iY2xzLTEiLz48L2NsaXBQYXRoPjxjbGlwUGF0aCBpZD0iY2xpcC1wYXRoLTIiPjxwYXRoIGQ9Ik0xMTkuMTkgMTM1LjM4aDM4LjU1djMyLjg5aC05LjE1di0xNGExMC4xMyAxMC4xMyAwIDEgMC0yMC4yNiAwdjE0aC05LjE1em0tNy43IDcybDU3LjIgMjUuMjEtMy45NC00OC4yNGg3LjQ5di0xNi4wOGgtNS41NXYtMzIuODloNS41NXYtOS40NWwtMzAuODYtMzAuMTl2LTIuMTJhMi45MSAyLjkxIDAgMCAwLTUuODIgMHYyLjEybC0zMC44NiAzMC4xOXY5LjQ1aDUuNTZ2MzIuODloLTUuNTZ2MTYuMTJoOC44MnoiIGNsYXNzPSJjbHMtMSIvPjwvY2xpcFBhdGg+PGNsaXBQYXRoIGlkPSJjbGlwLXBhdGgtMyI+PHBhdGggZD0iTTEwOC4xNCAyNDUuMjhsNjMuODggMjguMTYtLjk2LTExLjczLTYxLjk2LTI3LjMtLjk2IDEwLjg3eiIgY2xhc3M9ImNscy0xIi8+PC9jbGlwUGF0aD48Y2xpcFBhdGggaWQ9ImNsaXAtcGF0aC00Ij48cGF0aCBkPSJNMTA2LjQ2IDI2NC4yMWw2Ny4yMyAyOS42My0uOTYtMTEuNzItNjUuMzEtMjguNzgtLjk2IDEwLjg3eiIgY2xhc3M9ImNscy0xIi8+PC9jbGlwUGF0aD48Y2xpcFBhdGggaWQ9ImNsaXAtcGF0aC01Ij48cGF0aCBkPSJNMTEwLjc3IDIxNS40OGwtLjk2IDEwLjg3IDYwLjU0IDI2LjY4LS45NS0xMS43Mi01OC42My0yNS44M3oiIGNsYXNzPSJjbHMtMSIvPjwvY2xpcFBhdGg+PGNsaXBQYXRoIGlkPSJjbGlwLXBhdGgtNiI+PHBhdGggZD0iTTMxMy4xMyA2Ny41OWExNzUuMzEgMTc1LjMxIDAgMCAwLTI5Ljc1LTI4LjEzYy0xLjU3LTEuMTctMy4xOC0yLjMtNC43OS0zLjQyTDI1NiA1OS41bC04Mi4zNCA4NS42MyAxMTMuNDEtNTguNjggMjkuMDctMTVjLTEuMDEtMS4zMS0xLjk4LTIuNjItMy4wMS0zLjg2eiIgY2xhc3M9ImNscy0xIi8+PC9jbGlwUGF0aD48Y2xpcFBhdGggaWQ9ImNsaXAtcGF0aC03Ij48cGF0aCBkPSJNMzUzLjU5IDE3Ny42MWMwLTItLjE0LTQtLjIyLTUuOTNsLTMyLjIxLTIuMzEtMTQ3LjQ3LTEwLjU4TDMxOC4zNiAyMDlsMzAuNDEgMTAuNTVjLjA5LS4zNi4xOS0uNzEuMjgtMS4wOGExNzMuNjUgMTczLjY1IDAgMCAwIDQuNTctMzkuNDd2LTEuMzd6IiBjbGFzcz0iY2xzLTEiLz48L2NsaXBQYXRoPjxjbGlwUGF0aCBpZD0iY2xpcC1wYXRoLTgiPjxwYXRoIGQ9Ik0zNDguODQgMTM4LjYxYTE3Mi41NSAxNzIuNTUgMCAwIDAtMTMuODQtMzcuN2wtMzAuOTQgOS45NEwxNzUuOTIgMTUybDE0Mi00LjgzIDMyLjUtMS4xMWMtLjUtMi40OC0uOTktNC45OC0xLjU4LTcuNDV6IiBjbGFzcz0iY2xzLTEiLz48L2NsaXBQYXRoPjxzdHlsZT4uY2xzLTF7ZmlsbDpub25lfS5jbHMtMTN7ZmlsbDojNjk2NTY2fTwvc3R5bGU+PC9kZWZzPjxnIGlkPSJnMTIiPjxwYXRoIGlkPSJwYXRoMTQiIGZpbGw9IiNmZmYiIGQ9Ik0zMC44OSAxNzlhMTQ4Ljg3IDE0OC44NyAwIDEgMSAxNDguODcgMTQ4Ljg1QTE0OC44NyAxNDguODcgMCAwIDEgMzAuODkgMTc5Ii8+PGcgaWQ9ImczMCIgY2xpcC1wYXRoPSJ1cmwoI2NsaXAtcGF0aCkiPjxnIGlkPSJnMzIiPjxwYXRoIGlkPSJwYXRoNDYiIGZpbGw9InVybCgjbGluZWFyLWdyYWRpZW50KSIgZD0iTTE3NC40IDMwMi41MmwtNjguNjUtMzAuMjUtMy44MiA0My4yIDc1LjUgMjQtMy0zNi45MyIvPjwvZz48L2c+PGcgaWQ9Imc0OCIgY2xpcC1wYXRoPSJ1cmwoI2NsaXAtcGF0aC0yKSI+PGcgaWQ9Imc1MCI+PHBhdGggaWQ9InBhdGg2NCIgZmlsbD0idXJsKCNsaW5lYXItZ3JhZGllbnQtMikiIGQ9Ik0xMTkuMTkgMTM1LjM4aDM4LjU1djMyLjg5aC05LjE1di0xNGExMC4xMyAxMC4xMyAwIDEgMC0yMC4yNiAwdjE0aC05LjE1em0tNy43IDcybDU3LjIgMjUuMjEtMy45NC00OC4yNGg3LjQ5di0xNi4wOGgtNS41NXYtMzIuODloNS41NXYtOS40NWwtMzAuODYtMzAuMTl2LTIuMTJhMi45MSAyLjkxIDAgMCAwLTUuODIgMHYyLjEybC0zMC44NiAzMC4xOXY5LjQ1aDUuNTZ2MzIuODloLTUuNTZ2MTYuMTJoOC44MmwtMiAyMyIvPjwvZz48L2c+PGcgaWQ9Imc2NiIgY2xpcC1wYXRoPSJ1cmwoI2NsaXAtcGF0aC0zKSI+PGcgaWQ9Imc2OCI+PHBhdGggaWQ9InBhdGg4MiIgZmlsbD0idXJsKCNsaW5lYXItZ3JhZGllbnQtMykiIGQ9Ik0xMDguMTMgMjQ1LjI4TDE3MiAyNzMuNDRsLTEtMTEuNzItNjItMjcuMzEtMSAxMC44NyIvPjwvZz48L2c+PGcgaWQ9Imc4NCIgY2xpcC1wYXRoPSJ1cmwoI2NsaXAtcGF0aC00KSI+PGcgaWQ9Imc4NiI+PHBhdGggaWQ9InBhdGgxMDAiIGZpbGw9InVybCgjbGluZWFyLWdyYWRpZW50LTQpIiBkPSJNMTA2LjQ2IDI2NC4yMWw2Ny4yMyAyOS42My0xLTExLjcyLTY1LjMxLTI4Ljc4LTEgMTAuODciLz48L2c+PC9nPjxnIGlkPSJnMTAyIiBjbGlwLXBhdGg9InVybCgjY2xpcC1wYXRoLTUpIj48ZyBpZD0iZzEwNCI+PHBhdGggaWQ9InBhdGgxMTgiIGZpbGw9InVybCgjbGluZWFyLWdyYWRpZW50LTUpIiBkPSJNMTEwLjc3IDIxNS40OGwtMSAxMC44OEwxNzAuMzUgMjUzbC0xLTExLjcyLTU4LjYyLTI1LjgzIi8+PC9nPjwvZz48cGF0aCBpZD0icGF0aDEyMCIgZD0iTTMwNC4wNyAxMTAuODVsMzAuOTMtOS45NGMtLjExLS4yMi0uMjEtLjQ1LS4zMi0uNjZhMTc0LjQxIDE3NC40MSAwIDAgMC0xOC41NS0yOC44M2wtMjkuMDcgMTVhMTQyLjcxIDE0Mi43MSAwIDAgMSAxNi43MyAyMy44N2MuMS4xNy4xOC4zNS4yNy41MyIgY2xhc3M9ImNscy0xMyIvPjxwYXRoIGlkPSJwYXRoMTIyIiBkPSJNMzIxLjE1IDE2OS4zN2wzMi4yMSAyLjMxYTE3Mi44NiAxNzIuODYgMCAwIDAtMy0yNS41OWwtMzIuNSAxLjExYTE0MSAxNDEgMCAwIDEgMy4yNSAyMi4xNyIgY2xhc3M9ImNscy0xMyIvPjxwYXRoIGlkPSJwYXRoMTI0IiBkPSJNMTgyIDMyMC44MWMtNzguMiAwLTE0MS44My02My42Mi0xNDEuODMtMTQxLjgyUzEwMy44MiAzNy4xNiAxODIgMzcuMTZhMTQwLjkzIDE0MC45MyAwIDAgMSA3Ni4zIDIyLjM0TDI4MC44NSAzNkExNzIuODYgMTcyLjg2IDAgMCAwIDE4MiA1LjExQzg2LjE1IDUuMTEgOC4xNSA4My4xMSA4LjE1IDE3OXM3OCAxNzMuODUgMTczLjg1IDE3My44NWM4MS45IDAgMTUwLjY5LTU3IDE2OS0xMzMuMzJMMzIwLjYyIDIwOWMtMTMuOCA2My44NC03MC42OSAxMTEuODMtMTM4LjYgMTExLjgzIiBjbGFzcz0iY2xzLTEzIi8+PGcgaWQ9ImcxMjYiIGNsaXAtcGF0aD0idXJsKCNjbGlwLXBhdGgtNikiPjxnIGlkPSJnMTI4Ij48cGF0aCBpZD0icGF0aDE0MiIgZmlsbD0idXJsKCNsaW5lYXItZ3JhZGllbnQtNikiIGQ9Ik0zMTMuMTMgNjcuNTlhMTc1LjMxIDE3NS4zMSAwIDAgMC0yOS43NS0yOC4xM2MtMS41Ny0xLjE3LTMuMTgtMi4zLTQuNzktMy40MkwyNTYgNTkuNWwtODIuMzQgODUuNjMgMTEzLjQxLTU4LjY4IDI5LjA3LTE1Yy0xLTEuMjctMi0yLjU4LTMtMy44MiIvPjwvZz48L2c+PGcgaWQ9ImcxNDQiIGNsaXAtcGF0aD0idXJsKCNjbGlwLXBhdGgtNykiPjxnIGlkPSJnMTQ2Ij48cGF0aCBpZD0icGF0aDE2MCIgZmlsbD0idXJsKCNsaW5lYXItZ3JhZGllbnQtNykiIGQ9Ik0zNTMuNTkgMTc3LjYxYzAtMi0uMTQtNC0uMjItNS45M2wtMzIuMjEtMi4zMS0xNDcuNDctMTAuNThMMzE4LjM2IDIwOWwzMC40MSAxMC41NWMuMDktLjM2LjE5LS43MS4yOC0xLjA4YTE3My42NSAxNzMuNjUgMCAwIDAgNC41Ny0zOS40N3YtMS4zNyIvPjwvZz48L2c+PGcgaWQ9ImcxNjIiIGNsaXAtcGF0aD0idXJsKCNjbGlwLXBhdGgtOCkiPjxnIGlkPSJnMTY0Ij48cGF0aCBpZD0icGF0aDE3OCIgZmlsbD0idXJsKCNsaW5lYXItZ3JhZGllbnQtOCkiIGQ9Ik0zNDguODQgMTM4LjYxYTE3Mi41NSAxNzIuNTUgMCAwIDAtMTMuODQtMzcuN2wtMzAuOTQgOS45NEwxNzUuOTIgMTUybDE0Mi00LjgzIDMyLjUtMS4xMWMtLjQ4LTIuNTEtMS01LTEuNTYtNy40OCIvPjwvZz48L2c+PC9nPjwvc3ZnPg==" - keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "host"], ["spec", "storageClass"], ["spec", "core"], ["spec", "core", "resources"], ["spec", "core", "resourcesPreset"], ["spec", "registry"], ["spec", "registry", "storageType"], ["spec", "registry", "size"], ["spec", "registry", "resources"], ["spec", "registry", "resourcesPreset"], ["spec", "jobservice"], ["spec", "jobservice", "resources"], ["spec", "jobservice", "resourcesPreset"], ["spec", "trivy"], ["spec", "trivy", "enabled"], ["spec", "trivy", "size"], ["spec", "trivy", "resources"], ["spec", "trivy", "resourcesPreset"], ["spec", "database"], ["spec", "database", "size"], ["spec", "database", "replicas"], ["spec", "redis"], ["spec", "redis", "size"], ["spec", "redis", "replicas"]] + keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "host"], ["spec", "storageClass"], ["spec", "core"], ["spec", "core", "resources"], ["spec", "core", "resourcesPreset"], ["spec", "registry"], ["spec", "registry", "resources"], ["spec", "registry", "resourcesPreset"], ["spec", "jobservice"], ["spec", "jobservice", "resources"], ["spec", "jobservice", "resourcesPreset"], ["spec", "trivy"], ["spec", "trivy", "enabled"], ["spec", "trivy", "size"], ["spec", "trivy", "resources"], ["spec", "trivy", "resourcesPreset"], ["spec", "database"], ["spec", "database", "size"], ["spec", "database", "replicas"], ["spec", "redis"], ["spec", "redis", "size"], ["spec", "redis", "replicas"]] secrets: exclude: [] include: diff --git a/packages/system/harbor/templates/bucket-secret.yaml b/packages/system/harbor/templates/bucket-secret.yaml new file mode 100644 index 00000000..241fe638 --- /dev/null +++ b/packages/system/harbor/templates/bucket-secret.yaml @@ -0,0 +1,20 @@ +{{- if .Values.bucket.secretName }} +{{- $existingSecret := lookup "v1" "Secret" .Release.Namespace .Values.bucket.secretName }} +{{- $bucketInfo := fromJson (b64dec (index $existingSecret.data "BucketInfo")) }} +{{- $accessKeyID := index $bucketInfo.spec.secretS3 "accessKeyID" }} +{{- $accessSecretKey := index $bucketInfo.spec.secretS3 "accessSecretKey" }} +{{- $endpoint := index $bucketInfo.spec.secretS3 "endpoint" }} +{{- $bucketName := $bucketInfo.spec.bucketName }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Values.harbor.fullnameOverride }}-registry-s3 +type: Opaque +stringData: + REGISTRY_STORAGE_S3_ACCESSKEY: {{ $accessKeyID | quote }} + REGISTRY_STORAGE_S3_SECRETKEY: {{ $accessSecretKey | quote }} + REGISTRY_STORAGE_S3_REGIONENDPOINT: {{ $endpoint | quote }} + REGISTRY_STORAGE_S3_BUCKET: {{ $bucketName | quote }} + REGISTRY_STORAGE_S3_REGION: "us-east-1" +{{- end }} diff --git a/packages/system/harbor/values.yaml b/packages/system/harbor/values.yaml index 9038bd38..faac7068 100644 --- a/packages/system/harbor/values.yaml +++ b/packages/system/harbor/values.yaml @@ -1,4 +1,6 @@ harbor: {} +bucket: + secretName: "" db: replicas: 2 size: 5Gi From bff5468b52319c05d3d355a1e70402ab1b552413 Mon Sep 17 00:00:00 2001 From: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com> Date: Tue, 17 Feb 2026 22:36:40 +0000 Subject: [PATCH 326/889] Prepare release v1.0.0-beta.6 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/apps/kubernetes/images/ubuntu-container-disk.tag | 2 +- packages/core/installer/values.yaml | 4 ++-- packages/core/platform/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/backup-controller/values.yaml | 2 +- packages/system/backupstrategy-controller/values.yaml | 2 +- packages/system/cozystack-api/values.yaml | 2 +- packages/system/cozystack-controller/values.yaml | 2 +- packages/system/dashboard/templates/configmap.yaml | 2 +- packages/system/dashboard/values.yaml | 6 +++--- .../system/grafana-operator/images/grafana-dashboards.tag | 2 +- packages/system/kamaji/values.yaml | 4 ++-- packages/system/kubeovn-plunger/values.yaml | 2 +- packages/system/kubeovn-webhook/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 +- 22 files changed, 26 insertions(+), 26 deletions(-) diff --git a/packages/apps/http-cache/images/nginx-cache.tag b/packages/apps/http-cache/images/nginx-cache.tag index ee4d8890..2d0e803a 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:9e34fd50393b418d9516aadb488067a3a63675b045811beb1c0afc9c61e149e8 +ghcr.io/cozystack/cozystack/nginx-cache:0.0.0@sha256:cb25e40cb665b8bbeee8cb1ec39da4c9a7452ef3f2f371912bbc0d1b1e2d40a8 diff --git a/packages/apps/kubernetes/images/kubevirt-csi-driver.tag b/packages/apps/kubernetes/images/kubevirt-csi-driver.tag index a8e48958..d61f214a 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:1997d623bb60a5b540027a4e0716e7ca84f668ee09f31290e9c43324f0003137 +ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:604561e23df1b8eb25c24cf73fd93c7aaa6d1e7c56affbbda5c6f0f83424e4b1 diff --git a/packages/apps/kubernetes/images/ubuntu-container-disk.tag b/packages/apps/kubernetes/images/ubuntu-container-disk.tag index 930a3fd2..5b0830a6 100644 --- a/packages/apps/kubernetes/images/ubuntu-container-disk.tag +++ b/packages/apps/kubernetes/images/ubuntu-container-disk.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/ubuntu-container-disk:v1.33@sha256:71a74ca30f75967bae309be2758f19aa3d37c60b19426b9b622ff1c33a80362f +ghcr.io/cozystack/cozystack/ubuntu-container-disk:v1.33@sha256:19ee4c76f0b3b7b40b97995ca78988ad8c82f6e9c75288d8b7b4b88a64f75d50 diff --git a/packages/core/installer/values.yaml b/packages/core/installer/values.yaml index 3dd0f1d6..68894b3e 100644 --- a/packages/core/installer/values.yaml +++ b/packages/core/installer/values.yaml @@ -1,9 +1,9 @@ cozystackOperator: # Deployment variant: talos, generic, hosted variant: talos - image: ghcr.io/cozystack/cozystack/cozystack-operator:v1.0.0-beta.5@sha256:9f3089cb13b3e19dab14b8edc1220efde693f6066d3474c0137953e1873d16f3 + image: ghcr.io/cozystack/cozystack/cozystack-operator:v1.0.0-beta.6@sha256:c7490da9c1ccb51bff4dd5657ca6a33a29ac71ad9861dfa8c72fdfc8b5765b93 platformSourceUrl: 'oci://ghcr.io/cozystack/cozystack/cozystack-packages' - platformSourceRef: 'digest=sha256:86daf23f7b2ff2f448b273153733c55ac2f57c2bbe72c779ff14865d71e623cb' + platformSourceRef: 'digest=sha256:b29b87d1a2b80452ffd4db7516a102c30c55121552dcdb237055d4124d12c55d' # Generic variant configuration (only used when cozystackOperator.variant=generic) cozystack: # Kubernetes API server host (IP only, no protocol/port) diff --git a/packages/core/platform/values.yaml b/packages/core/platform/values.yaml index f9d4c8d3..5caab16d 100644 --- a/packages/core/platform/values.yaml +++ b/packages/core/platform/values.yaml @@ -5,7 +5,7 @@ sourceRef: path: / migrations: enabled: false - image: ghcr.io/cozystack/cozystack/platform-migrations:v1.0.0-beta.5@sha256:e7a9e0f0adc33e0be007af42f50ed3af064aa965ad628e48cc6c7943f31f239d + image: ghcr.io/cozystack/cozystack/platform-migrations:v1.0.0-beta.6@sha256:37c78dafcedbdad94acd9912550db0b4875897150666b8a06edfa894de99064e targetVersion: 32 # Bundle deployment configuration bundles: diff --git a/packages/core/testing/values.yaml b/packages/core/testing/values.yaml index 627d3096..0692cab6 100644 --- a/packages/core/testing/values.yaml +++ b/packages/core/testing/values.yaml @@ -1,2 +1,2 @@ e2e: - image: ghcr.io/cozystack/cozystack/e2e-sandbox:v1.0.0-beta.5@sha256:eac71ef0de3450fce96255629e77903630c63ade62b81e7055f1a689f92ee153 + image: ghcr.io/cozystack/cozystack/e2e-sandbox:v1.0.0-beta.6@sha256:09af5901abcbed2b612d2d93c163e8ad3948bc55a1d8beae714b4fb2b8f7d91d diff --git a/packages/extra/bootbox/images/matchbox.tag b/packages/extra/bootbox/images/matchbox.tag index 19014ae6..9729ce6e 100644 --- a/packages/extra/bootbox/images/matchbox.tag +++ b/packages/extra/bootbox/images/matchbox.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/matchbox:v1.0.0-beta.5@sha256:8facb6bbbaf336ff3dd606d6bcbf65f5d1fb7f079bec09966544398632005b47 +ghcr.io/cozystack/cozystack/matchbox:v1.0.0-beta.6@sha256:212f624957447f5a932fd5d4564eb8c97694d336b7dc877a2833c1513c0d074d diff --git a/packages/extra/seaweedfs/images/objectstorage-sidecar.tag b/packages/extra/seaweedfs/images/objectstorage-sidecar.tag index 9f5222a7..9657b956 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:v1.0.0-beta.5@sha256:235b194a531b70e266a10ef78d2955d19f5b659513f23d8b3cfbbc0dff7fc1c0 +ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.0.0-beta.6@sha256:235b194a531b70e266a10ef78d2955d19f5b659513f23d8b3cfbbc0dff7fc1c0 diff --git a/packages/system/backup-controller/values.yaml b/packages/system/backup-controller/values.yaml index 63c49ed1..9ce9bac9 100644 --- a/packages/system/backup-controller/values.yaml +++ b/packages/system/backup-controller/values.yaml @@ -1,5 +1,5 @@ backupController: - image: "ghcr.io/cozystack/cozystack/backup-controller:v1.0.0-beta.5@sha256:c99ff8ab11c5c016841acd9dca72c7a643dba72a98b8f816ea67ba7fa3549f88" + image: "ghcr.io/cozystack/cozystack/backup-controller:v1.0.0-beta.6@sha256:365214a74ffc34a9314a62a7d4b491590051fc5486f6bae9913c0c1289983d43" replicas: 2 debug: false metrics: diff --git a/packages/system/backupstrategy-controller/values.yaml b/packages/system/backupstrategy-controller/values.yaml index 5ff8efd5..d74557a0 100644 --- a/packages/system/backupstrategy-controller/values.yaml +++ b/packages/system/backupstrategy-controller/values.yaml @@ -1,5 +1,5 @@ backupStrategyController: - image: "ghcr.io/cozystack/cozystack/backupstrategy-controller:v1.0.0-beta.5@sha256:cd92a2620c9965512977b57c2829d931e7ecb7a8afc0693d7c8ab39bd8ff77d8" + image: "ghcr.io/cozystack/cozystack/backupstrategy-controller:v1.0.0-beta.6@sha256:aa04ee61dce11950162606fc8db2d5cbc6f5b32ba700f790b3f1eee10d65efb1" replicas: 2 debug: false metrics: diff --git a/packages/system/cozystack-api/values.yaml b/packages/system/cozystack-api/values.yaml index 511d1f33..cc6279bc 100644 --- a/packages/system/cozystack-api/values.yaml +++ b/packages/system/cozystack-api/values.yaml @@ -1,3 +1,3 @@ cozystackAPI: - image: ghcr.io/cozystack/cozystack/cozystack-api:v1.0.0-beta.5@sha256:b217c3d1cca7e35b9e6ee32297ebe923fee12ca48d94062484a9dfcffda0e2e3 + image: ghcr.io/cozystack/cozystack/cozystack-api:v1.0.0-beta.6@sha256:d89cf68fb622d0dbef7db98db09d352efc93c2cce448d11f2d73dcd363e911b7 replicas: 2 diff --git a/packages/system/cozystack-controller/values.yaml b/packages/system/cozystack-controller/values.yaml index 5ead5eae..7a17c992 100644 --- a/packages/system/cozystack-controller/values.yaml +++ b/packages/system/cozystack-controller/values.yaml @@ -1,4 +1,4 @@ cozystackController: - image: ghcr.io/cozystack/cozystack/cozystack-controller:v1.0.0-beta.5@sha256:edeb1788395d650f5f14a935c8f6b95ede7ca548cba198c18c75cc5eafd89104 + image: ghcr.io/cozystack/cozystack/cozystack-controller:v1.0.0-beta.6@sha256:d55a3c288934b1f69a00321bc8a94776915556b5f882fe6ac615e9de2701c61f debug: false disableTelemetry: false diff --git a/packages/system/dashboard/templates/configmap.yaml b/packages/system/dashboard/templates/configmap.yaml index c32fc638..3ac5029e 100644 --- a/packages/system/dashboard/templates/configmap.yaml +++ b/packages/system/dashboard/templates/configmap.yaml @@ -1,6 +1,6 @@ {{- $brandingConfig := .Values._cluster.branding | default dict }} -{{- $tenantText := "v1.0.0-beta.5" }} +{{- $tenantText := "v1.0.0-beta.6" }} {{- $footerText := "Cozystack" }} {{- $titleText := "Cozystack Dashboard" }} {{- $logoText := "" }} diff --git a/packages/system/dashboard/values.yaml b/packages/system/dashboard/values.yaml index a2f9c642..32823845 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:v1.0.0-beta.5@sha256:0d331986ac06de1fa5b660d69670cba0f2a59fcdab6cf43cb136827ecb8fcfe8 + image: ghcr.io/cozystack/cozystack/openapi-ui:v1.0.0-beta.6@sha256:c333637673a9e878f6c4ed0fc96db55967bbcf94b2434d075b0f0c6fcfcf9eff openapiUIK8sBff: - image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v1.0.0-beta.5@sha256:1b3ea6d4c7dbbe6a8def3b2807fffdfab2ac4afc39d7a846e57dd491fa168f92 + image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v1.0.0-beta.6@sha256:1b3ea6d4c7dbbe6a8def3b2807fffdfab2ac4afc39d7a846e57dd491fa168f92 tokenProxy: - image: ghcr.io/cozystack/cozystack/token-proxy:v1.0.0-beta.5@sha256:2e280991e07853ea48f97b0a42946afffa10d03d6a83d41099ed83e6ffc94fdc + image: ghcr.io/cozystack/cozystack/token-proxy:v1.0.0-beta.6@sha256:2e280991e07853ea48f97b0a42946afffa10d03d6a83d41099ed83e6ffc94fdc diff --git a/packages/system/grafana-operator/images/grafana-dashboards.tag b/packages/system/grafana-operator/images/grafana-dashboards.tag index 34111c9f..10837c35 100644 --- a/packages/system/grafana-operator/images/grafana-dashboards.tag +++ b/packages/system/grafana-operator/images/grafana-dashboards.tag @@ -1 +1 @@ -ghcr.io/cozystack/cozystack/grafana-dashboards:v1.0.0-beta.5@sha256:e866b5b3874b9d390b341183d2ee070e1387440c14cfe51af831695def6dc2ec +ghcr.io/cozystack/cozystack/grafana-dashboards:v1.0.0-beta.6@sha256:7a3c9af59f8d74d5a23750bbc845c7de64610dbd4d4f84011e10be037b3ce2a0 diff --git a/packages/system/kamaji/values.yaml b/packages/system/kamaji/values.yaml index cbf71457..334a5e92 100644 --- a/packages/system/kamaji/values.yaml +++ b/packages/system/kamaji/values.yaml @@ -3,7 +3,7 @@ kamaji: deploy: false image: pullPolicy: IfNotPresent - tag: v1.0.0-beta.5@sha256:05f8e166dc94aadb74cd6448f6f96fbd2167057a559097516a41b8f53e434918 + tag: v1.0.0-beta.6@sha256:05f8e166dc94aadb74cd6448f6f96fbd2167057a559097516a41b8f53e434918 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:v1.0.0-beta.5@sha256:05f8e166dc94aadb74cd6448f6f96fbd2167057a559097516a41b8f53e434918 + - --migrate-image=ghcr.io/cozystack/cozystack/kamaji:v1.0.0-beta.6@sha256:05f8e166dc94aadb74cd6448f6f96fbd2167057a559097516a41b8f53e434918 diff --git a/packages/system/kubeovn-plunger/values.yaml b/packages/system/kubeovn-plunger/values.yaml index 0abcc7db..c8357c90 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:v1.0.0-beta.5@sha256:67d615f86c3230e8c643b6bd347093cdb1e575c99f7c63599fd43e0c4ffc249a +image: ghcr.io/cozystack/cozystack/kubeovn-plunger:v1.0.0-beta.6@sha256:8e964605efe54e73a94c84abec7dbb5a011c02ccece282bef8ae7b70fce3d217 ovnCentralName: ovn-central diff --git a/packages/system/kubeovn-webhook/values.yaml b/packages/system/kubeovn-webhook/values.yaml index 26613989..b602e6df 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:v1.0.0-beta.5@sha256:e6334c29d3aaf0dea766c88e3e05b53ad623d1bb497b3c836e6f76adade45b29 +image: ghcr.io/cozystack/cozystack/kubeovn-webhook:v1.0.0-beta.6@sha256:e18f9fd679e38f65362a8d0042f25468272f6d081136ad47027168d8e7e07a4a diff --git a/packages/system/kubevirt-csi-node/values.yaml b/packages/system/kubevirt-csi-node/values.yaml index 521683be..ab26cc5a 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:1997d623bb60a5b540027a4e0716e7ca84f668ee09f31290e9c43324f0003137 + image: ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:604561e23df1b8eb25c24cf73fd93c7aaa6d1e7c56affbbda5c6f0f83424e4b1 diff --git a/packages/system/lineage-controller-webhook/values.yaml b/packages/system/lineage-controller-webhook/values.yaml index d5f28f89..2a9b2941 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:v1.0.0-beta.5@sha256:9dd5411d222fe7d7b0583a8e6807bf7745eb1412bf180827c5f1957e6ebbfeb4 + image: ghcr.io/cozystack/cozystack/lineage-controller-webhook:v1.0.0-beta.6@sha256:cf577e56ebc2b94205741d9c5b08f2983cec0811f0c2890edca8fdca22624de1 debug: false localK8sAPIEndpoint: enabled: true diff --git a/packages/system/objectstorage-controller/values.yaml b/packages/system/objectstorage-controller/values.yaml index b3c9d4c6..a270a7e8 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:v1.0.0-beta.5@sha256:b4c972769afda76c48b58e7acf0ac66a0abf16a622f245c60338f432872f640a" + image: "ghcr.io/cozystack/cozystack/objectstorage-controller:v1.0.0-beta.6@sha256:b4c972769afda76c48b58e7acf0ac66a0abf16a622f245c60338f432872f640a" diff --git a/packages/system/seaweedfs/values.yaml b/packages/system/seaweedfs/values.yaml index 94aba339..e62cd357 100644 --- a/packages/system/seaweedfs/values.yaml +++ b/packages/system/seaweedfs/values.yaml @@ -177,7 +177,7 @@ seaweedfs: bucketClassName: "seaweedfs" region: "" sidecar: - image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.0.0-beta.5@sha256:235b194a531b70e266a10ef78d2955d19f5b659513f23d8b3cfbbc0dff7fc1c0" + image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.0.0-beta.6@sha256:235b194a531b70e266a10ef78d2955d19f5b659513f23d8b3cfbbc0dff7fc1c0" certificates: commonName: "SeaweedFS CA" ipAddresses: [] From 96467cdefd223a3ed140916ebfbfaa4f690c0c33 Mon Sep 17 00:00:00 2001 From: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com> Date: Tue, 17 Feb 2026 22:44:01 +0000 Subject: [PATCH 327/889] docs: add changelog for v1.0.0-beta.6 Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com> --- docs/changelogs/v1.0.0-beta.6.md | 46 ++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 docs/changelogs/v1.0.0-beta.6.md diff --git a/docs/changelogs/v1.0.0-beta.6.md b/docs/changelogs/v1.0.0-beta.6.md new file mode 100644 index 00000000..99e565d7 --- /dev/null +++ b/docs/changelogs/v1.0.0-beta.6.md @@ -0,0 +1,46 @@ + + +> **⚠️ Beta Release Warning**: This is a pre-release version intended for testing and early adoption. Breaking changes may occur before the stable v1.0.0 release. + +## Features and Improvements + +* **[platform] Add cilium-kilo networking variant**: Added a new `cilium-kilo` networking variant that combines Cilium CNI with Kilo WireGuard mesh overlay. This variant enables `enable-ipip-termination` in Cilium for proper IPIP packet handling and deploys Kilo with `--compatibility=cilium` flag. Users can now select `cilium-kilo` as their networking variant during platform setup, simplifying the multi-location WireGuard setup compared to manually combining Cilium and standalone Kilo ([**@kvaps**](https://github.com/kvaps) in #2064). + +* **[nats] Add monitoring**: Added Grafana dashboards for NATS JetStream and server metrics monitoring, along with Prometheus monitoring support with TLS-aware endpoint configuration. Includes updated image customization options (digest and full image name) and component version upgrades for the NATS exporter and utilities. Users now have full observability into NATS message broker performance and health ([**@klinch0**](https://github.com/klinch0) in #1381). + +* **[platform] Add DNS-1035 validation for Application names**: Added dynamic DNS-1035 label validation for Application names in the Cozystack API, using `IsDNS1035Label` from `k8s.io/apimachinery`. Validation is performed at creation time and accounts for the root host length to prevent names that would exceed Kubernetes resource naming limits. This prevents creation of resources with invalid names that would fail downstream Kubernetes resource creation ([**@lexfrei**](https://github.com/lexfrei) in #1771). + +* **[operator] Add automatic CRD installation at startup**: Added `--install-crds` flag to the Cozystack operator that installs embedded CRD manifests at startup, ensuring CRDs exist before the operator begins reconciliation. CRD manifests are now embedded in the operator binary and verified for consistency with the Helm `crds/` directory via a new CI Makefile check. This eliminates ordering issues during initial cluster setup where CRDs might not yet be present ([**@lexfrei**](https://github.com/lexfrei) in #2060). + +## Fixes + +* **[platform] Adopt tenant-root into cozystack-basics during migration**: Added migration 31 to adopt existing `tenant-root` Namespace and HelmRelease into the `cozystack-basics` Helm release when upgrading from v0.41.x to v1.0. Previously these resources were applied via `kubectl apply` with no Helm release tracking, causing Helm to treat them as foreign resources and potentially delete them during reconciliation. This migration ensures a safe upgrade path by annotating and labeling these resources for Helm adoption ([**@kvaps**](https://github.com/kvaps) in #2065). + +* **[platform] Preserve tenant-root HelmRelease during migration**: Fixed a data-loss risk during migration from v0.41.x to v1.0.0-beta where the `tenant-root` HelmRelease (and the namespace it manages) could be deleted, causing tenant service outages. Added safety annotation to the HelmRelease and lookup logic to preserve current parameters during migration, preventing unwanted deletion of tenant-root resources ([**@sircthulhu**](https://github.com/sircthulhu) in #2063). + +* **[codegen] Add gen_client to update-codegen.sh and regenerate applyconfiguration**: Fixed a build error in `pkg/generated/applyconfiguration/utils.go` caused by a reference to `testing.TypeConverter` which was removed in client-go v0.34.1. The root cause was that `hack/update-codegen.sh` never called `gen_client`, leaving the generated applyconfiguration code stale. Running the full code generation now produces a consistent and compilable codebase ([**@lexfrei**](https://github.com/lexfrei) in #2061). + +* **[e2e] Make kubernetes test retries effective by cleaning up stale resources**: Fixed E2E test retries for the Kubernetes tenant test by adding pre-creation cleanup of backend deployment/service and NFS pod/PVC in `run-kubernetes.sh`. Previously, retries would fail immediately because stale resources from a failed attempt blocked re-creation. Also increased the tenant deployment wait timeout from 90s to 300s to handle CI resource pressure ([**@lexfrei**](https://github.com/lexfrei) in #2062). + +## Development, Testing, and CI/CD + +* **[e2e] Use helm install instead of kubectl apply for cozystack installation**: Replaced the pre-rendered static YAML application flow (`kubectl apply`) with direct `helm upgrade --install` of the `packages/core/installer` chart in E2E tests. Removed the CRD/operator artifact upload/download steps from the CI workflow, simplifying the pipeline. The chart with correct values is already present in the sandbox via workspace copy and `pr.patch` ([**@lexfrei**](https://github.com/lexfrei) in #2060). + +## Documentation + +* **[website] Improve Azure autoscaling troubleshooting guide**: Enhanced the Azure autoscaling troubleshooting documentation with serial console instructions for debugging VMSS worker nodes, a troubleshooting section for nodes stuck in maintenance mode due to invalid or missing machine config, `az vmss update --custom-data` instructions for updating machine config, and a warning that Azure does not support reading back `customData` ([**@kvaps**](https://github.com/kvaps) in cozystack/website#424). + +* **[website] Update multi-location documentation for cilium-kilo variant**: Updated multi-location networking documentation to reflect the new integrated `cilium-kilo` variant selection during platform setup, replacing the previous manual Kilo installation and Cilium configuration steps. Added explanation of `enable-ipip-termination` and updated the troubleshooting section ([**@kvaps**](https://github.com/kvaps) in cozystack/website@02d63f0). + +## Contributors + +We'd like to thank all contributors who made this release possible: + +* [**@klinch0**](https://github.com/klinch0) +* [**@kvaps**](https://github.com/kvaps) +* [**@lexfrei**](https://github.com/lexfrei) +* [**@sircthulhu**](https://github.com/sircthulhu) + +**Full Changelog**: https://github.com/cozystack/cozystack/compare/v1.0.0-beta.5...v1.0.0-beta.6 From 87d03902563171e49d7ad94cb1078b612a892db6 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 18 Feb 2026 02:07:38 +0300 Subject: [PATCH 328/889] fix(harbor): include tenant domain in default hostname and add E2E cleanup Use tenant base domain in default hostname construction (harbor.RELEASE.DOMAIN) to match the pattern used by other apps (kubernetes, vpn). Remove unused $ingress variable from harbor.yaml. Add cleanup of stale resources from previous failed E2E runs. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- hack/e2e-apps/harbor.bats | 5 +++++ packages/apps/harbor/templates/harbor.yaml | 3 +-- packages/apps/harbor/templates/ingress.yaml | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/hack/e2e-apps/harbor.bats b/hack/e2e-apps/harbor.bats index 09215546..26f407ea 100644 --- a/hack/e2e-apps/harbor.bats +++ b/hack/e2e-apps/harbor.bats @@ -3,6 +3,11 @@ @test "Create Harbor" { name='test' release="harbor-$name" + + # Clean up stale resources from previous failed runs + kubectl -n tenant-test delete harbor.apps.cozystack.io $name 2>/dev/null || true + kubectl -n tenant-test wait hr $release --timeout=60s --for=delete 2>/dev/null || true + kubectl apply -f- < Date: Wed, 18 Feb 2026 02:12:11 +0300 Subject: [PATCH 329/889] fix(harbor): enable database TLS and fix token key/cert check Change sslmode from disable to require for CNPG PostgreSQL connection, as CNPG supports TLS out of the box. Fix token key/cert preservation to verify both values are present before passing to Harbor core. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/apps/harbor/templates/harbor.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/apps/harbor/templates/harbor.yaml b/packages/apps/harbor/templates/harbor.yaml index c3fab16a..94154ec9 100644 --- a/packages/apps/harbor/templates/harbor.yaml +++ b/packages/apps/harbor/templates/harbor.yaml @@ -112,7 +112,7 @@ spec: portal: resources: {{- include "cozy-lib.resources.defaultingSanitize" (list "nano" (dict) $) | nindent 10 }} core: - {{- if $tokenKey }} + {{- if and $tokenKey $tokenCert }} tokenKey: {{ $tokenKey | quote }} tokenCert: {{ $tokenCert | quote }} {{- end }} @@ -136,7 +136,7 @@ spec: port: "5432" username: app coreDatabase: app - sslmode: disable + sslmode: require existingSecret: "{{ .Release.Name }}-db-app" redis: type: external From 0198c9896a10675bddc06277c9510527a700549c Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 18 Feb 2026 02:17:59 +0300 Subject: [PATCH 330/889] fix(harbor): use standard hostname pattern without double prefix Follow the same hostname pattern as kubernetes and vpn apps: use Release.Name directly (which already includes the harbor- prefix) instead of adding an extra harbor. subdomain. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/apps/harbor/templates/harbor.yaml | 2 +- packages/apps/harbor/templates/ingress.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/apps/harbor/templates/harbor.yaml b/packages/apps/harbor/templates/harbor.yaml index 94154ec9..ff8e88ad 100644 --- a/packages/apps/harbor/templates/harbor.yaml +++ b/packages/apps/harbor/templates/harbor.yaml @@ -1,5 +1,5 @@ {{- $host := .Values._namespace.host }} -{{- $harborHost := .Values.host | default (printf "harbor.%s.%s" .Release.Name $host) }} +{{- $harborHost := .Values.host | default (printf "%s.%s" .Release.Name $host) }} {{- $existingSecret := lookup "v1" "Secret" .Release.Namespace (printf "%s-credentials" .Release.Name) }} {{- $adminPassword := randAlphaNum 16 }} diff --git a/packages/apps/harbor/templates/ingress.yaml b/packages/apps/harbor/templates/ingress.yaml index 07d73b8e..28691654 100644 --- a/packages/apps/harbor/templates/ingress.yaml +++ b/packages/apps/harbor/templates/ingress.yaml @@ -1,6 +1,6 @@ {{- $ingress := .Values._namespace.ingress }} {{- $host := .Values._namespace.host }} -{{- $harborHost := .Values.host | default (printf "harbor.%s.%s" .Release.Name $host) }} +{{- $harborHost := .Values.host | default (printf "%s.%s" .Release.Name $host) }} {{- $issuerType := (index .Values._cluster "clusterissuer") | default "http01" }} --- apiVersion: networking.k8s.io/v1 From daf1b71e7c97b2910453245c159d7f774efeef9c Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 18 Feb 2026 02:23:00 +0300 Subject: [PATCH 331/889] fix(harbor): add explicit CNPG bootstrap configuration Specify initdb bootstrap with database and owner names explicitly instead of relying on CNPG defaults which may change between versions. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/system/harbor/templates/database.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/system/harbor/templates/database.yaml b/packages/system/harbor/templates/database.yaml index 5d1827aa..68f047f8 100644 --- a/packages/system/harbor/templates/database.yaml +++ b/packages/system/harbor/templates/database.yaml @@ -10,6 +10,10 @@ spec: {{- with .Values.db.storageClass }} storageClass: {{ . }} {{- end }} + bootstrap: + initdb: + database: app + owner: app monitoring: enablePodMonitor: true resources: From 199ffe319a1740d52c929f48991d8ec5b8d8cd60 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 18 Feb 2026 11:26:58 +0300 Subject: [PATCH 332/889] fix(harbor): set UTF-8 encoding and locale for CNPG database Add encoding, localeCollate, and localeCType to initdb bootstrap configuration to ensure fulltext search (ilike) works correctly. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/system/harbor/templates/database.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/system/harbor/templates/database.yaml b/packages/system/harbor/templates/database.yaml index 68f047f8..b1221e1c 100644 --- a/packages/system/harbor/templates/database.yaml +++ b/packages/system/harbor/templates/database.yaml @@ -14,6 +14,9 @@ spec: initdb: database: app owner: app + encoding: UTF8 + localeCollate: en_US.UTF-8 + localeCType: en_US.UTF-8 monitoring: enablePodMonitor: true resources: From b5b2f95c3e6cf36dbb6b50e83e386c4c59ed785a Mon Sep 17 00:00:00 2001 From: Kirill Ilin Date: Wed, 18 Feb 2026 15:45:14 +0500 Subject: [PATCH 333/889] [cozystack-basics] Preserve existing HelmRelease values during reconciliations Signed-off-by: Kirill Ilin --- .../system/cozystack-basics/templates/tenant-root.yaml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/system/cozystack-basics/templates/tenant-root.yaml b/packages/system/cozystack-basics/templates/tenant-root.yaml index 95af9d7f..93f5129e 100644 --- a/packages/system/cozystack-basics/templates/tenant-root.yaml +++ b/packages/system/cozystack-basics/templates/tenant-root.yaml @@ -23,7 +23,6 @@ spec: namespace: cozy-system interval: 1m0s timeout: 5m0s - values: - _cluster: - oidc-enabled: {{ .Values.oidcEnabled | quote }} - root-host: {{ .Values.rootHost | quote }} + valuesFrom: + - kind: Secret + name: cozystack-values From db1425a8deef2ec3096a1a82759a81563f432a41 Mon Sep 17 00:00:00 2001 From: Kirill Ilin Date: Thu, 19 Feb 2026 14:33:20 +0500 Subject: [PATCH 334/889] feat(dashboard): add API-backed dropdown for VMInstance instanceType Override spec.instanceType field with listInput type in schema so the dashboard renders it as a select dropdown populated from VirtualMachineClusterInstancetype resources. Default value is read dynamically from the ApplicationDefinition's OpenAPI schema. Co-Authored-By: Claude Opus 4.6 Signed-off-by: Kirill Ilin --- .../dashboard/customformsoverride.go | 71 +++++++- .../dashboard/customformsoverride_test.go | 164 ++++++++++++++++++ 2 files changed, 234 insertions(+), 1 deletion(-) diff --git a/internal/controller/dashboard/customformsoverride.go b/internal/controller/dashboard/customformsoverride.go index dfbccf5c..26afab50 100644 --- a/internal/controller/dashboard/customformsoverride.go +++ b/internal/controller/dashboard/customformsoverride.go @@ -46,8 +46,11 @@ func (m *Manager) ensureCustomFormsOverride(ctx context.Context, crd *cozyv1alph } } - // Build schema with multilineString for string fields without enum + // Parse OpenAPI schema once for reuse l := log.FromContext(ctx) + openAPIProps := parseOpenAPIProperties(crd.Spec.Application.OpenAPISchema) + + // Build schema with multilineString for string fields without enum schema, err := buildMultilineStringSchema(crd.Spec.Application.OpenAPISchema) if err != nil { // If schema parsing fails, log the error and use an empty schema @@ -55,6 +58,9 @@ func (m *Manager) ensureCustomFormsOverride(ctx context.Context, crd *cozyv1alph schema = map[string]any{} } + // Override specific fields with API-backed dropdowns (listInput type) + applyListInputOverrides(schema, kind, openAPIProps) + spec := map[string]any{ "customizationId": customizationID, "hidden": hidden, @@ -176,6 +182,69 @@ func buildMultilineStringSchema(openAPISchema string) (map[string]any, error) { return schema, nil } +// applyListInputOverrides injects listInput type overrides into the schema +// for fields that should be rendered as API-backed dropdowns in the dashboard. +// openAPIProps are the parsed top-level properties from the OpenAPI schema. +func applyListInputOverrides(schema map[string]any, kind string, openAPIProps map[string]any) { + switch kind { + case "VMInstance": + specProps := ensureSchemaPath(schema, "spec") + field := map[string]any{ + "type": "listInput", + "customProps": map[string]any{ + "valueUri": "/api/clusters/{cluster}/k8s/apis/instancetype.kubevirt.io/v1beta1/virtualmachineclusterinstancetypes", + "keysToValue": []any{"metadata", "name"}, + "keysToLabel": []any{"metadata", "name"}, + }, + } + if prop, _ := openAPIProps["instanceType"].(map[string]any); prop != nil { + if def := prop["default"]; def != nil { + field["default"] = def + } + } + specProps["instanceType"] = field + } +} + +// parseOpenAPIProperties parses the top-level properties from an OpenAPI schema JSON string. +func parseOpenAPIProperties(openAPISchema string) map[string]any { + if openAPISchema == "" { + return nil + } + var root map[string]any + if err := json.Unmarshal([]byte(openAPISchema), &root); err != nil { + return nil + } + props, _ := root["properties"].(map[string]any) + return props +} + +// ensureSchemaPath ensures the nested properties structure exists in a schema +// and returns the innermost properties map. +// e.g. ensureSchemaPath(schema, "spec") returns schema["properties"]["spec"]["properties"] +func ensureSchemaPath(schema map[string]any, segments ...string) map[string]any { + current := schema + for _, seg := range segments { + props, ok := current["properties"].(map[string]any) + if !ok { + props = map[string]any{} + current["properties"] = props + } + child, ok := props[seg].(map[string]any) + if !ok { + child = map[string]any{} + props[seg] = child + } + current = child + } + props, ok := current["properties"].(map[string]any) + if !ok { + props = map[string]any{} + current["properties"] = props + } + return props +} + // processSpecProperties recursively processes spec properties and adds multilineString type // for string fields without enum func processSpecProperties(props map[string]any, schemaProps map[string]any) { diff --git a/internal/controller/dashboard/customformsoverride_test.go b/internal/controller/dashboard/customformsoverride_test.go index 9f7babe9..3df24724 100644 --- a/internal/controller/dashboard/customformsoverride_test.go +++ b/internal/controller/dashboard/customformsoverride_test.go @@ -169,3 +169,167 @@ func TestBuildMultilineStringSchemaInvalidJSON(t *testing.T) { t.Errorf("Expected nil schema for invalid JSON, got %v", schema) } } + +func TestApplyListInputOverrides_VMInstance(t *testing.T) { + openAPIProps := map[string]any{ + "instanceType": map[string]any{"type": "string", "default": "u1.medium"}, + } + + schema := map[string]any{} + applyListInputOverrides(schema, "VMInstance", openAPIProps) + + specProps := schema["properties"].(map[string]any)["spec"].(map[string]any)["properties"].(map[string]any) + instanceType, ok := specProps["instanceType"].(map[string]any) + if !ok { + t.Fatal("instanceType not found in schema.properties.spec.properties") + } + + if instanceType["type"] != "listInput" { + t.Errorf("expected type listInput, got %v", instanceType["type"]) + } + + if instanceType["default"] != "u1.medium" { + t.Errorf("expected default u1.medium, got %v", instanceType["default"]) + } + + customProps, ok := instanceType["customProps"].(map[string]any) + if !ok { + t.Fatal("customProps not found") + } + + expectedURI := "/api/clusters/{cluster}/k8s/apis/instancetype.kubevirt.io/v1beta1/virtualmachineclusterinstancetypes" + if customProps["valueUri"] != expectedURI { + t.Errorf("expected valueUri %s, got %v", expectedURI, customProps["valueUri"]) + } +} + +func TestApplyListInputOverrides_UnknownKind(t *testing.T) { + schema := map[string]any{} + applyListInputOverrides(schema, "SomeOtherKind", map[string]any{}) + + if len(schema) != 0 { + t.Errorf("expected empty schema for unknown kind, got %v", schema) + } +} + +func TestApplyListInputOverrides_NoDefault(t *testing.T) { + openAPIProps := map[string]any{ + "instanceType": map[string]any{"type": "string"}, + } + + schema := map[string]any{} + applyListInputOverrides(schema, "VMInstance", openAPIProps) + + specProps := schema["properties"].(map[string]any)["spec"].(map[string]any)["properties"].(map[string]any) + instanceType := specProps["instanceType"].(map[string]any) + + if _, exists := instanceType["default"]; exists { + t.Errorf("expected no default key, got %v", instanceType["default"]) + } +} + +func TestApplyListInputOverrides_MergesWithExistingSchema(t *testing.T) { + openAPIProps := map[string]any{ + "instanceType": map[string]any{"type": "string", "default": "u1.medium"}, + } + + // Simulate schema that already has spec.properties from buildMultilineStringSchema + schema := map[string]any{ + "properties": map[string]any{ + "spec": map[string]any{ + "properties": map[string]any{ + "otherField": map[string]any{"type": "multilineString"}, + }, + }, + }, + } + applyListInputOverrides(schema, "VMInstance", openAPIProps) + + specProps := schema["properties"].(map[string]any)["spec"].(map[string]any)["properties"].(map[string]any) + + // instanceType should be added + if _, ok := specProps["instanceType"].(map[string]any); !ok { + t.Fatal("instanceType not found after override") + } + + // otherField should be preserved + otherField, ok := specProps["otherField"].(map[string]any) + if !ok { + t.Fatal("otherField was lost after override") + } + if otherField["type"] != "multilineString" { + t.Errorf("otherField type changed, got %v", otherField["type"]) + } +} + +func TestParseOpenAPIProperties(t *testing.T) { + t.Run("extracts properties", func(t *testing.T) { + props := parseOpenAPIProperties(`{"type":"object","properties":{"instanceType":{"type":"string","default":"u1.medium"}}}`) + field, _ := props["instanceType"].(map[string]any) + if field["default"] != "u1.medium" { + t.Errorf("expected default u1.medium, got %v", field["default"]) + } + }) + + t.Run("empty string", func(t *testing.T) { + if props := parseOpenAPIProperties(""); props != nil { + t.Errorf("expected nil, got %v", props) + } + }) + + t.Run("invalid JSON", func(t *testing.T) { + if props := parseOpenAPIProperties("{bad"); props != nil { + t.Errorf("expected nil, got %v", props) + } + }) + + t.Run("no properties key", func(t *testing.T) { + if props := parseOpenAPIProperties(`{"type":"object"}`); props != nil { + t.Errorf("expected nil, got %v", props) + } + }) +} + +func TestEnsureSchemaPath(t *testing.T) { + t.Run("creates path from empty schema", func(t *testing.T) { + schema := map[string]any{} + props := ensureSchemaPath(schema, "spec") + + props["field"] = "value" + + // Verify structure: schema.properties.spec.properties.field + got := schema["properties"].(map[string]any)["spec"].(map[string]any)["properties"].(map[string]any)["field"] + if got != "value" { + t.Errorf("expected value, got %v", got) + } + }) + + t.Run("preserves existing nested properties", func(t *testing.T) { + schema := map[string]any{ + "properties": map[string]any{ + "spec": map[string]any{ + "properties": map[string]any{ + "existing": "keep", + }, + }, + }, + } + props := ensureSchemaPath(schema, "spec") + + if props["existing"] != "keep" { + t.Errorf("existing property lost, got %v", props["existing"]) + } + }) + + t.Run("multi-level path", func(t *testing.T) { + schema := map[string]any{} + props := ensureSchemaPath(schema, "spec", "nested") + + props["deep"] = true + + got := schema["properties"].(map[string]any)["spec"].(map[string]any)["properties"].(map[string]any)["nested"].(map[string]any)["properties"].(map[string]any)["deep"] + if got != true { + t.Errorf("expected true, got %v", got) + } + }) +} From 4387a3e95f7304bf9c1952d21d4181bb314ecb67 Mon Sep 17 00:00:00 2001 From: Kirill Ilin Date: Thu, 19 Feb 2026 15:58:18 +0500 Subject: [PATCH 335/889] fix(dashboard): patch FormListInput to fix value binding The Flex wrapper between ResetedFormItem and Select prevented Ant Design's Form.Item from injecting value/onChange into the Select, causing the dropdown to appear empty even when the form store had a value. Move Flex outside ResetedFormItem so Select is its direct child. Signed-off-by: Kirill Ilin --- .../patches/formlistinput-value-binding.diff | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 packages/system/dashboard/images/openapi-ui/openapi-k8s-toolkit/patches/formlistinput-value-binding.diff diff --git a/packages/system/dashboard/images/openapi-ui/openapi-k8s-toolkit/patches/formlistinput-value-binding.diff b/packages/system/dashboard/images/openapi-ui/openapi-k8s-toolkit/patches/formlistinput-value-binding.diff new file mode 100644 index 00000000..0da12c00 --- /dev/null +++ b/packages/system/dashboard/images/openapi-ui/openapi-k8s-toolkit/patches/formlistinput-value-binding.diff @@ -0,0 +1,49 @@ +diff --git a/src/components/molecules/BlackholeForm/molecules/FormListInput/FormListInput.tsx b/src/components/molecules/BlackholeForm/molecules/FormListInput/FormListInput.tsx +index d5e5230..9038dbb 100644 +--- a/src/components/molecules/BlackholeForm/molecules/FormListInput/FormListInput.tsx ++++ b/src/components/molecules/BlackholeForm/molecules/FormListInput/FormListInput.tsx +@@ -259,14 +259,15 @@ export const FormListInput: FC = ({ + + + +- +- ++ ++ + = ({ - - - -- -- -+ -+ - ++ ++ ++ ++
++
++ lock ++ ++ ++
++
++
++
++ ++
++
++ ++ ++ ++ ++ ++ ++ ++{{ end }} From 175b3badd2642a21d249fcac590f807b66a6fa2c Mon Sep 17 00:00:00 2001 From: IvanHunters Date: Wed, 4 Mar 2026 18:30:49 +0300 Subject: [PATCH 435/889] feat(bucket): make credentials and basic auth conditional on users deployment.yaml: use s3._namespace.host for ENDPOINT instead of secret ref, inject ACCESS_KEY_ID/SECRET_ACCESS_KEY only when users exist. Without users, s3manager starts in login mode. ingress.yaml: nginx basic auth annotations only when users exist. Without users, s3manager handles authentication via its login form. Co-Authored-By: Claude Signed-off-by: IvanHunters --- packages/system/bucket/templates/deployment.yaml | 8 ++++---- packages/system/bucket/templates/ingress.yaml | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/system/bucket/templates/deployment.yaml b/packages/system/bucket/templates/deployment.yaml index 7e115de0..086a6934 100644 --- a/packages/system/bucket/templates/deployment.yaml +++ b/packages/system/bucket/templates/deployment.yaml @@ -1,3 +1,4 @@ +{{- $users := keys .Values.users | sortAlpha }} apiVersion: apps/v1 kind: Deployment metadata: @@ -17,12 +18,10 @@ spec: image: "{{ $.Files.Get "images/s3manager.tag" | trim }}" env: - name: ENDPOINT - valueFrom: - secretKeyRef: - name: {{ .Values.bucketName }}-credentials - key: endpoint + value: "s3.{{ .Values._namespace.host }}" - name: SKIP_SSL_VERIFICATION value: "true" + {{- if $users }} - name: ACCESS_KEY_ID valueFrom: secretKeyRef: @@ -33,3 +32,4 @@ spec: secretKeyRef: name: {{ .Values.bucketName }}-credentials key: secretKey + {{- end }} diff --git a/packages/system/bucket/templates/ingress.yaml b/packages/system/bucket/templates/ingress.yaml index 9ad9f46d..e54fede3 100644 --- a/packages/system/bucket/templates/ingress.yaml +++ b/packages/system/bucket/templates/ingress.yaml @@ -2,15 +2,18 @@ {{- $ingress := .Values._namespace.ingress }} {{- $solver := (index .Values._cluster "solver") | default "http01" }} {{- $clusterIssuer := (index .Values._cluster "issuer-name") | default "letsencrypt-prod" }} +{{- $users := keys .Values.users | sortAlpha }} apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: {{ .Values.bucketName }}-ui annotations: + {{- if $users }} nginx.ingress.kubernetes.io/auth-type: "basic" nginx.ingress.kubernetes.io/auth-secret: "{{ .Values.bucketName }}-ui-auth" nginx.ingress.kubernetes.io/auth-realm: "Authentication Required" + {{- end }} nginx.ingress.kubernetes.io/proxy-body-size: "0" nginx.ingress.kubernetes.io/proxy-read-timeout: "99999" nginx.ingress.kubernetes.io/proxy-send-timeout: "99999" From 84da47a2ce5494cab5ddefe6c3922a53201635a1 Mon Sep 17 00:00:00 2001 From: IvanHunters Date: Wed, 4 Mar 2026 18:47:51 +0300 Subject: [PATCH 436/889] refactor(bucket): replace basic auth with s3manager login page Remove nginx basic auth and credential secret injection from the bucket Helm chart. s3manager now always starts in login mode and handles authentication via its own login page with encrypted session cookies. This eliminates the dependency on the -credentials and -ui-auth secrets for the UI layer. Co-Authored-By: Claude Signed-off-by: IvanHunters --- .../bucket/images/s3manager/cozystack.patch | 648 ++++++++++-------- .../system/bucket/templates/deployment.yaml | 13 - packages/system/bucket/templates/ingress.yaml | 6 - packages/system/bucket/templates/secret.yaml | 32 +- 4 files changed, 353 insertions(+), 346 deletions(-) diff --git a/packages/system/bucket/images/s3manager/cozystack.patch b/packages/system/bucket/images/s3manager/cozystack.patch index 9e98edde..2f569042 100644 --- a/packages/system/bucket/images/s3manager/cozystack.patch +++ b/packages/system/bucket/images/s3manager/cozystack.patch @@ -23,187 +23,29 @@ index b5d8540..6ede8e8 100644 github.com/hashicorp/hcl v1.0.0 // indirect github.com/klauspost/compress v1.17.9 // indirect github.com/klauspost/cpuid/v2 v2.2.8 // indirect -diff --git a/internal/app/s3manager/auth.go b/internal/app/s3manager/auth.go -new file mode 100644 -index 0000000..a315cde ---- /dev/null -+++ b/internal/app/s3manager/auth.go -@@ -0,0 +1,173 @@ -+package s3manager -+ -+import ( -+ "context" -+ "crypto/rand" -+ "crypto/tls" -+ "fmt" -+ "html/template" -+ "io/fs" -+ "net/http" -+ -+ "github.com/gorilla/sessions" -+ "github.com/minio/minio-go/v7" -+ "github.com/minio/minio-go/v7/pkg/credentials" -+) -+ -+type contextKey string -+ -+const s3ContextKey contextKey = "s3client" -+ -+// S3FromContext retrieves the S3 client from the request context. -+func S3FromContext(ctx context.Context) S3 { -+ s3, _ := ctx.Value(s3ContextKey).(S3) -+ return s3 -+} -+ -+func contextWithS3(ctx context.Context, s3 S3) context.Context { -+ return context.WithValue(ctx, s3ContextKey, s3) -+} -+ -+// NewS3Client creates a new minio S3 client with the given credentials. -+func NewS3Client(endpoint, accessKey, secretKey string, useSSL, skipVerify bool) (*minio.Client, error) { -+ opts := &minio.Options{ -+ Creds: credentials.NewStaticV4(accessKey, secretKey, ""), -+ Secure: useSSL, -+ } -+ if useSSL && skipVerify { -+ opts.Transport = &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} //nolint:gosec -+ } -+ return minio.New(endpoint, opts) -+} -+ -+// NewSessionStore creates a new encrypted cookie store with a random key. -+func NewSessionStore() *sessions.CookieStore { -+ key := make([]byte, 32) -+ if _, err := rand.Read(key); err != nil { -+ panic(fmt.Sprintf("failed to generate session key: %v", err)) -+ } -+ store := sessions.NewCookieStore(key) -+ store.Options = &sessions.Options{ -+ Path: "/", -+ MaxAge: 86400, // 24 hours -+ HttpOnly: true, -+ Secure: true, -+ SameSite: http.SameSiteLaxMode, -+ } -+ return store -+} -+ -+// RequireAuth is middleware that checks for valid S3 credentials in the session. -+func RequireAuth(store *sessions.CookieStore, endpoint string, useSSL, skipVerify bool) func(http.Handler) http.Handler { -+ return func(next http.Handler) http.Handler { -+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { -+ session, err := store.Get(r, "s3session") -+ if err != nil { -+ http.Redirect(w, r, "/login", http.StatusFound) -+ return -+ } -+ -+ accessKey, ok1 := session.Values["accessKey"].(string) -+ secretKey, ok2 := session.Values["secretKey"].(string) -+ if !ok1 || !ok2 || accessKey == "" || secretKey == "" { -+ http.Redirect(w, r, "/login", http.StatusFound) -+ return -+ } -+ -+ s3Client, err := NewS3Client(endpoint, accessKey, secretKey, useSSL, skipVerify) -+ if err != nil { -+ http.Redirect(w, r, "/login?error=invalid_credentials", http.StatusFound) -+ return -+ } -+ -+ ctx := contextWithS3(r.Context(), s3Client) -+ next.ServeHTTP(w, r.WithContext(ctx)) -+ }) -+ } -+} -+ -+// HandleLoginView renders the login page. -+func HandleLoginView(templates fs.FS) http.HandlerFunc { -+ return func(w http.ResponseWriter, r *http.Request) { -+ data := struct { -+ Error string -+ }{ -+ Error: r.URL.Query().Get("error"), -+ } -+ t, err := template.ParseFS(templates, "layout.html.tmpl", "login.html.tmpl") -+ if err != nil { -+ handleHTTPError(w, fmt.Errorf("error parsing login template: %w", err)) -+ return -+ } -+ err = t.ExecuteTemplate(w, "layout", data) -+ if err != nil { -+ handleHTTPError(w, fmt.Errorf("error executing login template: %w", err)) -+ return -+ } -+ } -+} -+ -+// HandleLogin processes login form submission. -+func HandleLogin(store *sessions.CookieStore, endpoint string, useSSL, skipVerify bool) http.HandlerFunc { -+ return func(w http.ResponseWriter, r *http.Request) { -+ accessKey := r.FormValue("accessKey") -+ secretKey := r.FormValue("secretKey") -+ -+ if accessKey == "" || secretKey == "" { -+ http.Redirect(w, r, "/login?error=credentials_required", http.StatusFound) -+ return -+ } -+ -+ // Validate credentials by attempting to list buckets -+ s3Client, err := NewS3Client(endpoint, accessKey, secretKey, useSSL, skipVerify) -+ if err != nil { -+ http.Redirect(w, r, "/login?error=invalid_credentials", http.StatusFound) -+ return -+ } -+ -+ _, err = s3Client.ListBuckets(r.Context()) -+ if err != nil { -+ http.Redirect(w, r, "/login?error=invalid_credentials", http.StatusFound) -+ return -+ } -+ -+ session, err := store.Get(r, "s3session") -+ if err != nil { -+ http.Redirect(w, r, "/login?error=session_error", http.StatusFound) -+ return -+ } -+ -+ session.Values["accessKey"] = accessKey -+ session.Values["secretKey"] = secretKey -+ if err := session.Save(r, w); err != nil { -+ http.Redirect(w, r, "/login?error=session_error", http.StatusFound) -+ return -+ } -+ -+ http.Redirect(w, r, "/buckets", http.StatusFound) -+ } -+} -+ -+// HandleLogout destroys the session and redirects to login. -+func HandleLogout(store *sessions.CookieStore) http.HandlerFunc { -+ return func(w http.ResponseWriter, r *http.Request) { -+ session, err := store.Get(r, "s3session") -+ if err == nil { -+ session.Options.MaxAge = -1 -+ _ = session.Save(r, w) -+ } -+ http.Redirect(w, r, "/login", http.StatusFound) -+ } -+} -+ -+// DynamicS3Handler wraps a handler factory that takes S3 as its only parameter. -+func DynamicS3Handler(factory func(S3) http.HandlerFunc) http.HandlerFunc { -+ return func(w http.ResponseWriter, r *http.Request) { -+ s3 := S3FromContext(r.Context()) -+ if s3 == nil { -+ http.Redirect(w, r, "/login", http.StatusFound) -+ return -+ } -+ factory(s3).ServeHTTP(w, r) -+ } -+} +diff --git a/go.sum b/go.sum +index 1ea1b16..d7866ce 100644 +--- a/go.sum ++++ b/go.sum +@@ -16,10 +16,16 @@ github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= + github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= + 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/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= ++github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= + 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/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= + github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= ++github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= ++github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= ++github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= ++github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= + github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= + github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= + github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= diff --git a/main.go b/main.go -index 2ffe8ab..5fdc4cd 100644 +index 2ffe8ab..723a1b8 100644 --- a/main.go +++ b/main.go @@ -41,10 +41,12 @@ type configuration struct { @@ -219,7 +61,7 @@ index 2ffe8ab..5fdc4cd 100644 viper.AutomaticEnv() -@@ -57,13 +59,11 @@ func parseConfiguration() configuration { +@@ -57,13 +59,10 @@ func parseConfiguration() configuration { iamEndpoint = viper.GetString("IAM_ENDPOINT") } else { accessKeyID = viper.GetString("ACCESS_KEY_ID") @@ -230,14 +72,13 @@ index 2ffe8ab..5fdc4cd 100644 secretAccessKey = viper.GetString("SECRET_ACCESS_KEY") - if len(secretAccessKey) == 0 { - log.Fatal("please provide SECRET_ACCESS_KEY") -+ + if len(accessKeyID) == 0 || len(secretAccessKey) == 0 { + log.Println("ACCESS_KEY_ID or SECRET_ACCESS_KEY not set, starting in login mode") + loginMode = true } } -@@ -115,6 +115,7 @@ func parseConfiguration() configuration { +@@ -115,6 +114,7 @@ func parseConfiguration() configuration { Timeout: timeout, SseType: sseType, SseKey: sseKey, @@ -245,7 +86,7 @@ index 2ffe8ab..5fdc4cd 100644 } } -@@ -135,57 +136,103 @@ func main() { +@@ -135,57 +135,96 @@ func main() { log.Fatal(err) } @@ -255,54 +96,7 @@ index 2ffe8ab..5fdc4cd 100644 - } - if configuration.UseIam { - opts.Creds = credentials.NewIAM(configuration.IamEndpoint) -+ // Set up router -+ r := mux.NewRouter() -+ r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.FS(statics)))).Methods(http.MethodGet) -+ -+ if configuration.LoginMode { -+ // Login mode: no pre-configured S3 client, use per-session credentials -+ store := s3manager.NewSessionStore() -+ authMiddleware := s3manager.RequireAuth(store, configuration.Endpoint, configuration.UseSSL, configuration.SkipSSLVerification) -+ -+ // Public routes -+ r.Handle("/login", s3manager.HandleLoginView(templates)).Methods(http.MethodGet) -+ r.Handle("/login", s3manager.HandleLogin(store, configuration.Endpoint, configuration.UseSSL, configuration.SkipSSLVerification)).Methods(http.MethodPost) -+ r.Handle("/logout", s3manager.HandleLogout(store)).Methods(http.MethodPost) -+ -+ // Protected routes - use dynamic S3 client from session -+ protected := r.PathPrefix("/").Subrouter() -+ protected.Use(authMiddleware) -+ -+ protected.Handle("/", http.RedirectHandler("/buckets", http.StatusPermanentRedirect)).Methods(http.MethodGet) -+ protected.Handle("/buckets", s3manager.DynamicS3Handler(func(s3 s3manager.S3) http.HandlerFunc { -+ return s3manager.HandleBucketsView(s3, templates, configuration.AllowDelete) -+ })).Methods(http.MethodGet) -+ protected.PathPrefix("/buckets/").Handler(s3manager.DynamicS3Handler(func(s3 s3manager.S3) http.HandlerFunc { -+ return s3manager.HandleBucketView(s3, templates, configuration.AllowDelete, configuration.ListRecursive) -+ })).Methods(http.MethodGet) -+ protected.Handle("/api/buckets", s3manager.DynamicS3Handler(func(s3 s3manager.S3) http.HandlerFunc { -+ return s3manager.HandleCreateBucket(s3) -+ })).Methods(http.MethodPost) -+ if configuration.AllowDelete { -+ protected.Handle("/api/buckets/{bucketName}", s3manager.DynamicS3Handler(func(s3 s3manager.S3) http.HandlerFunc { -+ return s3manager.HandleDeleteBucket(s3) -+ })).Methods(http.MethodDelete) -+ } -+ protected.Handle("/api/buckets/{bucketName}/objects", s3manager.DynamicS3Handler(func(s3 s3manager.S3) http.HandlerFunc { -+ return s3manager.HandleCreateObject(s3, sseType) -+ })).Methods(http.MethodPost) -+ protected.Handle("/api/buckets/{bucketName}/objects/{objectName:.*}/url", s3manager.DynamicS3Handler(func(s3 s3manager.S3) http.HandlerFunc { -+ return s3manager.HandleGenerateUrl(s3) -+ })).Methods(http.MethodGet) -+ protected.Handle("/api/buckets/{bucketName}/objects/{objectName:.*}", s3manager.DynamicS3Handler(func(s3 s3manager.S3) http.HandlerFunc { -+ return s3manager.HandleGetObject(s3, configuration.ForceDownload) -+ })).Methods(http.MethodGet) -+ if configuration.AllowDelete { -+ protected.Handle("/api/buckets/{bucketName}/objects/{objectName:.*}", s3manager.DynamicS3Handler(func(s3 s3manager.S3) http.HandlerFunc { -+ return s3manager.HandleDeleteObject(s3) -+ })).Methods(http.MethodDelete) -+ } - } else { +- } else { - var signatureType credentials.SignatureType - - switch configuration.SignatureType { @@ -316,6 +110,59 @@ index 2ffe8ab..5fdc4cd 100644 - signatureType = credentials.SignatureAnonymous - default: - log.Fatalf("Invalid SIGNATURE_TYPE: %s", configuration.SignatureType) ++ // Set up router ++ r := mux.NewRouter() ++ r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.FS(statics)))).Methods(http.MethodGet) ++ ++ if configuration.LoginMode { ++ // Login mode: no pre-configured S3 client, per-session credentials ++ sessionCfg := &s3manager.SessionConfig{ ++ Store: s3manager.NewSessionStore(), ++ Endpoint: configuration.Endpoint, ++ UseSSL: configuration.UseSSL, ++ SkipSSLVerify: configuration.SkipSSLVerification, ++ AllowDelete: configuration.AllowDelete, ++ ForceDownload: configuration.ForceDownload, ++ ListRecursive: configuration.ListRecursive, ++ SseInfo: sseType, ++ Templates: templates, + } + +- opts.Creds = credentials.NewStatic(configuration.AccessKeyID, configuration.SecretAccessKey, "", signatureType) +- } ++ // Public routes (no auth required) ++ r.Handle("/login", s3manager.HandleLoginView(templates)).Methods(http.MethodGet) ++ r.Handle("/login", s3manager.HandleLogin(sessionCfg)).Methods(http.MethodPost) ++ r.Handle("/logout", s3manager.HandleLogout(sessionCfg)).Methods(http.MethodPost) ++ ++ // Protected routes (auth required via middleware) ++ protected := mux.NewRouter() ++ protected.Handle("/", http.RedirectHandler("/buckets", http.StatusPermanentRedirect)).Methods(http.MethodGet) ++ protected.Handle("/buckets", s3manager.HandleBucketsViewDynamic(templates, configuration.AllowDelete)).Methods(http.MethodGet) ++ protected.PathPrefix("/buckets/").Handler(s3manager.HandleBucketViewDynamic(templates, configuration.AllowDelete, configuration.ListRecursive)).Methods(http.MethodGet) ++ protected.Handle("/api/buckets", s3manager.HandleCreateBucketDynamic()).Methods(http.MethodPost) ++ if configuration.AllowDelete { ++ protected.Handle("/api/buckets/{bucketName}", s3manager.HandleDeleteBucketDynamic()).Methods(http.MethodDelete) ++ } ++ protected.Handle("/api/buckets/{bucketName}/objects", s3manager.HandleCreateObjectDynamic(sseType)).Methods(http.MethodPost) ++ protected.Handle("/api/buckets/{bucketName}/objects/{objectName:.*}/url", s3manager.HandleGenerateUrlDynamic()).Methods(http.MethodGet) ++ protected.Handle("/api/buckets/{bucketName}/objects/{objectName:.*}", s3manager.HandleGetObjectDynamic(configuration.ForceDownload)).Methods(http.MethodGet) ++ if configuration.AllowDelete { ++ protected.Handle("/api/buckets/{bucketName}/objects/{objectName:.*}", s3manager.HandleDeleteObjectDynamic()).Methods(http.MethodDelete) ++ } + +- if configuration.Region != "" { +- opts.Region = configuration.Region +- } +- if configuration.UseSSL && configuration.SkipSSLVerification { +- opts.Transport = &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} //nolint:gosec +- } +- s3, err := minio.New(configuration.Endpoint, opts) +- if err != nil { +- log.Fatalln(fmt.Errorf("error creating s3 client: %w", err)) +- } ++ r.PathPrefix("/").Handler(s3manager.RequireAuth(sessionCfg, protected)) ++ } else { + // Pre-configured mode: existing behavior with static S3 client + opts := &minio.Options{ + Secure: configuration.UseSSL, @@ -339,30 +186,6 @@ index 2ffe8ab..5fdc4cd 100644 + } + + opts.Creds = credentials.NewStatic(configuration.AccessKeyID, configuration.SecretAccessKey, "", signatureType) - } - -- opts.Creds = credentials.NewStatic(configuration.AccessKeyID, configuration.SecretAccessKey, "", signatureType) -- } -- -- if configuration.Region != "" { -- opts.Region = configuration.Region -- } -- if configuration.UseSSL && configuration.SkipSSLVerification { -- opts.Transport = &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} //nolint:gosec -- } -- s3, err := minio.New(configuration.Endpoint, opts) -- if err != nil { -- log.Fatalln(fmt.Errorf("error creating s3 client: %w", err)) -- } -+ if configuration.Region != "" { -+ opts.Region = configuration.Region -+ } -+ if configuration.UseSSL && configuration.SkipSSLVerification { -+ opts.Transport = &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} //nolint:gosec -+ } -+ s3, err := minio.New(configuration.Endpoint, opts) -+ if err != nil { -+ log.Fatalln(fmt.Errorf("error creating s3 client: %w", err)) + } - // Set up router @@ -380,6 +203,17 @@ index 2ffe8ab..5fdc4cd 100644 - r.Handle("/api/buckets/{bucketName}/objects/{objectName:.*}", s3manager.HandleGetObject(s3, configuration.ForceDownload)).Methods(http.MethodGet) - if configuration.AllowDelete { - r.Handle("/api/buckets/{bucketName}/objects/{objectName:.*}", s3manager.HandleDeleteObject(s3)).Methods(http.MethodDelete) ++ if configuration.Region != "" { ++ opts.Region = configuration.Region ++ } ++ if configuration.UseSSL && configuration.SkipSSLVerification { ++ opts.Transport = &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} //nolint:gosec ++ } ++ s3, err := minio.New(configuration.Endpoint, opts) ++ if err != nil { ++ log.Fatalln(fmt.Errorf("error creating s3 client: %w", err)) ++ } ++ + r.Handle("/", http.RedirectHandler("/buckets", http.StatusPermanentRedirect)).Methods(http.MethodGet) + r.Handle("/buckets", s3manager.HandleBucketsView(s3, templates, configuration.AllowDelete)).Methods(http.MethodGet) + r.PathPrefix("/buckets/").Handler(s3manager.HandleBucketView(s3, templates, configuration.AllowDelete, configuration.ListRecursive)).Methods(http.MethodGet) @@ -422,12 +256,255 @@ index c7ea184..fb1dce7 100644 +diff --git a/internal/app/s3manager/auth.go b/internal/app/s3manager/auth.go +new file mode 100644 +index 0000000..58589e2 +--- /dev/null ++++ b/internal/app/s3manager/auth.go +@@ -0,0 +1,237 @@ ++package s3manager ++ ++import ( ++ "context" ++ "crypto/rand" ++ "crypto/tls" ++ "fmt" ++ "html/template" ++ "io/fs" ++ "log" ++ "net/http" ++ ++ "github.com/gorilla/sessions" ++ "github.com/minio/minio-go/v7" ++ "github.com/minio/minio-go/v7/pkg/credentials" ++) ++ ++type contextKey string ++ ++const s3ContextKey contextKey = "s3client" ++ ++// SessionConfig holds session store and S3 connection settings for login mode. ++type SessionConfig struct { ++ Store *sessions.CookieStore ++ Endpoint string ++ UseSSL bool ++ SkipSSLVerify bool ++ AllowDelete bool ++ ForceDownload bool ++ ListRecursive bool ++ SseInfo SSEType ++ Templates fs.FS ++} ++ ++// NewSessionStore creates a CookieStore with a random encryption key. ++func NewSessionStore() *sessions.CookieStore { ++ key := make([]byte, 32) ++ if _, err := rand.Read(key); err != nil { ++ log.Fatal("failed to generate session key:", err) ++ } ++ store := sessions.NewCookieStore(key) ++ store.Options = &sessions.Options{ ++ Path: "/", ++ MaxAge: 86400, ++ HttpOnly: true, ++ Secure: true, ++ SameSite: http.SameSiteLaxMode, ++ } ++ return store ++} ++ ++// NewS3Client creates a minio client from user-provided credentials. ++func NewS3Client(endpoint, accessKey, secretKey string, useSSL, skipSSLVerify bool) (*minio.Client, error) { ++ opts := &minio.Options{ ++ Creds: credentials.NewStaticV4(accessKey, secretKey, ""), ++ Secure: useSSL, ++ } ++ if useSSL && skipSSLVerify { ++ opts.Transport = &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} //nolint:gosec ++ } ++ return minio.New(endpoint, opts) ++} ++ ++// S3FromContext retrieves the S3 client stored in request context. ++func S3FromContext(ctx context.Context) S3 { ++ if s3, ok := ctx.Value(s3ContextKey).(S3); ok { ++ return s3 ++ } ++ return nil ++} ++ ++func contextWithS3(ctx context.Context, s3 S3) context.Context { ++ return context.WithValue(ctx, s3ContextKey, s3) ++} ++ ++// RequireAuth is middleware that validates session credentials and injects ++// an S3 client into the request context. Redirects to /login if no session. ++func RequireAuth(cfg *SessionConfig, next http.Handler) http.Handler { ++ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ++ session, _ := cfg.Store.Get(r, "s3session") ++ accessKey, ok1 := session.Values["accessKey"].(string) ++ secretKey, ok2 := session.Values["secretKey"].(string) ++ if !ok1 || !ok2 || accessKey == "" || secretKey == "" { ++ http.Redirect(w, r, "/login", http.StatusFound) ++ return ++ } ++ ++ s3, err := NewS3Client(cfg.Endpoint, accessKey, secretKey, cfg.UseSSL, cfg.SkipSSLVerify) ++ if err != nil { ++ // Session has bad credentials — clear and redirect to login ++ session.Options.MaxAge = -1 ++ _ = session.Save(r, w) ++ http.Redirect(w, r, "/login", http.StatusFound) ++ return ++ } ++ ++ ctx := contextWithS3(r.Context(), s3) ++ next.ServeHTTP(w, r.WithContext(ctx)) ++ }) ++} ++ ++// HandleLoginView renders the login page. ++func HandleLoginView(templates fs.FS) http.HandlerFunc { ++ return func(w http.ResponseWriter, r *http.Request) { ++ errorMsg := r.URL.Query().Get("error") ++ ++ data := struct { ++ Error string ++ }{ ++ Error: errorMsg, ++ } ++ ++ t, err := template.ParseFS(templates, "layout.html.tmpl", "login.html.tmpl") ++ if err != nil { ++ handleHTTPError(w, fmt.Errorf("error parsing login template: %w", err)) ++ return ++ } ++ err = t.ExecuteTemplate(w, "layout", data) ++ if err != nil { ++ handleHTTPError(w, fmt.Errorf("error executing login template: %w", err)) ++ return ++ } ++ } ++} ++ ++// HandleLogin processes the login form POST. ++func HandleLogin(cfg *SessionConfig) http.HandlerFunc { ++ return func(w http.ResponseWriter, r *http.Request) { ++ accessKey := r.FormValue("accessKey") ++ secretKey := r.FormValue("secretKey") ++ ++ if accessKey == "" || secretKey == "" { ++ http.Redirect(w, r, "/login?error=credentials+required", http.StatusFound) ++ return ++ } ++ ++ // Validate credentials by attempting ListBuckets ++ s3, err := NewS3Client(cfg.Endpoint, accessKey, secretKey, cfg.UseSSL, cfg.SkipSSLVerify) ++ if err != nil { ++ http.Redirect(w, r, "/login?error=connection+failed", http.StatusFound) ++ return ++ } ++ _, err = s3.ListBuckets(r.Context()) ++ if err != nil { ++ http.Redirect(w, r, "/login?error=invalid+credentials", http.StatusFound) ++ return ++ } ++ ++ // Save credentials to session ++ session, _ := cfg.Store.Get(r, "s3session") ++ session.Values["accessKey"] = accessKey ++ session.Values["secretKey"] = secretKey ++ err = session.Save(r, w) ++ if err != nil { ++ handleHTTPError(w, fmt.Errorf("error saving session: %w", err)) ++ return ++ } ++ ++ http.Redirect(w, r, "/buckets", http.StatusFound) ++ } ++} ++ ++// HandleLogout destroys the session and redirects to login. ++func HandleLogout(cfg *SessionConfig) http.HandlerFunc { ++ return func(w http.ResponseWriter, r *http.Request) { ++ session, _ := cfg.Store.Get(r, "s3session") ++ session.Options.MaxAge = -1 ++ _ = session.Save(r, w) ++ http.Redirect(w, r, "/login", http.StatusFound) ++ } ++} ++ ++// Dynamic handler wrappers — extract S3 from context, delegate to original handlers. ++ ++// HandleBucketsViewDynamic wraps HandleBucketsView for login mode. ++func HandleBucketsViewDynamic(templates fs.FS, allowDelete bool) http.HandlerFunc { ++ return func(w http.ResponseWriter, r *http.Request) { ++ s3 := S3FromContext(r.Context()) ++ HandleBucketsView(s3, templates, allowDelete).ServeHTTP(w, r) ++ } ++} ++ ++// HandleBucketViewDynamic wraps HandleBucketView for login mode. ++func HandleBucketViewDynamic(templates fs.FS, allowDelete bool, listRecursive bool) http.HandlerFunc { ++ return func(w http.ResponseWriter, r *http.Request) { ++ s3 := S3FromContext(r.Context()) ++ HandleBucketView(s3, templates, allowDelete, listRecursive).ServeHTTP(w, r) ++ } ++} ++ ++// HandleCreateBucketDynamic wraps HandleCreateBucket for login mode. ++func HandleCreateBucketDynamic() http.HandlerFunc { ++ return func(w http.ResponseWriter, r *http.Request) { ++ s3 := S3FromContext(r.Context()) ++ HandleCreateBucket(s3).ServeHTTP(w, r) ++ } ++} ++ ++// HandleDeleteBucketDynamic wraps HandleDeleteBucket for login mode. ++func HandleDeleteBucketDynamic() http.HandlerFunc { ++ return func(w http.ResponseWriter, r *http.Request) { ++ s3 := S3FromContext(r.Context()) ++ HandleDeleteBucket(s3).ServeHTTP(w, r) ++ } ++} ++ ++// HandleCreateObjectDynamic wraps HandleCreateObject for login mode. ++func HandleCreateObjectDynamic(sseInfo SSEType) http.HandlerFunc { ++ return func(w http.ResponseWriter, r *http.Request) { ++ s3 := S3FromContext(r.Context()) ++ HandleCreateObject(s3, sseInfo).ServeHTTP(w, r) ++ } ++} ++ ++// HandleGenerateUrlDynamic wraps HandleGenerateUrl for login mode. ++func HandleGenerateUrlDynamic() http.HandlerFunc { ++ return func(w http.ResponseWriter, r *http.Request) { ++ s3 := S3FromContext(r.Context()) ++ HandleGenerateUrl(s3).ServeHTTP(w, r) ++ } ++} ++ ++// HandleGetObjectDynamic wraps HandleGetObject for login mode. ++func HandleGetObjectDynamic(forceDownload bool) http.HandlerFunc { ++ return func(w http.ResponseWriter, r *http.Request) { ++ s3 := S3FromContext(r.Context()) ++ HandleGetObject(s3, forceDownload).ServeHTTP(w, r) ++ } ++} ++ ++// HandleDeleteObjectDynamic wraps HandleDeleteObject for login mode. ++func HandleDeleteObjectDynamic() http.HandlerFunc { ++ return func(w http.ResponseWriter, r *http.Request) { ++ s3 := S3FromContext(r.Context()) ++ HandleDeleteObject(s3).ServeHTTP(w, r) ++ } ++} diff --git a/web/template/login.html.tmpl b/web/template/login.html.tmpl new file mode 100644 -index 0000000..7a1730a +index 0000000..f153018 --- /dev/null +++ b/web/template/login.html.tmpl -@@ -0,0 +1,69 @@ +@@ -0,0 +1,46 @@ +{{ define "content" }} +