feat(easy-docker): add custom image build flow

This commit is contained in:
RocketQuack 2026-03-01 17:21:09 +01:00
parent dd3d74f285
commit 6cd1723a40
5 changed files with 186 additions and 5 deletions

View file

@ -1,5 +1,7 @@
#!/usr/bin/env bash
EASY_DOCKER_BUILD_ERROR_DETAIL=""
render_stack_compose_from_metadata() {
local stack_dir="${1}"
local metadata_path=""
@ -74,3 +76,122 @@ EOF
return 0
}
build_stack_custom_image() {
local stack_dir="${1}"
local metadata_path=""
local env_path=""
local apps_json_path=""
local custom_image=""
local custom_tag=""
local frappe_branch=""
local frappe_path="https://github.com/frappe/frappe"
local repo_root=""
local containerfile_path=""
local apps_json_base64=""
local apps_refs_lines=""
local app_ref_line=""
local app_url=""
local app_branch=""
local git_error=""
local image_ref=""
EASY_DOCKER_BUILD_ERROR_DETAIL=""
metadata_path="${stack_dir}/metadata.json"
env_path="$(get_stack_env_path "${stack_dir}")"
apps_json_path="${stack_dir}/apps.json"
if [ ! -f "${metadata_path}" ]; then
return 11
fi
if [ ! -f "${env_path}" ]; then
return 12
fi
custom_image="$(get_env_file_key_value "${env_path}" "CUSTOM_IMAGE" || true)"
custom_tag="$(get_env_file_key_value "${env_path}" "CUSTOM_TAG" || true)"
frappe_branch="$(get_stack_frappe_branch "${stack_dir}" || true)"
if [ -z "${custom_image}" ]; then
return 13
fi
if [ -z "${custom_tag}" ]; then
return 14
fi
if [ -z "${frappe_branch}" ]; then
return 15
fi
# Keep apps.json aligned with current metadata app selection before build.
if ! persist_stack_apps_json_from_metadata_apps "${stack_dir}"; then
return 16
fi
if [ ! -f "${apps_json_path}" ]; then
return 17
fi
if ! command_exists git; then
return 22
fi
apps_refs_lines="$(
awk '
match($0, /"url"[[:space:]]*:[[:space:]]*"([^"]+)"/, url_parts) &&
match($0, /"branch"[[:space:]]*:[[:space:]]*"([^"]+)"/, branch_parts) {
print url_parts[1] "|" branch_parts[1]
}
' "${apps_json_path}"
)"
if [ -z "${apps_refs_lines}" ]; then
return 23
fi
while IFS= read -r app_ref_line; do
if [ -z "${app_ref_line}" ]; then
continue
fi
app_url="${app_ref_line%%|*}"
app_branch="${app_ref_line#*|}"
if [ -z "${app_url}" ] || [ -z "${app_branch}" ]; then
continue
fi
if git_error="$(git ls-remote --exit-code --heads "${app_url}" "${app_branch}" 2>&1)"; then
:
else
# shellcheck disable=SC2034 # Read by manage flow after build_stack_custom_image returns 24.
EASY_DOCKER_BUILD_ERROR_DETAIL="$(printf '%s@%s :: %s' "${app_url}" "${app_branch}" "${git_error}")"
return 24
fi
done <<EOF
${apps_refs_lines}
EOF
if ! command_exists base64; then
return 18
fi
apps_json_base64="$(base64 "${apps_json_path}" | tr -d '\r\n')"
if [ -z "${apps_json_base64}" ]; then
return 19
fi
repo_root="$(get_easy_docker_repo_root)"
containerfile_path="${repo_root}/images/layered/Containerfile"
if [ ! -f "${containerfile_path}" ]; then
return 20
fi
image_ref="${custom_image}:${custom_tag}"
docker build \
-f "${containerfile_path}" \
--build-arg "FRAPPE_BRANCH=${frappe_branch}" \
--build-arg "FRAPPE_PATH=${frappe_path}" \
--build-arg "APPS_JSON_BASE64=${apps_json_base64}" \
-t "${image_ref}" \
"${repo_root}" || return 21
return 0
}

View file

@ -11,6 +11,7 @@ handle_manage_selected_stack_flow() {
local custom_apps_update_status=0
local persist_apps_status=0
local render_compose_status=0
local build_image_status=0
local generated_compose_path=""
stack_dir="$(get_stack_dir_by_name "${stack_name}" || true)"
@ -26,7 +27,7 @@ handle_manage_selected_stack_flow() {
while true; do
apps_action="$(show_manage_stack_apps_menu "${stack_name}" "${stack_dir}" || true)"
case "${apps_action}" in
"Generate apps.json")
"Regenerate apps.json from metadata")
stack_metadata_path="${stack_dir}/metadata.json"
stack_apps_path="${stack_dir}/apps.json"
if [ ! -f "${stack_metadata_path}" ]; then
@ -84,6 +85,64 @@ handle_manage_selected_stack_flow() {
while true; do
docker_action="$(show_manage_stack_docker_menu "${stack_name}" "${stack_dir}" || true)"
case "${docker_action}" in
"Build custom image")
show_warning_message "Starting docker build for stack: ${stack_name}"
if build_stack_custom_image "${stack_dir}"; then
:
else
build_image_status=$?
case "${build_image_status}" in
11)
show_warning_and_wait "Custom image build failed: missing metadata.json in ${stack_dir}." 4
;;
12)
show_warning_and_wait "Custom image build failed: stack env file not found in ${stack_dir}." 4
;;
13)
show_warning_and_wait "Custom image build failed: CUSTOM_IMAGE is missing in stack env file." 4
;;
14)
show_warning_and_wait "Custom image build failed: CUSTOM_TAG is missing in stack env file." 4
;;
15)
show_warning_and_wait "Custom image build failed: frappe_branch missing in metadata.json." 4
;;
16)
show_warning_and_wait "Custom image build failed: could not generate apps.json from metadata app selection." 4
;;
17)
show_warning_and_wait "Custom image build failed: apps.json not found after generation." 4
;;
18)
show_warning_and_wait "Custom image build failed: base64 command is not available in this environment." 4
;;
19)
show_warning_and_wait "Custom image build failed: apps.json could not be base64-encoded." 4
;;
20)
show_warning_and_wait "Custom image build failed: images/layered/Containerfile not found." 4
;;
21)
show_warning_and_wait "Custom image build failed: docker build returned an error. Check the output above." 4
;;
22)
show_warning_and_wait "Custom image build failed: git is required for app branch precheck (git ls-remote)." 4
;;
23)
show_warning_and_wait "Custom image build failed: could not parse app entries from apps.json." 4
;;
24)
show_warning_and_wait "Custom image build failed: app branch precheck failed -> ${EASY_DOCKER_BUILD_ERROR_DETAIL}" 6
;;
*)
show_warning_and_wait "Custom image build failed (${build_image_status})." 4
;;
esac
continue
fi
show_warning_and_wait "Custom image build finished successfully for stack: ${stack_name}" 3
;;
"Generate docker compose from env")
generated_compose_path="$(get_stack_generated_compose_path "${stack_dir}")"
if render_stack_compose_from_metadata "${stack_dir}"; then

View file

@ -16,7 +16,7 @@ get_missing_docker_commands() {
local missing=()
local subcommand=""
for subcommand in ps exec inspect cp; do
for subcommand in ps exec inspect cp build; do
if ! docker_supports_command "${subcommand}"; then
missing+=("docker ${subcommand}")
fi

View file

@ -382,7 +382,7 @@ show_manage_stack_apps_menu() {
--header "Stack apps actions" \
--cursor.foreground 63 \
--selected.foreground 45 \
"Generate apps.json" \
"Regenerate apps.json from metadata" \
"Select apps and branches" \
"Back" \
"Exit and close easy-docker"
@ -400,10 +400,11 @@ show_manage_stack_docker_menu() {
render_box_message "${status_text}" "0 2" >&2
gum choose \
--height 7 \
--height 8 \
--header "Stack docker actions" \
--cursor.foreground 63 \
--selected.foreground 45 \
"Build custom image" \
"Generate docker compose from env" \
"Back" \
"Exit and close easy-docker"

View file

@ -9,7 +9,7 @@ show_tools_menu() {
render_box_message "${status_text}" "0 2" >&2
gum choose \
--height 8 \
--height 9 \
--header "Tools - App Catalog Utilities" \
--cursor.foreground 63 \
--selected.foreground 45 \