mirror of
https://github.com/regclient/regclient.git
synced 2025-07-29 09:01:11 +03:00
Refactor blob like manifests, add SetConfig
Signed-off-by: Brandon Mitchell <git@bmitch.net>
This commit is contained in:
2
blob.go
2
blob.go
@ -59,7 +59,7 @@ func (rc *RegClient) BlobCopy(ctx context.Context, refSrc ref.Ref, refTgt ref.Re
|
||||
return err
|
||||
}
|
||||
defer blobIO.Close()
|
||||
if _, _, err := rc.BlobPut(ctx, refTgt, d, blobIO, blobIO.Length()); err != nil {
|
||||
if _, _, err := rc.BlobPut(ctx, refTgt, d, blobIO, blobIO.GetDescriptor().Size); err != nil {
|
||||
rc.log.WithFields(logrus.Fields{
|
||||
"err": err,
|
||||
"src": refSrc.Reference,
|
||||
|
@ -208,7 +208,7 @@ func TestBlobGet(t *testing.T) {
|
||||
return
|
||||
}
|
||||
defer br.Close()
|
||||
if br.Length() != int64(blobLen) {
|
||||
if br.GetDescriptor().Size != int64(blobLen) {
|
||||
t.Errorf("Failed comparing blob length")
|
||||
}
|
||||
})
|
||||
|
@ -48,13 +48,13 @@ func TestBlob(t *testing.T) {
|
||||
t.Errorf("manifest get: %v", err)
|
||||
return
|
||||
}
|
||||
cd, err := m.GetConfigDigest()
|
||||
cd, err := m.GetConfig()
|
||||
if err != nil {
|
||||
t.Errorf("config digest: %v", err)
|
||||
return
|
||||
}
|
||||
// blob head
|
||||
bh, err := o.BlobHead(ctx, r, cd)
|
||||
bh, err := o.BlobHead(ctx, r, cd.Digest)
|
||||
if err != nil {
|
||||
t.Errorf("blob head: %v", err)
|
||||
return
|
||||
@ -64,7 +64,7 @@ func TestBlob(t *testing.T) {
|
||||
t.Errorf("blob head close: %v", err)
|
||||
}
|
||||
// blob get
|
||||
bg, err := o.BlobGet(ctx, r, cd)
|
||||
bg, err := o.BlobGet(ctx, r, cd.Digest)
|
||||
if err != nil {
|
||||
t.Errorf("blob get: %v", err)
|
||||
return
|
||||
@ -74,14 +74,14 @@ func TestBlob(t *testing.T) {
|
||||
t.Errorf("blob readall: %v", err)
|
||||
return
|
||||
}
|
||||
if bg.Digest() != cd {
|
||||
t.Errorf("blob digest mismatch, expected %s, received %s", cd.String(), bg.Digest().String())
|
||||
if bg.GetDescriptor().Digest != cd.Digest {
|
||||
t.Errorf("blob digest mismatch, expected %s, received %s", cd.Digest.String(), bg.GetDescriptor().Digest.String())
|
||||
}
|
||||
err = bg.Close()
|
||||
if err != nil {
|
||||
t.Errorf("blob get close: %v", err)
|
||||
}
|
||||
bFS, err := os.ReadFile(fmt.Sprintf("testdata/regctl/blobs/%s/%s", cd.Algorithm().String(), cd.Encoded()))
|
||||
bFS, err := os.ReadFile(fmt.Sprintf("testdata/regctl/blobs/%s/%s", cd.Digest.Algorithm().String(), cd.Digest.Encoded()))
|
||||
if err != nil {
|
||||
t.Errorf("blob read file: %v", err)
|
||||
}
|
||||
@ -90,7 +90,7 @@ func TestBlob(t *testing.T) {
|
||||
}
|
||||
|
||||
// toOCIConfig
|
||||
bg, err = o.BlobGet(ctx, r, cd)
|
||||
bg, err = o.BlobGet(ctx, r, cd.Digest)
|
||||
if err != nil {
|
||||
t.Errorf("blob get 2: %v", err)
|
||||
return
|
||||
@ -99,15 +99,15 @@ func TestBlob(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("to oci config: %v", err)
|
||||
}
|
||||
if ociConf.Digest() != cd {
|
||||
t.Errorf("config digest mismatch, expected %s, received %s", cd.String(), ociConf.Digest().String())
|
||||
if ociConf.GetDescriptor().Digest != cd.Digest {
|
||||
t.Errorf("config digest mismatch, expected %s, received %s", cd.Digest.String(), ociConf.GetDescriptor().Digest.String())
|
||||
}
|
||||
|
||||
// blob put (to memfs)
|
||||
fm := rwfs.MemNew()
|
||||
om := New(WithFS(fm))
|
||||
bRdr := bytes.NewReader(bBytes)
|
||||
bpd, bpl, err := om.BlobPut(ctx, r, cd, bRdr, int64(len(bBytes)))
|
||||
bpd, bpl, err := om.BlobPut(ctx, r, cd.Digest, bRdr, int64(len(bBytes)))
|
||||
if err != nil {
|
||||
t.Errorf("blob put: %v", err)
|
||||
return
|
||||
@ -115,10 +115,10 @@ func TestBlob(t *testing.T) {
|
||||
if bpl != int64(len(bBytes)) {
|
||||
t.Errorf("blob put length, expected %d, received %d", len(bBytes), bpl)
|
||||
}
|
||||
if bpd != cd {
|
||||
t.Errorf("blob put digest, expected %s, received %s", cd, bpd)
|
||||
if bpd != cd.Digest {
|
||||
t.Errorf("blob put digest, expected %s, received %s", cd.Digest, bpd)
|
||||
}
|
||||
fd, err := fm.Open(fmt.Sprintf("testdata/regctl/blobs/%s/%s", cd.Algorithm().String(), cd.Encoded()))
|
||||
fd, err := fm.Open(fmt.Sprintf("testdata/regctl/blobs/%s/%s", cd.Digest.Algorithm().String(), cd.Digest.Encoded()))
|
||||
if err != nil {
|
||||
t.Errorf("blob put open file: %v", err)
|
||||
}
|
||||
|
@ -210,7 +210,7 @@ func TestBlobGet(t *testing.T) {
|
||||
return
|
||||
}
|
||||
defer br.Close()
|
||||
if br.Length() != int64(blobLen) {
|
||||
if br.GetDescriptor().Size != int64(blobLen) {
|
||||
t.Errorf("Failed comparing blob length")
|
||||
}
|
||||
})
|
||||
|
@ -16,12 +16,13 @@ type Blob interface {
|
||||
}
|
||||
|
||||
type blobConfig struct {
|
||||
desc ociv1.Descriptor
|
||||
header http.Header
|
||||
image ociv1.Image
|
||||
r ref.Ref
|
||||
rdr io.Reader
|
||||
resp *http.Response
|
||||
desc ociv1.Descriptor
|
||||
header http.Header
|
||||
image *ociv1.Image
|
||||
r ref.Ref
|
||||
rdr io.Reader
|
||||
resp *http.Response
|
||||
rawBody []byte
|
||||
}
|
||||
|
||||
type Opts func(*blobConfig)
|
||||
@ -43,7 +44,14 @@ func WithHeader(header http.Header) Opts {
|
||||
// WithImage provides the OCI Image config needed for config blobs
|
||||
func WithImage(image ociv1.Image) Opts {
|
||||
return func(bc *blobConfig) {
|
||||
bc.image = image
|
||||
bc.image = &image
|
||||
}
|
||||
}
|
||||
|
||||
// WithRawBody defines the raw blob contents for OCIConfig
|
||||
func WithRawBody(raw []byte) Opts {
|
||||
return func(bc *blobConfig) {
|
||||
bc.rawBody = raw
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,7 +73,7 @@ func WithRef(r ref.Ref) Opts {
|
||||
func WithResp(resp *http.Response) Opts {
|
||||
return func(bc *blobConfig) {
|
||||
bc.resp = resp
|
||||
if bc.header == nil {
|
||||
if bc.header == nil && resp != nil {
|
||||
bc.header = resp.Header
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package blob
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@ -63,6 +64,11 @@ var (
|
||||
ContentLength: exLen,
|
||||
Body: io.NopCloser(bytes.NewReader(exBlob)),
|
||||
}
|
||||
exDesc = ociv1.Descriptor{
|
||||
MediaType: exMT,
|
||||
Digest: exDigest,
|
||||
Size: exLen,
|
||||
}
|
||||
)
|
||||
|
||||
func TestCommon(t *testing.T) {
|
||||
@ -141,14 +147,14 @@ func TestCommon(t *testing.T) {
|
||||
t.Errorf("rawbody, expected %s, received %s", string(tt.eBytes), string(bb))
|
||||
}
|
||||
}
|
||||
if tt.eDigest != "" && b.Digest() != tt.eDigest {
|
||||
t.Errorf("digest, expected %s, received %s", tt.eDigest, b.Digest())
|
||||
if tt.eDigest != "" && b.GetDescriptor().Digest != tt.eDigest {
|
||||
t.Errorf("digest, expected %s, received %s", tt.eDigest, b.GetDescriptor().Digest)
|
||||
}
|
||||
if tt.eLen > 0 && b.Length() != tt.eLen {
|
||||
t.Errorf("length, expected %d, received %d", tt.eLen, b.Length())
|
||||
if tt.eLen > 0 && b.GetDescriptor().Size != tt.eLen {
|
||||
t.Errorf("length, expected %d, received %d", tt.eLen, b.GetDescriptor().Size)
|
||||
}
|
||||
if tt.eMT != "" && b.MediaType() != tt.eMT {
|
||||
t.Errorf("media type, expected %s, received %s", tt.eMT, b.MediaType())
|
||||
if tt.eMT != "" && b.GetDescriptor().MediaType != tt.eMT {
|
||||
t.Errorf("media type, expected %s, received %s", tt.eMT, b.GetDescriptor().MediaType)
|
||||
}
|
||||
if tt.eHeaders != nil {
|
||||
bHeader := b.RawHeaders()
|
||||
@ -160,6 +166,10 @@ func TestCommon(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
err := b.Close()
|
||||
if err != nil {
|
||||
t.Errorf("failed closing blob: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -218,11 +228,11 @@ func TestReader(t *testing.T) {
|
||||
t.Errorf("readall: %v", err)
|
||||
return
|
||||
}
|
||||
if b.Digest() != exDigest {
|
||||
t.Errorf("digest mismatch, expected %s, received %s", exDigest, b.Digest())
|
||||
if b.GetDescriptor().Digest != exDigest {
|
||||
t.Errorf("digest mismatch, expected %s, received %s", exDigest, b.GetDescriptor().Digest)
|
||||
}
|
||||
if b.Length() != exLen {
|
||||
t.Errorf("length mismatch, expected %d, received %d", exLen, b.Length())
|
||||
if b.GetDescriptor().Size != exLen {
|
||||
t.Errorf("length mismatch, expected %d, received %d", exLen, b.GetDescriptor().Size)
|
||||
}
|
||||
})
|
||||
|
||||
@ -243,8 +253,8 @@ func TestReader(t *testing.T) {
|
||||
t.Errorf("ToOCIConfig: %v", err)
|
||||
return
|
||||
}
|
||||
if exDigest != oc.Digest() {
|
||||
t.Errorf("digest, expected %s, received %s", exDigest, oc.Digest())
|
||||
if exDigest != oc.GetDescriptor().Digest {
|
||||
t.Errorf("digest, expected %s, received %s", exDigest, oc.GetDescriptor().Digest)
|
||||
}
|
||||
ocb, err := oc.RawBody()
|
||||
if err != nil {
|
||||
@ -273,6 +283,102 @@ func TestReader(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestOCI(t *testing.T) {
|
||||
ociConfig := ociv1.Image{}
|
||||
err := json.Unmarshal(exBlob, &ociConfig)
|
||||
if err != nil {
|
||||
t.Errorf("failed to unmarshal exBlob: %v", err)
|
||||
return
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
opts []Opts
|
||||
wantRaw []byte
|
||||
wantDesc ociv1.Descriptor
|
||||
}{
|
||||
{
|
||||
name: "RawBody",
|
||||
opts: []Opts{
|
||||
WithRawBody(exBlob),
|
||||
WithDesc(exDesc),
|
||||
},
|
||||
wantDesc: exDesc,
|
||||
wantRaw: exBlob,
|
||||
},
|
||||
{
|
||||
name: "Config with Default Desc",
|
||||
opts: []Opts{
|
||||
WithImage(ociConfig),
|
||||
},
|
||||
wantDesc: ociv1.Descriptor{MediaType: types.MediaTypeOCI1ImageConfig},
|
||||
},
|
||||
{
|
||||
name: "Config with Docker Desc",
|
||||
opts: []Opts{
|
||||
WithImage(ociConfig),
|
||||
WithDesc(exDesc),
|
||||
},
|
||||
wantDesc: ociv1.Descriptor{MediaType: exMT},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
oc := NewOCIConfig(tt.opts...)
|
||||
|
||||
if tt.wantDesc.Digest != "" && tt.wantDesc.Digest != oc.GetDescriptor().Digest {
|
||||
t.Errorf("digest, expected %s, received %s", tt.wantDesc.Digest, oc.GetDescriptor().Digest)
|
||||
}
|
||||
if tt.wantDesc.MediaType != "" && tt.wantDesc.MediaType != oc.GetDescriptor().MediaType {
|
||||
t.Errorf("media type, expected %s, received %s", tt.wantDesc.MediaType, oc.GetDescriptor().MediaType)
|
||||
}
|
||||
if tt.wantDesc.Size > 0 && tt.wantDesc.Size != oc.GetDescriptor().Size {
|
||||
t.Errorf("size, expected %d, received %d", tt.wantDesc.Size, oc.GetDescriptor().Size)
|
||||
}
|
||||
if len(tt.wantRaw) > 0 {
|
||||
raw, err := oc.RawBody()
|
||||
if err != nil {
|
||||
t.Errorf("config rawbody: %v", err)
|
||||
return
|
||||
}
|
||||
if !bytes.Equal(tt.wantRaw, raw) {
|
||||
t.Errorf("config bytes, expected %s, received %s", string(tt.wantRaw), string(raw))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
t.Run("ModConfig", func(t *testing.T) {
|
||||
// create blob
|
||||
oc := NewOCIConfig(
|
||||
WithRawBody(exBlob),
|
||||
WithDesc(ociv1.Descriptor{
|
||||
MediaType: exMT,
|
||||
Digest: exDigest,
|
||||
Size: exLen,
|
||||
}),
|
||||
WithRef(exRef),
|
||||
)
|
||||
ociC := oc.GetConfig()
|
||||
ociC.History = append(ociC.History, ociv1.History{Comment: "test", EmptyLayer: true})
|
||||
oc.SetConfig(ociC)
|
||||
// ensure digest and raw body change
|
||||
if exDigest == oc.GetDescriptor().Digest {
|
||||
t.Errorf("digest did not change, received %s", oc.GetDescriptor().Digest)
|
||||
}
|
||||
if exMT != oc.GetDescriptor().MediaType {
|
||||
t.Errorf("media type changed, expected %s, received %s", exMT, oc.GetDescriptor().MediaType)
|
||||
}
|
||||
raw, err := oc.RawBody()
|
||||
if err != nil {
|
||||
t.Errorf("config rawbody: %v", err)
|
||||
return
|
||||
}
|
||||
if bytes.Equal(exBlob, raw) {
|
||||
t.Errorf("config bytes unchanged, received %s", string(raw))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func cmpSliceString(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
|
@ -10,11 +10,13 @@ import (
|
||||
|
||||
// Common interface is provided by all Blob implementations
|
||||
type Common interface {
|
||||
Digest() digest.Digest
|
||||
Length() int64
|
||||
MediaType() string
|
||||
GetDescriptor() ociv1.Descriptor
|
||||
Response() *http.Response
|
||||
RawHeaders() http.Header
|
||||
|
||||
Digest() digest.Digest // TODO: deprecate
|
||||
Length() int64 // TODO: deprecate
|
||||
MediaType() string // TODO: deprecate
|
||||
}
|
||||
|
||||
type common struct {
|
||||
@ -25,6 +27,11 @@ type common struct {
|
||||
resp *http.Response
|
||||
}
|
||||
|
||||
// GetDescriptor returns the descriptor associated with the blob
|
||||
func (b *common) GetDescriptor() ociv1.Descriptor {
|
||||
return b.desc
|
||||
}
|
||||
|
||||
// Digest returns the provided or calculated digest of the blob
|
||||
func (b *common) Digest() digest.Digest {
|
||||
return b.desc.Digest
|
||||
|
@ -4,13 +4,16 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/opencontainers/go-digest"
|
||||
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/regclient/regclient/types"
|
||||
)
|
||||
|
||||
// OCIConfig wraps an OCI Config struct extracted from a Blob
|
||||
type OCIConfig interface {
|
||||
Blob
|
||||
GetConfig() ociv1.Image
|
||||
SetConfig(ociv1.Image)
|
||||
}
|
||||
|
||||
// ociConfig includes an OCI Config struct extracted from a Blob
|
||||
@ -27,21 +30,47 @@ func NewOCIConfig(opts ...Opts) OCIConfig {
|
||||
for _, opt := range opts {
|
||||
opt(&bc)
|
||||
}
|
||||
if bc.image != nil && len(bc.rawBody) == 0 {
|
||||
var err error
|
||||
bc.rawBody, err = json.Marshal(bc.image)
|
||||
if err != nil {
|
||||
bc.rawBody = []byte{}
|
||||
}
|
||||
}
|
||||
if len(bc.rawBody) > 0 {
|
||||
if bc.image == nil {
|
||||
bc.image = &ociv1.Image{}
|
||||
err := json.Unmarshal(bc.rawBody, bc.image)
|
||||
if err != nil {
|
||||
bc.image = nil
|
||||
}
|
||||
}
|
||||
// force descriptor to match raw body, even if we generated the raw body
|
||||
bc.desc.Digest = digest.FromBytes(bc.rawBody)
|
||||
bc.desc.Size = int64(len(bc.rawBody))
|
||||
if bc.desc.MediaType == "" {
|
||||
bc.desc.MediaType = types.MediaTypeOCI1ImageConfig
|
||||
}
|
||||
}
|
||||
|
||||
c := common{
|
||||
blobSet: true,
|
||||
desc: bc.desc,
|
||||
r: bc.r,
|
||||
rawHeader: bc.header,
|
||||
resp: bc.resp,
|
||||
}
|
||||
b := ociConfig{
|
||||
common: c,
|
||||
Image: bc.image,
|
||||
common: c,
|
||||
rawBody: bc.rawBody,
|
||||
}
|
||||
if bc.image != nil {
|
||||
b.Image = *bc.image
|
||||
b.blobSet = true
|
||||
}
|
||||
return &b
|
||||
}
|
||||
|
||||
// GetConfig returns the original body from the request
|
||||
// GetConfig returns OCI config
|
||||
func (b *ociConfig) GetConfig() ociv1.Image {
|
||||
return b.Image
|
||||
}
|
||||
@ -57,3 +86,15 @@ func (b *ociConfig) RawBody() ([]byte, error) {
|
||||
}
|
||||
return b.rawBody, err
|
||||
}
|
||||
|
||||
// SetConfig updates the config, including raw body and descriptor
|
||||
func (b *ociConfig) SetConfig(c ociv1.Image) {
|
||||
b.Image = c
|
||||
b.rawBody, _ = json.Marshal(b.Image)
|
||||
if b.desc.MediaType == "" {
|
||||
b.desc.MediaType = types.MediaTypeOCI1ImageConfig
|
||||
}
|
||||
b.desc.Digest = digest.FromBytes(b.rawBody)
|
||||
b.desc.Size = int64(len(b.rawBody))
|
||||
b.blobSet = true
|
||||
}
|
||||
|
@ -1,14 +1,12 @@
|
||||
package blob
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
|
||||
"github.com/opencontainers/go-digest"
|
||||
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
// Reader is an unprocessed Blob with an available ReadCloser for reading the Blob
|
||||
@ -153,11 +151,11 @@ func (b *reader) ToOCIConfig() (OCIConfig, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading image config for %s: %w", b.r.CommonName(), err)
|
||||
}
|
||||
var ociImage ociv1.Image
|
||||
err = json.Unmarshal(blobBody, &ociImage)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing image config for %s: %w", b.r.CommonName(), err)
|
||||
}
|
||||
// return the resulting blobOCIConfig, reuse blobCommon, setting rawBody read above, and the unmarshaled OCI image config
|
||||
return &ociConfig{common: b.common, rawBody: blobBody, Image: ociImage}, nil
|
||||
return NewOCIConfig(
|
||||
WithDesc(b.desc),
|
||||
WithHeader(b.rawHeader),
|
||||
WithRawBody(blobBody),
|
||||
WithRef(b.r),
|
||||
WithResp(b.resp),
|
||||
), nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user