mirror of
https://github.com/cozystack/cozystack.git
synced 2026-03-03 13:38:56 +00:00
Compare commits
233 Commits
testing/ad
...
linstor-af
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a78b76f324 | ||
|
|
1f0b5ff9ac | ||
|
|
1ec14d6bd6 | ||
|
|
03a71eb8de | ||
|
|
ee2a34ca81 | ||
|
|
0f7bd3e395 | ||
|
|
0d71525f7e | ||
|
|
10d35742e2 | ||
|
|
61ec812a3e | ||
|
|
373a0d1359 | ||
|
|
680f70c03a | ||
|
|
b1ba1f2172 | ||
|
|
e3b96e12be | ||
|
|
4f5ae287f5 | ||
|
|
6b8c490b1d | ||
|
|
90c725194f | ||
|
|
a05cc3512e | ||
|
|
8513dd6b3f | ||
|
|
0bab895026 | ||
|
|
349677ffe9 | ||
|
|
1f47fbc3dd | ||
|
|
d079dd4731 | ||
|
|
f3207fcd10 | ||
|
|
650e5290ea | ||
|
|
19586e1eec | ||
|
|
578a810413 | ||
|
|
89897914fa | ||
|
|
892855276b | ||
|
|
58dd1f5881 | ||
|
|
67ecf3d0f6 | ||
|
|
38d6b98a70 | ||
|
|
0a93972c4f | ||
|
|
da4d6053bb | ||
|
|
28c933161a | ||
|
|
e50950b7a1 | ||
|
|
b1a55f5a38 | ||
|
|
1ee6eb8482 | ||
|
|
cbfa99148b | ||
|
|
54d0f52245 | ||
|
|
d0bfb6e2fc | ||
|
|
f86896eceb | ||
|
|
cb320f9d48 | ||
|
|
a7b423934f | ||
|
|
b1a7e9560e | ||
|
|
fe9d334880 | ||
|
|
1e2b66131c | ||
|
|
8d14bcb598 | ||
|
|
825390c209 | ||
|
|
09fd7c4094 | ||
|
|
8928731abf | ||
|
|
153635379a | ||
|
|
fbb2ea095a | ||
|
|
50c1d1a067 | ||
|
|
52ebcae8a2 | ||
|
|
5314d61987 | ||
|
|
8b29c53a45 | ||
|
|
4e4a5606d7 | ||
|
|
f2a4f1b1c8 | ||
|
|
33128748e6 | ||
|
|
7e1cad26e7 | ||
|
|
3d5118f5b3 | ||
|
|
06a25c1c45 | ||
|
|
13e0501acd | ||
|
|
ca29fc855a | ||
|
|
c7f478fc7d | ||
|
|
d53506ae2a | ||
|
|
8bc62d4c71 | ||
|
|
0b27f634c0 | ||
|
|
67e47256e2 | ||
|
|
df277b350c | ||
|
|
644d71eef7 | ||
|
|
9d1fb4ccf2 | ||
|
|
27efd3ad5e | ||
|
|
7b20e3f4cc | ||
|
|
5d354a07d6 | ||
|
|
aa8062c41c | ||
|
|
9ceb59e74c | ||
|
|
0df528a89d | ||
|
|
d70197c825 | ||
|
|
f2f8da0be1 | ||
|
|
094ee6da55 | ||
|
|
f256575fce | ||
|
|
d1ad38dd01 | ||
|
|
bc1fed4079 | ||
|
|
0b29ffefe0 | ||
|
|
c72a9333e9 | ||
|
|
d46cccda71 | ||
|
|
b5b12d0684 | ||
|
|
8283714930 | ||
|
|
aa428457db | ||
|
|
975011e04e | ||
|
|
0d88aa394a | ||
|
|
ec1a150d2c | ||
|
|
cbc6cd2567 | ||
|
|
fb7e39eaab | ||
|
|
9cc348733f | ||
|
|
00e0f45de3 | ||
|
|
b5c264de7d | ||
|
|
4ff60e4539 | ||
|
|
294458e7c4 | ||
|
|
42cb0e6974 | ||
|
|
73bf0e5f7e | ||
|
|
f512061a1c | ||
|
|
12db4fc520 | ||
|
|
8e351f1827 | ||
|
|
38a4adfaa3 | ||
|
|
91ddbb06ef | ||
|
|
7d2250be4d | ||
|
|
a070573af9 | ||
|
|
492aef93f5 | ||
|
|
23e6cf735a | ||
|
|
c5b1177149 | ||
|
|
84133ef2d3 | ||
|
|
1c9ae2bec5 | ||
|
|
03885f5ae2 | ||
|
|
bdff61eaed | ||
|
|
3d4ad39bce | ||
|
|
f2f575b450 | ||
|
|
aba4d2c977 | ||
|
|
e4021bbf57 | ||
|
|
ef8612e882 | ||
|
|
32b58dec5f | ||
|
|
1bafb7fb4f | ||
|
|
bc61d13ad3 | ||
|
|
972548cab4 | ||
|
|
bb8d07d384 | ||
|
|
6fdc9b0bad | ||
|
|
9c040cd42f | ||
|
|
5414d37376 | ||
|
|
a9818a7ce7 | ||
|
|
1651d94291 | ||
|
|
2b4afde373 | ||
|
|
a5c9bfabee | ||
|
|
143832c0b4 | ||
|
|
298206efc7 | ||
|
|
c81b222cf6 | ||
|
|
9d6af84449 | ||
|
|
7ddd9cf4a8 | ||
|
|
a861814c24 | ||
|
|
d65d293fbc | ||
|
|
523510469c | ||
|
|
cf5b2f2bbb | ||
|
|
4e5343e36c | ||
|
|
d8237b4321 | ||
|
|
83c3b0ca12 | ||
|
|
e1590aad1b | ||
|
|
304338d697 | ||
|
|
b65d639ecb | ||
|
|
339e71331f | ||
|
|
08be385665 | ||
|
|
2f0657f8ba | ||
|
|
a64ba184ce | ||
|
|
00328c8a31 | ||
|
|
7009c8da37 | ||
|
|
63db8ca009 | ||
|
|
369384f5ec | ||
|
|
4278692763 | ||
|
|
edc942b6c1 | ||
|
|
4c71e7fe57 | ||
|
|
627022972d | ||
|
|
1e8a9ee980 | ||
|
|
b45f4a6545 | ||
|
|
5b96190be8 | ||
|
|
8849570f74 | ||
|
|
b6958320b2 | ||
|
|
0a210bf5d3 | ||
|
|
90d50fef48 | ||
|
|
19ed058897 | ||
|
|
6438ce98b1 | ||
|
|
523d8ea638 | ||
|
|
e89896fdba | ||
|
|
ab5101a713 | ||
|
|
af460f1c41 | ||
|
|
634649f9ec | ||
|
|
df782fec9c | ||
|
|
172774b6cd | ||
|
|
62119eb761 | ||
|
|
48c6e23ca0 | ||
|
|
9064a72c92 | ||
|
|
dc06b16d11 | ||
|
|
739a74dc28 | ||
|
|
723eefea66 | ||
|
|
1d10907168 | ||
|
|
c19cddf08e | ||
|
|
4c08caafe1 | ||
|
|
be58047aba | ||
|
|
f60e2555c9 | ||
|
|
6443a1264e | ||
|
|
52a23eacfc | ||
|
|
2634b01465 | ||
|
|
15a3636d5f | ||
|
|
ef43ef6753 | ||
|
|
ba804b7c52 | ||
|
|
9c5abf49ca | ||
|
|
10e79651ef | ||
|
|
965818efd4 | ||
|
|
b1ebc9cc85 | ||
|
|
667c778f27 | ||
|
|
77d95e3b91 | ||
|
|
a8d3cbce82 | ||
|
|
eea685065a | ||
|
|
480f8027d7 | ||
|
|
19b56414a6 | ||
|
|
0f9806e9b0 | ||
|
|
177073596c | ||
|
|
93a9241899 | ||
|
|
5401ae9734 | ||
|
|
b78d97f374 | ||
|
|
8b95db06ee | ||
|
|
5a2d4d7e66 | ||
|
|
42e6f0e3f2 | ||
|
|
e2eb1e267b | ||
|
|
2ac533f2f6 | ||
|
|
ae9f9c57b1 | ||
|
|
18f253f77a | ||
|
|
bd9dcb52a3 | ||
|
|
be473a12be | ||
|
|
8f5adcccf5 | ||
|
|
08bd918a10 | ||
|
|
023276ebab | ||
|
|
19c4674ebb | ||
|
|
202da193c0 | ||
|
|
cc9687707c | ||
|
|
ac10e35272 | ||
|
|
fc7d5ee71f | ||
|
|
9d90503fb7 | ||
|
|
4be1c257d6 | ||
|
|
f3ba8eca8e | ||
|
|
0f286ee7ba | ||
|
|
ea74d7d59a | ||
|
|
74262977f6 | ||
|
|
d26d3e1f40 | ||
|
|
1e36722ab8 |
104
.github/workflows/backport.yaml
vendored
104
.github/workflows/backport.yaml
vendored
@@ -2,7 +2,7 @@ name: Automatic Backport
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [closed] # fires when PR is closed (merged)
|
||||
types: [closed, labeled] # fires when PR is closed (merged) or labeled
|
||||
|
||||
concurrency:
|
||||
group: backport-${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||
@@ -13,22 +13,46 @@ permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
backport:
|
||||
# Determine which backports are needed
|
||||
prepare:
|
||||
if: |
|
||||
github.event.pull_request.merged == true &&
|
||||
contains(github.event.pull_request.labels.*.name, 'backport')
|
||||
(
|
||||
contains(github.event.pull_request.labels.*.name, 'backport') ||
|
||||
contains(github.event.pull_request.labels.*.name, 'backport-previous') ||
|
||||
(github.event.action == 'labeled' && (github.event.label.name == 'backport' || github.event.label.name == 'backport-previous'))
|
||||
)
|
||||
runs-on: [self-hosted]
|
||||
|
||||
outputs:
|
||||
backport_current: ${{ steps.labels.outputs.backport }}
|
||||
backport_previous: ${{ steps.labels.outputs.backport_previous }}
|
||||
current_branch: ${{ steps.branches.outputs.current_branch }}
|
||||
previous_branch: ${{ steps.branches.outputs.previous_branch }}
|
||||
steps:
|
||||
# 1. Decide which maintenance branch should receive the back‑port
|
||||
- name: Determine target maintenance branch
|
||||
id: target
|
||||
- name: Check which labels are present
|
||||
id: labels
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
let rel;
|
||||
const pr = context.payload.pull_request;
|
||||
const labels = pr.labels.map(l => l.name);
|
||||
const isBackport = labels.includes('backport');
|
||||
const isBackportPrevious = labels.includes('backport-previous');
|
||||
|
||||
core.setOutput('backport', isBackport ? 'true' : 'false');
|
||||
core.setOutput('backport_previous', isBackportPrevious ? 'true' : 'false');
|
||||
|
||||
console.log(`backport label: ${isBackport}, backport-previous label: ${isBackportPrevious}`);
|
||||
|
||||
- name: Determine target branches
|
||||
id: branches
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
// Get latest release
|
||||
let latestRelease;
|
||||
try {
|
||||
rel = await github.rest.repos.getLatestRelease({
|
||||
latestRelease = await github.rest.repos.getLatestRelease({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo
|
||||
});
|
||||
@@ -36,18 +60,70 @@ jobs:
|
||||
core.setFailed('No existing releases found; cannot determine backport target.');
|
||||
return;
|
||||
}
|
||||
const [maj, min] = rel.data.tag_name.replace(/^v/, '').split('.');
|
||||
const branch = `release-${maj}.${min}`;
|
||||
core.setOutput('branch', branch);
|
||||
console.log(`Latest release ${rel.data.tag_name}; backporting to ${branch}`);
|
||||
|
||||
const [maj, min] = latestRelease.data.tag_name.replace(/^v/, '').split('.');
|
||||
const currentBranch = `release-${maj}.${min}`;
|
||||
const prevMin = parseInt(min) - 1;
|
||||
const previousBranch = prevMin >= 0 ? `release-${maj}.${prevMin}` : '';
|
||||
|
||||
core.setOutput('current_branch', currentBranch);
|
||||
core.setOutput('previous_branch', previousBranch);
|
||||
|
||||
console.log(`Current branch: ${currentBranch}, Previous branch: ${previousBranch || 'N/A'}`);
|
||||
|
||||
// Verify previous branch exists if we need it
|
||||
if (previousBranch && '${{ steps.labels.outputs.backport_previous }}' === 'true') {
|
||||
try {
|
||||
await github.rest.repos.getBranch({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
branch: previousBranch
|
||||
});
|
||||
console.log(`Previous branch ${previousBranch} exists`);
|
||||
} catch (e) {
|
||||
core.setFailed(`Previous branch ${previousBranch} does not exist.`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
backport:
|
||||
needs: prepare
|
||||
if: |
|
||||
github.event.pull_request.merged == true &&
|
||||
(needs.prepare.outputs.backport_current == 'true' || needs.prepare.outputs.backport_previous == 'true')
|
||||
runs-on: [self-hosted]
|
||||
strategy:
|
||||
matrix:
|
||||
backport_type: [current, previous]
|
||||
steps:
|
||||
# 1. Determine target branch based on matrix
|
||||
- name: Set target branch
|
||||
id: target
|
||||
if: |
|
||||
(matrix.backport_type == 'current' && needs.prepare.outputs.backport_current == 'true') ||
|
||||
(matrix.backport_type == 'previous' && needs.prepare.outputs.backport_previous == 'true')
|
||||
run: |
|
||||
if [ "${{ matrix.backport_type }}" == "current" ]; then
|
||||
echo "branch=${{ needs.prepare.outputs.current_branch }}" >> $GITHUB_OUTPUT
|
||||
echo "Target branch: ${{ needs.prepare.outputs.current_branch }}"
|
||||
else
|
||||
echo "branch=${{ needs.prepare.outputs.previous_branch }}" >> $GITHUB_OUTPUT
|
||||
echo "Target branch: ${{ needs.prepare.outputs.previous_branch }}"
|
||||
fi
|
||||
|
||||
# 2. Checkout (required by backport‑action)
|
||||
- name: Checkout repository
|
||||
if: steps.target.outcome == 'success'
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# 3. Create the back‑port pull request
|
||||
- name: Create back‑port PR
|
||||
uses: korthout/backport-action@v3
|
||||
id: backport
|
||||
if: steps.target.outcome == 'success'
|
||||
uses: korthout/backport-action@v3.2.1
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
label_pattern: '' # don't read labels for targets
|
||||
target_branches: ${{ steps.target.outputs.branch }}
|
||||
merge_commits: skip
|
||||
conflict_resolution: draft_commit_conflicts
|
||||
|
||||
2
.github/workflows/pre-commit.yml
vendored
2
.github/workflows/pre-commit.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
||||
|
||||
- name: Install generate
|
||||
run: |
|
||||
curl -sSL https://github.com/cozystack/cozyvalues-gen/releases/download/v1.0.5/cozyvalues-gen-linux-amd64.tar.gz | tar -xzvf- -C /usr/local/bin/ cozyvalues-gen
|
||||
curl -sSL https://github.com/cozystack/cozyvalues-gen/releases/download/v1.0.6/cozyvalues-gen-linux-amd64.tar.gz | tar -xzvf- -C /usr/local/bin/ cozyvalues-gen
|
||||
|
||||
- name: Run pre-commit hooks
|
||||
run: |
|
||||
|
||||
3
.github/workflows/pull-requests.yaml
vendored
3
.github/workflows/pull-requests.yaml
vendored
@@ -33,6 +33,9 @@ jobs:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
|
||||
- name: Run unit tests
|
||||
run: make unit-tests
|
||||
|
||||
- name: Set up Docker config
|
||||
run: |
|
||||
if [ -d ~/.docker ]; then
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
_out
|
||||
_repos
|
||||
.git
|
||||
.idea
|
||||
.vscode
|
||||
|
||||
@@ -31,4 +31,5 @@ This list is sorted in chronological order, based on the submission date.
|
||||
| [gohost](https://gohost.kz/) | @karabass_off | 2024-02-01 | Our company has been working in the market of Kazakhstan for more than 15 years, providing clients with a standard set of services: VPS/VDC, IaaS, shared hosting, etc. Now we are expanding the lineup by introducing Bare Metal Kubenetes cluster under Cozystack management. |
|
||||
| [Urmanac](https://urmanac.com) | @kingdonb | 2024-12-04 | Urmanac is the future home of a hosting platform for the knowledge base of a community of personal server enthusiasts. We use Cozystack to provide support services for web sites hosted using both conventional deployments and on SpinKube, with WASM. |
|
||||
| [Hidora](https://hikube.cloud) | @matthieu-robin | 2025-09-17 | Hidora is a Swiss cloud provider delivering managed services and infrastructure solutions through datacenters located in Switzerland, ensuring data sovereignty and reliability. Its sovereign cloud platform, Hikube, is designed to run workloads with high availability across multiple datacenters, providing enterprises with a secure and scalable foundation for their applications based on Cozystack. |
|
||||
|
|
||||
| [QOSI](https://qosi.kz) | @tabu-a | 2025-10-04 | QOSI is a non-profit organization driving open-source adoption and digital sovereignty across Kazakhstan and Central Asia. We use Cozystack as a platform for deploying sovereign, GPU-enabled clouds and educational environments under the National AI Program. Our goal is to accelerate the region’s transition toward open, self-hosted cloud-native technologies |
|
||||
|
|
||||
38
AGENTS.md
Normal file
38
AGENTS.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# AI Agents Overview
|
||||
|
||||
This file provides structured guidance for AI coding assistants and agents
|
||||
working with the **Cozystack** project.
|
||||
|
||||
## Agent Documentation
|
||||
|
||||
| Agent | Purpose |
|
||||
|-------|---------|
|
||||
| [overview.md](./docs/agents/overview.md) | Project structure and conventions |
|
||||
| [contributing.md](./docs/agents/contributing.md) | Commits, pull requests, and git workflow |
|
||||
| [changelog.md](./docs/agents/changelog.md) | Changelog generation instructions |
|
||||
| [releasing.md](./docs/agents/releasing.md) | Release process and workflow |
|
||||
|
||||
## Project Overview
|
||||
|
||||
**Cozystack** is a Kubernetes-based platform for building cloud infrastructure with managed services (databases, VMs, K8s clusters), multi-tenancy, and GitOps delivery.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Code Structure
|
||||
- `packages/core/` - Core platform charts (installer, platform)
|
||||
- `packages/system/` - System components (CSI, CNI, operators)
|
||||
- `packages/apps/` - User-facing applications in catalog
|
||||
- `packages/extra/` - Tenant-specific modules
|
||||
- `cmd/`, `internal/`, `pkg/` - Go code
|
||||
- `api/` - Kubernetes CRDs
|
||||
|
||||
### Conventions
|
||||
- **Helm Charts**: Umbrella pattern, vendored upstream charts in `charts/`
|
||||
- **Go Code**: Controller-runtime patterns, kubebuilder style
|
||||
- **Git Commits**: `[component] Description` format with `--signoff`
|
||||
|
||||
### What NOT to Do
|
||||
- ❌ Edit `/vendor/`, `zz_generated.*.go`, upstream charts directly
|
||||
- ❌ Modify `go.mod`/`go.sum` manually (use `go get`)
|
||||
- ❌ Force push to main/master
|
||||
- ❌ Commit built artifacts from `_out`
|
||||
10
Makefile
10
Makefile
@@ -1,4 +1,4 @@
|
||||
.PHONY: manifests repos assets
|
||||
.PHONY: manifests repos assets unit-tests helm-unit-tests
|
||||
|
||||
build-deps:
|
||||
@command -V find docker skopeo jq gh helm > /dev/null
|
||||
@@ -15,9 +15,9 @@ build: build-deps
|
||||
make -C packages/extra/monitoring image
|
||||
make -C packages/system/cozystack-api image
|
||||
make -C packages/system/cozystack-controller image
|
||||
make -C packages/system/backup-controller image
|
||||
make -C packages/system/lineage-controller-webhook image
|
||||
make -C packages/system/cilium image
|
||||
make -C packages/system/kubeovn image
|
||||
make -C packages/system/kubeovn-webhook image
|
||||
make -C packages/system/kubeovn-plunger image
|
||||
make -C packages/system/dashboard image
|
||||
@@ -26,6 +26,7 @@ build: build-deps
|
||||
make -C packages/system/bucket image
|
||||
make -C packages/system/objectstorage-controller image
|
||||
make -C packages/core/testing image
|
||||
make -C packages/core/platform image
|
||||
make -C packages/core/installer image
|
||||
make manifests
|
||||
|
||||
@@ -46,6 +47,11 @@ test:
|
||||
make -C packages/core/testing apply
|
||||
make -C packages/core/testing test
|
||||
|
||||
unit-tests: helm-unit-tests
|
||||
|
||||
helm-unit-tests:
|
||||
hack/helm-unit-tests.sh
|
||||
|
||||
prepare-env:
|
||||
make -C packages/core/testing apply
|
||||
make -C packages/core/testing prepare-cluster
|
||||
|
||||
1
api/.gitattributes
vendored
Normal file
1
api/.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
zz_generated_deepcopy.go linguist-generated
|
||||
37
api/backups/strategy/v1alpha1/groupversion_info.go
Normal file
37
api/backups/strategy/v1alpha1/groupversion_info.go
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
Copyright 2025.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package v1alpha1 contains API Schema definitions for the v1alpha1 API group.
|
||||
// +kubebuilder:object:generate=true
|
||||
// +groupName=strategy.backups.cozystack.io
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
var (
|
||||
GroupVersion = schema.GroupVersion{Group: "strategy.backups.cozystack.io", Version: "v1alpha1"}
|
||||
SchemeBuilder = runtime.NewSchemeBuilder(addGroupVersion)
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
func addGroupVersion(scheme *runtime.Scheme) error {
|
||||
metav1.AddToGroupVersion(scheme, GroupVersion)
|
||||
return nil
|
||||
}
|
||||
63
api/backups/strategy/v1alpha1/job_types.go
Normal file
63
api/backups/strategy/v1alpha1/job_types.go
Normal file
@@ -0,0 +1,63 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Package v1alpha1 defines strategy.backups.cozystack.io API types.
|
||||
//
|
||||
// Group: strategy.backups.cozystack.io
|
||||
// Version: v1alpha1
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(func(s *runtime.Scheme) error {
|
||||
s.AddKnownTypes(GroupVersion,
|
||||
&Job{},
|
||||
&JobList{},
|
||||
)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
const (
|
||||
JobStrategyKind = "Job"
|
||||
)
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:resource:scope=Cluster
|
||||
|
||||
// Job defines a backup strategy using a one-shot Job
|
||||
type Job struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec JobSpec `json:"spec,omitempty"`
|
||||
Status JobStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// JobList contains a list of backup Jobs.
|
||||
type JobList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []Job `json:"items"`
|
||||
}
|
||||
|
||||
// JobSpec specifies the desired behavior of a backup job.
|
||||
type JobSpec struct {
|
||||
// Template holds a PodTemplateSpec with the right shape to
|
||||
// run a single pod to completion and create a tarball with
|
||||
// a given apps data. Helm-like Go templates are supported.
|
||||
// The values of the source application are available under
|
||||
// `.Values`. `.Release.Name` and `.Release.Namespace` are
|
||||
// also exported.
|
||||
Template corev1.PodTemplateSpec `json:"template"`
|
||||
}
|
||||
|
||||
type JobStatus struct {
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||
}
|
||||
123
api/backups/strategy/v1alpha1/zz_generated.deepcopy.go
Normal file
123
api/backups/strategy/v1alpha1/zz_generated.deepcopy.go
Normal file
@@ -0,0 +1,123 @@
|
||||
//go:build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 2025 The Cozystack Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by controller-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Job) DeepCopyInto(out *Job) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Job.
|
||||
func (in *Job) DeepCopy() *Job {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Job)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *Job) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *JobList) DeepCopyInto(out *JobList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]Job, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JobList.
|
||||
func (in *JobList) DeepCopy() *JobList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(JobList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *JobList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *JobSpec) DeepCopyInto(out *JobSpec) {
|
||||
*out = *in
|
||||
in.Template.DeepCopyInto(&out.Template)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JobSpec.
|
||||
func (in *JobSpec) DeepCopy() *JobSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(JobSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *JobStatus) DeepCopyInto(out *JobStatus) {
|
||||
*out = *in
|
||||
if in.Conditions != nil {
|
||||
in, out := &in.Conditions, &out.Conditions
|
||||
*out = make([]v1.Condition, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JobStatus.
|
||||
func (in *JobStatus) DeepCopy() *JobStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(JobStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
421
api/backups/v1alpha1/DESIGN.md
Normal file
421
api/backups/v1alpha1/DESIGN.md
Normal file
@@ -0,0 +1,421 @@
|
||||
# Cozystack Backups – Core API & Contracts (Draft)
|
||||
|
||||
## 1. Overview
|
||||
|
||||
Cozystack’s backup subsystem provides a generic, composable way to back up and restore managed applications:
|
||||
|
||||
* Every **application instance** can have one or more **backup plans**.
|
||||
* Backups are stored in configurable **storage locations**.
|
||||
* The mechanics of *how* a backup/restore is performed are delegated to **strategy drivers**, each implementing driver-specific **BackupStrategy** CRDs.
|
||||
|
||||
The core API:
|
||||
|
||||
* Orchestrates **when** backups happen and **where** they’re stored.
|
||||
* Tracks **what** backups exist and their status.
|
||||
* Defines contracts with drivers via shared resources (`BackupJob`, `Backup`, `RestoreJob`).
|
||||
|
||||
It does **not** implement the backup logic itself.
|
||||
|
||||
This document covers only the **core** API and its contracts with drivers, not driver implementations.
|
||||
|
||||
---
|
||||
|
||||
## 2. Goals and non-goals
|
||||
|
||||
### Goals
|
||||
|
||||
* Provide a **stable core API** for:
|
||||
|
||||
* Declaring **backup plans** per application.
|
||||
* Configuring **storage targets** (S3, in-cluster bucket, etc.).
|
||||
* Tracking **backup artifacts**.
|
||||
* Initiating and tracking **restores**.
|
||||
* Allow multiple **strategy drivers** to plug in, each supporting specific kinds of applications and strategies.
|
||||
* Let application/product authors implement backup for their kinds by:
|
||||
|
||||
* Creating **Plan** objects referencing a **driver-specific strategy**.
|
||||
* Not having to write a backup engine themselves.
|
||||
|
||||
### Non-goals
|
||||
|
||||
* Implement backup logic for any specific application or storage backend.
|
||||
* Define the internal structure of driver-specific strategy CRDs.
|
||||
* Handle tenant-facing UI/UX (that’s built on top of these APIs).
|
||||
|
||||
---
|
||||
|
||||
## 3. Architecture
|
||||
|
||||
High-level components:
|
||||
|
||||
* **Core backups controller(s)** (Cozystack-owned):
|
||||
|
||||
* Group: `backups.cozystack.io`
|
||||
* Own:
|
||||
|
||||
* `Plan`
|
||||
* `BackupJob`
|
||||
* `Backup`
|
||||
* `RestoreJob`
|
||||
* Responsibilities:
|
||||
|
||||
* Schedule backups based on `Plan`.
|
||||
* Create `BackupJob` objects when due.
|
||||
* Provide stable contracts for drivers to:
|
||||
|
||||
* Perform backups and create `Backup`s.
|
||||
* Perform restores based on `Backup`s.
|
||||
|
||||
* **Strategy drivers** (pluggable, possibly third-party):
|
||||
|
||||
* Their own API groups, e.g. `jobdriver.backups.cozystack.io`.
|
||||
* Own **strategy CRDs** (e.g. `JobBackupStrategy`).
|
||||
* Implement controllers that:
|
||||
|
||||
* Watch `BackupJob` / `RestoreJob`.
|
||||
* Match runs whose `strategyRef` GVK they support.
|
||||
* Execute backup/restore logic.
|
||||
* Create and update `Backup` and run statuses.
|
||||
|
||||
Strategy drivers and core communicate entirely via Kubernetes objects; there are no webhook/HTTP calls between them.
|
||||
|
||||
* **Storage drivers** (pluggable, possibly third-party):
|
||||
|
||||
* **TBD**
|
||||
|
||||
---
|
||||
|
||||
## 4. Core API resources
|
||||
|
||||
### 4.1 Plan
|
||||
|
||||
**Group/Kind**
|
||||
`backups.cozystack.io/v1alpha1, Kind=Plan`
|
||||
|
||||
**Purpose**
|
||||
Describe **when**, **how**, and **where** to back up a specific managed application.
|
||||
|
||||
**Key fields (spec)**
|
||||
|
||||
```go
|
||||
type PlanSpec struct {
|
||||
// Application to back up.
|
||||
ApplicationRef corev1.TypedLocalObjectReference `json:"applicationRef"`
|
||||
|
||||
// Where backups should be stored.
|
||||
StorageRef corev1.TypedLocalObjectReference `json:"storageRef"`
|
||||
|
||||
// Driver-specific BackupStrategy to use.
|
||||
StrategyRef corev1.TypedLocalObjectReference `json:"strategyRef"`
|
||||
|
||||
// When backups should run.
|
||||
Schedule PlanSchedule `json:"schedule"`
|
||||
}
|
||||
```
|
||||
|
||||
`PlanSchedule` (initially) supports only cron:
|
||||
|
||||
```go
|
||||
type PlanScheduleType string
|
||||
|
||||
const (
|
||||
PlanScheduleTypeEmpty PlanScheduleType = ""
|
||||
PlanScheduleTypeCron PlanScheduleType = "cron"
|
||||
)
|
||||
```
|
||||
|
||||
```go
|
||||
type PlanSchedule struct {
|
||||
// Type is the schedule type. Currently only "cron" is supported.
|
||||
// Defaults to "cron".
|
||||
Type PlanScheduleType `json:"type,omitempty"`
|
||||
|
||||
// Cron expression (required for cron type).
|
||||
Cron string `json:"cron,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
**Plan reconciliation contract**
|
||||
|
||||
Core Plan controller:
|
||||
|
||||
1. **Read schedule** from `spec.schedule` and compute the next fire time.
|
||||
2. When due:
|
||||
|
||||
* Create a `BackupJob` in the same namespace:
|
||||
|
||||
* `spec.planRef.name = plan.Name`
|
||||
* `spec.applicationRef = plan.spec.applicationRef`
|
||||
* `spec.storageRef = plan.spec.storageRef`
|
||||
* `spec.strategyRef = plan.spec.strategyRef`
|
||||
* `spec.triggeredBy = "Plan"`
|
||||
* Set `ownerReferences` so the `BackupJob` is owned by the `Plan`.
|
||||
|
||||
The Plan controller does **not**:
|
||||
|
||||
* Execute backups itself.
|
||||
* Modify driver resources or `Backup` objects.
|
||||
* Touch `BackupJob.spec` after creation.
|
||||
|
||||
---
|
||||
|
||||
### 4.2 Storage
|
||||
|
||||
**API Shape**
|
||||
|
||||
TBD
|
||||
|
||||
**Storage usage**
|
||||
|
||||
* `Plan` and `BackupJob` reference `Storage` via `TypedLocalObjectReference`.
|
||||
* Drivers read `Storage` to know how/where to store or read artifacts.
|
||||
* Core treats `Storage` spec as opaque; it does not directly talk to S3 or buckets.
|
||||
|
||||
---
|
||||
|
||||
### 4.3 BackupJob
|
||||
|
||||
**Group/Kind**
|
||||
`backups.cozystack.io/v1alpha1, Kind=BackupJob`
|
||||
|
||||
**Purpose**
|
||||
Represent a single **execution** of a backup operation, typically created when a `Plan` fires or when a user triggers an ad-hoc backup.
|
||||
|
||||
**Key fields (spec)**
|
||||
|
||||
```go
|
||||
type BackupJobSpec struct {
|
||||
// Plan that triggered this run, if any.
|
||||
PlanRef *corev1.LocalObjectReference `json:"planRef,omitempty"`
|
||||
|
||||
// Application to back up.
|
||||
ApplicationRef corev1.TypedLocalObjectReference `json:"applicationRef"`
|
||||
|
||||
// Storage to use.
|
||||
StorageRef corev1.TypedLocalObjectReference `json:"storageRef"`
|
||||
|
||||
// Driver-specific BackupStrategy to use.
|
||||
StrategyRef corev1.TypedLocalObjectReference `json:"strategyRef"`
|
||||
|
||||
// Informational: what triggered this run ("Plan", "Manual", etc.).
|
||||
TriggeredBy string `json:"triggeredBy,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
**Key fields (status)**
|
||||
|
||||
```go
|
||||
type BackupJobStatus struct {
|
||||
Phase BackupJobPhase `json:"phase,omitempty"`
|
||||
BackupRef *corev1.LocalObjectReference `json:"backupRef,omitempty"`
|
||||
StartedAt *metav1.Time `json:"startedAt,omitempty"`
|
||||
CompletedAt *metav1.Time `json:"completedAt,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
`BackupJobPhase` is one of: `Pending`, `Running`, `Succeeded`, `Failed`.
|
||||
|
||||
**BackupJob contract with drivers**
|
||||
|
||||
* Core **creates** `BackupJob` and must treat `spec` as immutable afterwards.
|
||||
* Each driver controller:
|
||||
|
||||
* Watches `BackupJob`.
|
||||
* Reconciles runs where `spec.strategyRef.apiGroup/kind` matches its **strategy type(s)**.
|
||||
* Driver responsibilities:
|
||||
|
||||
1. On first reconcile:
|
||||
|
||||
* Set `status.startedAt` if unset.
|
||||
* Set `status.phase = Running`.
|
||||
2. Resolve inputs:
|
||||
|
||||
* Read `Strategy` (driver-owned CRD), `Storage`, `Application`, optionally `Plan`.
|
||||
3. Execute backup logic (implementation-specific).
|
||||
4. On success:
|
||||
|
||||
* Create a `Backup` resource (see below).
|
||||
* Set `status.backupRef` to the created `Backup`.
|
||||
* Set `status.completedAt`.
|
||||
* Set `status.phase = Succeeded`.
|
||||
5. On failure:
|
||||
|
||||
* Set `status.completedAt`.
|
||||
* Set `status.phase = Failed`.
|
||||
* Set `status.message` and conditions.
|
||||
|
||||
Drivers must **not** modify `BackupJob.spec` or delete `BackupJob` themselves.
|
||||
|
||||
---
|
||||
|
||||
### 4.4 Backup
|
||||
|
||||
**Group/Kind**
|
||||
`backups.cozystack.io/v1alpha1, Kind=Backup`
|
||||
|
||||
**Purpose**
|
||||
Represent a single **backup artifact** for a given application, decoupled from a particular run. usable as a stable, listable “thing you can restore from”.
|
||||
|
||||
**Key fields (spec)**
|
||||
|
||||
```go
|
||||
type BackupSpec struct {
|
||||
ApplicationRef corev1.TypedLocalObjectReference `json:"applicationRef"`
|
||||
PlanRef *corev1.LocalObjectReference `json:"planRef,omitempty"`
|
||||
StorageRef corev1.TypedLocalObjectReference `json:"storageRef"`
|
||||
StrategyRef corev1.TypedLocalObjectReference `json:"strategyRef"`
|
||||
TakenAt metav1.Time `json:"takenAt"`
|
||||
DriverMetadata map[string]string `json:"driverMetadata,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
**Key fields (status)**
|
||||
|
||||
```go
|
||||
type BackupStatus struct {
|
||||
Phase BackupPhase `json:"phase,omitempty"` // Pending, Ready, Failed, etc.
|
||||
Artifact *BackupArtifact `json:"artifact,omitempty"`
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
`BackupArtifact` describes the artifact (URI, size, checksum).
|
||||
|
||||
**Backup contract with drivers**
|
||||
|
||||
* On successful completion of a `BackupJob`, the **driver**:
|
||||
|
||||
* Creates a `Backup` in the same namespace (typically owned by the `BackupJob`).
|
||||
* Populates `spec` fields with:
|
||||
|
||||
* The application, storage, strategy references.
|
||||
* `takenAt`.
|
||||
* Optional `driverMetadata`.
|
||||
* Sets `status` with:
|
||||
|
||||
* `phase = Ready` (or equivalent when fully usable).
|
||||
* `artifact` describing the stored object.
|
||||
* Core:
|
||||
|
||||
* Treats `Backup` spec as mostly immutable and opaque.
|
||||
* Uses it to:
|
||||
|
||||
* List backups for a given application/plan.
|
||||
* Anchor `RestoreJob` operations.
|
||||
* Implement higher-level policies (retention) if needed.
|
||||
|
||||
---
|
||||
|
||||
### 4.5 RestoreJob
|
||||
|
||||
**Group/Kind**
|
||||
`backups.cozystack.io/v1alpha1, Kind=RestoreJob`
|
||||
|
||||
**Purpose**
|
||||
Represent a single **restore operation** from a `Backup`, either back into the same application or into a new target application.
|
||||
|
||||
**Key fields (spec)**
|
||||
|
||||
```go
|
||||
type RestoreJobSpec struct {
|
||||
// Backup to restore from.
|
||||
BackupRef corev1.LocalObjectReference `json:"backupRef"`
|
||||
|
||||
// Target application; if omitted, drivers SHOULD restore into
|
||||
// backup.spec.applicationRef.
|
||||
TargetApplicationRef *corev1.TypedLocalObjectReference `json:"targetApplicationRef,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
**Key fields (status)**
|
||||
|
||||
```go
|
||||
type RestoreJobStatus struct {
|
||||
Phase RestoreJobPhase `json:"phase,omitempty"` // Pending, Running, Succeeded, Failed
|
||||
StartedAt *metav1.Time `json:"startedAt,omitempty"`
|
||||
CompletedAt *metav1.Time `json:"completedAt,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
**RestoreJob contract with drivers**
|
||||
|
||||
* RestoreJob is created either manually or by core.
|
||||
* Driver controller:
|
||||
|
||||
1. Watches `RestoreJob`.
|
||||
2. On reconcile:
|
||||
|
||||
* Fetches the referenced `Backup`.
|
||||
* Determines effective:
|
||||
|
||||
* **Strategy**: `backup.spec.strategyRef`.
|
||||
* **Storage**: `backup.spec.storageRef`.
|
||||
* **Target application**: `spec.targetApplicationRef` or `backup.spec.applicationRef`.
|
||||
* If effective strategy’s GVK is one of its supported strategy types → driver is responsible.
|
||||
3. Behaviour:
|
||||
|
||||
* On first reconcile, set `status.startedAt` and `phase = Running`.
|
||||
* Resolve `Backup`, `Storage`, `Strategy`, target application.
|
||||
* Execute restore logic (implementation-specific).
|
||||
* On success:
|
||||
|
||||
* Set `status.completedAt`.
|
||||
* Set `status.phase = Succeeded`.
|
||||
* On failure:
|
||||
|
||||
* Set `status.completedAt`.
|
||||
* Set `status.phase = Failed`.
|
||||
* Set `status.message` and conditions.
|
||||
|
||||
Drivers must not modify `RestoreJob.spec` or delete `RestoreJob`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Strategy drivers (high-level)
|
||||
|
||||
Strategy drivers are separate controllers that:
|
||||
|
||||
* Define their own **strategy CRDs** (e.g. `JobBackupStrategy`) in their own API groups:
|
||||
|
||||
* e.g. `jobdriver.backups.cozystack.io/v1alpha1, Kind=JobBackupStrategy`
|
||||
* Implement the **BackupJob contract**:
|
||||
|
||||
* Watch `BackupJob`.
|
||||
* Filter by `spec.strategyRef.apiGroup/kind`.
|
||||
* Execute backup logic.
|
||||
* Create/update `Backup`.
|
||||
* Implement the **RestoreJob contract**:
|
||||
|
||||
* Watch `RestoreJob`.
|
||||
* Resolve `Backup`, then effective `strategyRef`.
|
||||
* Filter by effective strategy GVK.
|
||||
* Execute restore logic.
|
||||
|
||||
The core backups API **does not** dictate:
|
||||
|
||||
* The fields and structure of driver strategy specs.
|
||||
* How drivers implement backup/restore internally (Jobs, snapshots, native operator CRDs, etc.).
|
||||
|
||||
Drivers are interchangeable as long as they respect:
|
||||
|
||||
* The `BackupJob` and `RestoreJob` contracts.
|
||||
* The shapes and semantics of `Backup` objects.
|
||||
|
||||
---
|
||||
|
||||
## 6. Summary
|
||||
|
||||
The Cozystack backups core API:
|
||||
|
||||
* Uses a single group, `backups.cozystack.io`, for all core CRDs.
|
||||
* Cleanly separates:
|
||||
|
||||
* **When & where** (Plan + Storage) – core-owned.
|
||||
* **What backup artifacts exist** (Backup) – driver-created but cluster-visible.
|
||||
* **Execution lifecycle** (BackupJob, RestoreJob) – shared contract boundary.
|
||||
* Allows multiple strategy drivers to implement backup/restore logic without entangling their implementation with the core API.
|
||||
|
||||
118
api/backups/v1alpha1/backup_types.go
Normal file
118
api/backups/v1alpha1/backup_types.go
Normal file
@@ -0,0 +1,118 @@
|
||||
// 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,
|
||||
&Backup{},
|
||||
&BackupList{},
|
||||
)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// BackupPhase represents the lifecycle phase of a Backup.
|
||||
type BackupPhase string
|
||||
|
||||
const (
|
||||
BackupPhaseEmpty BackupPhase = ""
|
||||
BackupPhasePending BackupPhase = "Pending"
|
||||
BackupPhaseReady BackupPhase = "Ready"
|
||||
BackupPhaseFailed BackupPhase = "Failed"
|
||||
)
|
||||
|
||||
// BackupArtifact describes the stored backup object (tarball, snapshot, etc.).
|
||||
type BackupArtifact struct {
|
||||
// URI is a driver-/storage-specific URI pointing to the backup artifact.
|
||||
// For example: s3://bucket/prefix/file.tar.gz
|
||||
URI string `json:"uri"`
|
||||
|
||||
// SizeBytes is the size of the artifact in bytes, if known.
|
||||
// +optional
|
||||
SizeBytes int64 `json:"sizeBytes,omitempty"`
|
||||
|
||||
// Checksum is the checksum of the artifact, if computed.
|
||||
// For example: "sha256:<hex>".
|
||||
// +optional
|
||||
Checksum string `json:"checksum,omitempty"`
|
||||
}
|
||||
|
||||
// BackupSpec describes an immutable backup artifact produced by a BackupJob.
|
||||
type BackupSpec struct {
|
||||
// ApplicationRef refers to the application that was backed up.
|
||||
ApplicationRef corev1.TypedLocalObjectReference `json:"applicationRef"`
|
||||
|
||||
// PlanRef refers to the Plan that produced this backup, if any.
|
||||
// For manually triggered backups, this can be omitted.
|
||||
// +optional
|
||||
PlanRef *corev1.LocalObjectReference `json:"planRef,omitempty"`
|
||||
|
||||
// StorageRef refers to the Storage object that describes where the backup
|
||||
// artifact is stored.
|
||||
StorageRef corev1.TypedLocalObjectReference `json:"storageRef"`
|
||||
|
||||
// StrategyRef refers to the driver-specific BackupStrategy that was used
|
||||
// to create this backup. This allows the driver to later perform restores.
|
||||
StrategyRef corev1.TypedLocalObjectReference `json:"strategyRef"`
|
||||
|
||||
// TakenAt is the time at which the backup was taken (as reported by the
|
||||
// driver). It may differ slightly from metadata.creationTimestamp.
|
||||
TakenAt metav1.Time `json:"takenAt"`
|
||||
|
||||
// DriverMetadata holds driver-specific, opaque metadata associated with
|
||||
// this backup (for example snapshot IDs, schema versions, etc.).
|
||||
// This data is not interpreted by the core backup controllers.
|
||||
// +optional
|
||||
DriverMetadata map[string]string `json:"driverMetadata,omitempty"`
|
||||
}
|
||||
|
||||
// BackupStatus represents the observed state of a Backup.
|
||||
type BackupStatus struct {
|
||||
// Phase is a simple, high-level summary of the backup's state.
|
||||
// Typical values are: Pending, Ready, Failed.
|
||||
// +optional
|
||||
Phase BackupPhase `json:"phase,omitempty"`
|
||||
|
||||
// Artifact describes the stored backup object, if available.
|
||||
// +optional
|
||||
Artifact *BackupArtifact `json:"artifact,omitempty"`
|
||||
|
||||
// Conditions represents the latest available observations of a Backup's state.
|
||||
// +optional
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||
}
|
||||
|
||||
// The field indexing on applicationRef will be needed later to display per-app backup resources.
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:selectablefield:JSONPath=`.spec.applicationRef.apiGroup`
|
||||
// +kubebuilder:selectablefield:JSONPath=`.spec.applicationRef.kind`
|
||||
// +kubebuilder:selectablefield:JSONPath=`.spec.applicationRef.name`
|
||||
|
||||
// Backup represents a single backup artifact for a given application.
|
||||
type Backup struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec BackupSpec `json:"spec,omitempty"`
|
||||
Status BackupStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// BackupList contains a list of Backups.
|
||||
type BackupList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []Backup `json:"items"`
|
||||
}
|
||||
109
api/backups/v1alpha1/backupjob_types.go
Normal file
109
api/backups/v1alpha1/backupjob_types.go
Normal file
@@ -0,0 +1,109 @@
|
||||
// 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,
|
||||
&BackupJob{},
|
||||
&BackupJobList{},
|
||||
)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// BackupJobPhase represents the lifecycle phase of a BackupJob.
|
||||
type BackupJobPhase string
|
||||
|
||||
const (
|
||||
BackupJobPhaseEmpty BackupJobPhase = ""
|
||||
BackupJobPhasePending BackupJobPhase = "Pending"
|
||||
BackupJobPhaseRunning BackupJobPhase = "Running"
|
||||
BackupJobPhaseSucceeded BackupJobPhase = "Succeeded"
|
||||
BackupJobPhaseFailed BackupJobPhase = "Failed"
|
||||
)
|
||||
|
||||
// BackupJobSpec describes the execution of a single backup operation.
|
||||
type BackupJobSpec struct {
|
||||
// PlanRef refers to the Plan that requested this backup run.
|
||||
// For ad-hoc/manual backups, this can be omitted.
|
||||
// +optional
|
||||
PlanRef *corev1.LocalObjectReference `json:"planRef,omitempty"`
|
||||
|
||||
// ApplicationRef holds a reference to the managed application whose state
|
||||
// is being backed up.
|
||||
ApplicationRef corev1.TypedLocalObjectReference `json:"applicationRef"`
|
||||
|
||||
// StorageRef holds a reference to the Storage object that describes where
|
||||
// the backup will be stored.
|
||||
StorageRef corev1.TypedLocalObjectReference `json:"storageRef"`
|
||||
|
||||
// StrategyRef holds a reference to the driver-specific BackupStrategy object
|
||||
// that describes how the backup should be created.
|
||||
StrategyRef corev1.TypedLocalObjectReference `json:"strategyRef"`
|
||||
}
|
||||
|
||||
// BackupJobStatus represents the observed state of a BackupJob.
|
||||
type BackupJobStatus struct {
|
||||
// Phase is a high-level summary of the run's state.
|
||||
// Typical values: Pending, Running, Succeeded, Failed.
|
||||
// +optional
|
||||
Phase BackupJobPhase `json:"phase,omitempty"`
|
||||
|
||||
// BackupRef refers to the Backup object created by this run, if any.
|
||||
// +optional
|
||||
BackupRef *corev1.LocalObjectReference `json:"backupRef,omitempty"`
|
||||
|
||||
// StartedAt is the time at which the backup run started.
|
||||
// +optional
|
||||
StartedAt *metav1.Time `json:"startedAt,omitempty"`
|
||||
|
||||
// CompletedAt is the time at which the backup run completed (successfully
|
||||
// or otherwise).
|
||||
// +optional
|
||||
CompletedAt *metav1.Time `json:"completedAt,omitempty"`
|
||||
|
||||
// Message is a human-readable message indicating details about why the
|
||||
// backup run is in its current phase, if any.
|
||||
// +optional
|
||||
Message string `json:"message,omitempty"`
|
||||
|
||||
// Conditions represents the latest available observations of a BackupJob's state.
|
||||
// +optional
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||
}
|
||||
|
||||
// The field indexing on applicationRef will be needed later to display per-app backup resources.
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:selectablefield:JSONPath=`.spec.applicationRef.apiGroup`
|
||||
// +kubebuilder:selectablefield:JSONPath=`.spec.applicationRef.kind`
|
||||
// +kubebuilder:selectablefield:JSONPath=`.spec.applicationRef.name`
|
||||
|
||||
// BackupJob represents a single execution of a backup.
|
||||
// It is typically created by a Plan controller when a schedule fires.
|
||||
type BackupJob struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec BackupJobSpec `json:"spec,omitempty"`
|
||||
Status BackupJobStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// BackupJobList contains a list of BackupJobs.
|
||||
type BackupJobList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []BackupJob `json:"items"`
|
||||
}
|
||||
37
api/backups/v1alpha1/groupversion_info.go
Normal file
37
api/backups/v1alpha1/groupversion_info.go
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
Copyright 2025.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package v1alpha1 contains API Schema definitions for the v1alpha1 API group.
|
||||
// +kubebuilder:object:generate=true
|
||||
// +groupName=backups.cozystack.io
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
var (
|
||||
GroupVersion = schema.GroupVersion{Group: "backups.cozystack.io", Version: "v1alpha1"}
|
||||
SchemeBuilder = runtime.NewSchemeBuilder(addGroupVersion)
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
func addGroupVersion(scheme *runtime.Scheme) error {
|
||||
metav1.AddToGroupVersion(scheme, GroupVersion)
|
||||
return nil
|
||||
}
|
||||
98
api/backups/v1alpha1/plan_types.go
Normal file
98
api/backups/v1alpha1/plan_types.go
Normal file
@@ -0,0 +1,98 @@
|
||||
// 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,
|
||||
&Plan{},
|
||||
&PlanList{},
|
||||
)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
type PlanScheduleType string
|
||||
|
||||
const (
|
||||
PlanScheduleTypeEmpty PlanScheduleType = ""
|
||||
PlanScheduleTypeCron PlanScheduleType = "cron"
|
||||
)
|
||||
|
||||
// Condtions
|
||||
const (
|
||||
PlanConditionError = "Error"
|
||||
)
|
||||
|
||||
// The field indexing on applicationRef will be needed later to display per-app backup resources.
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:selectablefield:JSONPath=`.spec.applicationRef.apiGroup`
|
||||
// +kubebuilder:selectablefield:JSONPath=`.spec.applicationRef.kind`
|
||||
// +kubebuilder:selectablefield:JSONPath=`.spec.applicationRef.name`
|
||||
|
||||
// Plan describes the schedule, method and storage location for the
|
||||
// backup of a given target application.
|
||||
type Plan struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec PlanSpec `json:"spec,omitempty"`
|
||||
Status PlanStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// PlanList contains a list of backup Plans.
|
||||
type PlanList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []Plan `json:"items"`
|
||||
}
|
||||
|
||||
// PlanSpec references the storage, the strategy, the application to be
|
||||
// backed up and specifies the timetable on which the backups will run.
|
||||
type PlanSpec struct {
|
||||
// ApplicationRef holds a reference to the managed application,
|
||||
// whose state and configuration must be backed up.
|
||||
ApplicationRef corev1.TypedLocalObjectReference `json:"applicationRef"`
|
||||
|
||||
// StorageRef holds a reference to the Storage object that
|
||||
// describes the location where the backup will be stored.
|
||||
StorageRef corev1.TypedLocalObjectReference `json:"storageRef"`
|
||||
|
||||
// StrategyRef holds a reference to the Strategy object that
|
||||
// describes, how a backup copy is to be created.
|
||||
StrategyRef corev1.TypedLocalObjectReference `json:"strategyRef"`
|
||||
|
||||
// Schedule specifies when backup copies are created.
|
||||
Schedule PlanSchedule `json:"schedule"`
|
||||
}
|
||||
|
||||
// PlanSchedule specifies when backup copies are created.
|
||||
type PlanSchedule struct {
|
||||
// Type is the type of schedule specification. Supported values are
|
||||
// [`cron`]. If omitted, defaults to `cron`.
|
||||
// +optional
|
||||
Type PlanScheduleType `json:"type,omitempty"`
|
||||
|
||||
// Cron contains the cron spec for scheduling backups. Must be
|
||||
// specified if the schedule type is `cron`. Since only `cron` is
|
||||
// supported, omitting this field is not allowed.
|
||||
// +optional
|
||||
Cron string `json:"cron,omitempty"`
|
||||
}
|
||||
|
||||
type PlanStatus struct {
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||
}
|
||||
91
api/backups/v1alpha1/restorejob_types.go
Normal file
91
api/backups/v1alpha1/restorejob_types.go
Normal file
@@ -0,0 +1,91 @@
|
||||
// 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,
|
||||
&RestoreJob{},
|
||||
&RestoreJobList{},
|
||||
)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// RestoreJobPhase represents the lifecycle phase of a RestoreJob.
|
||||
type RestoreJobPhase string
|
||||
|
||||
const (
|
||||
RestoreJobPhaseEmpty RestoreJobPhase = ""
|
||||
RestoreJobPhasePending RestoreJobPhase = "Pending"
|
||||
RestoreJobPhaseRunning RestoreJobPhase = "Running"
|
||||
RestoreJobPhaseSucceeded RestoreJobPhase = "Succeeded"
|
||||
RestoreJobPhaseFailed RestoreJobPhase = "Failed"
|
||||
)
|
||||
|
||||
// RestoreJobSpec describes the execution of a single restore operation.
|
||||
type RestoreJobSpec struct {
|
||||
// BackupRef refers to the Backup that should be restored.
|
||||
BackupRef corev1.LocalObjectReference `json:"backupRef"`
|
||||
|
||||
// TargetApplicationRef refers to the application into which the backup
|
||||
// should be restored. If omitted, the driver SHOULD restore into the same
|
||||
// application as referenced by backup.spec.applicationRef.
|
||||
// +optional
|
||||
TargetApplicationRef *corev1.TypedLocalObjectReference `json:"targetApplicationRef,omitempty"`
|
||||
}
|
||||
|
||||
// RestoreJobStatus represents the observed state of a RestoreJob.
|
||||
type RestoreJobStatus struct {
|
||||
// Phase is a high-level summary of the run's state.
|
||||
// Typical values: Pending, Running, Succeeded, Failed.
|
||||
// +optional
|
||||
Phase RestoreJobPhase `json:"phase,omitempty"`
|
||||
|
||||
// StartedAt is the time at which the restore run started.
|
||||
// +optional
|
||||
StartedAt *metav1.Time `json:"startedAt,omitempty"`
|
||||
|
||||
// CompletedAt is the time at which the restore run completed (successfully
|
||||
// or otherwise).
|
||||
// +optional
|
||||
CompletedAt *metav1.Time `json:"completedAt,omitempty"`
|
||||
|
||||
// Message is a human-readable message indicating details about why the
|
||||
// restore run is in its current phase, if any.
|
||||
// +optional
|
||||
Message string `json:"message,omitempty"`
|
||||
|
||||
// Conditions represents the latest available observations of a RestoreJob's state.
|
||||
// +optional
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// RestoreJob represents a single execution of a restore from a Backup.
|
||||
type RestoreJob struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec RestoreJobSpec `json:"spec,omitempty"`
|
||||
Status RestoreJobStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// RestoreJobList contains a list of RestoreJobs.
|
||||
type RestoreJobList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []RestoreJob `json:"items"`
|
||||
}
|
||||
501
api/backups/v1alpha1/zz_generated.deepcopy.go
Normal file
501
api/backups/v1alpha1/zz_generated.deepcopy.go
Normal file
@@ -0,0 +1,501 @@
|
||||
//go:build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 2025 The Cozystack Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by controller-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Backup) DeepCopyInto(out *Backup) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Backup.
|
||||
func (in *Backup) DeepCopy() *Backup {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Backup)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *Backup) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *BackupArtifact) DeepCopyInto(out *BackupArtifact) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupArtifact.
|
||||
func (in *BackupArtifact) DeepCopy() *BackupArtifact {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(BackupArtifact)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *BackupJob) DeepCopyInto(out *BackupJob) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupJob.
|
||||
func (in *BackupJob) DeepCopy() *BackupJob {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(BackupJob)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *BackupJob) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *BackupJobList) DeepCopyInto(out *BackupJobList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]BackupJob, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupJobList.
|
||||
func (in *BackupJobList) DeepCopy() *BackupJobList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(BackupJobList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *BackupJobList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *BackupJobSpec) DeepCopyInto(out *BackupJobSpec) {
|
||||
*out = *in
|
||||
if in.PlanRef != nil {
|
||||
in, out := &in.PlanRef, &out.PlanRef
|
||||
*out = new(v1.LocalObjectReference)
|
||||
**out = **in
|
||||
}
|
||||
in.ApplicationRef.DeepCopyInto(&out.ApplicationRef)
|
||||
in.StorageRef.DeepCopyInto(&out.StorageRef)
|
||||
in.StrategyRef.DeepCopyInto(&out.StrategyRef)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupJobSpec.
|
||||
func (in *BackupJobSpec) DeepCopy() *BackupJobSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(BackupJobSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *BackupJobStatus) DeepCopyInto(out *BackupJobStatus) {
|
||||
*out = *in
|
||||
if in.BackupRef != nil {
|
||||
in, out := &in.BackupRef, &out.BackupRef
|
||||
*out = new(v1.LocalObjectReference)
|
||||
**out = **in
|
||||
}
|
||||
if in.StartedAt != nil {
|
||||
in, out := &in.StartedAt, &out.StartedAt
|
||||
*out = (*in).DeepCopy()
|
||||
}
|
||||
if in.CompletedAt != nil {
|
||||
in, out := &in.CompletedAt, &out.CompletedAt
|
||||
*out = (*in).DeepCopy()
|
||||
}
|
||||
if in.Conditions != nil {
|
||||
in, out := &in.Conditions, &out.Conditions
|
||||
*out = make([]metav1.Condition, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupJobStatus.
|
||||
func (in *BackupJobStatus) DeepCopy() *BackupJobStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(BackupJobStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *BackupList) DeepCopyInto(out *BackupList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]Backup, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupList.
|
||||
func (in *BackupList) DeepCopy() *BackupList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(BackupList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *BackupList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *BackupSpec) DeepCopyInto(out *BackupSpec) {
|
||||
*out = *in
|
||||
in.ApplicationRef.DeepCopyInto(&out.ApplicationRef)
|
||||
if in.PlanRef != nil {
|
||||
in, out := &in.PlanRef, &out.PlanRef
|
||||
*out = new(v1.LocalObjectReference)
|
||||
**out = **in
|
||||
}
|
||||
in.StorageRef.DeepCopyInto(&out.StorageRef)
|
||||
in.StrategyRef.DeepCopyInto(&out.StrategyRef)
|
||||
in.TakenAt.DeepCopyInto(&out.TakenAt)
|
||||
if in.DriverMetadata != nil {
|
||||
in, out := &in.DriverMetadata, &out.DriverMetadata
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupSpec.
|
||||
func (in *BackupSpec) DeepCopy() *BackupSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(BackupSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *BackupStatus) DeepCopyInto(out *BackupStatus) {
|
||||
*out = *in
|
||||
if in.Artifact != nil {
|
||||
in, out := &in.Artifact, &out.Artifact
|
||||
*out = new(BackupArtifact)
|
||||
**out = **in
|
||||
}
|
||||
if in.Conditions != nil {
|
||||
in, out := &in.Conditions, &out.Conditions
|
||||
*out = make([]metav1.Condition, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupStatus.
|
||||
func (in *BackupStatus) DeepCopy() *BackupStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(BackupStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Plan) DeepCopyInto(out *Plan) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Plan.
|
||||
func (in *Plan) DeepCopy() *Plan {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Plan)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *Plan) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PlanList) DeepCopyInto(out *PlanList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]Plan, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlanList.
|
||||
func (in *PlanList) DeepCopy() *PlanList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PlanList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *PlanList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PlanSchedule) DeepCopyInto(out *PlanSchedule) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlanSchedule.
|
||||
func (in *PlanSchedule) DeepCopy() *PlanSchedule {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PlanSchedule)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PlanSpec) DeepCopyInto(out *PlanSpec) {
|
||||
*out = *in
|
||||
in.ApplicationRef.DeepCopyInto(&out.ApplicationRef)
|
||||
in.StorageRef.DeepCopyInto(&out.StorageRef)
|
||||
in.StrategyRef.DeepCopyInto(&out.StrategyRef)
|
||||
out.Schedule = in.Schedule
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlanSpec.
|
||||
func (in *PlanSpec) DeepCopy() *PlanSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PlanSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PlanStatus) DeepCopyInto(out *PlanStatus) {
|
||||
*out = *in
|
||||
if in.Conditions != nil {
|
||||
in, out := &in.Conditions, &out.Conditions
|
||||
*out = make([]metav1.Condition, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlanStatus.
|
||||
func (in *PlanStatus) DeepCopy() *PlanStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PlanStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *RestoreJob) DeepCopyInto(out *RestoreJob) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RestoreJob.
|
||||
func (in *RestoreJob) DeepCopy() *RestoreJob {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(RestoreJob)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *RestoreJob) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *RestoreJobList) DeepCopyInto(out *RestoreJobList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]RestoreJob, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RestoreJobList.
|
||||
func (in *RestoreJobList) DeepCopy() *RestoreJobList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(RestoreJobList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *RestoreJobList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *RestoreJobSpec) DeepCopyInto(out *RestoreJobSpec) {
|
||||
*out = *in
|
||||
out.BackupRef = in.BackupRef
|
||||
if in.TargetApplicationRef != nil {
|
||||
in, out := &in.TargetApplicationRef, &out.TargetApplicationRef
|
||||
*out = new(v1.TypedLocalObjectReference)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RestoreJobSpec.
|
||||
func (in *RestoreJobSpec) DeepCopy() *RestoreJobSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(RestoreJobSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *RestoreJobStatus) DeepCopyInto(out *RestoreJobStatus) {
|
||||
*out = *in
|
||||
if in.StartedAt != nil {
|
||||
in, out := &in.StartedAt, &out.StartedAt
|
||||
*out = (*in).DeepCopy()
|
||||
}
|
||||
if in.CompletedAt != nil {
|
||||
in, out := &in.CompletedAt, &out.CompletedAt
|
||||
*out = (*in).DeepCopy()
|
||||
}
|
||||
if in.Conditions != nil {
|
||||
in, out := &in.Conditions, &out.Conditions
|
||||
*out = make([]metav1.Condition, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RestoreJobStatus.
|
||||
func (in *RestoreJobStatus) DeepCopy() *RestoreJobStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(RestoreJobStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
174
cmd/backup-controller/main.go
Normal file
174
cmd/backup-controller/main.go
Normal file
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
Copyright 2025.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"flag"
|
||||
"os"
|
||||
|
||||
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
|
||||
// to ensure that exec-entrypoint and run can make use of them.
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/healthz"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||
"sigs.k8s.io/controller-runtime/pkg/metrics/filters"
|
||||
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook"
|
||||
|
||||
backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1"
|
||||
"github.com/cozystack/cozystack/internal/backupcontroller"
|
||||
// +kubebuilder:scaffold:imports
|
||||
)
|
||||
|
||||
var (
|
||||
scheme = runtime.NewScheme()
|
||||
setupLog = ctrl.Log.WithName("setup")
|
||||
)
|
||||
|
||||
func init() {
|
||||
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
|
||||
|
||||
utilruntime.Must(backupsv1alpha1.AddToScheme(scheme))
|
||||
// +kubebuilder:scaffold:scheme
|
||||
}
|
||||
|
||||
func main() {
|
||||
var metricsAddr string
|
||||
var enableLeaderElection bool
|
||||
var probeAddr string
|
||||
var secureMetrics bool
|
||||
var enableHTTP2 bool
|
||||
var tlsOpts []func(*tls.Config)
|
||||
flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+
|
||||
"Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.")
|
||||
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
|
||||
flag.BoolVar(&enableLeaderElection, "leader-elect", false,
|
||||
"Enable leader election for controller manager. "+
|
||||
"Enabling this will ensure there is only one active controller manager.")
|
||||
flag.BoolVar(&secureMetrics, "metrics-secure", true,
|
||||
"If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.")
|
||||
flag.BoolVar(&enableHTTP2, "enable-http2", false,
|
||||
"If set, HTTP/2 will be enabled for the metrics and webhook servers")
|
||||
opts := zap.Options{
|
||||
Development: false,
|
||||
}
|
||||
opts.BindFlags(flag.CommandLine)
|
||||
flag.Parse()
|
||||
|
||||
ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
|
||||
|
||||
// if the enable-http2 flag is false (the default), http/2 should be disabled
|
||||
// due to its vulnerabilities. More specifically, disabling http/2 will
|
||||
// prevent from being vulnerable to the HTTP/2 Stream Cancellation and
|
||||
// Rapid Reset CVEs. For more information see:
|
||||
// - https://github.com/advisories/GHSA-qppj-fm5r-hxr3
|
||||
// - https://github.com/advisories/GHSA-4374-p667-p6c8
|
||||
disableHTTP2 := func(c *tls.Config) {
|
||||
setupLog.Info("disabling http/2")
|
||||
c.NextProtos = []string{"http/1.1"}
|
||||
}
|
||||
|
||||
if !enableHTTP2 {
|
||||
tlsOpts = append(tlsOpts, disableHTTP2)
|
||||
}
|
||||
|
||||
webhookServer := webhook.NewServer(webhook.Options{
|
||||
TLSOpts: tlsOpts,
|
||||
})
|
||||
|
||||
// Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server.
|
||||
// More info:
|
||||
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.19.1/pkg/metrics/server
|
||||
// - https://book.kubebuilder.io/reference/metrics.html
|
||||
metricsServerOptions := metricsserver.Options{
|
||||
BindAddress: metricsAddr,
|
||||
SecureServing: secureMetrics,
|
||||
TLSOpts: tlsOpts,
|
||||
}
|
||||
|
||||
if secureMetrics {
|
||||
// FilterProvider is used to protect the metrics endpoint with authn/authz.
|
||||
// These configurations ensure that only authorized users and service accounts
|
||||
// can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info:
|
||||
// https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.19.1/pkg/metrics/filters#WithAuthenticationAndAuthorization
|
||||
metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization
|
||||
|
||||
// TODO(user): If CertDir, CertName, and KeyName are not specified, controller-runtime will automatically
|
||||
// generate self-signed certificates for the metrics server. While convenient for development and testing,
|
||||
// this setup is not recommended for production.
|
||||
}
|
||||
|
||||
// Configure rate limiting for the Kubernetes client
|
||||
config := ctrl.GetConfigOrDie()
|
||||
config.QPS = 50.0 // Increased from default 5.0
|
||||
config.Burst = 100 // Increased from default 10
|
||||
|
||||
mgr, err := ctrl.NewManager(config, ctrl.Options{
|
||||
Scheme: scheme,
|
||||
Metrics: metricsServerOptions,
|
||||
WebhookServer: webhookServer,
|
||||
HealthProbeBindAddress: probeAddr,
|
||||
LeaderElection: enableLeaderElection,
|
||||
LeaderElectionID: "core.backups.cozystack.io",
|
||||
// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily
|
||||
// when the Manager ends. This requires the binary to immediately end when the
|
||||
// Manager is stopped, otherwise, this setting is unsafe. Setting this significantly
|
||||
// speeds up voluntary leader transitions as the new leader don't have to wait
|
||||
// LeaseDuration time first.
|
||||
//
|
||||
// In the default scaffold provided, the program ends immediately after
|
||||
// the manager stops, so would be fine to enable this option. However,
|
||||
// if you are doing or is intended to do any operation such as perform cleanups
|
||||
// after the manager stops then its usage might be unsafe.
|
||||
// LeaderElectionReleaseOnCancel: true,
|
||||
})
|
||||
if err != nil {
|
||||
setupLog.Error(err, "unable to start manager")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err = (&backupcontroller.PlanReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
}).SetupWithManager(mgr); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "Plan")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// +kubebuilder:scaffold:builder
|
||||
|
||||
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
|
||||
setupLog.Error(err, "unable to set up health check")
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
|
||||
setupLog.Error(err, "unable to set up ready check")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
setupLog.Info("starting manager")
|
||||
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
|
||||
setupLog.Error(err, "problem running manager")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
174
cmd/backupstrategy-controller/main.go
Normal file
174
cmd/backupstrategy-controller/main.go
Normal file
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
Copyright 2025.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"flag"
|
||||
"os"
|
||||
|
||||
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
|
||||
// to ensure that exec-entrypoint and run can make use of them.
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/healthz"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||
"sigs.k8s.io/controller-runtime/pkg/metrics/filters"
|
||||
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook"
|
||||
|
||||
backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1"
|
||||
"github.com/cozystack/cozystack/internal/backupcontroller"
|
||||
// +kubebuilder:scaffold:imports
|
||||
)
|
||||
|
||||
var (
|
||||
scheme = runtime.NewScheme()
|
||||
setupLog = ctrl.Log.WithName("setup")
|
||||
)
|
||||
|
||||
func init() {
|
||||
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
|
||||
|
||||
utilruntime.Must(backupsv1alpha1.AddToScheme(scheme))
|
||||
// +kubebuilder:scaffold:scheme
|
||||
}
|
||||
|
||||
func main() {
|
||||
var metricsAddr string
|
||||
var enableLeaderElection bool
|
||||
var probeAddr string
|
||||
var secureMetrics bool
|
||||
var enableHTTP2 bool
|
||||
var tlsOpts []func(*tls.Config)
|
||||
flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+
|
||||
"Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.")
|
||||
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
|
||||
flag.BoolVar(&enableLeaderElection, "leader-elect", false,
|
||||
"Enable leader election for controller manager. "+
|
||||
"Enabling this will ensure there is only one active controller manager.")
|
||||
flag.BoolVar(&secureMetrics, "metrics-secure", true,
|
||||
"If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.")
|
||||
flag.BoolVar(&enableHTTP2, "enable-http2", false,
|
||||
"If set, HTTP/2 will be enabled for the metrics and webhook servers")
|
||||
opts := zap.Options{
|
||||
Development: false,
|
||||
}
|
||||
opts.BindFlags(flag.CommandLine)
|
||||
flag.Parse()
|
||||
|
||||
ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
|
||||
|
||||
// if the enable-http2 flag is false (the default), http/2 should be disabled
|
||||
// due to its vulnerabilities. More specifically, disabling http/2 will
|
||||
// prevent from being vulnerable to the HTTP/2 Stream Cancellation and
|
||||
// Rapid Reset CVEs. For more information see:
|
||||
// - https://github.com/advisories/GHSA-qppj-fm5r-hxr3
|
||||
// - https://github.com/advisories/GHSA-4374-p667-p6c8
|
||||
disableHTTP2 := func(c *tls.Config) {
|
||||
setupLog.Info("disabling http/2")
|
||||
c.NextProtos = []string{"http/1.1"}
|
||||
}
|
||||
|
||||
if !enableHTTP2 {
|
||||
tlsOpts = append(tlsOpts, disableHTTP2)
|
||||
}
|
||||
|
||||
webhookServer := webhook.NewServer(webhook.Options{
|
||||
TLSOpts: tlsOpts,
|
||||
})
|
||||
|
||||
// Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server.
|
||||
// More info:
|
||||
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.19.1/pkg/metrics/server
|
||||
// - https://book.kubebuilder.io/reference/metrics.html
|
||||
metricsServerOptions := metricsserver.Options{
|
||||
BindAddress: metricsAddr,
|
||||
SecureServing: secureMetrics,
|
||||
TLSOpts: tlsOpts,
|
||||
}
|
||||
|
||||
if secureMetrics {
|
||||
// FilterProvider is used to protect the metrics endpoint with authn/authz.
|
||||
// These configurations ensure that only authorized users and service accounts
|
||||
// can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info:
|
||||
// https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.19.1/pkg/metrics/filters#WithAuthenticationAndAuthorization
|
||||
metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization
|
||||
|
||||
// TODO(user): If CertDir, CertName, and KeyName are not specified, controller-runtime will automatically
|
||||
// generate self-signed certificates for the metrics server. While convenient for development and testing,
|
||||
// this setup is not recommended for production.
|
||||
}
|
||||
|
||||
// Configure rate limiting for the Kubernetes client
|
||||
config := ctrl.GetConfigOrDie()
|
||||
config.QPS = 50.0 // Increased from default 5.0
|
||||
config.Burst = 100 // Increased from default 10
|
||||
|
||||
mgr, err := ctrl.NewManager(config, ctrl.Options{
|
||||
Scheme: scheme,
|
||||
Metrics: metricsServerOptions,
|
||||
WebhookServer: webhookServer,
|
||||
HealthProbeBindAddress: probeAddr,
|
||||
LeaderElection: enableLeaderElection,
|
||||
LeaderElectionID: "strategy.backups.cozystack.io",
|
||||
// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily
|
||||
// when the Manager ends. This requires the binary to immediately end when the
|
||||
// Manager is stopped, otherwise, this setting is unsafe. Setting this significantly
|
||||
// speeds up voluntary leader transitions as the new leader don't have to wait
|
||||
// LeaseDuration time first.
|
||||
//
|
||||
// In the default scaffold provided, the program ends immediately after
|
||||
// the manager stops, so would be fine to enable this option. However,
|
||||
// if you are doing or is intended to do any operation such as perform cleanups
|
||||
// after the manager stops then its usage might be unsafe.
|
||||
// LeaderElectionReleaseOnCancel: true,
|
||||
})
|
||||
if err != nil {
|
||||
setupLog.Error(err, "unable to start manager")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err = (&backupcontroller.BackupJobStrategyReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
}).SetupWithManager(mgr); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "Job")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// +kubebuilder:scaffold:builder
|
||||
|
||||
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
|
||||
setupLog.Error(err, "unable to set up health check")
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
|
||||
setupLog.Error(err, "unable to set up ready check")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
setupLog.Info("starting manager")
|
||||
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
|
||||
setupLog.Error(err, "problem running manager")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -229,6 +229,15 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
dashboardManager := &dashboard.Manager{
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
}
|
||||
if err = dashboardManager.SetupWithManager(mgr); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "DashboardReconciler")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// +kubebuilder:scaffold:builder
|
||||
|
||||
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
|
||||
@@ -254,7 +263,9 @@ func main() {
|
||||
}
|
||||
|
||||
setupLog.Info("starting manager")
|
||||
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
|
||||
ctx := ctrl.SetupSignalHandler()
|
||||
dashboardManager.InitializeStaticResources(ctx)
|
||||
if err := mgr.Start(ctx); err != nil {
|
||||
setupLog.Error(err, "problem running manager")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
666
docs/agents/changelog.md
Normal file
666
docs/agents/changelog.md
Normal file
@@ -0,0 +1,666 @@
|
||||
# Changelog Generation Instructions
|
||||
|
||||
This file contains detailed instructions for AI-powered IDE on how to generate changelogs for Cozystack releases.
|
||||
|
||||
## When to use these instructions
|
||||
|
||||
Follow these instructions when the user explicitly asks to generate a changelog.
|
||||
|
||||
## Required Tools
|
||||
|
||||
Before generating changelogs, ensure you have access to `gh` (GitHub CLI) tool, which is used to fetch commit and PR author information. The GitHub CLI is used to correctly identify PR authors from commits and pull requests.
|
||||
|
||||
## Changelog Generation Process
|
||||
|
||||
When the user asks to generate a changelog, follow these steps in the specified order:
|
||||
|
||||
**CHECKLIST - All actions that must be completed:**
|
||||
- [ ] Step 1: Update information from remote (git fetch)
|
||||
- [ ] Step 2: Check current branch (must be main)
|
||||
- [ ] Step 3: Determine release type and previous version (minor vs patch release)
|
||||
- [ ] Step 4: Determine versions and analyze existing changelogs
|
||||
- [ ] Step 5: Get the list of commits for the release period
|
||||
- [ ] Step 6: Check additional repositories (website is REQUIRED, optional repos if tags exist)
|
||||
- [ ] **MANDATORY**: Check website repository for documentation changes WITH authors and PR links via GitHub CLI
|
||||
- [ ] **MANDATORY**: Check ALL optional repositories (talm, boot-to-talos, cozypkg, cozy-proxy) for tags during release period
|
||||
- [ ] **MANDATORY**: For ALL commits from additional repos, get GitHub username via CLI, prioritizing PR author over commit author.
|
||||
- [ ] Step 7: Analyze commits (extract PR numbers, authors, user impact)
|
||||
- [ ] **MANDATORY**: For EVERY PR in main repo, get PR author via `gh pr view <PR_NUMBER> --json author --jq .author.login` (do NOT skip this step)
|
||||
- [ ] **MANDATORY**: Extract PR numbers from commit messages, then use `gh pr view` for each PR to get the PR author. Do NOT use commit author. Only for commits without PR numbers (rare), fall back to `gh api repos/cozystack/cozystack/commits/<hash> --jq '.author.login'`
|
||||
- [ ] Step 8: Form new changelog (structure, format, generate contributors list)
|
||||
- [ ] Step 9: Verify completeness and save
|
||||
|
||||
### 1. Updating information from remote
|
||||
|
||||
```bash
|
||||
git fetch --tags --force --prune
|
||||
```
|
||||
|
||||
This is necessary to get up-to-date information about tags and commits from the remote repository.
|
||||
|
||||
### 2. Checking current branch
|
||||
|
||||
Make sure we are on the `main` branch:
|
||||
|
||||
```bash
|
||||
git branch --show-current
|
||||
```
|
||||
|
||||
### 3. Determining release type and previous version
|
||||
|
||||
**Important**: Determine if you're generating a changelog for a **minor release** (vX.Y.0) or a **patch release** (vX.Y.Z where Z > 0).
|
||||
|
||||
**For minor releases (vX.Y.0):**
|
||||
- Each minor version lives and evolves in its own branch (`release-X.Y`)
|
||||
- You MUST compare with the **previous minor version** (v(X-1).Y.0), not the last patch release
|
||||
- This ensures you capture all changes from the entire minor version cycle, including all patch releases
|
||||
- Example: For v0.38.0, compare with v0.37.0 (not v0.37.8)
|
||||
- Run a separate cycle to check the diff with the zero version of the previous minor release
|
||||
|
||||
**For patch releases (vX.Y.Z where Z > 0):**
|
||||
- Compare with the previous patch version (vX.Y.(Z-1))
|
||||
- Example: For v0.37.2, compare with v0.37.1
|
||||
|
||||
### 4. Determining versions and analyzing existing changelogs
|
||||
|
||||
**Determine the last published version:**
|
||||
1. Get the list of version tags:
|
||||
```bash
|
||||
git tag -l 'v[0-9]*.[0-9]*.[0-9]*' | sort -V
|
||||
```
|
||||
|
||||
2. Get the last tag:
|
||||
```bash
|
||||
git tag -l 'v[0-9]*.[0-9]*.[0-9]*' | sort -V | tail -1
|
||||
```
|
||||
|
||||
3. Compare tags with existing changelog files in `docs/changelogs/` to determine the last published version (the newest file `vX.Y.Z.md`)
|
||||
|
||||
**Study existing changelog format:**
|
||||
- Review recent changelog files to understand the format and structure
|
||||
- Pay attention to:
|
||||
- **Feature Highlights format** (for minor releases): Use `## Feature Highlights` with `### Feature Name` subsections containing detailed descriptions (2-4 paragraphs each). See v0.35.0 and v0.36.0 for examples.
|
||||
- Section structure (Major Features and Improvements, Security, Fixes, Dependencies, etc.)
|
||||
- PR link format (e.g., `[**@username**](https://github.com/username) in #1234`)
|
||||
- Change description style
|
||||
- Presence of Breaking changes sections, etc.
|
||||
|
||||
### 5. Getting the list of commits
|
||||
|
||||
**Important**: Determine if you're generating a changelog for a **minor release** (vX.Y.0) or a **patch release** (vX.Y.Z where Z > 0).
|
||||
|
||||
**For patch releases (vX.Y.Z where Z > 0):**
|
||||
Get the list of commits starting from the previous patch version to HEAD:
|
||||
|
||||
**⚠️ CRITICAL: Do NOT use --first-parent flag! It will skip merge commits including backports!**
|
||||
|
||||
```bash
|
||||
# Get all commits including merge commits (backports)
|
||||
git log <previous_version>..HEAD --pretty=format:"%h - %s (%an, %ar)"
|
||||
```
|
||||
|
||||
For example, if generating changelog for `v0.37.2`:
|
||||
```bash
|
||||
git log v0.37.1..HEAD --pretty=format:"%h - %s (%an, %ar)"
|
||||
```
|
||||
|
||||
**⚠️ IMPORTANT: Check for backports:**
|
||||
- Look for commits with "[Backport release-X.Y]" in the commit message
|
||||
- For backport PRs, find the original PR number mentioned in the backport commit message or PR description
|
||||
- Use the original PR author (not the backport PR author) when creating changelog entries
|
||||
- Include both the original PR number and backport PR number in the changelog entry (e.g., `#1606, #1609`)
|
||||
|
||||
**For minor releases (vX.Y.0):**
|
||||
Minor releases must include **all changes** from patch releases of the previous minor version. Get commits from the previous minor release:
|
||||
|
||||
**⚠️ CRITICAL: Do NOT use --first-parent flag! It will skip merge commits including backports!**
|
||||
|
||||
```bash
|
||||
# For v0.38.0, get all commits since v0.37.0 (including all patch releases v0.37.1, v0.37.2, etc.)
|
||||
git log v<previous_minor_version>..HEAD --pretty=format:"%h - %s (%an, %ar)"
|
||||
```
|
||||
|
||||
For example, if generating changelog for `v0.38.0`:
|
||||
```bash
|
||||
git log v0.37.0..HEAD --pretty=format:"%h - %s (%an, %ar)"
|
||||
```
|
||||
|
||||
This will include all commits from v0.37.1, v0.37.2, v0.37.3, etc., up to v0.38.0.
|
||||
|
||||
**⚠️ IMPORTANT: Always check merge commits:**
|
||||
- Merge commits may contain backports that need to be included
|
||||
- Check all commits in the range, including merge commits
|
||||
- For backports, always find and reference the original PR
|
||||
|
||||
### 6. Analyzing additional repositories
|
||||
|
||||
**⚠️ CRITICAL: This step is MANDATORY and must NOT be skipped!**
|
||||
|
||||
Cozystack release may include changes from related repositories. Check and include commits from these repositories if tags were released during the release period:
|
||||
|
||||
**Required repositories:**
|
||||
- **Documentation**: [https://github.com/cozystack/website](https://github.com/cozystack/website)
|
||||
- **MANDATORY**: Always check this repository for documentation changes during the release period
|
||||
- **MANDATORY**: Get GitHub username for EVERY commit. Extract PR number from commit message, then use `gh pr view <PR_NUMBER> --repo cozystack/website --json author --jq .author.login` to get PR author. Only if no PR number, fall back to `gh api repos/cozystack/website/commits/<hash> --jq '.author.login'`
|
||||
|
||||
**Optional repositories (MUST check ALL of them for tags during release period):**
|
||||
- [https://github.com/cozystack/talm](https://github.com/cozystack/talm)
|
||||
- [https://github.com/cozystack/boot-to-talos](https://github.com/cozystack/boot-to-talos)
|
||||
- [https://github.com/cozystack/cozypkg](https://github.com/cozystack/cozypkg)
|
||||
- [https://github.com/cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy)
|
||||
|
||||
**⚠️ IMPORTANT**: You MUST check ALL optional repositories for tags created during the release period. Do NOT skip this step even if you think there might not be any tags. Use the process below to verify.
|
||||
|
||||
**Process for each repository:**
|
||||
|
||||
1. **Get release period dates:**
|
||||
```bash
|
||||
# Get dates for the release period
|
||||
cd /path/to/cozystack
|
||||
RELEASE_START=$(git log -1 --format=%ai v<previous_version>)
|
||||
RELEASE_END=$(git log -1 --format=%ai HEAD)
|
||||
```
|
||||
|
||||
2. **Check for commits in website repository (always required):**
|
||||
```bash
|
||||
# Ensure website repository is cloned and up-to-date
|
||||
mkdir -p _repos
|
||||
if [ ! -d "_repos/website" ]; then
|
||||
cd _repos && git clone https://github.com/cozystack/website.git && cd ..
|
||||
fi
|
||||
cd _repos/website
|
||||
git fetch --all --tags --force
|
||||
git checkout main 2>/dev/null || git checkout master
|
||||
git pull
|
||||
|
||||
# Get commits between release dates (with some buffer)
|
||||
git log --since="$RELEASE_START" --until="$RELEASE_END" --format="%H|%s|%an" | while IFS='|' read -r commit_hash subject author_name; do
|
||||
# Extract PR number from commit message
|
||||
PR_NUMBER=$(git log -1 --format="%B" "$commit_hash" | grep -oE '#[0-9]+' | head -1 | tr -d '#')
|
||||
|
||||
# ALWAYS use PR author if PR number found, not commit author
|
||||
if [ -n "$PR_NUMBER" ]; then
|
||||
GITHUB_USERNAME=$(gh pr view "$PR_NUMBER" --repo cozystack/website --json author --jq '.author.login // empty' 2>/dev/null)
|
||||
echo "$commit_hash|$subject|$author_name|$GITHUB_USERNAME|cozystack/website#$PR_NUMBER"
|
||||
else
|
||||
# Only fallback to commit author if no PR number found (rare)
|
||||
GITHUB_USERNAME=$(gh api repos/cozystack/website/commits/$commit_hash --jq '.author.login // empty')
|
||||
echo "$commit_hash|$subject|$author_name|$GITHUB_USERNAME|cozystack/website@${commit_hash:0:7}"
|
||||
fi
|
||||
done
|
||||
|
||||
# Look for documentation updates, new pages, or significant content changes
|
||||
# Include these in the "Documentation" section of the changelog WITH authors and PR links
|
||||
```
|
||||
|
||||
3. **For optional repositories, check if tags exist during release period:**
|
||||
|
||||
**⚠️ MANDATORY: You MUST check ALL optional repositories (talm, boot-to-talos, cozypkg, cozy-proxy). Do NOT skip any repository!**
|
||||
|
||||
**Use the helper script:**
|
||||
```bash
|
||||
# Get release period dates
|
||||
RELEASE_START=$(git log -1 --format=%ai v<previous_version>)
|
||||
RELEASE_END=$(git log -1 --format=%ai HEAD)
|
||||
|
||||
# Run the script to check all optional repositories
|
||||
./docs/changelogs/hack/check-optional-repos.sh "$RELEASE_START" "$RELEASE_END"
|
||||
```
|
||||
|
||||
The script will:
|
||||
- Check ALL optional repositories (talm, boot-to-talos, cozypkg, cozy-proxy)
|
||||
- Look for tags created during the release period
|
||||
- Get commits between tags (if tags exist) or by date range (if no tags)
|
||||
- Extract PR numbers from commit messages
|
||||
- For EVERY commit with PR number, get PR author via CLI: `gh pr view <PR_NUMBER> --repo cozystack/<repo> --json author --jq .author.login` (ALWAYS use PR author, not commit author)
|
||||
- For commits without PR numbers (rare), fallback to: `gh api repos/cozystack/<repo>/commits/<hash> --jq '.author.login'`
|
||||
- Output results in format: `commit_hash|subject|author_name|github_username|cozystack/repo#PR_NUMBER` or `cozystack/repo@commit_hash`
|
||||
|
||||
4. **Extract PR numbers and authors using GitHub CLI:**
|
||||
- **ALWAYS use PR author, not commit author** for commits from additional repositories
|
||||
- For each commit, extract PR number from commit message first: Extract `#123` pattern from commit message
|
||||
- If PR number found, use `gh pr view <PR_NUMBER> --repo cozystack/<repo> --json author --jq .author.login` to get PR author (the person who wrote the code)
|
||||
- Only if no PR number found (rare), fallback to commit author: `gh api repos/cozystack/<repo>/commits/<hash> --jq '.author.login'`
|
||||
- **Prefer PR numbers**: Use format `cozystack/website#123` if PR number found in commit message
|
||||
- **Fallback to commit hash**: Use format `cozystack/website@abc1234` if no PR number
|
||||
- **ALWAYS include author**: Every entry from additional repositories MUST include author in format `([**@username**](https://github.com/username) in cozystack/repo#123)`
|
||||
- Determine user impact and categorize appropriately
|
||||
- Format entries with repository prefix: `[website]`, `[talm]`, etc.
|
||||
|
||||
**Example entry format for additional repositories:**
|
||||
```markdown
|
||||
# If PR number found in commit message (REQUIRED format):
|
||||
* **[website] Update installation documentation**: Improved installation guide with new examples ([**@username**](https://github.com/username) in cozystack/website#123).
|
||||
|
||||
# If no PR number (fallback, use commit hash):
|
||||
* **[website] Update installation documentation**: Improved installation guide with new examples ([**@username**](https://github.com/username) in cozystack/website@abc1234).
|
||||
|
||||
# For optional repositories:
|
||||
* **[talm] Add new feature**: Description of the change ([**@username**](https://github.com/username) in cozystack/talm#456).
|
||||
```
|
||||
|
||||
**CRITICAL**:
|
||||
- **ALWAYS include author** for every entry from additional repositories
|
||||
- **ALWAYS include PR link or commit hash** for every entry
|
||||
- Never add entries without author and PR/commit reference
|
||||
- **ALWAYS use PR author, not commit author**: Extract PR number from commit message, then use `gh pr view <PR_NUMBER> --repo cozystack/<repo> --json author --jq .author.login` to get the PR author (the person who wrote the code)
|
||||
- Only if no PR number found (rare), fallback to commit author: `gh api repos/cozystack/<repo>/commits/<hash> --jq '.author.login'`
|
||||
- The commit author (especially for squash/merge commits) is usually the person who merged the PR, not the person who wrote the code
|
||||
|
||||
### 7. Analyzing commits and PRs
|
||||
|
||||
**⚠️ CRITICAL: You MUST get the author from PR, not from commit! Always use `gh pr view` to get the PR author. Do NOT use commit author!**
|
||||
|
||||
**Get all PR numbers from commits:**
|
||||
**⚠️ CRITICAL: Do NOT use --no-merges flag! It will skip merge commits including backports!**
|
||||
|
||||
```bash
|
||||
# Extract all PR numbers from commit messages in the release range (including merge commits)
|
||||
git log <previous_version>..<new_version> --format="%s%n%b" | grep -oE '#[0-9]+' | sort -u | tr -d '#'
|
||||
```
|
||||
|
||||
**⚠️ IMPORTANT: Handle backports correctly:**
|
||||
- Backport PRs have format: `[Backport release-X.Y] <original title> (#BACKPORT_PR_NUMBER)`
|
||||
- The backport commit message or PR description usually mentions the original PR number
|
||||
- For backport entries in changelog, use the original PR author (not the backport PR author)
|
||||
- Include both original and backport PR numbers in the changelog entry (e.g., `#1606, #1609`)
|
||||
- To find original PR from backport: Check the backport PR description or commit message for "Backport of #ORIGINAL_PR"
|
||||
|
||||
**For each PR number, get the author:**
|
||||
|
||||
**CRITICAL**: The commit author (especially for squash/merge commits) is usually the person who merged the PR (or GitHub bot), NOT the person who wrote the code. **ALWAYS use the PR author**, not the commit author.
|
||||
|
||||
**⚠️ MANDATORY: ALWAYS use `gh pr view` to get the PR author. Do NOT use commit author!**
|
||||
|
||||
**ALWAYS use GitHub CLI** to get the PR author:
|
||||
|
||||
```bash
|
||||
# Usage: Get PR author - MANDATORY for EVERY PR
|
||||
# Loop through ALL PR numbers and get PR author (including backports)
|
||||
git log <previous_version>..<new_version> --format="%s%n%b" | grep -oE '#[0-9]+' | sort -u | tr -d '#' | while read PR_NUMBER; do
|
||||
# Check if this is a backport PR
|
||||
BACKPORT_INFO=$(gh pr view "$PR_NUMBER" --json body --jq '.body' 2>/dev/null | grep -i "backport of #" || echo "")
|
||||
if [ -n "$BACKPORT_INFO" ]; then
|
||||
# Extract original PR number from backport description
|
||||
ORIGINAL_PR=$(echo "$BACKPORT_INFO" | grep -oE 'backport of #([0-9]+)' | grep -oE '[0-9]+' | head -1)
|
||||
if [ -n "$ORIGINAL_PR" ]; then
|
||||
# Use original PR author
|
||||
GITHUB_USERNAME=$(gh pr view "$ORIGINAL_PR" --json author --jq '.author.login // empty')
|
||||
PR_TITLE=$(gh pr view "$ORIGINAL_PR" --json title --jq '.title // empty')
|
||||
echo "$PR_NUMBER|$ORIGINAL_PR|$GITHUB_USERNAME|$PR_TITLE|BACKPORT"
|
||||
else
|
||||
# Fallback to backport PR author if original not found
|
||||
GITHUB_USERNAME=$(gh pr view "$PR_NUMBER" --json author --jq '.author.login // empty')
|
||||
PR_TITLE=$(gh pr view "$PR_NUMBER" --json title --jq '.title // empty')
|
||||
echo "$PR_NUMBER||$GITHUB_USERNAME|$PR_TITLE|BACKPORT"
|
||||
fi
|
||||
else
|
||||
# Regular PR
|
||||
GITHUB_USERNAME=$(gh pr view "$PR_NUMBER" --json author --jq '.author.login // empty')
|
||||
PR_TITLE=$(gh pr view "$PR_NUMBER" --json title --jq '.title // empty')
|
||||
echo "$PR_NUMBER||$GITHUB_USERNAME|$PR_TITLE|REGULAR"
|
||||
fi
|
||||
done
|
||||
```
|
||||
|
||||
**⚠️ IMPORTANT**: You must run this for EVERY PR in the release period. Do NOT skip any PRs or assume the GitHub username based on the git author name.
|
||||
|
||||
**CRITICAL**: Always use `gh pr view <PR_NUMBER> --json author --jq .author.login` to get the PR author. This correctly identifies the person who wrote the code, not the person who merged it (which is especially important for squash merges).
|
||||
|
||||
**Why this matters**: Using the wrong author in changelogs gives incorrect credit and can confuse contributors. The merge/squash commit is created by the person who clicks "Merge" in GitHub, not the PR author.
|
||||
|
||||
**For commits without PR numbers (rare):**
|
||||
- Only if a commit has no PR number, fall back to commit author: `gh api repos/cozystack/cozystack/commits/<hash> --jq '.author.login'`
|
||||
- But this should be very rare - most commits should have PR numbers
|
||||
|
||||
**Extract PR number from commit messages:**
|
||||
- Check commit message subject (`%s`) and body (`%b`) for PR references: `#1234` or `(#1234)`
|
||||
- **Primary method**: Extract from commit message format `(#PR_NUMBER)` or `in #PR_NUMBER` or `Merge pull request #1234`
|
||||
- Use regex: `grep -oE '#[0-9]+'` to find all PR numbers
|
||||
|
||||
**⚠️ CRITICAL: Verify PR numbers match commit messages!**
|
||||
- Always verify that the PR number in the changelog matches the PR number in the commit message
|
||||
- Common mistake: Using wrong PR number (e.g., #1614 instead of #1617) when multiple similar commits exist
|
||||
- To verify: Check the actual commit message: `git log <commit_hash> -1 --format="%s%n%b" | grep -oE '#[0-9]+'`
|
||||
- If multiple PR numbers appear in a commit, use the one that matches the PR title/description
|
||||
- For merge commits, check the merged branch commits, not just the merge commit message
|
||||
|
||||
3. **Understand the change:**
|
||||
```bash
|
||||
# Get PR details (preferred method)
|
||||
gh pr view <PR_NUMBER> --json title,body,url
|
||||
|
||||
# Or get commit details if no PR number
|
||||
git show <commit_hash> --stat
|
||||
git show <commit_hash>
|
||||
```
|
||||
- Review PR description and changed files
|
||||
- Understand functionality added/changed/fixed
|
||||
- **Determine user impact**: What can users do now? What problems are fixed? What improvements do users experience?
|
||||
|
||||
4. **For release branches (backports):**
|
||||
- If commit is from `release-X.Y` branch, check if it's a backport
|
||||
- Find original commit in `main` to get correct PR number:
|
||||
```bash
|
||||
git log origin/main --grep="<part of commit message>" --oneline
|
||||
```
|
||||
|
||||
### 8. Forming a new changelog
|
||||
|
||||
Create a new changelog file in the format matching previous versions:
|
||||
|
||||
1. **Determine the release type:**
|
||||
- **Minor release (vX.Y.0)** - use full format with **Feature Highlights** section. **Must include all changes from patch releases of the previous minor version** (e.g., v0.38.0 should include changes from v0.37.1, v0.37.2, v0.37.3, etc.)
|
||||
- **Patch release (vX.Y.Z, where Z > 0)** - use more compact format, includes only changes since the previous patch release
|
||||
|
||||
**Feature Highlights format for minor releases:**
|
||||
- Use section header: `## Feature Highlights`
|
||||
- Include 3-6 major features as subsections with `### Feature Name` headers
|
||||
- Each feature subsection should contain:
|
||||
- **Detailed description** (2-4 paragraphs) explaining:
|
||||
- What the feature is and what problem it solves
|
||||
- How it works and what users can do with it
|
||||
- How to use it (if applicable)
|
||||
- Benefits and impact for users
|
||||
- **Links to documentation** when available (use markdown links)
|
||||
- **Code examples or configuration snippets** if helpful
|
||||
- Focus on user value and practical implications, not just technical details
|
||||
- Each feature should be substantial enough to warrant its own subsection
|
||||
- Order features by importance/impact (most important first)
|
||||
- Example format:
|
||||
```markdown
|
||||
## Feature Highlights
|
||||
|
||||
### Feature Name
|
||||
|
||||
Detailed description paragraph explaining what the feature is...
|
||||
|
||||
Another paragraph explaining how it works and what users can do...
|
||||
|
||||
Learn more in the [documentation](https://cozystack.io/docs/...).
|
||||
```
|
||||
|
||||
**Important for minor releases**: After collecting all commits, **systematically verify** that all PRs from patch releases are included:
|
||||
```bash
|
||||
# Extract all PR numbers from patch release changelogs
|
||||
grep -h "#[0-9]\+" docs/changelogs/v<previous_minor>.*.md | sort -u
|
||||
|
||||
# Extract all PR numbers from the new minor release changelog
|
||||
grep -h "#[0-9]\+" docs/changelogs/v<new_minor>.0.md | sort -u
|
||||
|
||||
# Compare and identify missing PRs
|
||||
# Ensure every PR from patch releases appears in the minor release changelog
|
||||
```
|
||||
|
||||
2. **Structure changes by categories:**
|
||||
|
||||
**For minor releases (vX.Y.0):**
|
||||
- **Feature Highlights** (required) - see format above
|
||||
- **Major Features and Improvements** - detailed list of all major features and improvements
|
||||
- **Improvements (minor)** - smaller improvements and enhancements
|
||||
- **Bug fixes** - all bug fixes
|
||||
- **Security** - security-related changes
|
||||
- **Dependencies & version updates** - dependency updates
|
||||
- **System Configuration** - system-level configuration changes
|
||||
- **Development, Testing, and CI/CD** - development and testing improvements
|
||||
- **Documentation** (include changes from website repository here - **MUST include authors and PR links for all entries**)
|
||||
- **Breaking changes & upgrade notes** (if any)
|
||||
- **Refactors & chores** (if any)
|
||||
|
||||
**For patch releases (vX.Y.Z where Z > 0):**
|
||||
- **Features and Improvements** - new features and improvements
|
||||
- **Fixes** - bug fixes
|
||||
- **Security** - security-related changes
|
||||
- **Dependencies** - dependency updates
|
||||
- **System Configuration** - system-level configuration changes
|
||||
- **Development, Testing, and CI/CD** - development and testing improvements
|
||||
- **Documentation** (include changes from website repository here - **MUST include authors and PR links for all entries**)
|
||||
- **Migration and Upgrades** (if applicable)
|
||||
|
||||
**Note**: When including changes from additional repositories, group them logically with main repository changes, or create separate subsections if there are many changes from a specific repository.
|
||||
|
||||
3. **Entry format:**
|
||||
- Use the format: `* **Brief description**: detailed description ([**@username**](https://github.com/username) in #PR_NUMBER)`
|
||||
- **CRITICAL - Get authorship correctly**:
|
||||
- **ALWAYS use PR author, not commit author**: Extract PR number from commit message, then use `gh pr view` to get the PR author. The commit author (especially for squash/merge commits) is usually the person who merged the PR (or GitHub bot), NOT the person who wrote the code.
|
||||
```bash
|
||||
# Get PR author from GitHub CLI (correct method)
|
||||
# Step 1: Extract PR number from commit message
|
||||
PR_NUMBER=$(git log <commit_hash> -1 --format="%s%n%b" | grep -oE '#[0-9]+' | head -1 | tr -d '#')
|
||||
|
||||
# Step 2: Get PR author (the person who wrote the code)
|
||||
if [ -n "$PR_NUMBER" ]; then
|
||||
GITHUB_USERNAME=$(gh pr view "$PR_NUMBER" --json author --jq '.author.login')
|
||||
else
|
||||
# Only fallback to commit author if no PR number found (rare)
|
||||
GITHUB_USERNAME=$(gh api repos/cozystack/cozystack/commits/<commit_hash> --jq '.author.login')
|
||||
fi
|
||||
```
|
||||
**Example**: For PR #1507, the squash commit has author "kvaps" (who merged), but the PR author is "lllamnyp" (who wrote the code). Using `gh pr view 1507 --json author --jq .author.login` correctly returns "lllamnyp".
|
||||
- **For regular commits**: Use the commit author directly:
|
||||
```bash
|
||||
git log <commit_hash> -1 --format="%an|%ae"
|
||||
```
|
||||
- **Validation**: Before adding to changelog, verify the author by checking:
|
||||
- For merge commits: Compare merge commit author vs PR author (they should be different)
|
||||
- Check existing changelogs for author name to GitHub username mappings
|
||||
- Verify with: `git log <merge_commit>^1..<merge_commit>^2 --format="%an" --no-merges`
|
||||
- **Map author name to GitHub username**: Check existing changelogs for author name mappings, or extract from PR links in commit messages
|
||||
- **Always include user impact**: Each entry must explain how the change affects users
|
||||
- For new features: explain what users can now do
|
||||
- For bug fixes: explain what problem is solved for users
|
||||
- For improvements: explain what users will experience better
|
||||
- For breaking changes: clearly state what users need to do
|
||||
- Group related changes
|
||||
- Use bold font for important components/modules
|
||||
- Focus on user value, not just technical details
|
||||
|
||||
4. **Add a link to the full changelog:**
|
||||
|
||||
**For patch releases (vX.Y.Z where Z > 0):**
|
||||
```markdown
|
||||
**Full Changelog**: https://github.com/cozystack/cozystack/compare/v<previous_patch_version>...v<new_version>
|
||||
```
|
||||
Example: For v0.37.2, use `v0.37.1...v0.37.2`
|
||||
|
||||
**For minor releases (vX.Y.0):**
|
||||
```markdown
|
||||
**Full Changelog**: https://github.com/cozystack/cozystack/compare/v<previous_minor_version>...v<new_version>
|
||||
```
|
||||
Example: For v0.38.0, use `v0.37.0...v0.38.0` (NOT `v0.37.8...v0.38.0`)
|
||||
|
||||
**Important**: Minor releases must reference the previous minor release (vX.Y.0), not the last patch release, to include all changes from the entire minor version cycle.
|
||||
|
||||
5. **Generate contributors list:**
|
||||
|
||||
**⚠️ SIMPLIFIED APPROACH: Extract contributors from the generated changelog itself!**
|
||||
|
||||
Since you've already generated the changelog with all PR authors correctly identified, simply extract GitHub usernames from the changelog entries:
|
||||
|
||||
```bash
|
||||
# Extract all GitHub usernames from the current release changelog
|
||||
# This method is simpler and more reliable than extracting from git history
|
||||
|
||||
# For patch releases: extract from the current changelog file
|
||||
grep -oE '\[@[a-zA-Z0-9_-]+\]' docs/changelogs/v<version>.md | \
|
||||
sed 's/\[@/@/' | sed 's/\]//' | \
|
||||
sort -u
|
||||
|
||||
# For minor releases: extract from the current changelog file
|
||||
grep -oE '\[@[a-zA-Z0-9_-]+\]' docs/changelogs/v<version>.md | \
|
||||
sed 's/\[@/@/' | sed 's/\]//' | \
|
||||
sort -u
|
||||
```
|
||||
|
||||
**Get all previous contributors (to identify new ones):**
|
||||
```bash
|
||||
# Extract GitHub usernames from all previous changelogs
|
||||
grep -hE '\[@[a-zA-Z0-9_-]+\]' docs/changelogs/v*.md | \
|
||||
grep -oE '@[a-zA-Z0-9_-]+' | \
|
||||
sort -u > /tmp/previous_contributors.txt
|
||||
```
|
||||
|
||||
**Identify new contributors (first-time contributors):**
|
||||
```bash
|
||||
# Get current release contributors from the changelog
|
||||
grep -oE '@[a-zA-Z0-9_-]+' docs/changelogs/v<version>.md | \
|
||||
sort -u > /tmp/current_contributors.txt
|
||||
|
||||
# Get all previous contributors
|
||||
grep -hE '@[a-zA-Z0-9_-]+' docs/changelogs/v*.md | \
|
||||
grep -oE '@[a-zA-Z0-9_-]+' | \
|
||||
sort -u > /tmp/all_previous_contributors.txt
|
||||
|
||||
# Find new contributors (those in current but not in previous)
|
||||
comm -23 <(sort /tmp/current_contributors.txt) <(sort /tmp/all_previous_contributors.txt)
|
||||
```
|
||||
|
||||
**Why this approach is better:**
|
||||
- ✅ Uses the already-verified PR authors from the changelog (no need to query GitHub API again)
|
||||
- ✅ Automatically handles backports correctly (original PR authors are already in the changelog)
|
||||
- ✅ Simpler and faster (no git log parsing or API calls)
|
||||
- ✅ More reliable (matches exactly what's in the changelog)
|
||||
- ✅ Works for both patch and minor releases
|
||||
|
||||
**Add contributors section to changelog:**
|
||||
|
||||
Place the contributors section at the end of the changelog, before the "Full Changelog" link:
|
||||
```markdown
|
||||
## Contributors
|
||||
|
||||
We'd like to thank all contributors who made this release possible:
|
||||
|
||||
* [**@username1**](https://github.com/username1)
|
||||
* [**@username2**](https://github.com/username2)
|
||||
* [**@username3**](https://github.com/username3)
|
||||
* ...
|
||||
|
||||
### New Contributors
|
||||
|
||||
We're excited to welcome our first-time contributors:
|
||||
|
||||
* [**@newuser1**](https://github.com/newuser1) - First contribution!
|
||||
* [**@newuser2**](https://github.com/newuser2) - First contribution!
|
||||
```
|
||||
|
||||
**Formatting guidelines:**
|
||||
- List contributors in alphabetical order by GitHub username
|
||||
- Use the format: `* [**@username**](https://github.com/username)`
|
||||
- For new contributors, add " - First contribution!" note
|
||||
- If GitHub username cannot be determined, you can skip that contributor or use their git author name
|
||||
|
||||
**When to include:**
|
||||
- **For patch releases**: Contributors section is optional, but can be included for significant releases
|
||||
- **For minor releases (vX.Y.0)**: Contributors section is required - you must generate and include the contributors list
|
||||
- Always verify GitHub usernames by checking commit messages, PR links in changelog entries, or by examining PR details
|
||||
|
||||
6. **Add a comment with a link to the GitHub release:**
|
||||
```markdown
|
||||
<!--
|
||||
https://github.com/cozystack/cozystack/releases/tag/v<new_version>
|
||||
-->
|
||||
```
|
||||
|
||||
### 9. Verification and saving
|
||||
|
||||
**Before saving, verify completeness:**
|
||||
|
||||
**For ALL releases:**
|
||||
- [ ] Step 5 completed: **ALL commits included** (including merge commits and backports) - do not skip any commits
|
||||
- [ ] Step 5 completed: **Backports identified and handled correctly** - original PR author used, both original and backport PR numbers included
|
||||
- [ ] Step 6 completed: Website repository checked for documentation changes WITH authors and PR links via GitHub CLI
|
||||
- [ ] Step 6 completed: **ALL** optional repositories (talm, boot-to-talos, cozypkg, cozy-proxy) checked for tags during release period
|
||||
- [ ] Step 6 completed: For ALL commits from additional repos, GitHub username obtained via GitHub CLI (not skipped). For commits with PR numbers, PR author used via `gh pr view` (not commit author)
|
||||
- [ ] Step 7 completed: For EVERY PR in main repo (including backports), PR author obtained via `gh pr view <PR_NUMBER> --json author --jq .author.login` (not skipped or assumed). Commit author NOT used - always use PR author
|
||||
- [ ] Step 7 completed: **Backports verified** - for each backport PR, original PR found and original PR author used in changelog
|
||||
- [ ] Step 8 completed: Contributors list generated
|
||||
- [ ] All commits from main repository included (including merge commits)
|
||||
- [ ] User impact described for each change
|
||||
- [ ] Format matches existing changelogs
|
||||
|
||||
**For patch releases:**
|
||||
- [ ] All commits from the release period are included (including merge commits with backports)
|
||||
- [ ] PR numbers match commit messages
|
||||
- [ ] Backports are properly identified and linked to original PRs
|
||||
|
||||
**For minor releases (vX.Y.0):**
|
||||
- [ ] All changes from patch releases (vX.Y.1, vX.Y.2, etc.) are included
|
||||
- [ ] Contributors section is present and complete
|
||||
- [ ] Full Changelog link references previous minor version (vX.Y.0), not last patch
|
||||
- [ ] Verify all PRs from patch releases are included:
|
||||
```bash
|
||||
# Extract and compare PR numbers
|
||||
PATCH_PRS=$(grep -hE "#[0-9]+" docs/changelogs/v<previous_minor>.*.md | grep -oE "#[0-9]+" | sort -u)
|
||||
MINOR_PRS=$(grep -hE "#[0-9]+" docs/changelogs/v<new_minor>.0.md | grep -oE "#[0-9]+" | sort -u)
|
||||
MISSING=$(comm -23 <(echo "$PATCH_PRS") <(echo "$MINOR_PRS"))
|
||||
|
||||
if [ -n "$MISSING" ]; then
|
||||
echo "Missing PRs from patch releases:"
|
||||
echo "$MISSING"
|
||||
# For each missing PR, check if it's a backport and verify change is included by description
|
||||
fi
|
||||
```
|
||||
|
||||
**Only proceed to save after all checkboxes are verified!**
|
||||
|
||||
**Save the changelog:**
|
||||
Save the changelog to file `docs/changelogs/v<version>.md` according to the version for which the changelog is being generated.
|
||||
|
||||
### Important notes
|
||||
|
||||
- **After fetch with --force** local tags are up-to-date, use them for work
|
||||
- **For release branches** always check original commits in `main` to get correct PR numbers
|
||||
- **Preserve the format** of existing changelog files
|
||||
- **Group related changes** logically
|
||||
- **Be accurate** in describing changes, based on actual commit diffs
|
||||
- **Check for PR numbers** and commit authors
|
||||
- **CRITICAL - Get authorship from PR, not from commit**:
|
||||
- **ALWAYS use PR author**: Extract PR number from commit message, then use `gh pr view <PR_NUMBER> --json author --jq .author.login` to get the PR author
|
||||
- Do NOT use commit author - the commit author (especially for squash/merge commits) is usually the person who merged the PR, not the person who wrote the code
|
||||
- For commits without PR numbers (rare), fall back to commit author: `gh api repos/cozystack/cozystack/commits/<commit_hash> --jq '.author.login'`
|
||||
- **Workflow**: Extract PR numbers from commits → Use `gh pr view` for each PR → Get PR author (the person who wrote the code)
|
||||
- Example: For PR #1507, the commit author is `@kvaps` (who merged), but `gh pr view 1507 --json author --jq .author.login` correctly returns `@lllamnyp` (who wrote the code)
|
||||
- Check existing changelogs for author name to GitHub username mappings
|
||||
- **Validation**: Before adding to changelog, always verify the author using `gh pr view` - never use commit author for PRs
|
||||
- **MANDATORY**: Always describe user impact: Every changelog entry must explain how the change affects end users, not just what was changed technically. Focus on user value and practical implications.
|
||||
|
||||
**Required steps:**
|
||||
|
||||
- **Additional repositories (Step 6) - MANDATORY**:
|
||||
- **⚠️ CRITICAL**: Always check the **website** repository for documentation changes during the release period. This is a required step and MUST NOT be skipped.
|
||||
- **⚠️ CRITICAL**: You MUST check ALL optional repositories (talm, boot-to-talos, cozypkg, cozy-proxy) for tags during the release period. Do NOT skip any repository even if you think there might not be tags.
|
||||
- **CRITICAL**: For ALL entries from additional repositories (website and optional), you MUST:
|
||||
- **MANDATORY**: Extract PR number from commit message first
|
||||
- **MANDATORY**: For commits with PR numbers, ALWAYS use `gh pr view <PR_NUMBER> --repo cozystack/<repo> --json author --jq .author.login` to get PR author (not commit author)
|
||||
- **MANDATORY**: Only for commits without PR numbers (rare), fallback to: `gh api repos/cozystack/<repo>/commits/<hash> --jq '.author.login'`
|
||||
- **MANDATORY**: Do NOT skip getting GitHub username via CLI - do this for EVERY commit
|
||||
- **MANDATORY**: Do NOT use commit author for PRs - always use PR author
|
||||
- Include PR link or commit hash reference
|
||||
- Format: `* **[repo] Description**: details ([**@username**](https://github.com/username) in cozystack/repo#123)`
|
||||
- For **optional repositories** (talm, boot-to-talos, cozypkg, cozy-proxy), you MUST check ALL of them for tags during the release period. Use the loop provided in Step 6 to check each repository systematically.
|
||||
- When including changes from additional repositories, use the format: `[repo-name] Description` and link to the repository's PR/issue if available
|
||||
- **Prefer PR numbers over commit hashes**: For commits from additional repositories, extract PR number from commit message using GitHub API. Use PR format (`cozystack/website#123`) instead of commit hash (`cozystack/website@abc1234`) when available
|
||||
- **Never add entries without author and PR/commit reference**: Every entry from additional repositories must have both author and link
|
||||
- Group changes from additional repositories with main repository changes, or create separate subsections if there are many changes from a specific repository
|
||||
|
||||
- **PR author verification (Step 7) - MANDATORY**:
|
||||
- **⚠️ CRITICAL**: You MUST get the author from PR using `gh pr view`, NOT from commit
|
||||
- **⚠️ CRITICAL**: Extract PR numbers from commit messages, then use `gh pr view <PR_NUMBER> --json author --jq .author.login` for each PR
|
||||
- **⚠️ CRITICAL**: Do NOT use commit author - commit author is usually the person who merged, not the person who wrote the code
|
||||
- **⚠️ CRITICAL**: Do NOT skip this step for any PR, even if the author seems obvious
|
||||
- For commits without PR numbers (rare), fall back to: `gh api repos/cozystack/cozystack/commits/<hash> --jq '.author.login'`
|
||||
- This ensures correct attribution and prevents errors in changelog entries (especially important for squash/merge commits)
|
||||
|
||||
- **Contributors list (Step 8)**:
|
||||
- For minor releases (vX.Y.0): You must generate a list of all contributors and identify first-time contributors.
|
||||
- For patch releases: Contributors section is optional, but recommended for significant releases
|
||||
- Extract GitHub usernames from PR links in commit messages or changelog entries
|
||||
- This helps recognize community contributions and welcome new contributors
|
||||
- **Minor releases (vX.Y.0)**:
|
||||
- Must include **all changes** from patch releases of the previous minor version (e.g., v0.38.0 includes all changes from v0.37.1, v0.37.2, v0.37.3, etc.)
|
||||
- The "Full Changelog" link must reference the previous minor release (v0.37.0...v0.38.0), NOT the last patch release (v0.37.8...v0.38.0)
|
||||
- This ensures users can see the complete set of changes for the entire minor version cycle
|
||||
- **Verification step**: After creating the changelog, extract all PR numbers from patch release changelogs and verify they all appear in the minor release changelog to prevent missing entries
|
||||
- **Backport handling**: Patch releases may contain backports with different PR numbers (e.g., #1624 in patch release vs #1622 in main). For minor releases, use original PR numbers from main when available, but verify that all changes from patch releases are included regardless of PR number differences
|
||||
- **Content verification**: Don't rely solely on PR number matching - verify that change descriptions from patch releases appear in the minor release changelog, as backports may have different PR numbers
|
||||
|
||||
190
docs/agents/contributing.md
Normal file
190
docs/agents/contributing.md
Normal file
@@ -0,0 +1,190 @@
|
||||
# Instructions for AI Agents
|
||||
|
||||
Guidelines for AI agents contributing to Cozystack.
|
||||
|
||||
## Checklist for Creating a Pull Request
|
||||
|
||||
- [ ] Changes are made and tested
|
||||
- [ ] Commit message uses correct `[component]` prefix
|
||||
- [ ] Commit is signed off with `--signoff`
|
||||
- [ ] Branch is rebased on `upstream/main` (no extra commits)
|
||||
- [ ] PR body includes description and release note
|
||||
- [ ] PR is pushed and created with `gh pr create`
|
||||
|
||||
## How to Commit and Create Pull Requests
|
||||
|
||||
### 1. Make Your Changes
|
||||
|
||||
Edit the necessary files in the codebase.
|
||||
|
||||
### 2. Commit with Proper Format
|
||||
|
||||
Use the `[component]` prefix and `--signoff` flag:
|
||||
|
||||
```bash
|
||||
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]`
|
||||
- Other: `[tests]`, `[ci]`, `[docs]`, `[maintenance]`
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
git commit --signoff -m "[dashboard] Add config hash annotations to restart pods on config changes"
|
||||
git commit --signoff -m "[postgres] Update operator to version 1.2.3"
|
||||
git commit --signoff -m "[docs] Add installation guide"
|
||||
```
|
||||
|
||||
### 3. Rebase on upstream/main (if needed)
|
||||
|
||||
If your branch has extra commits, clean it up:
|
||||
|
||||
```bash
|
||||
# Fetch latest
|
||||
git fetch upstream
|
||||
|
||||
# Create clean branch from upstream/main
|
||||
git checkout -b my-feature upstream/main
|
||||
|
||||
# Cherry-pick only your commit
|
||||
git cherry-pick <your-commit-hash>
|
||||
|
||||
# Force push to your branch
|
||||
git push -f origin my-feature:my-branch-name
|
||||
```
|
||||
|
||||
### 4. Push Your Branch
|
||||
|
||||
```bash
|
||||
git push origin <branch-name>
|
||||
```
|
||||
|
||||
### 5. Create Pull Request
|
||||
|
||||
Write the PR body to a temporary file:
|
||||
|
||||
```bash
|
||||
cat > /tmp/pr_body.md << 'EOF'
|
||||
## What this PR does
|
||||
|
||||
Brief description of the changes.
|
||||
|
||||
Changes:
|
||||
- Change 1
|
||||
- Change 2
|
||||
|
||||
### Release note
|
||||
|
||||
```release-note
|
||||
[component] Description for changelog
|
||||
```
|
||||
EOF
|
||||
```
|
||||
|
||||
Create the PR:
|
||||
|
||||
```bash
|
||||
gh pr create --title "[component] Brief description" --body-file /tmp/pr_body.md
|
||||
```
|
||||
|
||||
Clean up:
|
||||
|
||||
```bash
|
||||
rm /tmp/pr_body.md
|
||||
```
|
||||
|
||||
## Addressing AI Bot Reviewer Comments
|
||||
|
||||
When the user asks to fix comments from AI bot reviewers (like Qodo, Copilot, etc.):
|
||||
|
||||
### 1. Get PR Comments
|
||||
|
||||
View all comments on the pull request:
|
||||
|
||||
```bash
|
||||
gh pr view <PR-number> --comments
|
||||
```
|
||||
|
||||
Or for the current branch:
|
||||
|
||||
```bash
|
||||
gh pr view --comments
|
||||
```
|
||||
|
||||
### 2. Review Each Comment Carefully
|
||||
|
||||
**Important**: Do NOT blindly apply all suggestions. Each comment should be evaluated:
|
||||
|
||||
- **Consider context** - Does the suggestion make sense for this specific case?
|
||||
- **Check project conventions** - Does it align with Cozystack patterns?
|
||||
- **Evaluate impact** - Will this improve code quality or introduce issues?
|
||||
- **Question validity** - AI bots can be wrong or miss context
|
||||
|
||||
**When to apply:**
|
||||
- ✅ Legitimate bugs or security issues
|
||||
- ✅ Clear improvements to code quality
|
||||
- ✅ Better error handling or edge cases
|
||||
- ✅ Conformance to project conventions
|
||||
|
||||
**When to skip:**
|
||||
- ❌ Stylistic preferences that don't match project style
|
||||
- ❌ Over-engineering simple code
|
||||
- ❌ Changes that break existing patterns
|
||||
- ❌ Suggestions that show misunderstanding of the code
|
||||
|
||||
### 3. Apply Valid Fixes
|
||||
|
||||
Make changes addressing the valid comments. Use your judgment.
|
||||
|
||||
### 4. Leave Changes Uncommitted
|
||||
|
||||
**Critical**: Do NOT commit or push the changes automatically.
|
||||
|
||||
Leave the changes in the working directory so the user can:
|
||||
- Review the fixes
|
||||
- Decide whether to commit them
|
||||
- Make additional adjustments if needed
|
||||
|
||||
```bash
|
||||
# After making changes, show status but DON'T commit
|
||||
git status
|
||||
git diff
|
||||
```
|
||||
|
||||
The user will commit and push when ready.
|
||||
|
||||
### Example Workflow
|
||||
|
||||
```bash
|
||||
# Get PR comments
|
||||
gh pr view 1234 --comments
|
||||
|
||||
# Review comments and identify valid ones
|
||||
# Make necessary changes to address valid comments
|
||||
# ... edit files ...
|
||||
|
||||
# Show what was changed (but don't commit)
|
||||
git status
|
||||
git diff
|
||||
|
||||
# Tell the user what was fixed and what was skipped
|
||||
```
|
||||
|
||||
## Git Permissions
|
||||
|
||||
Request these permissions when needed:
|
||||
- `git_write` - For commit, rebase, cherry-pick, branch operations
|
||||
- `network` - For push, fetch, pull operations
|
||||
|
||||
## Common Issues
|
||||
|
||||
**PR has extra commits?**
|
||||
→ Rebase on `upstream/main` and cherry-pick only your commits
|
||||
|
||||
**Wrong commit message?**
|
||||
→ `git commit --amend --signoff -m "[correct] message"` then `git push -f`
|
||||
|
||||
**Need to update PR?**
|
||||
→ `gh pr edit <number> --body "new description"`
|
||||
115
docs/agents/overview.md
Normal file
115
docs/agents/overview.md
Normal file
@@ -0,0 +1,115 @@
|
||||
# Cozystack Project Overview
|
||||
|
||||
This document provides detailed information about Cozystack project structure and conventions for AI agents.
|
||||
|
||||
## About Cozystack
|
||||
|
||||
Cozystack is an open-source Kubernetes-based platform and framework for building cloud infrastructure. It provides:
|
||||
|
||||
- **Managed Services**: Databases, VMs, Kubernetes clusters, object storage, and more
|
||||
- **Multi-tenancy**: Full isolation and self-service for tenants
|
||||
- **GitOps-driven**: FluxCD-based continuous delivery
|
||||
- **Modular Architecture**: Extensible with custom packages and services
|
||||
- **Developer Experience**: Simplified local development with cozypkg tool
|
||||
|
||||
The platform exposes infrastructure services via the Kubernetes API with ready-made configs, built-in monitoring, and alerts.
|
||||
|
||||
## Code Layout
|
||||
|
||||
```
|
||||
.
|
||||
├── packages/ # Main directory for cozystack packages
|
||||
│ ├── core/ # Core platform logic charts (installer, platform)
|
||||
│ ├── system/ # System charts (CSI, CNI, operators, etc.)
|
||||
│ ├── apps/ # User-facing charts shown in dashboard catalog
|
||||
│ └── extra/ # Tenant-specific modules, singleton charts which are used as dependencies
|
||||
├── dashboards/ # Grafana dashboards for monitoring
|
||||
├── hack/ # Helper scripts for local development
|
||||
│ └── e2e-apps/ # End-to-end application tests
|
||||
├── scripts/ # Scripts used by cozystack container
|
||||
│ └── migrations/ # Version migration scripts
|
||||
├── docs/ # Documentation
|
||||
│ ├── agents/ # AI agent instructions
|
||||
│ └── changelogs/ # Release changelogs
|
||||
├── cmd/ # Go command entry points
|
||||
│ ├── cozystack-api/
|
||||
│ ├── cozystack-controller/
|
||||
│ └── cozystack-assets-server/
|
||||
├── internal/ # Internal Go packages
|
||||
│ ├── controller/ # Controller implementations
|
||||
│ └── lineagecontrollerwebhook/
|
||||
├── pkg/ # Public Go packages
|
||||
│ ├── apis/
|
||||
│ ├── apiserver/
|
||||
│ └── registry/
|
||||
└── api/ # Kubernetes API definitions (CRDs)
|
||||
└── v1alpha1/
|
||||
```
|
||||
|
||||
## Package Structure
|
||||
|
||||
Every package is a Helm chart following the umbrella chart pattern:
|
||||
|
||||
```
|
||||
packages/<category>/<package-name>/
|
||||
├── Chart.yaml # Chart definition and parameter docs
|
||||
├── Makefile # Development workflow targets
|
||||
├── charts/ # Vendored upstream charts
|
||||
├── images/ # Dockerfiles and image build context
|
||||
├── patches/ # Optional upstream chart patches
|
||||
├── templates/ # Additional manifests
|
||||
├── templates/dashboard-resourcemap.yaml # Dashboard resource mapping
|
||||
├── values.yaml # Override values for upstream
|
||||
└── values.schema.json # JSON schema for validation and UI
|
||||
```
|
||||
|
||||
## Conventions
|
||||
|
||||
### Helm Charts
|
||||
- Follow **umbrella chart** pattern for system components
|
||||
- Include upstream charts in `charts/` directory (vendored, not referenced)
|
||||
- Override configuration in root `values.yaml`
|
||||
- Use `values.schema.json` for input validation and dashboard UI rendering
|
||||
|
||||
### Go Code
|
||||
- Follow standard **Go conventions** and idioms
|
||||
- Use **controller-runtime** patterns for Kubernetes controllers
|
||||
- Prefer **kubebuilder** for API definitions and controllers
|
||||
- Add proper error handling and structured logging
|
||||
|
||||
### Git Commits
|
||||
- Use format: `[component] Description`
|
||||
- Always use `--signoff` flag
|
||||
- Reference PR numbers when available
|
||||
- Keep commits atomic and focused
|
||||
- Follow conventional commit format for changelogs
|
||||
|
||||
### Documentation
|
||||
|
||||
Documentation is organized as follows:
|
||||
- `docs/` - General documentation
|
||||
- `docs/agents/` - Instructions for AI agents
|
||||
- `docs/changelogs/` - Release changelogs
|
||||
- Main website: https://github.com/cozystack/website
|
||||
|
||||
## Things Agents Should Not Do
|
||||
|
||||
### Never Edit These
|
||||
- Do not modify files in `/vendor/` (Go dependencies)
|
||||
- Do not edit generated files: `zz_generated.*.go`
|
||||
- Do not change `go.mod`/`go.sum` manually (use `go get`)
|
||||
- Do not edit upstream charts in `packages/*/charts/` directly (use patches)
|
||||
- Do not modify image digests in `values.yaml` (generated by build)
|
||||
|
||||
### Version Control
|
||||
- Do not commit built artifacts from `_out`
|
||||
- Do not commit test artifacts or temporary files
|
||||
|
||||
### Git Operations
|
||||
- Do not force push to main/master
|
||||
- Do not update git config
|
||||
- Do not perform destructive operations without explicit request
|
||||
|
||||
### Core Components
|
||||
- Do not modify `packages/core/platform/` without understanding migration impact
|
||||
|
||||
29
docs/agents/releasing.md
Normal file
29
docs/agents/releasing.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Release Process
|
||||
|
||||
This document provides instructions for AI agents on how to handle release-related tasks.
|
||||
|
||||
## When to Use
|
||||
|
||||
Follow these instructions when the user asks to:
|
||||
- Create a new release
|
||||
- Prepare a release
|
||||
- Tag a release
|
||||
- Perform release-related tasks
|
||||
|
||||
## Instructions
|
||||
|
||||
For detailed release process instructions, follow the steps documented in:
|
||||
|
||||
**[docs/release.md](../release.md)**
|
||||
|
||||
## Quick Reference
|
||||
|
||||
The release process typically involves:
|
||||
1. Preparing the release branch
|
||||
2. Generating changelog
|
||||
3. Updating version numbers
|
||||
4. Creating git tags
|
||||
5. Building and publishing artifacts
|
||||
|
||||
All detailed steps are documented in `docs/release.md`.
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# Changes after v0.37.0
|
||||
|
||||
* [lineage] Break webhook out into a separate daemonset. Reduce unnecessary webhook calls by marking handled resources and excluding them from consideration by the webhook's object selector (@lllamnyp in #1515).
|
||||
31
docs/changelogs/v0.37.1.md
Normal file
31
docs/changelogs/v0.37.1.md
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
<!--
|
||||
https://github.com/cozystack/cozystack/releases/tag/v0.37.1
|
||||
-->
|
||||
|
||||
## Features and Improvements
|
||||
|
||||
* **[api] Efficient listing of TenantNamespaces**: Optimized TenantNamespace listing by replacing per-namespace SubjectAccessReview calls with group-based rolebinding checks, significantly reducing API latency and improving performance ([**@lllamnyp**](https://github.com/lllamnyp) in #1507).
|
||||
|
||||
## Fixes
|
||||
|
||||
* **[api] Fix RBAC for listing of TenantNamespaces and handle system:masters**: Fixed regression in TenantNamespace listing RBAC and added proper handling for system:masters group to ensure correct authorization ([**@kvaps**](https://github.com/kvaps) in #1511).
|
||||
* **[dashboard] Fix logout**: Fixed dashboard logout functionality to properly clear session and redirect users ([**@kvaps**](https://github.com/kvaps) in #1510).
|
||||
* **[installer] Add additional check to wait for lineage-webhook**: Added additional readiness check to ensure lineage-webhook is fully ready before proceeding with installation, improving upgrade reliability ([**@kvaps**](https://github.com/kvaps) in #1506).
|
||||
|
||||
## Development, Testing, and CI/CD
|
||||
|
||||
* **[tests] Make Kubernetes tests POSIX-compatible**: Replaced bash-specific constructs with POSIX-compliant code, ensuring tests work reliably with /bin/sh and improving compatibility across different shell environments ([**@IvanHunters**](https://github.com/IvanHunters) in #1509).
|
||||
|
||||
## Documentation
|
||||
|
||||
* **[website] Update troubleshooting documentation**: Updated Kubernetes installation troubleshooting guide with additional information and fixes ([**@lb0o**](https://github.com/lb0o) in cozystack/website@82beddd).
|
||||
* **[website] Add LLDPD disabling documentation**: Added minimal patch documentation for disabling lldpd based on official LLDPD usage guide ([**@lb0o**](https://github.com/lb0o) in cozystack/website@7ec5d7b).
|
||||
* **[website] Fix typo in utility command**: Fixed typo in utility command documentation ([**@lb0o**](https://github.com/lb0o) in cozystack/website@6c76cb5).
|
||||
* **[website] Update backup and recovery docs**: Updated backup and recovery documentation with latest information ([**@kvaps**](https://github.com/kvaps) in cozystack/website@2781aa5).
|
||||
* **[website] Add Troubleshooting checklist**: Added troubleshooting checklist to help users diagnose and resolve common issues ([**@kvaps**](https://github.com/kvaps) in cozystack/website@59fc304).
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: [v0.37.0...v0.37.1](https://github.com/cozystack/cozystack/compare/v0.37.0...v0.37.1)
|
||||
|
||||
21
docs/changelogs/v0.37.2.md
Normal file
21
docs/changelogs/v0.37.2.md
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
<!--
|
||||
https://github.com/cozystack/cozystack/releases/tag/v0.37.2
|
||||
-->
|
||||
|
||||
## Features and Improvements
|
||||
|
||||
* **[lineage] Separate webhook from cozy controller**: Separated the lineage-controller-webhook from cozystack-controller into a separate daemonset component deployed on all control-plane nodes, reducing API server latency and improving performance by decreasing outgoing API calls. Introduced internal label to track resources already handled by the webhook ([**@lllamnyp**](https://github.com/lllamnyp) in #1515).
|
||||
|
||||
## Fixes
|
||||
|
||||
* **[api] Fix listing tenantnamespaces for non-oidc users**: Fixed TenantNamespace listing functionality for users not using OIDC authentication, ensuring proper namespace visibility for all authentication methods ([**@kvaps**](https://github.com/kvaps) in #1517, #1519).
|
||||
|
||||
## Migration and Upgrades
|
||||
|
||||
* **[platform] Better migration for 0.36.2->0.37.2+**: Improved migration script for users upgrading directly from 0.36.2 to 0.37.2+, ensuring the new lineage webhook daemonset is properly deployed and fixing a bug where webhook readiness was not appropriately verified during migration ([**@lllamnyp**](https://github.com/lllamnyp) in #1521, #1522).
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: [v0.37.1...v0.37.2](https://github.com/cozystack/cozystack/compare/v0.37.1...v0.37.2)
|
||||
|
||||
45
docs/changelogs/v0.37.3.md
Normal file
45
docs/changelogs/v0.37.3.md
Normal file
@@ -0,0 +1,45 @@
|
||||
|
||||
<!--
|
||||
https://github.com/cozystack/cozystack/releases/tag/v0.37.3
|
||||
-->
|
||||
|
||||
## Features and Improvements
|
||||
|
||||
* **[apps] Make VM service user facing**: Virtual machine services are now marked as user-facing, improving service discovery and visibility in the dashboard ([**@lllamnyp**](https://github.com/lllamnyp) in #1523).
|
||||
* **[seaweedfs] Allow users to discover their buckets**: Users can now discover and list their S3 buckets in SeaweedFS, improving usability and bucket management ([**@kvaps**](https://github.com/kvaps) in #1528).
|
||||
* **[seaweedfs] Update SeaweedFS v3.99 and deploy S3 as stacked service**: Updated SeaweedFS to version 3.99 and deployed S3 gateway as a stacked service for better integration and performance ([**@kvaps**](https://github.com/kvaps) in #1562).
|
||||
* **[dashboard] Show service LB IP**: Fixed JSON path issue to correctly display Service LoadBalancer IPs in the dashboard table view, improving visibility of service endpoints ([**@lllamnyp**](https://github.com/lllamnyp) in #1524).
|
||||
* **[dashboard] Update openapi-ui v1.0.3 + fixes**: Updated OpenAPI UI to version 1.0.3 with various fixes and improvements ([**@kvaps**](https://github.com/kvaps) in #1564).
|
||||
* **[kubernetes] Use controlPlane.replicas field**: Fixed managed Kubernetes app to properly use the `controlPlane.replicas` field instead of hardcoding the value, allowing users to configure control plane replica count ([**@lllamnyp**](https://github.com/lllamnyp) in #1556).
|
||||
* **[monitoring] add settings alert for slack**: Added Slack integration configuration for Alerta alerts, enabling notifications to Slack channels ([**@scooby87**](https://github.com/scooby87) in #1545).
|
||||
|
||||
## Fixes
|
||||
|
||||
* **[lineage] Check for nil chart in HelmRelease**: Added nil check to prevent crashes when lineage webhook encounters HelmReleases using `chartRef` instead of `chart`, improving stability ([**@lllamnyp**](https://github.com/lllamnyp) in #1525).
|
||||
* **[kamaji] Respect 3rd party labels**: Applied patch to Kamaji controller to respect third-party labels, preventing reconciliation loops between lineage webhook and Kamaji controller ([**@lllamnyp**](https://github.com/lllamnyp) in #1531, #1534).
|
||||
* **[redis-operator] Build patched operator in-tree**: Moved Redis operator build into Cozystack organization and patched it to prevent overwriting third-party labels on owned resources ([**@lllamnyp**](https://github.com/lllamnyp) in #1547).
|
||||
* **[mariadb-operator] Add post-delete job to remove PVCs**: Added post-delete job to automatically remove PersistentVolumeClaims when MariaDB instances are deleted, preventing orphaned storage resources ([**@IvanHunters**](https://github.com/IvanHunters) in #1553).
|
||||
* **[velero] Set defaultItemOperationTimeout=24h**: Set default item operation timeout to 24 hours for Velero backups, preventing timeouts on large backup operations ([**@kvaps**](https://github.com/kvaps) in #1542).
|
||||
|
||||
## Dependencies
|
||||
|
||||
* **Update LINSTOR v1.32.3**: Updated LINSTOR to version 1.32.3 with latest features and bug fixes ([**@kvaps**](https://github.com/kvaps) in #1565).
|
||||
|
||||
## System Configuration
|
||||
|
||||
* **[system] kube-ovn: turn off enableLb**: Disabled load balancer functionality in Kube-OVN configuration ([**@nbykov0**](https://github.com/nbykov0) in #1548).
|
||||
|
||||
## Documentation
|
||||
|
||||
* **[website] Update LINSTOR documentation**: Updated LINSTOR guide and set failmode=continue for ZFS configurations ([**@kvaps**](https://github.com/kvaps) in cozystack/website@033804e).
|
||||
* **[website] Update managed apps reference**: Updated managed applications reference documentation ([**@kvaps**](https://github.com/kvaps) in cozystack/website@b886a74).
|
||||
* **[website] Update external apps documentation**: Updated documentation for external applications ([**@kvaps**](https://github.com/kvaps) in cozystack/website@565dad9).
|
||||
* **[website] Add naming conventions**: Added naming conventions documentation ([**@kvaps**](https://github.com/kvaps) in cozystack/website@b227abb).
|
||||
* **[website] Update golden image documentation**: Updated documentation for creating golden images for virtual machines ([**@kvaps**](https://github.com/kvaps) in cozystack/website@34c2f3a, cozystack/website@ef65593).
|
||||
* **[website] Fix documentation formatting**: Fixed alerts, infoboxes, tabs styles and main page formatting ([**@kvaps**](https://github.com/kvaps) in cozystack/website@e992e97, cozystack/website@b2c4dee).
|
||||
* **[website] Fix typo in blog article**: Fixed typo in blog article ([**@kvaps**](https://github.com/kvaps) in cozystack/website@0a4bbf3).
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: [v0.37.2...v0.37.3](https://github.com/cozystack/cozystack/compare/v0.37.2...v0.37.3)
|
||||
|
||||
29
docs/changelogs/v0.37.4.md
Normal file
29
docs/changelogs/v0.37.4.md
Normal file
@@ -0,0 +1,29 @@
|
||||
|
||||
<!--
|
||||
https://github.com/cozystack/cozystack/releases/tag/v0.37.4
|
||||
-->
|
||||
|
||||
## Features and Improvements
|
||||
|
||||
* **[tenant] Allow listing workloads**: Enabled listing of workloads for tenants, improving visibility and management of tenant resources ([**@kvaps**](https://github.com/kvaps) in #1576, #1577).
|
||||
|
||||
## Fixes
|
||||
|
||||
* **[seaweedfs] Fix migration to v3.99**: Fixed migration issues when upgrading SeaweedFS to version 3.99, ensuring smooth upgrades ([**@kvaps**](https://github.com/kvaps) in #1572, #1575).
|
||||
* **[nats] Merge container spec, not podTemplate**: Fixed NATS configuration to properly merge container specifications instead of podTemplate, ensuring correct container configuration ([**@lllamnyp**](https://github.com/lllamnyp) in #1571, #1574).
|
||||
|
||||
## Development, Testing, and CI/CD
|
||||
|
||||
* **[e2e] Increase Kubernetes connection timeouts**: Increased connection and request timeouts in E2E tests when communicating with Kubernetes API, improving test stability under high load and slow cluster response conditions ([**@IvanHunters**](https://github.com/IvanHunters) in #1570, #1573).
|
||||
|
||||
## Documentation
|
||||
|
||||
* **[website] Optimize website for mobile devices**: Improved website layout and responsiveness for mobile devices ([**@kvaps**](https://github.com/kvaps) in cozystack/website@3ab2338).
|
||||
* **[website] Add OpenAPI UI**: Added OpenAPI UI documentation and integration ([**@kvaps**](https://github.com/kvaps) in cozystack/website@b1c1668).
|
||||
* **[website] Update Cozystack video in hero banner**: Updated hero banner with new Cozystack video ([**@kvaps**](https://github.com/kvaps) in cozystack/website@e351137).
|
||||
* **[website] Add screenshots carousel**: Added screenshots carousel to showcase Cozystack features ([**@kvaps**](https://github.com/kvaps) in cozystack/website@8422bd0).
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: [v0.37.3...v0.37.4](https://github.com/cozystack/cozystack/compare/v0.37.3...v0.37.4)
|
||||
|
||||
28
docs/changelogs/v0.37.5.md
Normal file
28
docs/changelogs/v0.37.5.md
Normal file
@@ -0,0 +1,28 @@
|
||||
|
||||
<!--
|
||||
https://github.com/cozystack/cozystack/releases/tag/v0.37.5
|
||||
-->
|
||||
|
||||
## Features and Improvements
|
||||
|
||||
* **[dashboard-controller] Move badges generation logic to internal dashboard component**: Moved badges generation logic to internal dashboard component for better code organization and maintainability ([**@kvaps**](https://github.com/kvaps) in #1567).
|
||||
|
||||
## Security
|
||||
|
||||
* **[redis] Bump Redis image version for security fixes**: Updated Redis image version to include latest security fixes, improving cluster security ([**@IvanHunters**](https://github.com/IvanHunters) in #1580).
|
||||
* **[flux] Close Flux Operator ports to external access**: Removed hostPort and hostNetwork from Flux Operator Deployment, ensuring ports 8080 and 8081 are only accessible within the cluster, preventing external exposure and improving security ([**@IvanHunters**](https://github.com/IvanHunters) in #1581).
|
||||
* **[ingress] Enforce HTTPS-only for API**: Added force-ssl-redirect annotation to default API Ingress, ensuring all HTTP traffic is redirected to HTTPS, preventing unencrypted external access and improving security ([**@IvanHunters**](https://github.com/IvanHunters) in #1582, #1585).
|
||||
|
||||
## Fixes
|
||||
|
||||
* **[nats] Fixes for NATS App Helm chart, fix template issues with config.merge**: Fixed template issues in NATS Helm chart related to config.merge value, ensuring correct configuration ([**@insignia96**](https://github.com/insignia96) in #1583, #1591).
|
||||
* **[kubevirt] Fix: kubevirt metrics rule**: Fixed KubeVirt metrics rule configuration ([**@kvaps**](https://github.com/kvaps) in #1584, #1588).
|
||||
|
||||
## System Configuration
|
||||
|
||||
* **[core] rm talos lldp extension**: Removed Talos LLDP extension from core configuration ([**@nbykov0**](https://github.com/nbykov0) in #1586).
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: [v0.37.4...v0.37.5](https://github.com/cozystack/cozystack/compare/v0.37.4...v0.37.5)
|
||||
|
||||
30
docs/changelogs/v0.37.6.md
Normal file
30
docs/changelogs/v0.37.6.md
Normal file
@@ -0,0 +1,30 @@
|
||||
|
||||
<!--
|
||||
https://github.com/cozystack/cozystack/releases/tag/v0.37.6
|
||||
-->
|
||||
|
||||
## Features and Improvements
|
||||
|
||||
* **[api] Use shared informer cache**: Optimized API server by using shared informer cache, reducing API server load and improving performance ([**@lllamnyp**](https://github.com/lllamnyp) in #1539).
|
||||
* **[dashboard] sync with upstream & enhancements**: Synchronized dashboard with upstream and added various enhancements ([**@kvaps**](https://github.com/kvaps) in #1603).
|
||||
* **[cozystack-api][dashboard] Fix filtering for application services/ingresses/secrets**: Fixed filtering functionality for application services, ingresses, and secrets in both API and dashboard ([**@kvaps**](https://github.com/kvaps) in #1612).
|
||||
|
||||
## Fixes
|
||||
|
||||
* **[controller] Remove crdmem, handle DaemonSet**: Removed crdmem and improved DaemonSet handling in controller ([**@lllamnyp**](https://github.com/lllamnyp) in #1555).
|
||||
* **[dashboard] Revert reconciler removal**: Reverted reconciler removal to restore proper dashboard functionality ([**@lllamnyp**](https://github.com/lllamnyp) in #1559).
|
||||
* **[dashboard-controller] Fix static resources reconciliation and showing secrets**: Fixed static resources reconciliation and improved secret display in dashboard controller ([**@kvaps**](https://github.com/kvaps) in #1605).
|
||||
* **[api,lineage] Ensure node-local traffic**: Ensured node-local traffic handling for API and lineage components ([**@lllamnyp**](https://github.com/lllamnyp) in #1606).
|
||||
* **[virtual-machine] Revert per-vm network policies**: Reverted per-VM network policies to previous behavior ([**@lllamnyp**](https://github.com/lllamnyp) in #1611).
|
||||
* **[cozy-lib] Fix: handling resources=nil**: Fixed handling of nil resources in cozy-lib templates ([**@kvaps**](https://github.com/kvaps) in #1607).
|
||||
* **[nats] Use dig function to check for existing secret and prevent nil indexing**: Fixed NATS app chart to use dig function for checking existing secrets and prevent nil indexing errors ([**@kvaps**](https://github.com/kvaps) in #1609, #1610).
|
||||
|
||||
## Development, Testing, and CI/CD
|
||||
|
||||
* **[cozystack-controller] improve API tests**: Improved API tests for cozystack-controller ([**@lllamnyp**](https://github.com/lllamnyp) in #1599).
|
||||
* **[kubernetes] Helm hooks for cleanup**: Added Helm hooks for cleanup operations in Kubernetes app ([**@lllamnyp**](https://github.com/lllamnyp) in #1616).
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: [v0.37.5...v0.37.6](https://github.com/cozystack/cozystack/compare/v0.37.5...v0.37.6)
|
||||
|
||||
18
docs/changelogs/v0.37.7.md
Normal file
18
docs/changelogs/v0.37.7.md
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
<!--
|
||||
https://github.com/cozystack/cozystack/releases/tag/v0.37.7
|
||||
-->
|
||||
|
||||
## Fixes
|
||||
|
||||
* **[kubernetes] Cleanup loadbalancer services**: Added cleanup functionality for load balancer services in Kubernetes app ([**@lllamnyp**](https://github.com/lllamnyp) in #1622).
|
||||
* **[rbac] Fix permissions for high-privilege users**: Fixed RBAC permissions for high-privilege users, ensuring proper access control ([**@lllamnyp**](https://github.com/lllamnyp) in #1624).
|
||||
|
||||
## System Configuration
|
||||
|
||||
* **[system] kubeovn: increase limits**: Increased resource limits for Kube-OVN components to improve stability and performance ([**@nbykov0**](https://github.com/nbykov0) in #1629).
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: [v0.37.6...v0.37.7](https://github.com/cozystack/cozystack/compare/v0.37.6...v0.37.7)
|
||||
|
||||
19
docs/changelogs/v0.37.8.md
Normal file
19
docs/changelogs/v0.37.8.md
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
<!--
|
||||
https://github.com/cozystack/cozystack/releases/tag/v0.37.8
|
||||
-->
|
||||
|
||||
## Fixes
|
||||
|
||||
* **[cozy-lib] Fix malformed ResourceQuota rendering for LoadBalancer services**: Fixed malformed ResourceQuota rendering for LoadBalancer services in cozy-lib templates ([**@IvanHunters**](https://github.com/IvanHunters) in #1642).
|
||||
* **[extra] ingress: rm spaces from external ip list**: Removed spaces from external IP list in ingress configuration, fixing formatting issues ([**@nbykov0**](https://github.com/nbykov0) in #1652).
|
||||
* **scripts: fix 20 migration**: Fixed migration script #20 to ensure proper execution during upgrades ([**@nbykov0**](https://github.com/nbykov0) in #1653).
|
||||
|
||||
## System Configuration
|
||||
|
||||
* **Increase strimzi memory limit**: Increased memory limit for Strimzi Kafka operator to improve stability and performance ([**@nbykov0**](https://github.com/nbykov0) in #1651).
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: [v0.37.7...v0.37.8](https://github.com/cozystack/cozystack/compare/v0.37.7...v0.37.8)
|
||||
|
||||
19
docs/changelogs/v0.37.9.md
Normal file
19
docs/changelogs/v0.37.9.md
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
<!--
|
||||
https://github.com/cozystack/cozystack/releases/tag/v0.37.9
|
||||
-->
|
||||
|
||||
## Improvements
|
||||
|
||||
* **[seaweedfs] Extended CA certificate duration to reduce disruptive CA rotations**: Extended CA certificate duration to reduce disruptive CA rotations. ([**@IvanHunters**](https://github.com/IvanHunters) in #1657, #1666).
|
||||
* **[dashboard] Add config hash annotations to restart pods on config changes**: Added config hash annotations to restart pods when configuration changes, ensuring pods are automatically restarted when their configuration is updated ([**@kvaps**](https://github.com/kvaps) in #1662, #1665).
|
||||
|
||||
## Fixes
|
||||
|
||||
* **[tenant][kubernetes] Introduce better cleanup logic**: Improved cleanup logic for tenant Kubernetes resources, ensuring proper resource cleanup when tenants are deleted or updated ([**@kvaps**](https://github.com/kvaps) in #1661).
|
||||
* **[dashboard] Fix loading arrays in forms when editing existing objects**: Fixed issue where arrays in forms were not loading correctly when editing existing objects in the dashboard ([**@kvaps**](https://github.com/kvaps)).
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: [v0.37.8...v0.37.9](https://github.com/cozystack/cozystack/compare/v0.37.8...v0.37.9)
|
||||
|
||||
235
docs/changelogs/v0.38.0.md
Normal file
235
docs/changelogs/v0.38.0.md
Normal file
@@ -0,0 +1,235 @@
|
||||
# Cozystack v0.38 — "VPC & Enhanced Networking"
|
||||
|
||||
This release introduces **Virtual Private Cloud (VPC)** support, enabling advanced networking capabilities for tenant applications. We've also added VNC console support in the dashboard, made Kubernetes worker versions configurable, and delivered numerous improvements and fixes across the platform.
|
||||
|
||||
### Virtual Private Cloud (VPC) Networking
|
||||
|
||||
Cozystack v0.38.0 introduces Virtual Private Cloud (VPC) support, enabling platform administrators to create isolated network segments for tenant applications. VPCs provide network isolation and allow fine-grained control over network topology, subnets, and routing. Each VPC can contain multiple subnets, and administrators can configure subnet details including IP ranges, gateway settings, and DNS configuration.
|
||||
|
||||
The VPC feature integrates seamlessly with the Cozystack dashboard, allowing users to view and manage VPCs and their subnets through an intuitive interface. Subnet details are exposed in the dashboard as tables, making it easy to understand network configuration at a glance. VPC configuration is stored in ConfigMaps with predictable naming, ensuring reliable access to subnet information.
|
||||
|
||||
This feature is particularly valuable for multi-tenant environments where network isolation is critical, and for applications that require specific network configurations or routing rules.
|
||||
|
||||
### VNC Console for Virtual Machines
|
||||
|
||||
The Cozystack dashboard now includes a built-in VNC console for virtual machines, enabling users to access VM console directly from the web interface without requiring external tools. This feature provides immediate access to virtual machine consoles for troubleshooting, configuration, and maintenance tasks. The VNC console integration streamlines VM management workflows and improves the user experience by keeping all VM operations within the Cozystack dashboard.
|
||||
|
||||
## Highlights
|
||||
|
||||
* **Virtual Private Cloud (VPC)**: New VPC system module enables advanced networking with Multus CNI, subnet management, and network isolation for tenant applications ([**@nbykov0**](https://github.com/nbykov0) in #1543; [**@lllamnyp**](https://github.com/lllamnyp) in #1587, #1590, #1600, #1621, #1638).
|
||||
* **VNC Console in Dashboard**: Users can now access virtual machine consoles directly from the dashboard, improving VM management experience ([**@kvaps**](https://github.com/kvaps) in #1627).
|
||||
* **Configurable Kubernetes Worker Versions**: Platform administrators can now configure Kubernetes worker node versions independently, providing more flexibility in cluster management ([**@lllamnyp**](https://github.com/lllamnyp) in #1619).
|
||||
* **Security Enhancements**: Multiple security improvements including HTTPS-only enforcement for API, closed Flux Operator ports, and Redis security updates ([**@IvanHunters**](https://github.com/IvanHunters) in #1580, #1581, #1582).
|
||||
* **Cozy-lib Improvements**: Enhanced flatten function with better ResourceQuota handling and nil resource support ([**@lllamnyp**](https://github.com/lllamnyp) in #1647; [**@IvanHunters**](https://github.com/IvanHunters) in #1642; [**@kvaps**](https://github.com/kvaps) in #1607).
|
||||
|
||||
---
|
||||
|
||||
## New features
|
||||
|
||||
### VPC (Virtual Private Cloud)
|
||||
|
||||
* **[system] Add VPC**: Introduced Virtual Private Cloud system module with Multus CNI integration, enabling advanced networking capabilities for tenant applications ([**@nbykov0**](https://github.com/nbykov0) in #1543).
|
||||
* **[vpc] Install Multus by default**: Multus CNI is now installed by default when VPC is enabled, providing multi-network interface support ([**@lllamnyp**](https://github.com/lllamnyp) in #1587).
|
||||
* **[vpc] Give predictable name to subnet configmap**: Subnet configuration maps now use predictable naming for better management and debugging ([**@lllamnyp**](https://github.com/lllamnyp) in #1590).
|
||||
* **[vpc] Entry per subnet in the subnets configmap**: Each subnet now has its own entry in the subnets configmap, improving subnet organization and management ([**@lllamnyp**](https://github.com/lllamnyp) in #1600).
|
||||
* **[vpc,dashboard] Print subnet details as table**: Subnet details are now displayed as a table in the dashboard, improving visibility and management ([**@lllamnyp**](https://github.com/lllamnyp) in #1621).
|
||||
* **[apps] Add VPC app**: Added VPC application for tenant use, enabling users to create and manage VPCs ([**@nbykov0**](https://github.com/nbykov0) in #1543).
|
||||
|
||||
### Dashboard
|
||||
|
||||
* **[dashboard] Introduce VNC console**: Added VNC console support in the dashboard, allowing users to access virtual machine consoles directly from the web interface ([**@kvaps**](https://github.com/kvaps) in #1627).
|
||||
* **[dashboard] sync with upstream & enhancements**: Synchronized dashboard with upstream project and added various enhancements ([**@kvaps**](https://github.com/kvaps) in #1603).
|
||||
* **[dashboard] Migrate patches to upstream project**: Migrated dashboard patches to upstream project for better maintainability ([**@kvaps**](https://github.com/kvaps) in #1569).
|
||||
|
||||
### Kubernetes
|
||||
|
||||
* **[kubernetes] Make worker version configurable**: Platform administrators can now configure Kubernetes worker node versions independently from control plane versions, providing more flexibility ([**@lllamnyp**](https://github.com/lllamnyp) in #1619).
|
||||
* **[kubernetes] Use controlPlane.replicas field**: Fixed managed Kubernetes app to properly use the `controlPlane.replicas` field instead of hardcoding the value ([**@lllamnyp**](https://github.com/lllamnyp) in #1556).
|
||||
* **[kubernetes] Helm hooks for cleanup**: Added Helm hooks for cleanup operations in Kubernetes app ([**@lllamnyp**](https://github.com/lllamnyp) in #1606).
|
||||
|
||||
### API & Platform
|
||||
|
||||
* **[api] Efficient listing of TenantNamespaces**: Optimized TenantNamespace listing by replacing per-namespace SubjectAccessReview calls with group-based rolebinding checks, significantly reducing API latency ([**@lllamnyp**](https://github.com/lllamnyp) in #1507).
|
||||
* **[api] Use shared informer cache**: Optimized API server by using shared informer cache, reducing API server load and improving performance ([**@lllamnyp**](https://github.com/lllamnyp) in #1539).
|
||||
* **[api] Fix representation of dynamic list kinds**: Fixed API representation of dynamic list kinds for better compatibility ([**@lllamnyp**](https://github.com/lllamnyp) in #1630).
|
||||
* **[api] Delete previous instance when changing type**: API now properly deletes previous instance when changing application type ([**@lllamnyp**](https://github.com/lllamnyp) in #1579).
|
||||
|
||||
### Applications
|
||||
|
||||
* **[tenant] Allow listing workloads**: Enabled listing of workloads for tenants, improving visibility and management of tenant resources ([**@kvaps**](https://github.com/kvaps) in #1576).
|
||||
* **[apps] Make VM service user facing**: Virtual machine services are now marked as user-facing, improving service discovery and visibility in the dashboard ([**@lllamnyp**](https://github.com/lllamnyp) in #1523).
|
||||
* **[foundationdb] Upgrade FDB app for latest Cozy**: Upgraded FoundationDB application for compatibility with latest Cozystack version ([**@lllamnyp**](https://github.com/lllamnyp) in #1505).
|
||||
|
||||
### Storage & Backups
|
||||
|
||||
* **[seaweedfs] Update SeaweedFS v3.99 and deploy S3 as stacked service**: Updated SeaweedFS to version 3.99 and deployed S3 gateway as a stacked service for better integration and performance ([**@kvaps**](https://github.com/kvaps) in #1562).
|
||||
* **[seaweedfs] Allow users to discover their buckets**: Users can now discover and list their S3 buckets in SeaweedFS, improving usability and bucket management ([**@kvaps**](https://github.com/kvaps) in #1528).
|
||||
* **[velero] Set defaultItemOperationTimeout=24h**: Set default item operation timeout to 24 hours for Velero backups, preventing timeouts on large backup operations ([**@kvaps**](https://github.com/kvaps) in #1542).
|
||||
|
||||
### Monitoring & Operations
|
||||
|
||||
* **[monitoring] add settings alert for slack**: Added Slack integration configuration for Alerta alerts, enabling notifications to Slack channels ([**@scooby87**](https://github.com/scooby87) in #1545).
|
||||
|
||||
---
|
||||
|
||||
## Improvements (minor)
|
||||
|
||||
* **[lineage] Separate webhook from cozy controller**: Separated the lineage-controller-webhook from cozystack-controller into a separate daemonset component deployed on all control-plane nodes, reducing API server latency ([**@lllamnyp**](https://github.com/lllamnyp) in #1515).
|
||||
* **[dashboard] Show service LB IP**: Fixed JSON path issue to correctly display Service LoadBalancer IPs in the dashboard table view ([**@lllamnyp**](https://github.com/lllamnyp) in #1524).
|
||||
* **[dashboard] Update openapi-ui v1.0.3 + fixes**: Updated OpenAPI UI to version 1.0.3 with various fixes and improvements ([**@kvaps**](https://github.com/kvaps) in #1564).
|
||||
* **[dashboard-controller] Move badges generation logic to internal dashboard component**: Moved badges generation logic to internal dashboard component for better code organization ([**@kvaps**](https://github.com/kvaps) in #1567).
|
||||
* **[bucket] Expose bucket name in secrets**: Bucket names are now exposed in secrets for better integration with applications ([**@lllamnyp**](https://github.com/lllamnyp) in #1518).
|
||||
* **[platform] Better migration for 0.36.2->0.37.2+**: Improved migration script for users upgrading directly from 0.36.2 to 0.37.2+ ([**@lllamnyp**](https://github.com/lllamnyp) in #1521).
|
||||
* **[cozy-lib] Improve flatten function**: Improved flatten function in cozy-lib with better handling of complex resource structures ([**@lllamnyp**](https://github.com/lllamnyp) in #1647).
|
||||
* **[dx] JSDoc compatible syntax for values.yaml**: Added JSDoc compatible syntax for values.yaml documentation ([**@kvaps**](https://github.com/kvaps) in #1536).
|
||||
* **[system] Tune kubevirt rollout and eviction settings**: Tuned KubeVirt rollout and eviction settings for better stability ([**@nbykov0**](https://github.com/nbykov0) in #1544).
|
||||
* **[system] multus: update to the latest version**: Updated Multus CNI to the latest version ([**@nbykov0**](https://github.com/nbykov0) in #1628).
|
||||
* **[system] kubeovn: increase limits**: Increased resource limits for Kube-OVN components to improve stability and performance ([**@nbykov0**](https://github.com/nbykov0) in #1629).
|
||||
* **[linstor] Update Piraeus Operator to v2.10.1 to enable RWX support**: Updated Piraeus Operator to v2.10.1, enabling ReadWriteMany (RWX) volume support ([**@kvaps**](https://github.com/kvaps) in #1650).
|
||||
* **[ci,dx] Bump MariaDB operator version**: Bumped MariaDB operator version for latest features and bug fixes ([**@IvanHunters**](https://github.com/IvanHunters) in #1646).
|
||||
|
||||
---
|
||||
|
||||
## Bug fixes
|
||||
|
||||
* **[api] Fix RBAC for listing of TenantNamespaces and handle system:masters**: Fixed regression in TenantNamespace listing RBAC and added proper handling for system:masters group ([**@kvaps**](https://github.com/kvaps) in #1511).
|
||||
* **[api] Fix listing tenantnamespaces for non-oidc users**: Fixed TenantNamespace listing functionality for users not using OIDC authentication ([**@kvaps**](https://github.com/kvaps) in #1517).
|
||||
* **[dashboard] Fix logout**: Fixed dashboard logout functionality to properly clear session and redirect users ([**@kvaps**](https://github.com/kvaps) in #1510).
|
||||
* **[installer] Add additional check to wait for lineage-webhook**: Added additional readiness check to ensure lineage-webhook is fully ready before proceeding with installation ([**@kvaps**](https://github.com/kvaps) in #1506).
|
||||
* **[lineage] Check for nil chart in HelmRelease**: Added nil check to prevent crashes when lineage webhook encounters HelmReleases using `chartRef` instead of `chart` ([**@lllamnyp**](https://github.com/lllamnyp) in #1525).
|
||||
* **[kamaji] Respect 3rd party labels**: Applied patch to Kamaji controller to respect third-party labels, preventing reconciliation loops ([**@lllamnyp**](https://github.com/lllamnyp) in #1531).
|
||||
* **[redis-operator] Build patched operator in-tree**: Moved Redis operator build into Cozystack organization and patched it to prevent overwriting third-party labels ([**@lllamnyp**](https://github.com/lllamnyp) in #1547).
|
||||
* **[mariadb-operator] Add post-delete job to remove PVCs**: Added post-delete job to automatically remove PersistentVolumeClaims when MariaDB instances are deleted ([**@IvanHunters**](https://github.com/IvanHunters) in #1553).
|
||||
* **[seaweedfs] Fix migration to v3.99**: Fixed migration issues when upgrading SeaweedFS to version 3.99 ([**@kvaps**](https://github.com/kvaps) in #1572).
|
||||
* **[nats] Merge container spec, not podTemplate**: Fixed NATS configuration to properly merge container specifications instead of podTemplate ([**@lllamnyp**](https://github.com/lllamnyp) in #1571).
|
||||
* **[nats] Fixes for NATS App Helm chart, fix template issues with config.merge**: Fixed template issues in NATS Helm chart related to config.merge value ([**@insignia96**](https://github.com/insignia96) in #1583).
|
||||
* **[nats] Fix NATS app chart to use existing secret credentials when present**: Fixed NATS app chart to use existing secret credentials when present, preventing credential regeneration ([**@insignia96**](https://github.com/insignia96) in #1599).
|
||||
* **[kubevirt] Fix: kubevirt metrics rule**: Fixed KubeVirt metrics rule configuration ([**@kvaps**](https://github.com/kvaps) in #1584).
|
||||
* **[controller] Remove crdmem, handle DaemonSet**: Removed crdmem and improved DaemonSet handling in controller ([**@lllamnyp**](https://github.com/lllamnyp) in #1555).
|
||||
* **[dashboard] Revert reconciler removal**: Reverted reconciler removal to restore proper dashboard functionality ([**@lllamnyp**](https://github.com/lllamnyp) in #1559).
|
||||
* **[dashboard-controller] Fix static resources reconciliation and showing secrets**: Fixed static resources reconciliation and improved secret display in dashboard controller ([**@kvaps**](https://github.com/kvaps) in #1615).
|
||||
* **[cozystack-api][dashboard] Fix filtering for application services/ingresses/secrets**: Fixed filtering functionality for application services, ingresses, and secrets in both API and dashboard ([**@kvaps**](https://github.com/kvaps) in #1612).
|
||||
* **[virtual-machine] Revert per-vm network policies**: Reverted per-VM network policies to previous behavior ([**@kvaps**](https://github.com/kvaps) in #1611).
|
||||
* **[cozy-lib] Fix: handling resources=nil**: Fixed handling of nil resources in cozy-lib templates ([**@kvaps**](https://github.com/kvaps) in #1607).
|
||||
* **[cozy-lib] Fix malformed ResourceQuota rendering for LoadBalancer services**: Fixed malformed ResourceQuota rendering for LoadBalancer services in cozy-lib templates ([**@IvanHunters**](https://github.com/IvanHunters) in #1642).
|
||||
* **[kubernetes] Cleanup loadbalancer services**: Added cleanup functionality for load balancer services in Kubernetes app ([**@lllamnyp**](https://github.com/lllamnyp) in #1631).
|
||||
* **[rbac] Fix permissions for high-privilege users**: Fixed RBAC permissions for high-privilege users, ensuring proper access control ([**@lllamnyp**](https://github.com/lllamnyp) in #1622).
|
||||
* **[vpc] Fix access to subnet details configmap**: Fixed access to subnet details configmap in VPC functionality ([**@lllamnyp**](https://github.com/lllamnyp) in #1638).
|
||||
* **[api,lineage] Ensure node-local traffic**: Ensured node-local traffic handling for API and lineage components ([**@lllamnyp**](https://github.com/lllamnyp) in #1554).
|
||||
* **[extra] ingress: rm spaces from external ip list**: Removed spaces from external IP list in ingress configuration, fixing formatting issues ([**@nbykov0**](https://github.com/nbykov0) in #1652).
|
||||
* **scripts: fix 20 migration**: Fixed migration script #20 to ensure proper execution during upgrades ([**@nbykov0**](https://github.com/nbykov0) in #1653).
|
||||
|
||||
---
|
||||
|
||||
## Security
|
||||
|
||||
* **[redis] Bump Redis image version for security fixes**: Updated Redis image version to include latest security fixes, improving cluster security ([**@IvanHunters**](https://github.com/IvanHunters) in #1580).
|
||||
* **[flux] Close Flux Operator ports to external access**: Removed hostPort and hostNetwork from Flux Operator Deployment, ensuring ports 8080 and 8081 are only accessible within the cluster ([**@IvanHunters**](https://github.com/IvanHunters) in #1581).
|
||||
* **[ingress] Enforce HTTPS-only for API**: Added force-ssl-redirect annotation to default API Ingress, ensuring all HTTP traffic is redirected to HTTPS ([**@IvanHunters**](https://github.com/IvanHunters) in #1582).
|
||||
|
||||
---
|
||||
|
||||
## Dependencies & version updates
|
||||
|
||||
* **Update LINSTOR v1.32.3**: Updated LINSTOR to version 1.32.3 with latest features and bug fixes ([**@kvaps**](https://github.com/kvaps) in #1565).
|
||||
* **Update Talos Linux v1.11.3**: Updated Talos Linux to version 1.11.3 ([**@kvaps**](https://github.com/kvaps) in #1527).
|
||||
* **Update Kube-OVN v1.14.11**: Updated Kube-OVN to version 1.14.11 ([**@kvaps**](https://github.com/kvaps) in #1514).
|
||||
* **[linstor] Update Piraeus Operator to v2.10.1**: Updated Piraeus Operator to v2.10.1 to enable RWX support ([**@kvaps**](https://github.com/kvaps) in #1650).
|
||||
* **[system] multus: update to the latest version**: Updated Multus CNI to the latest version ([**@nbykov0**](https://github.com/nbykov0) in #1628).
|
||||
* **[ci,dx] Bump MariaDB operator version**: Bumped MariaDB operator version ([**@IvanHunters**](https://github.com/IvanHunters) in #1646).
|
||||
* **Increase strimzi memory limit**: Increased memory limit for Strimzi Kafka operator to improve stability and performance ([**@nbykov0**](https://github.com/nbykov0) in #1651).
|
||||
|
||||
---
|
||||
|
||||
## System Configuration
|
||||
|
||||
* **[system] kube-ovn: turn off enableLb**: Disabled load balancer functionality in Kube-OVN configuration ([**@nbykov0**](https://github.com/nbykov0) in #1548).
|
||||
* **[core] rm talos lldp extension**: Removed Talos LLDP extension from core configuration ([**@nbykov0**](https://github.com/nbykov0) in #1586).
|
||||
|
||||
---
|
||||
|
||||
## Development, Testing, and CI/CD
|
||||
|
||||
* **[tests] Make Kubernetes tests POSIX-compatible**: Replaced bash-specific constructs with POSIX-compliant code, ensuring tests work reliably with /bin/sh ([**@IvanHunters**](https://github.com/IvanHunters) in #1509).
|
||||
* **[ferretdb] fix tests**: Fixed FerretDB tests to ensure proper execution ([**@IvanHunters**](https://github.com/IvanHunters) in #1540).
|
||||
* **[e2e] Increase Kubernetes connection timeouts**: Increased connection and request timeouts in E2E tests when communicating with Kubernetes API ([**@IvanHunters**](https://github.com/IvanHunters) in #1570).
|
||||
* **[cozystack-controller] improve API tests**: Improved API tests for cozystack-controller ([**@kvaps**](https://github.com/kvaps) in #1617).
|
||||
* **[ci] Fix build from external forks**: Fixed build process to work correctly from external forks ([**@kvaps**](https://github.com/kvaps) in #1530).
|
||||
* **[ci,dx] Add unit tests for cozy-lib**: Added unit tests for cozy-lib to improve code quality and reliability ([**@lllamnyp**](https://github.com/lllamnyp) in #1643).
|
||||
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
* **[website] Add VPC page**: Added VPC documentation page explaining VPC features and usage ([**@nbykov0**](https://github.com/nbykov0) in cozystack/website@9ccac78).
|
||||
* **[website] Add VPC to auto-update list**: Added VPC to auto-update list in documentation ([**@nbykov0**](https://github.com/nbykov0) in cozystack/website@ca2bce6).
|
||||
* **[website] Update dashboard part in OIDC configuration doc**: Updated OIDC configuration documentation with dashboard information ([**@nbykov0**](https://github.com/nbykov0) in cozystack/website@6c44b93).
|
||||
* **[website] Update storage requirements**: Updated storage requirements documentation ([**@nbykov0**](https://github.com/nbykov0) in cozystack/website@cac3af6).
|
||||
* **[website] Add System Resource Planning Recommendations**: Added system resource planning recommendations documentation ([**@kvaps**](https://github.com/kvaps) in cozystack/website@c877c2a).
|
||||
* **[website] Optimize website for mobile devices**: Improved website layout and responsiveness for mobile devices ([**@kvaps**](https://github.com/kvaps) in cozystack/website@3ab2338).
|
||||
* **[website] Add OpenAPI UI**: Added OpenAPI UI documentation and integration ([**@kvaps**](https://github.com/kvaps) in cozystack/website@b1c1668).
|
||||
* **[website] Update Cozystack video in hero banner**: Updated hero banner with new Cozystack video ([**@kvaps**](https://github.com/kvaps) in cozystack/website@e351137).
|
||||
* **[website] Add screenshots carousel**: Added screenshots carousel to showcase Cozystack features ([**@kvaps**](https://github.com/kvaps) in cozystack/website@8422bd0).
|
||||
* **[website] Update LINSTOR documentation**: Updated LINSTOR guide and set failmode=continue for ZFS configurations ([**@kvaps**](https://github.com/kvaps) in cozystack/website@033804e).
|
||||
* **[website] Update managed apps reference**: Updated managed applications reference documentation ([**@kvaps**](https://github.com/kvaps) in cozystack/website@b886a74, cozystack/website@41c1849, cozystack/website@0ab71fd).
|
||||
* **[website] Update external apps documentation**: Updated documentation for external applications ([**@kvaps**](https://github.com/kvaps) in cozystack/website@565dad9).
|
||||
* **[website] Add naming conventions**: Added naming conventions documentation ([**@kvaps**](https://github.com/kvaps) in cozystack/website@b227abb).
|
||||
* **[website] Update golden image documentation**: Updated documentation for creating golden images for virtual machines ([**@kvaps**](https://github.com/kvaps) in cozystack/website@34c2f3a, cozystack/website@ef65593).
|
||||
* **[website] Fix documentation formatting**: Fixed alerts, infoboxes, tabs styles and main page formatting ([**@kvaps**](https://github.com/kvaps) in cozystack/website@e992e97, cozystack/website@b2c4dee).
|
||||
* **[website] Fix typo in blog article**: Fixed typo in blog article ([**@kvaps**](https://github.com/kvaps) in cozystack/website@0a4bbf3).
|
||||
* **[apps] vpc: more docs**: Added more VPC documentation ([**@nbykov0**](https://github.com/nbykov0) in #1594).
|
||||
* **[apps] vpc: fix typo in README**: Fixed typo in VPC README ([**@nbykov0**](https://github.com/nbykov0) in #1637).
|
||||
|
||||
---
|
||||
|
||||
## Additional Repositories
|
||||
|
||||
### boot-to-talos
|
||||
|
||||
* **[boot-to-talos] Introduce boot/install mode**: Introduced boot/install mode in boot-to-talos tool ([**@kvaps**](https://github.com/kvaps) in cozystack/boot-to-talos#5).
|
||||
|
||||
### cozypkg
|
||||
|
||||
* **[cozypkg] Handle valuesFiles from cozypkg.cozystack.io/values-files annotation**: Added support for handling valuesFiles from annotation in cozypkg ([**@kvaps**](https://github.com/kvaps) in cozystack/cozypkg#8).
|
||||
|
||||
---
|
||||
|
||||
## Refactors & chores
|
||||
|
||||
* **[dashboard] Migrate patches to upstream project**: Migrated dashboard patches to upstream project for better maintainability ([**@kvaps**](https://github.com/kvaps) in #1569).
|
||||
* **Update CODEOWNERS**: Updated CODEOWNERS file ([**@nbykov0**](https://github.com/nbykov0) in #1537).
|
||||
* **Add QOSI to ADOPTERS.md**: Added QOSI to adopters list ([**@tabu-a**](https://github.com/tabu-a) in #1589).
|
||||
|
||||
---
|
||||
|
||||
## Breaking changes & upgrade notes
|
||||
|
||||
No breaking changes in this release.
|
||||
|
||||
---
|
||||
|
||||
## Contributors
|
||||
|
||||
We'd like to thank all contributors who made this release possible:
|
||||
|
||||
* [**@IvanHunters**](https://github.com/IvanHunters)
|
||||
* [**@insignia96**](https://github.com/insignia96)
|
||||
* [**@kvaps**](https://github.com/kvaps)
|
||||
* [**@lllamnyp**](https://github.com/lllamnyp)
|
||||
* [**@nbykov0**](https://github.com/nbykov0)
|
||||
* [**@scooby87**](https://github.com/scooby87)
|
||||
* [**@tabu-a**](https://github.com/tabu-a)
|
||||
|
||||
### New Contributors
|
||||
|
||||
We're excited to welcome our first-time contributors:
|
||||
|
||||
* [**@tabu-a**](https://github.com/tabu-a) - First contribution!
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: [v0.37.0...v0.38.0](https://github.com/cozystack/cozystack/compare/v0.37.0...v0.38.0)
|
||||
|
||||
<!--
|
||||
https://github.com/cozystack/cozystack/releases/tag/v0.38.0
|
||||
-->
|
||||
19
docs/changelogs/v0.38.1.md
Normal file
19
docs/changelogs/v0.38.1.md
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
<!--
|
||||
https://github.com/cozystack/cozystack/releases/tag/v0.38.1
|
||||
-->
|
||||
|
||||
## Improvements
|
||||
|
||||
* **[seaweedfs] Extended CA certificate duration to reduce disruptive CA rotations**: Extended CA certificate duration to reduce disruptive CA rotations. ([**@IvanHunters**](https://github.com/IvanHunters) in #1657, #1666).
|
||||
* **[dashboard] Add config hash annotations to restart pods on config changes**: Added config hash annotations to restart pods when configuration changes, ensuring pods are automatically restarted when their configuration is updated ([**@kvaps**](https://github.com/kvaps) in #1662, #1665).
|
||||
|
||||
## Fixes
|
||||
|
||||
* **[tenant][kubernetes] Introduce better cleanup logic**: Improved cleanup logic for tenant Kubernetes resources, ensuring proper resource cleanup when tenants are deleted or updated ([**@kvaps**](https://github.com/kvaps) in #1661).
|
||||
* **[dashboard] Fix loading arrays in forms when editing existing objects**: Fixed issue where arrays in forms were not loading correctly when editing existing objects in the dashboard ([**@kvaps**](https://github.com/kvaps)).
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: [v0.38.0...v0.38.1](https://github.com/cozystack/cozystack/compare/v0.38.0...v0.38.1)
|
||||
|
||||
13
docs/changelogs/v0.38.2.md
Normal file
13
docs/changelogs/v0.38.2.md
Normal file
@@ -0,0 +1,13 @@
|
||||
|
||||
<!--
|
||||
https://github.com/cozystack/cozystack/releases/tag/v0.38.2
|
||||
-->
|
||||
|
||||
## Fixes
|
||||
|
||||
* **[api] Revert dynamic list kinds representation fix (fixes namespace deletion regression)**: Reverted changes from #1630 that caused a regression affecting namespace deletion and upgrades from previous versions. The regression caused namespace deletion failures with errors like "content is not a list: []unstructured.Unstructured" during namespace finalization. This revert restores compatibility with namespace deletion controller and fixes upgrade issues from previous versions, particularly when running migration 20 ([**@kvaps**](https://github.com/kvaps) in #1677).
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: [v0.38.1...v0.38.2](https://github.com/cozystack/cozystack/compare/v0.38.1...v0.38.2)
|
||||
|
||||
12
go.mod
12
go.mod
@@ -6,11 +6,16 @@ go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/fluxcd/helm-controller/api v1.1.0
|
||||
github.com/go-logr/logr v1.4.2
|
||||
github.com/go-logr/zapr v1.3.0
|
||||
github.com/google/gofuzz v1.2.0
|
||||
github.com/onsi/ginkgo/v2 v2.19.0
|
||||
github.com/onsi/gomega v1.33.1
|
||||
github.com/prometheus/client_golang v1.19.1
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/stretchr/testify v1.9.0
|
||||
go.uber.org/zap v1.27.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
k8s.io/api v0.31.2
|
||||
k8s.io/apiextensions-apiserver v0.31.2
|
||||
@@ -44,9 +49,7 @@ require (
|
||||
github.com/fluxcd/pkg/apis/meta v1.6.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-logr/zapr v1.3.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
@@ -74,7 +77,6 @@ require (
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_golang v1.19.1 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.55.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
@@ -94,7 +96,6 @@ require (
|
||||
go.opentelemetry.io/otel/trace v1.28.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/crypto v0.31.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
@@ -119,3 +120,6 @@ require (
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
|
||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||
)
|
||||
|
||||
// See: issues.k8s.io/135537
|
||||
replace k8s.io/apimachinery => github.com/cozystack/apimachinery v0.0.0-20251201201312-18e522a87614
|
||||
|
||||
6
go.sum
6
go.sum
@@ -18,6 +18,8 @@ github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr
|
||||
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cozystack/apimachinery v0.0.0-20251201201312-18e522a87614 h1:jH9elECUvhiIs3IMv3oS5k1JgCLVsSK6oU4dmq5gyW8=
|
||||
github.com/cozystack/apimachinery v0.0.0-20251201201312-18e522a87614/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -145,6 +147,8 @@ github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G
|
||||
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
@@ -291,8 +295,6 @@ k8s.io/api v0.31.2 h1:3wLBbL5Uom/8Zy98GRPXpJ254nEFpl+hwndmk9RwmL0=
|
||||
k8s.io/api v0.31.2/go.mod h1:bWmGvrGPssSK1ljmLzd3pwCQ9MgoTsRCuK35u6SygUk=
|
||||
k8s.io/apiextensions-apiserver v0.31.2 h1:W8EwUb8+WXBLu56ser5IudT2cOho0gAKeTOnywBLxd0=
|
||||
k8s.io/apiextensions-apiserver v0.31.2/go.mod h1:i+Geh+nGCJEGiCGR3MlBDkS7koHIIKWVfWeRFiOsUcM=
|
||||
k8s.io/apimachinery v0.31.2 h1:i4vUt2hPK56W6mlT7Ry+AO8eEsyxMD1U44NR22CLTYw=
|
||||
k8s.io/apimachinery v0.31.2/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
|
||||
k8s.io/apiserver v0.31.2 h1:VUzOEUGRCDi6kX1OyQ801m4A7AUPglpsmGvdsekmcI4=
|
||||
k8s.io/apiserver v0.31.2/go.mod h1:o3nKZR7lPlJqkU5I3Ove+Zx3JuoFjQobGX1Gctw6XuE=
|
||||
k8s.io/client-go v0.31.2 h1:Y2F4dxU5d3AQj+ybwSMqQnpZH9F30//1ObxOKlTI9yc=
|
||||
|
||||
145
hack/check-optional-repos.sh
Executable file
145
hack/check-optional-repos.sh
Executable file
@@ -0,0 +1,145 @@
|
||||
#!/bin/bash
|
||||
###############################################################################
|
||||
# check-optional-repos.sh - Check optional repositories for tags and commits #
|
||||
# during a release period #
|
||||
###############################################################################
|
||||
set -eu
|
||||
|
||||
# Function to ensure repository is cloned and up-to-date
|
||||
update_repo() {
|
||||
local repo_name=$1
|
||||
local repo_url="https://github.com/cozystack/${repo_name}.git"
|
||||
|
||||
mkdir -p _repos
|
||||
cd _repos
|
||||
|
||||
if [ -d "$repo_name" ]; then
|
||||
cd "$repo_name"
|
||||
git fetch --all --tags --force
|
||||
git checkout main 2>/dev/null || git checkout master
|
||||
git pull
|
||||
else
|
||||
git clone "$repo_url"
|
||||
cd "$repo_name"
|
||||
fi
|
||||
|
||||
cd ../..
|
||||
}
|
||||
|
||||
# Check if required parameters are provided
|
||||
if [ $# -lt 2 ]; then
|
||||
echo "Usage: $0 <RELEASE_START> <RELEASE_END>"
|
||||
echo "Example: $0 '2025-10-10 12:27:31 +0400' '2025-10-13 16:04:33 +0200'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
RELEASE_START="$1"
|
||||
RELEASE_END="$2"
|
||||
|
||||
# Get the script directory to return to it later
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
COZYSTACK_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
|
||||
cd "$COZYSTACK_ROOT"
|
||||
|
||||
echo "Checking optional repositories for tags and commits between:"
|
||||
echo " Start: $RELEASE_START"
|
||||
echo " End: $RELEASE_END"
|
||||
echo ""
|
||||
|
||||
# Loop through ALL optional repositories
|
||||
for repo_name in talm boot-to-talos cozypkg cozy-proxy; do
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "Checking repository: $repo_name"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
# Update/clone repository
|
||||
update_repo "$repo_name"
|
||||
|
||||
cd "_repos/$repo_name"
|
||||
REPO_NAME=$(basename "$(pwd)")
|
||||
git fetch --all --tags --force
|
||||
|
||||
# Check for tags matching release version pattern or created during release period
|
||||
TAGS=$(git for-each-ref --format='%(refname:short) %(creatordate)' refs/tags 2>/dev/null | \
|
||||
awk -v start="$RELEASE_START" -v end="$RELEASE_END" '$2 >= start && $2 <= end {print $1}' || true)
|
||||
|
||||
if [ -n "$TAGS" ]; then
|
||||
echo "Found tags in $repo_name: $TAGS"
|
||||
PREV_TAG=$(echo "$TAGS" | head -1)
|
||||
NEW_TAG=$(echo "$TAGS" | tail -1)
|
||||
|
||||
echo ""
|
||||
echo "Commits between $PREV_TAG and $NEW_TAG:"
|
||||
# Include merge commits to capture backports
|
||||
git log "$PREV_TAG..$NEW_TAG" --format="%H|%s|%an" 2>/dev/null | while IFS='|' read -r commit_hash subject author_name; do
|
||||
if [ -z "$commit_hash" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Get PR number from commit message
|
||||
COMMIT_MSG=$(git log -1 --format=%B "$commit_hash" 2>/dev/null || echo "")
|
||||
PR_NUMBER=$(echo "$COMMIT_MSG" | grep -oE '#[0-9]+' | head -1 | tr -d '#' || echo "")
|
||||
|
||||
# Get author: prioritize PR author, fallback to commit author
|
||||
GITHUB_USERNAME=""
|
||||
if [ -n "$PR_NUMBER" ]; then
|
||||
GITHUB_USERNAME=$(gh pr view "$PR_NUMBER" --repo "cozystack/$REPO_NAME" --json author --jq '.author.login // empty' 2>/dev/null || echo "")
|
||||
fi
|
||||
if [ -z "$GITHUB_USERNAME" ]; then
|
||||
GITHUB_USERNAME=$(gh api "repos/cozystack/$REPO_NAME/commits/$commit_hash" --jq '.author.login // empty' 2>/dev/null || echo "")
|
||||
fi
|
||||
|
||||
if [ -n "$PR_NUMBER" ]; then
|
||||
echo " $commit_hash|$subject|$author_name|$GITHUB_USERNAME|cozystack/$REPO_NAME#$PR_NUMBER"
|
||||
else
|
||||
echo " $commit_hash|$subject|$author_name|$GITHUB_USERNAME|cozystack/$REPO_NAME@${commit_hash:0:7}"
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "No tags found in $repo_name during release period"
|
||||
|
||||
# Check for commits by dates if no exact version tags
|
||||
# Include merge commits to capture backports
|
||||
COMMITS=$(git log --since="$RELEASE_START" --until="$RELEASE_END" --format="%H|%s|%an" 2>/dev/null || true)
|
||||
|
||||
if [ -n "$COMMITS" ]; then
|
||||
echo ""
|
||||
echo "Commits found by date range:"
|
||||
echo "$COMMITS" | while IFS='|' read -r commit_hash subject author_name; do
|
||||
if [ -z "$commit_hash" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Get PR number from commit message
|
||||
COMMIT_MSG=$(git log -1 --format=%B "$commit_hash" 2>/dev/null || echo "")
|
||||
PR_NUMBER=$(echo "$COMMIT_MSG" | grep -oE '#[0-9]+' | head -1 | tr -d '#' || echo "")
|
||||
|
||||
# Get author: prioritize PR author, fallback to commit author
|
||||
GITHUB_USERNAME=""
|
||||
if [ -n "$PR_NUMBER" ]; then
|
||||
GITHUB_USERNAME=$(gh pr view "$PR_NUMBER" --repo "cozystack/$REPO_NAME" --json author --jq '.author.login // empty' 2>/dev/null || echo "")
|
||||
fi
|
||||
if [ -z "$GITHUB_USERNAME" ]; then
|
||||
GITHUB_USERNAME=$(gh api "repos/cozystack/$REPO_NAME/commits/$commit_hash" --jq '.author.login // empty' 2>/dev/null || echo "")
|
||||
fi
|
||||
|
||||
if [ -n "$PR_NUMBER" ]; then
|
||||
echo " $commit_hash|$subject|$author_name|$GITHUB_USERNAME|cozystack/$REPO_NAME#$PR_NUMBER"
|
||||
else
|
||||
echo " $commit_hash|$subject|$author_name|$GITHUB_USERNAME|cozystack/$REPO_NAME@${commit_hash:0:7}"
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo "No commits found in $repo_name during release period"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
cd "$COZYSTACK_ROOT"
|
||||
done
|
||||
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "Finished checking all optional repositories"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
@@ -80,58 +80,41 @@ 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
|
||||
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
|
||||
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
|
||||
bash -c 'timeout 200s kubectl port-forward service/kubernetes-'"${test_name}"' -n tenant-test '"${port}"':6443 > /dev/null 2>&1 &'
|
||||
bash -c 'timeout 300s kubectl port-forward service/kubernetes-'"${test_name}"' -n tenant-test '"${port}"':6443 > /dev/null 2>&1 &'
|
||||
# Verify the Kubernetes version matches what we expect (retry for up to 20 seconds)
|
||||
timeout 20 sh -ec 'until kubectl --kubeconfig tenantkubeconfig version 2>/dev/null | grep -Fq "Server Version: ${k8s_version}"; do sleep 5; done'
|
||||
timeout 20 sh -ec 'until kubectl --kubeconfig tenantkubeconfig-'"${test_name}"' version 2>/dev/null | grep -Fq "Server Version: ${k8s_version}"; do sleep 5; done'
|
||||
|
||||
# Wait for the nodes to be ready (timeout after 2 minutes)
|
||||
timeout 2m bash -c '
|
||||
until [ "$(kubectl --kubeconfig tenantkubeconfig get nodes -o jsonpath="{.items[*].metadata.name}" | wc -w)" -eq 2 ]; do
|
||||
sleep 3
|
||||
timeout 3m bash -c '
|
||||
until [ "$(kubectl --kubeconfig tenantkubeconfig-'"${test_name}"' get nodes -o jsonpath="{.items[*].metadata.name}" | wc -w)" -eq 2 ]; do
|
||||
sleep 2
|
||||
done
|
||||
'
|
||||
# Verify the nodes are ready
|
||||
kubectl --kubeconfig tenantkubeconfig wait node --all --timeout=2m --for=condition=Ready
|
||||
kubectl --kubeconfig tenantkubeconfig 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 get nodes -o jsonpath='{.items[*].status.nodeInfo.kubeletVersion}')
|
||||
versions=$(kubectl --kubeconfig "tenantkubeconfig-${test_name}" \
|
||||
get nodes -o jsonpath='{.items[*].status.nodeInfo.kubeletVersion}')
|
||||
|
||||
node_ok=true
|
||||
|
||||
case "$k8s_version" in
|
||||
v1.32*)
|
||||
echo "⚠️ TODO: Temporary stub — allowing nodes with v1.33 while k8s_version is v1.32"
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
for v in $versions; do
|
||||
case "$k8s_version" in
|
||||
v1.32|v1.32.*)
|
||||
case "$v" in
|
||||
v1.32 | v1.32.* | v1.32-* | v1.33 | v1.33.* | v1.33-*)
|
||||
;;
|
||||
*)
|
||||
node_ok=false
|
||||
break
|
||||
;;
|
||||
esac
|
||||
case "$v" in
|
||||
"${k8s_version}" | "${k8s_version}".* | "${k8s_version}"-*)
|
||||
# acceptable
|
||||
;;
|
||||
*)
|
||||
case "$v" in
|
||||
"${k8s_version}" | "${k8s_version}".* | "${k8s_version}"-*)
|
||||
;;
|
||||
*)
|
||||
node_ok=false
|
||||
break
|
||||
;;
|
||||
esac
|
||||
node_ok=false
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
@@ -118,7 +118,7 @@ EOF
|
||||
}
|
||||
|
||||
@test "Check Cozystack API service" {
|
||||
kubectl wait --for=condition=Available apiservices/v1alpha1.apps.cozystack.io --timeout=2m
|
||||
kubectl wait --for=condition=Available apiservices/v1alpha1.apps.cozystack.io apiservices/v1alpha1.core.cozystack.io --timeout=2m
|
||||
}
|
||||
|
||||
@test "Configure Tenant and wait for applications" {
|
||||
|
||||
@@ -132,7 +132,6 @@ machine:
|
||||
- usermode_helper=disabled
|
||||
- name: zfs
|
||||
- name: spl
|
||||
- name: lldpd
|
||||
registries:
|
||||
mirrors:
|
||||
docker.io:
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
@test "Test OpenAPI v3 endpoint" {
|
||||
kubectl get -v7 --raw '/openapi/v3/apis/apps.cozystack.io/v1alpha1' > /dev/null
|
||||
kubectl get -v7 --raw '/openapi/v3/apis/core.cozystack.io/v1alpha1' > /dev/null
|
||||
}
|
||||
|
||||
@test "Test OpenAPI v2 endpoint (protobuf)" {
|
||||
@@ -18,3 +19,35 @@
|
||||
curl -sS --fail 'http://localhost:21234/openapi/v2?timeout=32s' -H 'Accept: application/com.github.proto-openapi.spec.v2@v1.0+protobuf' > /dev/null
|
||||
)
|
||||
}
|
||||
|
||||
@test "Test kinds" {
|
||||
val=$(kubectl get --raw /apis/apps.cozystack.io/v1alpha1/tenants | jq -r '.kind')
|
||||
if [ "$val" != "TenantList" ]; then
|
||||
echo "Expected kind to be TenantList, got $val"
|
||||
exit 1
|
||||
fi
|
||||
val=$(kubectl get --raw /apis/apps.cozystack.io/v1alpha1/tenants | jq -r '.items[0].kind')
|
||||
if [ "$val" != "Tenant" ]; then
|
||||
echo "Expected kind to be Tenant, got $val"
|
||||
exit 1
|
||||
fi
|
||||
val=$(kubectl get --raw /apis/apps.cozystack.io/v1alpha1/ingresses | jq -r '.kind')
|
||||
if [ "$val" != "IngressList" ]; then
|
||||
echo "Expected kind to be IngressList, got $val"
|
||||
exit 1
|
||||
fi
|
||||
val=$(kubectl get --raw /apis/apps.cozystack.io/v1alpha1/ingresses | jq -r '.items[0].kind')
|
||||
if [ "$val" != "Ingress" ]; then
|
||||
echo "Expected kind to be Ingress, got $val"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
@test "Create and delete namespace" {
|
||||
kubectl create ns cozy-test-create-and-delete-namespace --dry-run=client -o yaml | kubectl apply -f -
|
||||
if ! kubectl delete ns cozy-test-create-and-delete-namespace; then
|
||||
echo "Failed to delete namespace"
|
||||
kubectl describe ns cozy-test-create-and-delete-namespace
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
59
hack/helm-unit-tests.sh
Executable file
59
hack/helm-unit-tests.sh
Executable file
@@ -0,0 +1,59 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
# Script to run unit tests for all Helm charts.
|
||||
# It iterates through directories in packages/apps, packages/extra,
|
||||
# packages/system, and packages/library and runs the 'test' Makefile
|
||||
# target if it exists.
|
||||
|
||||
FAILED_DIRS_FILE="$(mktemp)"
|
||||
trap 'rm -f "$FAILED_DIRS_FILE"' EXIT
|
||||
|
||||
tests_found=0
|
||||
|
||||
check_and_run_test() {
|
||||
dir="$1"
|
||||
makefile="$dir/Makefile"
|
||||
|
||||
if [ ! -f "$makefile" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if make -C "$dir" -n test >/dev/null 2>&1; then
|
||||
echo "Running tests in $dir"
|
||||
tests_found=$((tests_found + 1))
|
||||
if ! make -C "$dir" test; then
|
||||
printf '%s\n' "$dir" >> "$FAILED_DIRS_FILE"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
for package_dir in packages/apps packages/extra packages/system packages/library; do
|
||||
if [ ! -d "$package_dir" ]; then
|
||||
echo "Warning: Directory $package_dir does not exist, skipping..." >&2
|
||||
continue
|
||||
fi
|
||||
|
||||
for dir in "$package_dir"/*; do
|
||||
[ -d "$dir" ] || continue
|
||||
check_and_run_test "$dir" || true
|
||||
done
|
||||
done
|
||||
|
||||
if [ "$tests_found" -eq 0 ]; then
|
||||
echo "No directories with 'test' Makefile targets found."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ -s "$FAILED_DIRS_FILE" ]; then
|
||||
echo "ERROR: Tests failed in the following directories:" >&2
|
||||
while IFS= read -r dir; do
|
||||
echo " - $dir" >&2
|
||||
done < "$FAILED_DIRS_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "All Helm unit tests passed."
|
||||
@@ -23,6 +23,13 @@ CODEGEN_PKG=${CODEGEN_PKG:-$(cd "${SCRIPT_ROOT}"; ls -d -1 ./vendor/k8s.io/code-
|
||||
API_KNOWN_VIOLATIONS_DIR="${API_KNOWN_VIOLATIONS_DIR:-"${SCRIPT_ROOT}/api/api-rules"}"
|
||||
UPDATE_API_KNOWN_VIOLATIONS="${UPDATE_API_KNOWN_VIOLATIONS:-true}"
|
||||
CONTROLLER_GEN="go run sigs.k8s.io/controller-tools/cmd/controller-gen@v0.16.4"
|
||||
TMPDIR=$(mktemp -d)
|
||||
COZY_CONTROLLER_CRDDIR=packages/system/cozystack-controller/crds
|
||||
COZY_RD_CRDDIR=packages/system/cozystack-resource-definition-crd/definition
|
||||
BACKUPS_CORE_CRDDIR=packages/system/backup-controller/definitions
|
||||
BACKUPSTRATEGY_CRDDIR=packages/system/backupstrategy-controller/definitions
|
||||
|
||||
trap 'rm -rf ${TMPDIR}' EXIT
|
||||
|
||||
source "${CODEGEN_PKG}/kube_codegen.sh"
|
||||
|
||||
@@ -53,6 +60,12 @@ kube::codegen::gen_openapi \
|
||||
"${SCRIPT_ROOT}/pkg/apis"
|
||||
|
||||
$CONTROLLER_GEN object:headerFile="hack/boilerplate.go.txt" paths="./api/..."
|
||||
$CONTROLLER_GEN rbac:roleName=manager-role crd paths="./api/..." output:crd:artifacts:config=packages/system/cozystack-controller/crds
|
||||
mv packages/system/cozystack-controller/crds/cozystack.io_cozystackresourcedefinitions.yaml \
|
||||
packages/system/cozystack-resource-definition-crd/definition/cozystack.io_cozystackresourcedefinitions.yaml
|
||||
$CONTROLLER_GEN rbac:roleName=manager-role crd paths="./api/..." output:crd:artifacts:config=${TMPDIR}
|
||||
|
||||
mv ${TMPDIR}/cozystack.io_cozystackresourcedefinitions.yaml \
|
||||
${COZY_RD_CRDDIR}/cozystack.io_cozystackresourcedefinitions.yaml
|
||||
|
||||
mv ${TMPDIR}/backups.cozystack.io*.yaml ${BACKUPS_CORE_CRDDIR}/
|
||||
mv ${TMPDIR}/strategy.backups.cozystack.io*.yaml ${BACKUPSTRATEGY_CRDDIR}/
|
||||
|
||||
mv ${TMPDIR}/*.yaml ${COZY_CONTROLLER_CRDDIR}/
|
||||
|
||||
40
hack/upload-releasenotes.sh
Executable file
40
hack/upload-releasenotes.sh
Executable file
@@ -0,0 +1,40 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ $# -ne 1 ]; then
|
||||
echo "Usage: $0 <version>"
|
||||
echo "Example: 0.37.*"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERSION_PATTERN="$1"
|
||||
|
||||
# Collect matching files first
|
||||
FILES=$(find docs/changelogs -name "v${VERSION_PATTERN}.md" 2>/dev/null || true)
|
||||
|
||||
if [ -z "$FILES" ]; then
|
||||
echo "No changelog files found matching pattern: v${VERSION_PATTERN}.md"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Process each file
|
||||
echo "$FILES" | while IFS= read -r file; do
|
||||
if [ -z "$file" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Extract version from filename safely (basename without extension)
|
||||
version=$(basename "$file" .md)
|
||||
|
||||
if [ -z "$version" ]; then
|
||||
echo "Warning: Could not extract version from file: $file"
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "Uploading release notes for version: $version"
|
||||
|
||||
# Check exit status of gh release edit
|
||||
if ! gh release edit "$version" --notes-file "docs/changelogs/${version}.md"; then
|
||||
echo "Error: Failed to upload release notes for version: $version"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
28
internal/backupcontroller/factory/backupjob.go
Normal file
28
internal/backupcontroller/factory/backupjob.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package factory
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func BackupJob(p *backupsv1alpha1.Plan, scheduledFor time.Time) *backupsv1alpha1.BackupJob {
|
||||
job := &backupsv1alpha1.BackupJob{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("%s-%d", p.Name, scheduledFor.Unix()/60),
|
||||
Namespace: p.Namespace,
|
||||
},
|
||||
Spec: backupsv1alpha1.BackupJobSpec{
|
||||
PlanRef: &corev1.LocalObjectReference{
|
||||
Name: p.Name,
|
||||
},
|
||||
ApplicationRef: *p.Spec.ApplicationRef.DeepCopy(),
|
||||
StorageRef: *p.Spec.StorageRef.DeepCopy(),
|
||||
StrategyRef: *p.Spec.StrategyRef.DeepCopy(),
|
||||
},
|
||||
}
|
||||
return job
|
||||
}
|
||||
31
internal/backupcontroller/jobstrategy_controller.go
Normal file
31
internal/backupcontroller/jobstrategy_controller.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package backupcontroller
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
||||
backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1"
|
||||
)
|
||||
|
||||
// BackupJobStrategyReconciler reconciles BackupJob with a strategy referencing
|
||||
// Job.strategy.backups.cozystack.io objects.
|
||||
type BackupJobStrategyReconciler struct {
|
||||
client.Client
|
||||
Scheme *runtime.Scheme
|
||||
}
|
||||
|
||||
func (r *BackupJobStrategyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
_ = log.FromContext(ctx)
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// SetupWithManager registers our controller with the Manager and sets up watches.
|
||||
func (r *BackupJobStrategyReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&backupsv1alpha1.BackupJob{}).
|
||||
Complete(r)
|
||||
}
|
||||
104
internal/backupcontroller/plan_controller.go
Normal file
104
internal/backupcontroller/plan_controller.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package backupcontroller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
cron "github.com/robfig/cron/v3"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
||||
backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1"
|
||||
"github.com/cozystack/cozystack/internal/backupcontroller/factory"
|
||||
)
|
||||
|
||||
const (
|
||||
minRequeueDelay = 30 * time.Second
|
||||
startingDeadlineSeconds = 300 * time.Second
|
||||
)
|
||||
|
||||
// PlanReconciler reconciles a Plan object
|
||||
type PlanReconciler struct {
|
||||
client.Client
|
||||
Scheme *runtime.Scheme
|
||||
}
|
||||
|
||||
func (r *PlanReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
log := log.FromContext(ctx)
|
||||
|
||||
log.V(2).Info("reconciling")
|
||||
|
||||
p := &backupsv1alpha1.Plan{}
|
||||
|
||||
if err := r.Get(ctx, client.ObjectKey{Namespace: req.Namespace, Name: req.Name}, p); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
log.V(3).Info("Plan not found")
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
tCheck := time.Now().Add(-startingDeadlineSeconds)
|
||||
sch, err := cron.ParseStandard(p.Spec.Schedule.Cron)
|
||||
if err != nil {
|
||||
errWrapped := fmt.Errorf("could not parse cron %s: %w", p.Spec.Schedule.Cron, err)
|
||||
log.Error(err, "could not parse cron", "cron", p.Spec.Schedule.Cron)
|
||||
meta.SetStatusCondition(&p.Status.Conditions, metav1.Condition{
|
||||
Type: backupsv1alpha1.PlanConditionError,
|
||||
Status: metav1.ConditionTrue,
|
||||
Reason: "Failed to parse cron spec",
|
||||
Message: errWrapped.Error(),
|
||||
})
|
||||
if err := r.Status().Update(ctx, p); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
// Clear error condition if cron parsing succeeds
|
||||
if condition := meta.FindStatusCondition(p.Status.Conditions, backupsv1alpha1.PlanConditionError); condition != nil && condition.Status == metav1.ConditionTrue {
|
||||
meta.SetStatusCondition(&p.Status.Conditions, metav1.Condition{
|
||||
Type: backupsv1alpha1.PlanConditionError,
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: "Cron spec is valid",
|
||||
Message: "The cron schedule has been successfully parsed",
|
||||
})
|
||||
if err := r.Status().Update(ctx, p); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
}
|
||||
|
||||
tNext := sch.Next(tCheck)
|
||||
|
||||
if time.Now().Before(tNext) {
|
||||
return ctrl.Result{RequeueAfter: tNext.Sub(time.Now())}, nil
|
||||
}
|
||||
|
||||
job := factory.BackupJob(p, tNext)
|
||||
if err := controllerutil.SetControllerReference(p, job, r.Scheme); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
if err := r.Create(ctx, job); err != nil {
|
||||
if apierrors.IsAlreadyExists(err) {
|
||||
return ctrl.Result{RequeueAfter: startingDeadlineSeconds}, nil
|
||||
}
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
return ctrl.Result{RequeueAfter: startingDeadlineSeconds}, nil
|
||||
}
|
||||
|
||||
// SetupWithManager registers our controller with the Manager and sets up watches.
|
||||
func (r *PlanReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&backupsv1alpha1.Plan{}).
|
||||
Complete(r)
|
||||
}
|
||||
@@ -58,8 +58,8 @@ func (m *Manager) ensureBreadcrumb(ctx context.Context, crd *cozyv1alpha1.Cozyst
|
||||
"breadcrumbItems": items,
|
||||
}
|
||||
|
||||
_, err := controllerutil.CreateOrUpdate(ctx, m.client, obj, func() error {
|
||||
if err := controllerutil.SetOwnerReference(crd, obj, m.scheme); err != nil {
|
||||
_, err := controllerutil.CreateOrUpdate(ctx, m.Client, obj, func() error {
|
||||
if err := controllerutil.SetOwnerReference(crd, obj, m.Scheme); err != nil {
|
||||
return err
|
||||
}
|
||||
// Add dashboard labels to dynamic resources
|
||||
|
||||
@@ -30,10 +30,6 @@ func (m *Manager) ensureCustomColumnsOverride(ctx context.Context, crd *cozyv1al
|
||||
name := fmt.Sprintf("stock-namespace-%s.%s.%s", g, v, plural)
|
||||
id := fmt.Sprintf("stock-namespace-/%s/%s/%s", g, v, plural)
|
||||
|
||||
// Badge content & color derived from kind
|
||||
badgeText := initialsFromKind(kind) // e.g., "VirtualMachine" -> "VM", "Bucket" -> "B"
|
||||
badgeColor := hexColorForKind(kind) // deterministic, dark enough for white text
|
||||
|
||||
obj := &dashv1alpha1.CustomColumnsOverride{}
|
||||
obj.SetName(name)
|
||||
|
||||
@@ -62,25 +58,11 @@ func (m *Manager) ensureCustomColumnsOverride(ctx context.Context, crd *cozyv1al
|
||||
},
|
||||
"children": []any{
|
||||
map[string]any{
|
||||
"type": "antdText",
|
||||
"type": "ResourceBadge",
|
||||
"data": map[string]any{
|
||||
"id": "header-badge",
|
||||
"text": badgeText,
|
||||
"title": strings.ToLower(kind), // optional tooltip
|
||||
"style": map[string]any{
|
||||
"backgroundColor": badgeColor,
|
||||
"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",
|
||||
},
|
||||
"value": kind,
|
||||
// abbreviation auto-generated by ResourceBadge from value
|
||||
},
|
||||
},
|
||||
map[string]any{
|
||||
@@ -145,8 +127,8 @@ func (m *Manager) ensureCustomColumnsOverride(ctx context.Context, crd *cozyv1al
|
||||
},
|
||||
}
|
||||
|
||||
_, err := controllerutil.CreateOrUpdate(ctx, m.client, obj, func() error {
|
||||
if err := controllerutil.SetOwnerReference(crd, obj, m.scheme); err != nil {
|
||||
_, err := controllerutil.CreateOrUpdate(ctx, m.Client, obj, func() error {
|
||||
if err := controllerutil.SetOwnerReference(crd, obj, m.Scheme); err != nil {
|
||||
return err
|
||||
}
|
||||
// Add dashboard labels to dynamic resources
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
)
|
||||
|
||||
// ensureCustomFormsOverride creates or updates a CustomFormsOverride resource for the given CRD
|
||||
@@ -45,16 +46,25 @@ func (m *Manager) ensureCustomFormsOverride(ctx context.Context, crd *cozyv1alph
|
||||
}
|
||||
}
|
||||
|
||||
// Build schema with multilineString for string fields without enum
|
||||
l := log.FromContext(ctx)
|
||||
schema, err := buildMultilineStringSchema(crd.Spec.Application.OpenAPISchema)
|
||||
if err != nil {
|
||||
// If schema parsing fails, log the error and use an empty schema
|
||||
l.Error(err, "failed to build multiline string schema, using empty schema", "crd", crd.Name)
|
||||
schema = map[string]any{}
|
||||
}
|
||||
|
||||
spec := map[string]any{
|
||||
"customizationId": customizationID,
|
||||
"hidden": hidden,
|
||||
"sort": sort,
|
||||
"schema": map[string]any{}, // {}
|
||||
"schema": schema,
|
||||
"strategy": "merge",
|
||||
}
|
||||
|
||||
_, err := controllerutil.CreateOrUpdate(ctx, m.client, obj, func() error {
|
||||
if err := controllerutil.SetOwnerReference(crd, obj, m.scheme); err != nil {
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, m.Client, obj, func() error {
|
||||
if err := controllerutil.SetOwnerReference(crd, obj, m.Scheme); err != nil {
|
||||
return err
|
||||
}
|
||||
// Add dashboard labels to dynamic resources
|
||||
@@ -73,3 +83,112 @@ func (m *Manager) ensureCustomFormsOverride(ctx context.Context, crd *cozyv1alph
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// buildMultilineStringSchema parses OpenAPI schema and creates schema with multilineString
|
||||
// for all string fields inside spec that don't have enum
|
||||
func buildMultilineStringSchema(openAPISchema string) (map[string]any, error) {
|
||||
if openAPISchema == "" {
|
||||
return map[string]any{}, nil
|
||||
}
|
||||
|
||||
var root map[string]any
|
||||
if err := json.Unmarshal([]byte(openAPISchema), &root); err != nil {
|
||||
return nil, fmt.Errorf("cannot parse openAPISchema: %w", err)
|
||||
}
|
||||
|
||||
props, _ := root["properties"].(map[string]any)
|
||||
if props == nil {
|
||||
return map[string]any{}, nil
|
||||
}
|
||||
|
||||
schema := map[string]any{
|
||||
"properties": map[string]any{},
|
||||
}
|
||||
|
||||
// Check if there's a spec property
|
||||
specProp, ok := props["spec"].(map[string]any)
|
||||
if !ok {
|
||||
return map[string]any{}, nil
|
||||
}
|
||||
|
||||
specProps, ok := specProp["properties"].(map[string]any)
|
||||
if !ok {
|
||||
return map[string]any{}, nil
|
||||
}
|
||||
|
||||
// Create spec.properties structure in schema
|
||||
schemaProps := schema["properties"].(map[string]any)
|
||||
specSchema := map[string]any{
|
||||
"properties": map[string]any{},
|
||||
}
|
||||
schemaProps["spec"] = specSchema
|
||||
|
||||
// Process spec properties recursively
|
||||
processSpecProperties(specProps, specSchema["properties"].(map[string]any))
|
||||
|
||||
return schema, nil
|
||||
}
|
||||
|
||||
// processSpecProperties recursively processes spec properties and adds multilineString type
|
||||
// for string fields without enum
|
||||
func processSpecProperties(props map[string]any, schemaProps map[string]any) {
|
||||
for pname, raw := range props {
|
||||
sub, ok := raw.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
typ, _ := sub["type"].(string)
|
||||
|
||||
switch typ {
|
||||
case "string":
|
||||
// Check if this string field has enum
|
||||
if !hasEnum(sub) {
|
||||
// Add multilineString type for this field
|
||||
if schemaProps[pname] == nil {
|
||||
schemaProps[pname] = map[string]any{}
|
||||
}
|
||||
fieldSchema := schemaProps[pname].(map[string]any)
|
||||
fieldSchema["type"] = "multilineString"
|
||||
}
|
||||
case "object":
|
||||
// Recursively process nested objects
|
||||
if childProps, ok := sub["properties"].(map[string]any); ok {
|
||||
fieldSchema, ok := schemaProps[pname].(map[string]any)
|
||||
if !ok {
|
||||
fieldSchema = map[string]any{}
|
||||
schemaProps[pname] = fieldSchema
|
||||
}
|
||||
nestedSchemaProps, ok := fieldSchema["properties"].(map[string]any)
|
||||
if !ok {
|
||||
nestedSchemaProps = map[string]any{}
|
||||
fieldSchema["properties"] = nestedSchemaProps
|
||||
}
|
||||
processSpecProperties(childProps, nestedSchemaProps)
|
||||
}
|
||||
case "array":
|
||||
// Check if array items are objects with properties
|
||||
if items, ok := sub["items"].(map[string]any); ok {
|
||||
if itemProps, ok := items["properties"].(map[string]any); ok {
|
||||
// Create array item schema
|
||||
fieldSchema, ok := schemaProps[pname].(map[string]any)
|
||||
if !ok {
|
||||
fieldSchema = map[string]any{}
|
||||
schemaProps[pname] = fieldSchema
|
||||
}
|
||||
itemSchema, ok := fieldSchema["items"].(map[string]any)
|
||||
if !ok {
|
||||
itemSchema = map[string]any{}
|
||||
fieldSchema["items"] = itemSchema
|
||||
}
|
||||
itemSchemaProps, ok := itemSchema["properties"].(map[string]any)
|
||||
if !ok {
|
||||
itemSchemaProps = map[string]any{}
|
||||
itemSchema["properties"] = itemSchemaProps
|
||||
}
|
||||
processSpecProperties(itemProps, itemSchemaProps)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
171
internal/controller/dashboard/customformsoverride_test.go
Normal file
171
internal/controller/dashboard/customformsoverride_test.go
Normal file
@@ -0,0 +1,171 @@
|
||||
package dashboard
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBuildMultilineStringSchema(t *testing.T) {
|
||||
// Test OpenAPI schema with various field types
|
||||
openAPISchema := `{
|
||||
"properties": {
|
||||
"spec": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"simpleString": {
|
||||
"type": "string",
|
||||
"description": "A simple string field"
|
||||
},
|
||||
"stringWithEnum": {
|
||||
"type": "string",
|
||||
"enum": ["option1", "option2"],
|
||||
"description": "String with enum should be skipped"
|
||||
},
|
||||
"numberField": {
|
||||
"type": "number",
|
||||
"description": "Number field should be skipped"
|
||||
},
|
||||
"nestedObject": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"nestedString": {
|
||||
"type": "string",
|
||||
"description": "Nested string should get multilineString"
|
||||
},
|
||||
"nestedStringWithEnum": {
|
||||
"type": "string",
|
||||
"enum": ["a", "b"],
|
||||
"description": "Nested string with enum should be skipped"
|
||||
}
|
||||
}
|
||||
},
|
||||
"arrayOfObjects": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"itemString": {
|
||||
"type": "string",
|
||||
"description": "String in array item"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
schema, err := buildMultilineStringSchema(openAPISchema)
|
||||
if err != nil {
|
||||
t.Fatalf("buildMultilineStringSchema failed: %v", err)
|
||||
}
|
||||
|
||||
// Marshal to JSON for easier inspection
|
||||
schemaJSON, err := json.MarshalIndent(schema, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal schema: %v", err)
|
||||
}
|
||||
|
||||
t.Logf("Generated schema:\n%s", schemaJSON)
|
||||
|
||||
// Verify that simpleString has multilineString type
|
||||
props, ok := schema["properties"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatal("schema.properties is not a map")
|
||||
}
|
||||
|
||||
// Check spec property exists
|
||||
spec, ok := props["spec"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatal("spec not found in properties")
|
||||
}
|
||||
|
||||
specProps, ok := spec["properties"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatal("spec.properties is not a map")
|
||||
}
|
||||
|
||||
// Check simpleString
|
||||
simpleString, ok := specProps["simpleString"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatal("simpleString not found in spec.properties")
|
||||
}
|
||||
if simpleString["type"] != "multilineString" {
|
||||
t.Errorf("simpleString should have type multilineString, got %v", simpleString["type"])
|
||||
}
|
||||
|
||||
// Check stringWithEnum should not be present (or should not have multilineString)
|
||||
if stringWithEnum, ok := specProps["stringWithEnum"].(map[string]any); ok {
|
||||
if stringWithEnum["type"] == "multilineString" {
|
||||
t.Error("stringWithEnum should not have multilineString type")
|
||||
}
|
||||
}
|
||||
|
||||
// Check numberField should not be present
|
||||
if numberField, ok := specProps["numberField"].(map[string]any); ok {
|
||||
if numberField["type"] != nil {
|
||||
t.Error("numberField should not have any type override")
|
||||
}
|
||||
}
|
||||
|
||||
// Check nested object
|
||||
nestedObject, ok := specProps["nestedObject"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatal("nestedObject not found in spec.properties")
|
||||
}
|
||||
nestedProps, ok := nestedObject["properties"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatal("nestedObject.properties is not a map")
|
||||
}
|
||||
|
||||
// Check nestedString
|
||||
nestedString, ok := nestedProps["nestedString"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatal("nestedString not found in nestedObject.properties")
|
||||
}
|
||||
if nestedString["type"] != "multilineString" {
|
||||
t.Errorf("nestedString should have type multilineString, got %v", nestedString["type"])
|
||||
}
|
||||
|
||||
// Check array of objects
|
||||
arrayOfObjects, ok := specProps["arrayOfObjects"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatal("arrayOfObjects not found in spec.properties")
|
||||
}
|
||||
items, ok := arrayOfObjects["items"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatal("arrayOfObjects.items is not a map")
|
||||
}
|
||||
itemProps, ok := items["properties"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatal("arrayOfObjects.items.properties is not a map")
|
||||
}
|
||||
itemString, ok := itemProps["itemString"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatal("itemString not found in arrayOfObjects.items.properties")
|
||||
}
|
||||
if itemString["type"] != "multilineString" {
|
||||
t.Errorf("itemString should have type multilineString, got %v", itemString["type"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildMultilineStringSchemaEmpty(t *testing.T) {
|
||||
schema, err := buildMultilineStringSchema("")
|
||||
if err != nil {
|
||||
t.Fatalf("buildMultilineStringSchema failed on empty string: %v", err)
|
||||
}
|
||||
if len(schema) != 0 {
|
||||
t.Errorf("Expected empty schema for empty input, got %v", schema)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildMultilineStringSchemaInvalidJSON(t *testing.T) {
|
||||
schema, err := buildMultilineStringSchema("{invalid json")
|
||||
if err == nil {
|
||||
t.Error("Expected error for invalid JSON")
|
||||
}
|
||||
if schema != nil {
|
||||
t.Errorf("Expected nil schema for invalid JSON, got %v", schema)
|
||||
}
|
||||
}
|
||||
@@ -56,8 +56,8 @@ func (m *Manager) ensureCustomFormsPrefill(ctx context.Context, crd *cozyv1alpha
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, m.client, cfp, func() error {
|
||||
if err := controllerutil.SetOwnerReference(crd, cfp, m.scheme); err != nil {
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, m.Client, cfp, func() error {
|
||||
if err := controllerutil.SetOwnerReference(crd, cfp, m.Scheme); err != nil {
|
||||
return err
|
||||
}
|
||||
// Add dashboard labels to dynamic resources
|
||||
|
||||
@@ -44,6 +44,9 @@ func (m *Manager) ensureFactory(ctx context.Context, crd *cozyv1alpha1.Cozystack
|
||||
if flags.Secrets {
|
||||
tabs = append(tabs, secretsTab(kind))
|
||||
}
|
||||
if prefix, ok := vncTabPrefix(kind); ok {
|
||||
tabs = append(tabs, vncTab(prefix))
|
||||
}
|
||||
tabs = append(tabs, yamlTab(plural))
|
||||
|
||||
// Use unified factory creation
|
||||
@@ -53,7 +56,6 @@ func (m *Manager) ensureFactory(ctx context.Context, crd *cozyv1alpha1.Cozystack
|
||||
Kind: kind,
|
||||
Plural: plural,
|
||||
Title: strings.ToLower(plural),
|
||||
Size: BadgeSizeLarge,
|
||||
}
|
||||
|
||||
spec := createUnifiedFactory(config, tabs, []any{resourceFetch})
|
||||
@@ -61,8 +63,8 @@ func (m *Manager) ensureFactory(ctx context.Context, crd *cozyv1alpha1.Cozystack
|
||||
obj := &dashv1alpha1.Factory{}
|
||||
obj.SetName(factoryName)
|
||||
|
||||
_, err := controllerutil.CreateOrUpdate(ctx, m.client, obj, func() error {
|
||||
if err := controllerutil.SetOwnerReference(crd, obj, m.scheme); err != nil {
|
||||
_, err := controllerutil.CreateOrUpdate(ctx, m.Client, obj, func() error {
|
||||
if err := controllerutil.SetOwnerReference(crd, obj, m.Scheme); err != nil {
|
||||
return err
|
||||
}
|
||||
// Add dashboard labels to dynamic resources
|
||||
@@ -115,7 +117,7 @@ func detailsTab(kind, endpoint, schemaJSON string, keysOrder [][]string) map[str
|
||||
"gap": float64(6),
|
||||
},
|
||||
"children": []any{
|
||||
createUnifiedBadgeFromKind("ns-badge", "Namespace", "namespace", BadgeSizeMedium),
|
||||
createUnifiedBadgeFromKind("ns-badge", "Namespace"),
|
||||
antdLink("namespace-link",
|
||||
"{reqsJsonPath[0]['.metadata.namespace']['-']}",
|
||||
"/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace",
|
||||
@@ -151,6 +153,27 @@ func detailsTab(kind, endpoint, schemaJSON string, keysOrder [][]string) map[str
|
||||
}),
|
||||
paramsList,
|
||||
}
|
||||
if kind == "VirtualPrivateCloud" {
|
||||
rightColStack = append(rightColStack,
|
||||
antdFlexVertical("vpc-subnets-block", 4, []any{
|
||||
antdText("vpc-subnets-label", true, "Subnets", nil),
|
||||
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",
|
||||
"fieldSelector": map[string]any{
|
||||
"metadata.name": "virtualprivatecloud-{6}-subnets",
|
||||
},
|
||||
"pathToItems": []any{"items"},
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"key": "details",
|
||||
@@ -222,7 +245,7 @@ func workloadsTab(kind string) map[string]any {
|
||||
"baseprefix": "/openapi-ui",
|
||||
"customizationId": "factory-details-v1alpha1.cozystack.io.workloadmonitors",
|
||||
"pathToItems": []any{"items"},
|
||||
"labelsSelector": map[string]any{
|
||||
"labelSelector": map[string]any{
|
||||
"apps.cozystack.io/application.group": "apps.cozystack.io",
|
||||
"apps.cozystack.io/application.kind": kind,
|
||||
"apps.cozystack.io/application.name": "{reqs[0]['metadata','name']}",
|
||||
@@ -247,7 +270,7 @@ func servicesTab(kind string) map[string]any {
|
||||
"baseprefix": "/openapi-ui",
|
||||
"customizationId": "factory-details-v1.services",
|
||||
"pathToItems": []any{"items"},
|
||||
"labelsSelector": map[string]any{
|
||||
"labelSelector": map[string]any{
|
||||
"apps.cozystack.io/application.group": "apps.cozystack.io",
|
||||
"apps.cozystack.io/application.kind": kind,
|
||||
"apps.cozystack.io/application.name": "{reqs[0]['metadata','name']}",
|
||||
@@ -273,7 +296,7 @@ func ingressesTab(kind string) map[string]any {
|
||||
"baseprefix": "/openapi-ui",
|
||||
"customizationId": "factory-details-networking.k8s.io.v1.ingresses",
|
||||
"pathToItems": []any{"items"},
|
||||
"labelsSelector": map[string]any{
|
||||
"labelSelector": map[string]any{
|
||||
"apps.cozystack.io/application.group": "apps.cozystack.io",
|
||||
"apps.cozystack.io/application.kind": kind,
|
||||
"apps.cozystack.io/application.name": "{reqs[0]['metadata','name']}",
|
||||
@@ -294,12 +317,12 @@ func secretsTab(kind string) map[string]any {
|
||||
"type": "EnrichedTable",
|
||||
"data": map[string]any{
|
||||
"id": "secrets-table",
|
||||
"fetchUrl": "/api/clusters/{2}/k8s/apis/core.cozystack.io/v1alpha1/namespaces/{3}/tenantsecretstables",
|
||||
"fetchUrl": "/api/clusters/{2}/k8s/apis/core.cozystack.io/v1alpha1/namespaces/{3}/tenantsecrets",
|
||||
"clusterNamePartOfUrl": "{2}",
|
||||
"baseprefix": "/openapi-ui",
|
||||
"customizationId": "factory-details-v1alpha1.core.cozystack.io.tenantsecretstables",
|
||||
"customizationId": "factory-details-v1alpha1.core.cozystack.io.tenantsecrets",
|
||||
"pathToItems": []any{"items"},
|
||||
"labelsSelector": map[string]any{
|
||||
"labelSelector": map[string]any{
|
||||
"apps.cozystack.io/application.group": "apps.cozystack.io",
|
||||
"apps.cozystack.io/application.kind": kind,
|
||||
"apps.cozystack.io/application.name": "{reqs[0]['metadata','name']}",
|
||||
@@ -324,6 +347,7 @@ func yamlTab(plural string) map[string]any {
|
||||
"type": "builtin",
|
||||
"typeName": plural,
|
||||
"prefillValuesRequestIndex": float64(0),
|
||||
"readOnly": true,
|
||||
"substractHeight": float64(400),
|
||||
},
|
||||
},
|
||||
@@ -331,6 +355,36 @@ func yamlTab(plural string) map[string]any {
|
||||
}
|
||||
}
|
||||
|
||||
func vncTabPrefix(kind string) (string, bool) {
|
||||
switch kind {
|
||||
case "VirtualMachine":
|
||||
return "virtual-machine", true
|
||||
case "VMInstance":
|
||||
return "vm-instance", true
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
func vncTab(prefix string) map[string]any {
|
||||
return map[string]any{
|
||||
"key": "vnc",
|
||||
"label": "VNC",
|
||||
"children": []any{
|
||||
map[string]any{
|
||||
"type": "VMVNC",
|
||||
"data": map[string]any{
|
||||
"id": "vm-vnc",
|
||||
"cluster": "{2}",
|
||||
"namespace": "{reqsJsonPath[0]['.metadata.namespace']['-']}",
|
||||
"substractHeight": float64(400),
|
||||
"vmName": fmt.Sprintf("%s-{reqsJsonPath[0]['.metadata.name']['-']}", prefix),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------- OpenAPI → Right column ----------------
|
||||
|
||||
func buildOpenAPIParamsBlocks(schemaJSON string, keysOrder [][]string) []any {
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
package dashboard
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
@@ -57,97 +54,6 @@ func pickPlural(kind string, crd *cozyv1alpha1.CozystackResourceDefinition) stri
|
||||
return k + "s"
|
||||
}
|
||||
|
||||
// initialsFromKind splits CamelCase and returns the first letters in upper case.
|
||||
// "VirtualMachine" -> "VM"; "Bucket" -> "B".
|
||||
func initialsFromKind(kind string) string {
|
||||
parts := splitCamel(kind)
|
||||
if len(parts) == 0 {
|
||||
return strings.ToUpper(kind)
|
||||
}
|
||||
var b strings.Builder
|
||||
for _, p := range parts {
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
b.WriteString(strings.ToUpper(string(p[0])))
|
||||
// Limit to 3 chars to keep the badge compact (VM, PVC, etc.)
|
||||
if b.Len() >= 3 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// hexColorForKind returns a dark, saturated color (hex) derived from a stable hash of the kind.
|
||||
// We map the hash to an HSL hue; fix S/L for consistent readability with white text.
|
||||
func hexColorForKind(kind string) string {
|
||||
// Stable short hash (sha1 → bytes → hue)
|
||||
sum := sha1.Sum([]byte(kind))
|
||||
// Use first two bytes for hue [0..359]
|
||||
hue := int(sum[0])<<8 | int(sum[1])
|
||||
hue = hue % 360
|
||||
|
||||
// Fixed S/L chosen to contrast with white text:
|
||||
// S = 80%, L = 35% (dark enough so #fff is readable)
|
||||
r, g, b := hslToRGB(float64(hue), 0.80, 0.35)
|
||||
|
||||
return fmt.Sprintf("#%02x%02x%02x", r, g, b)
|
||||
}
|
||||
|
||||
// hslToRGB converts HSL (0..360, 0..1, 0..1) to sRGB (0..255).
|
||||
func hslToRGB(h float64, s float64, l float64) (uint8, uint8, uint8) {
|
||||
c := (1 - absFloat(2*l-1)) * s
|
||||
hp := h / 60.0
|
||||
x := c * (1 - absFloat(modFloat(hp, 2)-1))
|
||||
var r1, g1, b1 float64
|
||||
switch {
|
||||
case 0 <= hp && hp < 1:
|
||||
r1, g1, b1 = c, x, 0
|
||||
case 1 <= hp && hp < 2:
|
||||
r1, g1, b1 = x, c, 0
|
||||
case 2 <= hp && hp < 3:
|
||||
r1, g1, b1 = 0, c, x
|
||||
case 3 <= hp && hp < 4:
|
||||
r1, g1, b1 = 0, x, c
|
||||
case 4 <= hp && hp < 5:
|
||||
r1, g1, b1 = x, 0, c
|
||||
default:
|
||||
r1, g1, b1 = c, 0, x
|
||||
}
|
||||
m := l - c/2
|
||||
r := uint8(clamp01(r1+m) * 255.0)
|
||||
g := uint8(clamp01(g1+m) * 255.0)
|
||||
b := uint8(clamp01(b1+m) * 255.0)
|
||||
return r, g, b
|
||||
}
|
||||
|
||||
func absFloat(v float64) float64 {
|
||||
if v < 0 {
|
||||
return -v
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func modFloat(a, b float64) float64 {
|
||||
return a - b*float64(int(a/b))
|
||||
}
|
||||
|
||||
func clamp01(v float64) float64 {
|
||||
if v < 0 {
|
||||
return 0
|
||||
}
|
||||
if v > 1 {
|
||||
return 1
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// optional: tiny helper to expose the compact color hash (useful for debugging)
|
||||
func shortHashHex(s string) string {
|
||||
sum := sha1.Sum([]byte(s))
|
||||
return hex.EncodeToString(sum[:4])
|
||||
}
|
||||
|
||||
// ----------------------- Helpers (OpenAPI → values) -----------------------
|
||||
|
||||
// defaultOrZero returns the schema default if present; otherwise a reasonable zero value.
|
||||
@@ -295,12 +201,6 @@ func normalizeJSON(v any) any {
|
||||
}
|
||||
}
|
||||
|
||||
var camelSplitter = regexp.MustCompile(`(?m)([A-Z]+[a-z0-9]*|[a-z0-9]+)`)
|
||||
|
||||
func splitCamel(s string) []string {
|
||||
return camelSplitter.FindAllString(s, -1)
|
||||
}
|
||||
|
||||
// --- helpers for schema inspection ---
|
||||
|
||||
func isScalarType(n map[string]any) bool {
|
||||
|
||||
@@ -10,9 +10,12 @@ import (
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
managerpkg "sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
)
|
||||
|
||||
@@ -40,28 +43,51 @@ 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.
|
||||
type Manager struct {
|
||||
client client.Client
|
||||
scheme *runtime.Scheme
|
||||
crdListFn func(context.Context) ([]cozyv1alpha1.CozystackResourceDefinition, error)
|
||||
}
|
||||
|
||||
// Option pattern so callers can inject a custom lister.
|
||||
type Option func(*Manager)
|
||||
|
||||
// WithCRDListFunc overrides how Manager lists all CozystackResourceDefinitions.
|
||||
func WithCRDListFunc(fn func(context.Context) ([]cozyv1alpha1.CozystackResourceDefinition, error)) Option {
|
||||
return func(m *Manager) { m.crdListFn = fn }
|
||||
client.Client
|
||||
Scheme *runtime.Scheme
|
||||
}
|
||||
|
||||
// NewManager constructs a dashboard Manager.
|
||||
func NewManager(c client.Client, scheme *runtime.Scheme, opts ...Option) *Manager {
|
||||
m := &Manager{client: c, scheme: scheme}
|
||||
for _, o := range opts {
|
||||
o(m)
|
||||
}
|
||||
func NewManager(c client.Client, scheme *runtime.Scheme) *Manager {
|
||||
m := &Manager{Client: c, Scheme: scheme}
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *Manager) SetupWithManager(mgr ctrl.Manager) error {
|
||||
if err := ctrl.NewControllerManagedBy(mgr).
|
||||
Named("dashboard-reconciler").
|
||||
For(&cozyv1alpha1.CozystackResourceDefinition{}).
|
||||
Complete(m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return mgr.Add(managerpkg.RunnableFunc(func(ctx context.Context) error {
|
||||
if !mgr.GetCache().WaitForCacheSync(ctx) {
|
||||
return fmt.Errorf("dashboard static resources cache sync failed")
|
||||
}
|
||||
return m.ensureStaticResources(ctx)
|
||||
}))
|
||||
}
|
||||
|
||||
func (m *Manager) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
l := log.FromContext(ctx)
|
||||
|
||||
crd := &cozyv1alpha1.CozystackResourceDefinition{}
|
||||
|
||||
err := m.Get(ctx, types.NamespacedName{Name: req.Name}, crd)
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
if err := m.CleanupOrphanedResources(ctx); err != nil {
|
||||
l.Error(err, "Failed to cleanup orphaned dashboard resources")
|
||||
}
|
||||
return ctrl.Result{}, nil // no point in requeuing here
|
||||
}
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
return m.EnsureForCRD(ctx, crd)
|
||||
}
|
||||
|
||||
// EnsureForCRD is the single entry-point used by the controller.
|
||||
// Add more ensure* calls here as you implement support for other resources:
|
||||
//
|
||||
@@ -171,21 +197,11 @@ 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 {
|
||||
// Get all current CRDs to determine which resources should exist
|
||||
var allCRDs []cozyv1alpha1.CozystackResourceDefinition
|
||||
if m.crdListFn != nil {
|
||||
s, err := m.crdListFn(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
allCRDs = s
|
||||
} else {
|
||||
var crdList cozyv1alpha1.CozystackResourceDefinitionList
|
||||
if err := m.client.List(ctx, &crdList, &client.ListOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
allCRDs = crdList.Items
|
||||
var crdList cozyv1alpha1.CozystackResourceDefinitionList
|
||||
if err := m.List(ctx, &crdList, &client.ListOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
allCRDs := crdList.Items
|
||||
|
||||
// Build a set of expected resource names for each type
|
||||
expectedResources := m.buildExpectedResourceSet(allCRDs)
|
||||
@@ -349,7 +365,7 @@ func (m *Manager) cleanupResourceType(ctx context.Context, resourceType client.O
|
||||
}
|
||||
|
||||
// List with dashboard labels
|
||||
if err := m.client.List(ctx, list, m.getDashboardResourceSelector()); err != nil {
|
||||
if err := m.List(ctx, list, m.getDashboardResourceSelector()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -358,7 +374,7 @@ func (m *Manager) cleanupResourceType(ctx context.Context, resourceType client.O
|
||||
case *dashv1alpha1.CustomColumnsOverrideList:
|
||||
for _, item := range l.Items {
|
||||
if !expected[item.Name] {
|
||||
if err := m.client.Delete(ctx, &item); err != nil {
|
||||
if err := m.Delete(ctx, &item); err != nil {
|
||||
if !apierrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
@@ -369,7 +385,7 @@ func (m *Manager) cleanupResourceType(ctx context.Context, resourceType client.O
|
||||
case *dashv1alpha1.CustomFormsOverrideList:
|
||||
for _, item := range l.Items {
|
||||
if !expected[item.Name] {
|
||||
if err := m.client.Delete(ctx, &item); err != nil {
|
||||
if err := m.Delete(ctx, &item); err != nil {
|
||||
if !apierrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
@@ -380,7 +396,7 @@ func (m *Manager) cleanupResourceType(ctx context.Context, resourceType client.O
|
||||
case *dashv1alpha1.CustomFormsPrefillList:
|
||||
for _, item := range l.Items {
|
||||
if !expected[item.Name] {
|
||||
if err := m.client.Delete(ctx, &item); err != nil {
|
||||
if err := m.Delete(ctx, &item); err != nil {
|
||||
if !apierrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
@@ -391,7 +407,7 @@ func (m *Manager) cleanupResourceType(ctx context.Context, resourceType client.O
|
||||
case *dashv1alpha1.MarketplacePanelList:
|
||||
for _, item := range l.Items {
|
||||
if !expected[item.Name] {
|
||||
if err := m.client.Delete(ctx, &item); err != nil {
|
||||
if err := m.Delete(ctx, &item); err != nil {
|
||||
if !apierrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
@@ -402,7 +418,7 @@ func (m *Manager) cleanupResourceType(ctx context.Context, resourceType client.O
|
||||
case *dashv1alpha1.SidebarList:
|
||||
for _, item := range l.Items {
|
||||
if !expected[item.Name] {
|
||||
if err := m.client.Delete(ctx, &item); err != nil {
|
||||
if err := m.Delete(ctx, &item); err != nil {
|
||||
if !apierrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
@@ -413,7 +429,7 @@ func (m *Manager) cleanupResourceType(ctx context.Context, resourceType client.O
|
||||
case *dashv1alpha1.TableUriMappingList:
|
||||
for _, item := range l.Items {
|
||||
if !expected[item.Name] {
|
||||
if err := m.client.Delete(ctx, &item); err != nil {
|
||||
if err := m.Delete(ctx, &item); err != nil {
|
||||
if !apierrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
@@ -426,7 +442,7 @@ func (m *Manager) cleanupResourceType(ctx context.Context, resourceType client.O
|
||||
if !expected[item.Name] {
|
||||
logger := log.FromContext(ctx)
|
||||
logger.Info("Deleting orphaned Breadcrumb resource", "name", item.Name)
|
||||
if err := m.client.Delete(ctx, &item); err != nil {
|
||||
if err := m.Delete(ctx, &item); err != nil {
|
||||
if !apierrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
@@ -438,7 +454,7 @@ func (m *Manager) cleanupResourceType(ctx context.Context, resourceType client.O
|
||||
if !expected[item.Name] {
|
||||
logger := log.FromContext(ctx)
|
||||
logger.Info("Deleting orphaned Factory resource", "name", item.Name)
|
||||
if err := m.client.Delete(ctx, &item); err != nil {
|
||||
if err := m.Delete(ctx, &item); err != nil {
|
||||
if !apierrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -24,14 +24,14 @@ func (m *Manager) ensureMarketplacePanel(ctx context.Context, crd *cozyv1alpha1.
|
||||
|
||||
// If dashboard is not set, delete the panel if it exists.
|
||||
if crd.Spec.Dashboard == nil {
|
||||
err := m.client.Get(ctx, client.ObjectKey{Name: mp.Name}, mp)
|
||||
err := m.Get(ctx, client.ObjectKey{Name: mp.Name}, mp)
|
||||
if apierrors.IsNotFound(err) {
|
||||
return reconcile.Result{}, nil
|
||||
}
|
||||
if err != nil {
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
if err := m.client.Delete(ctx, mp); err != nil && !apierrors.IsNotFound(err) {
|
||||
if err := m.Delete(ctx, mp); err != nil && !apierrors.IsNotFound(err) {
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
logger.Info("Deleted MarketplacePanel because dashboard is not set", "name", mp.Name)
|
||||
@@ -40,14 +40,14 @@ func (m *Manager) ensureMarketplacePanel(ctx context.Context, crd *cozyv1alpha1.
|
||||
|
||||
// Skip module and tenant resources (they don't need MarketplacePanel)
|
||||
if crd.Spec.Dashboard.Module || crd.Spec.Application.Kind == "Tenant" {
|
||||
err := m.client.Get(ctx, client.ObjectKey{Name: mp.Name}, mp)
|
||||
err := m.Get(ctx, client.ObjectKey{Name: mp.Name}, mp)
|
||||
if apierrors.IsNotFound(err) {
|
||||
return reconcile.Result{}, nil
|
||||
}
|
||||
if err != nil {
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
if err := m.client.Delete(ctx, mp); err != nil && !apierrors.IsNotFound(err) {
|
||||
if err := m.Delete(ctx, mp); err != nil && !apierrors.IsNotFound(err) {
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
logger.Info("Deleted MarketplacePanel because resource is a module", "name", mp.Name)
|
||||
@@ -86,8 +86,8 @@ func (m *Manager) ensureMarketplacePanel(ctx context.Context, crd *cozyv1alpha1.
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, m.client, mp, func() error {
|
||||
if err := controllerutil.SetOwnerReference(crd, mp, m.scheme); err != nil {
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, m.Client, mp, func() error {
|
||||
if err := controllerutil.SetOwnerReference(crd, mp, m.Scheme); err != nil {
|
||||
return err
|
||||
}
|
||||
// Add dashboard labels to dynamic resources
|
||||
|
||||
@@ -33,19 +33,11 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Cozystack
|
||||
|
||||
// 1) Fetch all CRDs
|
||||
var all []cozyv1alpha1.CozystackResourceDefinition
|
||||
if m.crdListFn != nil {
|
||||
s, err := m.crdListFn(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
all = s
|
||||
} else {
|
||||
var crdList cozyv1alpha1.CozystackResourceDefinitionList
|
||||
if err := m.client.List(ctx, &crdList, &client.ListOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
all = crdList.Items
|
||||
var crdList cozyv1alpha1.CozystackResourceDefinitionList
|
||||
if err := m.List(ctx, &crdList, &client.ListOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
all = crdList.Items
|
||||
|
||||
// 2) Build category -> []item map (only for CRDs with spec.dashboard != nil)
|
||||
type item struct {
|
||||
@@ -251,7 +243,7 @@ func (m *Manager) upsertMultipleSidebars(
|
||||
obj := &dashv1alpha1.Sidebar{}
|
||||
obj.SetName(id)
|
||||
|
||||
if _, err := controllerutil.CreateOrUpdate(ctx, m.client, obj, func() error {
|
||||
if _, err := controllerutil.CreateOrUpdate(ctx, m.Client, obj, func() error {
|
||||
// Only set owner reference for dynamic sidebars (stock-project-factory-{kind}-details)
|
||||
// Static sidebars (stock-instance-*, stock-project-*) should not have owner references
|
||||
if strings.HasPrefix(id, "stock-project-factory-") && strings.HasSuffix(id, "-details") {
|
||||
@@ -260,7 +252,7 @@ func (m *Manager) upsertMultipleSidebars(
|
||||
lowerKind := strings.ToLower(kind)
|
||||
expectedID := fmt.Sprintf("stock-project-factory-%s-details", lowerKind)
|
||||
if id == expectedID {
|
||||
if err := controllerutil.SetOwnerReference(crd, obj, m.scheme); err != nil {
|
||||
if err := controllerutil.SetOwnerReference(crd, obj, m.Scheme); err != nil {
|
||||
return err
|
||||
}
|
||||
// Add dashboard labels to dynamic resources
|
||||
|
||||
@@ -122,7 +122,7 @@ func createCustomColumnsOverride(id string, additionalPrinterColumns []any) *das
|
||||
}
|
||||
}
|
||||
|
||||
if name == "factory-details-v1alpha1.core.cozystack.io.tenantsecretstables" {
|
||||
if name == "factory-details-v1alpha1.core.cozystack.io.tenantsecrets" {
|
||||
data["additionalPrinterColumnsTrimLengths"] = []any{
|
||||
map[string]any{
|
||||
"key": "Name",
|
||||
@@ -531,7 +531,6 @@ func createBreadcrumbItem(key, label string, link ...string) map[string]any {
|
||||
|
||||
// createCustomColumn creates a custom column with factory type and badge
|
||||
func createCustomColumn(name, kind, plural, href string) map[string]any {
|
||||
badge := createUnifiedBadgeFromKind("header-badge", kind, plural, BadgeSizeMedium)
|
||||
link := antdLink("name-link", "{reqsJsonPath[0]['.metadata.name']['-']}", href)
|
||||
|
||||
return map[string]any{
|
||||
@@ -541,8 +540,18 @@ func createCustomColumn(name, kind, plural, href string) map[string]any {
|
||||
"disableEventBubbling": true,
|
||||
"items": []any{
|
||||
map[string]any{
|
||||
"children": []any{badge, link},
|
||||
"type": "antdFlex",
|
||||
"children": []any{
|
||||
map[string]any{
|
||||
"type": "ResourceBadge",
|
||||
"data": map[string]any{
|
||||
"id": "header-badge",
|
||||
"value": kind,
|
||||
// abbreviation auto-generated by ResourceBadge from value
|
||||
},
|
||||
},
|
||||
link,
|
||||
},
|
||||
"type": "antdFlex",
|
||||
"data": map[string]any{
|
||||
"align": "center",
|
||||
"gap": float64(6),
|
||||
@@ -554,16 +563,16 @@ func createCustomColumn(name, kind, plural, href string) map[string]any {
|
||||
}
|
||||
|
||||
// createCustomColumnWithBadge creates a custom column with a specific badge
|
||||
func createCustomColumnWithBadge(name, badgeText, badgeColor, title, href string) map[string]any {
|
||||
config := BadgeConfig{
|
||||
Text: badgeText,
|
||||
Color: badgeColor,
|
||||
Title: title,
|
||||
Size: BadgeSizeMedium,
|
||||
}
|
||||
badge := createUnifiedBadge("header-badge", config)
|
||||
// badgeValue should be the kind in PascalCase (e.g., "Service", "Pod")
|
||||
// abbreviation is auto-generated by ResourceBadge from badgeValue
|
||||
func createCustomColumnWithBadge(name, badgeValue, href string) map[string]any {
|
||||
link := antdLink("name-link", "{reqsJsonPath[0]['.metadata.name']['-']}", href)
|
||||
|
||||
badgeData := map[string]any{
|
||||
"id": "header-badge",
|
||||
"value": badgeValue,
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"name": name,
|
||||
"type": "factory",
|
||||
@@ -571,8 +580,14 @@ func createCustomColumnWithBadge(name, badgeText, badgeColor, title, href string
|
||||
"disableEventBubbling": true,
|
||||
"items": []any{
|
||||
map[string]any{
|
||||
"children": []any{badge, link},
|
||||
"type": "antdFlex",
|
||||
"children": []any{
|
||||
map[string]any{
|
||||
"type": "ResourceBadge",
|
||||
"data": badgeData,
|
||||
},
|
||||
link,
|
||||
},
|
||||
"type": "antdFlex",
|
||||
"data": map[string]any{
|
||||
"align": "center",
|
||||
"gap": float64(6),
|
||||
@@ -583,17 +598,22 @@ func createCustomColumnWithBadge(name, badgeText, badgeColor, title, href string
|
||||
}
|
||||
}
|
||||
|
||||
// createCustomColumnWithSpecificColor creates a custom column with a specific color
|
||||
func createCustomColumnWithSpecificColor(name, kind, title, color, href string) map[string]any {
|
||||
config := BadgeConfig{
|
||||
Text: initialsFromKind(kind),
|
||||
Color: color,
|
||||
Title: title,
|
||||
Size: BadgeSizeMedium,
|
||||
}
|
||||
badge := createUnifiedBadge("header-badge", config)
|
||||
// createCustomColumnWithSpecificColor creates a custom column with a specific kind and optional color
|
||||
// badgeValue should be the kind in PascalCase (e.g., "Service", "Pod")
|
||||
func createCustomColumnWithSpecificColor(name, kind, color, href string) map[string]any {
|
||||
link := antdLink("name-link", "{reqsJsonPath[0]['.metadata.name']['-']}", href)
|
||||
|
||||
badgeData := map[string]any{
|
||||
"id": "header-badge",
|
||||
"value": kind,
|
||||
}
|
||||
// Add custom color if specified
|
||||
if color != "" {
|
||||
badgeData["style"] = map[string]any{
|
||||
"backgroundColor": color,
|
||||
}
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"name": name,
|
||||
"type": "factory",
|
||||
@@ -602,8 +622,14 @@ func createCustomColumnWithSpecificColor(name, kind, title, color, href string)
|
||||
"disableEventBubbling": true,
|
||||
"items": []any{
|
||||
map[string]any{
|
||||
"children": []any{badge, link},
|
||||
"type": "antdFlex",
|
||||
"children": []any{
|
||||
map[string]any{
|
||||
"type": "ResourceBadge",
|
||||
"data": badgeData,
|
||||
},
|
||||
link,
|
||||
},
|
||||
"type": "antdFlex",
|
||||
"data": map[string]any{
|
||||
"align": "center",
|
||||
"gap": float64(6),
|
||||
@@ -668,7 +694,7 @@ func createTimestampColumn(name, jsonPath string) map[string]any {
|
||||
// createFactoryHeader creates a header for factory resources
|
||||
func createFactoryHeader(kind, plural string) map[string]any {
|
||||
lowerKind := strings.ToLower(kind)
|
||||
badge := createUnifiedBadgeFromKind("badge-"+lowerKind, kind, plural, BadgeSizeLarge)
|
||||
badge := createUnifiedBadgeFromKind("badge-"+lowerKind, kind)
|
||||
nameText := parsedText(lowerKind+"-name", "{reqsJsonPath[0]['.metadata.name']['-']}", map[string]any{
|
||||
"fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif",
|
||||
"fontSize": float64(20),
|
||||
@@ -718,13 +744,26 @@ func createFactorySpec(key string, sidebarTags []any, urlsToFetch []any, header
|
||||
}
|
||||
|
||||
// createCustomColumnWithJsonPath creates a column with a custom badge and link using jsonPath
|
||||
func createCustomColumnWithJsonPath(name, jsonPath, badgeText, badgeTitle, badgeColor, linkHref string) map[string]any {
|
||||
// badgeValue should be the kind in PascalCase (e.g., "Service", "VirtualMachine")
|
||||
// abbreviation is auto-generated by ResourceBadge from badgeValue
|
||||
func createCustomColumnWithJsonPath(name, jsonPath, badgeValue, badgeColor, linkHref string) map[string]any {
|
||||
// Determine link ID based on jsonPath
|
||||
linkId := "name-link"
|
||||
if jsonPath == ".metadata.namespace" {
|
||||
linkId = "namespace-link"
|
||||
}
|
||||
|
||||
badgeData := map[string]any{
|
||||
"id": "header-badge",
|
||||
"value": badgeValue,
|
||||
}
|
||||
// Add custom color if specified
|
||||
if badgeColor != "" {
|
||||
badgeData["style"] = map[string]any{
|
||||
"backgroundColor": badgeColor,
|
||||
}
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"name": name,
|
||||
"type": "factory",
|
||||
@@ -741,26 +780,8 @@ func createCustomColumnWithJsonPath(name, jsonPath, badgeText, badgeTitle, badge
|
||||
},
|
||||
"children": []any{
|
||||
map[string]any{
|
||||
"type": "antdText",
|
||||
"data": map[string]any{
|
||||
"id": "header-badge",
|
||||
"text": badgeText,
|
||||
"title": badgeTitle,
|
||||
"style": map[string]any{
|
||||
"backgroundColor": badgeColor,
|
||||
"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",
|
||||
},
|
||||
},
|
||||
"type": "ResourceBadge",
|
||||
"data": badgeData,
|
||||
},
|
||||
map[string]any{
|
||||
"type": "antdLink",
|
||||
@@ -778,7 +799,20 @@ func createCustomColumnWithJsonPath(name, jsonPath, badgeText, badgeTitle, badge
|
||||
}
|
||||
|
||||
// createCustomColumnWithoutJsonPath creates a column with a custom badge and link without jsonPath
|
||||
func createCustomColumnWithoutJsonPath(name, badgeText, badgeTitle, badgeColor, linkHref string) map[string]any {
|
||||
// badgeValue should be the kind in PascalCase (e.g., "Node", "Pod")
|
||||
// abbreviation is auto-generated by ResourceBadge from badgeValue
|
||||
func createCustomColumnWithoutJsonPath(name, badgeValue, badgeColor, linkHref string) map[string]any {
|
||||
badgeData := map[string]any{
|
||||
"id": "header-badge",
|
||||
"value": badgeValue,
|
||||
}
|
||||
// Add custom color if specified
|
||||
if badgeColor != "" {
|
||||
badgeData["style"] = map[string]any{
|
||||
"backgroundColor": badgeColor,
|
||||
}
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"name": name,
|
||||
"type": "factory",
|
||||
@@ -794,26 +828,8 @@ func createCustomColumnWithoutJsonPath(name, badgeText, badgeTitle, badgeColor,
|
||||
},
|
||||
"children": []any{
|
||||
map[string]any{
|
||||
"type": "antdText",
|
||||
"data": map[string]any{
|
||||
"id": "header-badge",
|
||||
"text": badgeText,
|
||||
"title": badgeTitle,
|
||||
"style": map[string]any{
|
||||
"backgroundColor": badgeColor,
|
||||
"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",
|
||||
},
|
||||
},
|
||||
"type": "ResourceBadge",
|
||||
"data": badgeData,
|
||||
},
|
||||
map[string]any{
|
||||
"type": "antdLink",
|
||||
@@ -1030,6 +1046,15 @@ func createConverterBytesColumn(name, jsonPath string) map[string]any {
|
||||
}
|
||||
}
|
||||
|
||||
// createFlatMapColumn creates a flatMap column that expands a map into separate rows
|
||||
func createFlatMapColumn(name, jsonPath string) map[string]any {
|
||||
return map[string]any{
|
||||
"name": name,
|
||||
"type": "flatMap",
|
||||
"jsonPath": jsonPath,
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------- Factory UI helper functions ----------------
|
||||
|
||||
// labelsEditor creates a Labels editor component
|
||||
|
||||
@@ -32,7 +32,7 @@ func (m *Manager) ensureStaticResource(ctx context.Context, obj client.Object) e
|
||||
// Add dashboard labels to static resources
|
||||
m.addDashboardLabels(resource, nil, ResourceTypeStatic)
|
||||
|
||||
_, err := controllerutil.CreateOrUpdate(ctx, m.client, resource, func() error {
|
||||
_, err := controllerutil.CreateOrUpdate(ctx, m.Client, resource, func() error {
|
||||
// For static resources, we don't need to set owner references
|
||||
// as they are meant to be persistent across CRD changes
|
||||
// Copy Spec from the original object to the live object
|
||||
|
||||
@@ -132,7 +132,7 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
|
||||
return []*dashboardv1alpha1.CustomColumnsOverride{
|
||||
// Factory details v1 services
|
||||
createCustomColumnsOverride("factory-details-v1.services", []any{
|
||||
createCustomColumnWithSpecificColor("Name", "Service", "service", getColorForType("service"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-service-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
|
||||
createCustomColumnWithSpecificColor("Name", "Service", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-service-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
|
||||
createStringColumn("ClusterIP", ".spec.clusterIP"),
|
||||
createStringColumn("LoadbalancerIP", ".spec.loadBalancerIP"),
|
||||
createTimestampColumn("Created", ".metadata.creationTimestamp"),
|
||||
@@ -140,7 +140,7 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
|
||||
|
||||
// Stock namespace v1 services
|
||||
createCustomColumnsOverride("stock-namespace-/v1/services", []any{
|
||||
createCustomColumnWithJsonPath("Name", ".metadata.name", "S", "service", getColorForType("service"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-service-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
|
||||
createCustomColumnWithJsonPath("Name", ".metadata.name", "Service", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-service-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
|
||||
createStringColumn("ClusterIP", ".spec.clusterIP"),
|
||||
createStringColumn("LoadbalancerIP", ".status.loadBalancer.ingress[0].ip"),
|
||||
createTimestampColumn("Created", ".metadata.creationTimestamp"),
|
||||
@@ -148,7 +148,7 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
|
||||
|
||||
// Stock namespace core cozystack io v1alpha1 tenantmodules
|
||||
createCustomColumnsOverride("stock-namespace-/core.cozystack.io/v1alpha1/tenantmodules", []any{
|
||||
createCustomColumnWithJsonPath("Name", ".metadata.name", "M", "module", getColorForType("module"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/{reqsJsonPath[0]['.metadata.name']['-']}-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
|
||||
createCustomColumnWithJsonPath("Name", ".metadata.name", "Module", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/{reqsJsonPath[0]['.metadata.name']['-']}-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
|
||||
createReadyColumn(),
|
||||
createTimestampColumn("Created", ".metadata.creationTimestamp"),
|
||||
createStringColumn("Version", ".status.version"),
|
||||
@@ -164,7 +164,7 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
|
||||
|
||||
// Factory details v1alpha1 cozystack io workloadmonitors
|
||||
createCustomColumnsOverride("factory-details-v1alpha1.cozystack.io.workloadmonitors", []any{
|
||||
createCustomColumnWithJsonPath("Name", ".metadata.name", "W", "workloadmonitor", getColorForType("workloadmonitor"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/workloadmonitor-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
|
||||
createCustomColumnWithJsonPath("Name", ".metadata.name", "WorkloadMonitor", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/workloadmonitor-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
|
||||
createStringColumn("TYPE", ".spec.type"),
|
||||
createStringColumn("VERSION", ".spec.version"),
|
||||
createStringColumn("REPLICAS", ".spec.replicas"),
|
||||
@@ -173,18 +173,26 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
|
||||
createStringColumn("OBSERVED", ".status.observedReplicas"),
|
||||
}),
|
||||
|
||||
// Factory details v1alpha1 core cozystack io tenantsecretstables
|
||||
createCustomColumnsOverride("factory-details-v1alpha1.core.cozystack.io.tenantsecretstables", []any{
|
||||
createCustomColumnWithJsonPath("Name", ".metadata.name", "S", "secret", getColorForType("secret"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-secret-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
|
||||
createStringColumn("Key", ".data.key"),
|
||||
createSecretBase64Column("Value", ".data.value"),
|
||||
// Factory details v1alpha1 core cozystack io tenantsecrets
|
||||
createCustomColumnsOverride("factory-details-v1alpha1.core.cozystack.io.tenantsecrets", []any{
|
||||
createCustomColumnWithJsonPath("Name", ".metadata.name", "Secret", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-secret-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
|
||||
createFlatMapColumn("Data", ".data"),
|
||||
createStringColumn("Key", "_flatMapData_Key"),
|
||||
createSecretBase64Column("Value", "._flatMapData_Value"),
|
||||
createTimestampColumn("Created", ".metadata.creationTimestamp"),
|
||||
}),
|
||||
|
||||
// Virtual private cloud subnets
|
||||
createCustomColumnsOverride("virtualprivatecloud-subnets", []any{
|
||||
createFlatMapColumn("Data", ".data"),
|
||||
createStringColumn("Subnet Parameters", "_flatMapData_Key"),
|
||||
createStringColumn("Values", "_flatMapData_Value"),
|
||||
}),
|
||||
|
||||
// Factory ingress details rules
|
||||
createCustomColumnsOverride("factory-kube-ingress-details-rules", []any{
|
||||
createStringColumn("Host", ".host"),
|
||||
createCustomColumnWithJsonPath("Service", ".http.paths[0].backend.service.name", "S", "service", getColorForType("service"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-service-details/{reqsJsonPath[0]['.http.paths[0].backend.service.name']['-']}"),
|
||||
createCustomColumnWithJsonPath("Service", ".http.paths[0].backend.service.name", "Service", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-service-details/{reqsJsonPath[0]['.http.paths[0].backend.service.name']['-']}"),
|
||||
createStringColumn("Port", ".http.paths[0].backend.service.port.number"),
|
||||
createStringColumn("Path", ".http.paths[0].path"),
|
||||
}),
|
||||
@@ -250,7 +258,7 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
|
||||
|
||||
// Factory details networking k8s io v1 ingresses
|
||||
createCustomColumnsOverride("factory-details-networking.k8s.io.v1.ingresses", []any{
|
||||
createCustomColumnWithJsonPath("Name", ".metadata.name", "I", "ingress", getColorForType("ingress"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-ingress-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
|
||||
createCustomColumnWithJsonPath("Name", ".metadata.name", "Ingress", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-ingress-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
|
||||
createStringColumn("Hosts", ".spec.rules[*].host"),
|
||||
createStringColumn("Address", ".status.loadBalancer.ingress[0].ip"),
|
||||
createStringColumn("Port", ".spec.defaultBackend.service.port.number"),
|
||||
@@ -259,7 +267,7 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
|
||||
|
||||
// Stock namespace networking k8s io v1 ingresses
|
||||
createCustomColumnsOverride("stock-namespace-/networking.k8s.io/v1/ingresses", []any{
|
||||
createCustomColumnWithJsonPath("Name", ".metadata.name", "I", "ingress", getColorForType("ingress"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-ingress-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
|
||||
createCustomColumnWithJsonPath("Name", ".metadata.name", "Ingress", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-ingress-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
|
||||
createStringColumn("Hosts", ".spec.rules[*].host"),
|
||||
createStringColumn("Address", ".status.loadBalancer.ingress[0].ip"),
|
||||
createStringColumn("Port", ".spec.defaultBackend.service.port.number"),
|
||||
@@ -268,34 +276,34 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
|
||||
|
||||
// Stock cluster v1 configmaps
|
||||
createCustomColumnsOverride("stock-cluster-/v1/configmaps", []any{
|
||||
createCustomColumnWithJsonPath("Name", ".metadata.name", "CM", "configmap", getColorForType("configmap"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/configmap-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
|
||||
createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "NS", "namespace", getColorForType("namespace"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace"),
|
||||
createCustomColumnWithJsonPath("Name", ".metadata.name", "ConfigMap", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/configmap-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
|
||||
createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "Namespace", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace"),
|
||||
createTimestampColumn("Created", ".metadata.creationTimestamp"),
|
||||
}),
|
||||
|
||||
// Stock namespace v1 configmaps
|
||||
createCustomColumnsOverride("stock-namespace-/v1/configmaps", []any{
|
||||
createCustomColumnWithJsonPath("Name", ".metadata.name", "CM", "configmap", getColorForType("configmap"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/configmap-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
|
||||
createCustomColumnWithJsonPath("Name", ".metadata.name", "ConfigMap", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/configmap-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
|
||||
createTimestampColumn("Created", ".metadata.creationTimestamp"),
|
||||
}),
|
||||
|
||||
// Cluster v1 configmaps
|
||||
createCustomColumnsOverride("cluster-/v1/configmaps", []any{
|
||||
createCustomColumnWithJsonPath("Name", ".metadata.name", "CM", "configmap", getColorForType("configmap"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/configmap-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
|
||||
createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "NS", "namespace", getColorForType("namespace"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace"),
|
||||
createCustomColumnWithJsonPath("Name", ".metadata.name", "ConfigMap", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/configmap-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
|
||||
createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "Namespace", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace"),
|
||||
createTimestampColumn("Created", ".metadata.creationTimestamp"),
|
||||
}),
|
||||
|
||||
// Stock cluster v1 nodes
|
||||
createCustomColumnsOverride("stock-cluster-/v1/nodes", []any{
|
||||
createCustomColumnWithJsonPath("Name", ".metadata.name", "N", "node", getColorForType("node"), "/openapi-ui/{2}/factory/node-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
|
||||
createCustomColumnWithJsonPath("Name", ".metadata.name", "Node", "", "/openapi-ui/{2}/factory/node-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
|
||||
createSimpleStatusColumn("Status", "node-status"),
|
||||
}),
|
||||
|
||||
// Factory node details v1 pods
|
||||
createCustomColumnsOverride("factory-node-details-v1.pods", []any{
|
||||
createCustomColumnWithJsonPath("Name", ".metadata.name", "P", "pod", getColorForType("pod"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/pod-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
|
||||
createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "NS", "namespace", getColorForType("namespace"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace"),
|
||||
createCustomColumnWithJsonPath("Name", ".metadata.name", "Pod", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/pod-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
|
||||
createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "Namespace", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace"),
|
||||
createStringColumn("Restart Policy", ".spec.restartPolicy"),
|
||||
createStringColumn("Pod IP", ".status.podIP"),
|
||||
createStringColumn("QOS", ".status.qosClass"),
|
||||
@@ -304,8 +312,8 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
|
||||
|
||||
// Factory v1 pods
|
||||
createCustomColumnsOverride("factory-v1.pods", []any{
|
||||
createCustomColumnWithJsonPath("Name", ".metadata.name", "P", "pod", getColorForType("pod"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/pod-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
|
||||
createCustomColumnWithoutJsonPath("Node", "N", "node", getColorForType("node"), "/openapi-ui/{2}/factory/node-details/{reqsJsonPath[0]['.spec.nodeName']['-']}"),
|
||||
createCustomColumnWithJsonPath("Name", ".metadata.name", "Pod", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/pod-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
|
||||
createCustomColumnWithoutJsonPath("Node", "Node", "", "/openapi-ui/{2}/factory/node-details/{reqsJsonPath[0]['.spec.nodeName']['-']}"),
|
||||
createStringColumn("Restart Policy", ".spec.restartPolicy"),
|
||||
createStringColumn("Pod IP", ".status.podIP"),
|
||||
createStringColumn("QOS", ".status.qosClass"),
|
||||
@@ -314,9 +322,9 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
|
||||
|
||||
// Stock cluster v1 pods
|
||||
createCustomColumnsOverride("stock-cluster-/v1/pods", []any{
|
||||
createCustomColumnWithJsonPath("Name", ".metadata.name", "P", "pod", "#009596", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/pod-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
|
||||
createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "NS", "namespace", "#a25792ff", "/openapi-ui/{2}/factory/tenantnamespace/{reqsJsonPath[0]['.metadata.namespace']['-']}"),
|
||||
createCustomColumnWithJsonPath("Node", ".spec.nodeName", "N", "node", "#8476d1", "/openapi-ui/{2}/factory/node-details/{reqsJsonPath[0]['.spec.nodeName']['-']}"),
|
||||
createCustomColumnWithJsonPath("Name", ".metadata.name", "Pod", "#009596", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/pod-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
|
||||
createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "Namespace", "#a25792ff", "/openapi-ui/{2}/factory/tenantnamespace/{reqsJsonPath[0]['.metadata.namespace']['-']}"),
|
||||
createCustomColumnWithJsonPath("Node", ".spec.nodeName", "Node", "#8476d1", "/openapi-ui/{2}/factory/node-details/{reqsJsonPath[0]['.spec.nodeName']['-']}"),
|
||||
createStringColumn("Restart Policy", ".spec.restartPolicy"),
|
||||
createStringColumn("Pod IP", ".status.podIP"),
|
||||
createStringColumn("QOS", ".status.qosClass"),
|
||||
@@ -325,8 +333,8 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
|
||||
|
||||
// Stock namespace v1 pods
|
||||
createCustomColumnsOverride("stock-namespace-/v1/pods", []any{
|
||||
createCustomColumnWithJsonPath("Name", ".metadata.name", "P", "pod", "#009596", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/pod-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
|
||||
createCustomColumnWithoutJsonPath("Node", "N", "node", "#8476d1", "/openapi-ui/{2}/factory/node-details/{reqsJsonPath[0]['.spec.nodeName']['-']}"),
|
||||
createCustomColumnWithJsonPath("Name", ".metadata.name", "Pod", "#009596", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/pod-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
|
||||
createCustomColumnWithoutJsonPath("Node", "Node", "#8476d1", "/openapi-ui/{2}/factory/node-details/{reqsJsonPath[0]['.spec.nodeName']['-']}"),
|
||||
createStringColumn("Restart Policy", ".spec.restartPolicy"),
|
||||
createStringColumn("Pod IP", ".status.podIP"),
|
||||
createStringColumn("QOS", ".status.qosClass"),
|
||||
@@ -335,15 +343,15 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
|
||||
|
||||
// Stock cluster v1 secrets
|
||||
createCustomColumnsOverride("stock-cluster-/v1/secrets", []any{
|
||||
createCustomColumnWithJsonPath("Name", ".metadata.name", "S", "secret", "#c46100", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-secret-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
|
||||
createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "NS", "namespace", "#a25792ff", "/openapi-ui/{2}/factory/tenantnamespace/{reqsJsonPath[0]['.metadata.namespace']['-']}"),
|
||||
createCustomColumnWithJsonPath("Name", ".metadata.name", "Secret", "#c46100", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-secret-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
|
||||
createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "Namespace", "#a25792ff", "/openapi-ui/{2}/factory/tenantnamespace/{reqsJsonPath[0]['.metadata.namespace']['-']}"),
|
||||
createStringColumn("Type", ".type"),
|
||||
createTimestampColumn("Created", ".metadata.creationTimestamp"),
|
||||
}),
|
||||
|
||||
// Stock namespace v1 secrets
|
||||
createCustomColumnsOverride("stock-namespace-/v1/secrets", []any{
|
||||
createCustomColumnWithJsonPath("Name", ".metadata.name", "S", "secret", "#c46100", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-secret-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
|
||||
createCustomColumnWithJsonPath("Name", ".metadata.name", "Secret", "#c46100", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-secret-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
|
||||
createStringColumn("Type", ".type"),
|
||||
createTimestampColumn("Created", ".metadata.creationTimestamp"),
|
||||
}),
|
||||
@@ -360,7 +368,7 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
|
||||
|
||||
// Stock cluster core cozystack io v1alpha1 tenantnamespaces
|
||||
createCustomColumnsOverride("stock-cluster-/core.cozystack.io/v1alpha1/tenantnamespaces", []any{
|
||||
createCustomColumnWithJsonPath("Name", ".metadata.name", "TN", "tenantnamespace", getColorForType("tenantnamespace"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.name']['-']}/factory/marketplace"),
|
||||
createCustomColumnWithJsonPath("Name", ".metadata.name", "TenantNamespace", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.name']['-']}/factory/marketplace"),
|
||||
createTimestampColumn("Created", ".metadata.creationTimestamp"),
|
||||
}),
|
||||
}
|
||||
@@ -496,7 +504,6 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory {
|
||||
Kind: "Namespace",
|
||||
Plural: "namespaces",
|
||||
Title: "namespace",
|
||||
Size: BadgeSizeLarge,
|
||||
}
|
||||
namespaceSpec := createUnifiedFactory(namespaceConfig, nil, []any{"/api/clusters/{2}/k8s/api/v1/namespaces/{5}"})
|
||||
|
||||
@@ -796,6 +803,7 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory {
|
||||
"substractHeight": float64(400),
|
||||
"type": "builtin",
|
||||
"typeName": "secrets",
|
||||
"readOnly": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -1055,7 +1063,7 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory {
|
||||
"clusterNamePartOfUrl": "{2}",
|
||||
"customizationId": "factory-kube-service-details-endpointslice",
|
||||
"fetchUrl": "/api/clusters/{2}/k8s/apis/discovery.k8s.io/v1/namespaces/{3}/endpointslices",
|
||||
"labelsSelector": map[string]any{
|
||||
"labelSelector": map[string]any{
|
||||
"kubernetes.io/service-name": "{reqsJsonPath[0]['.metadata.name']['-']}",
|
||||
},
|
||||
"pathToItems": ".items[*].endpoints",
|
||||
@@ -1201,7 +1209,7 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory {
|
||||
"gap": 6,
|
||||
},
|
||||
"children": []any{
|
||||
createUnifiedBadgeFromKind("ns-badge", "Namespace", "namespace", BadgeSizeMedium),
|
||||
createUnifiedBadgeFromKind("ns-badge", "Namespace"),
|
||||
antdLink("namespace-link",
|
||||
"{reqsJsonPath[0]['.metadata.namespace']['-']}",
|
||||
"/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace",
|
||||
@@ -1396,7 +1404,7 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory {
|
||||
"clusterNamePartOfUrl": "{2}",
|
||||
"customizationId": "factory-details-v1alpha1.cozystack.io.workloads",
|
||||
"fetchUrl": "/api/clusters/{2}/k8s/apis/cozystack.io/v1alpha1/namespaces/{3}/workloads",
|
||||
"labelsSelector": map[string]any{
|
||||
"labelSelector": map[string]any{
|
||||
"workloads.cozystack.io/monitor": "{reqs[0]['metadata','name']}",
|
||||
},
|
||||
"pathToItems": []any{"items"},
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package dashboard
|
||||
|
||||
import "strings"
|
||||
|
||||
// ---------------- UI helpers (use float64 for numeric fields) ----------------
|
||||
|
||||
func contentCard(id string, style map[string]any, children []any) map[string]any {
|
||||
@@ -200,10 +198,10 @@ func createBadge(id, text, color, title string) map[string]any {
|
||||
|
||||
// createBadgeFromKind creates a badge using the existing badge generation functions
|
||||
func createBadgeFromKind(id, kind, title string) map[string]any {
|
||||
return createUnifiedBadgeFromKind(id, kind, title, BadgeSizeMedium)
|
||||
return createUnifiedBadgeFromKind(id, kind)
|
||||
}
|
||||
|
||||
// createHeaderBadge creates a badge specifically for headers with consistent styling
|
||||
func createHeaderBadge(id, kind, plural string) map[string]any {
|
||||
return createUnifiedBadgeFromKind(id, kind, strings.ToLower(plural), BadgeSizeLarge)
|
||||
return createUnifiedBadgeFromKind(id, kind)
|
||||
}
|
||||
|
||||
@@ -81,86 +81,47 @@ func isAlphanumeric(c byte) bool {
|
||||
|
||||
// BadgeConfig holds configuration for badge generation
|
||||
type BadgeConfig struct {
|
||||
Text string
|
||||
Color string
|
||||
Title string
|
||||
Size BadgeSize
|
||||
Kind string // Resource kind in PascalCase (e.g., "VirtualMachine") - used for value and auto-generation
|
||||
Text string // Optional abbreviation override (if empty, ResourceBadge auto-generates from Kind)
|
||||
Color string // Optional custom backgroundColor override
|
||||
}
|
||||
|
||||
// BadgeSize represents the size of the badge
|
||||
type BadgeSize int
|
||||
|
||||
const (
|
||||
BadgeSizeSmall BadgeSize = iota
|
||||
BadgeSizeMedium
|
||||
BadgeSizeLarge
|
||||
)
|
||||
|
||||
// generateBadgeConfig creates a BadgeConfig from kind and optional custom values
|
||||
func generateBadgeConfig(kind string, customText, customColor, customTitle string) BadgeConfig {
|
||||
config := BadgeConfig{
|
||||
Text: initialsFromKind(kind),
|
||||
Color: hexColorForKind(kind),
|
||||
Title: strings.ToLower(kind),
|
||||
Size: BadgeSizeMedium,
|
||||
}
|
||||
|
||||
// Override with custom values if provided
|
||||
if customText != "" {
|
||||
config.Text = customText
|
||||
}
|
||||
if customColor != "" {
|
||||
config.Color = customColor
|
||||
}
|
||||
if customTitle != "" {
|
||||
config.Title = customTitle
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// createUnifiedBadge creates a badge using the unified BadgeConfig
|
||||
// createUnifiedBadge creates a badge using the unified BadgeConfig with ResourceBadge component
|
||||
func createUnifiedBadge(id string, config BadgeConfig) map[string]any {
|
||||
fontSize := "15px"
|
||||
if config.Size == BadgeSizeLarge {
|
||||
fontSize = "20px"
|
||||
} else if config.Size == BadgeSizeSmall {
|
||||
fontSize = "12px"
|
||||
data := map[string]any{
|
||||
"id": id,
|
||||
"value": config.Kind,
|
||||
}
|
||||
|
||||
// Add abbreviation override if specified (otherwise ResourceBadge auto-generates from Kind)
|
||||
if config.Text != "" {
|
||||
data["abbreviation"] = config.Text
|
||||
}
|
||||
|
||||
// Add custom color if specified
|
||||
if config.Color != "" {
|
||||
data["style"] = map[string]any{
|
||||
"backgroundColor": config.Color,
|
||||
}
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"type": "antdText",
|
||||
"data": map[string]any{
|
||||
"id": id,
|
||||
"text": config.Text,
|
||||
"title": config.Title,
|
||||
"style": map[string]any{
|
||||
"backgroundColor": config.Color,
|
||||
"borderRadius": "20px",
|
||||
"color": "#fff",
|
||||
"display": "inline-block",
|
||||
"fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif",
|
||||
"fontSize": fontSize,
|
||||
"fontWeight": float64(400),
|
||||
"lineHeight": "24px",
|
||||
"minWidth": float64(24),
|
||||
"padding": "0 9px",
|
||||
"textAlign": "center",
|
||||
"whiteSpace": "nowrap",
|
||||
},
|
||||
},
|
||||
"type": "ResourceBadge",
|
||||
"data": data,
|
||||
}
|
||||
}
|
||||
|
||||
// createUnifiedBadgeFromKind creates a badge from kind with automatic color generation
|
||||
func createUnifiedBadgeFromKind(id, kind, title string, size BadgeSize) map[string]any {
|
||||
config := BadgeConfig{
|
||||
Text: initialsFromKind(kind),
|
||||
Color: hexColorForKind(kind),
|
||||
Title: title,
|
||||
Size: size,
|
||||
// createUnifiedBadgeFromKind creates a badge from kind with ResourceBadge component
|
||||
// Abbreviation is auto-generated by ResourceBadge from kind, but can be customized if needed
|
||||
func createUnifiedBadgeFromKind(id, kind string) map[string]any {
|
||||
return map[string]any{
|
||||
"type": "ResourceBadge",
|
||||
"data": map[string]any{
|
||||
"id": id,
|
||||
"value": kind,
|
||||
// abbreviation is optional - ResourceBadge auto-generates from value
|
||||
},
|
||||
}
|
||||
return createUnifiedBadge(id, config)
|
||||
}
|
||||
|
||||
// ---------------- Resource creation helpers with unified approach ----------------
|
||||
@@ -183,7 +144,9 @@ func createResourceConfig(components []string, kind, title string) ResourceConfi
|
||||
metadataName := generateMetadataName(specID)
|
||||
|
||||
// Generate badge config
|
||||
badgeConfig := generateBadgeConfig(kind, "", "", title)
|
||||
badgeConfig := BadgeConfig{
|
||||
Kind: kind,
|
||||
}
|
||||
|
||||
return ResourceConfig{
|
||||
SpecID: specID,
|
||||
@@ -196,35 +159,6 @@ func createResourceConfig(components []string, kind, title string) ResourceConfi
|
||||
|
||||
// ---------------- Enhanced color generation ----------------
|
||||
|
||||
// getColorForKind returns a color for a specific kind with improved distribution
|
||||
func getColorForKind(kind string) string {
|
||||
// Use existing hexColorForKind function
|
||||
return hexColorForKind(kind)
|
||||
}
|
||||
|
||||
// getColorForType returns a color for a specific type (like "namespace", "service", etc.)
|
||||
func getColorForType(typeName string) string {
|
||||
// Map common types to specific colors for consistency
|
||||
colorMap := map[string]string{
|
||||
"namespace": "#a25792ff",
|
||||
"service": "#6ca100",
|
||||
"pod": "#009596",
|
||||
"node": "#8476d1",
|
||||
"secret": "#c46100",
|
||||
"configmap": "#b48c78ff",
|
||||
"ingress": "#2e7dff",
|
||||
"workloadmonitor": "#c46100",
|
||||
"module": "#8b5cf6",
|
||||
}
|
||||
|
||||
if color, exists := colorMap[strings.ToLower(typeName)]; exists {
|
||||
return color
|
||||
}
|
||||
|
||||
// Fall back to hash-based color generation
|
||||
return hexColorForKind(typeName)
|
||||
}
|
||||
|
||||
// ---------------- Automatic ID generation for UI elements ----------------
|
||||
|
||||
// generateElementID creates an ID for UI elements based on context and type
|
||||
@@ -282,7 +216,6 @@ type UnifiedResourceConfig struct {
|
||||
Title string
|
||||
Color string
|
||||
BadgeText string
|
||||
Size BadgeSize
|
||||
}
|
||||
|
||||
// createUnifiedFactory creates a factory using unified approach
|
||||
@@ -292,16 +225,9 @@ func createUnifiedFactory(config UnifiedResourceConfig, tabs []any, urlsToFetch
|
||||
|
||||
// Create header with unified badge
|
||||
badgeConfig := BadgeConfig{
|
||||
Kind: config.Kind,
|
||||
Text: config.BadgeText,
|
||||
Color: config.Color,
|
||||
Title: config.Title,
|
||||
Size: config.Size,
|
||||
}
|
||||
if badgeConfig.Text == "" {
|
||||
badgeConfig.Text = initialsFromKind(config.Kind)
|
||||
}
|
||||
if badgeConfig.Color == "" {
|
||||
badgeConfig.Color = getColorForKind(config.Kind)
|
||||
}
|
||||
|
||||
badge := createUnifiedBadge(generateBadgeID("header", config.Kind), badgeConfig)
|
||||
@@ -348,7 +274,9 @@ func createUnifiedFactory(config UnifiedResourceConfig, tabs []any, urlsToFetch
|
||||
|
||||
// createUnifiedCustomColumn creates a custom column using unified approach
|
||||
func createUnifiedCustomColumn(name, jsonPath, kind, title, href string) map[string]any {
|
||||
badgeConfig := generateBadgeConfig(kind, "", "", title)
|
||||
badgeConfig := BadgeConfig{
|
||||
Kind: kind,
|
||||
}
|
||||
badge := createUnifiedBadge(generateBadgeID("column", kind), badgeConfig)
|
||||
|
||||
linkID := generateLinkID("column", "name")
|
||||
|
||||
@@ -1 +1 @@
|
||||
ghcr.io/cozystack/cozystack/nginx-cache:0.0.0@sha256:50ac1581e3100bd6c477a71161cb455a341ffaf9e5e2f6086802e4e25271e8af
|
||||
ghcr.io/cozystack/cozystack/nginx-cache:0.0.0@sha256:e0a07082bb6fc6aeaae2315f335386f1705a646c72f9e0af512aebbca5cb2b15
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
.helmignore
|
||||
/logos
|
||||
/Makefile
|
||||
/hack
|
||||
|
||||
@@ -6,9 +6,12 @@ include ../../../scripts/package.mk
|
||||
|
||||
generate:
|
||||
cozyvalues-gen -v values.yaml -s values.schema.json -r README.md
|
||||
yq -o=json -i '.properties.version.enum = (load("files/versions.yaml") | keys)' values.schema.json
|
||||
../../../hack/update-crd.sh
|
||||
|
||||
update:
|
||||
hack/update-versions.sh
|
||||
make generate
|
||||
|
||||
image: image-ubuntu-container-disk image-kubevirt-cloud-provider image-kubevirt-csi-driver image-cluster-autoscaler
|
||||
|
||||
image-ubuntu-container-disk:
|
||||
|
||||
@@ -104,7 +104,7 @@ See the reference for components utilized in this service:
|
||||
| `nodeGroups[name].resources.memory` | Memory (RAM) available. | `quantity` | `""` |
|
||||
| `nodeGroups[name].gpus` | List of GPUs to attach (NVIDIA driver requires at least 4 GiB RAM). | `[]object` | `[]` |
|
||||
| `nodeGroups[name].gpus[i].name` | Name of GPU, such as "nvidia.com/AD102GL_L40S". | `string` | `""` |
|
||||
| `version` | Kubernetes version (vMAJOR.MINOR). Supported: 1.28–1.33. | `string` | `v1.33` |
|
||||
| `version` | Kubernetes major.minor version to deploy | `string` | `v1.33` |
|
||||
| `host` | External hostname for Kubernetes cluster. Defaults to `<cluster-name>.<tenant-host>` if empty. | `string` | `""` |
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"v1.28": "v1.28.15"
|
||||
"v1.29": "v1.29.15"
|
||||
"v1.30": "v1.30.14"
|
||||
"v1.31": "v1.31.10"
|
||||
"v1.32": "v1.32.6"
|
||||
"v1.33": "v1.33.0"
|
||||
"v1.32": "v1.32.10"
|
||||
"v1.31": "v1.31.14"
|
||||
"v1.30": "v1.30.14"
|
||||
"v1.29": "v1.29.15"
|
||||
"v1.28": "v1.28.15"
|
||||
|
||||
260
packages/apps/kubernetes/hack/update-versions.sh
Executable file
260
packages/apps/kubernetes/hack/update-versions.sh
Executable file
@@ -0,0 +1,260 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
KUBERNETES_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
VALUES_FILE="${KUBERNETES_DIR}/values.yaml"
|
||||
VERSIONS_FILE="${KUBERNETES_DIR}/files/versions.yaml"
|
||||
MAKEFILE="${KUBERNETES_DIR}/Makefile"
|
||||
KAMAJI_DOCKERFILE="${KUBERNETES_DIR}/../../system/kamaji/images/kamaji/Dockerfile"
|
||||
|
||||
# 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 kamaji version from Dockerfile
|
||||
echo "Reading kamaji version from Dockerfile..."
|
||||
if [ ! -f "$KAMAJI_DOCKERFILE" ]; then
|
||||
echo "Error: Kamaji Dockerfile not found at $KAMAJI_DOCKERFILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
KAMAJI_VERSION=$(grep "^ARG VERSION=" "$KAMAJI_DOCKERFILE" | cut -d= -f2 | tr -d '"')
|
||||
if [ -z "$KAMAJI_VERSION" ]; then
|
||||
echo "Error: Could not extract kamaji version from Dockerfile" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Kamaji version: $KAMAJI_VERSION"
|
||||
|
||||
# Get Kubernetes version from kamaji repository
|
||||
echo "Fetching Kubernetes version from kamaji repository..."
|
||||
KUBERNETES_VERSION_FROM_KAMAJI=$(curl -sSL "https://raw.githubusercontent.com/clastix/kamaji/${KAMAJI_VERSION}/internal/upgrade/kubeadm_version.go" | grep "KubeadmVersion" | sed -E 's/.*KubeadmVersion = "([^"]+)".*/\1/')
|
||||
|
||||
if [ -z "$KUBERNETES_VERSION_FROM_KAMAJI" ]; then
|
||||
echo "Error: Could not fetch Kubernetes version from kamaji repository" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Kubernetes version from kamaji: $KUBERNETES_VERSION_FROM_KAMAJI"
|
||||
|
||||
# Extract major.minor version (e.g., "1.33" from "v1.33.0")
|
||||
KUBERNETES_MAJOR_MINOR=$(echo "$KUBERNETES_VERSION_FROM_KAMAJI" | sed -E 's/v([0-9]+)\.([0-9]+)\.[0-9]+/\1.\2/')
|
||||
KUBERNETES_MAJOR=$(echo "$KUBERNETES_MAJOR_MINOR" | cut -d. -f1)
|
||||
KUBERNETES_MINOR=$(echo "$KUBERNETES_MAJOR_MINOR" | cut -d. -f2)
|
||||
|
||||
echo "Kubernetes major.minor: $KUBERNETES_MAJOR_MINOR"
|
||||
|
||||
# Get available image tags
|
||||
echo "Fetching available image tags from registry..."
|
||||
AVAILABLE_TAGS=$(skopeo list-tags docker://registry.k8s.io/kube-apiserver | jq -r '.Tags[] | select(test("^v[0-9]+\\.[0-9]+\\.[0-9]+$"))' | sort -V)
|
||||
|
||||
if [ -z "$AVAILABLE_TAGS" ]; then
|
||||
echo "Error: Could not fetch available image tags" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Filter out versions higher than KUBERNETES_VERSION_FROM_KAMAJI
|
||||
echo "Filtering versions above ${KUBERNETES_VERSION_FROM_KAMAJI}..."
|
||||
FILTERED_TAGS=$(echo "$AVAILABLE_TAGS" | while read tag; do
|
||||
if [ -n "$tag" ]; then
|
||||
# Compare tag with KUBERNETES_VERSION_FROM_KAMAJI using version sort
|
||||
# Include tag if it's less than or equal to KUBERNETES_VERSION_FROM_KAMAJI
|
||||
if [ "$(printf '%s\n%s\n' "$tag" "$KUBERNETES_VERSION_FROM_KAMAJI" | sort -V | head -1)" = "$tag" ] || [ "$tag" = "$KUBERNETES_VERSION_FROM_KAMAJI" ]; then
|
||||
echo "$tag"
|
||||
fi
|
||||
fi
|
||||
done)
|
||||
|
||||
if [ -z "$FILTERED_TAGS" ]; then
|
||||
echo "Error: No versions found after filtering" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
AVAILABLE_TAGS="$FILTERED_TAGS"
|
||||
echo "Filtered to $(echo "$AVAILABLE_TAGS" | wc -l | tr -d ' ') versions"
|
||||
|
||||
# Find the latest patch version for the supported major.minor version
|
||||
echo "Finding latest patch version for ${KUBERNETES_MAJOR_MINOR}..."
|
||||
SUPPORTED_PATCH_TAGS=$(echo "$AVAILABLE_TAGS" | grep "^v${KUBERNETES_MAJOR}\\.${KUBERNETES_MINOR}\\.")
|
||||
if [ -z "$SUPPORTED_PATCH_TAGS" ]; then
|
||||
echo "Error: Could not find any patch versions for ${KUBERNETES_MAJOR_MINOR}" >&2
|
||||
exit 1
|
||||
fi
|
||||
KUBERNETES_VERSION=$(echo "$SUPPORTED_PATCH_TAGS" | tail -n1)
|
||||
echo "Using latest patch version: $KUBERNETES_VERSION"
|
||||
|
||||
# Build versions map: major.minor -> latest patch version
|
||||
# First, collect all unique major.minor versions from available tags
|
||||
echo "Collecting all available major.minor versions..."
|
||||
ALL_MAJOR_MINOR_VERSIONS=$(echo "$AVAILABLE_TAGS" | sed -E 's/v([0-9]+)\.([0-9]+)\.[0-9]+/v\1.\2/' | sort -V -u)
|
||||
|
||||
# Find the position of the supported version in the sorted list
|
||||
SUPPORTED_MAJOR_MINOR="v${KUBERNETES_MAJOR}.${KUBERNETES_MINOR}"
|
||||
echo "Looking for supported version: $SUPPORTED_MAJOR_MINOR"
|
||||
|
||||
# Get all versions that are <= supported version
|
||||
# Create a temporary file for filtering
|
||||
TEMP_VERSIONS=$(mktemp)
|
||||
|
||||
echo "$ALL_MAJOR_MINOR_VERSIONS" | while read version; do
|
||||
# Compare versions using sort -V (version sort)
|
||||
# If version <= supported, include it
|
||||
if [ "$(printf '%s\n%s\n' "$version" "$SUPPORTED_MAJOR_MINOR" | sort -V | head -1)" = "$version" ] || [ "$version" = "$SUPPORTED_MAJOR_MINOR" ]; then
|
||||
echo "$version"
|
||||
fi
|
||||
done > "$TEMP_VERSIONS"
|
||||
|
||||
# Get the supported version and 5 previous versions (total 6 versions)
|
||||
# First, find the position of supported version
|
||||
SUPPORTED_POS=$(grep -n "^${SUPPORTED_MAJOR_MINOR}$" "$TEMP_VERSIONS" | cut -d: -f1)
|
||||
|
||||
if [ -z "$SUPPORTED_POS" ]; then
|
||||
echo "Error: Supported version $SUPPORTED_MAJOR_MINOR not found in available versions" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Calculate start position (5 versions before supported, or from beginning if less than 5 available)
|
||||
TOTAL_LINES=$(wc -l < "$TEMP_VERSIONS" | tr -d ' ')
|
||||
START_POS=$((SUPPORTED_POS - 5))
|
||||
if [ $START_POS -lt 1 ]; then
|
||||
START_POS=1
|
||||
fi
|
||||
|
||||
# Extract versions from START_POS to SUPPORTED_POS (inclusive)
|
||||
CANDIDATE_VERSIONS=$(sed -n "${START_POS},${SUPPORTED_POS}p" "$TEMP_VERSIONS")
|
||||
|
||||
if [ -z "$CANDIDATE_VERSIONS" ]; then
|
||||
echo "Error: Could not find supported version $SUPPORTED_MAJOR_MINOR in available versions" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
declare -A VERSION_MAP
|
||||
VERSIONS=()
|
||||
|
||||
# Process each candidate version
|
||||
for major_minor_key in $CANDIDATE_VERSIONS; do
|
||||
# Extract major and minor for matching
|
||||
major=$(echo "$major_minor_key" | sed -E 's/v([0-9]+)\.([0-9]+)/\1/')
|
||||
minor=$(echo "$major_minor_key" | sed -E 's/v([0-9]+)\.([0-9]+)/\2/')
|
||||
|
||||
# Find all tags that match this major.minor version
|
||||
matching_tags=$(echo "$AVAILABLE_TAGS" | grep "^v${major}\\.${minor}\\.")
|
||||
|
||||
if [ -n "$matching_tags" ]; then
|
||||
# Get the latest patch version for this major.minor version
|
||||
latest_tag=$(echo "$matching_tags" | tail -n1)
|
||||
|
||||
VERSION_MAP["${major_minor_key}"]="${latest_tag}"
|
||||
VERSIONS+=("${major_minor_key}")
|
||||
echo "Found version: ${major_minor_key} -> ${latest_tag}"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#VERSIONS[@]} -eq 0 ]; then
|
||||
echo "Error: No matching versions found" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Sort versions in descending order (newest first)
|
||||
IFS=$'\n' VERSIONS=($(printf '%s\n' "${VERSIONS[@]}" | sort -V -r))
|
||||
unset IFS
|
||||
|
||||
echo "Versions to add: ${VERSIONS[*]}"
|
||||
|
||||
# Create/update versions.yaml file
|
||||
echo "Updating $VERSIONS_FILE..."
|
||||
{
|
||||
for ver in "${VERSIONS[@]}"; do
|
||||
echo "\"${ver}\": \"${VERSION_MAP[$ver]}\""
|
||||
done
|
||||
} > "$VERSIONS_FILE"
|
||||
|
||||
echo "Successfully updated $VERSIONS_FILE"
|
||||
|
||||
# Update values.yaml - enum with major.minor versions only
|
||||
TEMP_FILE=$(mktemp)
|
||||
trap "rm -f $TEMP_FILE $TEMP_VERSIONS" EXIT
|
||||
|
||||
# Build new version section
|
||||
NEW_VERSION_SECTION="## @enum {string} Version"
|
||||
for ver in "${VERSIONS[@]}"; do
|
||||
NEW_VERSION_SECTION="${NEW_VERSION_SECTION}
|
||||
## @value $ver"
|
||||
done
|
||||
NEW_VERSION_SECTION="${NEW_VERSION_SECTION}
|
||||
|
||||
## @param {Version} version - Kubernetes major.minor version to deploy
|
||||
version: \"${VERSIONS[0]}\""
|
||||
|
||||
# Check if version section already exists
|
||||
if grep -q "^## @enum {string} Version" "$VALUES_FILE"; then
|
||||
# Version section exists, update it using awk
|
||||
echo "Updating existing version section in $VALUES_FILE..."
|
||||
|
||||
# Use awk to replace the section from "## @enum {string} Version" to "version: " (inclusive)
|
||||
# Delete the old section and insert the new one
|
||||
awk -v new_section="$NEW_VERSION_SECTION" '
|
||||
/^## @enum {string} Version/ {
|
||||
in_section = 1
|
||||
print new_section
|
||||
next
|
||||
}
|
||||
in_section && /^version: / {
|
||||
in_section = 0
|
||||
next
|
||||
}
|
||||
in_section {
|
||||
next
|
||||
}
|
||||
{ print }
|
||||
' "$VALUES_FILE" > "$TEMP_FILE.tmp"
|
||||
mv "$TEMP_FILE.tmp" "$VALUES_FILE"
|
||||
else
|
||||
# Version section doesn't exist, insert it before Application-specific parameters section
|
||||
echo "Inserting new version section in $VALUES_FILE..."
|
||||
|
||||
# Use awk to insert before "## @section Application-specific parameters"
|
||||
awk -v new_section="$NEW_VERSION_SECTION" '
|
||||
/^## @section Application-specific parameters/ {
|
||||
print new_section
|
||||
print ""
|
||||
}
|
||||
{ print }
|
||||
' "$VALUES_FILE" > "$TEMP_FILE.tmp"
|
||||
mv "$TEMP_FILE.tmp" "$VALUES_FILE"
|
||||
fi
|
||||
|
||||
echo "Successfully updated $VALUES_FILE with versions: ${VERSIONS[*]}"
|
||||
|
||||
# Update KUBERNETES_VERSION in Makefile
|
||||
# Extract major.minor from KUBERNETES_VERSION (e.g., "v1.33" from "v1.33.4")
|
||||
KUBERNETES_MAJOR_MINOR_FOR_MAKEFILE=$(echo "$KUBERNETES_VERSION" | sed -E 's/v([0-9]+)\.([0-9]+)\.[0-9]+/v\1.\2/')
|
||||
|
||||
if grep -q "^KUBERNETES_VERSION" "$MAKEFILE"; then
|
||||
# Update existing KUBERNETES_VERSION line using awk
|
||||
echo "Updating KUBERNETES_VERSION in $MAKEFILE..."
|
||||
awk -v new_version="${KUBERNETES_MAJOR_MINOR_FOR_MAKEFILE}" '
|
||||
/^KUBERNETES_VERSION = / {
|
||||
print "KUBERNETES_VERSION = " new_version
|
||||
next
|
||||
}
|
||||
{ print }
|
||||
' "$MAKEFILE" > "$TEMP_FILE.tmp"
|
||||
mv "$TEMP_FILE.tmp" "$MAKEFILE"
|
||||
echo "Successfully updated KUBERNETES_VERSION in $MAKEFILE to ${KUBERNETES_MAJOR_MINOR_FOR_MAKEFILE}"
|
||||
else
|
||||
echo "Warning: KUBERNETES_VERSION not found in $MAKEFILE" >&2
|
||||
fi
|
||||
|
||||
@@ -1 +1 @@
|
||||
ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:c8b08084a86251cdd18e237de89b695bca0e4f7eb1f1f6ddc2b903b4d74ea5ff
|
||||
ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:d5c836ba33cf5dbed7e6f866784f668f80ffe69179e7c75847b680111984eefb
|
||||
|
||||
@@ -182,6 +182,33 @@ metadata:
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
files:
|
||||
- path: /usr/bin/update-k8s.sh
|
||||
owner: root:root
|
||||
permissions: "0755"
|
||||
content: |
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Expected to be passed in via preKubeadmCommands
|
||||
: "${KUBELET_VERSION:?KUBELET_VERSION must be set, e.g. v1.31.0}"
|
||||
|
||||
ARCH="$(uname -m)"
|
||||
case "${ARCH}" in
|
||||
x86_64) ARCH=amd64 ;;
|
||||
aarch64) ARCH=arm64 ;;
|
||||
esac
|
||||
|
||||
# Use your internal mirror here for real-world use.
|
||||
BASE_URL="https://dl.k8s.io/release/${KUBELET_VERSION}/bin/linux/${ARCH}"
|
||||
|
||||
echo "Installing kubelet and kubeadm ${KUBELET_VERSION} for ${ARCH}..."
|
||||
curl -fsSL "${BASE_URL}/kubelet" -o /root/kubelet
|
||||
curl -fsSL "${BASE_URL}/kubeadm" -o /root/kubeadm
|
||||
chmod 0755 /root/kubelet
|
||||
chmod 0755 /root/kubeadm
|
||||
if /root/kubelet --version ; then mv /root/kubelet /usr/bin/kubelet ; fi
|
||||
if /root/kubeadm version ; then mv /root/kubeadm /usr/bin/kubeadm ; fi
|
||||
diskSetup:
|
||||
filesystems:
|
||||
- device: /dev/vdb
|
||||
@@ -205,6 +232,7 @@ spec:
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
preKubeadmCommands:
|
||||
- KUBELET_VERSION={{ include "kubernetes.versionMap" $}} /usr/bin/update-k8s.sh || true
|
||||
- sed -i 's|root:x:|root::|' /etc/passwd
|
||||
- systemctl stop containerd.service
|
||||
- mkdir -p /ephemeral/kubelet /ephemeral/containerd
|
||||
|
||||
149
packages/apps/kubernetes/templates/delete.yaml
Normal file
149
packages/apps/kubernetes/templates/delete.yaml
Normal file
@@ -0,0 +1,149 @@
|
||||
---
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
annotations:
|
||||
"helm.sh/hook": pre-delete
|
||||
"helm.sh/hook-weight": "10"
|
||||
"helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation
|
||||
name: {{ .Release.Name }}-pre-cleanup
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
policy.cozystack.io/allow-to-apiserver: "true"
|
||||
spec:
|
||||
serviceAccountName: {{ .Release.Name }}-cleanup
|
||||
restartPolicy: Never
|
||||
tolerations:
|
||||
- key: CriticalAddonsOnly
|
||||
operator: Exists
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
effect: "NoSchedule"
|
||||
containers:
|
||||
- name: kubectl
|
||||
image: docker.io/clastix/kubectl:v1.32
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
echo "Step 1: Suspending all HelmReleases with label cozystack.io/target-cluster-name={{ .Release.Name }}"
|
||||
for hr in $(kubectl -n {{ .Release.Namespace }} get helmreleases.helm.toolkit.fluxcd.io -l "cozystack.io/target-cluster-name={{ .Release.Name }}" -o name 2>/dev/null || true); do
|
||||
if [ -n "$hr" ]; then
|
||||
echo " Suspending $hr"
|
||||
kubectl -n {{ .Release.Namespace }} patch "$hr" \
|
||||
-p '{"spec": {"suspend": true}}' \
|
||||
--type=merge --field-manager=flux-client-side-apply
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Step 2: Deleting HelmReleases with label cozystack.io/target-cluster-name={{ .Release.Name }}"
|
||||
kubectl -n {{ .Release.Namespace }} delete helmreleases.helm.toolkit.fluxcd.io \
|
||||
-l "cozystack.io/target-cluster-name={{ .Release.Name }}" \
|
||||
--ignore-not-found=true --wait=true
|
||||
|
||||
echo "Step 3: Deleting KamajiControlPlane {{ .Release.Name }}"
|
||||
kubectl -n {{ .Release.Namespace }} delete kamajicontrolplanes.controlplane.cluster.x-k8s.io {{ .Release.Name }} \
|
||||
--ignore-not-found=true
|
||||
|
||||
echo "Step 4: Deleting TenantControlPlane {{ .Release.Name }}"
|
||||
kubectl -n {{ .Release.Namespace }} delete tenantcontrolplanes.kamaji.clastix.io {{ .Release.Name }} \
|
||||
--ignore-not-found=true
|
||||
|
||||
echo "Step 5: Cleaning up DataVolumes"
|
||||
kubectl -n {{ .Release.Namespace }} delete datavolumes \
|
||||
-l "cluster.x-k8s.io/cluster-name={{ .Release.Name }}" \
|
||||
--ignore-not-found=true
|
||||
|
||||
echo "Step 6: Cleaning up LoadBalancer Services"
|
||||
kubectl -n {{ .Release.Namespace }} delete services \
|
||||
-l "cluster.x-k8s.io/cluster-name={{ .Release.Name }}" \
|
||||
--field-selector spec.type=LoadBalancer \
|
||||
--ignore-not-found=true
|
||||
|
||||
echo "Cleanup completed successfully"
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-cleanup
|
||||
annotations:
|
||||
helm.sh/hook: pre-delete
|
||||
helm.sh/hook-delete-policy: hook-succeeded,before-hook-creation,hook-failed
|
||||
helm.sh/hook-weight: "0"
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
annotations:
|
||||
"helm.sh/hook": pre-delete
|
||||
"helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation,hook-failed
|
||||
"helm.sh/hook-weight": "5"
|
||||
name: {{ .Release.Name }}-cleanup
|
||||
rules:
|
||||
- apiGroups:
|
||||
- "helm.toolkit.fluxcd.io"
|
||||
resources:
|
||||
- helmreleases
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- patch
|
||||
- delete
|
||||
- apiGroups:
|
||||
- "controlplane.cluster.x-k8s.io"
|
||||
resources:
|
||||
- kamajicontrolplanes
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- delete
|
||||
- apiGroups:
|
||||
- "kamaji.clastix.io"
|
||||
resources:
|
||||
- tenantcontrolplanes
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- delete
|
||||
- apiGroups:
|
||||
- "cdi.kubevirt.io"
|
||||
resources:
|
||||
- datavolumes
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- delete
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- services
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- delete
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
annotations:
|
||||
"helm.sh/hook": pre-delete
|
||||
"helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation,hook-failed
|
||||
"helm.sh/hook-weight": "5"
|
||||
name: {{ .Release.Name }}-cleanup
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: {{ .Release.Name }}-cleanup
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: {{ .Release.Name }}-cleanup
|
||||
namespace: {{ .Release.Namespace }}
|
||||
@@ -45,8 +45,6 @@ spec:
|
||||
- name: {{ .Release.Name }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
{{- end }}
|
||||
- name: {{ .Release.Name }}-cilium
|
||||
namespace: {{ .Release.Namespace }}
|
||||
{{- if .Values.addons.certManager.valuesOverride }}
|
||||
---
|
||||
apiVersion: v1
|
||||
|
||||
@@ -37,6 +37,8 @@ spec:
|
||||
dependsOn:
|
||||
- name: {{ .Release.Name }}-vsnap-crd
|
||||
namespace: {{ .Release.Namespace }}
|
||||
- name: {{ .Release.Name }}-cilium
|
||||
namespace: {{ .Release.Namespace }}
|
||||
{{- if lookup "helm.toolkit.fluxcd.io/v2" "HelmRelease" .Release.Namespace .Release.Name }}
|
||||
- name: {{ .Release.Name }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
---
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
annotations:
|
||||
"helm.sh/hook": pre-delete
|
||||
"helm.sh/hook-weight": "10"
|
||||
"helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation,hook-failed
|
||||
name: {{ .Release.Name }}-flux-teardown
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
serviceAccountName: {{ .Release.Name }}-flux-teardown
|
||||
restartPolicy: Never
|
||||
tolerations:
|
||||
- key: CriticalAddonsOnly
|
||||
operator: Exists
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
effect: "NoSchedule"
|
||||
containers:
|
||||
- name: kubectl
|
||||
image: docker.io/clastix/kubectl:v1.32
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
kubectl
|
||||
--namespace={{ .Release.Namespace }}
|
||||
patch
|
||||
helmrelease
|
||||
{{ .Release.Name }}-cilium
|
||||
{{ .Release.Name }}-gateway-api-crds
|
||||
{{ .Release.Name }}-csi
|
||||
{{ .Release.Name }}-cert-manager
|
||||
{{ .Release.Name }}-cert-manager-crds
|
||||
{{ .Release.Name }}-vertical-pod-autoscaler
|
||||
{{ .Release.Name }}-vertical-pod-autoscaler-crds
|
||||
{{ .Release.Name }}-ingress-nginx
|
||||
{{ .Release.Name }}-fluxcd-operator
|
||||
{{ .Release.Name }}-fluxcd
|
||||
{{ .Release.Name }}-gpu-operator
|
||||
{{ .Release.Name }}-velero
|
||||
{{ .Release.Name }}-coredns
|
||||
-p '{"spec": {"suspend": true}}'
|
||||
--type=merge --field-manager=flux-client-side-apply || true
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-flux-teardown
|
||||
annotations:
|
||||
helm.sh/hook: pre-delete
|
||||
helm.sh/hook-delete-policy: before-hook-creation,hook-failed
|
||||
helm.sh/hook-weight: "0"
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
annotations:
|
||||
"helm.sh/hook": pre-install,post-install,pre-delete
|
||||
"helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation,hook-failed
|
||||
"helm.sh/hook-weight": "5"
|
||||
name: {{ .Release.Name }}-flux-teardown
|
||||
rules:
|
||||
- apiGroups:
|
||||
- "helm.toolkit.fluxcd.io"
|
||||
resources:
|
||||
- helmreleases
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
resourceNames:
|
||||
- {{ .Release.Name }}-cilium
|
||||
- {{ .Release.Name }}-csi
|
||||
- {{ .Release.Name }}-cert-manager
|
||||
- {{ .Release.Name }}-cert-manager-crds
|
||||
- {{ .Release.Name }}-vertical-pod-autoscaler
|
||||
- {{ .Release.Name }}-vertical-pod-autoscaler-crds
|
||||
- {{ .Release.Name }}-ingress-nginx
|
||||
- {{ .Release.Name }}-fluxcd-operator
|
||||
- {{ .Release.Name }}-fluxcd
|
||||
- {{ .Release.Name }}-gpu-operator
|
||||
- {{ .Release.Name }}-velero
|
||||
- {{ .Release.Name }}-coredns
|
||||
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
annotations:
|
||||
helm.sh/hook: pre-delete
|
||||
helm.sh/hook-delete-policy: hook-succeeded,before-hook-creation,hook-failed
|
||||
helm.sh/hook-weight: "5"
|
||||
name: {{ .Release.Name }}-flux-teardown
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: {{ .Release.Name }}-flux-teardown
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: {{ .Release.Name }}-flux-teardown
|
||||
namespace: {{ .Release.Namespace }}
|
||||
@@ -0,0 +1,42 @@
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-metrics-server
|
||||
labels:
|
||||
cozystack.io/repository: system
|
||||
cozystack.io/target-cluster-name: {{ .Release.Name }}
|
||||
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'
|
||||
kubeConfig:
|
||||
secretRef:
|
||||
name: {{ .Release.Name }}-admin-kubeconfig
|
||||
key: super-admin.svc
|
||||
targetNamespace: cozy-monitoring
|
||||
storageNamespace: cozy-monitoring
|
||||
interval: 5m
|
||||
timeout: 10m
|
||||
install:
|
||||
createNamespace: true
|
||||
remediation:
|
||||
retries: -1
|
||||
upgrade:
|
||||
remediation:
|
||||
retries: -1
|
||||
dependsOn:
|
||||
{{- if lookup "helm.toolkit.fluxcd.io/v2" "HelmRelease" .Release.Namespace .Release.Name }}
|
||||
- name: {{ .Release.Name }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
{{- end }}
|
||||
- name: {{ .Release.Name }}-cilium
|
||||
namespace: {{ .Release.Namespace }}
|
||||
- name: {{ .Release.Name }}-prometheus-operator-crds
|
||||
namespace: {{ .Release.Namespace }}
|
||||
@@ -44,6 +44,10 @@ spec:
|
||||
namespace: {{ .Release.Namespace }}
|
||||
- name: {{ .Release.Name }}-vertical-pod-autoscaler-crds
|
||||
namespace: {{ .Release.Namespace }}
|
||||
- name: {{ .Release.Name }}-prometheus-operator-crds
|
||||
namespace: {{ .Release.Namespace }}
|
||||
- name: {{ .Release.Name }}-metrics-server
|
||||
namespace: {{ .Release.Namespace }}
|
||||
values:
|
||||
vmagent:
|
||||
externalLabels:
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-prometheus-operator-crds
|
||||
labels:
|
||||
cozystack.io/repository: system
|
||||
cozystack.io/target-cluster-name: {{ .Release.Name }}
|
||||
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'
|
||||
kubeConfig:
|
||||
secretRef:
|
||||
name: {{ .Release.Name }}-admin-kubeconfig
|
||||
key: super-admin.svc
|
||||
targetNamespace: cozy-victoria-metrics-operator
|
||||
storageNamespace: cozy-victoria-metrics-operator
|
||||
interval: 5m
|
||||
install:
|
||||
createNamespace: true
|
||||
remediation:
|
||||
retries: -1
|
||||
upgrade:
|
||||
remediation:
|
||||
retries: -1
|
||||
dependsOn:
|
||||
{{- if lookup "helm.toolkit.fluxcd.io/v2" "HelmRelease" .Release.Namespace .Release.Name }}
|
||||
- name: {{ .Release.Name }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
{{- end }}
|
||||
@@ -37,6 +37,4 @@ spec:
|
||||
- name: {{ .Release.Name }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
{{- end }}
|
||||
- name: {{ .Release.Name }}-cilium
|
||||
namespace: {{ .Release.Namespace }}
|
||||
{{- end }}
|
||||
|
||||
@@ -37,5 +37,3 @@ spec:
|
||||
- name: {{ .Release.Name }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
{{- end }}
|
||||
- name: {{ .Release.Name }}-cilium
|
||||
namespace: {{ .Release.Namespace }}
|
||||
@@ -492,7 +492,7 @@
|
||||
}
|
||||
},
|
||||
"host": {
|
||||
"description": "External hostname for Kubernetes cluster. Defaults to `<cluster-name>.<tenant-host>` if empty.",
|
||||
"description": "External hostname for Kubernetes cluster. Defaults to `\u003ccluster-name\u003e.\u003ctenant-host\u003e` if empty.",
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
@@ -615,17 +615,17 @@
|
||||
"default": "replicated"
|
||||
},
|
||||
"version": {
|
||||
"description": "Kubernetes version (vMAJOR.MINOR). Supported: 1.28–1.33.",
|
||||
"description": "Kubernetes major.minor version to deploy",
|
||||
"type": "string",
|
||||
"default": "v1.33",
|
||||
"enum": [
|
||||
"v1.28",
|
||||
"v1.29",
|
||||
"v1.30",
|
||||
"v1.31",
|
||||
"v1.33",
|
||||
"v1.32",
|
||||
"v1.33"
|
||||
"v1.31",
|
||||
"v1.30",
|
||||
"v1.29",
|
||||
"v1.28"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,9 +46,19 @@ nodeGroups:
|
||||
resources: {}
|
||||
gpus: []
|
||||
|
||||
## @param {string} version - Kubernetes version (vMAJOR.MINOR). Supported: 1.28–1.33.
|
||||
##
|
||||
## @enum {string} Version
|
||||
## @value v1.33
|
||||
## @value v1.32
|
||||
## @value v1.31
|
||||
## @value v1.30
|
||||
## @value v1.29
|
||||
## @value v1.28
|
||||
|
||||
## @param {Version} version - Kubernetes major.minor version to deploy
|
||||
version: "v1.33"
|
||||
|
||||
|
||||
## @param {string} host - External hostname for Kubernetes cluster. Defaults to `<cluster-name>.<tenant-host>` if empty.
|
||||
host: ""
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
.helmignore
|
||||
/logos
|
||||
/Makefile
|
||||
/hack
|
||||
|
||||
@@ -7,6 +7,10 @@ generate:
|
||||
cozyvalues-gen -v values.yaml -s values.schema.json -r README.md
|
||||
../../../hack/update-crd.sh
|
||||
|
||||
update:
|
||||
hack/update-versions.sh
|
||||
make generate
|
||||
|
||||
image:
|
||||
docker buildx build images/mariadb-backup \
|
||||
--tag $(REGISTRY)/mariadb-backup:$(call settag,$(MARIADB_BACKUP_TAG)) \
|
||||
|
||||
@@ -79,6 +79,7 @@ more details:
|
||||
| `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` |
|
||||
| `version` | MariaDB major.minor version to deploy | `string` | `v11.8` |
|
||||
|
||||
|
||||
### Application-specific parameters
|
||||
|
||||
4
packages/apps/mysql/files/versions.yaml
Normal file
4
packages/apps/mysql/files/versions.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
"v11.8": "11.8.5"
|
||||
"v11.4": "11.4.9"
|
||||
"v10.11": "10.11.15"
|
||||
"v10.6": "10.6.24"
|
||||
151
packages/apps/mysql/hack/update-versions.sh
Executable file
151
packages/apps/mysql/hack/update-versions.sh
Executable file
@@ -0,0 +1,151 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -o errexit
|
||||
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_API_URL="https://downloads.mariadb.org/rest-api/mariadb/"
|
||||
|
||||
# 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 LTS versions from MariaDB REST API
|
||||
echo "Fetching LTS versions from MariaDB REST API..."
|
||||
LTS_VERSIONS_JSON=$(curl -sSL "${MARIADB_API_URL}")
|
||||
|
||||
if [ -z "$LTS_VERSIONS_JSON" ]; then
|
||||
echo "Error: Could not fetch versions from MariaDB REST API" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract LTS stable major versions
|
||||
LTS_MAJOR_VERSIONS=$(echo "$LTS_VERSIONS_JSON" | jq -r '.major_releases[] | select(.release_support_type == "Long Term Support") | select(.release_status == "Stable") | .release_id' | sort -V -r)
|
||||
|
||||
if [ -z "$LTS_MAJOR_VERSIONS" ]; then
|
||||
echo "Error: Could not find any LTS stable versions" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Found LTS major versions: $(echo "$LTS_MAJOR_VERSIONS" | tr '\n' ' ')"
|
||||
|
||||
# Build versions map: major version -> latest patch version
|
||||
declare -A VERSION_MAP
|
||||
MAJOR_VERSIONS=()
|
||||
|
||||
for major_version in $LTS_MAJOR_VERSIONS; do
|
||||
echo "Fetching patch versions for ${major_version}..."
|
||||
|
||||
# Get patch versions for this major version
|
||||
PATCH_VERSIONS_JSON=$(curl -sSL "${MARIADB_API_URL}${major_version}")
|
||||
|
||||
if [ -z "$PATCH_VERSIONS_JSON" ]; then
|
||||
echo "Warning: Could not fetch patch versions for ${major_version}, skipping..." >&2
|
||||
continue
|
||||
fi
|
||||
|
||||
# Extract all stable patch version IDs (format: MAJOR.MINOR.PATCH)
|
||||
# Filter only Stable releases
|
||||
PATCH_VERSIONS=$(echo "$PATCH_VERSIONS_JSON" | jq -r --arg major "$major_version" '.releases | to_entries[] | select(.key | startswith($major + ".")) | select(.value.release_status == "Stable") | .key' | sort -V)
|
||||
|
||||
# If no stable releases found, try to get any releases (for backwards compatibility)
|
||||
if [ -z "$PATCH_VERSIONS" ]; then
|
||||
PATCH_VERSIONS=$(echo "$PATCH_VERSIONS_JSON" | jq -r '.releases | keys[]' | grep -E "^${major_version}\." | sort -V)
|
||||
fi
|
||||
|
||||
if [ -z "$PATCH_VERSIONS" ]; then
|
||||
echo "Warning: Could not find any patch versions for ${major_version}, skipping..." >&2
|
||||
continue
|
||||
fi
|
||||
|
||||
# Get the latest patch version
|
||||
LATEST_PATCH=$(echo "$PATCH_VERSIONS" | tail -n1)
|
||||
|
||||
# major_version already has format MAJOR.MINOR (e.g., "11.8")
|
||||
VERSION_MAP["v${major_version}"]="${LATEST_PATCH}"
|
||||
MAJOR_VERSIONS+=("v${major_version}")
|
||||
echo "Found version: v${major_version} -> ${LATEST_PATCH}"
|
||||
done
|
||||
|
||||
if [ ${#MAJOR_VERSIONS[@]} -eq 0 ]; then
|
||||
echo "Error: No matching versions found" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Sort major versions in descending order (newest first)
|
||||
IFS=$'\n' MAJOR_VERSIONS=($(printf '%s\n' "${MAJOR_VERSIONS[@]}" | sort -V -r))
|
||||
unset IFS
|
||||
|
||||
echo "Major versions to add: ${MAJOR_VERSIONS[*]}"
|
||||
|
||||
# Create/update versions.yaml file
|
||||
echo "Updating $VERSIONS_FILE..."
|
||||
{
|
||||
for major_ver in "${MAJOR_VERSIONS[@]}"; do
|
||||
echo "\"${major_ver}\": \"${VERSION_MAP[$major_ver]}\""
|
||||
done
|
||||
} > "$VERSIONS_FILE"
|
||||
|
||||
echo "Successfully updated $VERSIONS_FILE"
|
||||
|
||||
# Update values.yaml - enum with major.minor versions only
|
||||
TEMP_FILE=$(mktemp)
|
||||
trap "rm -f $TEMP_FILE" EXIT
|
||||
|
||||
# Build new version section
|
||||
NEW_VERSION_SECTION="## @enum {string} Version"
|
||||
for major_ver in "${MAJOR_VERSIONS[@]}"; do
|
||||
NEW_VERSION_SECTION="${NEW_VERSION_SECTION}
|
||||
## @value $major_ver"
|
||||
done
|
||||
NEW_VERSION_SECTION="${NEW_VERSION_SECTION}
|
||||
|
||||
## @param {Version} version - MariaDB major.minor version to deploy
|
||||
version: ${MAJOR_VERSIONS[0]}"
|
||||
|
||||
# Check if version section already exists
|
||||
if grep -q "^## @enum {string} Version" "$VALUES_FILE"; then
|
||||
# Version section exists, update it using awk
|
||||
echo "Updating existing version section in $VALUES_FILE..."
|
||||
|
||||
# Use awk to replace the section from "## @enum {string} Version" to "version: " (inclusive)
|
||||
# Delete the old section and insert the new one
|
||||
awk -v new_section="$NEW_VERSION_SECTION" '
|
||||
/^## @enum {string} Version/ {
|
||||
in_section = 1
|
||||
print new_section
|
||||
next
|
||||
}
|
||||
in_section && /^version: / {
|
||||
in_section = 0
|
||||
next
|
||||
}
|
||||
in_section {
|
||||
next
|
||||
}
|
||||
{ print }
|
||||
' "$VALUES_FILE" > "$TEMP_FILE.tmp"
|
||||
mv "$TEMP_FILE.tmp" "$VALUES_FILE"
|
||||
else
|
||||
# Version section doesn't exist, insert it before Application-specific parameters section
|
||||
echo "Inserting new version section in $VALUES_FILE..."
|
||||
|
||||
# Use awk to insert before "## @section Application-specific parameters"
|
||||
awk -v new_section="$NEW_VERSION_SECTION" '
|
||||
/^## @section Application-specific parameters/ {
|
||||
print new_section
|
||||
print ""
|
||||
}
|
||||
{ print }
|
||||
' "$VALUES_FILE" > "$TEMP_FILE.tmp"
|
||||
mv "$TEMP_FILE.tmp" "$VALUES_FILE"
|
||||
fi
|
||||
|
||||
echo "Successfully updated $VALUES_FILE with major.minor versions: ${MAJOR_VERSIONS[*]}"
|
||||
|
||||
8
packages/apps/mysql/templates/_versions.tpl
Normal file
8
packages/apps/mysql/templates/_versions.tpl
Normal file
@@ -0,0 +1,8 @@
|
||||
{{- define "mysql.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 }}
|
||||
{{- end }}
|
||||
{{- index $versionMap .Values.version }}
|
||||
{{- end }}
|
||||
|
||||
74
packages/apps/mysql/templates/hooks/cleanup-pvc.yaml
Normal file
74
packages/apps/mysql/templates/hooks/cleanup-pvc.yaml
Normal file
@@ -0,0 +1,74 @@
|
||||
---
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-cleanup
|
||||
labels:
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
annotations:
|
||||
"helm.sh/hook": post-delete
|
||||
"helm.sh/hook-weight": "10"
|
||||
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
policy.cozystack.io/allow-to-apiserver: "true"
|
||||
spec:
|
||||
serviceAccountName: {{ .Release.Name }}-cleanup
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
- name: cleanup
|
||||
image: docker.io/clastix/kubectl:v1.32
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
echo "Deleting orphaned PVCs for {{ .Release.Name }}..."
|
||||
kubectl delete pvc -n {{ .Release.Namespace }} -l app.kubernetes.io/instance={{ .Release.Name }} || true
|
||||
echo "PVC cleanup complete."
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-cleanup
|
||||
labels:
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
annotations:
|
||||
"helm.sh/hook": post-delete
|
||||
helm.sh/hook-weight: "0"
|
||||
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-cleanup
|
||||
labels:
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
annotations:
|
||||
"helm.sh/hook": post-delete
|
||||
"helm.sh/hook-weight": "5"
|
||||
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["persistentvolumeclaims"]
|
||||
verbs: ["get", "list", "delete"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-cleanup
|
||||
labels:
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
annotations:
|
||||
"helm.sh/hook": post-delete
|
||||
helm.sh/hook-weight: "5"
|
||||
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: {{ .Release.Name }}-cleanup
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: {{ .Release.Name }}-cleanup
|
||||
@@ -8,7 +8,7 @@ spec:
|
||||
name: {{ .Release.Name }}-credentials
|
||||
key: root
|
||||
|
||||
image: "mariadb:11.0.2"
|
||||
image: "mariadb:{{ include "mysql.versionMap" $ }}"
|
||||
|
||||
port: 3306
|
||||
|
||||
@@ -37,6 +37,10 @@ spec:
|
||||
# automaticFailover: true
|
||||
{{- end }}
|
||||
|
||||
podMetadata:
|
||||
labels:
|
||||
"policy.cozystack.io/allow-to-apiserver": "true"
|
||||
|
||||
metrics:
|
||||
enabled: true
|
||||
exporter:
|
||||
|
||||
@@ -186,6 +186,17 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"version": {
|
||||
"description": "MariaDB major.minor version to deploy",
|
||||
"type": "string",
|
||||
"default": "v11.8",
|
||||
"enum": [
|
||||
"v11.8",
|
||||
"v11.4",
|
||||
"v10.11",
|
||||
"v10.6"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,16 @@ storageClass: ""
|
||||
## @param {bool} external - Enable external access from outside the cluster.
|
||||
external: false
|
||||
|
||||
##
|
||||
## @enum {string} Version
|
||||
## @value v11.8
|
||||
## @value v11.4
|
||||
## @value v10.11
|
||||
## @value v10.6
|
||||
|
||||
## @param {Version} version - MariaDB major.minor version to deploy
|
||||
version: v11.8
|
||||
|
||||
##
|
||||
## @section Application-specific parameters
|
||||
##
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }}
|
||||
{{- $clusterDomain := (index $cozyConfig.data "cluster-domain") | default "cozy.local" }}
|
||||
{{- $existingSecret := lookup "v1" "Secret" .Release.Namespace (printf "%s-credentials" .Release.Name) }}
|
||||
{{- $passwords := dict }}
|
||||
|
||||
{{- with (dig "data" (dict) $existingSecret) }}
|
||||
{{- range $k, $v := . }}
|
||||
{{- $_ := set $passwords $k (b64dec $v) }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- range $user, $u := .Values.users }}
|
||||
{{- if $u.password }}
|
||||
{{- $_ := set $passwords $user $u.password }}
|
||||
@@ -47,18 +55,14 @@ spec:
|
||||
retries: -1
|
||||
values:
|
||||
nats:
|
||||
podTemplate:
|
||||
container:
|
||||
merge:
|
||||
spec:
|
||||
containers:
|
||||
- name: nats
|
||||
image: nats:2.10.17-alpine
|
||||
resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.resourcesPreset .Values.resources $) | nindent 22 }}
|
||||
resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.resourcesPreset .Values.resources $) | nindent 12 }}
|
||||
fullnameOverride: {{ .Release.Name }}
|
||||
config:
|
||||
{{- if or (gt (len $passwords) 0) (gt (len .Values.config.merge) 0) }}
|
||||
{{- if or $passwords .Values.config.merge }}
|
||||
merge:
|
||||
{{- if gt (len $passwords) 0 }}
|
||||
{{- if $passwords }}
|
||||
accounts:
|
||||
A:
|
||||
users:
|
||||
@@ -67,13 +71,13 @@ spec:
|
||||
password: "{{ $password }}"
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if and .Values.config (hasKey .Values.config "merge") }}
|
||||
{{ toYaml .Values.config.merge | nindent 12 }}
|
||||
{{- with .Values.config.merge }}
|
||||
{{- toYaml . | nindent 10 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if and .Values.config (hasKey .Values.config "resolver") }}
|
||||
{{- with .Values.config.resolver }}
|
||||
resolver:
|
||||
{{ toYaml .Values.config.resolver | nindent 12 }}
|
||||
{{- toYaml . | nindent 10 }}
|
||||
{{- end }}
|
||||
cluster:
|
||||
enabled: true
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
.helmignore
|
||||
/logos
|
||||
/Makefile
|
||||
/hack
|
||||
|
||||
@@ -3,3 +3,7 @@ include ../../../scripts/package.mk
|
||||
generate:
|
||||
cozyvalues-gen -v values.yaml -s values.schema.json -r README.md
|
||||
../../../hack/update-crd.sh
|
||||
|
||||
update:
|
||||
hack/update-versions.sh
|
||||
make generate
|
||||
|
||||
@@ -76,6 +76,7 @@ See:
|
||||
| `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` |
|
||||
| `version` | PostgreSQL major version to deploy | `string` | `v18` |
|
||||
|
||||
|
||||
### Application-specific parameters
|
||||
|
||||
6
packages/apps/postgres/files/versions.yaml
Normal file
6
packages/apps/postgres/files/versions.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
"v18": "v18.1"
|
||||
"v17": "v17.7"
|
||||
"v16": "v16.11"
|
||||
"v15": "v15.15"
|
||||
"v14": "v14.20"
|
||||
"v13": "v13.22"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user