1
0
mirror of https://github.com/regclient/regclient.git synced 2025-07-29 09:01:11 +03:00

Adjusting types to use New/Opts initializers

Signed-off-by: Brandon Mitchell <git@bmitch.net>
This commit is contained in:
Brandon Mitchell
2022-01-02 21:05:09 -05:00
parent 47eb58c1e8
commit 4b796535cb
24 changed files with 591 additions and 456 deletions

View File

@ -168,8 +168,13 @@ func TestBlobGet(t *testing.T) {
Hooks: make(logrus.LevelHooks), Hooks: make(logrus.LevelHooks),
Level: logrus.WarnLevel, Level: logrus.WarnLevel,
} }
rc := New(WithConfigHosts(rcHosts), WithLog(log)) delayInit, _ := time.ParseDuration("0.05s")
delayMax, _ := time.ParseDuration("0.10s")
rc := New(
WithConfigHosts(rcHosts),
WithLog(log),
WithRetryDelay(delayInit, delayMax),
)
// Test successful blob // Test successful blob
t.Run("Get", func(t *testing.T) { t.Run("Get", func(t *testing.T) {
ref, err := ref.New(tsURL.Host + blobRepo) ref, err := ref.New(tsURL.Host + blobRepo)

View File

@ -123,7 +123,10 @@ func (s *Sandbox) configExport(ls *lua.LState) int {
ls.RaiseError("Failed exporting config (go2lua): %v", err) ls.RaiseError("Failed exporting config (go2lua): %v", err)
} }
// save image to a new config // save image to a new config
bc := blob.NewOCIConfig(ociImage) bc := blob.NewOCIConfig(
blob.WithRef(origC.r),
blob.WithImage(ociImage),
)
newC = &config{ newC = &config{
conf: bc, conf: bc,
m: origC.m, m: origC.m,

View File

@ -145,7 +145,7 @@ func (s *Sandbox) manifestExport(ls *lua.LState) int {
ls.RaiseError("Failed exporting manifest (go2lua): %v", err) ls.RaiseError("Failed exporting manifest (go2lua): %v", err)
} }
// save image to a new manifest // save image to a new manifest
rcM, err := manifest.FromOrig(reflect.ValueOf(newMMP).Elem().Interface()) // reflect is needed again to deref the pointer now rcM, err := manifest.New(manifest.WithOrig(reflect.ValueOf(newMMP).Elem().Interface())) // reflect is needed again to deref the pointer now
// rcM, err := manifest.FromOrig(newMM) // rcM, err := manifest.FromOrig(newMM)
if err != nil { if err != nil {
ls.RaiseError("Failed exporting manifest (from orig): %v", err) ls.RaiseError("Failed exporting manifest (from orig): %v", err)
@ -238,7 +238,7 @@ func (s *Sandbox) manifestPut(ls *lua.LState) int {
"image": r.r.CommonName(), "image": r.r.CommonName(),
}).Debug("Put manifest") }).Debug("Put manifest")
m, err := manifest.FromOrig(sbm.m.GetOrigManifest()) m, err := manifest.New(manifest.WithOrig(sbm.m.GetOrigManifest()))
if err != nil { if err != nil {
ls.RaiseError("Failed to put manifest: %v", err) ls.RaiseError("Failed to put manifest: %v", err)
} }

View File

@ -434,7 +434,7 @@ func runArtifactPut(cmd *cobra.Command, args []string) error {
} }
// generate manifest // generate manifest
mm, err := manifest.FromOrig(m) mm, err := manifest.New(manifest.WithOrig(m))
if err != nil { if err != nil {
return err return err
} }

View File

@ -138,7 +138,7 @@ func getPlatformDesc(rc *regclient.RegClient, m manifest.Manifest) (*ociv1.Descr
if !m.IsSet() { if !m.IsSet() {
m, err = rc.ManifestGet(context.Background(), m.GetRef()) m, err = rc.ManifestGet(context.Background(), m.GetRef())
if err != nil { if err != nil {
return desc, err return desc, fmt.Errorf("unable to retrieve manifest list: %w", err)
} }
} }
@ -239,6 +239,9 @@ func runManifestDigest(cmd *cobra.Command, args []string) error {
// retrieve the specified platform from the manifest list // retrieve the specified platform from the manifest list
for m.IsList() && !manifestOpts.list && !manifestOpts.requireList { for m.IsList() && !manifestOpts.list && !manifestOpts.requireList {
desc, err := getPlatformDesc(rc, m) desc, err := getPlatformDesc(rc, m)
if err != nil {
return fmt.Errorf("Failed retrieving platform specific digest: %w", err)
}
r.Digest = desc.Digest.String() r.Digest = desc.Digest.String()
m, err = rc.ManifestHead(context.Background(), r) m, err = rc.ManifestHead(context.Background(), r)
if err != nil { if err != nil {
@ -283,7 +286,13 @@ func runManifestPut(cmd *cobra.Command, args []string) error {
if err != nil { if err != nil {
return err return err
} }
rcM, err := manifest.New(manifestOpts.contentType, raw, r, nil) rcM, err := manifest.New(
manifest.WithRef(r),
manifest.WithRaw(raw),
manifest.WithDesc(ociv1.Descriptor{
MediaType: manifestOpts.contentType,
}),
)
if err != nil { if err != nil {
return err return err
} }

View File

@ -196,7 +196,7 @@ func (rc *RegClient) imageCopyOpt(ctx context.Context, refSrc ref.Ref, refTgt re
cd, err := m.GetConfigDigest() cd, err := m.GetConfigDigest()
if err != nil { if err != nil {
// docker schema v1 does not have a config object, ignore if it's missing // docker schema v1 does not have a config object, ignore if it's missing
if !errors.Is(err, manifest.ErrUnsupportedMediaType) { if !errors.Is(err, types.ErrUnsupportedMediaType) {
rc.log.WithFields(logrus.Fields{ rc.log.WithFields(logrus.Fields{
"ref": refSrc.Reference, "ref": refSrc.Reference,
"err": err, "err": err,
@ -449,7 +449,7 @@ func (rc *RegClient) imageExportDescriptor(ctx context.Context, ref ref.Ref, des
// add config // add config
confD, err := m.GetConfigDescriptor() confD, err := m.GetConfigDescriptor()
// ignore unsupported media type errors // ignore unsupported media type errors
if err != nil && !errors.Is(err, manifest.ErrUnsupportedMediaType) { if err != nil && !errors.Is(err, types.ErrUnsupportedMediaType) {
return err return err
} }
if err == nil { if err == nil {
@ -462,7 +462,7 @@ func (rc *RegClient) imageExportDescriptor(ctx context.Context, ref ref.Ref, des
// loop over layers // loop over layers
layerDL, err := m.GetLayers() layerDL, err := m.GetLayers()
// ignore unsupported media type errors // ignore unsupported media type errors
if err != nil && !errors.Is(err, manifest.ErrUnsupportedMediaType) { if err != nil && !errors.Is(err, types.ErrUnsupportedMediaType) {
return err return err
} }
if err == nil { if err == nil {
@ -558,7 +558,7 @@ func (rc *RegClient) ImageImport(ctx context.Context, ref ref.Ref, rs io.ReadSee
return fmt.Errorf("Failed to import layers from docker tar: %w", err) return fmt.Errorf("Failed to import layers from docker tar: %w", err)
} }
// push docker manifest // push docker manifest
m, err := manifest.FromOrig(trd.dockerManifest) m, err := manifest.New(manifest.WithOrig(trd.dockerManifest))
if err != nil { if err != nil {
return err return err
} }
@ -677,7 +677,7 @@ func (rc *RegClient) imageImportOCIAddHandler(ctx context.Context, ref ref.Ref,
// no need to process docker manifest.json when OCI layout is available // no need to process docker manifest.json when OCI layout is available
delete(trd.handlers, dockerManifestFilename) delete(trd.handlers, dockerManifestFilename)
// create a manifest from the index // create a manifest from the index
trd.ociManifest, err = manifest.FromOrig(trd.ociIndex) trd.ociManifest, err = manifest.New(manifest.WithOrig(trd.ociIndex))
if err != nil { if err != nil {
return err return err
} }
@ -745,7 +745,7 @@ func (rc *RegClient) imageImportOCIHandleManifest(ctx context.Context, ref ref.R
types.MediaTypeDocker2Manifest, types.MediaTypeDocker2ManifestList, types.MediaTypeDocker2Manifest, types.MediaTypeDocker2ManifestList,
types.MediaTypeOCI1Manifest, types.MediaTypeOCI1ManifestList: types.MediaTypeOCI1Manifest, types.MediaTypeOCI1ManifestList:
// known manifest media types // known manifest media types
md, err := manifest.FromDescriptor(d, b) md, err := manifest.New(manifest.WithDesc(d), manifest.WithRaw(b))
if err != nil { if err != nil {
return err return err
} }
@ -757,7 +757,7 @@ func (rc *RegClient) imageImportOCIHandleManifest(ctx context.Context, ref ref.R
return rc.imageImportBlob(ctx, ref, d, trd) return rc.imageImportBlob(ctx, ref, d, trd)
default: default:
// attempt manifest import, fall back to blob import // attempt manifest import, fall back to blob import
md, err := manifest.FromDescriptor(d, b) md, err := manifest.New(manifest.WithDesc(d), manifest.WithRaw(b))
if err == nil { if err == nil {
return rc.imageImportOCIHandleManifest(ctx, ref, md, trd, true) return rc.imageImportOCIHandleManifest(ctx, ref, md, trd, true)
} }

View File

@ -10,6 +10,7 @@ import (
"net/url" "net/url"
"os" "os"
"testing" "testing"
"time"
"github.com/docker/distribution" "github.com/docker/distribution"
dockerSchema2 "github.com/docker/distribution/manifest/schema2" dockerSchema2 "github.com/docker/distribution/manifest/schema2"
@ -142,7 +143,13 @@ func TestManifest(t *testing.T) {
Hooks: make(logrus.LevelHooks), Hooks: make(logrus.LevelHooks),
Level: logrus.WarnLevel, Level: logrus.WarnLevel,
} }
rc := New(WithConfigHosts(rcHosts), WithLog(log)) delayInit, _ := time.ParseDuration("0.05s")
delayMax, _ := time.ParseDuration("0.10s")
rc := New(
WithConfigHosts(rcHosts),
WithLog(log),
WithRetryDelay(delayInit, delayMax),
)
t.Run("Get", func(t *testing.T) { t.Run("Get", func(t *testing.T) {
getRef, err := ref.New(tsURL.Host + repoPath + ":" + getTag) getRef, err := ref.New(tsURL.Host + repoPath + ":" + getTag)
if err != nil { if err != nil {

View File

@ -6,8 +6,12 @@
package manifest package manifest
import ( import (
"net/http"
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
topTypes "github.com/regclient/regclient/types" topTypes "github.com/regclient/regclient/types"
topManifest "github.com/regclient/regclient/types/manifest" topManifest "github.com/regclient/regclient/types/manifest"
"github.com/regclient/regclient/types/ref"
) )
const ( const (
@ -28,12 +32,30 @@ const (
type Manifest = topManifest.Manifest type Manifest = topManifest.Manifest
var ( var (
New = topManifest.New
FromDescriptor = topManifest.FromDescriptor
FromOrig = topManifest.FromOrig
ErrNotFound = topTypes.ErrNotFound ErrNotFound = topTypes.ErrNotFound
ErrNotImplemented = topTypes.ErrNotImplemented ErrNotImplemented = topTypes.ErrNotImplemented
ErrUnavailable = topTypes.ErrUnavailable ErrUnavailable = topTypes.ErrUnavailable
ErrUnsupportedMediaType = topTypes.ErrUnsupported ErrUnsupportedMediaType = topTypes.ErrUnsupported
) )
func New(mediaType string, raw []byte, r ref.Ref, header http.Header) (Manifest, error) {
return topManifest.New(
topManifest.WithDesc(ociv1.Descriptor{
MediaType: mediaType,
}),
topManifest.WithRef(r),
topManifest.WithRaw(raw),
topManifest.WithHeader(header),
)
}
func FromDescriptor(desc ociv1.Descriptor, mBytes []byte) (Manifest, error) {
return topManifest.New(
topManifest.WithDesc(desc),
topManifest.WithRaw(mBytes),
)
}
func FromOrig(orig interface{}) (Manifest, error) {
return topManifest.New(topManifest.WithOrig(orig))
}

View File

@ -12,6 +12,7 @@ import (
"strings" "strings"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/regclient/regclient/internal/reghttp" "github.com/regclient/regclient/internal/reghttp"
"github.com/regclient/regclient/types" "github.com/regclient/regclient/types"
"github.com/regclient/regclient/types/blob" "github.com/regclient/regclient/types/blob"
@ -62,9 +63,14 @@ func (reg *Reg) BlobGet(ctx context.Context, r ref.Ref, d digest.Digest) (blob.R
return nil, fmt.Errorf("Failed to get blob, digest %s, ref %s: %w", d, r.CommonName(), reghttp.HttpError(resp.HTTPResponse().StatusCode)) return nil, fmt.Errorf("Failed to get blob, digest %s, ref %s: %w", d, r.CommonName(), reghttp.HttpError(resp.HTTPResponse().StatusCode))
} }
b := blob.NewReader(resp) b := blob.NewReader(
b.SetMeta(r, d, 0) blob.WithRef(r),
b.SetResp(resp.HTTPResponse()) blob.WithReadCloser(resp),
blob.WithDesc(ociv1.Descriptor{
Digest: d,
}),
blob.WithResp(resp.HTTPResponse()),
)
return b, nil return b, nil
} }
@ -90,9 +96,13 @@ func (reg *Reg) BlobHead(ctx context.Context, r ref.Ref, d digest.Digest) (blob.
return nil, fmt.Errorf("Failed to request blob head, digest %s, ref %s: %w", d, r.CommonName(), reghttp.HttpError(resp.HTTPResponse().StatusCode)) return nil, fmt.Errorf("Failed to request blob head, digest %s, ref %s: %w", d, r.CommonName(), reghttp.HttpError(resp.HTTPResponse().StatusCode))
} }
b := blob.NewReader(nil) b := blob.NewReader(
b.SetMeta(r, d, 0) blob.WithRef(r),
b.SetResp(resp.HTTPResponse()) blob.WithDesc(ociv1.Descriptor{
Digest: d,
}),
blob.WithResp(resp.HTTPResponse()),
)
return b, nil return b, nil
} }

View File

@ -93,10 +93,11 @@ func (reg *Reg) ManifestGet(ctx context.Context, r ref.Ref) (manifest.Manifest,
return nil, fmt.Errorf("Error reading manifest for %s: %w", r.CommonName(), err) return nil, fmt.Errorf("Error reading manifest for %s: %w", r.CommonName(), err)
} }
// parse body into variable according to media type return manifest.New(
mt := resp.HTTPResponse().Header.Get("Content-Type") manifest.WithRef(r),
manifest.WithHeader(resp.HTTPResponse().Header),
return manifest.New(mt, rawBody, r, resp.HTTPResponse().Header) manifest.WithRaw(rawBody),
)
} }
// ManifestHead returns metadata on the manifest from the registry // ManifestHead returns metadata on the manifest from the registry
@ -142,10 +143,10 @@ func (reg *Reg) ManifestHead(ctx context.Context, r ref.Ref) (manifest.Manifest,
return nil, fmt.Errorf("Failed to request manifest head %s: %w", r.CommonName(), reghttp.HttpError(resp.HTTPResponse().StatusCode)) return nil, fmt.Errorf("Failed to request manifest head %s: %w", r.CommonName(), reghttp.HttpError(resp.HTTPResponse().StatusCode))
} }
// extract header data return manifest.New(
mt := resp.HTTPResponse().Header.Get("Content-Type") manifest.WithRef(r),
manifest.WithHeader(resp.HTTPResponse().Header),
return manifest.New(mt, []byte{}, r, resp.HTTPResponse().Header) )
} }
// ManifestPut uploads a manifest to a registry // ManifestPut uploads a manifest to a registry

View File

@ -106,7 +106,7 @@ func (reg *Reg) TagDelete(ctx context.Context, r ref.Ref) error {
// create manifest with config, matching the original tag manifest type // create manifest with config, matching the original tag manifest type
switch curManifest.GetMediaType() { switch curManifest.GetMediaType() {
case types.MediaTypeOCI1Manifest, types.MediaTypeOCI1ManifestList: case types.MediaTypeOCI1Manifest, types.MediaTypeOCI1ManifestList:
tempManifest, err = manifest.FromOrig(ociv1.Manifest{ tempManifest, err = manifest.New(manifest.WithOrig(ociv1.Manifest{
Versioned: ociv1Specs.Versioned{ Versioned: ociv1Specs.Versioned{
SchemaVersion: 2, SchemaVersion: 2,
}, },
@ -117,12 +117,12 @@ func (reg *Reg) TagDelete(ctx context.Context, r ref.Ref) error {
Size: int64(len(confB)), Size: int64(len(confB)),
}, },
Layers: []ociv1.Descriptor{}, Layers: []ociv1.Descriptor{},
}) }))
if err != nil { if err != nil {
return err return err
} }
default: // default to the docker v2 schema default: // default to the docker v2 schema
tempManifest, err = manifest.FromOrig(dockerSchema2.Manifest{ tempManifest, err = manifest.New(manifest.WithOrig(dockerSchema2.Manifest{
Versioned: dockerManifest.Versioned{ Versioned: dockerManifest.Versioned{
SchemaVersion: 2, SchemaVersion: 2,
MediaType: types.MediaTypeDocker2Manifest, MediaType: types.MediaTypeDocker2Manifest,
@ -133,7 +133,7 @@ func (reg *Reg) TagDelete(ctx context.Context, r ref.Ref) error {
Size: int64(len(confB)), Size: int64(len(confB)),
}, },
Layers: []dockerDistribution.Descriptor{}, Layers: []dockerDistribution.Descriptor{},
}) }))
if err != nil { if err != nil {
return err return err
} }

View File

@ -1,7 +1,61 @@
package blob package blob
import (
"io"
"net/http"
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/regclient/regclient/types/ref"
)
// Blob interface is used for returning blobs // Blob interface is used for returning blobs
type Blob interface { type Blob interface {
Common Common
RawBody() ([]byte, error) RawBody() ([]byte, error)
} }
type BlobConfig struct {
desc ociv1.Descriptor
header http.Header
image ociv1.Image
r ref.Ref
rc io.ReadCloser
resp *http.Response
}
type Opts func(*BlobConfig)
func WithDesc(d ociv1.Descriptor) Opts {
return func(bc *BlobConfig) {
bc.desc = d
}
}
func WithHeader(header http.Header) Opts {
return func(bc *BlobConfig) {
bc.header = header
}
}
func WithImage(image ociv1.Image) Opts {
return func(bc *BlobConfig) {
bc.image = image
}
}
func WithReadCloser(rc io.ReadCloser) Opts {
return func(bc *BlobConfig) {
bc.rc = rc
}
}
func WithRef(r ref.Ref) Opts {
return func(bc *BlobConfig) {
bc.r = r
}
}
func WithResp(resp *http.Response) Opts {
return func(bc *BlobConfig) {
bc.resp = resp
if bc.header == nil {
bc.header = resp.Header
}
}
}

View File

@ -2,9 +2,9 @@ package blob
import ( import (
"net/http" "net/http"
"strconv"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/regclient/regclient/types/ref" "github.com/regclient/regclient/types/ref"
) )
@ -15,15 +15,11 @@ type Common interface {
MediaType() string MediaType() string
Response() *http.Response Response() *http.Response
RawHeaders() http.Header RawHeaders() http.Header
SetMeta(r ref.Ref, d digest.Digest, cl int64)
SetResp(resp *http.Response)
} }
type common struct { type common struct {
r ref.Ref r ref.Ref
digest digest.Digest desc ociv1.Descriptor
cl int64
mt string
blobSet bool blobSet bool
rawHeader http.Header rawHeader http.Header
resp *http.Response resp *http.Response
@ -31,17 +27,17 @@ type common struct {
// Digest returns the provided or calculated digest of the blob // Digest returns the provided or calculated digest of the blob
func (b *common) Digest() digest.Digest { func (b *common) Digest() digest.Digest {
return b.digest return b.desc.Digest
} }
// Length returns the provided or calculated length of the blob // Length returns the provided or calculated length of the blob
func (b *common) Length() int64 { func (b *common) Length() int64 {
return b.cl return b.desc.Size
} }
// MediaType returns the Content-Type header received from the registry // MediaType returns the Content-Type header received from the registry
func (b *common) MediaType() string { func (b *common) MediaType() string {
return b.mt return b.desc.MediaType
} }
// RawHeaders returns the headers received from the registry // RawHeaders returns the headers received from the registry
@ -53,22 +49,3 @@ func (b *common) RawHeaders() http.Header {
func (b *common) Response() *http.Response { func (b *common) Response() *http.Response {
return b.resp return b.resp
} }
// SetMeta sets the various blob metadata (reference, digest, and content length)
func (b *common) SetMeta(ref ref.Ref, d digest.Digest, cl int64) {
b.r = ref
b.digest = d
b.cl = cl
}
// SetResp sets the response header data when pulling from a registry
func (b *common) SetResp(resp *http.Response) {
if resp == nil {
return
}
b.resp = resp
b.rawHeader = resp.Header
cl, _ := strconv.Atoi(resp.Header.Get("Content-Length"))
b.cl = int64(cl)
b.mt = resp.Header.Get("Content-Type")
}

View File

@ -22,13 +22,21 @@ type ociConfig struct {
} }
// NewOCIConfig creates a new BlobOCIConfig from an OCI Image // NewOCIConfig creates a new BlobOCIConfig from an OCI Image
func NewOCIConfig(ociImage ociv1.Image) OCIConfig { func NewOCIConfig(opts ...Opts) OCIConfig {
bc := common{ bc := BlobConfig{}
blobSet: true, for _, opt := range opts {
opt(&bc)
}
c := common{
blobSet: true,
desc: bc.desc,
r: bc.r,
rawHeader: bc.header,
resp: bc.resp,
} }
b := ociConfig{ b := ociConfig{
common: bc, common: c,
Image: ociImage, Image: bc.image,
} }
return &b return &b
} }

View File

@ -5,10 +5,10 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"strconv"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
ociv1 "github.com/opencontainers/image-spec/specs-go/v1" ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/regclient/regclient/types/ref"
) )
// Reader is an unprocessed Blob with an available ReadCloser for reading the Blob // Reader is an unprocessed Blob with an available ReadCloser for reading the Blob
@ -25,24 +25,41 @@ type reader struct {
reader io.Reader reader io.Reader
origRdr io.ReadCloser origRdr io.ReadCloser
digester digest.Digester digester digest.Digester
// io.ReadCloser
} }
// NewReader creates a new reader // NewReader creates a new reader
func NewReader(rdr io.ReadCloser) Reader { func NewReader(opts ...Opts) Reader {
digester := digest.Canonical.Digester() bc := BlobConfig{}
digestRdr := io.TeeReader(rdr, digester.Hash()) for _, opt := range opts {
bc := common{ opt(&bc)
r: ref.Ref{},
} }
if rdr != nil { if bc.resp != nil {
bc.blobSet = true // extract headers and reader if other fields not passed
if bc.desc.MediaType == "" {
bc.desc.MediaType = bc.resp.Header.Get("Content-Type")
}
if bc.desc.Size == 0 {
cl, _ := strconv.Atoi(bc.resp.Header.Get("Content-Length"))
bc.desc.Size = int64(cl)
}
if bc.desc.Digest == "" {
bc.desc.Digest = digest.FromString(bc.resp.Header.Get("Docker-Content-Digest"))
}
}
c := common{
r: bc.r,
desc: bc.desc,
rawHeader: bc.header,
resp: bc.resp,
} }
br := reader{ br := reader{
common: bc, common: c,
reader: digestRdr, origRdr: bc.rc,
origRdr: rdr, }
digester: digester, if bc.rc != nil {
br.blobSet = true
br.digester = digest.Canonical.Digester()
br.reader = io.TeeReader(bc.rc, br.digester.Hash())
} }
return &br return &br
} }
@ -65,16 +82,16 @@ func (b *reader) Read(p []byte) (int, error) {
b.readBytes = b.readBytes + int64(size) b.readBytes = b.readBytes + int64(size)
if err == io.EOF { if err == io.EOF {
// check/save size // check/save size
if b.cl == 0 { if b.desc.Size == 0 {
b.cl = b.readBytes b.desc.Size = b.readBytes
} else if b.readBytes != b.cl { } else if b.readBytes != b.desc.Size {
err = fmt.Errorf("Expected size mismatch [expected %d, received %d]: %w", b.cl, b.readBytes, err) err = fmt.Errorf("Expected size mismatch [expected %d, received %d]: %w", b.desc.Size, b.readBytes, err)
} }
// check/save digest // check/save digest
if b.digest == "" { if b.desc.Digest == "" {
b.digest = b.digester.Digest() b.desc.Digest = b.digester.Digest()
} else if b.digest != b.digester.Digest() { } else if b.desc.Digest != b.digester.Digest() {
err = fmt.Errorf("Expected digest mismatch [expected %s, calculated %s]: %w", b.digest.String(), b.digester.Digest().String(), err) err = fmt.Errorf("Expected digest mismatch [expected %s, calculated %s]: %w", b.desc.Digest.String(), b.digester.Digest().String(), err)
} }
} }
return size, err return size, err

View File

@ -6,14 +6,14 @@ import (
"strings" "strings"
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/regclient/regclient/types" "github.com/regclient/regclient/types"
"github.com/regclient/regclient/types/ref" "github.com/regclient/regclient/types/ref"
) )
type common struct { type common struct {
r ref.Ref r ref.Ref
digest digest.Digest desc ociv1.Descriptor
mt string
manifSet bool manifSet bool
ratelimit types.RateLimit ratelimit types.RateLimit
rawHeader http.Header rawHeader http.Header
@ -22,12 +22,12 @@ type common struct {
// GetDigest returns the digest // GetDigest returns the digest
func (m *common) GetDigest() digest.Digest { func (m *common) GetDigest() digest.Digest {
return m.digest return m.desc.Digest
} }
// GetMediaType returns the media type // GetMediaType returns the media type
func (m *common) GetMediaType() string { func (m *common) GetMediaType() string {
return m.mt return m.desc.MediaType
} }
// GetRateLimit returns the rate limit when the manifest was pulled from a registry. // GetRateLimit returns the rate limit when the manifest was pulled from a registry.
@ -48,7 +48,7 @@ func (m *common) HasRateLimit() bool {
// IsList indicates if the manifest is a docker Manifest List or OCI Index // IsList indicates if the manifest is a docker Manifest List or OCI Index
func (m *common) IsList() bool { func (m *common) IsList() bool {
switch m.mt { switch m.desc.MediaType {
case MediaTypeDocker2ManifestList, MediaTypeOCI1ManifestList: case MediaTypeDocker2ManifestList, MediaTypeOCI1ManifestList:
return true return true
default: default:
@ -65,7 +65,7 @@ func (m *common) IsSet() bool {
// RawBody returns the raw body from the manifest if available. // RawBody returns the raw body from the manifest if available.
func (m *common) RawBody() ([]byte, error) { func (m *common) RawBody() ([]byte, error) {
if len(m.rawBody) == 0 { if len(m.rawBody) == 0 {
return m.rawBody, ErrUnavailable return m.rawBody, types.ErrUnavailable
} }
return m.rawBody, nil return m.rawBody, nil
} }

View File

@ -9,6 +9,7 @@ import (
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
ociv1 "github.com/opencontainers/image-spec/specs-go/v1" ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/regclient/regclient/internal/wraperr" "github.com/regclient/regclient/internal/wraperr"
"github.com/regclient/regclient/types"
) )
const ( const (
@ -28,23 +29,23 @@ type docker1SignedManifest struct {
} }
func (m *docker1Manifest) GetConfigDescriptor() (ociv1.Descriptor, error) { func (m *docker1Manifest) GetConfigDescriptor() (ociv1.Descriptor, error) {
return ociv1.Descriptor{}, wraperr.New(fmt.Errorf("Config digest not available for media type %s", m.mt), ErrUnsupportedMediaType) return ociv1.Descriptor{}, wraperr.New(fmt.Errorf("Config digest not available for media type %s", m.desc.MediaType), types.ErrUnsupportedMediaType)
} }
func (m *docker1Manifest) GetConfigDigest() (digest.Digest, error) { func (m *docker1Manifest) GetConfigDigest() (digest.Digest, error) {
return "", wraperr.New(fmt.Errorf("Config digest not available for media type %s", m.mt), ErrUnsupportedMediaType) return "", wraperr.New(fmt.Errorf("Config digest not available for media type %s", m.desc.MediaType), types.ErrUnsupportedMediaType)
} }
func (m *docker1SignedManifest) GetConfigDescriptor() (ociv1.Descriptor, error) { func (m *docker1SignedManifest) GetConfigDescriptor() (ociv1.Descriptor, error) {
return ociv1.Descriptor{}, wraperr.New(fmt.Errorf("Config digest not available for media type %s", m.mt), ErrUnsupportedMediaType) return ociv1.Descriptor{}, wraperr.New(fmt.Errorf("Config digest not available for media type %s", m.desc.MediaType), types.ErrUnsupportedMediaType)
} }
func (m *docker1SignedManifest) GetConfigDigest() (digest.Digest, error) { func (m *docker1SignedManifest) GetConfigDigest() (digest.Digest, error) {
return "", wraperr.New(fmt.Errorf("Config digest not available for media type %s", m.mt), ErrUnsupportedMediaType) return "", wraperr.New(fmt.Errorf("Config digest not available for media type %s", m.desc.MediaType), types.ErrUnsupportedMediaType)
} }
func (m *docker1Manifest) GetDescriptorList() ([]ociv1.Descriptor, error) { func (m *docker1Manifest) GetDescriptorList() ([]ociv1.Descriptor, error) {
return []ociv1.Descriptor{}, wraperr.New(fmt.Errorf("Platform descriptor list not available for media type %s", m.mt), ErrUnsupportedMediaType) return []ociv1.Descriptor{}, wraperr.New(fmt.Errorf("Platform descriptor list not available for media type %s", m.desc.MediaType), types.ErrUnsupportedMediaType)
} }
func (m *docker1SignedManifest) GetDescriptorList() ([]ociv1.Descriptor, error) { func (m *docker1SignedManifest) GetDescriptorList() ([]ociv1.Descriptor, error) {
return []ociv1.Descriptor{}, wraperr.New(fmt.Errorf("Platform descriptor list not available for media type %s", m.mt), ErrUnsupportedMediaType) return []ociv1.Descriptor{}, wraperr.New(fmt.Errorf("Platform descriptor list not available for media type %s", m.desc.MediaType), types.ErrUnsupportedMediaType)
} }
func (m *docker1Manifest) GetLayers() ([]ociv1.Descriptor, error) { func (m *docker1Manifest) GetLayers() ([]ociv1.Descriptor, error) {
@ -74,22 +75,22 @@ func (m *docker1SignedManifest) GetOrigManifest() interface{} {
} }
func (m *docker1Manifest) GetPlatformDesc(p *ociv1.Platform) (*ociv1.Descriptor, error) { func (m *docker1Manifest) GetPlatformDesc(p *ociv1.Platform) (*ociv1.Descriptor, error) {
return nil, wraperr.New(fmt.Errorf("Platform lookup not available for media type %s", m.mt), ErrUnsupportedMediaType) return nil, wraperr.New(fmt.Errorf("Platform lookup not available for media type %s", m.desc.MediaType), types.ErrUnsupportedMediaType)
} }
func (m *docker1SignedManifest) GetPlatformDesc(p *ociv1.Platform) (*ociv1.Descriptor, error) { func (m *docker1SignedManifest) GetPlatformDesc(p *ociv1.Platform) (*ociv1.Descriptor, error) {
return nil, wraperr.New(fmt.Errorf("Platform lookup not available for media type %s", m.mt), ErrUnsupportedMediaType) return nil, wraperr.New(fmt.Errorf("Platform lookup not available for media type %s", m.desc.MediaType), types.ErrUnsupportedMediaType)
} }
func (m *docker1Manifest) GetPlatformList() ([]*ociv1.Platform, error) { func (m *docker1Manifest) GetPlatformList() ([]*ociv1.Platform, error) {
return nil, wraperr.New(fmt.Errorf("Platform list not available for media type %s", m.mt), ErrUnsupportedMediaType) return nil, wraperr.New(fmt.Errorf("Platform list not available for media type %s", m.desc.MediaType), types.ErrUnsupportedMediaType)
} }
func (m *docker1SignedManifest) GetPlatformList() ([]*ociv1.Platform, error) { func (m *docker1SignedManifest) GetPlatformList() ([]*ociv1.Platform, error) {
return nil, wraperr.New(fmt.Errorf("Platform list not available for media type %s", m.mt), ErrUnsupportedMediaType) return nil, wraperr.New(fmt.Errorf("Platform list not available for media type %s", m.desc.MediaType), types.ErrUnsupportedMediaType)
} }
func (m *docker1Manifest) MarshalJSON() ([]byte, error) { func (m *docker1Manifest) MarshalJSON() ([]byte, error) {
if !m.manifSet { if !m.manifSet {
return []byte{}, wraperr.New(fmt.Errorf("Manifest unavailable, perform a ManifestGet first"), ErrUnavailable) return []byte{}, wraperr.New(fmt.Errorf("Manifest unavailable, perform a ManifestGet first"), types.ErrUnavailable)
} }
if len(m.rawBody) > 0 { if len(m.rawBody) > 0 {

View File

@ -13,6 +13,7 @@ import (
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
ociv1 "github.com/opencontainers/image-spec/specs-go/v1" ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/regclient/regclient/internal/wraperr" "github.com/regclient/regclient/internal/wraperr"
"github.com/regclient/regclient/types"
) )
const ( const (
@ -45,14 +46,14 @@ func (m *docker2Manifest) GetConfigDigest() (digest.Digest, error) {
return m.Config.Digest, nil return m.Config.Digest, nil
} }
func (m *docker2ManifestList) GetConfigDescriptor() (ociv1.Descriptor, error) { func (m *docker2ManifestList) GetConfigDescriptor() (ociv1.Descriptor, error) {
return ociv1.Descriptor{}, wraperr.New(fmt.Errorf("Config digest not available for media type %s", m.mt), ErrUnsupportedMediaType) return ociv1.Descriptor{}, wraperr.New(fmt.Errorf("Config digest not available for media type %s", m.desc.MediaType), types.ErrUnsupportedMediaType)
} }
func (m *docker2ManifestList) GetConfigDigest() (digest.Digest, error) { func (m *docker2ManifestList) GetConfigDigest() (digest.Digest, error) {
return "", wraperr.New(fmt.Errorf("Config digest not available for media type %s", m.mt), ErrUnsupportedMediaType) return "", wraperr.New(fmt.Errorf("Config digest not available for media type %s", m.desc.MediaType), types.ErrUnsupportedMediaType)
} }
func (m *docker2Manifest) GetDescriptorList() ([]ociv1.Descriptor, error) { func (m *docker2Manifest) GetDescriptorList() ([]ociv1.Descriptor, error) {
return []ociv1.Descriptor{}, wraperr.New(fmt.Errorf("Platform descriptor list not available for media type %s", m.mt), ErrUnsupportedMediaType) return []ociv1.Descriptor{}, wraperr.New(fmt.Errorf("Platform descriptor list not available for media type %s", m.desc.MediaType), types.ErrUnsupportedMediaType)
} }
func (m *docker2ManifestList) GetDescriptorList() ([]ociv1.Descriptor, error) { func (m *docker2ManifestList) GetDescriptorList() ([]ociv1.Descriptor, error) {
dl := []ociv1.Descriptor{} dl := []ociv1.Descriptor{}
@ -70,7 +71,7 @@ func (m *docker2Manifest) GetLayers() ([]ociv1.Descriptor, error) {
return dl, nil return dl, nil
} }
func (m *docker2ManifestList) GetLayers() ([]ociv1.Descriptor, error) { func (m *docker2ManifestList) GetLayers() ([]ociv1.Descriptor, error) {
return []ociv1.Descriptor{}, wraperr.New(fmt.Errorf("Layers are not available for media type %s", m.mt), ErrUnsupportedMediaType) return []ociv1.Descriptor{}, wraperr.New(fmt.Errorf("Layers are not available for media type %s", m.desc.MediaType), types.ErrUnsupportedMediaType)
} }
func (m *docker2Manifest) GetOrigManifest() interface{} { func (m *docker2Manifest) GetOrigManifest() interface{} {
@ -81,7 +82,7 @@ func (m *docker2ManifestList) GetOrigManifest() interface{} {
} }
func (m *docker2Manifest) GetPlatformDesc(p *ociv1.Platform) (*ociv1.Descriptor, error) { func (m *docker2Manifest) GetPlatformDesc(p *ociv1.Platform) (*ociv1.Descriptor, error) {
return nil, wraperr.New(fmt.Errorf("Platform lookup not available for media type %s", m.mt), ErrUnsupportedMediaType) return nil, wraperr.New(fmt.Errorf("Platform lookup not available for media type %s", m.desc.MediaType), types.ErrUnsupportedMediaType)
} }
func (m *docker2ManifestList) GetPlatformDesc(p *ociv1.Platform) (*ociv1.Descriptor, error) { func (m *docker2ManifestList) GetPlatformDesc(p *ociv1.Platform) (*ociv1.Descriptor, error) {
dl, err := m.GetDescriptorList() dl, err := m.GetDescriptorList()
@ -92,7 +93,7 @@ func (m *docker2ManifestList) GetPlatformDesc(p *ociv1.Platform) (*ociv1.Descrip
} }
func (m *docker2Manifest) GetPlatformList() ([]*ociv1.Platform, error) { func (m *docker2Manifest) GetPlatformList() ([]*ociv1.Platform, error) {
return nil, wraperr.New(fmt.Errorf("Platform list not available for media type %s", m.mt), ErrUnsupportedMediaType) return nil, wraperr.New(fmt.Errorf("Platform list not available for media type %s", m.desc.MediaType), types.ErrUnsupportedMediaType)
} }
func (m *docker2ManifestList) GetPlatformList() ([]*ociv1.Platform, error) { func (m *docker2ManifestList) GetPlatformList() ([]*ociv1.Platform, error) {
dl, err := m.GetDescriptorList() dl, err := m.GetDescriptorList()
@ -104,7 +105,7 @@ func (m *docker2ManifestList) GetPlatformList() ([]*ociv1.Platform, error) {
func (m *docker2Manifest) MarshalJSON() ([]byte, error) { func (m *docker2Manifest) MarshalJSON() ([]byte, error) {
if !m.manifSet { if !m.manifSet {
return []byte{}, wraperr.New(fmt.Errorf("Manifest unavailable, perform a ManifestGet first"), ErrUnavailable) return []byte{}, wraperr.New(fmt.Errorf("Manifest unavailable, perform a ManifestGet first"), types.ErrUnavailable)
} }
if len(m.rawBody) > 0 { if len(m.rawBody) > 0 {
@ -115,7 +116,7 @@ func (m *docker2Manifest) MarshalJSON() ([]byte, error) {
} }
func (m *docker2ManifestList) MarshalJSON() ([]byte, error) { func (m *docker2ManifestList) MarshalJSON() ([]byte, error) {
if !m.manifSet { if !m.manifSet {
return []byte{}, wraperr.New(fmt.Errorf("Manifest unavailable, perform a ManifestGet first"), ErrUnavailable) return []byte{}, wraperr.New(fmt.Errorf("Manifest unavailable, perform a ManifestGet first"), types.ErrUnavailable)
} }
if len(m.rawBody) > 0 { if len(m.rawBody) > 0 {
@ -142,8 +143,8 @@ func (m *docker2ManifestList) MarshalPretty() ([]byte, error) {
if m.r.Reference != "" { if m.r.Reference != "" {
fmt.Fprintf(tw, "Name:\t%s\n", m.r.Reference) fmt.Fprintf(tw, "Name:\t%s\n", m.r.Reference)
} }
fmt.Fprintf(tw, "MediaType:\t%s\n", m.mt) fmt.Fprintf(tw, "MediaType:\t%s\n", m.desc.MediaType)
fmt.Fprintf(tw, "Digest:\t%s\n", m.digest.String()) fmt.Fprintf(tw, "Digest:\t%s\n", m.desc.Digest.String())
fmt.Fprintf(tw, "\t\n") fmt.Fprintf(tw, "\t\n")
fmt.Fprintf(tw, "Manifests:\t\n") fmt.Fprintf(tw, "Manifests:\t\n")
for _, d := range m.Manifests { for _, d := range m.Manifests {

View File

@ -1,14 +0,0 @@
package manifest
import "errors"
var (
// ErrNotFound isn't there, search for your value elsewhere
ErrNotFound = errors.New("Not found")
// ErrNotImplemented returned when method has not been implemented yet
ErrNotImplemented = errors.New("Not implemented")
// ErrUnavailable when a requested value is not available
ErrUnavailable = errors.New("Unavailable")
// ErrUnsupportedMediaType returned when media type is unknown or unsupported
ErrUnsupportedMediaType = errors.New("Unsupported media type")
)

View File

@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"strconv"
"github.com/containerd/containerd/platforms" "github.com/containerd/containerd/platforms"
dockerDistribution "github.com/docker/distribution" dockerDistribution "github.com/docker/distribution"
@ -38,42 +39,75 @@ type Manifest interface {
RawHeaders() (http.Header, error) RawHeaders() (http.Header, error)
} }
// New creates a new manifest from an unparsed raw manifest type ManifestConfig struct {
// mediaType: should be a known media-type. If empty, resp headers will be checked r ref.Ref
// raw: body of the manifest. If empty, unset manifest for a HEAD request is returned desc ociv1.Descriptor
// ref: reference, may be unset raw []byte
// header: headers from request, used to extract content type, digest, and rate limits orig interface{}
func New(mediaType string, raw []byte, r ref.Ref, header http.Header) (Manifest, error) { header http.Header
mc := common{
r: r,
mt: mediaType,
rawBody: raw,
}
if header != nil {
mc.rawHeader = header
if mc.mt == "" {
mc.mt = header.Get("Content-Type")
}
mc.digest, _ = digest.Parse(header.Get("Docker-Content-Digest"))
mc.setRateLimit(header)
}
return fromCommon(mc)
} }
type Opts func(*ManifestConfig)
// FromDescriptor creates a new manifest from a descriptor and the raw manifest bytes. // New creates a new manifest based on provided options
func FromDescriptor(desc ociv1.Descriptor, mBytes []byte) (Manifest, error) { func New(opts ...Opts) (Manifest, error) {
mc := common{ mc := ManifestConfig{}
digest: desc.Digest, for _, opt := range opts {
mt: desc.MediaType, opt(&mc)
manifSet: true, }
rawBody: mBytes, c := common{
r: mc.r,
desc: mc.desc,
rawBody: mc.raw,
rawHeader: mc.header,
}
// extract fields from header where available
if mc.header != nil {
if c.desc.MediaType == "" {
c.desc.MediaType = mc.header.Get("Content-Type")
}
if mc.desc.Size == 0 {
cl, _ := strconv.Atoi(mc.header.Get("Content-Length"))
mc.desc.Size = int64(cl)
}
if c.desc.Digest == "" {
c.desc.Digest, _ = digest.Parse(mc.header.Get("Docker-Content-Digest"))
}
c.setRateLimit(mc.header)
}
if mc.orig != nil {
return fromOrig(c, mc.orig)
}
return fromCommon(c)
}
func WithDesc(desc ociv1.Descriptor) Opts {
return func(mc *ManifestConfig) {
mc.desc = desc
}
}
func WithHeader(header http.Header) Opts {
return func(mc *ManifestConfig) {
mc.header = header
}
}
func WithOrig(orig interface{}) Opts {
return func(mc *ManifestConfig) {
mc.orig = orig
}
}
func WithRaw(raw []byte) Opts {
return func(mc *ManifestConfig) {
mc.raw = raw
}
}
func WithRef(r ref.Ref) Opts {
return func(mc *ManifestConfig) {
mc.r = r
} }
return fromCommon(mc)
} }
// FromOrig creates a new manifest from the original upstream manifest type. // FromOrig creates a new manifest from the original upstream manifest type.
// This method should be used if you are creating a new manifest rather than pulling one from a registry. // This method should be used if you are creating a new manifest rather than pulling one from a registry.
func FromOrig(orig interface{}) (Manifest, error) { func fromOrig(c common, orig interface{}) (Manifest, error) {
var mt string var mt string
var m Manifest var m Manifest
@ -81,156 +115,154 @@ func FromOrig(orig interface{}) (Manifest, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
mc := common{ c.manifSet = true
digest: digest.FromBytes(mj), if len(c.rawBody) == 0 {
rawBody: mj, c.rawBody = mj
manifSet: true, }
if _, ok := orig.(dockerSchema1.SignedManifest); !ok && c.desc.Digest == "" {
c.desc.Digest = digest.FromBytes(mj)
}
if c.desc.Size == 0 {
c.desc.Size = int64(len(mj))
} }
// create manifest based on type // create manifest based on type
switch orig.(type) { switch orig.(type) {
case dockerSchema1.Manifest: case dockerSchema1.Manifest:
mOrig := orig.(dockerSchema1.Manifest) mOrig := orig.(dockerSchema1.Manifest)
mt = mOrig.MediaType mt = mOrig.MediaType
mc.mt = MediaTypeDocker1Manifest c.desc.MediaType = types.MediaTypeDocker1Manifest
m = &docker1Manifest{ m = &docker1Manifest{
common: mc, common: c,
Manifest: mOrig, Manifest: mOrig,
} }
case dockerSchema1.SignedManifest: case dockerSchema1.SignedManifest:
mOrig := orig.(dockerSchema1.SignedManifest) mOrig := orig.(dockerSchema1.SignedManifest)
mt = mOrig.MediaType mt = mOrig.MediaType
c.desc.MediaType = types.MediaTypeDocker1ManifestSigned
// recompute digest on the canonical data // recompute digest on the canonical data
mc.digest = digest.FromBytes(mOrig.Canonical) if c.desc.Digest == "" {
mc.mt = MediaTypeDocker1ManifestSigned c.desc.Digest = digest.FromBytes(mOrig.Canonical)
}
m = &docker1SignedManifest{ m = &docker1SignedManifest{
common: mc, common: c,
SignedManifest: mOrig, SignedManifest: mOrig,
} }
case dockerSchema2.Manifest: case dockerSchema2.Manifest:
mOrig := orig.(dockerSchema2.Manifest) mOrig := orig.(dockerSchema2.Manifest)
mt = mOrig.MediaType mt = mOrig.MediaType
mc.mt = MediaTypeDocker2Manifest c.desc.MediaType = types.MediaTypeDocker2Manifest
m = &docker2Manifest{ m = &docker2Manifest{
common: mc, common: c,
Manifest: mOrig, Manifest: mOrig,
} }
case dockerManifestList.ManifestList: case dockerManifestList.ManifestList:
mOrig := orig.(dockerManifestList.ManifestList) mOrig := orig.(dockerManifestList.ManifestList)
mt = mOrig.MediaType mt = mOrig.MediaType
mc.mt = MediaTypeDocker2ManifestList c.desc.MediaType = types.MediaTypeDocker2ManifestList
m = &docker2ManifestList{ m = &docker2ManifestList{
common: mc, common: c,
ManifestList: mOrig, ManifestList: mOrig,
} }
case ociv1.Manifest: case ociv1.Manifest:
mOrig := orig.(ociv1.Manifest) mOrig := orig.(ociv1.Manifest)
mt = mOrig.MediaType mt = mOrig.MediaType
mc.mt = MediaTypeOCI1Manifest c.desc.MediaType = types.MediaTypeOCI1Manifest
m = &oci1Manifest{ m = &oci1Manifest{
common: mc, common: c,
Manifest: mOrig, Manifest: mOrig,
} }
case ociv1.Index: case ociv1.Index:
mOrig := orig.(ociv1.Index) mOrig := orig.(ociv1.Index)
mt = mOrig.MediaType mt = mOrig.MediaType
mc.mt = MediaTypeOCI1ManifestList c.desc.MediaType = types.MediaTypeOCI1ManifestList
m = &oci1Index{ m = &oci1Index{
common: mc, common: c,
Index: orig.(ociv1.Index), Index: orig.(ociv1.Index),
} }
case UnknownData:
m = &unknown{
common: mc,
UnknownData: orig.(UnknownData),
}
default: default:
return nil, fmt.Errorf("Unsupported type to convert to a manifest: %T", orig) return nil, fmt.Errorf("Unsupported type to convert to a manifest: %T", orig)
} }
// verify media type // verify media type
err = verifyMT(mc.mt, mt) err = verifyMT(c.desc.MediaType, mt)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return m, nil return m, nil
} }
func fromCommon(mc common) (Manifest, error) { func fromCommon(c common) (Manifest, error) {
var err error var err error
var m Manifest var m Manifest
var mt string var mt string
// compute/verify digest // compute/verify digest
if len(mc.rawBody) > 0 { if len(c.rawBody) > 0 {
mc.manifSet = true c.manifSet = true
if mc.mt != MediaTypeDocker1ManifestSigned { if c.desc.MediaType != MediaTypeDocker1ManifestSigned {
d := digest.FromBytes(mc.rawBody) d := digest.FromBytes(c.rawBody)
if mc.digest == "" { if c.desc.Digest == "" {
mc.digest = d c.desc.Digest = d
} else if mc.digest != d { } else if c.desc.Digest != d {
return nil, fmt.Errorf("digest mismatch, expected %s, found %s", mc.digest.String(), d.String()) return nil, fmt.Errorf("digest mismatch, expected %s, found %s", c.desc.Digest.String(), d.String())
} }
} }
} }
switch mc.mt { switch c.desc.MediaType {
case MediaTypeDocker1Manifest: case MediaTypeDocker1Manifest:
var mOrig dockerSchema1.Manifest var mOrig dockerSchema1.Manifest
if len(mc.rawBody) > 0 { if len(c.rawBody) > 0 {
err = json.Unmarshal(mc.rawBody, &mOrig) err = json.Unmarshal(c.rawBody, &mOrig)
mt = mOrig.MediaType mt = mOrig.MediaType
} }
m = &docker1Manifest{common: mc, Manifest: mOrig} m = &docker1Manifest{common: c, Manifest: mOrig}
case MediaTypeDocker1ManifestSigned: case MediaTypeDocker1ManifestSigned:
var mOrig dockerSchema1.SignedManifest var mOrig dockerSchema1.SignedManifest
if len(mc.rawBody) > 0 { if len(c.rawBody) > 0 {
err = json.Unmarshal(mc.rawBody, &mOrig) err = json.Unmarshal(c.rawBody, &mOrig)
mt = mOrig.MediaType mt = mOrig.MediaType
d := digest.FromBytes(mOrig.Canonical) d := digest.FromBytes(mOrig.Canonical)
if mc.digest == "" { if c.desc.Digest == "" {
mc.digest = d c.desc.Digest = d
} else if mc.digest != d { } else if c.desc.Digest != d {
return nil, fmt.Errorf("digest mismatch, expected %s, found %s", mc.digest.String(), d.String()) return nil, fmt.Errorf("digest mismatch, expected %s, found %s", c.desc.Digest.String(), d.String())
} }
} }
m = &docker1SignedManifest{common: mc, SignedManifest: mOrig} m = &docker1SignedManifest{common: c, SignedManifest: mOrig}
case MediaTypeDocker2Manifest: case MediaTypeDocker2Manifest:
var mOrig dockerSchema2.Manifest var mOrig dockerSchema2.Manifest
if len(mc.rawBody) > 0 { if len(c.rawBody) > 0 {
err = json.Unmarshal(mc.rawBody, &mOrig) err = json.Unmarshal(c.rawBody, &mOrig)
mt = mOrig.MediaType mt = mOrig.MediaType
} }
m = &docker2Manifest{common: mc, Manifest: mOrig} m = &docker2Manifest{common: c, Manifest: mOrig}
case MediaTypeDocker2ManifestList: case MediaTypeDocker2ManifestList:
var mOrig dockerManifestList.ManifestList var mOrig dockerManifestList.ManifestList
if len(mc.rawBody) > 0 { if len(c.rawBody) > 0 {
err = json.Unmarshal(mc.rawBody, &mOrig) err = json.Unmarshal(c.rawBody, &mOrig)
mt = mOrig.MediaType mt = mOrig.MediaType
} }
m = &docker2ManifestList{common: mc, ManifestList: mOrig} m = &docker2ManifestList{common: c, ManifestList: mOrig}
case MediaTypeOCI1Manifest: case MediaTypeOCI1Manifest:
var mOrig ociv1.Manifest var mOrig ociv1.Manifest
if len(mc.rawBody) > 0 { if len(c.rawBody) > 0 {
err = json.Unmarshal(mc.rawBody, &mOrig) err = json.Unmarshal(c.rawBody, &mOrig)
mt = mOrig.MediaType mt = mOrig.MediaType
} }
m = &oci1Manifest{common: mc, Manifest: mOrig} m = &oci1Manifest{common: c, Manifest: mOrig}
case MediaTypeOCI1ManifestList: case MediaTypeOCI1ManifestList:
var mOrig ociv1.Index var mOrig ociv1.Index
if len(mc.rawBody) > 0 { if len(c.rawBody) > 0 {
err = json.Unmarshal(mc.rawBody, &mOrig) err = json.Unmarshal(c.rawBody, &mOrig)
mt = mOrig.MediaType mt = mOrig.MediaType
} }
m = &oci1Index{common: mc, Index: mOrig} m = &oci1Index{common: c, Index: mOrig}
default: default:
var mOrig UnknownData return nil, fmt.Errorf("%w: \"%s\"", types.ErrUnsupportedMediaType, c.desc.MediaType)
if len(mc.rawBody) > 0 {
err = json.Unmarshal(mc.rawBody, &mOrig)
}
m = &unknown{common: mc, UnknownData: mOrig}
} }
if err != nil { if err != nil {
return nil, fmt.Errorf("error unmarshaling manifest for %s: %w", mc.r.CommonName(), err) return nil, fmt.Errorf("error unmarshaling manifest for %s: %w", c.r.CommonName(), err)
} }
// verify media type // verify media type
err = verifyMT(mc.mt, mt) err = verifyMT(c.desc.MediaType, mt)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -251,7 +283,7 @@ func getPlatformDesc(p *ociv1.Platform, dl []ociv1.Descriptor) (*ociv1.Descripto
return &d, nil return &d, nil
} }
} }
return nil, wraperr.New(fmt.Errorf("Platform not found: %s", platforms.Format(*p)), ErrNotFound) return nil, wraperr.New(fmt.Errorf("Platform not found: %s", platforms.Format(*p)), types.ErrNotFound)
} }
func getPlatformList(dl []ociv1.Descriptor) ([]*ociv1.Platform, error) { func getPlatformList(dl []ociv1.Descriptor) ([]*ociv1.Platform, error) {

View File

@ -2,6 +2,7 @@ package manifest
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"net/http" "net/http"
"testing" "testing"
@ -10,6 +11,7 @@ import (
dockerSchema2 "github.com/docker/distribution/manifest/schema2" dockerSchema2 "github.com/docker/distribution/manifest/schema2"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
ociv1 "github.com/opencontainers/image-spec/specs-go/v1" ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/regclient/regclient/types"
"github.com/regclient/regclient/types/ref" "github.com/regclient/regclient/types/ref"
) )
@ -279,167 +281,19 @@ var (
`) `)
) )
var () func TestNew(t *testing.T) {
r, _ := ref.New("localhost:5000/test:latest")
func TestNewManifest(t *testing.T) { digestDockerSchema2 := digest.FromBytes(rawDockerSchema2)
digestML := digest.FromBytes(rawDockerSchema2List) digestML := digest.FromBytes(rawDockerSchema2List)
digestInvalid := digest.FromString("invalid") digestInvalid := digest.FromString("invalid")
r, _ := ref.New("localhost:5000/test:latest")
var tests = []struct {
name string
mt string
raw []byte
r ref.Ref
header http.Header
wantE error
}{
{
name: "Docker Schema 2 Manifest",
mt: MediaTypeDocker2Manifest,
raw: rawDockerSchema2,
r: r,
wantE: nil,
},
{
name: "Docker Schema 2 List from Http",
header: http.Header{
"Content-Type": []string{MediaTypeDocker2ManifestList},
"Docker-Content-Digest": []string{digestML.String()},
},
raw: rawDockerSchema2List,
r: r,
wantE: nil,
},
{
name: "Docker Schema 1 Signed",
mt: MediaTypeDocker1ManifestSigned,
raw: rawDockerSchema1Signed,
r: r,
wantE: nil,
},
{
name: "Invalid Http Digest",
header: http.Header{
"Content-Type": []string{MediaTypeDocker2ManifestList},
"Docker-Content-Digest": []string{digestInvalid.String()},
},
raw: rawDockerSchema2List,
r: r,
wantE: fmt.Errorf("digest mismatch, expected %s, found %s", digestInvalid, digestML),
},
{
name: "Ambiguous OCI Image",
mt: MediaTypeOCI1Manifest,
raw: rawAmbiguousOCI,
r: r,
wantE: nil,
},
{
name: "Ambiguous OCI Index",
mt: MediaTypeOCI1ManifestList,
raw: rawAmbiguousOCI,
r: r,
wantE: nil,
},
{
name: "Invalid OCI Index",
mt: MediaTypeOCI1ManifestList,
raw: rawOCIImage,
r: r,
wantE: fmt.Errorf("manifest contains an unexpected media type: expected %s, received %s", MediaTypeOCI1ManifestList, MediaTypeOCI1Manifest),
},
{
name: "Invalid OCI Image",
mt: MediaTypeOCI1Manifest,
raw: rawOCIIndex,
r: r,
wantE: fmt.Errorf("manifest contains an unexpected media type: expected %s, received %s", MediaTypeOCI1Manifest, MediaTypeOCI1ManifestList),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := New(tt.mt, tt.raw, tt.r, tt.header)
if tt.wantE == nil && err != nil {
t.Errorf("failed creating manifest, err: %v", err)
} else if tt.wantE != nil && (err == nil || (tt.wantE != err && tt.wantE.Error() != err.Error())) {
t.Errorf("expected error not received, expected %v, received %v", tt.wantE, err)
}
})
}
}
func TestFromDescriptor(t *testing.T) {
digestInvalid := digest.FromString("invalid")
digestDockerSchema2 := digest.FromBytes(rawDockerSchema2)
digestDockerSchema1Signed, err := digest.Parse("sha256:f3ef067962554c3352dc0c659ca563f73cc396fe0dea2a2c23a7964c6290f782") digestDockerSchema1Signed, err := digest.Parse("sha256:f3ef067962554c3352dc0c659ca563f73cc396fe0dea2a2c23a7964c6290f782")
if err != nil { if err != nil {
t.Fatalf("failed to parse docker schema1 signed digest string: %v", err) t.Fatalf("failed to parse docker schema1 signed digest string: %v", err)
} }
digestOCIImage := digest.FromBytes(rawOCIImage) digestOCIImage := digest.FromBytes(rawOCIImage)
var tests = []struct {
name string
desc ociv1.Descriptor
raw []byte
wantE error
}{
{
name: "Docker Schema 2 Manifest",
desc: ociv1.Descriptor{
MediaType: MediaTypeDocker2Manifest,
Digest: digestDockerSchema2,
Size: int64(len(rawDockerSchema2)),
},
raw: rawDockerSchema2,
wantE: nil,
},
{
name: "Docker Schema 1 Signed Manifest",
desc: ociv1.Descriptor{
MediaType: MediaTypeDocker1ManifestSigned,
Digest: digestDockerSchema1Signed,
Size: int64(len(rawDockerSchema1Signed)),
},
raw: rawDockerSchema1Signed,
wantE: nil,
},
{
name: "Invalid digest",
desc: ociv1.Descriptor{
MediaType: MediaTypeDocker2Manifest,
Digest: digestInvalid,
Size: int64(len(rawDockerSchema2)),
},
raw: rawDockerSchema2,
wantE: fmt.Errorf("digest mismatch, expected %s, found %s", digestInvalid, digestDockerSchema2),
},
{
name: "Invalid Media Type",
desc: ociv1.Descriptor{
MediaType: MediaTypeOCI1ManifestList,
Digest: digestOCIImage,
Size: int64(len(rawOCIImage)),
},
raw: rawOCIImage,
wantE: fmt.Errorf("manifest contains an unexpected media type: expected %s, received %s", MediaTypeOCI1ManifestList, MediaTypeOCI1Manifest),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := FromDescriptor(tt.desc, tt.raw)
if tt.wantE == nil && err != nil {
t.Errorf("failed creating manifest, err: %v", err)
} else if tt.wantE != nil && (err == nil || (tt.wantE != err && tt.wantE.Error() != err.Error())) {
t.Errorf("expected error not received, expected %v, received %v", tt.wantE, err)
}
})
}
}
func TestFromOrig(t *testing.T) {
var manifestDockerSchema2, manifestInvalid dockerSchema2.Manifest var manifestDockerSchema2, manifestInvalid dockerSchema2.Manifest
var manifestDockerSchema1Signed dockerSchema1.SignedManifest var manifestDockerSchema1Signed dockerSchema1.SignedManifest
err := json.Unmarshal(rawDockerSchema2, &manifestDockerSchema2) err = json.Unmarshal(rawDockerSchema2, &manifestDockerSchema2)
if err != nil { if err != nil {
t.Fatalf("failed to unmarshal docker schema2 json: %v", err) t.Fatalf("failed to unmarshal docker schema2 json: %v", err)
} }
@ -447,42 +301,228 @@ func TestFromOrig(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("failed to unmarshal docker schema2 json: %v", err) t.Fatalf("failed to unmarshal docker schema2 json: %v", err)
} }
manifestInvalid.MediaType = MediaTypeOCI1Manifest manifestInvalid.MediaType = types.MediaTypeOCI1Manifest
err = json.Unmarshal(rawDockerSchema1Signed, &manifestDockerSchema1Signed) err = json.Unmarshal(rawDockerSchema1Signed, &manifestDockerSchema1Signed)
if err != nil {
t.Fatalf("failed to unmarshal docker schema1 signed json: %v", err)
}
var tests = []struct { var tests = []struct {
name string name string
orig interface{} opts []Opts
wantE error wantR ref.Ref
wantDesc ociv1.Descriptor
wantE error
}{ }{
{ {
name: "Nil interface", name: "empty",
orig: nil, wantE: fmt.Errorf("%w: \"%s\"", types.ErrUnsupportedMediaType, ""),
wantE: fmt.Errorf("Unsupported type to convert to a manifest: %v", nil),
}, },
{ {
name: "Docker Schema2", name: "Docker Schema 2 Manifest",
orig: manifestDockerSchema2, opts: []Opts{
WithRef(r),
WithDesc(ociv1.Descriptor{
MediaType: types.MediaTypeDocker2Manifest,
}),
WithRaw(rawDockerSchema2),
},
wantR: r,
wantDesc: ociv1.Descriptor{
MediaType: types.MediaTypeDocker2Manifest,
Size: int64(len(rawDockerSchema2)),
Digest: digestDockerSchema2,
},
wantE: nil, wantE: nil,
}, },
{ {
name: "Docker Schema1 Signed", name: "Docker Schema 2 Manifest full desc",
orig: manifestDockerSchema1Signed, opts: []Opts{
WithDesc(ociv1.Descriptor{
MediaType: types.MediaTypeDocker2Manifest,
Digest: digestDockerSchema2,
Size: int64(len(rawDockerSchema2)),
}),
WithRaw(rawDockerSchema2),
},
wantDesc: ociv1.Descriptor{
MediaType: types.MediaTypeDocker2Manifest,
Size: int64(len(rawDockerSchema2)),
Digest: digestDockerSchema2,
},
wantE: nil, wantE: nil,
}, },
{ {
name: "Invalid Media Type", name: "Docker Schema 2 List from Http",
orig: manifestInvalid, opts: []Opts{
wantE: fmt.Errorf("manifest contains an unexpected media type: expected %s, received %s", MediaTypeDocker2Manifest, MediaTypeOCI1Manifest), WithRef(r),
WithRaw(rawDockerSchema2List),
WithHeader(http.Header{
"Content-Type": []string{MediaTypeDocker2ManifestList},
"Docker-Content-Digest": []string{digestML.String()},
}),
},
wantE: nil,
}, },
{
name: "Docker Schema 1 Signed",
opts: []Opts{
WithRef(r),
WithRaw(rawDockerSchema1Signed),
WithDesc(ociv1.Descriptor{
MediaType: types.MediaTypeDocker1ManifestSigned,
}),
},
wantE: nil,
},
{
name: "Docker Schema 1 Signed Manifest",
opts: []Opts{
WithRaw(rawDockerSchema1Signed),
WithDesc(ociv1.Descriptor{
MediaType: types.MediaTypeDocker1ManifestSigned,
Digest: digestDockerSchema1Signed,
Size: int64(len(rawDockerSchema1Signed)),
}),
},
wantE: nil,
},
{
name: "Invalid Http Digest",
opts: []Opts{
WithRef(r),
WithRaw(rawDockerSchema2List),
WithHeader(http.Header{
"Content-Type": []string{MediaTypeDocker2ManifestList},
"Docker-Content-Digest": []string{digestInvalid.String()},
}),
},
wantE: fmt.Errorf("digest mismatch, expected %s, found %s", digestInvalid, digestML),
},
{
name: "Ambiguous OCI Image",
opts: []Opts{
WithRef(r),
WithRaw(rawAmbiguousOCI),
WithDesc(ociv1.Descriptor{
MediaType: types.MediaTypeOCI1Manifest,
}),
},
wantE: nil,
},
{
name: "Ambiguous OCI Index",
opts: []Opts{
WithRef(r),
WithRaw(rawAmbiguousOCI),
WithDesc(ociv1.Descriptor{
MediaType: types.MediaTypeOCI1ManifestList,
}),
},
wantE: nil,
},
{
name: "Invalid OCI Index",
opts: []Opts{
WithRef(r),
WithRaw(rawOCIImage),
WithDesc(ociv1.Descriptor{
MediaType: types.MediaTypeOCI1ManifestList,
}),
},
wantE: fmt.Errorf("manifest contains an unexpected media type: expected %s, received %s", types.MediaTypeOCI1ManifestList, types.MediaTypeOCI1Manifest),
},
{
name: "Invalid OCI Image",
opts: []Opts{
WithRef(r),
WithRaw(rawOCIIndex),
WithDesc(ociv1.Descriptor{
MediaType: types.MediaTypeOCI1Manifest,
}),
},
wantE: fmt.Errorf("manifest contains an unexpected media type: expected %s, received %s", types.MediaTypeOCI1Manifest, types.MediaTypeOCI1ManifestList),
},
{
name: "Invalid digest",
opts: []Opts{
WithRef(r),
WithRaw(rawDockerSchema2),
WithDesc(ociv1.Descriptor{
MediaType: types.MediaTypeDocker2Manifest,
Digest: digestInvalid,
Size: int64(len(rawDockerSchema2)),
}),
},
wantE: fmt.Errorf("digest mismatch, expected %s, found %s", digestInvalid, digestDockerSchema2),
},
{
name: "Invalid Media Type",
opts: []Opts{
WithRef(r),
WithRaw(rawOCIImage),
WithDesc(ociv1.Descriptor{
MediaType: types.MediaTypeOCI1ManifestList,
Digest: digestOCIImage,
Size: int64(len(rawOCIImage)),
}),
},
wantE: fmt.Errorf("manifest contains an unexpected media type: expected %s, received %s", types.MediaTypeOCI1ManifestList, types.MediaTypeOCI1Manifest),
},
{
name: "Docker Schema2 Orig",
opts: []Opts{
WithOrig(manifestDockerSchema2),
},
wantE: nil,
},
{
name: "Docker Schema1 Signed Orig",
opts: []Opts{
WithOrig(manifestDockerSchema1Signed),
},
wantE: nil,
},
{
name: "Invalid Media Type",
opts: []Opts{
WithOrig(manifestInvalid),
},
wantE: fmt.Errorf("manifest contains an unexpected media type: expected %s, received %s", types.MediaTypeDocker2Manifest, types.MediaTypeOCI1Manifest),
},
// TODO: add more tests to improve coverage
// - test rate limit
// - test retrieving descriptor lists from manifest lists
// - test retrieving layers from images
// - test retrieving config descriptor from image
// - test if manifest is set
// - test raw body
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
_, err := FromOrig(tt.orig) m, err := New(tt.opts...)
if tt.wantE == nil && err != nil { if tt.wantE != nil {
t.Errorf("failed creating manifest, err: %v", err) if err == nil {
} else if tt.wantE != nil && (err == nil || (tt.wantE != err && tt.wantE.Error() != err.Error())) { t.Errorf("did not receive expected error %v", tt.wantE)
t.Errorf("expected error not received, expected %v, received %v", tt.wantE, err) } else if !errors.Is(err, tt.wantE) && err.Error() != tt.wantE.Error() {
t.Errorf("expected error not received, expected %v, received %v", tt.wantE, err)
}
return
} }
if err != nil {
t.Errorf("failed running New: %v", err)
return
}
if tt.wantR.Scheme != "" && m.GetRef().CommonName() != tt.wantR.CommonName() {
t.Errorf("ref mismatch, expected %s, received %s", tt.wantR.CommonName(), m.GetRef().CommonName())
}
if tt.wantDesc.Digest != "" && m.GetDigest() != tt.wantDesc.Digest {
t.Errorf("digest mismatch, expected %s, received %s", tt.wantDesc.Digest, m.GetDigest())
}
if tt.wantDesc.MediaType != "" && m.GetMediaType() != tt.wantDesc.MediaType {
t.Errorf("media type mismatch, expected %s, received %s", tt.wantDesc.MediaType, m.GetMediaType())
}
}) })
} }
} }

View File

@ -11,6 +11,7 @@ import (
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
ociv1 "github.com/opencontainers/image-spec/specs-go/v1" ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/regclient/regclient/internal/wraperr" "github.com/regclient/regclient/internal/wraperr"
"github.com/regclient/regclient/types"
) )
const ( const (
@ -36,14 +37,14 @@ func (m *oci1Manifest) GetConfigDigest() (digest.Digest, error) {
return m.Config.Digest, nil return m.Config.Digest, nil
} }
func (m *oci1Index) GetConfigDescriptor() (ociv1.Descriptor, error) { func (m *oci1Index) GetConfigDescriptor() (ociv1.Descriptor, error) {
return ociv1.Descriptor{}, wraperr.New(fmt.Errorf("Config digest not available for media type %s", m.mt), ErrUnsupportedMediaType) return ociv1.Descriptor{}, wraperr.New(fmt.Errorf("Config digest not available for media type %s", m.desc.MediaType), types.ErrUnsupportedMediaType)
} }
func (m *oci1Index) GetConfigDigest() (digest.Digest, error) { func (m *oci1Index) GetConfigDigest() (digest.Digest, error) {
return "", wraperr.New(fmt.Errorf("Config digest not available for media type %s", m.mt), ErrUnsupportedMediaType) return "", wraperr.New(fmt.Errorf("Config digest not available for media type %s", m.desc.MediaType), types.ErrUnsupportedMediaType)
} }
func (m *oci1Manifest) GetDescriptorList() ([]ociv1.Descriptor, error) { func (m *oci1Manifest) GetDescriptorList() ([]ociv1.Descriptor, error) {
return []ociv1.Descriptor{}, wraperr.New(fmt.Errorf("Platform descriptor list not available for media type %s", m.mt), ErrUnsupportedMediaType) return []ociv1.Descriptor{}, wraperr.New(fmt.Errorf("Platform descriptor list not available for media type %s", m.desc.MediaType), types.ErrUnsupportedMediaType)
} }
func (m *oci1Index) GetDescriptorList() ([]ociv1.Descriptor, error) { func (m *oci1Index) GetDescriptorList() ([]ociv1.Descriptor, error) {
return m.Manifests, nil return m.Manifests, nil
@ -53,7 +54,7 @@ func (m *oci1Manifest) GetLayers() ([]ociv1.Descriptor, error) {
return m.Layers, nil return m.Layers, nil
} }
func (m *oci1Index) GetLayers() ([]ociv1.Descriptor, error) { func (m *oci1Index) GetLayers() ([]ociv1.Descriptor, error) {
return []ociv1.Descriptor{}, wraperr.New(fmt.Errorf("Layers are not available for media type %s", m.mt), ErrUnsupportedMediaType) return []ociv1.Descriptor{}, wraperr.New(fmt.Errorf("Layers are not available for media type %s", m.desc.MediaType), types.ErrUnsupportedMediaType)
} }
func (m *oci1Manifest) GetOrigManifest() interface{} { func (m *oci1Manifest) GetOrigManifest() interface{} {
@ -64,7 +65,7 @@ func (m *oci1Index) GetOrigManifest() interface{} {
} }
func (m *oci1Manifest) GetPlatformDesc(p *ociv1.Platform) (*ociv1.Descriptor, error) { func (m *oci1Manifest) GetPlatformDesc(p *ociv1.Platform) (*ociv1.Descriptor, error) {
return nil, wraperr.New(fmt.Errorf("Platform lookup not available for media type %s", m.mt), ErrUnsupportedMediaType) return nil, wraperr.New(fmt.Errorf("Platform lookup not available for media type %s", m.desc.MediaType), types.ErrUnsupportedMediaType)
} }
func (m *oci1Index) GetPlatformDesc(p *ociv1.Platform) (*ociv1.Descriptor, error) { func (m *oci1Index) GetPlatformDesc(p *ociv1.Platform) (*ociv1.Descriptor, error) {
dl, err := m.GetDescriptorList() dl, err := m.GetDescriptorList()
@ -75,7 +76,7 @@ func (m *oci1Index) GetPlatformDesc(p *ociv1.Platform) (*ociv1.Descriptor, error
} }
func (m *oci1Manifest) GetPlatformList() ([]*ociv1.Platform, error) { func (m *oci1Manifest) GetPlatformList() ([]*ociv1.Platform, error) {
return nil, wraperr.New(fmt.Errorf("Platform list not available for media type %s", m.mt), ErrUnsupportedMediaType) return nil, wraperr.New(fmt.Errorf("Platform list not available for media type %s", m.desc.MediaType), types.ErrUnsupportedMediaType)
} }
func (m *oci1Index) GetPlatformList() ([]*ociv1.Platform, error) { func (m *oci1Index) GetPlatformList() ([]*ociv1.Platform, error) {
dl, err := m.GetDescriptorList() dl, err := m.GetDescriptorList()
@ -87,7 +88,7 @@ func (m *oci1Index) GetPlatformList() ([]*ociv1.Platform, error) {
func (m *oci1Manifest) MarshalJSON() ([]byte, error) { func (m *oci1Manifest) MarshalJSON() ([]byte, error) {
if !m.manifSet { if !m.manifSet {
return []byte{}, wraperr.New(fmt.Errorf("Manifest unavailable, perform a ManifestGet first"), ErrUnavailable) return []byte{}, wraperr.New(fmt.Errorf("Manifest unavailable, perform a ManifestGet first"), types.ErrUnavailable)
} }
if len(m.rawBody) > 0 { if len(m.rawBody) > 0 {
@ -98,7 +99,7 @@ func (m *oci1Manifest) MarshalJSON() ([]byte, error) {
} }
func (m *oci1Index) MarshalJSON() ([]byte, error) { func (m *oci1Index) MarshalJSON() ([]byte, error) {
if !m.manifSet { if !m.manifSet {
return []byte{}, wraperr.New(fmt.Errorf("Manifest unavailable, perform a ManifestGet first"), ErrUnavailable) return []byte{}, wraperr.New(fmt.Errorf("Manifest unavailable, perform a ManifestGet first"), types.ErrUnavailable)
} }
if len(m.rawBody) > 0 { if len(m.rawBody) > 0 {
@ -125,8 +126,8 @@ func (m *oci1Index) MarshalPretty() ([]byte, error) {
if m.r.Reference != "" { if m.r.Reference != "" {
fmt.Fprintf(tw, "Name:\t%s\n", m.r.Reference) fmt.Fprintf(tw, "Name:\t%s\n", m.r.Reference)
} }
fmt.Fprintf(tw, "MediaType:\t%s\n", m.mt) fmt.Fprintf(tw, "MediaType:\t%s\n", m.desc.MediaType)
fmt.Fprintf(tw, "Digest:\t%s\n", m.digest.String()) fmt.Fprintf(tw, "Digest:\t%s\n", m.desc.Digest.String())
fmt.Fprintf(tw, "\t\n") fmt.Fprintf(tw, "\t\n")
fmt.Fprintf(tw, "Manifests:\t\n") fmt.Fprintf(tw, "Manifests:\t\n")
for _, d := range m.Manifests { for _, d := range m.Manifests {

View File

@ -1,50 +0,0 @@
package manifest
import (
"fmt"
digest "github.com/opencontainers/go-digest"
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/regclient/regclient/internal/wraperr"
)
type unknown struct {
common
UnknownData
}
type UnknownData struct {
Data map[string]interface{}
}
func (m *unknown) GetConfigDescriptor() (ociv1.Descriptor, error) {
return ociv1.Descriptor{}, wraperr.New(fmt.Errorf("Config digest not available for media type %s", m.mt), ErrUnsupportedMediaType)
}
func (m *unknown) GetConfigDigest() (digest.Digest, error) {
return "", wraperr.New(fmt.Errorf("Config digest not available for media type %s", m.mt), ErrUnsupportedMediaType)
}
func (m *unknown) GetDescriptorList() ([]ociv1.Descriptor, error) {
return []ociv1.Descriptor{}, wraperr.New(fmt.Errorf("Platform descriptor list not available for media type %s", m.mt), ErrUnsupportedMediaType)
}
func (m *unknown) GetLayers() ([]ociv1.Descriptor, error) {
return []ociv1.Descriptor{}, wraperr.New(fmt.Errorf("Layer list not available for media type %s", m.mt), ErrUnsupportedMediaType)
}
func (m *unknown) GetOrigManifest() interface{} {
return m.UnknownData
}
func (m *unknown) GetPlatformDesc(p *ociv1.Platform) (*ociv1.Descriptor, error) {
return nil, wraperr.New(fmt.Errorf("Platform lookup not available for media type %s", m.mt), ErrUnsupportedMediaType)
}
func (m *unknown) GetPlatformList() ([]*ociv1.Platform, error) {
return nil, wraperr.New(fmt.Errorf("Platform list not available for media type %s", m.mt), ErrUnsupportedMediaType)
}
func (m *unknown) MarshalJSON() ([]byte, error) {
return m.rawBody, nil
}

View File

@ -43,6 +43,7 @@ type tagConfig struct {
mt string mt string
raw []byte raw []byte
header http.Header header http.Header
tags []string
} }
type Opts func(*tagConfig) type Opts func(*tagConfig)
@ -61,6 +62,9 @@ func New(opts ...Opts) (*TagList, error) {
rawHeader: conf.header, rawHeader: conf.header,
rawBody: conf.raw, rawBody: conf.raw,
} }
if len(conf.tags) > 0 {
tl.Tags = conf.tags
}
mt := strings.Split(conf.mt, ";")[0] // "application/json; charset=utf-8" -> "application/json" mt := strings.Split(conf.mt, ";")[0] // "application/json; charset=utf-8" -> "application/json"
switch mt { switch mt {
case "application/json", "text/plain": case "application/json", "text/plain":
@ -68,6 +72,8 @@ func New(opts ...Opts) (*TagList, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
case types.MediaTypeOCI1ManifestList:
// noop
default: default:
return nil, fmt.Errorf("%w: media type: %s, reference: %s", types.ErrUnsupportedMediaType, conf.mt, conf.ref.CommonName()) return nil, fmt.Errorf("%w: media type: %s, reference: %s", types.ErrUnsupportedMediaType, conf.mt, conf.ref.CommonName())
} }
@ -96,6 +102,11 @@ func WithRef(ref ref.Ref) Opts {
tConf.ref = ref tConf.ref = ref
} }
} }
func WithTags(tags []string) Opts {
return func(tConf *tagConfig) {
tConf.tags = tags
}
}
func (t tagCommon) GetOrig() interface{} { func (t tagCommon) GetOrig() interface{} {
return t.orig return t.orig