reorder some source files

This commit is contained in:
2026-03-11 09:54:17 +02:00
parent 30fe237657
commit f4c9948c61
27 changed files with 283 additions and 2403 deletions
+1
View File
@@ -21,3 +21,4 @@ DIST
*.rpm *.rpm
mstored.service mstored.service
variant.go variant.go
initrc/mstored
+21 -11
View File
@@ -7,10 +7,10 @@ bin_PROGRAMS = mstorectl
sbin_PROGRAMS = mstored sbin_PROGRAMS = mstored
mstorectl_SOURCES = \ mstorectl_SOURCES = \
cmd/mstorectl/main.go \ cmd/mstorectl/main.go
cmd/mstorectl/util.go
EXTRA_mstorectl_SOURCES = \ EXTRA_mstorectl_SOURCES = \
cmd/mstorectl/util/util.go \
cmd/mstorectl/accountcmd/acccmd.go \ cmd/mstorectl/accountcmd/acccmd.go \
cmd/mstorectl/accountcmd/createacc.go \ cmd/mstorectl/accountcmd/createacc.go \
cmd/mstorectl/accountcmd/creategrant.go \ cmd/mstorectl/accountcmd/creategrant.go \
@@ -34,8 +34,15 @@ EXTRA_mstorectl_SOURCES = \
cmd/mstorectl/filecmd/listcolls.go \ cmd/mstorectl/filecmd/listcolls.go \
cmd/mstorectl/filecmd/listfiles.go \ cmd/mstorectl/filecmd/listfiles.go \
cmd/mstorectl/filecmd/putfile.go \ cmd/mstorectl/filecmd/putfile.go \
cmd/mstorectl/imagecmd/catimages.go \
cmd/mstorectl/imagecmd/delimage.go \
cmd/mstorectl/imagecmd/imagecmd.go \ cmd/mstorectl/imagecmd/imagecmd.go \
cmd/mstorectl/imagecmd/printresp.go cmd/mstorectl/imagecmd/imageman.go \
cmd/mstorectl/imagecmd/imagetags.go \
cmd/mstorectl/imagecmd/printresp.go \
cmd/mstorectl/imagecmd/gcrimageconf.go \
cmd/mstorectl/imagecmd/gcrpullimage.go \
cmd/mstorectl/imagecmd/gcrpushimage.go
mstored_SOURCES = cmd/mstored/main.go mstored_SOURCES = cmd/mstored/main.go
@@ -149,12 +156,12 @@ EXTRA_mstored_SOURCES += \
pkg/auxutar/utar.go \ pkg/auxutar/utar.go \
pkg/auxuuid/uuid.go \ pkg/auxuuid/uuid.go \
pkg/auxx509/x509cert.go \ pkg/auxx509/x509cert.go \
pkg/client/client.go \ pkg/gcrcli/client.go \
pkg/client/imageaux.go \ pkg/gcrcli/imageaux.go \
pkg/client/imagedelete.go \ pkg/gcrcli/imagedelete.go \
pkg/client/imageinfo.go \ pkg/gcrcli/imageinfo.go \
pkg/client/imagepull.go \ pkg/gcrcli/imagepull.go \
pkg/client/imagepush.go \ pkg/gcrcli/imagepush.go \
pkg/descr/account.go \ pkg/descr/account.go \
pkg/descr/blob.go \ pkg/descr/blob.go \
pkg/descr/file.go \ pkg/descr/file.go \
@@ -179,16 +186,19 @@ EXTRA_mstored_SOURCES += \
pkg/repocli/delblob.go \ pkg/repocli/delblob.go \
pkg/repocli/delman.go \ pkg/repocli/delman.go \
pkg/repocli/getblob.go \ pkg/repocli/getblob.go \
pkg/repocli/getcatalog.go \
pkg/repocli/getman.go \ pkg/repocli/getman.go \
pkg/repocli/getreferers.go \
pkg/repocli/gettags.go \
pkg/repocli/gettoken.go \ pkg/repocli/gettoken.go \
pkg/repocli/getupload.go \ pkg/repocli/getupload.go \
pkg/repocli/imager.go \ pkg/repocli/imager.go \
pkg/repocli/manexist.go \ pkg/repocli/manexist.go \
pkg/repocli/patchupload.go \ pkg/repocli/patchupload.go \
pkg/repocli/pullimage.go \ pkg/repocli/pullimage.go \
pkg/repocli/pushimage.go \
pkg/repocli/putman.go \ pkg/repocli/putman.go \
pkg/repocli/putupload.go \ pkg/repocli/putupload.go \
pkg/repocli/reference.go \
pkg/repocli/referer.go \ pkg/repocli/referer.go \
pkg/repocli/uuid.go \ pkg/repocli/uuid.go \
pkg/terms/terms.go pkg/terms/terms.go
@@ -353,7 +363,7 @@ endif
clean-local: clean-local:
$(FIND) $(CWD) -name '*~' | $(XARGS) rm -f $(FIND) $(CWD) -name '*~' | $(XARGS) rm -f
rm -rf autom4te.cache rm -rf autom4te.cache
rm -f cmd/mstored/istored rm -f cmd/mstored/mstored
rm -f cmd/mstorectl/mstorectl rm -f cmd/mstorectl/mstorectl
# rm -rf tmp/ # rm -rf tmp/
+22 -14
View File
@@ -353,10 +353,10 @@ AUTOMAKE_OPTIONS = foreign no-dependencies no-installinfo
SUBDIRS = mans SUBDIRS = mans
GOFLAGS = -v GOFLAGS = -v
mstorectl_SOURCES = \ mstorectl_SOURCES = \
cmd/mstorectl/main.go \ cmd/mstorectl/main.go
cmd/mstorectl/util.go
EXTRA_mstorectl_SOURCES = \ EXTRA_mstorectl_SOURCES = \
cmd/mstorectl/util/util.go \
cmd/mstorectl/accountcmd/acccmd.go \ cmd/mstorectl/accountcmd/acccmd.go \
cmd/mstorectl/accountcmd/createacc.go \ cmd/mstorectl/accountcmd/createacc.go \
cmd/mstorectl/accountcmd/creategrant.go \ cmd/mstorectl/accountcmd/creategrant.go \
@@ -380,8 +380,15 @@ EXTRA_mstorectl_SOURCES = \
cmd/mstorectl/filecmd/listcolls.go \ cmd/mstorectl/filecmd/listcolls.go \
cmd/mstorectl/filecmd/listfiles.go \ cmd/mstorectl/filecmd/listfiles.go \
cmd/mstorectl/filecmd/putfile.go \ cmd/mstorectl/filecmd/putfile.go \
cmd/mstorectl/imagecmd/catimages.go \
cmd/mstorectl/imagecmd/delimage.go \
cmd/mstorectl/imagecmd/imagecmd.go \ cmd/mstorectl/imagecmd/imagecmd.go \
cmd/mstorectl/imagecmd/printresp.go cmd/mstorectl/imagecmd/imageman.go \
cmd/mstorectl/imagecmd/imagetags.go \
cmd/mstorectl/imagecmd/printresp.go \
cmd/mstorectl/imagecmd/gcrimageconf.go \
cmd/mstorectl/imagecmd/gcrpullimage.go \
cmd/mstorectl/imagecmd/gcrpushimage.go
mstored_SOURCES = cmd/mstored/main.go mstored_SOURCES = cmd/mstored/main.go
EXTRA_mstored_SOURCES = cmd/mstored/starter/starter.go \ EXTRA_mstored_SOURCES = cmd/mstored/starter/starter.go \
@@ -431,10 +438,10 @@ EXTRA_mstored_SOURCES = cmd/mstored/starter/starter.go \
pkg/auxtool/fileex.go pkg/auxtool/randstr.go \ pkg/auxtool/fileex.go pkg/auxtool/randstr.go \
pkg/auxtool/tmpfile.go pkg/auxtool/unixnow.go \ pkg/auxtool/tmpfile.go pkg/auxtool/unixnow.go \
pkg/auxutar/utar.go pkg/auxuuid/uuid.go \ pkg/auxutar/utar.go pkg/auxuuid/uuid.go \
pkg/auxx509/x509cert.go pkg/client/client.go \ pkg/auxx509/x509cert.go pkg/gcrcli/client.go \
pkg/client/imageaux.go pkg/client/imagedelete.go \ pkg/gcrcli/imageaux.go pkg/gcrcli/imagedelete.go \
pkg/client/imageinfo.go pkg/client/imagepull.go \ pkg/gcrcli/imageinfo.go pkg/gcrcli/imagepull.go \
pkg/client/imagepush.go pkg/descr/account.go pkg/descr/blob.go \ pkg/gcrcli/imagepush.go pkg/descr/account.go pkg/descr/blob.go \
pkg/descr/file.go pkg/descr/grant.go pkg/descr/manifest.go \ pkg/descr/file.go pkg/descr/grant.go pkg/descr/manifest.go \
pkg/descr/server.go pkg/digest/digest.go \ pkg/descr/server.go pkg/digest/digest.go \
pkg/filecli/authbas.go pkg/filecli/client.go \ pkg/filecli/authbas.go pkg/filecli/client.go \
@@ -445,13 +452,14 @@ EXTRA_mstored_SOURCES = cmd/mstored/starter/starter.go \
pkg/filecli/referer.go pkg/repocli/blobexist.go \ pkg/filecli/referer.go pkg/repocli/blobexist.go \
pkg/repocli/client.go pkg/repocli/copywctx.go \ pkg/repocli/client.go pkg/repocli/copywctx.go \
pkg/repocli/delblob.go pkg/repocli/delman.go \ pkg/repocli/delblob.go pkg/repocli/delman.go \
pkg/repocli/getblob.go pkg/repocli/getman.go \ pkg/repocli/getblob.go pkg/repocli/getcatalog.go \
pkg/repocli/gettoken.go pkg/repocli/getupload.go \ pkg/repocli/getman.go pkg/repocli/getreferers.go \
pkg/repocli/imager.go pkg/repocli/manexist.go \ pkg/repocli/gettags.go pkg/repocli/gettoken.go \
pkg/repocli/patchupload.go pkg/repocli/pullimage.go \ pkg/repocli/getupload.go pkg/repocli/imager.go \
pkg/repocli/manexist.go pkg/repocli/patchupload.go \
pkg/repocli/pullimage.go pkg/repocli/pushimage.go \
pkg/repocli/putman.go pkg/repocli/putupload.go \ pkg/repocli/putman.go pkg/repocli/putupload.go \
pkg/repocli/reference.go pkg/repocli/referer.go \ pkg/repocli/referer.go pkg/repocli/uuid.go pkg/terms/terms.go
pkg/repocli/uuid.go pkg/terms/terms.go
CWD = $(shell pwd) CWD = $(shell pwd)
EXTRA_DIST = vendor/* \ EXTRA_DIST = vendor/* \
\ \
@@ -1189,7 +1197,7 @@ install-data-local:
clean-local: clean-local:
$(FIND) $(CWD) -name '*~' | $(XARGS) rm -f $(FIND) $(CWD) -name '*~' | $(XARGS) rm -f
rm -rf autom4te.cache rm -rf autom4te.cache
rm -f cmd/mstored/istored rm -f cmd/mstored/mstored
rm -f cmd/mstorectl/mstorectl rm -f cmd/mstorectl/mstorectl
# rm -rf tmp/ # rm -rf tmp/
-376
View File
@@ -1,376 +0,0 @@
/*
* Copyright 2026 Oleg Borodin <onborodin@gmail.com>
*
* 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 test
import (
"github.com/stretchr/testify/require"
"bytes"
"encoding/json"
"fmt"
"io"
"math/rand"
"net/http"
"net/http/httptest"
"testing"
"mstore/app/handler"
"mstore/app/operator"
"mstore/app/router"
"mstore/app/server"
"mstore/pkg/auxhttp"
)
func TestAccountOperations(t *testing.T) {
var err error
fmt.Printf("=== MakeServer ===\n")
srv, err := server.NewServer()
require.NoError(t, err)
var srvport int64 = 10240 + rand.Int63n(1024)
srvdir := t.TempDir()
//srvaddr := fmt.Sprintf("127.0.0.1:%d", srvport)
{
err = srv.Configure()
require.NoError(t, err)
err = srv.Configure()
require.NoError(t, err)
useTmpdir := true
if useTmpdir {
srv.SetDatadir(srvdir)
srv.SetLogdir(srvdir)
srv.SetRundir(srvdir)
}
srv.SetPort(srvport)
err = srv.Build()
require.NoError(t, err)
}
username := "testuser"
password := "testpass"
{
fmt.Printf("=== ServiceHello ===\n")
reqPath := "/service/hello"
routePath := "/service/hello"
rout := router.NewRouter()
hand := srv.Handler()
rout.Get(routePath, hand.SendHello)
request, err := http.NewRequest("GET", reqPath, nil)
require.NoError(t, err)
basic := auxhttp.EncodeBasicAuth(username, password)
request.Header.Add("Authorization", basic)
recorder := httptest.NewRecorder()
rout.ServeHTTP(recorder, request)
require.Equal(t, http.StatusOK, recorder.Code)
fmt.Printf("Response code: %d\n", recorder.Code)
bodyReader := recorder.Body
bodyBytes, err := io.ReadAll(bodyReader)
fmt.Printf("Response body: %s\n", string(bodyBytes))
}
return
var accountID string
var accountName = "testname1"
{
fmt.Printf("=== CreateAccount ===\n")
reqpath := `/v3/api/account/create`
routepath := `/v3/api/account/create`
rout := router.NewRouter()
hand := srv.Handler()
require.NotNil(t, hand)
req := operator.CreateAccountParams{
Username: accountName,
Password: "testpass",
}
reqdata, err := json.Marshal(req)
require.NoError(t, err)
reqsize := len(reqdata)
reqsrc := bytes.NewReader(reqdata)
request, err := http.NewRequest("POST", reqpath, reqsrc)
require.NoError(t, err)
request.ContentLength = int64(reqsize)
request.Header.Set("Content-Type", "application/json")
recorder := httptest.NewRecorder()
rout.Post(routepath, hand.CreateAccount)
rout.ServeHTTP(recorder, request)
fmt.Printf("Response code: %d\n", recorder.Code)
bodyReader := recorder.Body
bodyBytes, err := io.ReadAll(bodyReader)
fmt.Printf("Response body: %s\n", string(bodyBytes))
require.Equal(t, http.StatusOK, recorder.Code)
jsonBuffer := bytes.NewBuffer(nil)
err = json.Indent(jsonBuffer, bodyBytes, "", " ")
require.NoError(t, err)
fmt.Printf("Formatted body: \n%s\n", jsonBuffer.String())
resp := handler.Response[operator.CreateAccountResult]{}
err = json.Unmarshal(bodyBytes, &resp)
require.False(t, resp.Error)
require.Equal(t, len(resp.Result.AccountID), 36)
accountID = resp.Result.AccountID
}
fmt.Printf("AccountID: %s\n", accountID)
{
fmt.Printf("=== UpdateAccount ===\n")
reqpath := `/v3/api/account/create`
routepath := `/v3/api/account/create`
rout := router.NewRouter()
hand := srv.Handler()
require.NotNil(t, hand)
req := operator.UpdateAccountParams{
AccountID: accountID,
NewPassword: "newpass",
}
reqdata, err := json.Marshal(req)
require.NoError(t, err)
reqsize := len(reqdata)
reqsrc := bytes.NewReader(reqdata)
request, err := http.NewRequest("POST", reqpath, reqsrc)
require.NoError(t, err)
request.ContentLength = int64(reqsize)
request.Header.Set("Content-Type", "application/json")
recorder := httptest.NewRecorder()
rout.Post(routepath, hand.UpdateAccount)
rout.ServeHTTP(recorder, request)
fmt.Printf("Response code: %d\n", recorder.Code)
bodyReader := recorder.Body
bodyBytes, err := io.ReadAll(bodyReader)
fmt.Printf("Response body: %s\n", string(bodyBytes))
require.Equal(t, http.StatusOK, recorder.Code)
jsonBuffer := bytes.NewBuffer(nil)
err = json.Indent(jsonBuffer, bodyBytes, "", " ")
require.NoError(t, err)
fmt.Printf("Formatted body: \n%s\n", jsonBuffer.String())
resp := handler.Response[operator.UpdateAccountResult]{}
err = json.Unmarshal(bodyBytes, &resp)
require.False(t, resp.Error)
}
{
fmt.Printf("=== GetAccount ===\n")
reqpath := `/v3/api/account/get`
routepath := `/v3/api/account/get`
rout := router.NewRouter()
hand := srv.Handler()
require.NotNil(t, hand)
req := operator.GetAccountParams{
AccountID: accountID,
}
reqdata, err := json.Marshal(req)
require.NoError(t, err)
reqsize := len(reqdata)
reqsrc := bytes.NewReader(reqdata)
request, err := http.NewRequest("POST", reqpath, reqsrc)
require.NoError(t, err)
request.ContentLength = int64(reqsize)
request.Header.Set("Content-Type", "application/json")
recorder := httptest.NewRecorder()
rout.Post(routepath, hand.GetAccount)
rout.ServeHTTP(recorder, request)
fmt.Printf("Response code: %d\n", recorder.Code)
bodyReader := recorder.Body
bodyBytes, err := io.ReadAll(bodyReader)
fmt.Printf("Response body: %s\n", string(bodyBytes))
require.Equal(t, http.StatusOK, recorder.Code)
jsonBuffer := bytes.NewBuffer(nil)
err = json.Indent(jsonBuffer, bodyBytes, "", " ")
require.NoError(t, err)
fmt.Printf("Formatted body: \n%s\n", jsonBuffer.String())
resp := handler.Response[operator.GetAccountResult]{}
err = json.Unmarshal(bodyBytes, &resp)
require.False(t, resp.Error)
require.Equal(t, resp.Result.Account.Username, accountName)
}
{
fmt.Printf("=== ListAccounts ===\n")
reqpath := `/v3/api/accounts/list`
routepath := `/v3/api/accounts/list`
rout := router.NewRouter()
hand := srv.Handler()
require.NotNil(t, hand)
req := operator.ListAccountsParams{}
reqdata, err := json.Marshal(req)
require.NoError(t, err)
reqsize := len(reqdata)
reqsrc := bytes.NewReader(reqdata)
request, err := http.NewRequest("POST", reqpath, reqsrc)
require.NoError(t, err)
request.ContentLength = int64(reqsize)
request.Header.Set("Content-Type", "application/json")
recorder := httptest.NewRecorder()
rout.Post(routepath, hand.ListAccounts)
rout.ServeHTTP(recorder, request)
fmt.Printf("Response code: %d\n", recorder.Code)
bodyReader := recorder.Body
bodyBytes, err := io.ReadAll(bodyReader)
fmt.Printf("Response body: %s\n", string(bodyBytes))
require.Equal(t, http.StatusOK, recorder.Code)
jsonBuffer := bytes.NewBuffer(nil)
err = json.Indent(jsonBuffer, bodyBytes, "", " ")
require.NoError(t, err)
fmt.Printf("Formatted body: \n%s\n", jsonBuffer.String())
resp := handler.Response[operator.ListAccountsResult]{}
err = json.Unmarshal(bodyBytes, &resp)
require.False(t, resp.Error)
require.Equal(t, len(resp.Result.Accounts), 1)
require.Equal(t, resp.Result.Accounts[0].Username, accountName)
}
{
fmt.Printf("=== DeleteAccount ===\n")
reqpath := `/v3/api/account/delete`
routepath := `/v3/api/account/delete`
rout := router.NewRouter()
hand := srv.Handler()
require.NotNil(t, hand)
req := operator.DeleteAccountParams{
AccountID: accountID,
}
reqdata, err := json.Marshal(req)
require.NoError(t, err)
reqsize := len(reqdata)
reqsrc := bytes.NewReader(reqdata)
request, err := http.NewRequest("POST", reqpath, reqsrc)
require.NoError(t, err)
request.ContentLength = int64(reqsize)
request.Header.Set("Content-Type", "application/json")
recorder := httptest.NewRecorder()
rout.Post(routepath, hand.DeleteAccount)
rout.ServeHTTP(recorder, request)
fmt.Printf("Response code: %d\n", recorder.Code)
bodyReader := recorder.Body
bodyBytes, err := io.ReadAll(bodyReader)
fmt.Printf("Response body: %s\n", string(bodyBytes))
require.Equal(t, http.StatusOK, recorder.Code)
jsonBuffer := bytes.NewBuffer(nil)
err = json.Indent(jsonBuffer, bodyBytes, "", " ")
require.NoError(t, err)
fmt.Printf("Formatted body: \n%s\n", jsonBuffer.String())
resp := handler.Response[operator.DeleteAccountResult]{}
err = json.Unmarshal(bodyBytes, &resp)
require.False(t, resp.Error)
}
{
fmt.Printf("=== ListAccounts ===\n")
reqpath := `/v3/api/accounts/list`
routepath := `/v3/api/accounts/list`
rout := router.NewRouter()
hand := srv.Handler()
require.NotNil(t, hand)
req := operator.ListAccountsParams{}
reqdata, err := json.Marshal(req)
require.NoError(t, err)
reqsize := len(reqdata)
reqsrc := bytes.NewReader(reqdata)
request, err := http.NewRequest("POST", reqpath, reqsrc)
require.NoError(t, err)
request.ContentLength = int64(reqsize)
request.Header.Set("Content-Type", "application/json")
recorder := httptest.NewRecorder()
rout.Post(routepath, hand.ListAccounts)
rout.ServeHTTP(recorder, request)
fmt.Printf("Response code: %d\n", recorder.Code)
bodyReader := recorder.Body
bodyBytes, err := io.ReadAll(bodyReader)
fmt.Printf("Response body: %s\n", string(bodyBytes))
require.Equal(t, http.StatusOK, recorder.Code)
jsonBuffer := bytes.NewBuffer(nil)
err = json.Indent(jsonBuffer, bodyBytes, "", " ")
require.NoError(t, err)
fmt.Printf("Formatted body: \n%s\n", jsonBuffer.String())
resp := handler.Response[operator.ListAccountsResult]{}
err = json.Unmarshal(bodyBytes, &resp)
require.False(t, resp.Error)
require.Equal(t, len(resp.Result.Accounts), 0)
}
}
-226
View File
@@ -1,226 +0,0 @@
/*
* Copyright 2026 Oleg Borodin <onborodin@gmail.com>
*
* 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 test
import (
"github.com/stretchr/testify/require"
"bytes"
"encoding/hex"
"fmt"
"io"
"math/rand"
"net/http"
"net/http/httptest"
"path/filepath"
"strconv"
"testing"
"mstore/app/router"
"mstore/app/server"
)
func xxxTestFileOperations(t *testing.T) {
var err error
fmt.Printf("=== MakeServer ===\n")
srv, err := server.NewServer()
require.NoError(t, err)
var srvport int64 = 10240 + rand.Int63n(1024)
srvdir := t.TempDir()
filename := `bare.bin`
{
err = srv.Configure()
require.NoError(t, err)
err = srv.Configure()
require.NoError(t, err)
useTmpdir := false
if useTmpdir {
srv.SetDatadir(srvdir)
srv.SetLogdir(srvdir)
srv.SetRundir(srvdir)
}
srv.SetPort(srvport)
err = srv.Build()
require.NoError(t, err)
}
{
fmt.Printf("=== ServiceHello ===\n")
reqPath := "/service/hello"
routePath := "/service/hello"
rout := router.NewRouter()
hand := srv.Handler()
rout.Get(routePath, hand.SendHello)
request, err := http.NewRequest("GET", reqPath, nil)
require.NoError(t, err)
recorder := httptest.NewRecorder()
rout.ServeHTTP(recorder, request)
require.Equal(t, http.StatusOK, recorder.Code)
fmt.Printf("Response code: %d\n", recorder.Code)
bodyReader := recorder.Body
bodyBytes, err := io.ReadAll(bodyReader)
fmt.Printf("Response body: %s\n", string(bodyBytes))
}
{
fmt.Printf("=== PutFile ===\n")
reqPath := `/v3/api/file/` + filename
routePath := `/v3/api/file/{filepath}`
rout := router.NewRouter()
hand := srv.Handler()
require.NotNil(t, hand)
rout.Put(routePath, hand.PutFile)
datasize := 64
filedata := make([]byte, datasize)
_, err = rand.Read(filedata)
require.NoError(t, err)
filedata = []byte(hex.EncodeToString(filedata))
datasize *= 2
source := bytes.NewReader(filedata)
request, err := http.NewRequest("PUT", reqPath, source)
require.NoError(t, err)
request.ContentLength = int64(datasize)
request.Header.Set("Content-Type", "application/octet-stream")
request.Header.Set("Content-Size", strconv.FormatInt(int64(datasize), 10))
recorder := httptest.NewRecorder()
rout.ServeHTTP(recorder, request)
require.Equal(t, http.StatusOK, recorder.Code)
fmt.Printf("Response code: %d\n", recorder.Code)
bodyReader := recorder.Body
bodyBytes, err := io.ReadAll(bodyReader)
fmt.Printf("Response body: %s\n", string(bodyBytes))
}
{
fmt.Printf("=== FileInfo ===\n")
reqPath := filepath.Join(`/v3/api/file`, filename)
routePath := `/v3/api/file/{filepath}`
rout := router.NewRouter()
hand := srv.Handler()
require.NotNil(t, hand)
rout.Head(routePath, hand.FileInfo)
request, err := http.NewRequest("HEAD", reqPath, nil)
require.NoError(t, err)
recorder := httptest.NewRecorder()
rout.ServeHTTP(recorder, request)
require.Equal(t, http.StatusOK, recorder.Code)
fmt.Printf("Response code: %d\n", recorder.Code)
bodyReader := recorder.Body
bodyBytes, err := io.ReadAll(bodyReader)
fmt.Printf("Response body: %s\n", string(bodyBytes))
}
{
fmt.Printf("=== GetFile ===\n")
reqPath := filepath.Join(`/v3/api/file`, filename)
routePath := `/v3/api/file/{filepath}`
rout := router.NewRouter()
hand := srv.Handler()
require.NotNil(t, hand)
rout.Get(routePath, hand.GetFile)
request, err := http.NewRequest("GET", reqPath, nil)
require.NoError(t, err)
recorder := httptest.NewRecorder()
rout.ServeHTTP(recorder, request)
require.Equal(t, http.StatusOK, recorder.Code)
fmt.Printf("Response code: %d\n", recorder.Code)
bodyReader := recorder.Body
bodyBytes, err := io.ReadAll(bodyReader)
fmt.Printf("Response body: %s\n", string(bodyBytes))
}
return
{
fmt.Printf("=== DeleteFile ===\n")
reqPath := filepath.Join(`/v3/api/file`, filename)
routePath := `/v3/api/file/{filepath}`
rout := router.NewRouter()
hand := srv.Handler()
require.NotNil(t, hand)
rout.Delete(routePath, hand.DeleteFile)
request, err := http.NewRequest("DELETE", reqPath, nil)
require.NoError(t, err)
recorder := httptest.NewRecorder()
rout.ServeHTTP(recorder, request)
require.Equal(t, http.StatusOK, recorder.Code)
fmt.Printf("Response code: %d\n", recorder.Code)
bodyReader := recorder.Body
bodyBytes, err := io.ReadAll(bodyReader)
fmt.Printf("Response body: %s\n", string(bodyBytes))
}
{
fmt.Printf("=== FileNotExists ===\n")
reqPath := filepath.Join(`/v3/api/file`, filename)
routePath := `/v3/api/file/{filepath}`
rout := router.NewRouter()
hand := srv.Handler()
require.NotNil(t, hand)
rout.Head(routePath, hand.FileInfo)
request, err := http.NewRequest("HEAD", reqPath, nil)
require.NoError(t, err)
recorder := httptest.NewRecorder()
rout.ServeHTTP(recorder, request)
require.Equal(t, http.StatusNotFound, recorder.Code)
fmt.Printf("Response code: %d\n", recorder.Code)
bodyReader := recorder.Body
bodyBytes, err := io.ReadAll(bodyReader)
fmt.Printf("Response body: %s\n", string(bodyBytes))
}
}
+59
View File
@@ -0,0 +1,59 @@
/*
* Copyright 2026 Oleg Borodin <onborodin@gmail.com>
*
* 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 (
"context"
"time"
"github.com/spf13/cobra"
"mstore/pkg/gcrcli"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
// ImageConfig
type ImageConfigParams struct {
Imagepath string
}
type ImageConfigResult struct {
ImageConfig *ocispec.Image `json:"imageConfig"`
}
func (util *ImageUtil) ImageConfig(cmd *cobra.Command, args []string) {
util.imageConfigParams.Imagepath = args[0]
res, err := util.imageConfig(&util.commonImageParams, &util.imageConfigParams)
printResponse(res, err)
}
func (util *ImageUtil) imageConfig(common *CommonImageParams, params *ImageConfigParams) (*ImageConfigResult, error) {
var err error
res := &ImageConfigResult{
ImageConfig: &ocispec.Image{},
}
ctx := context.Background()
cli := gcrcli.NewClient(common.SkipTLSVerify)
timeout := time.Duration(common.Timeout) * time.Second
params.Imagepath, err = packUserinfo(params.Imagepath, common.Username, common.Password)
if err != nil {
return res, err
}
ctx, _ = context.WithTimeout(ctx, timeout)
opres, err := cli.ImageConfig(ctx, params.Imagepath)
if err != nil {
return res, err
}
res.ImageConfig = opres
return res, err
}
+65
View File
@@ -0,0 +1,65 @@
/*
* Copyright 2026 Oleg Borodin <onborodin@gmail.com>
*
* 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 (
"context"
"os"
"time"
"github.com/spf13/cobra"
"mstore/pkg/gcrcli"
)
// PullImage
type PullImageParams struct {
Imagepath string
Filepath string
}
type PullImageResult struct {
Filepath string `json:"filepath"`
Size int64 `json:"size"`
}
func (util *ImageUtil) PullImage(cmd *cobra.Command, args []string) {
util.pullImageParams.Imagepath = args[0]
util.pullImageParams.Filepath = args[1]
res, err := util.pullImage(&util.commonImageParams, &util.pullImageParams)
printResponse(res, err)
}
func (util *ImageUtil) pullImage(common *CommonImageParams, params *PullImageParams) (*PullImageResult, error) {
var err error
ctx := context.Background()
res := &PullImageResult{}
cli := gcrcli.NewClient(common.SkipTLSVerify)
timeout := time.Duration(common.Timeout) * time.Second
params.Imagepath, err = packUserinfo(params.Imagepath, common.Username, common.Password)
if err != nil {
return res, err
}
ctx, _ = context.WithTimeout(ctx, timeout)
err = cli.PullImage(ctx, params.Imagepath, params.Filepath)
if err != nil {
return res, err
}
filestat, err := os.Stat(params.Filepath)
if err != nil {
return res, err
}
res.Size = filestat.Size()
res.Filepath = params.Filepath
return res, err
}
+54
View File
@@ -0,0 +1,54 @@
/*
* Copyright 2026 Oleg Borodin <onborodin@gmail.com>
*
* 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 (
"context"
"time"
"github.com/spf13/cobra"
"mstore/pkg/gcrcli"
)
// PushImage
type PushImageParams struct {
Imagepath string
Filepath string
}
type PushImageResult struct{}
func (util *ImageUtil) PushImage(cmd *cobra.Command, args []string) {
util.pushImageParams.Filepath = args[0]
util.pushImageParams.Imagepath = args[1]
res, err := util.pushImage(&util.commonImageParams, &util.pushImageParams)
printResponse(res, err)
}
func (util *ImageUtil) pushImage(common *CommonImageParams, params *PushImageParams) (*PushImageResult, error) {
var err error
ctx := context.Background()
res := &PushImageResult{}
cli := gcrcli.NewClient(common.SkipTLSVerify)
timeout := time.Duration(common.Timeout) * time.Second
params.Imagepath, err = packUserinfo(params.Imagepath, common.Username, common.Password)
if err != nil {
return res, err
}
ctx, _ = context.WithTimeout(ctx, timeout)
err = cli.PushImage(ctx, params.Filepath, params.Imagepath)
if err != nil {
return res, err
}
return res, err
}
+2 -2
View File
@@ -49,13 +49,13 @@ func (util *ImageUtil) imageManifest(common *CommonImageParams, params *ImageMan
} }
timeout := time.Duration(common.Timeout) * time.Second timeout := time.Duration(common.Timeout) * time.Second
ctx, _ := context.WithTimeout(context.Background(), timeout) ctx, _ := context.WithTimeout(context.Background(), timeout)
ref, err := repocli.ParseReference(params.Imagepath) ref, err := repocli.NewReferer(params.Imagepath)
if err != nil { if err != nil {
return res, err return res, err
} }
mw := repocli.NewBasicAuthMiddleware(ref.Userinfo()) mw := repocli.NewBasicAuthMiddleware(ref.Userinfo())
cli := repocli.NewClient(nil, mw) cli := repocli.NewClient(nil, mw)
exists, mime, man, err := cli.GetRawManifest(ctx, ref.Repo()) exists, mime, man, _, err := cli.GetRawManifest(ctx, ref.RawRepo())
if !exists { if !exists {
err = fmt.Errorf("Manifest not found") err = fmt.Errorf("Manifest not found")
return res, err return res, err
+2 -1
View File
@@ -10,12 +10,13 @@
package main package main
import ( import (
"mstore/cmd/mstorectl/util"
"os" "os"
) )
func main() { func main() {
var err error var err error
util := NewUtil() util := util.NewUtil()
err = util.Build() err = util.Build()
if err != nil { if err != nil {
os.Exit(1) os.Exit(1)
@@ -7,7 +7,7 @@
* Distribution of this work is permitted, but commercial use and * Distribution of this work is permitted, but commercial use and
* modifications are strictly prohibited. * modifications are strictly prohibited.
*/ */
package main package util
import ( import (
"fmt" "fmt"
-277
View File
@@ -1,277 +0,0 @@
/*
* Copyright 2026 Oleg Borodin <onborodin@gmail.com>
*
* 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 (
"context"
"encoding/json"
"fmt"
"mstore/app/accoper"
"mstore/app/handler"
"mstore/pkg/descr"
)
func (cli *Client) CreateAccount(ctx context.Context, hosturi, username, password string) (string, error) {
var err error
var res string
apiuri, err := setApiPath(hosturi, "/v3/api/account/create")
if err != nil {
return res, err
}
operParams := accoper.CreateAccountParams{
Username: username,
Password: password,
}
paramsJson, err := json.Marshal(operParams)
if err != nil {
return res, err
}
respBytes, err := doHTTPCall(ctx, cli.skipTLSVerify, apiuri, paramsJson)
if err != nil {
return res, err
}
operRes := handler.NewResponse[accoper.CreateAccountResult]()
err = json.Unmarshal(respBytes, operRes)
if err != nil {
return res, err
}
if operRes.Error {
err = fmt.Errorf("%s", operRes.Message)
return res, err
}
res = operRes.Result.AccountID
return res, err
}
func (cli *Client) GetAccountByID(ctx context.Context, hosturi, accountID string) (*descr.AccountShort, error) {
var err error
res := &descr.AccountShort{}
apipath, err := setApiPath(hosturi, "/v3/api/account/get")
if err != nil {
return res, err
}
operParams := accoper.GetAccountParams{
AccountID: accountID,
}
paramsJson, err := json.Marshal(operParams)
if err != nil {
return res, err
}
respBytes, err := doHTTPCall(ctx, cli.skipTLSVerify, apipath, paramsJson)
if err != nil {
return res, err
}
operRes := handler.NewResponse[accoper.GetAccountResult]()
err = json.Unmarshal(respBytes, operRes)
if err != nil {
return res, err
}
if operRes.Error {
err = fmt.Errorf("%s", operRes.Message)
return res, err
}
res = operRes.Result.Account
return res, err
}
func (cli *Client) GetAccountByName(ctx context.Context, hosturi, username string) (*descr.AccountShort, error) {
var err error
res := &descr.AccountShort{}
apipath, err := setApiPath(hosturi, "/v3/api/account/get")
if err != nil {
return res, err
}
operParams := accoper.GetAccountParams{
Username: username,
}
paramsJson, err := json.Marshal(operParams)
if err != nil {
return res, err
}
respBytes, err := doHTTPCall(ctx, cli.skipTLSVerify, apipath, paramsJson)
if err != nil {
return res, err
}
operRes := handler.NewResponse[accoper.GetAccountResult]()
err = json.Unmarshal(respBytes, operRes)
if err != nil {
return res, err
}
if operRes.Error {
err = fmt.Errorf("%s", operRes.Message)
return res, err
}
res = operRes.Result.Account
return res, err
}
func (cli *Client) UpdateAccountByID(ctx context.Context, hosturi string, accountID, newUsername, newPassword string) error {
var err error
apipath, err := setApiPath(hosturi, "/v3/api/account/update")
if err != nil {
return err
}
operParams := accoper.UpdateAccountParams{
AccountID: accountID,
NewUsername: newUsername,
NewPassword: newPassword,
}
paramsJson, err := json.Marshal(operParams)
if err != nil {
return err
}
respBytes, err := doHTTPCall(ctx, cli.skipTLSVerify, apipath, paramsJson)
if err != nil {
return err
}
operRes := handler.NewResponse[accoper.UpdateAccountResult]()
err = json.Unmarshal(respBytes, operRes)
if err != nil {
return err
}
if operRes.Error {
err = fmt.Errorf("%s", operRes.Message)
return err
}
return err
}
func (cli *Client) UpdateAccountByName(ctx context.Context, hosturi, username, newUsername, newPassword string) error {
var err error
apipath, err := setApiPath(hosturi, "/v3/api/account/update")
if err != nil {
return err
}
operParams := accoper.UpdateAccountParams{
Username: username,
NewUsername: newUsername,
NewPassword: newPassword,
}
paramsJson, err := json.Marshal(operParams)
if err != nil {
return err
}
respBytes, err := doHTTPCall(ctx, cli.skipTLSVerify, apipath, paramsJson)
if err != nil {
return err
}
operRes := handler.NewResponse[accoper.UpdateAccountResult]()
err = json.Unmarshal(respBytes, operRes)
if err != nil {
return err
}
if operRes.Error {
err = fmt.Errorf("%s", operRes.Message)
return err
}
return err
}
func (cli *Client) DeleteAccountByName(ctx context.Context, hosturi, username string) error {
var err error
apipath, err := setApiPath(hosturi, "/v3/api/account/delete")
if err != nil {
return err
}
operParams := accoper.DeleteAccountParams{
Username: username,
}
paramsJson, err := json.Marshal(operParams)
if err != nil {
return err
}
respBytes, err := doHTTPCall(ctx, cli.skipTLSVerify, apipath, paramsJson)
if err != nil {
return err
}
operRes := handler.NewResponse[accoper.DeleteAccountResult]()
err = json.Unmarshal(respBytes, operRes)
if err != nil {
return err
}
if operRes.Error {
err = fmt.Errorf("%s", operRes.Message)
return err
}
return err
}
func (cli *Client) DeleteAccountByID(ctx context.Context, hosturi, accountID string) error {
var err error
apipath, err := setApiPath(hosturi, "/v3/api/account/delete")
if err != nil {
return err
}
operParams := accoper.DeleteAccountParams{
AccountID: accountID,
}
paramsJson, err := json.Marshal(operParams)
if err != nil {
return err
}
respBytes, err := doHTTPCall(ctx, cli.skipTLSVerify, apipath, paramsJson)
if err != nil {
return err
}
operRes := handler.NewResponse[accoper.DeleteAccountResult]()
err = json.Unmarshal(respBytes, operRes)
if err != nil {
return err
}
if operRes.Error {
err = fmt.Errorf("%s", operRes.Message)
return err
}
return err
}
func (cli *Client) ListAccounts(ctx context.Context, hosturi string) ([]descr.AccountShort, error) {
var err error
res := make([]descr.AccountShort, 0)
apipath, err := setApiPath(hosturi, "/v3/api/accounts/list")
if err != nil {
return res, err
}
operParams := accoper.ListAccountsParams{}
paramsJson, err := json.Marshal(operParams)
if err != nil {
return res, err
}
respBytes, err := doHTTPCall(ctx, cli.skipTLSVerify, apipath, paramsJson)
if err != nil {
return res, err
}
operRes := handler.NewResponse[accoper.ListAccountsResult]()
err = json.Unmarshal(respBytes, operRes)
if err != nil {
return res, err
}
if operRes.Error {
err = fmt.Errorf("%s", operRes.Message)
return res, err
}
res = operRes.Result.Accounts
return res, err
}
-417
View File
@@ -1,417 +0,0 @@
/*
* Copyright 2026 Oleg Borodin <onborodin@gmail.com>
*
* 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 (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
"strconv"
"mstore/pkg/auxhttp"
"mstore/pkg/descr"
)
func (cli *Client) FileInfo(ctx context.Context, fileuri string) (bool, *descr.File, error) {
var exists bool
var err error
file := &descr.File{}
fileuri, username, password, err := repackServiceURI(fileuri)
if err != nil {
return exists, file, err
}
fileuri, err = convertFileURI(fileuri)
if err != nil {
return exists, file, err
}
req, err := http.NewRequestWithContext(ctx, http.MethodHead, fileuri, nil)
if err != nil {
return exists, file, err
}
if username != "" && password != "" {
basic := auxhttp.EncodeBasicAuth(username, password)
req.Header.Add("Authorization", basic)
}
client := makeHTTPClient(cli.skipTLSVerify)
resp, err := client.Do(req)
if err != nil {
return exists, file, err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
file.Collection = resp.Header.Get("Content-Collection")
file.Name = resp.Header.Get("Content-Name")
contentSize := resp.Header.Get("Content-Size")
size, err := strconv.ParseInt(contentSize, 10, 64)
if err != nil {
return exists, file, err
}
file.Size = size
file.Type = resp.Header.Get("Content-Type")
file.Checksum = resp.Header.Get("Content-Digest")
exists = true
}
return exists, file, err
}
func (cli *Client) PutFile(ctx context.Context, filename, fileuri string) error {
var err error
fileuri, username, password, err := repackServiceURI(fileuri)
if err != nil {
return err
}
fileuri, err = convertFileURI(fileuri)
if err != nil {
return err
}
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
req, err := http.NewRequestWithContext(ctx, http.MethodPut, fileuri, file)
if err != nil {
return err
}
fileinfo, err := os.Stat(filename)
if err != nil {
return err
}
filesize := fileinfo.Size()
req.ContentLength = filesize
req.Header.Set("Content-Type", "application/octet-stream")
req.Header.Set("Content-Size", strconv.FormatInt(filesize, 10))
if username != "" && password != "" {
basic := auxhttp.EncodeBasicAuth(username, password)
req.Header.Add("Authorization", basic)
}
client := makeHTTPClient(cli.skipTLSVerify)
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
err := fmt.Errorf("Received wrong status code: %s", resp.Status)
return err
}
return err
}
func (cli *Client) GetFile(ctx context.Context, fileuri, filename string) (int64, error) {
var err error
var size int64
fileuri, username, password, err := repackServiceURI(fileuri)
if err != nil {
return size, err
}
fileuri, err = convertFileURI(fileuri)
if err != nil {
return size, err
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fileuri, nil)
if err != nil {
return size, err
}
if username != "" && password != "" {
basic := auxhttp.EncodeBasicAuth(username, password)
req.Header.Add("Authorization", basic)
}
client := makeHTTPClient(cli.skipTLSVerify)
resp, err := client.Do(req)
if err != nil {
return size, err
}
defer resp.Body.Close()
contentLength := resp.Header.Get("Content-Length")
if contentLength == "" {
err = fmt.Errorf("Empty Content-Length received")
return size, err
}
if resp.StatusCode != http.StatusOK {
err := fmt.Errorf("Received wrong status code: %s", resp.Status)
return size, err
}
declSize, err := strconv.ParseInt(contentLength, 10, 64)
if err != nil {
err = fmt.Errorf("Wrong Content-Length value: %v", err)
return size, err
}
dirname := filepath.Dir(filename)
err = os.MkdirAll(dirname, 0750)
if err != nil {
return size, err
}
file, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, 0640)
if err != nil {
return size, err
}
size, err = io.Copy(file, resp.Body)
if err != nil {
return size, err
}
if size != declSize {
err := fmt.Errorf("Mismatch Content-Length and recorded filesize")
return size, err
}
return size, err
}
func (cli *Client) DeleteFile(ctx context.Context, fileuri string) error {
var err error
fileuri, username, password, err := repackServiceURI(fileuri)
if err != nil {
return err
}
fileuri, err = convertFileURI(fileuri)
if err != nil {
return err
}
req, err := http.NewRequestWithContext(ctx, http.MethodDelete, fileuri, nil)
if err != nil {
return err
}
if username != "" && password != "" {
basic := auxhttp.EncodeBasicAuth(username, password)
req.Header.Add("Authorization", basic)
}
client := makeHTTPClient(cli.skipTLSVerify)
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
err := fmt.Errorf("Received wrong status code: %s", resp.Status)
return err
}
return err
}
func (cli *Client) ListFiles(ctx context.Context, catalogURI, usePathAs string) ([]descr.File, error) {
var err error
res := make([]descr.File, 0)
catalogURI, username, password, err := repackServiceURI(catalogURI)
if err != nil {
return res, err
}
catalogURI, err = convertFilesURI(catalogURI)
if err != nil {
return res, err
}
// Add values
values := url.Values{}
if usePathAs != "" {
values.Add("pathAs", string(usePathAs))
}
encodedValues := values.Encode()
if encodedValues != "" {
catalogURI = catalogURI + "?" + encodedValues
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, catalogURI, nil)
if err != nil {
return res, err
}
if username != "" && password != "" {
basic := auxhttp.EncodeBasicAuth(username, password)
req.Header.Add("Authorization", basic)
}
client := makeHTTPClient(cli.skipTLSVerify)
resp, err := client.Do(req)
if err != nil {
return res, err
}
defer resp.Body.Close()
contentLength := resp.Header.Get("Content-Length")
if contentLength == "" {
err = fmt.Errorf("Empty Content-Length received")
return res, err
}
if resp.StatusCode != http.StatusOK {
err := fmt.Errorf("Received wrong status code: %s", resp.Status)
return res, err
}
declSize, err := strconv.ParseInt(contentLength, 10, 64)
if err != nil {
err = fmt.Errorf("Wrong Content-Length value: %v", err)
return res, err
}
respBuffer := bytes.NewBuffer(nil)
size, err := io.Copy(respBuffer, resp.Body)
if err != nil {
return res, err
}
respBytes := respBuffer.Bytes()
if size != declSize {
err := fmt.Errorf("Mismatch Content-Length and recorded filesize")
return res, err
}
err = json.Unmarshal(respBytes, &res)
if err != nil {
return res, err
}
return res, err
}
func (cli *Client) ListCollections(ctx context.Context, catalogURI, usePathAs string) ([]string, error) {
var err error
res := make([]string, 0)
catalogURI, username, password, err := repackServiceURI(catalogURI)
if err != nil {
return res, err
}
catalogURI, err = convertCollectionsURI(catalogURI)
if err != nil {
return res, err
}
// Add values
values := url.Values{}
if usePathAs != "" {
values.Add("pathAs", string(usePathAs))
}
encodedValues := values.Encode()
if encodedValues != "" {
catalogURI = catalogURI + "?" + encodedValues
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, catalogURI, nil)
if err != nil {
return res, err
}
if username != "" && password != "" {
basic := auxhttp.EncodeBasicAuth(username, password)
req.Header.Add("Authorization", basic)
}
client := makeHTTPClient(cli.skipTLSVerify)
resp, err := client.Do(req)
if err != nil {
return res, err
}
defer resp.Body.Close()
contentLength := resp.Header.Get("Content-Length")
if contentLength == "" {
err = fmt.Errorf("Empty Content-Length received")
return res, err
}
if resp.StatusCode != http.StatusOK {
err := fmt.Errorf("Received wrong status code: %s", resp.Status)
return res, err
}
declSize, err := strconv.ParseInt(contentLength, 10, 64)
if err != nil {
err = fmt.Errorf("Wrong Content-Length value: %v", err)
return res, err
}
respBuffer := bytes.NewBuffer(nil)
size, err := io.Copy(respBuffer, resp.Body)
if err != nil {
return res, err
}
respBytes := respBuffer.Bytes()
if size != declSize {
err := fmt.Errorf("Mismatch Content-Length and recorded filesize")
return res, err
}
err = json.Unmarshal(respBytes, &res)
if err != nil {
return res, err
}
return res, err
}
func (cli *Client) DeleteCollection(ctx context.Context, catalogURI, usePathAs string, dryRun bool) ([]descr.File, error) {
var err error
res := make([]descr.File, 0)
catalogURI, username, password, err := repackServiceURI(catalogURI)
if err != nil {
return res, err
}
catalogURI, err = convertCollectionURI(catalogURI)
if err != nil {
return res, err
}
// Add values
values := url.Values{}
if usePathAs != "" {
values.Add("pathAs", string(usePathAs))
}
if dryRun {
values.Add("dryRun", "true")
}
encodedValues := values.Encode()
if encodedValues != "" {
catalogURI = catalogURI + "?" + encodedValues
}
req, err := http.NewRequestWithContext(ctx, http.MethodDelete, catalogURI, nil)
if err != nil {
return res, err
}
if username != "" && password != "" {
basic := auxhttp.EncodeBasicAuth(username, password)
req.Header.Add("Authorization", basic)
}
client := makeHTTPClient(cli.skipTLSVerify)
resp, err := client.Do(req)
if err != nil {
return res, err
}
defer resp.Body.Close()
contentLength := resp.Header.Get("Content-Length")
if contentLength == "" {
err = fmt.Errorf("Empty Content-Length received")
return res, err
}
if resp.StatusCode != http.StatusOK {
err := fmt.Errorf("Received wrong status code: %s", resp.Status)
return res, err
}
declSize, err := strconv.ParseInt(contentLength, 10, 64)
if err != nil {
err = fmt.Errorf("Wrong Content-Length value: %v", err)
return res, err
}
respBuffer := bytes.NewBuffer(nil)
size, err := io.Copy(respBuffer, resp.Body)
if err != nil {
return res, err
}
respBytes := respBuffer.Bytes()
if size != declSize {
err := fmt.Errorf("Mismatch Content-Length and recorded filesize")
return res, err
}
err = json.Unmarshal(respBytes, &res)
if err != nil {
return res, err
}
return res, err
}
-107
View File
@@ -1,107 +0,0 @@
/*
* Copyright 2026 Oleg Borodin <onborodin@gmail.com>
*
* 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 (
"crypto/tls"
"net/http"
"net/url"
"path"
"strings"
)
func makeHTTPClient(skipTLSVerify bool) *http.Client {
transport := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: skipTLSVerify,
},
}
client := &http.Client{
Transport: transport,
}
return client
}
func convertFileURI(fileuri string) (string, error) {
var err error
var res string
uri, err := url.Parse(fileuri)
if err != nil {
return res, err
}
const fileAPI = "/v3/api/file/"
uri.Path, err = url.JoinPath(fileAPI, uri.Path)
if err != nil {
return res, err
}
res = uri.String()
return res, err
}
func convertFilesURI(fileuri string) (string, error) {
var err error
var res string
uri, err := url.Parse(fileuri)
const filesAPI = "/v3/api/files/"
uri.Path, err = url.JoinPath(filesAPI, uri.Path)
if err != nil {
return res, err
}
res = uri.String()
return res, err
}
func convertCollectionsURI(fileuri string) (string, error) {
var err error
var res string
uri, err := url.Parse(fileuri)
const prefixAPI = "/v3/api/collections/"
uri.Path, err = url.JoinPath(prefixAPI, uri.Path)
if err != nil {
return res, err
}
res = uri.String()
return res, err
}
func convertCollectionURI(fileuri string) (string, error) {
var err error
var res string
uri, err := url.Parse(fileuri)
const prefixAPI = "/v3/api/collection/"
uri.Path, err = url.JoinPath(prefixAPI, uri.Path)
if err != nil {
return res, err
}
res = uri.String()
return res, err
}
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
}
-251
View File
@@ -1,251 +0,0 @@
/*
* Copyright 2026 Oleg Borodin <onborodin@gmail.com>
*
* 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 (
"context"
"encoding/json"
"fmt"
"mstore/app/accoper"
"mstore/app/handler"
"mstore/pkg/descr"
)
func (cli *Client) CreateGrantByAccountID(ctx context.Context, hosturi string, accountID, right, pattern string) (string, error) {
var err error
var res string
apiuri, err := setApiPath(hosturi, "/v3/api/grant/create")
if err != nil {
return res, err
}
operParams := accoper.CreateGrantParams{
AccountID: accountID,
Right: right,
Pattern: pattern,
}
paramsJson, err := json.Marshal(operParams)
if err != nil {
return res, err
}
respBytes, err := doHTTPCall(ctx, cli.skipTLSVerify, apiuri, paramsJson)
if err != nil {
return res, err
}
operRes := handler.NewResponse[accoper.CreateGrantResult]()
err = json.Unmarshal(respBytes, operRes)
if err != nil {
return res, err
}
if operRes.Error {
err = fmt.Errorf("%s", operRes.Message)
return res, err
}
res = operRes.Result.GrantID
return res, err
}
func (cli *Client) CreateGrantByUsername(ctx context.Context, hosturi, username string, right string, pattern string) (string, error) {
var err error
var res string
apiuri, err := setApiPath(hosturi, "/v3/api/grant/create")
if err != nil {
return res, err
}
operParams := accoper.CreateGrantParams{
Username: username,
Right: right,
Pattern: pattern,
}
paramsJson, err := json.Marshal(operParams)
if err != nil {
return res, err
}
respBytes, err := doHTTPCall(ctx, cli.skipTLSVerify, apiuri, paramsJson)
if err != nil {
return res, err
}
operRes := handler.NewResponse[accoper.CreateGrantResult]()
err = json.Unmarshal(respBytes, operRes)
if err != nil {
return res, err
}
if operRes.Error {
err = fmt.Errorf("%s", operRes.Message)
return res, err
}
res = operRes.Result.GrantID
return res, err
}
func (cli *Client) GetGrant(ctx context.Context, hosturi, grantID string) (*descr.Grant, error) {
var err error
res := &descr.Grant{}
apipath, err := setApiPath(hosturi, "/v3/api/grant/get")
if err != nil {
return res, err
}
operParams := accoper.GetGrantParams{
GrantID: grantID,
}
paramsJson, err := json.Marshal(operParams)
if err != nil {
return res, err
}
respBytes, err := doHTTPCall(ctx, cli.skipTLSVerify, apipath, paramsJson)
if err != nil {
return res, err
}
operRes := handler.NewResponse[accoper.GetGrantResult]()
err = json.Unmarshal(respBytes, operRes)
if err != nil {
return res, err
}
if operRes.Error {
err = fmt.Errorf("%s", operRes.Message)
return res, err
}
res = operRes.Result.Grant
return res, err
}
func (cli *Client) UpdateGrant(ctx context.Context, hosturi, grantID, newPattern string) error {
var err error
apipath, err := setApiPath(hosturi, "/v3/api/grant/update")
if err != nil {
return err
}
operParams := accoper.UpdateGrantParams{
GrantID: grantID,
NewPattern: newPattern,
}
paramsJson, err := json.Marshal(operParams)
if err != nil {
return err
}
respBytes, err := doHTTPCall(ctx, cli.skipTLSVerify, apipath, paramsJson)
if err != nil {
return err
}
operRes := handler.NewResponse[accoper.UpdateGrantResult]()
err = json.Unmarshal(respBytes, operRes)
if err != nil {
return err
}
if operRes.Error {
err = fmt.Errorf("%s", operRes.Message)
return err
}
return err
}
func (cli *Client) DeleteGrant(ctx context.Context, hosturi, grantID string) error {
var err error
apipath, err := setApiPath(hosturi, "/v3/api/grant/delete")
if err != nil {
return err
}
operParams := accoper.DeleteGrantParams{
GrantID: grantID,
}
paramsJson, err := json.Marshal(operParams)
if err != nil {
return err
}
respBytes, err := doHTTPCall(ctx, cli.skipTLSVerify, apipath, paramsJson)
if err != nil {
return err
}
operRes := handler.NewResponse[accoper.DeleteGrantResult]()
err = json.Unmarshal(respBytes, operRes)
if err != nil {
return err
}
if operRes.Error {
err = fmt.Errorf("%s", operRes.Message)
return err
}
return err
}
func (cli *Client) ListGrantsByAccountID(ctx context.Context, hosturi, accountID string) ([]descr.Grant, error) {
var err error
res := make([]descr.Grant, 0)
apipath, err := setApiPath(hosturi, "/v3/api/grants/list")
if err != nil {
return res, err
}
operParams := accoper.ListGrantsParams{
AccountID: accountID,
}
paramsJson, err := json.Marshal(operParams)
if err != nil {
return res, err
}
respBytes, err := doHTTPCall(ctx, cli.skipTLSVerify, apipath, paramsJson)
if err != nil {
return res, err
}
operRes := handler.NewResponse[accoper.ListGrantsResult]()
err = json.Unmarshal(respBytes, operRes)
if err != nil {
return res, err
}
if operRes.Error {
err = fmt.Errorf("%s", operRes.Message)
return res, err
}
res = operRes.Result.Grants
return res, err
}
func (cli *Client) ListGrantsByUsername(ctx context.Context, hosturi, username string) ([]descr.Grant, error) {
var err error
res := make([]descr.Grant, 0)
apipath, err := setApiPath(hosturi, "/v3/api/grants/list")
if err != nil {
return res, err
}
operParams := accoper.ListGrantsParams{
Username: username,
}
paramsJson, err := json.Marshal(operParams)
if err != nil {
return res, err
}
respBytes, err := doHTTPCall(ctx, cli.skipTLSVerify, apipath, paramsJson)
if err != nil {
return res, err
}
operRes := handler.NewResponse[accoper.ListGrantsResult]()
err = json.Unmarshal(respBytes, operRes)
if err != nil {
return res, err
}
if operRes.Error {
err = fmt.Errorf("%s", operRes.Message)
return res, err
}
res = operRes.Result.Grants
return res, err
}
-84
View File
@@ -1,84 +0,0 @@
/*
* Copyright 2026 Oleg Borodin <onborodin@gmail.com>
*
* 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 (
"bytes"
"context"
"crypto/tls"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"mstore/pkg/auxhttp"
)
func setApiPath(base, apipath string) (string, error) {
var res string
if !strings.Contains(base, "://") {
base = "https://" + base
}
uri, err := url.Parse(base)
if err != nil {
return res, err
}
uri.Path = "/"
uri.Path, err = url.JoinPath(uri.Path, apipath)
if err != nil {
return res, err
}
res = uri.String()
return res, nil
}
func doHTTPCall(ctx context.Context, skipTLSVerify bool, apiuri string, reqBytes []byte) ([]byte, error) {
var err error
respBytes := make([]byte, 0)
apiuri, username, password, err := repackServiceURI(apiuri)
if err != nil {
return respBytes, err
}
reqBuffer := bytes.NewBuffer(reqBytes)
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, apiuri, reqBuffer)
if err != nil {
return respBytes, err
}
httpReq.Header.Set("Content-Type", "application/json")
if username != "" && password != "" {
basicHeader := auxhttp.EncodeBasicAuth(username, password)
httpReq.Header.Add("Authorization", basicHeader)
}
transport := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: skipTLSVerify},
}
httpClient := &http.Client{
Transport: transport,
}
httpResp, err := httpClient.Do(httpReq)
if err != nil {
return respBytes, err
}
defer httpResp.Body.Close()
if httpResp.StatusCode != http.StatusOK {
err := fmt.Errorf("Wrong StatusCode header: %s", httpResp.Status)
return respBytes, err
}
respBuffer := bytes.NewBuffer(nil)
_, err = io.Copy(respBuffer, httpResp.Body)
if err != nil {
return respBytes, err
}
respBytes = respBuffer.Bytes()
return respBytes, err
}
-79
View File
@@ -1,79 +0,0 @@
/*
* Copyright 2026 Oleg Borodin <onborodin@gmail.com>
*
* 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 (
"context"
"crypto/tls"
"net/http"
"net/url"
"time"
"mstore/pkg/auxhttp"
)
func convertServiceURI(ref string) (string, error) {
var err error
var res string
const serviceAPI = "/v3/api/service/"
const serviceScheme = "https"
uri, err := url.Parse(ref)
if err != nil {
return res, err
}
uri.Path, err = url.JoinPath(serviceAPI, uri.Path)
if err != nil {
return res, err
}
uri.Scheme = serviceScheme
res = uri.String()
return res, err
}
func (cli *Client) ServiceHello(ctx context.Context, serviceuri string, timeout time.Duration) (bool, error) {
var res bool
var err error
ctx, _ = context.WithTimeout(ctx, timeout)
serviceuri, username, password, err := repackServiceURI(serviceuri)
if err != nil {
return res, err
}
serviceuri, err = convertServiceURI(serviceuri)
if err != nil {
return res, err
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, serviceuri, nil)
if err != nil {
return res, err
}
if username != "" && password != "" {
basic := auxhttp.EncodeBasicAuth(username, password)
req.Header.Add("Authorization", basic)
}
transport := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
}
client := &http.Client{
Transport: transport,
}
resp, err := client.Do(req)
if err != nil {
return res, err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
res = true
}
return res, err
}
@@ -7,7 +7,7 @@
* Distribution of this work is permitted, but commercial use and * Distribution of this work is permitted, but commercial use and
* modifications are strictly prohibited. * modifications are strictly prohibited.
*/ */
package client package gcrcli
type Client struct { type Client struct {
skipTLSVerify bool skipTLSVerify bool
@@ -7,7 +7,7 @@
* Distribution of this work is permitted, but commercial use and * Distribution of this work is permitted, but commercial use and
* modifications are strictly prohibited. * modifications are strictly prohibited.
*/ */
package client package gcrcli
import ( import (
"crypto/tls" "crypto/tls"
@@ -7,7 +7,7 @@
* Distribution of this work is permitted, but commercial use and * Distribution of this work is permitted, but commercial use and
* modifications are strictly prohibited. * modifications are strictly prohibited.
*/ */
package client package gcrcli
import ( import (
"context" "context"
@@ -7,7 +7,7 @@
* Distribution of this work is permitted, but commercial use and * Distribution of this work is permitted, but commercial use and
* modifications are strictly prohibited. * modifications are strictly prohibited.
*/ */
package client package gcrcli
import ( import (
"context" "context"
@@ -7,7 +7,7 @@
* Distribution of this work is permitted, but commercial use and * Distribution of this work is permitted, but commercial use and
* modifications are strictly prohibited. * modifications are strictly prohibited.
*/ */
package client package gcrcli
import ( import (
"context" "context"
@@ -7,7 +7,7 @@
* Distribution of this work is permitted, but commercial use and * Distribution of this work is permitted, but commercial use and
* modifications are strictly prohibited. * modifications are strictly prohibited.
*/ */
package client package gcrcli
import ( import (
"context" "context"
-220
View File
@@ -1,220 +0,0 @@
/*
* Copyright 2026 Oleg Borodin <onborodin@gmail.com>
*
* 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 test
import (
"context"
"fmt"
"sync"
"testing"
"time"
"mstore/app/server"
"mstore/pkg/client"
"mstore/pkg/terms"
"github.com/stretchr/testify/require"
yaml "go.yaml.in/yaml/v4"
)
func TestAccountLife(t *testing.T) {
var srvport int64 = 10250
srvdir := t.TempDir()
srvaddr := fmt.Sprintf("mstore:mstore@127.0.0.1:%d", srvport)
srv, err := server.NewServer()
require.NoError(t, err)
{
err = srv.Configure()
require.NoError(t, err)
useTmp := true
if useTmp {
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 := client.NewClient(true)
ctx := context.Background()
helloRes, err := cli.ServiceHello(ctx, srvaddr+"/hello", 1*time.Second)
require.NoError(t, err)
require.True(t, helloRes)
}
username := "testuser"
password := "testpass"
var accountID string
{
// CreateAccount
fmt.Printf("=== CreateAccount ===\n")
cli := client.NewClient(true)
ctx := context.Background()
ctx, _ = context.WithTimeout(ctx, 1*time.Second)
accountID, err = cli.CreateAccount(ctx, srvaddr, username, password)
require.NoError(t, err)
}
var grantID string
{
// CreateGrant
fmt.Printf("=== CreateGrant ===\n")
cli := client.NewClient(true)
ctx := context.Background()
ctx, _ = context.WithTimeout(ctx, 1*time.Second)
grantID, err = cli.CreateGrantByAccountID(ctx, srvaddr, accountID, terms.RightReadAccounts, ".*")
require.NoError(t, err)
require.Equal(t, len(grantID), 36)
}
{
// UpdateGrant
fmt.Printf("=== UpdateGrant ===\n")
cli := client.NewClient(true)
ctx := context.Background()
ctx, _ = context.WithTimeout(ctx, 1*time.Second)
err := cli.UpdateGrant(ctx, srvaddr, grantID, ".*")
require.NoError(t, err)
}
{
// CreateGrant
fmt.Printf("=== CreateGrant ===\n")
cli := client.NewClient(true)
ctx := context.Background()
ctx, _ = context.WithTimeout(ctx, 1*time.Second)
grantID, err = cli.CreateGrantByAccountID(ctx, srvaddr, accountID, terms.RightWriteAccounts, ".*")
require.NoError(t, err)
require.Equal(t, len(grantID), 36)
fmt.Printf("grantID: %s\n", grantID)
}
{
// GetGrant
fmt.Printf("=== GetGrant ===\n")
cli := client.NewClient(true)
ctx := context.Background()
ctx, _ = context.WithTimeout(ctx, 1*time.Second)
grantDescr, err := cli.GetGrant(ctx, srvaddr, grantID)
require.NoError(t, err)
require.Equal(t, len(grantID), 36)
fmt.Printf("grantID: %s\n", grantID)
grantYAML, err := yaml.Marshal(grantDescr)
require.NoError(t, err)
fmt.Printf("account:\n%s\n", string(grantYAML))
}
{
// UpdateGrant
fmt.Printf("=== UpdateGrant ===\n")
cli := client.NewClient(true)
ctx := context.Background()
ctx, _ = context.WithTimeout(ctx, 1*time.Second)
err := cli.UpdateGrant(ctx, srvaddr, grantID, "**")
require.NoError(t, err)
}
{
// GetGrant
fmt.Printf("=== GetGrant ===\n")
cli := client.NewClient(true)
ctx := context.Background()
ctx, _ = context.WithTimeout(ctx, 1*time.Second)
grantDescr, err := cli.GetGrant(ctx, srvaddr, grantID)
require.NoError(t, err)
require.Equal(t, len(grantID), 36)
fmt.Printf("grantID: %s\n", grantID)
grantYAML, err := yaml.Marshal(grantDescr)
require.NoError(t, err)
fmt.Printf("account:\n%s\n", string(grantYAML))
}
{
// DeleteGrant
fmt.Printf("=== DeleteGrant ===\n")
cli := client.NewClient(true)
ctx := context.Background()
ctx, _ = context.WithTimeout(ctx, 1*time.Second)
err := cli.DeleteGrant(ctx, srvaddr, grantID)
require.NoError(t, err)
}
{
// GetAccount
fmt.Printf("=== GetAccount ===\n")
cli := client.NewClient(true)
ctx := context.Background()
ctx, _ = context.WithTimeout(ctx, 1*time.Second)
accountDescr, err := cli.GetAccountByID(ctx, srvaddr, accountID)
require.NoError(t, err)
accountYAML, err := yaml.Marshal(accountDescr)
require.NoError(t, err)
fmt.Printf("account:\n%s\n", string(accountYAML))
}
{
// ListAccounts
fmt.Printf("=== ListAccounts ===\n")
cli := client.NewClient(true)
ctx := context.Background()
ctx, _ = context.WithTimeout(ctx, 1*time.Second)
accountDescrs, err := cli.ListAccounts(ctx, srvaddr+"/")
require.NoError(t, err)
require.NotZero(t, len(accountDescrs))
nameList := make([]string, 0)
for _, item := range accountDescrs {
nameList = append(nameList, item.Username)
}
accountsYAML, err := yaml.Marshal(nameList)
fmt.Printf("accounts:\n%s\n", string(accountsYAML))
}
{
// DeleteAccount
fmt.Printf("=== DeleteAccount ===\n")
cli := client.NewClient(true)
ctx := context.Background()
ctx, _ = context.WithTimeout(ctx, 1*time.Second)
err = cli.DeleteAccountByID(ctx, srvaddr, accountID)
require.NoError(t, err)
}
}
-163
View File
@@ -1,163 +0,0 @@
/*
* Copyright 2026 Oleg Borodin <onborodin@gmail.com>
*
* 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 test
import (
"context"
"fmt"
"math/rand"
"os"
"path/filepath"
"sync"
"testing"
"time"
"mstore/app/server"
"mstore/pkg/client"
"mstore/pkg/terms"
"github.com/stretchr/testify/require"
)
func TestFileLife(t *testing.T) {
var srvport int64 = 10250
srvdir := t.TempDir()
srvaddr := fmt.Sprintf("mstore:mstore@127.0.0.1:%d", srvport)
srv, err := server.NewServer()
require.NoError(t, err)
{
err = srv.Configure()
require.NoError(t, err)
useTmpDir := true
if useTmpDir {
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 := client.NewClient(true)
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 := client.NewClient(true)
ctx := context.Background()
ctx, _ = context.WithTimeout(ctx, 1*time.Second)
err = cli.PutFile(ctx, tmpfile, srvaddr+"/foo.bin")
require.NoError(t, err)
}
{
// FileInfo
fmt.Printf("=== FileInfo ===\n")
cli := client.NewClient(true)
ctx := context.Background()
ctx, _ = context.WithTimeout(ctx, 1*time.Second)
exists, file, err := cli.FileInfo(ctx, srvaddr+"/foo.bin")
require.NoError(t, err)
require.True(t, exists)
require.NotNil(t, file)
}
{
// GetFile
fmt.Printf("=== GetFile ===\n")
cli := client.NewClient(true)
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, int64(filesize), recsize)
}
{
// ListFiles
fmt.Printf("=== ListFiles ===\n")
cli := client.NewClient(true)
ctx := context.Background()
ctx, _ = context.WithTimeout(ctx, 1*time.Second)
files, err := cli.ListFiles(ctx, srvaddr+"/", terms.AsFinePath)
require.NoError(t, err)
require.NotZero(t, len(files))
}
{
// DeleteFile
fmt.Printf("=== DeleteFile ===\n")
cli := client.NewClient(true)
ctx := context.Background()
ctx, _ = context.WithTimeout(ctx, 1*time.Second)
err = cli.DeleteFile(ctx, srvaddr+"/foo.bin")
require.NoError(t, err)
}
{
// !FileInfo
fmt.Printf("=== FileInfo ===\n")
cli := client.NewClient(true)
ctx := context.Background()
ctx, _ = context.WithTimeout(ctx, 1*time.Second)
exists, _, err := cli.FileInfo(ctx, srvaddr+"/foo.bin")
require.NoError(t, err)
require.False(t, exists)
}
}
-106
View File
@@ -1,106 +0,0 @@
/*
* Copyright 2026 Oleg Borodin <onborodin@gmail.com>
*
* 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 test
import (
"context"
"fmt"
"sync"
"testing"
"time"
"mstore/app/server"
"mstore/pkg/client"
"github.com/stretchr/testify/require"
yaml "go.yaml.in/yaml/v4"
)
func TestImageLife(t *testing.T) {
var srvport int64 = 10250
srvdir := t.TempDir()
srvaddr := fmt.Sprintf("mstore:mstore@127.0.0.1:%d", srvport)
srv, err := server.NewServer()
require.NoError(t, err)
{
err = srv.Configure()
require.NoError(t, err)
useTmp := true
if useTmp {
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() {
time.Sleep(5 * time.Second)
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 := client.NewClient(true)
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 := client.NewClient(true)
ctx, _ := context.WithTimeout(context.Background(), 1*time.Second)
err := cli.PushImage(ctx, "test-oci.tar", srvaddr+"/foo/test:123")
require.NoError(t, err)
}
{
// ImageAmnifest
fmt.Printf("=== ImageManifest ===\n")
cli := client.NewClient(true)
ctx, _ := context.WithTimeout(context.Background(), 1*time.Second)
info, err := cli.ImageManifest(ctx, srvaddr+"/foo/test:123")
require.NoError(t, err)
infoYaml, err := yaml.Marshal(info)
require.NoError(t, err)
fmt.Printf("imagemanifest:\n%s\n", string(infoYaml))
}
{
// DeleteImage
fmt.Printf("=== DeleteImage ===\n")
cli := client.NewClient(true)
ctx, _ := context.WithTimeout(context.Background(), 1*time.Second)
err := cli.DeleteImage(ctx, srvaddr+"/foo/test:123")
require.NoError(t, err)
}
}
+2 -14
View File
@@ -10,18 +10,14 @@
package test package test
import ( import (
"bytes"
"context" "context"
"fmt" "fmt"
//"math/rand"
//"os"
//"path/filepath"
"bytes"
"sync" "sync"
"testing" "testing"
"time" "time"
"mstore/app/server" "mstore/app/server"
"mstore/pkg/client"
"mstore/pkg/filecli" "mstore/pkg/filecli"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -70,15 +66,7 @@ func TestFileLife(t *testing.T) {
go startFunc() go startFunc()
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
} }
{
// ServiceHello
fmt.Printf("=== ServiceHello ===\n")
cli := client.NewClient(true)
ctx := context.Background()
helloRes, err := cli.ServiceHello(ctx, srvaddr+"/hello", 1*time.Second)
require.NoError(t, err)
require.True(t, helloRes)
}
fileaddr := srvaddr + "/foo2/bare.bin" fileaddr := srvaddr + "/foo2/bare.bin"
filedata := []byte("Hello, World") filedata := []byte("Hello, World")
filesize := int64(len(filedata)) filesize := int64(len(filedata))