Phases 5, 6, 8: OCI pull/push paths and admin REST API

Phase 5 (OCI pull): internal/oci/ package with manifest GET/HEAD by
tag/digest, blob GET/HEAD with repo membership check, tag listing with
OCI pagination, catalog listing. Multi-segment repo names via
parseOCIPath() right-split routing. DB query layer in
internal/db/repository.go.

Phase 6 (OCI push): blob uploads (monolithic and chunked) with
uploadManager tracking in-progress BlobWriters, manifest push
implementing full ARCHITECTURE.md §5 flow in a single SQLite
transaction (create repo, upsert manifest, populate manifest_blobs,
atomic tag move). Digest verification on both blob commit and manifest
push-by-digest.

Phase 8 (admin REST): /v1 endpoints for auth (login/logout/health),
repository management (list/detail/delete), policy CRUD with engine
reload, audit log listing with filters, GC trigger/status stubs.
RequireAdmin middleware, platform-standard error format.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-19 18:25:18 -07:00
parent f5e67bd4aa
commit dddc66f31b
40 changed files with 6832 additions and 7 deletions

278
internal/db/push_test.go Normal file
View File

@@ -0,0 +1,278 @@
package db
import "testing"
func TestGetOrCreateRepositoryNew(t *testing.T) {
d := openTestDB(t)
if err := d.Migrate(); err != nil {
t.Fatalf("Migrate: %v", err)
}
id, err := d.GetOrCreateRepository("newrepo")
if err != nil {
t.Fatalf("GetOrCreateRepository: %v", err)
}
if id <= 0 {
t.Fatalf("id: got %d, want > 0", id)
}
// Second call should return the same ID.
id2, err := d.GetOrCreateRepository("newrepo")
if err != nil {
t.Fatalf("GetOrCreateRepository (second): %v", err)
}
if id2 != id {
t.Fatalf("id mismatch: got %d, want %d", id2, id)
}
}
func TestGetOrCreateRepositoryExisting(t *testing.T) {
d := openTestDB(t)
if err := d.Migrate(); err != nil {
t.Fatalf("Migrate: %v", err)
}
_, err := d.Exec(`INSERT INTO repositories (name) VALUES ('existing')`)
if err != nil {
t.Fatalf("insert repo: %v", err)
}
id, err := d.GetOrCreateRepository("existing")
if err != nil {
t.Fatalf("GetOrCreateRepository: %v", err)
}
if id <= 0 {
t.Fatalf("id: got %d, want > 0", id)
}
}
func TestBlobExists(t *testing.T) {
d := openTestDB(t)
if err := d.Migrate(); err != nil {
t.Fatalf("Migrate: %v", err)
}
_, err := d.Exec(`INSERT INTO blobs (digest, size) VALUES ('sha256:aaa', 100)`)
if err != nil {
t.Fatalf("insert blob: %v", err)
}
exists, err := d.BlobExists("sha256:aaa")
if err != nil {
t.Fatalf("BlobExists: %v", err)
}
if !exists {
t.Fatal("expected blob to exist")
}
exists, err = d.BlobExists("sha256:nonexistent")
if err != nil {
t.Fatalf("BlobExists (nonexistent): %v", err)
}
if exists {
t.Fatal("expected blob to not exist")
}
}
func TestInsertBlob(t *testing.T) {
d := openTestDB(t)
if err := d.Migrate(); err != nil {
t.Fatalf("Migrate: %v", err)
}
if err := d.InsertBlob("sha256:bbb", 200); err != nil {
t.Fatalf("InsertBlob: %v", err)
}
exists, err := d.BlobExists("sha256:bbb")
if err != nil {
t.Fatalf("BlobExists: %v", err)
}
if !exists {
t.Fatal("expected blob to exist after insert")
}
// Insert again — should be a no-op (INSERT OR IGNORE).
if err := d.InsertBlob("sha256:bbb", 200); err != nil {
t.Fatalf("InsertBlob (dup): %v", err)
}
}
func TestPushManifestByTag(t *testing.T) {
d := openTestDB(t)
if err := d.Migrate(); err != nil {
t.Fatalf("Migrate: %v", err)
}
// Insert blobs first.
if err := d.InsertBlob("sha256:config111", 50); err != nil {
t.Fatalf("insert config blob: %v", err)
}
if err := d.InsertBlob("sha256:layer111", 1000); err != nil {
t.Fatalf("insert layer blob: %v", err)
}
content := []byte(`{"schemaVersion":2}`)
params := PushManifestParams{
RepoName: "myrepo",
Digest: "sha256:manifest111",
MediaType: "application/vnd.oci.image.manifest.v1+json",
Content: content,
Size: int64(len(content)),
Tag: "latest",
BlobDigests: []string{"sha256:config111", "sha256:layer111"},
}
if err := d.PushManifest(params); err != nil {
t.Fatalf("PushManifest: %v", err)
}
// Verify repository was created.
repoID, err := d.GetRepositoryByName("myrepo")
if err != nil {
t.Fatalf("GetRepositoryByName: %v", err)
}
if repoID <= 0 {
t.Fatalf("repo id: got %d, want > 0", repoID)
}
// Verify manifest exists.
m, err := d.GetManifestByDigest(repoID, "sha256:manifest111")
if err != nil {
t.Fatalf("GetManifestByDigest: %v", err)
}
if m.MediaType != "application/vnd.oci.image.manifest.v1+json" {
t.Fatalf("media type: got %q", m.MediaType)
}
if m.Size != int64(len(content)) {
t.Fatalf("size: got %d, want %d", m.Size, len(content))
}
// Verify tag points to manifest.
m2, err := d.GetManifestByTag(repoID, "latest")
if err != nil {
t.Fatalf("GetManifestByTag: %v", err)
}
if m2.Digest != "sha256:manifest111" {
t.Fatalf("tag digest: got %q", m2.Digest)
}
// Verify manifest_blobs join table.
var mbCount int
if err := d.QueryRow(`SELECT COUNT(*) FROM manifest_blobs WHERE manifest_id = ?`, m.ID).Scan(&mbCount); err != nil {
t.Fatalf("count manifest_blobs: %v", err)
}
if mbCount != 2 {
t.Fatalf("manifest_blobs count: got %d, want 2", mbCount)
}
}
func TestPushManifestByDigest(t *testing.T) {
d := openTestDB(t)
if err := d.Migrate(); err != nil {
t.Fatalf("Migrate: %v", err)
}
content := []byte(`{"schemaVersion":2}`)
params := PushManifestParams{
RepoName: "myrepo",
Digest: "sha256:manifest222",
MediaType: "application/vnd.oci.image.manifest.v1+json",
Content: content,
Size: int64(len(content)),
Tag: "", // push by digest — no tag
}
if err := d.PushManifest(params); err != nil {
t.Fatalf("PushManifest: %v", err)
}
// Verify no tag was created.
var tagCount int
if err := d.QueryRow(`SELECT COUNT(*) FROM tags`).Scan(&tagCount); err != nil {
t.Fatalf("count tags: %v", err)
}
if tagCount != 0 {
t.Fatalf("tag count: got %d, want 0", tagCount)
}
}
func TestPushManifestTagMove(t *testing.T) {
d := openTestDB(t)
if err := d.Migrate(); err != nil {
t.Fatalf("Migrate: %v", err)
}
// Push first manifest with tag "latest".
content1 := []byte(`{"schemaVersion":2,"v":"1"}`)
if err := d.PushManifest(PushManifestParams{
RepoName: "myrepo",
Digest: "sha256:first",
MediaType: "application/vnd.oci.image.manifest.v1+json",
Content: content1,
Size: int64(len(content1)),
Tag: "latest",
}); err != nil {
t.Fatalf("PushManifest (first): %v", err)
}
// Push second manifest with same tag "latest" — should atomically move tag.
content2 := []byte(`{"schemaVersion":2,"v":"2"}`)
if err := d.PushManifest(PushManifestParams{
RepoName: "myrepo",
Digest: "sha256:second",
MediaType: "application/vnd.oci.image.manifest.v1+json",
Content: content2,
Size: int64(len(content2)),
Tag: "latest",
}); err != nil {
t.Fatalf("PushManifest (second): %v", err)
}
repoID, err := d.GetRepositoryByName("myrepo")
if err != nil {
t.Fatalf("GetRepositoryByName: %v", err)
}
m, err := d.GetManifestByTag(repoID, "latest")
if err != nil {
t.Fatalf("GetManifestByTag: %v", err)
}
if m.Digest != "sha256:second" {
t.Fatalf("tag should point to second manifest, got %q", m.Digest)
}
}
func TestPushManifestIdempotent(t *testing.T) {
d := openTestDB(t)
if err := d.Migrate(); err != nil {
t.Fatalf("Migrate: %v", err)
}
content := []byte(`{"schemaVersion":2}`)
params := PushManifestParams{
RepoName: "myrepo",
Digest: "sha256:manifest333",
MediaType: "application/vnd.oci.image.manifest.v1+json",
Content: content,
Size: int64(len(content)),
Tag: "latest",
}
// Push twice — should not fail.
if err := d.PushManifest(params); err != nil {
t.Fatalf("PushManifest (first): %v", err)
}
if err := d.PushManifest(params); err != nil {
t.Fatalf("PushManifest (second): %v", err)
}
// Verify only one manifest exists.
var mCount int
if err := d.QueryRow(`SELECT COUNT(*) FROM manifests`).Scan(&mCount); err != nil {
t.Fatalf("count manifests: %v", err)
}
if mCount != 1 {
t.Fatalf("manifest count: got %d, want 1", mCount)
}
}