diff --git a/secrets b/secrets index 6b18cfe..e4b86d3 100755 --- a/secrets +++ b/secrets @@ -2138,47 +2138,40 @@ _migrate_project() { info "Store is already format v2 — nothing to migrate." return 0 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 pdir="$SECRETS_DIR/$project" - local moved=0 already=0 would=0 etype epath slug old new - while IFS=$'\t' read -r etype epath _; do - [ -n "$etype" ] || continue - # Only `properties` blobs change name in v2; dotenv and `file` are already - # in their v2 shape and never move. - [ "$etype" = "gradle-properties" ] || continue - slug=$(_secrets_files_slug "$epath") - old="$pdir/external/$slug.gradle-properties.age" - new="$pdir/external/$slug.properties.age" - [ -f "$old" ] || continue # nothing pushed yet (or already dropped) - if [ -f "$new" ]; then # idempotent: twin already exists + # Source of truth for the copy-forward is the STORE, not a project manifest. + # Every v1 properties blob is a `*.gradle-properties.age` file whose v2 twin + # is the same name with the `.properties.age` suffix (the only on-disk change + # v2 makes). Enumerating the store — exactly as `_migrate_finalize` does — + # means migrate twins precisely the blobs finalize will demand twins for, with + # no manifest dependency. This is why a legacy `.secrets-files`-only project + # (no `.secrets.json` yet) migrates cleanly instead of dead-ending, and why a + # store blob the manifest no longer declares still gets a twin. + local moved=0 already=0 would=0 f new + while IFS= read -r f; do + [ -f "$f" ] || continue + new="${f%.gradle-properties.age}.properties.age" + if [ -f "$new" ]; then already=$((already + 1)) continue fi 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)) else - cp "$old" "$new" + cp "$f" "$new" moved=$((moved + 1)) fi - done < <(_json_external_entries "$manifest") + done < <(find "$pdir/external" -type f -name '*.gradle-properties.age' 2>/dev/null) 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'." return 0 fi 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 fi ensure_store_protections diff --git a/test/migrate.bats b/test/migrate.bats index 84f7ec9..cd7dd30 100644 --- a/test/migrate.bats +++ b/test/migrate.bats @@ -123,12 +123,29 @@ m_file_src() { mkdir -p "$HOME/keystores"; printf 'KS\x00\x01\x02\xffDATA\n' > [ "$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 local dir="$WORK_DIR/nomanifest"; mkdir -p "$dir"; cd "$dir" run "$SECRETS_BIN" migrate - [ "$status" -eq 1 ] - [[ "$output" == *".secrets.json"* ]] || false + [ "$status" -eq 0 ] + [[ "$output" == *"no v1 properties blobs"* ]] || false } @test "migrate on an already-v2 store is a no-op" {