Restore: фолбэк pull по тегу, если образ по digest удалён из реестра
CI / test (push) Successful in 3m32s
Release / release (push) Successful in 3m34s

Общий pull падал целиком (minio/minio удалён с Docker Hub), create не
проходил. Теперь при ошибке образы тянутся по одному, для недоступных
по digest закрепление снимается, а при полном отказе в todo попадает подсказка.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
mrleo1nidandClaude Sonnet 5.5 committed 2026-11-06 02:12:16 +03:00
1 parent 97c7051021
commit 7e29fb11eb
1 file changed
+47 -2
+47 -2
View File
@@ -54,6 +54,7 @@ func (r *restorer) restoreCompose(c manifest.Category) error {
if err := compose("pull", "--ignore-buildable", "--quiet"); err != nil { if err := compose("pull", "--ignore-buildable", "--quiet"); err != nil {
r.warn("%s: pull: %v", c.Name, err) r.warn("%s: pull: %v", c.Name, err)
r.pullFallback(c, compose)
} }
if hasBuilt(info) { if hasBuilt(info) {
if err := compose("build"); err != nil { if err := compose("build"); err != nil {
@@ -94,6 +95,46 @@ func hasBuilt(info *manifest.Compose) bool {
// writePinFile пишет compose.restore.yml с образами по digest. Пустая строка — закреплять нечего. // writePinFile пишет compose.restore.yml с образами по digest. Пустая строка — закреплять нечего.
func (r *restorer) writePinFile(c manifest.Category) (string, error) { 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 var services []string
for name := range c.Compose.Images { for name := range c.Compose.Images {
services = append(services, name) services = append(services, name)
@@ -105,16 +146,20 @@ func (r *restorer) writePinFile(c manifest.Category) (string, error) {
n := 0 n := 0
for _, svc := range services { for _, svc := range services {
img := c.Compose.Images[svc] img := c.Compose.Images[svc]
if img.Built || img.Digest == "" { if img.Built || img.Digest == "" || skip[svc] {
continue continue
} }
fmt.Fprintf(&b, " %q:\n image: %q\n", svc, img.Digest) fmt.Fprintf(&b, " %q:\n image: %q\n", svc, img.Digest)
n++ n++
} }
p := filepath.Join(c.Compose.Dir, pinFile)
if n == 0 { if n == 0 {
if skip != nil {
// файл уже подключён через -f: оставляем валидный пустой
return p, os.WriteFile(p, []byte("services: {}\n"), 0o644)
}
return "", nil return "", nil
} }
p := filepath.Join(c.Compose.Dir, pinFile)
return p, os.WriteFile(p, []byte(b.String()), 0o644) return p, os.WriteFile(p, []byte(b.String()), 0o644)
} }