working commit
This commit is contained in:
+33
-10
@@ -1,3 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* 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 descr
|
package descr
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -8,17 +18,30 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Account struct {
|
type Account struct {
|
||||||
ID int64 `json:"id" yaml:"id" db:"id"`
|
ID string `json:"id" db:"id"`
|
||||||
Username string `json:"username" yaml:"username" db:"username"`
|
Username string `json:"username" db:"username"`
|
||||||
Passhash string `json:"passhash" yaml:"passhash" db:"passhash"`
|
Passhash string `json:"passhash" db:"passhash"`
|
||||||
Disabled bool `json:"disabled" yaml:"disabled" db:"disabled"`
|
Disabled bool `json:"disabled" db:"disabled"`
|
||||||
CreatedAt string `json:"createdAt" yaml:"createdAt" db:"created_at"`
|
CreatedAt string `json:"createdAt" db:"created_at"`
|
||||||
UpdatedAt string `json:"updatedAt,omitempty" yaml:"updatedAt,omitempty" db:"updated_at"`
|
UpdatedAt string `json:"updatedAt,omitempty" db:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Grant struct {
|
type Grant struct {
|
||||||
ID int64 `json:"id" yaml:"id" db:"id"`
|
ID string `json:"id" db:"id"`
|
||||||
AccountID int64 `json:"accountID" yaml:"accountID" db:"account_id"`
|
AccountID string `json:"accountID" db:"account_id"`
|
||||||
Operation string `json:"operation" yaml:"operation" db:"operation"`
|
Operation string `json:"operation" db:"operation"`
|
||||||
CreatedAt string `json:"createdAt" yaml:"createdAt" db:"created_at"`
|
CreatedAt string `json:"createdAt" db:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GrantShortDescr struct {
|
||||||
|
Operation string `json:"operation"`
|
||||||
|
CreatedAt string `json:"createdAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccountShortDescr struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Disabled bool `json:"disabled"`
|
||||||
|
CreatedAt string `json:"createdAt"`
|
||||||
|
UpdatedAt string `json:"updatedAt,omitempty"`
|
||||||
|
Grants []GrantShortDescr `json:"grants"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,14 +36,14 @@ func (hand *Handler) CheckAccess(rctx *router.Context) (bool, error) {
|
|||||||
var res bool
|
var res bool
|
||||||
|
|
||||||
authHeader := rctx.GetHeader("Authorization")
|
authHeader := rctx.GetHeader("Authorization")
|
||||||
if authHeader != "" {
|
if authHeader != "" {
|
||||||
hand.logg.Debugf("Authorization header is %s", authHeader)
|
hand.logg.Debugf("Authorization header is %s", authHeader)
|
||||||
username, password, err := auxhttp.ParseBasicAuth(authHeader)
|
username, password, err := auxhttp.ParseBasicAuth(authHeader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
hand.logg.Debugf("Authorization username is %s:%s", username, password)
|
hand.logg.Debugf("Authorization username is %s:%s", username, password)
|
||||||
}
|
}
|
||||||
|
|
||||||
res = true
|
res = true
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +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 handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
+9
-9
@@ -31,24 +31,23 @@ func (hand *Handler) FileExists(rctx *router.Context) {
|
|||||||
rctx.SetStatus(code)
|
rctx.SetStatus(code)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// TODO
|
|
||||||
rctx.SetHeader("Content-Type", res.ContentType)
|
rctx.SetHeader("Content-Type", res.ContentType)
|
||||||
rctx.SetHeader("Content-Length", res.ContentLength)
|
rctx.SetHeader("Content-Size", res.ContentSize)
|
||||||
rctx.SetHeader("Content-Digest", res.ContentDigest)
|
rctx.SetHeader("Content-Digest", res.ContentDigest)
|
||||||
//rctx.SetHeader("Content-Length", zeroContentLength)
|
rctx.SetHeader("Content-Length", zeroContentLength)
|
||||||
rctx.SetStatus(code)
|
rctx.SetStatus(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hand *Handler) PutFile(rctx *router.Context) {
|
func (hand *Handler) PutFile(rctx *router.Context) {
|
||||||
contentLength := rctx.GetHeader("Content-Length")
|
contentSize := rctx.GetHeader("Content-Size")
|
||||||
contentType := rctx.GetHeader("Content-Type")
|
contentType := rctx.GetHeader("Content-Type")
|
||||||
filepath, _ := rctx.GetSubpath("filepath")
|
filepath, _ := rctx.GetSubpath("filepath")
|
||||||
|
|
||||||
params := &operator.PutFileParams{
|
params := &operator.PutFileParams{
|
||||||
Filepath: filepath,
|
Filepath: filepath,
|
||||||
ContentLength: contentLength,
|
ContentType: contentType,
|
||||||
ContentType: contentType,
|
ContentSize: contentSize,
|
||||||
Source: rctx.Request.Body,
|
Source: rctx.Request.Body,
|
||||||
}
|
}
|
||||||
ctx := rctx.GetContext()
|
ctx := rctx.GetContext()
|
||||||
|
|
||||||
@@ -76,8 +75,9 @@ func (hand *Handler) GetFile(rctx *router.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rctx.SetHeader("Content-Type", res.ContentType)
|
rctx.SetHeader("Content-Type", res.ContentType)
|
||||||
rctx.SetHeader("Content-Length", res.ContentLength)
|
rctx.SetHeader("Content-Size", res.ContentSize)
|
||||||
rctx.SetHeader("Content-Digest", res.ContentDigest)
|
rctx.SetHeader("Content-Digest", res.ContentDigest)
|
||||||
|
rctx.SetHeader("Content-Length", res.ContentSize)
|
||||||
rctx.SetStatus(code)
|
rctx.SetStatus(code)
|
||||||
|
|
||||||
if res.Source != nil {
|
if res.Source != nil {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"mstore/app/operator"
|
"mstore/app/operator"
|
||||||
"mstore/app/router"
|
"mstore/app/router"
|
||||||
@@ -21,12 +21,12 @@ func (hand *Handler) GetVersion(rctx *router.Context) {
|
|||||||
params := &operator.GetVersionParams{}
|
params := &operator.GetVersionParams{}
|
||||||
|
|
||||||
hand.DumpHeaders("GetVersion", rctx)
|
hand.DumpHeaders("GetVersion", rctx)
|
||||||
authorization := rctx.GetHeader("Authorization")
|
authorization := rctx.GetHeader("Authorization")
|
||||||
if authorization == "" {
|
if authorization == "" {
|
||||||
rctx.SetHeader("WWW-Authenticate", `Basic realm="mstore"`)
|
rctx.SetHeader("WWW-Authenticate", `Basic realm="mstore"`)
|
||||||
rctx.SetStatus(http.StatusUnauthorized)
|
rctx.SetStatus(http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx := rctx.GetContext()
|
ctx := rctx.GetContext()
|
||||||
_, code, err := hand.oper.GetVersion(ctx, params)
|
_, code, err := hand.oper.GetVersion(ctx, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func (db *Database) InsertAccount(ctx context.Context, account *descr.Account) e
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Database) UpdateAccountByID(ctx context.Context, accountID int64, account *descr.Account) error {
|
func (db *Database) UpdateAccountByID(ctx context.Context, accountID string, account *descr.Account) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
request := `UPDATE accounts SET username = $1, passhash = $2, disabled = $3, updated_at = $4 WHERE id = $6`
|
request := `UPDATE accounts SET username = $1, passhash = $2, disabled = $3, updated_at = $4 WHERE id = $6`
|
||||||
@@ -52,7 +52,7 @@ func (db *Database) CompletedListAccounts(ctx context.Context) ([]descr.Account,
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Database) GetAccountByID(ctx context.Context, accountID int64) (bool, *descr.Account, error) {
|
func (db *Database) GetAccountByID(ctx context.Context, accountID string) (bool, *descr.Account, error) {
|
||||||
var err error
|
var err error
|
||||||
var res *descr.Account
|
var res *descr.Account
|
||||||
var exists bool
|
var exists bool
|
||||||
@@ -90,7 +90,7 @@ func (db *Database) GetAccountByUsername(ctx context.Context, username string) (
|
|||||||
return exists, res, err
|
return exists, res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Database) DeleteAccountByID(ctx context.Context, accountID int64) error {
|
func (db *Database) DeleteAccountByID(ctx context.Context, accountID string) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
request := `DELETE FROM accounts WHERE id = $1`
|
request := `DELETE FROM accounts WHERE id = $1`
|
||||||
|
|||||||
+4
-4
@@ -17,7 +17,7 @@ func (db *Database) InsertGrant(ctx context.Context, grant *descr.Grant) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Database) ListGrantsByAccountID(ctx context.Context, accountID int64) ([]descr.Grant, error) {
|
func (db *Database) ListGrantsByAccountID(ctx context.Context, accountID string) ([]descr.Grant, error) {
|
||||||
var err error
|
var err error
|
||||||
request := `SELECT * FROM grants WHERE account_id = $1`
|
request := `SELECT * FROM grants WHERE account_id = $1`
|
||||||
res := make([]descr.Grant, 0)
|
res := make([]descr.Grant, 0)
|
||||||
@@ -39,7 +39,7 @@ func (db *Database) ListGrants(ctx context.Context) ([]descr.Grant, error) {
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Database) GetGrant(ctx context.Context, accountID int64, operation string) (bool, *descr.Grant, error) {
|
func (db *Database) GetGrant(ctx context.Context, accountID, operation string) (bool, *descr.Grant, error) {
|
||||||
var err error
|
var err error
|
||||||
res := &descr.Grant{}
|
res := &descr.Grant{}
|
||||||
request := `SELECT * FROM grants WHERE account_id = $1 AND operation = $2 LIMIT 1`
|
request := `SELECT * FROM grants WHERE account_id = $1 AND operation = $2 LIMIT 1`
|
||||||
@@ -56,7 +56,7 @@ func (db *Database) GetGrant(ctx context.Context, accountID int64, operation str
|
|||||||
return true, res, err
|
return true, res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Database) DeleteGrantByAccountID(ctx context.Context, grantID int64, operation string) error {
|
func (db *Database) DeleteGrantByAccountID(ctx context.Context, grantID, operation string) error {
|
||||||
var err error
|
var err error
|
||||||
request := `DELETE FROM grants WHERE account_id = $1 AND operation = $2`
|
request := `DELETE FROM grants WHERE account_id = $1 AND operation = $2`
|
||||||
_, err = db.db.Exec(request, grantID, operation)
|
_, err = db.db.Exec(request, grantID, operation)
|
||||||
@@ -66,7 +66,7 @@ func (db *Database) DeleteGrantByAccountID(ctx context.Context, grantID int64, o
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Database) DeleteAllGrantsForAccountID(ctx context.Context, grantID int64) error {
|
func (db *Database) DeleteAllGrantsForAccountID(ctx context.Context, grantID string) error {
|
||||||
var err error
|
var err error
|
||||||
request := `DELETE FROM grants WHERE account_id = $1`
|
request := `DELETE FROM grants WHERE account_id = $1`
|
||||||
_, err = db.db.Exec(request, grantID)
|
_, err = db.db.Exec(request, grantID)
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ const schema = `
|
|||||||
|
|
||||||
--- DROP TABLE IF EXISTS accounts;
|
--- DROP TABLE IF EXISTS accounts;
|
||||||
CREATE TABLE IF NOT EXISTS accounts (
|
CREATE TABLE IF NOT EXISTS accounts (
|
||||||
id INT NOT NULL,
|
id TEXT NOT NULL,
|
||||||
username TEXT NOT NULL,
|
username TEXT NOT NULL,
|
||||||
passhash TEXT NOT NULL,
|
passhash TEXT NOT NULL,
|
||||||
created_at TEXT NOT NULL,
|
created_at TEXT NOT NULL,
|
||||||
@@ -72,7 +72,7 @@ const schema = `
|
|||||||
|
|
||||||
--- DROP TABLE IF EXISTS grants;
|
--- DROP TABLE IF EXISTS grants;
|
||||||
CREATE TABLE IF NOT EXISTS grants (
|
CREATE TABLE IF NOT EXISTS grants (
|
||||||
id INT NOT NULL,
|
id TEXT NOT NULL,
|
||||||
account_id INT NOT NULL,
|
account_id INT NOT NULL,
|
||||||
operation TEXT NOT NULL,
|
operation TEXT NOT NULL,
|
||||||
created_at TEXT NOT NULL
|
created_at TEXT NOT NULL
|
||||||
|
|||||||
+11
-11
@@ -31,7 +31,7 @@ type FileExistsParams struct {
|
|||||||
}
|
}
|
||||||
type FileExistsResult struct {
|
type FileExistsResult struct {
|
||||||
ContentType string
|
ContentType string
|
||||||
ContentLength string
|
ContentSize string
|
||||||
ContentDigest string
|
ContentDigest string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ func (oper *Operator) FileExists(ctx context.Context, param *FileExistsParams) (
|
|||||||
return code, res, err
|
return code, res, err
|
||||||
}
|
}
|
||||||
res = &FileExistsResult{
|
res = &FileExistsResult{
|
||||||
ContentLength: strconv.FormatInt(fileDescr.Size, 10),
|
ContentSize: strconv.FormatInt(fileDescr.Size, 10),
|
||||||
ContentType: fileDescr.Type,
|
ContentType: fileDescr.Type,
|
||||||
ContentDigest: fileDescr.Checksum,
|
ContentDigest: fileDescr.Checksum,
|
||||||
}
|
}
|
||||||
@@ -73,10 +73,10 @@ func (oper *Operator) FileExists(ctx context.Context, param *FileExistsParams) (
|
|||||||
|
|
||||||
// PutFile
|
// PutFile
|
||||||
type PutFileParams struct {
|
type PutFileParams struct {
|
||||||
ContentType string
|
ContentType string
|
||||||
ContentLength string
|
ContentSize string
|
||||||
Filepath string
|
Filepath string
|
||||||
Source io.ReadCloser
|
Source io.ReadCloser
|
||||||
}
|
}
|
||||||
type PutFileResult struct{}
|
type PutFileResult struct{}
|
||||||
|
|
||||||
@@ -86,12 +86,12 @@ func (oper *Operator) PutFile(ctx context.Context, param *PutFileParams) (int, *
|
|||||||
var err error
|
var err error
|
||||||
res := &PutFileResult{}
|
res := &PutFileResult{}
|
||||||
|
|
||||||
if param.ContentLength == "" {
|
if param.ContentSize == "" {
|
||||||
code := http.StatusLengthRequired
|
code := http.StatusLengthRequired
|
||||||
err = fmt.Errorf("Content-Length is empty")
|
err = fmt.Errorf("Required Content-Size header is empty")
|
||||||
return code, res, err
|
return code, res, err
|
||||||
}
|
}
|
||||||
size, err := strconv.ParseInt(param.ContentLength, 10, 64)
|
size, err := strconv.ParseInt(param.ContentSize, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
code := http.StatusLengthRequired
|
code := http.StatusLengthRequired
|
||||||
return code, res, err
|
return code, res, err
|
||||||
@@ -166,7 +166,7 @@ type GetFileParams struct {
|
|||||||
}
|
}
|
||||||
type GetFileResult struct {
|
type GetFileResult struct {
|
||||||
ContentType string
|
ContentType string
|
||||||
ContentLength string
|
ContentSize string
|
||||||
ContentDigest string
|
ContentDigest string
|
||||||
Source io.ReadCloser
|
Source io.ReadCloser
|
||||||
}
|
}
|
||||||
@@ -200,7 +200,7 @@ func (oper *Operator) GetFile(ctx context.Context, param *GetFileParams) (int, *
|
|||||||
return code, res, err
|
return code, res, err
|
||||||
}
|
}
|
||||||
res = &GetFileResult{
|
res = &GetFileResult{
|
||||||
ContentLength: strconv.FormatInt(fileDescr.Size, 10),
|
ContentSize: strconv.FormatInt(fileDescr.Size, 10),
|
||||||
ContentType: fileDescr.Type,
|
ContentType: fileDescr.Type,
|
||||||
ContentDigest: fileDescr.Checksum,
|
ContentDigest: fileDescr.Checksum,
|
||||||
Source: reader,
|
Source: reader,
|
||||||
|
|||||||
@@ -97,6 +97,12 @@ func (svc *Service) Build() error {
|
|||||||
svc.rout.Get(`/v2/{name}/tags/list`, svc.hand.GetTags)
|
svc.rout.Get(`/v2/{name}/tags/list`, svc.hand.GetTags)
|
||||||
svc.rout.Get(`/v2/{name}/referrers/{digest}`, svc.hand.GetReferer)
|
svc.rout.Get(`/v2/{name}/referrers/{digest}`, svc.hand.GetReferer)
|
||||||
|
|
||||||
|
svc.rout.Post(`/v3/account/create`, svc.hand.CreateAccount)
|
||||||
|
svc.rout.Post(`/v3/account/get`, svc.hand.GetAccount)
|
||||||
|
svc.rout.Post(`/v3/accounts/list`, svc.hand.ListAccounts)
|
||||||
|
svc.rout.Post(`/v3/account/update`, svc.hand.UpdateAccount)
|
||||||
|
svc.rout.Post(`/v3/account/delete`, svc.hand.DeleteAccount)
|
||||||
|
|
||||||
svc.rout.NotFound(svc.hand.NotFound)
|
svc.rout.NotFound(svc.hand.NotFound)
|
||||||
|
|
||||||
selector := svc.rout.Selector()
|
selector := svc.rout.Selector()
|
||||||
|
|||||||
@@ -12,12 +12,7 @@ package client
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -70,191 +65,3 @@ func (cli *Client) ServiceHello(ctx context.Context, ref string, timeout time.Du
|
|||||||
}
|
}
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *Client) FileExists(ctx context.Context, ref string) (bool, error) {
|
|
||||||
var res bool
|
|
||||||
var err error
|
|
||||||
|
|
||||||
ref, err = convertFileRefer(ref)
|
|
||||||
if err != nil {
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodHead, ref, nil)
|
|
||||||
if err != nil {
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if cli.username != "" && cli.password != "" {
|
|
||||||
req.Header.Add("Authorization", encodeBasicAuth(cli.username, cli.password))
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *Client) PutFile(ctx context.Context, filename, ref string) error {
|
|
||||||
var err error
|
|
||||||
ref, err = convertFileRefer(ref)
|
|
||||||
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, ref, file)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if cli.username != "" && cli.password != "" {
|
|
||||||
req.Header.Add("Authorization", encodeBasicAuth(cli.username, cli.password))
|
|
||||||
}
|
|
||||||
|
|
||||||
transport := &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{
|
|
||||||
InsecureSkipVerify: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
client := &http.Client{
|
|
||||||
Transport: transport,
|
|
||||||
}
|
|
||||||
fileinfo, err := os.Stat(filename)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
filesize := fileinfo.Size()
|
|
||||||
|
|
||||||
req.ContentLength = filesize
|
|
||||||
req.Header.Set("Content-Type", "application/octet-stream")
|
|
||||||
|
|
||||||
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, ref, filename string) (int64, error) {
|
|
||||||
var err error
|
|
||||||
var size int64
|
|
||||||
ref, err = convertFileRefer(ref)
|
|
||||||
if err != nil {
|
|
||||||
return size, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, ref, nil)
|
|
||||||
if err != nil {
|
|
||||||
return size, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if cli.username != "" && cli.password != "" {
|
|
||||||
req.Header.Add("Authorization", encodeBasicAuth(cli.username, cli.password))
|
|
||||||
}
|
|
||||||
|
|
||||||
transport := &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{
|
|
||||||
InsecureSkipVerify: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
client := &http.Client{
|
|
||||||
Transport: transport,
|
|
||||||
}
|
|
||||||
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, ref, filename string) error {
|
|
||||||
var err error
|
|
||||||
ref, err = convertFileRefer(ref)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodDelete, ref, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if cli.username != "" && cli.password != "" {
|
|
||||||
req.Header.Add("Authorization", encodeBasicAuth(cli.username, cli.password))
|
|
||||||
}
|
|
||||||
|
|
||||||
transport := &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{
|
|
||||||
InsecureSkipVerify: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
client := &http.Client{
|
|
||||||
Transport: transport,
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|||||||
+40
-29
@@ -20,6 +20,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"mstore/app/router"
|
"mstore/app/router"
|
||||||
@@ -32,21 +33,54 @@ func TestFileOperations(t *testing.T) {
|
|||||||
srv, err := server.NewServer()
|
srv, err := server.NewServer()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var srvport int64 = 10240 + rand.Int63n(1024)
|
||||||
|
srvdir := t.TempDir()
|
||||||
|
//srvaddr := fmt.Sprintf("127.0.0.1:%d", srvport)
|
||||||
|
|
||||||
filename := `bare.bin?abc=12`
|
filename := `bare.bin?abc=12`
|
||||||
|
|
||||||
{
|
{
|
||||||
err = srv.Configure()
|
err = srv.Configure()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
//tmpdir := t.TempDir()
|
err = srv.Configure()
|
||||||
//srv.SetDatadir(tmpdir)
|
require.NoError(t, err)
|
||||||
//srv.SetLogdir(tmpdir)
|
var tmpdir bool
|
||||||
//srv.SetRundir(tmpdir)
|
tmpdir = true
|
||||||
|
if tmpdir {
|
||||||
|
srv.SetDatadir(srvdir)
|
||||||
|
srv.SetLogdir(srvdir)
|
||||||
|
srv.SetRundir(srvdir)
|
||||||
|
}
|
||||||
|
srv.SetPort(srvport)
|
||||||
|
|
||||||
err = srv.Build()
|
err = srv.Build()
|
||||||
require.NoError(t, err)
|
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))
|
||||||
|
}
|
||||||
|
//return
|
||||||
{
|
{
|
||||||
fmt.Printf("=== PutFile ===\n")
|
fmt.Printf("=== PutFile ===\n")
|
||||||
reqPath := `/v3/api/file/` + filename
|
reqPath := `/v3/api/file/` + filename
|
||||||
@@ -71,8 +105,9 @@ func TestFileOperations(t *testing.T) {
|
|||||||
request, err := http.NewRequest("PUT", reqPath, source)
|
request, err := http.NewRequest("PUT", reqPath, source)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
request.Header.Set("Content-Size", fmt.Sprintf("%d", datasize))
|
request.ContentLength = int64(datasize)
|
||||||
request.Header.Set("Content-Type", "application/octet-stream")
|
request.Header.Set("Content-Type", "application/octet-stream")
|
||||||
|
request.Header.Set("Content-Size", strconv.FormatInt(int64(datasize), 10))
|
||||||
|
|
||||||
recorder := httptest.NewRecorder()
|
recorder := httptest.NewRecorder()
|
||||||
rout.ServeHTTP(recorder, request)
|
rout.ServeHTTP(recorder, request)
|
||||||
@@ -136,7 +171,6 @@ func TestFileOperations(t *testing.T) {
|
|||||||
|
|
||||||
fmt.Printf("Response body: %s\n", string(bodyBytes))
|
fmt.Printf("Response body: %s\n", string(bodyBytes))
|
||||||
}
|
}
|
||||||
return
|
|
||||||
{
|
{
|
||||||
fmt.Printf("=== DeleteFile ===\n")
|
fmt.Printf("=== DeleteFile ===\n")
|
||||||
reqPath := filepath.Join(`/v3/api/file`, filename)
|
reqPath := filepath.Join(`/v3/api/file`, filename)
|
||||||
@@ -187,29 +221,6 @@ func TestFileOperations(t *testing.T) {
|
|||||||
bodyReader := recorder.Body
|
bodyReader := recorder.Body
|
||||||
bodyBytes, err := io.ReadAll(bodyReader)
|
bodyBytes, err := io.ReadAll(bodyReader)
|
||||||
|
|
||||||
fmt.Printf("Response body: %s\n", string(bodyBytes))
|
|
||||||
}
|
|
||||||
{
|
|
||||||
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("Response body: %s\n", string(bodyBytes))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user