fix: migrate copy-forward is manifest-free, no dead-end on legacy projects (EGB-710)

This commit is contained in:
Brian Majewski 2026-06-08 06:58:22 -07:00
parent 679ddbc1b7
commit c121982dcd
2 changed files with 37 additions and 27 deletions

41
secrets
View file

@ -2138,47 +2138,40 @@ _migrate_project() {
info "Store is already format v2 — nothing to migrate." info "Store is already format v2 — nothing to migrate."
return 0 return 0
fi fi
local manifest="$PWD/$SECRETS_JSON_NAME"
if [ ! -e "$manifest" ]; then
die "No $SECRETS_JSON_NAME in $PWD.
'secrets migrate' copy-forwards a project's v1 blobs to their v2 names and
reads the project manifest to do so. cd into a project that has a manifest,
then run 'secrets migrate'. (Store-wide 'secrets migrate --finalize' comes
after every project is migrated.)"
fi
_check_manifest_file "$manifest"
local project; project=$(derive_project_name "") local project; project=$(derive_project_name "")
local pdir="$SECRETS_DIR/$project" local pdir="$SECRETS_DIR/$project"
local moved=0 already=0 would=0 etype epath slug old new # Source of truth for the copy-forward is the STORE, not a project manifest.
while IFS=$'\t' read -r etype epath _; do # Every v1 properties blob is a `*.gradle-properties.age` file whose v2 twin
[ -n "$etype" ] || continue # is the same name with the `.properties.age` suffix (the only on-disk change
# Only `properties` blobs change name in v2; dotenv and `file` are already # v2 makes). Enumerating the store — exactly as `_migrate_finalize` does —
# in their v2 shape and never move. # means migrate twins precisely the blobs finalize will demand twins for, with
[ "$etype" = "gradle-properties" ] || continue # no manifest dependency. This is why a legacy `.secrets-files`-only project
slug=$(_secrets_files_slug "$epath") # (no `.secrets.json` yet) migrates cleanly instead of dead-ending, and why a
old="$pdir/external/$slug.gradle-properties.age" # store blob the manifest no longer declares still gets a twin.
new="$pdir/external/$slug.properties.age" local moved=0 already=0 would=0 f new
[ -f "$old" ] || continue # nothing pushed yet (or already dropped) while IFS= read -r f; do
if [ -f "$new" ]; then # idempotent: twin already exists [ -f "$f" ] || continue
new="${f%.gradle-properties.age}.properties.age"
if [ -f "$new" ]; then
already=$((already + 1)) already=$((already + 1))
continue continue
fi fi
if [ "$dry_run" = true ]; then if [ "$dry_run" = true ]; then
echo "would migrate: $project/external/$slug.gradle-properties.age -> $slug.properties.age" echo "would migrate: ${f#"$SECRETS_DIR"/} -> $(basename "$new")"
would=$((would + 1)) would=$((would + 1))
else else
cp "$old" "$new" cp "$f" "$new"
moved=$((moved + 1)) moved=$((moved + 1))
fi fi
done < <(_json_external_entries "$manifest") done < <(find "$pdir/external" -type f -name '*.gradle-properties.age' 2>/dev/null)
if [ "$dry_run" = true ]; then if [ "$dry_run" = true ]; then
echo "migrate --dry-run: $would blob(s) would be copy-forwarded for '$project' (writes nothing); $already already present. v1 blobs are kept until 'secrets migrate --finalize'." echo "migrate --dry-run: $would blob(s) would be copy-forwarded for '$project' (writes nothing); $already already present. v1 blobs are kept until 'secrets migrate --finalize'."
return 0 return 0
fi fi
if [ "$moved" -eq 0 ] && [ "$already" -eq 0 ]; then if [ "$moved" -eq 0 ] && [ "$already" -eq 0 ]; then
info "Nothing to migrate for '$project' (no v1 properties blobs)." info "Nothing to migrate for '$project' — no v1 properties blobs in the store (already v2-shaped). If you expected one, run 'secrets push' first, then re-run 'secrets migrate'."
return 0 return 0
fi fi
ensure_store_protections ensure_store_protections

View file

@ -123,12 +123,29 @@ m_file_src() { mkdir -p "$HOME/keystores"; printf 'KS\x00\x01\x02\xffDATA\n' >
[ "$output" = "1" ] [ "$output" = "1" ]
} }
@test "migrate with no manifest in cwd dies with a directed message" { @test "migrate copy-forwards a v1 properties blob with no .secrets.json (manifest-free)" {
# The EGB-710 repro: a legacy project has a v1 properties blob in the store
# but no .secrets.json (it predates the manifest). migrate must NOT dead-end.
make_v1_store
m_gradle_src $'beaconClerkPkTest=pk_test_abc\n'
create_project_dir nomanifestblob
printf 'gradle-properties ~/.gradle/gradle.properties beaconClerkPkTest\n' > .secrets-files
"$SECRETS_BIN" push nomanifestblob >/dev/null 2>&1
rm -f .secrets.json # simulate a pre-manifest project
run "$SECRETS_BIN" migrate
[ "$status" -eq 0 ]
run bash -c "ls $SECRETS_DIR/nomanifestblob/external/*.properties.age"
[ "$status" -eq 0 ] # v2 twin written despite no manifest
run bash -c "ls $SECRETS_DIR/nomanifestblob/external/*.gradle-properties.age"
[ "$status" -eq 0 ] # v1 kept (non-destructive)
}
@test "migrate in a project with no manifest and no store blobs is a clean no-op" {
make_v1_store make_v1_store
local dir="$WORK_DIR/nomanifest"; mkdir -p "$dir"; cd "$dir" local dir="$WORK_DIR/nomanifest"; mkdir -p "$dir"; cd "$dir"
run "$SECRETS_BIN" migrate run "$SECRETS_BIN" migrate
[ "$status" -eq 1 ] [ "$status" -eq 0 ]
[[ "$output" == *".secrets.json"* ]] || false [[ "$output" == *"no v1 properties blobs"* ]] || false
} }
@test "migrate on an already-v2 store is a no-op" { @test "migrate on an already-v2 store is a no-op" {