Compare commits

..

48 Commits

Author SHA1 Message Date
Andrei Kvapil
4f0e042eac Merge remote-tracking branch 'upstream/main' into refactor-engine 2025-12-01 22:38:51 +01:00
Andrei Kvapil
66ab048612 fix ci pipeline
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-27 19:34:48 +01:00
Andrei Kvapil
cc52c69922 rebuild images
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-25 18:43:45 +01:00
Andrei Kvapil
4270d66376 Merge remote-tracking branch 'upstream/main' into refactor-engine 2025-11-25 18:35:43 +01:00
Andrei Kvapil
2ca68eda69 rebuild images
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-25 18:26:35 +01:00
Andrei Kvapil
9db99f7233 upd cozyreport
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-25 18:23:49 +01:00
Andrei Kvapil
a89dd819ff Return cozystack.io/system-app=true label
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-25 18:23:46 +01:00
Andrei Kvapil
657bddaeb9 Update e2e
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-25 18:23:41 +01:00
Andrei Kvapil
51d0001589 move Makefiles to hack
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-25 18:23:30 +01:00
Andrei Kvapil
e0ec967120 Rename resources
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-25 17:43:33 +01:00
Andrei Kvapil
b77791a5fe return removed files
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-25 17:17:03 +01:00
Andrei Kvapil
3d9cfee401 update migration
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-25 17:03:56 +01:00
Andrei Kvapil
e046206d2b refactor a bit
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-25 16:24:08 +01:00
Andrei Kvapil
c69756de51 Update SA for tenants
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-25 15:35:22 +01:00
Andrei Kvapil
15a9180b67 do not require apiServerEndpoint
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-25 15:34:40 +01:00
Andrei Kvapil
451ef73172 refactor escaping
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-25 15:01:06 +01:00
Andrei Kvapil
2077b0e515 fix namespace
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-25 02:17:33 +01:00
Andrei Kvapil
aaf2d1326a Move telemetry from cozystack-controller to cozystack-operator
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-25 02:17:05 +01:00
Andrei Kvapil
ea1d0363d1 [platform] Get rid of lookups for apps
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-25 01:15:08 +01:00
Andrei Kvapil
45bd323c6e [platform] Get rid of lookups
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-24 20:09:03 +01:00
Andrei Kvapil
b328124be7 1
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-24 17:12:11 +01:00
Andrei Kvapil
35086bc362 bug fixes and cleanup
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-24 15:12:37 +01:00
Andrei Kvapil
7b28139ad9 [platform] Add OCIRegistry with artefacts
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-24 13:44:16 +01:00
Andrei Kvapil
5883fbf7ea [cozystack-controller] Add reconciliation for CozystackResourceDefinitions
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-24 10:24:01 +01:00
Andrei Kvapil
167e85004c rename packages
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-23 00:37:27 +01:00
Andrei Kvapil
7fc458d136 [cozystack-api] Show revision
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-22 23:32:15 +01:00
Andrei Kvapil
bb220647ad Instruct Cozystack API server and lineage webhook logic to use labels
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-22 23:20:17 +01:00
Andrei Kvapil
a4cb9ae30b Move migrations to platform package
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-22 23:10:40 +01:00
Andrei Kvapil
982727ac91 refactor and install flux-aio
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-22 01:35:59 +01:00
Andrei Kvapil
6c3a7b7efb remove flux from bundles
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-21 16:17:10 +01:00
Andrei Kvapil
923dbd209d Fix artifact sources processing
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-20 23:39:16 +01:00
Andrei Kvapil
c23826efac separate artifactgeenrators
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-20 04:42:41 +01:00
Andrei Kvapil
36119cec45 Introduce CozystackPlatformConfiguration
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-20 04:28:44 +01:00
Andrei Kvapil
f98b429ad2 Implement business logic
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-20 03:33:54 +01:00
Andrei Kvapil
8a0935fb37 Design CozystackBundle API
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-20 02:22:45 +01:00
Andrei Kvapil
5dc9f590cf WIP WIP WIP WIP WIP WIP WIP WIP WIP
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-19 20:38:21 +01:00
Andrei Kvapil
17286ad213 fix merging values.yaml
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-19 17:59:06 +01:00
Andrei Kvapil
ea9d44b4af Refactor bundles (WIP)
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-19 15:49:29 +01:00
Andrei Kvapil
7c2bec197b [flux] disable network policies
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-19 15:48:58 +01:00
Andrei Kvapil
4b1525a5f8 Cleanup
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-19 15:32:40 +01:00
Andrei Kvapil
2113d17a54 fix dependencies
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-19 15:22:51 +01:00
Andrei Kvapil
4f97aef04c Update apps to use ExternalArtifacts
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-19 14:31:54 +01:00
Andrei Kvapil
4b5d777b81 [fluxcd] fix advertisning url for source-watcher
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-19 09:47:27 +01:00
Andrei Kvapil
75197c6d25 Update apps to use ExternalArtifacts
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-19 00:41:09 +01:00
Andrei Kvapil
c808ed6f24 Add ArtifactGenerators reconciler
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-19 00:07:20 +01:00
Andrei Kvapil
222b582b68 Introduce cozystack-operator
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-18 23:14:36 +01:00
Andrei Kvapil
2a87c83043 Update Fluxcd 2.7.x, enable source-watcher
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-18 19:56:27 +01:00
Andrei Kvapil
e5b65e8e77 Remove cozystack-assets-server and move grafana-dashboards into separate pod
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2025-11-18 18:13:52 +01:00
1361 changed files with 15246 additions and 119451 deletions

2
.github/CODEOWNERS vendored
View File

@@ -1 +1 @@
* @kvaps @lllamnyp @lexfrei @androndo @IvanHunters @sircthulhu
* @kvaps @lllamnyp @nbykov0

View File

@@ -1,188 +0,0 @@
name: Auto Patch Release
on:
schedule:
# Run daily at 2:00 AM CET (1:00 UTC in winter, 0:00 UTC in summer)
# Using 1:00 UTC to approximate 2:00 AM CET
- cron: '0 1 * * *'
workflow_dispatch: # Allow manual trigger
concurrency:
group: auto-release-${{ github.workflow }}
cancel-in-progress: false
jobs:
auto-release:
name: Auto Patch Release
runs-on: [self-hosted]
permissions:
contents: write
pull-requests: read
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: Configure git
env:
GH_PAT: ${{ secrets.GH_PAT }}
run: |
git config user.name "cozystack-bot"
git config user.email "217169706+cozystack-bot@users.noreply.github.com"
git remote set-url origin https://cozystack-bot:${GH_PAT}@github.com/${GITHUB_REPOSITORY}
git config --unset-all http.https://github.com/.extraheader || true
- name: Process release branches
uses: actions/github-script@v7
env:
GH_PAT: ${{ secrets.GH_PAT }}
with:
github-token: ${{ secrets.GH_PAT }}
script: |
const { execSync } = require('child_process');
// Configure git to use PAT for authentication
execSync('git config user.name "cozystack-bot"', { encoding: 'utf8' });
execSync('git config user.email "217169706+cozystack-bot@users.noreply.github.com"', { encoding: 'utf8' });
execSync(`git remote set-url origin https://cozystack-bot:${process.env.GH_PAT}@github.com/${process.env.GITHUB_REPOSITORY}`, { encoding: 'utf8' });
// Remove GITHUB_TOKEN extraheader to ensure PAT is used (needed to trigger other workflows)
execSync('git config --unset-all http.https://github.com/.extraheader || true', { encoding: 'utf8' });
// Get all release-X.Y branches
const branches = execSync('git branch -r | grep -E "origin/release-[0-9]+\\.[0-9]+$" | sed "s|origin/||" | tr -d " "', { encoding: 'utf8' })
.split('\n')
.filter(b => b.trim())
.filter(b => /^release-\d+\.\d+$/.test(b));
console.log(`Found ${branches.length} release branches: ${branches.join(', ')}`);
// Get all published releases (not draft)
const allReleases = await github.rest.repos.listReleases({
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 100
});
// Filter to only published releases (not draft) with tags matching vX.Y.Z (no suffixes)
const publishedReleases = allReleases.data
.filter(r => !r.draft)
.filter(r => /^v\d+\.\d+\.\d+$/.test(r.tag_name));
console.log(`Found ${publishedReleases.length} published releases without suffixes`);
for (const branch of branches) {
console.log(`\n=== Processing branch: ${branch} ===`);
// Extract X.Y from branch name (release-X.Y)
const match = branch.match(/^release-(\d+\.\d+)$/);
if (!match) {
console.log(` ⚠️ Branch ${branch} doesn't match pattern, skipping`);
continue;
}
const [major, minor] = match[1].split('.');
const versionPrefix = `v${major}.${minor}.`;
console.log(` Looking for releases with prefix: ${versionPrefix}`);
// Find the latest published release for this branch (vX.Y.Z without suffixes)
const branchReleases = publishedReleases
.filter(r => r.tag_name.startsWith(versionPrefix))
.filter(r => /^v\d+\.\d+\.\d+$/.test(r.tag_name)); // Ensure no suffixes
if (branchReleases.length === 0) {
console.log(` ⚠️ No published releases found for ${branch}, skipping`);
continue;
}
// Sort by version (descending) to get the latest
branchReleases.sort((a, b) => {
const aVersion = a.tag_name.match(/^v(\d+)\.(\d+)\.(\d+)$/);
const bVersion = b.tag_name.match(/^v(\d+)\.(\d+)\.(\d+)$/);
if (!aVersion || !bVersion) return 0;
const aNum = parseInt(aVersion[1]) * 10000 + parseInt(aVersion[2]) * 100 + parseInt(aVersion[3]);
const bNum = parseInt(bVersion[1]) * 10000 + parseInt(bVersion[2]) * 100 + parseInt(bVersion[3]);
return bNum - aNum;
});
const latestRelease = branchReleases[0];
console.log(` ✅ Latest published release: ${latestRelease.tag_name}`);
// Get the commit SHA for this release tag
let releaseCommitSha;
try {
releaseCommitSha = execSync(`git rev-list -n 1 ${latestRelease.tag_name}`, { encoding: 'utf8' }).trim();
console.log(` Release commit SHA: ${releaseCommitSha}`);
} catch (error) {
console.log(` ⚠️ Could not find commit for tag ${latestRelease.tag_name}, skipping`);
continue;
}
// Checkout the branch
execSync(`git fetch origin ${branch}:${branch}`, { encoding: 'utf8' });
execSync(`git checkout ${branch}`, { encoding: 'utf8' });
// Get the latest commit on the branch
const latestBranchCommit = execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim();
console.log(` Latest branch commit: ${latestBranchCommit}`);
// Check if there are new commits after the release
const commitsAfterRelease = execSync(
`git rev-list ${releaseCommitSha}..HEAD --oneline`,
{ encoding: 'utf8' }
).trim();
if (!commitsAfterRelease) {
console.log(` No new commits after ${latestRelease.tag_name}, skipping`);
continue;
}
console.log(` ✅ Found new commits after release:`);
console.log(commitsAfterRelease);
// Calculate next version (Z+1)
const versionMatch = latestRelease.tag_name.match(/^v(\d+)\.(\d+)\.(\d+)$/);
if (!versionMatch) {
console.log(` ❌ Could not parse version from ${latestRelease.tag_name}, skipping`);
continue;
}
const nextPatch = parseInt(versionMatch[3]) + 1;
const nextTag = `v${versionMatch[1]}.${versionMatch[2]}.${nextPatch}`;
console.log(` 🏷️ Creating new tag: ${nextTag} on commit ${latestBranchCommit}`);
// Create and push the tag with base_ref for workflow triggering
try {
// Delete local tag if exists to force update
try {
execSync(`git tag -d ${nextTag}`, { encoding: 'utf8' });
} catch (e) {
// Tag doesn't exist locally, that's fine
}
// Delete remote tag if exists
try {
execSync(`git push origin :refs/tags/${nextTag}`, { encoding: 'utf8' });
} catch (e) {
// Tag doesn't exist remotely, that's fine
}
// Create tag locally
execSync(`git tag ${nextTag} ${latestBranchCommit}`, { encoding: 'utf8' });
// Push tag with HEAD reference to preserve base_ref
execSync(`git push origin HEAD:refs/tags/${nextTag}`, { encoding: 'utf8' });
console.log(` ✅ Successfully created and pushed tag ${nextTag}`);
} catch (error) {
console.log(` ❌ Error creating/pushing tag ${nextTag}: ${error.message}`);
core.setFailed(`Failed to create tag ${nextTag} for branch ${branch}`);
}
}
console.log(`\n✅ Finished processing all release branches`);

View File

@@ -2,7 +2,7 @@ name: Automatic Backport
on:
pull_request_target:
types: [closed, labeled] # fires when PR is closed (merged) or labeled
types: [closed] # fires when PR is closed (merged)
concurrency:
group: backport-${{ github.workflow }}-${{ github.event.pull_request.number }}
@@ -13,46 +13,22 @@ permissions:
pull-requests: write
jobs:
# Determine which backports are needed
prepare:
backport:
if: |
github.event.pull_request.merged == true &&
(
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'))
)
contains(github.event.pull_request.labels.*.name, 'backport')
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:
- name: Check which labels are present
id: labels
# 1. Decide which maintenance branch should receive the backport
- name: Determine target maintenance branch
id: target
uses: actions/github-script@v7
with:
script: |
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;
let rel;
try {
latestRelease = await github.rest.repos.getLatestRelease({
rel = await github.rest.repos.getLatestRelease({
owner: context.repo.owner,
repo: context.repo.repo
});
@@ -60,70 +36,18 @@ jobs:
core.setFailed('No existing releases found; cannot determine backport target.');
return;
}
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
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}`);
# 2. Checkout (required by backportaction)
- name: Checkout repository
if: steps.target.outcome == 'success'
uses: actions/checkout@v4
# 3. Create the backport pull request
- name: Create backport PR
id: backport
if: steps.target.outcome == 'success'
uses: korthout/backport-action@v3.2.1
uses: korthout/backport-action@v3
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

View File

@@ -28,7 +28,7 @@ jobs:
- name: Install generate
run: |
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
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
- name: Run pre-commit hooks
run: |

View File

@@ -46,12 +46,7 @@ jobs:
fetch-depth: 0
- name: Create tag on merge commit
env:
GH_PAT: ${{ secrets.GH_PAT }}
run: |
git config user.name "cozystack-bot"
git config user.email "217169706+cozystack-bot@users.noreply.github.com"
git remote set-url origin https://cozystack-bot:${GH_PAT}@github.com/${GITHUB_REPOSITORY}
git tag -f ${{ steps.get_tag.outputs.tag }} ${{ github.sha }}
git push -f origin ${{ steps.get_tag.outputs.tag }}
@@ -110,95 +105,67 @@ jobs:
}
}
# Publish draft release and ensure correct latest flag
- name: Publish draft release
# Get the latest published release
- name: Get the latest published release
id: latest_release
uses: actions/github-script@v7
with:
script: |
const tag = '${{ steps.get_tag.outputs.tag }}';
try {
const rel = await github.rest.repos.getLatestRelease({
owner: context.repo.owner,
repo: context.repo.repo
});
core.setOutput('tag', rel.data.tag_name);
} catch (_) {
core.setOutput('tag', '');
}
# Compare current tag vs latest using semver-utils
- name: Semver compare
id: semver
uses: madhead/semver-utils@v4.3.0
with:
version: ${{ steps.get_tag.outputs.tag }}
compare-to: ${{ steps.latest_release.outputs.tag }}
# Derive flags: prerelease? make_latest?
- name: Calculate publish flags
id: flags
uses: actions/github-script@v7
with:
script: |
const tag = '${{ steps.get_tag.outputs.tag }}'; // v0.31.5-rc.1
const m = tag.match(/^v(\d+\.\d+\.\d+)(-(?:alpha|beta|rc)\.\d+)?$/);
if (!m) {
core.setFailed(`❌ tag '${tag}' must match 'vX.Y.Z' or 'vX.Y.Z-(alpha|beta|rc).N'`);
return;
}
const isRc = Boolean(m[2]);
const version = m[1] + (m[2] ?? ''); // 0.31.5-rc.1
const isRc = Boolean(m[2]);
core.setOutput('is_rc', isRc);
const outdated = '${{ steps.semver.outputs.comparison-result }}' === '<';
core.setOutput('make_latest', isRc || outdated ? 'false' : 'legacy');
// Parse semver string to comparable numbers
function parseSemver(v) {
const match = v.replace(/^v/, '').match(/^(\d+)\.(\d+)\.(\d+)/);
if (!match) return null;
return {
major: parseInt(match[1]),
minor: parseInt(match[2]),
patch: parseInt(match[3])
};
}
// Compare two semver objects
function compareSemver(a, b) {
if (a.major !== b.major) return a.major - b.major;
if (a.minor !== b.minor) return a.minor - b.minor;
return a.patch - b.patch;
}
const currentSemver = parseSemver(tag);
// Get all releases
# Publish draft release with correct flags
- name: Publish draft release
uses: actions/github-script@v7
with:
script: |
const tag = '${{ steps.get_tag.outputs.tag }}';
const releases = await github.rest.repos.listReleases({
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 100
repo: context.repo.repo
});
// Find draft release to publish
const draft = releases.data.find(r => r.tag_name === tag && r.draft);
if (!draft) throw new Error(`Draft release for ${tag} not found`);
// Find max semver among published releases (excluding current draft)
const publishedReleases = releases.data
.filter(r => !r.draft && !r.prerelease)
.filter(r => /^v\d+\.\d+\.\d+$/.test(r.tag_name))
.map(r => ({ id: r.id, tag: r.tag_name, semver: parseSemver(r.tag_name) }))
.filter(r => r.semver !== null);
let maxRelease = null;
for (const rel of publishedReleases) {
if (!maxRelease || compareSemver(rel.semver, maxRelease.semver) > 0) {
maxRelease = rel;
}
}
// Determine if this release should be latest
const isOutdated = maxRelease && compareSemver(currentSemver, maxRelease.semver) < 0;
const makeLatest = (isRc || isOutdated) ? 'false' : 'true';
if (isRc) {
console.log(`🏷️ ${tag} is a prerelease, make_latest: false`);
} else if (isOutdated) {
console.log(`🏷️ ${tag} < ${maxRelease.tag} (max semver), make_latest: false`);
} else {
console.log(`🏷️ ${tag} is the highest version, make_latest: true`);
}
// Publish the release
await github.rest.repos.updateRelease({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: draft.id,
draft: false,
prerelease: isRc,
make_latest: makeLatest
owner: context.repo.owner,
repo: context.repo.repo,
release_id: draft.id,
draft: false,
prerelease: ${{ steps.flags.outputs.is_rc }},
make_latest: '${{ steps.flags.outputs.make_latest }}'
});
console.log(`🚀 Published release ${tag}`);
// If this is a backport/outdated release, ensure the correct release is marked as latest
if (isOutdated && maxRelease) {
console.log(`🔧 Ensuring ${maxRelease.tag} remains the latest release...`);
await github.rest.repos.updateRelease({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: maxRelease.id,
make_latest: 'true'
});
console.log(`✅ Restored ${maxRelease.tag} as latest release`);
}
console.log(`🚀 Published release for ${tag}`);

View File

@@ -71,6 +71,12 @@ jobs:
name: pr-patch
path: _out/assets/pr.patch
- name: Upload installer
uses: actions/upload-artifact@v4
with:
name: cozystack-installer
path: _out/assets/cozystack-installer.yaml
- name: Upload Talos image
uses: actions/upload-artifact@v4
with:
@@ -82,7 +88,8 @@ jobs:
runs-on: ubuntu-latest
if: contains(github.event.pull_request.labels.*.name, 'release')
outputs:
disk_id: ${{ steps.fetch_assets.outputs.disk_id }}
installer_id: ${{ steps.fetch_assets.outputs.installer_id }}
disk_id: ${{ steps.fetch_assets.outputs.disk_id }}
steps:
- name: Checkout code
@@ -125,18 +132,19 @@ jobs:
return;
}
const find = (n) => draft.assets.find(a => a.name === n)?.id;
const diskId = find('nocloud-amd64.raw.xz');
if (!diskId) {
const installerId = find('cozystack-installer.yaml');
const diskId = find('nocloud-amd64.raw.xz');
if (!installerId || !diskId) {
core.setFailed('Required assets missing in draft release');
return;
}
core.setOutput('disk_id', diskId);
core.setOutput('installer_id', installerId);
core.setOutput('disk_id', diskId);
e2e:
name: "E2E Tests"
runs-on: ${{ contains(github.event.pull_request.labels.*.name, 'debug') && 'self-hosted' || 'oracle-vm-24cpu-96gb-x86-64' }}
#runs-on: [oracle-vm-32cpu-128gb-x86-64]
prepare_env:
name: "Prepare environment"
runs-on: [self-hosted]
permissions:
contents: read
packages: read
@@ -182,7 +190,7 @@ jobs:
- name: Set sandbox ID
run: echo "SANDBOX_NAME=cozy-e2e-sandbox-$(echo "${GITHUB_REPOSITORY}:${GITHUB_WORKFLOW}:${GITHUB_REF}" | sha256sum | cut -c1-10)" >> $GITHUB_ENV
# ▸ Prepare environment
# ▸ Start actual job steps
- name: Prepare workspace
run: |
rm -rf /tmp/$SANDBOX_NAME
@@ -202,7 +210,47 @@ jobs:
done
echo "✅ The task completed successfully after $attempt attempts"
# ▸ Install Cozystack
install_cozystack:
name: "Install Cozystack"
runs-on: [self-hosted]
permissions:
contents: read
packages: read
needs: ["prepare_env", "resolve_assets"]
if: ${{ always() && needs.prepare_env.result == 'success' }}
steps:
- name: Prepare _out/assets directory
run: mkdir -p _out/assets
# ▸ Regular PR path download artefacts produced by the *build* job
- name: "Download installer (regular PR)"
if: "!contains(github.event.pull_request.labels.*.name, 'release')"
uses: actions/download-artifact@v4
with:
name: cozystack-installer
path: _out/assets
# ▸ Release PR path fetch artefacts from the corresponding draft release
- name: Download assets from draft release (release PR)
if: contains(github.event.pull_request.labels.*.name, 'release')
run: |
mkdir -p _out/assets
curl -sSL -H "Authorization: token ${GH_PAT}" -H "Accept: application/octet-stream" \
-o _out/assets/cozystack-installer.yaml \
"https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/assets/${{ needs.resolve_assets.outputs.installer_id }}"
env:
GH_PAT: ${{ secrets.GH_PAT }}
# ▸ Start actual job steps
- name: Set sandbox ID
run: echo "SANDBOX_NAME=cozy-e2e-sandbox-$(echo "${GITHUB_REPOSITORY}:${GITHUB_WORKFLOW}:${GITHUB_REF}" | sha256sum | cut -c1-10)" >> $GITHUB_ENV
- name: Sync _out/assets directory
run: |
mkdir -p /tmp/$SANDBOX_NAME/_out/assets
mv _out/assets/* /tmp/$SANDBOX_NAME/_out/assets/
- name: Install Cozystack into sandbox
run: |
cd /tmp/$SANDBOX_NAME
@@ -215,77 +263,107 @@ jobs:
fi
echo "❌ Attempt $attempt failed, retrying..."
done
echo "✅ The task completed successfully after $attempt attempts"
echo "✅ The task completed successfully after $attempt attempts."
- name: Run OpenAPI tests
run: |
cd /tmp/$SANDBOX_NAME
make -C packages/core/testing SANDBOX_NAME=$SANDBOX_NAME test-openapi
# ▸ Run E2E tests
- name: Run E2E tests
id: e2e_tests
run: |
cd /tmp/$SANDBOX_NAME
failed_tests=""
for app in $(ls hack/e2e-apps/*.bats | xargs -n1 basename | cut -d. -f1); do
echo "::group::Testing $app"
attempt=0
success=false
until [ $attempt -ge 3 ]; do
if make -C packages/core/testing SANDBOX_NAME=$SANDBOX_NAME test-apps-$app; then
success=true
break
fi
attempt=$((attempt + 1))
echo "❌ Attempt $attempt failed, retrying..."
done
if [ "$success" = true ]; then
echo "✅ Test $app completed successfully"
else
echo "❌ Test $app failed after $attempt attempts"
failed_tests="$failed_tests $app"
fi
echo "::endgroup::"
done
if [ -n "$failed_tests" ]; then
echo "❌ Failed tests:$failed_tests"
exit 1
fi
echo "✅ All E2E tests passed"
detect_test_matrix:
name: "Detect e2e test matrix"
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set.outputs.matrix }}
# ▸ Collect debug information (always runs)
- name: Collect report
if: always()
steps:
- uses: actions/checkout@v4
- id: set
run: |
apps=$(ls hack/e2e-apps/*.bats | cut -f3 -d/ | cut -f1 -d. | jq -R | jq -cs)
echo "matrix={\"app\":$apps}" >> "$GITHUB_OUTPUT"
test_apps:
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.detect_test_matrix.outputs.matrix) }}
name: Test ${{ matrix.app }}
runs-on: [self-hosted]
needs: [install_cozystack,detect_test_matrix]
if: ${{ always() && (needs.install_cozystack.result == 'success' && needs.detect_test_matrix.result == 'success') }}
steps:
- name: Set sandbox ID
run: echo "SANDBOX_NAME=cozy-e2e-sandbox-$(echo "${GITHUB_REPOSITORY}:${GITHUB_WORKFLOW}:${GITHUB_REF}" | sha256sum | cut -c1-10)" >> $GITHUB_ENV
- name: E2E Apps
run: |
cd /tmp/$SANDBOX_NAME
make -C packages/core/testing SANDBOX_NAME=$SANDBOX_NAME collect-report || true
attempt=0
until make -C packages/core/testing SANDBOX_NAME=$SANDBOX_NAME test-apps-${{ matrix.app }}; do
attempt=$((attempt + 1))
if [ $attempt -ge 3 ]; then
echo "❌ Attempt $attempt failed, exiting..."
exit 1
fi
echo "❌ Attempt $attempt failed, retrying..."
done
echo "✅ The task completed successfully after $attempt attempts"
collect_debug_information:
name: Collect debug information
runs-on: [self-hosted]
needs: [test_apps]
if: ${{ always() }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set sandbox ID
run: echo "SANDBOX_NAME=cozy-e2e-sandbox-$(echo "${GITHUB_REPOSITORY}:${GITHUB_WORKFLOW}:${GITHUB_REF}" | sha256sum | cut -c1-10)" >> $GITHUB_ENV
- name: Collect report
run: |
cd /tmp/$SANDBOX_NAME
make -C packages/core/testing SANDBOX_NAME=$SANDBOX_NAME collect-report
- name: Upload cozyreport.tgz
if: always()
uses: actions/upload-artifact@v4
with:
name: cozyreport
path: /tmp/${{ env.SANDBOX_NAME }}/_out/cozyreport.tgz
- name: Collect images list
if: always()
run: |
cd /tmp/$SANDBOX_NAME
make -C packages/core/testing SANDBOX_NAME=$SANDBOX_NAME collect-images || true
make -C packages/core/testing SANDBOX_NAME=$SANDBOX_NAME collect-images
- name: Upload image list
if: always()
uses: actions/upload-artifact@v4
with:
name: image-list
path: /tmp/${{ env.SANDBOX_NAME }}/_out/images.txt
# ▸ Tear down environment (always runs)
cleanup:
name: Tear down environment
runs-on: [self-hosted]
needs: [collect_debug_information]
if: ${{ always() && needs.test_apps.result == 'success' }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: Set sandbox ID
run: echo "SANDBOX_NAME=cozy-e2e-sandbox-$(echo "${GITHUB_REPOSITORY}:${GITHUB_WORKFLOW}:${GITHUB_REF}" | sha256sum | cut -c1-10)" >> $GITHUB_ENV
- name: Tear down sandbox
if: always()
run: make -C packages/core/testing SANDBOX_NAME=$SANDBOX_NAME delete || true
run: make -C packages/core/testing SANDBOX_NAME=$SANDBOX_NAME delete
- name: Remove workspace
if: always()
run: rm -rf /tmp/$SANDBOX_NAME

View File

@@ -1,78 +0,0 @@
name: Retest
on:
issue_comment:
types: [created]
jobs:
retest:
name: Retest PR
runs-on: ubuntu-latest
if: |
github.event.issue.pull_request &&
contains(github.event.comment.body, '/retest')
permissions:
actions: write
pull-requests: read
steps:
- name: Rerun from Prepare environment
uses: actions/github-script@v7
with:
script: |
const prNumber = context.issue.number;
// Get the PR to find the head SHA
const pr = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
});
// Find the latest workflow run for this PR
const runs = await github.rest.actions.listWorkflowRuns({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'pull-requests.yaml',
head_sha: pr.data.head.sha
});
if (runs.data.workflow_runs.length === 0) {
core.setFailed('No workflow runs found for this PR');
return;
}
const latestRun = runs.data.workflow_runs[0];
console.log(`Found workflow run: ${latestRun.id} (${latestRun.status})`);
// Check if workflow is waiting for approval (fork PRs)
if (latestRun.conclusion === 'action_required') {
core.setFailed('Workflow is waiting for approval. A maintainer must approve the workflow first.');
return;
}
// Get jobs for this run
const jobs = await github.rest.actions.listJobsForWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: latestRun.id
});
// Find "Prepare environment" job
const prepareJob = jobs.data.jobs.find(j => j.name === 'Prepare environment');
if (!prepareJob) {
core.setFailed('Could not find "Prepare environment" job');
return;
}
console.log(`Found job: ${prepareJob.name} (id: ${prepareJob.id}, status: ${prepareJob.status})`);
// Rerun the job
await github.rest.actions.reRunJobForWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
job_id: prepareJob.id
});
console.log(`✅ Triggered rerun of job "${prepareJob.name}" (${prepareJob.id})`);

View File

@@ -123,6 +123,32 @@ jobs:
git commit -m "Prepare release ${GITHUB_REF#refs/tags/}" -s || echo "No changes to commit"
git push origin HEAD || true
# Get `latest_version` from latest published release
- name: Get latest published release
if: steps.check_release.outputs.skip == 'false'
id: latest_release
uses: actions/github-script@v7
with:
script: |
try {
const rel = await github.rest.repos.getLatestRelease({
owner: context.repo.owner,
repo: context.repo.repo
});
core.setOutput('tag', rel.data.tag_name);
} catch (_) {
core.setOutput('tag', '');
}
# Compare tag (A) with latest (B)
- name: Semver compare
if: steps.check_release.outputs.skip == 'false'
id: semver
uses: madhead/semver-utils@v4.3.0
with:
version: ${{ steps.tag.outputs.tag }} # A
compare-to: ${{ steps.latest_release.outputs.tag }} # B
# Create or reuse draft release
- name: Create / reuse draft release
if: steps.check_release.outputs.skip == 'false'
@@ -213,160 +239,3 @@ jobs:
} else {
console.log(`PR already exists from ${head} to ${base}`);
}
generate-changelog:
name: Generate Changelog
runs-on: [self-hosted]
needs: [prepare-release]
permissions:
contents: write
pull-requests: write
if: needs.prepare-release.result == 'success'
steps:
- name: Parse tag
id: tag
uses: actions/github-script@v7
with:
script: |
const ref = context.ref.replace('refs/tags/', '');
const m = ref.match(/^v(\d+\.\d+\.\d+)(-(?:alpha|beta|rc)\.\d+)?$/);
if (!m) {
core.setFailed(`❌ tag '${ref}' must match 'vX.Y.Z' or 'vX.Y.Z-(alpha|beta|rc).N'`);
return;
}
const version = m[1] + (m[2] ?? '');
core.setOutput('version', version);
core.setOutput('tag', ref);
- name: Checkout main branch
uses: actions/checkout@v4
with:
ref: main
fetch-depth: 0
fetch-tags: true
token: ${{ secrets.GH_PAT }}
- name: Check if changelog already exists
id: check_changelog
run: |
CHANGELOG_FILE="docs/changelogs/v${{ steps.tag.outputs.version }}.md"
if [ -f "$CHANGELOG_FILE" ]; then
echo "exists=true" >> $GITHUB_OUTPUT
echo "Changelog file $CHANGELOG_FILE already exists"
else
echo "exists=false" >> $GITHUB_OUTPUT
echo "Changelog file $CHANGELOG_FILE does not exist"
fi
- name: Setup Node.js
if: steps.check_changelog.outputs.exists == 'false'
uses: actions/setup-node@v4
with:
node-version: 22
- name: Install GitHub Copilot CLI
if: steps.check_changelog.outputs.exists == 'false'
run: npm i -g @github/copilot
- name: Generate changelog using AI
if: steps.check_changelog.outputs.exists == 'false'
env:
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
GH_TOKEN: ${{ secrets.GH_PAT }}
run: |
copilot --prompt "prepare changelog file for tagged release v${{ steps.tag.outputs.version }}, use @docs/agents/changelog.md for it. Create the changelog file at docs/changelogs/v${{ steps.tag.outputs.version }}.md" \
--allow-all-tools --allow-all-paths < /dev/null
- name: Create changelog branch and commit
if: steps.check_changelog.outputs.exists == 'false'
env:
GH_PAT: ${{ secrets.GH_PAT }}
run: |
git config user.name "cozystack-bot"
git config user.email "217169706+cozystack-bot@users.noreply.github.com"
git remote set-url origin https://cozystack-bot:${GH_PAT}@github.com/${GITHUB_REPOSITORY}
CHANGELOG_FILE="docs/changelogs/v${{ steps.tag.outputs.version }}.md"
CHANGELOG_BRANCH="changelog-v${{ steps.tag.outputs.version }}"
if [ -f "$CHANGELOG_FILE" ]; then
# Fetch latest main branch
git fetch origin main
# Delete local branch if it exists
git branch -D "$CHANGELOG_BRANCH" 2>/dev/null || true
# Create and checkout new branch from main
git checkout -b "$CHANGELOG_BRANCH" origin/main
# Add and commit changelog
git add "$CHANGELOG_FILE"
if git diff --staged --quiet; then
echo "⚠️ No changes to commit (file may already be committed)"
else
git commit -m "docs: add changelog for v${{ steps.tag.outputs.version }}" -s
echo "✅ Changelog committed to branch $CHANGELOG_BRANCH"
fi
# Push the branch (force push to update if it exists)
git push -f origin "$CHANGELOG_BRANCH"
else
echo "⚠️ Changelog file was not generated"
exit 1
fi
- name: Create PR for changelog
if: steps.check_changelog.outputs.exists == 'false'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GH_PAT }}
script: |
const version = '${{ steps.tag.outputs.version }}';
const changelogBranch = `changelog-v${version}`;
const baseBranch = 'main';
// Check if PR already exists
const prs = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
head: `${context.repo.owner}:${changelogBranch}`,
base: baseBranch,
state: 'open'
});
if (prs.data.length > 0) {
const pr = prs.data[0];
console.log(`PR #${pr.number} already exists for changelog branch ${changelogBranch}`);
// Update PR body with latest info
const body = `This PR adds the changelog for release \`v${version}\`.\n\n✅ Changelog has been automatically generated in \`docs/changelogs/v${version}.md\`.`;
await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
body: body
});
console.log(`Updated existing PR #${pr.number}`);
} else {
// Create new PR
const pr = await github.rest.pulls.create({
owner: context.repo.owner,
repo: context.repo.repo,
head: changelogBranch,
base: baseBranch,
title: `docs: add changelog for v${version}`,
body: `This PR adds the changelog for release \`v${version}\`.\n\n✅ Changelog has been automatically generated in \`docs/changelogs/v${version}.md\`.`,
draft: false
});
// Add label if needed
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.data.number,
labels: ['documentation', 'automated']
});
console.log(`Created PR #${pr.data.number} for changelog`);
}

View File

@@ -1,92 +0,0 @@
name: Update Release Notes
on:
push:
branches:
- main
concurrency:
group: update-releasenotes-${{ github.workflow }}
cancel-in-progress: false
jobs:
update-releasenotes:
name: Update Release Notes
runs-on: [self-hosted]
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Update release notes from changelogs
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const path = require('path');
const changelogDir = 'docs/changelogs';
// Get releases from first page
const releases = await github.rest.repos.listReleases({
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 30
});
console.log(`Found ${releases.data.length} releases (first page only)`);
// Process each release
for (const release of releases.data) {
const tag = release.tag_name;
const changelogFile = `${tag}.md`;
const changelogPath = path.join(changelogDir, changelogFile);
console.log(`\nProcessing release: ${tag}`);
// Check if changelog file exists
if (!fs.existsSync(changelogPath)) {
console.log(` ⚠️ Changelog file ${changelogFile} does not exist, skipping...`);
continue;
}
// Read changelog file content
let changelogContent;
try {
changelogContent = fs.readFileSync(changelogPath, 'utf8');
} catch (error) {
console.log(` ❌ Error reading file ${changelogPath}: ${error.message}`);
continue;
}
if (!changelogContent.trim()) {
console.log(` ⚠️ Changelog file ${changelogFile} is empty, skipping...`);
continue;
}
// Check if content is already up to date
const currentBody = release.body || '';
if (currentBody.trim() === changelogContent.trim()) {
console.log(` ✓ Content is already up to date, skipping...`);
continue;
}
// Update release notes
try {
await github.rest.repos.updateRelease({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: release.id,
body: changelogContent
});
console.log(` ✅ Successfully updated release notes for ${tag}`);
} catch (error) {
console.log(` ❌ Error updating release ${tag}: ${error.message}`);
core.setFailed(`Failed to update release notes for ${tag}`);
}
}

View File

@@ -32,4 +32,4 @@ This list is sorted in chronological order, based on the submission date.
| [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 regions transition toward open, self-hosted cloud-native technologies |
| [Cloupard](https://cloupard.kz/) | @serjiott | 2025-12-18 | Cloupard is a public cloud provider offering IaaS and PaaS services via datacenters in Kazakhstan and Uzbekistan. Uses CozyStack on bare metal to extend its managed PaaS offerings. |
|

View File

@@ -3,38 +3,14 @@
This file provides structured guidance for AI coding assistants and agents
working with the **Cozystack** project.
## Activation
## Agent Documentation
**CRITICAL**: When the user asks you to do something that matches the scope of a documented process, you MUST read the corresponding documentation file and follow the instructions exactly as written.
- **Commits, PRs, git operations** (e.g., "create a commit", "make a PR", "fix review comments", "rebase", "cherry-pick")
- Read: [`contributing.md`](./docs/agents/contributing.md)
- Action: Read the entire file and follow ALL instructions step-by-step
- **Changelog generation** (e.g., "generate changelog", "create changelog", "prepare changelog for version X")
- Read: [`changelog.md`](./docs/agents/changelog.md)
- Action: Read the entire file and follow ALL steps in the checklist. Do NOT skip any mandatory steps
- **Release creation** (e.g., "create release", "prepare release", "tag release", "make a release")
- Read: [`releasing.md`](./docs/agents/releasing.md)
- Action: Read the file and follow the referenced release process in `docs/release.md`
- **Project structure, conventions, code layout** (e.g., "where should I put X", "what's the convention for Y", "how is the project organized")
- Read: [`overview.md`](./docs/agents/overview.md)
- Action: Read relevant sections to understand project structure and conventions
- **General questions about contributing**
- Read: [`contributing.md`](./docs/agents/contributing.md)
- Action: Read the file to understand git workflow, commit format, PR process
**Important rules:**
-**ONLY read the file if the task matches the documented process scope** - do not read files for tasks that don't match their purpose
-**ALWAYS read the file FIRST** before starting the task (when applicable)
-**Follow instructions EXACTLY** as written in the documentation
-**Do NOT skip mandatory steps** (especially in changelog.md)
-**Do NOT assume** you know the process - always check the documentation when the task matches
-**Do NOT read files** for tasks that are outside their documented scope
- 📖 **Note**: [`overview.md`](./docs/agents/overview.md) can be useful as a reference to understand project structure and conventions, even when not explicitly required by the task
| 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

View File

@@ -1,6 +1,4 @@
.PHONY: manifests assets unit-tests helm-unit-tests
include hack/common-envs.mk
.PHONY: manifests repos assets unit-tests helm-unit-tests
build-deps:
@command -V find docker skopeo jq gh helm > /dev/null
@@ -11,17 +9,15 @@ build-deps:
build: build-deps
make -C packages/apps/http-cache image
make -C packages/apps/mariadb image
make -C packages/apps/mysql image
make -C packages/apps/clickhouse image
make -C packages/apps/kubernetes image
make -C packages/system/monitoring image
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/backupstrategy-controller image
make -C packages/system/lineage-controller-webhook image
make -C packages/system/cilium image
make -C packages/system/linstor image
make -C packages/system/kubeovn image
make -C packages/system/kubeovn-webhook image
make -C packages/system/kubeovn-plunger image
make -C packages/system/dashboard image
@@ -29,50 +25,20 @@ build: build-deps
make -C packages/system/kamaji image
make -C packages/system/bucket image
make -C packages/system/objectstorage-controller image
make -C packages/system/grafana-operator image
make -C packages/core/testing image
make -C packages/core/installer image-operator
make -C packages/core/talos image
make -C packages/core/platform image
make -C packages/core/installer image
make -C packages/core/installer image-packages
make manifests
manifests:
mkdir -p _out/assets
cat internal/crdinstall/manifests/*.yaml > _out/assets/cozystack-crds.yaml
# Talos variant (default)
helm template installer packages/core/installer -n cozy-system \
--show-only templates/cozystack-operator.yaml \
> _out/assets/cozystack-operator-talos.yaml
# Generic Kubernetes variant (k3s, kubeadm, RKE2)
helm template installer packages/core/installer -n cozy-system \
--set cozystackOperator.variant=generic \
--set cozystack.apiServerHost=REPLACE_ME \
--show-only templates/cozystack-operator.yaml \
> _out/assets/cozystack-operator-generic.yaml
# Hosted variant (managed Kubernetes)
helm template installer packages/core/installer -n cozy-system \
--set cozystackOperator.variant=hosted \
--show-only templates/cozystack-operator.yaml \
> _out/assets/cozystack-operator-hosted.yaml
(cd packages/core/installer/; helm template -n cozy-system cozystack-operator . | sed '/^WARNING/d') > _out/assets/cozystack-installer.yaml
cozypkg:
go build -ldflags "-X github.com/cozystack/cozystack/cmd/cozypkg/cmd.Version=v$(COZYSTACK_VERSION)" -o _out/bin/cozypkg ./cmd/cozypkg
assets: assets-talos assets-cozypkg
assets-talos:
assets:
make -C packages/core/talos assets
assets-cozypkg: assets-cozypkg-linux-amd64 assets-cozypkg-linux-arm64 assets-cozypkg-darwin-amd64 assets-cozypkg-darwin-arm64 assets-cozypkg-windows-amd64 assets-cozypkg-windows-arm64
(cd _out/assets/ && sha256sum cozypkg-*.tar.gz) > _out/assets/cozypkg-checksums.txt
assets-cozypkg-%:
$(eval EXT := $(if $(filter windows,$(firstword $(subst -, ,$*))),.exe,))
mkdir -p _out/assets
GOOS=$(firstword $(subst -, ,$*)) GOARCH=$(lastword $(subst -, ,$*)) go build -ldflags "-X github.com/cozystack/cozystack/cmd/cozypkg/cmd.Version=v$(COZYSTACK_VERSION)" -o _out/bin/cozypkg-$*/cozypkg$(EXT) ./cmd/cozypkg
cp LICENSE _out/bin/cozypkg-$*/LICENSE
tar -C _out/bin/cozypkg-$* -czf _out/assets/cozypkg-$*.tar.gz LICENSE cozypkg$(EXT)
test:
make -C packages/core/testing apply
make -C packages/core/testing test

1
api/.gitattributes vendored
View File

@@ -1 +0,0 @@
zz_generated_deepcopy.go linguist-generated

View File

@@ -1,37 +0,0 @@
/*
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
}

View File

@@ -1,63 +0,0 @@
// 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"`
}

View File

@@ -1,65 +0,0 @@
// 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 (
velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/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,
&Velero{},
&VeleroList{},
)
return nil
})
}
const (
VeleroStrategyKind = "Velero"
)
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:scope=Cluster
// Velero defines a backup strategy using Velero as the driver.
type Velero struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec VeleroSpec `json:"spec,omitempty"`
Status VeleroStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// VeleroList contains a list of Velero backup strategies.
type VeleroList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Velero `json:"items"`
}
// VeleroSpec specifies the desired strategy for backing up with Velero.
type VeleroSpec struct {
Template VeleroTemplate `json:"template"`
}
// VeleroTemplate describes the data a backup.velero.io should have when
// templated from a Velero backup strategy.
type VeleroTemplate struct {
Spec velerov1.BackupSpec `json:"spec"`
// +optional
RestoreSpec *velerov1.RestoreSpec `json:"restoreSpec,omitempty"`
}
type VeleroStatus struct {
Conditions []metav1.Condition `json:"conditions,omitempty"`
}

View File

@@ -1,242 +0,0 @@
//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 (
velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"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
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Velero) DeepCopyInto(out *Velero) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Velero.
func (in *Velero) DeepCopy() *Velero {
if in == nil {
return nil
}
out := new(Velero)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Velero) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VeleroList) DeepCopyInto(out *VeleroList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Velero, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VeleroList.
func (in *VeleroList) DeepCopy() *VeleroList {
if in == nil {
return nil
}
out := new(VeleroList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *VeleroList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VeleroSpec) DeepCopyInto(out *VeleroSpec) {
*out = *in
in.Template.DeepCopyInto(&out.Template)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VeleroSpec.
func (in *VeleroSpec) DeepCopy() *VeleroSpec {
if in == nil {
return nil
}
out := new(VeleroSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VeleroStatus) DeepCopyInto(out *VeleroStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]v1.Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VeleroStatus.
func (in *VeleroStatus) DeepCopy() *VeleroStatus {
if in == nil {
return nil
}
out := new(VeleroStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VeleroTemplate) DeepCopyInto(out *VeleroTemplate) {
*out = *in
in.Spec.DeepCopyInto(&out.Spec)
if in.RestoreSpec != nil {
in, out := &in.RestoreSpec, &out.RestoreSpec
*out = new(velerov1.RestoreSpec)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VeleroTemplate.
func (in *VeleroTemplate) DeepCopy() *VeleroTemplate {
if in == nil {
return nil
}
out := new(VeleroTemplate)
in.DeepCopyInto(out)
return out
}

View File

@@ -1,478 +0,0 @@
# Cozystack Backups Core API & Contracts (Draft)
## 1. Overview
Cozystacks 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** theyre 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 (thats 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.
// If apiGroup is not specified, it defaults to "apps.cozystack.io".
ApplicationRef corev1.TypedLocalObjectReference `json:"applicationRef"`
// BackupClassName references a BackupClass that contains strategy and other parameters (e.g. storage reference).
// The BackupClass will be resolved to determine the appropriate strategy and parameters
// based on the ApplicationRef.
BackupClassName string `json:"backupClassName"`
// When backups should run.
Schedule PlanSchedule `json:"schedule"`
}
```
`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` (normalized with default apiGroup if not specified)
* `spec.backupClassName = plan.spec.backupClassName`
* Set `ownerReferences` so the `BackupJob` is owned by the `Plan`.
**Note:** The `BackupJob` controller resolves the `BackupClass` to determine the appropriate strategy and parameters, based on the `ApplicationRef`. The strategy template is processed with a context containing the `Application` object and `Parameters` from the `BackupClass`.
The Plan controller does **not**:
* Execute backups itself.
* Modify driver resources or `Backup` objects.
* Touch `BackupJob.spec` after creation.
---
### 4.2 BackupClass
**Group/Kind**
`backups.cozystack.io/v1alpha1, Kind=BackupClass`
**Purpose**
Define a class of backup configurations that encapsulate strategy and parameters per application type. `BackupClass` is a cluster-scoped resource that allows admins to configure backup strategies and parameters in a reusable way.
**Key fields (spec)**
```go
type BackupClassSpec struct {
// Strategies is a list of backup strategies, each matching a specific application type.
Strategies []BackupClassStrategy `json:"strategies"`
}
type BackupClassStrategy struct {
// StrategyRef references the driver-specific BackupStrategy (e.g., Velero).
StrategyRef corev1.TypedLocalObjectReference `json:"strategyRef"`
// Application specifies which application types this strategy applies to.
// If apiGroup is not specified, it defaults to "apps.cozystack.io".
Application ApplicationSelector `json:"application"`
// Parameters holds strategy-specific parameters, like storage reference.
// Common parameters include:
// - backupStorageLocationName: Name of Velero BackupStorageLocation
// +optional
Parameters map[string]string `json:"parameters,omitempty"`
}
type ApplicationSelector struct {
// APIGroup is the API group of the application.
// If not specified, defaults to "apps.cozystack.io".
// +optional
APIGroup *string `json:"apiGroup,omitempty"`
// Kind is the kind of the application (e.g., VirtualMachine, MariaDB).
Kind string `json:"kind"`
}
```
**BackupClass resolution**
* When a `BackupJob` or `Plan` references a `BackupClass` via `backupClassName`, the controller:
1. Fetches the `BackupClass` by name.
2. Matches the `ApplicationRef` against strategies in the `BackupClass`:
* Normalizes `ApplicationRef.apiGroup` (defaults to `"apps.cozystack.io"` if not specified).
* Finds a strategy where `ApplicationSelector` matches the `ApplicationRef` (apiGroup and kind).
3. Returns the matched `StrategyRef` and `Parameters`.
* Strategy templates (e.g., Velero's `backupTemplate.spec`) are processed with a context containing:
* `Application`: The application object being backed up.
* `Parameters`: The parameters from the matched `BackupClassStrategy`.
**Parameters**
* Parameters are passed via `Parameters` in the `BackupClass` (e.g., `backupStorageLocationName` for Velero).
* The driver uses these parameters to resolve the actual resources (e.g., Velero's `BackupStorageLocation` CRD).
---
### 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.
// If apiGroup is not specified, it defaults to "apps.cozystack.io".
ApplicationRef corev1.TypedLocalObjectReference `json:"applicationRef"`
// BackupClassName references a BackupClass that contains strategy and related parameters
// The BackupClass will be resolved to determine the appropriate strategy and parameters
// based on the ApplicationRef.
BackupClassName string `json:"backupClassName"`
}
```
**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`.
* Resolves the `BackupClass` referenced by `spec.backupClassName`.
* Matches the `ApplicationRef` against strategies in the `BackupClass` to find the appropriate strategy.
* Reconciles runs where the resolved strategy's `apiGroup/kind` matches its **strategy type(s)**.
* Driver responsibilities:
1. On first reconcile:
* Set `status.startedAt` if unset.
* Set `status.phase = Running`.
2. Resolve inputs:
* Resolve `BackupClass` from `spec.backupClassName`.
* Match `ApplicationRef` against `BackupClass` strategies to get `StrategyRef` and `Parameters`.
* Read `Strategy` (driver-owned CRD) from `StrategyRef`.
* Read `Application` from `ApplicationRef`.
* Extract parameters from `Parameters` (e.g., `backupStorageLocationName` for Velero).
* Process strategy template with context: `Application` object and `Parameters` from `BackupClass`.
3. Execute backup logic (implementation-specific).
4. On success:
* 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"`
StrategyRef corev1.TypedLocalObjectReference `json:"strategyRef"`
TakenAt metav1.Time `json:"takenAt"`
DriverMetadata map[string]string `json:"driverMetadata,omitempty"`
}
```
**Note:** Parameters are not stored directly in `Backup`. Instead, they are resolved from `BackupClass` parameters when the backup was created. The storage location is managed by the driver (e.g., Velero's `BackupStorageLocation`) and referenced via parameters in the `BackupClass`.
**Key fields (status)**
```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 reference.
* The strategy reference (resolved from `BackupClass` during `BackupJob` execution).
* `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.
**Note:** Parameters are resolved from `BackupClass` when the `BackupJob` is created. The driver uses these parameters to determine where to store backups. The storage location itself is managed by the driver (e.g., Velero's `BackupStorageLocation` CRD) and is not directly referenced in the `Backup` resource. When restoring, the driver resolves the storage location from the original `BackupClass` parameters or from the driver's own metadata.
---
### 4.5 RestoreJob
**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**: Resolved from driver metadata or `BackupClass` parameters (e.g., `backupStorageLocationName` stored in `driverMetadata` or resolved from the original `BackupClass`).
* **Target application**: `spec.targetApplicationRef` or `backup.spec.applicationRef`.
* If effective strategys 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 location (from driver metadata or `BackupClass`), `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** (Plan schedule) core-owned.
* **How & where** (BackupClass) central configuration unit that encapsulates strategy and parameters (e.g., storage reference) per application type, resolved per BackupJob/Plan.
* **Execution** (BackupJob) created by Plan when schedule fires, resolves BackupClass to get strategy and parameters, then delegates to driver.
* **What backup artifacts exist** (Backup) driver-created but cluster-visible.
* **Restore lifecycle** (RestoreJob) shared contract boundary.
* Allows multiple strategy drivers to implement backup/restore logic without entangling their implementation with the core API.

View File

@@ -1,114 +0,0 @@
// 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"`
// 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"`
}

View File

@@ -1,85 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
// Package v1alpha1 defines backups.cozystack.io API types.
//
// Group: backups.cozystack.io
// Version: v1alpha1
package v1alpha1
import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
func init() {
SchemeBuilder.Register(func(s *runtime.Scheme) error {
s.AddKnownTypes(GroupVersion,
&BackupClass{},
&BackupClassList{},
)
return nil
})
}
// +kubebuilder:object:root=true
// +kubebuilder:resource:scope=Cluster
// +kubebuilder:subresource:status
// BackupClass defines a class of backup configurations that can be referenced
// by BackupJob and Plan resources. It encapsulates strategy and storage configuration
// per application type.
type BackupClass struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec BackupClassSpec `json:"spec,omitempty"`
Status BackupClassStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// BackupClassList contains a list of BackupClasses.
type BackupClassList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []BackupClass `json:"items"`
}
// BackupClassSpec defines the desired state of a BackupClass.
type BackupClassSpec struct {
// Strategies is a list of backup strategies, each matching a specific application type.
Strategies []BackupClassStrategy `json:"strategies"`
}
// BackupClassStrategy defines a backup strategy for a specific application type.
type BackupClassStrategy struct {
// StrategyRef references the driver-specific BackupStrategy (e.g., Velero).
StrategyRef corev1.TypedLocalObjectReference `json:"strategyRef"`
// Application specifies which application types this strategy applies to.
Application ApplicationSelector `json:"application"`
// Parameters holds strategy-specific and storage-specific parameters.
// Common parameters include:
// - backupStorageLocationName: Name of Velero BackupStorageLocation
// +optional
Parameters map[string]string `json:"parameters,omitempty"`
}
// ApplicationSelector specifies which application types a strategy applies to.
type ApplicationSelector struct {
// APIGroup is the API group of the application.
// If not specified, defaults to "apps.cozystack.io".
// +optional
APIGroup *string `json:"apiGroup,omitempty"`
// Kind is the kind of the application (e.g., VirtualMachine, MariaDB).
Kind string `json:"kind"`
}
// BackupClassStatus defines the observed state of a BackupClass.
type BackupClassStatus struct {
// Conditions represents the latest available observations of a BackupClass's state.
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`
}

View File

@@ -1,131 +0,0 @@
// 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
})
}
const (
OwningJobNameLabel = thisGroup + "/owned-by.BackupJobName"
OwningJobNamespaceLabel = thisGroup + "/owned-by.BackupJobNamespace"
// DefaultApplicationAPIGroup is the default API group for applications
// when not specified in ApplicationRef or ApplicationSelector.
DefaultApplicationAPIGroup = "apps.cozystack.io"
)
// BackupJobPhase represents the lifecycle phase of a BackupJob.
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.
// If apiGroup is not specified, it defaults to "apps.cozystack.io".
ApplicationRef corev1.TypedLocalObjectReference `json:"applicationRef"`
// BackupClassName references a BackupClass that contains strategy and storage configuration.
// The BackupClass will be resolved to determine the appropriate strategy and storage
// based on the ApplicationRef.
// This field is immutable once the BackupJob is created.
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="backupClassName is immutable"
BackupClassName string `json:"backupClassName"`
}
// 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:subresource:status
// +kubebuilder:printcolumn:name="Phase",type="string",JSONPath=".status.phase",priority=0
// +kubebuilder:selectablefield:JSONPath=`.spec.applicationRef.apiGroup`
// +kubebuilder:selectablefield:JSONPath=`.spec.applicationRef.kind`
// +kubebuilder:selectablefield:JSONPath=`.spec.applicationRef.name`
// 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"`
}
// NormalizeApplicationRef sets the default apiGroup to DefaultApplicationAPIGroup if it's not specified.
// This function is exported so it can be used by other packages (e.g., controllers, factories).
func NormalizeApplicationRef(ref corev1.TypedLocalObjectReference) corev1.TypedLocalObjectReference {
if ref.APIGroup == nil || *ref.APIGroup == "" {
apiGroup := DefaultApplicationAPIGroup
ref.APIGroup = &apiGroup
}
return ref
}

View File

@@ -1,42 +0,0 @@
/*
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"
)
const (
thisGroup = "backups.cozystack.io"
thisVersion = "v1alpha1"
)
var (
GroupVersion = schema.GroupVersion{Group: thisGroup, Version: thisVersion}
SchemeBuilder = runtime.NewSchemeBuilder(addGroupVersion)
AddToScheme = SchemeBuilder.AddToScheme
)
func addGroupVersion(scheme *runtime.Scheme) error {
metav1.AddToGroupVersion(scheme, GroupVersion)
return nil
}

View File

@@ -1,96 +0,0 @@
// 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.
// If apiGroup is not specified, it defaults to "apps.cozystack.io".
ApplicationRef corev1.TypedLocalObjectReference `json:"applicationRef"`
// BackupClassName references a BackupClass that contains strategy and storage configuration.
// The BackupClass will be resolved to determine the appropriate strategy and storage
// based on the ApplicationRef.
BackupClassName string `json:"backupClassName"`
// Schedule specifies when backup copies are created.
Schedule PlanSchedule `json:"schedule"`
}
// 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"`
}

View File

@@ -1,93 +0,0 @@
// 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
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Phase",type="string",JSONPath=".status.phase",priority=0
// RestoreJob represents a single execution of a restore from a Backup.
type RestoreJob struct {
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"`
}

View File

@@ -1,643 +0,0 @@
//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 *ApplicationSelector) DeepCopyInto(out *ApplicationSelector) {
*out = *in
if in.APIGroup != nil {
in, out := &in.APIGroup, &out.APIGroup
*out = new(string)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationSelector.
func (in *ApplicationSelector) DeepCopy() *ApplicationSelector {
if in == nil {
return nil
}
out := new(ApplicationSelector)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Backup) DeepCopyInto(out *Backup) {
*out = *in
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 *BackupClass) DeepCopyInto(out *BackupClass) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupClass.
func (in *BackupClass) DeepCopy() *BackupClass {
if in == nil {
return nil
}
out := new(BackupClass)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *BackupClass) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BackupClassList) DeepCopyInto(out *BackupClassList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]BackupClass, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupClassList.
func (in *BackupClassList) DeepCopy() *BackupClassList {
if in == nil {
return nil
}
out := new(BackupClassList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *BackupClassList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BackupClassSpec) DeepCopyInto(out *BackupClassSpec) {
*out = *in
if in.Strategies != nil {
in, out := &in.Strategies, &out.Strategies
*out = make([]BackupClassStrategy, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupClassSpec.
func (in *BackupClassSpec) DeepCopy() *BackupClassSpec {
if in == nil {
return nil
}
out := new(BackupClassSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BackupClassStatus) DeepCopyInto(out *BackupClassStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]metav1.Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupClassStatus.
func (in *BackupClassStatus) DeepCopy() *BackupClassStatus {
if in == nil {
return nil
}
out := new(BackupClassStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BackupClassStrategy) DeepCopyInto(out *BackupClassStrategy) {
*out = *in
in.StrategyRef.DeepCopyInto(&out.StrategyRef)
in.Application.DeepCopyInto(&out.Application)
if in.Parameters != nil {
in, out := &in.Parameters, &out.Parameters
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupClassStrategy.
func (in *BackupClassStrategy) DeepCopy() *BackupClassStrategy {
if in == nil {
return nil
}
out := new(BackupClassStrategy)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BackupJob) DeepCopyInto(out *BackupJob) {
*out = *in
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)
}
// 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.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)
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
}

View File

@@ -253,25 +253,3 @@ type FactoryList struct {
metav1.ListMeta `json:"metadata,omitempty"`
Items []Factory `json:"items"`
}
// -----------------------------------------------------------------------------
// CustomFormsOverrideMapping
// -----------------------------------------------------------------------------
// +kubebuilder:object:root=true
// +kubebuilder:resource:path=cfomappings,scope=Cluster
// +kubebuilder:subresource:status
type CFOMapping struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ArbitrarySpec `json:"spec"`
Status CommonStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
type CFOMappingList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []CFOMapping `json:"items"`
}

View File

@@ -69,9 +69,6 @@ func addKnownTypes(scheme *runtime.Scheme) error {
&Factory{},
&FactoryList{},
&CFOMapping{},
&CFOMappingList{},
)
metav1.AddToGroupVersion(scheme, GroupVersion)
return nil

View File

@@ -159,65 +159,6 @@ func (in *BreadcrumbList) DeepCopyObject() runtime.Object {
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CFOMapping) DeepCopyInto(out *CFOMapping) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CFOMapping.
func (in *CFOMapping) DeepCopy() *CFOMapping {
if in == nil {
return nil
}
out := new(CFOMapping)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *CFOMapping) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CFOMappingList) DeepCopyInto(out *CFOMappingList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]CFOMapping, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CFOMappingList.
func (in *CFOMappingList) DeepCopy() *CFOMappingList {
if in == nil {
return nil
}
out := new(CFOMappingList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *CFOMappingList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CommonStatus) DeepCopyInto(out *CommonStatus) {
*out = *in

View File

@@ -17,12 +17,12 @@ limitations under the License.
package v1alpha1
import (
helmv2 "github.com/fluxcd/helm-controller/api/v2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
)
// +kubebuilder:object:root=true
// +kubebuilder:resource:scope=Cluster
// +kubebuilder:resource:scope=Cluster,shortName=appdef
// ApplicationDefinition is the Schema for the applicationdefinitions API
type ApplicationDefinition struct {
@@ -62,6 +62,24 @@ type ApplicationDefinitionSpec struct {
Dashboard *ApplicationDefinitionDashboard `json:"dashboard,omitempty"`
}
type ApplicationDefinitionChart struct {
// Name of the Helm chart
Name string `json:"name"`
// Source reference for the Helm chart
SourceRef SourceRef `json:"sourceRef"`
}
type SourceRef struct {
// Kind of the source reference
// +kubebuilder:default:="HelmRepository"
Kind string `json:"kind"`
// Name of the source reference
Name string `json:"name"`
// Namespace of the source reference
// +kubebuilder:default:="cozy-public"
Namespace string `json:"namespace"`
}
type ApplicationDefinitionApplication struct {
// Kind of the application, used for UI and API
Kind string `json:"kind"`
@@ -73,13 +91,27 @@ type ApplicationDefinitionApplication struct {
Singular string `json:"singular"`
}
// +kubebuilder:validation:XValidation:rule="(has(self.chart) && !has(self.chartRef)) || (!has(self.chart) && has(self.chartRef))",message="either chart or chartRef must be set, but not both"
type ApplicationDefinitionRelease struct {
// Reference to the chart source
ChartRef *helmv2.CrossNamespaceSourceReference `json:"chartRef"`
// Helm chart configuration (for HelmRepository source)
// +optional
Chart *ApplicationDefinitionChart `json:"chart,omitempty"`
// Chart reference configuration (for ExternalArtifact source)
// +optional
ChartRef *ApplicationDefinitionChartRef `json:"chartRef,omitempty"`
// Labels for the release
Labels map[string]string `json:"labels,omitempty"`
// Prefix for the release name
Prefix string `json:"prefix"`
// Default values to be merged into every HelmRelease created from this resource definition
// User-specified values in Application spec will override these default values
// +optional
Values *apiextensionsv1.JSON `json:"values,omitempty"`
}
type ApplicationDefinitionChartRef struct {
// Source reference for the chart (ExternalArtifact)
SourceRef SourceRef `json:"sourceRef"`
}
// ApplicationDefinitionResourceSelector extends metav1.LabelSelector with resourceNames support.
@@ -93,18 +125,17 @@ type ApplicationDefinitionRelease struct {
// - {{ .namespace }}: The namespace of the resource being processed
//
// Example YAML:
//
// secrets:
// include:
// - matchExpressions:
// - key: badlabel
// operator: DoesNotExist
// matchLabels:
// goodlabel: goodvalue
// resourceNames:
// - "{{ .name }}-secret"
// - "{{ .kind }}-{{ .name }}-tls"
// - "specificname"
// secrets:
// include:
// - matchExpressions:
// - key: badlabel
// operator: DoesNotExist
// matchLabels:
// goodlabel: goodvalue
// resourceNames:
// - "{{ .name }}-secret"
// - "{{ .kind }}-{{ .name }}-tls"
// - "specificname"
type ApplicationDefinitionResourceSelector struct {
metav1.LabelSelector `json:",inline"`
// ResourceNames is a list of resource names to match

View File

@@ -0,0 +1,230 @@
/*
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
import (
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +kubebuilder:object:root=true
// +kubebuilder:resource:scope=Cluster,shortName=bundle
// Bundle is the Schema for the bundles API
type Bundle struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec BundleSpec `json:"spec,omitempty"`
}
// +kubebuilder:object:root=true
// BundleList contains a list of Bundles
type BundleList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Bundle `json:"items"`
}
func init() {
SchemeBuilder.Register(&Bundle{}, &BundleList{})
}
// BundleSpec defines the desired state of Bundle
type BundleSpec struct {
// SourceRef is the source reference for the bundle charts
// +required
SourceRef BundleSourceRef `json:"sourceRef"`
// DependsOn is a list of bundle dependencies in the format "bundleName/target"
// For example: "cozystack-system/network"
// If specified, the dependencies listed in the target's packages will be taken
// from the specified bundle and added to all packages in this bundle
// +optional
DependsOn []string `json:"dependsOn,omitempty"`
// DependencyTargets defines named groups of packages that can be referenced
// by other bundles via dependsOn. Each target has a name and a list of packages.
// +optional
DependencyTargets []BundleDependencyTarget `json:"dependencyTargets,omitempty"`
// Libraries is a list of Helm library charts used by packages
// +optional
Libraries []BundleLibrary `json:"libraries,omitempty"`
// Artifacts is a list of Helm charts that will be built as ExternalArtifacts
// These artifacts can be referenced by ApplicationDefinitions
// +optional
Artifacts []BundleArtifact `json:"artifacts,omitempty"`
// Packages is a list of Helm releases to be installed as part of this bundle
// +required
Packages []BundleRelease `json:"packages"`
// DeletionPolicy defines how child resources should be handled when the bundle is deleted.
// - "Delete" (default): Child resources will be deleted when the bundle is deleted (via ownerReference).
// - "Orphan": Child resources will be orphaned (ownerReferences will be removed).
// +kubebuilder:validation:Enum=Delete;Orphan
// +kubebuilder:default=Delete
// +optional
DeletionPolicy DeletionPolicy `json:"deletionPolicy,omitempty"`
// Labels are labels that will be applied to all resources created by this bundle
// (ArtifactGenerators and HelmReleases). These labels are merged with the default
// cozystack.io/bundle label.
// +optional
Labels map[string]string `json:"labels,omitempty"`
// BasePath is the base path where packages are located in the source.
// For GitRepository, defaults to "packages" if not specified.
// For OCIRepository, defaults to empty string (root) if not specified.
// +optional
BasePath string `json:"basePath,omitempty"`
}
// DeletionPolicy defines how child resources should be handled when the parent is deleted.
// +kubebuilder:validation:Enum=Delete;Orphan
type DeletionPolicy string
const (
// DeletionPolicyDelete means child resources will be deleted when the parent is deleted.
DeletionPolicyDelete DeletionPolicy = "Delete"
// DeletionPolicyOrphan means child resources will be orphaned (ownerReferences removed).
DeletionPolicyOrphan DeletionPolicy = "Orphan"
)
// BundleDependencyTarget defines a named group of packages that can be referenced
// by other bundles via dependsOn
type BundleDependencyTarget struct {
// Name is the unique identifier for this dependency target
// +required
Name string `json:"name"`
// Packages is a list of package names that belong to this target
// These packages will be added as dependencies when this target is referenced
// +required
Packages []string `json:"packages"`
}
// BundleLibrary defines a Helm library chart
type BundleLibrary struct {
// Name is the unique identifier for this library
// +required
Name string `json:"name"`
// Path is the path to the library chart directory
// +required
Path string `json:"path"`
}
// BundleArtifact defines a Helm chart artifact that will be built as ExternalArtifact
type BundleArtifact struct {
// Name is the unique identifier for this artifact (used as ExternalArtifact name)
// +required
Name string `json:"name"`
// Path is the path to the Helm chart directory
// +required
Path string `json:"path"`
// Libraries is a list of library names that this artifact depends on
// +optional
Libraries []string `json:"libraries,omitempty"`
}
// BundleSourceRef defines the source reference for bundle charts
type BundleSourceRef struct {
// Kind of the source reference
// +kubebuilder:validation:Enum=GitRepository;OCIRepository
// +required
Kind string `json:"kind"`
// Name of the source reference
// +required
Name string `json:"name"`
// Namespace of the source reference
// +required
Namespace string `json:"namespace"`
}
// +kubebuilder:validation:XValidation:rule="(has(self.path) && !has(self.artifact)) || (!has(self.path) && has(self.artifact))",message="either path or artifact must be set, but not both"
// BundleRelease defines a single Helm release within a bundle
type BundleRelease struct {
// Name is the unique identifier for this release within the bundle
// +required
Name string `json:"name"`
// ReleaseName is the name of the HelmRelease resource that will be created
// +required
ReleaseName string `json:"releaseName"`
// Path is the path to the Helm chart directory
// Either Path or Artifact must be specified, but not both
// +optional
Path string `json:"path,omitempty"`
// Artifact is the name of an artifact from the bundle's artifacts list
// The artifact must exist in the bundle's artifacts section
// Either Path or Artifact must be specified, but not both
// +optional
Artifact string `json:"artifact,omitempty"`
// Namespace is the Kubernetes namespace where the release will be installed
// +required
Namespace string `json:"namespace"`
// Privileged indicates whether this release requires privileged access
// +optional
Privileged bool `json:"privileged,omitempty"`
// Disabled indicates whether this release is disabled (should not be installed)
// +optional
Disabled bool `json:"disabled,omitempty"`
// DependsOn is a list of release names that must be installed before this release
// +optional
DependsOn []string `json:"dependsOn,omitempty"`
// Libraries is a list of library names that this package depends on
// +optional
Libraries []string `json:"libraries,omitempty"`
// Values contains Helm chart values as a JSON object
// +optional
Values *apiextensionsv1.JSON `json:"values,omitempty"`
// ValuesFiles is a list of values file names to use
// +optional
ValuesFiles []string `json:"valuesFiles,omitempty"`
// Labels are labels that will be applied to the HelmRelease created for this package
// These labels are merged with bundle-level labels and the default cozystack.io/bundle label
// +optional
Labels map[string]string `json:"labels,omitempty"`
// NamespaceLabels are labels that will be applied to the namespace for this package
// These labels are merged with labels from other packages in the same namespace
// +optional
NamespaceLabels map[string]string `json:"namespaceLabels,omitempty"`
// NamespaceAnnotations are annotations that will be applied to the namespace for this package
// These annotations are merged with annotations from other packages in the same namespace
// +optional
NamespaceAnnotations map[string]string `json:"namespaceAnnotations,omitempty"`
}

View File

@@ -1,100 +0,0 @@
/*
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
import (
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +kubebuilder:object:root=true
// +kubebuilder:resource:scope=Cluster,shortName={pkg,pkgs}
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Variant",type="string",JSONPath=".spec.variant",description="Selected variant"
// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status",description="Ready status"
// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].message",description="Ready message"
// Package is the Schema for the packages API
type Package struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec PackageSpec `json:"spec,omitempty"`
Status PackageStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// PackageList contains a list of Packages
type PackageList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Package `json:"items"`
}
func init() {
SchemeBuilder.Register(&Package{}, &PackageList{})
}
// PackageSpec defines the desired state of Package
type PackageSpec struct {
// Variant is the name of the variant to use from the PackageSource
// If not specified, defaults to "default"
// +optional
Variant string `json:"variant,omitempty"`
// IgnoreDependencies is a list of package source dependencies to ignore
// Dependencies listed here will not be installed even if they are specified in the PackageSource
// +optional
IgnoreDependencies []string `json:"ignoreDependencies,omitempty"`
// Components is a map of release name to component overrides
// Allows overriding values and enabling/disabling specific components from the PackageSource
// +optional
Components map[string]PackageComponent `json:"components,omitempty"`
}
// PackageComponent defines overrides for a specific component
type PackageComponent struct {
// Enabled indicates whether this component should be installed
// If false, the component will be disabled even if it's defined in the PackageSource
// +optional
Enabled *bool `json:"enabled,omitempty"`
// Values contains Helm chart values as a JSON object
// These values will be merged with the default values from the PackageSource
// +optional
Values *apiextensionsv1.JSON `json:"values,omitempty"`
}
// PackageStatus defines the observed state of Package
type PackageStatus struct {
// Conditions represents the latest available observations of a Package's state
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`
// Dependencies tracks the readiness status of each dependency
// Key is the dependency package name, value indicates if the dependency is ready
// +optional
Dependencies map[string]DependencyStatus `json:"dependencies,omitempty"`
}
// DependencyStatus represents the readiness status of a dependency
type DependencyStatus struct {
// Ready indicates whether the dependency is ready
Ready bool `json:"ready"`
}

View File

@@ -1,171 +0,0 @@
/*
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
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +kubebuilder:object:root=true
// +kubebuilder:resource:scope=Cluster,shortName={pks}
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Variants",type="string",JSONPath=".status.variants",description="Package variants (comma-separated)"
// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status",description="Ready status"
// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].message",description="Ready message"
// PackageSource is the Schema for the packagesources API
type PackageSource struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec PackageSourceSpec `json:"spec,omitempty"`
Status PackageSourceStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// PackageSourceList contains a list of PackageSources
type PackageSourceList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []PackageSource `json:"items"`
}
func init() {
SchemeBuilder.Register(&PackageSource{}, &PackageSourceList{})
}
// PackageSourceSpec defines the desired state of PackageSource
type PackageSourceSpec struct {
// SourceRef is the source reference for the package source charts
// +optional
SourceRef *PackageSourceRef `json:"sourceRef,omitempty"`
// Variants is a list of package source variants
// Each variant defines components, applications, dependencies, and libraries for a specific configuration
// +optional
Variants []Variant `json:"variants,omitempty"`
}
// Variant defines a single variant configuration
type Variant struct {
// Name is the unique identifier for this variant
// +required
Name string `json:"name"`
// DependsOn is a list of package source dependencies
// For example: "cozystack.networking"
// +optional
DependsOn []string `json:"dependsOn,omitempty"`
// Libraries is a list of Helm library charts used by components in this variant
// +optional
Libraries []Library `json:"libraries,omitempty"`
// Components is a list of Helm releases to be installed as part of this variant
// +optional
Components []Component `json:"components,omitempty"`
}
// Library defines a Helm library chart
type Library struct {
// Name is the optional name for library placed in charts
// +optional
Name string `json:"name,omitempty"`
// Path is the path to the library chart directory
// +required
Path string `json:"path"`
}
// PackageSourceRef defines the source reference for package source charts
type PackageSourceRef struct {
// Kind of the source reference
// +kubebuilder:validation:Enum=GitRepository;OCIRepository
// +required
Kind string `json:"kind"`
// Name of the source reference
// +required
Name string `json:"name"`
// Namespace of the source reference
// +required
Namespace string `json:"namespace"`
// Path is the base path where packages are located in the source.
// For GitRepository, defaults to "packages" if not specified.
// For OCIRepository, defaults to empty string (root) if not specified.
// +optional
Path string `json:"path,omitempty"`
}
// ComponentInstall defines installation parameters for a component
type ComponentInstall struct {
// ReleaseName is the name of the HelmRelease resource that will be created
// If not specified, defaults to the component Name field
// +optional
ReleaseName string `json:"releaseName,omitempty"`
// Namespace is the Kubernetes namespace where the release will be installed
// +optional
Namespace string `json:"namespace,omitempty"`
// Privileged indicates whether this release requires privileged access
// +optional
Privileged bool `json:"privileged,omitempty"`
// DependsOn is a list of component names that must be installed before this component
// +optional
DependsOn []string `json:"dependsOn,omitempty"`
}
// Component defines a single Helm release component within a package source
type Component struct {
// Name is the unique identifier for this component within the package source
// +required
Name string `json:"name"`
// Path is the path to the Helm chart directory
// +required
Path string `json:"path"`
// Install defines installation parameters for this component
// +optional
Install *ComponentInstall `json:"install,omitempty"`
// Libraries is a list of library names that this component depends on
// These libraries must be defined at the variant level
// +optional
Libraries []string `json:"libraries,omitempty"`
// ValuesFiles is a list of values file names to use
// +optional
ValuesFiles []string `json:"valuesFiles,omitempty"`
}
// PackageSourceStatus defines the observed state of PackageSource
type PackageSourceStatus struct {
// Variants is a comma-separated list of package variant names
// This field is populated by the controller based on spec.variants keys
// +optional
Variants string `json:"variants,omitempty"`
// Conditions represents the latest available observations of a PackageSource's state
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`
}

View File

@@ -0,0 +1,71 @@
/*
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
import (
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +kubebuilder:object:root=true
// +kubebuilder:resource:scope=Cluster,shortName=platform
// Platform is the Schema for the platforms API
type Platform struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec PlatformSpec `json:"spec,omitempty"`
}
// +kubebuilder:object:root=true
// PlatformList contains a list of Platform
type PlatformList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Platform `json:"items"`
}
func init() {
SchemeBuilder.Register(&Platform{}, &PlatformList{})
}
// PlatformSpec defines the desired state of Platform
type PlatformSpec struct {
// SourceRef is the source reference for the platform chart
// This is used to generate the ArtifactGenerator
// +required
SourceRef SourceRef `json:"sourceRef"`
// Values contains Helm chart values as a JSON object
// These values are passed directly to HelmRelease.values
// +optional
Values *apiextensionsv1.JSON `json:"values,omitempty"`
// Interval is the interval at which to reconcile the HelmRelease
// +kubebuilder:default="5m"
// +optional
Interval *metav1.Duration `json:"interval,omitempty"`
// BasePath is the base path where the platform chart is located in the source.
// For GitRepository, defaults to "packages/core/platform" if not specified.
// For OCIRepository, defaults to "core/platform" if not specified.
// +optional
BasePath string `json:"basePath,omitempty"`
}

View File

@@ -21,7 +21,6 @@ limitations under the License.
package v1alpha1
import (
"github.com/fluxcd/helm-controller/api/v2"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -69,6 +68,38 @@ func (in *ApplicationDefinitionApplication) DeepCopy() *ApplicationDefinitionApp
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ApplicationDefinitionChart) DeepCopyInto(out *ApplicationDefinitionChart) {
*out = *in
out.SourceRef = in.SourceRef
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationDefinitionChart.
func (in *ApplicationDefinitionChart) DeepCopy() *ApplicationDefinitionChart {
if in == nil {
return nil
}
out := new(ApplicationDefinitionChart)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ApplicationDefinitionChartRef) DeepCopyInto(out *ApplicationDefinitionChartRef) {
*out = *in
out.SourceRef = in.SourceRef
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationDefinitionChartRef.
func (in *ApplicationDefinitionChartRef) DeepCopy() *ApplicationDefinitionChartRef {
if in == nil {
return nil
}
out := new(ApplicationDefinitionChartRef)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ApplicationDefinitionDashboard) DeepCopyInto(out *ApplicationDefinitionDashboard) {
*out = *in
@@ -140,9 +171,14 @@ func (in *ApplicationDefinitionList) DeepCopyObject() runtime.Object {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ApplicationDefinitionRelease) DeepCopyInto(out *ApplicationDefinitionRelease) {
*out = *in
if in.Chart != nil {
in, out := &in.Chart, &out.Chart
*out = new(ApplicationDefinitionChart)
**out = **in
}
if in.ChartRef != nil {
in, out := &in.ChartRef, &out.ChartRef
*out = new(v2.CrossNamespaceSourceReference)
*out = new(ApplicationDefinitionChartRef)
**out = **in
}
if in.Labels != nil {
@@ -152,6 +188,11 @@ func (in *ApplicationDefinitionRelease) DeepCopyInto(out *ApplicationDefinitionR
(*out)[key] = val
}
}
if in.Values != nil {
in, out := &in.Values, &out.Values
*out = new(v1.JSON)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationDefinitionRelease.
@@ -248,344 +289,323 @@ func (in *ApplicationDefinitionSpec) DeepCopy() *ApplicationDefinitionSpec {
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Component) DeepCopyInto(out *Component) {
func (in *Bundle) DeepCopyInto(out *Bundle) {
*out = *in
if in.Install != nil {
in, out := &in.Install, &out.Install
*out = new(ComponentInstall)
(*in).DeepCopyInto(*out)
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Bundle.
func (in *Bundle) DeepCopy() *Bundle {
if in == nil {
return nil
}
out := new(Bundle)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Bundle) 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 *BundleArtifact) DeepCopyInto(out *BundleArtifact) {
*out = *in
if in.Libraries != nil {
in, out := &in.Libraries, &out.Libraries
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.ValuesFiles != nil {
in, out := &in.ValuesFiles, &out.ValuesFiles
*out = make([]string, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Component.
func (in *Component) DeepCopy() *Component {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BundleArtifact.
func (in *BundleArtifact) DeepCopy() *BundleArtifact {
if in == nil {
return nil
}
out := new(Component)
out := new(BundleArtifact)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ComponentInstall) DeepCopyInto(out *ComponentInstall) {
func (in *BundleDependencyTarget) DeepCopyInto(out *BundleDependencyTarget) {
*out = *in
if in.Packages != nil {
in, out := &in.Packages, &out.Packages
*out = make([]string, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BundleDependencyTarget.
func (in *BundleDependencyTarget) DeepCopy() *BundleDependencyTarget {
if in == nil {
return nil
}
out := new(BundleDependencyTarget)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BundleLibrary) DeepCopyInto(out *BundleLibrary) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BundleLibrary.
func (in *BundleLibrary) DeepCopy() *BundleLibrary {
if in == nil {
return nil
}
out := new(BundleLibrary)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BundleList) DeepCopyInto(out *BundleList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Bundle, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BundleList.
func (in *BundleList) DeepCopy() *BundleList {
if in == nil {
return nil
}
out := new(BundleList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *BundleList) 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 *BundleRelease) DeepCopyInto(out *BundleRelease) {
*out = *in
if in.DependsOn != nil {
in, out := &in.DependsOn, &out.DependsOn
*out = make([]string, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentInstall.
func (in *ComponentInstall) DeepCopy() *ComponentInstall {
if in == nil {
return nil
}
out := new(ComponentInstall)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DependencyStatus) DeepCopyInto(out *DependencyStatus) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DependencyStatus.
func (in *DependencyStatus) DeepCopy() *DependencyStatus {
if in == nil {
return nil
}
out := new(DependencyStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Library) DeepCopyInto(out *Library) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Library.
func (in *Library) DeepCopy() *Library {
if in == nil {
return nil
}
out := new(Library)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Package) DeepCopyInto(out *Package) {
*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 Package.
func (in *Package) DeepCopy() *Package {
if in == nil {
return nil
}
out := new(Package)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Package) 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 *PackageComponent) DeepCopyInto(out *PackageComponent) {
*out = *in
if in.Enabled != nil {
in, out := &in.Enabled, &out.Enabled
*out = new(bool)
**out = **in
if in.Libraries != nil {
in, out := &in.Libraries, &out.Libraries
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Values != nil {
in, out := &in.Values, &out.Values
*out = new(v1.JSON)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PackageComponent.
func (in *PackageComponent) DeepCopy() *PackageComponent {
if in == nil {
return nil
}
out := new(PackageComponent)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PackageList) DeepCopyInto(out *PackageList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Package, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PackageList.
func (in *PackageList) DeepCopy() *PackageList {
if in == nil {
return nil
}
out := new(PackageList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *PackageList) 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 *PackageSource) DeepCopyInto(out *PackageSource) {
*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 PackageSource.
func (in *PackageSource) DeepCopy() *PackageSource {
if in == nil {
return nil
}
out := new(PackageSource)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *PackageSource) 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 *PackageSourceList) DeepCopyInto(out *PackageSourceList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]PackageSource, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PackageSourceList.
func (in *PackageSourceList) DeepCopy() *PackageSourceList {
if in == nil {
return nil
}
out := new(PackageSourceList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *PackageSourceList) 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 *PackageSourceRef) DeepCopyInto(out *PackageSourceRef) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PackageSourceRef.
func (in *PackageSourceRef) DeepCopy() *PackageSourceRef {
if in == nil {
return nil
}
out := new(PackageSourceRef)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PackageSourceSpec) DeepCopyInto(out *PackageSourceSpec) {
*out = *in
if in.SourceRef != nil {
in, out := &in.SourceRef, &out.SourceRef
*out = new(PackageSourceRef)
**out = **in
}
if in.Variants != nil {
in, out := &in.Variants, &out.Variants
*out = make([]Variant, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PackageSourceSpec.
func (in *PackageSourceSpec) DeepCopy() *PackageSourceSpec {
if in == nil {
return nil
}
out := new(PackageSourceSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PackageSourceStatus) DeepCopyInto(out *PackageSourceStatus) {
*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 PackageSourceStatus.
func (in *PackageSourceStatus) DeepCopy() *PackageSourceStatus {
if in == nil {
return nil
}
out := new(PackageSourceStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PackageSpec) DeepCopyInto(out *PackageSpec) {
*out = *in
if in.IgnoreDependencies != nil {
in, out := &in.IgnoreDependencies, &out.IgnoreDependencies
if in.ValuesFiles != nil {
in, out := &in.ValuesFiles, &out.ValuesFiles
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Components != nil {
in, out := &in.Components, &out.Components
*out = make(map[string]PackageComponent, len(*in))
if in.Labels != nil {
in, out := &in.Labels, &out.Labels
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = *val.DeepCopy()
(*out)[key] = val
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PackageSpec.
func (in *PackageSpec) DeepCopy() *PackageSpec {
if in == nil {
return nil
}
out := new(PackageSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PackageStatus) DeepCopyInto(out *PackageStatus) {
*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])
if in.NamespaceLabels != nil {
in, out := &in.NamespaceLabels, &out.NamespaceLabels
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.Dependencies != nil {
in, out := &in.Dependencies, &out.Dependencies
*out = make(map[string]DependencyStatus, len(*in))
if in.NamespaceAnnotations != nil {
in, out := &in.NamespaceAnnotations, &out.NamespaceAnnotations
*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 PackageStatus.
func (in *PackageStatus) DeepCopy() *PackageStatus {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BundleRelease.
func (in *BundleRelease) DeepCopy() *BundleRelease {
if in == nil {
return nil
}
out := new(PackageStatus)
out := new(BundleRelease)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BundleSourceRef) DeepCopyInto(out *BundleSourceRef) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BundleSourceRef.
func (in *BundleSourceRef) DeepCopy() *BundleSourceRef {
if in == nil {
return nil
}
out := new(BundleSourceRef)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BundleSpec) DeepCopyInto(out *BundleSpec) {
*out = *in
out.SourceRef = in.SourceRef
if in.DependsOn != nil {
in, out := &in.DependsOn, &out.DependsOn
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.DependencyTargets != nil {
in, out := &in.DependencyTargets, &out.DependencyTargets
*out = make([]BundleDependencyTarget, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Libraries != nil {
in, out := &in.Libraries, &out.Libraries
*out = make([]BundleLibrary, len(*in))
copy(*out, *in)
}
if in.Artifacts != nil {
in, out := &in.Artifacts, &out.Artifacts
*out = make([]BundleArtifact, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Packages != nil {
in, out := &in.Packages, &out.Packages
*out = make([]BundleRelease, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.Labels != nil {
in, out := &in.Labels, &out.Labels
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BundleSpec.
func (in *BundleSpec) DeepCopy() *BundleSpec {
if in == nil {
return nil
}
out := new(BundleSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Platform) DeepCopyInto(out *Platform) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Platform.
func (in *Platform) DeepCopy() *Platform {
if in == nil {
return nil
}
out := new(Platform)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Platform) 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 *PlatformList) DeepCopyInto(out *PlatformList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Platform, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlatformList.
func (in *PlatformList) DeepCopy() *PlatformList {
if in == nil {
return nil
}
out := new(PlatformList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *PlatformList) 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 *PlatformSpec) DeepCopyInto(out *PlatformSpec) {
*out = *in
out.SourceRef = in.SourceRef
if in.Values != nil {
in, out := &in.Values, &out.Values
*out = new(v1.JSON)
(*in).DeepCopyInto(*out)
}
if in.Interval != nil {
in, out := &in.Interval, &out.Interval
*out = new(metav1.Duration)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlatformSpec.
func (in *PlatformSpec) DeepCopy() *PlatformSpec {
if in == nil {
return nil
}
out := new(PlatformSpec)
in.DeepCopyInto(out)
return out
}
@@ -612,33 +632,16 @@ func (in Selector) DeepCopy() Selector {
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Variant) DeepCopyInto(out *Variant) {
func (in *SourceRef) DeepCopyInto(out *SourceRef) {
*out = *in
if in.DependsOn != nil {
in, out := &in.DependsOn, &out.DependsOn
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.Libraries != nil {
in, out := &in.Libraries, &out.Libraries
*out = make([]Library, len(*in))
copy(*out, *in)
}
if in.Components != nil {
in, out := &in.Components, &out.Components
*out = make([]Component, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Variant.
func (in *Variant) DeepCopy() *Variant {
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SourceRef.
func (in *SourceRef) DeepCopy() *SourceRef {
if in == nil {
return nil
}
out := new(Variant)
out := new(SourceRef)
in.DeepCopyInto(out)
return out
}

View File

@@ -1,181 +0,0 @@
/*
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/cache"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"sigs.k8s.io/controller-runtime/pkg/metrics/filters"
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",
Cache: cache.Options{
ByObject: map[client.Object]cache.ByObject{
&backupsv1alpha1.BackupClass{}: {},
},
},
// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily
// when the Manager ends. This requires the binary to immediately end when the
// Manager is stopped, otherwise, this setting is unsafe. Setting this significantly
// 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)
}
}

View File

@@ -1,195 +0,0 @@
/*
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/cache"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"sigs.k8s.io/controller-runtime/pkg/metrics/filters"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
"sigs.k8s.io/controller-runtime/pkg/webhook"
strategyv1alpha1 "github.com/cozystack/cozystack/api/backups/strategy/v1alpha1"
backupsv1alpha1 "github.com/cozystack/cozystack/api/backups/v1alpha1"
"github.com/cozystack/cozystack/internal/backupcontroller"
velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
// +kubebuilder:scaffold:imports
)
var (
scheme = runtime.NewScheme()
setupLog = ctrl.Log.WithName("setup")
)
func init() {
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
utilruntime.Must(backupsv1alpha1.AddToScheme(scheme))
utilruntime.Must(strategyv1alpha1.AddToScheme(scheme))
utilruntime.Must(velerov1.AddToScheme(scheme))
// +kubebuilder:scaffold:scheme
}
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",
Cache: cache.Options{
ByObject: map[client.Object]cache.ByObject{
&backupsv1alpha1.BackupClass{}: {},
},
},
// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily
// when the Manager ends. This requires the binary to immediately end when the
// Manager is stopped, otherwise, this setting is unsafe. Setting this significantly
// 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.BackupJobReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Recorder: mgr.GetEventRecorderFor("backup-controller"),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "BackupJob")
os.Exit(1)
}
if err = (&backupcontroller.RestoreJobReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Recorder: mgr.GetEventRecorderFor("restore-controller"),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "RestoreJob")
os.Exit(1)
}
// +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)
}
}

View File

@@ -1,549 +0,0 @@
/*
Copyright 2025 The Cozystack Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"bufio"
"context"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/spf13/cobra"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer/yaml"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
_ "k8s.io/client-go/plugin/pkg/client/auth"
)
var addCmdFlags struct {
files []string
kubeconfig string
}
var addCmd = &cobra.Command{
Use: "add [package]...",
Short: "Install PackageSource and its dependencies interactively",
Long: `Install PackageSource and its dependencies interactively.
You can specify packages as arguments or use -f flag to read from files.
Multiple -f flags can be specified, and they can point to files or directories.`,
Args: cobra.ArbitraryArgs,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
// Collect package names from arguments and files
packageNames := make(map[string]bool)
packagesFromFiles := make(map[string]string) // packageName -> filePath
for _, arg := range args {
packageNames[arg] = true
}
// Read packages from files
for _, filePath := range addCmdFlags.files {
packages, err := readPackagesFromFile(filePath)
if err != nil {
return fmt.Errorf("failed to read packages from %s: %w", filePath, err)
}
for _, pkg := range packages {
packageNames[pkg] = true
if oldPath, ok := packagesFromFiles[pkg]; ok {
fmt.Fprintf(os.Stderr, "warning: package %q is defined in both %s and %s, using the latter\n", pkg, oldPath, filePath)
}
packagesFromFiles[pkg] = filePath
}
}
if len(packageNames) == 0 {
return fmt.Errorf("no packages specified")
}
// Create Kubernetes client config
var config *rest.Config
var err error
if addCmdFlags.kubeconfig != "" {
config, err = clientcmd.BuildConfigFromFlags("", addCmdFlags.kubeconfig)
if err != nil {
return fmt.Errorf("failed to load kubeconfig from %s: %w", addCmdFlags.kubeconfig, err)
}
} else {
config, err = ctrl.GetConfig()
if err != nil {
return fmt.Errorf("failed to get kubeconfig: %w", err)
}
}
scheme := runtime.NewScheme()
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
utilruntime.Must(cozyv1alpha1.AddToScheme(scheme))
k8sClient, err := client.New(config, client.Options{Scheme: scheme})
if err != nil {
return fmt.Errorf("failed to create k8s client: %w", err)
}
// Process each package
for packageName := range packageNames {
// Check if package comes from a file
if filePath, fromFile := packagesFromFiles[packageName]; fromFile {
// Try to create Package directly from file
if err := createPackageFromFile(ctx, k8sClient, filePath, packageName); err == nil {
fmt.Fprintf(os.Stderr, "✓ Added Package %s\n", packageName)
continue
}
// If failed, fall back to interactive installation
}
// Interactive installation from PackageSource
if err := installPackage(ctx, k8sClient, packageName); err != nil {
return err
}
}
return nil
},
}
func readPackagesFromFile(filePath string) ([]string, error) {
info, err := os.Stat(filePath)
if err != nil {
return nil, err
}
var packages []string
if info.IsDir() {
// Read all YAML files from directory
err := filepath.Walk(filePath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() || !strings.HasSuffix(path, ".yaml") && !strings.HasSuffix(path, ".yml") {
return nil
}
pkgs, err := readPackagesFromYAMLFile(path)
if err != nil {
return fmt.Errorf("failed to read %s: %w", path, err)
}
packages = append(packages, pkgs...)
return nil
})
if err != nil {
return nil, err
}
} else {
packages, err = readPackagesFromYAMLFile(filePath)
if err != nil {
return nil, err
}
}
return packages, nil
}
func readPackagesFromYAMLFile(filePath string) ([]string, error) {
data, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}
var packages []string
// Split YAML documents (in case of multiple resources)
documents := strings.Split(string(data), "---")
for _, doc := range documents {
doc = strings.TrimSpace(doc)
if doc == "" {
continue
}
// Parse using Kubernetes decoder
decoder := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme)
obj := &unstructured.Unstructured{}
_, _, err := decoder.Decode([]byte(doc), nil, obj)
if err != nil {
continue
}
// Check if it's a Package
if obj.GetKind() == "Package" {
name := obj.GetName()
if name != "" {
packages = append(packages, name)
}
continue
}
// Check if it's a PackageSource
if obj.GetKind() == "PackageSource" {
name := obj.GetName()
if name != "" {
packages = append(packages, name)
}
continue
}
// Try to parse as PackageList or PackageSourceList
if obj.GetKind() == "PackageList" || obj.GetKind() == "PackageSourceList" {
items, found, err := unstructured.NestedSlice(obj.Object, "items")
if err == nil && found {
for _, item := range items {
if itemMap, ok := item.(map[string]interface{}); ok {
if metadata, ok := itemMap["metadata"].(map[string]interface{}); ok {
if name, ok := metadata["name"].(string); ok && name != "" {
packages = append(packages, name)
}
}
}
}
}
continue
}
}
// Return empty list if no packages found - don't error out
// The check for whether any packages were specified at all is handled later in RunE
return packages, nil
}
// buildDependencyTree builds a dependency tree starting from the root PackageSource
// Returns both the dependency tree and a map of dependencies to their requesters
func buildDependencyTree(ctx context.Context, k8sClient client.Client, rootName string) (map[string][]string, map[string]string, error) {
tree := make(map[string][]string)
dependencyRequesters := make(map[string]string) // dep -> requester
visited := make(map[string]bool)
// Ensure root is in tree even if it has no dependencies
tree[rootName] = []string{}
var buildTree func(string) error
buildTree = func(pkgName string) error {
if visited[pkgName] {
return nil
}
visited[pkgName] = true
// Get PackageSource
ps := &cozyv1alpha1.PackageSource{}
if err := k8sClient.Get(ctx, client.ObjectKey{Name: pkgName}, ps); err != nil {
// If PackageSource doesn't exist, just skip it
return nil
}
// Collect all dependencies from all variants
deps := make(map[string]bool)
for _, variant := range ps.Spec.Variants {
for _, dep := range variant.DependsOn {
deps[dep] = true
}
}
// Add dependencies to tree
for dep := range deps {
if _, exists := tree[pkgName]; !exists {
tree[pkgName] = []string{}
}
tree[pkgName] = append(tree[pkgName], dep)
// Track who requested this dependency
dependencyRequesters[dep] = pkgName
// Recursively build tree for dependencies
if err := buildTree(dep); err != nil {
return err
}
}
return nil
}
if err := buildTree(rootName); err != nil {
return nil, nil, err
}
return tree, dependencyRequesters, nil
}
// topologicalSort performs topological sort on the dependency tree
// Returns order from root to leaves (dependencies first)
func topologicalSort(tree map[string][]string) ([]string, error) {
// Build reverse graph (dependencies -> dependents)
reverseGraph := make(map[string][]string)
allNodes := make(map[string]bool)
for node, deps := range tree {
allNodes[node] = true
for _, dep := range deps {
allNodes[dep] = true
reverseGraph[dep] = append(reverseGraph[dep], node)
}
}
// Calculate in-degrees (how many dependencies a node has)
inDegree := make(map[string]int)
for node := range allNodes {
inDegree[node] = 0
}
for node, deps := range tree {
inDegree[node] = len(deps)
}
// Kahn's algorithm - start with nodes that have no dependencies
var queue []string
for node, degree := range inDegree {
if degree == 0 {
queue = append(queue, node)
}
}
var result []string
for len(queue) > 0 {
node := queue[0]
queue = queue[1:]
result = append(result, node)
// Process dependents (nodes that depend on this node)
for _, dependent := range reverseGraph[node] {
inDegree[dependent]--
if inDegree[dependent] == 0 {
queue = append(queue, dependent)
}
}
}
// Check for cycles
if len(result) != len(allNodes) {
return nil, fmt.Errorf("dependency cycle detected")
}
return result, nil
}
// createPackageFromFile creates a Package resource directly from a YAML file
func createPackageFromFile(ctx context.Context, k8sClient client.Client, filePath string, packageName string) error {
data, err := os.ReadFile(filePath)
if err != nil {
return err
}
// Split YAML documents
documents := strings.Split(string(data), "---")
for _, doc := range documents {
doc = strings.TrimSpace(doc)
if doc == "" {
continue
}
// Parse using Kubernetes decoder
decoder := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme)
obj := &unstructured.Unstructured{}
_, _, err := decoder.Decode([]byte(doc), nil, obj)
if err != nil {
continue
}
// Check if it's a Package with matching name
if obj.GetKind() == "Package" && obj.GetName() == packageName {
// Convert to Package
var pkg cozyv1alpha1.Package
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &pkg); err != nil {
return fmt.Errorf("failed to convert Package: %w", err)
}
// Create Package
if err := k8sClient.Create(ctx, &pkg); err != nil {
return fmt.Errorf("failed to create Package: %w", err)
}
return nil
}
}
return fmt.Errorf("Package %s not found in file", packageName)
}
func installPackage(ctx context.Context, k8sClient client.Client, packageSourceName string) error {
// Get PackageSource
packageSource := &cozyv1alpha1.PackageSource{}
if err := k8sClient.Get(ctx, client.ObjectKey{Name: packageSourceName}, packageSource); err != nil {
return fmt.Errorf("failed to get PackageSource %s: %w", packageSourceName, err)
}
// Build dependency tree
dependencyTree, dependencyRequesters, err := buildDependencyTree(ctx, k8sClient, packageSourceName)
if err != nil {
return fmt.Errorf("failed to build dependency tree: %w", err)
}
// Topological sort (install from root to leaves)
installOrder, err := topologicalSort(dependencyTree)
if err != nil {
return fmt.Errorf("failed to sort dependencies: %w", err)
}
// Get all PackageSources for variant selection
var allPackageSources cozyv1alpha1.PackageSourceList
if err := k8sClient.List(ctx, &allPackageSources); err != nil {
return fmt.Errorf("failed to list PackageSources: %w", err)
}
packageSourceMap := make(map[string]*cozyv1alpha1.PackageSource)
for i := range allPackageSources.Items {
packageSourceMap[allPackageSources.Items[i].Name] = &allPackageSources.Items[i]
}
// Get all installed Packages
var installedPackages cozyv1alpha1.PackageList
if err := k8sClient.List(ctx, &installedPackages); err != nil {
return fmt.Errorf("failed to list Packages: %w", err)
}
installedMap := make(map[string]*cozyv1alpha1.Package)
for i := range installedPackages.Items {
installedMap[installedPackages.Items[i].Name] = &installedPackages.Items[i]
}
// First, collect all variant selections
fmt.Fprintf(os.Stderr, "Installing %s and its dependencies...\n\n", packageSourceName)
packageVariants := make(map[string]string) // packageName -> variant
for _, pkgName := range installOrder {
// Check if already installed
if installed, exists := installedMap[pkgName]; exists {
variant := installed.Spec.Variant
if variant == "" {
variant = "default"
}
fmt.Fprintf(os.Stderr, "✓ %s (already installed, variant: %s)\n", pkgName, variant)
packageVariants[pkgName] = variant
continue
}
// Get PackageSource for this dependency
ps, exists := packageSourceMap[pkgName]
if !exists {
requester := dependencyRequesters[pkgName]
if requester != "" {
return fmt.Errorf("PackageSource %s not found (required by %s)", pkgName, requester)
}
return fmt.Errorf("PackageSource %s not found", pkgName)
}
// Select variant interactively
variant, err := selectVariantInteractive(ps)
if err != nil {
return fmt.Errorf("failed to select variant for %s: %w", pkgName, err)
}
packageVariants[pkgName] = variant
}
// Now create all Package resources
for _, pkgName := range installOrder {
// Skip if already installed
if _, exists := installedMap[pkgName]; exists {
continue
}
variant := packageVariants[pkgName]
// Create Package
pkg := &cozyv1alpha1.Package{
ObjectMeta: metav1.ObjectMeta{
Name: pkgName,
},
Spec: cozyv1alpha1.PackageSpec{
Variant: variant,
},
}
if err := k8sClient.Create(ctx, pkg); err != nil {
return fmt.Errorf("failed to create Package %s: %w", pkgName, err)
}
fmt.Fprintf(os.Stderr, "✓ Added Package %s\n", pkgName)
}
return nil
}
// selectVariantInteractive prompts user to select a variant
func selectVariantInteractive(ps *cozyv1alpha1.PackageSource) (string, error) {
if len(ps.Spec.Variants) == 0 {
return "", fmt.Errorf("no variants available for PackageSource %s", ps.Name)
}
reader := bufio.NewReader(os.Stdin)
fmt.Fprintf(os.Stderr, "\nPackageSource: %s\n", ps.Name)
fmt.Fprintf(os.Stderr, "Available variants:\n")
for i, variant := range ps.Spec.Variants {
fmt.Fprintf(os.Stderr, " %d. %s\n", i+1, variant.Name)
}
// If only one variant, use it as default
defaultVariant := ps.Spec.Variants[0].Name
var prompt string
if len(ps.Spec.Variants) == 1 {
prompt = "Select variant [1]: "
} else {
prompt = fmt.Sprintf("Select variant (1-%d): ", len(ps.Spec.Variants))
}
for {
fmt.Fprintf(os.Stderr, prompt)
input, err := reader.ReadString('\n')
if err != nil {
return "", fmt.Errorf("failed to read input: %w", err)
}
input = strings.TrimSpace(input)
// If input is empty and there's a default variant, use it
if input == "" && len(ps.Spec.Variants) == 1 {
return defaultVariant, nil
}
choice, err := strconv.Atoi(input)
if err != nil || choice < 1 || choice > len(ps.Spec.Variants) {
fmt.Fprintf(os.Stderr, "Invalid choice. Please enter a number between 1 and %d.\n", len(ps.Spec.Variants))
continue
}
return ps.Spec.Variants[choice-1].Name, nil
}
}
func init() {
rootCmd.AddCommand(addCmd)
addCmd.Flags().StringArrayVarP(&addCmdFlags.files, "file", "f", []string{}, "Read packages from file or directory (can be specified multiple times)")
addCmd.Flags().StringVar(&addCmdFlags.kubeconfig, "kubeconfig", "", "Path to kubeconfig file (defaults to ~/.kube/config or KUBECONFIG env var)")
}

View File

@@ -1,396 +0,0 @@
/*
Copyright 2025 The Cozystack Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"bufio"
"context"
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
_ "k8s.io/client-go/plugin/pkg/client/auth"
)
var delCmdFlags struct {
files []string
kubeconfig string
}
var delCmd = &cobra.Command{
Use: "del [package]...",
Short: "Delete Package resources",
Long: `Delete Package resources.
You can specify packages as arguments or use -f flag to read from files.
Multiple -f flags can be specified, and they can point to files or directories.`,
Args: cobra.ArbitraryArgs,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
// Collect package names from arguments and files
packageNames := make(map[string]bool)
packagesFromFiles := make(map[string]string) // packageName -> filePath
for _, arg := range args {
packageNames[arg] = true
}
// Read packages from files (reuse function from add.go)
for _, filePath := range delCmdFlags.files {
packages, err := readPackagesFromFile(filePath)
if err != nil {
return fmt.Errorf("failed to read packages from %s: %w", filePath, err)
}
for _, pkg := range packages {
packageNames[pkg] = true
if oldPath, ok := packagesFromFiles[pkg]; ok {
fmt.Fprintf(os.Stderr, "warning: package %q is defined in both %s and %s, using the latter\n", pkg, oldPath, filePath)
}
packagesFromFiles[pkg] = filePath
}
}
if len(packageNames) == 0 {
return fmt.Errorf("no packages specified")
}
// Create Kubernetes client config
var config *rest.Config
var err error
if delCmdFlags.kubeconfig != "" {
config, err = clientcmd.BuildConfigFromFlags("", delCmdFlags.kubeconfig)
if err != nil {
return fmt.Errorf("failed to load kubeconfig from %s: %w", delCmdFlags.kubeconfig, err)
}
} else {
config, err = ctrl.GetConfig()
if err != nil {
return fmt.Errorf("failed to get kubeconfig: %w", err)
}
}
scheme := runtime.NewScheme()
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
utilruntime.Must(cozyv1alpha1.AddToScheme(scheme))
k8sClient, err := client.New(config, client.Options{Scheme: scheme})
if err != nil {
return fmt.Errorf("failed to create k8s client: %w", err)
}
// Check which requested packages are installed
var installedPackages cozyv1alpha1.PackageList
if err := k8sClient.List(ctx, &installedPackages); err != nil {
return fmt.Errorf("failed to list Packages: %w", err)
}
installedMap := make(map[string]bool)
for _, pkg := range installedPackages.Items {
installedMap[pkg.Name] = true
}
// Warn about requested packages that are not installed
for pkgName := range packageNames {
if !installedMap[pkgName] {
fmt.Fprintf(os.Stderr, "⚠ Package %s is not installed, skipping\n", pkgName)
}
}
// Find all packages to delete (including dependents)
packagesToDelete, err := findPackagesToDelete(ctx, k8sClient, packageNames)
if err != nil {
return fmt.Errorf("failed to analyze dependencies: %w", err)
}
if len(packagesToDelete) == 0 {
fmt.Fprintf(os.Stderr, "No packages found to delete\n")
return nil
}
// Show packages to be deleted and ask for confirmation
if err := confirmDeletion(packagesToDelete, packageNames); err != nil {
return err
}
// Delete packages in reverse topological order (dependents first, then dependencies)
deleteOrder, err := getDeleteOrder(ctx, k8sClient, packagesToDelete)
if err != nil {
return fmt.Errorf("failed to determine delete order: %w", err)
}
// Delete each package
for _, packageName := range deleteOrder {
pkg := &cozyv1alpha1.Package{}
pkg.Name = packageName
if err := k8sClient.Delete(ctx, pkg); err != nil {
if apierrors.IsNotFound(err) {
fmt.Fprintf(os.Stderr, "⚠ Package %s not found, skipping\n", packageName)
continue
}
return fmt.Errorf("failed to delete Package %s: %w", packageName, err)
}
fmt.Fprintf(os.Stderr, "✓ Deleted Package %s\n", packageName)
}
return nil
},
}
// findPackagesToDelete finds all packages that need to be deleted, including dependents
func findPackagesToDelete(ctx context.Context, k8sClient client.Client, requestedPackages map[string]bool) (map[string]bool, error) {
// Get all installed Packages
var installedPackages cozyv1alpha1.PackageList
if err := k8sClient.List(ctx, &installedPackages); err != nil {
return nil, fmt.Errorf("failed to list Packages: %w", err)
}
installedMap := make(map[string]bool)
for _, pkg := range installedPackages.Items {
installedMap[pkg.Name] = true
}
// Get all PackageSources to build dependency graph
var packageSources cozyv1alpha1.PackageSourceList
if err := k8sClient.List(ctx, &packageSources); err != nil {
return nil, fmt.Errorf("failed to list PackageSources: %w", err)
}
// Build reverse dependency graph (dependents -> dependencies)
// This tells us: for each package, which packages depend on it
reverseDeps := make(map[string][]string)
for _, ps := range packageSources.Items {
// Only consider installed packages
if !installedMap[ps.Name] {
continue
}
for _, variant := range ps.Spec.Variants {
for _, dep := range variant.DependsOn {
// Only consider installed dependencies
if installedMap[dep] {
reverseDeps[dep] = append(reverseDeps[dep], ps.Name)
}
}
}
}
// Find all packages to delete (requested + their dependents)
packagesToDelete := make(map[string]bool)
visited := make(map[string]bool)
var findDependents func(string)
findDependents = func(pkgName string) {
if visited[pkgName] {
return
}
visited[pkgName] = true
// Only add if it's installed
if installedMap[pkgName] {
packagesToDelete[pkgName] = true
}
// Recursively find all dependents
for _, dependent := range reverseDeps[pkgName] {
if installedMap[dependent] {
findDependents(dependent)
}
}
}
// Start from requested packages
for pkgName := range requestedPackages {
if !installedMap[pkgName] {
continue
}
findDependents(pkgName)
}
return packagesToDelete, nil
}
// confirmDeletion shows the list of packages to be deleted and asks for user confirmation
func confirmDeletion(packagesToDelete map[string]bool, requestedPackages map[string]bool) error {
// Separate requested packages from dependents
var requested []string
var dependents []string
for pkg := range packagesToDelete {
if requestedPackages[pkg] {
requested = append(requested, pkg)
} else {
dependents = append(dependents, pkg)
}
}
fmt.Fprintf(os.Stderr, "\nThe following packages will be deleted:\n\n")
if len(requested) > 0 {
fmt.Fprintf(os.Stderr, "Requested packages:\n")
for _, pkg := range requested {
fmt.Fprintf(os.Stderr, " - %s\n", pkg)
}
fmt.Fprintf(os.Stderr, "\n")
}
if len(dependents) > 0 {
fmt.Fprintf(os.Stderr, "Dependent packages (will also be deleted):\n")
for _, pkg := range dependents {
fmt.Fprintf(os.Stderr, " - %s\n", pkg)
}
fmt.Fprintf(os.Stderr, "\n")
}
fmt.Fprintf(os.Stderr, "Total: %d package(s)\n\n", len(packagesToDelete))
fmt.Fprintf(os.Stderr, "Do you want to continue? [y/N]: ")
reader := bufio.NewReader(os.Stdin)
input, err := reader.ReadString('\n')
if err != nil {
return fmt.Errorf("failed to read input: %w", err)
}
input = strings.TrimSpace(strings.ToLower(input))
if input != "y" && input != "yes" {
return fmt.Errorf("deletion cancelled")
}
return nil
}
// getDeleteOrder returns packages in reverse topological order (dependents first, then dependencies)
// This ensures we delete dependents before their dependencies
func getDeleteOrder(ctx context.Context, k8sClient client.Client, packagesToDelete map[string]bool) ([]string, error) {
// Get all PackageSources to build dependency graph
var packageSources cozyv1alpha1.PackageSourceList
if err := k8sClient.List(ctx, &packageSources); err != nil {
return nil, fmt.Errorf("failed to list PackageSources: %w", err)
}
// Build forward dependency graph (package -> dependencies)
dependencyGraph := make(map[string][]string)
for _, ps := range packageSources.Items {
if !packagesToDelete[ps.Name] {
continue
}
deps := make(map[string]bool)
for _, variant := range ps.Spec.Variants {
for _, dep := range variant.DependsOn {
if packagesToDelete[dep] {
deps[dep] = true
}
}
}
var depList []string
for dep := range deps {
depList = append(depList, dep)
}
dependencyGraph[ps.Name] = depList
}
// Build reverse graph for topological sort
reverseGraph := make(map[string][]string)
allNodes := make(map[string]bool)
for node, deps := range dependencyGraph {
allNodes[node] = true
for _, dep := range deps {
allNodes[dep] = true
reverseGraph[dep] = append(reverseGraph[dep], node)
}
}
// Add nodes that have no dependencies
for pkg := range packagesToDelete {
if !allNodes[pkg] {
allNodes[pkg] = true
dependencyGraph[pkg] = []string{}
}
}
// Calculate in-degrees
inDegree := make(map[string]int)
for node := range allNodes {
inDegree[node] = 0
}
for node, deps := range dependencyGraph {
inDegree[node] = len(deps)
}
// Kahn's algorithm - start with nodes that have no dependencies
var queue []string
for node, degree := range inDegree {
if degree == 0 {
queue = append(queue, node)
}
}
var result []string
for len(queue) > 0 {
node := queue[0]
queue = queue[1:]
result = append(result, node)
// Process dependents
for _, dependent := range reverseGraph[node] {
inDegree[dependent]--
if inDegree[dependent] == 0 {
queue = append(queue, dependent)
}
}
}
// Check for cycles: if not all nodes were processed, there's a cycle
if len(result) != len(allNodes) {
// Find unprocessed nodes
processed := make(map[string]bool)
for _, node := range result {
processed[node] = true
}
var unprocessed []string
for node := range allNodes {
if !processed[node] {
unprocessed = append(unprocessed, node)
}
}
return nil, fmt.Errorf("dependency cycle detected: the following packages form a cycle and cannot be deleted: %v", unprocessed)
}
// Reverse the result to get dependents first, then dependencies
for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 {
result[i], result[j] = result[j], result[i]
}
return result, nil
}
func init() {
rootCmd.AddCommand(delCmd)
delCmd.Flags().StringArrayVarP(&delCmdFlags.files, "file", "f", []string{}, "Read packages from file or directory (can be specified multiple times)")
delCmd.Flags().StringVar(&delCmdFlags.kubeconfig, "kubeconfig", "", "Path to kubeconfig file (defaults to ~/.kube/config or KUBECONFIG env var)")
}

View File

@@ -1,564 +0,0 @@
/*
Copyright 2025 The Cozystack Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"context"
"fmt"
"os"
"strings"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
"github.com/emicklei/dot"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
_ "k8s.io/client-go/plugin/pkg/client/auth"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)
var dotCmdFlags struct {
installed bool
components bool
files []string
kubeconfig string
}
var dotCmd = &cobra.Command{
Use: "dot [package]...",
Short: "Generate dependency graph as graphviz DOT format",
Long: `Generate dependency graph as graphviz DOT format.
Pipe the output through the "dot" program (part of graphviz package) to render the graph:
cozypkg dot | dot -Tpng > graph.png
By default, shows dependencies for all PackageSource resources.
Use --installed to show only installed Package resources.
Specify packages as arguments or use -f flag to read from files.`,
Args: cobra.ArbitraryArgs,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
// Collect package names from arguments and files
packageNames := make(map[string]bool)
for _, arg := range args {
packageNames[arg] = true
}
// Read packages from files (reuse function from add.go)
for _, filePath := range dotCmdFlags.files {
packages, err := readPackagesFromFile(filePath)
if err != nil {
return fmt.Errorf("failed to read packages from %s: %w", filePath, err)
}
for _, pkg := range packages {
packageNames[pkg] = true
}
}
// Convert to slice, empty means all packages
var selectedPackages []string
if len(packageNames) > 0 {
for pkg := range packageNames {
selectedPackages = append(selectedPackages, pkg)
}
}
// If multiple packages specified, show graph for all of them
// If single package, use packageName for backward compatibility
var packageName string
if len(selectedPackages) == 1 {
packageName = selectedPackages[0]
} else if len(selectedPackages) > 1 {
// Multiple packages - pass empty string to packageName, use selectedPackages
packageName = ""
}
// packagesOnly is inverse of components flag (if components=false, then packagesOnly=true)
packagesOnly := !dotCmdFlags.components
graph, allNodes, edgeVariants, packageNames, err := buildGraphFromCluster(ctx, dotCmdFlags.kubeconfig, packagesOnly, dotCmdFlags.installed, packageName, selectedPackages)
if err != nil {
return fmt.Errorf("error getting PackageSource dependencies: %w", err)
}
dotGraph := generateDOTGraph(graph, allNodes, packagesOnly, edgeVariants, packageNames)
dotGraph.Write(os.Stdout)
return nil
},
}
func init() {
rootCmd.AddCommand(dotCmd)
dotCmd.Flags().BoolVarP(&dotCmdFlags.installed, "installed", "i", false, "show dependencies only for installed Package resources")
dotCmd.Flags().BoolVar(&dotCmdFlags.components, "components", false, "show component-level dependencies")
dotCmd.Flags().StringArrayVarP(&dotCmdFlags.files, "file", "f", []string{}, "Read packages from file or directory (can be specified multiple times)")
dotCmd.Flags().StringVar(&dotCmdFlags.kubeconfig, "kubeconfig", "", "Path to kubeconfig file (defaults to ~/.kube/config or KUBECONFIG env var)")
}
var (
dependenciesScheme = runtime.NewScheme()
)
func init() {
utilruntime.Must(clientgoscheme.AddToScheme(dependenciesScheme))
utilruntime.Must(cozyv1alpha1.AddToScheme(dependenciesScheme))
}
// buildGraphFromCluster builds a dependency graph from PackageSource resources in the cluster.
// Returns: graph, allNodes, edgeVariants (map[edgeKey]variants), packageNames, error
func buildGraphFromCluster(ctx context.Context, kubeconfig string, packagesOnly bool, installedOnly bool, packageName string, selectedPackages []string) (map[string][]string, map[string]bool, map[string][]string, map[string]bool, error) {
// Create Kubernetes client config
var config *rest.Config
var err error
if kubeconfig != "" {
// Load kubeconfig from explicit path
config, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
if err != nil {
return nil, nil, nil, nil, fmt.Errorf("failed to load kubeconfig from %s: %w", kubeconfig, err)
}
} else {
// Use default kubeconfig loading (from env var or ~/.kube/config)
config, err = ctrl.GetConfig()
if err != nil {
return nil, nil, nil, nil, fmt.Errorf("failed to get kubeconfig: %w", err)
}
}
k8sClient, err := client.New(config, client.Options{Scheme: dependenciesScheme})
if err != nil {
return nil, nil, nil, nil, fmt.Errorf("failed to create k8s client: %w", err)
}
// Get installed Packages if needed
installedPackages := make(map[string]bool)
if installedOnly {
var packageList cozyv1alpha1.PackageList
if err := k8sClient.List(ctx, &packageList); err != nil {
return nil, nil, nil, nil, fmt.Errorf("failed to list Packages: %w", err)
}
for _, pkg := range packageList.Items {
installedPackages[pkg.Name] = true
}
}
// List all PackageSource resources
var packageSourceList cozyv1alpha1.PackageSourceList
if err := k8sClient.List(ctx, &packageSourceList); err != nil {
return nil, nil, nil, nil, fmt.Errorf("failed to list PackageSources: %w", err)
}
// Build map of existing packages and components
packageNames := make(map[string]bool)
allExistingComponents := make(map[string]bool) // "package.component" -> true
for _, ps := range packageSourceList.Items {
if ps.Name != "" {
packageNames[ps.Name] = true
for _, variant := range ps.Spec.Variants {
for _, component := range variant.Components {
if component.Install != nil {
componentFullName := fmt.Sprintf("%s.%s", ps.Name, component.Name)
allExistingComponents[componentFullName] = true
}
}
}
}
}
graph := make(map[string][]string)
allNodes := make(map[string]bool)
edgeVariants := make(map[string][]string) // key: "source->target", value: list of variant names
existingEdges := make(map[string]bool) // key: "source->target" to avoid duplicates
componentHasLocalDeps := make(map[string]bool) // componentName -> has local component dependencies
// Process each PackageSource
for _, ps := range packageSourceList.Items {
psName := ps.Name
if psName == "" {
continue
}
// Filter by package name if specified
if packageName != "" && psName != packageName {
continue
}
// Filter by selected packages if specified
if len(selectedPackages) > 0 {
found := false
for _, selected := range selectedPackages {
if psName == selected {
found = true
break
}
}
if !found {
continue
}
}
// Filter by installed packages if flag is set
if installedOnly && !installedPackages[psName] {
continue
}
allNodes[psName] = true
// Track package dependencies per variant
packageDepVariants := make(map[string]map[string]bool) // dep -> variant -> true
allVariantNames := make(map[string]bool)
for _, v := range ps.Spec.Variants {
allVariantNames[v.Name] = true
}
// Track component dependencies per variant
componentDepVariants := make(map[string]map[string]map[string]bool) // componentName -> dep -> variant -> true
componentVariants := make(map[string]map[string]bool) // componentName -> variant -> true
// Extract dependencies from variants
for _, variant := range ps.Spec.Variants {
// Variant-level dependencies (package-level)
for _, dep := range variant.DependsOn {
// If installedOnly is set, only include dependencies that are installed
if installedOnly && !installedPackages[dep] {
continue
}
// Track which variant this dependency comes from
if packageDepVariants[dep] == nil {
packageDepVariants[dep] = make(map[string]bool)
}
packageDepVariants[dep][variant.Name] = true
edgeKey := fmt.Sprintf("%s->%s", psName, dep)
if !existingEdges[edgeKey] {
graph[psName] = append(graph[psName], dep)
existingEdges[edgeKey] = true
}
// Add to allNodes only if package exists
if packageNames[dep] {
allNodes[dep] = true
}
// If package doesn't exist, don't add to allNodes - it will be shown as missing (red)
}
// Component-level dependencies
if !packagesOnly {
for _, component := range variant.Components {
// Skip components without install section
if component.Install == nil {
continue
}
componentName := fmt.Sprintf("%s.%s", psName, component.Name)
allNodes[componentName] = true
// Track which variants this component appears in
if componentVariants[componentName] == nil {
componentVariants[componentName] = make(map[string]bool)
}
componentVariants[componentName][variant.Name] = true
if component.Install != nil {
if componentDepVariants[componentName] == nil {
componentDepVariants[componentName] = make(map[string]map[string]bool)
}
for _, dep := range component.Install.DependsOn {
// Track which variant this dependency comes from
if componentDepVariants[componentName][dep] == nil {
componentDepVariants[componentName][dep] = make(map[string]bool)
}
componentDepVariants[componentName][dep][variant.Name] = true
// Check if it's a local component dependency or external
if strings.Contains(dep, ".") {
// External component dependency (package.component format)
// Mark that this component has local dependencies (for edge to package logic)
componentHasLocalDeps[componentName] = true
// Check if target component exists
if allExistingComponents[dep] {
// Component exists
edgeKey := fmt.Sprintf("%s->%s", componentName, dep)
if !existingEdges[edgeKey] {
graph[componentName] = append(graph[componentName], dep)
existingEdges[edgeKey] = true
}
allNodes[dep] = true
} else {
// Component doesn't exist - create missing component node
edgeKey := fmt.Sprintf("%s->%s", componentName, dep)
if !existingEdges[edgeKey] {
graph[componentName] = append(graph[componentName], dep)
existingEdges[edgeKey] = true
}
// Don't add to allNodes - will be shown as missing (red)
// Add edge from missing component to its package
parts := strings.SplitN(dep, ".", 2)
if len(parts) == 2 {
depPackageName := parts[0]
missingEdgeKey := fmt.Sprintf("%s->%s", dep, depPackageName)
if !existingEdges[missingEdgeKey] {
graph[dep] = append(graph[dep], depPackageName)
existingEdges[missingEdgeKey] = true
}
// Add package to allNodes only if it exists
if packageNames[depPackageName] {
allNodes[depPackageName] = true
}
// If package doesn't exist, it will be shown as missing (red)
}
}
} else {
// Local component dependency (same package)
// Mark that this component has local dependencies
componentHasLocalDeps[componentName] = true
localDep := fmt.Sprintf("%s.%s", psName, dep)
// Check if target component exists
if allExistingComponents[localDep] {
// Component exists
edgeKey := fmt.Sprintf("%s->%s", componentName, localDep)
if !existingEdges[edgeKey] {
graph[componentName] = append(graph[componentName], localDep)
existingEdges[edgeKey] = true
}
allNodes[localDep] = true
} else {
// Component doesn't exist - create missing component node
edgeKey := fmt.Sprintf("%s->%s", componentName, localDep)
if !existingEdges[edgeKey] {
graph[componentName] = append(graph[componentName], localDep)
existingEdges[edgeKey] = true
}
// Don't add to allNodes - will be shown as missing (red)
// Add edge from missing component to its package
missingEdgeKey := fmt.Sprintf("%s->%s", localDep, psName)
if !existingEdges[missingEdgeKey] {
graph[localDep] = append(graph[localDep], psName)
existingEdges[missingEdgeKey] = true
}
}
}
}
}
}
}
}
// Store variant information for package dependencies that are not in all variants
for dep, variants := range packageDepVariants {
if len(variants) < len(allVariantNames) {
var variantList []string
for v := range variants {
variantList = append(variantList, v)
}
edgeKey := fmt.Sprintf("%s->%s", psName, dep)
edgeVariants[edgeKey] = variantList
}
}
// Add component->package edges for components without local dependencies
if !packagesOnly {
for componentName := range componentVariants {
// Only add edge to package if component has no local component dependencies
if !componentHasLocalDeps[componentName] {
edgeKey := fmt.Sprintf("%s->%s", componentName, psName)
if !existingEdges[edgeKey] {
graph[componentName] = append(graph[componentName], psName)
existingEdges[edgeKey] = true
}
// If component is not in all variants, store variant info for component->package edge
componentAllVariants := componentVariants[componentName]
if len(componentAllVariants) < len(allVariantNames) {
var variantList []string
for v := range componentAllVariants {
variantList = append(variantList, v)
}
edgeVariants[edgeKey] = variantList
}
}
}
}
// Store variant information for component dependencies that are not in all variants
for componentName, deps := range componentDepVariants {
componentAllVariants := componentVariants[componentName]
for dep, variants := range deps {
if len(variants) < len(componentAllVariants) {
var variantList []string
for v := range variants {
variantList = append(variantList, v)
}
// Determine the actual target name
var targetName string
if strings.Contains(dep, ".") {
targetName = dep
} else {
targetName = fmt.Sprintf("%s.%s", psName, dep)
}
edgeKey := fmt.Sprintf("%s->%s", componentName, targetName)
edgeVariants[edgeKey] = variantList
}
}
}
}
return graph, allNodes, edgeVariants, packageNames, nil
}
// generateDOTGraph generates a DOT graph from the dependency graph.
func generateDOTGraph(graph map[string][]string, allNodes map[string]bool, packagesOnly bool, edgeVariants map[string][]string, packageNames map[string]bool) *dot.Graph {
g := dot.NewGraph(dot.Directed)
g.Attr("rankdir", "RL")
g.Attr("nodesep", "0.5")
g.Attr("ranksep", "1.0")
// Helper function to check if a node is a package
// A node is a package if:
// 1. It's directly in packageNames
// 2. It doesn't contain a dot (simple package name)
// 3. It contains a dot but the part before the first dot is a package name
isPackage := func(nodeName string) bool {
if packageNames[nodeName] {
return true
}
if !strings.Contains(nodeName, ".") {
return true
}
// If it contains a dot, check if the part before the first dot is a package
parts := strings.SplitN(nodeName, ".", 2)
if len(parts) > 0 {
return packageNames[parts[0]]
}
return false
}
// Add nodes
for node := range allNodes {
if packagesOnly && !isPackage(node) {
// Skip component nodes when packages-only is enabled
continue
}
n := g.Node(node)
// Style nodes based on type
if isPackage(node) {
// Package node
n.Attr("shape", "box")
n.Attr("style", "rounded,filled")
n.Attr("fillcolor", "lightblue")
n.Attr("label", node)
} else {
// Component node
n.Attr("shape", "box")
n.Attr("style", "rounded,filled")
n.Attr("fillcolor", "lightyellow")
// Extract component name (part after last dot)
parts := strings.Split(node, ".")
if len(parts) > 0 {
n.Attr("label", parts[len(parts)-1])
} else {
n.Attr("label", node)
}
}
}
// Add edges
for source, targets := range graph {
if packagesOnly && !isPackage(source) {
// Skip component edges when packages-only is enabled
continue
}
for _, target := range targets {
if packagesOnly && !isPackage(target) {
// Skip component edges when packages-only is enabled
continue
}
// Check if target exists
targetExists := allNodes[target]
// Determine edge type for coloring
sourceIsPackage := isPackage(source)
targetIsPackage := isPackage(target)
// Add edge
edge := g.Edge(g.Node(source), g.Node(target))
// Set edge color based on type (if target exists)
if targetExists {
if sourceIsPackage && targetIsPackage {
// Package -> Package: black (default)
edge.Attr("color", "black")
} else {
// Component -> Package or Component -> Component: green
edge.Attr("color", "green")
}
}
// If target doesn't exist, mark it as missing (red color)
if !targetExists {
edge.Attr("color", "red")
edge.Attr("style", "dashed")
// Also add the missing node with red color
missingNode := g.Node(target)
missingNode.Attr("shape", "box")
missingNode.Attr("style", "rounded,filled,dashed")
missingNode.Attr("fillcolor", "lightcoral")
// Determine label based on node type
if isPackage(target) {
// Package node
missingNode.Attr("label", target)
} else {
// Component node - extract component name
parts := strings.Split(target, ".")
if len(parts) > 0 {
missingNode.Attr("label", parts[len(parts)-1])
} else {
missingNode.Attr("label", target)
}
}
} else {
// Check if this edge has variant information (dependency not in all variants)
edgeKey := fmt.Sprintf("%s->%s", source, target)
if variants, hasVariants := edgeVariants[edgeKey]; hasVariants {
// Add label with variant names
edge.Attr("label", strings.Join(variants, ","))
}
}
}
}
return g
}

View File

@@ -1,220 +0,0 @@
/*
Copyright 2025 The Cozystack Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"context"
"fmt"
"os"
"strings"
"text/tabwriter"
"github.com/spf13/cobra"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
_ "k8s.io/client-go/plugin/pkg/client/auth"
)
var listCmdFlags struct {
installed bool
components bool
kubeconfig string
}
var listCmd = &cobra.Command{
Use: "list",
Short: "List PackageSource or Package resources",
Long: `List PackageSource or Package resources in table format.
By default, lists PackageSource resources. Use --installed flag to list installed Package resources.
Use --components flag to show components on separate lines.`,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
// Create Kubernetes client config
var config *rest.Config
var err error
if listCmdFlags.kubeconfig != "" {
config, err = clientcmd.BuildConfigFromFlags("", listCmdFlags.kubeconfig)
if err != nil {
return fmt.Errorf("failed to load kubeconfig from %s: %w", listCmdFlags.kubeconfig, err)
}
} else {
config, err = ctrl.GetConfig()
if err != nil {
return fmt.Errorf("failed to get kubeconfig: %w", err)
}
}
scheme := runtime.NewScheme()
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
utilruntime.Must(cozyv1alpha1.AddToScheme(scheme))
k8sClient, err := client.New(config, client.Options{Scheme: scheme})
if err != nil {
return fmt.Errorf("failed to create k8s client: %w", err)
}
if listCmdFlags.installed {
return listPackages(ctx, k8sClient, listCmdFlags.components)
}
return listPackageSources(ctx, k8sClient, listCmdFlags.components)
},
}
func listPackageSources(ctx context.Context, k8sClient client.Client, showComponents bool) error {
var psList cozyv1alpha1.PackageSourceList
if err := k8sClient.List(ctx, &psList); err != nil {
return fmt.Errorf("failed to list PackageSources: %w", err)
}
// Use tabwriter for better column alignment
w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
defer w.Flush()
// Print header
fmt.Fprintln(w, "NAME\tVARIANTS\tREADY\tSTATUS")
// Print rows
for _, ps := range psList.Items {
// Get variants
var variants []string
for _, variant := range ps.Spec.Variants {
variants = append(variants, variant.Name)
}
variantsStr := strings.Join(variants, ",")
if len(variantsStr) > 28 {
variantsStr = variantsStr[:25] + "..."
}
// Get Ready condition
ready := "Unknown"
status := ""
for _, condition := range ps.Status.Conditions {
if condition.Type == "Ready" {
ready = string(condition.Status)
status = condition.Message
if len(status) > 48 {
status = status[:45] + "..."
}
break
}
}
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", ps.Name, variantsStr, ready, status)
// Show components if requested
if showComponents {
for _, variant := range ps.Spec.Variants {
for _, component := range variant.Components {
fmt.Fprintf(w, " %s\t%s\t\t\n",
fmt.Sprintf("%s.%s", ps.Name, component.Name),
variant.Name)
}
}
}
}
return nil
}
func listPackages(ctx context.Context, k8sClient client.Client, showComponents bool) error {
var pkgList cozyv1alpha1.PackageList
if err := k8sClient.List(ctx, &pkgList); err != nil {
return fmt.Errorf("failed to list Packages: %w", err)
}
// Fetch all PackageSource resources once if components are requested
var psMap map[string]*cozyv1alpha1.PackageSource
if showComponents {
var psList cozyv1alpha1.PackageSourceList
if err := k8sClient.List(ctx, &psList); err != nil {
return fmt.Errorf("failed to list PackageSources: %w", err)
}
psMap = make(map[string]*cozyv1alpha1.PackageSource)
for i := range psList.Items {
psMap[psList.Items[i].Name] = &psList.Items[i]
}
}
// Use tabwriter for better column alignment
w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
defer w.Flush()
// Print header
fmt.Fprintln(w, "NAME\tVARIANT\tREADY\tSTATUS")
// Print rows
for _, pkg := range pkgList.Items {
variant := pkg.Spec.Variant
if variant == "" {
variant = "default"
}
// Get Ready condition
ready := "Unknown"
status := ""
for _, condition := range pkg.Status.Conditions {
if condition.Type == "Ready" {
ready = string(condition.Status)
status = condition.Message
if len(status) > 48 {
status = status[:45] + "..."
}
break
}
}
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", pkg.Name, variant, ready, status)
// Show components if requested
if showComponents {
// Look up PackageSource from map instead of making API call
if ps, exists := psMap[pkg.Name]; exists {
// Find the variant
for _, v := range ps.Spec.Variants {
if v.Name == variant {
for _, component := range v.Components {
fmt.Fprintf(w, " %s\t%s\t\t\n",
fmt.Sprintf("%s.%s", pkg.Name, component.Name),
variant)
}
break
}
}
}
}
}
return nil
}
func init() {
rootCmd.AddCommand(listCmd)
listCmd.Flags().BoolVarP(&listCmdFlags.installed, "installed", "i", false, "list installed Package resources instead of PackageSource resources")
listCmd.Flags().BoolVar(&listCmdFlags.components, "components", false, "show components on separate lines")
listCmd.Flags().StringVar(&listCmdFlags.kubeconfig, "kubeconfig", "", "Path to kubeconfig file (defaults to ~/.kube/config or KUBECONFIG env var)")
}

View File

@@ -1,52 +0,0 @@
/*
Copyright 2025 The Cozystack Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
// Version is set at build time via -ldflags.
var Version = "dev"
// rootCmd represents the base command when called without any subcommands.
var rootCmd = &cobra.Command{
Use: "cozypkg",
Short: "A CLI for managing Cozystack packages",
Long: ``,
SilenceErrors: true,
SilenceUsage: true,
DisableAutoGenTag: true,
}
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() error {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err.Error())
return err
}
return nil
}
func init() {
rootCmd.Version = Version
}

View File

@@ -1,30 +0,0 @@
/*
Copyright 2025 The Cozystack Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"os"
"github.com/cozystack/cozystack/cmd/cozypkg/cmd"
)
func main() {
if err := cmd.Execute(); err != nil {
os.Exit(1)
}
}

View File

@@ -20,7 +20,6 @@ import (
"crypto/tls"
"flag"
"os"
"time"
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
// to ensure that exec-entrypoint and run can make use of them.
@@ -39,7 +38,6 @@ import (
cozystackiov1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
"github.com/cozystack/cozystack/internal/controller"
"github.com/cozystack/cozystack/internal/controller/dashboard"
"github.com/cozystack/cozystack/internal/telemetry"
helmv2 "github.com/fluxcd/helm-controller/api/v2"
// +kubebuilder:scaffold:imports
@@ -65,9 +63,7 @@ func main() {
var probeAddr string
var secureMetrics bool
var enableHTTP2 bool
var disableTelemetry bool
var telemetryEndpoint string
var telemetryInterval string
var reconcileDeployment bool
var tlsOpts []func(*tls.Config)
flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+
"Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.")
@@ -79,32 +75,14 @@ func main() {
"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")
flag.BoolVar(&disableTelemetry, "disable-telemetry", false,
"Disable telemetry collection")
flag.StringVar(&telemetryEndpoint, "telemetry-endpoint", "https://telemetry.cozystack.io",
"Endpoint for sending telemetry data")
flag.StringVar(&telemetryInterval, "telemetry-interval", "15m",
"Interval between telemetry data collection (e.g. 15m, 1h)")
flag.BoolVar(&reconcileDeployment, "reconcile-deployment", false,
"If set, the Cozystack API server is assumed to run as a Deployment, else as a DaemonSet.")
opts := zap.Options{
Development: false,
}
opts.BindFlags(flag.CommandLine)
flag.Parse()
// Parse telemetry interval
interval, err := time.ParseDuration(telemetryInterval)
if err != nil {
setupLog.Error(err, "invalid telemetry interval")
os.Exit(1)
}
// Configure telemetry
telemetryConfig := telemetry.Config{
Disabled: disableTelemetry,
Endpoint: telemetryEndpoint,
Interval: interval,
}
ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
// if the enable-http2 flag is false (the default), http/2 should be disabled
@@ -193,19 +171,20 @@ func main() {
os.Exit(1)
}
if err = (&controller.ApplicationDefinitionReconciler{
if err = (&controller.NamespaceHelmReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "ApplicationDefinitionReconciler")
setupLog.Error(err, "unable to create controller", "controller", "NamespaceHelmReconciler")
os.Exit(1)
}
if err = (&controller.ApplicationDefinitionHelmReconciler{
if err = (&controller.ApplicationDefinitionReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
CozystackAPIKind: "Deployment",
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "ApplicationDefinitionHelmReconciler")
setupLog.Error(err, "unable to create controller", "controller", "ApplicationDefinitionReconciler")
os.Exit(1)
}
@@ -229,19 +208,6 @@ func main() {
os.Exit(1)
}
// Initialize telemetry collector
collector, err := telemetry.NewCollector(mgr.GetClient(), &telemetryConfig, mgr.GetConfig())
if err != nil {
setupLog.V(1).Error(err, "unable to create telemetry collector, telemetry will be disabled")
}
if collector != nil {
if err := mgr.Add(collector); err != nil {
setupLog.Error(err, "unable to set up telemetry collector")
setupLog.V(1).Error(err, "unable to set up telemetry collector, continuing without telemetry")
}
}
setupLog.Info("starting manager")
ctx := ctrl.SetupSignalHandler()
dashboardManager.InitializeStaticResources(ctx)

View File

@@ -18,6 +18,7 @@ package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"os"
@@ -32,16 +33,12 @@ import (
helmv2 "github.com/fluxcd/helm-controller/api/v2"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
sourcewatcherv1beta1 "github.com/fluxcd/source-watcher/api/v2/v1beta1"
corev1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/cache"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log"
@@ -49,8 +46,6 @@ import (
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"github.com/cozystack/cozystack/internal/cozyvaluesreplicator"
"github.com/cozystack/cozystack/internal/crdinstall"
"github.com/cozystack/cozystack/internal/fluxinstall"
"github.com/cozystack/cozystack/internal/operator"
"github.com/cozystack/cozystack/internal/telemetry"
@@ -62,6 +57,18 @@ var (
setupLog = ctrl.Log.WithName("setup")
)
// stringSliceFlag is a custom flag type that allows multiple values
type stringSliceFlag []string
func (f *stringSliceFlag) String() string {
return strings.Join(*f, ",")
}
func (f *stringSliceFlag) Set(value string) error {
*f = append(*f, value)
return nil
}
func init() {
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
utilruntime.Must(apiextensionsv1.AddToScheme(scheme))
@@ -78,17 +85,12 @@ func main() {
var probeAddr string
var secureMetrics bool
var enableHTTP2 bool
var installCRDs bool
var installFlux bool
var disableTelemetry bool
var telemetryEndpoint string
var telemetryInterval string
var cozyValuesSecretName string
var cozyValuesSecretNamespace string
var cozyValuesNamespaceSelector string
var platformSourceURL string
var platformSourceName string
var platformSourceRef string
var cozystackVersion string
var installFluxResources stringSliceFlag
flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
@@ -99,20 +101,16 @@ func main() {
"If set the metrics endpoint is served securely")
flag.BoolVar(&enableHTTP2, "enable-http2", false,
"If set, HTTP/2 will be enabled for the metrics and webhook servers")
flag.BoolVar(&installCRDs, "install-crds", false, "Install Cozystack CRDs before starting reconcile loop")
flag.BoolVar(&installFlux, "install-flux", false, "Install Flux components before starting reconcile loop")
flag.Var(&installFluxResources, "install-flux-resource", "Install Flux resource (JSON format). Can be specified multiple times. Applied after Flux installation.")
flag.BoolVar(&disableTelemetry, "disable-telemetry", false,
"Disable telemetry collection")
flag.StringVar(&telemetryEndpoint, "telemetry-endpoint", "https://telemetry.cozystack.io",
"Endpoint for sending telemetry data")
flag.StringVar(&telemetryInterval, "telemetry-interval", "15m",
"Interval between telemetry data collection (e.g. 15m, 1h)")
flag.StringVar(&platformSourceURL, "platform-source-url", "", "Platform source URL (oci:// or https://). If specified, generates OCIRepository or GitRepository resource.")
flag.StringVar(&platformSourceName, "platform-source-name", "cozystack-platform", "Name for the generated platform source resource and PackageSource")
flag.StringVar(&platformSourceRef, "platform-source-ref", "", "Reference specification as key=value pairs (e.g., 'branch=main' or 'digest=sha256:...,tag=v1.0'). For OCI: digest, semver, semverFilter, tag. For Git: branch, tag, semver, name, commit.")
flag.StringVar(&cozyValuesSecretName, "cozy-values-secret-name", "cozystack-values", "The name of the secret containing cluster-wide configuration values.")
flag.StringVar(&cozyValuesSecretNamespace, "cozy-values-secret-namespace", "cozy-system", "The namespace of the secret containing cluster-wide configuration values.")
flag.StringVar(&cozyValuesNamespaceSelector, "cozy-values-namespace-selector", "cozystack.io/system=true", "The label selector for namespaces where the cluster-wide configuration values must be replicated.")
flag.StringVar(&cozystackVersion, "cozystack-version", "unknown",
"Version of Cozystack")
opts := zap.Options{
Development: true,
@@ -120,39 +118,29 @@ func main() {
opts.BindFlags(flag.CommandLine)
flag.Parse()
// Parse telemetry interval
interval, err := time.ParseDuration(telemetryInterval)
if err != nil {
setupLog.Error(err, "invalid telemetry interval")
os.Exit(1)
}
// Configure telemetry
telemetryConfig := telemetry.Config{
Disabled: disableTelemetry,
Endpoint: telemetryEndpoint,
Interval: interval,
CozystackVersion: cozystackVersion,
}
ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
config := ctrl.GetConfigOrDie()
// Create a direct client (without cache) for pre-start operations
directClient, err := client.New(config, client.Options{Scheme: scheme})
if err != nil {
setupLog.Error(err, "unable to create direct client")
os.Exit(1)
}
targetNSSelector, err := labels.Parse(cozyValuesNamespaceSelector)
if err != nil {
setupLog.Error(err, "could not parse namespace label selector")
os.Exit(1)
}
// Initialize the controller manager
// Start the controller manager
setupLog.Info("Starting controller manager")
mgr, err := ctrl.NewManager(config, ctrl.Options{
Scheme: scheme,
Cache: cache.Options{
ByObject: map[client.Object]cache.ByObject{
// Cache only Secrets named <secretName> (in any namespace)
&corev1.Secret{}: {
Field: fields.OneTermEqualSelector("metadata.name", cozyValuesSecretName),
},
// Cache only Namespaces that match a label selector
&corev1.Namespace{}: {
Label: targetNSSelector,
},
},
},
Metrics: metricsserver.Options{
BindAddress: metricsAddr,
SecureServing: secureMetrics,
@@ -162,7 +150,7 @@ func main() {
}),
HealthProbeBindAddress: probeAddr,
LeaderElection: enableLeaderElection,
LeaderElectionID: "cozystack-operator.cozystack.io",
LeaderElectionID: "platform-operator.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, setting this significantly speeds up voluntary
@@ -179,101 +167,51 @@ func main() {
os.Exit(1)
}
// Set up signal handler early so install phases respect SIGTERM
mgrCtx := ctrl.SetupSignalHandler()
// Install Cozystack CRDs before starting reconcile loop
if installCRDs {
setupLog.Info("Installing Cozystack CRDs before starting reconcile loop")
installCtx, installCancel := context.WithTimeout(mgrCtx, 2*time.Minute)
defer installCancel()
if err := crdinstall.Install(installCtx, directClient, crdinstall.WriteEmbeddedManifests); err != nil {
setupLog.Error(err, "failed to install CRDs")
os.Exit(1)
}
setupLog.Info("CRD installation completed successfully")
}
// Install Flux before starting reconcile loop
if installFlux {
setupLog.Info("Installing Flux components before starting reconcile loop")
installCtx, installCancel := context.WithTimeout(mgrCtx, 5*time.Minute)
installCtx, installCancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer installCancel()
// Use direct client for pre-start operations (cache is not ready yet)
if err := fluxinstall.Install(installCtx, directClient, fluxinstall.WriteEmbeddedManifests); err != nil {
setupLog.Error(err, "failed to install Flux")
os.Exit(1)
}
setupLog.Info("Flux installation completed successfully")
}
// Generate and install platform source resource if specified
if platformSourceURL != "" {
setupLog.Info("Generating platform source resource", "url", platformSourceURL, "name", platformSourceName, "ref", platformSourceRef)
installCtx, installCancel := context.WithTimeout(mgrCtx, 2*time.Minute)
defer installCancel()
// Use direct client for pre-start operations (cache is not ready yet)
if err := installPlatformSourceResource(installCtx, directClient, platformSourceURL, platformSourceName, platformSourceRef); err != nil {
setupLog.Error(err, "failed to install platform source resource")
os.Exit(1)
// The namespace will be automatically extracted from the embedded manifests
if err := fluxinstall.Install(installCtx, mgr.GetClient(), fluxinstall.WriteEmbeddedManifests); err != nil {
setupLog.Error(err, "failed to install Flux, continuing anyway")
// Don't exit - allow operator to start even if Flux install fails
// This allows the operator to work in environments where Flux is already installed
} else {
setupLog.Info("Platform source resource installation completed successfully")
setupLog.Info("Flux installation completed successfully")
}
}
// Create platform PackageSource when CRDs are managed by the operator and
// a platform source URL is configured. Without a URL there is no Flux source
// resource to reference, so creating a PackageSource would leave a dangling SourceRef.
if installCRDs && platformSourceURL != "" {
sourceRefKind := "OCIRepository"
sourceType, _, err := parsePlatformSourceURL(platformSourceURL)
if err != nil {
setupLog.Error(err, "failed to parse platform source URL for PackageSource")
os.Exit(1)
// Install Flux resources after Flux installation
if len(installFluxResources) > 0 {
setupLog.Info("Installing Flux resources", "count", len(installFluxResources))
installCtx, installCancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer installCancel()
if err := installFluxResourcesFunc(installCtx, mgr.GetClient(), installFluxResources); err != nil {
setupLog.Error(err, "failed to install Flux resources, continuing anyway")
// Don't exit - allow operator to start even if resource installation fails
} else {
setupLog.Info("Flux resources installation completed successfully")
}
if sourceType == "git" {
sourceRefKind = "GitRepository"
}
setupLog.Info("Creating platform PackageSource", "platformSourceName", platformSourceName)
psCtx, psCancel := context.WithTimeout(mgrCtx, 2*time.Minute)
defer psCancel()
if err := installPlatformPackageSource(psCtx, directClient, platformSourceName, sourceRefKind); err != nil {
setupLog.Error(err, "failed to create platform PackageSource")
os.Exit(1)
}
setupLog.Info("Platform PackageSource creation completed successfully")
}
// Setup PackageSource reconciler
if err := (&operator.PackageSourceReconciler{
bundleReconciler := &operator.BundleReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "PackageSource")
}
if err = bundleReconciler.SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Bundle")
os.Exit(1)
}
// Setup Package reconciler
if err := (&operator.PackageReconciler{
platformReconciler := &operator.PlatformReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Package")
os.Exit(1)
}
// Setup CozyValuesReplicator reconciler
if err := (&cozyvaluesreplicator.SecretReplicatorReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
SourceNamespace: cozyValuesSecretNamespace,
SecretName: cozyValuesSecretName,
TargetNamespaceSelector: targetNSSelector,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "CozyValuesReplicator")
if err = platformReconciler.SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Platform")
os.Exit(1)
}
@@ -288,366 +226,87 @@ func main() {
os.Exit(1)
}
// Parse telemetry interval
interval, err := time.ParseDuration(telemetryInterval)
if err != nil {
setupLog.Error(err, "invalid telemetry interval")
os.Exit(1)
}
// Configure telemetry
telemetryConfig := telemetry.Config{
Disabled: disableTelemetry,
Endpoint: telemetryEndpoint,
Interval: interval,
}
// Initialize telemetry collector
// Use APIReader (non-cached) because the manager's cache is filtered
// and doesn't include resources needed for telemetry (e.g., kube-system namespace, nodes, etc.)
collector, err := telemetry.NewOperatorCollector(mgr.GetAPIReader(), &telemetryConfig, config)
collector, err := telemetry.NewCollector(mgr.GetClient(), &telemetryConfig, mgr.GetConfig())
if err != nil {
setupLog.V(1).Info("unable to create telemetry collector, telemetry will be disabled", "error", err)
setupLog.V(1).Error(err, "unable to create telemetry collector, telemetry will be disabled")
}
if collector != nil {
if err := mgr.Add(collector); err != nil {
setupLog.V(1).Info("unable to set up telemetry collector, continuing without telemetry", "error", err)
setupLog.Error(err, "unable to set up telemetry collector")
setupLog.V(1).Error(err, "unable to set up telemetry collector, continuing without telemetry")
}
}
setupLog.Info("Starting controller manager")
mgrCtx := ctrl.SetupSignalHandler()
if err := mgr.Start(mgrCtx); err != nil {
setupLog.Error(err, "problem running manager")
os.Exit(1)
}
}
// installPlatformSourceResource generates and installs a Flux source resource (OCIRepository or GitRepository)
// based on the platform source URL
func installPlatformSourceResource(ctx context.Context, k8sClient client.Client, sourceURL, resourceName, refSpec string) error {
// installFluxResourcesFunc installs Flux resources from JSON strings
func installFluxResourcesFunc(ctx context.Context, k8sClient client.Client, resources []string) error {
logger := log.FromContext(ctx)
// Parse the source URL to determine type
sourceType, repoURL, err := parsePlatformSourceURL(sourceURL)
if err != nil {
return fmt.Errorf("failed to parse platform source URL: %w", err)
}
for i, resourceJSON := range resources {
logger.Info("Installing Flux resource", "index", i+1, "total", len(resources))
// Parse reference specification
refMap, err := parseRefSpec(refSpec)
if err != nil {
return fmt.Errorf("failed to parse reference specification: %w", err)
}
var obj client.Object
switch sourceType {
case "oci":
obj, err = generateOCIRepository(resourceName, repoURL, refMap)
if err != nil {
return fmt.Errorf("failed to generate OCIRepository: %w", err)
// Parse JSON into unstructured object
var obj unstructured.Unstructured
if err := json.Unmarshal([]byte(resourceJSON), &obj.Object); err != nil {
return fmt.Errorf("failed to parse resource JSON at index %d: %w", i, err)
}
case "git":
obj, err = generateGitRepository(resourceName, repoURL, refMap)
if err != nil {
return fmt.Errorf("failed to generate GitRepository: %w", err)
// Validate that it has required fields
if obj.GetAPIVersion() == "" {
return fmt.Errorf("resource at index %d missing apiVersion", i)
}
if obj.GetKind() == "" {
return fmt.Errorf("resource at index %d missing kind", i)
}
if obj.GetName() == "" {
return fmt.Errorf("resource at index %d missing metadata.name", i)
}
default:
return fmt.Errorf("unsupported source type: %s (expected oci:// or https://)", sourceType)
}
// Apply the resource (create or update)
logger.Info("Applying platform source resource",
"apiVersion", obj.GetObjectKind().GroupVersionKind().GroupVersion().String(),
"kind", obj.GetObjectKind().GroupVersionKind().Kind,
"name", obj.GetName(),
"namespace", obj.GetNamespace(),
)
// Apply the resource (create or update)
logger.Info("Applying Flux resource",
"apiVersion", obj.GetAPIVersion(),
"kind", obj.GetKind(),
"name", obj.GetName(),
"namespace", obj.GetNamespace(),
)
existing := obj.DeepCopyObject().(client.Object)
key := client.ObjectKeyFromObject(obj)
// Use server-side apply or create/update
existing := &unstructured.Unstructured{}
existing.SetGroupVersionKind(obj.GroupVersionKind())
key := client.ObjectKey{
Name: obj.GetName(),
Namespace: obj.GetNamespace(),
}
err = k8sClient.Get(ctx, key, existing)
if err != nil {
if client.IgnoreNotFound(err) == nil {
// Resource doesn't exist, create it
if err := k8sClient.Create(ctx, obj); err != nil {
return fmt.Errorf("failed to create resource %s/%s: %w", obj.GetObjectKind().GroupVersionKind().Kind, obj.GetName(), err)
err := k8sClient.Get(ctx, key, existing)
if err != nil {
if client.IgnoreNotFound(err) == nil {
// Resource doesn't exist, create it
if err := k8sClient.Create(ctx, &obj); err != nil {
return fmt.Errorf("failed to create resource %s/%s: %w", obj.GetKind(), obj.GetName(), err)
}
logger.Info("Created Flux resource", "kind", obj.GetKind(), "name", obj.GetName())
} else {
return fmt.Errorf("failed to check if resource exists: %w", err)
}
logger.Info("Created platform source resource", "kind", obj.GetObjectKind().GroupVersionKind().Kind, "name", obj.GetName())
} else {
return fmt.Errorf("failed to check if resource exists: %w", err)
}
} else {
// Resource exists, update it
obj.SetResourceVersion(existing.GetResourceVersion())
if err := k8sClient.Update(ctx, obj); err != nil {
return fmt.Errorf("failed to update resource %s/%s: %w", obj.GetObjectKind().GroupVersionKind().Kind, obj.GetName(), err)
}
logger.Info("Updated platform source resource", "kind", obj.GetObjectKind().GroupVersionKind().Kind, "name", obj.GetName())
}
return nil
}
// parsePlatformSourceURL parses the source URL and returns the source type and repository URL.
// Supports formats:
// - oci://registry.example.com/repo
// - https://github.com/user/repo
// - http://github.com/user/repo
// - ssh://git@github.com/user/repo
func parsePlatformSourceURL(sourceURL string) (sourceType, repoURL string, err error) {
sourceURL = strings.TrimSpace(sourceURL)
if strings.HasPrefix(sourceURL, "oci://") {
return "oci", sourceURL, nil
}
if strings.HasPrefix(sourceURL, "https://") || strings.HasPrefix(sourceURL, "http://") || strings.HasPrefix(sourceURL, "ssh://") {
return "git", sourceURL, nil
}
return "", "", fmt.Errorf("unsupported source URL scheme (expected oci://, https://, http://, or ssh://): %s", sourceURL)
}
// parseRefSpec parses a reference specification string in the format "key1=value1,key2=value2".
// Returns a map of key-value pairs.
func parseRefSpec(refSpec string) (map[string]string, error) {
result := make(map[string]string)
refSpec = strings.TrimSpace(refSpec)
if refSpec == "" {
return result, nil
}
pairs := strings.Split(refSpec, ",")
for _, pair := range pairs {
pair = strings.TrimSpace(pair)
if pair == "" {
continue
}
// Split on first '=' only to allow '=' in values (e.g., digest=sha256:...)
idx := strings.Index(pair, "=")
if idx == -1 {
return nil, fmt.Errorf("invalid reference specification format: %q (expected key=value)", pair)
}
key := strings.TrimSpace(pair[:idx])
value := strings.TrimSpace(pair[idx+1:])
if key == "" {
return nil, fmt.Errorf("empty key in reference specification: %q", pair)
}
if value == "" {
return nil, fmt.Errorf("empty value for key %q in reference specification", key)
}
result[key] = value
}
return result, nil
}
// Valid reference keys for OCI repositories
var validOCIRefKeys = map[string]bool{
"digest": true,
"semver": true,
"semverFilter": true,
"tag": true,
}
// Valid reference keys for Git repositories
var validGitRefKeys = map[string]bool{
"branch": true,
"tag": true,
"semver": true,
"name": true,
"commit": true,
}
// validateOCIRef validates reference keys for OCI repositories
func validateOCIRef(refMap map[string]string) error {
for key := range refMap {
if !validOCIRefKeys[key] {
return fmt.Errorf("invalid OCI reference key %q (valid keys: digest, semver, semverFilter, tag)", key)
}
}
// Validate digest format if provided
if digest, ok := refMap["digest"]; ok {
if !strings.HasPrefix(digest, "sha256:") {
return fmt.Errorf("digest must be in format 'sha256:<hash>', got: %s", digest)
}
}
return nil
}
// validateGitRef validates reference keys for Git repositories
func validateGitRef(refMap map[string]string) error {
for key := range refMap {
if !validGitRefKeys[key] {
return fmt.Errorf("invalid Git reference key %q (valid keys: branch, tag, semver, name, commit)", key)
}
}
// Validate commit format if provided (should be a hex string)
if commit, ok := refMap["commit"]; ok {
if len(commit) < 7 {
return fmt.Errorf("commit SHA should be at least 7 characters, got: %s", commit)
}
for _, c := range commit {
if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) {
return fmt.Errorf("commit SHA should be a hexadecimal string, got: %s", commit)
// Resource exists, update it
obj.SetResourceVersion(existing.GetResourceVersion())
if err := k8sClient.Update(ctx, &obj); err != nil {
return fmt.Errorf("failed to update resource %s/%s: %w", obj.GetKind(), obj.GetName(), err)
}
logger.Info("Updated Flux resource", "kind", obj.GetKind(), "name", obj.GetName())
}
}
return nil
}
// generateOCIRepository creates an OCIRepository resource
func generateOCIRepository(name, repoURL string, refMap map[string]string) (*sourcev1.OCIRepository, error) {
if err := validateOCIRef(refMap); err != nil {
return nil, err
}
obj := &sourcev1.OCIRepository{
TypeMeta: metav1.TypeMeta{
APIVersion: sourcev1.GroupVersion.String(),
Kind: sourcev1.OCIRepositoryKind,
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: "cozy-system",
},
Spec: sourcev1.OCIRepositorySpec{
URL: repoURL,
Interval: metav1.Duration{Duration: 5 * time.Minute},
},
}
// Set reference if any ref options are provided
if len(refMap) > 0 {
obj.Spec.Reference = &sourcev1.OCIRepositoryRef{
Digest: refMap["digest"],
SemVer: refMap["semver"],
SemverFilter: refMap["semverFilter"],
Tag: refMap["tag"],
}
}
return obj, nil
}
// generateGitRepository creates a GitRepository resource
func generateGitRepository(name, repoURL string, refMap map[string]string) (*sourcev1.GitRepository, error) {
if err := validateGitRef(refMap); err != nil {
return nil, err
}
obj := &sourcev1.GitRepository{
TypeMeta: metav1.TypeMeta{
APIVersion: sourcev1.GroupVersion.String(),
Kind: sourcev1.GitRepositoryKind,
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: "cozy-system",
},
Spec: sourcev1.GitRepositorySpec{
URL: repoURL,
Interval: metav1.Duration{Duration: 5 * time.Minute},
},
}
// Set reference if any ref options are provided
if len(refMap) > 0 {
obj.Spec.Reference = &sourcev1.GitRepositoryRef{
Branch: refMap["branch"],
Tag: refMap["tag"],
SemVer: refMap["semver"],
Name: refMap["name"],
Commit: refMap["commit"],
}
}
return obj, nil
}
// installPlatformPackageSource creates the platform PackageSource resource
// that references the Flux source resource (OCIRepository or GitRepository).
//
// The variant list is intentionally hardcoded here. These are platform-defined
// deployment profiles (not user-extensible), matching what was previously in
// the Helm template. Changes require a new operator build and release.
func installPlatformPackageSource(ctx context.Context, k8sClient client.Client, platformSourceName, sourceRefKind string) error {
logger := log.FromContext(ctx)
packageSourceName := "cozystack." + platformSourceName
ps := &cozyv1alpha1.PackageSource{
TypeMeta: metav1.TypeMeta{
APIVersion: cozyv1alpha1.GroupVersion.String(),
Kind: "PackageSource",
},
ObjectMeta: metav1.ObjectMeta{
Name: packageSourceName,
Annotations: map[string]string{
"operator.cozystack.io/skip-cozystack-values": "true",
},
},
Spec: cozyv1alpha1.PackageSourceSpec{
SourceRef: &cozyv1alpha1.PackageSourceRef{
Kind: sourceRefKind,
Name: platformSourceName,
Namespace: "cozy-system",
Path: "/",
},
},
}
variantData := []struct {
name string
valuesFiles []string
}{
{"default", []string{"values.yaml"}},
{"isp-full", []string{"values.yaml", "values-isp-full.yaml"}},
{"isp-hosted", []string{"values.yaml", "values-isp-hosted.yaml"}},
{"isp-full-generic", []string{"values.yaml", "values-isp-full-generic.yaml"}},
}
variants := make([]cozyv1alpha1.Variant, len(variantData))
for i, v := range variantData {
variants[i] = cozyv1alpha1.Variant{
Name: v.name,
Components: []cozyv1alpha1.Component{
{
Name: "platform",
Path: "core/platform",
Install: &cozyv1alpha1.ComponentInstall{
Namespace: "cozy-system",
ReleaseName: "cozystack-platform",
},
ValuesFiles: v.valuesFiles,
},
},
}
}
ps.Spec.Variants = variants
logger.Info("Applying platform PackageSource", "name", packageSourceName)
patchOptions := &client.PatchOptions{
FieldManager: "cozystack-operator",
Force: func() *bool { b := true; return &b }(),
}
if err := k8sClient.Patch(ctx, ps, client.Apply, patchOptions); err != nil {
return fmt.Errorf("failed to apply PackageSource %s: %w", packageSourceName, err)
}
logger.Info("Applied platform PackageSource", "name", packageSourceName)
return nil
}

View File

@@ -1,574 +0,0 @@
/*
Copyright 2025 The Cozystack Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"testing"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)
func newTestScheme() *runtime.Scheme {
s := runtime.NewScheme()
_ = cozyv1alpha1.AddToScheme(s)
return s
}
func TestInstallPlatformPackageSource_Creates(t *testing.T) {
s := newTestScheme()
k8sClient := fake.NewClientBuilder().WithScheme(s).Build()
err := installPlatformPackageSource(context.Background(), k8sClient, "cozystack-platform", "OCIRepository")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
ps := &cozyv1alpha1.PackageSource{}
if err := k8sClient.Get(context.Background(), client.ObjectKey{Name: "cozystack.cozystack-platform"}, ps); err != nil {
t.Fatalf("PackageSource not found: %v", err)
}
// Verify name
if ps.Name != "cozystack.cozystack-platform" {
t.Errorf("expected name %q, got %q", "cozystack.cozystack-platform", ps.Name)
}
// Verify annotation
if ps.Annotations["operator.cozystack.io/skip-cozystack-values"] != "true" {
t.Errorf("expected skip-cozystack-values annotation to be 'true', got %q", ps.Annotations["operator.cozystack.io/skip-cozystack-values"])
}
// Verify sourceRef
if ps.Spec.SourceRef == nil {
t.Fatal("expected SourceRef to be set")
}
if ps.Spec.SourceRef.Kind != "OCIRepository" {
t.Errorf("expected sourceRef.kind %q, got %q", "OCIRepository", ps.Spec.SourceRef.Kind)
}
if ps.Spec.SourceRef.Name != "cozystack-platform" {
t.Errorf("expected sourceRef.name %q, got %q", "cozystack-platform", ps.Spec.SourceRef.Name)
}
if ps.Spec.SourceRef.Namespace != "cozy-system" {
t.Errorf("expected sourceRef.namespace %q, got %q", "cozy-system", ps.Spec.SourceRef.Namespace)
}
if ps.Spec.SourceRef.Path != "/" {
t.Errorf("expected sourceRef.path %q, got %q", "/", ps.Spec.SourceRef.Path)
}
// Verify variants
expectedVariants := []string{"default", "isp-full", "isp-hosted", "isp-full-generic"}
if len(ps.Spec.Variants) != len(expectedVariants) {
t.Fatalf("expected %d variants, got %d", len(expectedVariants), len(ps.Spec.Variants))
}
for i, name := range expectedVariants {
if ps.Spec.Variants[i].Name != name {
t.Errorf("expected variant[%d].name %q, got %q", i, name, ps.Spec.Variants[i].Name)
}
if len(ps.Spec.Variants[i].Components) != 1 {
t.Errorf("expected variant[%d] to have 1 component, got %d", i, len(ps.Spec.Variants[i].Components))
}
}
}
func TestInstallPlatformPackageSource_Updates(t *testing.T) {
s := newTestScheme()
existing := &cozyv1alpha1.PackageSource{
ObjectMeta: metav1.ObjectMeta{
Name: "cozystack.cozystack-platform",
ResourceVersion: "1",
Labels: map[string]string{
"custom-label": "should-be-preserved",
},
},
Spec: cozyv1alpha1.PackageSourceSpec{
SourceRef: &cozyv1alpha1.PackageSourceRef{
Kind: "OCIRepository",
Name: "old-name",
Namespace: "cozy-system",
},
},
}
k8sClient := fake.NewClientBuilder().WithScheme(s).WithObjects(existing).Build()
err := installPlatformPackageSource(context.Background(), k8sClient, "cozystack-platform", "OCIRepository")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
ps := &cozyv1alpha1.PackageSource{}
if err := k8sClient.Get(context.Background(), client.ObjectKey{Name: "cozystack.cozystack-platform"}, ps); err != nil {
t.Fatalf("PackageSource not found: %v", err)
}
// Verify sourceRef was updated
if ps.Spec.SourceRef.Name != "cozystack-platform" {
t.Errorf("expected updated sourceRef.name %q, got %q", "cozystack-platform", ps.Spec.SourceRef.Name)
}
// Verify all 4 variants are present after update
if len(ps.Spec.Variants) != 4 {
t.Errorf("expected 4 variants after update, got %d", len(ps.Spec.Variants))
}
// Verify that labels set by other controllers are preserved (SSA does not overwrite unmanaged fields)
if ps.Labels["custom-label"] != "should-be-preserved" {
t.Errorf("expected custom-label to be preserved, got %q", ps.Labels["custom-label"])
}
}
func TestParsePlatformSourceURL(t *testing.T) {
tests := []struct {
name string
url string
wantType string
wantURL string
wantErr bool
}{
{
name: "OCI URL",
url: "oci://ghcr.io/cozystack/cozystack/cozystack-packages",
wantType: "oci",
wantURL: "oci://ghcr.io/cozystack/cozystack/cozystack-packages",
},
{
name: "HTTPS URL",
url: "https://github.com/cozystack/cozystack",
wantType: "git",
wantURL: "https://github.com/cozystack/cozystack",
},
{
name: "SSH URL",
url: "ssh://git@github.com/cozystack/cozystack",
wantType: "git",
wantURL: "ssh://git@github.com/cozystack/cozystack",
},
{
name: "empty URL",
url: "",
wantErr: true,
},
{
name: "unsupported scheme",
url: "ftp://example.com/repo",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sourceType, repoURL, err := parsePlatformSourceURL(tt.url)
if tt.wantErr {
if err == nil {
t.Fatalf("expected error for URL %q, got nil", tt.url)
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if sourceType != tt.wantType {
t.Errorf("expected type %q, got %q", tt.wantType, sourceType)
}
if repoURL != tt.wantURL {
t.Errorf("expected URL %q, got %q", tt.wantURL, repoURL)
}
})
}
}
func TestInstallPlatformPackageSource_VariantValuesFiles(t *testing.T) {
s := newTestScheme()
k8sClient := fake.NewClientBuilder().WithScheme(s).Build()
err := installPlatformPackageSource(context.Background(), k8sClient, "cozystack-platform", "OCIRepository")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
ps := &cozyv1alpha1.PackageSource{}
if err := k8sClient.Get(context.Background(), client.ObjectKey{Name: "cozystack.cozystack-platform"}, ps); err != nil {
t.Fatalf("PackageSource not found: %v", err)
}
expectedValuesFiles := map[string][]string{
"default": {"values.yaml"},
"isp-full": {"values.yaml", "values-isp-full.yaml"},
"isp-hosted": {"values.yaml", "values-isp-hosted.yaml"},
"isp-full-generic": {"values.yaml", "values-isp-full-generic.yaml"},
}
for _, v := range ps.Spec.Variants {
expected, ok := expectedValuesFiles[v.Name]
if !ok {
t.Errorf("unexpected variant %q", v.Name)
continue
}
if len(v.Components) != 1 {
t.Errorf("variant %q: expected 1 component, got %d", v.Name, len(v.Components))
continue
}
comp := v.Components[0]
if comp.Name != "platform" {
t.Errorf("variant %q: expected component name %q, got %q", v.Name, "platform", comp.Name)
}
if comp.Path != "core/platform" {
t.Errorf("variant %q: expected component path %q, got %q", v.Name, "core/platform", comp.Path)
}
if comp.Install == nil {
t.Errorf("variant %q: expected Install to be set", v.Name)
} else {
if comp.Install.Namespace != "cozy-system" {
t.Errorf("variant %q: expected install namespace %q, got %q", v.Name, "cozy-system", comp.Install.Namespace)
}
if comp.Install.ReleaseName != "cozystack-platform" {
t.Errorf("variant %q: expected install releaseName %q, got %q", v.Name, "cozystack-platform", comp.Install.ReleaseName)
}
}
if len(comp.ValuesFiles) != len(expected) {
t.Errorf("variant %q: expected %d valuesFiles, got %d", v.Name, len(expected), len(comp.ValuesFiles))
continue
}
for i, f := range expected {
if comp.ValuesFiles[i] != f {
t.Errorf("variant %q: expected valuesFiles[%d] %q, got %q", v.Name, i, f, comp.ValuesFiles[i])
}
}
}
}
func TestInstallPlatformPackageSource_CustomName(t *testing.T) {
s := newTestScheme()
k8sClient := fake.NewClientBuilder().WithScheme(s).Build()
err := installPlatformPackageSource(context.Background(), k8sClient, "custom-source", "OCIRepository")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
ps := &cozyv1alpha1.PackageSource{}
if err := k8sClient.Get(context.Background(), client.ObjectKey{Name: "cozystack.custom-source"}, ps); err != nil {
t.Fatalf("PackageSource not found: %v", err)
}
if ps.Name != "cozystack.custom-source" {
t.Errorf("expected name %q, got %q", "cozystack.custom-source", ps.Name)
}
if ps.Spec.SourceRef.Name != "custom-source" {
t.Errorf("expected sourceRef.name %q, got %q", "custom-source", ps.Spec.SourceRef.Name)
}
}
func TestInstallPlatformPackageSource_GitRepository(t *testing.T) {
s := newTestScheme()
k8sClient := fake.NewClientBuilder().WithScheme(s).Build()
err := installPlatformPackageSource(context.Background(), k8sClient, "my-source", "GitRepository")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
ps := &cozyv1alpha1.PackageSource{}
if err := k8sClient.Get(context.Background(), client.ObjectKey{Name: "cozystack.my-source"}, ps); err != nil {
t.Fatalf("PackageSource not found: %v", err)
}
if ps.Spec.SourceRef.Kind != "GitRepository" {
t.Errorf("expected sourceRef.kind %q, got %q", "GitRepository", ps.Spec.SourceRef.Kind)
}
if ps.Spec.SourceRef.Name != "my-source" {
t.Errorf("expected sourceRef.name %q, got %q", "my-source", ps.Spec.SourceRef.Name)
}
}
func TestParseRefSpec(t *testing.T) {
tests := []struct {
name string
input string
want map[string]string
wantErr bool
}{
{
name: "empty string",
input: "",
want: map[string]string{},
},
{
name: "single key-value",
input: "tag=v1.0",
want: map[string]string{"tag": "v1.0"},
},
{
name: "multiple key-values",
input: "digest=sha256:abc123,tag=v1.0",
want: map[string]string{"digest": "sha256:abc123", "tag": "v1.0"},
},
{
name: "whitespace around pairs",
input: " tag=v1.0 , branch=main ",
want: map[string]string{"tag": "v1.0", "branch": "main"},
},
{
name: "equals sign in value",
input: "digest=sha256:abc=123",
want: map[string]string{"digest": "sha256:abc=123"},
},
{
name: "missing equals sign",
input: "tag",
wantErr: true,
},
{
name: "empty key",
input: "=value",
wantErr: true,
},
{
name: "empty value",
input: "tag=",
wantErr: true,
},
{
name: "trailing comma",
input: "tag=v1.0,",
want: map[string]string{"tag": "v1.0"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := parseRefSpec(tt.input)
if tt.wantErr {
if err == nil {
t.Fatalf("expected error for input %q, got nil", tt.input)
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(got) != len(tt.want) {
t.Fatalf("expected %d entries, got %d: %v", len(tt.want), len(got), got)
}
for k, v := range tt.want {
if got[k] != v {
t.Errorf("expected %q=%q, got %q=%q", k, v, k, got[k])
}
}
})
}
}
func TestValidateOCIRef(t *testing.T) {
tests := []struct {
name string
refMap map[string]string
wantErr bool
}{
{
name: "valid tag",
refMap: map[string]string{"tag": "v1.0"},
},
{
name: "valid digest",
refMap: map[string]string{"digest": "sha256:abc123def456"},
},
{
name: "valid semver",
refMap: map[string]string{"semver": ">=1.0.0"},
},
{
name: "multiple valid keys",
refMap: map[string]string{"tag": "v1.0", "digest": "sha256:abc"},
},
{
name: "empty map",
refMap: map[string]string{},
},
{
name: "invalid key",
refMap: map[string]string{"branch": "main"},
wantErr: true,
},
{
name: "invalid digest format",
refMap: map[string]string{"digest": "md5:abc"},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateOCIRef(tt.refMap)
if tt.wantErr && err == nil {
t.Fatal("expected error, got nil")
}
if !tt.wantErr && err != nil {
t.Fatalf("unexpected error: %v", err)
}
})
}
}
func TestValidateGitRef(t *testing.T) {
tests := []struct {
name string
refMap map[string]string
wantErr bool
}{
{
name: "valid branch",
refMap: map[string]string{"branch": "main"},
},
{
name: "valid commit",
refMap: map[string]string{"commit": "abc1234"},
},
{
name: "valid tag and branch",
refMap: map[string]string{"tag": "v1.0", "branch": "release"},
},
{
name: "empty map",
refMap: map[string]string{},
},
{
name: "invalid key",
refMap: map[string]string{"digest": "sha256:abc"},
wantErr: true,
},
{
name: "commit too short",
refMap: map[string]string{"commit": "abc"},
wantErr: true,
},
{
name: "commit not hex",
refMap: map[string]string{"commit": "zzzzzzz"},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateGitRef(tt.refMap)
if tt.wantErr && err == nil {
t.Fatal("expected error, got nil")
}
if !tt.wantErr && err != nil {
t.Fatalf("unexpected error: %v", err)
}
})
}
}
func TestGenerateOCIRepository(t *testing.T) {
refMap := map[string]string{"tag": "v1.0", "digest": "sha256:abc123"}
obj, err := generateOCIRepository("my-repo", "oci://registry.example.com/repo", refMap)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if obj.Name != "my-repo" {
t.Errorf("expected name %q, got %q", "my-repo", obj.Name)
}
if obj.Namespace != "cozy-system" {
t.Errorf("expected namespace %q, got %q", "cozy-system", obj.Namespace)
}
if obj.Spec.URL != "oci://registry.example.com/repo" {
t.Errorf("expected URL %q, got %q", "oci://registry.example.com/repo", obj.Spec.URL)
}
if obj.Spec.Reference == nil {
t.Fatal("expected Reference to be set")
}
if obj.Spec.Reference.Tag != "v1.0" {
t.Errorf("expected tag %q, got %q", "v1.0", obj.Spec.Reference.Tag)
}
if obj.Spec.Reference.Digest != "sha256:abc123" {
t.Errorf("expected digest %q, got %q", "sha256:abc123", obj.Spec.Reference.Digest)
}
}
func TestGenerateOCIRepository_NoRef(t *testing.T) {
obj, err := generateOCIRepository("my-repo", "oci://registry.example.com/repo", map[string]string{})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if obj.Spec.Reference != nil {
t.Error("expected Reference to be nil for empty refMap")
}
}
func TestGenerateOCIRepository_InvalidRef(t *testing.T) {
_, err := generateOCIRepository("my-repo", "oci://registry.example.com/repo", map[string]string{"branch": "main"})
if err == nil {
t.Fatal("expected error for invalid OCI ref key, got nil")
}
}
func TestGenerateGitRepository(t *testing.T) {
refMap := map[string]string{"branch": "main", "commit": "abc1234def5678"}
obj, err := generateGitRepository("my-repo", "https://github.com/user/repo", refMap)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if obj.Name != "my-repo" {
t.Errorf("expected name %q, got %q", "my-repo", obj.Name)
}
if obj.Namespace != "cozy-system" {
t.Errorf("expected namespace %q, got %q", "cozy-system", obj.Namespace)
}
if obj.Spec.URL != "https://github.com/user/repo" {
t.Errorf("expected URL %q, got %q", "https://github.com/user/repo", obj.Spec.URL)
}
if obj.Spec.Reference == nil {
t.Fatal("expected Reference to be set")
}
if obj.Spec.Reference.Branch != "main" {
t.Errorf("expected branch %q, got %q", "main", obj.Spec.Reference.Branch)
}
if obj.Spec.Reference.Commit != "abc1234def5678" {
t.Errorf("expected commit %q, got %q", "abc1234def5678", obj.Spec.Reference.Commit)
}
}
func TestGenerateGitRepository_NoRef(t *testing.T) {
obj, err := generateGitRepository("my-repo", "https://github.com/user/repo", map[string]string{})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if obj.Spec.Reference != nil {
t.Error("expected Reference to be nil for empty refMap")
}
}
func TestGenerateGitRepository_InvalidRef(t *testing.T) {
_, err := generateGitRepository("my-repo", "https://github.com/user/repo", map[string]string{"digest": "sha256:abc"})
if err == nil {
t.Fatal("expected error for invalid Git ref key, got nil")
}
}

View File

@@ -1,151 +0,0 @@
/*
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"
helmv2 "github.com/fluxcd/helm-controller/api/v2"
"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"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
"github.com/cozystack/cozystack/internal/controller/fluxplunger"
// +kubebuilder:scaffold:imports
)
var (
scheme = runtime.NewScheme()
setupLog = ctrl.Log.WithName("setup")
)
func init() {
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
utilruntime.Must(helmv2.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 server")
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)
}
// 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,
}
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
Metrics: metricsServerOptions,
HealthProbeBindAddress: probeAddr,
LeaderElection: enableLeaderElection,
LeaderElectionID: "flux-plunger.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 create manager")
os.Exit(1)
}
if err = (&fluxplunger.FluxPlunger{
Client: mgr.GetClient(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "FluxPlunger")
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)
}
}

View File

@@ -1,602 +0,0 @@
{
"__inputs": [
{
"name": "DS_PROMETHEUS",
"label": "Prometheus",
"description": "",
"type": "datasource",
"pluginId": "prometheus",
"pluginName": "Prometheus"
}
],
"__elements": {},
"__requires": [
{
"type": "panel",
"id": "bargauge",
"name": "Bar gauge",
"version": ""
},
{
"type": "grafana",
"id": "grafana",
"name": "Grafana",
"version": "9.4.7"
},
{
"type": "datasource",
"id": "prometheus",
"name": "Prometheus",
"version": "1.0.0"
},
{
"type": "panel",
"id": "timeseries",
"name": "Time series",
"version": ""
}
],
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "datasource",
"uid": "grafana"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"target": {
"limit": 100,
"matchAny": false,
"tags": [],
"type": "dashboard"
},
"type": "dashboard"
}
]
},
"description": "",
"editable": true,
"fiscalYearStartMonth": 0,
"gnetId": 16612,
"graphTooltip": 0,
"id": null,
"links": [
{
"asDropdown": true,
"icon": "external link",
"includeVars": true,
"keepTime": true,
"tags": [
"cilium-overview"
],
"targetBlank": false,
"title": "Cilium Overviews",
"tooltip": "",
"type": "dashboards",
"url": ""
},
{
"asDropdown": true,
"icon": "external link",
"includeVars": false,
"keepTime": true,
"tags": [
"hubble"
],
"targetBlank": false,
"title": "Hubble",
"tooltip": "",
"type": "dashboards",
"url": ""
}
],
"liveNow": false,
"panels": [
{
"collapsed": false,
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 0
},
"id": 2,
"panels": [],
"title": "DNS",
"type": "row"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"description": "",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "reqps"
},
"overrides": []
},
"gridPos": {
"h": 9,
"w": 12,
"x": 0,
"y": 1
},
"id": 37,
"options": {
"legend": {
"calcs": [
"mean",
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "sum(rate(hubble_dns_queries_total{cluster=~\"$cluster\", source_namespace=~\"$source_namespace\", destination_namespace=~\"$destination_namespace\"}[$__rate_interval])) by (source) > 0",
"legendFormat": "{{source}}",
"range": true,
"refId": "A"
}
],
"title": "DNS queries",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
},
"unit": "reqps"
},
"overrides": []
},
"gridPos": {
"h": 9,
"w": 12,
"x": 12,
"y": 1
},
"id": 41,
"options": {
"displayMode": "gradient",
"minVizHeight": 10,
"minVizWidth": 0,
"orientation": "horizontal",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"showUnfilled": true
},
"pluginVersion": "9.4.7",
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "topk(10, sum(rate(hubble_dns_queries_total{cluster=~\"$cluster\", source_namespace=~\"$source_namespace\", destination_namespace=~\"$destination_namespace\"}[$__rate_interval])*60) by (query))",
"legendFormat": "{{query}}",
"range": true,
"refId": "A"
}
],
"title": "Top 10 DNS queries",
"type": "bargauge"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "reqps"
},
"overrides": []
},
"gridPos": {
"h": 9,
"w": 12,
"x": 0,
"y": 10
},
"id": 39,
"options": {
"legend": {
"calcs": [
"mean",
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "round(sum(rate(hubble_dns_queries_total{cluster=~\"$cluster\", source_namespace=~\"$source_namespace\", destination_namespace=~\"$destination_namespace\"}[$__rate_interval])) by (source) - sum(label_replace(sum(rate(hubble_dns_responses_total{cluster=~\"$cluster\", source_namespace=~\"$destination_namespace\", destination_namespace=~\"$source_namespace\"}[$__rate_interval])) by (destination), \"source\", \"$1\", \"destination\", \"(.*)\")) without (destination), 0.001) > 0",
"legendFormat": "{{source}}",
"range": true,
"refId": "A"
}
],
"title": "Missing DNS responses",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 10,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "auto",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "normal"
},
"thresholdsStyle": {
"mode": "off"
}
},
"mappings": [],
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "reqps"
},
"overrides": []
},
"gridPos": {
"h": 9,
"w": 12,
"x": 12,
"y": 10
},
"id": 43,
"options": {
"legend": {
"calcs": [
"mean",
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "sum(rate(hubble_dns_responses_total{cluster=~\"$cluster\", source_namespace=~\"$destination_namespace\", destination_namespace=~\"$source_namespace\", rcode!=\"No Error\"}[$__rate_interval])) by (destination, rcode) > 0",
"legendFormat": "{{destination}}: {{rcode}}",
"range": true,
"refId": "A"
}
],
"title": "DNS errors",
"type": "timeseries"
}
],
"refresh": "",
"revision": 1,
"schemaVersion": 38,
"style": "dark",
"tags": [
"kubecon-demo"
],
"templating": {
"list": [
{
"current": {
"selected": false,
"text": "default",
"value": "default"
},
"hide": 0,
"includeAll": false,
"label": "Data Source",
"multi": false,
"name": "DS_PROMETHEUS",
"options": [],
"query": "prometheus",
"queryValue": "",
"refresh": 1,
"regex": "(?!grafanacloud-usage|grafanacloud-ml-metrics).+",
"skipUrlSync": false,
"type": "datasource"
},
{
"current": {},
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"definition": "label_values(cilium_version, cluster)",
"hide": 0,
"includeAll": true,
"multi": true,
"name": "cluster",
"options": [],
"query": {
"query": "label_values(cilium_version, cluster)",
"refId": "StandardVariableQuery"
},
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"type": "query"
},
{
"allValue": ".*",
"current": {},
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"definition": "label_values(source_namespace)",
"hide": 0,
"includeAll": true,
"label": "Source Namespace",
"multi": true,
"name": "source_namespace",
"options": [],
"query": {
"query": "label_values(source_namespace)",
"refId": "StandardVariableQuery"
},
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"type": "query"
},
{
"allValue": ".*",
"current": {},
"datasource": {
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"definition": "label_values(destination_namespace)",
"hide": 0,
"includeAll": true,
"label": "Destination Namespace",
"multi": true,
"name": "destination_namespace",
"options": [],
"query": {
"query": "label_values(destination_namespace)",
"refId": "StandardVariableQuery"
},
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"type": "query"
}
]
},
"time": {
"from": "now-1h",
"to": "now"
},
"timepicker": {
"refresh_intervals": [
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
]
},
"timezone": "",
"title": "Hubble / DNS Overview (Namespace)",
"uid": "_f0DUpY4k",
"version": 26,
"weekStart": ""
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -22,7 +22,7 @@ When the user asks to generate a changelog, follow these steps in the specified
- [ ] 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, cozyhr, cozy-proxy) for tags during release period
- [ ] **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)
@@ -146,7 +146,7 @@ Cozystack release may include changes from related repositories. Check and inclu
**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/cozyhr](https://github.com/cozystack/cozyhr)
- [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.
@@ -195,7 +195,7 @@ Cozystack release may include changes from related repositories. Check and inclu
3. **For optional repositories, check if tags exist during release period:**
**⚠️ MANDATORY: You MUST check ALL optional repositories (talm, boot-to-talos, cozyhr, cozy-proxy). Do NOT skip any repository!**
**⚠️ MANDATORY: You MUST check ALL optional repositories (talm, boot-to-talos, cozypkg, cozy-proxy). Do NOT skip any repository!**
**Use the helper script:**
```bash
@@ -208,7 +208,7 @@ Cozystack release may include changes from related repositories. Check and inclu
```
The script will:
- Check ALL optional repositories (talm, boot-to-talos, cozyhr, cozy-proxy)
- 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
@@ -569,7 +569,7 @@ Create a new changelog file in the format matching previous versions:
- [ ] 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, cozyhr, cozy-proxy) checked for tags during release period
- [ ] 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
@@ -628,7 +628,7 @@ Save the changelog to file `docs/changelogs/v<version>.md` according to the vers
- **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, cozyhr, cozy-proxy) for tags during the release period. Do NOT skip any repository even if you think there might not be tags.
- **⚠️ 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)
@@ -637,7 +637,7 @@ Save the changelog to file `docs/changelogs/v<version>.md` according to the vers
- **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, cozyhr, 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.
- 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

View File

@@ -27,7 +27,7 @@ git commit --signoff -m "[component] Brief description of changes"
**Component prefixes:**
- System: `[dashboard]`, `[platform]`, `[cilium]`, `[kube-ovn]`, `[linstor]`, `[fluxcd]`, `[cluster-api]`
- Apps: `[postgres]`, `[mariadb]`, `[redis]`, `[kafka]`, `[clickhouse]`, `[virtual-machine]`, `[kubernetes]`
- Apps: `[postgres]`, `[mysql]`, `[redis]`, `[kafka]`, `[clickhouse]`, `[virtual-machine]`, `[kubernetes]`
- Other: `[tests]`, `[ci]`, `[docs]`, `[maintenance]`
**Examples:**
@@ -155,91 +155,6 @@ git diff
The user will commit and push when ready.
## Code Review Comments
When asked to fix code review comments, **always work only with unresolved (open) comments**. Resolved comments should be ignored as they have already been addressed.
### Getting Unresolved Review Comments
Use GitHub GraphQL API to fetch only unresolved review comments from a pull request:
```bash
gh api graphql -F owner=cozystack -F repo=cozystack -F pr=<PR_NUMBER> -f query='
query($owner: String!, $repo: String!, $pr: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $pr) {
reviewThreads(first: 100) {
nodes {
isResolved
comments(first: 100) {
nodes {
id
path
line
author { login }
bodyText
url
createdAt
}
}
}
}
}
}
}' --jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false) | .comments.nodes[]'
```
### Filtering for Unresolved Comments
The key filter is `select(.isResolved == false)` which ensures only unresolved review threads are processed. Each thread can contain multiple comments, but if the thread is resolved, all its comments should be ignored.
### Working with Review Comments
1. **Fetch unresolved comments** using the GraphQL query above
2. **Parse the results** to identify:
- File path (`path`)
- Line number (`line` or `originalLine`)
- Comment text (`bodyText`)
- Author (`author.login`)
3. **Address each unresolved comment** by:
- Locating the relevant code section
- Making the requested changes
- Ensuring the fix addresses the concern raised
4. **Do NOT process resolved comments** - they have already been handled
### Example: Compact List of Unresolved Comments
For a quick overview of unresolved comments:
```bash
gh api graphql -F owner=cozystack -F repo=cozystack -F pr=<PR_NUMBER> -f query='
query($owner: String!, $repo: String!, $pr: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $pr) {
reviewThreads(first: 100) {
nodes {
isResolved
comments(first: 100) {
nodes {
path
line
author { login }
bodyText
}
}
}
}
}
}
}' --jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false) | .comments.nodes[] | "\(.path):\(.line // "N/A") - \(.author.login): \(.bodyText[:150])"'
```
### Important Notes
- **REST API limitation**: The REST endpoint `/pulls/{pr}/reviews` returns review summaries, not individual review comments. Use GraphQL API for accessing `reviewThreads` with `isResolved` status.
- **Thread-based resolution**: Comments are organized in threads. If a thread is resolved (`isResolved: true`), ignore all comments in that thread.
- **Always filter**: Never process comments from resolved threads, even if they appear in the results.
### Example Workflow
```bash

View File

@@ -10,7 +10,7 @@ Cozystack is an open-source Kubernetes-based platform and framework for building
- **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 cozyhr tool
- **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.

View File

@@ -5,13 +5,10 @@ https://github.com/cozystack/cozystack/releases/tag/v0.36.2
## Features and Improvements
* [vm-disk] New SVG icon for VM disk application. (@kvaps and @kvapsova in https://github.com/cozystack/cozystack/pull/1435)
## Security
## Fixes
* [kubernetes] Pin CoreDNS image tag to v1.12.4 for consistent, reproducible deployments. (@kvaps in https://github.com/cozystack/cozystack/pull/1469)
* [dashboard] Fix FerretDB spec typo that prevented deploy/display in the web UI. (@lllamnyp in https://github.com/cozystack/cozystack/pull/1440)
## Dependencies
## Development, Testing, and CI/CD

View File

@@ -1,16 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v0.37.10
-->
## Features and Improvements
* **[virtual-machine] Improve check for resizing job**: Improved storage resize logic to only expand persistent volume claims when storage is being increased, preventing unintended storage reduction operations. Added validation to accurately compare current and desired storage sizes before triggering resize operations ([**@kvaps**](https://github.com/kvaps) in #1688, #1702).
## Fixes
* **[dashboard] Fix CustomFormsOverride schema to nest properties under spec.properties**: Fixed the logic for generating CustomFormsOverride schema to properly nest properties under `spec.properties` instead of directly under `properties`, ensuring correct form schema generation in the dashboard ([**@kvaps**](https://github.com/kvaps) in #1692, #1699).
---
**Full Changelog**: [v0.37.9...v0.37.10](https://github.com/cozystack/cozystack/compare/v0.37.9...v0.37.10)

View File

@@ -1,14 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v0.38.3
-->
## Improvements
* **[core:installer] Address buildx warnings for installer image builds**: Aligns Dockerfile syntax casing to remove buildx warnings, keeping installer builds clean ([**@nbykov0**](https://github.com/nbykov0) in #1682).
* **[system:coredns] Align CoreDNS app labels with Talos defaults**: Matches CoreDNS labels to Talos conventions so services select pods consistently across platform and tenant clusters ([**@nbykov0**](https://github.com/nbykov0) in #1675).
* **[system:monitoring-agents] Rename CoreDNS metrics service to avoid conflicts**: Renames the metrics service so it no longer clashes with the CoreDNS service used for name resolution in tenant clusters ([**@nbykov0**](https://github.com/nbykov0) in #1676).
---
**Full Changelog**: [v0.38.2...v0.38.3](https://github.com/cozystack/cozystack/compare/v0.38.2...v0.38.3)

View File

@@ -1,18 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v0.38.4
-->
## Fixes
* **[linstor] Update piraeus-operator v2.10.2 to handle fsck checks reliably**: Upgrades LINSTOR CSI to avoid failed mounts when fsck sees mounted volumes, improving volume publish reliability ([**@kvaps**](https://github.com/kvaps) in #1689, #1697).
* **[dashboard] Nest CustomFormsOverride properties under spec.properties**: Fixes schema generation so custom form properties are placed under `spec.properties`, preventing mis-rendered or missing form fields ([**@kvaps**](https://github.com/kvaps) in #1692, #1700).
* **[virtual-machine] Guard PVC resize to only expand storage**: Ensures resize jobs run only when storage size increases, avoiding unintended shrink attempts during VM updates ([**@kvaps**](https://github.com/kvaps) in #1688, #1701).
## Documentation
* **[website] Clarify GPU check command**: Makes the kubectl command for validating GPU binding more explicit, including namespace context ([**@nbykov0**](https://github.com/nbykov0) in cozystack/website#379).
---
**Full Changelog**: [v0.38.3...v0.38.4](https://github.com/cozystack/cozystack/compare/v0.38.3...v0.38.4)

View File

@@ -1,18 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v0.38.5
-->
## Features and Improvements
* **[virtual-machine,vm-instance] Add nodeAffinity for Windows VMs based on scheduling config**: Added nodeAffinity configuration to virtual-machine and vm-instance charts to support dedicated nodes for Windows VMs. When `dedicatedNodesForWindowsVMs` is enabled in the `cozystack-scheduling` ConfigMap, Windows VMs are scheduled on nodes with label `scheduling.cozystack.io/vm-windows=true`, while non-Windows VMs prefer nodes without this label ([**@kvaps**](https://github.com/kvaps) in #1693, #1744).
* **[cilium] Enable automatic pod rollout on configmap updates**: Cilium and Cilium operator pods now automatically restart when the cilium-config ConfigMap is updated, ensuring configuration changes are applied immediately without manual intervention ([**@kvaps**](https://github.com/kvaps) in #1728, #1745).
* **Update SeaweedFS v4.02**: Updated SeaweedFS to version 4.02 with improved S3 daemon performance and fixes. This update includes better S3 compatibility and performance improvements ([**@kvaps**](https://github.com/kvaps) in #1725, #1732).
## Fixes
* **[apps] Refactor apiserver to use typed objects and fix UnstructuredList GVK**: Refactored the apiserver REST handlers to use typed objects (`appsv1alpha1.Application`) instead of `unstructured.Unstructured`, eliminating the need for runtime conversions and simplifying the codebase. Additionally, fixed an issue where `UnstructuredList` objects were using the first registered kind from `typeToGVK` instead of the kind from the object's field when multiple kinds are registered with the same Go type. This fix includes the upstream fix from kubernetes/kubernetes#135537 ([**@kvaps**](https://github.com/kvaps) in #1679, #1709).
---
**Full Changelog**: [v0.38.4...v0.38.5](https://github.com/cozystack/cozystack/compare/v0.38.4...v0.38.5)

View File

@@ -1,12 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v0.38.6
-->
## Development, Testing, and CI/CD
* **[kubernetes] Add lb tests for tenant k8s**: Added load balancer tests for tenant Kubernetes clusters, improving test coverage and ensuring proper load balancer functionality in tenant environments ([**@IvanHunters**](https://github.com/IvanHunters) in #1783, #1792).
---
**Full Changelog**: [v0.38.5...v0.38.6](https://github.com/cozystack/cozystack/compare/v0.38.5...v0.38.6)

View File

@@ -1,13 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v0.38.7
-->
## Fixes
* **[kubevirt-operator] Fix typo in VMNotRunningFor10Minutes alert**: Fixed typo in VM alert name, ensuring proper alert triggering and monitoring for virtual machines that are not running for extended periods ([**@lexfrei**](https://github.com/lexfrei) in #1770).
* **[kubevirt-operator] Revert incorrect case change in VM alerts**: Reverted incorrect case change in VM alert names to maintain consistency with alert naming conventions ([**@lexfrei**](https://github.com/lexfrei) in #1804, #1805).
---
**Full Changelog**: [v0.38.6...v0.38.7](https://github.com/cozystack/cozystack/compare/v0.38.6...v0.38.7)

View File

@@ -1,12 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v0.38.8
-->
## Improvements
* **[multus] Remove memory limit**: Removed memory limit for Multus daemonset due to unpredictable memory consumption spikes during startup after node reboots (reported up to 3Gi). This temporary change prevents out-of-memory issues while the root cause is addressed in future releases ([**@nbykov0**](https://github.com/nbykov0) in #1834).
---
**Full Changelog**: [v0.38.7...v0.38.8](https://github.com/cozystack/cozystack/compare/v0.38.7...v0.38.8)

View File

@@ -1,95 +0,0 @@
# Cozystack v0.39 — "Enhanced Networking & Monitoring"
This release introduces topology-aware routing for Cilium services, automatic pod rollouts on configuration changes, improved monitoring capabilities, and numerous bug fixes and improvements across the platform.
## Highlights
* **Topology-Aware Routing**: Enabled topology-aware routing for Cilium services, improving traffic distribution and reducing latency by routing traffic to endpoints in the same zone when possible ([**@nbykov0**](https://github.com/nbykov0) in #1734).
* **Automatic Pod Rollouts**: Cilium and Cilium operator pods now automatically restart when configuration changes, ensuring configuration updates are applied immediately ([**@kvaps**](https://github.com/kvaps) in #1728).
* **Windows VM Scheduling**: Added nodeAffinity configuration for Windows VMs based on scheduling config, enabling dedicated nodes for Windows workloads ([**@kvaps**](https://github.com/kvaps) in #1693).
* **SeaweedFS Updates**: Updated to SeaweedFS v4.02 with improved S3 daemon performance and fixes ([**@kvaps**](https://github.com/kvaps) in #1725).
---
## Major Features and Improvements
### Networking
* **[system/cilium] Enable topology-aware routing for services**: Enabled topology-aware routing for services, improving traffic distribution and reducing latency by routing traffic to endpoints in the same zone when possible. This feature helps optimize network performance in multi-zone deployments ([**@nbykov0**](https://github.com/nbykov0) in #1734).
* **[cilium] Enable automatic pod rollout on configmap updates**: Cilium and Cilium operator pods now automatically restart when the cilium-config ConfigMap is updated, ensuring configuration changes are applied immediately without manual intervention ([**@kvaps**](https://github.com/kvaps) in #1728).
### Virtual Machines
* **[virtual-machine,vm-instance] Add nodeAffinity for Windows VMs based on scheduling config**: Added nodeAffinity configuration to virtual-machine and vm-instance charts to support dedicated nodes for Windows VMs. When `dedicatedNodesForWindowsVMs` is enabled in the `cozystack-scheduling` ConfigMap, Windows VMs are scheduled on nodes with label `scheduling.cozystack.io/vm-windows=true`, while non-Windows VMs prefer nodes without this label ([**@kvaps**](https://github.com/kvaps) in #1693).
### Storage
* **Update SeaweedFS v4.02**: Updated SeaweedFS to version 4.02 with improved performance for S3 daemon and fixes for known issues. This update includes better S3 compatibility and performance improvements ([**@kvaps**](https://github.com/kvaps) in #1725).
### Tools
* **[talm] feat(init)!: require --name flag for cluster name**: Breaking change: The `talm init` command now requires the `--name` flag to specify the cluster name. This ensures consistent cluster naming and prevents accidental initialization without a name ([**@lexfrei**](https://github.com/lexfrei) in cozystack/talm#86).
* **[talm] feat(template): preserve extra YAML documents in output**: Templates now preserve extra YAML documents in the output, allowing for more flexible template processing ([**@lexfrei**](https://github.com/lexfrei) in cozystack/talm#87).
* **[talm] feat: add directory expansion for -f flag**: Added directory expansion support for the `-f` flag, allowing users to specify directories instead of individual files ([**@kvaps**](https://github.com/kvaps) in cozystack/talm@ca5713e).
* **[talm] Introduce automatic root detection**: Added automatic root detection logic to simplify talm usage and reduce manual configuration ([**@kvaps**](https://github.com/kvaps) in cozystack/talm@d165162).
* **[talm] Introduce talm kubeconfig --login command**: Added new `talm kubeconfig --login` command for easier kubeconfig management ([**@kvaps**](https://github.com/kvaps) in cozystack/talm@5f7e05b).
* **[talm] Introduce encryption**: Added encryption support to talm for secure configuration management ([**@kvaps**](https://github.com/kvaps) in cozystack/talm#81).
* **[talm] Replace code-generation with wrapper on talosctl**: Refactored talm to use a wrapper on talosctl instead of code generation, simplifying the codebase and improving maintainability ([**@kvaps**](https://github.com/kvaps) in cozystack/talm#80).
* **[talm] Use go embed instead of code generation**: Migrated from code generation to go embed for better build performance and simpler dependency management ([**@kvaps**](https://github.com/kvaps) in cozystack/talm#79).
* **[boot-to-talos] Cozystack: Update Talos Linux v1.11.3**: Updated boot-to-talos to use Talos Linux v1.11.3 ([**@kvaps**](https://github.com/kvaps) in cozystack/boot-to-talos#7).
## Improvements
* **[seaweedfs] Extended CA certificate duration to reduce disruptive CA rotations**: Extended CA certificate duration to reduce disruptive CA rotations, improving long-term certificate management and reducing operational overhead ([**@IvanHunters**](https://github.com/IvanHunters) in #1657).
* **[dashboard] Add config hash annotations to restart pods on config changes**: Added config hash annotations to dashboard deployment templates to ensure pods are automatically restarted when their configuration changes, ensuring configuration updates are applied immediately ([**@kvaps**](https://github.com/kvaps) in #1662).
* **[tenant][kubernetes] Introduce better cleanup logic**: Improved cleanup logic for tenant Kubernetes resources, ensuring proper resource cleanup when tenants are deleted or updated. Added automated pre-delete cleanup job for tenant namespaces to remove tenant-related releases during uninstall ([**@kvaps**](https://github.com/kvaps) in #1661).
* **[system:coredns] update coredns app labels to match Talos coredns labels**: Updated coredns app labels to match Talos coredns labels, ensuring consistency across the platform ([**@nbykov0**](https://github.com/nbykov0) in #1675).
* **[system:monitoring-agents] rename coredns metrics service**: Renamed coredns metrics service to avoid interference with coredns service used for name resolution in tenant k8s clusters ([**@nbykov0**](https://github.com/nbykov0) in #1676).
* **[core:installer] Address buildx warnings**: Fixed Dockerfile syntax warnings from buildx, ensuring clean builds without warnings ([**@nbykov0**](https://github.com/nbykov0) in #1682).
* **[talm] Refactor root detection logic into single file**: Improved code organization by consolidating root detection logic into a single file ([**@kvaps**](https://github.com/kvaps) in cozystack/talm@487b479).
* **[talm] Refactor init logic, better upgrade**: Improved initialization logic and upgrade process for better reliability ([**@kvaps**](https://github.com/kvaps) in cozystack/talm@c512777).
* **[talm] Sugar for kubeconfig command**: Added convenience features to the kubeconfig command for improved usability ([**@kvaps**](https://github.com/kvaps) in cozystack/talm@a4010b3).
* **[talm] wrap upgrade command**: Wrapped upgrade command for better integration and error handling ([**@kvaps**](https://github.com/kvaps) in cozystack/talm@2e1afbf).
* **[talm] docs(readme): add Homebrew installation option**: Added Homebrew installation option to the README for easier installation on macOS ([**@lexfrei**](https://github.com/lexfrei) in cozystack/talm@12bd4f2).
* **[talm] cozystack: disable nodeCIDRs allocation**: Disabled nodeCIDRs allocation in talm for better network configuration control ([**@kvaps**](https://github.com/kvaps) in cozystack/talm#82).
* **[talm] Update license to Apache2.0**: Updated license to Apache 2.0 for better compatibility and clarity ([**@kvaps**](https://github.com/kvaps) in cozystack/talm@eda1032).
## Fixes
* **[apps] Refactor apiserver to use typed objects and fix UnstructuredList GVK**: Refactored the apiserver REST handlers to use typed objects (`appsv1alpha1.Application`) instead of `unstructured.Unstructured`, eliminating the need for runtime conversions and simplifying the codebase. Additionally, fixed an issue where `UnstructuredList` objects were using the first registered kind from `typeToGVK` instead of the kind from the object's field when multiple kinds are registered with the same Go type. This fix includes the upstream fix from kubernetes/kubernetes#135537 ([**@kvaps**](https://github.com/kvaps) in #1679).
* **[dashboard] Fix CustomFormsOverride schema to nest properties under spec.properties**: Fixed the logic for generating CustomFormsOverride schema to properly nest properties under `spec.properties` instead of directly under `properties`, ensuring correct form schema generation ([**@kvaps**](https://github.com/kvaps) in #1692).
* **[virtual-machine] Improve check for resizing job**: Improved storage resize logic to only expand persistent volume claims when storage is being increased, preventing unintended storage reduction operations. Added validation to accurately compare current and desired storage sizes before triggering resize operations ([**@kvaps**](https://github.com/kvaps) in #1688).
* **[linstor] Update piraeus-operator v2.10.2**: Updated LINSTOR CSI to fix issues with the new fsck behaviour, resolving mount failures when fsck attempts to run on mounted devices ([**@kvaps**](https://github.com/kvaps) in #1689).
* **[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 ([**@kvaps**](https://github.com/kvaps) in #1677).
* **[talm] fix: normalize template paths for Windows compatibility**: Fixed template path handling to ensure Windows compatibility by normalizing paths ([**@lexfrei**](https://github.com/lexfrei) in cozystack/talm#88).
## Dependencies
* **Update SeaweedFS v4.02**: Updated SeaweedFS to version 4.02 ([**@kvaps**](https://github.com/kvaps) in #1725).
* **[linstor] Update piraeus-operator v2.10.2**: Updated piraeus-operator to version 2.10.2 ([**@kvaps**](https://github.com/kvaps) in #1689).
* **[talm] Cozystack: Update Talos Linux v1.11.3**: Updated talm to use Talos Linux v1.11.3 ([**@kvaps**](https://github.com/kvaps) in cozystack/talm#83).
## Documentation
* **[website] Add article: Talm v0.17: Built-in Age Encryption for Secrets Management**: Added comprehensive blog post announcing Talm v0.17 and its built-in age-based encryption for secrets. Covers initial setup and key generation, encryption/decryption workflows, idempotent encryption behavior, automatic .gitignore handling, file permission safeguards, security best practices, and guidance for GitOps and CI/CD integration ([**@kvaps**](https://github.com/kvaps) in [cozystack/website#384](https://github.com/cozystack/website/pull/384)).
* **[website] docs(talm): update talm init syntax for mandatory --preset and --name flags**: Updated documentation to reflect breaking changes in talm, adding mandatory `--preset` and `--name` flags to the talm init command ([**@lexfrei**](https://github.com/lexfrei) in cozystack/website#386).
---
## Contributors
We'd like to thank all contributors who made this release possible:
* [**@IvanHunters**](https://github.com/IvanHunters)
* [**@kvaps**](https://github.com/kvaps)
* [**@lexfrei**](https://github.com/lexfrei)
* [**@nbykov0**](https://github.com/nbykov0)
---
**Full Changelog**: [v0.38.0...v0.39.0](https://github.com/cozystack/cozystack/compare/v0.38.0...v0.39.0)
<!--
https://github.com/cozystack/cozystack/releases/tag/v0.39.0
-->

View File

@@ -1,12 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v0.39.1
-->
## Features and Improvements
* **[monitoring] Add SLACK_SEVERITY_FILTER field and VMAgent for tenant monitoring**: Introduced the SLACK_SEVERITY_FILTER environment variable in the Alerta deployment to enable filtering of alert severities for Slack notifications based on the disabledSeverity configuration. Additionally, added a VMAgent resource template for scraping metrics within tenant namespaces, improving monitoring granularity and control. This enhancement allows administrators to configure which alert severities are sent to Slack and enables tenant-specific metrics collection for better observability ([**@IvanHunters**](https://github.com/IvanHunters) in #1712).
---
**Full Changelog**: [v0.39.0...v0.39.1](https://github.com/cozystack/cozystack/compare/v0.39.0...v0.39.1)

View File

@@ -1,19 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v0.39.2
-->
## Features and Improvements
* **[vm] Always expose VMs with a service**: Virtual machines are now always exposed with at least a ClusterIP service, ensuring they have in-cluster DNS names and can be accessed from other pods even without public IP addresses ([**@lllamnyp**](https://github.com/lllamnyp) in #1738, #1751).
* **[tenant] Allow egress to parent ingress pods**: Updated tenant network policies to allow egress traffic to parent cluster ingress pods, enabling proper communication patterns between tenant namespaces and parent cluster ingress controllers ([**@lexfrei**](https://github.com/lexfrei) in #1765, #1776).
* **[system] Add resource requests and limits to etcd-defrag**: Added resource requests and limits to etcd-defrag job to ensure proper resource allocation and prevent resource contention during etcd maintenance operations ([**@matthieu-robin**](https://github.com/matthieu-robin) in #1785, #1786).
* **[tenant] Run cleanup job from system namespace**: Moved tenant cleanup job to run from system namespace, improving security and resource isolation for tenant cleanup operations ([**@lllamnyp**](https://github.com/lllamnyp) in #1774, #1777).
## Fixes
* **[kubevirt-operator] Fix typo in VMNotRunningFor10Minutes alert**: Fixed typo in VM alert name, ensuring proper alert triggering and monitoring for virtual machines that are not running for extended periods ([**@lexfrei**](https://github.com/lexfrei) in #1770, #1775).
---
**Full Changelog**: [v0.39.1...v0.39.2](https://github.com/cozystack/cozystack/compare/v0.39.1...v0.39.2)

View File

@@ -1,36 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v0.39.3
-->
## Features and Improvements
* **[seaweedfs] Traffic locality**: Upgraded SeaweedFS to v4.05 with traffic locality capabilities, new admin component with web-based UI, worker component for distributed operations, and enhanced S3 monitoring with Grafana dashboards. Improves S3 service performance by routing requests to nearest available volume servers ([**@nbykov0**](https://github.com/nbykov0) in #1748, #1830).
* **[kube-ovn] Update to v1.14.25**: Updated Kube-OVN to version 1.14.25 with improved stability and new features ([**@kvaps**](https://github.com/kvaps) in #1819, #1837).
* **[linstor] Build linstor-server with custom patches**: Added custom patches to linstor-server build process, enabling platform-specific optimizations and fixes ([**@kvaps**](https://github.com/kvaps) in #1726, #1818).
* **[api, lineage] Tolerate all taints**: Updated API and lineage webhook to tolerate all taints, ensuring controllers can run on any node regardless of taint configuration ([**@nbykov0**](https://github.com/nbykov0) in #1781, #1827).
* **[ingress] Add topology anti-affinities**: Added topology anti-affinity rules to ingress controller deployment for better pod distribution across nodes ([**@kvaps**](https://github.com/kvaps) in commit 25f31022).
## Fixes
* **[linstor] fix: prevent DRBD device race condition in updateDiscGran**: Fixed race condition in DRBD device management during granularity updates, preventing potential data corruption or device conflicts ([**@kvaps**](https://github.com/kvaps) in #1829, #1836).
* **fix(linstor): prevent orphaned DRBD devices during toggle-disk retry**: Fixed issue where retry logic during disk toggle operations could leave orphaned DRBD devices, now properly cleans up devices during retry attempts ([**@kvaps**](https://github.com/kvaps) in #1823, #1825).
* **[kubernetes] Fix endpoints for cilium-gateway**: Fixed endpoint configuration for cilium-gateway, ensuring proper service discovery and connectivity ([**@kvaps**](https://github.com/kvaps) in #1729, #1808).
* **[kubevirt-operator] Revert incorrect case change in VM alerts**: Reverted incorrect case change in VM alert names to maintain consistency with alert naming conventions ([**@lexfrei**](https://github.com/lexfrei) in #1804, #1806).
## System Configuration
* **[kubeovn] Package from external repo**: Extracted Kube-OVN packaging from main repository to external repository, improving modularity ([**@lllamnyp**](https://github.com/lllamnyp) in #1535).
## Development, Testing, and CI/CD
* **[testing] Add aliases and autocomplete**: Added shell aliases and autocomplete support for testing commands, improving developer experience ([**@lllamnyp**](https://github.com/lllamnyp) in #1803, #1809).
## Dependencies
* **[seaweedfs] Traffic locality**: Upgraded SeaweedFS to v4.05 with traffic locality capabilities ([**@nbykov0**](https://github.com/nbykov0) in #1748, #1830).
* **[kube-ovn] Update to v1.14.25**: Updated Kube-OVN to version 1.14.25 ([**@kvaps**](https://github.com/kvaps) in #1819, #1837).
---
**Full Changelog**: [v0.39.2...v0.39.3](https://github.com/cozystack/cozystack/compare/v0.39.2...v0.39.3)

View File

@@ -1,12 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v0.39.4
-->
## Features and Improvements
* **[paas-full] Add multus dependencies similar to other CNIs**: Added Multus as a dependency in the paas-full package, consistent with how other CNIs are included. This ensures proper dependency management and simplifies the installation process for environments using Multus networking ([**@nbykov0**](https://github.com/nbykov0) in #1835).
---
**Full Changelog**: [v0.39.3...v0.39.4](https://github.com/cozystack/cozystack/compare/v0.39.3...v0.39.4)

View File

@@ -1,11 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v0.39.5
-->
## Fixes
* **[linstor] Update piraeus-server patches with critical fixes**: Backported critical patches to piraeus-server that address storage stability issues and improve DRBD resource handling. These patches fix edge cases in device management and ensure more reliable storage operations ([**@kvaps**](https://github.com/kvaps) in #1850, #1853).
---
**Full Changelog**: [v0.39.4...v0.39.5](https://github.com/cozystack/cozystack/compare/v0.39.4...v0.39.5)

View File

@@ -1,206 +0,0 @@
# Cozystack v0.40 — "Enhanced Storage & Platform Architecture"
This release introduces LINSTOR scheduler for optimal pod placement, SeaweedFS traffic locality, a new valuesFrom-based configuration mechanism, auto-diskful for LINSTOR, automated version management systems, and numerous improvements across the platform.
## Feature Highlights
### LINSTOR Scheduler for Optimal Pod Placement
Cozystack now includes a custom Kubernetes scheduler extender that works alongside the default kube-scheduler to optimize pod placement on nodes with LINSTOR storage. When a pod requests LINSTOR-backed storage, the scheduler communicates with the LINSTOR controller to find nodes that have local replicas of the requested volumes, prioritizing placement on nodes with existing data to minimize network traffic and improve I/O performance.
The scheduler includes an admission webhook that automatically routes pods using LINSTOR CSI volumes to the custom scheduler, ensuring seamless integration without manual configuration. This feature significantly improves performance for workloads using LINSTOR storage by reducing network latency and improving data locality.
Learn more about LINSTOR in the [documentation](https://cozystack.io/docs/operations/storage/linstor/).
### SeaweedFS Traffic Locality
SeaweedFS has been upgraded to version 4.05 with new traffic locality capabilities that optimize S3 service traffic distribution. The update includes a new admin component with a web-based UI and authentication support, as well as a worker component for distributed operations. These enhancements improve S3 service performance and provide better visibility through enhanced Grafana dashboard panels for buckets, API calls, costs, and performance metrics.
The traffic locality feature ensures that S3 requests are routed to the nearest available volume servers, reducing latency and improving overall performance for distributed storage operations. TLS certificate support for admin and worker components adds an extra layer of security for management operations.
### ValuesFrom Configuration Mechanism
Cozystack now uses FluxCD's valuesFrom mechanism to replace Helm lookup functions for configuration propagation. This architectural improvement provides cleaner config propagation and eliminates the need for force reconcile controllers. Configuration from ConfigMaps (cozystack, cozystack-branding, cozystack-scheduling) and namespace service references (etcd, host, ingress, monitoring, seaweedfs) is now centrally managed through a `cozystack-values` Secret in each namespace.
This change simplifies Helm chart templates by replacing complex lookup functions with direct value references, improves configuration consistency, and reduces the reconciliation overhead. All HelmReleases now automatically receive cluster and namespace configuration through the valuesFrom mechanism, making configuration management more transparent and maintainable.
### Auto-diskful for LINSTOR
The LINSTOR integration now includes automatic diskful functionality that converts diskless nodes to diskful when they hold DRBD resources in Primary state for an extended period (30 minutes). This feature addresses scenarios where workloads are scheduled on nodes without local storage replicas by automatically creating local disk replicas when needed, improving I/O performance for long-running workloads.
When enabled with cleanup options, the system can automatically remove disk replicas that are no longer needed, preventing storage waste from temporary replicas. This intelligent storage management reduces network traffic for frequently accessed data while maintaining efficient storage utilization.
### Automated Version Management Systems
Cozystack now includes automated version management systems for PostgreSQL, Kubernetes, MariaDB, and Redis applications. These systems automatically track upstream versions and provide mechanisms for automated version updates, ensuring that platform users always have access to the latest stable versions while maintaining compatibility with existing deployments.
The version management systems integrate with the Cozystack API and dashboard, providing administrators with visibility into available versions and update paths. This infrastructure sets the foundation for future automated upgrade workflows and version compatibility management.
---
## Major Features and Improvements
### Storage
* **[linstor] Add linstor-scheduler package**: Added LINSTOR scheduler extender for optimal pod placement on nodes with LINSTOR storage. Includes admission webhook that automatically routes pods using LINSTOR CSI volumes to the custom scheduler, ensuring pods are placed on nodes with local replicas to minimize network traffic and improve I/O performance ([**@kvaps**](https://github.com/kvaps) in #1824).
* **[linstor] Enable auto-diskful for diskless nodes**: Enabled DRBD auto-diskful functionality to automatically convert diskless nodes to diskful when they hold volumes in Primary state for more than 30 minutes. Improves I/O performance for long-running workloads by creating local replicas and includes automatic cleanup options to prevent storage waste ([**@kvaps**](https://github.com/kvaps) in #1826).
* **[linstor] Build linstor-server with custom patches**: Added custom patches to linstor-server build process, enabling platform-specific optimizations and fixes ([**@kvaps**](https://github.com/kvaps) in #1726).
* **[seaweedfs] Traffic locality**: Upgraded SeaweedFS to v4.05 with traffic locality capabilities, new admin component with web-based UI, worker component for distributed operations, and enhanced S3 monitoring with Grafana dashboards. Improves S3 service performance by routing requests to nearest available volume servers ([**@nbykov0**](https://github.com/nbykov0) in #1748).
* **[linstor] fix: prevent DRBD device race condition in updateDiscGran**: Fixed race condition in DRBD device management during granularity updates, preventing potential data corruption or device conflicts ([**@kvaps**](https://github.com/kvaps) in #1829).
* **fix(linstor): prevent orphaned DRBD devices during toggle-disk retry**: Fixed issue where retry logic during disk toggle operations could leave orphaned DRBD devices, now properly cleans up devices during retry attempts ([**@kvaps**](https://github.com/kvaps) in #1823).
### Platform Architecture
* **[platform] Replace Helm lookup with valuesFrom mechanism**: Replaced Helm lookup functions with FluxCD valuesFrom mechanism for configuration propagation. Configuration from ConfigMaps and namespace references is now managed through `cozystack-values` Secret, simplifying templates and eliminating force reconcile controllers ([**@kvaps**](https://github.com/kvaps) in #1787).
* **[platform] refactor: split cozystack-resource-definitions into separate packages**: Refactored cozystack-resource-definitions into separate packages for better organization and maintainability, improving code structure and reducing coupling between components ([**@kvaps**](https://github.com/kvaps) in #1778).
* **[platform] Separate assets server into dedicated deployment**: Separated assets server from main platform deployment, improving scalability and allowing independent scaling of asset delivery infrastructure ([**@kvaps**](https://github.com/kvaps) in #1705).
* **[core] Extract Talos package from installer**: Extracted Talos package configuration from installer into a separate package, improving modularity and enabling independent updates ([**@kvaps**](https://github.com/kvaps) in #1724).
* **[registry] Add application labels and update filtering mechanism**: Added application labels to registry resources and improved filtering mechanism for better resource discovery and organization ([**@kvaps**](https://github.com/kvaps) in #1707).
* **fix(registry): implement field selector filtering for label-based resources**: Implemented field selector filtering for label-based resources in the registry, improving query performance and resource lookup efficiency ([**@kvaps**](https://github.com/kvaps) in #1845).
* **[platform] Add alphabetical sorting to registry resource lists**: Added alphabetical sorting to registry resource lists in the API and dashboard, improving user experience when browsing available applications ([**@lexfrei**](https://github.com/lexfrei) in #1764).
### Version Management
* **[postgres] Add version management system with automated version updates**: Introduced version management system for PostgreSQL with automated version tracking and update mechanisms ([**@kvaps**](https://github.com/kvaps) in #1671).
* **[kubernetes] Add version management system with automated version updates**: Added version management system for Kubernetes tenant clusters with automated version tracking and update capabilities ([**@kvaps**](https://github.com/kvaps) in #1672).
* **[mariadb] Add version management system with automated version updates**: Implemented version management system for MariaDB with automated version tracking and update mechanisms ([**@kvaps**](https://github.com/kvaps) in #1680).
* **[redis] Add version management system with automated version updates**: Added version management system for Redis with automated version tracking and update capabilities ([**@kvaps**](https://github.com/kvaps) in #1681).
### Networking
* **[kube-ovn] Update to v1.14.25**: Updated Kube-OVN to version 1.14.25 with improved stability and new features ([**@kvaps**](https://github.com/kvaps) in #1819).
* **[kubeovn] Package from external repo**: Extracted Kube-OVN packaging from main repository to external repository, improving modularity ([**@lllamnyp**](https://github.com/lllamnyp) in #1535).
* **[cilium] Update Cilium to v1.18.5**: Updated Cilium to version 1.18.5 with latest features and bug fixes ([**@lexfrei**](https://github.com/lexfrei) in #1769).
* **[system/cilium] Enable topology-aware routing for services**: Enabled topology-aware routing for Cilium services, improving traffic distribution and reducing latency by routing traffic to endpoints in the same zone when possible ([**@nbykov0**](https://github.com/nbykov0) in #1734).
* **[cilium] Enable automatic pod rollout on configmap updates**: Cilium and Cilium operator pods now automatically restart when the cilium-config ConfigMap is updated, ensuring configuration changes are applied immediately ([**@kvaps**](https://github.com/kvaps) in #1728).
* **[kubernetes] Fix endpoints for cilium-gateway**: Fixed endpoint configuration for cilium-gateway, ensuring proper service discovery and connectivity ([**@kvaps**](https://github.com/kvaps) in #1729).
* **[multus] Increase memory limit**: Increased memory limits for Multus components to handle larger network configurations and reduce out-of-memory issues ([**@nbykov0**](https://github.com/nbykov0) in #1773).
* **[main][paas-full] Add multus dependencies similar to other CNIs**: Added Multus as a dependency in the paas-full package, consistent with how other CNIs are included ([**@nbykov0**](https://github.com/nbykov0) in #1842).
### Virtual Machines
* **[vm] Always expose VMs with a service**: Virtual machines are now always exposed with at least a ClusterIP service, ensuring they have in-cluster DNS names and can be accessed from other pods even without public IP addresses ([**@lllamnyp**](https://github.com/lllamnyp) in #1738).
* **[virtual-machine] Improve check for resizing job**: Improved storage resize logic to only expand persistent volume claims when storage is being increased, preventing unintended storage reduction operations ([**@kvaps**](https://github.com/kvaps) in #1688).
* **[virtual-machine,vm-instance] Add nodeAffinity for Windows VMs based on scheduling config**: Added nodeAffinity configuration to virtual-machine and vm-instance charts to support dedicated nodes for Windows VMs ([**@kvaps**](https://github.com/kvaps) in #1693).
### Monitoring
* **[monitoring] Add SLACK_SEVERITY_FILTER field and VMAgent for tenant monitoring**: Introduced SLACK_SEVERITY_FILTER environment variable in Alerta deployment to enable filtering of alert severities for Slack notifications. Added VMAgent resource template for scraping metrics within tenant namespaces, improving monitoring granularity ([**@IvanHunters**](https://github.com/IvanHunters) in #1712).
* **[monitoring] Improve tenant metrics collection**: Improved tenant metrics collection mechanisms for better observability and monitoring coverage ([**@IvanHunters**](https://github.com/IvanHunters) in #1684).
### System Configuration
* **[api, lineage] Tolerate all taints**: Updated API and lineage webhook to tolerate all taints, ensuring controllers can run on any node regardless of taint configuration ([**@nbykov0**](https://github.com/nbykov0) in #1781).
* **[system] Add resource requests and limits to etcd-defrag**: Added resource requests and limits to etcd-defrag job to ensure proper resource allocation and prevent resource contention ([**@matthieu-robin**](https://github.com/matthieu-robin) in #1785).
* **[system:coredns] update coredns app labels to match Talos coredns labels**: Updated coredns app labels to match Talos coredns labels, ensuring consistency across the platform ([**@nbykov0**](https://github.com/nbykov0) in #1675).
* **[system:monitoring-agents] rename coredns metrics service**: Renamed coredns metrics service to avoid interference with coredns service used for name resolution in tenant k8s clusters ([**@nbykov0**](https://github.com/nbykov0) in #1676).
### Tenants and Namespaces
* **[tenant] Allow egress to parent ingress pods**: Updated tenant network policies to allow egress traffic to parent cluster ingress pods, enabling proper communication patterns ([**@lexfrei**](https://github.com/lexfrei) in #1765).
* **[tenant] Run cleanup job from system namespace**: Moved tenant cleanup job to run from system namespace, improving security and resource isolation ([**@lllamnyp**](https://github.com/lllamnyp) in #1774).
### FluxCD
* **[fluxcd] Add flux-aio module and migration**: Added FluxCD all-in-one module with migration support, simplifying FluxCD installation and management ([**@kvaps**](https://github.com/kvaps) in #1698).
* **[fluxcd] Enable source-watcher**: Enabled source-watcher in FluxCD configuration for improved GitOps synchronization and faster update detection ([**@kvaps**](https://github.com/kvaps) in #1706).
### Applications
* **[dashboard] Fix CustomFormsOverride schema to nest properties under spec.properties**: Fixed CustomFormsOverride schema generation to properly nest properties under `spec.properties` instead of directly under `properties`, ensuring correct form schema generation ([**@kvaps**](https://github.com/kvaps) in #1692).
* **[apps] Refactor apiserver to use typed objects and fix UnstructuredList GVK**: Refactored apiserver REST handlers to use typed objects instead of unstructured.Unstructured, eliminating runtime conversions. Fixed UnstructuredList GVK issue where objects were using the first registered kind instead of the correct kind ([**@kvaps**](https://github.com/kvaps) in #1679).
* **[keycloak] Make kubernetes client public**: Made Kubernetes client public in Keycloak configuration, enabling broader access patterns for Kubernetes integrations ([**@lllamnyp**](https://github.com/lllamnyp) in #1802).
## Improvements
* **[granular kubernetes application extensions dependencies]**: Improved dependency management for Kubernetes application extensions with more granular control over dependencies ([**@nbykov0**](https://github.com/nbykov0) in #1683).
* **[core:installer] Address buildx warnings**: Fixed Dockerfile syntax warnings from buildx, ensuring clean builds without warnings ([**@nbykov0**](https://github.com/nbykov0) in #1682).
* **[linstor] Update piraeus-operator v2.10.2**: Updated LINSTOR CSI to version 2.10.2 with improved stability and bug fixes ([**@kvaps**](https://github.com/kvaps) in #1689).
* **Update SeaweedFS v4.02**: Updated SeaweedFS to version 4.02 with improved S3 daemon performance and fixes ([**@kvaps**](https://github.com/kvaps) in #1725).
* **[installer,dx] Rename cozypkg to cozyhr**: Renamed cozypkg tool to cozyhr for better branding and consistency ([**@kvaps**](https://github.com/kvaps) in #1763).
## Fixes
* **fix(platform): fix migrations for v0.40 release**: Fixed platform migrations for v0.40 release, ensuring smooth upgrades from previous versions ([**@kvaps**](https://github.com/kvaps) in #1846).
* **[platform] fix migration for removing fluxcd-operator**: Fixed migration logic for removing fluxcd-operator, ensuring clean removal without leaving orphaned resources ([**@kvaps**](https://github.com/kvaps) in commit 4a83d2c7).
* **[kubevirt-operator] Fix typo in VMNotRunningFor10Minutes alert**: Fixed typo in VM alert name, ensuring proper alert triggering and monitoring ([**@kvaps**](https://github.com/kvaps) in #1770).
* **[kubevirt-operator] Revert incorrect case change in VM alerts**: Reverted incorrect case change in VM alert names to maintain consistency ([**@lexfrei**](https://github.com/lexfrei) in #1804).
* **[cozystack-controller] Fix: move crds to definitions**: Fixed CRD placement by moving them to definitions directory, ensuring proper resource organization ([**@kvaps**](https://github.com/kvaps) in #1759).
## Dependencies
* **Update SeaweedFS v4.02**: Updated SeaweedFS to version 4.02 ([**@kvaps**](https://github.com/kvaps) in #1725).
* **[seaweedfs] Traffic locality**: Upgraded SeaweedFS to v4.05 with traffic locality capabilities ([**@nbykov0**](https://github.com/nbykov0) in #1748).
* **[linstor] Update piraeus-operator v2.10.2**: Updated piraeus-operator to version 2.10.2 ([**@kvaps**](https://github.com/kvaps) in #1689).
* **[kube-ovn] Update to v1.14.25**: Updated Kube-OVN to version 1.14.25 ([**@kvaps**](https://github.com/kvaps) in #1819).
* **[cilium] Update Cilium to v1.18.5**: Updated Cilium to version 1.18.5 ([**@lexfrei**](https://github.com/lexfrei) in #1769).
* **Update go modules**: Updated Go modules to latest versions ([**@kvaps**](https://github.com/kvaps) in #1736).
## Development, Testing, and CI/CD
* **[ci] Fix auto-release workflow**: Fixed auto-release workflow to ensure correct release publishing and tagging ([**@kvaps**](https://github.com/kvaps) in commit 526af294).
* **fix(ci): ensure correct latest release after backport publishing**: Fixed CI workflow to correctly identify and tag the latest release after backport publishing ([**@kvaps**](https://github.com/kvaps) in #1800).
* **[workflows] Add auto patch release workflow**: Added automated patch release workflow for streamlined release management ([**@kvaps**](https://github.com/kvaps) in #1754).
* **[workflow] Add GitHub Action to update release notes from changelogs**: Added GitHub Action to automatically update release notes from changelog files ([**@kvaps**](https://github.com/kvaps) in #1752).
* **[ci] Improve backport workflow with merge_commits skip and conflict resolution**: Improved backport workflow with better merge commit handling and conflict resolution ([**@kvaps**](https://github.com/kvaps) in #1694).
* **[testing] Add aliases and autocomplete**: Added shell aliases and autocomplete support for testing commands, improving developer experience ([**@lllamnyp**](https://github.com/lllamnyp) in #1803).
* **[kubernetes] Add lb tests for tenant k8s**: Added load balancer tests for tenant Kubernetes clusters, improving test coverage ([**@IvanHunters**](https://github.com/IvanHunters) in #1783).
* **[agents] Add instructions for working with unresolved code review comments**: Added documentation and instructions for working with unresolved code review comments in agent workflows ([**@kvaps**](https://github.com/kvaps) in #1710).
* **feat(ci): add /retest command to rerun tests from Prepare environment**: Added `/retest` command to rerun tests from Prepare environment workflow ([**@kvaps**](https://github.com/kvaps) in commit 30c1041e).
* **fix(ci): remove GITHUB_TOKEN extraheader to trigger workflows**: Removed GITHUB_TOKEN extraheader to properly trigger workflows ([**@kvaps**](https://github.com/kvaps) in commit 68a639b3).
* **Fix: Add missing components to `distro-full` bundle**: Fixed missing components in distro-full bundle, ensuring all required components are included ([**@LoneExile**](https://github.com/LoneExile) in #1620).
* **Update Flux Operator (v0.33.0)**: Updated Flux Operator to version 0.33.0 ([**@kingdonb**](https://github.com/kingdonb) in #1649).
* **Add changelogs for v0.38.3 and v.0.38.4**: Added missing changelogs for v0.38.3 and v0.38.4 releases ([**@androndo**](https://github.com/androndo) in #1743).
* **Add changelogs to v.0.39.1**: Added changelog for v0.39.1 release ([**@androndo**](https://github.com/androndo) in #1750).
* **Add Cloupard to ADOPTERS.md**: Added Cloupard to the adopters list ([**@SerjioTT**](https://github.com/SerjioTT) in #1733).
## Documentation
* **[website] docs: expand monitoring and alerting documentation**: Expanded monitoring and alerting documentation with comprehensive guides, examples, and troubleshooting information ([**@IvanHunters**](https://github.com/IvanHunters) in [cozystack/website#388](https://github.com/cozystack/website/pull/388)).
* **[website] fix auto-generation of documentation**: Fixed automatic documentation generation process, ensuring all documentation is properly generated and formatted ([**@IvanHunters**](https://github.com/IvanHunters) in [cozystack/website#391](https://github.com/cozystack/website/pull/391)).
* **[website] secure boot**: Added documentation for Secure Boot support in Talos Linux ([**@kvaps**](https://github.com/kvaps) in [cozystack/website#387](https://github.com/cozystack/website/pull/387)).
## Tools
* **[talm] feat(helpers): add bond interface discovery helpers**: Added bond interface discovery helpers to talm for easier network configuration ([**@kvaps**](https://github.com/kvaps) in [cozystack/talm#94](https://github.com/cozystack/talm/pull/94)).
* **[talm] feat(talosconfig): add certificate regeneration from secrets.yaml**: Added certificate regeneration functionality to talm talosconfig command, allowing certificates to be regenerated from secrets.yaml ([**@kvaps**](https://github.com/kvaps) in cozystack/talm@1319dde).
* **[talm] fix(init): make name optional for -u flag**: Made name parameter optional for init command with -u flag, improving flexibility ([**@kvaps**](https://github.com/kvaps) in cozystack/talm@da29320).
* **[talm] fix(wrapper): copy NoOptDefVal when remapping -f to -F flag**: Fixed wrapper to properly copy NoOptDefVal when remapping flags, ensuring correct default value handling ([**@kvaps**](https://github.com/kvaps) in cozystack/talm@f6a6f1d).
* **[talm] fix(root): detect project root with secrets.encrypted.yaml**: Fixed root detection to properly identify project root when secrets.encrypted.yaml is present ([**@kvaps**](https://github.com/kvaps) in cozystack/talm@cf56780).
* **[talm] Fix interfaces helper for Talos v1.12**: Fixed interfaces helper to work correctly with Talos v1.12 ([**@kvaps**](https://github.com/kvaps) in cozystack/talm@34984ae).
* **[talm] Fix typo on README.md**: Fixed typo in README documentation ([**@diegolakatos**](https://github.com/diegolakatos) in [cozystack/talm#92](https://github.com/cozystack/talm/pull/92)).
* **[talm] fix(template): return error for invalid YAML in template output**: Fixed template command to return proper error for invalid YAML output ([**@kvaps**](https://github.com/kvaps) in [cozystack/talm#93](https://github.com/cozystack/talm/pull/93)).
* **[talm] feat(cozystack): enable allocateNodeCIDRs by default**: Enabled allocateNodeCIDRs by default in talm cozystack preset ([**@lexfrei**](https://github.com/lexfrei) in [cozystack/talm#91](https://github.com/cozystack/talm/pull/91)).
* **[boot-to-talos] feat(network): add VLAN interface support via netlink**: Added VLAN interface support via netlink in boot-to-talos for advanced network configuration ([**@kvaps**](https://github.com/kvaps) in cozystack/boot-to-talos@02874d7).
* **[boot-to-talos] feat(network): add bond interface support via netlink**: Added bond interface support via netlink in boot-to-talos for network bonding configurations ([**@kvaps**](https://github.com/kvaps) in cozystack/boot-to-talos@067822d).
* **[boot-to-talos] Draft EFI Support**: Added draft EFI support in boot-to-talos for UEFI boot scenarios ([**@kvaps**](https://github.com/kvaps) in cozystack/boot-to-talos@e194bc8).
* **[boot-to-talos] Change default install image size from 2GB to 3GB**: Changed default install image size from 2GB to 3GB to accommodate larger installations ([**@kvaps**](https://github.com/kvaps) in cozystack/boot-to-talos@3bfb035).
* **[cozyhr] feat(values): add valuesFrom support for HelmRelease**: Added valuesFrom support for HelmRelease in cozyhr tool, enabling better configuration management ([**@kvaps**](https://github.com/kvaps) in cozystack/cozyhr@7dff0c8).
* **[cozyhr] Rename cozypkg to cozyhr**: Renamed cozypkg tool to cozyhr for better branding ([**@kvaps**](https://github.com/kvaps) in cozystack/cozyhr@1029461).
---
## Contributors
We'd like to thank all contributors who made this release possible:
* [**@IvanHunters**](https://github.com/IvanHunters)
* [**@kvaps**](https://github.com/kvaps)
* [**@lexfrei**](https://github.com/lexfrei)
* [**@lllamnyp**](https://github.com/lllamnyp)
* [**@nbykov0**](https://github.com/nbykov0)
* [**@LoneExile**](https://github.com/LoneExile)
* [**@kingdonb**](https://github.com/kingdonb)
* [**@androndo**](https://github.com/androndo)
* [**@SerjioTT**](https://github.com/SerjioTT)
* [**@matthieu-robin**](https://github.com/matthieu-robin)
* [**@diegolakatos**](https://github.com/diegolakatos)
---
**Full Changelog**: [v0.39.0...v0.40.0](https://github.com/cozystack/cozystack/compare/v0.39.0...v0.40.0)
<!--
https://github.com/cozystack/cozystack/releases/tag/v0.40.0
-->

View File

@@ -1,11 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v0.40.1
-->
## Fixes
* **[linstor] Update piraeus-server patches with critical fixes**: Backported critical patches to piraeus-server that address storage stability issues and improve DRBD resource handling. These patches fix edge cases in device management and ensure more reliable storage operations ([**@kvaps**](https://github.com/kvaps) in #1850, #1852).
---
**Full Changelog**: [v0.40.0...v0.40.1](https://github.com/cozystack/cozystack/compare/v0.40.0...v0.40.1)

View File

@@ -1,15 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v0.40.2
-->
## Improvements
* **[linstor] Refactor node-level RWX validation**: Refactored the node-level ReadWriteMany (RWX) validation logic in LINSTOR CSI. The validation has been moved to the CSI driver level with a custom linstor-csi image build, providing more reliable RWX volume handling and clearer error messages when RWX requirements cannot be satisfied ([**@kvaps**](https://github.com/kvaps) in #1856, #1857).
## Fixes
* **[linstor] Remove node-level RWX validation**: Removed the problematic node-level RWX validation that was causing issues with volume provisioning. The validation logic has been refactored and moved to a more appropriate location in the LINSTOR CSI driver ([**@kvaps**](https://github.com/kvaps) in #1851).
---
**Full Changelog**: [v0.40.1...v0.40.2](https://github.com/cozystack/cozystack/compare/v0.40.1...v0.40.2)

View File

@@ -1,15 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v0.40.3
-->
## Fixes
* **[apiserver] Fix Watch resourceVersion and bookmark handling**: Fixed issues with Watch API handling of resourceVersion and bookmarks, ensuring proper event streaming and state synchronization for API clients ([**@kvaps**](https://github.com/kvaps) in #1860).
## Dependencies
* **[cilium] Update Cilium to v1.18.6**: Updated Cilium CNI to v1.18.6 with security fixes and performance improvements ([**@sircthulhu**](https://github.com/sircthulhu) in #1868, #1870).
---
**Full Changelog**: [v0.40.2...v0.40.3](https://github.com/cozystack/cozystack/compare/v0.40.2...v0.40.3)

View File

@@ -1,23 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v0.40.4
-->
## Improvements
* **[kubernetes] Increase default apiServer resourcesPreset to large**: Increased the default resource preset for kube-apiserver to `large` to ensure more reliable operation under higher workloads and prevent resource constraints ([**@kvaps**](https://github.com/kvaps) in #1875, #1882).
* **[kubernetes] Increase kube-apiserver startup probe threshold**: Increased the startup probe threshold for kube-apiserver to allow more time for the API server to become ready, especially in scenarios with slow storage or high load ([**@kvaps**](https://github.com/kvaps) in #1876, #1883).
* **[etcd] Increase probe thresholds for better recovery**: Increased etcd probe thresholds to provide more time for recovery operations, improving cluster resilience during network issues or temporary slowdowns ([**@kvaps**](https://github.com/kvaps) in #1874, #1878).
## Fixes
* **[dashboard] Fix view of loadbalancer IP in services window**: Fixed an issue where load balancer IP addresses were not displayed correctly in the services window of the dashboard ([**@IvanHunters**](https://github.com/IvanHunters) in #1884, #1887).
## Dependencies
* **Update Talos Linux v1.11.6**: Updated Talos Linux to v1.11.6 with latest security patches and improvements ([**@kvaps**](https://github.com/kvaps) in #1879).
---
**Full Changelog**: [v0.40.3...v0.40.4](https://github.com/cozystack/cozystack/compare/v0.40.3...v0.40.4)

View File

@@ -1,15 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v0.40.5
-->
## Improvements
* **[dashboard] Improve dashboard session params**: Improved session parameter handling in the dashboard for better user experience and more reliable session management ([**@lllamnyp**](https://github.com/lllamnyp) in #1913, #1919).
## Dependencies
* **Update cozyhr to v1.6.1**: Updated cozyhr to v1.6.1, which fixes a critical bug causing helm-controller v0.37.0+ to unexpectedly uninstall HelmReleases after cozyhr apply by correcting history snapshot fields for helm-controller compatibility ([**@kvaps**](https://github.com/kvaps) in cozystack/cozyhr#10).
---
**Full Changelog**: [v0.40.4...v0.40.5](https://github.com/cozystack/cozystack/compare/v0.40.4...v0.40.5)

View File

@@ -1,11 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v0.40.6
-->
## Fixes
* **[kubernetes] Fix manifests for kubernetes deployment**: Fixed incorrect manifests that prevented proper Kubernetes deployment, restoring correct application behavior ([**@IvanHunters**](https://github.com/IvanHunters) in #1943, #1944).
---
**Full Changelog**: [v0.40.5...v0.40.6](https://github.com/cozystack/cozystack/compare/v0.40.5...v0.40.6)

View File

@@ -1,11 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v0.40.7
-->
## Security
* **[dashboard] Verify JWT token**: Added JWT token verification to the dashboard, ensuring that authentication tokens are properly validated before granting access. This prevents unauthorized access through forged or expired tokens ([**@lllamnyp**](https://github.com/lllamnyp) in #1980, #1984).
---
**Full Changelog**: [v0.40.6...v0.40.7](https://github.com/cozystack/cozystack/compare/v0.40.6...v0.40.7)

View File

@@ -1,63 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v0.41.0
-->
# Cozystack v0.41.0 — "MongoDB"
This release introduces MongoDB as a new managed application, expanding Cozystack's database offerings alongside existing PostgreSQL, MySQL, and Redis services. The release also includes storage improvements, Kubernetes stability enhancements, and updated documentation.
## Feature Highlights
### MongoDB Managed Application
Cozystack now includes MongoDB as a fully managed database service. Users can deploy production-ready MongoDB instances directly from the application catalog with minimal configuration.
Key capabilities:
- **Replica Set deployment**: Automatic configuration of MongoDB replica sets for high availability
- **Persistent storage**: Integration with Cozystack storage backends for reliable data persistence
- **Resource management**: Configurable CPU, memory, and storage resources
- **Monitoring integration**: Built-in metrics export for platform monitoring
Deploy MongoDB through the Cozystack dashboard or using the standard application deployment workflow ([**@lexfrei**](https://github.com/lexfrei) in #1822, #1881).
## Improvements
* **[linstor] Update piraeus-server patches with critical fixes**: Backported critical patches to piraeus-server that address storage stability issues and improve DRBD resource handling. These patches fix edge cases in device management and ensure more reliable storage operations ([**@kvaps**](https://github.com/kvaps) in #1850, #1852).
* **[linstor] Refactor node-level RWX validation**: Refactored the node-level ReadWriteMany (RWX) validation logic in LINSTOR CSI. The validation has been moved to the CSI driver level with a custom linstor-csi image build, providing more reliable RWX volume handling and clearer error messages when RWX requirements cannot be satisfied ([**@kvaps**](https://github.com/kvaps) in #1856, #1857).
* **[kubernetes] Increase default apiServer resourcesPreset to large**: Increased the default resource preset for kube-apiserver to `large` to ensure more reliable operation under higher workloads and prevent resource constraints ([**@kvaps**](https://github.com/kvaps) in #1875, #1882).
* **[kubernetes] Increase kube-apiserver startup probe threshold**: Increased the startup probe threshold for kube-apiserver to allow more time for the API server to become ready, especially in scenarios with slow storage or high load ([**@kvaps**](https://github.com/kvaps) in #1876, #1883).
* **[etcd] Increase probe thresholds for better recovery**: Increased etcd probe thresholds to provide more time for recovery operations, improving cluster resilience during network issues or temporary slowdowns ([**@kvaps**](https://github.com/kvaps) in #1874, #1878).
## Fixes
* **[linstor] Remove node-level RWX validation**: Removed the problematic node-level RWX validation that was causing issues with volume provisioning. The validation logic has been refactored and moved to a more appropriate location in the LINSTOR CSI driver ([**@kvaps**](https://github.com/kvaps) in #1851).
* **[apiserver] Fix Watch resourceVersion and bookmark handling**: Fixed issues with Watch API handling of resourceVersion and bookmarks, ensuring proper event streaming and state synchronization for API clients ([**@kvaps**](https://github.com/kvaps) in #1860).
* **[dashboard] Fix view of loadbalancer IP in services window**: Fixed an issue where load balancer IP addresses were not displayed correctly in the services window of the dashboard ([**@IvanHunters**](https://github.com/IvanHunters) in #1884, #1887).
## Dependencies
* **[cilium] Update cilium to v1.18.6**: Updated Cilium CNI to v1.18.6 with security fixes and performance improvements ([**@sircthulhu**](https://github.com/sircthulhu) in #1868, #1870).
* **Update Talos Linux v1.11.6**: Updated Talos Linux to v1.11.6 with latest security patches and improvements ([**@kvaps**](https://github.com/kvaps) in #1879).
## Documentation
* **[website] Add documentation for creating and managing cloned virtual machines**: Added comprehensive guide for VM cloning operations ([**@sircthulhu**](https://github.com/sircthulhu) in [cozystack/website#401](https://github.com/cozystack/website/pull/401)).
* **[website] Simplify NFS driver setup instructions**: Improved NFS driver setup documentation with clearer instructions ([**@kvaps**](https://github.com/kvaps) in [cozystack/website#399](https://github.com/cozystack/website/pull/399)).
* **[website] Update Talos installation docs for Hetzner and Servers.com**: Updated installation documentation with improved instructions for Hetzner and Servers.com environments ([**@kvaps**](https://github.com/kvaps) in [cozystack/website#395](https://github.com/cozystack/website/pull/395)).
* **[website] Add Hetzner RobotLB documentation**: Added documentation for configuring public IP with Hetzner RobotLB ([**@kvaps**](https://github.com/kvaps) in [cozystack/website#394](https://github.com/cozystack/website/pull/394)).
* **[website] Add Hidora organization support details**: Added Hidora to the support page with organization details ([**@matthieu-robin**](https://github.com/matthieu-robin) in [cozystack/website#397](https://github.com/cozystack/website/pull/397), [cozystack/website#398](https://github.com/cozystack/website/pull/398)).
---
**Full Changelog**: [v0.40.0...v0.41.0](https://github.com/cozystack/cozystack/compare/v0.40.0...v0.41.0)

View File

@@ -1,11 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v0.41.1
-->
## Improvements
* **[kubernetes] Add enum validation for IngressNginx exposeMethod**: Added enum validation for the `exposeMethod` field in IngressNginx configuration, preventing invalid values and improving user experience with clear valid options ([**@sircthulhu**](https://github.com/sircthulhu) in #1895, #1897).
---
**Full Changelog**: [v0.41.0...v0.41.1](https://github.com/cozystack/cozystack/compare/v0.41.0...v0.41.1)

View File

@@ -1,13 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v0.41.2
-->
## Improvements
* **[monitoring-agents] Set minReplicas to 1 for VPA for VMAgent**: Configured VPA (Vertical Pod Autoscaler) to maintain at least 1 replica for VMAgent, ensuring monitoring availability during scaling operations ([**@sircthulhu**](https://github.com/sircthulhu) in #1894, #1905).
* **[mongodb] Remove user-configurable images from MongoDB chart**: Removed user-configurable image options from the MongoDB chart to simplify configuration and ensure consistency with tested image versions ([**@kvaps**](https://github.com/kvaps) in #1901, #1904).
---
**Full Changelog**: [v0.41.1...v0.41.2](https://github.com/cozystack/cozystack/compare/v0.41.1...v0.41.2)

View File

@@ -1,15 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v0.41.3
-->
## Improvements
* **[kubernetes] Show Service and Ingress resources for Kubernetes app in dashboard**: Added visibility of Service and Ingress resources for Kubernetes applications in the dashboard, improving resource management and monitoring capabilities ([**@sircthulhu**](https://github.com/sircthulhu) in #1912, #1915).
## Fixes
* **[dashboard] Fix filtering on Pods tab for Service**: Fixed an issue where pod filtering was not working correctly on the Pods tab when viewing Services in the dashboard ([**@sircthulhu**](https://github.com/sircthulhu) in #1909, #1914).
---
**Full Changelog**: [v0.41.2...v0.41.3](https://github.com/cozystack/cozystack/compare/v0.41.2...v0.41.3)

View File

@@ -1,11 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v0.41.4
-->
## Dependencies
* **Update cozyhr to v1.6.1**: Updated cozyhr to v1.6.1, which fixes a critical bug causing helm-controller v0.37.0+ to unexpectedly uninstall HelmReleases after cozyhr apply by correcting history snapshot fields for helm-controller compatibility ([**@kvaps**](https://github.com/kvaps) in cozystack/cozyhr#10).
---
**Full Changelog**: [v0.41.3...v0.41.4](https://github.com/cozystack/cozystack/compare/v0.41.3...v0.41.4)

View File

@@ -1,21 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v0.41.5
-->
## Features and Improvements
* **[dashboard] Add "Edit" button to all resources**: Added an "Edit" button across all resource views in the dashboard, allowing users to modify resource configurations directly from the UI ([**@sircthulhu**](https://github.com/sircthulhu) in #1928, #1931).
* **[dashboard] Add resource quota usage to tenant details page**: Added resource quota usage display to the tenant details page, giving administrators visibility into how much of allocated resources each tenant is consuming ([**@sircthulhu**](https://github.com/sircthulhu) in #1929, #1932).
* **[branding] Separate values for keycloak**: Separated Keycloak branding values into dedicated configuration, allowing more granular customization of Keycloak appearance without affecting other branding settings ([**@nbykov0**](https://github.com/nbykov0) in #1946).
* **Add instance profile label to workload monitor**: Added instance profile metadata labels to the workload monitor, enabling better resource tracking and monitoring by instance profile type ([**@matthieu-robin**](https://github.com/matthieu-robin) in #1954, #1957).
## Fixes
* **[kubernetes] Fix manifests for kubernetes deployment**: Fixed incorrect manifests that prevented proper Kubernetes deployment, restoring correct application behavior ([**@IvanHunters**](https://github.com/IvanHunters) in #1943, #1945).
---
**Full Changelog**: [v0.41.4...v0.41.5](https://github.com/cozystack/cozystack/compare/v0.41.4...v0.41.5)

View File

@@ -1,17 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v0.41.6
-->
## Improvements
* **[vm] Allow changing field external after creation**: Users can now modify the external network field on virtual machines after initial creation, providing more flexibility in VM networking configuration without requiring recreation ([**@sircthulhu**](https://github.com/sircthulhu) in #1956, #1962).
* **[branding] Separate values for keycloak**: Separated Keycloak branding values into dedicated configuration for more granular customization of Keycloak appearance ([**@nbykov0**](https://github.com/nbykov0) in #1947, #1963).
## Fixes
* **[kubernetes] Fix coredns serviceaccount to match kubernetes bootstrap RBAC**: Configured the CoreDNS chart to create a `kube-dns` ServiceAccount matching the Kubernetes bootstrap ClusterRoleBinding, fixing RBAC errors (`Failed to watch`) when CoreDNS pods restart ([**@mattia-eleuteri**](https://github.com/mattia-eleuteri) in #1958, #1978).
---
**Full Changelog**: [v0.41.5...v0.41.6](https://github.com/cozystack/cozystack/compare/v0.41.5...v0.41.6)

View File

@@ -1,15 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v0.41.7
-->
## Security
* **[dashboard] Verify JWT token**: Added JWT token verification to the dashboard, ensuring that authentication tokens are properly validated before granting access. This prevents unauthorized access through forged or expired tokens ([**@lllamnyp**](https://github.com/lllamnyp) in #1980, #1983).
## Fixes
* **[postgres-operator] Correct PromQL syntax in CNPGClusterOffline alert**: Fixed incorrect PromQL syntax in the `CNPGClusterOffline` alert rule for CloudNativePG, ensuring the alert fires correctly when all instances of a PostgreSQL cluster are offline ([**@mattia-eleuteri**](https://github.com/mattia-eleuteri) in #1981, #1989).
---
**Full Changelog**: [v0.41.6...v0.41.7](https://github.com/cozystack/cozystack/compare/v0.41.6...v0.41.7)

View File

@@ -1,17 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v0.41.8
-->
## Features and Improvements
* **[kubernetes] Auto-enable Gateway API support in cert-manager**: cert-manager now automatically enables `enableGatewayAPI` when the Gateway API addon is active in the Kubernetes application. Users no longer need to manually configure this setting, and the option can still be overridden via `valuesOverride` ([**@kvaps**](https://github.com/kvaps) in #1997, #2012).
* **[vm] Allow switching between instancetype and custom resources**: Users can now switch virtual machines between instancetype-based and custom resource configurations after creation. The upgrade hook atomically patches VM resources, providing more flexibility in adjusting VM sizing without recreation ([**@sircthulhu**](https://github.com/sircthulhu) in #2008, #2013).
## Fixes
* **[dashboard] Add startupProbe to prevent container restarts on slow hardware**: Added `startupProbe` to both `bff` and `web` containers in the dashboard deployment. On slow hardware, kubelet was killing containers because the `livenessProbe` only allowed ~33 seconds for startup. The `startupProbe` gives containers up to 60 seconds to start before `livenessProbe` kicks in ([**@kvaps**](https://github.com/kvaps) in #1996, #2014).
---
**Full Changelog**: [v0.41.7...v0.41.8](https://github.com/cozystack/cozystack/compare/v0.41.7...v0.41.8)

View File

@@ -1,15 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v0.41.9
-->
## Fixes
* **[cozystack-basics] Deny resourcequotas deletion for tenant admin**: Prevented tenant administrators from deleting resource quotas, ensuring that resource limits set by platform administrators cannot be bypassed by tenant-level users ([**@myasnikovdaniil**](https://github.com/myasnikovdaniil) in #2076).
## Dependencies
* **Update Kube-OVN to v1.15.3**: Updated Kube-OVN CNI to v1.15.3 with latest bug fixes and improvements ([**@kvaps**](https://github.com/kvaps)).
---
**Full Changelog**: [v0.41.8...v0.41.9](https://github.com/cozystack/cozystack/compare/v0.41.8...v0.41.9)

View File

@@ -1,134 +0,0 @@
# Cozystack v1.0.0-alpha.1 — "Package-Based Architecture"
This alpha release introduces a fundamental architectural shift from HelmRelease bundles to Package-based deployment managed by the new cozystack-operator. It includes a comprehensive backup system with Velero integration, significant API changes that rename the core CRD, Flux sharding for improved tenant workload distribution, enhanced monitoring capabilities, and various improvements to virtual machines, tenants, and the build workflow.
> **⚠️ Alpha Release Warning**: This is a pre-release version intended for testing and early adoption. Breaking changes may occur before the stable v1.0.0 release.
## Breaking Changes
### API Rename: CozystackResourceDefinition → ApplicationDefinition
The `CozystackResourceDefinition` CRD has been renamed to `ApplicationDefinition` for better clarity and consistency. This change affects:
- All Go types and controller files
- CRD Helm chart renamed from `cozystack-resource-definition-crd` to `application-definition-crd`
- All cozyrds YAML manifests updated to use `kind: ApplicationDefinition`
A migration (v24) is included to handle the transition automatically.
### Package-Based Deployment
The platform now uses Package resources managed by cozystack-operator instead of HelmRelease bundles. Key changes:
- Restructured values.yaml with full configuration support (networking, publishing, authentication, scheduling, branding, resources)
- Added values-isp-full.yaml and values-isp-hosted.yaml for bundle variants
- Package resources replace old HelmRelease templates
- PackageSources moved from sources/ to templates/sources/
- Migration script `hack/migrate-to-version-1.0.sh` provided for converting ConfigMaps to Package resources
---
## Major Features and Improvements
### Cozystack Operator
A new operator has been introduced to manage Package and PackageSource resources, providing declarative package management for the platform:
* **[cozystack-operator] Introduce API objects: packages and packagesources**: Added new CRDs for declarative package management, defining the API for Package and PackageSource resources ([**@kvaps**](https://github.com/kvaps) in #1740).
* **[cozystack-operator] Introduce Cozystack-operator core logic**: Implemented core reconciliation logic for the operator, handling Package and PackageSource lifecycle management ([**@kvaps**](https://github.com/kvaps) in #1741).
* **[cozystack-operator] Add Package and PackageSource reconcilers**: Added controllers for Package and PackageSource resources with full reconciliation support ([**@kvaps**](https://github.com/kvaps) in #1755).
* **[cozystack-operator] Add deployment files**: Added Kubernetes deployment manifests for running cozystack-operator in the cluster ([**@kvaps**](https://github.com/kvaps) in #1761).
* **[platform] Add PackageSources for cozystack-operator**: Added PackageSource definitions for cozystack-operator integration ([**@kvaps**](https://github.com/kvaps) in #1760).
* **[cozypkg] Add tool for managing Package and PackageSources**: Added CLI tool for managing Package and PackageSource resources ([**@kvaps**](https://github.com/kvaps) in #1756).
### Backup System
Comprehensive backup functionality has been added with Velero integration for managing application backups:
* **[backups] Implement core backup Plan controller**: Core controller for managing backup schedules and plans, providing the foundation for backup orchestration ([**@lllamnyp**](https://github.com/lllamnyp) in #1640).
* **[backups] Build and deploy backup controller**: Deployment infrastructure for the backup controller, including container image builds and Kubernetes manifests ([**@lllamnyp**](https://github.com/lllamnyp) in #1685).
* **[backups] Scaffold a backup strategy API group**: Added API group for backup strategies, enabling pluggable backup implementations ([**@lllamnyp**](https://github.com/lllamnyp) in #1687).
* **[backups] Add indices to core backup resources**: Added indices to backup resources for improved query performance ([**@lllamnyp**](https://github.com/lllamnyp) in #1719).
* **[backups] Stub the Job backup strategy controller**: Added stub implementation for Job-based backup strategy ([**@lllamnyp**](https://github.com/lllamnyp) in #1720).
* **[backups] Implement Velero strategy controller**: Integration with Velero for backup operations, enabling enterprise-grade backup capabilities ([**@androndo**](https://github.com/androndo) in #1762).
* **[backups,dashboard] User-facing UI**: Dashboard interface for managing backups and backup jobs, providing visibility into backup status and history ([**@lllamnyp**](https://github.com/lllamnyp) in #1737).
### Platform Architecture
* **[platform] Migrate from HelmRelease bundles to Package-based deployment**: Replaced HelmRelease bundle system with Package resources managed by cozystack-operator. Includes restructured values.yaml with full configuration support and migration tooling ([**@kvaps**](https://github.com/kvaps) in #1816).
* **refactor(api): rename CozystackResourceDefinition to ApplicationDefinition**: Renamed CRD and all related types for better clarity and consistency. Updated all Go types, controllers, and 25+ YAML manifests ([**@kvaps**](https://github.com/kvaps) in #1864).
* **feat(flux): implement flux sharding for tenant HelmReleases**: Added Flux sharding support to distribute tenant HelmRelease reconciliation across multiple controllers, improving scalability in multi-tenant environments ([**@kvaps**](https://github.com/kvaps) in #1816).
* **refactor(installer): migrate installer to cozystack-operator**: Moved installer functionality to cozystack-operator for unified management ([**@kvaps**](https://github.com/kvaps) in #1816).
* **feat(api): add chartRef to ApplicationDefinition**: Added chartRef field to support ExternalArtifact references for flexible chart sourcing ([**@kvaps**](https://github.com/kvaps) in #1816).
* **feat(api): show only hash in version column for applications and modules**: Simplified version display in API responses for cleaner output ([**@kvaps**](https://github.com/kvaps) in #1816).
### Virtual Machines
* **[vm] Always expose VMs with a service**: Virtual machines are now always exposed with at least a ClusterIP service, ensuring they have in-cluster DNS names and can be accessed from other pods even without public IP addresses ([**@lllamnyp**](https://github.com/lllamnyp) in #1738, #1751).
### Monitoring
* **[monitoring] Add SLACK_SEVERITY_FILTER field and VMAgent for tenant monitoring**: Introduced the SLACK_SEVERITY_FILTER environment variable in the Alerta deployment to enable filtering of alert severities for Slack notifications based on the disabledSeverity configuration. Additionally, added a VMAgent resource template for scraping metrics within tenant namespaces, improving monitoring granularity and control ([**@IvanHunters**](https://github.com/IvanHunters) in #1712).
### Tenants
* **[tenant] Allow egress to parent ingress pods**: Updated tenant network policies to allow egress traffic to parent cluster ingress pods, enabling proper communication patterns between tenant namespaces and parent cluster ingress controllers ([**@lexfrei**](https://github.com/lexfrei) in #1765, #1776).
* **[tenant] Run cleanup job from system namespace**: Moved tenant cleanup job to run from system namespace, improving security and resource isolation for tenant cleanup operations ([**@lllamnyp**](https://github.com/lllamnyp) in #1774, #1777).
### System
* **[system] Add resource requests and limits to etcd-defrag**: Added resource requests and limits to etcd-defrag job to ensure proper resource allocation and prevent resource contention during etcd maintenance operations ([**@matthieu-robin**](https://github.com/matthieu-robin) in #1785, #1786).
### Development and Build
* **feat(cozypkg): add cross-platform build targets with version injection**: Added cross-platform build targets (linux/amd64, linux/arm64, darwin/amd64, darwin/arm64) for cozypkg/cozyhr tool with automatic version injection from git tags ([**@kvaps**](https://github.com/kvaps) in #1862).
* **refactor: move scripts to hack directory**: Reorganized scripts to standard hack/ location following Kubernetes project conventions ([**@kvaps**](https://github.com/kvaps) in #1863).
## Fixes
* **fix(talos): skip rebuilding assets if files already exist**: Improved Talos package build process to avoid redundant asset rebuilds when files are already present, reducing build time ([**@kvaps**](https://github.com/kvaps)).
* **[kubevirt-operator] Fix typo in VMNotRunningFor10Minutes alert**: Fixed typo in VM alert name, ensuring proper alert triggering and monitoring for virtual machines that are not running for extended periods ([**@lexfrei**](https://github.com/lexfrei) in #1770, #1775).
* **[backups] Fix malformed glob and split in template**: Fixed malformed glob pattern and split operation in backup template processing ([**@lllamnyp**](https://github.com/lllamnyp) in #1708).
## Documentation
* **[website] docs(storage): simplify NFS driver setup instructions**: Simplified NFS driver setup documentation with clearer instructions ([**@kvaps**](https://github.com/kvaps) in [cozystack/website#399](https://github.com/cozystack/website/pull/399)).
* **[website] Add Hidora organization support details**: Added Hidora to the support page with organization details ([**@matthieu-robin**](https://github.com/matthieu-robin) in [cozystack/website#397](https://github.com/cozystack/website/pull/397)).
* **[website] Update LinkedIn link for Hidora organization**: Updated LinkedIn link for Hidora organization on the support page ([**@matthieu-robin**](https://github.com/matthieu-robin) in [cozystack/website#398](https://github.com/cozystack/website/pull/398)).
---
## Migration Guide
### From v0.38.x / v0.39.x to v1.0.0-alpha.1
1. **Backup your cluster** before upgrading
2. Run the migration script: `hack/migrate-to-version-1.0.sh`
3. The migration will:
- Convert ConfigMaps to Package resources
- Rename CozystackResourceDefinition to ApplicationDefinition
- Update HelmRelease references to use Package-based deployment
### Known Issues
- This is an alpha release; some features may be incomplete or change before stable release
- Migration script should be tested in a non-production environment first
---
## Contributors
We'd like to thank all contributors who made this release possible:
* [**@androndo**](https://github.com/androndo)
* [**@IvanHunters**](https://github.com/IvanHunters)
* [**@kvaps**](https://github.com/kvaps)
* [**@lexfrei**](https://github.com/lexfrei)
* [**@lllamnyp**](https://github.com/lllamnyp)
* [**@matthieu-robin**](https://github.com/matthieu-robin)
---
**Full Changelog**: [v0.38.0...v1.0.0-alpha.1](https://github.com/cozystack/cozystack/compare/v0.38.0...v1.0.0-alpha.1)
<!--
https://github.com/cozystack/cozystack/releases/tag/v1.0.0-alpha.1
-->

View File

@@ -1,71 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v1.0.0-alpha.2
-->
> **⚠️ Alpha Release Warning**: This is a pre-release version intended for testing and early adoption. Breaking changes may occur before the stable v1.0.0 release.
## Major Features and Improvements
### New Applications
* **[apps] Add MongoDB managed application**: Added MongoDB as a new managed application, providing a fully managed MongoDB database with automatic scaling, backups, and high availability support ([**@lexfrei**](https://github.com/lexfrei) in #1822).
### Networking
* **[kilo] Introduce kilo**: Added Kilo WireGuard mesh networking support. Kilo provides secure WireGuard-based VPN mesh for connecting Kubernetes nodes across different networks and regions ([**@kvaps**](https://github.com/kvaps) in #1691).
* **[local-ccm] Add local-ccm package**: Added local cloud controller manager package for managing load balancer services in local/bare-metal environments without a cloud provider ([**@kvaps**](https://github.com/kvaps) in #1831).
### Platform
* **[platform] Add flux-plunger controller**: Added flux-plunger controller to automatically fix stuck HelmRelease errors by cleaning up failed resources and retrying reconciliation ([**@kvaps**](https://github.com/kvaps) in #1843).
* **[platform] Split telemetry between operator and controller**: Separated telemetry collection between cozystack-operator and cozystack-controller for better metrics isolation and monitoring capabilities ([**@kvaps**](https://github.com/kvaps) in #1869).
* **[platform] Remove cozystack.io/ui label**: Cleaned up deprecated `cozystack.io/ui` labels from platform components ([**@kvaps**](https://github.com/kvaps) in #1872).
## Improvements
* **[kubernetes] Increase default apiServer resourcesPreset to large**: Increased the default resource preset for kube-apiserver to `large` to ensure more reliable operation under higher workloads ([**@kvaps**](https://github.com/kvaps) in #1875).
* **[kubernetes] Increase kube-apiserver startup probe threshold**: Increased the startup probe threshold for kube-apiserver to allow more time for the API server to become ready ([**@kvaps**](https://github.com/kvaps) in #1876).
* **[etcd] Increase probe thresholds for better recovery**: Increased etcd probe thresholds to provide more time for recovery operations, improving cluster resilience ([**@kvaps**](https://github.com/kvaps) in #1874).
## Fixes
* **[apiserver] Fix Watch resourceVersion and bookmark handling**: Fixed issues with Watch API handling of resourceVersion and bookmarks, ensuring proper event streaming and state synchronization ([**@kvaps**](https://github.com/kvaps) in #1860).
* **[dashboard] Fix view of loadbalancer IP in services window**: Fixed an issue where load balancer IP addresses were not displayed correctly in the services window of the dashboard ([**@IvanHunters**](https://github.com/IvanHunters) in #1884).
## Dependencies
* **[cilium] Update cilium to v1.18.6**: Updated Cilium CNI to v1.18.6 with security fixes and performance improvements ([**@sircthulhu**](https://github.com/sircthulhu) in #1868).
* **Update Talos Linux v1.12.1**: Updated Talos Linux to v1.12.1 with latest features, security patches and improvements ([**@kvaps**](https://github.com/kvaps) in #1877).
## Documentation
* **[website] Add documentation for creating and managing cloned virtual machines**: Added comprehensive guide for VM cloning operations ([**@sircthulhu**](https://github.com/sircthulhu) in [cozystack/website#401](https://github.com/cozystack/website/pull/401)).
* **[website] Simplify NFS driver setup instructions**: Improved NFS driver setup documentation with clearer instructions ([**@kvaps**](https://github.com/kvaps) in [cozystack/website#399](https://github.com/cozystack/website/pull/399)).
* **[website] Update Talos installation docs for Hetzner and Servers.com**: Updated installation documentation with improved instructions for Hetzner and Servers.com environments ([**@kvaps**](https://github.com/kvaps) in [cozystack/website#395](https://github.com/cozystack/website/pull/395)).
* **[website] Add Hetzner RobotLB documentation**: Added documentation for configuring public IP with Hetzner RobotLB ([**@kvaps**](https://github.com/kvaps) in [cozystack/website#394](https://github.com/cozystack/website/pull/394)).
* **[website] Add Hidora organization support details**: Added Hidora to the support page with organization details ([**@matthieu-robin**](https://github.com/matthieu-robin) in [cozystack/website#397](https://github.com/cozystack/website/pull/397), [cozystack/website#398](https://github.com/cozystack/website/pull/398)).
---
## Contributors
* [**@IvanHunters**](https://github.com/IvanHunters)
* [**@kvaps**](https://github.com/kvaps)
* [**@lexfrei**](https://github.com/lexfrei)
* [**@matthieu-robin**](https://github.com/matthieu-robin)
* [**@sircthulhu**](https://github.com/sircthulhu)
---
**Full Changelog**: [v1.0.0-alpha.1...v1.0.0-alpha.2](https://github.com/cozystack/cozystack/compare/v1.0.0-alpha.1...v1.0.0-alpha.2)

View File

@@ -1,144 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v1.0.0-beta.3
-->
> **⚠️ Beta Release Warning**: This is a pre-release version intended for testing and early adoption. Breaking changes may occur before the stable v1.0.0 release.
## Major Features and Improvements
### New Applications
* **[qdrant] Add Qdrant vector database application**: Added Qdrant as a new managed application, providing a high-performance vector database for AI and machine learning workloads. Supports single replica or clustered mode, persistent storage, resource presets, API key authentication, and optional external LoadBalancer access ([**@lexfrei**](https://github.com/lexfrei) in #1987).
### System Components
* **[system] Add cluster-autoscaler package for Hetzner and Azure**: Added cluster-autoscaler system package with support for multiple cloud providers (Hetzner and Azure) to automatically scale management cluster nodes. Includes comprehensive documentation for Hetzner setup with Talos Linux, vSwitch configuration, and Kilo mesh networking integration ([**@kvaps**](https://github.com/kvaps) in #1964).
* **[system] Add clustersecret-operator package**: Added clustersecret-operator system package for managing secrets across multiple namespaces in Kubernetes clusters ([**@sircthulhu**](https://github.com/sircthulhu) in #2025).
### Networking
* **[kilo] Update to v0.7.0 and add configurable MTU**: Updated Kilo WireGuard mesh networking to v0.7.0 from cozystack fork with pre-built images. Added configurable MTU parameter (default: auto) for WireGuard interface, allowing automatic MTU detection or manual override ([**@kvaps**](https://github.com/kvaps) in #2003).
* **[local-ccm] Add node-lifecycle-controller component**: Added optional node-lifecycle-controller to local-ccm package that automatically deletes unreachable NotReady nodes from the cluster. Solves the "zombie" node problem when cluster autoscaler deletes cloud instances but node objects remain in Kubernetes. Supports configurable node selectors, protected labels, and HA deployment with leader election ([**@IvanHunters**](https://github.com/IvanHunters) in #1992).
### Virtual Machines
* **[vm] Add cpuModel field to specify CPU model without instanceType**: Added cpuModel field to VirtualMachine API, allowing users to specify CPU model directly without using instanceType, providing more granular control over VM CPU configuration ([**@sircthulhu**](https://github.com/sircthulhu) in #2007).
* **[vm] Allow switching between instancetype and custom resources**: Implemented atomic upgrade hook that allows switching between instanceType-based and custom resource-based VM configuration, providing more flexibility in VM resource management ([**@sircthulhu**](https://github.com/sircthulhu) in #2008).
* **[vm] Migrate to runStrategy instead of running**: Migrated VirtualMachine API from deprecated `running` field to `runStrategy` field, following KubeVirt upstream best practices ([**@sircthulhu**](https://github.com/sircthulhu) in #2004).
### Backups
* **[backups] Add comprehensive backup and restore functionality**: Major update to backup system including BackupClass for Velero, virtual machine backup strategies, RestoreJob resource with end-to-end restore workflows, Velero integration with polling and status tracking, and enhanced backup plans UI with simplified Plan/BackupJob API ([**@androndo**](https://github.com/androndo) in #1967, [**@lllamnyp**](https://github.com/lllamnyp) in #1968).
* **[backups] Add kubevirt plugin to velero**: Added KubeVirt plugin to Velero for proper virtual machine backup support, enabling consistent snapshots of VM state and data ([**@lllamnyp**](https://github.com/lllamnyp) in #2017).
* **[backups] Install backupstrategy controller by default**: Enabled backupstrategy controller by default to provide automatic backup scheduling and management for managed applications ([**@lllamnyp**](https://github.com/lllamnyp) in #2020).
* **[backups] Better selectors for VM strategy**: Improved VM backup strategy selectors for more accurate and reliable backup targeting ([**@lllamnyp**](https://github.com/lllamnyp) in #2023).
### Platform
* **[kubernetes] Auto-enable Gateway API support in cert-manager**: Added automatic Gateway API support in cert-manager for tenant Kubernetes clusters, enabling automatic certificate management for Gateway API resources ([**@kvaps**](https://github.com/kvaps) in #1997).
* **[tenant,rbac] Use shared clusterroles**: Refactored tenant RBAC to use shared ClusterRoles, improving maintainability and consistency across tenant namespaces ([**@lllamnyp**](https://github.com/lllamnyp) in #1999).
* **[mongodb] Unify users and databases configuration**: Simplified MongoDB user and database configuration with a more unified API structure ([**@kvaps**](https://github.com/kvaps) in #1923).
## Improvements
* **[keycloak-configure,dashboard] Enable insecure TLS verification by default**: Made SSL certificate verification configurable with insecure mode enabled by default for easier local development and testing ([**@IvanHunters**](https://github.com/IvanHunters) in #2005).
* **[dashboard] Add startupProbe to prevent container restarts on slow hardware**: Added startup probe to dashboard pods to prevent unnecessary container restarts on slow hardware or during high load ([**@kvaps**](https://github.com/kvaps) in #1996).
* **[cilium] Change cilium-operator replicas to 1**: Reduced Cilium operator replicas from 2 to 1 to decrease resource consumption in smaller deployments ([**@IvanHunters**](https://github.com/IvanHunters) in #1784).
* **[monitoring] Enable monitoring for core components**: Enhanced monitoring capabilities with better dashboards and metrics collection for core Cozystack components ([**@IvanHunters**](https://github.com/IvanHunters) in #1937).
* **[branding] Separate values for Keycloak**: Separated Keycloak branding values for better customization capabilities ([**@nbykov0**](https://github.com/nbykov0) in #1947).
* **[kubernetes] Use ingress-nginx nodeport service**: Changed Kubernetes managed clusters to use ingress-nginx NodePort service for improved compatibility and flexibility ([**@sircthulhu**](https://github.com/sircthulhu) in #1948).
## Fixes
* **[linstor] Extract piraeus-operator CRDs into separate package**: Fixed issue where Helm did not reliably install all CRDs from large crds.yaml files by creating dedicated piraeus-operator-crds package. This ensures the linstorsatellites.io CRD is properly installed, preventing satellite pod creation failures ([**@IvanHunters**](https://github.com/IvanHunters) in #1991).
* **[platform] Fix cozystack-values secret race condition**: Fixed race condition in cozystack-values secret creation that could cause platform initialization failures ([**@lllamnyp**](https://github.com/lllamnyp) in #2024).
* **[seaweedfs] Increase certificate duration to 10 years**: Increased SeaweedFS certificate validity from 1 year to 10 years to reduce certificate rotation overhead and prevent unexpected certificate expiration issues ([**@IvanHunters**](https://github.com/IvanHunters) in #1986).
* **[monitoring] Remove cozystack-controller dependency**: Fixed monitoring package to remove unnecessary dependency on cozystack-controller, allowing monitoring to be installed independently ([**@IvanHunters**](https://github.com/IvanHunters) in #1990).
* **[monitoring] Remove duplicate dashboards.list from extra/monitoring**: Fixed duplicate dashboards.list configuration in extra/monitoring package ([**@IvanHunters**](https://github.com/IvanHunters) in #2016).
* **[mongodb] Fix pre-commit check**: Fixed pre-commit linting issues in MongoDB package ([**@kvaps**](https://github.com/kvaps) in #1753).
* **[mongodb] Update MongoDB logo**: Updated MongoDB application logo in the dashboard to use the correct branding ([**@kvaps**](https://github.com/kvaps) in #2027).
* **[bootbox] Auto-create bootbox-application as dependency**: Fixed bootbox package to automatically create required bootbox-application dependency ([**@kvaps**](https://github.com/kvaps) in #1974).
* **[migrations] Add migration 25 for v1.0 upgrade cleanup**: Added migration script to handle cleanup during v1.0 upgrade path ([**@kvaps**](https://github.com/kvaps) in #1975).
* **[build] Fix platform migrations image build**: Fixed Docker image build process for platform migrations ([**@kvaps**](https://github.com/kvaps) in #1976).
* **[postgres-operator] Correct PromQL syntax in CNPGClusterOffline alert**: Fixed incorrect PromQL syntax in CNPGClusterOffline Prometheus alert for PostgreSQL clusters ([**@mattia-eleuteri**](https://github.com/mattia-eleuteri) in #1981).
* **[coredns] Fix serviceaccount to match kubernetes bootstrap RBAC**: Fixed CoreDNS service account configuration to correctly match Kubernetes bootstrap RBAC requirements ([**@mattia-eleuteri**](https://github.com/mattia-eleuteri) in #1958).
* **[dashboard] Verify JWT token**: Added JWT token verification to dashboard for improved security ([**@lllamnyp**](https://github.com/lllamnyp) in #1980).
* **[talm] Skip config loading for completion subcommands**: Fixed talm CLI to skip unnecessary config loading for shell completion commands ([**@kitsunoff**](https://github.com/kitsunoff) in [cozystack/talm#109](https://github.com/cozystack/talm/pull/109)).
## Dependencies
* **[kube-ovn] Update Kube-OVN to v1.15.3**: Updated Kube-OVN CNI to v1.15.3 with performance improvements and bug fixes ([**@kvaps**](https://github.com/kvaps) in #2022).
* **[local-ccm] Update to v0.3.0**: Updated local cloud controller manager to v0.3.0 with node-lifecycle-controller support ([**@kvaps**](https://github.com/kvaps) in #1992).
* **[kilo] Update to v0.7.0**: Updated Kilo to v0.7.0 from cozystack fork with improved MTU handling ([**@kvaps**](https://github.com/kvaps) in #2003).
## Development, Testing, and CI/CD
* **[ci] Use GitHub Copilot CLI for changelog generation**: Automated changelog generation using GitHub Copilot CLI to improve release process efficiency ([**@androndo**](https://github.com/androndo) in #1753).
* **[ci] Choose runner conditional on label**: Added conditional runner selection in CI based on PR labels for more flexible CI/CD workflows ([**@lllamnyp**](https://github.com/lllamnyp) in #1998).
* **[backups] Add restore jobs controller**: Added controller for managing backup restore jobs ([**@androndo**](https://github.com/androndo) in #1811).
* **Update CODEOWNERS**: Updated CODEOWNERS file to include new maintainers ([**@lllamnyp**](https://github.com/lllamnyp) in #1972, [**@IvanHunters**](https://github.com/IvanHunters) in #2015).
## Documentation
* **[website] Add LINSTOR disk preparation guide**: Added comprehensive documentation for preparing disks for LINSTOR storage system ([**@IvanHunters**](https://github.com/IvanHunters) in [cozystack/website#411](https://github.com/cozystack/website/pull/411)).
* **[website] Add Proxmox VM migration guide**: Added detailed guide for migrating virtual machines from Proxmox to Cozystack ([**@IvanHunters**](https://github.com/IvanHunters) in [cozystack/website#410](https://github.com/cozystack/website/pull/410)).
* **[website] Describe operator-based and HelmRelease-based package patterns**: Added development documentation explaining operator-based and HelmRelease-based package patterns for Cozystack ([**@kvaps**](https://github.com/kvaps) in [cozystack/website#413](https://github.com/cozystack/website/pull/413)).
* **[website] Correct typo in kubeconfig reference in Kubernetes installation guide**: Fixed documentation typo in kubeconfig reference ([**@shkarface**](https://github.com/shkarface) in [cozystack/website#414](https://github.com/cozystack/website/pull/414)).
* **[website] Check quotas before an upgrade**: Added troubleshooting documentation for checking resource quotas before performing upgrades ([**@nbykov0**](https://github.com/nbykov0) in [cozystack/website#405](https://github.com/cozystack/website/pull/405)).
---
## Contributors
We'd like to thank all contributors who made this release possible:
* [**@IvanHunters**](https://github.com/IvanHunters)
* [**@androndo**](https://github.com/androndo)
* [**@kitsunoff**](https://github.com/kitsunoff)
* [**@kvaps**](https://github.com/kvaps)
* [**@lexfrei**](https://github.com/lexfrei)
* [**@lllamnyp**](https://github.com/lllamnyp)
* [**@mattia-eleuteri**](https://github.com/mattia-eleuteri)
* [**@nbykov0**](https://github.com/nbykov0)
* [**@shkarface**](https://github.com/shkarface)
* [**@sircthulhu**](https://github.com/sircthulhu)
---
**Full Changelog**: [v1.0.0-beta.2...v1.0.0-beta.3](https://github.com/cozystack/cozystack/compare/v1.0.0-beta.2...v1.0.0-beta.3)

View File

@@ -1,96 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v1.0.0-beta.4
-->
> **⚠️ Beta Release Warning**: This is a pre-release version intended for testing and early adoption. Breaking changes may occur before the stable v1.0.0 release.
## Major Features and Improvements
### Virtual Machines
* **[vm-instance] Complete migration from virtual-machine to vm-disk and vm-instance**: Completed the architectural redesign of virtual machine management by fully migrating from the legacy `virtual-machine` application to the new `vm-disk` and `vm-instance` applications. This includes automatic migration scripts (migration 28) that convert existing virtual machines, handle CDI webhook configurations, and update cloud-init references. The new architecture provides better separation of concerns between disk management and VM lifecycle, enabling more flexible VM configuration and improved resource management ([**@kvaps**](https://github.com/kvaps) in #2040).
* **[vm-instance] Port advanced VM features**: Ported critical VM features from the legacy virtual-machine application including cpuModel field for direct CPU model specification, support for switching between instanceType and custom resource configurations, and migration from deprecated `running` field to `runStrategy` field following KubeVirt best practices ([**@kvaps**](https://github.com/kvaps) in #2040).
### Storage and CSI
* **[kubevirt-csi-driver] Add RWX Filesystem (NFS) support**: Added Read-Write-Many (RWX) filesystem support to kubevirt-csi-driver, enabling multiple pods to mount the same persistent volume simultaneously via NFS. This provides native NFS support for shared storage use cases without requiring external NFS provisioners, with automatic NFS server deployment per PVC and seamless integration with KubeVirt's storage layer ([**@kvaps**](https://github.com/kvaps) in #2042).
### Platform and Infrastructure
* **[cozystack-api] Switch from DaemonSet to Deployment**: Migrated cozystack-api from DaemonSet to Deployment with PreferClose topology spread constraints, improving resource efficiency while maintaining high availability. The Deployment approach reduces resource consumption compared to running API pods on every node, while topology spreading ensures resilient pod placement across the cluster ([**@kvaps**](https://github.com/kvaps) in #2041, #2048).
* **[linstor] Move CRDs installation to dedicated chart**: Refactored LINSTOR CRDs installation by moving them to a dedicated `piraeus-operator-crds` chart, solving Helm's limitation with large CRD files that could cause unreliable installations. This ensures all LINSTOR CRDs (including linstorsatellites.io) are properly installed before the operator starts, preventing satellite pod creation failures. Includes automatic migration script to reassign existing CRDs to the new chart ([**@kvaps**](https://github.com/kvaps) in #2036).
* **[installer] Unify operator templates**: Merged separate operator templates into a single variant-based template, simplifying the installation process and reducing configuration duplication. The new template supports different deployment variants (Talos, non-Talos) through a unified configuration approach ([**@kvaps**](https://github.com/kvaps) in #2034).
### Applications
* **[mariadb] Rename mysql application to mariadb**: Renamed the MySQL application to MariaDB to accurately reflect the underlying database engine being used. Includes automatic migration script (migration 27) that handles resource renaming and ensures seamless upgrade path for existing MySQL deployments. All resources, including databases, users, backups, and configurations, are automatically migrated to use the mariadb naming ([**@kvaps**](https://github.com/kvaps) in #2026).
* **[ferretdb] Remove FerretDB application**: Removed the FerretDB application from the catalog as it has been superseded by native MongoDB support with improved performance and features ([**@kvaps**](https://github.com/kvaps) in #2028).
## Improvements
* **[rbac] Use hierarchical naming scheme**: Refactored RBAC configuration to use hierarchical naming scheme for cluster roles and role bindings, improving organization and maintainability of permission structures across the platform ([**@lllamnyp**](https://github.com/lllamnyp) in #2019).
* **[backups] Create RBAC for backup resources**: Added comprehensive RBAC configuration for backup resources, enabling proper permission management for backup operations and restore jobs across different user roles ([**@lllamnyp**](https://github.com/lllamnyp) in #2018).
* **[etcd-operator] Add vertical-pod-autoscaler dependency**: Added vertical-pod-autoscaler as a dependency to etcd-operator package, ensuring proper resource scaling and optimization for etcd clusters ([**@sircthulhu**](https://github.com/sircthulhu) in #2047).
## Fixes
* **[cozystack-operator] Preserve existing suspend field in package reconciler**: Fixed package reconciler to properly preserve the existing suspend field state during reconciliation, preventing unintended resumption of suspended packages ([**@sircthulhu**](https://github.com/sircthulhu) in #2043).
* **[cozystack-operator] Fix namespace privileged flag resolution**: Fixed operator to correctly resolve namespace privileged flag by checking all Packages in the namespace, not just the first one. This ensures namespaces are properly marked as privileged when any package requires elevated permissions ([**@kvaps**](https://github.com/kvaps) in #2046).
* **[cozystack-operator] Fix namespace reconciliation field ownership**: Fixed Server-Side Apply (SSA) field ownership conflicts by using per-Package field owner for namespace reconciliation, preventing conflicts when multiple packages reconcile the same namespace ([**@kvaps**](https://github.com/kvaps) in #2046).
* **[platform] Clean up Helm secrets for removed releases**: Added cleanup logic to migration 23 to remove orphaned Helm secrets from removed -rd releases, preventing secret accumulation and reducing cluster resource usage ([**@kvaps**](https://github.com/kvaps) in #2035).
* **[monitoring] Fix YAML parse error in vmagent template**: Fixed YAML parsing error in monitoring-agents vmagent template that could cause monitoring stack deployment failures ([**@kvaps**](https://github.com/kvaps) in #2037).
* **[talm] Fix metadata.id type casting in physical_links_info**: Fixed Prometheus query in physical_links_info chart to properly cast metadata.id to string for regexMatch operations, preventing query failures with numeric interface IDs ([**@kvaps**](https://github.com/kvaps) in cozystack/talm#110).
## Dependencies
* **[kilo] Update to v0.7.1**: Updated Kilo WireGuard mesh networking to v0.7.1 with bug fixes and improvements ([**@kvaps**](https://github.com/kvaps) in #2049).
## Development, Testing, and CI/CD
* **[ci] Improve cozyreport functionality**: Enhanced cozyreport tool with improved reporting capabilities for CI/CD pipelines, providing better visibility into test results and build status ([**@lllamnyp**](https://github.com/lllamnyp) in #2032).
* **[e2e] Increase HelmRelease readiness timeout for kubernetes test**: Increased HelmRelease readiness timeout in Kubernetes end-to-end tests to prevent false failures on slower hardware or during high load conditions, specifically targeting ingress-nginx component which may take longer to become ready ([**@lexfrei**](https://github.com/lexfrei) in #2033).
## Documentation
* **[website] Add documentation versioning**: Implemented comprehensive documentation versioning system with separate v0 and v1 documentation trees, version selector in the UI, proper URL redirects for unversioned docs, and improved navigation for users working with different Cozystack versions ([**@IvanStukov**](https://github.com/IvanStukov) in cozystack/website#415).
* **[website] Describe upgrade to v1.0**: Added detailed upgrade instructions for migrating from v0.x to v1.0, including prerequisites, upgrade steps, and troubleshooting guidance ([**@nbykov0**](https://github.com/nbykov0) in cozystack/website@21bbe84).
* **[website] Update support documentation**: Updated support documentation with current contact information and support channels ([**@xrmtech-isk**](https://github.com/xrmtech-isk) in cozystack/website#420).
---
## Contributors
We'd like to thank all contributors who made this release possible:
* [**@IvanStukov**](https://github.com/IvanStukov)
* [**@kvaps**](https://github.com/kvaps)
* [**@lexfrei**](https://github.com/lexfrei)
* [**@lllamnyp**](https://github.com/lllamnyp)
* [**@nbykov0**](https://github.com/nbykov0)
* [**@sircthulhu**](https://github.com/sircthulhu)
* [**@xrmtech-isk**](https://github.com/xrmtech-isk)
### New Contributors
We're excited to welcome our first-time contributors:
* [**@IvanStukov**](https://github.com/IvanStukov) - First contribution!
* [**@xrmtech-isk**](https://github.com/xrmtech-isk) - First contribution!
---
**Full Changelog**: [v1.0.0-beta.3...v1.0.0-beta.4](https://github.com/cozystack/cozystack/compare/v1.0.0-beta.3...v1.0.0-beta.4)

View File

@@ -1,36 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v1.0.0-beta.5
-->
> **⚠️ Beta Release Warning**: This is a pre-release version intended for testing and early adoption. Breaking changes may occur before the stable v1.0.0 release.
## Features and Improvements
* **[installer] Add variant-aware templates for generic Kubernetes support**: Extended the installer chart to support generic and hosted Kubernetes deployments via the existing `cozystackOperator.variant` parameter. When using `variant=generic`, the installer now renders separate templates for the Cozystack operator, skipping Talos-specific components. This enables users to deploy Cozystack on standard Kubernetes distributions and hosted Kubernetes services, expanding platform compatibility beyond Talos Linux ([**@lexfrei**](https://github.com/lexfrei) in #2010).
* **[kilo] Add Cilium compatibility variant**: Added a new `cilium` variant to the kilo PackageSource that deploys kilo with the `--compatibility=cilium` flag. This enables Cilium-aware IPIP encapsulation where the outer packet IP matches the inner packet source, allowing Cilium's network policies to function correctly with kilo's WireGuard mesh networking. Users can now run kilo alongside Cilium CNI while maintaining full network policy enforcement capabilities ([**@kvaps**](https://github.com/kvaps) in #2055).
* **[cluster-autoscaler] Enable enforce-node-group-min-size by default**: Enabled the `enforce-node-group-min-size` option for the system cluster-autoscaler chart. This ensures node groups are always scaled up to their configured minimum size, even when current workload demands are lower, preventing unexpected scale-down below minimum thresholds and improving cluster stability for production workloads ([**@kvaps**](https://github.com/kvaps) in #2050).
* **[dashboard] Upgrade dashboard to version 1.4.0**: Updated the Cozystack dashboard to version 1.4.0 with new features and improvements for better user experience and cluster management capabilities ([**@sircthulhu**](https://github.com/sircthulhu) in #2051).
## Breaking Changes & Upgrade Notes
* **[vpc] Migrate subnets definition from map to array format**: Migrated VPC subnets definition from map format (`map[string]Subnet`) to array format (`[]Subnet`) with an explicit `name` field. This aligns VPC subnet definitions with the vm-instance `networks` field pattern and provides more intuitive configuration. Existing VPC deployments are automatically migrated via migration 30, which converts the subnet map to an array while preserving all existing subnet configurations and network connectivity ([**@kvaps**](https://github.com/kvaps) in #2052).
## Dependencies
* **[kilo] Update to v0.8.0**: Updated Kilo WireGuard mesh networking to v0.8.0 with performance improvements, bug fixes, and new compatibility features ([**@kvaps**](https://github.com/kvaps) in #2053).
* **[talm] Skip config loading for __complete command**: Fixed CLI completion behavior by skipping config loading for the `__complete` command, preventing errors during shell completion when configuration files are not available or misconfigured ([**@kitsunoff**](https://github.com/kitsunoff) in cozystack/talm#109).
## Contributors
We'd like to thank all contributors who made this release possible:
* [**@kitsunoff**](https://github.com/kitsunoff)
* [**@kvaps**](https://github.com/kvaps)
* [**@lexfrei**](https://github.com/lexfrei)
* [**@sircthulhu**](https://github.com/sircthulhu)
**Full Changelog**: https://github.com/cozystack/cozystack/compare/v1.0.0-beta.4...v1.0.0-beta.5

View File

@@ -1,46 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v1.0.0-beta.6
-->
> **⚠️ Beta Release Warning**: This is a pre-release version intended for testing and early adoption. Breaking changes may occur before the stable v1.0.0 release.
## Features and Improvements
* **[platform] Add cilium-kilo networking variant**: Added a new `cilium-kilo` networking variant that combines Cilium CNI with Kilo WireGuard mesh overlay. This variant enables `enable-ipip-termination` in Cilium for proper IPIP packet handling and deploys Kilo with `--compatibility=cilium` flag. Users can now select `cilium-kilo` as their networking variant during platform setup, simplifying the multi-location WireGuard setup compared to manually combining Cilium and standalone Kilo ([**@kvaps**](https://github.com/kvaps) in #2064).
* **[nats] Add monitoring**: Added Grafana dashboards for NATS JetStream and server metrics monitoring, along with Prometheus monitoring support with TLS-aware endpoint configuration. Includes updated image customization options (digest and full image name) and component version upgrades for the NATS exporter and utilities. Users now have full observability into NATS message broker performance and health ([**@klinch0**](https://github.com/klinch0) in #1381).
* **[platform] Add DNS-1035 validation for Application names**: Added dynamic DNS-1035 label validation for Application names in the Cozystack API, using `IsDNS1035Label` from `k8s.io/apimachinery`. Validation is performed at creation time and accounts for the root host length to prevent names that would exceed Kubernetes resource naming limits. This prevents creation of resources with invalid names that would fail downstream Kubernetes resource creation ([**@lexfrei**](https://github.com/lexfrei) in #1771).
* **[operator] Add automatic CRD installation at startup**: Added `--install-crds` flag to the Cozystack operator that installs embedded CRD manifests at startup, ensuring CRDs exist before the operator begins reconciliation. CRD manifests are now embedded in the operator binary and verified for consistency with the Helm `crds/` directory via a new CI Makefile check. This eliminates ordering issues during initial cluster setup where CRDs might not yet be present ([**@lexfrei**](https://github.com/lexfrei) in #2060).
## Fixes
* **[platform] Adopt tenant-root into cozystack-basics during migration**: Added migration 31 to adopt existing `tenant-root` Namespace and HelmRelease into the `cozystack-basics` Helm release when upgrading from v0.41.x to v1.0. Previously these resources were applied via `kubectl apply` with no Helm release tracking, causing Helm to treat them as foreign resources and potentially delete them during reconciliation. This migration ensures a safe upgrade path by annotating and labeling these resources for Helm adoption ([**@kvaps**](https://github.com/kvaps) in #2065).
* **[platform] Preserve tenant-root HelmRelease during migration**: Fixed a data-loss risk during migration from v0.41.x to v1.0.0-beta where the `tenant-root` HelmRelease (and the namespace it manages) could be deleted, causing tenant service outages. Added safety annotation to the HelmRelease and lookup logic to preserve current parameters during migration, preventing unwanted deletion of tenant-root resources ([**@sircthulhu**](https://github.com/sircthulhu) in #2063).
* **[codegen] Add gen_client to update-codegen.sh and regenerate applyconfiguration**: Fixed a build error in `pkg/generated/applyconfiguration/utils.go` caused by a reference to `testing.TypeConverter` which was removed in client-go v0.34.1. The root cause was that `hack/update-codegen.sh` never called `gen_client`, leaving the generated applyconfiguration code stale. Running the full code generation now produces a consistent and compilable codebase ([**@lexfrei**](https://github.com/lexfrei) in #2061).
* **[e2e] Make kubernetes test retries effective by cleaning up stale resources**: Fixed E2E test retries for the Kubernetes tenant test by adding pre-creation cleanup of backend deployment/service and NFS pod/PVC in `run-kubernetes.sh`. Previously, retries would fail immediately because stale resources from a failed attempt blocked re-creation. Also increased the tenant deployment wait timeout from 90s to 300s to handle CI resource pressure ([**@lexfrei**](https://github.com/lexfrei) in #2062).
## Development, Testing, and CI/CD
* **[e2e] Use helm install instead of kubectl apply for cozystack installation**: Replaced the pre-rendered static YAML application flow (`kubectl apply`) with direct `helm upgrade --install` of the `packages/core/installer` chart in E2E tests. Removed the CRD/operator artifact upload/download steps from the CI workflow, simplifying the pipeline. The chart with correct values is already present in the sandbox via workspace copy and `pr.patch` ([**@lexfrei**](https://github.com/lexfrei) in #2060).
## Documentation
* **[website] Improve Azure autoscaling troubleshooting guide**: Enhanced the Azure autoscaling troubleshooting documentation with serial console instructions for debugging VMSS worker nodes, a troubleshooting section for nodes stuck in maintenance mode due to invalid or missing machine config, `az vmss update --custom-data` instructions for updating machine config, and a warning that Azure does not support reading back `customData` ([**@kvaps**](https://github.com/kvaps) in cozystack/website#424).
* **[website] Update multi-location documentation for cilium-kilo variant**: Updated multi-location networking documentation to reflect the new integrated `cilium-kilo` variant selection during platform setup, replacing the previous manual Kilo installation and Cilium configuration steps. Added explanation of `enable-ipip-termination` and updated the troubleshooting section ([**@kvaps**](https://github.com/kvaps) in cozystack/website@02d63f0).
## Contributors
We'd like to thank all contributors who made this release possible:
* [**@klinch0**](https://github.com/klinch0)
* [**@kvaps**](https://github.com/kvaps)
* [**@lexfrei**](https://github.com/lexfrei)
* [**@sircthulhu**](https://github.com/sircthulhu)
**Full Changelog**: https://github.com/cozystack/cozystack/compare/v1.0.0-beta.5...v1.0.0-beta.6

View File

@@ -1,65 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v1.0.0-rc.1
-->
> **⚠️ Release Candidate Warning**: This is a release candidate intended for final validation before the stable v1.0.0 release. Breaking changes are not expected at this stage, but please test thoroughly before deploying to production.
## Features and Improvements
* **[harbor] Add managed Harbor container registry**: Added Harbor v2.14.2 as a managed tenant-level container registry service. The application uses CloudNativePG for PostgreSQL, the Redis operator for caching, and S3 via COSI BucketClaim (from SeaweedFS) for registry image storage. Auto-generated admin credentials are persisted across upgrades, TLS is handled by cert-manager, and Trivy vulnerability scanner is included. Users can now deploy a fully managed, production-ready OCI container registry within their tenant ([**@lexfrei**](https://github.com/lexfrei) in #2058).
* **[kubernetes] Update supported Kubernetes versions to v1.30v1.35**: Updated the tenant Kubernetes version matrix to v1.30, v1.31, v1.32, v1.33, v1.34, and v1.35 (now the default). EOL versions v1.28 and v1.29 are removed. Kamaji is updated to edge-26.2.4 with full Kubernetes 1.35 support, and the CAPI Kamaji provider is updated to v0.16.0. A compatibility patch ensures kubelets older than v1.35 are not broken by Kamaji injecting 1.35-specific kubelet fields ([**@lexfrei**](https://github.com/lexfrei) in #2073).
* **[platform] Make cluster issuer name and ACME solver configurable**: Added `publishing.certificates.solver` (`http01` or `dns01`) and `publishing.certificates.issuerName` (default: `letsencrypt-prod`) parameters to the platform chart. This allows operators to point all ingress TLS annotations at any ClusterIssuer — custom ACME, self-signed, or internal CA — without modifying individual package templates. See the Breaking Changes section for the rename from the previous `issuerType` field ([**@myasnikovdaniil**](https://github.com/myasnikovdaniil) in #2077).
* **[dashboard] VMInstance dropdowns for disks and instanceType**: The VM instance creation form now renders API-backed dropdowns for the `instanceType` field (populated from `VirtualMachineClusterInstancetype` cluster resources) and for disk `name` fields (populated from `VMDisk` resources in the same namespace). Default values are read from the ApplicationDefinition's OpenAPI schema. This eliminates manual lookups and reduces misconfiguration when attaching disks or selecting VM instance types ([**@sircthulhu**](https://github.com/sircthulhu) in #2071).
* **[installer] Remove CRDs from Helm chart, delegate lifecycle to operator**: The `cozy-installer` Helm chart no longer ships CRDs in its `crds/` directory. CRD lifecycle is now fully managed by the Cozystack operator via the `--install-crds` flag, which applies embedded CRD manifests on every startup using server-side apply. The platform PackageSource is also created by the operator instead of a Helm template. This ensures CRDs and the PackageSource are always up to date after each operator restart, eliminating stale CRDs from Helm's install-only behavior ([**@lexfrei**](https://github.com/lexfrei) in #2074).
## Fixes
* **[kubevirt] Update KubeVirt to v1.6.4 and CDI to v1.64.0, fix VM pod initialization**: Updated KubeVirt operator to v1.6.4 and CDI operator to v1.64.0, including live migration of existing VMs during the upgrade. Additionally, disabled serial console logging globally via the KubeVirt CR to prevent a known v1.6.x issue ([upstream #15989](https://github.com/kubevirt/kubevirt/issues/15989)) where the `guest-console-log` init container blocked virt-launcher pods from starting, causing all VMs to get stuck in `PodInitializing` state ([**@nbykov0**](https://github.com/nbykov0) in #1833; [**@kvaps**](https://github.com/kvaps) in 7dfb819).
* **[linstor] Fix DRBD+LUKS+STORAGE resource creation failure**: All newly created encrypted volumes were failing because the DRBD `.res` file was never written due to a missing `setExists(true)` call in the `LuksLayer`. Applied the upstream `skip-adjust-when-device-inaccessible` patch ([LINBIT/linstor-server#477](https://github.com/LINBIT/linstor-server/pull/477)) which fixes the root cause and also prevents unnecessary lsblk calls when devices are not yet physically present ([**@kvaps**](https://github.com/kvaps) in #2072).
* **[system] Fix monitoring-agents FQDN resolution for tenant workload clusters**: Monitoring agents (`vmagent`, `fluent-bit`) in tenant workload clusters were failing to deliver metrics and logs because service addresses used short DNS names without the cluster domain suffix. Fixed by appending the configured cluster domain from `_cluster.cluster-domain` (with fallback to `cluster.local`) to all vmagent remoteWrite URLs and fluent-bit output hosts ([**@IvanHunters**](https://github.com/IvanHunters) in #2075).
* **[cozystack-basics] Preserve existing HelmRelease values during reconciliations**: Fixed a data-loss bug where changes made to the `tenant-root` HelmRelease were silently dropped on the next forced or upgrade reconciliation of the `cozystack-basics` HelmRelease. The reconciler now merges new configuration with existing values instead of overwriting them ([**@sircthulhu**](https://github.com/sircthulhu) in #2068).
* **[cozystack-basics] Deny resourcequotas deletion for tenant admin**: Fixed the `cozy:tenant:admin:base` ClusterRole to explicitly deny deletion of `ResourceQuota` objects for tenant admins and superadmins, preventing accidental removal of tenant resource limits ([**@myasnikovdaniil**](https://github.com/myasnikovdaniil) in #2076).
## Breaking Changes & Upgrade Notes
* **[platform] Certificate issuer configuration parameters renamed**: The `publishing.certificates.issuerType` field is renamed to `publishing.certificates.solver`, and the value `cloudflare` is renamed to `dns01` to align with standard ACME terminology. A new `publishing.certificates.issuerName` field (default: `letsencrypt-prod`) is introduced to allow pointing all ingresses at a custom ClusterIssuer. Migration 32 is included and automatically converts existing configurations during upgrade — no manual action is required ([**@myasnikovdaniil**](https://github.com/myasnikovdaniil) in #2077).
## Documentation
* **[website] Migrate ConfigMap references to Platform Package in v1 documentation**: Updated the entire v1 documentation tree to replace legacy ConfigMap-based configuration references with the new Platform Package API, ensuring guides are consistent with the v1 configuration model ([**@sircthulhu**](https://github.com/sircthulhu) in cozystack/website#426).
* **[website] Add generic Kubernetes deployment guide for v1**: Added a new installation guide covering Cozystack deployment on any generic Kubernetes cluster, expanding the set of supported deployment targets beyond provider-specific guides ([**@lexfrei**](https://github.com/lexfrei) in cozystack/website#408).
* **[website] Refactor resource planning documentation**: Improved the resource planning guide with a clearer structure and more comprehensive coverage of planning considerations for Cozystack deployments ([**@IvanStukov**](https://github.com/IvanStukov) in cozystack/website#423).
* **[website] Add ServiceAccount API access documentation and update FAQ**: Added a new article documenting ServiceAccount API access token configuration and updated the FAQ to include related troubleshooting guidance ([**@IvanStukov**](https://github.com/IvanStukov) in cozystack/website#421).
* **[website] Update networking-mesh allowed-location-ips example**: Replaced provider-specific CLI usage with standard `kubectl` commands in the multi-location networking guide's `allowed-location-ips` example, making the documentation more universally applicable ([**@kvaps**](https://github.com/kvaps) in cozystack/website#425).
## Contributors
We'd like to thank all contributors who made this release possible:
* [**@IvanHunters**](https://github.com/IvanHunters)
* [**@IvanStukov**](https://github.com/IvanStukov)
* [**@kvaps**](https://github.com/kvaps)
* [**@lexfrei**](https://github.com/lexfrei)
* [**@myasnikovdaniil**](https://github.com/myasnikovdaniil)
* [**@nbykov0**](https://github.com/nbykov0)
* [**@sircthulhu**](https://github.com/sircthulhu)
### New Contributors
We're excited to welcome our first-time contributors:
* [**@myasnikovdaniil**](https://github.com/myasnikovdaniil) - First contribution!
**Full Changelog**: https://github.com/cozystack/cozystack/compare/v1.0.0-beta.6...v1.0.0-rc.1

View File

@@ -1,57 +0,0 @@
<!--
https://github.com/cozystack/cozystack/releases/tag/v1.0.0-rc.2
-->
> **⚠️ Release Candidate Warning**: This is a release candidate intended for final validation before the stable v1.0.0 release. Breaking changes are not expected at this stage, but please test thoroughly before deploying to production.
## Features and Improvements
* **[keycloak] Allow custom Ingress hostname via values**: Added an `ingress.host` field to the cozy-keycloak chart values, allowing operators to override the default `keycloak.<root-host>` Ingress hostname. The custom hostname is applied to both the Ingress resource and the `KC_HOSTNAME` environment variable in the StatefulSet. When left empty, the original behavior is preserved (fully backward compatible) ([**@sircthulhu**](https://github.com/sircthulhu) in #2101).
## Fixes
* **[platform] Fix upgrade issues in migrations, etcd timeout, and migration script**: Fixed multiple upgrade failures discovered during v0.41.1 → v1.0 upgrade testing. Migration 26 now uses the `cozystack.io/ui=true` label (always present on v0.41.1) instead of the new label that depends on migration 22 having run, and adds robust Helm secret deletion with fallback and verification. Migrations 28 and 29 wrap `grep` calls to prevent `pipefail` exits and fix the reconcile annotation to use RFC3339 format. Migration 27 now skips missing CRDs and adds a name-pattern fallback for Helm secret deletion. The etcd HelmRelease timeout is increased from 10m to 30m to accommodate TLS cert rotation hooks. The `migrate-to-version-1.0.sh` script gains the missing `bundle-disable`, `bundle-enable`, `expose-ingress`, and `expose-services` field mappings ([**@kvaps**](https://github.com/kvaps) in #2096).
* **[platform] Fix orphaned -rd HelmReleases after application renames**: After the `ferretdb→mongodb`, `mysql→mariadb`, and `virtual-machine→vm-disk+vm-instance` renames, the system-level `-rd` HelmReleases in `cozy-system` (`ferretdb-rd`, `mysql-rd`, `virtual-machine-rd`) were left orphaned, referencing ExternalArtifacts that no longer exist and causing persistent reconciliation failures. Migrations 28 and 29 are updated to remove these resources, and migration 33 is added as a safety net for clusters that already passed those migrations ([**@kvaps**](https://github.com/kvaps) in #2102).
* **[monitoring-agents] Fix FQDN resolution regression in tenant workload clusters**: The fix introduced in #2075 used `_cluster.cluster-domain` references in `values.yaml`, but `_cluster` values are not accessible from Helm subchart contexts — meaning fluent-bit received empty hostnames and failed to forward logs. This PR replaces the `_cluster` references with a new `global.clusterDomain` variable (empty by default for management clusters, set to the cluster domain for tenant clusters), which is correctly shared with all subcharts ([**@kvaps**](https://github.com/kvaps) in #2086).
* **[dashboard] Fix legacy templating and cluster identifier in sidebar links**: Standardized the cluster identifier used across dashboard menu links, administration links, and API request paths, resolving incorrect or broken link targets for the Backups and External IPs sidebar sections ([**@androndo**](https://github.com/androndo) in #2093).
* **[dashboard] Fix backupjobs creation form and sidebar backup category identifier**: Fixed the backup job creation form configuration, adding the required Name, Namespace, Plan Name, Application, and Backup Class fields. Fixed the sidebar backup category identifier that was causing incorrect navigation ([**@androndo**](https://github.com/androndo) in #2103).
## Documentation
* **[website] Add Helm chart development principles guide**: Added a new developer guide section documenting Cozystack's four core Helm chart principles: easy upstream updates, local-first artifacts, local dev/test workflow, and no external dependencies ([**@kvaps**](https://github.com/kvaps) in cozystack/website#418).
* **[website] Add network architecture overview**: Added comprehensive network architecture documentation covering the multi-layered networking stack — MetalLB (L2/BGP), Cilium eBPF (kube-proxy replacement), Kube-OVN (centralized IPAM), and tenant isolation with identity-based eBPF policies — with Mermaid diagrams for all major traffic flows ([**@IvanHunters**](https://github.com/IvanHunters) in cozystack/website#422).
* **[website] Update documentation to use jsonpatch for service exposure**: Improved `kubectl patch` commands throughout installation and configuration guides to use JSON Patch `add` operations for extending arrays instead of replacing them wholesale, making the documented commands safer and more precise ([**@sircthulhu**](https://github.com/sircthulhu) in cozystack/website#427).
* **[website] Update certificates section in Platform Package documentation**: Updated the certificate configuration documentation to reflect the new `solver` and `issuerName` fields introduced in v1.0.0-rc.1, replacing the legacy `issuerType` references ([**@myasnikovdaniil**](https://github.com/myasnikovdaniil) in cozystack/website#429).
* **[website] Add tenant Kubernetes cluster log querying guide**: Added documentation for querying logs from tenant Kubernetes clusters in Grafana using VictoriaLogs labels (`tenant`, `kubernetes_namespace_name`, `kubernetes_pod_name`), including the `monitoringAgents` addon prerequisite and step-by-step filtering examples ([**@IvanHunters**](https://github.com/IvanHunters) in cozystack/website#430).
* **[website] Replace non-idempotent commands with idempotent alternatives**: Updated `helm install` to `helm upgrade --install`, `kubectl create -f` to `kubectl apply -f`, and `kubectl create ns` to the dry-run+apply pattern across all installation and deployment guides so commands can be safely re-run ([**@lexfrei**](https://github.com/lexfrei) in cozystack/website#431).
* **[website] Fix broken documentation links with `.md` suffix**: Fixed incorrect internal links with `.md` suffix across virtualization guides for both v0 and v1 documentation, standardizing link text to "Developer Guide" ([**@cheese**](https://github.com/cheese) in cozystack/website#432).
## Contributors
We'd like to thank all contributors who made this release possible:
* [**@androndo**](https://github.com/androndo)
* [**@cheese**](https://github.com/cheese)
* [**@IvanHunters**](https://github.com/IvanHunters)
* [**@kvaps**](https://github.com/kvaps)
* [**@lexfrei**](https://github.com/lexfrei)
* [**@myasnikovdaniil**](https://github.com/myasnikovdaniil)
* [**@sircthulhu**](https://github.com/sircthulhu)
### New Contributors
We're excited to welcome our first-time contributors:
* [**@cheese**](https://github.com/cheese) - First contribution!
**Full Changelog**: https://github.com/cozystack/cozystack/compare/v1.0.0-rc.1...v1.0.0-rc.2

View File

@@ -1,99 +0,0 @@
# Enabling Hubble for Network Observability
Hubble is a network and security observability platform built on top of Cilium. It provides deep visibility into the communication and behavior of services in your Kubernetes cluster.
## Prerequisites
- Cozystack platform running with Cilium as the CNI
- Monitoring hub enabled for Grafana access
## Configuration
Hubble is disabled by default in Cozystack. To enable it, update the Cilium configuration.
### Enable Hubble
Edit the Cilium values in your platform configuration to enable Hubble:
```yaml
cilium:
hubble:
enabled: true
relay:
enabled: true
ui:
enabled: true
metrics:
enabled:
- dns
- drop
- tcp
- flow
- port-distribution
- icmp
- httpV2:exemplars=true;labelsContext=source_ip,source_namespace,source_workload,destination_ip,destination_namespace,destination_workload,traffic_direction
```
### Components
When Hubble is enabled, the following components become available:
- **Hubble Relay**: Aggregates flow data from all Cilium agents
- **Hubble UI**: Web-based interface for exploring network flows
- **Hubble Metrics**: Prometheus metrics for network observability
## Grafana Dashboards
Once Hubble is enabled and the monitoring hub is deployed, the following dashboards become available in Grafana under the `hubble` folder:
| Dashboard | Description |
|-----------|-------------|
| **Overview** | General Hubble metrics including processing statistics |
| **DNS Namespace** | DNS query and response metrics by namespace |
| **L7 HTTP Metrics** | HTTP layer 7 metrics by workload |
| **Network Overview** | Network flow overview by namespace |
### Accessing Dashboards
1. Navigate to Grafana via the monitoring hub
2. Browse to the `hubble` folder in the dashboard browser
3. Select a dashboard to view network observability data
## Metrics Available
Hubble exposes various metrics that can be queried in Grafana:
- `hubble_flows_processed_total`: Total number of flows processed
- `hubble_dns_queries_total`: DNS queries by type
- `hubble_dns_responses_total`: DNS responses by status
- `hubble_drop_total`: Dropped packets by reason
- `hubble_tcp_flags_total`: TCP connections by flag
- `hubble_http_requests_total`: HTTP requests by method and status
## Troubleshooting
### Verify Hubble Status
Check if Hubble is running:
```bash
kubectl get pods -n cozy-cilium -l k8s-app=hubble-relay
kubectl get pods -n cozy-cilium -l k8s-app=hubble-ui
```
### Check Metrics Endpoint
Verify Hubble metrics are being scraped:
```bash
kubectl port-forward -n cozy-cilium svc/hubble-metrics 9965:9965
curl http://localhost:9965/metrics
```
### Verify ServiceMonitor
Ensure the ServiceMonitor is created for Prometheus scraping:
```bash
kubectl get servicemonitor -n cozy-cilium
```

View File

@@ -1,20 +0,0 @@
apiVersion: backups.cozystack.io/v1alpha1
kind: BackupJob
metadata:
name: desired-backup
namespace: tenant-root
labels:
backups.cozystack.io/triggered-by: manual
spec:
applicationRef:
apiGroup: apps.cozystack.io
kind: VirtualMachine
name: vm1
storageRef:
apiGroup: apps.cozystack.io
kind: Bucket
name: test-bucket
strategyRef:
apiGroup: strategy.backups.cozystack.io
kind: Velero
name: velero-strategy-default

View File

@@ -0,0 +1,31 @@
apiVersion: cozystack.io/v1alpha1
kind: Platform
metadata:
name: cozystack-platform
# Cluster-scoped resource, no namespace needed
spec:
# SourceRef is required - reference to the OCIRepository or GitRepository
sourceRef:
kind: OCIRepository
name: cozystack-packages
namespace: cozy-system
# Optional: Interval for HelmRelease reconciliation (default: 5m)
interval: 5m
# Optional: BasePath is the base path where the platform chart is located in the source.
# For GitRepository, defaults to "packages/core/platform" if not specified.
# For OCIRepository, defaults to "core/platform" if not specified.
# basePath: core/platform
# Optional: Values to pass to HelmRelease
# These values will be merged with sourceRef (which is automatically added)
values:
# Any custom values can be added here
# sourceRef will be automatically added by the controller
# Example custom values:
# customKey: customValue
# nested:
# config:
# enabled: true

49
go.mod
View File

@@ -5,31 +5,30 @@ module github.com/cozystack/cozystack
go 1.25.0
require (
github.com/emicklei/dot v1.10.0
github.com/fluxcd/helm-controller/api v1.4.3
github.com/fluxcd/source-controller/api v1.7.4
github.com/fluxcd/source-watcher/api/v2 v2.0.3
github.com/fluxcd/source-controller/api v1.6.2
github.com/fluxcd/source-watcher/api/v2 v2.0.2
github.com/go-logr/logr v1.4.3
github.com/go-logr/zapr v1.3.0
github.com/google/gofuzz v1.2.0
github.com/onsi/ginkgo/v2 v2.23.3
github.com/onsi/gomega v1.37.0
github.com/prometheus/client_golang v1.22.0
github.com/robfig/cron/v3 v3.0.1
github.com/spf13/cobra v1.9.1
github.com/vmware-tanzu/velero v1.17.1
github.com/stretchr/testify v1.11.1
go.uber.org/zap v1.27.0
gopkg.in/yaml.v2 v2.4.0
k8s.io/api v0.34.1
k8s.io/apiextensions-apiserver v0.34.1
k8s.io/apimachinery v0.34.2
k8s.io/apimachinery v0.34.1
k8s.io/apiserver v0.34.1
k8s.io/client-go v0.34.1
k8s.io/component-base v0.34.1
k8s.io/klog/v2 v2.130.1
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b
k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d
sigs.k8s.io/controller-runtime v0.22.4
sigs.k8s.io/structured-merge-diff/v6 v6.3.0
sigs.k8s.io/controller-runtime v0.22.2
sigs.k8s.io/structured-merge-diff/v4 v4.7.0
)
require (
@@ -49,7 +48,7 @@ require (
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fluxcd/pkg/apis/acl v0.9.0 // indirect
github.com/fluxcd/pkg/apis/kustomize v1.13.0 // indirect
github.com/fluxcd/pkg/apis/meta v1.23.0 // indirect
github.com/fluxcd/pkg/apis/meta v1.22.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
@@ -80,8 +79,8 @@ require (
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.65.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/spf13/pflag v1.0.7 // indirect
github.com/stoewer/go-strcase v1.3.0 // indirect
@@ -90,14 +89,14 @@ require (
go.etcd.io/etcd/client/pkg/v3 v3.6.4 // indirect
go.etcd.io/etcd/client/v3 v3.6.4 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
go.opentelemetry.io/otel v1.37.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect
go.opentelemetry.io/otel v1.35.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect
go.opentelemetry.io/otel/metric v1.37.0 // indirect
go.opentelemetry.io/otel/sdk v1.37.0 // indirect
go.opentelemetry.io/otel/trace v1.37.0 // indirect
go.opentelemetry.io/otel/metric v1.35.0 // indirect
go.opentelemetry.io/otel/sdk v1.34.0 // indirect
go.opentelemetry.io/otel/trace v1.35.0 // indirect
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
@@ -105,18 +104,18 @@ require (
golang.org/x/crypto v0.42.0 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/net v0.45.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/oauth2 v0.29.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/sys v0.36.0 // indirect
golang.org/x/term v0.35.0 // indirect
golang.org/x/text v0.29.0 // indirect
golang.org/x/time v0.12.0 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/tools v0.37.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/grpc v1.73.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/grpc v1.72.1 // indirect
google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
@@ -125,8 +124,6 @@ require (
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
sigs.k8s.io/yaml v1.6.0 // indirect
)
// See: issues.k8s.io/135537
replace k8s.io/apimachinery => github.com/cozystack/apimachinery v0.0.0-20251219010959-1f91eabae46c

93
go.sum
View File

@@ -18,8 +18,6 @@ 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-20251219010959-1f91eabae46c h1:C2wIfH/OzhU9XOK/e6Ik9cg7nZ1z6fN4lf6a3yFdik8=
github.com/cozystack/apimachinery v0.0.0-20251219010959-1f91eabae46c/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -27,8 +25,6 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/emicklei/dot v1.10.0 h1:z17n0ce/FBMz3QbShSzVGhiW447Qhu7fljzvp3Gs6ig=
github.com/emicklei/dot v1.10.0/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s=
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
@@ -43,12 +39,12 @@ github.com/fluxcd/pkg/apis/acl v0.9.0 h1:wBpgsKT+jcyZEcM//OmZr9RiF8klL3ebrDp2u2T
github.com/fluxcd/pkg/apis/acl v0.9.0/go.mod h1:TttNS+gocsGLwnvmgVi3/Yscwqrjc17+vhgYfqkfrV4=
github.com/fluxcd/pkg/apis/kustomize v1.13.0 h1:GGf0UBVRIku+gebY944icVeEIhyg1P/KE3IrhOyJJnE=
github.com/fluxcd/pkg/apis/kustomize v1.13.0/go.mod h1:TLKVqbtnzkhDuhWnAsN35977HvRfIjs+lgMuNro/LEc=
github.com/fluxcd/pkg/apis/meta v1.23.0 h1:fLis5YcHnOsyKYptzBtituBm5EWNx13I0bXQsy0FG4s=
github.com/fluxcd/pkg/apis/meta v1.23.0/go.mod h1:UWsIbBPCxYvoVklr2mV2uLFBf/n17dNAmKFjRfApdDo=
github.com/fluxcd/source-controller/api v1.7.4 h1:+EOVnRA9LmLxOx7J273l7IOEU39m+Slt/nQGBy69ygs=
github.com/fluxcd/source-controller/api v1.7.4/go.mod h1:ruf49LEgZRBfcP+eshl2n9SX1MfHayCcViAIGnZcaDY=
github.com/fluxcd/source-watcher/api/v2 v2.0.3 h1:SsVGAaMBxzvcgrOz/Kl6c2ybMHVqoiEFwtI+bDuSeSs=
github.com/fluxcd/source-watcher/api/v2 v2.0.3/go.mod h1:Nx3QZweVyuhaOtSNrw+oxifG+qrakPvjgNAN9qlUTb0=
github.com/fluxcd/pkg/apis/meta v1.22.0 h1:EHWQH5ZWml7i8eZ/AMjm1jxid3j/PQ31p+hIwCt6crM=
github.com/fluxcd/pkg/apis/meta v1.22.0/go.mod h1:Kc1+bWe5p0doROzuV9XiTfV/oL3ddsemYXt8ZYWdVVg=
github.com/fluxcd/source-controller/api v1.6.2 h1:UmodAeqLIeF29HdTqf2GiacZyO+hJydJlepDaYsMvhc=
github.com/fluxcd/source-controller/api v1.6.2/go.mod h1:ZJcAi0nemsnBxjVgmJl0WQzNvB0rMETxQMTdoFosmMw=
github.com/fluxcd/source-watcher/api/v2 v2.0.2 h1:fWSxsDqYN7My2AEpQwbP7O6Qjix8nGBX+UE/qWHtZfM=
github.com/fluxcd/source-watcher/api/v2 v2.0.2/go.mod h1:Hs6ueayPt23jlkIr/d1pGPZ+OHiibQwWjxvU6xqljzg=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
@@ -81,6 +77,7 @@ github.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI=
github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM=
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -143,14 +140,12 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/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.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -178,8 +173,6 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE=
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk=
github.com/vmware-tanzu/velero v1.17.1 h1:ldKeiTuUwkThOw7zrUucNA1NwnLG66zl13YetWAoE0I=
github.com/vmware-tanzu/velero v1.17.1/go.mod h1:3KTxuUN6Un38JzmYAX+8U6j2k6EexGoNNxa8jrJML8U=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 h1:S2dVYn90KE98chqDkyE9Z4N61UnQd+KOfgp5Iu53llk=
@@ -202,24 +195,24 @@ go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ=
go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q=
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
@@ -247,8 +240,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -265,8 +258,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
@@ -279,14 +272,14 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=
gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950=
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
@@ -296,6 +289,8 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@@ -303,6 +298,8 @@ k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM=
k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk=
k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI=
k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc=
k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4=
k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
k8s.io/apiserver v0.34.1 h1:U3JBGdgANK3dfFcyknWde1G6X1F4bg7PXuvlqt8lITA=
k8s.io/apiserver v0.34.1/go.mod h1:eOOc9nrVqlBI1AFCvVzsob0OxtPZUCPiUJL45JOTBG0=
k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY=
@@ -319,13 +316,17 @@ k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPG
k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
sigs.k8s.io/controller-runtime v0.22.4 h1:GEjV7KV3TY8e+tJ2LCTxUTanW4z/FmNB7l327UfMq9A=
sigs.k8s.io/controller-runtime v0.22.4/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8=
sigs.k8s.io/controller-runtime v0.22.2 h1:cK2l8BGWsSWkXz09tcS4rJh95iOLney5eawcK5A33r4=
sigs.k8s.io/controller-runtime v0.22.2/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI=
sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=

Some files were not shown because too many files have changed in this diff Show More