Compare commits

...
Sign in to create a new pull request.

1 commit

Author SHA1 Message Date
Myasnikov Daniil
397612c14b
[platform] Added dev-overlay for building dev ocirepos
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-03-29 21:59:35 +05:00
3 changed files with 304 additions and 0 deletions

65
hack/dev-overlay.md Normal file
View file

@ -0,0 +1,65 @@
# Dev Overlay
Patch the cluster's platform OCI artifact with your local changes without rebuilding or uploading the entire packages tree.
## Why not `make image-packages`?
`make image-packages` (from `packages/core/installer/Makefile`) pushes the **entire** `packages/` directory from your current working tree as a new OCI artifact. This has two problems:
1. **Image version leakage.** Your working tree inherits image versions from the branch you're on (e.g. `cozystack-controller:v1.1.0` on main), which differ from the pinned versions in the cluster's release (e.g. `v1.1.4`). Uploading the whole tree overwrites them all.
2. **Single-branch only.** If you develop features across multiple branches (branch A changes component A, branch B changes component B), you can only upload from one branch at a time. The old workaround was to create a "frankenstein" branch by merging all feature branches onto a release tag — manual and error-prone.
## How dev-overlay works
Instead of replacing the entire artifact, dev-overlay **patches** it with only the files you changed:
1. Pulls the existing overlay from the registry (or the cluster's base OCI on first run)
2. Runs `git diff origin/main -- packages/` against your working tree to find changed files
3. Copies only those files into the artifact, deletes removed files, cleans up renames
4. Pushes the patched artifact and points the operator at its digest
Because the diff is against `origin/main` (or a ref you choose), files you didn't touch — including values with pinned image versions — are never overwritten. The cluster keeps its release versions.
## Accumulation across branches
Changes from multiple branches stack on top of each other:
```
# On branch A (changes component A)
make dev-overlay
# Switch to branch B (changes component B)
make dev-overlay
```
After both runs, the overlay contains changes from both A and B. Files that only A touched are preserved when B runs (B's diff doesn't mention them). If both branches modify the same file, the last applied version wins.
## Usage
```bash
cd packages/core/installer
# Preview what would be applied
make dev-overlay-diff
# Apply changes (defaults to diffing against origin/main)
make dev-overlay
# Override the diff base
make dev-overlay DEV_BASE_TAG=some-commit-hash
```
## Resetting
To discard all overlay changes, delete the overlay tag from the registry and restore the operator to the original platform source. There is no automated undo — just point the operator back at the base artifact.
## How DEV_BASE_TAG affects correctness
`DEV_BASE_TAG` (default: `origin/main`) is the git ref your changes are diffed against. For image versions to be preserved correctly, it must be a ref where inherited values match your working tree:
- Your branch has `image: v1.1.0` (inherited from main)
- `origin/main` also has `image: v1.1.0`
- No diff detected — cluster keeps its `v1.1.4`
If you rebase onto a newer main that bumped a version, `origin/main` tracks that automatically. If you need a fixed point, pass a specific commit hash.

216
hack/dev-overlay.sh Executable file
View file

@ -0,0 +1,216 @@
#!/bin/bash
set -eu
# Dev overlay: overlay working-tree changes onto a deployed OCI artifact.
#
# Usage: dev-overlay.sh <diff|apply>
#
# Required env vars:
# REGISTRY OCI registry to push to
# Optional env vars:
# DEV_BASE_TAG git ref to diff against (default: origin/main)
# DEV_OVERLAY_TAG tag for the overlay artifact (default: dev-overlay)
# OPERATOR_DEPLOY operator Deployment name (default: cozystack-operator)
# OPERATOR_CONTAINER container name in the Deployment (default: $OPERATOR_DEPLOY)
# PLATFORM_OCIREPO OCIRepository resource name (default: cozystack-platform)
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(git -C "$SCRIPT_DIR" rev-parse --show-toplevel)"
DEV_BASE_TAG="${DEV_BASE_TAG:-origin/main}"
DEV_OVERLAY_TAG="${DEV_OVERLAY_TAG:-dev-overlay}"
OPERATOR_DEPLOY="${OPERATOR_DEPLOY:-cozystack-operator}"
OPERATOR_CONTAINER="${OPERATOR_CONTAINER:-$OPERATOR_DEPLOY}"
PLATFORM_OCIREPO="${PLATFORM_OCIREPO:-cozystack-platform}"
cluster_oci_url() {
kubectl get ocirepository "$PLATFORM_OCIREPO" -n cozy-system \
-o jsonpath='{.spec.url}' 2>/dev/null || true
}
resolve_cluster_pull_ref() {
local url="$1"
local tag digest
tag=$(kubectl get ocirepository "$PLATFORM_OCIREPO" -n cozy-system \
-o jsonpath='{.spec.ref.tag}' 2>/dev/null || true)
digest=$(kubectl get ocirepository "$PLATFORM_OCIREPO" -n cozy-system \
-o jsonpath='{.spec.ref.digest}' 2>/dev/null || true)
if [ -n "$tag" ]; then
echo "${url}:${tag}"
elif [ -n "$digest" ]; then
echo "${url}@${digest}"
fi
}
pull_or_init_workdir() {
local workdir="$1"
local oci_url="$2"
if flux pull artifact "oci://${REGISTRY}/cozystack-packages:${DEV_OVERLAY_TAG}" \
--output "$workdir" 2>/dev/null; then
echo "Existing overlay found, accumulating on top"
return
fi
local base_pull
base_pull=$(resolve_cluster_pull_ref "$oci_url")
if [ -z "$base_pull" ]; then
echo "ERROR: cluster OCIRepository has no tag or digest ref" >&2
rm -rf "$workdir"
exit 1
fi
echo "No overlay yet, pulling base from cluster: $base_pull"
flux pull artifact "$base_pull" --output "$workdir"
}
apply_branch_changes() {
local workdir="$1"
local f rel
# Copy changed/added/modified files from working tree
while IFS= read -r f; do
[ -z "$f" ] && continue
rel="${f#packages/}"
mkdir -p "$workdir/$(dirname "$rel")"
cp -r "$REPO_ROOT/$f" "$workdir/$rel"
done < <(git -C "$REPO_ROOT" diff --name-only --diff-filter=ACMR \
"${DEV_BASE_TAG}" -- packages/)
# Copy untracked (new) files not yet known to git
while IFS= read -r f; do
[ -z "$f" ] && continue
rel="${f#packages/}"
mkdir -p "$workdir/$(dirname "$rel")"
cp -r "$REPO_ROOT/$f" "$workdir/$rel"
done < <(git -C "$REPO_ROOT" ls-files --others --exclude-standard -- packages/)
# Delete files removed relative to DEV_BASE_TAG
while IFS= read -r f; do
[ -z "$f" ] && continue
rel="${f#packages/}"
rm -rf "$workdir/$rel"
done < <(git -C "$REPO_ROOT" diff --name-only --diff-filter=D \
"${DEV_BASE_TAG}" -- packages/)
# Delete old paths of renamed files to prevent duplicate Helm resources
while IFS=$'\t' read -r _ old_path _; do
[ -z "$old_path" ] && continue
rel="${old_path#packages/}"
rm -rf "$workdir/$rel"
done < <(git -C "$REPO_ROOT" diff --diff-filter=R --name-status \
"${DEV_BASE_TAG}" -- packages/)
}
push_artifact() {
local workdir="$1"
local logfile
logfile=$(mktemp)
trap 'rm -f "$logfile"' RETURN
flux push artifact \
"oci://${REGISTRY}/cozystack-packages:${DEV_OVERLAY_TAG}" \
--path="$workdir" \
--source=dev-overlay \
--revision="dev:$(git -C "$REPO_ROOT" rev-parse HEAD)" \
2>&1 | tee "$logfile" >&2
local digest
digest=$(awk -F @ '/artifact successfully pushed/ {print $2}' "$logfile")
if [ -z "$digest" ]; then
echo "ERROR: could not parse digest from push output" >&2
exit 1
fi
echo "$digest"
}
patch_operator_ref() {
local ref="$1"
local url="$2"
local jq_filter
jq_filter=$(cat <<'JQ'
.spec.template.spec.containers |= map(
if .name == $container then
.args |= map(
if startswith("--platform-source-url=") then "--platform-source-url=" + $url
elif startswith("--platform-source-ref=") then "--platform-source-ref=" + $ref
else . end)
else . end)
JQ
)
kubectl get deploy "$OPERATOR_DEPLOY" -n cozy-system -o json \
| jq --arg ref "$ref" \
--arg url "$url" \
--arg container "$OPERATOR_CONTAINER" \
"$jq_filter" \
| kubectl apply -f -
}
cmd_diff() {
local oci_url base_pull
oci_url=$(cluster_oci_url)
base_pull=$(resolve_cluster_pull_ref "$oci_url")
: "${base_pull:=${oci_url}:${DEV_BASE_TAG}}"
echo "Base: ${DEV_BASE_TAG} Branch: $(git -C "$REPO_ROOT" rev-parse --abbrev-ref HEAD)"
echo "Pull from: $base_pull"
echo "Push to: oci://${REGISTRY}/cozystack-packages:${DEV_OVERLAY_TAG}"
local probe
probe=$(mktemp -d)
if flux pull artifact "oci://${REGISTRY}/cozystack-packages:${DEV_OVERLAY_TAG}" \
--output "$probe" 2>/dev/null; then
rm -rf "$probe"
echo "Existing overlay found, changes will be layered on top"
else
rm -rf "$probe"
echo "No overlay exists yet, $base_pull will be used as starting point"
fi
echo ""
echo "=== Changed files (git diff) ==="
git -C "$REPO_ROOT" diff --stat "${DEV_BASE_TAG}" -- packages/
local untracked
untracked=$(git -C "$REPO_ROOT" ls-files --others --exclude-standard -- packages/)
if [ -n "$untracked" ]; then
echo ""
echo "=== Untracked new files ==="
echo "$untracked"
fi
echo ""
git -C "$REPO_ROOT" diff "${DEV_BASE_TAG}" -- packages/
}
cmd_apply() {
local oci_url
oci_url=$(cluster_oci_url)
local workdir
workdir=$(mktemp -d)
trap "rm -rf '$workdir'" EXIT
pull_or_init_workdir "$workdir" "$oci_url"
apply_branch_changes "$workdir"
local digest
digest=$(push_artifact "$workdir")
echo "Updating Deployment ${OPERATOR_DEPLOY} url=oci://${REGISTRY}/cozystack-packages ref=digest=${digest}"
patch_operator_ref "digest=${digest}" "oci://${REGISTRY}/cozystack-packages"
echo "Operator restarting — watch: kubectl rollout status deploy/${OPERATOR_DEPLOY} -n cozy-system"
}
case "${1:-}" in
diff) cmd_diff ;;
apply) cmd_apply ;;
*)
echo "Usage: $0 <diff|apply>" >&2
exit 1
;;
esac

View file

@ -50,3 +50,26 @@ chart:
PKG=$$(helm package . --version $(COZYSTACK_VERSION) | awk '{print $$NF}'); \
trap 'rm -f "$$PKG"' EXIT; \
if [ "$(PUSH)" = "1" ]; then helm push "$$PKG" oci://$(REGISTRY); fi
# === Dev overlay targets ===
# See hack/dev-overlay.sh for full documentation and implementation.
DEV_BASE_TAG ?= origin/main
DEV_OVERLAY_TAG ?= dev-overlay
OPERATOR_DEPLOY ?= cozystack-operator
OPERATOR_CONTAINER ?= $(OPERATOR_DEPLOY)
PLATFORM_OCIREPO ?= cozystack-platform
DEV_OVERLAY_ENV = \
REGISTRY="$(REGISTRY)" \
DEV_BASE_TAG="$(DEV_BASE_TAG)" \
DEV_OVERLAY_TAG="$(DEV_OVERLAY_TAG)" \
OPERATOR_DEPLOY="$(OPERATOR_DEPLOY)" \
OPERATOR_CONTAINER="$(OPERATOR_CONTAINER)" \
PLATFORM_OCIREPO="$(PLATFORM_OCIREPO)"
dev-overlay-diff: ## Show what files would be added/removed by dev-overlay (dry run, no changes)
@$(DEV_OVERLAY_ENV) ../../../hack/dev-overlay.sh diff
dev-overlay: ## Overlay current branch's changes onto deployed artifact, update operator ref
@$(DEV_OVERLAY_ENV) ../../../hack/dev-overlay.sh apply