1
0
mirror of https://github.com/regclient/regclient.git synced 2025-07-30 20:03:04 +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),
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
t.Run("Get", func(t *testing.T) {
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)
}
// save image to a new config
bc := blob.NewOCIConfig(ociImage)
bc := blob.NewOCIConfig(
blob.WithRef(origC.r),
blob.WithImage(ociImage),
)
newC = &config{
conf: bc,
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)
}
// 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)
if err != nil {
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(),
}).Debug("Put manifest")
m, err := manifest.FromOrig(sbm.m.GetOrigManifest())
m, err := manifest.New(manifest.WithOrig(sbm.m.GetOrigManifest()))
if err != nil {
ls.RaiseError("Failed to put manifest: %v", err)
}

View File

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

View File

@ -138,7 +138,7 @@ func getPlatformDesc(rc *regclient.RegClient, m manifest.Manifest) (*ociv1.Descr
if !m.IsSet() {
m, err = rc.ManifestGet(context.Background(), m.GetRef())
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
for m.IsList() && !manifestOpts.list && !manifestOpts.requireList {
desc, err := getPlatformDesc(rc, m)
if err != nil {
return fmt.Errorf("Failed retrieving platform specific digest: %w", err)
}
r.Digest = desc.Digest.String()
m, err = rc.ManifestHead(context.Background(), r)
if err != nil {
@ -283,7 +286,13 @@ func runManifestPut(cmd *cobra.Command, args []string) error {
if err != nil {
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 {
return err
}

View File

@ -196,7 +196,7 @@ func (rc *RegClient) imageCopyOpt(ctx context.Context, refSrc ref.Ref, refTgt re
cd, err := m.GetConfigDigest()
if err != nil {
// 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{
"ref": refSrc.Reference,
"err": err,
@ -449,7 +449,7 @@ func (rc *RegClient) imageExportDescriptor(ctx context.Context, ref ref.Ref, des
// add config
confD, err := m.GetConfigDescriptor()
// ignore unsupported media type errors
if err != nil && !errors.Is(err, manifest.ErrUnsupportedMediaType) {
if err != nil && !errors.Is(err, types.ErrUnsupportedMediaType) {
return err
}
if err == nil {
@ -462,7 +462,7 @@ func (rc *RegClient) imageExportDescriptor(ctx context.Context, ref ref.Ref, des
// loop over layers
layerDL, err := m.GetLayers()
// ignore unsupported media type errors
if err != nil && !errors.Is(err, manifest.ErrUnsupportedMediaType) {
if err != nil && !errors.Is(err, types.ErrUnsupportedMediaType) {
return err
}
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)
}
// push docker manifest
m, err := manifest.FromOrig(trd.dockerManifest)
m, err := manifest.New(manifest.WithOrig(trd.dockerManifest))
if err != nil {
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
delete(trd.handlers, dockerManifestFilename)
// 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 {
return err
}
@ -745,7 +745,7 @@ func (rc *RegClient) imageImportOCIHandleManifest(ctx context.Context, ref ref.R
types.MediaTypeDocker2Manifest, types.MediaTypeDocker2ManifestList,
types.MediaTypeOCI1Manifest, types.MediaTypeOCI1ManifestList:
// known manifest media types
md, err := manifest.FromDescriptor(d, b)
md, err := manifest.New(manifest.WithDesc(d), manifest.WithRaw(b))
if err != nil {
return err
}
@ -757,7 +757,7 @@ func (rc *RegClient) imageImportOCIHandleManifest(ctx context.Context, ref ref.R
return rc.imageImportBlob(ctx, ref, d, trd)
default:
// 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 {
return rc.imageImportOCIHandleManifest(ctx, ref, md, trd, true)
}

View File

@ -10,6 +10,7 @@ import (
"net/url"
"os"
"testing"
"time"
"github.com/docker/distribution"
dockerSchema2 "github.com/docker/distribution/manifest/schema2"
@ -142,7 +143,13 @@ func TestManifest(t *testing.T) {
Hooks: make(logrus.LevelHooks),
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) {
getRef, err := ref.New(tsURL.Host + repoPath + ":" + getTag)
if err != nil {

View File

@ -6,8 +6,12 @@
package manifest
import (
"net/http"
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
topTypes "github.com/regclient/regclient/types"
topManifest "github.com/regclient/regclient/types/manifest"
"github.com/regclient/regclient/types/ref"
)
const (
@ -28,12 +32,30 @@ const (
type Manifest = topManifest.Manifest
var (
New = topManifest.New
FromDescriptor = topManifest.FromDescriptor
FromOrig = topManifest.FromOrig
ErrNotFound = topTypes.ErrNotFound
ErrNotImplemented = topTypes.ErrNotImplemented
ErrUnavailable = topTypes.ErrUnavailable
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"
"github.com/opencontainers/go-digest"
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/regclient/regclient/internal/reghttp"
"github.com/regclient/regclient/types"
"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))
}
b := blob.NewReader(resp)
b.SetMeta(r, d, 0)
b.SetResp(resp.HTTPResponse())
b := blob.NewReader(
blob.WithRef(r),
blob.WithReadCloser(resp),
blob.WithDesc(ociv1.Descriptor{
Digest: d,
}),
blob.WithResp(resp.HTTPResponse()),
)
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))
}
b := blob.NewReader(nil)
b.SetMeta(r, d, 0)
b.SetResp(resp.HTTPResponse())
b := blob.NewReader(
blob.WithRef(r),
blob.WithDesc(ociv1.Descriptor{
Digest: d,
}),
blob.WithResp(resp.HTTPResponse()),
)
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)
}
// parse body into variable according to media type
mt := resp.HTTPResponse().Header.Get("Content-Type")
return manifest.New(mt, rawBody, r, resp.HTTPResponse().Header)
return manifest.New(
manifest.WithRef(r),
manifest.WithHeader(resp.HTTPResponse().Header),
manifest.WithRaw(rawBody),
)
}
// 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))
}
// extract header data
mt := resp.HTTPResponse().Header.Get("Content-Type")
return manifest.New(mt, []byte{}, r, resp.HTTPResponse().Header)
return manifest.New(
manifest.WithRef(r),
manifest.WithHeader(resp.HTTPResponse().Header),
)
}
// 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
switch curManifest.GetMediaType() {
case types.MediaTypeOCI1Manifest, types.MediaTypeOCI1ManifestList:
tempManifest, err = manifest.FromOrig(ociv1.Manifest{
tempManifest, err = manifest.New(manifest.WithOrig(ociv1.Manifest{
Versioned: ociv1Specs.Versioned{
SchemaVersion: 2,
},
@ -117,12 +117,12 @@ func (reg *Reg) TagDelete(ctx context.Context, r ref.Ref) error {
Size: int64(len(confB)),
},
Layers: []ociv1.Descriptor{},
})
}))
if err != nil {
return err
}
default: // default to the docker v2 schema
tempManifest, err = manifest.FromOrig(dockerSchema2.Manifest{
tempManifest, err = manifest.New(manifest.WithOrig(dockerSchema2.Manifest{
Versioned: dockerManifest.Versioned{
SchemaVersion: 2,
MediaType: types.MediaTypeDocker2Manifest,
@ -133,7 +133,7 @@ func (reg *Reg) TagDelete(ctx context.Context, r ref.Ref) error {
Size: int64(len(confB)),
},
Layers: []dockerDistribution.Descriptor{},
})
}))
if err != nil {
return err
}

View File

@ -1,7 +1,61 @@
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
type Blob interface {
Common
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 (
"net/http"
"strconv"
"github.com/opencontainers/go-digest"
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/regclient/regclient/types/ref"
)
@ -15,15 +15,11 @@ type Common interface {
MediaType() string
Response() *http.Response
RawHeaders() http.Header
SetMeta(r ref.Ref, d digest.Digest, cl int64)
SetResp(resp *http.Response)
}
type common struct {
r ref.Ref
digest digest.Digest
cl int64
mt string
desc ociv1.Descriptor
blobSet bool
rawHeader http.Header
resp *http.Response
@ -31,17 +27,17 @@ type common struct {
// Digest returns the provided or calculated digest of the blob
func (b *common) Digest() digest.Digest {
return b.digest
return b.desc.Digest
}
// Length returns the provided or calculated length of the blob
func (b *common) Length() int64 {
return b.cl
return b.desc.Size
}
// MediaType returns the Content-Type header received from the registry
func (b *common) MediaType() string {
return b.mt
return b.desc.MediaType
}
// RawHeaders returns the headers received from the registry
@ -53,22 +49,3 @@ func (b *common) RawHeaders() http.Header {
func (b *common) Response() *http.Response {
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
func NewOCIConfig(ociImage ociv1.Image) OCIConfig {
bc := common{
func NewOCIConfig(opts ...Opts) OCIConfig {
bc := BlobConfig{}
for _, opt := range opts {
opt(&bc)
}
c := common{
blobSet: true,
desc: bc.desc,
r: bc.r,
rawHeader: bc.header,
resp: bc.resp,
}
b := ociConfig{
common: bc,
Image: ociImage,
common: c,
Image: bc.image,
}
return &b
}

View File

@ -5,10 +5,10 @@ import (
"fmt"
"io"
"io/ioutil"
"strconv"
"github.com/opencontainers/go-digest"
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
@ -25,24 +25,41 @@ type reader struct {
reader io.Reader
origRdr io.ReadCloser
digester digest.Digester
// io.ReadCloser
}
// NewReader creates a new reader
func NewReader(rdr io.ReadCloser) Reader {
digester := digest.Canonical.Digester()
digestRdr := io.TeeReader(rdr, digester.Hash())
bc := common{
r: ref.Ref{},
func NewReader(opts ...Opts) Reader {
bc := BlobConfig{}
for _, opt := range opts {
opt(&bc)
}
if rdr != nil {
bc.blobSet = true
if bc.resp != nil {
// 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{
common: bc,
reader: digestRdr,
origRdr: rdr,
digester: digester,
common: c,
origRdr: bc.rc,
}
if bc.rc != nil {
br.blobSet = true
br.digester = digest.Canonical.Digester()
br.reader = io.TeeReader(bc.rc, br.digester.Hash())
}
return &br
}
@ -65,16 +82,16 @@ func (b *reader) Read(p []byte) (int, error) {
b.readBytes = b.readBytes + int64(size)
if err == io.EOF {
// check/save size
if b.cl == 0 {
b.cl = b.readBytes
} else if b.readBytes != b.cl {
err = fmt.Errorf("Expected size mismatch [expected %d, received %d]: %w", b.cl, b.readBytes, err)
if b.desc.Size == 0 {
b.desc.Size = b.readBytes
} else if b.readBytes != b.desc.Size {
err = fmt.Errorf("Expected size mismatch [expected %d, received %d]: %w", b.desc.Size, b.readBytes, err)
}
// check/save digest
if b.digest == "" {
b.digest = b.digester.Digest()
} else if b.digest != b.digester.Digest() {
err = fmt.Errorf("Expected digest mismatch [expected %s, calculated %s]: %w", b.digest.String(), b.digester.Digest().String(), err)
if b.desc.Digest == "" {
b.desc.Digest = b.digester.Digest()
} else if b.desc.Digest != b.digester.Digest() {
err = fmt.Errorf("Expected digest mismatch [expected %s, calculated %s]: %w", b.desc.Digest.String(), b.digester.Digest().String(), err)
}
}
return size, err

View File

@ -6,14 +6,14 @@ import (
"strings"
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/ref"
)
type common struct {
r ref.Ref
digest digest.Digest
mt string
desc ociv1.Descriptor
manifSet bool
ratelimit types.RateLimit
rawHeader http.Header
@ -22,12 +22,12 @@ type common struct {
// GetDigest returns the digest
func (m *common) GetDigest() digest.Digest {
return m.digest
return m.desc.Digest
}
// GetMediaType returns the media type
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.
@ -48,7 +48,7 @@ func (m *common) HasRateLimit() bool {
// IsList indicates if the manifest is a docker Manifest List or OCI Index
func (m *common) IsList() bool {
switch m.mt {
switch m.desc.MediaType {
case MediaTypeDocker2ManifestList, MediaTypeOCI1ManifestList:
return true
default:
@ -65,7 +65,7 @@ func (m *common) IsSet() bool {
// RawBody returns the raw body from the manifest if available.
func (m *common) RawBody() ([]byte, error) {
if len(m.rawBody) == 0 {
return m.rawBody, ErrUnavailable
return m.rawBody, types.ErrUnavailable
}
return m.rawBody, nil
}

View File

@ -9,6 +9,7 @@ import (
digest "github.com/opencontainers/go-digest"
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/regclient/regclient/internal/wraperr"
"github.com/regclient/regclient/types"
)
const (
@ -28,23 +29,23 @@ type docker1SignedManifest struct {
}
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) {
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) {
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) {
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) {
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) {
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) {
@ -74,22 +75,22 @@ func (m *docker1SignedManifest) GetOrigManifest() interface{} {
}
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) {
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) {
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) {
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) {
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 {

View File

@ -13,6 +13,7 @@ import (
digest "github.com/opencontainers/go-digest"
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/regclient/regclient/internal/wraperr"
"github.com/regclient/regclient/types"
)
const (
@ -45,14 +46,14 @@ func (m *docker2Manifest) GetConfigDigest() (digest.Digest, error) {
return m.Config.Digest, nil
}
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) {
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) {
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) {
dl := []ociv1.Descriptor{}
@ -70,7 +71,7 @@ func (m *docker2Manifest) GetLayers() ([]ociv1.Descriptor, error) {
return dl, nil
}
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{} {
@ -81,7 +82,7 @@ func (m *docker2ManifestList) GetOrigManifest() interface{} {
}
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) {
dl, err := m.GetDescriptorList()
@ -92,7 +93,7 @@ func (m *docker2ManifestList) GetPlatformDesc(p *ociv1.Platform) (*ociv1.Descrip
}
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) {
dl, err := m.GetDescriptorList()
@ -104,7 +105,7 @@ func (m *docker2ManifestList) GetPlatformList() ([]*ociv1.Platform, error) {
func (m *docker2Manifest) MarshalJSON() ([]byte, error) {
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 {
@ -115,7 +116,7 @@ func (m *docker2Manifest) MarshalJSON() ([]byte, error) {
}
func (m *docker2ManifestList) MarshalJSON() ([]byte, error) {
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 {
@ -142,8 +143,8 @@ func (m *docker2ManifestList) MarshalPretty() ([]byte, error) {
if 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, "Digest:\t%s\n", m.digest.String())
fmt.Fprintf(tw, "MediaType:\t%s\n", m.desc.MediaType)
fmt.Fprintf(tw, "Digest:\t%s\n", m.desc.Digest.String())
fmt.Fprintf(tw, "\t\n")
fmt.Fprintf(tw, "Manifests:\t\n")
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"
"fmt"
"net/http"
"strconv"
"github.com/containerd/containerd/platforms"
dockerDistribution "github.com/docker/distribution"
@ -38,42 +39,75 @@ type Manifest interface {
RawHeaders() (http.Header, error)
}
// New creates a new manifest from an unparsed raw manifest
// mediaType: should be a known media-type. If empty, resp headers will be checked
// raw: body of the manifest. If empty, unset manifest for a HEAD request is returned
// ref: reference, may be unset
// header: headers from request, used to extract content type, digest, and rate limits
func New(mediaType string, raw []byte, r ref.Ref, header http.Header) (Manifest, error) {
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 ManifestConfig struct {
r ref.Ref
desc ociv1.Descriptor
raw []byte
orig interface{}
header http.Header
}
type Opts func(*ManifestConfig)
// FromDescriptor creates a new manifest from a descriptor and the raw manifest bytes.
func FromDescriptor(desc ociv1.Descriptor, mBytes []byte) (Manifest, error) {
mc := common{
digest: desc.Digest,
mt: desc.MediaType,
manifSet: true,
rawBody: mBytes,
// New creates a new manifest based on provided options
func New(opts ...Opts) (Manifest, error) {
mc := ManifestConfig{}
for _, opt := range opts {
opt(&mc)
}
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.
// 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 m Manifest
@ -81,156 +115,154 @@ func FromOrig(orig interface{}) (Manifest, error) {
if err != nil {
return nil, err
}
mc := common{
digest: digest.FromBytes(mj),
rawBody: mj,
manifSet: true,
c.manifSet = true
if len(c.rawBody) == 0 {
c.rawBody = mj
}
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
switch orig.(type) {
case dockerSchema1.Manifest:
mOrig := orig.(dockerSchema1.Manifest)
mt = mOrig.MediaType
mc.mt = MediaTypeDocker1Manifest
c.desc.MediaType = types.MediaTypeDocker1Manifest
m = &docker1Manifest{
common: mc,
common: c,
Manifest: mOrig,
}
case dockerSchema1.SignedManifest:
mOrig := orig.(dockerSchema1.SignedManifest)
mt = mOrig.MediaType
c.desc.MediaType = types.MediaTypeDocker1ManifestSigned
// recompute digest on the canonical data
mc.digest = digest.FromBytes(mOrig.Canonical)
mc.mt = MediaTypeDocker1ManifestSigned
if c.desc.Digest == "" {
c.desc.Digest = digest.FromBytes(mOrig.Canonical)
}
m = &docker1SignedManifest{
common: mc,
common: c,
SignedManifest: mOrig,
}
case dockerSchema2.Manifest:
mOrig := orig.(dockerSchema2.Manifest)
mt = mOrig.MediaType
mc.mt = MediaTypeDocker2Manifest
c.desc.MediaType = types.MediaTypeDocker2Manifest
m = &docker2Manifest{
common: mc,
common: c,
Manifest: mOrig,
}
case dockerManifestList.ManifestList:
mOrig := orig.(dockerManifestList.ManifestList)
mt = mOrig.MediaType
mc.mt = MediaTypeDocker2ManifestList
c.desc.MediaType = types.MediaTypeDocker2ManifestList
m = &docker2ManifestList{
common: mc,
common: c,
ManifestList: mOrig,
}
case ociv1.Manifest:
mOrig := orig.(ociv1.Manifest)
mt = mOrig.MediaType
mc.mt = MediaTypeOCI1Manifest
c.desc.MediaType = types.MediaTypeOCI1Manifest
m = &oci1Manifest{
common: mc,
common: c,
Manifest: mOrig,
}
case ociv1.Index:
mOrig := orig.(ociv1.Index)
mt = mOrig.MediaType
mc.mt = MediaTypeOCI1ManifestList
c.desc.MediaType = types.MediaTypeOCI1ManifestList
m = &oci1Index{
common: mc,
common: c,
Index: orig.(ociv1.Index),
}
case UnknownData:
m = &unknown{
common: mc,
UnknownData: orig.(UnknownData),
}
default:
return nil, fmt.Errorf("Unsupported type to convert to a manifest: %T", orig)
}
// verify media type
err = verifyMT(mc.mt, mt)
err = verifyMT(c.desc.MediaType, mt)
if err != nil {
return nil, err
}
return m, nil
}
func fromCommon(mc common) (Manifest, error) {
func fromCommon(c common) (Manifest, error) {
var err error
var m Manifest
var mt string
// compute/verify digest
if len(mc.rawBody) > 0 {
mc.manifSet = true
if mc.mt != MediaTypeDocker1ManifestSigned {
d := digest.FromBytes(mc.rawBody)
if mc.digest == "" {
mc.digest = d
} else if mc.digest != d {
return nil, fmt.Errorf("digest mismatch, expected %s, found %s", mc.digest.String(), d.String())
if len(c.rawBody) > 0 {
c.manifSet = true
if c.desc.MediaType != MediaTypeDocker1ManifestSigned {
d := digest.FromBytes(c.rawBody)
if c.desc.Digest == "" {
c.desc.Digest = d
} else if c.desc.Digest != d {
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:
var mOrig dockerSchema1.Manifest
if len(mc.rawBody) > 0 {
err = json.Unmarshal(mc.rawBody, &mOrig)
if len(c.rawBody) > 0 {
err = json.Unmarshal(c.rawBody, &mOrig)
mt = mOrig.MediaType
}
m = &docker1Manifest{common: mc, Manifest: mOrig}
m = &docker1Manifest{common: c, Manifest: mOrig}
case MediaTypeDocker1ManifestSigned:
var mOrig dockerSchema1.SignedManifest
if len(mc.rawBody) > 0 {
err = json.Unmarshal(mc.rawBody, &mOrig)
if len(c.rawBody) > 0 {
err = json.Unmarshal(c.rawBody, &mOrig)
mt = mOrig.MediaType
d := digest.FromBytes(mOrig.Canonical)
if mc.digest == "" {
mc.digest = d
} else if mc.digest != d {
return nil, fmt.Errorf("digest mismatch, expected %s, found %s", mc.digest.String(), d.String())
if c.desc.Digest == "" {
c.desc.Digest = d
} else if c.desc.Digest != d {
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:
var mOrig dockerSchema2.Manifest
if len(mc.rawBody) > 0 {
err = json.Unmarshal(mc.rawBody, &mOrig)
if len(c.rawBody) > 0 {
err = json.Unmarshal(c.rawBody, &mOrig)
mt = mOrig.MediaType
}
m = &docker2Manifest{common: mc, Manifest: mOrig}
m = &docker2Manifest{common: c, Manifest: mOrig}
case MediaTypeDocker2ManifestList:
var mOrig dockerManifestList.ManifestList
if len(mc.rawBody) > 0 {
err = json.Unmarshal(mc.rawBody, &mOrig)
if len(c.rawBody) > 0 {
err = json.Unmarshal(c.rawBody, &mOrig)
mt = mOrig.MediaType
}
m = &docker2ManifestList{common: mc, ManifestList: mOrig}
m = &docker2ManifestList{common: c, ManifestList: mOrig}
case MediaTypeOCI1Manifest:
var mOrig ociv1.Manifest
if len(mc.rawBody) > 0 {
err = json.Unmarshal(mc.rawBody, &mOrig)
if len(c.rawBody) > 0 {
err = json.Unmarshal(c.rawBody, &mOrig)
mt = mOrig.MediaType
}
m = &oci1Manifest{common: mc, Manifest: mOrig}
m = &oci1Manifest{common: c, Manifest: mOrig}
case MediaTypeOCI1ManifestList:
var mOrig ociv1.Index
if len(mc.rawBody) > 0 {
err = json.Unmarshal(mc.rawBody, &mOrig)
if len(c.rawBody) > 0 {
err = json.Unmarshal(c.rawBody, &mOrig)
mt = mOrig.MediaType
}
m = &oci1Index{common: mc, Index: mOrig}
m = &oci1Index{common: c, Index: mOrig}
default:
var mOrig UnknownData
if len(mc.rawBody) > 0 {
err = json.Unmarshal(mc.rawBody, &mOrig)
}
m = &unknown{common: mc, UnknownData: mOrig}
return nil, fmt.Errorf("%w: \"%s\"", types.ErrUnsupportedMediaType, c.desc.MediaType)
}
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
err = verifyMT(mc.mt, mt)
err = verifyMT(c.desc.MediaType, mt)
if err != nil {
return nil, err
}
@ -251,7 +283,7 @@ func getPlatformDesc(p *ociv1.Platform, dl []ociv1.Descriptor) (*ociv1.Descripto
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) {

View File

@ -2,6 +2,7 @@ package manifest
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"testing"
@ -10,6 +11,7 @@ import (
dockerSchema2 "github.com/docker/distribution/manifest/schema2"
"github.com/opencontainers/go-digest"
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/regclient/regclient/types"
"github.com/regclient/regclient/types/ref"
)
@ -279,167 +281,19 @@ var (
`)
)
var ()
func TestNewManifest(t *testing.T) {
func TestNew(t *testing.T) {
r, _ := ref.New("localhost:5000/test:latest")
digestDockerSchema2 := digest.FromBytes(rawDockerSchema2)
digestML := digest.FromBytes(rawDockerSchema2List)
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")
if err != nil {
t.Fatalf("failed to parse docker schema1 signed digest string: %v", err)
}
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 manifestDockerSchema1Signed dockerSchema1.SignedManifest
err := json.Unmarshal(rawDockerSchema2, &manifestDockerSchema2)
err = json.Unmarshal(rawDockerSchema2, &manifestDockerSchema2)
if err != nil {
t.Fatalf("failed to unmarshal docker schema2 json: %v", err)
}
@ -447,42 +301,228 @@ func TestFromOrig(t *testing.T) {
if err != nil {
t.Fatalf("failed to unmarshal docker schema2 json: %v", err)
}
manifestInvalid.MediaType = MediaTypeOCI1Manifest
manifestInvalid.MediaType = types.MediaTypeOCI1Manifest
err = json.Unmarshal(rawDockerSchema1Signed, &manifestDockerSchema1Signed)
if err != nil {
t.Fatalf("failed to unmarshal docker schema1 signed json: %v", err)
}
var tests = []struct {
name string
orig interface{}
opts []Opts
wantR ref.Ref
wantDesc ociv1.Descriptor
wantE error
}{
{
name: "Nil interface",
orig: nil,
wantE: fmt.Errorf("Unsupported type to convert to a manifest: %v", nil),
name: "empty",
wantE: fmt.Errorf("%w: \"%s\"", types.ErrUnsupportedMediaType, ""),
},
{
name: "Docker Schema2",
orig: manifestDockerSchema2,
name: "Docker Schema 2 Manifest",
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,
},
{
name: "Docker Schema 2 Manifest full desc",
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,
},
{
name: "Docker Schema 2 List from Http",
opts: []Opts{
WithRef(r),
WithRaw(rawDockerSchema2List),
WithHeader(http.Header{
"Content-Type": []string{MediaTypeDocker2ManifestList},
"Docker-Content-Digest": []string{digestML.String()},
}),
},
wantE: nil,
},
{
name: "Docker Schema 1 Signed",
orig: manifestDockerSchema1Signed,
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",
orig: manifestInvalid,
wantE: fmt.Errorf("manifest contains an unexpected media type: expected %s, received %s", MediaTypeDocker2Manifest, MediaTypeOCI1Manifest),
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 {
t.Run(tt.name, func(t *testing.T) {
_, err := FromOrig(tt.orig)
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())) {
m, err := New(tt.opts...)
if tt.wantE != nil {
if err == nil {
t.Errorf("did not receive expected error %v", tt.wantE)
} 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"
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/regclient/regclient/internal/wraperr"
"github.com/regclient/regclient/types"
)
const (
@ -36,14 +37,14 @@ func (m *oci1Manifest) GetConfigDigest() (digest.Digest, error) {
return m.Config.Digest, nil
}
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) {
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) {
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) {
return m.Manifests, nil
@ -53,7 +54,7 @@ func (m *oci1Manifest) GetLayers() ([]ociv1.Descriptor, error) {
return m.Layers, nil
}
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{} {
@ -64,7 +65,7 @@ func (m *oci1Index) GetOrigManifest() interface{} {
}
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) {
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) {
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) {
dl, err := m.GetDescriptorList()
@ -87,7 +88,7 @@ func (m *oci1Index) GetPlatformList() ([]*ociv1.Platform, error) {
func (m *oci1Manifest) MarshalJSON() ([]byte, error) {
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 {
@ -98,7 +99,7 @@ func (m *oci1Manifest) MarshalJSON() ([]byte, error) {
}
func (m *oci1Index) MarshalJSON() ([]byte, error) {
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 {
@ -125,8 +126,8 @@ func (m *oci1Index) MarshalPretty() ([]byte, error) {
if 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, "Digest:\t%s\n", m.digest.String())
fmt.Fprintf(tw, "MediaType:\t%s\n", m.desc.MediaType)
fmt.Fprintf(tw, "Digest:\t%s\n", m.desc.Digest.String())
fmt.Fprintf(tw, "\t\n")
fmt.Fprintf(tw, "Manifests:\t\n")
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
raw []byte
header http.Header
tags []string
}
type Opts func(*tagConfig)
@ -61,6 +62,9 @@ func New(opts ...Opts) (*TagList, error) {
rawHeader: conf.header,
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"
switch mt {
case "application/json", "text/plain":
@ -68,6 +72,8 @@ func New(opts ...Opts) (*TagList, error) {
if err != nil {
return nil, err
}
case types.MediaTypeOCI1ManifestList:
// noop
default:
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
}
}
func WithTags(tags []string) Opts {
return func(tConf *tagConfig) {
tConf.tags = tags
}
}
func (t tagCommon) GetOrig() interface{} {
return t.orig