Files
miandmhoennig f9c1ae835d Api socket (#5)
Co-authored-by: mhoennig <michael@hoennig.de>
Reviewed-on: #5
2026-09-10 22:09:58 +02:00

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")
}