From 7e29fb11eb18872a1dcc59e18c328e6aed9ba4f0 Mon Sep 17 00:00:00 2001 From: mrleo1nid <43583385+mrleo1nid@users.noreply.github.com> Date: Fri, 6 Nov 2026 02:12:16 +0300 Subject: [PATCH] =?UTF-8?q?Restore:=20=D1=84=D0=BE=D0=BB=D0=B1=D1=8D=D0=BA?= =?UTF-8?q?=20pull=20=D0=BF=D0=BE=20=D1=82=D0=B5=D0=B3=D1=83,=20=D0=B5?= =?UTF-8?q?=D1=81=D0=BB=D0=B8=20=D0=BE=D0=B1=D1=80=D0=B0=D0=B7=20=D0=BF?= =?UTF-8?q?=D0=BE=20digest=20=D1=83=D0=B4=D0=B0=D0=BB=D1=91=D0=BD=20=D0=B8?= =?UTF-8?q?=D0=B7=20=D1=80=D0=B5=D0=B5=D1=81=D1=82=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Общий pull падал целиком (minio/minio удалён с Docker Hub), create не проходил. Теперь при ошибке образы тянутся по одному, для недоступных по digest закрепление снимается, а при полном отказе в todo попадает подсказка. Co-Authored-By: Claude Sonnet 5.5 --- internal/restore/compose.go | 49 +++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/internal/restore/compose.go b/internal/restore/compose.go index f467e8d..8b1695e 100644 --- a/internal/restore/compose.go +++ b/internal/restore/compose.go @@ -54,6 +54,7 @@ func (r *restorer) restoreCompose(c manifest.Category) error { if err := compose("pull", "--ignore-buildable", "--quiet"); err != nil { r.warn("%s: pull: %v", c.Name, err) + r.pullFallback(c, compose) } if hasBuilt(info) { if err := compose("build"); err != nil { @@ -94,6 +95,46 @@ func hasBuilt(info *manifest.Compose) bool { // writePinFile пишет compose.restore.yml с образами по digest. Пустая строка — закреплять нечего. func (r *restorer) writePinFile(c manifest.Category) (string, error) { + return r.writePinFileSkip(c, nil) +} + +// pullFallback вызывается, когда общий pull не удался (например, образ по digest +// удалён из реестра — так случилось с minio/minio на Docker Hub). Тянет сервисы +// по одному; для тех, что не нашлись по digest, снимает закрепление и пробует +// тег из исходного compose-файла. +func (r *restorer) pullFallback(c manifest.Category, compose func(...string) error) { + var failed []string + for svc, img := range c.Compose.Images { + if img.Built { + continue + } + if err := compose("pull", "--quiet", svc); err != nil { + failed = append(failed, svc) + } + } + if len(failed) == 0 { + return + } + slices.Sort(failed) + skip := map[string]bool{} + for _, svc := range failed { + skip[svc] = true + } + if _, err := r.writePinFileSkip(c, skip); err != nil { + r.warn("%s: не удалось переписать %s: %v", c.Name, pinFile, err) + return + } + for _, svc := range failed { + if err := compose("pull", "--quiet", svc); err != nil { + r.warn("%s: образ сервиса %s недоступен ни по digest, ни по тегу: %v", c.Name, svc, err) + r.todo = append(r.todo, fmt.Sprintf("%s: образ сервиса %s недоступен в реестре (возможно, удалён или переехал, напр. minio/minio → quay.io/minio/minio). Поправьте image в compose-файле и выполните `docker compose up -d`", c.Name, svc)) + continue + } + r.warn("%s: сервис %s: образ по digest недоступен, взят по тегу из compose-файла (версия может отличаться)", c.Name, svc) + } +} + +func (r *restorer) writePinFileSkip(c manifest.Category, skip map[string]bool) (string, error) { var services []string for name := range c.Compose.Images { services = append(services, name) @@ -105,16 +146,20 @@ func (r *restorer) writePinFile(c manifest.Category) (string, error) { n := 0 for _, svc := range services { img := c.Compose.Images[svc] - if img.Built || img.Digest == "" { + if img.Built || img.Digest == "" || skip[svc] { continue } fmt.Fprintf(&b, " %q:\n image: %q\n", svc, img.Digest) n++ } + p := filepath.Join(c.Compose.Dir, pinFile) if n == 0 { + if skip != nil { + // файл уже подключён через -f: оставляем валидный пустой + return p, os.WriteFile(p, []byte("services: {}\n"), 0o644) + } return "", nil } - p := filepath.Join(c.Compose.Dir, pinFile) return p, os.WriteFile(p, []byte(b.String()), 0o644) }