#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Скрипт публикации Docker-образа (multi-arch) в реестр. Автор: Сергей Антропов Сайт: https://devops.org.ru Поведение: - Собирает multi-arch образ с использованием docker buildx - Логинится в реестр (пароль можно передать через аргумент/ENV, либо будет запрошен интерактивно) - Публикует образ Параметры через аргументы/ENV: - --registry / $REGISTRY_HOST (например, docker.io, ghcr.io) - --image / $IMAGE_NAME_FULL (например, inecs/logboard) - --tag / $IMAGE_TAG (например, v1 или короткий sha) - --user / $REG_USER - --password / $REG_PASS - --platforms / $PLATFORMS (по умолчанию: linux/amd64,linux/arm64) """ from __future__ import annotations import argparse import getpass import os import subprocess import sys from typing import List def run(cmd: List[str], check: bool = True, quiet: bool = False) -> subprocess.CompletedProcess: stdout = subprocess.DEVNULL if quiet else None stderr = subprocess.STDOUT if quiet else None return subprocess.run(cmd, check=check, stdout=stdout, stderr=stderr) def git_short_sha_default() -> str: try: out = subprocess.check_output(["git", "rev-parse", "--short", "HEAD"], stderr=subprocess.DEVNULL) return out.decode("utf-8").strip() or "latest" except Exception: return "latest" def ensure_buildx_builder(builder_name: str) -> None: try: run(["docker", "buildx", "inspect", builder_name], check=True, quiet=True) except subprocess.CalledProcessError: run(["docker", "buildx", "create", "--name", builder_name, "--use"], check=True) run(["docker", "buildx", "use", builder_name], check=True) def ensure_binfmt() -> None: try: run(["docker", "run", "--privileged", "--rm", "tonistiigi/binfmt", "--install", "all"], check=False, quiet=True) except Exception: pass def main() -> int: parser = argparse.ArgumentParser(description="Публикация Docker-образа в реестр (multi-arch)") parser.add_argument("--registry", default=os.getenv("REGISTRY_HOST", "")) parser.add_argument("--image", dest="image", default=os.getenv("IMAGE_NAME_FULL", "")) parser.add_argument("--tag", dest="tag", default=os.getenv("IMAGE_TAG", "")) parser.add_argument("--user", dest="user", default=os.getenv("REG_USER", "")) parser.add_argument("--password", dest="password", default=os.getenv("REG_PASS", "")) parser.add_argument("--platforms", default=os.getenv("PLATFORMS", "linux/amd64,linux/arm64")) parser.add_argument("--builder", default=os.getenv("BUILDX_BUILDER", "logboard_builder")) args = parser.parse_args() # 1) Определяем реестр, поддерживая ввод вида "host" или "host/namespace" registry_input = (args.registry or os.getenv("REGISTRY_HOST", "")).strip() if not registry_input: registry_input = input( "Введите Docker Registry (например, ghcr.io, docker.io, registry.hub.docker.com/namespace): " ).strip() registry_host = registry_input.split("/", 1)[0] if registry_input else "docker.io" registry_path = "" if "/" in registry_input: registry_path = registry_input.split("/", 1)[1] if not registry_host or registry_host == "registry.hub.docker.com": registry_host = "docker.io" # 2) Имя образа. Если не задано, спрашиваем пользователя (по умолчанию logboard) image_input = (args.image or os.getenv("IMAGE_NAME_FULL", "")).strip() if not image_input: image_input = input( "Введите имя образа (по умолчанию: logboard): " ).strip() or "logboard" # Если пользователь ввёл только имя без namespace, но registry_path задан, добавим префикс if "/" not in image_input and registry_path: image_full = f"{registry_path}/{image_input}" else: image_full = image_input # 3) Тег: если не задан, спросим. По умолчанию короткий SHA tag_input = (args.tag or os.getenv("IMAGE_TAG", "")).strip() if not tag_input: default_tag = git_short_sha_default() tag_input = input(f"Введите тег образа (по умолчанию: {default_tag}): ").strip() or default_tag user = (args.user or os.getenv("REG_USER", "")).strip() password = args.password if not user: user = input("Введите имя пользователя реестра: ").strip() if not password: password = getpass.getpass("Введите пароль реестра: ") image_remote = f"{registry_host}/{image_full}:{tag_input}" print(f"Логин в реестр: {registry_host}") p = subprocess.Popen(["docker", "login", registry_host, "-u", user, "--password-stdin"], stdin=subprocess.PIPE) p.communicate(input=password.encode("utf-8")) if p.returncode != 0: print("Ошибка логина в реестр", file=sys.stderr) return 1 print("Установка binfmt (для multi-arch)...") ensure_binfmt() print(f"Подготовка buildx builder: {args.builder}") ensure_buildx_builder(args.builder) print(f"Сборка и публикация образа: {image_remote}") run([ "docker", "buildx", "build", "--platform", args.platforms, "-t", image_remote, "--push", ".", ], check=True) print("Образ опубликован успешно!") print("Готово. Для генерации compose выполните: make release-compose") return 0 if __name__ == "__main__": raise SystemExit(main())