Files
Dockerfile/makefile

410 lines
17 KiB
Makefile
Raw 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.

# ==========================
# Universal Makefile (namespaced only)
# Author: Sergey Antropov
# https://devops.org.ru
# ==========================
# Замена пробелов на табы в консоли (фикс ошибок)
# sed -i 's/^ /\t/' makefile
# ---- Предустановленные значения под вашу сборку ----
REGISTRY ?= hub.cism-ms.ru/library
IMAGE ?=
TAG ?= latest
CONTEXT ?= .
DOCKERFILE ?= Dockerfile
# ---- Режимы сборки ----
USE_BUILDX ?= 0 # 1 -> использовать docker buildx
PLATFORMS ?= linux/amd64 # например: linux/amd64,linux/arm64
PUSH ?= 0 # 1 -> пушить образ в buildx (в рамках build)
LOAD ?= 1 # 1 -> загрузить образ в локальный докер (buildx)
NO_CACHE ?= 0 # 1 -> без кэша
PULL ?= 0 # 1 -> принудительно тянуть base-образы
QUIET ?= 0 # 1 -> тихий билд (минимум вывода)
# ---- Build args ----
BUILD_ARGS ?= # строка с доп. флагами: --build-arg FOO=bar --build-arg BAZ=qux
ARG_FILE ?= # путь к файлу KEY=VAL; будет превращён в --build-arg KEY=VAL
# ---- Проверка контейнера ----
RUN_CMD ?= lsb_release -a # Что проверить после сборки (docker check)
# ---- Buildx ----
BUILDER ?= universal-builder
# ---- Логи ----
LOG_DIR ?= logs
LOG_FILE ?= $(LOG_DIR)/build_$(shell date +%Y%m%d_%H%M%S).log
# --- Настройки Git ---
GIT_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
GIT_REMOTE ?= origin
GIT_MSG ?=
# ======================
# OFFLINE LOAD/RETAG/PUSH
# ======================
ARCHIVE ?= image.tar # путь к docker save архиву
LOGIN ?= 1 # 1 -> docker login в REGISTRY перед push
PUSH_OFFLINE ?= 1 # 1 -> docker push после load
KEEP_SRC_TAG ?= 1 # 1 -> если IMAGE пуст, взять тег из архива
OFFLINE_DIR ?= .offline
LOAD_LOG := $(OFFLINE_DIR)/load.log
SRC_FILE := $(OFFLINE_DIR)/src_image.txt
DST_FILE := $(OFFLINE_DIR)/dst_image.txt
LAST_SAVE := $(OFFLINE_DIR)/last_save.txt
# ======================
# Universal save (local)
# ======================
IMAGES_DIR ?= images # куда сохраняем архивы
SRC_IMAGE ?= # образ-источник; если пусто — $(FULL_IMAGE)
COMPRESS ?= 0 # 1 -> gzip архив
SPLIT_SIZE ?= # размер чанка, напр.: 2G (пусто = не резать)
# ---- Полное имя образа ----
ifneq ($(strip $(REGISTRY)),)
FULL_IMAGE := $(REGISTRY)/$(IMAGE):$(TAG)
else
FULL_IMAGE := $(IMAGE):$(TAG)
endif
# ---- Цвета ----
COLOR_RESET := \033[0m
COLOR_INFO := \033[1;34m
COLOR_OK := \033[1;32m
COLOR_ERR := \033[1;31m
COLOR_WARN := \033[1;33m
# ---- Флаги сборки ----
ifeq ($(NO_CACHE),1)
CACHE_FLAG := --no-cache
else
CACHE_FLAG :=
endif
ifeq ($(PULL),1)
PULL_FLAG := --pull
else
PULL_FLAG :=
endif
ifeq ($(QUIET),1)
QUIET_FLAG := --quiet
else
QUIET_FLAG :=
endif
# Аргументы из файла KEY=VALUE -> --build-arg KEY=VALUE
ifneq ($(strip $(ARG_FILE)),)
ARG_FILE_FLAGS := $(shell sed -n 's/^\([A-Za-z_][A-Za-z0-9_]*\)=\(.*\)/--build-arg \1=\2/p' $(ARG_FILE))
else
ARG_FILE_FLAGS :=
endif
# ======================
# Help
# ======================
.PHONY: help
help:
@echo "Namespaces:"
@echo " make docker [build|check|push|save|load|load-raw|inspect|retag|create-builder|use-builder|login|clean|print-config]"
@echo " make git [status|pull|commit|push|sync]"
@echo ""
@echo "Key vars (override via make VAR=value):"
@echo " REGISTRY=$(REGISTRY) IMAGE=$(IMAGE) TAG=$(TAG) CONTEXT=$(CONTEXT) DOCKERFILE=$(DOCKERFILE)"
@echo " USE_BUILDX=$(USE_BUILDX) PLATFORMS=$(PLATFORMS) PUSH=$(PUSH) LOAD=$(LOAD) NO_CACHE=$(NO_CACHE) PULL=$(PULL) QUIET=$(QUIET)"
@echo " BUILD_ARGS=$(BUILD_ARGS) ARG_FILE=$(ARG_FILE) RUN_CMD=$(RUN_CMD)"
@echo " ARCHIVE=$(ARCHIVE) LOGIN=$(LOGIN) PUSH_OFFLINE=$(PUSH_OFFLINE) KEEP_SRC_TAG=$(KEEP_SRC_TAG)"
@echo " IMAGES_DIR=$(IMAGES_DIR)"
@echo ""
@echo "Examples:"
@echo " make docker build IMAGE=myapp TAG=1.0.0"
@echo " make docker push REGISTRY=registry.local/library IMAGE=myapp TAG=1.0.0"
@echo " make docker save IMAGE=myapp TAG=1.0.0 COMPRESS=1"
@echo " make docker load ARCHIVE=images/myapp_1.0.0.tar.gz LOGIN=1 PUSH_OFFLINE=1"
@echo " make git commit # спросит сообщение"
@echo " make git push # commit+push с запросом сообщения"
# ======================
# Служебные директории
# ======================
$(LOG_DIR):
@mkdir -p $(LOG_DIR)
$(IMAGES_DIR):
@mkdir -p $(IMAGES_DIR)
$(OFFLINE_DIR):
@mkdir -p $(OFFLINE_DIR)
# ======================
# Docker: реализация
# ======================
# Внутренние цели (не вызывать напрямую)
.PHONY: __docker_build __docker_check __docker_push __docker_login __docker_retag __docker_save __docker_load __docker_load_raw __docker_inspect __docker_create_builder __docker_use_builder __docker_clean __docker_print_config
# --- Команды сборки ---
define DOCKER_BUILD_CLASSIC
docker build $(CACHE_FLAG) $(PULL_FLAG) $(QUIET_FLAG) \
-f $(DOCKERFILE) -t "$(FULL_IMAGE)" \
$(BUILD_ARGS) $(ARG_FILE_FLAGS) \
"$(CONTEXT)"
endef
define DOCKER_BUILD_BUILDX
docker buildx build $(CACHE_FLAG) $(PULL_FLAG) $(QUIET_FLAG) \
$(if $(filter 1,$(PUSH)),--push,) \
$(if $(filter 1,$(LOAD)),--load,) \
--platform $(PLATFORMS) \
-f $(DOCKERFILE) -t "$(FULL_IMAGE)" \
$(BUILD_ARGS) $(ARG_FILE_FLAGS) \
"$(CONTEXT)"
endef
__docker_build: $(LOG_DIR)
@echo "$(COLOR_INFO)[INFO] Build: $(FULL_IMAGE)$(COLOR_RESET)"
@echo "$(COLOR_INFO)[INFO] Log: $(LOG_FILE)$(COLOR_RESET)"
@{ \
echo "=== Build started: $$(date) ==="; \
if [ "$(USE_BUILDX)" = "1" ]; then \
$(DOCKER_BUILD_BUILDX); \
else \
$(DOCKER_BUILD_CLASSIC); \
fi; \
echo "=== Build finished: $$(date) ==="; \
} | tee $(LOG_FILE)
@echo "$(COLOR_OK)[OK] Build success$(COLOR_RESET)"
__docker_check:
@echo "$(COLOR_INFO)[INFO] Check (RUN_CMD) for $(FULL_IMAGE): $(RUN_CMD)$(COLOR_RESET)"
@docker run --rm --entrypoint /bin/sh "$(FULL_IMAGE)" -lc "$(RUN_CMD)"
__docker_push:
@if [ "$(USE_BUILDX)" != "1" ]; then \
echo "$(COLOR_INFO)[INFO] Pushing: $(FULL_IMAGE)$(COLOR_RESET)"; \
docker push "$(FULL_IMAGE)"; \
else \
echo "$(COLOR_WARN)[WARN] USE_BUILDX=1: push выполняется при buildx (PUSH=1).$(COLOR_RESET)"; \
echo "$(COLOR_WARN)[WARN] Пересоберите с PUSH=1 или выполните docker push вручную.$(COLOR_RESET)"; \
fi
@echo "$(COLOR_OK)[OK] Push done$(COLOR_RESET)"
__docker_login:
@if [ -z "$(REGISTRY)" ]; then \
echo "$(COLOR_ERR)[ERR] REGISTRY не задан. Пример: make docker login REGISTRY=registry.local$(COLOR_RESET)"; \
exit 1; \
fi
@echo "$(COLOR_INFO)[INFO] docker login $(REGISTRY)$(COLOR_RESET)"
@docker login $(REGISTRY)
__docker_retag:
@if [ -z "$(NEW_TAG)" ]; then \
echo "$(COLOR_ERR)[ERR] Укажи NEW_TAG: make docker retag NEW_TAG=vX.Y.Z$(COLOR_RESET)"; \
exit 1; \
fi
@echo "$(COLOR_INFO)[INFO] Retag: $(FULL_IMAGE) -> $(IMAGE):$(NEW_TAG)$(COLOR_RESET)"
@docker tag "$(FULL_IMAGE)" "$(IMAGE):$(NEW_TAG)"
@echo "$(COLOR_OK)[OK] Retag done$(COLOR_RESET)"
__docker_save: | $(IMAGES_DIR) $(OFFLINE_DIR)
@src="$(SRC_IMAGE)"; \
if [ -z "$$src" ]; then \
if [ -n "$(IMAGE)" ]; then src="$(FULL_IMAGE)"; else echo "$(COLOR_ERR)[ERR] Укажи SRC_IMAGE или IMAGE/TAG$(COLOR_RESET)"; exit 1; fi; \
fi; \
docker image inspect "$$src" >/dev/null 2>&1 || { echo "$(COLOR_ERR)[ERR] Образ не найден: $$src$(COLOR_RESET)"; exit 1; }; \
safe="$${src//\//_}"; safe="$${safe//:/_}"; out="$(IMAGES_DIR)/$$safe.tar"; \
echo "$(COLOR_INFO)[INFO] Сохранение $$src -> $$out$(COLOR_RESET)"; \
docker save -o "$$out" "$$src"; \
if [ "$(COMPRESS)" = "1" ]; then \
echo "$(COLOR_INFO)[INFO] Gzip $$out$(COLOR_RESET)"; gzip -f "$$out"; out="$$out.gz"; \
fi; \
if [ -n "$(SPLIT_SIZE)" ]; then \
echo "$(COLOR_INFO)[INFO] Резка архива на чанки по $(SPLIT_SIZE)$(COLOR_RESET)"; \
split -b $(SPLIT_SIZE) -d -a 3 "$$out" "$$out.part."; \
fi; \
{ sha256sum "$$out" 2>/dev/null || shasum -a 256 "$$out"; } > "$$out.sha256" 2>/dev/null || true; \
echo "$$out" > "$(LAST_SAVE)"; \
echo "$(COLOR_OK)[OK] Образ сохранён: $$out$(COLOR_RESET)"; \
ls -lh "$$out"*
__docker_load_raw:
@if [ -z "$(FILE)" ]; then \
echo "$(COLOR_ERR)[ERR] Укажи FILE: make docker load-raw FILE=img.tar$(COLOR_RESET)"; \
exit 1; \
fi
@echo "$(COLOR_INFO)[INFO] Loading image from $(FILE)$(COLOR_RESET)"
@docker load -i "$(FILE)"
@echo "$(COLOR_OK)[OK] Load done$(COLOR_RESET)"
__docker_load: | $(OFFLINE_DIR)
@if [ ! -f "$(ARCHIVE)" ]; then \
echo "$(COLOR_ERR)[ERR] Не найден архив: $(ARCHIVE) (ARCHIVE=... )$(COLOR_RESET)"; exit 1; \
fi
@echo "$(COLOR_INFO)[INFO] docker load -i $(ARCHIVE)$(COLOR_RESET)"
@docker load -i "$(ARCHIVE)" | tee "$(LOAD_LOG)"
@awk '/Loaded image:/ { last=$$0 } END { gsub(/^.*Loaded image: /,"",last); print last }' "$(LOAD_LOG)" > "$(SRC_FILE)"
@src="$$(cat "$(SRC_FILE)" 2>/dev/null || true)"; \
if [ -z "$$src" ]; then \
echo "$(COLOR_ERR)[ERR] Не удалось определить repo:tag из docker load. Укажи IMAGE/TAG вручную.$(COLOR_RESET)"; \
exit 1; \
fi; \
echo "$(COLOR_OK)[OK] Загружен исходный образ: $$src$(COLOR_RESET)"; \
repo="$${src%:*}"; srctag="$${src#*:}"; [ "$$repo" = "$$srctag" ] && srctag="latest"; \
src_name="$${repo##*/}"; \
dst_image="$(IMAGE)"; [ -z "$$dst_image" ] && dst_image="$$src_name"; \
if [ -z "$(IMAGE)" ] && [ "$(KEEP_SRC_TAG)" = "1" ]; then dst_tag="$$srctag"; else dst_tag="$(TAG)"; fi; \
if [ -z "$(REGISTRY)" ]; then echo "$(COLOR_ERR)[ERR] REGISTRY пуст$(COLOR_RESET)"; exit 1; fi; \
full_dst="$(REGISTRY)/$$dst_image:$$dst_tag"; echo "$$full_dst" > "$(DST_FILE)"; \
echo "$(COLOR_INFO)[INFO] Тегируем: $$src -> $$full_dst$(COLOR_RESET)"; \
docker tag "$$src" "$$full_dst"; \
if [ "$(LOGIN)" = "1" ]; then echo "$(COLOR_INFO)[INFO] docker login $(REGISTRY)$(COLOR_RESET)"; docker login "$(REGISTRY)"; fi; \
if [ "$(PUSH_OFFLINE)" = "1" ]; then echo "$(COLOR_INFO)[INFO] docker push $$full_dst$(COLOR_RESET)"; docker push "$$full_dst"; echo "$(COLOR_OK)[OK] Пуш завершён$(COLOR_RESET)"; else echo "$(COLOR_WARN)[WARN] PUSH_OFFLINE=0: пуш пропущен$(COLOR_RESET)"; fi
__docker_inspect:
@docker image inspect "$(FULL_IMAGE)" || true
__docker_create_builder:
@docker buildx create --name $(BUILDER) --use --bootstrap || docker buildx use $(BUILDER)
@echo "$(COLOR_OK)[OK] Builder ready: $(BUILDER)$(COLOR_RESET)"
__docker_use_builder:
@docker buildx use $(BUILDER)
@docker buildx inspect --bootstrap
@echo "$(COLOR_OK)[OK] Using builder: $(BUILDER)$(COLOR_RESET)"
__docker_clean:
@echo "$(COLOR_WARN)[WARN] Removing logs and .offline$(COLOR_RESET)"
@rm -rf "$(LOG_DIR)" "$(OFFLINE_DIR)"
@echo "$(COLOR_OK)[OK] Clean done$(COLOR_RESET)"
__docker_print_config:
@echo "FULL_IMAGE : $(FULL_IMAGE)"
@echo "REGISTRY : $(REGISTRY)"
@echo "IMAGE : $(IMAGE)"
@echo "TAG : $(TAG)"
@echo "CONTEXT : $(CONTEXT)"
@echo "DOCKERFILE : $(DOCKERFILE)"
@echo "USE_BUILDX : $(USE_BUILDX)"
@echo "PLATFORMS : $(PLATFORMS)"
@echo "PUSH : $(PUSH)"
@echo "LOAD : $(LOAD)"
@echo "NO_CACHE : $(NO_CACHE)"
@echo "PULL : $(PULL)"
@echo "QUIET : $(QUIET)"
@echo "BUILD_ARGS : $(BUILD_ARGS)"
@echo "ARG_FILE : $(ARG_FILE)"
@echo "RUN_CMD : $(RUN_CMD)"
@echo "BUILDER : $(BUILDER)"
@echo "LOG_DIR : $(LOG_DIR)"
@echo "LOG_FILE : $(LOG_FILE)"
# ======================
# Docker dispatcher
# ======================
.PHONY: docker
docker:
@sub="$(word 2,$(MAKECMDGOALS))"; \
case "$$sub" in \
build) $(MAKE) __docker_build ;; \
check) $(MAKE) __docker_check ;; \
push) $(MAKE) __docker_build __docker_check __docker_push ;; \
save) $(MAKE) __docker_save ;; \
load) $(MAKE) __docker_load ;; \
load-raw) $(MAKE) __docker_load_raw ;; \
inspect) $(MAKE) __docker_inspect ;; \
retag) $(MAKE) __docker_retag ;; \
create-builder) $(MAKE) __docker_create_builder ;; \
use-builder) $(MAKE) __docker_use_builder ;; \
login) $(MAKE) __docker_login ;; \
clean) $(MAKE) __docker_clean ;; \
print-config) $(MAKE) __docker_print_config ;; \
"") \
echo "$(COLOR_INFO)[INFO] Docker commands:$(COLOR_RESET)"; \
echo " make docker build — собрать образ"; \
echo " make docker check — проверить контейнер (RUN_CMD)"; \
echo " make docker push — build + check + push"; \
echo " make docker save — сохранить образ в ./images/"; \
echo " make docker load — загрузить из архива и (опц.) запушить"; \
echo " make docker load-raw — чистый docker load -i FILE"; \
echo " make docker inspect — docker image inspect"; \
echo " make docker retag — перетегировать локальный образ"; \
echo " make docker create-builder — создать buildx builder"; \
echo " make docker use-builder — выбрать buildx builder"; \
echo " make docker login — docker login в REGISTRY"; \
echo " make docker clean — удалить логи и .offline"; \
echo " make docker print-config — показать конфигурацию"; \
;; \
*) echo "$(COLOR_ERR)[ERR] Unknown docker subcommand: $$sub$(COLOR_RESET)"; exit 1 ;; \
esac
# ======================
# Git: реализация
# ======================
.PHONY: __git_status __git_pull __git_commit __git_push __git_sync
__git_status:
@echo "$(COLOR_INFO)[INFO] Git status ($(GIT_BRANCH))$(COLOR_RESET)"
@git status -sb || echo "$(COLOR_WARN)[WARN] Not a git repository$(COLOR_RESET)"
__git_pull:
@echo "$(COLOR_INFO)[INFO] Pulling from $(GIT_REMOTE)/$(GIT_BRANCH)$(COLOR_RESET)"
@git pull $(GIT_REMOTE) $(GIT_BRANCH) || echo "$(COLOR_ERR)[ERR] git pull failed$(COLOR_RESET)"
__git_commit:
@echo "$(COLOR_INFO)[INFO] Preparing commit$(COLOR_RESET)"
@if [ -z "$(GIT_MSG)" ]; then \
read -p "Введите сообщение коммита: " msg; \
GIT_MSG="$$msg"; \
else \
GIT_MSG="$(GIT_MSG)"; \
fi; \
git add -A; \
if git diff --cached --quiet; then \
echo "$(COLOR_WARN)[WARN] Нет изменений для коммита$(COLOR_RESET)"; \
else \
git commit -m "$$GIT_MSG" && echo "$(COLOR_OK)[OK] Commit created: $$GIT_MSG$(COLOR_RESET)"; \
fi
__git_push:
@echo "$(COLOR_INFO)[INFO] Preparing push for $(GIT_BRANCH)$(COLOR_RESET)"
@$(MAKE) __git_commit
@echo "$(COLOR_INFO)[INFO] Pushing to $(GIT_REMOTE)/$(GIT_BRANCH)$(COLOR_RESET)"
@git push $(GIT_REMOTE) $(GIT_BRANCH) || echo "$(COLOR_ERR)[ERR] git push failed$(COLOR_RESET)"
__git_sync:
@echo "$(COLOR_INFO)[INFO] Git sync (pull + commit + push)$(COLOR_RESET)"
@$(MAKE) __git_pull
@$(MAKE) __git_push
@echo "$(COLOR_OK)[OK] Git sync complete for branch $(GIT_BRANCH)$(COLOR_RESET)"
# ======================
# Git dispatcher
# ======================
.PHONY: git
git:
@sub="$(word 2,$(MAKECMDGOALS))"; \
case "$$sub" in \
status) $(MAKE) __git_status ;; \
pull) $(MAKE) __git_pull ;; \
commit) $(MAKE) __git_commit ;; \
push) $(MAKE) __git_push ;; \
sync) $(MAKE) __git_sync ;; \
"") \
echo "$(COLOR_INFO)[INFO] Git commands:$(COLOR_RESET)"; \
echo " make git status — показать статус"; \
echo " make git pull — получить изменения"; \
echo " make git commit — коммит (со вводом сообщения)"; \
echo " make git push — commit + push (со вводом сообщения)"; \
echo " make git sync — pull + commit + push"; \
;; \
*) echo "$(COLOR_ERR)[ERR] Unknown git subcommand: $$sub$(COLOR_RESET)"; exit 1 ;; \
esac
.PHONY: status pull commit push sync \
build check save load load-raw inspect retag create-builder use-builder login clean print-config
status pull commit push sync \
build check save load load-raw inspect retag create-builder use-builder login clean print-config:
@: