Files
warewulf/internal/pkg/oci/puller.go
2020-11-06 16:34:36 -08:00

183 lines
4.2 KiB
Go

package oci
import (
"context"
"crypto/sha256"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/containers/image/copy"
"github.com/containers/image/docker"
"github.com/containers/image/oci/layout"
"github.com/containers/image/signature"
"github.com/containers/image/types"
imgSpecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/opencontainers/umoci"
"github.com/opencontainers/umoci/oci/layer"
)
type pullerOpt func(*puller) error
func optSetBlobCachePath(path string) pullerOpt {
return func(p *puller) error {
p.blobCachePath = path
return nil
}
}
func optSetTmpDirPath(path string) pullerOpt {
return func(p *puller) error {
p.tmpDirPath = path
return nil
}
}
func optSetSystemContext(s *types.SystemContext) pullerOpt {
return func(p *puller) error {
p.sysCtx = s
return nil
}
}
type puller struct {
id string
blobCachePath string
tmpDirPath string
sysCtx *types.SystemContext
}
func newPuller(opts ...pullerOpt) (*puller, error) {
p := &puller{
// default to a sensible value, but caller should set this with opts
blobCachePath: filepath.Join(defaultCachePath, blobPrefix),
}
for _, o := range opts {
if err := o(p); err != nil {
return nil, err
}
}
return p, nil
}
// stripScheme prepares the docker uri for external library parsing and returns an error
// if it detects a malformed schema
func stripScheme(uri string) (string, error) {
if !strings.HasPrefix(uri, "docker://") {
return "", fmt.Errorf("unsupported uri schema: %q", uri)
}
return strings.TrimPrefix(uri, "docker:"), nil
}
// generateID stores and returns a unique identifier derived from the sha256sum of the image manifest
func (p *puller) generateID(ctx context.Context, uri string) (string, error) {
cleanURI, err := stripScheme(uri)
if err != nil {
return "", err
}
ref, err := docker.ParseReference(cleanURI)
if err != nil {
return "", err
}
src, err := ref.NewImageSource(ctx, nil)
if err != nil {
return "", err
}
manifestBytes, _, err := src.GetManifest(ctx, nil)
if err != nil {
return "", err
}
p.id = fmt.Sprintf("sha256:%x", sha256.Sum256(manifestBytes))
return p.id, nil
}
func (p *puller) pull(ctx context.Context, uri, dst string) (err error) {
cleanURI, err := stripScheme(uri)
if err != nil {
return err
}
srcRef, err := docker.ParseReference(cleanURI)
if err != nil {
return fmt.Errorf("unable to parse uri: %v", err)
}
cacheRef, err := layout.ParseReference(p.blobCachePath + ":" + p.id)
if err != nil {
return fmt.Errorf("unable to generate local oci reference: %v", err)
}
// Create a wide open oci image signature policy
policy := &signature.Policy{Default: []signature.PolicyRequirement{signature.NewPRInsecureAcceptAnything()}}
policyCtx, err := signature.NewPolicyContext(policy)
if err != nil {
return fmt.Errorf("unable to create policy context: %v", err)
}
// copy to cache location
_, err = copy.Image(ctx, policyCtx, cacheRef, srcRef, &copy.Options{
ReportWriter: os.Stdout,
SourceCtx: p.sysCtx,
})
if err != nil {
return err
}
// defaults to $TMPDIR or /tmp
tmpDir, err := ioutil.TempDir(p.tmpDirPath, "oci-bundle-")
if err != nil {
return err
}
defer os.RemoveAll(tmpDir)
// create an oci bundle our tmpdir to avoid issues with umoci.UnpackRootfs()
tmpRef, err := layout.ParseReference(tmpDir + ":" + "tmp")
if err != nil {
return fmt.Errorf("unable to generate local oci reference: %v", err)
}
// copy to temporary location
_, err = copy.Image(ctx, policyCtx, tmpRef, cacheRef, &copy.Options{})
if err != nil {
return err
}
tmp, err := tmpRef.NewImageSource(ctx, nil)
if err != nil {
return err
}
manifestBytes, _, err := tmp.GetManifest(ctx, nil)
if err != nil {
return err
}
var manifest imgSpecs.Manifest
if err := json.Unmarshal(manifestBytes, &manifest); err != nil {
return fmt.Errorf("unable to unmarshall mafinest json: %v", err)
}
eng, err := umoci.OpenLayout(tmpDir)
if err != nil {
return fmt.Errorf("unable to open oci layout: %v", err)
}
var mo layer.MapOptions
err = layer.UnpackRootfs(ctx, eng, dst, manifest, &mo, nil, imgSpecs.Descriptor{})
if err != nil {
return fmt.Errorf("unable to unpack rootfs: %v", err)
}
return nil
}