Compare commits
67 commits
main
...
release-1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2eb484d8d4 | ||
|
|
5de17e5191 | ||
|
|
72efe285c9 | ||
|
|
3f3470a120 | ||
|
|
ddd452dcea | ||
|
|
208a9337ee | ||
|
|
50977618f5 | ||
|
|
718eb81b4b | ||
|
|
7d9e9107eb | ||
|
|
53a8ce6fd0 | ||
|
|
601b605f97 | ||
|
|
8364c25787 | ||
|
|
fe26737ede | ||
|
|
a81a3679d2 | ||
|
|
7880d55587 | ||
|
|
ae72c69c1c | ||
|
|
576e9ac5d7 | ||
|
|
82a833267e | ||
|
|
9910188c5a | ||
|
|
7fc6207fbc | ||
|
|
8c24435ee7 | ||
|
|
9fb1f79d7c | ||
|
|
890bb00e4b | ||
|
|
f961438c9d | ||
|
|
53d603f50c | ||
|
|
f7161c2af8 | ||
|
|
a33c143c2d | ||
|
|
002d3eb44c | ||
|
|
e77ee47e15 | ||
|
|
7780f50714 | ||
|
|
ec7cef8c69 | ||
|
|
bcdf308226 | ||
|
|
d5d4d86556 | ||
|
|
efc640103d | ||
|
|
d799731923 | ||
|
|
7fed9d4a42 | ||
|
|
01ef9f1223 | ||
|
|
e4fadb50dc | ||
|
|
736790852e | ||
|
|
6017dabaee | ||
|
|
ea641f7ec7 | ||
|
|
2c23f1fab6 | ||
|
|
2de20be50b | ||
|
|
1c87fe2004 | ||
|
|
a18047fc16 | ||
|
|
803d6b15ba | ||
|
|
c94768c64b | ||
|
|
093329cdce | ||
|
|
0e3c7fabff | ||
|
|
0ba129b4b7 | ||
|
|
095d3ab150 | ||
|
|
b06e2cecd5 | ||
|
|
b255214da0 | ||
|
|
4c9c68b7f5 | ||
|
|
002bd20f19 | ||
|
|
22c46d7271 | ||
|
|
dce757c884 | ||
|
|
109b4a333e | ||
|
|
4dada99a92 | ||
|
|
fd18a699b9 | ||
|
|
532669ad61 | ||
|
|
116e1baf63 | ||
|
|
52b4a0a7c6 | ||
|
|
0b4f3c7d30 | ||
|
|
ccb37d3fac | ||
|
|
89d90cac2d | ||
|
|
4f9a035c5b |
61 changed files with 572 additions and 199 deletions
30
.github/workflows/auto-release.yaml
vendored
30
.github/workflows/auto-release.yaml
vendored
|
|
@ -20,6 +20,14 @@ jobs:
|
|||
pull-requests: read
|
||||
|
||||
steps:
|
||||
- name: Generate GitHub App token
|
||||
id: app-token
|
||||
uses: actions/create-github-app-token@v1
|
||||
with:
|
||||
app-id: ${{ secrets.COZYSTACK_CI_APP_ID }}
|
||||
private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }}
|
||||
owner: cozystack
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
|
|
@ -28,27 +36,27 @@ jobs:
|
|||
|
||||
- name: Configure git
|
||||
env:
|
||||
GH_PAT: ${{ secrets.GH_PAT }}
|
||||
APP_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
run: |
|
||||
git config user.name "cozystack-bot"
|
||||
git config user.email "217169706+cozystack-bot@users.noreply.github.com"
|
||||
git remote set-url origin https://cozystack-bot:${GH_PAT}@github.com/${GITHUB_REPOSITORY}
|
||||
git config user.name "cozystack-ci[bot]"
|
||||
git config user.email "274107086+cozystack-ci[bot]@users.noreply.github.com"
|
||||
git remote set-url origin https://x-access-token:${APP_TOKEN}@github.com/${GITHUB_REPOSITORY}
|
||||
git config --unset-all http.https://github.com/.extraheader || true
|
||||
|
||||
- name: Process release branches
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
GH_PAT: ${{ secrets.GH_PAT }}
|
||||
APP_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
with:
|
||||
github-token: ${{ secrets.GH_PAT }}
|
||||
github-token: ${{ steps.app-token.outputs.token }}
|
||||
script: |
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
// Configure git to use PAT for authentication
|
||||
execSync('git config user.name "cozystack-bot"', { encoding: 'utf8' });
|
||||
execSync('git config user.email "217169706+cozystack-bot@users.noreply.github.com"', { encoding: 'utf8' });
|
||||
execSync(`git remote set-url origin https://cozystack-bot:${process.env.GH_PAT}@github.com/${process.env.GITHUB_REPOSITORY}`, { encoding: 'utf8' });
|
||||
// Remove GITHUB_TOKEN extraheader to ensure PAT is used (needed to trigger other workflows)
|
||||
// Configure git to use GitHub App token for authentication
|
||||
execSync('git config user.name "cozystack-ci[bot]"', { encoding: 'utf8' });
|
||||
execSync('git config user.email "274107086+cozystack-ci[bot]@users.noreply.github.com"', { encoding: 'utf8' });
|
||||
execSync(`git remote set-url origin https://x-access-token:${process.env.APP_TOKEN}@github.com/${process.env.GITHUB_REPOSITORY}`, { encoding: 'utf8' });
|
||||
// Remove GITHUB_TOKEN extraheader to ensure App token is used (needed to trigger other workflows)
|
||||
execSync('git config --unset-all http.https://github.com/.extraheader || true', { encoding: 'utf8' });
|
||||
|
||||
// Get all release-X.Y branches
|
||||
|
|
|
|||
18
.github/workflows/pull-requests-release.yaml
vendored
18
.github/workflows/pull-requests-release.yaml
vendored
|
|
@ -23,6 +23,14 @@ jobs:
|
|||
contains(github.event.pull_request.labels.*.name, 'release')
|
||||
|
||||
steps:
|
||||
- name: Generate GitHub App token
|
||||
id: app-token
|
||||
uses: actions/create-github-app-token@v1
|
||||
with:
|
||||
app-id: ${{ secrets.COZYSTACK_CI_APP_ID }}
|
||||
private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }}
|
||||
owner: cozystack
|
||||
|
||||
# Extract tag from branch name (branch = release-X.Y.Z*)
|
||||
- name: Extract tag from branch name
|
||||
id: get_tag
|
||||
|
|
@ -47,11 +55,11 @@ jobs:
|
|||
|
||||
- name: Create tag on merge commit
|
||||
env:
|
||||
GH_PAT: ${{ secrets.GH_PAT }}
|
||||
APP_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
run: |
|
||||
git config user.name "cozystack-bot"
|
||||
git config user.email "217169706+cozystack-bot@users.noreply.github.com"
|
||||
git remote set-url origin https://cozystack-bot:${GH_PAT}@github.com/${GITHUB_REPOSITORY}
|
||||
git config user.name "cozystack-ci[bot]"
|
||||
git config user.email "274107086+cozystack-ci[bot]@users.noreply.github.com"
|
||||
git remote set-url origin https://x-access-token:${APP_TOKEN}@github.com/${GITHUB_REPOSITORY}
|
||||
git tag -f ${{ steps.get_tag.outputs.tag }} ${{ github.sha }}
|
||||
git push -f origin ${{ steps.get_tag.outputs.tag }}
|
||||
|
||||
|
|
@ -59,7 +67,7 @@ jobs:
|
|||
- name: Ensure maintenance branch release-X.Y
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GH_PAT }}
|
||||
github-token: ${{ steps.app-token.outputs.token }}
|
||||
script: |
|
||||
const tag = '${{ steps.get_tag.outputs.tag }}'; // e.g. v0.1.3 or v0.1.3-rc3
|
||||
const match = tag.match(/^v(\d+)\.(\d+)\.\d+(?:[-\w\.]+)?$/);
|
||||
|
|
|
|||
23
.github/workflows/pull-requests.yaml
vendored
23
.github/workflows/pull-requests.yaml
vendored
|
|
@ -85,6 +85,14 @@ jobs:
|
|||
disk_id: ${{ steps.fetch_assets.outputs.disk_id }}
|
||||
|
||||
steps:
|
||||
- name: Generate GitHub App token
|
||||
id: app-token
|
||||
uses: actions/create-github-app-token@v1
|
||||
with:
|
||||
app-id: ${{ secrets.COZYSTACK_CI_APP_ID }}
|
||||
private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }}
|
||||
owner: cozystack
|
||||
|
||||
- name: Checkout code
|
||||
if: contains(github.event.pull_request.labels.*.name, 'release')
|
||||
uses: actions/checkout@v4
|
||||
|
|
@ -111,7 +119,7 @@ jobs:
|
|||
id: fetch_assets
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GH_PAT }}
|
||||
github-token: ${{ steps.app-token.outputs.token }}
|
||||
script: |
|
||||
const tag = '${{ steps.get_tag.outputs.tag }}';
|
||||
const releases = await github.rest.repos.listReleases({
|
||||
|
|
@ -144,6 +152,15 @@ jobs:
|
|||
if: ${{ always() && (needs.build.result == 'success' || needs.resolve_assets.result == 'success') }}
|
||||
|
||||
steps:
|
||||
- name: Generate GitHub App token
|
||||
if: contains(github.event.pull_request.labels.*.name, 'release')
|
||||
id: app-token
|
||||
uses: actions/create-github-app-token@v1
|
||||
with:
|
||||
app-id: ${{ secrets.COZYSTACK_CI_APP_ID }}
|
||||
private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }}
|
||||
owner: cozystack
|
||||
|
||||
# ▸ Checkout and prepare the codebase
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
|
@ -173,11 +190,11 @@ jobs:
|
|||
if: contains(github.event.pull_request.labels.*.name, 'release')
|
||||
run: |
|
||||
mkdir -p _out/assets
|
||||
curl -sSL -H "Authorization: token ${GH_PAT}" -H "Accept: application/octet-stream" \
|
||||
curl -sSL -H "Authorization: token ${APP_TOKEN}" -H "Accept: application/octet-stream" \
|
||||
-o _out/assets/nocloud-amd64.raw.xz \
|
||||
"https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/assets/${{ needs.resolve_assets.outputs.disk_id }}"
|
||||
env:
|
||||
GH_PAT: ${{ secrets.GH_PAT }}
|
||||
APP_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
|
||||
- name: Set sandbox ID
|
||||
run: echo "SANDBOX_NAME=cozy-e2e-sandbox-$(echo "${GITHUB_REPOSITORY}:${GITHUB_WORKFLOW}:${GITHUB_REF}" | sha256sum | cut -c1-10)" >> $GITHUB_ENV
|
||||
|
|
|
|||
142
.github/workflows/tags.yaml
vendored
142
.github/workflows/tags.yaml
vendored
|
|
@ -23,6 +23,14 @@ jobs:
|
|||
actions: write
|
||||
|
||||
steps:
|
||||
- name: Generate GitHub App token
|
||||
id: app-token
|
||||
uses: actions/create-github-app-token@v1
|
||||
with:
|
||||
app-id: ${{ secrets.COZYSTACK_CI_APP_ID }}
|
||||
private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }}
|
||||
owner: cozystack
|
||||
|
||||
# Check if a non-draft release with this tag already exists
|
||||
- name: Check if release already exists
|
||||
id: check_release
|
||||
|
|
@ -113,16 +121,31 @@ jobs:
|
|||
- name: Commit release artifacts
|
||||
if: steps.check_release.outputs.skip == 'false'
|
||||
env:
|
||||
GH_PAT: ${{ secrets.GH_PAT }}
|
||||
APP_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
run: |
|
||||
git config user.name "cozystack-bot"
|
||||
git config user.email "217169706+cozystack-bot@users.noreply.github.com"
|
||||
git remote set-url origin https://cozystack-bot:${GH_PAT}@github.com/${GITHUB_REPOSITORY}
|
||||
git config user.name "cozystack-ci[bot]"
|
||||
git config user.email "274107086+cozystack-ci[bot]@users.noreply.github.com"
|
||||
git remote set-url origin https://x-access-token:${APP_TOKEN}@github.com/${GITHUB_REPOSITORY}
|
||||
git config --unset-all http.https://github.com/.extraheader || true
|
||||
git add .
|
||||
git commit -m "Prepare release ${GITHUB_REF#refs/tags/}" -s || echo "No changes to commit"
|
||||
git push origin HEAD || true
|
||||
|
||||
# Tag the api/apps/v1alpha1 submodule for pkg.go.dev
|
||||
- name: Tag API submodule
|
||||
if: steps.check_release.outputs.skip == 'false'
|
||||
env:
|
||||
APP_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
run: |
|
||||
VTAG="${{ steps.tag.outputs.tag }}"
|
||||
SUBTAG="api/apps/v1alpha1/${VTAG}"
|
||||
git config user.name "cozystack-ci[bot]"
|
||||
git config user.email "274107086+cozystack-ci[bot]@users.noreply.github.com"
|
||||
git remote set-url origin https://x-access-token:${APP_TOKEN}@github.com/${GITHUB_REPOSITORY}
|
||||
TARGET="$(git rev-parse "${VTAG}^{}")"
|
||||
git tag -f "${SUBTAG}" "$TARGET"
|
||||
git push -f origin "refs/tags/${SUBTAG}"
|
||||
|
||||
# Create or reuse draft release
|
||||
- name: Create / reuse draft release
|
||||
if: steps.check_release.outputs.skip == 'false'
|
||||
|
|
@ -167,11 +190,11 @@ jobs:
|
|||
- name: Create release branch
|
||||
if: steps.check_release.outputs.skip == 'false'
|
||||
env:
|
||||
GH_PAT: ${{ secrets.GH_PAT }}
|
||||
APP_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
run: |
|
||||
git config user.name "cozystack-bot"
|
||||
git config user.email "217169706+cozystack-bot@users.noreply.github.com"
|
||||
git remote set-url origin https://cozystack-bot:${GH_PAT}@github.com/${GITHUB_REPOSITORY}
|
||||
git config user.name "cozystack-ci[bot]"
|
||||
git config user.email "274107086+cozystack-ci[bot]@users.noreply.github.com"
|
||||
git remote set-url origin https://x-access-token:${APP_TOKEN}@github.com/${GITHUB_REPOSITORY}
|
||||
BRANCH="release-${GITHUB_REF#refs/tags/v}"
|
||||
git branch -f "$BRANCH"
|
||||
git push -f origin "$BRANCH"
|
||||
|
|
@ -181,7 +204,7 @@ jobs:
|
|||
if: steps.check_release.outputs.skip == 'false'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GH_PAT }}
|
||||
github-token: ${{ steps.app-token.outputs.token }}
|
||||
script: |
|
||||
const version = context.ref.replace('refs/tags/v', '');
|
||||
const base = '${{ steps.get_base.outputs.branch }}';
|
||||
|
|
@ -223,6 +246,14 @@ jobs:
|
|||
pull-requests: write
|
||||
if: needs.prepare-release.result == 'success'
|
||||
steps:
|
||||
- name: Generate GitHub App token
|
||||
id: app-token
|
||||
uses: actions/create-github-app-token@v1
|
||||
with:
|
||||
app-id: ${{ secrets.COZYSTACK_CI_APP_ID }}
|
||||
private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }}
|
||||
owner: cozystack
|
||||
|
||||
- name: Parse tag
|
||||
id: tag
|
||||
uses: actions/github-script@v7
|
||||
|
|
@ -245,7 +276,7 @@ jobs:
|
|||
ref: main
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
token: ${{ secrets.GH_PAT }}
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
|
||||
- name: Check if changelog already exists
|
||||
id: check_changelog
|
||||
|
|
@ -273,7 +304,7 @@ jobs:
|
|||
if: steps.check_changelog.outputs.exists == 'false'
|
||||
env:
|
||||
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
|
||||
GH_TOKEN: ${{ secrets.GH_PAT }}
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
run: |
|
||||
copilot --prompt "prepare changelog file for tagged release v${{ steps.tag.outputs.version }}, use @docs/agents/changelog.md for it. Create the changelog file at docs/changelogs/v${{ steps.tag.outputs.version }}.md" \
|
||||
--allow-all-tools --allow-all-paths < /dev/null
|
||||
|
|
@ -281,11 +312,11 @@ jobs:
|
|||
- name: Create changelog branch and commit
|
||||
if: steps.check_changelog.outputs.exists == 'false'
|
||||
env:
|
||||
GH_PAT: ${{ secrets.GH_PAT }}
|
||||
APP_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
run: |
|
||||
git config user.name "cozystack-bot"
|
||||
git config user.email "217169706+cozystack-bot@users.noreply.github.com"
|
||||
git remote set-url origin https://cozystack-bot:${GH_PAT}@github.com/${GITHUB_REPOSITORY}
|
||||
git config user.name "cozystack-ci[bot]"
|
||||
git config user.email "274107086+cozystack-ci[bot]@users.noreply.github.com"
|
||||
git remote set-url origin https://x-access-token:${APP_TOKEN}@github.com/${GITHUB_REPOSITORY}
|
||||
|
||||
CHANGELOG_FILE="docs/changelogs/v${{ steps.tag.outputs.version }}.md"
|
||||
CHANGELOG_BRANCH="changelog-v${{ steps.tag.outputs.version }}"
|
||||
|
|
@ -320,7 +351,7 @@ jobs:
|
|||
if: steps.check_changelog.outputs.exists == 'false'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GH_PAT }}
|
||||
github-token: ${{ steps.app-token.outputs.token }}
|
||||
script: |
|
||||
const version = '${{ steps.tag.outputs.version }}';
|
||||
const changelogBranch = `changelog-v${version}`;
|
||||
|
|
@ -370,3 +401,82 @@ jobs:
|
|||
|
||||
console.log(`Created PR #${pr.data.number} for changelog`);
|
||||
}
|
||||
|
||||
update-website-docs:
|
||||
name: Update Website Docs
|
||||
runs-on: [self-hosted]
|
||||
needs: [generate-changelog, prepare-release]
|
||||
if: needs.generate-changelog.result == 'success' && needs.prepare-release.outputs.skip != 'true'
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Generate GitHub App token
|
||||
id: app-token
|
||||
uses: actions/create-github-app-token@v1
|
||||
with:
|
||||
app-id: ${{ secrets.COZYSTACK_CI_APP_ID }}
|
||||
private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }}
|
||||
owner: cozystack
|
||||
|
||||
- name: Parse tag
|
||||
id: tag
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const ref = context.ref.replace('refs/tags/', '');
|
||||
const m = ref.match(/^v(\d+\.\d+\.\d+)(-(?:alpha|beta|rc)\.\d+)?$/);
|
||||
if (!m) {
|
||||
core.setFailed(`❌ tag '${ref}' must match 'vX.Y.Z' or 'vX.Y.Z-(alpha|beta|rc).N'`);
|
||||
return;
|
||||
}
|
||||
const version = m[1] + (m[2] ?? '');
|
||||
core.setOutput('tag', ref); // v0.22.0
|
||||
core.setOutput('version', version); // 0.22.0
|
||||
|
||||
- name: Checkout website repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: cozystack/website
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
ref: main
|
||||
|
||||
- name: Update docs from release branch
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
run: make update-all BRANCH=release-${{ steps.tag.outputs.version }} RELEASE_TAG=${{ steps.tag.outputs.tag }}
|
||||
|
||||
- name: Commit and push
|
||||
id: commit
|
||||
run: |
|
||||
git config user.name "cozystack-ci[bot]"
|
||||
git config user.email "274107086+cozystack-ci[bot]@users.noreply.github.com"
|
||||
git add content
|
||||
if git diff --cached --quiet; then
|
||||
echo "No changes to commit"
|
||||
echo "changed=false" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
BRANCH="update-docs-v${{ steps.tag.outputs.version }}"
|
||||
git branch -D "$BRANCH" 2>/dev/null || true
|
||||
git checkout -b "$BRANCH"
|
||||
git commit --signoff -m "[docs] Update managed apps reference for v${{ steps.tag.outputs.version }}"
|
||||
git push --force --set-upstream origin "$BRANCH"
|
||||
echo "changed=true" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Open pull request
|
||||
if: steps.commit.outputs.changed == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
run: |
|
||||
BRANCH="update-docs-v${{ steps.tag.outputs.version }}"
|
||||
pr_state=$(gh pr view "$BRANCH" --repo cozystack/website --json state --jq .state 2>/dev/null || echo "")
|
||||
if [[ "$pr_state" == "OPEN" ]]; then
|
||||
echo "PR already open, skipping creation."
|
||||
else
|
||||
gh pr create \
|
||||
--repo cozystack/website \
|
||||
--title "[docs] Update managed apps reference for v${{ steps.tag.outputs.version }}" \
|
||||
--body "Automated docs update for release \`v${{ steps.tag.outputs.version }}\`." \
|
||||
--head "update-docs-v${{ steps.tag.outputs.version }}" \
|
||||
--base main
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@ else
|
|||
endif
|
||||
|
||||
REGISTRY ?= ghcr.io/cozystack/cozystack
|
||||
TAG = $(shell git describe --tags --exact-match 2>/dev/null || echo latest)
|
||||
TAG = $(shell git describe --tags --exact-match --match 'v*' 2>/dev/null || echo latest)
|
||||
PUSH := 1
|
||||
LOAD := 0
|
||||
BUILDER ?=
|
||||
PLATFORM ?=
|
||||
BUILDX_EXTRA_ARGS ?=
|
||||
COZYSTACK_VERSION = $(patsubst v%,%,$(shell git describe --tags))
|
||||
COZYSTACK_VERSION = $(patsubst v%,%,$(shell git describe --tags --match 'v*'))
|
||||
|
||||
BUILDX_ARGS := --provenance=false --push=$(PUSH) --load=$(LOAD) \
|
||||
--label org.opencontainers.image.source=https://github.com/cozystack/cozystack \
|
||||
|
|
@ -28,6 +28,6 @@ endef
|
|||
ifeq ($(COZYSTACK_VERSION),)
|
||||
$(shell git remote add upstream https://github.com/cozystack/cozystack.git || true)
|
||||
$(shell git fetch upstream --tags)
|
||||
COZYSTACK_VERSION = $(patsubst v%,%,$(shell git describe --tags))
|
||||
COZYSTACK_VERSION = $(patsubst v%,%,$(shell git describe --tags --match 'v*'))
|
||||
endif
|
||||
|
||||
|
|
|
|||
|
|
@ -68,31 +68,46 @@ func (m *Manager) ensureMarketplacePanel(ctx context.Context, crd *cozyv1alpha1.
|
|||
tags[i] = t
|
||||
}
|
||||
|
||||
specMap := map[string]any{
|
||||
"description": d.Description,
|
||||
"name": displayName,
|
||||
"type": "nonCrd",
|
||||
"apiGroup": "apps.cozystack.io",
|
||||
"apiVersion": "v1alpha1",
|
||||
"plural": app.Plural, // e.g., "buckets"
|
||||
"disabled": false,
|
||||
"hidden": false,
|
||||
"tags": tags,
|
||||
"icon": d.Icon,
|
||||
}
|
||||
|
||||
specBytes, err := json.Marshal(specMap)
|
||||
if err != nil {
|
||||
return reconcile.Result{}, err
|
||||
}
|
||||
|
||||
_, err = controllerutil.CreateOrUpdate(ctx, m.Client, mp, func() error {
|
||||
_, 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
|
||||
m.addDashboardLabels(mp, crd, ResourceTypeDynamic)
|
||||
|
||||
// Preserve user-set disabled/hidden values from existing resource
|
||||
disabled := false
|
||||
hidden := false
|
||||
if mp.Spec.Raw != nil {
|
||||
var existing map[string]any
|
||||
if err := json.Unmarshal(mp.Spec.Raw, &existing); err == nil {
|
||||
if v, ok := existing["disabled"].(bool); ok {
|
||||
disabled = v
|
||||
}
|
||||
if v, ok := existing["hidden"].(bool); ok {
|
||||
hidden = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
specMap := map[string]any{
|
||||
"description": d.Description,
|
||||
"name": displayName,
|
||||
"type": "nonCrd",
|
||||
"apiGroup": "apps.cozystack.io",
|
||||
"apiVersion": "v1alpha1",
|
||||
"plural": app.Plural, // e.g., "buckets"
|
||||
"disabled": disabled,
|
||||
"hidden": hidden,
|
||||
"tags": tags,
|
||||
"icon": d.Icon,
|
||||
}
|
||||
|
||||
specBytes, err := json.Marshal(specMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Only update spec if it's different to avoid unnecessary updates
|
||||
newSpec := dashv1alpha1.ArbitrarySpec{
|
||||
JSON: apiextv1.JSON{Raw: specBytes},
|
||||
|
|
|
|||
|
|
@ -38,6 +38,23 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Applicati
|
|||
}
|
||||
all = crdList.Items
|
||||
|
||||
// 1b) Fetch all MarketplacePanels to determine which resources are hidden
|
||||
hiddenResources := map[string]bool{}
|
||||
var mpList dashv1alpha1.MarketplacePanelList
|
||||
if err := m.List(ctx, &mpList, &client.ListOptions{}); err == nil {
|
||||
for i := range mpList.Items {
|
||||
mp := &mpList.Items[i]
|
||||
if mp.Spec.Raw != nil {
|
||||
var spec map[string]any
|
||||
if err := json.Unmarshal(mp.Spec.Raw, &spec); err == nil {
|
||||
if hidden, ok := spec["hidden"].(bool); ok && hidden {
|
||||
hiddenResources[mp.Name] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2) Build category -> []item map (only for CRDs with spec.dashboard != nil)
|
||||
type item struct {
|
||||
Key string
|
||||
|
|
@ -63,6 +80,11 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Applicati
|
|||
plural := pickPlural(kind, def)
|
||||
lowerKind := strings.ToLower(kind)
|
||||
|
||||
// Skip resources hidden via MarketplacePanel
|
||||
if hiddenResources[def.Name] {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if this resource is a module
|
||||
if def.Spec.Dashboard.Module {
|
||||
// Special case: info should have its own keysAndTags, not be in modules
|
||||
|
|
|
|||
|
|
@ -154,6 +154,14 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
|
|||
createStringColumn("Version", ".status.version"),
|
||||
}),
|
||||
|
||||
// Factory service details endpointslice (Pod serving table)
|
||||
createCustomColumnsOverride("factory-kube-service-details-endpointslice", []any{
|
||||
createStringColumn("Pod", ".targetRef.name"),
|
||||
createArrayColumn("Addresses", ".addresses"),
|
||||
createBoolColumn("Ready", ".conditions.ready"),
|
||||
createStringColumn("Node", ".nodeName"),
|
||||
}),
|
||||
|
||||
// Factory service details port mapping
|
||||
createCustomColumnsOverride("factory-kube-service-details-port-mapping", []any{
|
||||
createStringColumn("Name", ".name"),
|
||||
|
|
@ -1924,12 +1932,12 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory {
|
|||
map[string]any{
|
||||
"type": "EnrichedTable",
|
||||
"data": map[string]any{
|
||||
"id": "external-ips-table",
|
||||
"fetchUrl": "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/services",
|
||||
"clusterNamePartOfUrl": "{2}",
|
||||
"baseprefix": "/openapi-ui",
|
||||
"customizationId": "factory-details-v1.services",
|
||||
"pathToItems": []any{"items"},
|
||||
"id": "external-ips-table",
|
||||
"fetchUrl": "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/services",
|
||||
"cluster": "{2}",
|
||||
"baseprefix": "/openapi-ui",
|
||||
"customizationId": "factory-details-v1.services",
|
||||
"pathToItems": ".items",
|
||||
"fieldSelector": map[string]any{
|
||||
"spec.type": "LoadBalancer",
|
||||
},
|
||||
|
|
@ -1985,6 +1993,10 @@ func CreateAllNavigations() []*dashboardv1alpha1.Navigation {
|
|||
// Namespaced API resources
|
||||
"base-factory-namespaced-api-networking.k8s.io-v1-ingresses": "kube-ingress-details",
|
||||
"base-factory-namespaced-api-cozystack.io-v1alpha1-workloadmonitors": "workloadmonitor-details",
|
||||
// Backup resources (not ApplicationDefinitions, so ensureNavigation doesn't cover them)
|
||||
"base-factory-namespaced-api-backups.cozystack.io-v1alpha1-plans": "plan-details",
|
||||
"base-factory-namespaced-api-backups.cozystack.io-v1alpha1-backupjobs": "backupjob-details",
|
||||
"base-factory-namespaced-api-backups.cozystack.io-v1alpha1-backups": "backup-details",
|
||||
}
|
||||
|
||||
return []*dashboardv1alpha1.Navigation{
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ metadata:
|
|||
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||
nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
|
||||
{{- if eq $solver "http01" }}
|
||||
acme.cert-manager.io/http01-ingress-class: {{ $ingress }}
|
||||
acme.cert-manager.io/http01-ingress-ingressclassname: {{ $ingress }}
|
||||
{{- end }}
|
||||
cert-manager.io/cluster-issuer: {{ $clusterIssuer }}
|
||||
spec:
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
ghcr.io/cozystack/cozystack/nginx-cache:0.0.0@sha256:cb25e40cb665b8bbeee8cb1ec39da4c9a7452ef3f2f371912bbc0d1b1e2d40a8
|
||||
ghcr.io/cozystack/cozystack/nginx-cache:0.0.0@sha256:d397781152ab9123b11b8191d92eba7a0d2faa376aa2c15ddeb67842a9b59bab
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
ghcr.io/cozystack/cozystack/cluster-autoscaler:0.0.0@sha256:3753b735b0315bee90de54cb25cfebc63bd2cc90ad11ca4fdc0e70439abd5096
|
||||
ghcr.io/cozystack/cozystack/cluster-autoscaler:0.0.0@sha256:e9d0aa7e651b03a6713b380101a61832818a91b5f989da9754f58693898c4056
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:faaa6bcdb68196edb4baafe643679bd7d2ef35f910c639b71e06a4ecc034f232
|
||||
ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:c40b352c18e4a7d9b3a40d6c29c32bbf6dd9bd8fdca35185b194187e797aeeb6
|
||||
|
|
|
|||
|
|
@ -317,11 +317,7 @@ func (w *WrappedControllerService) ControllerPublishVolume(ctx context.Context,
|
|||
"ownerReferences": []interface{}{vmiOwnerRef},
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"endpointSelector": map[string]interface{}{
|
||||
"matchLabels": map[string]interface{}{
|
||||
"kubevirt.io/vm": vmName,
|
||||
},
|
||||
},
|
||||
"endpointSelector": buildEndpointSelector([]string{vmName}),
|
||||
"egress": []interface{}{
|
||||
map[string]interface{}{
|
||||
"toEndpoints": []interface{}{
|
||||
|
|
@ -441,6 +437,13 @@ func (w *WrappedControllerService) addCNPOwnerReference(ctx context.Context, nam
|
|||
if err := unstructured.SetNestedSlice(existing.Object, ownerRefs, "metadata", "ownerReferences"); err != nil {
|
||||
return status.Errorf(codes.Internal, "failed to set ownerReferences: %v", err)
|
||||
}
|
||||
|
||||
// Rebuild endpointSelector to include all VMs
|
||||
selector := buildEndpointSelector(vmNamesFromOwnerRefs(ownerRefs))
|
||||
if err := unstructured.SetNestedField(existing.Object, selector, "spec", "endpointSelector"); err != nil {
|
||||
return status.Errorf(codes.Internal, "failed to set endpointSelector: %v", err)
|
||||
}
|
||||
|
||||
if _, err := w.dynamicClient.Resource(ciliumNetworkPolicyGVR).Namespace(namespace).Update(ctx, existing, metav1.UpdateOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -486,6 +489,13 @@ func (w *WrappedControllerService) removeCNPOwnerReference(ctx context.Context,
|
|||
if err := unstructured.SetNestedSlice(existing.Object, remaining, "metadata", "ownerReferences"); err != nil {
|
||||
return status.Errorf(codes.Internal, "failed to set ownerReferences: %v", err)
|
||||
}
|
||||
|
||||
// Rebuild endpointSelector from remaining VMs
|
||||
selector := buildEndpointSelector(vmNamesFromOwnerRefs(remaining))
|
||||
if err := unstructured.SetNestedField(existing.Object, selector, "spec", "endpointSelector"); err != nil {
|
||||
return status.Errorf(codes.Internal, "failed to set endpointSelector: %v", err)
|
||||
}
|
||||
|
||||
if _, err := w.dynamicClient.Resource(ciliumNetworkPolicyGVR).Namespace(namespace).Update(ctx, existing, metav1.UpdateOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -494,6 +504,37 @@ func (w *WrappedControllerService) removeCNPOwnerReference(ctx context.Context,
|
|||
})
|
||||
}
|
||||
|
||||
// buildEndpointSelector returns an endpointSelector using matchExpressions
|
||||
// so that multiple VMs can be listed in a single selector.
|
||||
func buildEndpointSelector(vmNames []string) map[string]interface{} {
|
||||
values := make([]interface{}, len(vmNames))
|
||||
for i, name := range vmNames {
|
||||
values[i] = name
|
||||
}
|
||||
return map[string]interface{}{
|
||||
"matchExpressions": []interface{}{
|
||||
map[string]interface{}{
|
||||
"key": "kubevirt.io/vm",
|
||||
"operator": "In",
|
||||
"values": values,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// vmNamesFromOwnerRefs extracts VM names from ownerReferences.
|
||||
func vmNamesFromOwnerRefs(ownerRefs []interface{}) []string {
|
||||
var names []string
|
||||
for _, ref := range ownerRefs {
|
||||
if refMap, ok := ref.(map[string]interface{}); ok {
|
||||
if name, ok := refMap["name"].(string); ok {
|
||||
names = append(names, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func hasRWXAccessMode(pvc *corev1.PersistentVolumeClaim) bool {
|
||||
for _, mode := range pvc.Spec.AccessModes {
|
||||
if mode == corev1.ReadWriteMany {
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
ghcr.io/cozystack/cozystack/ubuntu-container-disk:v1.35@sha256:39f626c802dd84f95720ffb54fcd80dfb8a58ac280498870d0a1aa30d4252f94
|
||||
ghcr.io/cozystack/cozystack/ubuntu-container-disk:v1.35@sha256:364c6d454891f1eb1a598fddb69cf328a14dbc451a8ac65812b038a7756da60a
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
ghcr.io/cozystack/cozystack/mariadb-backup:0.0.0@sha256:0ddbbec0568dcb9fbc317cd9cc654e826dbe88ba3f184fa9b6b58aacb93b4570
|
||||
ghcr.io/cozystack/cozystack/mariadb-backup:0.0.0@sha256:3841eb171416711977dea0cf8cd45d32344caac9727af760c37d5e1dd41ee4bb
|
||||
|
|
|
|||
|
|
@ -34,6 +34,12 @@ spec:
|
|||
metadata:
|
||||
annotations:
|
||||
kubevirt.io/allow-pod-bridge-network-live-migration: "true"
|
||||
{{- $ovnIPName := printf "%s.%s" (include "virtual-machine.fullname" .) .Release.Namespace }}
|
||||
{{- $ovnIP := lookup "kubeovn.io/v1" "IP" "" $ovnIPName }}
|
||||
{{- if $ovnIP }}
|
||||
ovn.kubernetes.io/mac_address: {{ $ovnIP.spec.macAddress | quote }}
|
||||
ovn.kubernetes.io/ip_address: {{ $ovnIP.spec.ipAddress | quote }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "virtual-machine.labels" . | nindent 8 }}
|
||||
spec:
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
cozystackOperator:
|
||||
# Deployment variant: talos, generic, hosted
|
||||
variant: talos
|
||||
image: ghcr.io/cozystack/cozystack/cozystack-operator:v1.1.0@sha256:9367001a8d1d2dcf08ae74a42ac234eaa6af18f1af64ac28ce8a5946af9c5d3f
|
||||
image: ghcr.io/cozystack/cozystack/cozystack-operator:v1.1.7@sha256:cac3ed524881459a70b1b2384feacc045674e2b6390b695b959a01022b469642
|
||||
platformSourceUrl: 'oci://ghcr.io/cozystack/cozystack/cozystack-packages'
|
||||
platformSourceRef: 'digest=sha256:7c6da38e7b99ec80d35ba2cef721ea1579f8a0824989454544fa85318bb7bf15'
|
||||
platformSourceRef: 'digest=sha256:bda5caa46b06dab270ef2416fc2468c79e6df8c40532de0b8e6172e72a7e5f44'
|
||||
# Generic variant configuration (only used when cozystackOperator.variant=generic)
|
||||
cozystack:
|
||||
# Kubernetes API server host (IP only, no protocol/port)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,15 @@
|
|||
set -euo pipefail
|
||||
|
||||
DEFAULT_VERSION="v3.13"
|
||||
|
||||
# Skip if the CRD does not exist (rabbitmq was never installed)
|
||||
if ! kubectl api-resources --api-group=apps.cozystack.io -o name 2>/dev/null | grep -q '^rabbitmqs\.'; then
|
||||
echo "CRD rabbitmqs.apps.cozystack.io not found, skipping migration"
|
||||
kubectl create configmap -n cozy-system cozystack-version \
|
||||
--from-literal=version=35 --dry-run=client -o yaml | kubectl apply -f-
|
||||
exit 0
|
||||
fi
|
||||
|
||||
RABBITMQS=$(kubectl get rabbitmqs.apps.cozystack.io -A -o jsonpath='{range .items[*]}{.metadata.namespace}/{.metadata.name}{"\n"}{end}')
|
||||
for resource in $RABBITMQS; do
|
||||
NS="${resource%%/*}"
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ apiVersion: cozystack.io/v1alpha1
|
|||
kind: Package
|
||||
metadata:
|
||||
name: {{ $name }}
|
||||
annotations:
|
||||
helm.sh/resource-policy: keep
|
||||
spec:
|
||||
variant: {{ $variant }}
|
||||
{{- if $components }}
|
||||
|
|
@ -40,6 +42,8 @@ apiVersion: cozystack.io/v1alpha1
|
|||
kind: Package
|
||||
metadata:
|
||||
name: {{ $name }}
|
||||
annotations:
|
||||
helm.sh/resource-policy: keep
|
||||
spec:
|
||||
variant: {{ $variant }}
|
||||
{{- end }}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ sourceRef:
|
|||
path: /
|
||||
migrations:
|
||||
enabled: false
|
||||
image: ghcr.io/cozystack/cozystack/platform-migrations:v1.1.0@sha256:d7e8955c1ad8c8fbd4ce42b014c0f849d73d0c3faf0cedaac8e15d647fb2f663
|
||||
image: ghcr.io/cozystack/cozystack/platform-migrations:v1.1.7@sha256:bcbe612879cecd2ae1cef91dfff6d34d009c2f7de6592145c04a2d6d21b28f4b
|
||||
targetVersion: 35
|
||||
# Bundle deployment configuration
|
||||
bundles:
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
e2e:
|
||||
image: ghcr.io/cozystack/cozystack/e2e-sandbox:v1.1.0@sha256:0eae9f519669667d60b160ebb93c127843c470ad9ca3447fceaa54604503a7ba
|
||||
image: ghcr.io/cozystack/cozystack/e2e-sandbox:v1.1.7@sha256:0367a03b981df2a3ea13f411d4cb7869c2bf2c89c07d3d5c8971b9a28921ccef
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
ghcr.io/cozystack/cozystack/matchbox:v1.1.0@sha256:e4c872f6dadc2bbcb9200d04a1d9878f62502f74e979b4eae6c7203abc6d8fa6
|
||||
ghcr.io/cozystack/cozystack/matchbox:v1.1.7@sha256:c184033a07b48c8c0519bcf1d31de3820730786b23b2f2d4deb9f56b1b62a49e
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ metadata:
|
|||
app: bootbox
|
||||
annotations:
|
||||
{{- if eq $solver "http01" }}
|
||||
acme.cert-manager.io/http01-ingress-class: {{ $ingress }}
|
||||
acme.cert-manager.io/http01-ingress-ingressclassname: {{ $ingress }}
|
||||
{{- end }}
|
||||
cert-manager.io/cluster-issuer: {{ $clusterIssuer }}
|
||||
{{- if .Values.whitelistHTTP }}
|
||||
|
|
|
|||
|
|
@ -4,9 +4,14 @@ metadata:
|
|||
name: {{ .Release.Name }}-defrag
|
||||
spec:
|
||||
schedule: "0 * * * *"
|
||||
concurrencyPolicy: Forbid
|
||||
startingDeadlineSeconds: 300
|
||||
successfulJobsHistoryLimit: 3
|
||||
failedJobsHistoryLimit: 1
|
||||
jobTemplate:
|
||||
spec:
|
||||
activeDeadlineSeconds: 1800
|
||||
backoffLimit: 2
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.1.0@sha256:2a3595cd88b30af55b2000d3ca204899beecef0012b0e0402754c3914aad1f7f
|
||||
ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.1.7@sha256:fd3571e746efbc65fab342ca557beb2d0ad46fc0183772605fe84c6d3dd446a4
|
||||
|
|
|
|||
|
|
@ -243,7 +243,7 @@ spec:
|
|||
nginx.ingress.kubernetes.io/proxy-body-size: "0"
|
||||
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
|
||||
{{- if eq $solver "http01" }}
|
||||
acme.cert-manager.io/http01-ingress-class: {{ $ingress }}
|
||||
acme.cert-manager.io/http01-ingress-ingressclassname: {{ $ingress }}
|
||||
{{- end }}
|
||||
cert-manager.io/cluster-issuer: {{ $clusterIssuer }}
|
||||
tls:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
backupController:
|
||||
image: "ghcr.io/cozystack/cozystack/backup-controller:v1.1.0@sha256:8e42e29f5d30ecbef1f05cb0601c32703c5f9572b89d2c9032c1dff186e9a526"
|
||||
image: "ghcr.io/cozystack/cozystack/backup-controller:v1.1.7@sha256:ee54ad2007f7f103ba3d01ba95fa62e28419e96ba14cc8f96c7283bdac886f18"
|
||||
replicas: 2
|
||||
debug: false
|
||||
metrics:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
backupStrategyController:
|
||||
image: "ghcr.io/cozystack/cozystack/backupstrategy-controller:v1.1.0@sha256:508e3bd5a83a316732cfb84fe598064e3092482d941cfc53738ca21237642e6f"
|
||||
image: "ghcr.io/cozystack/cozystack/backupstrategy-controller:v1.1.7@sha256:3dcdbf368a33c85961fe1b692c0bfe5280c8c3c64bb8235c89d5efc1ea33c2d9"
|
||||
replicas: 2
|
||||
debug: false
|
||||
metrics:
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:5a7cae722ff6b424bdfbc4aba9d072c11b6930e2ee0f5fa97c3a565bd1c8dc88
|
||||
ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:c785518051e005de3e2a8393949b7a0040bd9bca9c0aea556ee3f401cc93e631
|
||||
|
|
|
|||
|
|
@ -1,3 +1,12 @@
|
|||
{{- $endpoint := printf "s3.%s" .Values._namespace.host }}
|
||||
{{- range $name, $user := .Values.users }}
|
||||
{{- $secretName := printf "%s-%s" $.Values.bucketName $name }}
|
||||
{{- $existingSecret := lookup "v1" "Secret" $.Release.Namespace $secretName }}
|
||||
{{- if $existingSecret }}
|
||||
{{- $bucketInfo := fromJson (b64dec (index $existingSecret.data "BucketInfo")) }}
|
||||
{{- $endpoint = trimPrefix "https://" (index $bucketInfo.spec.secretS3 "endpoint") }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
|
|
@ -17,6 +26,6 @@ spec:
|
|||
image: "{{ $.Files.Get "images/s3manager.tag" | trim }}"
|
||||
env:
|
||||
- name: ENDPOINT
|
||||
value: "s3.{{ .Values._namespace.host }}"
|
||||
value: {{ $endpoint | quote }}
|
||||
- name: SKIP_SSL_VERIFICATION
|
||||
value: "true"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ metadata:
|
|||
nginx.ingress.kubernetes.io/proxy-read-timeout: "99999"
|
||||
nginx.ingress.kubernetes.io/proxy-send-timeout: "99999"
|
||||
{{- if eq $solver "http01" }}
|
||||
acme.cert-manager.io/http01-ingress-class: {{ $ingress }}
|
||||
acme.cert-manager.io/http01-ingress-ingressclassname: {{ $ingress }}
|
||||
{{- end }}
|
||||
cert-manager.io/cluster-issuer: {{ $clusterIssuer }}
|
||||
spec:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
{{- $solver := (index .Values._cluster "solver") | default "http01" }}
|
||||
{{- $exposeIngress := (index .Values._cluster "expose-ingress") | default "tenant-root" }}
|
||||
|
||||
apiVersion: cert-manager.io/v1
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
name: letsencrypt-prod
|
||||
|
|
@ -17,9 +18,9 @@ spec:
|
|||
name: cloudflare-api-token-secret
|
||||
key: api-token
|
||||
{{- else }}
|
||||
http01:
|
||||
ingress:
|
||||
class: nginx
|
||||
http01:
|
||||
ingress:
|
||||
ingressClassName: {{ $exposeIngress }}
|
||||
{{- end }}
|
||||
|
||||
---
|
||||
|
|
@ -41,9 +42,9 @@ spec:
|
|||
name: cloudflare-api-token-secret
|
||||
key: api-token
|
||||
{{- else }}
|
||||
http01:
|
||||
ingress:
|
||||
class: nginx
|
||||
http01:
|
||||
ingress:
|
||||
ingressClassName: {{ $exposeIngress }}
|
||||
{{- end }}
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
cozystackAPI:
|
||||
image: ghcr.io/cozystack/cozystack/cozystack-api:v1.1.0@sha256:3a8e559b1a71cffb445bab14178d9abeba1b90509f9fec31df5ff5a9a38333d1
|
||||
image: ghcr.io/cozystack/cozystack/cozystack-api:v1.1.7@sha256:035d2dafb4a4cd5e806c5cdab19916fca7f9550ef898263cc8c891eaac745085
|
||||
replicas: 2
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ rules:
|
|||
- apiGroups: [""]
|
||||
resources: ["pods", "services", "persistentvolumes", "endpoints", "events", "resourcequotas"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: ["discovery.k8s.io"]
|
||||
resources: ["endpointslices"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: ["networking.k8s.io"]
|
||||
resources: ["ingresses"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
|
|
@ -94,6 +97,14 @@ rules:
|
|||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- discovery.k8s.io
|
||||
resources:
|
||||
- endpointslices
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- networking.k8s.io
|
||||
resources:
|
||||
|
|
@ -194,12 +205,18 @@ rules:
|
|||
- buckets
|
||||
- clickhouses
|
||||
- foos
|
||||
- foundationdbs
|
||||
- harbors
|
||||
- httpcaches
|
||||
- kafkas
|
||||
- kuberneteses
|
||||
- mariadbs
|
||||
- mongodbs
|
||||
- natses
|
||||
- openbaos
|
||||
- opensearches
|
||||
- postgreses
|
||||
- qdrants
|
||||
- rabbitmqs
|
||||
- redises
|
||||
- seaweedfses
|
||||
|
|
@ -207,6 +224,7 @@ rules:
|
|||
- virtualmachines
|
||||
- vmdisks
|
||||
- vminstances
|
||||
- vpns
|
||||
- infos
|
||||
- virtualprivateclouds
|
||||
verbs:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
cozystackController:
|
||||
image: ghcr.io/cozystack/cozystack/cozystack-controller:v1.1.0@sha256:f04fa839924a761571e1035d83f380f39f62d1708ea8d22f7a323f17bb59ff96
|
||||
image: ghcr.io/cozystack/cozystack/cozystack-controller:v1.1.7@sha256:84a4b7770622308242be8371d4c25af201f817b75c5fc0b1d54da27d2f68f102
|
||||
debug: false
|
||||
disableTelemetry: false
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{{- $brandingConfig := .Values._cluster.branding | default dict }}
|
||||
|
||||
{{- $tenantText := "v1.1.0" }}
|
||||
{{- $tenantText := "v1.1.7" }}
|
||||
{{- $footerText := "Cozystack" }}
|
||||
{{- $titleText := "Cozystack Dashboard" }}
|
||||
{{- $logoText := "" }}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ metadata:
|
|||
annotations:
|
||||
cert-manager.io/cluster-issuer: {{ $clusterIssuer }}
|
||||
{{- if eq $solver "http01" }}
|
||||
acme.cert-manager.io/http01-ingress-class: {{ $exposeIngress }}
|
||||
acme.cert-manager.io/http01-ingress-ingressclassname: {{ $exposeIngress }}
|
||||
{{- end }}
|
||||
nginx.ingress.kubernetes.io/rewrite-target: /
|
||||
nginx.ingress.kubernetes.io/client-max-body-size: 100m
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ apiVersion: v1.edp.epam.com/v1
|
|||
kind: KeycloakClient
|
||||
metadata:
|
||||
name: dashboard-client
|
||||
annotations:
|
||||
secret-hash: {{ $dashboardClient | sha256sum }}
|
||||
spec:
|
||||
serviceAccount:
|
||||
enabled: true
|
||||
|
|
|
|||
|
|
@ -20,6 +20,14 @@ rules:
|
|||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- storage.k8s.io
|
||||
resources:
|
||||
- storageclasses
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
openapiUI:
|
||||
image: ghcr.io/cozystack/cozystack/openapi-ui:v1.1.0@sha256:bc530ae2e428727eed284d7f80b2eea4fdd98b7618d20cab262eef7199af5fa5
|
||||
image: ghcr.io/cozystack/cozystack/openapi-ui:v1.1.7@sha256:e5971ae9138bb736bfbcccd39ca4c0d96d9df98190f7e33963b4de0223b31d72
|
||||
openapiUIK8sBff:
|
||||
image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v1.1.0@sha256:c938fee904acd948800d4dc5e121c4c5cd64cb4a3160fb8d2f9dbff0e5168740
|
||||
image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v1.1.7@sha256:75d51a65e190e83f538ae3cafdaa756930e53c7112a1daa4080d92dc67a9532a
|
||||
tokenProxy:
|
||||
image: ghcr.io/cozystack/cozystack/token-proxy:v1.1.0@sha256:2e280991e07853ea48f97b0a42946afffa10d03d6a83d41099ed83e6ffc94fdc
|
||||
image: ghcr.io/cozystack/cozystack/token-proxy:v1.1.7@sha256:2e280991e07853ea48f97b0a42946afffa10d03d6a83d41099ed83e6ffc94fdc
|
||||
|
|
|
|||
|
|
@ -38,8 +38,8 @@
|
|||
| kubeRbacProxy.args[2] | string | `"--logtostderr=true"` | |
|
||||
| kubeRbacProxy.args[3] | string | `"--v=0"` | |
|
||||
| kubeRbacProxy.image.pullPolicy | string | `"IfNotPresent"` | Image pull policy |
|
||||
| kubeRbacProxy.image.repository | string | `"gcr.io/kubebuilder/kube-rbac-proxy"` | Image repository |
|
||||
| kubeRbacProxy.image.tag | string | `"v0.16.0"` | Version of image |
|
||||
| kubeRbacProxy.image.repository | string | `"quay.io/brancz/kube-rbac-proxy"` | Image repository |
|
||||
| kubeRbacProxy.image.tag | string | `"v0.18.1"` | Version of image |
|
||||
| kubeRbacProxy.livenessProbe | object | `{}` | https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ |
|
||||
| kubeRbacProxy.readinessProbe | object | `{}` | https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ |
|
||||
| kubeRbacProxy.resources | object | `{"limits":{"cpu":"250m","memory":"128Mi"},"requests":{"cpu":"100m","memory":"64Mi"}}` | ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ |
|
||||
|
|
|
|||
|
|
@ -98,13 +98,13 @@ kubeRbacProxy:
|
|||
image:
|
||||
|
||||
# -- Image repository
|
||||
repository: gcr.io/kubebuilder/kube-rbac-proxy
|
||||
repository: quay.io/brancz/kube-rbac-proxy
|
||||
|
||||
# -- Image pull policy
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
# -- Version of image
|
||||
tag: v0.16.0
|
||||
tag: v0.18.1
|
||||
|
||||
args:
|
||||
- --secure-listen-address=0.0.0.0:8443
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
ghcr.io/cozystack/cozystack/grafana-dashboards:v1.1.0@sha256:2c9aa0b48e2bf6167db198f4d15882bfe51700108edf2e9f6d0942940a2c1204
|
||||
ghcr.io/cozystack/cozystack/grafana-dashboards:v1.1.7@sha256:2c9aa0b48e2bf6167db198f4d15882bfe51700108edf2e9f6d0942940a2c1204
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ kamaji:
|
|||
deploy: false
|
||||
image:
|
||||
pullPolicy: IfNotPresent
|
||||
tag: v1.1.0@sha256:914d04f7442f0faecf18f8282c192dee9fe244a711494a8c892e2f9e2ad415f7
|
||||
tag: v1.1.7@sha256:e9456e26c2b6f681704ad7b3017444eb244b370c8b5b3aee8ae30e967022f08b
|
||||
repository: ghcr.io/cozystack/cozystack/kamaji
|
||||
resources:
|
||||
limits:
|
||||
|
|
@ -13,4 +13,4 @@ kamaji:
|
|||
cpu: 100m
|
||||
memory: 100Mi
|
||||
extraArgs:
|
||||
- --migrate-image=ghcr.io/cozystack/cozystack/kamaji:v1.1.0@sha256:914d04f7442f0faecf18f8282c192dee9fe244a711494a8c892e2f9e2ad415f7
|
||||
- --migrate-image=ghcr.io/cozystack/cozystack/kamaji:v1.1.7@sha256:e9456e26c2b6f681704ad7b3017444eb244b370c8b5b3aee8ae30e967022f08b
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ metadata:
|
|||
{{- with .Values.ingress.annotations }}
|
||||
annotations:
|
||||
{{- if eq $solver "http01" }}
|
||||
acme.cert-manager.io/http01-ingress-class: {{ $exposeIngress }}
|
||||
acme.cert-manager.io/http01-ingress-ingressclassname: {{ $exposeIngress }}
|
||||
{{- end }}
|
||||
cert-manager.io/cluster-issuer: {{ $clusterIssuer }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
|
|
|
|||
|
|
@ -76,6 +76,8 @@ spec:
|
|||
{{- end }}
|
||||
- name: KC_METRICS_ENABLED
|
||||
value: "true"
|
||||
- name: KC_HEALTH_ENABLED
|
||||
value: "true"
|
||||
- name: KC_LOG_LEVEL
|
||||
value: "info"
|
||||
- name: KC_CACHE
|
||||
|
|
@ -130,16 +132,27 @@ spec:
|
|||
- name: http
|
||||
containerPort: 8080
|
||||
protocol: TCP
|
||||
- name: management
|
||||
containerPort: 9000
|
||||
protocol: TCP
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /health/ready
|
||||
port: management
|
||||
failureThreshold: 30
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
initialDelaySeconds: 120
|
||||
path: /health/live
|
||||
port: management
|
||||
periodSeconds: 15
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 5
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /realms/master
|
||||
port: http
|
||||
initialDelaySeconds: 60
|
||||
timeoutSeconds: 1
|
||||
path: /health/ready
|
||||
port: management
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
terminationGracePeriodSeconds: 60
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
portSecurity: true
|
||||
routes: ""
|
||||
image: ghcr.io/cozystack/cozystack/kubeovn-plunger:v1.1.0@sha256:b91bf0964a3204e50f703092f190b7d96c078a6ccee430215042ae1275ed5127
|
||||
image: ghcr.io/cozystack/cozystack/kubeovn-plunger:v1.1.7@sha256:3c134647f27ec8c0fdd6f3a0780dcc999300f35ad8d6f3a4d3cf221bb5bbd39d
|
||||
ovnCentralName: ovn-central
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
portSecurity: true
|
||||
routes: ""
|
||||
image: ghcr.io/cozystack/cozystack/kubeovn-webhook:v1.1.0@sha256:e18f9fd679e38f65362a8d0042f25468272f6d081136ad47027168d8e7e07a4a
|
||||
image: ghcr.io/cozystack/cozystack/kubeovn-webhook:v1.1.7@sha256:e18f9fd679e38f65362a8d0042f25468272f6d081136ad47027168d8e7e07a4a
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
storageClass: replicated
|
||||
csiDriver:
|
||||
image: ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:faaa6bcdb68196edb4baafe643679bd7d2ef35f910c639b71e06a4ecc034f232
|
||||
image: ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:c40b352c18e4a7d9b3a40d6c29c32bbf6dd9bd8fdca35185b194187e797aeeb6
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
lineageControllerWebhook:
|
||||
image: ghcr.io/cozystack/cozystack/lineage-controller-webhook:v1.1.0@sha256:4d6a2bb76cae84e24cd48c7377b03ed6bdfefe611221d2c0a7f77a5457db8849
|
||||
image: ghcr.io/cozystack/cozystack/lineage-controller-webhook:v1.1.7@sha256:bc03f10192158e4e7df3e3b3dfcad646b422245c6d31c6f25637cb6bf2db6e27
|
||||
debug: false
|
||||
localK8sAPIEndpoint:
|
||||
enabled: true
|
||||
|
|
|
|||
|
|
@ -61,6 +61,8 @@ RUN test -d .gradlehome && echo ".gradlehome found in tarball" || (echo ".gradle
|
|||
# Build DEB packages from tarball
|
||||
# Override GRADLE_FLAGS to remove --offline flag, allowing Gradle to download missing dependencies
|
||||
RUN sed -i 's/GRADLE_FLAGS = --offline/GRADLE_FLAGS =/' debian/rules || true
|
||||
# Skip dh_strip_nondeterminism to avoid failures on some JAR files (logback-core)
|
||||
RUN printf '\noverride_dh_strip_nondeterminism:\n\ttrue\n' >> debian/rules
|
||||
RUN LD_LIBRARY_PATH='' dpkg-buildpackage -rfakeroot -b -uc
|
||||
|
||||
# Copy built .deb packages to a location accessible from final image
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ Custom patches for piraeus-server (linstor-server) v1.32.3.
|
|||
- Upstream: [#475](https://github.com/LINBIT/linstor-server/pull/475)
|
||||
- **force-metadata-check-on-disk-add.diff** — Create metadata during toggle-disk from diskless to diskful
|
||||
- Upstream: [#474](https://github.com/LINBIT/linstor-server/pull/474)
|
||||
- **fix-duplicate-tcp-ports.diff** — Prevent duplicate TCP ports after toggle-disk operations
|
||||
- Upstream: [#476](https://github.com/LINBIT/linstor-server/pull/476)
|
||||
- **fix-duplicate-tcp-ports.diff** — Preserve TCP ports during toggle-disk to prevent port mismatch between controller and satellites
|
||||
- Upstream: [#476](https://github.com/LINBIT/linstor-server/pull/476) (superseded by this expanded fix)
|
||||
- **skip-adjust-when-device-inaccessible.diff** — Fix resources stuck in StandAlone after reboot, Unknown state race condition, and encrypted resource deletion
|
||||
- Upstream: [#477](https://github.com/LINBIT/linstor-server/pull/477)
|
||||
|
|
|
|||
|
|
@ -1,80 +1,131 @@
|
|||
From 1250abe99d64a0501795e37d3b6af62410002239 Mon Sep 17 00:00:00 2001
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Andrei Kvapil <kvapss@gmail.com>
|
||||
Date: Mon, 12 Jan 2026 13:44:46 +0100
|
||||
Subject: [PATCH] fix(drbd): prevent duplicate TCP ports after toggle-disk
|
||||
operations
|
||||
Date: Fri, 28 Mar 2026 13:00:00 +0100
|
||||
Subject: [PATCH] fix(drbd): preserve TCP ports during toggle-disk operations
|
||||
|
||||
Remove redundant ensureStackDataExists() call with empty payload from
|
||||
resetStoragePools() method that was causing TCP port conflicts after
|
||||
toggle-disk operations.
|
||||
Prevent TCP port mismatches after toggle-disk operations by preserving
|
||||
existing TCP ports when rebuilding DrbdRscData.
|
||||
|
||||
Root Cause:
|
||||
-----------
|
||||
The resetStoragePools() method, introduced in 2019 (commit 95cc17d0b8),
|
||||
calls ensureStackDataExists() with an empty LayerPayload. This worked
|
||||
correctly when TCP ports were stored at RscDfn level.
|
||||
During toggle-disk operations, removeLayerData() deletes DrbdRscData
|
||||
(freeing its TCP ports from the number pool), then ensureStackDataExists()
|
||||
creates new DrbdRscData. Since the payload has no explicit tcpPorts,
|
||||
the controller allocates new ports from the pool -- which may differ from
|
||||
the old ports if other resources claimed them in the meantime.
|
||||
|
||||
After the TCP port migration to per-node level (commit f754943463, May
|
||||
2025), this empty payload results in DrbdRscData being created without
|
||||
TCP ports assigned. The controller then sends a Pojo with an empty port
|
||||
Set to satellites.
|
||||
The controller correctly avoids collisions in its own number pool, but
|
||||
the satellite may miss the update (e.g. during controller restart or
|
||||
network issues). When this happens, the satellite keeps the old ports
|
||||
while peers receive the new ones, causing DRBD connection failures
|
||||
(StandAlone/Connecting state).
|
||||
|
||||
On satellites, when DrbdRscData is initialized with an empty port list,
|
||||
initPorts() uses preferredNewPortsRef from peer resources. Since
|
||||
SatelliteDynamicNumberPool.tryAllocate() always returns true (no-op),
|
||||
any port from preferredNewPortsRef is accepted without conflict checking,
|
||||
leading to duplicate TCP port assignments.
|
||||
|
||||
Impact:
|
||||
-------
|
||||
This regression affects toggle-disk operations, particularly:
|
||||
- Snapshot creation/restore operations
|
||||
- Manual toggle-disk operations
|
||||
- Any operation calling resetStoragePools()
|
||||
|
||||
Symptoms include:
|
||||
- DRBD resources failing to adjust with "port is also used" errors
|
||||
- Resources stuck in StandAlone or Connecting states
|
||||
- Multiple resources on the same node using identical TCP ports
|
||||
Additionally, remove the redundant ensureStackDataExists() call from
|
||||
resetStoragePools() -- the caller already invokes it with the correct
|
||||
payload.
|
||||
|
||||
Solution:
|
||||
---------
|
||||
Remove the ensureStackDataExists() call from resetStoragePools() as it
|
||||
is redundant. The calling code (e.g., CtrlRscToggleDiskApiCallHandler
|
||||
line 1071) already invokes ensureStackDataExists() with the correct
|
||||
payload immediately after resetStoragePools().
|
||||
1. Add copyDrbdTcpPortsIfExists() to save existing TCP ports into the
|
||||
LayerPayload before removeLayerData() deletes them.
|
||||
2. Call it from copyDrbdNodeIdIfExists() (covers both toggle-disk paths)
|
||||
and from the needsDeactivate path (shared storage pool case).
|
||||
3. Remove the redundant ensureStackDataExists() from resetStoragePools().
|
||||
|
||||
This fix ensures:
|
||||
1. resetStoragePools() only resets storage pool assignments
|
||||
2. Layer data creation with proper TCP ports happens via the caller's
|
||||
ensureStackDataExists() with correct payload
|
||||
3. No DrbdRscData objects are created without TCP port assignments
|
||||
|
||||
Related Issues:
|
||||
---------------
|
||||
Fixes #454 - Duplicate TCP ports after backup/restore operations
|
||||
Related to user reports of resources stuck in StandAlone after node
|
||||
reboots when toggle-disk or backup operations were in progress.
|
||||
|
||||
Testing:
|
||||
--------
|
||||
Verified that:
|
||||
- Toggle-disk operations no longer create resources without TCP ports
|
||||
- Backup/restore operations complete without TCP port conflicts
|
||||
- Resources maintain unique TCP ports across toggle-disk cycles
|
||||
This ensures the same TCP ports are reused when DrbdRscData is recreated,
|
||||
eliminating the window for port mismatch between controller and satellites.
|
||||
|
||||
Co-Authored-By: Claude <noreply@anthropic.com>
|
||||
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
|
||||
---
|
||||
.../linbit/linstor/layer/resource/CtrlRscLayerDataFactory.java | 2 --
|
||||
1 file changed, 2 deletions(-)
|
||||
.../controller/CtrlRscToggleDiskApiCallHandler.java | 40 +++++++++++++++++++--
|
||||
.../linstor/layer/resource/CtrlRscLayerDataFactory.java | 2 --
|
||||
2 files changed, 38 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/controller/src/main/java/com/linbit/linstor/core/apicallhandler/controller/CtrlRscToggleDiskApiCallHandler.java b/controller/src/main/java/com/linbit/linstor/core/apicallhandler/controller/CtrlRscToggleDiskApiCallHandler.java
|
||||
index ccdb0cee5..b0554c2ec 100644
|
||||
--- a/controller/src/main/java/com/linbit/linstor/core/apicallhandler/controller/CtrlRscToggleDiskApiCallHandler.java
|
||||
+++ b/controller/src/main/java/com/linbit/linstor/core/apicallhandler/controller/CtrlRscToggleDiskApiCallHandler.java
|
||||
@@ -58,6 +58,7 @@ import com.linbit.linstor.stateflags.StateFlags;
|
||||
import com.linbit.linstor.storage.StorageException;
|
||||
import com.linbit.linstor.storage.data.adapter.drbd.DrbdRscData;
|
||||
import com.linbit.linstor.storage.interfaces.categories.resource.AbsRscLayerObject;
|
||||
+import com.linbit.linstor.core.types.TcpPortNumber;
|
||||
import com.linbit.linstor.storage.interfaces.categories.resource.VlmProviderObject;
|
||||
import com.linbit.linstor.storage.kinds.DeviceLayerKind;
|
||||
import com.linbit.linstor.storage.kinds.DeviceProviderKind;
|
||||
@@ -88,6 +89,7 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
+import java.util.TreeSet;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
@@ -587,8 +589,9 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL
|
||||
|
||||
/*
|
||||
* We also have to remove the currently diskless DrbdRscData and free up the node-id as now we must
|
||||
- * use the shared resource's node-id
|
||||
+ * use the shared resource's node-id. We still need to preserve TCP ports though.
|
||||
*/
|
||||
+ copyDrbdTcpPortsIfExists(rsc, payload);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -726,7 +729,7 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL
|
||||
/**
|
||||
* Although we need to rebuild the layerData as the layerList might have changed, if we do not
|
||||
* deactivate (i.e. down) the current resource, we need to make sure that deleting DrbdRscData
|
||||
- * and recreating a new DrbdRscData ends up with the same node-id as before.
|
||||
+ * and recreating a new DrbdRscData ends up with the same node-id and TCP ports as before.
|
||||
*/
|
||||
private void copyDrbdNodeIdIfExists(Resource rsc, LayerPayload payload) throws ImplementationError
|
||||
{
|
||||
@@ -743,6 +746,37 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL
|
||||
DrbdRscData<Resource> drbdRscData = (DrbdRscData<Resource>) drbdRscDataSet.iterator().next();
|
||||
payload.drbdRsc.nodeId = drbdRscData.getNodeId().value;
|
||||
}
|
||||
+ copyDrbdTcpPortsIfExists(rsc, payload);
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * Preserves existing TCP ports during toggle-disk operations.
|
||||
+ *
|
||||
+ * When removeLayerData() deletes DrbdRscData, the TCP ports are freed from the number pool.
|
||||
+ * If ensureStackDataExists() then allocates different ports, and the satellite misses the update
|
||||
+ * (e.g. due to controller restart or connectivity issues), the satellite keeps the old ports
|
||||
+ * while peers get the new ones, causing DRBD connections to fail with StandAlone state.
|
||||
+ */
|
||||
+ private void copyDrbdTcpPortsIfExists(Resource rsc, LayerPayload payload) throws ImplementationError
|
||||
+ {
|
||||
+ Set<AbsRscLayerObject<Resource>> drbdRscDataSet = LayerRscUtils.getRscDataByLayer(
|
||||
+ getLayerData(apiCtx, rsc),
|
||||
+ DeviceLayerKind.DRBD
|
||||
+ );
|
||||
+ if (!drbdRscDataSet.isEmpty())
|
||||
+ {
|
||||
+ DrbdRscData<Resource> drbdRscData = (DrbdRscData<Resource>) drbdRscDataSet.iterator().next();
|
||||
+ Collection<TcpPortNumber> tcpPorts = drbdRscData.getTcpPortList();
|
||||
+ if (tcpPorts != null && !tcpPorts.isEmpty())
|
||||
+ {
|
||||
+ Set<Integer> portInts = new TreeSet<>();
|
||||
+ for (TcpPortNumber port : tcpPorts)
|
||||
+ {
|
||||
+ portInts.add(port.value);
|
||||
+ }
|
||||
+ payload.drbdRsc.tcpPorts = portInts;
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
|
||||
private List<DeviceLayerKind> removeLayerData(Resource rscRef)
|
||||
diff --git a/controller/src/main/java/com/linbit/linstor/layer/resource/CtrlRscLayerDataFactory.java b/controller/src/main/java/com/linbit/linstor/layer/resource/CtrlRscLayerDataFactory.java
|
||||
index 3538b380c..4f589145e 100644
|
||||
--- a/controller/src/main/java/com/linbit/linstor/layer/resource/CtrlRscLayerDataFactory.java
|
||||
+++ b/controller/src/main/java/com/linbit/linstor/layer/resource/CtrlRscLayerDataFactory.java
|
||||
@@ -276,8 +276,6 @@ public class CtrlRscLayerDataFactory
|
||||
|
||||
|
||||
rscDataToProcess.addAll(rscData.getChildren());
|
||||
}
|
||||
-
|
||||
|
|
@ -82,6 +133,6 @@ index 3538b380c..4f589145e 100644
|
|||
}
|
||||
catch (AccessDeniedException exc)
|
||||
{
|
||||
--
|
||||
--
|
||||
2.39.5 (Apple Git-154)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ spec:
|
|||
relabelConfigs:
|
||||
- action: labeldrop
|
||||
regex: (endpoint|namespace|pod|container)
|
||||
- replacement: linstor-controller
|
||||
- replacement: linstor-satellite
|
||||
targetLabel: job
|
||||
- sourceLabels: [__meta_kubernetes_pod_node_name]
|
||||
targetLabel: node
|
||||
|
|
@ -34,7 +34,7 @@ spec:
|
|||
relabelConfigs:
|
||||
- action: labeldrop
|
||||
regex: (endpoint|namespace|pod|container)
|
||||
- replacement: linstor-satellite
|
||||
- replacement: linstor-controller
|
||||
targetLabel: job
|
||||
- sourceLabels: [__meta_kubernetes_pod_node_name]
|
||||
targetLabel: controller_node
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
piraeusServer:
|
||||
image:
|
||||
repository: ghcr.io/cozystack/cozystack/piraeus-server
|
||||
tag: 1.32.3@sha256:aa97f39d90c0726b587f0a376504f13d1f308adeb42db7d98cec9ac7de237361
|
||||
tag: 1.32.3@sha256:6d2b12f4bb0641f997b96b93f9c9d29efeb790fb1f179e2856b85ab992235bf8
|
||||
# Talos-specific workarounds (disable for generic Linux like Ubuntu/Debian)
|
||||
talos:
|
||||
enabled: true
|
||||
|
|
@ -13,4 +13,4 @@ linstor:
|
|||
linstorCSI:
|
||||
image:
|
||||
repository: ghcr.io/cozystack/cozystack/linstor-csi
|
||||
tag: v1.10.5@sha256:50ab1ab0210d4e7ebfca311f445bb764516db5ddb63fc6d28536b28622eee753
|
||||
tag: v1.10.5@sha256:651d67f0f2123c4ef5cb15c6be9f9c3c759549d3304467e492a01588cf8bd50e
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@ metadata:
|
|||
app: alerta
|
||||
annotations:
|
||||
{{- if eq $solver "http01" }}
|
||||
acme.cert-manager.io/http01-ingress-class: {{ $ingress }}
|
||||
acme.cert-manager.io/http01-ingress-ingressclassname: {{ $ingress }}
|
||||
{{- end }}
|
||||
cert-manager.io/cluster-issuer: {{ $clusterIssuer }}
|
||||
spec:
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ spec:
|
|||
metadata:
|
||||
annotations:
|
||||
{{- if eq $solver "http01" }}
|
||||
acme.cert-manager.io/http01-ingress-class: "{{ $ingress }}"
|
||||
acme.cert-manager.io/http01-ingress-ingressclassname: "{{ $ingress }}"
|
||||
{{- end }}
|
||||
cert-manager.io/cluster-issuer: {{ $clusterIssuer }}
|
||||
spec:
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
objectstorage:
|
||||
controller:
|
||||
image: "ghcr.io/cozystack/cozystack/objectstorage-controller:v1.1.0@sha256:e40e94f3014cfd04cce4230597315a1acfcca2daa8051b987614d0c05da6d928"
|
||||
image: "ghcr.io/cozystack/cozystack/objectstorage-controller:v1.1.7@sha256:080b89a5bbee971180f9e2ad16672795d990751f7953ebfd83042a32acb57a35"
|
||||
|
|
|
|||
|
|
@ -7,19 +7,31 @@ spec:
|
|||
groups:
|
||||
- name: linstor.rules
|
||||
rules:
|
||||
- alert: linstorControllerOffline
|
||||
- alert: linstorControllerUnavailable
|
||||
annotations:
|
||||
description: |
|
||||
LINSTOR Controller is not reachable.
|
||||
expr: up{job="linstor-controller"} == 0
|
||||
LINSTOR Controller deployment has no available replicas.
|
||||
expr: kube_deployment_status_replicas_available{namespace="cozy-linstor",deployment="linstor-controller"} < 1
|
||||
for: 3m
|
||||
labels:
|
||||
severity: critical
|
||||
- alert: linstorControllerMetricsScrapeFailing
|
||||
annotations:
|
||||
description: |
|
||||
LINSTOR Controller metrics endpoint is not being scraped successfully.
|
||||
expr: up{job="linstor-controller"} == 0
|
||||
for: 10m
|
||||
labels:
|
||||
severity: warning
|
||||
- alert: linstorSatelliteErrorRate
|
||||
annotations:
|
||||
description: |
|
||||
LINSTOR Satellite "{{ $labels.name }}" reports {{ $value }} errors in the last 15 minutes.
|
||||
Use "linstor error-reports list --nodes {{ $labels.name }} --since 15minutes" to see them.
|
||||
expr: increase(linstor_error_reports_count{module="SATELLITE"}[15m]) > 0
|
||||
LINSTOR Satellite "{{ $labels.hostname }}" reports {{ $value }} errors in the last 15 minutes.
|
||||
Use "linstor error-reports list --nodes {{ $labels.hostname }}" to inspect the reports.
|
||||
expr: |
|
||||
increase(linstor_error_reports_count{module="SATELLITE"}[15m]) > 0
|
||||
and on(instance, job)
|
||||
min_over_time(up{job="linstor-controller"}[15m]) == 1
|
||||
labels:
|
||||
severity: warning
|
||||
- alert: linstorControllerErrorRate
|
||||
|
|
@ -33,7 +45,7 @@ spec:
|
|||
- alert: linstorSatelliteNotOnline
|
||||
annotations:
|
||||
description: |
|
||||
LINSTOR Satellite "{{ $labels.name }}" is not ONLINE.
|
||||
LINSTOR Satellite "{{ $labels.hostname }}" is not ONLINE.
|
||||
Check that the Satellite is running and reachable from the LINSTOR Controller.
|
||||
expr: linstor_node_state{nodetype="SATELLITE"} != 2
|
||||
labels:
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ seaweedfs:
|
|||
nginx.ingress.kubernetes.io/client-body-timeout: "3600"
|
||||
nginx.ingress.kubernetes.io/client-header-timeout: "120"
|
||||
nginx.ingress.kubernetes.io/service-upstream: "true"
|
||||
acme.cert-manager.io/http01-ingress-class: tenant-root
|
||||
acme.cert-manager.io/http01-ingress-ingressclassname: tenant-root
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
tls:
|
||||
- hosts:
|
||||
|
|
@ -177,7 +177,7 @@ seaweedfs:
|
|||
bucketClassName: "seaweedfs"
|
||||
region: ""
|
||||
sidecar:
|
||||
image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.1.0@sha256:2a3595cd88b30af55b2000d3ca204899beecef0012b0e0402754c3914aad1f7f"
|
||||
image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.1.7@sha256:fd3571e746efbc65fab342ca557beb2d0ad46fc0183772605fe84c6d3dd446a4"
|
||||
certificates:
|
||||
commonName: "SeaweedFS CA"
|
||||
ipAddresses: []
|
||||
|
|
|
|||
|
|
@ -224,8 +224,8 @@ func buildPostProcessV3(kindSchemas map[string]string) func(*spec3.OpenAPI) (*sp
|
|||
base, ok1 := doc.Components.Schemas[baseRef]
|
||||
list, ok2 := doc.Components.Schemas[baseListRef]
|
||||
stat, ok3 := doc.Components.Schemas[baseStatusRef]
|
||||
if !(ok1 && ok2 && ok3) && len(kindSchemas) > 0 {
|
||||
return doc, fmt.Errorf("base Application* schemas not found")
|
||||
if !(ok1 && ok2 && ok3) {
|
||||
return doc, nil // not the apps GV — nothing to patch
|
||||
}
|
||||
|
||||
// Clone base schemas for each kind
|
||||
|
|
@ -339,8 +339,8 @@ func buildPostProcessV2(kindSchemas map[string]string) func(*spec.Swagger) (*spe
|
|||
base, ok1 := defs[baseRef]
|
||||
list, ok2 := defs[baseListRef]
|
||||
stat, ok3 := defs[baseStatusRef]
|
||||
if !(ok1 && ok2 && ok3) && len(kindSchemas) > 0 {
|
||||
return sw, fmt.Errorf("base Application* schemas not found")
|
||||
if !(ok1 && ok2 && ok3) {
|
||||
return sw, nil // not the apps GV — nothing to patch
|
||||
}
|
||||
|
||||
for kind, raw := range kindSchemas {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue