carlos / platform Public

release: StageManifest creates the account dir (#58)

Paul Campbell 9105ad5ee7a1ae8df367bda05efbb4eec575c7ab parent 7a3d8c1
2 files changed, +39 −0
  • internal/release/versions.go +12 −0
  • internal/release/versions_test.go +27 −0
diff --git a/internal/release/versions.go b/internal/release/versions.go
index 1f6063750..ef244d415 100644
--- a/internal/release/versions.go
+++ b/internal/release/versions.go
@@ -184,6 +184,18 @@ func StageManifest(optDir, acct, ver, app string, raw []byte) error {
if acct != "" && !ValidAccount(acct) {
return fmt.Errorf("invalid account %q", acct)
}
+ // A version staged by a pre-capability binary has no <acct>/ subdirectory
+ // at all (issue #58): WriteFileAtomic's tmp-file create fails ENOENT the
+ // first time a sibling is back-filled for it under a real account. Create
+ // (and Chmod — umask masking, see StageArtifact's comment above) the
+ // parent dir first, exactly as StageArtifact does for the binary.
+ dir := filepath.Dir(StagedManifestPath(optDir, acct, ver, app))
+ if err := os.MkdirAll(dir, 0o755); err != nil {
+ return err
+ }
+ if err := os.Chmod(dir, 0o755); err != nil {
+ return err
+ }
return WriteFileAtomic(StagedManifestPath(optDir, acct, ver, app), raw, 0o644)
}
diff --git a/internal/release/versions_test.go b/internal/release/versions_test.go
index f67043cda..2599b78c1 100644
--- a/internal/release/versions_test.go
+++ b/internal/release/versions_test.go
@@ -204,6 +204,33 @@ func TestStageManifestRoundTripsCapabilities(t *testing.T) {
}
}
+// TestStageManifestCreatesTheAccountDir pins issue #58: a version staged by
+// a pre-capability binary has NO <acct>/ subdirectory at all (the bare
+// no-account layout StageArtifact(optDir, "", ...) produces — exactly what
+// the old binary wrote). Back-filling a sibling for that version under a
+// real account must not assume the directory already exists; StageManifest
+// has to create it itself, the way StageArtifact already does for the
+// binary, or the write fails ENOENT on every affected box, forever (seen
+// live, hourly, on both production boxes).
+func TestStageManifestCreatesTheAccountDir(t *testing.T) {
+ opt := t.TempDir()
+ // Pre-capability layout: versions/<ver>/<app>, no account level at all.
+ if err := StageArtifact(opt, "", "v1abc", "myapp", []byte("binary")); err != nil {
+ t.Fatalf("StageArtifact: %v", err)
+ }
+ if err := StageManifest(opt, "bad", "v1abc", "myapp", []byte("{}")); err != nil {
+ t.Fatalf("StageManifest: %v", err)
+ }
+ p := StagedManifestPath(opt, "bad", "v1abc", "myapp")
+ st, err := os.Stat(p)
+ if err != nil {
+ t.Fatalf("staged manifest: %v", err)
+ }
+ if st.Mode().Perm() != 0o644 {
+ t.Fatalf("staged manifest mode = %v, want 0644", st.Mode().Perm())
+ }
+}
+
// TestReadStagedManifestRunsTheValidationGate pins the Task-2-review
// backstop: a stale sibling written by a NEWER binary — one that understood
// a "v" this binary does not — must refuse cleanly here. This particular