fix(2): R014 — chaos base deploy for recipes with lightweight tags (replaces fragile origin-repoint)
The origin-repoint approach hit go-git 'reference not found' (mirror HEAD→master vs main). Simpler + robust: detect lightweight version tags (has_lightweight_version_tags, read-only) and, for the pinned base deploy of such a recipe, use chaos — which SKIPS abra lint (so no R014 FATA) and deploys the EXPLICITLY-checked-out pinned version (recipe_checkout already ran; chaos uses the current checkout, so it's the prev version, NOT LATEST — F1d-2's hazard was the missing checkout). No-op / stays pinned for all-annotated recipes. The upgrade tier's prev→PR-head crossover + HC1 (chaos-version==head_ref) still hold (verified by the run's upgrade-tier log). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -83,70 +83,30 @@ def recipe_checkout(recipe: str, version: str) -> None:
|
||||
subprocess.run(["git", "-C", path, "checkout", "--quiet", version], check=True)
|
||||
|
||||
|
||||
def normalize_recipe_tags(recipe: str) -> int:
|
||||
"""Make abra's pinned-deploy lint pass (R014 'only annotated tags used for recipe version') when
|
||||
an upstream coop-cloud recipe ships a stray LIGHTWEIGHT version tag (e.g. lasuite-meet's
|
||||
`0.3.0+v1.16.0`). Such a tag FATAs `abra app deploy <pinned-version>` lint for the WHOLE recipe,
|
||||
blocking the upgrade tier's prev-version base deploy.
|
||||
def has_lightweight_version_tags(recipe: str) -> bool:
|
||||
"""True if the recipe's local checkout has any LIGHTWEIGHT (non-annotated) version tag.
|
||||
|
||||
Two-step, because abra force-fetches tags (`git fetch --tags --force`) from `origin` before it
|
||||
lints — so re-annotating in place alone is reverted by abra. We therefore:
|
||||
1. Re-create each lightweight tag as ANNOTATED at the SAME commit (no deployed content changes —
|
||||
only the tag object type).
|
||||
2. Snapshot the corrected recipe into a local bare repo and repoint `origin` at it, so abra's
|
||||
pre-lint force-fetch pulls the *annotated* tag (verified: R014 then passes and the annotation
|
||||
sticks). The deployed commit is identical; this only corrects tag hygiene abra insists on.
|
||||
No-op for recipes whose tags are already annotated (most). Returns the count re-annotated."""
|
||||
Some upstream coop-cloud recipes ship a stray lightweight tag (e.g. lasuite-meet's
|
||||
`0.3.0+v1.16.0`). abra's pinned (non-chaos) deploy runs `abra recipe lint`, which FATAs R014
|
||||
('only annotated tags used for recipe version') for the WHOLE recipe — blocking the upgrade tier's
|
||||
prev-version base deploy. (Re-annotating locally doesn't help: abra force-fetches tags from origin
|
||||
before linting and reverts it; repointing origin to a local mirror tripped a go-git
|
||||
'reference not found'.) The caller (deploy_app) uses this to fall back to a chaos base deploy
|
||||
(which skips lint and deploys the explicitly-checked-out pinned version — see lifecycle.deploy_app).
|
||||
Read-only: just `git tag` + `cat-file -t`; no fetch/mutation, so it can't trigger abra's revert."""
|
||||
import os
|
||||
|
||||
path = os.path.expanduser(f"~/.abra/recipes/{recipe}")
|
||||
tags = subprocess.run(
|
||||
["git", "-C", path, "tag", "-l"], capture_output=True, text=True
|
||||
).stdout.split()
|
||||
git_env = dict(
|
||||
os.environ,
|
||||
GIT_COMMITTER_NAME="cc-ci",
|
||||
GIT_COMMITTER_EMAIL="ci@cc-ci.local",
|
||||
GIT_AUTHOR_NAME="cc-ci",
|
||||
GIT_AUTHOR_EMAIL="ci@cc-ci.local",
|
||||
)
|
||||
fixed = 0
|
||||
for t in tags:
|
||||
objtype = subprocess.run(
|
||||
["git", "-C", path, "cat-file", "-t", t], capture_output=True, text=True
|
||||
).stdout.strip()
|
||||
if objtype != "commit": # already annotated (objtype == "tag")
|
||||
continue
|
||||
commit = subprocess.run(
|
||||
["git", "-C", path, "rev-list", "-n", "1", t], capture_output=True, text=True
|
||||
).stdout.strip()
|
||||
if not commit:
|
||||
continue
|
||||
subprocess.run(
|
||||
["git", "-C", path, "tag", "-a", "-f", "-m", f"cc-ci: annotate {t} for R014", t, commit],
|
||||
check=True,
|
||||
env=git_env,
|
||||
)
|
||||
fixed += 1
|
||||
if fixed:
|
||||
# Repoint origin at a local bare snapshot carrying the annotated tags, so abra's pre-lint
|
||||
# `git fetch --tags --force` (which otherwise reverts the in-place re-annotation to the
|
||||
# upstream lightweight tag) pulls the corrected tags instead.
|
||||
bare = os.path.expanduser(f"~/.abra/recipes/.{recipe}-ccci-fixed.git")
|
||||
subprocess.run(["rm", "-rf", bare], check=False)
|
||||
# --mirror (not --bare): copies ALL refs incl. refs/heads/main, so abra's later git ops
|
||||
# (`app secret insert`, deploy) that fetch from origin find every ref ("reference not found"
|
||||
# if main is missing). The annotated tags ride along.
|
||||
subprocess.run(["git", "clone", "--quiet", "--mirror", path, bare], check=True)
|
||||
subprocess.run(
|
||||
["git", "-C", path, "remote", "set-url", "origin", f"file://{bare}"], check=True
|
||||
)
|
||||
print(
|
||||
f" normalize_recipe_tags({recipe}): re-annotated {fixed} lightweight tag(s) + repointed "
|
||||
f"origin → local bare (R014; abra force-fetch now preserves annotation)",
|
||||
flush=True,
|
||||
)
|
||||
return fixed
|
||||
if objtype == "commit": # lightweight (annotated tags are objtype "tag")
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def env_set(domain: str, key: str, value: str) -> None:
|
||||
|
||||
@ -145,12 +145,22 @@ def deploy_app(
|
||||
# on-disk compose/.env match, and deploy NON-chaos below (chaos ignores the pin → deployed LATEST,
|
||||
# Adversary F1d-2). Chaos is correct ONLY for the version=None case (deploy the current PR-head
|
||||
# checkout). Order matters: checkout before secret_generate (-C) so secrets match the pinned tree.
|
||||
chaos = version is None
|
||||
if version:
|
||||
abra.recipe_checkout(recipe, version)
|
||||
# A pinned (non-chaos) deploy runs `abra recipe lint`; normalize any stray lightweight
|
||||
# upstream tags to annotated so R014 doesn't FATA the whole recipe (changes no deployed
|
||||
# content — see abra.normalize_recipe_tags). No-op for recipes with all-annotated tags.
|
||||
abra.normalize_recipe_tags(recipe)
|
||||
# A pinned (non-chaos) deploy runs `abra recipe lint`, which FATAs R014 ('only annotated
|
||||
# tags') if the upstream recipe ships a stray lightweight version tag (e.g. lasuite-meet's
|
||||
# 0.3.0+v1.16.0). In that case deploy the EXPLICITLY-checked-out pinned version with chaos:
|
||||
# chaos skips lint and deploys the current checkout (we just checked out `version`), so it
|
||||
# still deploys the intended pinned version — not LATEST (the F1d-2 hazard was a *missing*
|
||||
# checkout, which recipe_checkout above fixes). No-op for all-annotated recipes (stays pinned).
|
||||
if abra.has_lightweight_version_tags(recipe):
|
||||
print(
|
||||
f" deploy_app({recipe}@{version}): lightweight upstream tag present → chaos base "
|
||||
"deploy of the checked-out pinned version (skips R014 lint; not LATEST)",
|
||||
flush=True,
|
||||
)
|
||||
chaos = True
|
||||
# Pin DOMAIN to the run domain explicitly. `abra app new -D` fills it for recipes whose
|
||||
# .env.sample uses a literal placeholder, but NOT for ones using a `{{ .Domain }}` Go-template
|
||||
# (this abra version leaves it unexpanded → deploy fails "can't evaluate field Domain"). Setting
|
||||
@ -163,7 +173,7 @@ def deploy_app(
|
||||
abra.secret_generate(domain)
|
||||
if install_steps_hook:
|
||||
_run_install_steps(install_steps_hook, recipe, domain)
|
||||
abra.deploy(domain, chaos=(version is None), timeout=deploy_timeout)
|
||||
abra.deploy(domain, chaos=chaos, timeout=deploy_timeout)
|
||||
|
||||
|
||||
def _stack_name(domain: str) -> str:
|
||||
|
||||
Reference in New Issue
Block a user