diff --git a/pkg/v1/layout/write.go b/pkg/v1/layout/write.go index d6e35c391..06ded2780 100644 --- a/pkg/v1/layout/write.go +++ b/pkg/v1/layout/write.go @@ -318,6 +318,23 @@ func (l Path) RemoveBlob(hash v1.Hash) error { return nil } +// BlobExists checks a blob exists at blobs/{hash.Algorithm}/{hash.Hex} +func (l Path) BlobExists(hash v1.Hash) bool { + dir := l.path("blobs", hash.Algorithm) + _, err := os.Stat(dir) + return !errors.Is(err, os.ErrNotExist) +} + +// BlobExists checks a blob exists at blobs/{hash.Algorithm}/{hash.Hex} +func (l Path) BlobSize(hash v1.Hash) (int64, error) { + dir := l.path("blobs", hash.Algorithm) + stat, err := os.Stat(dir) + if err != nil { + return 0, err + } + return stat.Size(), nil +} + // WriteImage writes an image, including its manifest, config and all of its // layers, to the blobs directory. If any blob already exists, as determined by // the hash filename, does not write it. diff --git a/pkg/v1/local/layer.go b/pkg/v1/local/layer.go new file mode 100644 index 000000000..c85242caa --- /dev/null +++ b/pkg/v1/local/layer.go @@ -0,0 +1,44 @@ +package local + +import ( + "io" + + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/layout" + "github.com/google/go-containerregistry/pkg/v1/partial" + "github.com/google/go-containerregistry/pkg/v1/types" +) + +// remoteImagelayer implements partial.CompressedLayer +type localLayer struct { + path layout.Path + digest v1.Hash +} + +// Compressed implements partial.CompressedLayer. +func (lp *localLayer) Compressed() (io.ReadCloser, error) { + return lp.path.Blob(lp.digest) +} + +// Digest implements partial.CompressedLayer. +func (lp *localLayer) Digest() (v1.Hash, error) { + return lp.digest, nil +} + +// MediaType implements partial.CompressedLayer. +func (*localLayer) MediaType() (types.MediaType, error) { + // TODO + return types.DockerLayer, nil +} + +// Size implements partial.CompressedLayer. +func (rl *localLayer) Size() (int64, error) { + return rl.path.BlobSize(rl.digest) +} + +// See partial.Exists. +func (rl *localLayer) Exists() (bool, error) { + return rl.path.BlobExists(rl.digest), nil +} + +var _ partial.CompressedLayer = (*localLayer)(nil) diff --git a/pkg/v1/local/puller.go b/pkg/v1/local/puller.go index 6a2340c8b..246abc9df 100644 --- a/pkg/v1/local/puller.go +++ b/pkg/v1/local/puller.go @@ -3,49 +3,103 @@ package local import ( "context" "errors" + "fmt" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/layout" "github.com/google/go-containerregistry/pkg/v1/partial" "github.com/google/go-containerregistry/pkg/v1/remote" + specsv1 "github.com/opencontainers/image-spec/specs-go/v1" ) type puller struct { + path layout.Path +} + +func NewLocalPuller(path layout.Path) remote.Puller { + return &puller{ + path, + } } var _ remote.Puller = (*puller)(nil) // Artifact implements remote.Puller. -func (*puller) Artifact(ctx context.Context, ref name.Reference) (partial.Artifact, error) { - panic("unimplemented") +func (p *puller) getDescriptor(ctx context.Context, ref name.Reference) (*v1.Descriptor, error) { + idx, err := p.path.ImageIndex() + if err != nil { + return nil, err + } + im, err := idx.IndexManifest() + if err != nil { + return nil, err + } + for _, manifest := range im.Manifests { + if rref, ok := manifest.Annotations[specsv1.AnnotationRefName]; ok { + if ref.String() == rref { + return &manifest, nil + } + } + } + return nil, fmt.Errorf("unknown image: %s", ref.String()) } -// Get implements remote.Puller. -func (*puller) Get(ctx context.Context, ref name.Reference) (*remote.Descriptor, error) { - panic("unimplemented") +// Artifact implements remote.Puller. +func (p *puller) Artifact(ctx context.Context, ref name.Reference) (partial.Artifact, error) { + desc, err := p.getDescriptor(ctx, ref) + if err != nil { + return nil, err + } + if desc.MediaType.IsImage() { + return p.path.Image(desc.Digest) + } else if desc.MediaType.IsIndex() { + reg, err := p.path.ImageIndex() + if err != nil { + return nil, err + } + return reg.ImageIndex(desc.Digest) + } + return nil, errors.ErrUnsupported } // Head implements remote.Puller. -func (*puller) Head(ctx context.Context, ref name.Reference) (*v1.Descriptor, error) { - panic("unimplemented") +func (p *puller) Head(ctx context.Context, ref name.Reference) (*v1.Descriptor, error) { + return p.getDescriptor(ctx, ref) } // Layer implements remote.Puller. -func (*puller) Layer(ctx context.Context, ref name.Digest) (v1.Layer, error) { - panic("unimplemented") +func (p *puller) Layer(ctx context.Context, ref name.Digest) (v1.Layer, error) { + h, err := v1.NewHash(ref.Identifier()) + if err != nil { + return nil, err + } + l, err := partial.CompressedToLayer(&localLayer{ + path: p.path, + digest: h, + }) + if err != nil { + return nil, err + } + return l, nil } // List implements remote.Puller. func (*puller) List(ctx context.Context, repo name.Repository) ([]string, error) { - return nil, errors.New("unimplemented") + return nil, errors.ErrUnsupported +} + +// Get implements remote.Puller. +func (*puller) Get(ctx context.Context, ref name.Reference) (*remote.Descriptor, error) { + return nil, errors.ErrUnsupported } // Lister implements remote.Puller. func (*puller) Lister(ctx context.Context, repo name.Repository) (*remote.Lister, error) { - return nil, errors.New("unimplemented") + return nil, errors.ErrUnsupported } // Catalogger implements remote.Puller. func (*puller) Catalogger(ctx context.Context, reg name.Registry) (*remote.Catalogger, error) { - return nil, errors.New("unimplemented") + return nil, errors.ErrUnsupported }