From c79d85f87ca8f751d713ba1989e306955c3a7699 Mon Sep 17 00:00:00 2001 From: ZdenekSrotyr Date: Fri, 10 Apr 2026 13:30:05 +0200 Subject: [PATCH] fix: config path mismatch + CalVer race condition (Devin review round 2) - _discover_and_register_tables reads from data_source.keboola.url (matches what /api/admin/configure writes) instead of top-level keboola.url which doesn't exist - CalVer: claim git tag BEFORE Docker build with retry loop (up to 5 attempts). Prevents race where two concurrent CI runs get same N. Git tag acts as a distributed lock for version uniqueness. 663 tests pass. --- .github/workflows/release.yml | 44 +++++++++++++++++++++-------------- app/api/admin.py | 6 +++-- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ac856bb..0784f83 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -42,32 +42,48 @@ jobs: fetch-depth: 0 fetch-tags: true - - name: Determine channel and version + - name: Claim version tag (with retry to avoid race conditions) id: meta run: | - YEAR_MONTH=$(date +%Y.%m) + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + YEAR_MONTH=$(date +%Y.%m) if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then CHANNEL="stable" else CHANNEL="dev" fi - - # Count existing tags GLOBALLY across all channels for this month - # (spec requires unique N per month: dev-2026.04.1 and stable-2026.04.2, never both .1) - EXISTING=$(git tag -l "*-${YEAR_MONTH}.*" | wc -l | tr -d ' ') - N=$((EXISTING + 1)) - VERSION="${YEAR_MONTH}.${N}" SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7) + # Claim a unique version by pushing a git tag BEFORE building. + # Retry up to 5 times if another CI run took our N. + for ATTEMPT in 1 2 3 4 5; do + git fetch --tags --force + EXISTING=$(git tag -l "*-${YEAR_MONTH}.*" | wc -l | tr -d ' ') + N=$((EXISTING + 1)) + VERSION="${YEAR_MONTH}.${N}" + TAG="${CHANNEL}-${VERSION}" + + git tag -a "$TAG" -m "Release $TAG" + if git push origin "$TAG" 2>/dev/null; then + echo "Claimed tag $TAG (attempt $ATTEMPT)" + break + else + echo "Tag $TAG already exists, retrying... (attempt $ATTEMPT)" + git tag -d "$TAG" + sleep 1 + fi + done + echo "channel=${CHANNEL}" >> "$GITHUB_OUTPUT" echo "version=${VERSION}" >> "$GITHUB_OUTPUT" - echo "versioned_tag=${CHANNEL}-${VERSION}" >> "$GITHUB_OUTPUT" + echo "versioned_tag=${TAG}" >> "$GITHUB_OUTPUT" echo "short_sha=${SHORT_SHA}" >> "$GITHUB_OUTPUT" echo "Channel: ${CHANNEL}" echo "Version: ${VERSION}" - echo "Versioned tag: ${CHANNEL}-${VERSION}" + echo "Versioned tag: ${TAG}" - name: Log in to GHCR uses: docker/login-action@v4 @@ -88,14 +104,6 @@ jobs: ghcr.io/${{ github.repository }}:${{ steps.meta.outputs.versioned_tag }} ghcr.io/${{ github.repository }}:sha-${{ steps.meta.outputs.short_sha }} - - name: Create git tag - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - TAG="${{ steps.meta.outputs.versioned_tag }}" - git tag -a "$TAG" -m "Release $TAG" - git push origin "$TAG" || echo "Tag $TAG already exists, skipping" - smoke-test: needs: build-and-push if: github.ref == 'refs/heads/main' diff --git a/app/api/admin.py b/app/api/admin.py index 08b4efe..36d2181 100644 --- a/app/api/admin.py +++ b/app/api/admin.py @@ -289,8 +289,10 @@ def _discover_and_register_tables(conn: duckdb.DuckDBPyConnection, user_email: s return {"registered": 0, "skipped": 0, "errors": 0, "tables": [], "source": source_type} from connectors.keboola.client import KeboolaClient - url = get_value("keboola", "url", default="") - token = os.environ.get(get_value("keboola", "token_env", default="KEBOOLA_STORAGE_TOKEN"), "") + # Read from data_source.keboola (matches what /api/admin/configure writes) + url = get_value("data_source", "keboola", "url", default="") + token_env = get_value("data_source", "keboola", "token_env", default="KEBOOLA_STORAGE_TOKEN") + token = os.environ.get(token_env, "") if token_env else "" if not token: token = os.environ.get("KEBOOLA_STORAGE_TOKEN", "")