logboard/release/publish_image.py

143 lines
5.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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())