working commit

This commit is contained in:
2026-03-10 12:52:12 +02:00
parent d0a5fab362
commit d1ef1fbe50
42 changed files with 242 additions and 426 deletions
-34
View File
@@ -1,34 +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 accountcmd
import (
"net/url"
"path"
"strings"
)
func packUserinfo(resurseuri, username, password string) (string, error) {
var err error
var res string
if !strings.Contains(resurseuri, "://") {
resurseuri = "https://" + resurseuri
}
uri, err := url.Parse(resurseuri)
if err != nil {
return res, err
}
uri.Path = path.Clean(uri.Path)
if username != "" && password != "" {
uri.User = url.UserPassword(username, password)
}
res = uri.String()
return res, err
}
+6 -7
View File
@@ -15,7 +15,7 @@ import (
"github.com/spf13/cobra"
"mstore/pkg/client"
"mstore/pkg/repocli"
)
// CatalogImages
@@ -38,17 +38,16 @@ func (util *ImageUtil) catalogImages(common *CommonImageParams, params *CatalogI
res := &CatalogImagesResult{
Repositories: make([]string, 0),
}
ctx := context.Background()
cli := client.NewClient(common.SkipTLSVerify)
timeout := time.Duration(common.Timeout) * time.Second
ctx, _ := context.WithTimeout(context.Background(), timeout)
params.Source, err = packUserinfo(params.Source, common.Username, common.Password)
ref, err := repocli.NewReferer(params.Source)
if err != nil {
return res, err
}
ctx, _ = context.WithTimeout(ctx, timeout)
opres, err := cli.CatalogImages(ctx, params.Source)
mw := repocli.NewBasicAuthMiddleware(ref.Userinfo())
cli := repocli.NewClient(nil, mw)
opres, err := cli.GetCatalog(ctx, ref.Raw())
if err != nil {
return res, err
}
+7 -6
View File
@@ -15,7 +15,7 @@ import (
"github.com/spf13/cobra"
"mstore/pkg/client"
"mstore/pkg/repocli"
)
// DeleteImage
@@ -35,19 +35,20 @@ func (util *ImageUtil) DeleteImage(cmd *cobra.Command, args []string) {
func (util *ImageUtil) deleteImage(common *CommonImageParams, params *DeleteImageParams) (*DeleteImageResult, error) {
var err error
res := &DeleteImageResult{}
ctx := context.Background()
cli := client.NewClient(common.SkipTLSVerify)
timeout := time.Duration(common.Timeout) * time.Second
ctx, _ := context.WithTimeout(context.Background(), timeout)
params.Imagepath, err = packUserinfo(params.Imagepath, common.Username, common.Password)
ref, err := repocli.NewReferer(params.Imagepath)
if err != nil {
return res, err
}
ctx, _ = context.WithTimeout(ctx, timeout)
err = cli.DeleteImage(ctx, params.Imagepath)
mw := repocli.NewBasicAuthMiddleware(ref.Userinfo())
cli := repocli.NewClient(nil, mw)
_, err = cli.DeleteImage(ctx, ref.Raw())
if err != nil {
return res, err
}
return res, err
}
-59
View File
@@ -1,59 +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 imagecmd
import (
"context"
"time"
"github.com/spf13/cobra"
"mstore/pkg/client"
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 := client.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
}
+3 -4
View File
@@ -15,11 +15,10 @@ import (
"fmt"
"time"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/spf13/cobra"
"mstore/pkg/repocli"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
// ImageManifest
@@ -55,8 +54,8 @@ func (util *ImageUtil) imageManifest(common *CommonImageParams, params *ImageMan
return res, err
}
mw := repocli.NewBasicAuthMiddleware(ref.Userinfo())
cli := repocli.NewClientWithTransport(nil, mw)
exists, mime, man, err := cli.GetManifest(ctx, ref.Repo(), ref.Tag())
cli := repocli.NewClient(nil, mw)
exists, mime, man, err := cli.GetRawManifest(ctx, ref.Repo())
if !exists {
err = fmt.Errorf("Manifest not found")
return res, err
+6 -6
View File
@@ -15,7 +15,7 @@ import (
"github.com/spf13/cobra"
"mstore/pkg/client"
"mstore/pkg/repocli"
)
// ImageTags
@@ -38,17 +38,17 @@ func (util *ImageUtil) imageTags(common *CommonImageParams, params *ImageTagsPar
res := &ImageTagsResult{
ImageTags: make([]string, 0),
}
ctx := context.Background()
cli := client.NewClient(common.SkipTLSVerify)
timeout := time.Duration(common.Timeout) * time.Second
ctx, _ := context.WithTimeout(context.Background(), timeout)
params.Imagepath, err = packUserinfo(params.Imagepath, common.Username, common.Password)
ref, err := repocli.NewReferer(params.Imagepath)
if err != nil {
return res, err
}
ctx, _ = context.WithTimeout(ctx, timeout)
opres, err := cli.ImageTags(ctx, params.Imagepath)
mw := repocli.NewBasicAuthMiddleware(ref.Userinfo())
cli := repocli.NewClient(nil, mw)
opres, err := cli.GetTags(ctx, ref.Raw())
if err != nil {
return res, err
}
-65
View File
@@ -1,65 +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 imagecmd
import (
"context"
"os"
"time"
"github.com/spf13/cobra"
"mstore/pkg/client"
)
// 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 := client.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
@@ -1,54 +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 imagecmd
import (
"context"
"time"
"github.com/spf13/cobra"
"mstore/pkg/client"
)
// 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 := client.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
}
+1 -1
View File
@@ -16,7 +16,7 @@ func (cli *Client) DeleteCollection(ctx context.Context, rawpath string) ([]byte
if err != nil {
return list, err
}
uri := ref.Collection()
uri := ref.CollectionEP()
req, err := http.NewRequestWithContext(ctx, http.MethodDelete, uri, nil)
if err != nil {
return list, err
+1 -1
View File
@@ -14,7 +14,7 @@ func (cli *Client) DeleteFile(ctx context.Context, rawpath string) (bool, error)
if err != nil {
return exist, err
}
uri := ref.File()
uri := ref.FileEP()
req, err := http.NewRequestWithContext(ctx, http.MethodDelete, uri, nil)
if err != nil {
return exist, err
+1 -1
View File
@@ -17,7 +17,7 @@ func (cli *Client) FileInfo(ctx context.Context, rawpath string) (bool, int64, s
if err != nil {
return exist, size, digest, err
}
uri := ref.File()
uri := ref.FileEP()
fmt.Println(uri)
req, err := http.NewRequestWithContext(ctx, http.MethodHead, uri, nil)
+1 -1
View File
@@ -16,7 +16,7 @@ func (cli *Client) GetFile(ctx context.Context, rawpath string, writer io.Writer
if err != nil {
return exist, err
}
uri := ref.File()
uri := ref.FileEP()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
if err != nil {
+1 -1
View File
@@ -16,7 +16,7 @@ func (cli *Client) ListCollections(ctx context.Context, rawpath string) ([]byte,
if err != nil {
return list, err
}
uri := ref.Collections()
uri := ref.CollectionsEP()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
if err != nil {
+1 -1
View File
@@ -16,7 +16,7 @@ func (cli *Client) ListFiles(ctx context.Context, rawpath string) ([]byte, error
if err != nil {
return list, err
}
uri := ref.Files()
uri := ref.FilesEP()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
if err != nil {
+1 -1
View File
@@ -14,7 +14,7 @@ func (cli *Client) PutFile(ctx context.Context, rawpath string, src io.Reader, s
if err != nil {
return err
}
uri := ref.File()
uri := ref.FileEP()
req, err := http.NewRequestWithContext(ctx, http.MethodPut, uri, src)
if err != nil {
return err
+4 -4
View File
@@ -68,22 +68,22 @@ func (repo *Referer) DryRun(yesno bool) {
repo.values.Set("dryRun", strconv.FormatBool(yesno))
}
func (repo *Referer) File() string {
func (repo *Referer) FileEP() string {
curl := repo.urlobj.JoinPath("/v3/api/file/", repo.resource)
return curl.String()
}
func (repo *Referer) Files() string {
func (repo *Referer) FilesEP() string {
curl := repo.urlobj.JoinPath("/v3/api/files/", repo.resource)
return curl.String()
}
func (repo *Referer) Collection() string {
func (repo *Referer) CollectionEP() string {
curl := repo.urlobj.JoinPath("/v3/api/collection/", repo.resource)
return curl.String()
}
func (repo *Referer) Collections() string {
func (repo *Referer) CollectionsEP() string {
curl := repo.urlobj.JoinPath("/v3/api/collections/", repo.resource)
return curl.String()
}
+1 -1
View File
@@ -16,7 +16,7 @@ func (cli *Client) BlobExists(ctx context.Context, rawrepo string, digest string
if err != nil {
return exist, size, err
}
uri := ref.Blob(digest)
uri := ref.BlobEP(digest)
fmt.Println(uri)
req, err := http.NewRequestWithContext(ctx, http.MethodHead, uri, nil)
+1 -1
View File
@@ -14,7 +14,7 @@ func xxxTestClientBlobExists(t *testing.T) {
"mirror.gcr.io/alpine",
}
for _, rawrepo := range rawrepos {
cli := NewClient()
cli := NewClient(nil, nil)
ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
digstr := "sha256:3b8747b05489980f63da1d2b8e5a444c55777f69540394397b0bc1c76c3e41f2"
exist, size, err := cli.BlobExists(ctx, rawrepo, digstr)
+9 -12
View File
@@ -11,24 +11,19 @@ type Client struct {
userAgent string
}
func NewClient() *Client {
defaultTripper := NewDefaultTransport()
httpClient := &http.Client{
Transport: defaultTripper,
}
return &Client{
httpClient: httpClient,
userAgent: "ociClient/1.0",
}
}
func NewClientWithTransport(transport http.RoundTripper, mwFunc ...MiddlewareFunc) *Client {
func NewClient(transport http.RoundTripper, mwFuncs ...MiddlewareFunc) *Client {
if transport == nil {
transport = NewDefaultTransport()
}
for _, mwFunc := range mwFuncs {
if mwFunc != nil {
transport = mwFunc(transport)
}
}
httpClient := &http.Client{
Transport: transport,
}
return &Client{
httpClient: httpClient,
userAgent: "ociClient/1.0",
@@ -42,8 +37,10 @@ func (cli *Client) SetTransport(transport http.RoundTripper) {
type MiddlewareFunc func(next http.RoundTripper) http.RoundTripper
func (cli *Client) UseMiddleware(mwFunc MiddlewareFunc) {
if mwFunc != nil {
cli.httpClient.Transport = mwFunc(cli.httpClient.Transport)
}
}
// ExampleMiddleware
func NewExampleMiddleware() MiddlewareFunc {
+1 -1
View File
@@ -14,7 +14,7 @@ func (cli *Client) DeleteBlob(ctx context.Context, rawrepo, digest string) (bool
if err != nil {
return exist, err
}
uri := ref.Blob(digest)
uri := ref.BlobEP(digest)
req, err := http.NewRequestWithContext(ctx, http.MethodDelete, uri, nil)
if err != nil {
return exist, err
+12 -2
View File
@@ -6,7 +6,17 @@ import (
"net/http"
)
func (cli *Client) DeleteManifest(ctx context.Context, rawrepo, tag string) (bool, error) {
func (cli *Client) DeleteImage(ctx context.Context, rawrepo string) (bool, error) {
var err error
var exist bool
ref, err := NewReferer(rawrepo)
if err != nil {
return exist, err
}
return cli.DeleteManifest(ctx, ref.ManifestEP())
}
func (cli *Client) DeleteManifest(ctx context.Context, rawrepo string) (bool, error) {
var err error
var exist bool
@@ -14,7 +24,7 @@ func (cli *Client) DeleteManifest(ctx context.Context, rawrepo, tag string) (boo
if err != nil {
return exist, err
}
uri := ref.Manifest(tag)
uri := ref.ManifestEP()
req, err := http.NewRequestWithContext(ctx, http.MethodDelete, uri, nil)
if err != nil {
return exist, err
+1 -1
View File
@@ -16,7 +16,7 @@ func (cli *Client) GetBlob(ctx context.Context, rawrepo string, writer io.Writer
if err != nil {
return exist, err
}
uri := ref.Blob(digest)
uri := ref.BlobEP(digest)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
if err != nil {
+1 -1
View File
@@ -15,7 +15,7 @@ func xxxTestClientGetBlob(t *testing.T) {
"mirror.gcr.io/alpine",
}
for _, rawrepo := range rawrepos {
cli := NewClient()
cli := NewClient(nil, nil)
ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
buffer := bytes.NewBuffer(nil)
digstr := "sha256:3b8747b05489980f63da1d2b8e5a444c55777f69540394397b0bc1c76c3e41f2"
+3 -3
View File
@@ -13,7 +13,7 @@ import (
func (cli *Client) GetCatalog(ctx context.Context, rawrepo string) ([]string, error) {
var err error
list := make([]string, 0)
catdata, err := cli.getRawCatalog(ctx, rawrepo)
catdata, err := cli.GetRawCatalog(ctx, rawrepo)
if err != nil {
return list, err
}
@@ -24,7 +24,7 @@ func (cli *Client) GetCatalog(ctx context.Context, rawrepo string) ([]string, er
return list, err
}
func (cli *Client) getRawCatalog(ctx context.Context, rawrepo string) ([]byte, error) {
func (cli *Client) GetRawCatalog(ctx context.Context, rawrepo string) ([]byte, error) {
var err error
var list []byte
@@ -32,7 +32,7 @@ func (cli *Client) getRawCatalog(ctx context.Context, rawrepo string) ([]byte, e
if err != nil {
return list, err
}
uri := ref.Catalog()
uri := ref.CatalogEP()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
if err != nil {
+8 -7
View File
@@ -13,17 +13,18 @@ import (
ocidigest "github.com/opencontainers/go-digest"
)
func (cli *Client) GetManifest(ctx context.Context, rawrepo, tag string) (bool, string, []byte, error) {
func (cli *Client) GetRawManifest(ctx context.Context, rawrepo string) (bool, string, []byte, error) {
var err error
var exist bool
var mime string
var man []byte
fmt.Printf("=== %s\n", rawrepo)
ref, err := NewReferer(rawrepo)
if err != nil {
return exist, mime, man, err
}
uri := ref.Manifest(tag)
uri := ref.ManifestEP()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
if err != nil {
@@ -44,6 +45,11 @@ func (cli *Client) GetManifest(ctx context.Context, rawrepo, tag string) (bool,
err := fmt.Errorf("Unxected response code %s", resp.Status)
return exist, mime, man, err
}
digstr := resp.Header.Get("Docker-Content-Digest")
if digstr == "" {
err := fmt.Errorf("Empty digest declaration")
return exist, mime, man, err
}
contentLength := resp.Header.Get("Content-Length")
if contentLength == "" {
err = errors.New("Empty Content-Length header")
@@ -58,11 +64,6 @@ func (cli *Client) GetManifest(ctx context.Context, rawrepo, tag string) (bool,
err := fmt.Errorf("Empty MIME type declaration")
return exist, mime, man, err
}
digstr := resp.Header.Get("Docker-Content-Digest")
if digstr == "" {
err := fmt.Errorf("Empty digest declaration")
return exist, mime, man, err
}
digstr = strings.ToLower(digstr)
digobj, err := ocidigest.Parse(digstr)
if err != nil {
+2 -2
View File
@@ -18,9 +18,9 @@ func xxxTestClientGetManifest(t *testing.T) {
"sha256:29e5ba63e79337818e6c63cfcc68e2ab4e9ca483853b2de303bfbfba9372426c",
}
for _, tag := range tags {
cli := NewClient()
cli := NewClient(nil, nil)
ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
exist, mime, man, err := cli.GetManifest(ctx, rawrepo, tag)
exist, mime, man, err := cli.GetRawManifest(ctx, rawrepo+":"+tag)
require.NoError(t, err)
require.True(t, exist)
+3 -3
View File
@@ -13,7 +13,7 @@ import (
func (cli *Client) GetReferers(ctx context.Context, rawrepo, digest string) ([]string, error) {
var err error
list := make([]string, 0)
tagdata, err := cli.getRawReferers(ctx, rawrepo, digest)
tagdata, err := cli.GetRawReferers(ctx, rawrepo, digest)
if err != nil {
return list, err
}
@@ -24,7 +24,7 @@ func (cli *Client) GetReferers(ctx context.Context, rawrepo, digest string) ([]s
return list, err
}
func (cli *Client) getRawReferers(ctx context.Context, rawrepo, digest string) ([]byte, error) {
func (cli *Client) GetRawReferers(ctx context.Context, rawrepo, digest string) ([]byte, error) {
var err error
var list []byte
@@ -32,7 +32,7 @@ func (cli *Client) getRawReferers(ctx context.Context, rawrepo, digest string) (
if err != nil {
return list, err
}
uri := ref.Referers(digest)
uri := ref.ReferersEP(digest)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
if err != nil {
+3 -3
View File
@@ -13,7 +13,7 @@ import (
func (cli *Client) GetTags(ctx context.Context, rawrepo string) ([]string, error) {
var err error
list := make([]string, 0)
tagdata, err := cli.getRawTags(ctx, rawrepo)
tagdata, err := cli.GetRawTags(ctx, rawrepo)
if err != nil {
return list, err
}
@@ -24,7 +24,7 @@ func (cli *Client) GetTags(ctx context.Context, rawrepo string) ([]string, error
return list, err
}
func (cli *Client) getRawTags(ctx context.Context, rawrepo string) ([]byte, error) {
func (cli *Client) GetRawTags(ctx context.Context, rawrepo string) ([]byte, error) {
var err error
var list []byte
@@ -32,7 +32,7 @@ func (cli *Client) getRawTags(ctx context.Context, rawrepo string) ([]byte, erro
if err != nil {
return list, err
}
uri := ref.Tags()
uri := ref.TagsEP()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
if err != nil {
+7 -6
View File
@@ -9,12 +9,12 @@ import (
"time"
)
func xxxxTestClientGetToken(t *testing.T) {
func xxxTestClientGetToken(t *testing.T) {
var token string
var err error
{
cli := NewClient()
cli.UseMiddleware(NewBasicAuthMiddleware("xxxxxxx", "xxxxxxxxxx"))
cli := NewClient(nil, nil)
//cli.UseMiddleware(NewBasicAuthMiddleware("xxxxxxx", "xxxxxxxxxx"))
ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
token, err = cli.GetToken(ctx, "https://auth.docker.io/token")
require.NoError(t, err)
@@ -23,11 +23,12 @@ func xxxxTestClientGetToken(t *testing.T) {
fmt.Printf("Token: %s\n", token)
{
rawrepo := "docker.io/onborodin/toolbox:0.18"
cli := NewClient()
cli.UseMiddleware(NewBearerAuthMiddleware(token))
cli := NewClient(nil, nil)
//cli.UseMiddleware(NewBearerAuthMiddleware(token))
ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
loc, err := cli.GetUpload(ctx, rawrepo)
id, loc, err := cli.GetUpload(ctx, rawrepo)
require.NoError(t, err)
fmt.Printf("Upload ID: %s\n", id)
fmt.Printf("Upload Location: %s\n", loc)
}
+14 -9
View File
@@ -6,37 +6,42 @@ import (
"net/http"
)
func (cli *Client) GetUpload(ctx context.Context, rawrepo string) (string, error) {
func (cli *Client) GetUpload(ctx context.Context, rawrepo string) (string, string, error) {
var err error
var loc string
var id string
ref, err := NewReferer(rawrepo)
if err != nil {
return loc, err
return id, loc, err
}
uri := ref.Upload()
uri := ref.UploadEP()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri, nil)
if err != nil {
return loc, err
return id, loc, err
}
req.Header.Set("User-Agent", cli.userAgent)
req.Header.Set("Accept", "*/*")
resp, err := cli.httpClient.Do(req)
if err != nil {
return loc, err
return id, loc, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusAccepted {
err := fmt.Errorf("Unxected response code %s", resp.Status)
return loc, err
return id, loc, err
}
loc = resp.Header.Get("Location")
if loc == "" {
err := fmt.Errorf("Empty location declaration")
return loc, err
return id, loc, err
}
return loc, err
id = resp.Header.Get("Docker-Upload-UUID")
if id == "" {
err := fmt.Errorf("Empty Docker-Upload-UUID declaration")
return id, loc, err
}
return id, loc, err
}
+5 -4
View File
@@ -15,19 +15,20 @@ import (
func xxxTestClientGetUpload(t *testing.T) {
rawrepos := []string{
"mstore:mstore@localhost:1025/alpine:3.20.0",
"localhost:1025/alpine:3.20.0",
}
cli := NewClient()
cli.UseMiddleware(NewBasicAuthMiddleware("mstore", "mstore"))
mw := NewBasicAuthMiddleware("mstore", "mstore")
cli := NewClient(nil, mw)
for _, rawrepo := range rawrepos {
var err error
var loc string
{
ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
loc, err = cli.GetUpload(ctx, rawrepo)
id, loc, err := cli.GetUpload(ctx, rawrepo)
require.NoError(t, err)
fmt.Printf("Upload Location: %s\n", loc)
fmt.Printf("Upload ID: %s\n", id)
}
{
srcsize := 1024 + 145
+49
View File
@@ -85,6 +85,10 @@ func NewImagerFromPlace(place string) (*Imager, error) {
return imager, err
}
func (ima *Imager) Index() *ocispec.Index {
return &ima.index
}
func (ima *Imager) ReadManifest(ctx context.Context, digstr string) (bool, string, []byte, error) {
var exist bool
var mime string
@@ -181,6 +185,28 @@ func (ima *Imager) writeManifest(ctx context.Context, digest, mime string, paylo
return err
}
func (ima *Imager) readIndex(ctx context.Context) (*ocispec.Index, error) {
// Read index
var err error
res := &ocispec.Index{}
ipath := filepath.Join(ima.place, ocispec.ImageIndexFile)
ifile, err := os.OpenFile(ipath, os.O_RDONLY, 0)
if err != nil {
return res, err
}
defer ifile.Close()
buffer := bytes.NewBuffer(nil)
_, err = io.Copy(buffer, buffer)
if err != nil {
return res, err
}
err = json.Unmarshal(buffer.Bytes(), res)
if err != nil {
return res, err
}
return res, err
}
func (ima *Imager) writeIndex(ctx context.Context) error {
indexdat, err := json.Marshal(ima.index)
if err != nil {
@@ -293,3 +319,26 @@ func (ima *Imager) ReadLayer(ctx context.Context, digstr string, writer io.Write
exist = true
return exist, err
}
func (ima *Imager) LayerReader(ctx context.Context, digstr string) (int64, io.ReadCloser, error) {
var reader io.ReadCloser
var err error
var size int64
digobj, err := ocidigest.Parse(digstr)
if err != nil {
return size, reader, err
}
subdir := string(digobj.Algorithm())
mpath := filepath.Join(ima.place, ocispec.ImageBlobsDir, subdir, digobj.Encoded())
stat, err := os.Stat(mpath)
if err != nil {
return size, reader, err
}
size = stat.Size()
mfile, err := os.OpenFile(mpath, os.O_RDONLY, 0)
if err != nil {
return size, reader, err
}
reader = mfile
return size, reader, err
}
+2 -2
View File
@@ -7,7 +7,7 @@ import (
"strconv"
)
func (cli *Client) ManifestExists(ctx context.Context, rawrepo, tag string) (bool, string, int64, string, error) {
func (cli *Client) ManifestExists(ctx context.Context, rawrepo string) (bool, string, int64, string, error) {
var err error
var exist bool
var mime string
@@ -18,7 +18,7 @@ func (cli *Client) ManifestExists(ctx context.Context, rawrepo, tag string) (boo
if err != nil {
return exist, mime, size, csum, err
}
uri := ref.Manifest(tag)
uri := ref.ManifestEP()
req, err := http.NewRequestWithContext(ctx, http.MethodHead, uri, nil)
if err != nil {
+2 -2
View File
@@ -17,9 +17,9 @@ func xxxTestClientManifestExists(t *testing.T) {
}
for _, tag := range tags {
cli := NewClient()
cli := NewClient(nil, nil)
ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
exist, mime, size, csum, err := cli.ManifestExists(ctx, rawrepo, tag)
exist, mime, size, csum, err := cli.ManifestExists(ctx, rawrepo+":"+tag)
require.NoError(t, err)
require.True(t, exist)
+1 -1
View File
@@ -16,7 +16,7 @@ func (cli *Client) PatchUpload(ctx context.Context, rawrepo string, src io.Reade
if err != nil {
return ouloc, err
}
uri, err := ref.Patch(uploc)
uri, err := ref.PatchEP(uploc)
if err != nil {
return ouloc, err
}
+12 -8
View File
@@ -18,26 +18,26 @@ const (
MediaTypeOIMv1 = "application/vnd.oci.image.manifest.v1+json"
)
type Downloader struct {
type Loader struct {
cli *Client
}
func NewDownloader(client *Client) *Downloader {
return &Downloader{
func NewLoader(client *Client) *Loader {
return &Loader{
cli: client,
}
}
func (down *Downloader) Pull(ctx context.Context, rawref, dir, osname, arch string) error {
func (down *Loader) Pull(ctx context.Context, rawref, dir, osname, arch string) error {
var err error
ref, err := ParseReference(rawref)
ref, err := NewReferer(rawref)
if err != nil {
return err
}
rawrepo := ref.Repo()
rawrepo := ref.RawRepo()
tag := ref.Tag()
exist, mime, man, err := down.cli.GetManifest(ctx, rawrepo, tag)
exist, mime, man, err := down.cli.GetRawManifest(ctx, ref.Raw())
if err != nil {
return err
}
@@ -64,8 +64,12 @@ func (down *Downloader) Pull(ctx context.Context, rawref, dir, osname, arch stri
}
}
}
if digstr == "" {
err = errors.New("Manifest for required arch and OS not found")
return err
}
fmt.Printf("Tag: %s\n", tag)
exist, mime, man, err = down.cli.GetManifest(ctx, rawrepo, tag)
exist, mime, man, err = down.cli.GetRawManifest(ctx, ref.RawWithTag(digstr))
if err != nil {
return err
}
+2 -2
View File
@@ -12,10 +12,10 @@ import (
func TestPullImage(t *testing.T) {
ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
cli := NewClientWithTransport(nil, nil)
cli := NewClient(nil, nil)
require.NotNil(t, cli)
down := NewDownloader(cli)
down := NewLoader(cli)
require.NotNil(t, down)
rawref := "mirror.gcr.io/alpine:3.20.0"
+1 -1
View File
@@ -16,7 +16,7 @@ func (cli *Client) PutManifest(ctx context.Context, rawrepo, tag string, man []b
if err != nil {
return err
}
uri := ref.Manifest(tag)
uri := ref.ManifestEP()
buffer := bytes.NewBuffer(man)
req, err := http.NewRequestWithContext(ctx, http.MethodPut, uri, buffer)
+2 -2
View File
@@ -8,7 +8,7 @@ import (
"strconv"
)
func (cli *Client) PutUpload(ctx context.Context, rawrepo string, src io.Reader, uploc, digest string, size int64) (string, error) {
func (cli *Client) PutUpload(ctx context.Context, rawrepo string, src io.Reader, id, digest string, size int64) (string, error) {
var err error
var bloc string
@@ -16,7 +16,7 @@ func (cli *Client) PutUpload(ctx context.Context, rawrepo string, src io.Reader,
if err != nil {
return bloc, err
}
uri, err := ref.Put(uploc, digest)
uri, err := ref.PutEP(id, digest)
if err != nil {
return bloc, err
}
-60
View File
@@ -1,60 +0,0 @@
package repocli
import (
"errors"
"net/url"
"path"
"strings"
)
type Reference struct {
urlobj *url.URL
user, pass string
base, tag string
}
func ParseReference(rawref string) (*Reference, error) {
ref := &Reference{}
if !strings.Contains(rawref, "://") {
rawref = "https://" + rawref
}
urlobj, err := url.Parse(rawref)
if err != nil {
return ref, err
}
if urlobj.User != nil {
ref.user = urlobj.User.Username()
ref.pass, _ = urlobj.User.Password()
urlobj.User = nil
}
ref.urlobj = urlobj
repotag := strings.SplitN(ref.urlobj.Path, ":", 2)
if len(repotag) != 2 {
err = errors.New("Incorrect reference format")
return ref, err
}
ref.base = repotag[0]
ref.tag = repotag[1]
ref.urlobj.Path = "/"
ref.urlobj = urlobj
return ref, err
}
func (ref *Reference) String() string {
return path.Join(ref.urlobj.Host, ref.base+":"+ref.tag)
}
func (ref *Reference) Repo() string {
return path.Join(ref.urlobj.Host, ref.base)
}
func (ref *Reference) Tag() string {
return ref.tag
}
func (ref *Reference) Userinfo() (string, string) {
return ref.user, ref.pass
}
+60 -39
View File
@@ -2,63 +2,84 @@ package repocli
import (
"net/url"
"path"
"strings"
)
type Referer struct {
urlobj *url.URL
user, pass string
base string
base, tag string
}
func NewReferer(rawrepo string) (*Referer, error) {
repo := &Referer{}
if !strings.Contains(rawrepo, "://") {
rawrepo = "https://" + rawrepo
func NewReferer(rawref string) (*Referer, error) {
ref := &Referer{}
if !strings.Contains(rawref, "://") {
rawref = "https://" + rawref
}
urlobj, err := url.Parse(rawrepo)
urlobj, err := url.Parse(rawref)
if err != nil {
return repo, err
return ref, err
}
if urlobj.User != nil {
repo.user = urlobj.User.Username()
repo.pass, _ = urlobj.User.Password()
ref.user = urlobj.User.Username()
ref.pass, _ = urlobj.User.Password()
urlobj.User = nil
}
repo.urlobj = urlobj
repo.base = repo.urlobj.Path
repo.urlobj.Path = "/"
repo.urlobj = urlobj
ref.urlobj = urlobj
if strings.Contains(ref.urlobj.Path, ":") {
m := strings.SplitN(ref.urlobj.Path, ":", 2)
ref.base = m[0]
ref.tag = m[1]
} else {
ref.base = ref.urlobj.Path
}
ref.urlobj.Path = "/"
ref.urlobj = urlobj
return repo, err
return ref, err
}
func (repo *Referer) String() string {
curl := repo.urlobj.JoinPath(repo.base)
func (ref *Referer) RawWithTag(tag string) string {
return path.Join(ref.urlobj.Host, ref.base+":"+tag)
}
func (ref *Referer) Raw() string {
if ref.tag != "" {
return path.Join(ref.urlobj.Host, ref.base+":"+ref.tag)
}
return path.Join(ref.urlobj.Host, ref.base)
}
func (ref *Referer) RawRepo() string {
return path.Join(ref.urlobj.Host, ref.base)
}
func (ref *Referer) Tag() string {
return ref.tag
}
func (ref *Referer) ManifestEP() string {
curl := ref.urlobj.JoinPath("/v2", ref.base, "/manifests", ref.tag)
return curl.String()
}
func (repo *Referer) Manifest(tag string) string {
curl := repo.urlobj.JoinPath("/v2", repo.base, "/manifests", tag)
func (ref *Referer) BlobEP(digest string) string {
curl := ref.urlobj.JoinPath("/v2", ref.base, "/blobs", digest)
return curl.String()
}
func (repo *Referer) Blob(digest string) string {
curl := repo.urlobj.JoinPath("/v2", repo.base, "/blobs", digest)
func (ref *Referer) UploadEP() string {
curl := ref.urlobj.JoinPath("/v2", ref.base, "/blobs/uploads/")
return curl.String()
}
func (repo *Referer) Upload() string {
curl := repo.urlobj.JoinPath("/v2", repo.base, "/blobs/uploads/")
return curl.String()
}
func (repo *Referer) Patch(loc string) (string, error) {
func (ref *Referer) PatchEP(loc string) (string, error) {
var curl *url.URL
var out string
var err error
if isUUID(loc) {
curl = repo.urlobj.JoinPath("/v2/", repo.base, "/blobs/uploads/", loc)
curl = ref.urlobj.JoinPath("/v2/", ref.base, "/blobs/uploads/", loc)
return curl.String(), nil
}
if strings.Contains(loc, "://") {
@@ -67,26 +88,26 @@ func (repo *Referer) Patch(loc string) (string, error) {
return out, err
}
} else {
curl = repo.urlobj.JoinPath(loc)
curl = ref.urlobj.JoinPath(loc)
}
out = curl.String()
return out, err
}
func (repo *Referer) Put(loc, digest string) (string, error) {
func (ref *Referer) PutEP(loc, digest string) (string, error) {
var curl *url.URL
var out string
var err error
if isUUID(loc) {
curl = repo.urlobj.JoinPath("/v2/", repo.base, "/blobs/uploads/", loc)
curl = ref.urlobj.JoinPath("/v2/", ref.base, "/blobs/uploads/", loc)
} else if strings.Contains(loc, "://") {
curl, err = url.Parse(loc)
if err != nil {
return out, err
}
} else {
curl = repo.urlobj.JoinPath(loc)
curl = ref.urlobj.JoinPath(loc)
}
query := curl.Query()
query.Set("digest", digest)
@@ -95,21 +116,21 @@ func (repo *Referer) Put(loc, digest string) (string, error) {
return out, err
}
func (repo *Referer) Tags() string {
curl := repo.urlobj.JoinPath("/v2", repo.base, "/tags/list")
func (ref *Referer) TagsEP() string {
curl := ref.urlobj.JoinPath("/v2", ref.base, "/tags/list")
return curl.String()
}
func (repo *Referer) Referers(digest string) string {
curl := repo.urlobj.JoinPath("/v2", repo.base, "/referrers/", digest)
func (ref *Referer) ReferersEP(digest string) string {
curl := ref.urlobj.JoinPath("/v2", ref.base, "/referers/", digest)
return curl.String()
}
func (repo *Referer) Catalog() string {
curl := repo.urlobj.JoinPath("/v2/_catalog")
func (ref *Referer) CatalogEP() string {
curl := ref.urlobj.JoinPath("/v2/_catalog")
return curl.String()
}
func (repo *Referer) Userinfo() (string, string) {
return repo.user, repo.pass
func (ref *Referer) Userinfo() (string, string) {
return ref.user, ref.pass
}
+6 -6
View File
@@ -10,18 +10,18 @@ import (
)
func xxxTestResrerer(t *testing.T) {
ref, err := NewReferer("registry.example.com/lib/alpine")
ref, err := NewReferer("registry.example.com/lib/alpine:3.30.0")
require.NoError(t, err)
fmt.Printf("Manifest:\t%s\n", ref.Manifest("3.30.0"))
fmt.Printf("Manifest:\t%s\n", ref.ManifestEP())
digobj := digest.NewDigest(digest.SHA256, []byte("qwerty"))
fmt.Printf("Blob:\t\t%s\n", ref.Blob(digobj.Encoded()))
fmt.Printf("POST:\t\t%s\n", ref.Upload())
fmt.Printf("Blob:\t\t%s\n", ref.BlobEP(digobj.Encoded()))
fmt.Printf("POST:\t\t%s\n", ref.UploadEP())
uuid := "8be4df61-93ca-11d2-aa0d-00e098032b8c"
rawurl, err := ref.Patch(uuid)
rawurl, err := ref.PatchEP(uuid)
require.NoError(t, err)
fmt.Printf("PATH:\t\t%s\n", rawurl)
rawurl, err = ref.Put(uuid, digobj.Encoded())
rawurl, err = ref.PutEP(uuid, digobj.Encoded())
fmt.Printf("PUT:\t\t%s\n", rawurl)
}