repocli: first version of pull & push image
This commit is contained in:
@@ -52,7 +52,7 @@ func (hand *Handler) CheckAccess(rctx *router.Context) (bool, string, error) {
|
||||
|
||||
accountID = terms.AnonymousID
|
||||
|
||||
hand.logg.Debugf("URL: %s", rctx.URL().String())
|
||||
//hand.logg.Debugf("URL: %s", rctx.URL().String())
|
||||
authHeader := rctx.GetHeader("Authorization")
|
||||
hand.logg.Debugf("Authorization: %s", authHeader)
|
||||
if authHeader != "" {
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ func (hand *Handler) BlobExists(rctx *router.Context) {
|
||||
func (hand *Handler) PostUpload(rctx *router.Context) {
|
||||
name, _ := rctx.GetSubpath("name")
|
||||
|
||||
hand.DumpHeaders("PostUploads", rctx)
|
||||
//hand.DumpHeaders("PostUploads", rctx)
|
||||
|
||||
authorization := rctx.GetHeader("Authorization")
|
||||
if authorization == "" {
|
||||
|
||||
@@ -88,6 +88,7 @@ func (oper *Operator) PutManifest(ctx context.Context, params *PutManifestParams
|
||||
}
|
||||
|
||||
incomingManifestBytes := buffer.Bytes()
|
||||
|
||||
if int64(len(incomingManifestBytes)) != contentLength {
|
||||
err = fmt.Errorf("Mismatch Content-Length and received manifest size: %d vs %d",
|
||||
contentLength, len(incomingManifestBytes))
|
||||
@@ -163,11 +164,14 @@ func (oper *Operator) PutManifest(ctx context.Context, params *PutManifestParams
|
||||
if err != nil {
|
||||
return res, http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
// Starting manifest and blobs transaction
|
||||
err = oper.mdb.UpdateManifestWithBlobs(ctx, &incomingManifestDescr, addedBlobDescrs, uselessBlobDescrs)
|
||||
if err != nil {
|
||||
return res, http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
//goto end
|
||||
for _, blob := range uselessBlobDescrs {
|
||||
exists, _, err := oper.store.BlobExists(blob.Digest)
|
||||
if err != nil {
|
||||
@@ -201,6 +205,8 @@ func (oper *Operator) PutManifest(ctx context.Context, params *PutManifestParams
|
||||
}
|
||||
}
|
||||
|
||||
//end:
|
||||
|
||||
res.Location = fmt.Sprintf(`/v2/%s/manifests/%s`, params.Name, params.Reference)
|
||||
return res, http.StatusCreated, err
|
||||
}
|
||||
|
||||
@@ -129,7 +129,8 @@ func (db *Database) UpdateManifestWithBlobs(ctx context.Context, manifest *descr
|
||||
|
||||
// Manifest
|
||||
request = `UPDATE manifests SET contentType = $1, payload = $2, digest = $3, updated_at = $4, updated_by = $5
|
||||
WHERE name = $6 AND reference = $7, architecture = $8, os = $9, variant = $10`
|
||||
WHERE name = $6 AND reference = $7
|
||||
AND architecture = $8 AND os = $9 AND variant = $10`
|
||||
_, err = tx.Exec(request, manifest.ContentType, manifest.Payload, manifest.Digest,
|
||||
manifest.UpdatedAt, manifest.UpdatedBy,
|
||||
manifest.Name, manifest.Reference,
|
||||
|
||||
@@ -97,7 +97,7 @@ func (svc *Service) Build() error {
|
||||
|
||||
svc.rout.Post(`/v2/{name}/blobs/uploads/`, svc.hand.PostUpload)
|
||||
svc.rout.Patch(`/v2/{name}/blobs/uploads/{reference}`, svc.hand.PatchUpload)
|
||||
svc.rout.Put(`/v2/{name}/uploads/{reference}`, svc.hand.PutUpload)
|
||||
svc.rout.Put(`/v2/{name}/blobs/uploads/{reference}`, svc.hand.PutUpload)
|
||||
|
||||
svc.rout.Get(`/v2/{name}/blobs/{digest}`, svc.hand.GetBlob)
|
||||
svc.rout.Delete(`/v2/{name}/blobs/{digest}`, svc.hand.DeleteBlob)
|
||||
|
||||
@@ -1,37 +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 (
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func repackServiceURI(fileuri string) (string, string, string, error) {
|
||||
var err error
|
||||
var res, username, password string
|
||||
if !strings.Contains(fileuri, "://") {
|
||||
fileuri = "https://" + fileuri
|
||||
}
|
||||
uri, err := url.Parse(fileuri)
|
||||
if err != nil {
|
||||
return res, username, password, err
|
||||
}
|
||||
uri.Path = path.Clean(uri.Path)
|
||||
if uri.User != nil {
|
||||
username = uri.User.Username()
|
||||
password, _ = uri.User.Password()
|
||||
}
|
||||
uri.User = nil
|
||||
//uri.Scheme = "https"
|
||||
res = uri.String()
|
||||
return res, username, password, err
|
||||
}
|
||||
+16
-15
@@ -13,61 +13,62 @@ import (
|
||||
ocidigest "github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
func (cli *Client) GetRawManifest(ctx context.Context, rawrepo string) (bool, string, []byte, error) {
|
||||
func (cli *Client) GetRawManifest(ctx context.Context, rawrepo string) (bool, string, []byte, string, error) {
|
||||
var err error
|
||||
var exist bool
|
||||
var mime string
|
||||
var man []byte
|
||||
var digstr string
|
||||
|
||||
fmt.Printf("=== %s\n", rawrepo)
|
||||
ref, err := NewReferer(rawrepo)
|
||||
if err != nil {
|
||||
return exist, mime, man, err
|
||||
return exist, mime, man, digstr, err
|
||||
}
|
||||
uri := ref.ManifestEP()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
|
||||
if err != nil {
|
||||
return exist, mime, man, err
|
||||
return exist, mime, man, digstr, err
|
||||
}
|
||||
req.Header.Set("User-Agent", cli.userAgent)
|
||||
req.Header.Set("Accept", "*/*")
|
||||
resp, err := cli.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return exist, mime, man, err
|
||||
return exist, mime, man, digstr, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
return exist, mime, man, err
|
||||
return exist, mime, man, digstr, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
err := fmt.Errorf("Unxected response code %s", resp.Status)
|
||||
return exist, mime, man, err
|
||||
return exist, mime, man, digstr, err
|
||||
}
|
||||
digstr := resp.Header.Get("Docker-Content-Digest")
|
||||
digstr = resp.Header.Get("Docker-Content-Digest")
|
||||
if digstr == "" {
|
||||
err := fmt.Errorf("Empty digest declaration")
|
||||
return exist, mime, man, err
|
||||
return exist, mime, man, digstr, err
|
||||
}
|
||||
contentLength := resp.Header.Get("Content-Length")
|
||||
if contentLength == "" {
|
||||
err = errors.New("Empty Content-Length header")
|
||||
return exist, mime, man, err
|
||||
return exist, mime, man, digstr, err
|
||||
}
|
||||
manSize, err := strconv.ParseInt(contentLength, 10, 64)
|
||||
if err != nil {
|
||||
return exist, mime, man, err
|
||||
return exist, mime, man, digstr, err
|
||||
}
|
||||
mime = resp.Header.Get("Content-Type")
|
||||
if mime == "" {
|
||||
err := fmt.Errorf("Empty MIME type declaration")
|
||||
return exist, mime, man, err
|
||||
return exist, mime, man, digstr, err
|
||||
}
|
||||
digstr = strings.ToLower(digstr)
|
||||
digobj, err := ocidigest.Parse(digstr)
|
||||
if err != nil {
|
||||
return exist, mime, man, err
|
||||
return exist, mime, man, digstr, err
|
||||
}
|
||||
verifier := digobj.Verifier()
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
@@ -75,13 +76,13 @@ func (cli *Client) GetRawManifest(ctx context.Context, rawrepo string) (bool, st
|
||||
recSize, err := Copy(ctx, mwriter, resp.Body)
|
||||
if manSize != recSize {
|
||||
err := fmt.Errorf("Mismatch declared and actual size")
|
||||
return exist, mime, man, err
|
||||
return exist, mime, man, digstr, err
|
||||
}
|
||||
man = buffer.Bytes()
|
||||
if !verifier.Verified() {
|
||||
err := fmt.Errorf("Mismatch digest declaration and actual")
|
||||
return exist, mime, man, err
|
||||
return exist, mime, man, digstr, err
|
||||
}
|
||||
exist = true
|
||||
return exist, mime, man, err
|
||||
return exist, mime, man, digstr, err
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func xxxTestClientGetManifest(t *testing.T) {
|
||||
func TestClientGetManifest(t *testing.T) {
|
||||
rawrepo := "mirror.gcr.io/alpine"
|
||||
tags := []string{
|
||||
"3.20.0",
|
||||
@@ -20,11 +20,12 @@ func xxxTestClientGetManifest(t *testing.T) {
|
||||
for _, tag := range tags {
|
||||
cli := NewClient(nil, nil)
|
||||
ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
exist, mime, man, err := cli.GetRawManifest(ctx, rawrepo+":"+tag)
|
||||
exist, mime, man, digstr, err := cli.GetRawManifest(ctx, rawrepo+":"+tag)
|
||||
require.NoError(t, err)
|
||||
require.True(t, exist)
|
||||
|
||||
fmt.Printf("Type: %s\n", mime)
|
||||
fmt.Printf("Digest: %s\n", digstr)
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
err = json.Indent(buffer, man, " ", " ")
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
@@ -35,9 +34,8 @@ func (down *Loader) Pull(ctx context.Context, rawref, dir, osname, arch string)
|
||||
return err
|
||||
}
|
||||
rawrepo := ref.RawRepo()
|
||||
tag := ref.Tag()
|
||||
|
||||
exist, mime, man, err := down.cli.GetRawManifest(ctx, ref.Raw())
|
||||
exist, mime, man, digstr, err := down.cli.GetRawManifest(ctx, ref.Raw())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -45,8 +43,7 @@ func (down *Loader) Pull(ctx context.Context, rawref, dir, osname, arch string)
|
||||
err = errors.New("Manifest not found")
|
||||
return err
|
||||
}
|
||||
|
||||
var digstr string
|
||||
tag := ref.Tag()
|
||||
if mime == MediaTypeOIIv1 || mime == MediaTypeDDMLv2 {
|
||||
var index ocispec.Index
|
||||
err = json.Unmarshal(man, &index)
|
||||
@@ -63,25 +60,24 @@ func (down *Loader) Pull(ctx context.Context, rawref, dir, osname, arch string)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if digstr == "" {
|
||||
if tag == "" {
|
||||
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.GetRawManifest(ctx, ref.RawWithTag(digstr))
|
||||
|
||||
exist, mime, man, digstr, err = down.cli.GetRawManifest(ctx, ref.RawWithTag(tag))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Mime: %s\n", mime)
|
||||
if !exist {
|
||||
err = errors.New("Manifest not found")
|
||||
return err
|
||||
}
|
||||
if mime != MediaTypeOIMv1 && mime != MediaTypeDDMv2 {
|
||||
} else if mime != MediaTypeOIMv1 && mime != MediaTypeDDMv2 {
|
||||
err = errors.New("Unknown manifest media type")
|
||||
return err
|
||||
}
|
||||
|
||||
var manifest ocispec.Manifest
|
||||
err = json.Unmarshal(man, &manifest)
|
||||
if err != nil {
|
||||
|
||||
@@ -22,4 +22,5 @@ func xxxTestPullImage(t *testing.T) {
|
||||
destdir := "aaa"
|
||||
err := down.Pull(ctx, rawref, destdir, "linux", "amd64")
|
||||
require.NoError(t, err)
|
||||
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ func (down *Loader) Push(ctx context.Context, rawref, dir, osname, arch string)
|
||||
index := imager.Index()
|
||||
for _, descr := range index.Manifests {
|
||||
digstr := descr.Digest.String()
|
||||
|
||||
mime := descr.MediaType
|
||||
_, _, mandata, err := imager.ReadManifest(ctx, digstr)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -54,8 +54,10 @@ func (down *Loader) Push(ctx context.Context, rawref, dir, osname, arch string)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = down.cli.PutManifest(ctx, ref.Raw(), mandata, mime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
|
||||
}
|
||||
|
||||
@@ -9,17 +9,17 @@ import (
|
||||
ocidigest "github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
func (cli *Client) PutManifest(ctx context.Context, rawrepo, tag string, man []byte, mime string) error {
|
||||
func (cli *Client) PutManifest(ctx context.Context, rawref string, man []byte, mime string) error {
|
||||
var err error
|
||||
|
||||
ref, err := NewReferer(rawrepo)
|
||||
ref, err := NewReferer(rawref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uri := ref.ManifestEP()
|
||||
|
||||
buffer := bytes.NewBuffer(man)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPut, uri, buffer)
|
||||
reader := bytes.NewReader(man)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPut, uri, reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -33,7 +33,7 @@ func (cli *Client) PutManifest(ctx context.Context, rawrepo, tag string, man []b
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusAccepted {
|
||||
if resp.StatusCode != http.StatusAccepted && resp.StatusCode != http.StatusCreated {
|
||||
err = fmt.Errorf("Manifest not accepted, code %d", resp.StatusCode)
|
||||
return err
|
||||
}
|
||||
|
||||
+3
-3
@@ -11,9 +11,9 @@
|
||||
package terms
|
||||
|
||||
const (
|
||||
AsFinePath string = "asFinePath"
|
||||
AsPrefix string = "asPrefix"
|
||||
AsRegexp string = "asRegexp"
|
||||
xxxAsFinePath string = "asFinePath"
|
||||
xxxAsPrefix string = "asPrefix"
|
||||
xxxAsRegexp string = "asRegexp"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
Reference in New Issue
Block a user