Files
reg/cmd/image_pull.go
2026-11-05 06:40:24 +03:00

130 lines
3.7 KiB
Go

package cmd
import (
"context"
"encoding/json"
"flag"
"io"
"os"
"path/filepath"
"strings"
reg "reg/internal/reg"
)
// RunPull реализует команду `reg pull <image>[:<tag>|@<digest>] [options]`.
func RunPull(ctx context.Context, client *reg.RegistryClient, args []string) int {
if len(args) == 0 {
reg.PrintError(MsgPullRequiresImage.String())
return 1
}
imageArg := args[0]
args = args[1:]
// Предварительно разбираем -f (необязательный по значению флаг).
tarSet, tarName, rest := extractTarFlag(args)
if tarSet && tarName != "" && filepath.Base(tarName) != tarName {
reg.PrintError(MsgPullTarNoPath.String())
return 1
}
cmdFlags := flag.NewFlagSet("pull", flag.ContinueOnError)
cmdFlags.SetOutput(io.Discard)
outputFlag := cmdFlags.String("o", "", "parent directory for saving")
platformFlag := cmdFlags.String("platform", "", "platform (os/arch[/variant]); default — current")
verboseFlag := cmdFlags.Bool("v", false, "print progress to stderr (one JSON line per event)")
cmdFlags.Usage = func() {}
if code, ok := parseFlags(cmdFlags, rest); !ok {
return code
}
ref, err := reg.ParseImageRef(imageArg)
if err != nil {
reg.PrintError(err.Error())
return 1
}
platform := reg.DefaultPlatform()
if *platformFlag != "" {
platform, err = reg.ParsePlatform(*platformFlag)
if err != nil {
reg.PrintError(err.Error())
return 1
}
}
progress := func(event string, data map[string]interface{}) {
if !*verboseFlag {
return
}
m := map[string]interface{}{"event": event}
for k, v := range data {
m[k] = v
}
enc := json.NewEncoder(os.Stderr)
_ = enc.Encode(m)
}
// Режим tar (-f).
if tarSet {
name := tarName
if name == "" {
name = reg.DefaultOutputDir(ref) + ".tar"
}
tarPath := name
if *outputFlag != "" {
tarPath = filepath.Join(*outputFlag, name)
}
result, err := reg.PullImageToTar(ctx, client, ref, platform, tarPath, progress)
if err != nil {
reg.PrintError(MsgPullTarError.F(ref.String(), err))
return 1
}
reg.PrintJSON(result)
return 0
}
// Режим каталога (по умолчанию).
outDir := reg.DefaultOutputDir(ref)
if *outputFlag != "" {
outDir = filepath.Join(*outputFlag, outDir)
}
result, err := reg.PullImage(ctx, client, ref, platform, outDir, progress)
if err != nil {
reg.PrintError(MsgPullError.F(ref.String(), err))
return 1
}
reg.PrintJSON(result)
return 0
}
// extractTarFlag выделяет флаг -f из args.
// Поддерживаются формы:
// -f → set=true, name=""
// -f name → set=true, name="name"
// -f=name → set=true, name="name"
//
// Всё остальное возвращается в rest (для последующего flag.Parse).
func extractTarFlag(args []string) (bool, string, []string) {
var set bool
var name string
out := make([]string, 0, len(args))
for i := 0; i < len(args); i++ {
a := args[i]
switch {
case a == "-f":
set = true
if i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") {
name = args[i+1]
i++
}
case strings.HasPrefix(a, "-f="):
set = true
name = strings.TrimPrefix(a, "-f=")
default:
out = append(out, a)
}
}
return set, name, out
}