86 lines
2.8 KiB
Go
86 lines
2.8 KiB
Go
// Package registry fetches images from an OCI registry into the store.
|
|
// The registry side (auth, manifest lists, blobs) is crane's,
|
|
// github.com/google/go-containerregistry, an unprivileged single
|
|
// binary; werkdock runs it to a `docker save` tarball and loads that
|
|
// the way `load -i` does (RFC 0002 level 2, RFC 0004). The surface is
|
|
// werkdock's, so an in-process client can replace crane later without
|
|
// a caller noticing.
|
|
package registry
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
|
|
"werkdock/internal/store"
|
|
)
|
|
|
|
// DefaultPlatform is what a pull asks for when the caller does not say.
|
|
func DefaultPlatform() string { return "linux/" + runtime.GOARCH }
|
|
|
|
// Pull fetches reference into st and returns the names it was stored
|
|
// under. Progress from crane goes to progress, which may be nil.
|
|
// An image that already exists is refused before anything is
|
|
// downloaded, as `load` would refuse it afterwards.
|
|
func Pull(st store.Store, reference, platform string, progress io.Writer) ([]string, error) {
|
|
ref, err := store.ParseReference(reference)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if platform == "" {
|
|
platform = DefaultPlatform()
|
|
}
|
|
if _, err := st.Dir(ref.String()); err == nil {
|
|
return nil, fmt.Errorf("image %q already exists (werkdock rmi %s to replace it)", ref.Short(), ref.Short())
|
|
}
|
|
crane, err := FindCrane(os.Getenv("WERKDOCK_CRANE"), os.Executable)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := os.MkdirAll(st.Root, 0o755); err != nil {
|
|
return nil, err
|
|
}
|
|
tarball, err := os.CreateTemp(st.Root, ".pull-*.tar")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tarball.Close()
|
|
defer os.Remove(tarball.Name())
|
|
if progress == nil {
|
|
progress = io.Discard
|
|
}
|
|
cmd := exec.Command(crane, "pull", "--platform", platform, ref.String(), tarball.Name())
|
|
cmd.Stdout, cmd.Stderr = progress, progress
|
|
if err := cmd.Run(); err != nil {
|
|
return nil, fmt.Errorf("crane pull %s: %w", ref.Short(), err)
|
|
}
|
|
return st.Load(tarball.Name(), ref.String())
|
|
}
|
|
|
|
// FindCrane locates the crane binary: WERKDOCK_CRANE if set, else PATH,
|
|
// else beside the werkdock binary — the managed webspaces have no
|
|
// package for it, so a deployment ships it next to werkdock.
|
|
func FindCrane(configured string, executable func() (string, error)) (string, error) {
|
|
if configured != "" {
|
|
if _, err := os.Stat(configured); err != nil {
|
|
return "", fmt.Errorf("WERKDOCK_CRANE: %w", err)
|
|
}
|
|
return configured, nil
|
|
}
|
|
if path, err := exec.LookPath("crane"); err == nil {
|
|
return path, nil
|
|
}
|
|
if self, err := executable(); err == nil {
|
|
beside := filepath.Join(filepath.Dir(self), "crane")
|
|
if info, err := os.Stat(beside); err == nil && !info.IsDir() {
|
|
return beside, nil
|
|
}
|
|
}
|
|
return "", errors.New("pull needs the crane binary (github.com/google/go-containerregistry): " +
|
|
"put it in PATH or beside werkdock, or set WERKDOCK_CRANE to its path")
|
|
}
|