From 24b9acd678315b0b129942917e80be030685f0d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9E=D0=BB=D0=B5=D0=B3=20=D0=91=D0=BE=D1=80=D0=BE=D0=B4?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Tue, 3 Feb 2026 11:55:07 +0200 Subject: [PATCH] working commit --- app/descr/blob.go | 12 ++ app/descr/manifest.go | 12 ++ app/handler/notfound.go | 12 ++ app/handler/version.go | 17 +++ app/maindb/blob.go | 100 +++++++++++++ app/maindb/manifest.go | 272 +++++++++++++++++++++++++++++++++++ app/operator/version.go | 17 +++ pkg/client/filelife_test.go | 138 ++++++++++++++++++ pkg/client/imagelife_test.go | 77 ++++++++++ pkg/client/test-oci.img | Bin 0 -> 8192 bytes 10 files changed, 657 insertions(+) create mode 100644 app/descr/blob.go create mode 100644 app/descr/manifest.go create mode 100644 app/handler/notfound.go create mode 100644 app/handler/version.go create mode 100644 app/maindb/blob.go create mode 100644 app/maindb/manifest.go create mode 100644 app/operator/version.go create mode 100644 pkg/client/filelife_test.go create mode 100644 pkg/client/imagelife_test.go create mode 100644 pkg/client/test-oci.img diff --git a/app/descr/blob.go b/app/descr/blob.go new file mode 100644 index 0000000..604ff46 --- /dev/null +++ b/app/descr/blob.go @@ -0,0 +1,12 @@ +package descr + +type Blob struct { + ID string `db:"id" json:"id"` + Name string `db:"name" json:"name"` + Reference string `db:"reference" json:"reference"` + MediaType string `db:"mediaType" json:"mediaType"` + Digest string `db:"digest" json:"digest"` + Size int64 `db:"size" json:"size"` + CreatedAt string `db:"created_at" json:"createdAt"` + UpdatedAt string `db:"updated_at" json:"updatedAt"` +} diff --git a/app/descr/manifest.go b/app/descr/manifest.go new file mode 100644 index 0000000..7694036 --- /dev/null +++ b/app/descr/manifest.go @@ -0,0 +1,12 @@ +package descr + +type Manifest struct { + ID string `db:"id" json:"id"` + Name string `db:"name" json:"name"` + Reference string `db:"reference" json:"reference"` + ContentType string `db:"contentType" json:"contentType"` + Payload string `db:"payload" json:"-"` + Digest string `db:"digest" json:"digest"` + CreatedAt string `db:"created_at" json:"createdAt"` + UpdatedAt string `db:"updated_at" json:"updatedAt"` +} diff --git a/app/handler/notfound.go b/app/handler/notfound.go new file mode 100644 index 0000000..7f0ef42 --- /dev/null +++ b/app/handler/notfound.go @@ -0,0 +1,12 @@ +package handler + +import ( + "net/http" + + "mstore/app/router" +) + +func (hand *Handler) NotFound(rctx *router.Context) { + hand.logg.Debugf("Route for [%s %s] not found", rctx.Request.Method, rctx.Request.URL.String()) + rctx.SetStatus(http.StatusNotFound) +} diff --git a/app/handler/version.go b/app/handler/version.go new file mode 100644 index 0000000..d9ff447 --- /dev/null +++ b/app/handler/version.go @@ -0,0 +1,17 @@ +package handler + +import ( + "mstore/app/operator" + "mstore/app/router" +) + +// GET /v2/ 200 404/401 +func (hand *Handler) GetVersion(rctx *router.Context) { + params := &operator.GetVersionParams{} + ctx := rctx.GetContext() + _, code, err := hand.oper.GetVersion(ctx, params) + if err != nil { + hand.logg.Errorf("GetVersion error: %v", err) + } + rctx.SetStatus(code) +} diff --git a/app/maindb/blob.go b/app/maindb/blob.go new file mode 100644 index 0000000..7edd029 --- /dev/null +++ b/app/maindb/blob.go @@ -0,0 +1,100 @@ +package maindb + +import ( + "context" + + "mstore/app/descr" +) + +func (db *Database) InsertBlob(ctx context.Context, layer *descr.Blob) error { + var err error + request := ` + INSERT INTO blobs(id, name, reference, mediaType, digest, size, created_at, updated_at) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8)` + _, err = db.db.Exec(request, + layer.ID, layer.Name, layer.Reference, layer.MediaType, + layer.Digest, layer.Size, layer.CreatedAt, layer.UpdatedAt) + if err != nil { + return err + } + return err +} + +func (db *Database) ListAllBlobs(ctx context.Context) ([]descr.Blob, error) { + var err error + blobs := make([]descr.Blob, 0) + request := `SELECT * FROM blobs` + err = db.db.Select(&blobs, request) + if err != nil { + return blobs, err + } + return blobs, err +} + +func (db *Database) BlobExists(ctx context.Context, name, reference, digest string) (bool, error) { + var err error + blobs := make([]descr.Blob, 0) + request := ` + SELECT * FROM blobs WHERE name = $1 AND reference = $2 AND digest = $3 LIMIT 1` + err = db.db.Select(&blobs, request, name, reference, digest) + if err != nil { + return false, err + } + if len(blobs) > 0 { + return true, err + } + return false, err +} + +func (db *Database) GetBlobsByReferense(ctx context.Context, name, reference string) ([]descr.Blob, error) { + var err error + blobs := make([]descr.Blob, 0) + request := ` + SELECT * FROM blobs WHERE name = $1 AND reference = $2` + err = db.db.Select(&blobs, request, name, reference) + if err != nil { + return blobs, err + } + return blobs, err +} + +func (db *Database) GetBlobByDigest(ctx context.Context, digest string) (bool, descr.Blob, error) { + var err error + blobs := make([]descr.Blob, 0) + res := descr.Blob{} + exists := false + request := `SELECT * FROM blobs WHERE digest = $1 LIMIT 1` + err = db.db.Select(&blobs, request, digest) + if err != nil { + return exists, res, err + } + if len(blobs) > 0 { + res = blobs[0] + exists = true + } + return exists, res, err +} + +func (db *Database) GetBlobUsage(ctx context.Context, digest string) (int64, error) { + var err error + var usage int64 + count := make([]int64, 0) + request := ` + SELECT count(id) AS count FROM blobs WHERE digest = $1` + err = db.db.Select(&count, request, digest) + if err != nil { + return usage, err + } + usage = count[0] + return usage, err +} + +func (db *Database) DeleteBlobByDigest(ctx context.Context, digest string) error { + var err error + request := `DELETE FROM blobs WHERE digest = $1;` + _, err = db.db.Exec(request, digest) + if err != nil { + return err + } + return err +} diff --git a/app/maindb/manifest.go b/app/maindb/manifest.go new file mode 100644 index 0000000..40b40db --- /dev/null +++ b/app/maindb/manifest.go @@ -0,0 +1,272 @@ +package maindb + +import ( + "context" + + "mstore/app/descr" +) + +func (db *Database) InsertManifest(ctx context.Context, manifest *descr.Manifest) error { + var err error + var request string + request = ` + INSERT INTO manifests(id, name, reference, contentType, payload, digest, created_at, updated_at) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8);` + _, err = db.db.Exec(request, manifest.ID, manifest.Name, manifest.Reference, + manifest.ContentType, manifest.Payload, manifest.Digest, + manifest.CreatedAt, manifest.UpdatedAt) + if err != nil { + return err + } + return err +} + +func (db *Database) UpdateManifest(ctx context.Context, manifest *descr.Manifest) error { + var err error + var request string + // Manifest + request = ` + UPDATE manifests + SET contentType = $1, payload = $2, digest = $3, updated_at = $4 + WHERE name = $5 AND reference = $6` + _, err = db.db.Exec(request, manifest.ContentType, manifest.Payload, manifest.Digest, + manifest.UpdatedAt, manifest.Name, manifest.Reference) + if err != nil { + return err + } + return err +} + +func (db *Database) InsertManifestWithBlobs(ctx context.Context, manifest *descr.Manifest, config *descr.Blob, layers []descr.Blob) error { + var err error + var request string + + // Begin Tx + tx, err := db.db.BeginTx(ctx, nil) + // Manifest + request = ` + INSERT INTO manifests(id, name, reference, contentType, payload, digest, created_at, updated_at) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8);` + _, err = tx.Exec(request, manifest.ID, manifest.Name, manifest.Reference, + manifest.ContentType, manifest.Payload, manifest.Digest, + manifest.CreatedAt, manifest.UpdatedAt) + if err != nil { + return err + } + // Config + request = ` + INSERT INTO blobs(id, name, reference, mediaType, digest, size, created_at, updated_at) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8)` + _, err = tx.Exec(request, config.ID, config.Name, config.Reference, config.MediaType, + config.Digest, config.Size, config.CreatedAt, config.UpdatedAt) + if err != nil { + return err + } + // Layers + for _, layer := range layers { + request = ` + INSERT INTO blobs(id, name, reference, mediaType, digest, size, created_at, updated_at) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8)` + _, err = tx.Exec(request, + layer.ID, layer.Name, layer.Reference, layer.MediaType, + layer.Digest, layer.Size, layer.CreatedAt, layer.UpdatedAt) + if err != nil { + return err + } + } + // Commit + err = tx.Commit() + if err != nil { + return err + } + // TODO: tx.Rollback() + return err +} + +func (db *Database) UpdateManifestWithBlobs(ctx context.Context, manifest *descr.Manifest, newLayers []descr.Blob, oldLayers []descr.Blob) error { + var err error + var request string + + // Begin Tx + tx, err := db.db.BeginTx(ctx, nil) + // TODO: tx.Rollback() + + // Manifest + request = ` + UPDATE manifests + SET contentType = $1, payload = $2, digest = $3, updated_at = $4 + WHERE name = $5 AND reference = $6` + _, err = tx.Exec(request, manifest.ContentType, manifest.Payload, manifest.Digest, + manifest.UpdatedAt, manifest.Name, manifest.Reference) + if err != nil { + return err + } + // New layers + for _, layer := range newLayers { + request = ` + INSERT INTO blobs(id, name, reference, mediaType, digest, size, created_at, updated_at) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8)` + _, err = tx.Exec(request, layer.ID, layer.Name, layer.Reference, layer.MediaType, + layer.Digest, layer.Size, layer.CreatedAt, layer.UpdatedAt) + if err != nil { + return err + } + } + // Old layers + for _, layer := range oldLayers { + request = ` + DELETE FROM blobs + WHERE name == $1 + AND reference == $2 + AND digest == $3` + _, err = tx.Exec(request, layer.Name, layer.Reference, layer.Digest) + } + // Commit + err = tx.Commit() + if err != nil { + return err + } + return err +} + +func (db *Database) ManifestExistsByReference(ctx context.Context, name, reference string) (bool, error) { + var err error + var count int + var exists bool + request := ` + SELECT count(id) AS count FROM manifests + WHERE name = $1 + AND reference = $2 + LIMIT 1` + err = db.db.Get(&count, request, name, reference) + if err != nil { + return exists, err + } + if count > 0 { + exists = true + } + return exists, err +} + +func (db *Database) ManifestExistsByDigest(ctx context.Context, name, digest string) (bool, error) { + var err error + var count int + var exists bool + request := ` + SELECT count(id) AS count FROM manifests + WHERE name = $1 AND digest = $2 LIMIT 1` + err = db.db.Get(&count, request, name, digest) + if err != nil { + return exists, err + } + if count > 0 { + exists = true + } + return exists, err +} + +func (db *Database) GetManifestByDigest(ctx context.Context, name, digest string) (bool, descr.Manifest, error) { + var err error + manifest := descr.Manifest{} + manifests := make([]descr.Manifest, 0) + exists := false + request := ` + SELECT * FROM manifests + WHERE name = $1 AND digest = $2 + LIMIT 1` + err = db.db.Select(&manifests, request, name, digest) + if err != nil { + return exists, manifest, err + } + if len(manifests) > 0 { + exists = true + manifest = manifests[0] + } + return exists, manifest, err +} + +func (db *Database) GetManifestByReference(ctx context.Context, name, reference string) (bool, descr.Manifest, error) { + var err error + manifest := descr.Manifest{} + exists := false + manifests := make([]descr.Manifest, 0) + request := ` + SELECT * FROM manifests + WHERE name = $1 AND reference = $2 + LIMIT 1` + err = db.db.Select(&manifests, request, name, reference) + if err != nil { + return exists, manifest, err + } + + if len(manifests) > 0 { + exists = true + manifest = manifests[0] + } + return exists, manifest, err +} + +func (db *Database) ListManifestsByName(ctx context.Context, name string) ([]descr.Manifest, error) { + var err error + manifests := make([]descr.Manifest, 0) + request := ` + SELECT id, name, reference, contentType, payload FROM manifests + WHERE name = $1` + err = db.db.Select(&manifests, request, name) + if err != nil { + return manifests, err + } + return manifests, err +} + +func (db *Database) ListAllManifests(ctx context.Context) ([]descr.Manifest, error) { + var err error + manifests := make([]descr.Manifest, 0) + request := `SELECT * FROM manifests` + err = db.db.Select(&manifests, request) + if err != nil { + return manifests, err + } + return manifests, err +} + +func (db *Database) GetReferer(ctx context.Context, name, digest string) ([]descr.Manifest, error) { + var err error + manifests := make([]descr.Manifest, 0) + request := `SELECT * FROM manifests WHERE name = $1, digest = $2` + err = db.db.Select(&manifests, request, name, digest) + if err != nil { + return manifests, err + } + return manifests, err +} + +func (db *Database) DeleteManifest(ctx context.Context, name, reference string) error { + var err error + var request string + + // Begin Tx + tx, err := db.db.BeginTx(ctx, nil) + // TODO: tx.Rollback() + + // Blobs + request = ` + DELETE FROM blobs + WHERE name = $1 AND reference = $2` + _, err = tx.Exec(request, name, reference) + if err != nil { + return err + } + // Manifest + request = `DELETE FROM manifests WHERE name = $1 AND reference = $2` + _, err = tx.Exec(request, name, reference) + if err != nil { + return err + } + // Commit + err = tx.Commit() + if err != nil { + return err + } + return err +} diff --git a/app/operator/version.go b/app/operator/version.go new file mode 100644 index 0000000..edac3ab --- /dev/null +++ b/app/operator/version.go @@ -0,0 +1,17 @@ +package operator + +import ( + "context" + "net/http" +) + +type GetVersionParams struct{} +type GetVersionResult struct{} + +func (oper *Operator) GetVersion(ctx context.Context, params *GetVersionParams) (*GetVersionResult, int, error) { + var err error + code := http.StatusOK + res := &GetVersionResult{} + oper.logg.Debugf("Call GetVersion") + return res, code, err +} diff --git a/pkg/client/filelife_test.go b/pkg/client/filelife_test.go new file mode 100644 index 0000000..66ea497 --- /dev/null +++ b/pkg/client/filelife_test.go @@ -0,0 +1,138 @@ +package client + +import ( + "context" + "fmt" + "math/rand" + "os" + "path/filepath" + "sync" + "testing" + "time" + + "mstore/app/server" + + "github.com/stretchr/testify/require" +) + +func xxxTestFileLife(t *testing.T) { + var srvport int64 = 10250 + srvdir := t.TempDir() + srvaddr := fmt.Sprintf("127.0.0.1:%d", srvport) + + srv, err := server.NewServer() + require.NoError(t, err) + { + err = srv.Configure() + require.NoError(t, err) + + srv.SetDatadir(srvdir) + srv.SetLogdir(srvdir) + srv.SetRundir(srvdir) + srv.SetPort(srvport) + + err = srv.Build() + require.NoError(t, err) + + var svcWG sync.WaitGroup + errPipe := make(chan error, 5) + + startFunc := func() { + err := srv.Service().Run() + errPipe <- err + svcWG.Done() + } + + stopFunc := func() { + srv.Service().Stop() + svcWG.Wait() + err = <-errPipe + require.NoError(t, err) + } + defer stopFunc() + + svcWG.Add(1) + go startFunc() + time.Sleep(1 * time.Second) + } + { + // ServiceHello + fmt.Printf("=== ServiceHello ===\n") + cli := NewClient() + ctx := context.Background() + helloRes, err := cli.ServiceHello(ctx, srvaddr+"/hello", 1*time.Second) + require.NoError(t, err) + require.True(t, helloRes) + } + filesize := 32 + { + // PutFile + tmpdir := t.TempDir() + tmpfile := filepath.Join(tmpdir, "foo.bin") + + filedata := make([]byte, filesize) + _, err = rand.Read(filedata) + require.NoError(t, err) + + err := os.WriteFile(tmpfile, filedata, 0666) + require.NoError(t, err) + + fmt.Printf("=== PutFile ===\n") + cli := NewClient() + ctx := context.Background() + ctx, _ = context.WithTimeout(ctx, 1*time.Second) + + err = cli.PutFile(ctx, tmpfile, srvaddr+"/foo.bin") + require.NoError(t, err) + } + { + // FileExists + fmt.Printf("=== FileExists ===\n") + cli := NewClient() + ctx := context.Background() + ctx, _ = context.WithTimeout(ctx, 1*time.Second) + + exists, err := cli.FileExists(ctx, srvaddr+"/foo.bin") + require.NoError(t, err) + require.True(t, exists) + } + { + // GetFile + fmt.Printf("=== GetFile ===\n") + cli := NewClient() + ctx := context.Background() + ctx, _ = context.WithTimeout(ctx, 1*time.Second) + + tmpdir := t.TempDir() + tmpfile := filepath.Join(tmpdir, "foo.bin") + + recsize, err := cli.GetFile(ctx, srvaddr+"/foo.bin", tmpfile) + require.NoError(t, err) + require.Equal(t, recsize, int64(filesize)) + } + { + // DeleteFile + fmt.Printf("=== DeleteFile ===\n") + cli := NewClient() + ctx := context.Background() + ctx, _ = context.WithTimeout(ctx, 1*time.Second) + + tmpdir := t.TempDir() + tmpfile := filepath.Join(tmpdir, "foo.bin") + + err = cli.DeleteFile(ctx, srvaddr+"/foo.bin", tmpfile) + require.NoError(t, err) + } + { + // !FileExists + fmt.Printf("=== FileExists ===\n") + cli := NewClient() + ctx := context.Background() + ctx, _ = context.WithTimeout(ctx, 1*time.Second) + + exists, err := cli.FileExists(ctx, srvaddr+"/foo.bin") + require.NoError(t, err) + require.False(t, exists) + } + +} diff --git a/pkg/client/imagelife_test.go b/pkg/client/imagelife_test.go new file mode 100644 index 0000000..703d827 --- /dev/null +++ b/pkg/client/imagelife_test.go @@ -0,0 +1,77 @@ +package client + +import ( + "context" + "fmt" + //"math/rand" + //"os" + //"path/filepath" + "sync" + "testing" + "time" + + "mstore/app/server" + + "github.com/stretchr/testify/require" +) + +func TestImageLife(t *testing.T) { + var srvport int64 = 10250 + srvdir := t.TempDir() + srvaddr := fmt.Sprintf("127.0.0.1:%d", srvport) + + srv, err := server.NewServer() + require.NoError(t, err) + { + err = srv.Configure() + require.NoError(t, err) + + srv.SetDatadir(srvdir) + srv.SetLogdir(srvdir) + srv.SetRundir(srvdir) + srv.SetPort(srvport) + + err = srv.Build() + require.NoError(t, err) + + var svcWG sync.WaitGroup + errPipe := make(chan error, 5) + + startFunc := func() { + err := srv.Service().Run() + errPipe <- err + svcWG.Done() + } + + stopFunc := func() { + srv.Service().Stop() + svcWG.Wait() + err = <-errPipe + require.NoError(t, err) + } + defer stopFunc() + + svcWG.Add(1) + go startFunc() + time.Sleep(1 * time.Second) + } + { + // ServiceHello + fmt.Printf("=== ServiceHello ===\n") + cli := NewClient() + ctx := context.Background() + helloRes, err := cli.ServiceHello(ctx, srvaddr+"/hello", 1*time.Second) + require.NoError(t, err) + require.True(t, helloRes) + } + + { + // PishImage + fmt.Printf("=== PushImage ===\n") + cli := NewClient() + ctx := context.Background() + err := cli.PushImage(ctx, "test-oci.img", srvaddr+"/foo/testapp:v123", 1*time.Second) + require.NoError(t, err) + } + +} diff --git a/pkg/client/test-oci.img b/pkg/client/test-oci.img new file mode 100644 index 0000000000000000000000000000000000000000..c0d5540ac4d702d8743bf9db62729d313ec5d307 GIT binary patch literal 8192 zcmeHL&5ImG6dzDR6hU+H=ujf8X1Axlt7}j&kbs7e1X%>#tV@0Fw9Ir5-91Y-%PJ^- zfI@N*{0BtH!Gi}84;~G85lpm|{U;bU`~RPuk{$bxV2m{r8XIIdfrct+I5nOM%aIXY zc}AS$OdE)`U>H&qTdfg~6^B-<2^K>HA*aEw$ItF}7)3k%AJfp+YQqWoX4-w9`~Lf^IAAfSqMYcFe#w& z)EW;RW&{V7MRFk+!3p1-2Yt03tOebrX$`Cef_+g4^+#7PB*WX-sIirlc}Tpl*h9>{ zG?1(C>lif3hcoGrRl;Z*E~%J%L9kZBVW}EecNRgZH7BGi74|1@w|? zqmQ_zw}||A_yXHt~M~oHOly;)BDj z)p~$#@S=bzh_s#p_50Rd7~7c%8Q?`UHbZqU#@?@^yryCKRTCx`_%Q(E+JhAqmY*~LI|R-22P=J(@RQc%28f3!hz+qq8fqWfPRvW&WFY=@^~U$ z3mgrcJ+}D5bDdFy%(H8ip1vA53Jz`z(WmdlVN1@I8|u_TO91eU{!y`(6!e7)wW z6G6sZCL>8WWm2*l