mirror of
https://github.com/regclient/regclient.git
synced 2025-07-29 09:01:11 +03:00
Refactoring the blob package
The main goal was to remove unnecessary interfaces. To avoid breaking users, type aliases were used on the old interface names. Comments were updated to better align with the godoc style. Signed-off-by: Brandon Mitchell <git@bmitch.net>
This commit is contained in:
@ -1,19 +1,39 @@
|
||||
// Package blob is the underlying type for pushing and pulling blobs
|
||||
// Package blob is the underlying type for pushing and pulling blobs.
|
||||
package blob
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/regclient/regclient/types"
|
||||
v1 "github.com/regclient/regclient/types/oci/v1"
|
||||
"github.com/regclient/regclient/types/ref"
|
||||
)
|
||||
|
||||
// Blob interface is used for returning blobs
|
||||
// Blob interface is used for returning blobs.
|
||||
type Blob interface {
|
||||
Common
|
||||
// GetDescriptor returns the descriptor associated with the blob.
|
||||
GetDescriptor() types.Descriptor
|
||||
// RawBody returns the raw content of the blob.
|
||||
RawBody() ([]byte, error)
|
||||
// RawHeaders returns the headers received from the registry.
|
||||
RawHeaders() http.Header
|
||||
// Response returns the response associated with the blob.
|
||||
Response() *http.Response
|
||||
|
||||
// Digest returns the provided or calculated digest of the blob.
|
||||
//
|
||||
// Deprecated: Digest should be replaced by GetDescriptor().Digest.
|
||||
Digest() digest.Digest
|
||||
// Length returns the provided or calculated length of the blob.
|
||||
//
|
||||
// Deprecated: Length should be replaced by GetDescriptor().Size.
|
||||
Length() int64
|
||||
// MediaType returns the Content-Type header received from the registry.
|
||||
//
|
||||
// Deprecated: MediaType should be replaced by GetDescriptor().MediaType.
|
||||
MediaType() string
|
||||
}
|
||||
|
||||
type blobConfig struct {
|
||||
@ -26,51 +46,52 @@ type blobConfig struct {
|
||||
rawBody []byte
|
||||
}
|
||||
|
||||
// Opts is used for options to create a new blob.
|
||||
type Opts func(*blobConfig)
|
||||
|
||||
// WithDesc specifies the descriptor associated with the blob
|
||||
// WithDesc specifies the descriptor associated with the blob.
|
||||
func WithDesc(d types.Descriptor) Opts {
|
||||
return func(bc *blobConfig) {
|
||||
bc.desc = d
|
||||
}
|
||||
}
|
||||
|
||||
// WithHeader defines the headers received when pulling a blob
|
||||
// WithHeader defines the headers received when pulling a blob.
|
||||
func WithHeader(header http.Header) Opts {
|
||||
return func(bc *blobConfig) {
|
||||
bc.header = header
|
||||
}
|
||||
}
|
||||
|
||||
// WithImage provides the OCI Image config needed for config blobs
|
||||
// WithImage provides the OCI Image config needed for config blobs.
|
||||
func WithImage(image v1.Image) Opts {
|
||||
return func(bc *blobConfig) {
|
||||
bc.image = &image
|
||||
}
|
||||
}
|
||||
|
||||
// WithRawBody defines the raw blob contents for OCIConfig
|
||||
// WithRawBody defines the raw blob contents for OCIConfig.
|
||||
func WithRawBody(raw []byte) Opts {
|
||||
return func(bc *blobConfig) {
|
||||
bc.rawBody = raw
|
||||
}
|
||||
}
|
||||
|
||||
// WithReader defines the reader for a new blob
|
||||
// WithReader defines the reader for a new blob.
|
||||
func WithReader(rc io.Reader) Opts {
|
||||
return func(bc *blobConfig) {
|
||||
bc.rdr = rc
|
||||
}
|
||||
}
|
||||
|
||||
// WithRef specifies the reference where the blob was pulled from
|
||||
// WithRef specifies the reference where the blob was pulled from.
|
||||
func WithRef(r ref.Ref) Opts {
|
||||
return func(bc *blobConfig) {
|
||||
bc.r = r
|
||||
}
|
||||
}
|
||||
|
||||
// WithResp includes the http response, which is used to extract the headers and reader
|
||||
// WithResp includes the http response, which is used to extract the headers and reader.
|
||||
func WithResp(resp *http.Response) Opts {
|
||||
return func(bc *blobConfig) {
|
||||
bc.resp = resp
|
||||
|
@ -257,16 +257,11 @@ func TestReader(t *testing.T) {
|
||||
if i != bl {
|
||||
t.Errorf("read length, expected %d, received %d", bl, i)
|
||||
}
|
||||
bSeek, ok := b.(io.Seeker)
|
||||
if !ok {
|
||||
t.Errorf("seek interface missing")
|
||||
return
|
||||
}
|
||||
_, err = bSeek.Seek(5, io.SeekStart)
|
||||
_, err = b.Seek(5, io.SeekStart)
|
||||
if err == nil {
|
||||
t.Errorf("seek to non-zero position did not fail")
|
||||
}
|
||||
pos, err := bSeek.Seek(0, io.SeekStart)
|
||||
pos, err := b.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
t.Errorf("seek err: %v", err)
|
||||
return
|
||||
@ -298,12 +293,7 @@ func TestReader(t *testing.T) {
|
||||
if i != bl {
|
||||
t.Errorf("read length, expected %d, received %d", bl, i)
|
||||
}
|
||||
bSeek, ok = b.(io.Seeker)
|
||||
if !ok {
|
||||
t.Errorf("seek interface missing")
|
||||
return
|
||||
}
|
||||
_, err = bSeek.Seek(0, io.SeekStart)
|
||||
_, err = b.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
t.Errorf("seek err: %v", err)
|
||||
return
|
||||
@ -372,10 +362,12 @@ func TestOCI(t *testing.T) {
|
||||
t.Errorf("failed to unmarshal exBlob: %v", err)
|
||||
return
|
||||
}
|
||||
tests := []struct {
|
||||
tt := []struct {
|
||||
name string
|
||||
opts []Opts
|
||||
fromJSON []byte
|
||||
wantRaw []byte
|
||||
wantJSON []byte
|
||||
wantDesc types.Descriptor
|
||||
}{
|
||||
{
|
||||
@ -386,6 +378,16 @@ func TestOCI(t *testing.T) {
|
||||
},
|
||||
wantDesc: exDesc,
|
||||
wantRaw: exBlob,
|
||||
wantJSON: exBlob,
|
||||
},
|
||||
{
|
||||
name: "JSONMarshal",
|
||||
opts: []Opts{
|
||||
WithDesc(exDesc),
|
||||
},
|
||||
fromJSON: exBlob,
|
||||
wantDesc: exDesc,
|
||||
wantJSON: exBlob,
|
||||
},
|
||||
{
|
||||
name: "Config with Default Desc",
|
||||
@ -404,27 +406,43 @@ func TestOCI(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
oc := NewOCIConfig(tt.opts...)
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
oc := NewOCIConfig(tc.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 len(tc.fromJSON) > 0 {
|
||||
err := oc.UnmarshalJSON(tc.fromJSON)
|
||||
if err != nil {
|
||||
t.Errorf("failed to unmarshal json: %v", err)
|
||||
}
|
||||
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 tc.wantDesc.Digest != "" && tc.wantDesc.Digest != oc.GetDescriptor().Digest {
|
||||
t.Errorf("digest, expected %s, received %s", tc.wantDesc.Digest, oc.GetDescriptor().Digest)
|
||||
}
|
||||
if len(tt.wantRaw) > 0 {
|
||||
if tc.wantDesc.MediaType != "" && tc.wantDesc.MediaType != oc.GetDescriptor().MediaType {
|
||||
t.Errorf("media type, expected %s, received %s", tc.wantDesc.MediaType, oc.GetDescriptor().MediaType)
|
||||
}
|
||||
if tc.wantDesc.Size > 0 && tc.wantDesc.Size != oc.GetDescriptor().Size {
|
||||
t.Errorf("size, expected %d, received %d", tc.wantDesc.Size, oc.GetDescriptor().Size)
|
||||
}
|
||||
if len(tc.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))
|
||||
if !bytes.Equal(tc.wantRaw, raw) {
|
||||
t.Errorf("config bytes, expected %s, received %s", string(tc.wantRaw), string(raw))
|
||||
}
|
||||
}
|
||||
if len(tc.wantJSON) > 0 {
|
||||
ocJSON, err := oc.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Errorf("json marshal: %v", err)
|
||||
return
|
||||
}
|
||||
if !bytes.Equal(tc.wantJSON, ocJSON) {
|
||||
t.Errorf("json marshal, expected %s, received %s", string(tc.wantJSON), string(ocJSON))
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -476,7 +494,7 @@ func TestTarReader(t *testing.T) {
|
||||
fh.Close()
|
||||
dig := digger.Digest()
|
||||
|
||||
tests := []struct {
|
||||
tt := []struct {
|
||||
name string
|
||||
opts []Opts
|
||||
errClose bool
|
||||
@ -507,14 +525,14 @@ func TestTarReader(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
fh, err := os.Open(fileLayer)
|
||||
if err != nil {
|
||||
t.Errorf("failed to open test data: %v", err)
|
||||
return
|
||||
}
|
||||
opts := append(tt.opts, WithReader(fh))
|
||||
opts := append(tc.opts, WithReader(fh))
|
||||
btr := NewTarReader(opts...)
|
||||
tr, err := btr.GetTarReader()
|
||||
if err != nil {
|
||||
@ -542,9 +560,9 @@ func TestTarReader(t *testing.T) {
|
||||
}
|
||||
}
|
||||
err = btr.Close()
|
||||
if !tt.errClose && err != nil {
|
||||
if !tc.errClose && err != nil {
|
||||
t.Errorf("failed to close tar reader: %v", err)
|
||||
} else if tt.errClose && err == nil {
|
||||
} else if tc.errClose && err == nil {
|
||||
t.Errorf("close did not fail")
|
||||
}
|
||||
})
|
||||
@ -552,7 +570,7 @@ func TestTarReader(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestReadFile(t *testing.T) {
|
||||
tests := []struct {
|
||||
tt := []struct {
|
||||
name string
|
||||
filename string
|
||||
content string
|
||||
@ -600,20 +618,20 @@ func TestReadFile(t *testing.T) {
|
||||
return
|
||||
}
|
||||
blobDigest := digest.FromBytes(fileBytes)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
fh, err := os.Open(fileLayerWH)
|
||||
if err != nil {
|
||||
t.Errorf("failed to open test data: %v", err)
|
||||
return
|
||||
}
|
||||
btr := NewTarReader(WithReader(fh), WithDesc(types.Descriptor{Size: int64(len(fileBytes)), Digest: blobDigest, MediaType: types.MediaTypeOCI1Layer}))
|
||||
th, rdr, err := btr.ReadFile(tt.filename)
|
||||
if tt.expectErr != nil {
|
||||
th, rdr, err := btr.ReadFile(tc.filename)
|
||||
if tc.expectErr != nil {
|
||||
if err == nil {
|
||||
t.Errorf("ReadFile did not fail")
|
||||
} else if !errors.Is(err, tt.expectErr) && err.Error() != tt.expectErr.Error() {
|
||||
t.Errorf("unexpected error, expected %v, received %v", tt.expectErr, err)
|
||||
} else if !errors.Is(err, tc.expectErr) && err.Error() != tc.expectErr.Error() {
|
||||
t.Errorf("unexpected error, expected %v, received %v", tc.expectErr, err)
|
||||
}
|
||||
err = btr.Close()
|
||||
if err != nil {
|
||||
@ -640,8 +658,8 @@ func TestReadFile(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("failed reading file: %v", err)
|
||||
}
|
||||
if tt.content != string(content) {
|
||||
t.Errorf("file content mismatch: expected %s, received %s", tt.content, string(content))
|
||||
if tc.content != string(content) {
|
||||
t.Errorf("file content mismatch: expected %s, received %s", tc.content, string(content))
|
||||
}
|
||||
err = btr.Close()
|
||||
if err != nil {
|
||||
|
@ -12,21 +12,11 @@ import (
|
||||
"github.com/regclient/regclient/types/ref"
|
||||
)
|
||||
|
||||
// Common interface is provided by all Blob implementations
|
||||
type Common interface {
|
||||
GetDescriptor() types.Descriptor
|
||||
Response() *http.Response
|
||||
RawHeaders() http.Header
|
||||
// Common was previously an interface. A type alias is provided for upgrades.
|
||||
type Common = *BCommon
|
||||
|
||||
// Deprecated: Digest should be replaced by GetDescriptor().Digest
|
||||
Digest() digest.Digest
|
||||
// Deprecated: Length should be replaced by GetDescriptor().Size
|
||||
Length() int64
|
||||
// Deprecated: MediaType should be replaced by GetDescriptor().MediaType
|
||||
MediaType() string
|
||||
}
|
||||
|
||||
type common struct {
|
||||
// BCommon is a common struct for all blobs which includes various shared methods.
|
||||
type BCommon struct {
|
||||
r ref.Ref
|
||||
desc types.Descriptor
|
||||
blobSet bool
|
||||
@ -34,32 +24,38 @@ type common struct {
|
||||
resp *http.Response
|
||||
}
|
||||
|
||||
// GetDescriptor returns the descriptor associated with the blob
|
||||
func (b *common) GetDescriptor() types.Descriptor {
|
||||
return b.desc
|
||||
// GetDescriptor returns the descriptor associated with the blob.
|
||||
func (c *BCommon) GetDescriptor() types.Descriptor {
|
||||
return c.desc
|
||||
}
|
||||
|
||||
// Digest returns the provided or calculated digest of the blob
|
||||
func (b *common) Digest() digest.Digest {
|
||||
return b.desc.Digest
|
||||
// Digest returns the provided or calculated digest of the blob.
|
||||
//
|
||||
// Deprecated: Digest should be replaced by GetDescriptor().Digest.
|
||||
func (c *BCommon) Digest() digest.Digest {
|
||||
return c.desc.Digest
|
||||
}
|
||||
|
||||
// Length returns the provided or calculated length of the blob
|
||||
func (b *common) Length() int64 {
|
||||
return b.desc.Size
|
||||
// Length returns the provided or calculated length of the blob.
|
||||
//
|
||||
// Deprecated: Length should be replaced by GetDescriptor().Size.
|
||||
func (c *BCommon) Length() int64 {
|
||||
return c.desc.Size
|
||||
}
|
||||
|
||||
// MediaType returns the Content-Type header received from the registry
|
||||
func (b *common) MediaType() string {
|
||||
return b.desc.MediaType
|
||||
// MediaType returns the Content-Type header received from the registry.
|
||||
//
|
||||
// Deprecated: MediaType should be replaced by GetDescriptor().MediaType.
|
||||
func (c *BCommon) MediaType() string {
|
||||
return c.desc.MediaType
|
||||
}
|
||||
|
||||
// RawHeaders returns the headers received from the registry
|
||||
func (b *common) RawHeaders() http.Header {
|
||||
return b.rawHeader
|
||||
// RawHeaders returns the headers received from the registry.
|
||||
func (c *BCommon) RawHeaders() http.Header {
|
||||
return c.rawHeader
|
||||
}
|
||||
|
||||
// Response returns the response associated with the blob
|
||||
func (b *common) Response() *http.Response {
|
||||
return b.resp
|
||||
// Response returns the response associated with the blob.
|
||||
func (c *BCommon) Response() *http.Response {
|
||||
return c.resp
|
||||
}
|
||||
|
@ -13,23 +13,19 @@ import (
|
||||
v1 "github.com/regclient/regclient/types/oci/v1"
|
||||
)
|
||||
|
||||
// OCIConfig wraps an OCI Config struct extracted from a Blob
|
||||
type OCIConfig interface {
|
||||
Blob
|
||||
GetConfig() v1.Image
|
||||
SetConfig(v1.Image)
|
||||
}
|
||||
// OCIConfig was previously an interface. A type alias is provided for upgrading.
|
||||
type OCIConfig = *BOCIConfig
|
||||
|
||||
// ociConfig includes an OCI Config struct extracted from a Blob
|
||||
// Image is included as an anonymous field to facilitate json and templating calls transparently
|
||||
type ociConfig struct {
|
||||
common
|
||||
// BOCIConfig includes an OCI Image Config struct that may be extracted from or pushed to a blob.
|
||||
type BOCIConfig struct {
|
||||
BCommon
|
||||
rawBody []byte
|
||||
v1.Image
|
||||
image v1.Image
|
||||
}
|
||||
|
||||
// NewOCIConfig creates a new BlobOCIConfig from an OCI Image
|
||||
func NewOCIConfig(opts ...Opts) OCIConfig {
|
||||
// NewOCIConfig creates a new BOCIConfig.
|
||||
// When created from an existing blob, a BOCIConfig will be created using BReader.ToOCIConfig().
|
||||
func NewOCIConfig(opts ...Opts) *BOCIConfig {
|
||||
bc := blobConfig{}
|
||||
for _, opt := range opts {
|
||||
opt(&bc)
|
||||
@ -56,49 +52,76 @@ func NewOCIConfig(opts ...Opts) OCIConfig {
|
||||
bc.desc.MediaType = types.MediaTypeOCI1ImageConfig
|
||||
}
|
||||
}
|
||||
|
||||
c := common{
|
||||
b := BOCIConfig{
|
||||
BCommon: BCommon{
|
||||
desc: bc.desc,
|
||||
r: bc.r,
|
||||
rawHeader: bc.header,
|
||||
resp: bc.resp,
|
||||
}
|
||||
b := ociConfig{
|
||||
common: c,
|
||||
},
|
||||
rawBody: bc.rawBody,
|
||||
}
|
||||
if bc.image != nil {
|
||||
b.Image = *bc.image
|
||||
b.image = *bc.image
|
||||
b.blobSet = true
|
||||
}
|
||||
return &b
|
||||
}
|
||||
|
||||
// GetConfig returns OCI config
|
||||
func (b *ociConfig) GetConfig() v1.Image {
|
||||
return b.Image
|
||||
// GetConfig returns OCI config.
|
||||
func (oc *BOCIConfig) GetConfig() v1.Image {
|
||||
return oc.image
|
||||
}
|
||||
|
||||
// RawBody returns the original body from the request
|
||||
func (b *ociConfig) RawBody() ([]byte, error) {
|
||||
// RawBody returns the original body from the request.
|
||||
func (oc *BOCIConfig) RawBody() ([]byte, error) {
|
||||
var err error
|
||||
if !b.blobSet {
|
||||
if !oc.blobSet {
|
||||
return []byte{}, fmt.Errorf("Blob is not defined")
|
||||
}
|
||||
if len(b.rawBody) == 0 {
|
||||
b.rawBody, err = json.Marshal(b.Image)
|
||||
if len(oc.rawBody) == 0 {
|
||||
oc.rawBody, err = json.Marshal(oc.image)
|
||||
}
|
||||
return b.rawBody, err
|
||||
return oc.rawBody, err
|
||||
}
|
||||
|
||||
// SetConfig updates the config, including raw body and descriptor
|
||||
func (b *ociConfig) SetConfig(c v1.Image) {
|
||||
b.Image = c
|
||||
b.rawBody, _ = json.Marshal(b.Image)
|
||||
if b.desc.MediaType == "" {
|
||||
b.desc.MediaType = types.MediaTypeOCI1ImageConfig
|
||||
// SetConfig updates the config, including raw body and descriptor.
|
||||
func (oc *BOCIConfig) SetConfig(image v1.Image) {
|
||||
oc.image = image
|
||||
oc.rawBody, _ = json.Marshal(oc.image)
|
||||
if oc.desc.MediaType == "" {
|
||||
oc.desc.MediaType = types.MediaTypeOCI1ImageConfig
|
||||
}
|
||||
b.desc.Digest = digest.FromBytes(b.rawBody)
|
||||
b.desc.Size = int64(len(b.rawBody))
|
||||
b.blobSet = true
|
||||
oc.desc.Digest = digest.FromBytes(oc.rawBody)
|
||||
oc.desc.Size = int64(len(oc.rawBody))
|
||||
oc.blobSet = true
|
||||
}
|
||||
|
||||
// MarshalJSON passes through the marshalling to the underlying image if rawBody is not available.
|
||||
func (oc *BOCIConfig) MarshalJSON() ([]byte, error) {
|
||||
if !oc.blobSet {
|
||||
return []byte{}, fmt.Errorf("Blob is not defined")
|
||||
}
|
||||
if len(oc.rawBody) > 0 {
|
||||
return oc.rawBody, nil
|
||||
}
|
||||
return json.Marshal(oc.image)
|
||||
}
|
||||
|
||||
// UnmarshalJSON extracts json content and populates the content.
|
||||
func (oc *BOCIConfig) UnmarshalJSON(data []byte) error {
|
||||
image := v1.Image{}
|
||||
err := json.Unmarshal(data, &image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oc.rawBody = make([]byte, len(data))
|
||||
copy(oc.rawBody, data)
|
||||
if oc.desc.MediaType == "" {
|
||||
oc.desc.MediaType = types.MediaTypeOCI1ImageConfig
|
||||
}
|
||||
oc.desc.Digest = digest.FromBytes(oc.rawBody)
|
||||
oc.desc.Size = int64(len(oc.rawBody))
|
||||
oc.blobSet = true
|
||||
return nil
|
||||
}
|
||||
|
@ -14,25 +14,20 @@ import (
|
||||
"github.com/regclient/regclient/types"
|
||||
)
|
||||
|
||||
// Reader is an unprocessed Blob with an available ReadCloser for reading the Blob
|
||||
type Reader interface {
|
||||
Blob
|
||||
io.ReadSeekCloser
|
||||
ToOCIConfig() (OCIConfig, error)
|
||||
ToTarReader() (TarReader, error)
|
||||
}
|
||||
// Reader was previously an interface. A type alias is provided for upgrading.
|
||||
type Reader = *BReader
|
||||
|
||||
// reader is the internal struct implementing BlobReader
|
||||
type reader struct {
|
||||
common
|
||||
// BReader is used to read blobs.
|
||||
type BReader struct {
|
||||
BCommon
|
||||
readBytes int64
|
||||
reader io.Reader
|
||||
origRdr io.Reader
|
||||
digester digest.Digester
|
||||
}
|
||||
|
||||
// NewReader creates a new reader
|
||||
func NewReader(opts ...Opts) Reader {
|
||||
// NewReader creates a new BReader.
|
||||
func NewReader(opts ...Opts) *BReader {
|
||||
bc := blobConfig{}
|
||||
for _, opt := range opts {
|
||||
opt(&bc)
|
||||
@ -59,14 +54,13 @@ func NewReader(opts ...Opts) Reader {
|
||||
bc.desc.Digest, _ = digest.Parse(bc.header.Get("Docker-Content-Digest"))
|
||||
}
|
||||
}
|
||||
c := common{
|
||||
br := BReader{
|
||||
BCommon: BCommon{
|
||||
r: bc.r,
|
||||
desc: bc.desc,
|
||||
rawHeader: bc.header,
|
||||
resp: bc.resp,
|
||||
}
|
||||
br := reader{
|
||||
common: c,
|
||||
},
|
||||
origRdr: bc.rdr,
|
||||
}
|
||||
if bc.rdr != nil {
|
||||
@ -84,119 +78,121 @@ func NewReader(opts ...Opts) Reader {
|
||||
return &br
|
||||
}
|
||||
|
||||
func (b *reader) Close() error {
|
||||
if b.origRdr == nil {
|
||||
// Close attempts to close the reader and populates/validates the digest.
|
||||
func (r *BReader) Close() error {
|
||||
if r.origRdr == nil {
|
||||
return nil
|
||||
}
|
||||
// attempt to close if available in original reader
|
||||
bc, ok := b.origRdr.(io.Closer)
|
||||
bc, ok := r.origRdr.(io.Closer)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return bc.Close()
|
||||
}
|
||||
|
||||
// RawBody returns the original body from the request
|
||||
func (b *reader) RawBody() ([]byte, error) {
|
||||
return io.ReadAll(b)
|
||||
// RawBody returns the original body from the request.
|
||||
func (r *BReader) RawBody() ([]byte, error) {
|
||||
return io.ReadAll(r)
|
||||
}
|
||||
|
||||
// Read passes through the read operation while computing the digest and tracking the size
|
||||
func (b *reader) Read(p []byte) (int, error) {
|
||||
if b.reader == nil {
|
||||
// Read passes through the read operation while computing the digest and tracking the size.
|
||||
func (r *BReader) Read(p []byte) (int, error) {
|
||||
if r.reader == nil {
|
||||
return 0, fmt.Errorf("blob has no reader: %w", io.ErrUnexpectedEOF)
|
||||
}
|
||||
size, err := b.reader.Read(p)
|
||||
b.readBytes = b.readBytes + int64(size)
|
||||
size, err := r.reader.Read(p)
|
||||
r.readBytes = r.readBytes + int64(size)
|
||||
if err == io.EOF {
|
||||
// check/save size
|
||||
if b.desc.Size == 0 {
|
||||
b.desc.Size = b.readBytes
|
||||
} else if b.readBytes < b.desc.Size {
|
||||
err = fmt.Errorf("%w [expected %d, received %d]: %v", types.ErrShortRead, b.desc.Size, b.readBytes, err)
|
||||
} else if b.readBytes > b.desc.Size {
|
||||
err = fmt.Errorf("%w [expected %d, received %d]: %v", types.ErrSizeLimitExceeded, b.desc.Size, b.readBytes, err)
|
||||
if r.desc.Size == 0 {
|
||||
r.desc.Size = r.readBytes
|
||||
} else if r.readBytes < r.desc.Size {
|
||||
err = fmt.Errorf("%w [expected %d, received %d]: %v", types.ErrShortRead, r.desc.Size, r.readBytes, err)
|
||||
} else if r.readBytes > r.desc.Size {
|
||||
err = fmt.Errorf("%w [expected %d, received %d]: %v", types.ErrSizeLimitExceeded, r.desc.Size, r.readBytes, err)
|
||||
}
|
||||
// check/save digest
|
||||
if b.desc.Digest == "" {
|
||||
b.desc.Digest = b.digester.Digest()
|
||||
} else if b.desc.Digest != b.digester.Digest() {
|
||||
err = fmt.Errorf("%w [expected %s, calculated %s]: %v", types.ErrDigestMismatch, b.desc.Digest.String(), b.digester.Digest().String(), err)
|
||||
if r.desc.Digest == "" {
|
||||
r.desc.Digest = r.digester.Digest()
|
||||
} else if r.desc.Digest != r.digester.Digest() {
|
||||
err = fmt.Errorf("%w [expected %s, calculated %s]: %v", types.ErrDigestMismatch, r.desc.Digest.String(), r.digester.Digest().String(), err)
|
||||
}
|
||||
}
|
||||
return size, err
|
||||
}
|
||||
|
||||
// Seek passes through the seek operation, reseting or invalidating the digest
|
||||
func (b *reader) Seek(offset int64, whence int) (int64, error) {
|
||||
func (r *BReader) Seek(offset int64, whence int) (int64, error) {
|
||||
if offset == 0 && whence == io.SeekCurrent {
|
||||
return b.readBytes, nil
|
||||
return r.readBytes, nil
|
||||
}
|
||||
// cannot do an arbitrary seek and still digest without a lot more complication
|
||||
if offset != 0 || whence != io.SeekStart {
|
||||
return b.readBytes, fmt.Errorf("unable to seek to arbitrary position")
|
||||
return r.readBytes, fmt.Errorf("unable to seek to arbitrary position")
|
||||
}
|
||||
rdrSeek, ok := b.origRdr.(io.Seeker)
|
||||
rdrSeek, ok := r.origRdr.(io.Seeker)
|
||||
if !ok {
|
||||
return b.readBytes, fmt.Errorf("Seek unsupported")
|
||||
return r.readBytes, fmt.Errorf("Seek unsupported")
|
||||
}
|
||||
o, err := rdrSeek.Seek(offset, whence)
|
||||
if err != nil || o != 0 {
|
||||
return b.readBytes, err
|
||||
return r.readBytes, err
|
||||
}
|
||||
// reset internal offset and digest calculation
|
||||
rdr := b.origRdr
|
||||
if b.desc.Size > 0 {
|
||||
rdr := r.origRdr
|
||||
if r.desc.Size > 0 {
|
||||
rdr = &limitread.LimitRead{
|
||||
Reader: rdr,
|
||||
Limit: b.desc.Size,
|
||||
Limit: r.desc.Size,
|
||||
}
|
||||
}
|
||||
digester := digest.Canonical.Digester()
|
||||
b.reader = io.TeeReader(rdr, digester.Hash())
|
||||
b.digester = digester
|
||||
b.readBytes = 0
|
||||
r.reader = io.TeeReader(rdr, digester.Hash())
|
||||
r.digester = digester
|
||||
r.readBytes = 0
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// ToOCIConfig converts a blobReader to a BlobOCIConfig
|
||||
func (b *reader) ToOCIConfig() (OCIConfig, error) {
|
||||
if !b.blobSet {
|
||||
// ToOCIConfig converts a BReader to a BOCIConfig.
|
||||
func (r *BReader) ToOCIConfig() (*BOCIConfig, error) {
|
||||
if !r.blobSet {
|
||||
return nil, fmt.Errorf("blob is not defined")
|
||||
}
|
||||
if b.readBytes != 0 {
|
||||
if r.readBytes != 0 {
|
||||
return nil, fmt.Errorf("unable to convert after read has been performed")
|
||||
}
|
||||
blobBody, err := io.ReadAll(b)
|
||||
errC := b.Close()
|
||||
blobBody, err := io.ReadAll(r)
|
||||
errC := r.Close()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading image config for %s: %w", b.r.CommonName(), err)
|
||||
return nil, fmt.Errorf("error reading image config for %s: %w", r.r.CommonName(), err)
|
||||
}
|
||||
if errC != nil {
|
||||
return nil, fmt.Errorf("error closing blob reader: %w", err)
|
||||
}
|
||||
return NewOCIConfig(
|
||||
WithDesc(b.desc),
|
||||
WithHeader(b.rawHeader),
|
||||
WithDesc(r.desc),
|
||||
WithHeader(r.rawHeader),
|
||||
WithRawBody(blobBody),
|
||||
WithRef(b.r),
|
||||
WithResp(b.resp),
|
||||
WithRef(r.r),
|
||||
WithResp(r.resp),
|
||||
), nil
|
||||
}
|
||||
|
||||
func (b *reader) ToTarReader() (TarReader, error) {
|
||||
if !b.blobSet {
|
||||
// ToTarReader converts a BReader to a BTarReader
|
||||
func (r *BReader) ToTarReader() (*BTarReader, error) {
|
||||
if !r.blobSet {
|
||||
return nil, fmt.Errorf("blob is not defined")
|
||||
}
|
||||
if b.readBytes != 0 {
|
||||
if r.readBytes != 0 {
|
||||
return nil, fmt.Errorf("unable to convert after read has been performed")
|
||||
}
|
||||
return NewTarReader(
|
||||
WithDesc(b.desc),
|
||||
WithHeader(b.rawHeader),
|
||||
WithRef(b.r),
|
||||
WithResp(b.resp),
|
||||
WithReader(b.reader),
|
||||
WithDesc(r.desc),
|
||||
WithHeader(r.rawHeader),
|
||||
WithRef(r.r),
|
||||
WithResp(r.resp),
|
||||
WithReader(r.reader),
|
||||
), nil
|
||||
}
|
||||
|
@ -14,36 +14,32 @@ import (
|
||||
"github.com/regclient/regclient/types"
|
||||
)
|
||||
|
||||
// TarReader reads or writes to a blob with tar contents and optional compression
|
||||
type TarReader interface {
|
||||
Blob
|
||||
io.Closer
|
||||
GetTarReader() (*tar.Reader, error)
|
||||
ReadFile(filename string) (*tar.Header, io.Reader, error)
|
||||
}
|
||||
// TarReader was previously an interface. A type alias is provided for upgrading.
|
||||
type TarReader = *BTarReader
|
||||
|
||||
type tarReader struct {
|
||||
common
|
||||
// BTarReader is used to read individual files from an image layer.
|
||||
type BTarReader struct {
|
||||
BCommon
|
||||
origRdr io.Reader
|
||||
reader io.Reader
|
||||
digester digest.Digester
|
||||
tr *tar.Reader
|
||||
}
|
||||
|
||||
// NewTarReader creates a TarReader
|
||||
func NewTarReader(opts ...Opts) TarReader {
|
||||
// NewTarReader creates a BTarReader.
|
||||
// Typically a BTarReader will be created using BReader.ToTarReader().
|
||||
func NewTarReader(opts ...Opts) *BTarReader {
|
||||
bc := blobConfig{}
|
||||
for _, opt := range opts {
|
||||
opt(&bc)
|
||||
}
|
||||
c := common{
|
||||
tr := BTarReader{
|
||||
BCommon: BCommon{
|
||||
desc: bc.desc,
|
||||
r: bc.r,
|
||||
rawHeader: bc.header,
|
||||
resp: bc.resp,
|
||||
}
|
||||
tr := tarReader{
|
||||
common: c,
|
||||
},
|
||||
origRdr: bc.rdr,
|
||||
}
|
||||
if bc.rdr != nil {
|
||||
@ -61,8 +57,8 @@ func NewTarReader(opts ...Opts) TarReader {
|
||||
return &tr
|
||||
}
|
||||
|
||||
// Close attempts to close the reader and populates/validates the digest
|
||||
func (tr *tarReader) Close() error {
|
||||
// Close attempts to close the reader and populates/validates the digest.
|
||||
func (tr *BTarReader) Close() error {
|
||||
// attempt to close if available in original reader
|
||||
if trc, ok := tr.origRdr.(io.Closer); ok && trc != nil {
|
||||
return trc.Close()
|
||||
@ -70,8 +66,8 @@ func (tr *tarReader) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTarReader returns the tar.Reader for the blob
|
||||
func (tr *tarReader) GetTarReader() (*tar.Reader, error) {
|
||||
// GetTarReader returns the tar.Reader for the blob.
|
||||
func (tr *BTarReader) GetTarReader() (*tar.Reader, error) {
|
||||
if tr.reader == nil {
|
||||
return nil, fmt.Errorf("blob has no reader defined")
|
||||
}
|
||||
@ -85,8 +81,8 @@ func (tr *tarReader) GetTarReader() (*tar.Reader, error) {
|
||||
return tr.tr, nil
|
||||
}
|
||||
|
||||
// RawBody returns the original body from the request
|
||||
func (tr *tarReader) RawBody() ([]byte, error) {
|
||||
// RawBody returns the original body from the request.
|
||||
func (tr *BTarReader) RawBody() ([]byte, error) {
|
||||
if !tr.blobSet {
|
||||
return []byte{}, fmt.Errorf("Blob is not defined")
|
||||
}
|
||||
@ -109,8 +105,8 @@ func (tr *tarReader) RawBody() ([]byte, error) {
|
||||
return b, err
|
||||
}
|
||||
|
||||
// ReadFile parses the tar to find a file
|
||||
func (tr *tarReader) ReadFile(filename string) (*tar.Header, io.Reader, error) {
|
||||
// ReadFile parses the tar to find a file.
|
||||
func (tr *BTarReader) ReadFile(filename string) (*tar.Header, io.Reader, error) {
|
||||
if strings.HasPrefix(filename, ".wh.") {
|
||||
return nil, nil, fmt.Errorf(".wh. prefix is reserved for whiteout files")
|
||||
}
|
||||
|
Reference in New Issue
Block a user