Compare commits

..

5 Commits

Author SHA1 Message Date
Timofei Larkin
eeb85eed39 Merge branch 'main' into testing/add-vpc
Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
2025-10-30 14:59:28 +03:00
nbykov0
603dcbe27e [apps] tenant: add vpcs to tenant roles
Signed-off-by: nbykov0 <166552198+nbykov0@users.noreply.github.com>
2025-10-28 15:12:31 +03:00
nbykov0
0bb538b1a1 [apps] vm-instance: add vpc support
Signed-off-by: nbykov0 <166552198+nbykov0@users.noreply.github.com>
2025-10-28 15:12:31 +03:00
nbykov0
c63fb203b6 [apps] virtual-machine: add vpc support
Signed-off-by: nbykov0 <166552198+nbykov0@users.noreply.github.com>
2025-10-28 15:12:31 +03:00
nbykov0
dfaf51d3e4 [apps] Add VPC app
Signed-off-by: nbykov0 <166552198+nbykov0@users.noreply.github.com>
2025-10-28 15:12:18 +03:00
165 changed files with 2467 additions and 6807 deletions

View File

@@ -33,9 +33,6 @@ jobs:
fetch-depth: 0
fetch-tags: true
- name: Run unit tests
run: make unit-tests
- name: Set up Docker config
run: |
if [ -d ~/.docker ]; then

1
.gitignore vendored
View File

@@ -1,5 +1,4 @@
_out
_repos
.git
.idea
.vscode

View File

@@ -31,5 +31,4 @@ This list is sorted in chronological order, based on the submission date.
| [gohost](https://gohost.kz/) | @karabass_off | 2024-02-01 | Our company has been working in the market of Kazakhstan for more than 15 years, providing clients with a standard set of services: VPS/VDC, IaaS, shared hosting, etc. Now we are expanding the lineup by introducing Bare Metal Kubenetes cluster under Cozystack management. |
| [Urmanac](https://urmanac.com) | @kingdonb | 2024-12-04 | Urmanac is the future home of a hosting platform for the knowledge base of a community of personal server enthusiasts. We use Cozystack to provide support services for web sites hosted using both conventional deployments and on SpinKube, with WASM. |
| [Hidora](https://hikube.cloud) | @matthieu-robin | 2025-09-17 | Hidora is a Swiss cloud provider delivering managed services and infrastructure solutions through datacenters located in Switzerland, ensuring data sovereignty and reliability. Its sovereign cloud platform, Hikube, is designed to run workloads with high availability across multiple datacenters, providing enterprises with a secure and scalable foundation for their applications based on Cozystack. |
| [QOSI](https://qosi.kz) | @tabu-a | 2025-10-04 | QOSI is a non-profit organization driving open-source adoption and digital sovereignty across Kazakhstan and Central Asia. We use Cozystack as a platform for deploying sovereign, GPU-enabled clouds and educational environments under the National AI Program. Our goal is to accelerate the regions transition toward open, self-hosted cloud-native technologies |
|
|

View File

@@ -1,38 +0,0 @@
# AI Agents Overview
This file provides structured guidance for AI coding assistants and agents
working with the **Cozystack** project.
## Agent Documentation
| Agent | Purpose |
|-------|---------|
| [overview.md](./docs/agents/overview.md) | Project structure and conventions |
| [contributing.md](./docs/agents/contributing.md) | Commits, pull requests, and git workflow |
| [changelog.md](./docs/agents/changelog.md) | Changelog generation instructions |
| [releasing.md](./docs/agents/releasing.md) | Release process and workflow |
## Project Overview
**Cozystack** is a Kubernetes-based platform for building cloud infrastructure with managed services (databases, VMs, K8s clusters), multi-tenancy, and GitOps delivery.
## Quick Reference
### Code Structure
- `packages/core/` - Core platform charts (installer, platform)
- `packages/system/` - System components (CSI, CNI, operators)
- `packages/apps/` - User-facing applications in catalog
- `packages/extra/` - Tenant-specific modules
- `cmd/`, `internal/`, `pkg/` - Go code
- `api/` - Kubernetes CRDs
### Conventions
- **Helm Charts**: Umbrella pattern, vendored upstream charts in `charts/`
- **Go Code**: Controller-runtime patterns, kubebuilder style
- **Git Commits**: `[component] Description` format with `--signoff`
### What NOT to Do
- ❌ Edit `/vendor/`, `zz_generated.*.go`, upstream charts directly
- ❌ Modify `go.mod`/`go.sum` manually (use `go get`)
- ❌ Force push to main/master
- ❌ Commit built artifacts from `_out`

View File

@@ -1,4 +1,4 @@
.PHONY: manifests repos assets unit-tests helm-unit-tests
.PHONY: manifests repos assets
build-deps:
@command -V find docker skopeo jq gh helm > /dev/null
@@ -46,11 +46,6 @@ test:
make -C packages/core/testing apply
make -C packages/core/testing test
unit-tests: helm-unit-tests
helm-unit-tests:
hack/helm-unit-tests.sh
prepare-env:
make -C packages/core/testing apply
make -C packages/core/testing prepare-cluster

View File

@@ -229,15 +229,6 @@ func main() {
os.Exit(1)
}
dashboardManager := &dashboard.Manager{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}
if err = dashboardManager.SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "DashboardReconciler")
os.Exit(1)
}
// +kubebuilder:scaffold:builder
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
@@ -263,9 +254,7 @@ func main() {
}
setupLog.Info("starting manager")
ctx := ctrl.SetupSignalHandler()
dashboardManager.InitializeStaticResources(ctx)
if err := mgr.Start(ctx); err != nil {
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
setupLog.Error(err, "problem running manager")
os.Exit(1)
}

View File

@@ -1,666 +0,0 @@
# Changelog Generation Instructions
This file contains detailed instructions for AI-powered IDE on how to generate changelogs for Cozystack releases.
## When to use these instructions
Follow these instructions when the user explicitly asks to generate a changelog.
## Required Tools
Before generating changelogs, ensure you have access to `gh` (GitHub CLI) tool, which is used to fetch commit and PR author information. The GitHub CLI is used to correctly identify PR authors from commits and pull requests.
## Changelog Generation Process
When the user asks to generate a changelog, follow these steps in the specified order:
**CHECKLIST - All actions that must be completed:**
- [ ] Step 1: Update information from remote (git fetch)
- [ ] Step 2: Check current branch (must be main)
- [ ] Step 3: Determine release type and previous version (minor vs patch release)
- [ ] Step 4: Determine versions and analyze existing changelogs
- [ ] Step 5: Get the list of commits for the release period
- [ ] Step 6: Check additional repositories (website is REQUIRED, optional repos if tags exist)
- [ ] **MANDATORY**: Check website repository for documentation changes WITH authors and PR links via GitHub CLI
- [ ] **MANDATORY**: Check ALL optional repositories (talm, boot-to-talos, cozypkg, cozy-proxy) for tags during release period
- [ ] **MANDATORY**: For ALL commits from additional repos, get GitHub username via CLI, prioritizing PR author over commit author.
- [ ] Step 7: Analyze commits (extract PR numbers, authors, user impact)
- [ ] **MANDATORY**: For EVERY PR in main repo, get PR author via `gh pr view <PR_NUMBER> --json author --jq .author.login` (do NOT skip this step)
- [ ] **MANDATORY**: Extract PR numbers from commit messages, then use `gh pr view` for each PR to get the PR author. Do NOT use commit author. Only for commits without PR numbers (rare), fall back to `gh api repos/cozystack/cozystack/commits/<hash> --jq '.author.login'`
- [ ] Step 8: Form new changelog (structure, format, generate contributors list)
- [ ] Step 9: Verify completeness and save
### 1. Updating information from remote
```bash
git fetch --tags --force --prune
```
This is necessary to get up-to-date information about tags and commits from the remote repository.
### 2. Checking current branch
Make sure we are on the `main` branch:
```bash
git branch --show-current
```
### 3. Determining release type and previous version
**Important**: Determine if you're generating a changelog for a **minor release** (vX.Y.0) or a **patch release** (vX.Y.Z where Z > 0).
**For minor releases (vX.Y.0):**
- Each minor version lives and evolves in its own branch (`release-X.Y`)
- You MUST compare with the **previous minor version** (v(X-1).Y.0), not the last patch release
- This ensures you capture all changes from the entire minor version cycle, including all patch releases
- Example: For v0.38.0, compare with v0.37.0 (not v0.37.8)
- Run a separate cycle to check the diff with the zero version of the previous minor release
**For patch releases (vX.Y.Z where Z > 0):**
- Compare with the previous patch version (vX.Y.(Z-1))
- Example: For v0.37.2, compare with v0.37.1
### 4. Determining versions and analyzing existing changelogs
**Determine the last published version:**
1. Get the list of version tags:
```bash
git tag -l 'v[0-9]*.[0-9]*.[0-9]*' | sort -V
```
2. Get the last tag:
```bash
git tag -l 'v[0-9]*.[0-9]*.[0-9]*' | sort -V | tail -1
```
3. Compare tags with existing changelog files in `docs/changelogs/` to determine the last published version (the newest file `vX.Y.Z.md`)
**Study existing changelog format:**
- Review recent changelog files to understand the format and structure
- Pay attention to:
- **Feature Highlights format** (for minor releases): Use `## Feature Highlights` with `### Feature Name` subsections containing detailed descriptions (2-4 paragraphs each). See v0.35.0 and v0.36.0 for examples.
- Section structure (Major Features and Improvements, Security, Fixes, Dependencies, etc.)
- PR link format (e.g., `[**@username**](https://github.com/username) in #1234`)
- Change description style
- Presence of Breaking changes sections, etc.
### 5. Getting the list of commits
**Important**: Determine if you're generating a changelog for a **minor release** (vX.Y.0) or a **patch release** (vX.Y.Z where Z > 0).
**For patch releases (vX.Y.Z where Z > 0):**
Get the list of commits starting from the previous patch version to HEAD:
**⚠️ CRITICAL: Do NOT use --first-parent flag! It will skip merge commits including backports!**
```bash
# Get all commits including merge commits (backports)
git log <previous_version>..HEAD --pretty=format:"%h - %s (%an, %ar)"
```
For example, if generating changelog for `v0.37.2`:
```bash
git log v0.37.1..HEAD --pretty=format:"%h - %s (%an, %ar)"
```
**⚠️ IMPORTANT: Check for backports:**
- Look for commits with "[Backport release-X.Y]" in the commit message
- For backport PRs, find the original PR number mentioned in the backport commit message or PR description
- Use the original PR author (not the backport PR author) when creating changelog entries
- Include both the original PR number and backport PR number in the changelog entry (e.g., `#1606, #1609`)
**For minor releases (vX.Y.0):**
Minor releases must include **all changes** from patch releases of the previous minor version. Get commits from the previous minor release:
**⚠️ CRITICAL: Do NOT use --first-parent flag! It will skip merge commits including backports!**
```bash
# For v0.38.0, get all commits since v0.37.0 (including all patch releases v0.37.1, v0.37.2, etc.)
git log v<previous_minor_version>..HEAD --pretty=format:"%h - %s (%an, %ar)"
```
For example, if generating changelog for `v0.38.0`:
```bash
git log v0.37.0..HEAD --pretty=format:"%h - %s (%an, %ar)"
```
This will include all commits from v0.37.1, v0.37.2, v0.37.3, etc., up to v0.38.0.
**⚠️ IMPORTANT: Always check merge commits:**
- Merge commits may contain backports that need to be included
- Check all commits in the range, including merge commits
- For backports, always find and reference the original PR
### 6. Analyzing additional repositories
**⚠️ CRITICAL: This step is MANDATORY and must NOT be skipped!**
Cozystack release may include changes from related repositories. Check and include commits from these repositories if tags were released during the release period:
**Required repositories:**
- **Documentation**: https://github.com/cozystack/website
- **MANDATORY**: Always check this repository for documentation changes during the release period
- **MANDATORY**: Get GitHub username for EVERY commit. Extract PR number from commit message, then use `gh pr view <PR_NUMBER> --repo cozystack/website --json author --jq .author.login` to get PR author. Only if no PR number, fall back to `gh api repos/cozystack/website/commits/<hash> --jq '.author.login'`
**Optional repositories (MUST check ALL of them for tags during release period):**
- https://github.com/cozystack/talm
- https://github.com/cozystack/boot-to-talos
- https://github.com/cozystack/cozypkg
- https://github.com/cozystack/cozy-proxy
**⚠️ IMPORTANT**: You MUST check ALL optional repositories for tags created during the release period. Do NOT skip this step even if you think there might not be any tags. Use the process below to verify.
**Process for each repository:**
1. **Get release period dates:**
```bash
# Get dates for the release period
cd /path/to/cozystack
RELEASE_START=$(git log -1 --format=%ai v<previous_version>)
RELEASE_END=$(git log -1 --format=%ai HEAD)
```
2. **Check for commits in website repository (always required):**
```bash
# Ensure website repository is cloned and up-to-date
mkdir -p _repos
if [ ! -d "_repos/website" ]; then
cd _repos && git clone https://github.com/cozystack/website.git && cd ..
fi
cd _repos/website
git fetch --all --tags --force
git checkout main 2>/dev/null || git checkout master
git pull
# Get commits between release dates (with some buffer)
git log --since="$RELEASE_START" --until="$RELEASE_END" --format="%H|%s|%an" | while IFS='|' read -r commit_hash subject author_name; do
# Extract PR number from commit message
PR_NUMBER=$(git log -1 --format="%B" "$commit_hash" | grep -oE '#[0-9]+' | head -1 | tr -d '#')
# ALWAYS use PR author if PR number found, not commit author
if [ -n "$PR_NUMBER" ]; then
GITHUB_USERNAME=$(gh pr view "$PR_NUMBER" --repo cozystack/website --json author --jq '.author.login // empty' 2>/dev/null)
echo "$commit_hash|$subject|$author_name|$GITHUB_USERNAME|cozystack/website#$PR_NUMBER"
else
# Only fallback to commit author if no PR number found (rare)
GITHUB_USERNAME=$(gh api repos/cozystack/website/commits/$commit_hash --jq '.author.login // empty')
echo "$commit_hash|$subject|$author_name|$GITHUB_USERNAME|cozystack/website@${commit_hash:0:7}"
fi
done
# Look for documentation updates, new pages, or significant content changes
# Include these in the "Documentation" section of the changelog WITH authors and PR links
```
3. **For optional repositories, check if tags exist during release period:**
**⚠️ MANDATORY: You MUST check ALL optional repositories (talm, boot-to-talos, cozypkg, cozy-proxy). Do NOT skip any repository!**
**Use the helper script:**
```bash
# Get release period dates
RELEASE_START=$(git log -1 --format=%ai v<previous_version>)
RELEASE_END=$(git log -1 --format=%ai HEAD)
# Run the script to check all optional repositories
./docs/changelogs/hack/check-optional-repos.sh "$RELEASE_START" "$RELEASE_END"
```
The script will:
- Check ALL optional repositories (talm, boot-to-talos, cozypkg, cozy-proxy)
- Look for tags created during the release period
- Get commits between tags (if tags exist) or by date range (if no tags)
- Extract PR numbers from commit messages
- For EVERY commit with PR number, get PR author via CLI: `gh pr view <PR_NUMBER> --repo cozystack/<repo> --json author --jq .author.login` (ALWAYS use PR author, not commit author)
- For commits without PR numbers (rare), fallback to: `gh api repos/cozystack/<repo>/commits/<hash> --jq '.author.login'`
- Output results in format: `commit_hash|subject|author_name|github_username|cozystack/repo#PR_NUMBER` or `cozystack/repo@commit_hash`
4. **Extract PR numbers and authors using GitHub CLI:**
- **ALWAYS use PR author, not commit author** for commits from additional repositories
- For each commit, extract PR number from commit message first: Extract `#123` pattern from commit message
- If PR number found, use `gh pr view <PR_NUMBER> --repo cozystack/<repo> --json author --jq .author.login` to get PR author (the person who wrote the code)
- Only if no PR number found (rare), fallback to commit author: `gh api repos/cozystack/<repo>/commits/<hash> --jq '.author.login'`
- **Prefer PR numbers**: Use format `cozystack/website#123` if PR number found in commit message
- **Fallback to commit hash**: Use format `cozystack/website@abc1234` if no PR number
- **ALWAYS include author**: Every entry from additional repositories MUST include author in format `([**@username**](https://github.com/username) in cozystack/repo#123)`
- Determine user impact and categorize appropriately
- Format entries with repository prefix: `[website]`, `[talm]`, etc.
**Example entry format for additional repositories:**
```markdown
# If PR number found in commit message (REQUIRED format):
* **[website] Update installation documentation**: Improved installation guide with new examples ([**@username**](https://github.com/username) in cozystack/website#123).
# If no PR number (fallback, use commit hash):
* **[website] Update installation documentation**: Improved installation guide with new examples ([**@username**](https://github.com/username) in cozystack/website@abc1234).
# For optional repositories:
* **[talm] Add new feature**: Description of the change ([**@username**](https://github.com/username) in cozystack/talm#456).
```
**CRITICAL**:
- **ALWAYS include author** for every entry from additional repositories
- **ALWAYS include PR link or commit hash** for every entry
- Never add entries without author and PR/commit reference
- **ALWAYS use PR author, not commit author**: Extract PR number from commit message, then use `gh pr view <PR_NUMBER> --repo cozystack/<repo> --json author --jq .author.login` to get the PR author (the person who wrote the code)
- Only if no PR number found (rare), fallback to commit author: `gh api repos/cozystack/<repo>/commits/<hash> --jq '.author.login'`
- The commit author (especially for squash/merge commits) is usually the person who merged the PR, not the person who wrote the code
### 7. Analyzing commits and PRs
**⚠️ CRITICAL: You MUST get the author from PR, not from commit! Always use `gh pr view` to get the PR author. Do NOT use commit author!**
**Get all PR numbers from commits:**
**⚠️ CRITICAL: Do NOT use --no-merges flag! It will skip merge commits including backports!**
```bash
# Extract all PR numbers from commit messages in the release range (including merge commits)
git log <previous_version>..<new_version> --format="%s%n%b" | grep -oE '#[0-9]+' | sort -u | tr -d '#'
```
**⚠️ IMPORTANT: Handle backports correctly:**
- Backport PRs have format: `[Backport release-X.Y] <original title> (#BACKPORT_PR_NUMBER)`
- The backport commit message or PR description usually mentions the original PR number
- For backport entries in changelog, use the original PR author (not the backport PR author)
- Include both original and backport PR numbers in the changelog entry (e.g., `#1606, #1609`)
- To find original PR from backport: Check the backport PR description or commit message for "Backport of #ORIGINAL_PR"
**For each PR number, get the author:**
**CRITICAL**: The commit author (especially for squash/merge commits) is usually the person who merged the PR (or GitHub bot), NOT the person who wrote the code. **ALWAYS use the PR author**, not the commit author.
**⚠️ MANDATORY: ALWAYS use `gh pr view` to get the PR author. Do NOT use commit author!**
**ALWAYS use GitHub CLI** to get the PR author:
```bash
# Usage: Get PR author - MANDATORY for EVERY PR
# Loop through ALL PR numbers and get PR author (including backports)
git log <previous_version>..<new_version> --format="%s%n%b" | grep -oE '#[0-9]+' | sort -u | tr -d '#' | while read PR_NUMBER; do
# Check if this is a backport PR
BACKPORT_INFO=$(gh pr view "$PR_NUMBER" --json body --jq '.body' 2>/dev/null | grep -i "backport of #" || echo "")
if [ -n "$BACKPORT_INFO" ]; then
# Extract original PR number from backport description
ORIGINAL_PR=$(echo "$BACKPORT_INFO" | grep -oE 'backport of #([0-9]+)' | grep -oE '[0-9]+' | head -1)
if [ -n "$ORIGINAL_PR" ]; then
# Use original PR author
GITHUB_USERNAME=$(gh pr view "$ORIGINAL_PR" --json author --jq '.author.login // empty')
PR_TITLE=$(gh pr view "$ORIGINAL_PR" --json title --jq '.title // empty')
echo "$PR_NUMBER|$ORIGINAL_PR|$GITHUB_USERNAME|$PR_TITLE|BACKPORT"
else
# Fallback to backport PR author if original not found
GITHUB_USERNAME=$(gh pr view "$PR_NUMBER" --json author --jq '.author.login // empty')
PR_TITLE=$(gh pr view "$PR_NUMBER" --json title --jq '.title // empty')
echo "$PR_NUMBER||$GITHUB_USERNAME|$PR_TITLE|BACKPORT"
fi
else
# Regular PR
GITHUB_USERNAME=$(gh pr view "$PR_NUMBER" --json author --jq '.author.login // empty')
PR_TITLE=$(gh pr view "$PR_NUMBER" --json title --jq '.title // empty')
echo "$PR_NUMBER||$GITHUB_USERNAME|$PR_TITLE|REGULAR"
fi
done
```
**⚠️ IMPORTANT**: You must run this for EVERY PR in the release period. Do NOT skip any PRs or assume the GitHub username based on the git author name.
**CRITICAL**: Always use `gh pr view <PR_NUMBER> --json author --jq .author.login` to get the PR author. This correctly identifies the person who wrote the code, not the person who merged it (which is especially important for squash merges).
**Why this matters**: Using the wrong author in changelogs gives incorrect credit and can confuse contributors. The merge/squash commit is created by the person who clicks "Merge" in GitHub, not the PR author.
**For commits without PR numbers (rare):**
- Only if a commit has no PR number, fall back to commit author: `gh api repos/cozystack/cozystack/commits/<hash> --jq '.author.login'`
- But this should be very rare - most commits should have PR numbers
**Extract PR number from commit messages:**
- Check commit message subject (`%s`) and body (`%b`) for PR references: `#1234` or `(#1234)`
- **Primary method**: Extract from commit message format `(#PR_NUMBER)` or `in #PR_NUMBER` or `Merge pull request #1234`
- Use regex: `grep -oE '#[0-9]+'` to find all PR numbers
**⚠️ CRITICAL: Verify PR numbers match commit messages!**
- Always verify that the PR number in the changelog matches the PR number in the commit message
- Common mistake: Using wrong PR number (e.g., #1614 instead of #1617) when multiple similar commits exist
- To verify: Check the actual commit message: `git log <commit_hash> -1 --format="%s%n%b" | grep -oE '#[0-9]+'`
- If multiple PR numbers appear in a commit, use the one that matches the PR title/description
- For merge commits, check the merged branch commits, not just the merge commit message
3. **Understand the change:**
```bash
# Get PR details (preferred method)
gh pr view <PR_NUMBER> --json title,body,url
# Or get commit details if no PR number
git show <commit_hash> --stat
git show <commit_hash>
```
- Review PR description and changed files
- Understand functionality added/changed/fixed
- **Determine user impact**: What can users do now? What problems are fixed? What improvements do users experience?
4. **For release branches (backports):**
- If commit is from `release-X.Y` branch, check if it's a backport
- Find original commit in `main` to get correct PR number:
```bash
git log origin/main --grep="<part of commit message>" --oneline
```
### 8. Forming a new changelog
Create a new changelog file in the format matching previous versions:
1. **Determine the release type:**
- **Minor release (vX.Y.0)** - use full format with **Feature Highlights** section. **Must include all changes from patch releases of the previous minor version** (e.g., v0.38.0 should include changes from v0.37.1, v0.37.2, v0.37.3, etc.)
- **Patch release (vX.Y.Z, where Z > 0)** - use more compact format, includes only changes since the previous patch release
**Feature Highlights format for minor releases:**
- Use section header: `## Feature Highlights`
- Include 3-6 major features as subsections with `### Feature Name` headers
- Each feature subsection should contain:
- **Detailed description** (2-4 paragraphs) explaining:
- What the feature is and what problem it solves
- How it works and what users can do with it
- How to use it (if applicable)
- Benefits and impact for users
- **Links to documentation** when available (use markdown links)
- **Code examples or configuration snippets** if helpful
- Focus on user value and practical implications, not just technical details
- Each feature should be substantial enough to warrant its own subsection
- Order features by importance/impact (most important first)
- Example format:
```markdown
## Feature Highlights
### Feature Name
Detailed description paragraph explaining what the feature is...
Another paragraph explaining how it works and what users can do...
Learn more in the [documentation](https://cozystack.io/docs/...).
```
**Important for minor releases**: After collecting all commits, **systematically verify** that all PRs from patch releases are included:
```bash
# Extract all PR numbers from patch release changelogs
grep -h "#[0-9]\+" docs/changelogs/v<previous_minor>.*.md | sort -u
# Extract all PR numbers from the new minor release changelog
grep -h "#[0-9]\+" docs/changelogs/v<new_minor>.0.md | sort -u
# Compare and identify missing PRs
# Ensure every PR from patch releases appears in the minor release changelog
```
2. **Structure changes by categories:**
**For minor releases (vX.Y.0):**
- **Feature Highlights** (required) - see format above
- **Major Features and Improvements** - detailed list of all major features and improvements
- **Improvements (minor)** - smaller improvements and enhancements
- **Bug fixes** - all bug fixes
- **Security** - security-related changes
- **Dependencies & version updates** - dependency updates
- **System Configuration** - system-level configuration changes
- **Development, Testing, and CI/CD** - development and testing improvements
- **Documentation** (include changes from website repository here - **MUST include authors and PR links for all entries**)
- **Breaking changes & upgrade notes** (if any)
- **Refactors & chores** (if any)
**For patch releases (vX.Y.Z where Z > 0):**
- **Features and Improvements** - new features and improvements
- **Fixes** - bug fixes
- **Security** - security-related changes
- **Dependencies** - dependency updates
- **System Configuration** - system-level configuration changes
- **Development, Testing, and CI/CD** - development and testing improvements
- **Documentation** (include changes from website repository here - **MUST include authors and PR links for all entries**)
- **Migration and Upgrades** (if applicable)
**Note**: When including changes from additional repositories, group them logically with main repository changes, or create separate subsections if there are many changes from a specific repository.
3. **Entry format:**
- Use the format: `* **Brief description**: detailed description ([**@username**](https://github.com/username) in #PR_NUMBER)`
- **CRITICAL - Get authorship correctly**:
- **ALWAYS use PR author, not commit author**: Extract PR number from commit message, then use `gh pr view` to get the PR author. The commit author (especially for squash/merge commits) is usually the person who merged the PR (or GitHub bot), NOT the person who wrote the code.
```bash
# Get PR author from GitHub CLI (correct method)
# Step 1: Extract PR number from commit message
PR_NUMBER=$(git log <commit_hash> -1 --format="%s%n%b" | grep -oE '#[0-9]+' | head -1 | tr -d '#')
# Step 2: Get PR author (the person who wrote the code)
if [ -n "$PR_NUMBER" ]; then
GITHUB_USERNAME=$(gh pr view "$PR_NUMBER" --json author --jq '.author.login')
else
# Only fallback to commit author if no PR number found (rare)
GITHUB_USERNAME=$(gh api repos/cozystack/cozystack/commits/<commit_hash> --jq '.author.login')
fi
```
**Example**: For PR #1507, the squash commit has author "kvaps" (who merged), but the PR author is "lllamnyp" (who wrote the code). Using `gh pr view 1507 --json author --jq .author.login` correctly returns "lllamnyp".
- **For regular commits**: Use the commit author directly:
```bash
git log <commit_hash> -1 --format="%an|%ae"
```
- **Validation**: Before adding to changelog, verify the author by checking:
- For merge commits: Compare merge commit author vs PR author (they should be different)
- Check existing changelogs for author name to GitHub username mappings
- Verify with: `git log <merge_commit>^1..<merge_commit>^2 --format="%an" --no-merges`
- **Map author name to GitHub username**: Check existing changelogs for author name mappings, or extract from PR links in commit messages
- **Always include user impact**: Each entry must explain how the change affects users
- For new features: explain what users can now do
- For bug fixes: explain what problem is solved for users
- For improvements: explain what users will experience better
- For breaking changes: clearly state what users need to do
- Group related changes
- Use bold font for important components/modules
- Focus on user value, not just technical details
4. **Add a link to the full changelog:**
**For patch releases (vX.Y.Z where Z > 0):**
```markdown
**Full Changelog**: https://github.com/cozystack/cozystack/compare/v<previous_patch_version>...v<new_version>
```
Example: For v0.37.2, use `v0.37.1...v0.37.2`
**For minor releases (vX.Y.0):**
```markdown
**Full Changelog**: https://github.com/cozystack/cozystack/compare/v<previous_minor_version>...v<new_version>
```
Example: For v0.38.0, use `v0.37.0...v0.38.0` (NOT `v0.37.8...v0.38.0`)
**Important**: Minor releases must reference the previous minor release (vX.Y.0), not the last patch release, to include all changes from the entire minor version cycle.
5. **Generate contributors list:**
**⚠️ SIMPLIFIED APPROACH: Extract contributors from the generated changelog itself!**
Since you've already generated the changelog with all PR authors correctly identified, simply extract GitHub usernames from the changelog entries:
```bash
# Extract all GitHub usernames from the current release changelog
# This method is simpler and more reliable than extracting from git history
# For patch releases: extract from the current changelog file
grep -oE '\[@[a-zA-Z0-9_-]+\]' docs/changelogs/v<version>.md | \
sed 's/\[@/@/' | sed 's/\]//' | \
sort -u
# For minor releases: extract from the current changelog file
grep -oE '\[@[a-zA-Z0-9_-]+\]' docs/changelogs/v<version>.md | \
sed 's/\[@/@/' | sed 's/\]//' | \
sort -u
```
**Get all previous contributors (to identify new ones):**
```bash
# Extract GitHub usernames from all previous changelogs
grep -hE '\[@[a-zA-Z0-9_-]+\]' docs/changelogs/v*.md | \
grep -oE '@[a-zA-Z0-9_-]+' | \
sort -u > /tmp/previous_contributors.txt
```
**Identify new contributors (first-time contributors):**
```bash
# Get current release contributors from the changelog
grep -oE '@[a-zA-Z0-9_-]+' docs/changelogs/v<version>.md | \
sort -u > /tmp/current_contributors.txt
# Get all previous contributors
grep -hE '@[a-zA-Z0-9_-]+' docs/changelogs/v*.md | \
grep -oE '@[a-zA-Z0-9_-]+' | \
sort -u > /tmp/all_previous_contributors.txt
# Find new contributors (those in current but not in previous)
comm -23 <(sort /tmp/current_contributors.txt) <(sort /tmp/all_previous_contributors.txt)
```
**Why this approach is better:**
- ✅ Uses the already-verified PR authors from the changelog (no need to query GitHub API again)
- ✅ Automatically handles backports correctly (original PR authors are already in the changelog)
- ✅ Simpler and faster (no git log parsing or API calls)
- ✅ More reliable (matches exactly what's in the changelog)
- ✅ Works for both patch and minor releases
**Add contributors section to changelog:**
Place the contributors section at the end of the changelog, before the "Full Changelog" link:
```markdown
## Contributors
We'd like to thank all contributors who made this release possible:
* [**@username1**](https://github.com/username1)
* [**@username2**](https://github.com/username2)
* [**@username3**](https://github.com/username3)
* ...
### New Contributors
We're excited to welcome our first-time contributors:
* [**@newuser1**](https://github.com/newuser1) - First contribution!
* [**@newuser2**](https://github.com/newuser2) - First contribution!
```
**Formatting guidelines:**
- List contributors in alphabetical order by GitHub username
- Use the format: `* [**@username**](https://github.com/username)`
- For new contributors, add " - First contribution!" note
- If GitHub username cannot be determined, you can skip that contributor or use their git author name
**When to include:**
- **For patch releases**: Contributors section is optional, but can be included for significant releases
- **For minor releases (vX.Y.0)**: Contributors section is required - you must generate and include the contributors list
- Always verify GitHub usernames by checking commit messages, PR links in changelog entries, or by examining PR details
6. **Add a comment with a link to the GitHub release:**
```markdown
<!--
https://github.com/cozystack/cozystack/releases/tag/v<new_version>
-->
```
### 9. Verification and saving
**Before saving, verify completeness:**
**For ALL releases:**
- [ ] Step 5 completed: **ALL commits included** (including merge commits and backports) - do not skip any commits
- [ ] Step 5 completed: **Backports identified and handled correctly** - original PR author used, both original and backport PR numbers included
- [ ] Step 6 completed: Website repository checked for documentation changes WITH authors and PR links via GitHub CLI
- [ ] Step 6 completed: **ALL** optional repositories (talm, boot-to-talos, cozypkg, cozy-proxy) checked for tags during release period
- [ ] Step 6 completed: For ALL commits from additional repos, GitHub username obtained via GitHub CLI (not skipped). For commits with PR numbers, PR author used via `gh pr view` (not commit author)
- [ ] Step 7 completed: For EVERY PR in main repo (including backports), PR author obtained via `gh pr view <PR_NUMBER> --json author --jq .author.login` (not skipped or assumed). Commit author NOT used - always use PR author
- [ ] Step 7 completed: **Backports verified** - for each backport PR, original PR found and original PR author used in changelog
- [ ] Step 8 completed: Contributors list generated
- [ ] All commits from main repository included (including merge commits)
- [ ] User impact described for each change
- [ ] Format matches existing changelogs
**For patch releases:**
- [ ] All commits from the release period are included (including merge commits with backports)
- [ ] PR numbers match commit messages
- [ ] Backports are properly identified and linked to original PRs
**For minor releases (vX.Y.0):**
- [ ] All changes from patch releases (vX.Y.1, vX.Y.2, etc.) are included
- [ ] Contributors section is present and complete
- [ ] Full Changelog link references previous minor version (vX.Y.0), not last patch
- [ ] Verify all PRs from patch releases are included:
```bash
# Extract and compare PR numbers
PATCH_PRS=$(grep -hE "#[0-9]+" docs/changelogs/v<previous_minor>.*.md | grep -oE "#[0-9]+" | sort -u)
MINOR_PRS=$(grep -hE "#[0-9]+" docs/changelogs/v<new_minor>.0.md | grep -oE "#[0-9]+" | sort -u)
MISSING=$(comm -23 <(echo "$PATCH_PRS") <(echo "$MINOR_PRS"))
if [ -n "$MISSING" ]; then
echo "Missing PRs from patch releases:"
echo "$MISSING"
# For each missing PR, check if it's a backport and verify change is included by description
fi
```
**Only proceed to save after all checkboxes are verified!**
**Save the changelog:**
Save the changelog to file `docs/changelogs/v<version>.md` according to the version for which the changelog is being generated.
### Important notes
- **After fetch with --force** local tags are up-to-date, use them for work
- **For release branches** always check original commits in `main` to get correct PR numbers
- **Preserve the format** of existing changelog files
- **Group related changes** logically
- **Be accurate** in describing changes, based on actual commit diffs
- **Check for PR numbers** and commit authors
- **CRITICAL - Get authorship from PR, not from commit**:
- **ALWAYS use PR author**: Extract PR number from commit message, then use `gh pr view <PR_NUMBER> --json author --jq .author.login` to get the PR author
- Do NOT use commit author - the commit author (especially for squash/merge commits) is usually the person who merged the PR, not the person who wrote the code
- For commits without PR numbers (rare), fall back to commit author: `gh api repos/cozystack/cozystack/commits/<commit_hash> --jq '.author.login'`
- **Workflow**: Extract PR numbers from commits → Use `gh pr view` for each PR → Get PR author (the person who wrote the code)
- Example: For PR #1507, the commit author is `@kvaps` (who merged), but `gh pr view 1507 --json author --jq .author.login` correctly returns `@lllamnyp` (who wrote the code)
- Check existing changelogs for author name to GitHub username mappings
- **Validation**: Before adding to changelog, always verify the author using `gh pr view` - never use commit author for PRs
- **MANDATORY**: Always describe user impact: Every changelog entry must explain how the change affects end users, not just what was changed technically. Focus on user value and practical implications.
**Required steps:**
- **Additional repositories (Step 6) - MANDATORY**:
- **⚠️ CRITICAL**: Always check the **website** repository for documentation changes during the release period. This is a required step and MUST NOT be skipped.
- **⚠️ CRITICAL**: You MUST check ALL optional repositories (talm, boot-to-talos, cozypkg, cozy-proxy) for tags during the release period. Do NOT skip any repository even if you think there might not be tags.
- **CRITICAL**: For ALL entries from additional repositories (website and optional), you MUST:
- **MANDATORY**: Extract PR number from commit message first
- **MANDATORY**: For commits with PR numbers, ALWAYS use `gh pr view <PR_NUMBER> --repo cozystack/<repo> --json author --jq .author.login` to get PR author (not commit author)
- **MANDATORY**: Only for commits without PR numbers (rare), fallback to: `gh api repos/cozystack/<repo>/commits/<hash> --jq '.author.login'`
- **MANDATORY**: Do NOT skip getting GitHub username via CLI - do this for EVERY commit
- **MANDATORY**: Do NOT use commit author for PRs - always use PR author
- Include PR link or commit hash reference
- Format: `* **[repo] Description**: details ([**@username**](https://github.com/username) in cozystack/repo#123)`
- For **optional repositories** (talm, boot-to-talos, cozypkg, cozy-proxy), you MUST check ALL of them for tags during the release period. Use the loop provided in Step 6 to check each repository systematically.
- When including changes from additional repositories, use the format: `[repo-name] Description` and link to the repository's PR/issue if available
- **Prefer PR numbers over commit hashes**: For commits from additional repositories, extract PR number from commit message using GitHub API. Use PR format (`cozystack/website#123`) instead of commit hash (`cozystack/website@abc1234`) when available
- **Never add entries without author and PR/commit reference**: Every entry from additional repositories must have both author and link
- Group changes from additional repositories with main repository changes, or create separate subsections if there are many changes from a specific repository
- **PR author verification (Step 7) - MANDATORY**:
- **⚠️ CRITICAL**: You MUST get the author from PR using `gh pr view`, NOT from commit
- **⚠️ CRITICAL**: Extract PR numbers from commit messages, then use `gh pr view <PR_NUMBER> --json author --jq .author.login` for each PR
- **⚠️ CRITICAL**: Do NOT use commit author - commit author is usually the person who merged, not the person who wrote the code
- **⚠️ CRITICAL**: Do NOT skip this step for any PR, even if the author seems obvious
- For commits without PR numbers (rare), fall back to: `gh api repos/cozystack/cozystack/commits/<hash> --jq '.author.login'`
- This ensures correct attribution and prevents errors in changelog entries (especially important for squash/merge commits)
- **Contributors list (Step 8)**:
- For minor releases (vX.Y.0): You must generate a list of all contributors and identify first-time contributors.
- For patch releases: Contributors section is optional, but recommended for significant releases
- Extract GitHub usernames from PR links in commit messages or changelog entries
- This helps recognize community contributions and welcome new contributors
- **Minor releases (vX.Y.0)**:
- Must include **all changes** from patch releases of the previous minor version (e.g., v0.38.0 includes all changes from v0.37.1, v0.37.2, v0.37.3, etc.)
- The "Full Changelog" link must reference the previous minor release (v0.37.0...v0.38.0), NOT the last patch release (v0.37.8...v0.38.0)
- This ensures users can see the complete set of changes for the entire minor version cycle
- **Verification step**: After creating the changelog, extract all PR numbers from patch release changelogs and verify they all appear in the minor release changelog to prevent missing entries
- **Backport handling**: Patch releases may contain backports with different PR numbers (e.g., #1624 in patch release vs #1622 in main). For minor releases, use original PR numbers from main when available, but verify that all changes from patch releases are included regardless of PR number differences
- **Content verification**: Don't rely solely on PR number matching - verify that change descriptions from patch releases appear in the minor release changelog, as backports may have different PR numbers

View File

@@ -1,113 +0,0 @@
# Instructions for AI Agents
Guidelines for AI agents contributing to Cozystack.
## Checklist for Creating a Pull Request
- [ ] Changes are made and tested
- [ ] Commit message uses correct `[component]` prefix
- [ ] Commit is signed off with `--signoff`
- [ ] Branch is rebased on `upstream/main` (no extra commits)
- [ ] PR body includes description and release note
- [ ] PR is pushed and created with `gh pr create`
## How to Commit and Create Pull Requests
### 1. Make Your Changes
Edit the necessary files in the codebase.
### 2. Commit with Proper Format
Use the `[component]` prefix and `--signoff` flag:
```bash
git commit --signoff -m "[component] Brief description of changes"
```
**Component prefixes:**
- System: `[dashboard]`, `[platform]`, `[cilium]`, `[kube-ovn]`, `[linstor]`, `[fluxcd]`, `[cluster-api]`
- Apps: `[postgres]`, `[mysql]`, `[redis]`, `[kafka]`, `[clickhouse]`, `[virtual-machine]`, `[kubernetes]`
- Other: `[tests]`, `[ci]`, `[docs]`, `[maintenance]`
**Examples:**
```bash
git commit --signoff -m "[dashboard] Add config hash annotations to restart pods on config changes"
git commit --signoff -m "[postgres] Update operator to version 1.2.3"
git commit --signoff -m "[docs] Add installation guide"
```
### 3. Rebase on upstream/main (if needed)
If your branch has extra commits, clean it up:
```bash
# Fetch latest
git fetch upstream
# Create clean branch from upstream/main
git checkout -b my-feature upstream/main
# Cherry-pick only your commit
git cherry-pick <your-commit-hash>
# Force push to your branch
git push -f origin my-feature:my-branch-name
```
### 4. Push Your Branch
```bash
git push origin <branch-name>
```
### 5. Create Pull Request
Write the PR body to a temporary file:
```bash
cat > /tmp/pr_body.md << 'EOF'
## What this PR does
Brief description of the changes.
Changes:
- Change 1
- Change 2
### Release note
```release-note
[component] Description for changelog
```
EOF
```
Create the PR:
```bash
gh pr create --title "[component] Brief description" --body-file /tmp/pr_body.md
```
Clean up:
```bash
rm /tmp/pr_body.md
```
## Git Permissions
Request these permissions when needed:
- `git_write` - For commit, rebase, cherry-pick, branch operations
- `network` - For push, fetch, pull operations
## Common Issues
**PR has extra commits?**
→ Rebase on `upstream/main` and cherry-pick only your commits
**Wrong commit message?**
`git commit --amend --signoff -m "[correct] message"` then `git push -f`
**Need to update PR?**
`gh pr edit <number> --body "new description"`

View File

@@ -1,115 +0,0 @@
# Cozystack Project Overview
This document provides detailed information about Cozystack project structure and conventions for AI agents.
## About Cozystack
Cozystack is an open-source Kubernetes-based platform and framework for building cloud infrastructure. It provides:
- **Managed Services**: Databases, VMs, Kubernetes clusters, object storage, and more
- **Multi-tenancy**: Full isolation and self-service for tenants
- **GitOps-driven**: FluxCD-based continuous delivery
- **Modular Architecture**: Extensible with custom packages and services
- **Developer Experience**: Simplified local development with cozypkg tool
The platform exposes infrastructure services via the Kubernetes API with ready-made configs, built-in monitoring, and alerts.
## Code Layout
```
.
├── packages/ # Main directory for cozystack packages
│ ├── core/ # Core platform logic charts (installer, platform)
│ ├── system/ # System charts (CSI, CNI, operators, etc.)
│ ├── apps/ # User-facing charts shown in dashboard catalog
│ └── extra/ # Tenant-specific modules, singleton charts which are used as dependencies
├── dashboards/ # Grafana dashboards for monitoring
├── hack/ # Helper scripts for local development
│ └── e2e-apps/ # End-to-end application tests
├── scripts/ # Scripts used by cozystack container
│ └── migrations/ # Version migration scripts
├── docs/ # Documentation
│ ├── agents/ # AI agent instructions
│ └── changelogs/ # Release changelogs
├── cmd/ # Go command entry points
│ ├── cozystack-api/
│ ├── cozystack-controller/
│ └── cozystack-assets-server/
├── internal/ # Internal Go packages
│ ├── controller/ # Controller implementations
│ └── lineagecontrollerwebhook/
├── pkg/ # Public Go packages
│ ├── apis/
│ ├── apiserver/
│ └── registry/
└── api/ # Kubernetes API definitions (CRDs)
└── v1alpha1/
```
## Package Structure
Every package is a Helm chart following the umbrella chart pattern:
```
packages/<category>/<package-name>/
├── Chart.yaml # Chart definition and parameter docs
├── Makefile # Development workflow targets
├── charts/ # Vendored upstream charts
├── images/ # Dockerfiles and image build context
├── patches/ # Optional upstream chart patches
├── templates/ # Additional manifests
├── templates/dashboard-resourcemap.yaml # Dashboard resource mapping
├── values.yaml # Override values for upstream
└── values.schema.json # JSON schema for validation and UI
```
## Conventions
### Helm Charts
- Follow **umbrella chart** pattern for system components
- Include upstream charts in `charts/` directory (vendored, not referenced)
- Override configuration in root `values.yaml`
- Use `values.schema.json` for input validation and dashboard UI rendering
### Go Code
- Follow standard **Go conventions** and idioms
- Use **controller-runtime** patterns for Kubernetes controllers
- Prefer **kubebuilder** for API definitions and controllers
- Add proper error handling and structured logging
### Git Commits
- Use format: `[component] Description`
- Always use `--signoff` flag
- Reference PR numbers when available
- Keep commits atomic and focused
- Follow conventional commit format for changelogs
### Documentation
Documentation is organized as follows:
- `docs/` - General documentation
- `docs/agents/` - Instructions for AI agents
- `docs/changelogs/` - Release changelogs
- Main website: https://github.com/cozystack/website
## Things Agents Should Not Do
### Never Edit These
- Do not modify files in `/vendor/` (Go dependencies)
- Do not edit generated files: `zz_generated.*.go`
- Do not change `go.mod`/`go.sum` manually (use `go get`)
- Do not edit upstream charts in `packages/*/charts/` directly (use patches)
- Do not modify image digests in `values.yaml` (generated by build)
### Version Control
- Do not commit built artifacts from `_out`
- Do not commit test artifacts or temporary files
### Git Operations
- Do not force push to main/master
- Do not update git config
- Do not perform destructive operations without explicit request
### Core Components
- Do not modify `packages/core/platform/` without understanding migration impact

View File

@@ -1,29 +0,0 @@
# Release Process
This document provides instructions for AI agents on how to handle release-related tasks.
## When to Use
Follow these instructions when the user asks to:
- Create a new release
- Prepare a release
- Tag a release
- Perform release-related tasks
## Instructions
For detailed release process instructions, follow the steps documented in:
**[docs/release.md](../release.md)**
## Quick Reference
The release process typically involves:
1. Preparing the release branch
2. Generating changelog
3. Updating version numbers
4. Creating git tags
5. Building and publishing artifacts
All detailed steps are documented in `docs/release.md`.

View File

@@ -0,0 +1,3 @@
# Changes after v0.37.0
* [lineage] Break webhook out into a separate daemonset. Reduce unnecessary webhook calls by marking handled resources and excluding them from consideration by the webhook's object selector (@lllamnyp in #1515).

View File

@@ -1,145 +0,0 @@
#!/bin/sh
###############################################################################
# check-optional-repos.sh - Check optional repositories for tags and commits #
# during a release period #
###############################################################################
set -eu
# Function to ensure repository is cloned and up-to-date
update_repo() {
local repo_name=$1
local repo_url="https://github.com/cozystack/${repo_name}.git"
mkdir -p _repos
cd _repos
if [ -d "$repo_name" ]; then
cd "$repo_name"
git fetch --all --tags --force
git checkout main 2>/dev/null || git checkout master
git pull
else
git clone "$repo_url"
cd "$repo_name"
fi
cd ../..
}
# Check if required parameters are provided
if [ $# -lt 2 ]; then
echo "Usage: $0 <RELEASE_START> <RELEASE_END>"
echo "Example: $0 '2025-10-10 12:27:31 +0400' '2025-10-13 16:04:33 +0200'"
exit 1
fi
RELEASE_START="$1"
RELEASE_END="$2"
# Get the script directory to return to it later
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
COZYSTACK_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
cd "$COZYSTACK_ROOT"
echo "Checking optional repositories for tags and commits between:"
echo " Start: $RELEASE_START"
echo " End: $RELEASE_END"
echo ""
# Loop through ALL optional repositories
for repo_name in talm boot-to-talos cozypkg cozy-proxy; do
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Checking repository: $repo_name"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
# Update/clone repository
update_repo "$repo_name"
cd "_repos/$repo_name"
REPO_NAME=$(basename "$(pwd)")
git fetch --all --tags --force
# Check for tags matching release version pattern or created during release period
TAGS=$(git for-each-ref --format='%(refname:short) %(creatordate)' refs/tags 2>/dev/null | \
awk -v start="$RELEASE_START" -v end="$RELEASE_END" '$2 >= start && $2 <= end {print $1}' || true)
if [ -n "$TAGS" ]; then
echo "Found tags in $repo_name: $TAGS"
PREV_TAG=$(echo "$TAGS" | head -1)
NEW_TAG=$(echo "$TAGS" | tail -1)
echo ""
echo "Commits between $PREV_TAG and $NEW_TAG:"
# Include merge commits to capture backports
git log "$PREV_TAG..$NEW_TAG" --format="%H|%s|%an" 2>/dev/null | while IFS='|' read -r commit_hash subject author_name; do
if [ -z "$commit_hash" ]; then
continue
fi
# Get PR number from commit message
COMMIT_MSG=$(git log -1 --format=%B "$commit_hash" 2>/dev/null || echo "")
PR_NUMBER=$(echo "$COMMIT_MSG" | grep -oE '#[0-9]+' | head -1 | tr -d '#' || echo "")
# Get author: prioritize PR author, fallback to commit author
GITHUB_USERNAME=""
if [ -n "$PR_NUMBER" ]; then
GITHUB_USERNAME=$(gh pr view "$PR_NUMBER" --repo "cozystack/$REPO_NAME" --json author --jq '.author.login // empty' 2>/dev/null || echo "")
fi
if [ -z "$GITHUB_USERNAME" ]; then
GITHUB_USERNAME=$(gh api "repos/cozystack/$REPO_NAME/commits/$commit_hash" --jq '.author.login // empty' 2>/dev/null || echo "")
fi
if [ -n "$PR_NUMBER" ]; then
echo " $commit_hash|$subject|$author_name|$GITHUB_USERNAME|cozystack/$REPO_NAME#$PR_NUMBER"
else
echo " $commit_hash|$subject|$author_name|$GITHUB_USERNAME|cozystack/$REPO_NAME@${commit_hash:0:7}"
fi
done
else
echo "No tags found in $repo_name during release period"
# Check for commits by dates if no exact version tags
# Include merge commits to capture backports
COMMITS=$(git log --since="$RELEASE_START" --until="$RELEASE_END" --format="%H|%s|%an" 2>/dev/null || true)
if [ -n "$COMMITS" ]; then
echo ""
echo "Commits found by date range:"
echo "$COMMITS" | while IFS='|' read -r commit_hash subject author_name; do
if [ -z "$commit_hash" ]; then
continue
fi
# Get PR number from commit message
COMMIT_MSG=$(git log -1 --format=%B "$commit_hash" 2>/dev/null || echo "")
PR_NUMBER=$(echo "$COMMIT_MSG" | grep -oE '#[0-9]+' | head -1 | tr -d '#' || echo "")
# Get author: prioritize PR author, fallback to commit author
GITHUB_USERNAME=""
if [ -n "$PR_NUMBER" ]; then
GITHUB_USERNAME=$(gh pr view "$PR_NUMBER" --repo "cozystack/$REPO_NAME" --json author --jq '.author.login // empty' 2>/dev/null || echo "")
fi
if [ -z "$GITHUB_USERNAME" ]; then
GITHUB_USERNAME=$(gh api "repos/cozystack/$REPO_NAME/commits/$commit_hash" --jq '.author.login // empty' 2>/dev/null || echo "")
fi
if [ -n "$PR_NUMBER" ]; then
echo " $commit_hash|$subject|$author_name|$GITHUB_USERNAME|cozystack/$REPO_NAME#$PR_NUMBER"
else
echo " $commit_hash|$subject|$author_name|$GITHUB_USERNAME|cozystack/$REPO_NAME@${commit_hash:0:7}"
fi
done
else
echo "No commits found in $repo_name during release period"
fi
fi
echo ""
cd "$COZYSTACK_ROOT"
done
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Finished checking all optional repositories"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

View File

@@ -80,41 +80,58 @@ EOF
# Wait for the machine deployment to scale to 2 replicas (timeout after 1 minute)
kubectl wait machinedeployment kubernetes-${test_name}-md0 -n tenant-test --timeout=1m --for=jsonpath='{.status.replicas}'=2
# Get the admin kubeconfig and save it to a file
kubectl get secret kubernetes-${test_name}-admin-kubeconfig -ojsonpath='{.data.super-admin\.conf}' -n tenant-test | base64 -d > tenantkubeconfig-${test_name}
kubectl get secret kubernetes-${test_name}-admin-kubeconfig -ojsonpath='{.data.super-admin\.conf}' -n tenant-test | base64 -d > tenantkubeconfig
# Update the kubeconfig to use localhost for the API server
yq -i ".clusters[0].cluster.server = \"https://localhost:${port}\"" tenantkubeconfig-${test_name}
yq -i ".clusters[0].cluster.server = \"https://localhost:${port}\"" tenantkubeconfig
# Set up port forwarding to the Kubernetes API server for a 200 second timeout
bash -c 'timeout 300s kubectl port-forward service/kubernetes-'"${test_name}"' -n tenant-test '"${port}"':6443 > /dev/null 2>&1 &'
bash -c 'timeout 200s kubectl port-forward service/kubernetes-'"${test_name}"' -n tenant-test '"${port}"':6443 > /dev/null 2>&1 &'
# Verify the Kubernetes version matches what we expect (retry for up to 20 seconds)
timeout 20 sh -ec 'until kubectl --kubeconfig tenantkubeconfig-'"${test_name}"' version 2>/dev/null | grep -Fq "Server Version: ${k8s_version}"; do sleep 5; done'
timeout 20 sh -ec 'until kubectl --kubeconfig tenantkubeconfig version 2>/dev/null | grep -Fq "Server Version: ${k8s_version}"; do sleep 5; done'
# Wait for the nodes to be ready (timeout after 2 minutes)
timeout 3m bash -c '
until [ "$(kubectl --kubeconfig tenantkubeconfig-'"${test_name}"' get nodes -o jsonpath="{.items[*].metadata.name}" | wc -w)" -eq 2 ]; do
sleep 2
timeout 2m bash -c '
until [ "$(kubectl --kubeconfig tenantkubeconfig get nodes -o jsonpath="{.items[*].metadata.name}" | wc -w)" -eq 2 ]; do
sleep 3
done
'
# Verify the nodes are ready
kubectl --kubeconfig tenantkubeconfig-${test_name} wait node --all --timeout=2m --for=condition=Ready
kubectl --kubeconfig tenantkubeconfig-${test_name} get nodes -o wide
kubectl --kubeconfig tenantkubeconfig wait node --all --timeout=2m --for=condition=Ready
kubectl --kubeconfig tenantkubeconfig get nodes -o wide
# Verify the kubelet version matches what we expect
versions=$(kubectl --kubeconfig "tenantkubeconfig-${test_name}" \
get nodes -o jsonpath='{.items[*].status.nodeInfo.kubeletVersion}')
versions=$(kubectl --kubeconfig tenantkubeconfig get nodes -o jsonpath='{.items[*].status.nodeInfo.kubeletVersion}')
node_ok=true
case "$k8s_version" in
v1.32*)
echo "⚠️ TODO: Temporary stub — allowing nodes with v1.33 while k8s_version is v1.32"
;;
esac
for v in $versions; do
case "$v" in
"${k8s_version}" | "${k8s_version}".* | "${k8s_version}"-*)
# acceptable
case "$k8s_version" in
v1.32|v1.32.*)
case "$v" in
v1.32 | v1.32.* | v1.32-* | v1.33 | v1.33.* | v1.33-*)
;;
*)
node_ok=false
break
;;
esac
;;
*)
node_ok=false
break
case "$v" in
"${k8s_version}" | "${k8s_version}".* | "${k8s_version}"-*)
;;
*)
node_ok=false
break
;;
esac
;;
esac
done

View File

@@ -118,7 +118,7 @@ EOF
}
@test "Check Cozystack API service" {
kubectl wait --for=condition=Available apiservices/v1alpha1.apps.cozystack.io apiservices/v1alpha1.core.cozystack.io --timeout=2m
kubectl wait --for=condition=Available apiservices/v1alpha1.apps.cozystack.io --timeout=2m
}
@test "Configure Tenant and wait for applications" {

View File

@@ -132,6 +132,7 @@ machine:
- usermode_helper=disabled
- name: zfs
- name: spl
- name: lldpd
registries:
mirrors:
docker.io:

View File

@@ -9,7 +9,6 @@
@test "Test OpenAPI v3 endpoint" {
kubectl get -v7 --raw '/openapi/v3/apis/apps.cozystack.io/v1alpha1' > /dev/null
kubectl get -v7 --raw '/openapi/v3/apis/core.cozystack.io/v1alpha1' > /dev/null
}
@test "Test OpenAPI v2 endpoint (protobuf)" {
@@ -19,26 +18,3 @@
curl -sS --fail 'http://localhost:21234/openapi/v2?timeout=32s' -H 'Accept: application/com.github.proto-openapi.spec.v2@v1.0+protobuf' > /dev/null
)
}
@test "Test kinds" {
val=$(kubectl get --raw /apis/apps.cozystack.io/v1alpha1/tenants | jq -r '.kind')
if [ "$val" != "TenantList" ]; then
echo "Expected kind to be TenantList, got $val"
exit 1
fi
val=$(kubectl get --raw /apis/apps.cozystack.io/v1alpha1/tenants | jq -r '.items[0].kind')
if [ "$val" != "Tenant" ]; then
echo "Expected kind to be Tenant, got $val"
exit 1
fi
val=$(kubectl get --raw /apis/apps.cozystack.io/v1alpha1/ingresses | jq -r '.kind')
if [ "$val" != "IngressList" ]; then
echo "Expected kind to be IngressList, got $val"
exit 1
fi
val=$(kubectl get --raw /apis/apps.cozystack.io/v1alpha1/ingresses | jq -r '.items[0].kind')
if [ "$val" != "Ingress" ]; then
echo "Expected kind to be Ingress, got $val"
exit 1
fi
}

View File

@@ -1,59 +0,0 @@
#!/bin/sh
set -eu
# Script to run unit tests for all Helm charts.
# It iterates through directories in packages/apps, packages/extra,
# packages/system, and packages/library and runs the 'test' Makefile
# target if it exists.
FAILED_DIRS_FILE="$(mktemp)"
trap 'rm -f "$FAILED_DIRS_FILE"' EXIT
tests_found=0
check_and_run_test() {
dir="$1"
makefile="$dir/Makefile"
if [ ! -f "$makefile" ]; then
return 0
fi
if make -C "$dir" -n test >/dev/null 2>&1; then
echo "Running tests in $dir"
tests_found=$((tests_found + 1))
if ! make -C "$dir" test; then
printf '%s\n' "$dir" >> "$FAILED_DIRS_FILE"
return 1
fi
fi
return 0
}
for package_dir in packages/apps packages/extra packages/system packages/library; do
if [ ! -d "$package_dir" ]; then
echo "Warning: Directory $package_dir does not exist, skipping..." >&2
continue
fi
for dir in "$package_dir"/*; do
[ -d "$dir" ] || continue
check_and_run_test "$dir" || true
done
done
if [ "$tests_found" -eq 0 ]; then
echo "No directories with 'test' Makefile targets found."
exit 0
fi
if [ -s "$FAILED_DIRS_FILE" ]; then
echo "ERROR: Tests failed in the following directories:" >&2
while IFS= read -r dir; do
echo " - $dir" >&2
done < "$FAILED_DIRS_FILE"
exit 1
fi
echo "All Helm unit tests passed."

View File

@@ -1,40 +0,0 @@
#!/bin/sh
if [ $# -ne 1 ]; then
echo "Usage: $0 <version>"
echo "Example: 0.37.*"
exit 1
fi
VERSION_PATTERN="$1"
# Collect matching files first
FILES=$(find docs/changelogs -name "v${VERSION_PATTERN}.md" 2>/dev/null || true)
if [ -z "$FILES" ]; then
echo "No changelog files found matching pattern: v${VERSION_PATTERN}.md"
exit 1
fi
# Process each file
echo "$FILES" | while IFS= read -r file; do
if [ -z "$file" ]; then
continue
fi
# Extract version from filename safely (basename without extension)
version=$(basename "$file" .md)
if [ -z "$version" ]; then
echo "Warning: Could not extract version from file: $file"
continue
fi
echo "Uploading release notes for version: $version"
# Check exit status of gh release edit
if ! gh release edit "$version" --notes-file "docs/changelogs/${version}.md"; then
echo "Error: Failed to upload release notes for version: $version"
exit 1
fi
done

View File

@@ -58,8 +58,8 @@ func (m *Manager) ensureBreadcrumb(ctx context.Context, crd *cozyv1alpha1.Cozyst
"breadcrumbItems": items,
}
_, err := controllerutil.CreateOrUpdate(ctx, m.Client, obj, func() error {
if err := controllerutil.SetOwnerReference(crd, obj, m.Scheme); err != nil {
_, err := controllerutil.CreateOrUpdate(ctx, m.client, obj, func() error {
if err := controllerutil.SetOwnerReference(crd, obj, m.scheme); err != nil {
return err
}
// Add dashboard labels to dynamic resources

View File

@@ -30,6 +30,10 @@ func (m *Manager) ensureCustomColumnsOverride(ctx context.Context, crd *cozyv1al
name := fmt.Sprintf("stock-namespace-%s.%s.%s", g, v, plural)
id := fmt.Sprintf("stock-namespace-/%s/%s/%s", g, v, plural)
// Badge content & color derived from kind
badgeText := initialsFromKind(kind) // e.g., "VirtualMachine" -> "VM", "Bucket" -> "B"
badgeColor := hexColorForKind(kind) // deterministic, dark enough for white text
obj := &dashv1alpha1.CustomColumnsOverride{}
obj.SetName(name)
@@ -58,11 +62,25 @@ func (m *Manager) ensureCustomColumnsOverride(ctx context.Context, crd *cozyv1al
},
"children": []any{
map[string]any{
"type": "ResourceBadge",
"type": "antdText",
"data": map[string]any{
"id": "header-badge",
"value": kind,
// abbreviation auto-generated by ResourceBadge from value
"text": badgeText,
"title": strings.ToLower(kind), // optional tooltip
"style": map[string]any{
"backgroundColor": badgeColor,
"borderRadius": "20px",
"color": "#fff",
"display": "inline-block",
"fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif",
"fontSize": "15px",
"fontWeight": 400,
"lineHeight": "24px",
"minWidth": 24,
"padding": "0 9px",
"textAlign": "center",
"whiteSpace": "nowrap",
},
},
},
map[string]any{
@@ -127,8 +145,8 @@ func (m *Manager) ensureCustomColumnsOverride(ctx context.Context, crd *cozyv1al
},
}
_, err := controllerutil.CreateOrUpdate(ctx, m.Client, obj, func() error {
if err := controllerutil.SetOwnerReference(crd, obj, m.Scheme); err != nil {
_, err := controllerutil.CreateOrUpdate(ctx, m.client, obj, func() error {
if err := controllerutil.SetOwnerReference(crd, obj, m.scheme); err != nil {
return err
}
// Add dashboard labels to dynamic resources

View File

@@ -11,7 +11,6 @@ import (
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/log"
)
// ensureCustomFormsOverride creates or updates a CustomFormsOverride resource for the given CRD
@@ -46,25 +45,16 @@ func (m *Manager) ensureCustomFormsOverride(ctx context.Context, crd *cozyv1alph
}
}
// Build schema with multilineString for string fields without enum
l := log.FromContext(ctx)
schema, err := buildMultilineStringSchema(crd.Spec.Application.OpenAPISchema)
if err != nil {
// If schema parsing fails, log the error and use an empty schema
l.Error(err, "failed to build multiline string schema, using empty schema", "crd", crd.Name)
schema = map[string]any{}
}
spec := map[string]any{
"customizationId": customizationID,
"hidden": hidden,
"sort": sort,
"schema": schema,
"schema": map[string]any{}, // {}
"strategy": "merge",
}
_, err = controllerutil.CreateOrUpdate(ctx, m.Client, obj, func() error {
if err := controllerutil.SetOwnerReference(crd, obj, m.Scheme); err != nil {
_, err := controllerutil.CreateOrUpdate(ctx, m.client, obj, func() error {
if err := controllerutil.SetOwnerReference(crd, obj, m.scheme); err != nil {
return err
}
// Add dashboard labels to dynamic resources
@@ -83,94 +73,3 @@ func (m *Manager) ensureCustomFormsOverride(ctx context.Context, crd *cozyv1alph
})
return err
}
// buildMultilineStringSchema parses OpenAPI schema and creates schema with multilineString
// for all string fields inside spec that don't have enum
func buildMultilineStringSchema(openAPISchema string) (map[string]any, error) {
if openAPISchema == "" {
return map[string]any{}, nil
}
var root map[string]any
if err := json.Unmarshal([]byte(openAPISchema), &root); err != nil {
return nil, fmt.Errorf("cannot parse openAPISchema: %w", err)
}
props, _ := root["properties"].(map[string]any)
if props == nil {
return map[string]any{}, nil
}
schema := map[string]any{
"properties": map[string]any{},
}
// Process spec properties recursively
processSpecProperties(props, schema["properties"].(map[string]any))
return schema, nil
}
// processSpecProperties recursively processes spec properties and adds multilineString type
// for string fields without enum
func processSpecProperties(props map[string]any, schemaProps map[string]any) {
for pname, raw := range props {
sub, ok := raw.(map[string]any)
if !ok {
continue
}
typ, _ := sub["type"].(string)
switch typ {
case "string":
// Check if this string field has enum
if !hasEnum(sub) {
// Add multilineString type for this field
if schemaProps[pname] == nil {
schemaProps[pname] = map[string]any{}
}
fieldSchema := schemaProps[pname].(map[string]any)
fieldSchema["type"] = "multilineString"
}
case "object":
// Recursively process nested objects
if childProps, ok := sub["properties"].(map[string]any); ok {
fieldSchema, ok := schemaProps[pname].(map[string]any)
if !ok {
fieldSchema = map[string]any{}
schemaProps[pname] = fieldSchema
}
nestedSchemaProps, ok := fieldSchema["properties"].(map[string]any)
if !ok {
nestedSchemaProps = map[string]any{}
fieldSchema["properties"] = nestedSchemaProps
}
processSpecProperties(childProps, nestedSchemaProps)
}
case "array":
// Check if array items are objects with properties
if items, ok := sub["items"].(map[string]any); ok {
if itemProps, ok := items["properties"].(map[string]any); ok {
// Create array item schema
fieldSchema, ok := schemaProps[pname].(map[string]any)
if !ok {
fieldSchema = map[string]any{}
schemaProps[pname] = fieldSchema
}
itemSchema, ok := fieldSchema["items"].(map[string]any)
if !ok {
itemSchema = map[string]any{}
fieldSchema["items"] = itemSchema
}
itemSchemaProps, ok := itemSchema["properties"].(map[string]any)
if !ok {
itemSchemaProps = map[string]any{}
itemSchema["properties"] = itemSchemaProps
}
processSpecProperties(itemProps, itemSchemaProps)
}
}
}
}
}

View File

@@ -1,155 +0,0 @@
package dashboard
import (
"encoding/json"
"testing"
)
func TestBuildMultilineStringSchema(t *testing.T) {
// Test OpenAPI schema with various field types
openAPISchema := `{
"properties": {
"simpleString": {
"type": "string",
"description": "A simple string field"
},
"stringWithEnum": {
"type": "string",
"enum": ["option1", "option2"],
"description": "String with enum should be skipped"
},
"numberField": {
"type": "number",
"description": "Number field should be skipped"
},
"nestedObject": {
"type": "object",
"properties": {
"nestedString": {
"type": "string",
"description": "Nested string should get multilineString"
},
"nestedStringWithEnum": {
"type": "string",
"enum": ["a", "b"],
"description": "Nested string with enum should be skipped"
}
}
},
"arrayOfObjects": {
"type": "array",
"items": {
"type": "object",
"properties": {
"itemString": {
"type": "string",
"description": "String in array item"
}
}
}
}
}
}`
schema, err := buildMultilineStringSchema(openAPISchema)
if err != nil {
t.Fatalf("buildMultilineStringSchema failed: %v", err)
}
// Marshal to JSON for easier inspection
schemaJSON, err := json.MarshalIndent(schema, "", " ")
if err != nil {
t.Fatalf("Failed to marshal schema: %v", err)
}
t.Logf("Generated schema:\n%s", schemaJSON)
// Verify that simpleString has multilineString type
props, ok := schema["properties"].(map[string]any)
if !ok {
t.Fatal("schema.properties is not a map")
}
// Check simpleString
simpleString, ok := props["simpleString"].(map[string]any)
if !ok {
t.Fatal("simpleString not found in properties")
}
if simpleString["type"] != "multilineString" {
t.Errorf("simpleString should have type multilineString, got %v", simpleString["type"])
}
// Check stringWithEnum should not be present (or should not have multilineString)
if stringWithEnum, ok := props["stringWithEnum"].(map[string]any); ok {
if stringWithEnum["type"] == "multilineString" {
t.Error("stringWithEnum should not have multilineString type")
}
}
// Check numberField should not be present
if numberField, ok := props["numberField"].(map[string]any); ok {
if numberField["type"] != nil {
t.Error("numberField should not have any type override")
}
}
// Check nested object
nestedObject, ok := props["nestedObject"].(map[string]any)
if !ok {
t.Fatal("nestedObject not found in properties")
}
nestedProps, ok := nestedObject["properties"].(map[string]any)
if !ok {
t.Fatal("nestedObject.properties is not a map")
}
// Check nestedString
nestedString, ok := nestedProps["nestedString"].(map[string]any)
if !ok {
t.Fatal("nestedString not found in nestedObject.properties")
}
if nestedString["type"] != "multilineString" {
t.Errorf("nestedString should have type multilineString, got %v", nestedString["type"])
}
// Check array of objects
arrayOfObjects, ok := props["arrayOfObjects"].(map[string]any)
if !ok {
t.Fatal("arrayOfObjects not found in properties")
}
items, ok := arrayOfObjects["items"].(map[string]any)
if !ok {
t.Fatal("arrayOfObjects.items is not a map")
}
itemProps, ok := items["properties"].(map[string]any)
if !ok {
t.Fatal("arrayOfObjects.items.properties is not a map")
}
itemString, ok := itemProps["itemString"].(map[string]any)
if !ok {
t.Fatal("itemString not found in arrayOfObjects.items.properties")
}
if itemString["type"] != "multilineString" {
t.Errorf("itemString should have type multilineString, got %v", itemString["type"])
}
}
func TestBuildMultilineStringSchemaEmpty(t *testing.T) {
schema, err := buildMultilineStringSchema("")
if err != nil {
t.Fatalf("buildMultilineStringSchema failed on empty string: %v", err)
}
if len(schema) != 0 {
t.Errorf("Expected empty schema for empty input, got %v", schema)
}
}
func TestBuildMultilineStringSchemaInvalidJSON(t *testing.T) {
schema, err := buildMultilineStringSchema("{invalid json")
if err == nil {
t.Error("Expected error for invalid JSON")
}
if schema != nil {
t.Errorf("Expected nil schema for invalid JSON, got %v", schema)
}
}

View File

@@ -56,8 +56,8 @@ func (m *Manager) ensureCustomFormsPrefill(ctx context.Context, crd *cozyv1alpha
return reconcile.Result{}, err
}
_, err = controllerutil.CreateOrUpdate(ctx, m.Client, cfp, func() error {
if err := controllerutil.SetOwnerReference(crd, cfp, m.Scheme); err != nil {
_, err = controllerutil.CreateOrUpdate(ctx, m.client, cfp, func() error {
if err := controllerutil.SetOwnerReference(crd, cfp, m.scheme); err != nil {
return err
}
// Add dashboard labels to dynamic resources

View File

@@ -44,9 +44,6 @@ func (m *Manager) ensureFactory(ctx context.Context, crd *cozyv1alpha1.Cozystack
if flags.Secrets {
tabs = append(tabs, secretsTab(kind))
}
if prefix, ok := vncTabPrefix(kind); ok {
tabs = append(tabs, vncTab(prefix))
}
tabs = append(tabs, yamlTab(plural))
// Use unified factory creation
@@ -56,6 +53,7 @@ func (m *Manager) ensureFactory(ctx context.Context, crd *cozyv1alpha1.Cozystack
Kind: kind,
Plural: plural,
Title: strings.ToLower(plural),
Size: BadgeSizeLarge,
}
spec := createUnifiedFactory(config, tabs, []any{resourceFetch})
@@ -63,8 +61,8 @@ func (m *Manager) ensureFactory(ctx context.Context, crd *cozyv1alpha1.Cozystack
obj := &dashv1alpha1.Factory{}
obj.SetName(factoryName)
_, err := controllerutil.CreateOrUpdate(ctx, m.Client, obj, func() error {
if err := controllerutil.SetOwnerReference(crd, obj, m.Scheme); err != nil {
_, err := controllerutil.CreateOrUpdate(ctx, m.client, obj, func() error {
if err := controllerutil.SetOwnerReference(crd, obj, m.scheme); err != nil {
return err
}
// Add dashboard labels to dynamic resources
@@ -117,7 +115,7 @@ func detailsTab(kind, endpoint, schemaJSON string, keysOrder [][]string) map[str
"gap": float64(6),
},
"children": []any{
createUnifiedBadgeFromKind("ns-badge", "Namespace"),
createUnifiedBadgeFromKind("ns-badge", "Namespace", "namespace", BadgeSizeMedium),
antdLink("namespace-link",
"{reqsJsonPath[0]['.metadata.namespace']['-']}",
"/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace",
@@ -153,27 +151,6 @@ func detailsTab(kind, endpoint, schemaJSON string, keysOrder [][]string) map[str
}),
paramsList,
}
if kind == "VirtualPrivateCloud" {
rightColStack = append(rightColStack,
antdFlexVertical("vpc-subnets-block", 4, []any{
antdText("vpc-subnets-label", true, "Subnets", nil),
map[string]any{
"type": "EnrichedTable",
"data": map[string]any{
"id": "vpc-subnets-table",
"baseprefix": "/openapi-ui",
"clusterNamePartOfUrl": "{2}",
"customizationId": "virtualprivatecloud-subnets",
"fetchUrl": "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/configmaps",
"fieldSelector": map[string]any{
"metadata.name": "virtualprivatecloud-{6}-subnets",
},
"pathToItems": []any{"items"},
},
},
}),
)
}
return map[string]any{
"key": "details",
@@ -245,7 +222,7 @@ func workloadsTab(kind string) map[string]any {
"baseprefix": "/openapi-ui",
"customizationId": "factory-details-v1alpha1.cozystack.io.workloadmonitors",
"pathToItems": []any{"items"},
"labelSelector": map[string]any{
"labelsSelector": map[string]any{
"apps.cozystack.io/application.group": "apps.cozystack.io",
"apps.cozystack.io/application.kind": kind,
"apps.cozystack.io/application.name": "{reqs[0]['metadata','name']}",
@@ -270,7 +247,7 @@ func servicesTab(kind string) map[string]any {
"baseprefix": "/openapi-ui",
"customizationId": "factory-details-v1.services",
"pathToItems": []any{"items"},
"labelSelector": map[string]any{
"labelsSelector": map[string]any{
"apps.cozystack.io/application.group": "apps.cozystack.io",
"apps.cozystack.io/application.kind": kind,
"apps.cozystack.io/application.name": "{reqs[0]['metadata','name']}",
@@ -296,7 +273,7 @@ func ingressesTab(kind string) map[string]any {
"baseprefix": "/openapi-ui",
"customizationId": "factory-details-networking.k8s.io.v1.ingresses",
"pathToItems": []any{"items"},
"labelSelector": map[string]any{
"labelsSelector": map[string]any{
"apps.cozystack.io/application.group": "apps.cozystack.io",
"apps.cozystack.io/application.kind": kind,
"apps.cozystack.io/application.name": "{reqs[0]['metadata','name']}",
@@ -317,12 +294,12 @@ func secretsTab(kind string) map[string]any {
"type": "EnrichedTable",
"data": map[string]any{
"id": "secrets-table",
"fetchUrl": "/api/clusters/{2}/k8s/apis/core.cozystack.io/v1alpha1/namespaces/{3}/tenantsecrets",
"fetchUrl": "/api/clusters/{2}/k8s/apis/core.cozystack.io/v1alpha1/namespaces/{3}/tenantsecretstables",
"clusterNamePartOfUrl": "{2}",
"baseprefix": "/openapi-ui",
"customizationId": "factory-details-v1alpha1.core.cozystack.io.tenantsecrets",
"customizationId": "factory-details-v1alpha1.core.cozystack.io.tenantsecretstables",
"pathToItems": []any{"items"},
"labelSelector": map[string]any{
"labelsSelector": map[string]any{
"apps.cozystack.io/application.group": "apps.cozystack.io",
"apps.cozystack.io/application.kind": kind,
"apps.cozystack.io/application.name": "{reqs[0]['metadata','name']}",
@@ -347,7 +324,6 @@ func yamlTab(plural string) map[string]any {
"type": "builtin",
"typeName": plural,
"prefillValuesRequestIndex": float64(0),
"readOnly": true,
"substractHeight": float64(400),
},
},
@@ -355,36 +331,6 @@ func yamlTab(plural string) map[string]any {
}
}
func vncTabPrefix(kind string) (string, bool) {
switch kind {
case "VirtualMachine":
return "virtual-machine", true
case "VMInstance":
return "vm-instance", true
default:
return "", false
}
}
func vncTab(prefix string) map[string]any {
return map[string]any{
"key": "vnc",
"label": "VNC",
"children": []any{
map[string]any{
"type": "VMVNC",
"data": map[string]any{
"id": "vm-vnc",
"cluster": "{2}",
"namespace": "{reqsJsonPath[0]['.metadata.namespace']['-']}",
"substractHeight": float64(400),
"vmName": fmt.Sprintf("%s-{reqsJsonPath[0]['.metadata.name']['-']}", prefix),
},
},
},
}
}
// ---------------- OpenAPI → Right column ----------------
func buildOpenAPIParamsBlocks(schemaJSON string, keysOrder [][]string) []any {

View File

@@ -1,9 +1,12 @@
package dashboard
import (
"crypto/sha1"
"encoding/hex"
"encoding/json"
"fmt"
"reflect"
"regexp"
"sort"
"strings"
@@ -54,6 +57,97 @@ func pickPlural(kind string, crd *cozyv1alpha1.CozystackResourceDefinition) stri
return k + "s"
}
// initialsFromKind splits CamelCase and returns the first letters in upper case.
// "VirtualMachine" -> "VM"; "Bucket" -> "B".
func initialsFromKind(kind string) string {
parts := splitCamel(kind)
if len(parts) == 0 {
return strings.ToUpper(kind)
}
var b strings.Builder
for _, p := range parts {
if p == "" {
continue
}
b.WriteString(strings.ToUpper(string(p[0])))
// Limit to 3 chars to keep the badge compact (VM, PVC, etc.)
if b.Len() >= 3 {
break
}
}
return b.String()
}
// hexColorForKind returns a dark, saturated color (hex) derived from a stable hash of the kind.
// We map the hash to an HSL hue; fix S/L for consistent readability with white text.
func hexColorForKind(kind string) string {
// Stable short hash (sha1 → bytes → hue)
sum := sha1.Sum([]byte(kind))
// Use first two bytes for hue [0..359]
hue := int(sum[0])<<8 | int(sum[1])
hue = hue % 360
// Fixed S/L chosen to contrast with white text:
// S = 80%, L = 35% (dark enough so #fff is readable)
r, g, b := hslToRGB(float64(hue), 0.80, 0.35)
return fmt.Sprintf("#%02x%02x%02x", r, g, b)
}
// hslToRGB converts HSL (0..360, 0..1, 0..1) to sRGB (0..255).
func hslToRGB(h float64, s float64, l float64) (uint8, uint8, uint8) {
c := (1 - absFloat(2*l-1)) * s
hp := h / 60.0
x := c * (1 - absFloat(modFloat(hp, 2)-1))
var r1, g1, b1 float64
switch {
case 0 <= hp && hp < 1:
r1, g1, b1 = c, x, 0
case 1 <= hp && hp < 2:
r1, g1, b1 = x, c, 0
case 2 <= hp && hp < 3:
r1, g1, b1 = 0, c, x
case 3 <= hp && hp < 4:
r1, g1, b1 = 0, x, c
case 4 <= hp && hp < 5:
r1, g1, b1 = x, 0, c
default:
r1, g1, b1 = c, 0, x
}
m := l - c/2
r := uint8(clamp01(r1+m) * 255.0)
g := uint8(clamp01(g1+m) * 255.0)
b := uint8(clamp01(b1+m) * 255.0)
return r, g, b
}
func absFloat(v float64) float64 {
if v < 0 {
return -v
}
return v
}
func modFloat(a, b float64) float64 {
return a - b*float64(int(a/b))
}
func clamp01(v float64) float64 {
if v < 0 {
return 0
}
if v > 1 {
return 1
}
return v
}
// optional: tiny helper to expose the compact color hash (useful for debugging)
func shortHashHex(s string) string {
sum := sha1.Sum([]byte(s))
return hex.EncodeToString(sum[:4])
}
// ----------------------- Helpers (OpenAPI → values) -----------------------
// defaultOrZero returns the schema default if present; otherwise a reasonable zero value.
@@ -201,6 +295,12 @@ func normalizeJSON(v any) any {
}
}
var camelSplitter = regexp.MustCompile(`(?m)([A-Z]+[a-z0-9]*|[a-z0-9]+)`)
func splitCamel(s string) []string {
return camelSplitter.FindAllString(s, -1)
}
// --- helpers for schema inspection ---
func isScalarType(n map[string]any) bool {

View File

@@ -10,12 +10,9 @@ import (
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
managerpkg "sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
@@ -43,51 +40,28 @@ func AddToScheme(s *runtime.Scheme) error {
// Manager owns logic for creating/updating dashboard resources derived from CRDs.
// Its easy to extend: add new ensure* methods and wire them into EnsureForCRD.
type Manager struct {
client.Client
Scheme *runtime.Scheme
client client.Client
scheme *runtime.Scheme
crdListFn func(context.Context) ([]cozyv1alpha1.CozystackResourceDefinition, error)
}
// Option pattern so callers can inject a custom lister.
type Option func(*Manager)
// WithCRDListFunc overrides how Manager lists all CozystackResourceDefinitions.
func WithCRDListFunc(fn func(context.Context) ([]cozyv1alpha1.CozystackResourceDefinition, error)) Option {
return func(m *Manager) { m.crdListFn = fn }
}
// NewManager constructs a dashboard Manager.
func NewManager(c client.Client, scheme *runtime.Scheme) *Manager {
m := &Manager{Client: c, Scheme: scheme}
func NewManager(c client.Client, scheme *runtime.Scheme, opts ...Option) *Manager {
m := &Manager{client: c, scheme: scheme}
for _, o := range opts {
o(m)
}
return m
}
func (m *Manager) SetupWithManager(mgr ctrl.Manager) error {
if err := ctrl.NewControllerManagedBy(mgr).
Named("dashboard-reconciler").
For(&cozyv1alpha1.CozystackResourceDefinition{}).
Complete(m); err != nil {
return err
}
return mgr.Add(managerpkg.RunnableFunc(func(ctx context.Context) error {
if !mgr.GetCache().WaitForCacheSync(ctx) {
return fmt.Errorf("dashboard static resources cache sync failed")
}
return m.ensureStaticResources(ctx)
}))
}
func (m *Manager) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
l := log.FromContext(ctx)
crd := &cozyv1alpha1.CozystackResourceDefinition{}
err := m.Get(ctx, types.NamespacedName{Name: req.Name}, crd)
if err != nil {
if apierrors.IsNotFound(err) {
if err := m.CleanupOrphanedResources(ctx); err != nil {
l.Error(err, "Failed to cleanup orphaned dashboard resources")
}
return ctrl.Result{}, nil // no point in requeuing here
}
return ctrl.Result{}, err
}
return m.EnsureForCRD(ctx, crd)
}
// EnsureForCRD is the single entry-point used by the controller.
// Add more ensure* calls here as you implement support for other resources:
//
@@ -197,11 +171,21 @@ func (m *Manager) getStaticResourceSelector() client.MatchingLabels {
// CleanupOrphanedResources removes dashboard resources that are no longer needed
// This should be called after cache warming to ensure all current resources are known
func (m *Manager) CleanupOrphanedResources(ctx context.Context) error {
var crdList cozyv1alpha1.CozystackResourceDefinitionList
if err := m.List(ctx, &crdList, &client.ListOptions{}); err != nil {
return err
// Get all current CRDs to determine which resources should exist
var allCRDs []cozyv1alpha1.CozystackResourceDefinition
if m.crdListFn != nil {
s, err := m.crdListFn(ctx)
if err != nil {
return err
}
allCRDs = s
} else {
var crdList cozyv1alpha1.CozystackResourceDefinitionList
if err := m.client.List(ctx, &crdList, &client.ListOptions{}); err != nil {
return err
}
allCRDs = crdList.Items
}
allCRDs := crdList.Items
// Build a set of expected resource names for each type
expectedResources := m.buildExpectedResourceSet(allCRDs)
@@ -365,7 +349,7 @@ func (m *Manager) cleanupResourceType(ctx context.Context, resourceType client.O
}
// List with dashboard labels
if err := m.List(ctx, list, m.getDashboardResourceSelector()); err != nil {
if err := m.client.List(ctx, list, m.getDashboardResourceSelector()); err != nil {
return err
}
@@ -374,7 +358,7 @@ func (m *Manager) cleanupResourceType(ctx context.Context, resourceType client.O
case *dashv1alpha1.CustomColumnsOverrideList:
for _, item := range l.Items {
if !expected[item.Name] {
if err := m.Delete(ctx, &item); err != nil {
if err := m.client.Delete(ctx, &item); err != nil {
if !apierrors.IsNotFound(err) {
return err
}
@@ -385,7 +369,7 @@ func (m *Manager) cleanupResourceType(ctx context.Context, resourceType client.O
case *dashv1alpha1.CustomFormsOverrideList:
for _, item := range l.Items {
if !expected[item.Name] {
if err := m.Delete(ctx, &item); err != nil {
if err := m.client.Delete(ctx, &item); err != nil {
if !apierrors.IsNotFound(err) {
return err
}
@@ -396,7 +380,7 @@ func (m *Manager) cleanupResourceType(ctx context.Context, resourceType client.O
case *dashv1alpha1.CustomFormsPrefillList:
for _, item := range l.Items {
if !expected[item.Name] {
if err := m.Delete(ctx, &item); err != nil {
if err := m.client.Delete(ctx, &item); err != nil {
if !apierrors.IsNotFound(err) {
return err
}
@@ -407,7 +391,7 @@ func (m *Manager) cleanupResourceType(ctx context.Context, resourceType client.O
case *dashv1alpha1.MarketplacePanelList:
for _, item := range l.Items {
if !expected[item.Name] {
if err := m.Delete(ctx, &item); err != nil {
if err := m.client.Delete(ctx, &item); err != nil {
if !apierrors.IsNotFound(err) {
return err
}
@@ -418,7 +402,7 @@ func (m *Manager) cleanupResourceType(ctx context.Context, resourceType client.O
case *dashv1alpha1.SidebarList:
for _, item := range l.Items {
if !expected[item.Name] {
if err := m.Delete(ctx, &item); err != nil {
if err := m.client.Delete(ctx, &item); err != nil {
if !apierrors.IsNotFound(err) {
return err
}
@@ -429,7 +413,7 @@ func (m *Manager) cleanupResourceType(ctx context.Context, resourceType client.O
case *dashv1alpha1.TableUriMappingList:
for _, item := range l.Items {
if !expected[item.Name] {
if err := m.Delete(ctx, &item); err != nil {
if err := m.client.Delete(ctx, &item); err != nil {
if !apierrors.IsNotFound(err) {
return err
}
@@ -442,7 +426,7 @@ func (m *Manager) cleanupResourceType(ctx context.Context, resourceType client.O
if !expected[item.Name] {
logger := log.FromContext(ctx)
logger.Info("Deleting orphaned Breadcrumb resource", "name", item.Name)
if err := m.Delete(ctx, &item); err != nil {
if err := m.client.Delete(ctx, &item); err != nil {
if !apierrors.IsNotFound(err) {
return err
}
@@ -454,7 +438,7 @@ func (m *Manager) cleanupResourceType(ctx context.Context, resourceType client.O
if !expected[item.Name] {
logger := log.FromContext(ctx)
logger.Info("Deleting orphaned Factory resource", "name", item.Name)
if err := m.Delete(ctx, &item); err != nil {
if err := m.client.Delete(ctx, &item); err != nil {
if !apierrors.IsNotFound(err) {
return err
}

View File

@@ -24,14 +24,14 @@ func (m *Manager) ensureMarketplacePanel(ctx context.Context, crd *cozyv1alpha1.
// If dashboard is not set, delete the panel if it exists.
if crd.Spec.Dashboard == nil {
err := m.Get(ctx, client.ObjectKey{Name: mp.Name}, mp)
err := m.client.Get(ctx, client.ObjectKey{Name: mp.Name}, mp)
if apierrors.IsNotFound(err) {
return reconcile.Result{}, nil
}
if err != nil {
return reconcile.Result{}, err
}
if err := m.Delete(ctx, mp); err != nil && !apierrors.IsNotFound(err) {
if err := m.client.Delete(ctx, mp); err != nil && !apierrors.IsNotFound(err) {
return reconcile.Result{}, err
}
logger.Info("Deleted MarketplacePanel because dashboard is not set", "name", mp.Name)
@@ -40,14 +40,14 @@ func (m *Manager) ensureMarketplacePanel(ctx context.Context, crd *cozyv1alpha1.
// Skip module and tenant resources (they don't need MarketplacePanel)
if crd.Spec.Dashboard.Module || crd.Spec.Application.Kind == "Tenant" {
err := m.Get(ctx, client.ObjectKey{Name: mp.Name}, mp)
err := m.client.Get(ctx, client.ObjectKey{Name: mp.Name}, mp)
if apierrors.IsNotFound(err) {
return reconcile.Result{}, nil
}
if err != nil {
return reconcile.Result{}, err
}
if err := m.Delete(ctx, mp); err != nil && !apierrors.IsNotFound(err) {
if err := m.client.Delete(ctx, mp); err != nil && !apierrors.IsNotFound(err) {
return reconcile.Result{}, err
}
logger.Info("Deleted MarketplacePanel because resource is a module", "name", mp.Name)
@@ -86,8 +86,8 @@ func (m *Manager) ensureMarketplacePanel(ctx context.Context, crd *cozyv1alpha1.
return reconcile.Result{}, err
}
_, err = controllerutil.CreateOrUpdate(ctx, m.Client, mp, func() error {
if err := controllerutil.SetOwnerReference(crd, mp, m.Scheme); err != nil {
_, err = controllerutil.CreateOrUpdate(ctx, m.client, mp, func() error {
if err := controllerutil.SetOwnerReference(crd, mp, m.scheme); err != nil {
return err
}
// Add dashboard labels to dynamic resources

View File

@@ -33,11 +33,19 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Cozystack
// 1) Fetch all CRDs
var all []cozyv1alpha1.CozystackResourceDefinition
var crdList cozyv1alpha1.CozystackResourceDefinitionList
if err := m.List(ctx, &crdList, &client.ListOptions{}); err != nil {
return err
if m.crdListFn != nil {
s, err := m.crdListFn(ctx)
if err != nil {
return err
}
all = s
} else {
var crdList cozyv1alpha1.CozystackResourceDefinitionList
if err := m.client.List(ctx, &crdList, &client.ListOptions{}); err != nil {
return err
}
all = crdList.Items
}
all = crdList.Items
// 2) Build category -> []item map (only for CRDs with spec.dashboard != nil)
type item struct {
@@ -243,7 +251,7 @@ func (m *Manager) upsertMultipleSidebars(
obj := &dashv1alpha1.Sidebar{}
obj.SetName(id)
if _, err := controllerutil.CreateOrUpdate(ctx, m.Client, obj, func() error {
if _, err := controllerutil.CreateOrUpdate(ctx, m.client, obj, func() error {
// Only set owner reference for dynamic sidebars (stock-project-factory-{kind}-details)
// Static sidebars (stock-instance-*, stock-project-*) should not have owner references
if strings.HasPrefix(id, "stock-project-factory-") && strings.HasSuffix(id, "-details") {
@@ -252,7 +260,7 @@ func (m *Manager) upsertMultipleSidebars(
lowerKind := strings.ToLower(kind)
expectedID := fmt.Sprintf("stock-project-factory-%s-details", lowerKind)
if id == expectedID {
if err := controllerutil.SetOwnerReference(crd, obj, m.Scheme); err != nil {
if err := controllerutil.SetOwnerReference(crd, obj, m.scheme); err != nil {
return err
}
// Add dashboard labels to dynamic resources

View File

@@ -122,7 +122,7 @@ func createCustomColumnsOverride(id string, additionalPrinterColumns []any) *das
}
}
if name == "factory-details-v1alpha1.core.cozystack.io.tenantsecrets" {
if name == "factory-details-v1alpha1.core.cozystack.io.tenantsecretstables" {
data["additionalPrinterColumnsTrimLengths"] = []any{
map[string]any{
"key": "Name",
@@ -531,6 +531,7 @@ func createBreadcrumbItem(key, label string, link ...string) map[string]any {
// createCustomColumn creates a custom column with factory type and badge
func createCustomColumn(name, kind, plural, href string) map[string]any {
badge := createUnifiedBadgeFromKind("header-badge", kind, plural, BadgeSizeMedium)
link := antdLink("name-link", "{reqsJsonPath[0]['.metadata.name']['-']}", href)
return map[string]any{
@@ -540,18 +541,8 @@ func createCustomColumn(name, kind, plural, href string) map[string]any {
"disableEventBubbling": true,
"items": []any{
map[string]any{
"children": []any{
map[string]any{
"type": "ResourceBadge",
"data": map[string]any{
"id": "header-badge",
"value": kind,
// abbreviation auto-generated by ResourceBadge from value
},
},
link,
},
"type": "antdFlex",
"children": []any{badge, link},
"type": "antdFlex",
"data": map[string]any{
"align": "center",
"gap": float64(6),
@@ -563,15 +554,15 @@ func createCustomColumn(name, kind, plural, href string) map[string]any {
}
// createCustomColumnWithBadge creates a custom column with a specific badge
// badgeValue should be the kind in PascalCase (e.g., "Service", "Pod")
// abbreviation is auto-generated by ResourceBadge from badgeValue
func createCustomColumnWithBadge(name, badgeValue, href string) map[string]any {
link := antdLink("name-link", "{reqsJsonPath[0]['.metadata.name']['-']}", href)
badgeData := map[string]any{
"id": "header-badge",
"value": badgeValue,
func createCustomColumnWithBadge(name, badgeText, badgeColor, title, href string) map[string]any {
config := BadgeConfig{
Text: badgeText,
Color: badgeColor,
Title: title,
Size: BadgeSizeMedium,
}
badge := createUnifiedBadge("header-badge", config)
link := antdLink("name-link", "{reqsJsonPath[0]['.metadata.name']['-']}", href)
return map[string]any{
"name": name,
@@ -580,14 +571,8 @@ func createCustomColumnWithBadge(name, badgeValue, href string) map[string]any {
"disableEventBubbling": true,
"items": []any{
map[string]any{
"children": []any{
map[string]any{
"type": "ResourceBadge",
"data": badgeData,
},
link,
},
"type": "antdFlex",
"children": []any{badge, link},
"type": "antdFlex",
"data": map[string]any{
"align": "center",
"gap": float64(6),
@@ -598,22 +583,17 @@ func createCustomColumnWithBadge(name, badgeValue, href string) map[string]any {
}
}
// createCustomColumnWithSpecificColor creates a custom column with a specific kind and optional color
// badgeValue should be the kind in PascalCase (e.g., "Service", "Pod")
func createCustomColumnWithSpecificColor(name, kind, color, href string) map[string]any {
// createCustomColumnWithSpecificColor creates a custom column with a specific color
func createCustomColumnWithSpecificColor(name, kind, title, color, href string) map[string]any {
config := BadgeConfig{
Text: initialsFromKind(kind),
Color: color,
Title: title,
Size: BadgeSizeMedium,
}
badge := createUnifiedBadge("header-badge", config)
link := antdLink("name-link", "{reqsJsonPath[0]['.metadata.name']['-']}", href)
badgeData := map[string]any{
"id": "header-badge",
"value": kind,
}
// Add custom color if specified
if color != "" {
badgeData["style"] = map[string]any{
"backgroundColor": color,
}
}
return map[string]any{
"name": name,
"type": "factory",
@@ -622,14 +602,8 @@ func createCustomColumnWithSpecificColor(name, kind, color, href string) map[str
"disableEventBubbling": true,
"items": []any{
map[string]any{
"children": []any{
map[string]any{
"type": "ResourceBadge",
"data": badgeData,
},
link,
},
"type": "antdFlex",
"children": []any{badge, link},
"type": "antdFlex",
"data": map[string]any{
"align": "center",
"gap": float64(6),
@@ -694,7 +668,7 @@ func createTimestampColumn(name, jsonPath string) map[string]any {
// createFactoryHeader creates a header for factory resources
func createFactoryHeader(kind, plural string) map[string]any {
lowerKind := strings.ToLower(kind)
badge := createUnifiedBadgeFromKind("badge-"+lowerKind, kind)
badge := createUnifiedBadgeFromKind("badge-"+lowerKind, kind, plural, BadgeSizeLarge)
nameText := parsedText(lowerKind+"-name", "{reqsJsonPath[0]['.metadata.name']['-']}", map[string]any{
"fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif",
"fontSize": float64(20),
@@ -744,26 +718,13 @@ func createFactorySpec(key string, sidebarTags []any, urlsToFetch []any, header
}
// createCustomColumnWithJsonPath creates a column with a custom badge and link using jsonPath
// badgeValue should be the kind in PascalCase (e.g., "Service", "VirtualMachine")
// abbreviation is auto-generated by ResourceBadge from badgeValue
func createCustomColumnWithJsonPath(name, jsonPath, badgeValue, badgeColor, linkHref string) map[string]any {
func createCustomColumnWithJsonPath(name, jsonPath, badgeText, badgeTitle, badgeColor, linkHref string) map[string]any {
// Determine link ID based on jsonPath
linkId := "name-link"
if jsonPath == ".metadata.namespace" {
linkId = "namespace-link"
}
badgeData := map[string]any{
"id": "header-badge",
"value": badgeValue,
}
// Add custom color if specified
if badgeColor != "" {
badgeData["style"] = map[string]any{
"backgroundColor": badgeColor,
}
}
return map[string]any{
"name": name,
"type": "factory",
@@ -780,8 +741,26 @@ func createCustomColumnWithJsonPath(name, jsonPath, badgeValue, badgeColor, link
},
"children": []any{
map[string]any{
"type": "ResourceBadge",
"data": badgeData,
"type": "antdText",
"data": map[string]any{
"id": "header-badge",
"text": badgeText,
"title": badgeTitle,
"style": map[string]any{
"backgroundColor": badgeColor,
"borderRadius": "20px",
"color": "#fff",
"display": "inline-block",
"fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif",
"fontSize": "15px",
"fontWeight": 400,
"lineHeight": "24px",
"minWidth": 24,
"padding": "0 9px",
"textAlign": "center",
"whiteSpace": "nowrap",
},
},
},
map[string]any{
"type": "antdLink",
@@ -799,20 +778,7 @@ func createCustomColumnWithJsonPath(name, jsonPath, badgeValue, badgeColor, link
}
// createCustomColumnWithoutJsonPath creates a column with a custom badge and link without jsonPath
// badgeValue should be the kind in PascalCase (e.g., "Node", "Pod")
// abbreviation is auto-generated by ResourceBadge from badgeValue
func createCustomColumnWithoutJsonPath(name, badgeValue, badgeColor, linkHref string) map[string]any {
badgeData := map[string]any{
"id": "header-badge",
"value": badgeValue,
}
// Add custom color if specified
if badgeColor != "" {
badgeData["style"] = map[string]any{
"backgroundColor": badgeColor,
}
}
func createCustomColumnWithoutJsonPath(name, badgeText, badgeTitle, badgeColor, linkHref string) map[string]any {
return map[string]any{
"name": name,
"type": "factory",
@@ -828,8 +794,26 @@ func createCustomColumnWithoutJsonPath(name, badgeValue, badgeColor, linkHref st
},
"children": []any{
map[string]any{
"type": "ResourceBadge",
"data": badgeData,
"type": "antdText",
"data": map[string]any{
"id": "header-badge",
"text": badgeText,
"title": badgeTitle,
"style": map[string]any{
"backgroundColor": badgeColor,
"borderRadius": "20px",
"color": "#fff",
"display": "inline-block",
"fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif",
"fontSize": "15px",
"fontWeight": 400,
"lineHeight": "24px",
"minWidth": 24,
"padding": "0 9px",
"textAlign": "center",
"whiteSpace": "nowrap",
},
},
},
map[string]any{
"type": "antdLink",
@@ -1046,15 +1030,6 @@ func createConverterBytesColumn(name, jsonPath string) map[string]any {
}
}
// createFlatMapColumn creates a flatMap column that expands a map into separate rows
func createFlatMapColumn(name, jsonPath string) map[string]any {
return map[string]any{
"name": name,
"type": "flatMap",
"jsonPath": jsonPath,
}
}
// ---------------- Factory UI helper functions ----------------
// labelsEditor creates a Labels editor component

View File

@@ -32,7 +32,7 @@ func (m *Manager) ensureStaticResource(ctx context.Context, obj client.Object) e
// Add dashboard labels to static resources
m.addDashboardLabels(resource, nil, ResourceTypeStatic)
_, err := controllerutil.CreateOrUpdate(ctx, m.Client, resource, func() error {
_, err := controllerutil.CreateOrUpdate(ctx, m.client, resource, func() error {
// For static resources, we don't need to set owner references
// as they are meant to be persistent across CRD changes
// Copy Spec from the original object to the live object

View File

@@ -132,7 +132,7 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
return []*dashboardv1alpha1.CustomColumnsOverride{
// Factory details v1 services
createCustomColumnsOverride("factory-details-v1.services", []any{
createCustomColumnWithSpecificColor("Name", "Service", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-service-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithSpecificColor("Name", "Service", "service", getColorForType("service"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-service-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createStringColumn("ClusterIP", ".spec.clusterIP"),
createStringColumn("LoadbalancerIP", ".spec.loadBalancerIP"),
createTimestampColumn("Created", ".metadata.creationTimestamp"),
@@ -140,7 +140,7 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
// Stock namespace v1 services
createCustomColumnsOverride("stock-namespace-/v1/services", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "Service", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-service-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Name", ".metadata.name", "S", "service", getColorForType("service"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-service-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createStringColumn("ClusterIP", ".spec.clusterIP"),
createStringColumn("LoadbalancerIP", ".status.loadBalancer.ingress[0].ip"),
createTimestampColumn("Created", ".metadata.creationTimestamp"),
@@ -148,7 +148,7 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
// Stock namespace core cozystack io v1alpha1 tenantmodules
createCustomColumnsOverride("stock-namespace-/core.cozystack.io/v1alpha1/tenantmodules", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "Module", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/{reqsJsonPath[0]['.metadata.name']['-']}-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Name", ".metadata.name", "M", "module", getColorForType("module"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/{reqsJsonPath[0]['.metadata.name']['-']}-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createReadyColumn(),
createTimestampColumn("Created", ".metadata.creationTimestamp"),
createStringColumn("Version", ".status.version"),
@@ -164,7 +164,7 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
// Factory details v1alpha1 cozystack io workloadmonitors
createCustomColumnsOverride("factory-details-v1alpha1.cozystack.io.workloadmonitors", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "WorkloadMonitor", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/workloadmonitor-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Name", ".metadata.name", "W", "workloadmonitor", getColorForType("workloadmonitor"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/workloadmonitor-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createStringColumn("TYPE", ".spec.type"),
createStringColumn("VERSION", ".spec.version"),
createStringColumn("REPLICAS", ".spec.replicas"),
@@ -173,26 +173,18 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
createStringColumn("OBSERVED", ".status.observedReplicas"),
}),
// Factory details v1alpha1 core cozystack io tenantsecrets
createCustomColumnsOverride("factory-details-v1alpha1.core.cozystack.io.tenantsecrets", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "Secret", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-secret-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createFlatMapColumn("Data", ".data"),
createStringColumn("Key", "_flatMapData_Key"),
createSecretBase64Column("Value", "._flatMapData_Value"),
// Factory details v1alpha1 core cozystack io tenantsecretstables
createCustomColumnsOverride("factory-details-v1alpha1.core.cozystack.io.tenantsecretstables", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "S", "secret", getColorForType("secret"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-secret-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createStringColumn("Key", ".data.key"),
createSecretBase64Column("Value", ".data.value"),
createTimestampColumn("Created", ".metadata.creationTimestamp"),
}),
// Virtual private cloud subnets
createCustomColumnsOverride("virtualprivatecloud-subnets", []any{
createFlatMapColumn("Data", ".data"),
createStringColumn("Subnet Parameters", "_flatMapData_Key"),
createStringColumn("Values", "_flatMapData_Value"),
}),
// Factory ingress details rules
createCustomColumnsOverride("factory-kube-ingress-details-rules", []any{
createStringColumn("Host", ".host"),
createCustomColumnWithJsonPath("Service", ".http.paths[0].backend.service.name", "Service", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-service-details/{reqsJsonPath[0]['.http.paths[0].backend.service.name']['-']}"),
createCustomColumnWithJsonPath("Service", ".http.paths[0].backend.service.name", "S", "service", getColorForType("service"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-service-details/{reqsJsonPath[0]['.http.paths[0].backend.service.name']['-']}"),
createStringColumn("Port", ".http.paths[0].backend.service.port.number"),
createStringColumn("Path", ".http.paths[0].path"),
}),
@@ -258,7 +250,7 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
// Factory details networking k8s io v1 ingresses
createCustomColumnsOverride("factory-details-networking.k8s.io.v1.ingresses", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "Ingress", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-ingress-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Name", ".metadata.name", "I", "ingress", getColorForType("ingress"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-ingress-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createStringColumn("Hosts", ".spec.rules[*].host"),
createStringColumn("Address", ".status.loadBalancer.ingress[0].ip"),
createStringColumn("Port", ".spec.defaultBackend.service.port.number"),
@@ -267,7 +259,7 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
// Stock namespace networking k8s io v1 ingresses
createCustomColumnsOverride("stock-namespace-/networking.k8s.io/v1/ingresses", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "Ingress", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-ingress-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Name", ".metadata.name", "I", "ingress", getColorForType("ingress"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-ingress-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createStringColumn("Hosts", ".spec.rules[*].host"),
createStringColumn("Address", ".status.loadBalancer.ingress[0].ip"),
createStringColumn("Port", ".spec.defaultBackend.service.port.number"),
@@ -276,34 +268,34 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
// Stock cluster v1 configmaps
createCustomColumnsOverride("stock-cluster-/v1/configmaps", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "ConfigMap", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/configmap-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "Namespace", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace"),
createCustomColumnWithJsonPath("Name", ".metadata.name", "CM", "configmap", getColorForType("configmap"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/configmap-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "NS", "namespace", getColorForType("namespace"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace"),
createTimestampColumn("Created", ".metadata.creationTimestamp"),
}),
// Stock namespace v1 configmaps
createCustomColumnsOverride("stock-namespace-/v1/configmaps", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "ConfigMap", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/configmap-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Name", ".metadata.name", "CM", "configmap", getColorForType("configmap"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/configmap-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createTimestampColumn("Created", ".metadata.creationTimestamp"),
}),
// Cluster v1 configmaps
createCustomColumnsOverride("cluster-/v1/configmaps", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "ConfigMap", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/configmap-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "Namespace", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace"),
createCustomColumnWithJsonPath("Name", ".metadata.name", "CM", "configmap", getColorForType("configmap"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/configmap-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "NS", "namespace", getColorForType("namespace"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace"),
createTimestampColumn("Created", ".metadata.creationTimestamp"),
}),
// Stock cluster v1 nodes
createCustomColumnsOverride("stock-cluster-/v1/nodes", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "Node", "", "/openapi-ui/{2}/factory/node-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Name", ".metadata.name", "N", "node", getColorForType("node"), "/openapi-ui/{2}/factory/node-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createSimpleStatusColumn("Status", "node-status"),
}),
// Factory node details v1 pods
createCustomColumnsOverride("factory-node-details-v1.pods", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "Pod", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/pod-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "Namespace", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace"),
createCustomColumnWithJsonPath("Name", ".metadata.name", "P", "pod", getColorForType("pod"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/pod-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "NS", "namespace", getColorForType("namespace"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace"),
createStringColumn("Restart Policy", ".spec.restartPolicy"),
createStringColumn("Pod IP", ".status.podIP"),
createStringColumn("QOS", ".status.qosClass"),
@@ -312,8 +304,8 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
// Factory v1 pods
createCustomColumnsOverride("factory-v1.pods", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "Pod", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/pod-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithoutJsonPath("Node", "Node", "", "/openapi-ui/{2}/factory/node-details/{reqsJsonPath[0]['.spec.nodeName']['-']}"),
createCustomColumnWithJsonPath("Name", ".metadata.name", "P", "pod", getColorForType("pod"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/pod-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithoutJsonPath("Node", "N", "node", getColorForType("node"), "/openapi-ui/{2}/factory/node-details/{reqsJsonPath[0]['.spec.nodeName']['-']}"),
createStringColumn("Restart Policy", ".spec.restartPolicy"),
createStringColumn("Pod IP", ".status.podIP"),
createStringColumn("QOS", ".status.qosClass"),
@@ -322,9 +314,9 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
// Stock cluster v1 pods
createCustomColumnsOverride("stock-cluster-/v1/pods", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "Pod", "#009596", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/pod-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "Namespace", "#a25792ff", "/openapi-ui/{2}/factory/tenantnamespace/{reqsJsonPath[0]['.metadata.namespace']['-']}"),
createCustomColumnWithJsonPath("Node", ".spec.nodeName", "Node", "#8476d1", "/openapi-ui/{2}/factory/node-details/{reqsJsonPath[0]['.spec.nodeName']['-']}"),
createCustomColumnWithJsonPath("Name", ".metadata.name", "P", "pod", "#009596", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/pod-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "NS", "namespace", "#a25792ff", "/openapi-ui/{2}/factory/tenantnamespace/{reqsJsonPath[0]['.metadata.namespace']['-']}"),
createCustomColumnWithJsonPath("Node", ".spec.nodeName", "N", "node", "#8476d1", "/openapi-ui/{2}/factory/node-details/{reqsJsonPath[0]['.spec.nodeName']['-']}"),
createStringColumn("Restart Policy", ".spec.restartPolicy"),
createStringColumn("Pod IP", ".status.podIP"),
createStringColumn("QOS", ".status.qosClass"),
@@ -333,8 +325,8 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
// Stock namespace v1 pods
createCustomColumnsOverride("stock-namespace-/v1/pods", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "Pod", "#009596", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/pod-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithoutJsonPath("Node", "Node", "#8476d1", "/openapi-ui/{2}/factory/node-details/{reqsJsonPath[0]['.spec.nodeName']['-']}"),
createCustomColumnWithJsonPath("Name", ".metadata.name", "P", "pod", "#009596", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/pod-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithoutJsonPath("Node", "N", "node", "#8476d1", "/openapi-ui/{2}/factory/node-details/{reqsJsonPath[0]['.spec.nodeName']['-']}"),
createStringColumn("Restart Policy", ".spec.restartPolicy"),
createStringColumn("Pod IP", ".status.podIP"),
createStringColumn("QOS", ".status.qosClass"),
@@ -343,15 +335,15 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
// Stock cluster v1 secrets
createCustomColumnsOverride("stock-cluster-/v1/secrets", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "Secret", "#c46100", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-secret-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "Namespace", "#a25792ff", "/openapi-ui/{2}/factory/tenantnamespace/{reqsJsonPath[0]['.metadata.namespace']['-']}"),
createCustomColumnWithJsonPath("Name", ".metadata.name", "S", "secret", "#c46100", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-secret-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Namespace", ".metadata.namespace", "NS", "namespace", "#a25792ff", "/openapi-ui/{2}/factory/tenantnamespace/{reqsJsonPath[0]['.metadata.namespace']['-']}"),
createStringColumn("Type", ".type"),
createTimestampColumn("Created", ".metadata.creationTimestamp"),
}),
// Stock namespace v1 secrets
createCustomColumnsOverride("stock-namespace-/v1/secrets", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "Secret", "#c46100", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-secret-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createCustomColumnWithJsonPath("Name", ".metadata.name", "S", "secret", "#c46100", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/kube-secret-details/{reqsJsonPath[0]['.metadata.name']['-']}"),
createStringColumn("Type", ".type"),
createTimestampColumn("Created", ".metadata.creationTimestamp"),
}),
@@ -368,7 +360,7 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
// Stock cluster core cozystack io v1alpha1 tenantnamespaces
createCustomColumnsOverride("stock-cluster-/core.cozystack.io/v1alpha1/tenantnamespaces", []any{
createCustomColumnWithJsonPath("Name", ".metadata.name", "TenantNamespace", "", "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.name']['-']}/factory/marketplace"),
createCustomColumnWithJsonPath("Name", ".metadata.name", "TN", "tenantnamespace", getColorForType("tenantnamespace"), "/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.name']['-']}/factory/marketplace"),
createTimestampColumn("Created", ".metadata.creationTimestamp"),
}),
}
@@ -504,6 +496,7 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory {
Kind: "Namespace",
Plural: "namespaces",
Title: "namespace",
Size: BadgeSizeLarge,
}
namespaceSpec := createUnifiedFactory(namespaceConfig, nil, []any{"/api/clusters/{2}/k8s/api/v1/namespaces/{5}"})
@@ -803,7 +796,6 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory {
"substractHeight": float64(400),
"type": "builtin",
"typeName": "secrets",
"readOnly": true,
},
},
},
@@ -1063,7 +1055,7 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory {
"clusterNamePartOfUrl": "{2}",
"customizationId": "factory-kube-service-details-endpointslice",
"fetchUrl": "/api/clusters/{2}/k8s/apis/discovery.k8s.io/v1/namespaces/{3}/endpointslices",
"labelSelector": map[string]any{
"labelsSelector": map[string]any{
"kubernetes.io/service-name": "{reqsJsonPath[0]['.metadata.name']['-']}",
},
"pathToItems": ".items[*].endpoints",
@@ -1209,7 +1201,7 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory {
"gap": 6,
},
"children": []any{
createUnifiedBadgeFromKind("ns-badge", "Namespace"),
createUnifiedBadgeFromKind("ns-badge", "Namespace", "namespace", BadgeSizeMedium),
antdLink("namespace-link",
"{reqsJsonPath[0]['.metadata.namespace']['-']}",
"/openapi-ui/{2}/{reqsJsonPath[0]['.metadata.namespace']['-']}/factory/marketplace",
@@ -1404,7 +1396,7 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory {
"clusterNamePartOfUrl": "{2}",
"customizationId": "factory-details-v1alpha1.cozystack.io.workloads",
"fetchUrl": "/api/clusters/{2}/k8s/apis/cozystack.io/v1alpha1/namespaces/{3}/workloads",
"labelSelector": map[string]any{
"labelsSelector": map[string]any{
"workloads.cozystack.io/monitor": "{reqs[0]['metadata','name']}",
},
"pathToItems": []any{"items"},

View File

@@ -1,5 +1,7 @@
package dashboard
import "strings"
// ---------------- UI helpers (use float64 for numeric fields) ----------------
func contentCard(id string, style map[string]any, children []any) map[string]any {
@@ -198,10 +200,10 @@ func createBadge(id, text, color, title string) map[string]any {
// createBadgeFromKind creates a badge using the existing badge generation functions
func createBadgeFromKind(id, kind, title string) map[string]any {
return createUnifiedBadgeFromKind(id, kind)
return createUnifiedBadgeFromKind(id, kind, title, BadgeSizeMedium)
}
// createHeaderBadge creates a badge specifically for headers with consistent styling
func createHeaderBadge(id, kind, plural string) map[string]any {
return createUnifiedBadgeFromKind(id, kind)
return createUnifiedBadgeFromKind(id, kind, strings.ToLower(plural), BadgeSizeLarge)
}

View File

@@ -81,49 +81,88 @@ func isAlphanumeric(c byte) bool {
// BadgeConfig holds configuration for badge generation
type BadgeConfig struct {
Kind string // Resource kind in PascalCase (e.g., "VirtualMachine") - used for value and auto-generation
Text string // Optional abbreviation override (if empty, ResourceBadge auto-generates from Kind)
Color string // Optional custom backgroundColor override
Text string
Color string
Title string
Size BadgeSize
}
// createUnifiedBadge creates a badge using the unified BadgeConfig with ResourceBadge component
// BadgeSize represents the size of the badge
type BadgeSize int
const (
BadgeSizeSmall BadgeSize = iota
BadgeSizeMedium
BadgeSizeLarge
)
// generateBadgeConfig creates a BadgeConfig from kind and optional custom values
func generateBadgeConfig(kind string, customText, customColor, customTitle string) BadgeConfig {
config := BadgeConfig{
Text: initialsFromKind(kind),
Color: hexColorForKind(kind),
Title: strings.ToLower(kind),
Size: BadgeSizeMedium,
}
// Override with custom values if provided
if customText != "" {
config.Text = customText
}
if customColor != "" {
config.Color = customColor
}
if customTitle != "" {
config.Title = customTitle
}
return config
}
// createUnifiedBadge creates a badge using the unified BadgeConfig
func createUnifiedBadge(id string, config BadgeConfig) map[string]any {
data := map[string]any{
"id": id,
"value": config.Kind,
}
// Add abbreviation override if specified (otherwise ResourceBadge auto-generates from Kind)
if config.Text != "" {
data["abbreviation"] = config.Text
}
// Add custom color if specified
if config.Color != "" {
data["style"] = map[string]any{
"backgroundColor": config.Color,
}
fontSize := "15px"
if config.Size == BadgeSizeLarge {
fontSize = "20px"
} else if config.Size == BadgeSizeSmall {
fontSize = "12px"
}
return map[string]any{
"type": "ResourceBadge",
"data": data,
}
}
// createUnifiedBadgeFromKind creates a badge from kind with ResourceBadge component
// Abbreviation is auto-generated by ResourceBadge from kind, but can be customized if needed
func createUnifiedBadgeFromKind(id, kind string) map[string]any {
return map[string]any{
"type": "ResourceBadge",
"type": "antdText",
"data": map[string]any{
"id": id,
"value": kind,
// abbreviation is optional - ResourceBadge auto-generates from value
"text": config.Text,
"title": config.Title,
"style": map[string]any{
"backgroundColor": config.Color,
"borderRadius": "20px",
"color": "#fff",
"display": "inline-block",
"fontFamily": "RedHatDisplay, Overpass, overpass, helvetica, arial, sans-serif",
"fontSize": fontSize,
"fontWeight": float64(400),
"lineHeight": "24px",
"minWidth": float64(24),
"padding": "0 9px",
"textAlign": "center",
"whiteSpace": "nowrap",
},
},
}
}
// createUnifiedBadgeFromKind creates a badge from kind with automatic color generation
func createUnifiedBadgeFromKind(id, kind, title string, size BadgeSize) map[string]any {
config := BadgeConfig{
Text: initialsFromKind(kind),
Color: hexColorForKind(kind),
Title: title,
Size: size,
}
return createUnifiedBadge(id, config)
}
// ---------------- Resource creation helpers with unified approach ----------------
// ResourceConfig holds configuration for resource creation
@@ -144,9 +183,7 @@ func createResourceConfig(components []string, kind, title string) ResourceConfi
metadataName := generateMetadataName(specID)
// Generate badge config
badgeConfig := BadgeConfig{
Kind: kind,
}
badgeConfig := generateBadgeConfig(kind, "", "", title)
return ResourceConfig{
SpecID: specID,
@@ -159,6 +196,35 @@ func createResourceConfig(components []string, kind, title string) ResourceConfi
// ---------------- Enhanced color generation ----------------
// getColorForKind returns a color for a specific kind with improved distribution
func getColorForKind(kind string) string {
// Use existing hexColorForKind function
return hexColorForKind(kind)
}
// getColorForType returns a color for a specific type (like "namespace", "service", etc.)
func getColorForType(typeName string) string {
// Map common types to specific colors for consistency
colorMap := map[string]string{
"namespace": "#a25792ff",
"service": "#6ca100",
"pod": "#009596",
"node": "#8476d1",
"secret": "#c46100",
"configmap": "#b48c78ff",
"ingress": "#2e7dff",
"workloadmonitor": "#c46100",
"module": "#8b5cf6",
}
if color, exists := colorMap[strings.ToLower(typeName)]; exists {
return color
}
// Fall back to hash-based color generation
return hexColorForKind(typeName)
}
// ---------------- Automatic ID generation for UI elements ----------------
// generateElementID creates an ID for UI elements based on context and type
@@ -216,6 +282,7 @@ type UnifiedResourceConfig struct {
Title string
Color string
BadgeText string
Size BadgeSize
}
// createUnifiedFactory creates a factory using unified approach
@@ -225,9 +292,16 @@ func createUnifiedFactory(config UnifiedResourceConfig, tabs []any, urlsToFetch
// Create header with unified badge
badgeConfig := BadgeConfig{
Kind: config.Kind,
Text: config.BadgeText,
Color: config.Color,
Title: config.Title,
Size: config.Size,
}
if badgeConfig.Text == "" {
badgeConfig.Text = initialsFromKind(config.Kind)
}
if badgeConfig.Color == "" {
badgeConfig.Color = getColorForKind(config.Kind)
}
badge := createUnifiedBadge(generateBadgeID("header", config.Kind), badgeConfig)
@@ -274,9 +348,7 @@ func createUnifiedFactory(config UnifiedResourceConfig, tabs []any, urlsToFetch
// createUnifiedCustomColumn creates a custom column using unified approach
func createUnifiedCustomColumn(name, jsonPath, kind, title, href string) map[string]any {
badgeConfig := BadgeConfig{
Kind: kind,
}
badgeConfig := generateBadgeConfig(kind, "", "", title)
badge := createUnifiedBadge(generateBadgeID("column", kind), badgeConfig)
linkID := generateLinkID("column", "name")

View File

@@ -1 +1 @@
ghcr.io/cozystack/cozystack/nginx-cache:0.0.0@sha256:b7633717cd7449c0042ae92d8ca9b36e4d69566561f5c7d44e21058e7d05c6d5
ghcr.io/cozystack/cozystack/nginx-cache:0.0.0@sha256:50ac1581e3100bd6c477a71161cb455a341ffaf9e5e2f6086802e4e25271e8af

View File

@@ -1 +1 @@
ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:d5c836ba33cf5dbed7e6f866784f668f80ffe69179e7c75847b680111984eefb
ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:c8b08084a86251cdd18e237de89b695bca0e4f7eb1f1f6ddc2b903b4d74ea5ff

View File

@@ -182,33 +182,6 @@ metadata:
spec:
template:
spec:
files:
- path: /usr/bin/update-k8s.sh
owner: root:root
permissions: "0755"
content: |
#!/usr/bin/env bash
set -euo pipefail
# Expected to be passed in via preKubeadmCommands
: "${KUBELET_VERSION:?KUBELET_VERSION must be set, e.g. v1.31.0}"
ARCH="$(uname -m)"
case "${ARCH}" in
x86_64) ARCH=amd64 ;;
aarch64) ARCH=arm64 ;;
esac
# Use your internal mirror here for real-world use.
BASE_URL="https://dl.k8s.io/release/${KUBELET_VERSION}/bin/linux/${ARCH}"
echo "Installing kubelet and kubeadm ${KUBELET_VERSION} for ${ARCH}..."
curl -fsSL "${BASE_URL}/kubelet" -o /root/kubelet
curl -fsSL "${BASE_URL}/kubeadm" -o /root/kubeadm
chmod 0755 /root/kubelet
chmod 0755 /root/kubeadm
if /root/kubelet --version ; then mv /root/kubelet /usr/bin/kubelet ; fi
if /root/kubeadm version ; then mv /root/kubeadm /usr/bin/kubeadm ; fi
diskSetup:
filesystems:
- device: /dev/vdb
@@ -232,7 +205,6 @@ spec:
{{- end }}
{{- end }}
preKubeadmCommands:
- KUBELET_VERSION={{ include "kubernetes.versionMap" $}} /usr/bin/update-k8s.sh || true
- sed -i 's|root:x:|root::|' /etc/passwd
- systemctl stop containerd.service
- mkdir -p /ephemeral/kubelet /ephemeral/containerd

View File

@@ -1,89 +0,0 @@
---
apiVersion: batch/v1
kind: Job
metadata:
annotations:
"helm.sh/hook": post-delete
"helm.sh/hook-weight": "10"
"helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation,hook-failed
name: {{ .Release.Name }}-cleanup
spec:
template:
spec:
serviceAccountName: {{ .Release.Name }}-cleanup
restartPolicy: Never
tolerations:
- key: CriticalAddonsOnly
operator: Exists
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: "NoSchedule"
containers:
- name: kubectl
image: docker.io/clastix/kubectl:v1.32
command:
- /bin/sh
- -c
- kubectl -n {{ .Release.Namespace }} delete datavolumes
-l "cluster.x-k8s.io/cluster-name={{ .Release.Name }}"
--ignore-not-found=true
kubectl -n {{ .Release.Namespace }} delete services
-l "cluster.x-k8s.io/cluster-name={{ .Release.Name }}"
--field-selector spec.type=LoadBalancer
--ignore-not-found=true
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ .Release.Name }}-cleanup
annotations:
helm.sh/hook: post-delete
helm.sh/hook-delete-policy: before-hook-creation,hook-failed,hook-succeeded
helm.sh/hook-weight: "0"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
annotations:
"helm.sh/hook": post-delete
"helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation,hook-failed
"helm.sh/hook-weight": "5"
name: {{ .Release.Name }}-cleanup
rules:
- apiGroups:
- "cdi.kubevirt.io"
resources:
- datavolumes
verbs:
- get
- list
- delete
- apiGroups:
- ""
resources:
- services
verbs:
- get
- list
- delete
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
annotations:
"helm.sh/hook": post-delete
"helm.sh/hook-delete-policy": hook-succeeded,before-hook-creation,hook-failed
"helm.sh/hook-weight": "5"
name: {{ .Release.Name }}-cleanup
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ .Release.Name }}-cleanup
subjects:
- kind: ServiceAccount
name: {{ .Release.Name }}-cleanup
namespace: {{ .Release.Namespace }}

View File

@@ -24,26 +24,26 @@ spec:
command:
- /bin/sh
- -c
- >-
kubectl
--namespace={{ .Release.Namespace }}
patch
helmrelease
{{ .Release.Name }}-cilium
{{ .Release.Name }}-gateway-api-crds
{{ .Release.Name }}-csi
{{ .Release.Name }}-cert-manager
{{ .Release.Name }}-cert-manager-crds
{{ .Release.Name }}-vertical-pod-autoscaler
{{ .Release.Name }}-vertical-pod-autoscaler-crds
{{ .Release.Name }}-ingress-nginx
{{ .Release.Name }}-fluxcd-operator
{{ .Release.Name }}-fluxcd
{{ .Release.Name }}-gpu-operator
{{ .Release.Name }}-velero
{{ .Release.Name }}-coredns
-p '{"spec": {"suspend": true}}'
--type=merge --field-manager=flux-client-side-apply || true
- |
kubectl
--namespace={{ .Release.Namespace }}
patch
helmrelease
{{ .Release.Name }}-cilium
{{ .Release.Name }}-gateway-api-crds
{{ .Release.Name }}-csi
{{ .Release.Name }}-cert-manager
{{ .Release.Name }}-cert-manager-crds
{{ .Release.Name }}-vertical-pod-autoscaler
{{ .Release.Name }}-vertical-pod-autoscaler-crds
{{ .Release.Name }}-ingress-nginx
{{ .Release.Name }}-fluxcd-operator
{{ .Release.Name }}-fluxcd
{{ .Release.Name }}-gpu-operator
{{ .Release.Name }}-velero
{{ .Release.Name }}-coredns
-p '{"spec": {"suspend": true}}'
--type=merge --field-manager=flux-client-side-apply || true
---
apiVersion: v1
kind: ServiceAccount
@@ -51,7 +51,7 @@ metadata:
name: {{ .Release.Name }}-flux-teardown
annotations:
helm.sh/hook: pre-delete
helm.sh/hook-delete-policy: before-hook-creation,hook-failed,hook-succeeded
helm.sh/hook-delete-policy: before-hook-creation,hook-failed
helm.sh/hook-weight: "0"
---
apiVersion: rbac.authorization.k8s.io/v1
@@ -75,7 +75,6 @@ rules:
- {{ .Release.Name }}-csi
- {{ .Release.Name }}-cert-manager
- {{ .Release.Name }}-cert-manager-crds
- {{ .Release.Name }}-gateway-api-crds
- {{ .Release.Name }}-vertical-pod-autoscaler
- {{ .Release.Name }}-vertical-pod-autoscaler-crds
- {{ .Release.Name }}-ingress-nginx

View File

@@ -1,74 +0,0 @@
---
apiVersion: batch/v1
kind: Job
metadata:
name: {{ .Release.Name }}-cleanup
labels:
app.kubernetes.io/instance: {{ .Release.Name }}
annotations:
"helm.sh/hook": post-delete
"helm.sh/hook-weight": "10"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
template:
metadata:
labels:
app.kubernetes.io/instance: {{ .Release.Name }}
policy.cozystack.io/allow-to-apiserver: "true"
spec:
serviceAccountName: {{ .Release.Name }}-cleanup
restartPolicy: Never
containers:
- name: cleanup
image: docker.io/clastix/kubectl:v1.32
command:
- /bin/sh
- -c
- |
echo "Deleting orphaned PVCs for {{ .Release.Name }}..."
kubectl delete pvc -n {{ .Release.Namespace }} -l app.kubernetes.io/instance={{ .Release.Name }} || true
echo "PVC cleanup complete."
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ .Release.Name }}-cleanup
labels:
app.kubernetes.io/instance: {{ .Release.Name }}
annotations:
"helm.sh/hook": post-delete
helm.sh/hook-weight: "0"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ .Release.Name }}-cleanup
labels:
app.kubernetes.io/instance: {{ .Release.Name }}
annotations:
"helm.sh/hook": post-delete
"helm.sh/hook-weight": "5"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
rules:
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ .Release.Name }}-cleanup
labels:
app.kubernetes.io/instance: {{ .Release.Name }}
annotations:
"helm.sh/hook": post-delete
helm.sh/hook-weight: "5"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ .Release.Name }}-cleanup
subjects:
- kind: ServiceAccount
name: {{ .Release.Name }}-cleanup

View File

@@ -37,10 +37,6 @@ spec:
# automaticFailover: true
{{- end }}
podMetadata:
labels:
"policy.cozystack.io/allow-to-apiserver": "true"
metrics:
enabled: true
exporter:

View File

@@ -1,14 +1,6 @@
{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }}
{{- $clusterDomain := (index $cozyConfig.data "cluster-domain") | default "cozy.local" }}
{{- $existingSecret := lookup "v1" "Secret" .Release.Namespace (printf "%s-credentials" .Release.Name) }}
{{- $passwords := dict }}
{{- with (dig "data" (dict) $existingSecret) }}
{{- range $k, $v := . }}
{{- $_ := set $passwords $k (b64dec $v) }}
{{- end }}
{{- end }}
{{- range $user, $u := .Values.users }}
{{- if $u.password }}
{{- $_ := set $passwords $user $u.password }}
@@ -55,14 +47,18 @@ spec:
retries: -1
values:
nats:
container:
podTemplate:
merge:
resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.resourcesPreset .Values.resources $) | nindent 12 }}
spec:
containers:
- name: nats
image: nats:2.10.17-alpine
resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.resourcesPreset .Values.resources $) | nindent 22 }}
fullnameOverride: {{ .Release.Name }}
config:
{{- if or $passwords .Values.config.merge }}
{{- if or (gt (len $passwords) 0) (gt (len .Values.config.merge) 0) }}
merge:
{{- if $passwords }}
{{- if gt (len $passwords) 0 }}
accounts:
A:
users:
@@ -71,13 +67,13 @@ spec:
password: "{{ $password }}"
{{- end }}
{{- end }}
{{- with .Values.config.merge }}
{{- toYaml . | nindent 10 }}
{{- if and .Values.config (hasKey .Values.config "merge") }}
{{ toYaml .Values.config.merge | nindent 12 }}
{{- end }}
{{- end }}
{{- with .Values.config.resolver }}
{{- if and .Values.config (hasKey .Values.config "resolver") }}
resolver:
{{- toYaml . | nindent 10 }}
{{ toYaml .Values.config.resolver | nindent 12 }}
{{- end }}
cluster:
enabled: true

View File

@@ -27,7 +27,6 @@ spec:
replicas: 3
resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.resourcesPreset .Values.resources $) | nindent 6 }}
redis:
image: "redis:8.2.0"
resources: {{- include "cozy-lib.resources.defaultingSanitize" (list .Values.resourcesPreset .Values.resources $) | nindent 6 }}
replicas: {{ .Values.replicas }}
{{- with .Values.size }}

View File

@@ -20,7 +20,11 @@ metadata:
name: allow-external-communication
namespace: {{ include "tenant.name" . }}
spec:
endpointSelector: {}
endpointSelector:
matchExpressions:
- key: policy.cozystack.io/allow-external-communication
operator: NotIn
values: ["false"]
ingress:
- fromEntities:
- world

View File

@@ -28,13 +28,13 @@ rules:
- cozystack.io
resources:
- workloadmonitors
- workloads
verbs: ["get", "list", "watch"]
- apiGroups:
- core.cozystack.io
resources:
- tenantmodules
- tenantsecrets
- tenantsecretstables
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
@@ -113,7 +113,6 @@ rules:
- cozystack.io
resources:
- workloadmonitors
- workloads
verbs: ["get", "list", "watch"]
---
kind: RoleBinding
@@ -122,7 +121,7 @@ metadata:
name: {{ include "tenant.name" . }}-view
namespace: {{ include "tenant.name" . }}
subjects:
{{ include "cozy-lib.rbac.subjectsForTenantAndAccessLevel" (list "view" (include "tenant.name" .)) | nindent 2 }}
{{ include "cozy-lib.rbac.subjectsForTenant" (list "view" (include "tenant.name" .)) | nindent 2 }}
roleRef:
kind: Role
name: {{ include "tenant.name" . }}-view
@@ -185,13 +184,13 @@ rules:
- cozystack.io
resources:
- workloadmonitors
- workloads
verbs: ["get", "list", "watch"]
- apiGroups:
- core.cozystack.io
resources:
- tenantmodules
- tenantsecrets
- tenantsecretstables
verbs: ["get", "list", "watch"]
---
kind: RoleBinding
@@ -200,7 +199,7 @@ metadata:
name: {{ include "tenant.name" . }}-use
namespace: {{ include "tenant.name" . }}
subjects:
{{ include "cozy-lib.rbac.subjectsForTenantAndAccessLevel" (list "use" (include "tenant.name" .)) | nindent 2 }}
{{ include "cozy-lib.rbac.subjectsForTenant" (list "use" (include "tenant.name" .)) | nindent 2 }}
roleRef:
kind: Role
name: {{ include "tenant.name" . }}-use
@@ -284,13 +283,13 @@ rules:
- cozystack.io
resources:
- workloadmonitors
- workloads
verbs: ["get", "list", "watch"]
- apiGroups:
- core.cozystack.io
resources:
- tenantmodules
- tenantsecrets
- tenantsecretstables
verbs: ["get", "list", "watch"]
---
kind: RoleBinding
@@ -299,7 +298,7 @@ metadata:
name: {{ include "tenant.name" . }}-admin
namespace: {{ include "tenant.name" . }}
subjects:
{{ include "cozy-lib.rbac.subjectsForTenantAndAccessLevel" (list "admin" (include "tenant.name" .)) | nindent 2 }}
{{ include "cozy-lib.rbac.subjectsForTenant" (list "admin" (include "tenant.name" .)) | nindent 2 }}
roleRef:
kind: Role
name: {{ include "tenant.name" . }}-admin
@@ -358,13 +357,13 @@ rules:
- cozystack.io
resources:
- workloadmonitors
- workloads
verbs: ["get", "list", "watch"]
- apiGroups:
- core.cozystack.io
resources:
- tenantmodules
- tenantsecrets
- tenantsecretstables
verbs: ["get", "list", "watch"]
---
kind: RoleBinding
@@ -373,7 +372,7 @@ metadata:
name: {{ include "tenant.name" . }}-super-admin
namespace: {{ include "tenant.name" . }}
subjects:
{{ include "cozy-lib.rbac.subjectsForTenantAndAccessLevel" (list "super-admin" (include "tenant.name" .) ) | nindent 2 }}
{{ include "cozy-lib.rbac.subjectsForTenant" (list "super-admin" (include "tenant.name" .) ) | nindent 2 }}
roleRef:
kind: Role
name: {{ include "tenant.name" . }}-super-admin

View File

@@ -36,29 +36,29 @@ virtctl ssh <user>@<vm>
### Common parameters
| Name | Description | Type | Value |
| ------------------------- | ------------------------------------------------------- | ---------- | ------------ |
| `external` | Enable external access from outside the cluster. | `bool` | `false` |
| `externalMethod` | Method to pass through traffic to the VM. | `string` | `PortList` |
| `externalPorts` | Ports to forward from outside the cluster. | `[]int` | `[22]` |
| `running` | Whether the virtual machine should be running. | `bool` | `true` |
| `instanceType` | Virtual Machine instance type. | `string` | `u1.medium` |
| `instanceProfile` | Virtual Machine preferences profile. | `string` | `ubuntu` |
| `systemDisk` | System disk configuration. | `object` | `{}` |
| `systemDisk.image` | The base image for the virtual machine. | `string` | `ubuntu` |
| `systemDisk.storage` | The size of the disk allocated for the virtual machine. | `string` | `5Gi` |
| `systemDisk.storageClass` | StorageClass used to store the data. | `string` | `replicated` |
| `subnets` | Additional subnets | `[]object` | `[]` |
| `subnets[i].name` | Subnet name | `string` | `""` |
| `gpus` | List of GPUs to attach. | `[]object` | `[]` |
| `gpus[i].name` | The name of the GPU resource to attach. | `string` | `""` |
| `resources` | Resource configuration for the virtual machine. | `object` | `{}` |
| `resources.cpu` | Number of CPU cores allocated. | `quantity` | `""` |
| `resources.sockets` | Number of CPU sockets (vCPU topology). | `quantity` | `""` |
| `resources.memory` | Amount of memory allocated. | `quantity` | `""` |
| `sshKeys` | List of SSH public keys for authentication. | `[]string` | `[]` |
| `cloudInit` | Cloud-init user data. | `string` | `""` |
| `cloudInitSeed` | Seed string to generate SMBIOS UUID for the VM. | `string` | `""` |
| Name | Description | Type | Value |
| ------------------------- | ------------------------------------------------------------------ | ---------- | ------------ |
| `external` | Enable external access from outside the cluster. | `bool` | `false` |
| `externalMethod` | Method to pass through traffic to the VM. | `string` | `PortList` |
| `externalPorts` | Ports to forward from outside the cluster. | `[]int` | `[22]` |
| `running` | Whether the virtual machine should be running. | `bool` | `true` |
| `instanceType` | Virtual Machine instance type. | `string` | `u1.medium` |
| `instanceProfile` | Virtual Machine preferences profile. | `string` | `ubuntu` |
| `systemDisk` | System disk configuration. | `object` | `{}` |
| `systemDisk.image` | The base image for the virtual machine. | `string` | `ubuntu` |
| `systemDisk.storage` | The size of the disk allocated for the virtual machine. | `string` | `5Gi` |
| `systemDisk.storageClass` | StorageClass used to store the data. | `string` | `replicated` |
| `subnets` | Additional subnets (management subnet will be attached by default) | `[]object` | `[]` |
| `subnets[i].name` | Name of the subnet to attach | `string` | `""` |
| `gpus` | List of GPUs to attach. | `[]object` | `[]` |
| `gpus[i].name` | The name of the GPU resource to attach. | `string` | `""` |
| `resources` | Resource configuration for the virtual machine. | `object` | `{}` |
| `resources.cpu` | Number of CPU cores allocated. | `quantity` | `""` |
| `resources.sockets` | Number of CPU sockets (vCPU topology). | `quantity` | `""` |
| `resources.memory` | Amount of memory allocated. | `quantity` | `""` |
| `sshKeys` | List of SSH public keys for authentication. | `[]string` | `[]` |
| `cloudInit` | Cloud-init user data. | `string` | `""` |
| `cloudInitSeed` | Seed string to generate SMBIOS UUID for the VM. | `string` | `""` |
## U Series

View File

@@ -28,3 +28,27 @@ spec:
{{- end }}
{{- end }}
{{- end }}
---
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: {{ include "virtual-machine.fullname" . }}
spec:
endpointSelector:
matchLabels:
{{- include "virtual-machine.selectorLabels" . | nindent 6 }}
ingress:
- fromEntities:
- cluster
- fromEntities:
- world
{{- if eq .Values.externalMethod "PortList" }}
toPorts:
- ports:
{{- range .Values.externalPorts }}
- port: {{ quote . }}
{{- end }}
{{- end }}
egress:
- toEntities:
- world

View File

@@ -62,6 +62,7 @@ spec:
template:
metadata:
annotations:
policy.cozystack.io/allow-external-communication: "false"
kubevirt.io/allow-pod-bridge-network-live-migration: "true"
labels:
{{- include "virtual-machine.labels" . | nindent 8 }}

View File

@@ -168,14 +168,17 @@
}
},
"subnets": {
"description": "Additional subnets",
"description": "Additional subnets (management subnet will be attached by default)",
"type": "array",
"default": [],
"items": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"description": "Subnet name",
"description": "Name of the subnet to attach",
"type": "string"
}
}

View File

@@ -18,9 +18,6 @@
## @field {string} storage - The size of the disk allocated for the virtual machine.
## @field {string} [storageClass] - StorageClass used to store the data.
## @typedef {struct} Subnet - Additional subnets
## @field {string} [name] - Subnet name
## @typedef {struct} GPU - GPU device configuration.
## @field {string} name - The name of the GPU resource to attach.
@@ -29,6 +26,9 @@
## @field {quantity} [sockets] - Number of CPU sockets (vCPU topology).
## @field {quantity} [memory] - Amount of memory allocated.
## @typedef {struct} Subnet - Additional subnet to attach to
## @field {string} name - Name of the subnet to attach
## @param {bool} external - Enable external access from outside the cluster.
external: false
@@ -53,7 +53,7 @@ systemDisk:
storage: 5Gi
storageClass: replicated
## @param {[]Subnet} subnets - Additional subnets
## @param {[]Subnet} subnets - Additional subnets (management subnet will be attached by default)
subnets: []
## Example:
## subnets:

View File

@@ -47,10 +47,10 @@ virtctl ssh <user>@<vm>
| `disks` | List of disks to attach. | `[]object` | `[]` |
| `disks[i].name` | Disk name. | `string` | `""` |
| `disks[i].bus` | Disk bus type (e.g. "sata"). | `string` | `""` |
| `subnets` | Additional subnets | `[]object` | `[]` |
| `subnets[i].name` | Subnet name | `string` | `""` |
| `gpus` | List of GPUs to attach (NVIDIA driver requires at least 4 GiB RAM). | `[]object` | `[]` |
| `gpus[i].name` | The name of the GPU resource to attach. | `string` | `""` |
| `subnets` | Additional subnets (management subnet will be attached by default) | `[]object` | `[]` |
| `subnets[i].name` | Name of the subnet to attach | `string` | `""` |
| `resources` | Resource configuration for the virtual machine. | `object` | `{}` |
| `resources.cpu` | Number of CPU cores allocated. | `quantity` | `""` |
| `resources.memory` | Amount of memory allocated. | `quantity` | `""` |

View File

@@ -28,3 +28,27 @@ spec:
{{- end }}
{{- end }}
{{- end }}
---
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: {{ include "virtual-machine.fullname" . }}
spec:
endpointSelector:
matchLabels:
{{- include "virtual-machine.selectorLabels" . | nindent 6 }}
ingress:
- fromEntities:
- cluster
- fromEntities:
- world
{{- if eq .Values.externalMethod "PortList" }}
toPorts:
- ports:
{{- range .Values.externalPorts }}
- port: {{ quote . }}
{{- end }}
{{- end }}
egress:
- toEntities:
- world

View File

@@ -26,6 +26,7 @@ spec:
template:
metadata:
annotations:
policy.cozystack.io/allow-external-communication: "false"
kubevirt.io/allow-pod-bridge-network-live-migration: "true"
labels:
{{- include "virtual-machine.labels" . | nindent 8 }}

View File

@@ -189,14 +189,17 @@
}
},
"subnets": {
"description": "Additional subnets",
"description": "Additional subnets (management subnet will be attached by default)",
"type": "array",
"default": [],
"items": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"description": "Subnet name",
"description": "Name of the subnet to attach",
"type": "string"
}
}

View File

@@ -10,9 +10,6 @@
## @field {string} name - Disk name.
## @field {string} [bus] - Disk bus type (e.g. "sata").
## @typedef {struct} Subnet - Additional subnets
## @field {string} [name] - Subnet name
## @typedef {struct} GPU - GPU device configuration.
## @field {string} name - The name of the GPU resource to attach.
@@ -21,6 +18,9 @@
## @field {quantity} [memory] - Amount of memory allocated.
## @field {quantity} [sockets] - Number of CPU sockets (vCPU topology).
## @typedef {struct} Subnet - Additional subnet to attach to
## @field {string} name - Name of the subnet to attach
## @param {bool} external - Enable external access from outside the cluster.
external: false
@@ -48,7 +48,13 @@ disks: []
## - name: example-data
## bus: sata
## @param {[]Subnet} subnets - Additional subnets
## @param {[]GPU} gpus - List of GPUs to attach (NVIDIA driver requires at least 4 GiB RAM).
gpus: []
## Example:
## gpus:
## - name: nvidia.com/GA102GL_A10
## @param {[]Subnet} subnets - Additional subnets (management subnet will be attached by default)
subnets: []
## Example:
## subnets:
@@ -56,14 +62,13 @@ subnets: []
## - name: subnet-aa8896b5
## - name: subnet-e9b97196
## @param {[]GPU} gpus - List of GPUs to attach (NVIDIA driver requires at least 4 GiB RAM).
gpus: []
## Example:
## gpus:
## - name: nvidia.com/GA102GL_A10
## @param {Resources} [resources] - Resource configuration for the virtual machine.
resources: {}
## Example:
## resources:
## cpu: "4"
## sockets: "1"
## memory: "8Gi"
## @param {[]string} sshKeys - List of SSH public keys for authentication.
sshKeys: []

View File

@@ -1,50 +0,0 @@
# VPC
VPC offers a subset of dedicated subnets with networking services related to it.
As the service evolves, it will provide more ways to isolate your workloads.
## Service details
To function, the service requires kube-ovn and multus CNI to be present, so by default it will only work on `paas-full` bundle.
Kube-ovn provides VPC and Subnet resources and performs isolation and networking maintenance such as DHCP. Under the hood it uses ovn virtual routers and virtual switches.
Multus enables a multi-nic capability, so a pod or a VM could have two or more network interfaces.
Currently every workload will have a connection to a default management network which will also have a default gateway, and the majority of traffic will go through it.
VPC subnets are for now an additional dedicated networking spaces.
## Deployment notes
VPC name must be unique within a tenant.
Subnet name and ip address range must be unique within a VPC.
Subnet ip address space must not overlap with the default management network ip address range, subsets of 172.16.0.0/12 are recommended.
Currently there are no fail-safe checks, however they are planned for the future.
Different VPCs may have subnets with overlapping ip address ranges.
A VM or a pod may be connected to multiple secondary Subnets at once. Each secondary connection will be represented as an additional network interface.
## Parameters
### Common parameters
| Name | Description | Type | Value |
| -------------------- | -------------------------------- | ------------------- | ------- |
| `subnets` | Subnets of a VPC | `map[string]object` | `{...}` |
| `subnets[name].cidr` | Subnet CIDR, e.g. 192.168.0.0/24 | `cidr` | `{}` |
## Examples
```yaml
apiVersion: apps.cozystack.io/v1alpha1
kind: VirtualPrivateCloud
metadata:
name: vpc00
spec:
subnets:
sub00:
cidr: 172.16.0.0/24
sub01:
cidr: 172.16.1.0/24
sub02:
cidr: 172.16.2.0/24
```

View File

@@ -1 +0,0 @@
../../../library/cozy-lib

View File

@@ -58,35 +58,15 @@ spec:
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ $.Release.Name }}-subnets
name: {{ $vpcId }}-subnets
labels:
apps.cozystack.io/application.group: apps.cozystack.io
apps.cozystack.io/application.kind: VirtualPrivateCloud
apps.cozystack.io/application.name: {{ trimPrefix "virtualprivatecloud-" .Release.Name }}
cozystack.io/vpcId: {{ $vpcId }}
cozystack.io/vpcName: {{ $.Release.Name }}
cozystack.io/tenantName: {{ $.Release.Namespace }}
data:
{{- range $subnetName, $subnetConfig := .Values.subnets }}
{{ $subnetName }}.ID: {{ print "subnet-" (print $.Release.Namespace "/" $vpcId "/" $subnetName | sha256sum | trunc 8) }}
{{ $subnetName }}.CIDR: {{ $subnetConfig.cidr }}
{{- end }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: "{{ .Release.Name }}-subnets"
subjects: {{- include "cozy-lib.rbac.subjectsForTenantAndAccessLevel" (list "view" .Release.Namespace ) | nindent 2 }}
roleRef:
kind: Role
name: "{{ .Release.Name }}-subnets"
apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: "{{ .Release.Name }}-subnets"
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get","list","watch"]
resourceNames: ["{{ .Release.Name }}-subnets"]
subnets: |
{{- range $subnetName, $subnetConfig := .Values.subnets }}
- subnetName: {{ $subnetName }}
subnetId: {{ print "subnet-" (print $.Release.Namespace "/" $vpcId "/" $subnetName | sha256sum | trunc 8) }}
subnetCIDR: {{ $subnetConfig.cidr }}
{{- end }}

View File

@@ -8,9 +8,12 @@
"default": {},
"additionalProperties": {
"type": "object",
"required": [
"cidr"
],
"properties": {
"cidr": {
"description": "IP address range",
"description": "Subnet CIDR, e.g. 192.168.0.0/24",
"type": "string"
}
}

View File

@@ -1,15 +1,14 @@
##
## @section Common parameters
##
## @typedef {struct} Subnet - Subnet of a VPC
## @field {string} [cidr] - IP address range
## @field {string} cidr - Subnet CIDR, e.g. 192.168.0.0/24
## @param {map[string]Subnet} subnets - Subnets of a VPC
subnets: {}
##
## Example:
## subnets:
## mysubnet0:
## cidr: "172.16.0.0/24"
## cidr: "172.16.0.0/16"
## mysubnet1:
## cidr: "172.16.1.0/24"
## cidr: "172.17.0.0/16"
subnets: {}

View File

@@ -5,7 +5,7 @@ set -u
TMPDIR=$(mktemp -d)
PROFILES="initramfs kernel iso installer nocloud metal"
FIRMWARES="amd-ucode amdgpu bnx2-bnx2x i915 intel-ice-firmware intel-ucode qlogic-firmware"
EXTENSIONS="drbd zfs"
EXTENSIONS="drbd zfs lldpd"
mkdir -p images/talos/profiles
@@ -90,6 +90,7 @@ input:
- imageRef: ${QLOGIC_FIRMWARE_IMAGE}
- imageRef: ${DRBD_IMAGE}
- imageRef: ${ZFS_IMAGE}
- imageRef: ${LLDPD_IMAGE}
output:
kind: ${kind}
imageOptions: ${image_options}

View File

@@ -21,6 +21,7 @@ input:
- imageRef: ghcr.io/siderolabs/qlogic-firmware:20250917@sha256:7094e5db6931a1b68240416b65ddc0f3b546bd9b8520e3cfb1ddebcbfc83e890
- imageRef: ghcr.io/siderolabs/drbd:9.2.14-v1.11.3@sha256:4393756875751e2664a04e96c1ccff84c99958ca819dd93b46b82ad8f3b4be67
- imageRef: ghcr.io/siderolabs/zfs:2.3.3-v1.11.3@sha256:3c0b34a760914980ac234e66f130d829e428018e46420b7bca33219b1cc2dd87
- imageRef: ghcr.io/siderolabs/lldpd:1.0.20@sha256:4c6370518f5b2e1f03214a6ed54778eaea663fda8850e3f4da174ed69b636172
output:
kind: initramfs
imageOptions: {}

View File

@@ -21,6 +21,7 @@ input:
- imageRef: ghcr.io/siderolabs/qlogic-firmware:20250917@sha256:7094e5db6931a1b68240416b65ddc0f3b546bd9b8520e3cfb1ddebcbfc83e890
- imageRef: ghcr.io/siderolabs/drbd:9.2.14-v1.11.3@sha256:4393756875751e2664a04e96c1ccff84c99958ca819dd93b46b82ad8f3b4be67
- imageRef: ghcr.io/siderolabs/zfs:2.3.3-v1.11.3@sha256:3c0b34a760914980ac234e66f130d829e428018e46420b7bca33219b1cc2dd87
- imageRef: ghcr.io/siderolabs/lldpd:1.0.20@sha256:4c6370518f5b2e1f03214a6ed54778eaea663fda8850e3f4da174ed69b636172
output:
kind: installer
imageOptions: {}

View File

@@ -21,6 +21,7 @@ input:
- imageRef: ghcr.io/siderolabs/qlogic-firmware:20250917@sha256:7094e5db6931a1b68240416b65ddc0f3b546bd9b8520e3cfb1ddebcbfc83e890
- imageRef: ghcr.io/siderolabs/drbd:9.2.14-v1.11.3@sha256:4393756875751e2664a04e96c1ccff84c99958ca819dd93b46b82ad8f3b4be67
- imageRef: ghcr.io/siderolabs/zfs:2.3.3-v1.11.3@sha256:3c0b34a760914980ac234e66f130d829e428018e46420b7bca33219b1cc2dd87
- imageRef: ghcr.io/siderolabs/lldpd:1.0.20@sha256:4c6370518f5b2e1f03214a6ed54778eaea663fda8850e3f4da174ed69b636172
output:
kind: iso
imageOptions: {}

View File

@@ -21,6 +21,7 @@ input:
- imageRef: ghcr.io/siderolabs/qlogic-firmware:20250917@sha256:7094e5db6931a1b68240416b65ddc0f3b546bd9b8520e3cfb1ddebcbfc83e890
- imageRef: ghcr.io/siderolabs/drbd:9.2.14-v1.11.3@sha256:4393756875751e2664a04e96c1ccff84c99958ca819dd93b46b82ad8f3b4be67
- imageRef: ghcr.io/siderolabs/zfs:2.3.3-v1.11.3@sha256:3c0b34a760914980ac234e66f130d829e428018e46420b7bca33219b1cc2dd87
- imageRef: ghcr.io/siderolabs/lldpd:1.0.20@sha256:4c6370518f5b2e1f03214a6ed54778eaea663fda8850e3f4da174ed69b636172
output:
kind: kernel
imageOptions: {}

View File

@@ -21,6 +21,7 @@ input:
- imageRef: ghcr.io/siderolabs/qlogic-firmware:20250917@sha256:7094e5db6931a1b68240416b65ddc0f3b546bd9b8520e3cfb1ddebcbfc83e890
- imageRef: ghcr.io/siderolabs/drbd:9.2.14-v1.11.3@sha256:4393756875751e2664a04e96c1ccff84c99958ca819dd93b46b82ad8f3b4be67
- imageRef: ghcr.io/siderolabs/zfs:2.3.3-v1.11.3@sha256:3c0b34a760914980ac234e66f130d829e428018e46420b7bca33219b1cc2dd87
- imageRef: ghcr.io/siderolabs/lldpd:1.0.20@sha256:4c6370518f5b2e1f03214a6ed54778eaea663fda8850e3f4da174ed69b636172
output:
kind: image
imageOptions: { diskSize: 1306525696, diskFormat: raw }

View File

@@ -21,6 +21,7 @@ input:
- imageRef: ghcr.io/siderolabs/qlogic-firmware:20250917@sha256:7094e5db6931a1b68240416b65ddc0f3b546bd9b8520e3cfb1ddebcbfc83e890
- imageRef: ghcr.io/siderolabs/drbd:9.2.14-v1.11.3@sha256:4393756875751e2664a04e96c1ccff84c99958ca819dd93b46b82ad8f3b4be67
- imageRef: ghcr.io/siderolabs/zfs:2.3.3-v1.11.3@sha256:3c0b34a760914980ac234e66f130d829e428018e46420b7bca33219b1cc2dd87
- imageRef: ghcr.io/siderolabs/lldpd:1.0.20@sha256:4c6370518f5b2e1f03214a6ed54778eaea663fda8850e3f4da174ed69b636172
output:
kind: image
imageOptions: { diskSize: 1306525696, diskFormat: raw }

View File

@@ -1,2 +1,2 @@
cozystack:
image: ghcr.io/cozystack/cozystack/installer:v0.38.0@sha256:1a902ebd15fe375079098c088dd5b40475926c8d9576faf6348433f0fd86a963
image: ghcr.io/cozystack/cozystack/installer:v0.37.0@sha256:256c5a0f0ae2fc3ad6865b9fda74c42945b38a5384240fa29554617185b60556

View File

@@ -76,13 +76,6 @@ releases:
namespace: cozy-kubeovn
dependsOn: [cilium,kubeovn]
- name: multus
releaseName: multus
chart: cozy-multus
namespace: cozy-multus
privileged: true
dependsOn: [cilium,kubeovn]
- name: cozy-proxy
releaseName: cozystack
chart: cozy-cozy-proxy

View File

@@ -40,10 +40,6 @@ releases:
chart: cozy-cozystack-api
namespace: cozy-system
dependsOn: [cozystack-controller]
values:
cozystackAPI:
localK8sAPIEndpoint:
enabled: false
- name: cozystack-controller
releaseName: cozystack-controller

View File

@@ -1,2 +1,2 @@
e2e:
image: ghcr.io/cozystack/cozystack/e2e-sandbox:v0.38.0@sha256:cb17739b46eca263b2a31c714a3cb211da6f9de259b1641c2fc72c91bdfc93bb
image: ghcr.io/cozystack/cozystack/e2e-sandbox:v0.37.0@sha256:10afd0a6c39248ec41d0e59ff1bc6c29bd0075b7cc9a512b01cf603ef39c33ea

View File

@@ -1 +1 @@
ghcr.io/cozystack/cozystack/matchbox:v0.38.0@sha256:9ff2bdcf802445f6c1cabdf0e6fc32ee10043b1067945232a91088abad63f583
ghcr.io/cozystack/cozystack/matchbox:v0.37.0@sha256:5cca5f56b755285aefa11b1052fe55e1aa83b25bae34aef80cdb77ff63091044

View File

@@ -1,6 +1,6 @@
{{- $cozyConfig := lookup "v1" "ConfigMap" "cozy-system" "cozystack" }}
{{- $exposeIngress := index $cozyConfig.data "expose-ingress" | default "tenant-root" }}
{{- $exposeExternalIPs := (index $cozyConfig.data "expose-external-ips") | default "" | nospace }}
{{- $exposeExternalIPs := (index $cozyConfig.data "expose-external-ips") | default "" }}
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:

View File

@@ -1 +1 @@
ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.38.0@sha256:4548d85e7e69150aaf52fbb17fb9487e9714bdd8407aff49762cf39b9d0ab29c
ghcr.io/cozystack/cozystack/objectstorage-sidecar:v0.37.0@sha256:f166f09cdc9cdbb758209883819ab8261a3793bc1d7a6b6685efd5a2b2930847

View File

@@ -4,5 +4,3 @@ include ../../../scripts/package.mk
generate:
cozyvalues-gen -v values.yaml -s values.schema.json -r README.md
test:
$(MAKE) -C ../../tests/cozy-lib-tests/ test

View File

@@ -154,7 +154,7 @@
{{- $resources := index . 1 }}
{{- $global := index . 2 }}
{{- $presetMap := include "cozy-lib.resources.unsanitizedPreset" $preset | fromYaml }}
{{- $mergedMap := deepCopy (default (dict) $resources) | mergeOverwrite $presetMap }}
{{- $mergedMap := deepCopy $resources | mergeOverwrite $presetMap }}
{{- include "cozy-lib.resources.sanitize" (list $mergedMap $global) }}
{{- end }}
@@ -174,46 +174,15 @@
{{- end }}
{{- define "cozy-lib.resources.flatten" -}}
{{- $out := dict -}}
{{- $res := include "cozy-lib.resources.sanitize" . | fromYaml -}}
{{- range $section, $values := $res }}
{{- range $k, $v := $values }}
{{- with include "cozy-lib.resources.flattenResource" (list $section $k) }}
{{- $_ := set $out . $v }}
{{- end }}
{{- end }}
{{- end }}
{{- $out | toYaml }}
{{- $out := dict -}}
{{- $res := include "cozy-lib.resources.sanitize" . | fromYaml -}}
{{- range $section, $values := $res }}
{{- range $k, $v := $values }}
{{- $key := printf "%s.%s" $section $k }}
{{- if ne $key "limits.storage" }}
{{- $_ := set $out $key $v }}
{{- end }}
{{- end }}
{{- end }}
{{/*
This is a helper function that takes an argument like `list "limits" "services.loadbalancers"`
or `list "limits" "storage"` or `list "requests" "cpu"` and returns "services.loadbalancers",
"", and "requests.cpu", respectively, thus transforming them to an acceptable format for k8s
ResourceQuotas objects.
*/}}
{{- define "cozy-lib.resources.flattenResource" }}
{{- $rawQuotaKeys := list
"pods"
"services"
"services.loadbalancers"
"services.nodeports"
"services.clusterip"
"configmaps"
"secrets"
"persistentvolumeclaims"
"replicationcontrollers"
"resourcequotas"
-}}
{{- $section := index . 0 }}
{{- $type := index . 1 }}
{{- $out := "" }}
{{- if and (eq $section "limits") (eq $type "storage") }}
{{- $out = "" }}
{{- else if and (eq $section "limits") (has $type $rawQuotaKeys) }}
{{- $out = $type }}
{{- else if not (has $type $rawQuotaKeys) }}
{{- $out = printf "%s.%s" $section $type }}
{{- end }}
{{- $out -}}
{{- $out | toYaml }}
{{- end }}

View File

@@ -1 +1 @@
ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:f21b1c37872221323cee0490f9c58e04fa360c2b8c68700ab0455bc39f3ad160
ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:7348bec610f08bd902c88c9a9f28fdd644727e2728a1e4103f88f0c99febd5e7

View File

@@ -1 +0,0 @@
apiserver.local.config/

View File

@@ -4,18 +4,6 @@ NAMESPACE=cozy-system
include ../../../scripts/common-envs.mk
include ../../../scripts/package.mk
run-local:
openssl req -nodes -new -x509 -keyout /tmp/ca.key -out /tmp/ca.crt -subj "/CN=kube-ca"
openssl req -out /tmp/client.csr -new -newkey rsa:2048 -nodes -keyout /tmp/client.key -subj "/C=US/ST=SomeState/L=L/OU=Dev/CN=development/O=system:masters"
openssl x509 -req -days 365 -in /tmp/client.csr -CA /tmp/ca.crt -CAkey /tmp/ca.key -set_serial 01 -sha256 -out /tmp/client.crt
openssl req -out /tmp/apiserver.csr -new -newkey rsa:2048 -nodes -keyout /tmp/apiserver.key -subj "/CN=cozystack-api" -config cozystack-api-openssl.cnf
openssl x509 -req -days 365 -in /tmp/apiserver.csr -CA /tmp/ca.crt -CAkey /tmp/ca.key -set_serial 01 -sha256 -out /tmp/apiserver.crt -extensions v3_req -extfile cozystack-api-openssl.cnf
CGO_ENABLED=0 go build -o /tmp/cozystack-api ../../../cmd/cozystack-api/main.go
/tmp/cozystack-api --client-ca-file /tmp/ca.crt --tls-cert-file /tmp/apiserver.crt --tls-private-key-file /tmp/apiserver.key --secure-port 6443 --kubeconfig $(KUBECONFIG) --authorization-kubeconfig $(KUBECONFIG) --authentication-kubeconfig $(KUBECONFIG)
debug:
dlv debug ../../../cmd/cozystack-api/main.go -- --client-ca-file /tmp/ca.crt --tls-cert-file /tmp/apiserver.crt --tls-private-key-file /tmp/apiserver.key --secure-port 6443 --kubeconfig $(KUBECONFIG) --authorization-kubeconfig $(KUBECONFIG) --authentication-kubeconfig $(KUBECONFIG)
image: image-cozystack-api
image-cozystack-api:

View File

@@ -1,13 +0,0 @@
[ req ]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no
[ req_distinguished_name ]
CN = cozystack-api
[ v3_req ]
subjectAltName = @alt_names
[ alt_names ]
IP.1 = 127.0.0.1

View File

@@ -10,7 +10,6 @@ metadata:
annotations:
nginx.ingress.kubernetes.io/backend-protocol: HTTPS
nginx.ingress.kubernetes.io/ssl-passthrough: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
name: kubernetes
namespace: default
spec:

View File

@@ -1,87 +0,0 @@
{{- $shouldRunUpdateHook := false }}
{{- $previousKind := "Deployment" }}
{{- $previousKindPlural := "deployments" }}
{{- if not .Values.cozystackAPI.localK8sAPIEndpoint.enabled }}
{{- $previousKind = "DaemonSet" }}
{{- $previousKindPlural = "daemonsets" }}
{{- end }}
{{- $previous := lookup "apps/v1" $previousKind .Release.Namespace "cozystack-api" }}
{{- if $previous }}
{{- $shouldRunUpdateHook = true }}
{{- end }}
{{- if $shouldRunUpdateHook }}
---
apiVersion: batch/v1
kind: Job
metadata:
name: "cozystack-api-hook"
annotations:
helm.sh/hook: post-upgrade
helm.sh/hook-weight: "1"
helm.sh/hook-delete-policy: hook-succeeded,before-hook-creation
spec:
template:
metadata:
labels:
policy.cozystack.io/allow-to-apiserver: "true"
spec:
serviceAccountName: "cozystack-api-hook"
containers:
- name: kubectl
image: docker.io/alpine/k8s:1.33.4
command:
- sh
args:
- -exc
- |-
kubectl --namespace={{ .Release.Namespace }} delete --ignore-not-found \
{{ $previousKindPlural }}.apps cozystack-api
restartPolicy: Never
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
annotations:
helm.sh/hook: post-upgrade
helm.sh/hook-weight: "1"
helm.sh/hook-delete-policy: hook-succeeded,before-hook-creation
name: "cozystack-api-hook"
rules:
- apiGroups:
- "apps"
resources:
- "{{ $previousKindPlural }}"
verbs:
- get
- delete
resourceNames:
- "cozystack-api"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: "cozystack-api-hook"
annotations:
helm.sh/hook: post-upgrade
helm.sh/hook-weight: "1"
helm.sh/hook-delete-policy: hook-succeeded,before-hook-creation
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: "cozystack-api-hook"
subjects:
- kind: ServiceAccount
name: "cozystack-api-hook"
namespace: "{{ .Release.Namespace }}"
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: "cozystack-api-hook"
annotations:
helm.sh/hook: post-upgrade
helm.sh/hook-weight: "1"
helm.sh/hook-delete-policy: hook-succeeded,before-hook-creation
{{- end }}

View File

@@ -1,5 +1,5 @@
cozystackAPI:
image: ghcr.io/cozystack/cozystack/cozystack-api:v0.38.0@sha256:5eb5d6369c7c7ba0fa6b34b7c5022faa15c860b72e441b5fbde3eceda94efc88
image: ghcr.io/cozystack/cozystack/cozystack-api:v0.37.0@sha256:19d89e8afb90ce38ab7e42ecedfc28402f7c0b56f30957db957c5415132ff6ca
localK8sAPIEndpoint:
enabled: true
replicas: 2

View File

@@ -1,6 +1,6 @@
cozystackController:
image: ghcr.io/cozystack/cozystack/cozystack-controller:v0.38.0@sha256:4628a3711b6a6fc2e446255ee172cd268b28b07c65e98c302ea8897574dcbf22
image: ghcr.io/cozystack/cozystack/cozystack-controller:v0.37.0@sha256:845b8e68cbc277c2303080bcd55597e4334610d396dad258ad56fd906530acc3
debug: false
disableTelemetry: false
cozystackVersion: "v0.38.0"
cozystackVersion: "v0.37.0"
cozystackAPIKind: "DaemonSet"

View File

@@ -8,7 +8,7 @@ spec:
plural: virtualmachines
singular: virtualmachine
openAPISchema: |-
{"title":"Chart Values","type":"object","properties":{"cloudInit":{"description":"Cloud-init user data.","type":"string","default":""},"cloudInitSeed":{"description":"Seed string to generate SMBIOS UUID for the VM.","type":"string","default":""},"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"externalMethod":{"description":"Method to pass through traffic to the VM.","type":"string","default":"PortList","enum":["PortList","WholeIP"]},"externalPorts":{"description":"Ports to forward from outside the cluster.","type":"array","default":[22],"items":{"type":"integer"}},"gpus":{"description":"List of GPUs to attach.","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"name":{"description":"The name of the GPU resource to attach.","type":"string"}}}},"instanceProfile":{"description":"Virtual Machine preferences profile.","type":"string","default":"ubuntu","enum":["alpine","centos.7","centos.7.desktop","centos.stream10","centos.stream10.desktop","centos.stream8","centos.stream8.desktop","centos.stream8.dpdk","centos.stream9","centos.stream9.desktop","centos.stream9.dpdk","cirros","fedora","fedora.arm64","opensuse.leap","opensuse.tumbleweed","rhel.10","rhel.10.arm64","rhel.7","rhel.7.desktop","rhel.8","rhel.8.desktop","rhel.8.dpdk","rhel.9","rhel.9.arm64","rhel.9.desktop","rhel.9.dpdk","rhel.9.realtime","sles","ubuntu","windows.10","windows.10.virtio","windows.11","windows.11.virtio","windows.2k16","windows.2k16.virtio","windows.2k19","windows.2k19.virtio","windows.2k22","windows.2k22.virtio","windows.2k25","windows.2k25.virtio",""]},"instanceType":{"description":"Virtual Machine instance type.","type":"string","default":"u1.medium"},"resources":{"description":"Resource configuration for the virtual machine.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Amount of memory allocated.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"sockets":{"description":"Number of CPU sockets (vCPU topology).","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"running":{"description":"Whether the virtual machine should be running.","type":"boolean","default":true},"sshKeys":{"description":"List of SSH public keys for authentication.","type":"array","default":[],"items":{"type":"string"}},"subnets":{"description":"Additional subnets","type":"array","default":[],"items":{"type":"object","properties":{"name":{"description":"Subnet name","type":"string"}}}},"systemDisk":{"description":"System disk configuration.","type":"object","default":{},"required":["image","storage"],"properties":{"image":{"description":"The base image for the virtual machine.","type":"string","default":"ubuntu","enum":["ubuntu","cirros","alpine","fedora","talos"]},"storage":{"description":"The size of the disk allocated for the virtual machine.","type":"string","default":"5Gi"},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":"replicated"}}}}}
{"title":"Chart Values","type":"object","properties":{"cloudInit":{"description":"Cloud-init user data.","type":"string","default":""},"cloudInitSeed":{"description":"Seed string to generate SMBIOS UUID for the VM.","type":"string","default":""},"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"externalMethod":{"description":"Method to pass through traffic to the VM.","type":"string","default":"PortList","enum":["PortList","WholeIP"]},"externalPorts":{"description":"Ports to forward from outside the cluster.","type":"array","default":[22],"items":{"type":"integer"}},"gpus":{"description":"List of GPUs to attach.","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"name":{"description":"The name of the GPU resource to attach.","type":"string"}}}},"instanceProfile":{"description":"Virtual Machine preferences profile.","type":"string","default":"ubuntu","enum":["alpine","centos.7","centos.7.desktop","centos.stream10","centos.stream10.desktop","centos.stream8","centos.stream8.desktop","centos.stream8.dpdk","centos.stream9","centos.stream9.desktop","centos.stream9.dpdk","cirros","fedora","fedora.arm64","opensuse.leap","opensuse.tumbleweed","rhel.10","rhel.10.arm64","rhel.7","rhel.7.desktop","rhel.8","rhel.8.desktop","rhel.8.dpdk","rhel.9","rhel.9.arm64","rhel.9.desktop","rhel.9.dpdk","rhel.9.realtime","sles","ubuntu","windows.10","windows.10.virtio","windows.11","windows.11.virtio","windows.2k16","windows.2k16.virtio","windows.2k19","windows.2k19.virtio","windows.2k22","windows.2k22.virtio","windows.2k25","windows.2k25.virtio",""]},"instanceType":{"description":"Virtual Machine instance type.","type":"string","default":"u1.medium"},"resources":{"description":"Resource configuration for the virtual machine.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Amount of memory allocated.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"sockets":{"description":"Number of CPU sockets (vCPU topology).","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"running":{"description":"Whether the virtual machine should be running.","type":"boolean","default":true},"sshKeys":{"description":"List of SSH public keys for authentication.","type":"array","default":[],"items":{"type":"string"}},"subnets":{"description":"Additional subnets (management subnet will be attached by default)","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"name":{"description":"Name of the subnet to attach","type":"string"}}}},"systemDisk":{"description":"System disk configuration.","type":"object","default":{},"required":["image","storage"],"properties":{"image":{"description":"The base image for the virtual machine.","type":"string","default":"ubuntu","enum":["ubuntu","cirros","alpine","fedora","talos"]},"storage":{"description":"The size of the disk allocated for the virtual machine.","type":"string","default":"5Gi"},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":"replicated"}}}}}
release:
prefix: virtual-machine-
labels:

View File

@@ -8,7 +8,7 @@ spec:
plural: virtualprivateclouds
singular: virtualprivatecloud
openAPISchema: |-
{"title":"Chart Values","type":"object","properties":{"subnets":{"description":"Subnets of a VPC","type":"object","default":{},"additionalProperties":{"type":"object","properties":{"cidr":{"description":"IP address range","type":"string"}}}}}}
{"title":"Chart Values","type":"object","properties":{"subnets":{"description":"Subnets of a VPC","type":"object","default":{},"additionalProperties":{"type":"object","required":["cidr"],"properties":{"cidr":{"description":"Subnet CIDR, e.g. 192.168.0.0/24","type":"string"}}}}}}
release:
prefix: "virtualprivatecloud-"
labels:

View File

@@ -8,7 +8,7 @@ spec:
singular: vminstance
plural: vminstances
openAPISchema: |-
{"title":"Chart Values","type":"object","properties":{"cloudInit":{"description":"Cloud-init user data.","type":"string","default":""},"cloudInitSeed":{"description":"Seed string to generate SMBIOS UUID for the VM.","type":"string","default":""},"disks":{"description":"List of disks to attach.","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"bus":{"description":"Disk bus type (e.g. \"sata\").","type":"string"},"name":{"description":"Disk name.","type":"string"}}}},"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"externalMethod":{"description":"Method to pass through traffic to the VM.","type":"string","default":"PortList","enum":["PortList","WholeIP"]},"externalPorts":{"description":"Ports to forward from outside the cluster.","type":"array","default":[22],"items":{"type":"integer"}},"gpus":{"description":"List of GPUs to attach (NVIDIA driver requires at least 4 GiB RAM).","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"name":{"description":"The name of the GPU resource to attach.","type":"string"}}}},"instanceProfile":{"description":"Virtual Machine preferences profile.","type":"string","default":"ubuntu","enum":["alpine","centos.7","centos.7.desktop","centos.stream10","centos.stream10.desktop","centos.stream8","centos.stream8.desktop","centos.stream8.dpdk","centos.stream9","centos.stream9.desktop","centos.stream9.dpdk","cirros","fedora","fedora.arm64","opensuse.leap","opensuse.tumbleweed","rhel.10","rhel.10.arm64","rhel.7","rhel.7.desktop","rhel.8","rhel.8.desktop","rhel.8.dpdk","rhel.9","rhel.9.arm64","rhel.9.desktop","rhel.9.dpdk","rhel.9.realtime","sles","ubuntu","windows.10","windows.10.virtio","windows.11","windows.11.virtio","windows.2k16","windows.2k16.virtio","windows.2k19","windows.2k19.virtio","windows.2k22","windows.2k22.virtio","windows.2k25","windows.2k25.virtio",""]},"instanceType":{"description":"Virtual Machine instance type.","type":"string","default":"u1.medium"},"resources":{"description":"Resource configuration for the virtual machine.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Amount of memory allocated.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"sockets":{"description":"Number of CPU sockets (vCPU topology).","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"running":{"description":"Determines if the virtual machine should be running.","type":"boolean","default":true},"sshKeys":{"description":"List of SSH public keys for authentication.","type":"array","default":[],"items":{"type":"string"}},"subnets":{"description":"Additional subnets","type":"array","default":[],"items":{"type":"object","properties":{"name":{"description":"Subnet name","type":"string"}}}}}}
{"title":"Chart Values","type":"object","properties":{"cloudInit":{"description":"Cloud-init user data.","type":"string","default":""},"cloudInitSeed":{"description":"Seed string to generate SMBIOS UUID for the VM.","type":"string","default":""},"disks":{"description":"List of disks to attach.","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"bus":{"description":"Disk bus type (e.g. \"sata\").","type":"string"},"name":{"description":"Disk name.","type":"string"}}}},"external":{"description":"Enable external access from outside the cluster.","type":"boolean","default":false},"externalMethod":{"description":"Method to pass through traffic to the VM.","type":"string","default":"PortList","enum":["PortList","WholeIP"]},"externalPorts":{"description":"Ports to forward from outside the cluster.","type":"array","default":[22],"items":{"type":"integer"}},"gpus":{"description":"List of GPUs to attach (NVIDIA driver requires at least 4 GiB RAM).","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"name":{"description":"The name of the GPU resource to attach.","type":"string"}}}},"instanceProfile":{"description":"Virtual Machine preferences profile.","type":"string","default":"ubuntu","enum":["alpine","centos.7","centos.7.desktop","centos.stream10","centos.stream10.desktop","centos.stream8","centos.stream8.desktop","centos.stream8.dpdk","centos.stream9","centos.stream9.desktop","centos.stream9.dpdk","cirros","fedora","fedora.arm64","opensuse.leap","opensuse.tumbleweed","rhel.10","rhel.10.arm64","rhel.7","rhel.7.desktop","rhel.8","rhel.8.desktop","rhel.8.dpdk","rhel.9","rhel.9.arm64","rhel.9.desktop","rhel.9.dpdk","rhel.9.realtime","sles","ubuntu","windows.10","windows.10.virtio","windows.11","windows.11.virtio","windows.2k16","windows.2k16.virtio","windows.2k19","windows.2k19.virtio","windows.2k22","windows.2k22.virtio","windows.2k25","windows.2k25.virtio",""]},"instanceType":{"description":"Virtual Machine instance type.","type":"string","default":"u1.medium"},"resources":{"description":"Resource configuration for the virtual machine.","type":"object","default":{},"properties":{"cpu":{"description":"Number of CPU cores allocated.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Amount of memory allocated.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"sockets":{"description":"Number of CPU sockets (vCPU topology).","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"running":{"description":"Determines if the virtual machine should be running.","type":"boolean","default":true},"sshKeys":{"description":"List of SSH public keys for authentication.","type":"array","default":[],"items":{"type":"string"}},"subnets":{"description":"Additional subnets (management subnet will be attached by default)","type":"array","default":[],"items":{"type":"object","required":["name"],"properties":{"name":{"description":"Name of the subnet to attach","type":"string"}}}}}}
release:
prefix: vm-instance-
labels:
@@ -28,7 +28,7 @@ spec:
tags:
- compute
icon: PHN2ZyB3aWR0aD0iMTQ0IiBoZWlnaHQ9IjE0NCIgdmlld0JveD0iMCAwIDE0NCAxNDQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNDQiIGhlaWdodD0iMTQ0IiByeD0iMjQiIGZpbGw9InVybCgjcGFpbnQwX2xpbmVhcl82ODdfMzQ1NCkiLz4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzY4N18zNDU0KSI+CjxwYXRoIGQ9Ik04OS41MDM5IDExMS43MDdINTQuNDk3QzU0LjE3MjcgMTExLjcwNyA1NC4wMTA4IDExMS4yMjEgNTQuMzM0OSAxMTEuMDU5TDU3LjI1MjIgMTA4Ljk1MkM2MC4zMzE0IDEwNi42ODMgNjEuOTUyMiAxMDIuNjMxIDYwLjk3OTcgOTguNzQxMkg4My4wMjFDODIuMDQ4NSAxMDIuNjMxIDgzLjY2OTMgMTA2LjY4MyA4Ni43NDg1IDEwOC45NTJMODkuNjY1OCAxMTEuMDU5Qzg5Ljk5IDExMS4yMjEgODkuODI3OSAxMTEuNzA3IDg5LjUwMzkgMTExLjcwN1oiIGZpbGw9IiNCMEI2QkIiLz4KPHBhdGggZD0iTTExMy4zMjggOTguNzQxSDMwLjY3MjVDMjcuNTkzMSA5OC43NDEgMjUgOTYuMTQ4IDI1IDkzLjA2ODdWMzMuMTAzMkMyNSAzMC4wMjM5IDI3LjU5MzEgMjcuNDMwNyAzMC42NzI1IDI3LjQzMDdIMTEzLjMyOEMxMTYuNDA3IDI3LjQzMDcgMTE5IDMwLjAyMzcgMTE5IDMzLjEwMzJWOTMuMDY4N0MxMTkgOTYuMTQ4IDExNi40MDcgOTguNzQxIDExMy4zMjggOTguNzQxWiIgZmlsbD0iI0U4RURFRSIvPgo8cGF0aCBkPSJNMTE5IDg0LjE1NDlIMjVWMzMuMTAzMkMyNSAzMC4wMjM5IDI3LjU5MzEgMjcuNDMwNyAzMC42NzI1IDI3LjQzMDdIMTEzLjMyOEMxMTYuNDA3IDI3LjQzMDcgMTE5IDMwLjAyMzcgMTE5IDMzLjEwMzJMMTE5IDg0LjE1NDlaIiBmaWxsPSIjMDBCM0ZGIi8+CjxwYXRoIGQ9Ik05MC42Mzc0IDExNi41NjlINTMuMzYxNkM1Mi4wNjUxIDExNi41NjkgNTAuOTMwNyAxMTUuNDM1IDUwLjkzMDcgMTE0LjEzOEM1MC45MzA3IDExMi44NDEgNTIuMDY1MSAxMTEuNzA3IDUzLjM2MTYgMTExLjcwN0g5MC42Mzc0QzkxLjkzMzkgMTExLjcwNyA5My4wNjg0IDExMi44NDEgOTMuMDY4NCAxMTQuMTM4QzkzLjA2ODQgMTE1LjQzNSA5MS45MzM5IDExNi41NjkgOTAuNjM3NCAxMTYuNTY5WiIgZmlsbD0iI0U4RURFRSIvPgo8L2c+CjxwYXRoIGQ9Ik03Mi41Mjc1IDUzLjgzNjdDNzIuNDQzMSA1My44MzUxIDcyLjM2MDUgNTMuODEyMiA3Mi4yODczIDUzLjc3MDFMNTYuNDY5OSA0NC43OTM0QzU2LjM5ODMgNDQuNzUxOSA1Ni4zMzg4IDQ0LjY5MjMgNTYuMjk3MyA0NC42MjA3QzU2LjI1NTkgNDQuNTQ5IDU2LjIzMzggNDQuNDY3OCA1Ni4yMzM0IDQ0LjM4NUM1Ni4yMzM0IDQ0LjIxNjkgNTYuMzI1OCA0NC4wNjE3IDU2LjQ2OTkgNDMuOTc4NUw3Mi4xOTEyIDM1LjA2MDlDNzIuMjYzNyAzNS4wMjEgNzIuMzQ1IDM1IDcyLjQyNzcgMzVDNzIuNTEwNSAzNSA3Mi41OTE4IDM1LjAyMSA3Mi42NjQzIDM1LjA2MDlMODguNDg3MiA0NC4wMzk1Qzg4LjU1OTEgNDQuMDgwMSA4OC42MTg4IDQ0LjEzOTIgODguNjYgNDQuMjEwN0M4OC43MDEzIDQ0LjI4MjIgODguNzIyNyA0NC4zNjM1IDg4LjcyMTkgNDQuNDQ2Qzg4LjcyMjUgNDQuNTI4NSA4OC43MDEgNDQuNjA5NyA4OC42NTk4IDQ0LjY4MTJDODguNjE4NSA0NC43NTI2IDg4LjU1ODkgNDQuODExOCA4OC40ODcyIDQ0Ljg1MjVMNzIuNzcxNCA1My43NjgzQzcyLjY5NzIgNTMuODExNCA3Mi42MTMzIDUzLjgzNDkgNzIuNTI3NSA1My44MzY3IiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBvcGFjaXR5PSIwLjciIGQ9Ik03MC4yNTUzIDc1LjY1MTdDNzAuMTcxIDc1LjY1MzUgNzAuMDg3OCA3NS42MzE3IDcwLjAxNTEgNzUuNTg4OEw1NC4yNDU4IDY2LjY0MTdDNTQuMTcxNSA2Ni42MDI0IDU0LjEwOTUgNjYuNTQzNiA1NC4wNjYxIDY2LjQ3MTZDNTQuMDIyOCA2Ni4zOTk3IDU0IDY2LjMxNzMgNTQgNjYuMjMzM1Y0OC4yNzhDNTQgNDguMTA4IDU0LjA5MjQgNDcuOTU0NiA1NC4yNDM5IDQ3Ljg2OTZDNTQuMzE3MiA0Ny44MjcxIDU0LjQwMDQgNDcuODA0NyA1NC40ODUxIDQ3LjgwNDdDNTQuNTY5NyA0Ny44MDQ3IDU0LjY1MjkgNDcuODI3MSA1NC43MjYyIDQ3Ljg2OTZMNzAuNDkzNyA1Ni44MTMxQzcwLjU2NDIgNTYuODU2NSA3MC42MjI1IDU2LjkxNyA3MC42NjMyIDU2Ljk4OTFDNzAuNzAzOSA1Ny4wNjEyIDcwLjcyNTcgNTcuMTQyNCA3MC43MjY1IDU3LjIyNTFWNzUuMTgwNUM3MC43MjU5IDc1LjI2MjggNzAuNzA0MiA3NS4zNDM2IDcwLjY2MzUgNzUuNDE1MUM3MC42MjI3IDc1LjQ4NjYgNzAuNTY0MiA3NS41NDY0IDcwLjQ5MzcgNzUuNTg4OEM3MC40MjA2IDc1LjYyOTEgNzAuMzM4NyA3NS42NTA3IDcwLjI1NTMgNzUuNjUxNyIgZmlsbD0id2hpdGUiLz4KPHBhdGggb3BhY2l0eT0iMC40IiBkPSJNNzQuNzE5OCA3NS42NTExQzc0LjYzMzMgNzUuNjUxMiA3NC41NDgyIDc1LjYyOTYgNzQuNDcyMiA3NS41ODgzQzc0LjQwMTYgNzUuNTQ2MSA3NC4zNDMyIDc1LjQ4NjIgNzQuMzAyNyA3NS40MTQ3Qzc0LjI2MjMgNzUuMzQzMSA3NC4yNDExIDc1LjI2MjIgNzQuMjQxMiA3NS4xOFY1Ny4zMzczQzc0LjI0MTIgNTcuMTcxIDc0LjMzMzYgNTcuMDE1OCA3NC40NzIyIDU2LjkyOUw5MC4yMzk3IDQ3Ljk4NTVDOTAuMzExOSA0Ny45NDM4IDkwLjM5MzggNDcuOTIxOSA5MC40NzcxIDQ3LjkyMTlDOTAuNTYwNSA0Ny45MjE5IDkwLjY0MjQgNDcuOTQzOCA5MC43MTQ2IDQ3Ljk4NTVDOTAuNzg3NiA0OC4wMjU1IDkwLjg0ODUgNDguMDg0MiA5MC44OTExIDQ4LjE1NTdDOTAuOTMzNyA0OC4yMjcyIDkwLjk1NjMgNDguMzA4OCA5MC45NTY2IDQ4LjM5MlY2Ni4yMzI4QzkwLjk1NyA2Ni4zMTY0IDkwLjkzNDcgNjYuMzk4NSA5MC44OTIxIDY2LjQ3MDRDOTAuODQ5NSA2Ni41NDI0IDkwLjc4ODEgNjYuNjAxNCA5MC43MTQ2IDY2LjY0MTFMNzQuOTUyNiA3NS41ODgzQzc0Ljg4MjUgNzUuNjMwNyA3NC44MDE4IDc1LjY1MjUgNzQuNzE5OCA3NS42NTExIiBmaWxsPSJ3aGl0ZSIvPgo8ZGVmcz4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyXzY4N18zNDU0IiB4MT0iMTYxIiB5MT0iMTgwIiB4Mj0iMy41OTI4NGUtMDciIHkyPSI0Ljk5OTk4IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CjxzdG9wLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNTk1NjU2Ii8+CjwvbGluZWFyR3JhZGllbnQ+CjxjbGlwUGF0aCBpZD0iY2xpcDBfNjg3XzM0NTQiPgo8cmVjdCB3aWR0aD0iOTQiIGhlaWdodD0iOTQiIGZpbGw9IndoaXRlIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgyNSAyNSkiLz4KPC9jbGlwUGF0aD4KPC9kZWZzPgo8L3N2Zz4K
keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "external"], ["spec", "externalMethod"], ["spec", "externalPorts"], ["spec", "running"], ["spec", "instanceType"], ["spec", "instanceProfile"], ["spec", "disks"], ["spec", "subnets"], ["spec", "gpus"], ["spec", "resources"], ["spec", "sshKeys"], ["spec", "cloudInit"], ["spec", "cloudInitSeed"]]
keysOrder: [["apiVersion"], ["appVersion"], ["kind"], ["metadata"], ["metadata", "name"], ["spec", "external"], ["spec", "externalMethod"], ["spec", "externalPorts"], ["spec", "running"], ["spec", "instanceType"], ["spec", "instanceProfile"], ["spec", "disks"], ["spec", "gpus"], ["spec", "subnets"], ["spec", "resources"], ["spec", "sshKeys"], ["spec", "cloudInit"], ["spec", "cloudInitSeed"]]
secrets:
exclude: []
include: []

View File

@@ -1,11 +1,15 @@
# imported from https://github.com/cozystack/openapi-ui-k8s-bff
ARG NODE_VERSION=20.18.1
FROM node:${NODE_VERSION}-alpine AS builder
RUN apk add git
WORKDIR /src
ARG COMMIT_REF=ba56271739505284aee569f914fc90e6a9c670da
ARG COMMIT_REF=22f9143f5109fb90332651c857d70b51bffccd9b
RUN wget -O- https://github.com/PRO-Robotech/openapi-ui-k8s-bff/archive/${COMMIT_REF}.tar.gz | tar xzf - --strip-components=1
COPY patches /patches
RUN git apply /patches/*.diff
ENV PATH=/src/node_modules/.bin:$PATH
RUN npm install
RUN npm run build

View File

@@ -0,0 +1,21 @@
diff --git a/src/endpoints/forms/formPrepare/formPrepare.ts b/src/endpoints/forms/formPrepare/formPrepare.ts
index 7e437db..90c40f6 100644
--- a/src/endpoints/forms/formPrepare/formPrepare.ts
+++ b/src/endpoints/forms/formPrepare/formPrepare.ts
@@ -15,6 +15,7 @@ export const prepareFormProps: RequestHandler = async (req: TPrepareFormReq, res
const filteredHeaders = { ...req.headers }
delete filteredHeaders['host'] // Avoid passing internal host header
+ delete filteredHeaders['content-length'] // This header causes "stream has been aborted"
const { data: formsOverridesData } = await userKubeApi.get<TFormsOverridesData>(
`/apis/${BASE_API_GROUP}/${BASE_API_VERSION}/customformsoverrides`,
@@ -40,7 +41,7 @@ export const prepareFormProps: RequestHandler = async (req: TPrepareFormReq, res
},
)
- const { data: namespacesData } = await userKubeApi.get<TBuiltinResources>(`/api/v1/namespaces`, {
+ const { data: namespacesData } = await userKubeApi.get<TBuiltinResources>(`/apis/core.cozystack.io/v1alpha1/tenantnamespaces`, {
headers: {
// Authorization: `Bearer ${bearerToken}`,
// Cookie: cookies,

View File

@@ -3,14 +3,9 @@ ARG NODE_VERSION=20.18.1
# openapi-k8s-toolkit
# imported from https://github.com/cozystack/openapi-k8s-toolkit
FROM node:${NODE_VERSION}-alpine AS openapi-k8s-toolkit-builder
RUN apk add git
WORKDIR /src
ARG COMMIT=cb2f122caafaa2fd5455750213d9e633017ec555
ARG COMMIT=4f57ab295b2a886eb294b0b987554194fbe67dcd
RUN wget -O- https://github.com/cozystack/openapi-k8s-toolkit/archive/${COMMIT}.tar.gz | tar -xzvf- --strip-components=1
COPY openapi-k8s-toolkit/patches /patches
RUN git apply /patches/*.diff
RUN npm install
RUN npm install --build-from-source @swc/core
RUN npm run build
@@ -19,14 +14,14 @@ RUN npm run build
# openapi-ui
# imported from https://github.com/cozystack/openapi-ui
FROM node:${NODE_VERSION}-alpine AS builder
#RUN apk add git
RUN apk add git
WORKDIR /src
ARG COMMIT_REF=3cfbbf2156b6a5e4a1f283a032019530c0c2d37d
ARG COMMIT_REF=65e7fa8b3dc530a36e94c8435622bb09961aef97
RUN wget -O- https://github.com/PRO-Robotech/openapi-ui/archive/${COMMIT_REF}.tar.gz | tar xzf - --strip-components=1
#COPY openapi-ui/patches /patches
#RUN git apply /patches/*.diff
COPY patches /patches
RUN git apply /patches/*.diff
ENV PATH=/src/node_modules/.bin:$PATH

View File

@@ -1,50 +0,0 @@
diff --git a/src/components/molecules/EnrichedTable/organisms/EnrichedTable/utils.tsx b/src/components/molecules/EnrichedTable/organisms/EnrichedTable/utils.tsx
index 8bcef4d..2551e92 100644
--- a/src/components/molecules/EnrichedTable/organisms/EnrichedTable/utils.tsx
+++ b/src/components/molecules/EnrichedTable/organisms/EnrichedTable/utils.tsx
@@ -22,6 +22,15 @@ import { TableFactory } from '../../molecules'
import { ShortenedTextWithTooltip, FilterDropdown, TrimmedTags, TextAlignContainer, TinyButton } from './atoms'
import { TInternalDataForControls } from './types'
+const getPluralForm = (singular: string): string => {
+ // If already ends with 's', add 'es'
+ if (singular.endsWith('s')) {
+ return `${singular}es`
+ }
+ // Otherwise just add 's'
+ return `${singular}s`
+}
+
export const getCellRender = ({
value,
record,
@@ -255,7 +264,7 @@ export const getEnrichedColumnsWithControls = ({
key: 'controls',
className: 'controls',
width: 60,
- render: (value: TInternalDataForControls) => {
+ render: (value: TInternalDataForControls, record: unknown) => {
return (
// <TextAlignContainer $align="right" className="hideable">
<TextAlignContainer $align="center">
@@ -279,10 +288,19 @@ export const getEnrichedColumnsWithControls = ({
domEvent.stopPropagation()
domEvent.preventDefault()
if (key === 'edit') {
+ // Special case: redirect tenantmodules from core.cozystack.io to apps.cozystack.io with plural form
+ let apiGroupAndVersion = value.apiGroupAndVersion
+ let typeName = value.typeName
+ if (apiGroupAndVersion?.startsWith('core.cozystack.io/') && typeName === 'tenantmodules') {
+ const appsApiVersion = apiGroupAndVersion.replace('core.cozystack.io/', 'apps.cozystack.io/')
+ const pluralTypeName = getPluralForm(value.entryName)
+ apiGroupAndVersion = appsApiVersion
+ typeName = pluralTypeName
+ }
navigate(
`${baseprefix}/${value.cluster}${value.namespace ? `/${value.namespace}` : ''}${
value.syntheticProject ? `/${value.syntheticProject}` : ''
- }/${value.pathPrefix}/${value.apiGroupAndVersion}/${value.typeName}/${value.entryName}?backlink=${
+ }/${value.pathPrefix}/${apiGroupAndVersion}/${typeName}/${value.entryName}?backlink=${
value.backlink
}`,
)

View File

@@ -0,0 +1,89 @@
diff --git a/src/components/organisms/ListInsideClusterAndNs/ListInsideClusterAndNs.tsx b/src/components/organisms/ListInsideClusterAndNs/ListInsideClusterAndNs.tsx
index 577ba0f..018df9c 100644
--- a/src/components/organisms/ListInsideClusterAndNs/ListInsideClusterAndNs.tsx
+++ b/src/components/organisms/ListInsideClusterAndNs/ListInsideClusterAndNs.tsx
@@ -1,11 +1,16 @@
import React, { FC, useState } from 'react'
import { Button, Alert, Spin, Typography } from 'antd'
-import { filterSelectOptions, Spacer, useBuiltinResources } from '@prorobotech/openapi-k8s-toolkit'
+import { filterSelectOptions, Spacer, useApiResources } from '@prorobotech/openapi-k8s-toolkit'
import { useNavigate } from 'react-router-dom'
import { useSelector, useDispatch } from 'react-redux'
import { RootState } from 'store/store'
import { setCluster } from 'store/cluster/cluster/cluster'
import { Styled } from './styled'
+import {
+ BASE_PROJECTS_API_GROUP,
+ BASE_PROJECTS_VERSION,
+ BASE_PROJECTS_RESOURCE_NAME,
+} from 'constants/customizationApiGroupAndVersion'
export const ListInsideClusterAndNs: FC = () => {
const clusterList = useSelector((state: RootState) => state.clusterList.clusterList)
@@ -17,9 +22,11 @@ export const ListInsideClusterAndNs: FC = () => {
const [selectedCluster, setSelectedCluster] = useState<string>()
const [selectedNamespace, setSelectedNamespace] = useState<string>()
- const namespacesData = useBuiltinResources({
+ const namespacesData = useApiResources({
clusterName: cluster,
- typeName: 'namespaces',
+ apiGroup: BASE_PROJECTS_API_GROUP,
+ apiVersion: BASE_PROJECTS_VERSION,
+ typeName: BASE_PROJECTS_RESOURCE_NAME,
limit: null,
})
diff --git a/src/hooks/useNavSelectorInside.ts b/src/hooks/useNavSelectorInside.ts
index d69405e..5adbd5d 100644
--- a/src/hooks/useNavSelectorInside.ts
+++ b/src/hooks/useNavSelectorInside.ts
@@ -1,6 +1,11 @@
-import { TClusterList, TSingleResource, useBuiltinResources } from '@prorobotech/openapi-k8s-toolkit'
+import { TClusterList, TSingleResource, useApiResources } from '@prorobotech/openapi-k8s-toolkit'
import { useSelector } from 'react-redux'
import { RootState } from 'store/store'
+import {
+ BASE_PROJECTS_API_GROUP,
+ BASE_PROJECTS_VERSION,
+ BASE_PROJECTS_RESOURCE_NAME,
+} from 'constants/customizationApiGroupAndVersion'
const mappedClusterToOptionInSidebar = ({ name }: TClusterList[number]): { value: string; label: string } => ({
value: name,
@@ -15,9 +20,11 @@ const mappedNamespaceToOptionInSidebar = ({ metadata }: TSingleResource): { valu
export const useNavSelectorInside = (clusterName?: string) => {
const clusterList = useSelector((state: RootState) => state.clusterList.clusterList)
- const { data: namespaces } = useBuiltinResources({
+ const { data: namespaces } = useApiResources({
clusterName: clusterName || '',
- typeName: 'namespaces',
+ apiGroup: BASE_PROJECTS_API_GROUP,
+ apiVersion: BASE_PROJECTS_VERSION,
+ typeName: BASE_PROJECTS_RESOURCE_NAME,
limit: null,
})
diff --git a/src/utils/getBacklink.ts b/src/utils/getBacklink.ts
index a862354..f24e2bc 100644
--- a/src/utils/getBacklink.ts
+++ b/src/utils/getBacklink.ts
@@ -28,7 +28,7 @@ export const getFormsBackLink = ({
}
if (namespacesMode) {
- return `${baseprefix}/${clusterName}/builtin-table/namespaces`
+ return `${baseprefix}/${clusterName}/api-table/core.cozystack.io/v1alpha1/tenantnamespaces`
}
if (possibleProject) {
@@ -64,7 +64,7 @@ export const getTablesBackLink = ({
}
if (namespacesMode) {
- return `${baseprefix}/${clusterName}/builtin-table/namespaces`
+ return `${baseprefix}/${clusterName}/api-table/core.cozystack.io/v1alpha1/tenantnamespaces`
}
if (possibleProject) {

File diff suppressed because one or more lines are too long

View File

@@ -34,14 +34,6 @@ data:
}
location /k8s {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
rewrite /k8s/(.*) /$1 break;
proxy_pass https://kubernetes.default.svc:443;
}

View File

@@ -42,12 +42,10 @@ spec:
value: dashboard.cozystack.io
- name: BASE_API_VERSION
value: v1alpha1
- name: BASE_NAMESPACE_FULL_PATH
value: "/apis/core.cozystack.io/v1alpha1/tenantnamespaces"
- name: LOGGER
value: "true"
value: "TRUE"
- name: LOGGER_WITH_HEADERS
value: "false"
value: "TRUE"
- name: PORT
value: "64231"
image: {{ .Values.openapiUIK8sBff.image | quote }}
@@ -94,8 +92,6 @@ spec:
- env:
- name: BASEPREFIX
value: /openapi-ui
- name: HIDE_INSIDE
value: "true"
- name: CUSTOMIZATION_API_GROUP
value: dashboard.cozystack.io
- name: CUSTOMIZATION_API_VERSION
@@ -126,12 +122,6 @@ spec:
value: tenantnamespaces
- name: PROJECTS_VERSION
value: v1alpha1
- name: CUSTOM_NAMESPACE_API_RESOURCE_API_GROUP
value: core.cozystack.io
- name: CUSTOM_NAMESPACE_API_RESOURCE_API_VERSION
value: v1alpha1
- name: CUSTOM_NAMESPACE_API_RESOURCE_RESOURCE_NAME
value: tenantnamespaces
- name: USE_NAMESPACE_NAV
value: "true"
- name: LOGIN_URL
@@ -150,21 +140,21 @@ spec:
configMapKeyRef:
name: incloud-web-dashboard-config
key: TITLE_TEXT
- name: CUSTOM_TENANT_TEXT
- name: TENANT_TEXT
valueFrom:
configMapKeyRef:
name: incloud-web-dashboard-config
key: CUSTOM_TENANT_TEXT
key: TENANT_TEXT
- name: LOGO_TEXT
valueFrom:
configMapKeyRef:
name: incloud-web-dashboard-config
key: LOGO_TEXT
- name: CUSTOM_LOGO_SVG
- name: LOGO_SVG
valueFrom:
configMapKeyRef:
name: incloud-web-dashboard-config
key: CUSTOM_LOGO_SVG
key: LOGO_SVG
- name: ICON_SVG
valueFrom:
configMapKeyRef:

View File

@@ -1,6 +1,6 @@
openapiUI:
image: ghcr.io/cozystack/cozystack/openapi-ui:v0.38.0@sha256:78570edb9f4e329ffed0f8da3942acee1536323169d56324e57360df66044c28
image: ghcr.io/cozystack/cozystack/openapi-ui:v0.37.0@sha256:13f38cf56830e899eb5e3d9dc8184965dd8dba9f8cd3c5ca10df0970355842d6
openapiUIK8sBff:
image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v0.38.0@sha256:b7f18b86913d94338f1ceb93fca6409d19f565e35d6d6e683ca93441920fec71
image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v0.37.0@sha256:2b626dbbf87241e8621ac5b0285f402edbc2c2069ba254ca2ace2dd5c9248ac8
tokenProxy:
image: ghcr.io/cozystack/cozystack/token-proxy:v0.38.0@sha256:fad27112617bb17816702571e1f39d0ac3fe5283468d25eb12f79906cdab566b
image: ghcr.io/cozystack/cozystack/token-proxy:v0.37.0@sha256:fad27112617bb17816702571e1f39d0ac3fe5283468d25eb12f79906cdab566b

View File

@@ -10,4 +10,3 @@ update:
rm -rf charts
helm pull oci://ghcr.io/controlplaneio-fluxcd/charts/flux-operator --untar --untardir charts
patch --no-backup-if-mismatch -p1 < patches/kubernetesEnvs.diff
patch --no-backup-if-mismatch -p1 < patches/networkPolicy.diff

View File

@@ -1,21 +0,0 @@
{{- if .Capabilities.APIVersions.Has "cilium.io/v2/CiliumClusterwideNetworkPolicy" }}
---
apiVersion: cilium.io/v2
kind: CiliumClusterwideNetworkPolicy
metadata:
name: {{ include "flux-operator.fullname" . }}-restrict
spec:
nodeSelector: {}
ingressDeny:
- fromEntities:
- world
toPorts:
- ports:
- port: "8080"
protocol: TCP
- port: "8081"
protocol: TCP
ingress:
- fromEntities:
- cluster
{{- end }}

View File

@@ -1,25 +0,0 @@
diff --git a/packages/system/fluxcd-operator/charts/flux-operator/templates/network-policy.yaml b/packages/system/fluxcd-operator/charts/flux-operator/templates/network-policy.yaml
new file mode 100644
--- /dev/null (revision 52a23eacfc32430d8b008b765c64a81526521bae)
+++ b/packages/system/fluxcd-operator/charts/flux-operator/templates/network-policy.yaml (revision 52a23eacfc32430d8b008b765c64a81526521bae)
@@ -0,0 +1,18 @@
+{{- if .Capabilities.APIVersions.Has "cilium.io/v2/CiliumClusterwideNetworkPolicy" }}
+apiVersion: cilium.io/v2
+kind: CiliumClusterwideNetworkPolicy
+metadata:
+ name: {{ include "flux-operator.fullname" . }}-restrict
+spec:
+ nodeSelector: {}
+ ingressDeny:
+ - fromEntities:
+ - world
+ toPorts:
+ - ports:
+ - port: "8080"
+ protocol: TCP
+ - port: "8081"
+ protocol: TCP
+ ingress:
+ - fromEntities:
+ - cluster
+{{- end }}

View File

@@ -1,7 +1,4 @@
strimzi-kafka-operator:
watchAnyNamespace: true
generateNetworkPolicy: false
kubernetesServiceDnsDomain: cozy.local
resources:
limits:
memory: 512Mi
kubernetesServiceDnsDomain: cozy.local

View File

@@ -3,7 +3,7 @@ kamaji:
deploy: false
image:
pullPolicy: IfNotPresent
tag: v0.38.0@sha256:125e4e6a8b86418e891416d29353053ab8b65182b7e443f221b557c11a385280
tag: v0.37.0@sha256:9f4fd5045ede2909fbaf2572e4138fcbd8921071ecf8f08446257fddd0e6f655
repository: ghcr.io/cozystack/cozystack/kamaji
resources:
limits:
@@ -13,4 +13,4 @@ kamaji:
cpu: 100m
memory: 100Mi
extraArgs:
- --migrate-image=ghcr.io/cozystack/cozystack/kamaji:v0.38.0@sha256:125e4e6a8b86418e891416d29353053ab8b65182b7e443f221b557c11a385280
- --migrate-image=ghcr.io/cozystack/cozystack/kamaji:v0.37.0@sha256:9f4fd5045ede2909fbaf2572e4138fcbd8921071ecf8f08446257fddd0e6f655

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