From 223ae2e96e31957435f663a02f9cd15eeacd3906 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: Thu, 5 Mar 2026 12:26:14 +0200 Subject: [PATCH] alittle splitted mstorectl code; etc --- DEVEL.md | 11 ++- Makefile.am | 28 +----- Makefile.in | 20 +--- app/config/variant.go | 2 +- app/imageoper/{blob.go => _blob.go} | 0 app/imageoper/blobexist.go | 69 ++++++++++++++ app/imageoper/delblob.go | 71 +++++++++++++++ app/imageoper/getblob.go | 70 ++++++++++++++ app/imageoper/patchupload.go | 86 ++++++++++++++++++ app/imageoper/postupload.go | 49 ++++++++++ app/imageoper/putupload.go | 91 +++++++++++++++++++ .../{command => accountcmd}/accountcmd.go | 2 +- cmd/mstorectl/accountcmd/auxfunc.go | 34 +++++++ .../{command => accountcmd}/grantcmd.go | 2 +- cmd/mstorectl/accountcmd/printresp.go | 33 +++++++ .../{command => imagecmd}/imagecmd.go | 2 +- cmd/mstorectl/imagecmd/printresp.go | 32 +++++++ cmd/mstorectl/main.go | 4 +- cmd/mstorectl/{command => }/util.go | 28 ++---- pkg/client/account.go | 34 +++---- pkg/client/{ => attic}/file.go | 0 pkg/client/{ => attic}/fileaux.go | 0 pkg/client/grant.go | 30 +++--- pkg/client/servaux.go | 37 ++++++++ 24 files changed, 631 insertions(+), 104 deletions(-) rename app/imageoper/{blob.go => _blob.go} (100%) create mode 100644 app/imageoper/blobexist.go create mode 100644 app/imageoper/delblob.go create mode 100644 app/imageoper/getblob.go create mode 100644 app/imageoper/patchupload.go create mode 100644 app/imageoper/postupload.go create mode 100644 app/imageoper/putupload.go rename cmd/mstorectl/{command => accountcmd}/accountcmd.go (99%) create mode 100644 cmd/mstorectl/accountcmd/auxfunc.go rename cmd/mstorectl/{command => accountcmd}/grantcmd.go (99%) create mode 100644 cmd/mstorectl/accountcmd/printresp.go rename cmd/mstorectl/{command => imagecmd}/imagecmd.go (99%) create mode 100644 cmd/mstorectl/imagecmd/printresp.go rename cmd/mstorectl/{command => }/util.go (72%) rename pkg/client/{ => attic}/file.go (100%) rename pkg/client/{ => attic}/fileaux.go (100%) create mode 100644 pkg/client/servaux.go diff --git a/DEVEL.md b/DEVEL.md index 564d10a..72a309e 100644 --- a/DEVEL.md +++ b/DEVEL.md @@ -1,11 +1,12 @@ -Данный сервис был написан мной в целях представить простой в развертывании, модификации и использовании сервис хранения артефактов. -Прежде всего образов контейнеров и helm charts. Я не претендовал на +Данный сервис был написан мной в целях представить простой в развертывании, модификации и +использовании сервис хранения артефактов. +Прежде всего образов контейнеров и helm charts. Я не претендовал и не претендую на коммерческий продукт в широком публичном испльзовании. Он успешно испытан при создании k8s кластеров и развертывании приложений в нем. -В этом продукте представлено три условных протокола взаимодействия +В этом продукте представлено три условных протокола взаимодействия: 1 Это протокол передачи компонент образов контейнеров, выросший из протокола, придуманном неким человеком в Docker.com. @@ -18,6 +19,10 @@ Но интегрированный в деловые процессы он стал основой де-факто, судя по всему без значительных попыток пересмотра. +Кодирование состояний приложения в коды HTTP считаю возможным, есть и более страннные подходы. +Но это как будто к тебе присылают немого курьера в красной куртке, что бы сообщить +что твоя посылка потеряна. + Позднее этот протокол был несколько облагорожен и получил статус и редакцию стандарта от Open Container Initiative. diff --git a/Makefile.am b/Makefile.am index c447e8f..ee2755b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -7,13 +7,10 @@ bin_PROGRAMS = mstorectl sbin_PROGRAMS = mstored mstorectl_SOURCES = \ - cmd/mstorectl/main.go + cmd/mstorectl/main.go \ + cmd/mstorectl/util.go -EXTRA_mstorectl_SOURCES = \ - cmd/mstorectl/command/accountcmd.go \ - cmd/mstorectl/command/grantcmd.go \ - cmd/mstorectl/command/imagecmd.go \ - cmd/mstorectl/command/util.go +EXTRA_mstorectl_SOURCES = mstored_SOURCES = cmd/mstored/main.go @@ -66,23 +63,6 @@ EXTRA_mstored_SOURCES += \ app/maindb/manifest.go \ app/maindb/scheme.go \ \ - app/operator/account.go \ - app/operator/blob.go \ - app/operator/grant.go \ - app/operator/imgaux.go \ - app/operator/manifest.go \ - app/operator/ociaux.go \ - app/operator/operator.go \ - app/operator/service.go \ - app/operator/version.go \ - app/router/bindobj.go \ - app/router/context.go \ - app/router/corsmw.go \ - app/router/loggingmw.go \ - app/router/pathc.go \ - app/router/recovermw.go \ - app/router/router.go \ - \ app/server/server.go \ app/service/service.go \ app/storage/storage.go \ @@ -99,9 +79,9 @@ EXTRA_mstored_SOURCES += \ pkg/auxutar/utar.go \ pkg/auxuuid/uuid.go \ pkg/auxx509/x509cert.go \ + \ pkg/client/account.go \ pkg/client/client.go \ - pkg/client/fileaux.go \ pkg/client/grant.go \ pkg/client/httpcall.go \ pkg/client/imageaux.go \ diff --git a/Makefile.in b/Makefile.in index fdd80ad..b729fb0 100644 --- a/Makefile.in +++ b/Makefile.in @@ -353,14 +353,10 @@ AUTOMAKE_OPTIONS = foreign no-dependencies no-installinfo SUBDIRS = mans GOFLAGS = -v mstorectl_SOURCES = \ - cmd/mstorectl/main.go - -EXTRA_mstorectl_SOURCES = \ - cmd/mstorectl/command/accountcmd.go \ - cmd/mstorectl/command/grantcmd.go \ - cmd/mstorectl/command/imagecmd.go \ - cmd/mstorectl/command/util.go + cmd/mstorectl/main.go \ + cmd/mstorectl/util.go +EXTRA_mstorectl_SOURCES = mstored_SOURCES = cmd/mstored/main.go EXTRA_mstored_SOURCES = cmd/mstored/starter/starter.go \ app/config/config.go app/config/variant.go \ @@ -375,14 +371,6 @@ EXTRA_mstored_SOURCES = cmd/mstored/starter/starter.go \ app/maindb/account.go app/maindb/blob.go app/maindb/file.go \ app/maindb/grant.go app/maindb/init.go app/maindb/maindb.go \ app/maindb/manifest.go app/maindb/scheme.go \ - app/operator/account.go app/operator/blob.go \ - app/operator/grant.go app/operator/imgaux.go \ - app/operator/manifest.go app/operator/ociaux.go \ - app/operator/operator.go app/operator/service.go \ - app/operator/version.go app/router/bindobj.go \ - app/router/context.go app/router/corsmw.go \ - app/router/loggingmw.go app/router/pathc.go \ - app/router/recovermw.go app/router/router.go \ app/server/server.go app/service/service.go \ app/storage/storage.go pkg/auxhttp/basic.go \ pkg/auxhttp/crange.go pkg/auxoci/ociaux.go \ @@ -391,7 +379,7 @@ EXTRA_mstored_SOURCES = cmd/mstored/starter/starter.go \ pkg/auxtool/tmpfile.go pkg/auxtool/unixnow.go \ pkg/auxutar/utar.go pkg/auxuuid/uuid.go \ pkg/auxx509/x509cert.go pkg/client/account.go \ - pkg/client/client.go pkg/client/fileaux.go pkg/client/grant.go \ + pkg/client/client.go pkg/client/grant.go \ pkg/client/httpcall.go pkg/client/imageaux.go \ pkg/client/imagedelete.go pkg/client/imageinfo.go \ pkg/client/imagepull.go pkg/client/imagepush.go \ diff --git a/app/config/variant.go b/app/config/variant.go index 6cf7189..6edc328 100644 --- a/app/config/variant.go +++ b/app/config/variant.go @@ -6,5 +6,5 @@ const ( logdir = "/home/ziggi/Projects/mstore/tmp/log" datadir = "/home/ziggi/Projects/mstore/tmp/data" version = "0.2.0" - srvname = "mstored" + srvname = "mstored" ) diff --git a/app/imageoper/blob.go b/app/imageoper/_blob.go similarity index 100% rename from app/imageoper/blob.go rename to app/imageoper/_blob.go diff --git a/app/imageoper/blobexist.go b/app/imageoper/blobexist.go new file mode 100644 index 0000000..57e356a --- /dev/null +++ b/app/imageoper/blobexist.go @@ -0,0 +1,69 @@ +/* + * Copyright 2026 Oleg Borodin + * + * This work is published and licensed under a Creative Commons + * Attribution-NonCommercial-NoDerivatives 4.0 International License. + * + * Distribution of this work is permitted, but commercial use and + * modifications are strictly prohibited. + */ +package imageoper + +import ( + "context" + "fmt" + "net/http" + "strconv" +) + +type BlobExistsParams struct { + Name string + Digest string +} +type BlobExistsResult struct { + DockerContentDigest string + ContentLength string + ContentType string + //Exists bool +} + +func (oper *Operator) BlobExists(ctx context.Context, operatorID string, params *BlobExistsParams) (*BlobExistsResult, int, error) { + var err error + res := &BlobExistsResult{} + + if params.Digest == "" { + err = fmt.Errorf("Empty reference") + return res, http.StatusBadRequest, err + } + if params.Name == "" { + err = fmt.Errorf("Empty name") + return res, http.StatusBadRequest, err + } + + resName := params.Name + oper.iLock.WaitAndLock(resName) + defer oper.iLock.Done(resName) + + // Check blob descriptor + descrExists, blobDescr, err := oper.mdb.GetBlobByNameDigest(ctx, params.Digest, params.Digest) + if err != nil { + return res, http.StatusInternalServerError, err + } + if !descrExists { + return res, http.StatusNotFound, err + } + // Check blob file + blobExists, _, err := oper.store.BlobExists(params.Digest) + if err != nil { + return res, http.StatusInternalServerError, err + } + if !blobExists { + return res, http.StatusNotFound, err + } + + res.ContentLength = strconv.FormatInt(blobDescr.Size, 10) + res.DockerContentDigest = blobDescr.Digest + res.ContentType = blobDescr.MediaType + + return res, http.StatusOK, err +} diff --git a/app/imageoper/delblob.go b/app/imageoper/delblob.go new file mode 100644 index 0000000..b94a4f5 --- /dev/null +++ b/app/imageoper/delblob.go @@ -0,0 +1,71 @@ +/* + * Copyright 2026 Oleg Borodin + * + * This work is published and licensed under a Creative Commons + * Attribution-NonCommercial-NoDerivatives 4.0 International License. + * + * Distribution of this work is permitted, but commercial use and + * modifications are strictly prohibited. + */ +package imageoper + +import ( + "context" + "fmt" + "net/http" +) + +type DeleteBlobParams struct { + Name string + Digest string +} +type DeleteBlobResult struct{} + +// Removing an individual layer is very probably to compromise data integrity. +// - If the data layers are to be reused, this is like shooting yourself in the foot. +// - It also prevents the manifest from being issued, as one +// of the layers is missing. + +func (oper *Operator) DeleteBlob(ctx context.Context, operatorID string, params *DeleteBlobParams) (*DeleteBlobResult, int, error) { + var err error + res := &DeleteBlobResult{} + + if params.Digest == "" { + err = fmt.Errorf("Empty digest") + return res, http.StatusBadRequest, err + } + if params.Name == "" { + err = fmt.Errorf("Empty name") + return res, http.StatusBadRequest, err + } + + resName := params.Name + oper.iLock.WaitAndLock(resName) + defer oper.iLock.Done(resName) + + // Check namespace record + descrExists, _, err := oper.mdb.GetBlobByNameDigest(ctx, params.Name, params.Digest) + if err != nil { + return res, http.StatusInternalServerError, err + } + + if !descrExists { + return res, http.StatusNotFound, err + } + // Deleting blob record + oper.logg.Warningf("Deleting blob record %s:%s", params.Name, params.Digest) + err = oper.mdb.DeleteBlobByNameDigest(ctx, params.Name, params.Digest) + if err != nil { + return res, http.StatusInternalServerError, err + } + // Removing the blob binary if usage == 0 + blobUsage, err := oper.mdb.GetBlobUsage(ctx, params.Digest) + if err != nil { + return res, http.StatusInternalServerError, err + } + if blobUsage == 0 { + oper.logg.Warningf("Deleting useless blob binary %s", params.Digest) + oper.store.DeleteBlob(params.Digest) + } + return res, http.StatusOK, err +} diff --git a/app/imageoper/getblob.go b/app/imageoper/getblob.go new file mode 100644 index 0000000..20a1ac6 --- /dev/null +++ b/app/imageoper/getblob.go @@ -0,0 +1,70 @@ +/* + * Copyright 2026 Oleg Borodin + * + * This work is published and licensed under a Creative Commons + * Attribution-NonCommercial-NoDerivatives 4.0 International License. + * + * Distribution of this work is permitted, but commercial use and + * modifications are strictly prohibited. + */ +package imageoper + +import ( + "context" + "fmt" + "io" + "net/http" + "strconv" +) + +type GetBlobParams struct { + Name string + Digest string +} +type GetBlobResult struct { + ContentLength string + ContentType string + DockerContentDigest string + ReadCloser io.ReadCloser +} + +func (oper *Operator) GetBlob(ctx context.Context, operatorID string, params *GetBlobParams) (*GetBlobResult, int, error) { + var err error + res := &GetBlobResult{} + + if params.Name == "" { + err = fmt.Errorf("Empty name") + return res, http.StatusBadRequest, err + } + if params.Digest == "" { + err = fmt.Errorf("Empty digest") + return res, http.StatusBadRequest, err + } + + resName := params.Name + oper.iLock.WaitAndLock(resName) + defer oper.iLock.Done(resName) + + blobExists, blobSize, err := oper.store.BlobExists(params.Digest) + if err != nil { + return res, http.StatusInternalServerError, err + } + descrExist, blobDescr, err := oper.mdb.GetBlobByNameDigest(ctx, params.Name, params.Digest) + if err != nil { + return res, http.StatusInternalServerError, err + } + if !blobExists || !descrExist { + return res, http.StatusNotFound, err + } + + _, readCloser, err := oper.store.BlobReader(params.Digest) + if err != nil { + return res, http.StatusInternalServerError, err + } + + res.ContentType = blobDescr.MediaType + res.ContentLength = strconv.FormatInt(blobSize, 10) + res.DockerContentDigest = params.Digest + res.ReadCloser = readCloser + return res, http.StatusOK, err +} diff --git a/app/imageoper/patchupload.go b/app/imageoper/patchupload.go new file mode 100644 index 0000000..8bff511 --- /dev/null +++ b/app/imageoper/patchupload.go @@ -0,0 +1,86 @@ +/* + * Copyright 2026 Oleg Borodin + * + * This work is published and licensed under a Creative Commons + * Attribution-NonCommercial-NoDerivatives 4.0 International License. + * + * Distribution of this work is permitted, but commercial use and + * modifications are strictly prohibited. + */ +package imageoper + +import ( + "context" + "fmt" + "io" + "net/http" + "strconv" +) + +type PatchUploadParams struct { + ContentType string + ContentLength string + ContentRange string + Name string + Reference string + Reader io.Reader +} +type PatchUploadResult struct { + Location string + Range string +} + +// TODO: partial uploading by range? Does anyone use this? +func (oper *Operator) PatchUpload(ctx context.Context, operatorID string, params *PatchUploadParams) (*PatchUploadResult, int, error) { + var err error + res := &PatchUploadResult{} + + if params.Reference == "" { + err = fmt.Errorf("Empty reference") + return res, http.StatusBadRequest, err + } + if params.Name == "" { + err = fmt.Errorf("Empty name") + return res, http.StatusBadRequest, err + } + + exists, uploadSize, err := oper.store.UploadExists(params.Name, params.Reference) + if err != nil { + return res, http.StatusInternalServerError, err + } + if exists { + res.Location = fmt.Sprintf("/v2/%s/uploads/%s", params.Name, params.Reference) + res.Range = fmt.Sprintf("0-%d", uploadSize-1) + return res, http.StatusNoContent, err + } + + if params.ContentType != "application/octet-stream" { + err = fmt.Errorf("Wrong Conten-Type header: %s", params.ContentType) + return res, http.StatusBadRequest, err + } + var contentLength int64 + + // Unfortunately, podman and github.com/containers/image don't sent + // Content-Length header for docker transport + + if params.ContentLength != "" { + contentLength, err = strconv.ParseInt(params.ContentLength, 10, 64) + if err != nil { + err = fmt.Errorf("Wrong Content-length header") + return res, http.StatusBadRequest, err + } + } + + recsize, _, err := oper.store.WriteUpload(params.Reference, params.Reader) + if err != nil { + return res, http.StatusInternalServerError, err + } + if contentLength != 0 && recsize != contentLength { + oper.store.RemoveUpload(params.Reference) + err = fmt.Errorf("Mismatch upload recorded size and content length") + return res, http.StatusInternalServerError, err + } + res.Location = fmt.Sprintf("/v2/%s/uploads/%s", params.Name, params.Reference) + res.Range = fmt.Sprintf("0-%d", recsize-1) + return res, http.StatusAccepted, err +} diff --git a/app/imageoper/postupload.go b/app/imageoper/postupload.go new file mode 100644 index 0000000..5105bee --- /dev/null +++ b/app/imageoper/postupload.go @@ -0,0 +1,49 @@ +/* + * Copyright 2026 Oleg Borodin + * + * This work is published and licensed under a Creative Commons + * Attribution-NonCommercial-NoDerivatives 4.0 International License. + * + * Distribution of this work is permitted, but commercial use and + * modifications are strictly prohibited. + */ +package imageoper + +import ( + "context" + "fmt" + "net/http" + "strconv" + + "mstore/pkg/auxuuid" +) + +type PostUploadParams struct { + Name string + Digest string + Mount string + From string +} +type PostUploadResult struct { + DockerUploadUUID string + Location string + ContentLength string +} + +func (oper *Operator) PostUpload(ctx context.Context, operatorID string, params *PostUploadParams) (*PostUploadResult, int, error) { + var err error + res := &PostUploadResult{} + + if params.Digest == "" { + uuid := auxuuid.NewUUID() + location := fmt.Sprintf("/v2/%s/blobs/uploads/%s", params.Name, uuid) + res.DockerUploadUUID = uuid + res.Location = location + res.ContentLength = strconv.FormatInt(0, 10) + return res, http.StatusAccepted, err + } else { + err = fmt.Errorf("PostUpload: Not empty digest header") + return res, http.StatusInternalServerError, err + } + return res, http.StatusOK, err +} diff --git a/app/imageoper/putupload.go b/app/imageoper/putupload.go new file mode 100644 index 0000000..1b8f4e8 --- /dev/null +++ b/app/imageoper/putupload.go @@ -0,0 +1,91 @@ +/* + * Copyright 2026 Oleg Borodin + * + * This work is published and licensed under a Creative Commons + * Attribution-NonCommercial-NoDerivatives 4.0 International License. + * + * Distribution of this work is permitted, but commercial use and + * modifications are strictly prohibited. + */ +package imageoper + +import ( + "context" + "fmt" + "io" + "net/http" + "strconv" +) + +type PutUploadParams struct { + ContentType string + ContentLength string + ContentRange string + Name string + Reference string + Digest string + Reader io.Reader +} +type PutUploadResult struct { + Location string +} + +func (oper *Operator) PutUpload(ctx context.Context, operatorID string, params *PutUploadParams) (*PutUploadResult, int, error) { + var err error + res := &PutUploadResult{} + + if params.Reference == "" { + err = fmt.Errorf("Empty reference") + return res, http.StatusBadRequest, err + } + if params.Name == "" { + err = fmt.Errorf("Empty name") + return res, http.StatusBadRequest, err + } + if params.Digest == "" { + err = fmt.Errorf("Empty digest") + return res, http.StatusBadRequest, err + } + if params.ContentType != "application/octet-stream" { + err = fmt.Errorf("Wrong conten type: %s", params.ContentType) + return res, http.StatusBadRequest, err + } + + resName := params.Name + oper.iLock.WaitAndLock(resName) + defer oper.iLock.Done(resName) + + var contentLength int64 + if params.ContentLength != "" { + contentLength, err = strconv.ParseInt(params.ContentLength, 10, 64) + if err != nil { + err = fmt.Errorf("Cannot convert Content-Length=%s to integer: %v", params.ContentLength, err) + return res, http.StatusBadRequest, err + } + + } + if contentLength != 0 { + recsize, _, err := oper.store.WriteUpload(params.Reference, params.Reader) + if err != nil { + return res, http.StatusInternalServerError, err + } + if contentLength != 0 && recsize != contentLength { + oper.store.RemoveUpload(params.Reference) + err = fmt.Errorf("Mismatch upload recorded size and content length") + return res, http.StatusInternalServerError, err + } + if err != nil { + return res, http.StatusInternalServerError, err + } + res.Location = fmt.Sprintf("/v2/%s/blobs/%s", params.Name, params.Digest) + } + + err = oper.store.LinkUpload(params.Reference, params.Digest) + if err != nil { + err = fmt.Errorf("Failed to link upload %s, err: %v", params.Reference, err) + return res, http.StatusInternalServerError, err + } + + res.Location = fmt.Sprintf("/v2/%s/blobs/%s", params.Name, params.Digest) + return res, http.StatusCreated, err +} diff --git a/cmd/mstorectl/command/accountcmd.go b/cmd/mstorectl/accountcmd/accountcmd.go similarity index 99% rename from cmd/mstorectl/command/accountcmd.go rename to cmd/mstorectl/accountcmd/accountcmd.go index f6eb1f0..c71d6e3 100644 --- a/cmd/mstorectl/command/accountcmd.go +++ b/cmd/mstorectl/accountcmd/accountcmd.go @@ -7,7 +7,7 @@ * Distribution of this work is permitted, but commercial use and * modifications are strictly prohibited. */ -package command +package accountcmd import ( "context" diff --git a/cmd/mstorectl/accountcmd/auxfunc.go b/cmd/mstorectl/accountcmd/auxfunc.go new file mode 100644 index 0000000..1e0e5ad --- /dev/null +++ b/cmd/mstorectl/accountcmd/auxfunc.go @@ -0,0 +1,34 @@ +/* + * Copyright 2026 Oleg Borodin + * + * This work is published and licensed under a Creative Commons + * Attribution-NonCommercial-NoDerivatives 4.0 International License. + * + * Distribution of this work is permitted, but commercial use and + * modifications are strictly prohibited. + */ +package accountcmd + +import ( + "net/url" + "path" + "strings" +) + +func packUserinfo(resurseuri, username, password string) (string, error) { + var err error + var res string + if !strings.Contains(resurseuri, "://") { + resurseuri = "https://" + resurseuri + } + uri, err := url.Parse(resurseuri) + if err != nil { + return res, err + } + uri.Path = path.Clean(uri.Path) + if username != "" && password != "" { + uri.User = url.UserPassword(username, password) + } + res = uri.String() + return res, err +} diff --git a/cmd/mstorectl/command/grantcmd.go b/cmd/mstorectl/accountcmd/grantcmd.go similarity index 99% rename from cmd/mstorectl/command/grantcmd.go rename to cmd/mstorectl/accountcmd/grantcmd.go index d96772d..4dc74ba 100644 --- a/cmd/mstorectl/command/grantcmd.go +++ b/cmd/mstorectl/accountcmd/grantcmd.go @@ -7,7 +7,7 @@ * Distribution of this work is permitted, but commercial use and * modifications are strictly prohibited. */ -package command +package accountcmd import ( "context" diff --git a/cmd/mstorectl/accountcmd/printresp.go b/cmd/mstorectl/accountcmd/printresp.go new file mode 100644 index 0000000..a432144 --- /dev/null +++ b/cmd/mstorectl/accountcmd/printresp.go @@ -0,0 +1,33 @@ +/* + * Copyright 2026 Oleg Borodin + * + * This work is published and licensed under a Creative Commons + * Attribution-NonCommercial-NoDerivatives 4.0 International License. + * + * Distribution of this work is permitted, but commercial use and + * modifications are strictly prohibited. + */ +package accountcmd + +import ( + "fmt" + + "sigs.k8s.io/yaml" +) + +func printResponse(res any, err error) { + type Response struct { + Error bool `json:"error" yaml:"error"` + Message string `json:"message,omitempty" yaml:"message,omitempty"` + Result any `json:"result,omitempty" yaml:"result,omitempty"` + } + resp := Response{} + if err != nil { + resp.Error = true + resp.Message = err.Error() + } else { + resp.Result = res + } + respBytes, _ := yaml.Marshal(resp) + fmt.Printf("---\n%s\n", string(respBytes)) +} diff --git a/cmd/mstorectl/command/imagecmd.go b/cmd/mstorectl/imagecmd/imagecmd.go similarity index 99% rename from cmd/mstorectl/command/imagecmd.go rename to cmd/mstorectl/imagecmd/imagecmd.go index 0a9f010..464eea1 100644 --- a/cmd/mstorectl/command/imagecmd.go +++ b/cmd/mstorectl/imagecmd/imagecmd.go @@ -7,7 +7,7 @@ * Distribution of this work is permitted, but commercial use and * modifications are strictly prohibited. */ -package command +package imagecmd import ( "context" diff --git a/cmd/mstorectl/imagecmd/printresp.go b/cmd/mstorectl/imagecmd/printresp.go new file mode 100644 index 0000000..f4753de --- /dev/null +++ b/cmd/mstorectl/imagecmd/printresp.go @@ -0,0 +1,32 @@ +/* + * Copyright 2026 Oleg Borodin + * + * This work is published and licensed under a Creative Commons + * Attribution-NonCommercial-NoDerivatives 4.0 International License. + * + * Distribution of this work is permitted, but commercial use and + * modifications are strictly prohibited. + */ +package imagecmd + +import ( + "fmt" + "sigs.k8s.io/yaml" +) + +func printResponse(res any, err error) { + type Response struct { + Error bool `json:"error" yaml:"error"` + Message string `json:"message,omitempty" yaml:"message,omitempty"` + Result any `json:"result,omitempty" yaml:"result,omitempty"` + } + resp := Response{} + if err != nil { + resp.Error = true + resp.Message = err.Error() + } else { + resp.Result = res + } + respBytes, _ := yaml.Marshal(resp) + fmt.Printf("---\n%s\n", string(respBytes)) +} diff --git a/cmd/mstorectl/main.go b/cmd/mstorectl/main.go index 0b0ad1a..b7831ea 100644 --- a/cmd/mstorectl/main.go +++ b/cmd/mstorectl/main.go @@ -11,13 +11,11 @@ package main import ( "os" - - "mstore/cmd/mstorectl/command" ) func main() { var err error - util := command.NewUtil() + util := NewUtil() err = util.Build() if err != nil { os.Exit(1) diff --git a/cmd/mstorectl/command/util.go b/cmd/mstorectl/util.go similarity index 72% rename from cmd/mstorectl/command/util.go rename to cmd/mstorectl/util.go index 9d691af..78238ac 100644 --- a/cmd/mstorectl/command/util.go +++ b/cmd/mstorectl/util.go @@ -7,7 +7,7 @@ * Distribution of this work is permitted, but commercial use and * modifications are strictly prohibited. */ -package command +package main import ( "fmt" @@ -15,16 +15,17 @@ import ( "path/filepath" "github.com/spf13/cobra" - "sigs.k8s.io/yaml" + "mstore/cmd/mstorectl/accountcmd" "mstore/cmd/mstorectl/filecmd" + "mstore/cmd/mstorectl/imagecmd" ) type Util struct { + accountcmd.AccountUtil filecmd.FileUtil - ImageUtil - AccountUtil - GrantUtil + imagecmd.ImageUtil + accountcmd.GrantUtil rootCmd *cobra.Command } @@ -67,20 +68,3 @@ func (util *Util) Exec(args []string) error { func (util *Util) Hello(cmd *cobra.Command, args []string) { fmt.Println("hello, world!") } - -func printResponse(res any, err error) { - type Response struct { - Error bool `json:"error" yaml:"error"` - Message string `json:"message,omitempty" yaml:"message,omitempty"` - Result any `json:"result,omitempty" yaml:"result,omitempty"` - } - resp := Response{} - if err != nil { - resp.Error = true - resp.Message = err.Error() - } else { - resp.Result = res - } - respBytes, _ := yaml.Marshal(resp) - fmt.Printf("---\n%s\n", string(respBytes)) -} diff --git a/pkg/client/account.go b/pkg/client/account.go index bbfa952..25f0037 100644 --- a/pkg/client/account.go +++ b/pkg/client/account.go @@ -14,8 +14,8 @@ import ( "encoding/json" "fmt" + "mstore/app/accoper" "mstore/app/handler" - "mstore/app/operator" "mstore/pkg/descr" ) @@ -27,7 +27,7 @@ func (cli *Client) CreateAccount(ctx context.Context, hosturi, username, passwor if err != nil { return res, err } - operParams := operator.CreateAccountParams{ + operParams := accoper.CreateAccountParams{ Username: username, Password: password, } @@ -40,7 +40,7 @@ func (cli *Client) CreateAccount(ctx context.Context, hosturi, username, passwor return res, err } - operRes := handler.NewResponse[operator.CreateAccountResult]() + operRes := handler.NewResponse[accoper.CreateAccountResult]() err = json.Unmarshal(respBytes, operRes) if err != nil { return res, err @@ -62,7 +62,7 @@ func (cli *Client) GetAccountByID(ctx context.Context, hosturi, accountID string return res, err } - operParams := operator.GetAccountParams{ + operParams := accoper.GetAccountParams{ AccountID: accountID, } paramsJson, err := json.Marshal(operParams) @@ -74,7 +74,7 @@ func (cli *Client) GetAccountByID(ctx context.Context, hosturi, accountID string return res, err } - operRes := handler.NewResponse[operator.GetAccountResult]() + operRes := handler.NewResponse[accoper.GetAccountResult]() err = json.Unmarshal(respBytes, operRes) if err != nil { return res, err @@ -95,7 +95,7 @@ func (cli *Client) GetAccountByName(ctx context.Context, hosturi, username strin if err != nil { return res, err } - operParams := operator.GetAccountParams{ + operParams := accoper.GetAccountParams{ Username: username, } paramsJson, err := json.Marshal(operParams) @@ -107,7 +107,7 @@ func (cli *Client) GetAccountByName(ctx context.Context, hosturi, username strin return res, err } - operRes := handler.NewResponse[operator.GetAccountResult]() + operRes := handler.NewResponse[accoper.GetAccountResult]() err = json.Unmarshal(respBytes, operRes) if err != nil { return res, err @@ -127,7 +127,7 @@ func (cli *Client) UpdateAccountByID(ctx context.Context, hosturi string, accoun if err != nil { return err } - operParams := operator.UpdateAccountParams{ + operParams := accoper.UpdateAccountParams{ AccountID: accountID, NewUsername: newUsername, NewPassword: newPassword, @@ -140,7 +140,7 @@ func (cli *Client) UpdateAccountByID(ctx context.Context, hosturi string, accoun if err != nil { return err } - operRes := handler.NewResponse[operator.UpdateAccountResult]() + operRes := handler.NewResponse[accoper.UpdateAccountResult]() err = json.Unmarshal(respBytes, operRes) if err != nil { return err @@ -158,7 +158,7 @@ func (cli *Client) UpdateAccountByName(ctx context.Context, hosturi, username, n if err != nil { return err } - operParams := operator.UpdateAccountParams{ + operParams := accoper.UpdateAccountParams{ Username: username, NewUsername: newUsername, NewPassword: newPassword, @@ -171,7 +171,7 @@ func (cli *Client) UpdateAccountByName(ctx context.Context, hosturi, username, n if err != nil { return err } - operRes := handler.NewResponse[operator.UpdateAccountResult]() + operRes := handler.NewResponse[accoper.UpdateAccountResult]() err = json.Unmarshal(respBytes, operRes) if err != nil { return err @@ -190,7 +190,7 @@ func (cli *Client) DeleteAccountByName(ctx context.Context, hosturi, username st if err != nil { return err } - operParams := operator.DeleteAccountParams{ + operParams := accoper.DeleteAccountParams{ Username: username, } paramsJson, err := json.Marshal(operParams) @@ -202,7 +202,7 @@ func (cli *Client) DeleteAccountByName(ctx context.Context, hosturi, username st return err } - operRes := handler.NewResponse[operator.DeleteAccountResult]() + operRes := handler.NewResponse[accoper.DeleteAccountResult]() err = json.Unmarshal(respBytes, operRes) if err != nil { return err @@ -221,7 +221,7 @@ func (cli *Client) DeleteAccountByID(ctx context.Context, hosturi, accountID str if err != nil { return err } - operParams := operator.DeleteAccountParams{ + operParams := accoper.DeleteAccountParams{ AccountID: accountID, } paramsJson, err := json.Marshal(operParams) @@ -233,7 +233,7 @@ func (cli *Client) DeleteAccountByID(ctx context.Context, hosturi, accountID str return err } - operRes := handler.NewResponse[operator.DeleteAccountResult]() + operRes := handler.NewResponse[accoper.DeleteAccountResult]() err = json.Unmarshal(respBytes, operRes) if err != nil { return err @@ -253,7 +253,7 @@ func (cli *Client) ListAccounts(ctx context.Context, hosturi string) ([]descr.Ac if err != nil { return res, err } - operParams := operator.ListAccountsParams{} + operParams := accoper.ListAccountsParams{} paramsJson, err := json.Marshal(operParams) if err != nil { return res, err @@ -263,7 +263,7 @@ func (cli *Client) ListAccounts(ctx context.Context, hosturi string) ([]descr.Ac return res, err } - operRes := handler.NewResponse[operator.ListAccountsResult]() + operRes := handler.NewResponse[accoper.ListAccountsResult]() err = json.Unmarshal(respBytes, operRes) if err != nil { return res, err diff --git a/pkg/client/file.go b/pkg/client/attic/file.go similarity index 100% rename from pkg/client/file.go rename to pkg/client/attic/file.go diff --git a/pkg/client/fileaux.go b/pkg/client/attic/fileaux.go similarity index 100% rename from pkg/client/fileaux.go rename to pkg/client/attic/fileaux.go diff --git a/pkg/client/grant.go b/pkg/client/grant.go index f962d00..5e98da9 100644 --- a/pkg/client/grant.go +++ b/pkg/client/grant.go @@ -14,8 +14,8 @@ import ( "encoding/json" "fmt" + "mstore/app/accoper" "mstore/app/handler" - "mstore/app/operator" "mstore/pkg/descr" ) @@ -27,7 +27,7 @@ func (cli *Client) CreateGrantByAccountID(ctx context.Context, hosturi string, a if err != nil { return res, err } - operParams := operator.CreateGrantParams{ + operParams := accoper.CreateGrantParams{ AccountID: accountID, Right: right, Pattern: pattern, @@ -41,7 +41,7 @@ func (cli *Client) CreateGrantByAccountID(ctx context.Context, hosturi string, a return res, err } - operRes := handler.NewResponse[operator.CreateGrantResult]() + operRes := handler.NewResponse[accoper.CreateGrantResult]() err = json.Unmarshal(respBytes, operRes) if err != nil { return res, err @@ -62,7 +62,7 @@ func (cli *Client) CreateGrantByUsername(ctx context.Context, hosturi, username if err != nil { return res, err } - operParams := operator.CreateGrantParams{ + operParams := accoper.CreateGrantParams{ Username: username, Right: right, Pattern: pattern, @@ -76,7 +76,7 @@ func (cli *Client) CreateGrantByUsername(ctx context.Context, hosturi, username return res, err } - operRes := handler.NewResponse[operator.CreateGrantResult]() + operRes := handler.NewResponse[accoper.CreateGrantResult]() err = json.Unmarshal(respBytes, operRes) if err != nil { return res, err @@ -97,7 +97,7 @@ func (cli *Client) GetGrant(ctx context.Context, hosturi, grantID string) (*desc if err != nil { return res, err } - operParams := operator.GetGrantParams{ + operParams := accoper.GetGrantParams{ GrantID: grantID, } paramsJson, err := json.Marshal(operParams) @@ -109,7 +109,7 @@ func (cli *Client) GetGrant(ctx context.Context, hosturi, grantID string) (*desc return res, err } - operRes := handler.NewResponse[operator.GetGrantResult]() + operRes := handler.NewResponse[accoper.GetGrantResult]() err = json.Unmarshal(respBytes, operRes) if err != nil { return res, err @@ -129,7 +129,7 @@ func (cli *Client) UpdateGrant(ctx context.Context, hosturi, grantID, newPattern if err != nil { return err } - operParams := operator.UpdateGrantParams{ + operParams := accoper.UpdateGrantParams{ GrantID: grantID, NewPattern: newPattern, } @@ -141,7 +141,7 @@ func (cli *Client) UpdateGrant(ctx context.Context, hosturi, grantID, newPattern if err != nil { return err } - operRes := handler.NewResponse[operator.UpdateGrantResult]() + operRes := handler.NewResponse[accoper.UpdateGrantResult]() err = json.Unmarshal(respBytes, operRes) if err != nil { return err @@ -160,7 +160,7 @@ func (cli *Client) DeleteGrant(ctx context.Context, hosturi, grantID string) err if err != nil { return err } - operParams := operator.DeleteGrantParams{ + operParams := accoper.DeleteGrantParams{ GrantID: grantID, } paramsJson, err := json.Marshal(operParams) @@ -172,7 +172,7 @@ func (cli *Client) DeleteGrant(ctx context.Context, hosturi, grantID string) err return err } - operRes := handler.NewResponse[operator.DeleteGrantResult]() + operRes := handler.NewResponse[accoper.DeleteGrantResult]() err = json.Unmarshal(respBytes, operRes) if err != nil { return err @@ -192,7 +192,7 @@ func (cli *Client) ListGrantsByAccountID(ctx context.Context, hosturi, accountID if err != nil { return res, err } - operParams := operator.ListGrantsParams{ + operParams := accoper.ListGrantsParams{ AccountID: accountID, } paramsJson, err := json.Marshal(operParams) @@ -204,7 +204,7 @@ func (cli *Client) ListGrantsByAccountID(ctx context.Context, hosturi, accountID return res, err } - operRes := handler.NewResponse[operator.ListGrantsResult]() + operRes := handler.NewResponse[accoper.ListGrantsResult]() err = json.Unmarshal(respBytes, operRes) if err != nil { return res, err @@ -225,7 +225,7 @@ func (cli *Client) ListGrantsByUsername(ctx context.Context, hosturi, username s if err != nil { return res, err } - operParams := operator.ListGrantsParams{ + operParams := accoper.ListGrantsParams{ Username: username, } paramsJson, err := json.Marshal(operParams) @@ -237,7 +237,7 @@ func (cli *Client) ListGrantsByUsername(ctx context.Context, hosturi, username s return res, err } - operRes := handler.NewResponse[operator.ListGrantsResult]() + operRes := handler.NewResponse[accoper.ListGrantsResult]() err = json.Unmarshal(respBytes, operRes) if err != nil { return res, err diff --git a/pkg/client/servaux.go b/pkg/client/servaux.go new file mode 100644 index 0000000..22e0cf1 --- /dev/null +++ b/pkg/client/servaux.go @@ -0,0 +1,37 @@ +/* + * Copyright 2026 Oleg Borodin + * + * This work is published and licensed under a Creative Commons + * Attribution-NonCommercial-NoDerivatives 4.0 International License. + * + * Distribution of this work is permitted, but commercial use and + * modifications are strictly prohibited. + */ +package client + +import ( + "net/url" + "path" + "strings" +) + +func repackServiceURI(fileuri string) (string, string, string, error) { + var err error + var res, username, password string + if !strings.Contains(fileuri, "://") { + fileuri = "https://" + fileuri + } + uri, err := url.Parse(fileuri) + if err != nil { + return res, username, password, err + } + uri.Path = path.Clean(uri.Path) + if uri.User != nil { + username = uri.User.Username() + password, _ = uri.User.Password() + } + uri.User = nil + //uri.Scheme = "https" + res = uri.String() + return res, username, password, err +}