mirror of
https://github.com/frappe/frappe_docker.git
synced 2026-06-20 23:05:09 +00:00
refactor(easy-docker): migrate stack json processing to jq
This commit is contained in:
parent
77a0f9e171
commit
0753037544
13 changed files with 1002 additions and 304 deletions
|
|
@ -29,33 +29,11 @@ get_metadata_apps_predefined_csv() {
|
|||
return 1
|
||||
fi
|
||||
|
||||
awk '
|
||||
/"apps"[[:space:]]*:[[:space:]]*{/ {
|
||||
in_apps = 1
|
||||
}
|
||||
in_apps && /"predefined"[[:space:]]*:[[:space:]]*\[/ {
|
||||
in_predefined = 1
|
||||
next
|
||||
}
|
||||
in_predefined && /\]/ {
|
||||
in_predefined = 0
|
||||
next
|
||||
}
|
||||
in_predefined {
|
||||
if (match($0, /"([^"]+)"/, parts)) {
|
||||
if (csv == "") {
|
||||
csv = parts[1]
|
||||
} else {
|
||||
csv = csv "," parts[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
END {
|
||||
if (csv != "") {
|
||||
print csv
|
||||
}
|
||||
}
|
||||
' "${metadata_path}"
|
||||
if ! easy_docker_require_jq; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
easy_docker_run_jq -r '(.apps.predefined // []) | join(",")' "${metadata_path}"
|
||||
}
|
||||
|
||||
get_metadata_apps_custom_lines() {
|
||||
|
|
@ -65,34 +43,11 @@ get_metadata_apps_custom_lines() {
|
|||
return 1
|
||||
fi
|
||||
|
||||
awk '
|
||||
/"apps"[[:space:]]*:[[:space:]]*{/ {
|
||||
in_apps = 1
|
||||
}
|
||||
in_apps && /"custom"[[:space:]]*:[[:space:]]*\[/ {
|
||||
in_custom = 1
|
||||
next
|
||||
}
|
||||
in_custom && /\]/ {
|
||||
in_custom = 0
|
||||
repo = ""
|
||||
branch = ""
|
||||
next
|
||||
}
|
||||
in_custom {
|
||||
if (match($0, /"repo"[[:space:]]*:[[:space:]]*"([^"]+)"/, repo_parts)) {
|
||||
repo = repo_parts[1]
|
||||
}
|
||||
if (match($0, /"branch"[[:space:]]*:[[:space:]]*"([^"]+)"/, branch_parts)) {
|
||||
branch = branch_parts[1]
|
||||
}
|
||||
if (repo != "" && branch != "") {
|
||||
print repo "|" branch
|
||||
repo = ""
|
||||
branch = ""
|
||||
}
|
||||
}
|
||||
' "${metadata_path}"
|
||||
if ! easy_docker_require_jq; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
easy_docker_run_jq -r '(.apps.custom // [])[]? | select(has("repo") and has("branch")) | "\(.repo)|\(.branch)"' "${metadata_path}"
|
||||
}
|
||||
|
||||
get_metadata_apps_predefined_branch_lines() {
|
||||
|
|
@ -102,32 +57,64 @@ get_metadata_apps_predefined_branch_lines() {
|
|||
return 1
|
||||
fi
|
||||
|
||||
awk '
|
||||
/"apps"[[:space:]]*:[[:space:]]*{/ {
|
||||
in_apps = 1
|
||||
}
|
||||
in_apps && /"predefined_branches"[[:space:]]*:[[:space:]]*{/ {
|
||||
in_predefined_branches = 1
|
||||
next
|
||||
}
|
||||
in_predefined_branches && /}/ {
|
||||
in_predefined_branches = 0
|
||||
next
|
||||
}
|
||||
in_predefined_branches {
|
||||
if (match($0, /"([^"]+)"[[:space:]]*:[[:space:]]*"([^"]+)"/, parts)) {
|
||||
print parts[1] "|" parts[2]
|
||||
}
|
||||
}
|
||||
' "${metadata_path}"
|
||||
if ! easy_docker_require_jq; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
easy_docker_run_jq -r '(.apps.predefined_branches // {}) | to_entries[]? | "\(.key)|\(.value)"' "${metadata_path}"
|
||||
}
|
||||
|
||||
get_metadata_apps_predefined_branch_for_id() {
|
||||
local metadata_path="${1}"
|
||||
local app_id_lookup="${2}"
|
||||
local line=""
|
||||
|
||||
if [ ! -f "${metadata_path}" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! easy_docker_require_jq; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2016
|
||||
easy_docker_run_jq -r --arg app_id "${app_id_lookup}" '.apps.predefined_branches[$app_id] // empty' "${metadata_path}"
|
||||
}
|
||||
|
||||
build_metadata_apps_json_object() {
|
||||
local result_var="${1}"
|
||||
local predefined_csv="${2}"
|
||||
local branch_lines="${3}"
|
||||
local custom_apps_lines="${4:-}"
|
||||
local app_id=""
|
||||
local app_branch=""
|
||||
local custom_repo=""
|
||||
local custom_branch=""
|
||||
local predefined_json_entries=""
|
||||
local branch_json_entries=""
|
||||
local custom_json_entries=""
|
||||
local escaped_app_id=""
|
||||
local escaped_branch=""
|
||||
local escaped_repo=""
|
||||
local entry_json=""
|
||||
local line=""
|
||||
local -a predefined_ids=()
|
||||
|
||||
if [ -n "${predefined_csv}" ]; then
|
||||
IFS=',' read -r -a predefined_ids <<<"${predefined_csv}"
|
||||
for app_id in "${predefined_ids[@]}"; do
|
||||
if [ -z "${app_id}" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
escaped_app_id="$(json_escape_string "${app_id}")"
|
||||
entry_json="$(printf ' "%s"' "${escaped_app_id}")"
|
||||
if [ -z "${predefined_json_entries}" ]; then
|
||||
predefined_json_entries="${entry_json}"
|
||||
else
|
||||
predefined_json_entries="${predefined_json_entries}"$',\n'"${entry_json}"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
while IFS= read -r line; do
|
||||
if [ -z "${line}" ]; then
|
||||
|
|
@ -136,13 +123,61 @@ get_metadata_apps_predefined_branch_for_id() {
|
|||
|
||||
app_id="${line%%|*}"
|
||||
app_branch="${line#*|}"
|
||||
if [ "${app_id}" = "${app_id_lookup}" ] && [ -n "${app_branch}" ]; then
|
||||
printf '%s\n' "${app_branch}"
|
||||
return 0
|
||||
if [ -z "${app_id}" ] || [ -z "${app_branch}" ]; then
|
||||
continue
|
||||
fi
|
||||
done < <(get_metadata_apps_predefined_branch_lines "${metadata_path}" || true)
|
||||
|
||||
return 1
|
||||
escaped_app_id="$(json_escape_string "${app_id}")"
|
||||
escaped_branch="$(json_escape_string "${app_branch}")"
|
||||
entry_json="$(printf ' "%s": "%s"' "${escaped_app_id}" "${escaped_branch}")"
|
||||
if [ -z "${branch_json_entries}" ]; then
|
||||
branch_json_entries="${entry_json}"
|
||||
else
|
||||
branch_json_entries="${branch_json_entries}"$',\n'"${entry_json}"
|
||||
fi
|
||||
done <<EOF
|
||||
${branch_lines}
|
||||
EOF
|
||||
|
||||
while IFS= read -r line; do
|
||||
if [ -z "${line}" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
custom_repo="${line%%|*}"
|
||||
custom_branch="${line#*|}"
|
||||
if [ -z "${custom_repo}" ] || [ -z "${custom_branch}" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
escaped_repo="$(json_escape_string "${custom_repo}")"
|
||||
escaped_branch="$(json_escape_string "${custom_branch}")"
|
||||
entry_json="$(printf ' {\n "repo": "%s",\n "branch": "%s"\n }' "${escaped_repo}" "${escaped_branch}")"
|
||||
if [ -z "${custom_json_entries}" ]; then
|
||||
custom_json_entries="${entry_json}"
|
||||
else
|
||||
custom_json_entries="${custom_json_entries}"$',\n'"${entry_json}"
|
||||
fi
|
||||
done <<EOF
|
||||
${custom_apps_lines}
|
||||
EOF
|
||||
|
||||
printf -v "${result_var}" '{\n "predefined": [\n%s\n ],\n "predefined_branches": {\n%s\n },\n "custom": [\n%s\n ]\n }' "${predefined_json_entries}" "${branch_json_entries}" "${custom_json_entries}"
|
||||
}
|
||||
|
||||
render_metadata_apps_json_object_from_metadata() {
|
||||
local result_var="${1}"
|
||||
local metadata_path="${2}"
|
||||
local predefined_csv=""
|
||||
local branch_lines=""
|
||||
local custom_lines=""
|
||||
local apps_json_object=""
|
||||
|
||||
predefined_csv="$(get_metadata_apps_predefined_csv "${metadata_path}" || true)"
|
||||
branch_lines="$(get_metadata_apps_predefined_branch_lines "${metadata_path}" || true)"
|
||||
custom_lines="$(get_metadata_apps_custom_lines "${metadata_path}" || true)"
|
||||
build_metadata_apps_json_object apps_json_object "${predefined_csv}" "${branch_lines}" "${custom_lines}"
|
||||
printf -v "${result_var}" "%s" "${apps_json_object}"
|
||||
}
|
||||
|
||||
build_stack_apps_json_content_from_metadata_apps() {
|
||||
|
|
@ -170,6 +205,10 @@ build_stack_apps_json_content_from_metadata_apps() {
|
|||
return 1
|
||||
fi
|
||||
|
||||
if ! easy_docker_require_jq; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
preset_apps_csv="$(get_metadata_apps_predefined_csv "${metadata_path}" || true)"
|
||||
custom_apps_lines="$(get_metadata_apps_custom_lines "${metadata_path}" || true)"
|
||||
preset_branch="$(get_stack_frappe_branch "${stack_dir}" || true)"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,113 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
render_stack_metadata_top_level_entry_from_json_file() {
|
||||
local metadata_path="${1}"
|
||||
local metadata_key="${2}"
|
||||
local entry_value=""
|
||||
local rendered_entry=""
|
||||
local line=""
|
||||
local delta=0
|
||||
local depth=0
|
||||
local started=0
|
||||
|
||||
while IFS= read -r line || [ -n "${line}" ]; do
|
||||
if [ "${started}" -eq 0 ]; then
|
||||
case "${line}" in
|
||||
" \"${metadata_key}\":"*)
|
||||
entry_value="${line# \""${metadata_key}"\": }"
|
||||
entry_value="${entry_value%,}"
|
||||
if [[ "${entry_value}" == \{* || "${entry_value}" == \[* ]]; then
|
||||
rendered_entry=" \"${metadata_key}\": ${entry_value}"
|
||||
started=1
|
||||
delta="$(count_stack_metadata_json_structure_delta "${entry_value}")"
|
||||
depth=$((depth + delta))
|
||||
if [ "${depth}" -le 0 ]; then
|
||||
printf '%s' "${rendered_entry}"
|
||||
return 0
|
||||
fi
|
||||
else
|
||||
printf ' "%s": %s' "${metadata_key}" "${entry_value}"
|
||||
return 0
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
continue
|
||||
fi
|
||||
|
||||
delta="$(count_stack_metadata_json_structure_delta "${line}")"
|
||||
if [ $((depth + delta)) -le 0 ]; then
|
||||
rendered_entry="${rendered_entry}"$'\n'"${line%,}"
|
||||
printf '%s' "${rendered_entry}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
rendered_entry="${rendered_entry}"$'\n'"${line}"
|
||||
depth=$((depth + delta))
|
||||
done <"${metadata_path}"
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
count_stack_metadata_json_structure_delta() {
|
||||
local line="${1}"
|
||||
local opens=0
|
||||
local closes=0
|
||||
local matches=""
|
||||
|
||||
matches="${line//[^\{]/}"
|
||||
opens=$((opens + ${#matches}))
|
||||
matches="${line//[^\[]/}"
|
||||
opens=$((opens + ${#matches}))
|
||||
matches="${line//[^\}]/}"
|
||||
closes=$((closes + ${#matches}))
|
||||
matches="${line//[^\]]/}"
|
||||
closes=$((closes + ${#matches}))
|
||||
|
||||
printf '%s\n' "$((opens - closes))"
|
||||
}
|
||||
|
||||
build_stack_metadata_top_level_object_content() {
|
||||
local result_var="${1}"
|
||||
local metadata_path="${2}"
|
||||
local object_key="${3}"
|
||||
local object_json="${4}"
|
||||
shift 4
|
||||
local rendered_metadata=""
|
||||
local entry_json=""
|
||||
local metadata_key=""
|
||||
local index=0
|
||||
local total_keys=0
|
||||
local -a ordered_keys=("$@")
|
||||
|
||||
total_keys="${#ordered_keys[@]}"
|
||||
rendered_metadata="{"
|
||||
if [ "${total_keys}" -gt 0 ]; then
|
||||
rendered_metadata="${rendered_metadata}"$'\n'
|
||||
fi
|
||||
|
||||
for index in "${!ordered_keys[@]}"; do
|
||||
metadata_key="${ordered_keys[${index}]}"
|
||||
if [ "${metadata_key}" = "${object_key}" ]; then
|
||||
entry_json="$(printf ' "%s": %s' "${metadata_key}" "${object_json}")"
|
||||
else
|
||||
entry_json="$(render_stack_metadata_top_level_entry_from_json_file "${metadata_path}" "${metadata_key}")" || return 1
|
||||
fi
|
||||
|
||||
rendered_metadata="${rendered_metadata}${entry_json}"
|
||||
if [ "${index}" -lt $((total_keys - 1)) ]; then
|
||||
rendered_metadata="${rendered_metadata},"
|
||||
fi
|
||||
rendered_metadata="${rendered_metadata}"$'\n'
|
||||
done
|
||||
|
||||
rendered_metadata="${rendered_metadata}}"$'\n'
|
||||
if ! printf '%s' "${rendered_metadata}" | easy_docker_run_jq -e 'type == "object"' >/dev/null 2>&1; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
printf -v "${result_var}" "%s" "${rendered_metadata}"
|
||||
}
|
||||
|
||||
persist_stack_metadata_top_level_object() {
|
||||
local stack_dir="${1}"
|
||||
local object_key="${2}"
|
||||
|
|
@ -7,6 +115,11 @@ persist_stack_metadata_top_level_object() {
|
|||
local insert_before_key="${4:-}"
|
||||
local metadata_path=""
|
||||
local metadata_tmp_path=""
|
||||
local metadata_content=""
|
||||
local existing_key=""
|
||||
local inserted=0
|
||||
local -a existing_keys=()
|
||||
local -a ordered_keys=()
|
||||
|
||||
metadata_path="${stack_dir}/metadata.json"
|
||||
metadata_tmp_path="${metadata_path}.tmp"
|
||||
|
|
@ -18,85 +131,41 @@ persist_stack_metadata_top_level_object() {
|
|||
return 1
|
||||
fi
|
||||
|
||||
if ! awk -v object_key="${object_key}" -v object_json="${object_json}" -v insert_before_key="${insert_before_key}" '
|
||||
BEGIN {
|
||||
target_regex = "^ \"" object_key "\"[[:space:]]*:"
|
||||
before_regex = ""
|
||||
if (insert_before_key != "") {
|
||||
before_regex = "^ \"" insert_before_key "\"[[:space:]]*:"
|
||||
}
|
||||
in_target = 0
|
||||
target_depth = 0
|
||||
inserted = 0
|
||||
prev = ""
|
||||
}
|
||||
function flush_prev() {
|
||||
if (prev != "") {
|
||||
print prev
|
||||
prev = ""
|
||||
}
|
||||
}
|
||||
{
|
||||
if (!in_target && $0 ~ target_regex) {
|
||||
flush_prev()
|
||||
if (object_key == "wizard") {
|
||||
print " \"" object_key "\": " object_json
|
||||
} else {
|
||||
print " \"" object_key "\": " object_json ","
|
||||
}
|
||||
in_target = 1
|
||||
inserted = 1
|
||||
if ($0 ~ /{/) {
|
||||
target_depth += gsub(/{/, "{", $0)
|
||||
target_depth -= gsub(/}/, "}", $0)
|
||||
} else {
|
||||
target_depth = 0
|
||||
}
|
||||
if (target_depth <= 0) {
|
||||
in_target = 0
|
||||
}
|
||||
next
|
||||
}
|
||||
if ! easy_docker_require_jq; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
if (in_target) {
|
||||
target_depth += gsub(/{/, "{", $0)
|
||||
target_depth -= gsub(/}/, "}", $0)
|
||||
if (target_depth <= 0) {
|
||||
in_target = 0
|
||||
}
|
||||
next
|
||||
}
|
||||
if ! easy_docker_run_jq -e 'type == "object"' "${metadata_path}" >/dev/null 2>&1; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
if (!inserted && before_regex != "" && $0 ~ before_regex) {
|
||||
flush_prev()
|
||||
print " \"" object_key "\": " object_json ","
|
||||
inserted = 1
|
||||
}
|
||||
object_json="${object_json%$'\n'}"
|
||||
if ! printf '%s\n' "${object_json}" | easy_docker_run_jq -e 'type == "object"' >/dev/null 2>&1; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
if (!inserted && $0 ~ /^}/) {
|
||||
if (prev != "") {
|
||||
if (prev !~ /,[[:space:]]*$/) {
|
||||
prev = prev ","
|
||||
}
|
||||
print prev
|
||||
prev = ""
|
||||
}
|
||||
print " \"" object_key "\": " object_json
|
||||
inserted = 1
|
||||
print $0
|
||||
next
|
||||
}
|
||||
mapfile -t existing_keys < <(easy_docker_run_jq -r 'keys_unsorted[]' "${metadata_path}") || return 1
|
||||
|
||||
flush_prev()
|
||||
prev = $0
|
||||
}
|
||||
END {
|
||||
flush_prev()
|
||||
if (!inserted) {
|
||||
exit 2
|
||||
}
|
||||
}
|
||||
' "${metadata_path}" >"${metadata_tmp_path}"; then
|
||||
for existing_key in "${existing_keys[@]}"; do
|
||||
if [ "${existing_key}" = "${object_key}" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ "${inserted}" -eq 0 ] && [ -n "${insert_before_key}" ] && [ "${existing_key}" = "${insert_before_key}" ]; then
|
||||
ordered_keys+=("${object_key}")
|
||||
inserted=1
|
||||
fi
|
||||
|
||||
ordered_keys+=("${existing_key}")
|
||||
done
|
||||
|
||||
if [ "${inserted}" -eq 0 ]; then
|
||||
ordered_keys+=("${object_key}")
|
||||
fi
|
||||
|
||||
build_stack_metadata_top_level_object_content metadata_content "${metadata_path}" "${object_key}" "${object_json}" "${ordered_keys[@]}" || return 1
|
||||
|
||||
if ! printf '%s' "${metadata_content}" >"${metadata_tmp_path}"; then
|
||||
rm -f -- "${metadata_tmp_path}" >/dev/null 2>&1 || true
|
||||
return 1
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -31,6 +31,9 @@ build_stack_custom_image() {
|
|||
if [ ! -f "${env_path}" ]; then
|
||||
return 12
|
||||
fi
|
||||
if ! easy_docker_require_jq; then
|
||||
return 25
|
||||
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)"
|
||||
|
|
@ -58,13 +61,8 @@ build_stack_custom_image() {
|
|||
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}"
|
||||
)"
|
||||
easy_docker_run_jq -r '.[]? | select((.url // "") != "" and (.branch // "") != "") | "\(.url)|\(.branch)"' "${apps_json_path}"
|
||||
)" || return 23
|
||||
if [ -z "${apps_refs_lines}" ]; then
|
||||
return 23
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -119,12 +119,12 @@ get_metadata_string_field() {
|
|||
return 1
|
||||
fi
|
||||
|
||||
awk -v field_name="${field_name}" '
|
||||
match($0, "\"" field_name "\"[[:space:]]*:[[:space:]]*\"([^\"]*)\"", parts) {
|
||||
print parts[1]
|
||||
exit
|
||||
}
|
||||
' "${metadata_path}"
|
||||
if ! easy_docker_require_jq; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2016
|
||||
easy_docker_run_jq -r --arg field_name "${field_name}" '[.. | objects | .[$field_name]? | select(type == "string")][0] // empty' "${metadata_path}"
|
||||
}
|
||||
|
||||
get_env_file_key_value() {
|
||||
|
|
@ -364,19 +364,9 @@ get_metadata_compose_files_lines() {
|
|||
return 1
|
||||
fi
|
||||
|
||||
awk '
|
||||
/"compose_files"[[:space:]]*:[[:space:]]*\[/ {
|
||||
in_compose_files = 1
|
||||
next
|
||||
}
|
||||
in_compose_files && /\]/ {
|
||||
in_compose_files = 0
|
||||
exit
|
||||
}
|
||||
in_compose_files {
|
||||
if (match($0, /"([^"]+)"/, parts)) {
|
||||
print parts[1]
|
||||
}
|
||||
}
|
||||
' "${metadata_path}"
|
||||
if ! easy_docker_require_jq; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
easy_docker_run_jq -r '([.. | objects | .compose_files? | select(type == "array")] | .[0] // [])[]?' "${metadata_path}"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,27 +8,12 @@ get_metadata_site_string_field() {
|
|||
return 1
|
||||
fi
|
||||
|
||||
awk -v field_name="${field_name}" '
|
||||
/"site"[[:space:]]*:[[:space:]]*{/ {
|
||||
in_site = 1
|
||||
site_depth = 1
|
||||
next
|
||||
}
|
||||
in_site {
|
||||
if (match($0, "\"" field_name "\"[[:space:]]*:[[:space:]]*\"([^\"]*)\"", parts)) {
|
||||
print parts[1]
|
||||
exit
|
||||
}
|
||||
if ! easy_docker_require_jq; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
line = $0
|
||||
open_count = gsub(/{/, "{", line)
|
||||
close_count = gsub(/}/, "}", line)
|
||||
site_depth += open_count - close_count
|
||||
if (site_depth <= 0) {
|
||||
exit
|
||||
}
|
||||
}
|
||||
' "${metadata_path}"
|
||||
# shellcheck disable=SC2016
|
||||
easy_docker_run_jq -r --arg field_name "${field_name}" '.site[$field_name] // empty' "${metadata_path}"
|
||||
}
|
||||
|
||||
get_metadata_site_apps_installed_lines() {
|
||||
|
|
@ -38,35 +23,11 @@ get_metadata_site_apps_installed_lines() {
|
|||
return 1
|
||||
fi
|
||||
|
||||
awk '
|
||||
/"site"[[:space:]]*:[[:space:]]*{/ {
|
||||
in_site = 1
|
||||
site_depth = 1
|
||||
next
|
||||
}
|
||||
in_site && /"apps_installed"[[:space:]]*:[[:space:]]*\[/ {
|
||||
in_apps_installed = 1
|
||||
next
|
||||
}
|
||||
in_apps_installed && /\]/ {
|
||||
in_apps_installed = 0
|
||||
next
|
||||
}
|
||||
in_apps_installed {
|
||||
if (match($0, /"([^"]+)"/, parts)) {
|
||||
print parts[1]
|
||||
}
|
||||
}
|
||||
in_site {
|
||||
line = $0
|
||||
open_count = gsub(/{/, "{", line)
|
||||
close_count = gsub(/}/, "}", line)
|
||||
site_depth += open_count - close_count
|
||||
if (site_depth <= 0) {
|
||||
exit
|
||||
}
|
||||
}
|
||||
' "${metadata_path}"
|
||||
if ! easy_docker_require_jq; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
easy_docker_run_jq -r '(.site.apps_installed // [])[]? | select(type == "string")' "${metadata_path}"
|
||||
}
|
||||
|
||||
get_stack_site_name() {
|
||||
|
|
|
|||
|
|
@ -68,11 +68,9 @@ persist_stack_site_metadata() {
|
|||
local updated_at="${9:-}"
|
||||
local last_backup_at="${10-__KEEP_CURRENT__}"
|
||||
local metadata_path=""
|
||||
local metadata_tmp_path=""
|
||||
local site_json_object=""
|
||||
|
||||
metadata_path="${stack_dir}/metadata.json"
|
||||
metadata_tmp_path="${metadata_path}.tmp"
|
||||
if [ ! -f "${metadata_path}" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
|
@ -83,77 +81,7 @@ persist_stack_site_metadata() {
|
|||
|
||||
build_stack_site_metadata_json_object site_json_object "${site_mode}" "${site_name}" "${apps_installed_lines}" "${last_action}" "${last_error}" "${error_log_path}" "${created_at}" "${updated_at}" "${last_backup_at}"
|
||||
|
||||
if ! awk -v site_object="${site_json_object}" '
|
||||
BEGIN {
|
||||
in_site = 0
|
||||
inserted = 0
|
||||
site_depth = 0
|
||||
prev = ""
|
||||
}
|
||||
function flush_prev() {
|
||||
if (prev != "") {
|
||||
print prev
|
||||
prev = ""
|
||||
}
|
||||
}
|
||||
{
|
||||
if (!in_site && $0 ~ /^ "site"[[:space:]]*:[[:space:]]*{/) {
|
||||
in_site = 1
|
||||
site_depth = 1
|
||||
next
|
||||
}
|
||||
|
||||
if (in_site) {
|
||||
line = $0
|
||||
open_count = gsub(/{/, "{", line)
|
||||
close_count = gsub(/}/, "}", line)
|
||||
site_depth += open_count - close_count
|
||||
if (site_depth <= 0) {
|
||||
in_site = 0
|
||||
}
|
||||
next
|
||||
}
|
||||
|
||||
if (!inserted && $0 ~ /^}$/) {
|
||||
if (prev != "") {
|
||||
if (prev ~ /,$/) {
|
||||
print prev
|
||||
} else {
|
||||
print prev ","
|
||||
}
|
||||
prev = ""
|
||||
}
|
||||
print " \"site\": " site_object
|
||||
print "}"
|
||||
inserted = 1
|
||||
next
|
||||
}
|
||||
|
||||
flush_prev()
|
||||
prev = $0
|
||||
}
|
||||
END {
|
||||
if (!inserted) {
|
||||
if (prev != "") {
|
||||
if (prev ~ /,$/) {
|
||||
print prev
|
||||
} else {
|
||||
print prev ","
|
||||
}
|
||||
}
|
||||
print " \"site\": " site_object
|
||||
print "}"
|
||||
} else if (prev != "") {
|
||||
print prev
|
||||
}
|
||||
}
|
||||
' "${metadata_path}" >"${metadata_tmp_path}"; then
|
||||
rm -f -- "${metadata_tmp_path}" >/dev/null 2>&1 || true
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! mv -- "${metadata_tmp_path}" "${metadata_path}"; then
|
||||
rm -f -- "${metadata_tmp_path}" >/dev/null 2>&1 || true
|
||||
if ! persist_stack_metadata_top_level_object "${stack_dir}" "site" "${site_json_object}"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
|
|
|||
|
|
@ -72,6 +72,9 @@ run_build_stack_custom_image_with_feedback() {
|
|||
24)
|
||||
show_warning_and_wait "Custom image build failed: app branch precheck failed -> ${EASY_DOCKER_BUILD_ERROR_DETAIL}" 6
|
||||
;;
|
||||
25)
|
||||
show_warning_and_wait "Custom image build failed: jq is required for stack metadata and apps.json processing." 4
|
||||
;;
|
||||
*)
|
||||
show_warning_and_wait "Custom image build failed (${build_image_status})." 4
|
||||
;;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ load 'test_helper.bash'
|
|||
setup() {
|
||||
easy_docker_test_begin
|
||||
easy_docker_test_source_core_render_modules
|
||||
easy_docker_test_install_jq_stub
|
||||
unset ERPNEXT_VERSION
|
||||
unset FRAPPE_BRANCH
|
||||
}
|
||||
|
|
@ -106,6 +107,38 @@ EOF
|
|||
[ "${output}" = "${expected}" ]
|
||||
}
|
||||
|
||||
@test "get_metadata_compose_files_lines keeps the first compose_files array only" {
|
||||
local sandbox_root=""
|
||||
local stack_dir=""
|
||||
local expected=""
|
||||
|
||||
sandbox_root="$(easy_docker_test_create_repo_sandbox "compose-lines-first-array")"
|
||||
easy_docker_test_override_repo_root "${sandbox_root}"
|
||||
stack_dir="$(easy_docker_test_stack_dir "compose-lines-first-array")"
|
||||
mkdir -p "${stack_dir}"
|
||||
|
||||
cat >"${stack_dir}/metadata.json" <<'EOF'
|
||||
{
|
||||
"stack_name": "compose-lines-first-array",
|
||||
"compose_files": [
|
||||
"compose.yaml",
|
||||
"overrides/compose.proxy.yaml"
|
||||
],
|
||||
"wizard": {
|
||||
"compose_files": [
|
||||
"should-not-appear.yaml"
|
||||
]
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
expected=$'compose.yaml\noverrides/compose.proxy.yaml'
|
||||
|
||||
run get_metadata_compose_files_lines "${stack_dir}/metadata.json"
|
||||
[ "${status}" -eq 0 ]
|
||||
[ "${output}" = "${expected}" ]
|
||||
}
|
||||
|
||||
@test "render_stack_compose_from_metadata writes generated compose with stubbed docker config" {
|
||||
local sandbox_root=""
|
||||
local stack_dir=""
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ load 'test_helper.bash'
|
|||
setup() {
|
||||
easy_docker_test_begin
|
||||
easy_docker_test_source_core_render_modules
|
||||
easy_docker_test_install_jq_stub
|
||||
}
|
||||
|
||||
teardown() {
|
||||
|
|
|
|||
124
tests/easy-docker/56_site_metadata_read.bats
Executable file
124
tests/easy-docker/56_site_metadata_read.bats
Executable file
|
|
@ -0,0 +1,124 @@
|
|||
#!/usr/bin/env bats
|
||||
|
||||
load 'test_helper.bash'
|
||||
|
||||
setup() {
|
||||
local repo_root=""
|
||||
|
||||
easy_docker_test_begin
|
||||
easy_docker_test_source_apps_modules
|
||||
easy_docker_test_install_jq_stub
|
||||
|
||||
repo_root="$(easy_docker_test_repo_root)"
|
||||
# shellcheck source=scripts/easy-docker/lib/app/wizard/common/site/metadata.sh
|
||||
source "${repo_root}/scripts/easy-docker/lib/app/wizard/common/site/metadata.sh"
|
||||
}
|
||||
|
||||
teardown() {
|
||||
easy_docker_test_end
|
||||
}
|
||||
|
||||
@test "site metadata readers keep existing values independent of JSON layout" {
|
||||
local sandbox_root=""
|
||||
local stack_dir=""
|
||||
local expected_apps=""
|
||||
|
||||
sandbox_root="$(easy_docker_test_create_repo_sandbox "site-metadata-read")"
|
||||
easy_docker_test_override_repo_root "${sandbox_root}"
|
||||
stack_dir="$(easy_docker_test_stack_dir "site-metadata-read")"
|
||||
mkdir -p "${stack_dir}"
|
||||
|
||||
cat >"${stack_dir}/metadata.json" <<'EOF'
|
||||
{
|
||||
"site": {
|
||||
"name": "site-a.local",
|
||||
"last_error": "",
|
||||
"created_at": "2026-04-20T10:00:00Z",
|
||||
"last_backup_at": "2026-04-20T12:00:00Z",
|
||||
"apps_installed": [
|
||||
"erpnext",
|
||||
"crm",
|
||||
"my_custom_app"
|
||||
]
|
||||
}
|
||||
}
|
||||
EOF
|
||||
expected_apps=$'erpnext\ncrm\nmy_custom_app'
|
||||
|
||||
run get_stack_site_name "${stack_dir}"
|
||||
[ "${status}" -eq 0 ]
|
||||
[ "${output}" = "site-a.local" ]
|
||||
|
||||
run get_stack_site_created_at "${stack_dir}"
|
||||
[ "${status}" -eq 0 ]
|
||||
[ "${output}" = "2026-04-20T10:00:00Z" ]
|
||||
|
||||
run get_stack_site_last_backup_at "${stack_dir}"
|
||||
[ "${status}" -eq 0 ]
|
||||
[ "${output}" = "2026-04-20T12:00:00Z" ]
|
||||
|
||||
run get_stack_site_apps_installed_lines "${stack_dir}"
|
||||
[ "${status}" -eq 0 ]
|
||||
[ "${output}" = "${expected_apps}" ]
|
||||
}
|
||||
|
||||
@test "persist_stack_site_metadata keeps the canonical site layout and preserves top-level metadata order" {
|
||||
local sandbox_root=""
|
||||
local stack_dir=""
|
||||
local expected_metadata=""
|
||||
|
||||
sandbox_root="$(easy_docker_test_create_repo_sandbox "site-metadata-write")"
|
||||
easy_docker_test_override_repo_root "${sandbox_root}"
|
||||
stack_dir="$(easy_docker_test_stack_dir "site-metadata-write")"
|
||||
mkdir -p "${stack_dir}"
|
||||
|
||||
cat >"${stack_dir}/metadata.json" <<'EOF'
|
||||
{
|
||||
"schema_version": 1,
|
||||
"stack_name": "site-metadata-write",
|
||||
"setup_type": "production",
|
||||
"frappe_branch": "version-16",
|
||||
"created_at": "2026-04-20T10:00:00Z",
|
||||
"wizard": {
|
||||
"topology": "single-host"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
if ! persist_stack_site_metadata "${stack_dir}" "single-site" "site-a.local" $'erpnext\ncrm' "create-site" "" "" "2026-04-20T10:00:00Z" "2026-04-20T12:00:00Z" ""; then
|
||||
false
|
||||
fi
|
||||
|
||||
expected_metadata="$(
|
||||
cat <<'EOF'
|
||||
{
|
||||
"schema_version": 1,
|
||||
"stack_name": "site-metadata-write",
|
||||
"setup_type": "production",
|
||||
"frappe_branch": "version-16",
|
||||
"created_at": "2026-04-20T10:00:00Z",
|
||||
"wizard": {
|
||||
"topology": "single-host"
|
||||
},
|
||||
"site": {
|
||||
"mode": "single-site",
|
||||
"name": "site-a.local",
|
||||
"apps_installed": [
|
||||
"erpnext",
|
||||
"crm"
|
||||
],
|
||||
"last_action": "create-site",
|
||||
"last_error": "",
|
||||
"error_log_path": "",
|
||||
"created_at": "2026-04-20T10:00:00Z",
|
||||
"updated_at": "2026-04-20T12:00:00Z",
|
||||
"last_backup_at": ""
|
||||
}
|
||||
}
|
||||
EOF
|
||||
)"
|
||||
|
||||
run cat "${stack_dir}/metadata.json"
|
||||
[ "${status}" -eq 0 ]
|
||||
[ "${output}" = "${expected_metadata}" ]
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ load 'test_helper.bash'
|
|||
setup() {
|
||||
easy_docker_test_begin
|
||||
easy_docker_test_source_core_render_modules
|
||||
easy_docker_test_install_jq_stub
|
||||
}
|
||||
|
||||
teardown() {
|
||||
|
|
|
|||
273
tests/easy-docker/65_apps_jq_migration.bats
Executable file
273
tests/easy-docker/65_apps_jq_migration.bats
Executable file
|
|
@ -0,0 +1,273 @@
|
|||
#!/usr/bin/env bats
|
||||
|
||||
load 'test_helper.bash'
|
||||
|
||||
setup() {
|
||||
easy_docker_test_begin
|
||||
easy_docker_test_source_apps_modules
|
||||
easy_docker_test_install_jq_stub
|
||||
}
|
||||
|
||||
teardown() {
|
||||
easy_docker_test_end
|
||||
}
|
||||
|
||||
write_predefined_apps_catalog() {
|
||||
local sandbox_root="${1}"
|
||||
|
||||
mkdir -p "${sandbox_root}/scripts/easy-docker/config"
|
||||
cat >"${sandbox_root}/scripts/easy-docker/config/apps.tsv" <<'EOF'
|
||||
erpnext ERPNext https://github.com/frappe/erpnext version-16 version-16,version-15
|
||||
crm CRM https://github.com/frappe/crm main main,develop
|
||||
EOF
|
||||
}
|
||||
|
||||
write_containerfile_fixture() {
|
||||
local sandbox_root="${1}"
|
||||
|
||||
mkdir -p "${sandbox_root}/images/layered"
|
||||
cat >"${sandbox_root}/images/layered/Containerfile" <<'EOF'
|
||||
FROM scratch
|
||||
EOF
|
||||
}
|
||||
|
||||
write_stack_metadata_fixture() {
|
||||
local stack_dir="${1}"
|
||||
|
||||
cat >"${stack_dir}/metadata.json" <<'EOF'
|
||||
{
|
||||
"schema_version": 1,
|
||||
"stack_name": "my-production-stack",
|
||||
"setup_type": "production",
|
||||
"frappe_branch": "version-16",
|
||||
"created_at": "2026-04-08T16:12:09Z",
|
||||
"apps": {
|
||||
"predefined": [
|
||||
"erpnext",
|
||||
"crm"
|
||||
],
|
||||
"predefined_branches": {
|
||||
"erpnext": "version-16",
|
||||
"crm": "main"
|
||||
},
|
||||
"custom": [
|
||||
{
|
||||
"repo": "https://github.com/example/custom-app",
|
||||
"branch": "stable"
|
||||
}
|
||||
]
|
||||
},
|
||||
"wizard": {
|
||||
"topology": "single-host",
|
||||
"selection": {
|
||||
"proxy_mode_id": "traefik-http"
|
||||
},
|
||||
"env": {
|
||||
"CUSTOM_IMAGE": "production_image",
|
||||
"CUSTOM_TAG": "v1.0.0"
|
||||
},
|
||||
"compose_files": [
|
||||
"compose.yaml",
|
||||
"overrides/compose.proxy.yaml"
|
||||
],
|
||||
"updated_at": "2026-04-08T16:19:02Z"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
@test "metadata app readers use jq and keep expected line formats" {
|
||||
local sandbox_root=""
|
||||
local stack_dir=""
|
||||
local expected_custom=""
|
||||
local expected_branches=""
|
||||
|
||||
sandbox_root="$(easy_docker_test_create_repo_sandbox "apps-reader")"
|
||||
easy_docker_test_override_repo_root "${sandbox_root}"
|
||||
stack_dir="$(easy_docker_test_stack_dir "apps-reader")"
|
||||
mkdir -p "${stack_dir}"
|
||||
write_stack_metadata_fixture "${stack_dir}"
|
||||
|
||||
expected_custom='https://github.com/example/custom-app|stable'
|
||||
expected_branches=$'erpnext|version-16\ncrm|main'
|
||||
|
||||
run get_metadata_apps_predefined_csv "${stack_dir}/metadata.json"
|
||||
[ "${status}" -eq 0 ]
|
||||
[ "${output}" = "erpnext,crm" ]
|
||||
|
||||
run get_metadata_apps_custom_lines "${stack_dir}/metadata.json"
|
||||
[ "${status}" -eq 0 ]
|
||||
[ "${output}" = "${expected_custom}" ]
|
||||
|
||||
run get_metadata_apps_predefined_branch_lines "${stack_dir}/metadata.json"
|
||||
[ "${status}" -eq 0 ]
|
||||
[ "${output}" = "${expected_branches}" ]
|
||||
}
|
||||
|
||||
@test "persist_stack_metadata_apps_object keeps apps before wizard with legacy formatting" {
|
||||
local sandbox_root=""
|
||||
local stack_dir=""
|
||||
local metadata_path=""
|
||||
local apps_json_object=""
|
||||
local expected=""
|
||||
|
||||
sandbox_root="$(easy_docker_test_create_repo_sandbox "apps-persist")"
|
||||
easy_docker_test_override_repo_root "${sandbox_root}"
|
||||
stack_dir="$(easy_docker_test_stack_dir "apps-persist")"
|
||||
mkdir -p "${stack_dir}"
|
||||
metadata_path="${stack_dir}/metadata.json"
|
||||
|
||||
cat >"${metadata_path}" <<'EOF'
|
||||
{
|
||||
"schema_version": 1,
|
||||
"stack_name": "my-production-stack",
|
||||
"wizard": {
|
||||
"topology": "single-host"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
build_metadata_apps_json_object apps_json_object "erpnext,crm" $'erpnext|version-16\ncrm|main' ""
|
||||
|
||||
run persist_stack_metadata_apps_object "${stack_dir}" "${apps_json_object}"
|
||||
[ "${status}" -eq 0 ]
|
||||
|
||||
expected=$'{\n "schema_version": 1,\n "stack_name": "my-production-stack",\n "apps": {\n "predefined": [\n "erpnext",\n "crm"\n ],\n "predefined_branches": {\n "erpnext": "version-16",\n "crm": "main"\n },\n "custom": [\n\n ]\n },\n "wizard": {\n "topology": "single-host"\n }\n}\n'
|
||||
|
||||
run cat "${metadata_path}"
|
||||
[ "${status}" -eq 0 ]
|
||||
[ "${output}"$'\n' = "${expected}" ]
|
||||
}
|
||||
|
||||
@test "persist_stack_metadata_wizard_object preserves existing apps formatting" {
|
||||
local sandbox_root=""
|
||||
local stack_dir=""
|
||||
local metadata_path=""
|
||||
local wizard_json_object=""
|
||||
local expected=""
|
||||
|
||||
sandbox_root="$(easy_docker_test_create_repo_sandbox "wizard-persist")"
|
||||
easy_docker_test_override_repo_root "${sandbox_root}"
|
||||
stack_dir="$(easy_docker_test_stack_dir "wizard-persist")"
|
||||
mkdir -p "${stack_dir}"
|
||||
metadata_path="${stack_dir}/metadata.json"
|
||||
write_stack_metadata_fixture "${stack_dir}"
|
||||
|
||||
wizard_json_object=$'{\n "topology": "single-host",\n "selection": {\n "proxy_mode_id": "traefik-https"\n },\n "env": {\n "CUSTOM_IMAGE": "production_image",\n "CUSTOM_TAG": "v2.0.0"\n }\n }'
|
||||
|
||||
run persist_stack_metadata_wizard_object "${stack_dir}" "${wizard_json_object}"
|
||||
[ "${status}" -eq 0 ]
|
||||
|
||||
expected=$'{\n "schema_version": 1,\n "stack_name": "my-production-stack",\n "setup_type": "production",\n "frappe_branch": "version-16",\n "created_at": "2026-04-08T16:12:09Z",\n "apps": {\n "predefined": [\n "erpnext",\n "crm"\n ],\n "predefined_branches": {\n "erpnext": "version-16",\n "crm": "main"\n },\n "custom": [\n {\n "repo": "https://github.com/example/custom-app",\n "branch": "stable"\n }\n ]\n },\n "wizard": {\n "topology": "single-host",\n "selection": {\n "proxy_mode_id": "traefik-https"\n },\n "env": {\n "CUSTOM_IMAGE": "production_image",\n "CUSTOM_TAG": "v2.0.0"\n }\n }\n}\n'
|
||||
|
||||
run cat "${metadata_path}"
|
||||
[ "${status}" -eq 0 ]
|
||||
[ "${output}"$'\n' = "${expected}" ]
|
||||
}
|
||||
|
||||
@test "build_stack_apps_json_content_from_metadata_apps keeps apps.json output format" {
|
||||
local sandbox_root=""
|
||||
local stack_dir=""
|
||||
local apps_json_content=""
|
||||
local expected=""
|
||||
|
||||
sandbox_root="$(easy_docker_test_create_repo_sandbox "apps-json")"
|
||||
easy_docker_test_override_repo_root "${sandbox_root}"
|
||||
write_predefined_apps_catalog "${sandbox_root}"
|
||||
stack_dir="$(easy_docker_test_stack_dir "apps-json")"
|
||||
mkdir -p "${stack_dir}"
|
||||
write_stack_metadata_fixture "${stack_dir}"
|
||||
|
||||
if ! build_stack_apps_json_content_from_metadata_apps apps_json_content "${stack_dir}"; then
|
||||
false
|
||||
fi
|
||||
|
||||
expected=$'[\n {"url": "https://github.com/frappe/erpnext", "branch": "version-16"},\n {"url": "https://github.com/frappe/crm", "branch": "main"},\n {"url": "https://github.com/example/custom-app", "branch": "stable"}\n]\n'
|
||||
|
||||
[ "${apps_json_content}" = "${expected}" ]
|
||||
}
|
||||
|
||||
@test "build_stack_custom_image fails clearly when jq is unavailable" {
|
||||
local sandbox_root=""
|
||||
local stack_dir=""
|
||||
local env_path=""
|
||||
|
||||
sandbox_root="$(easy_docker_test_create_repo_sandbox "build-no-jq")"
|
||||
easy_docker_test_override_repo_root "${sandbox_root}"
|
||||
write_predefined_apps_catalog "${sandbox_root}"
|
||||
write_containerfile_fixture "${sandbox_root}"
|
||||
stack_dir="$(easy_docker_test_stack_dir "build-no-jq")"
|
||||
mkdir -p "${stack_dir}"
|
||||
write_stack_metadata_fixture "${stack_dir}"
|
||||
env_path="${stack_dir}/build-no-jq.env"
|
||||
|
||||
cat >"${env_path}" <<'EOF'
|
||||
CUSTOM_IMAGE=production_image
|
||||
CUSTOM_TAG=v1.0.0
|
||||
EOF
|
||||
|
||||
get_easy_docker_jq_command() {
|
||||
return 1
|
||||
}
|
||||
|
||||
run build_stack_custom_image "${stack_dir}"
|
||||
[ "${status}" -eq 25 ]
|
||||
}
|
||||
|
||||
@test "build_stack_custom_image parses apps.json with jq before git branch checks" {
|
||||
local sandbox_root=""
|
||||
local stack_dir=""
|
||||
local env_path=""
|
||||
local git_log=""
|
||||
local docker_log=""
|
||||
|
||||
sandbox_root="$(easy_docker_test_create_repo_sandbox "build-with-jq")"
|
||||
easy_docker_test_override_repo_root "${sandbox_root}"
|
||||
write_predefined_apps_catalog "${sandbox_root}"
|
||||
write_containerfile_fixture "${sandbox_root}"
|
||||
stack_dir="$(easy_docker_test_stack_dir "build-with-jq")"
|
||||
mkdir -p "${stack_dir}"
|
||||
write_stack_metadata_fixture "${stack_dir}"
|
||||
env_path="${stack_dir}/my-production-stack.env"
|
||||
|
||||
cat >"${env_path}" <<'EOF'
|
||||
CUSTOM_IMAGE=production_image
|
||||
CUSTOM_TAG=v1.0.0
|
||||
EOF
|
||||
|
||||
git_log="${EASY_DOCKER_TEST_TMPDIR}/git.log"
|
||||
docker_log="${EASY_DOCKER_TEST_TMPDIR}/docker.log"
|
||||
|
||||
# shellcheck disable=SC2016
|
||||
easy_docker_test_write_bin_command git \
|
||||
'set -euo pipefail' \
|
||||
"printf '%s\n' \"git \$*\" >>\"${git_log}\"" \
|
||||
'if [ "${1:-}" = "ls-remote" ]; then' \
|
||||
' exit 0' \
|
||||
'fi' \
|
||||
'exit 64'
|
||||
|
||||
easy_docker_test_write_bin_command docker \
|
||||
'set -euo pipefail' \
|
||||
"printf '%s\n' \"docker \$*\" >>\"${docker_log}\"" \
|
||||
'exit 0'
|
||||
|
||||
easy_docker_test_write_bin_command base64 \
|
||||
'set -euo pipefail' \
|
||||
'printf "%s\n" "W3sidXJsIjogImh0dHBzOi8vZXhhbXBsZS5pbnZhbGlkL2FwcCIsICJicmFuY2giOiAidmVyc2lvbi0xNiJ9XQ=="'
|
||||
|
||||
easy_docker_test_prepend_bin_dir
|
||||
|
||||
run build_stack_custom_image "${stack_dir}"
|
||||
[ "${status}" -eq 0 ]
|
||||
|
||||
run cat "${git_log}"
|
||||
[ "${status}" -eq 0 ]
|
||||
[[ "${output}" == *'git ls-remote --exit-code --heads https://github.com/frappe/erpnext version-16'* ]]
|
||||
[[ "${output}" == *'git ls-remote --exit-code --heads https://github.com/frappe/crm main'* ]]
|
||||
[[ "${output}" == *'git ls-remote --exit-code --heads https://github.com/example/custom-app stable'* ]]
|
||||
|
||||
run cat "${docker_log}"
|
||||
[ "${status}" -eq 0 ]
|
||||
[[ "${output}" == *'docker build -f '* ]]
|
||||
}
|
||||
|
|
@ -61,6 +61,8 @@ easy_docker_test_source_common_modules() {
|
|||
source "${repo_root}/scripts/easy-docker/lib/core/commands.sh"
|
||||
# shellcheck source=scripts/easy-docker/lib/core/messages.sh
|
||||
source "${repo_root}/scripts/easy-docker/lib/core/messages.sh"
|
||||
# shellcheck source=scripts/easy-docker/lib/core/json.sh
|
||||
source "${repo_root}/scripts/easy-docker/lib/core/json.sh"
|
||||
}
|
||||
|
||||
easy_docker_test_source_core_render_modules() {
|
||||
|
|
@ -76,6 +78,27 @@ easy_docker_test_source_core_render_modules() {
|
|||
source "${repo_root}/scripts/easy-docker/lib/app/wizard/common/compose/render.sh"
|
||||
}
|
||||
|
||||
easy_docker_test_source_apps_modules() {
|
||||
local repo_root=""
|
||||
|
||||
repo_root="$(easy_docker_test_repo_root)"
|
||||
|
||||
easy_docker_test_source_common_modules
|
||||
|
||||
# shellcheck source=scripts/easy-docker/lib/app/wizard/common/core.sh
|
||||
source "${repo_root}/scripts/easy-docker/lib/app/wizard/common/core.sh"
|
||||
# shellcheck source=scripts/easy-docker/lib/app/wizard/common/helpers.sh
|
||||
source "${repo_root}/scripts/easy-docker/lib/app/wizard/common/helpers.sh"
|
||||
# shellcheck source=scripts/easy-docker/lib/app/wizard/common/apps/metadata.sh
|
||||
source "${repo_root}/scripts/easy-docker/lib/app/wizard/common/apps/metadata.sh"
|
||||
# shellcheck source=scripts/easy-docker/lib/app/wizard/common/apps/persistence.sh
|
||||
source "${repo_root}/scripts/easy-docker/lib/app/wizard/common/apps/persistence.sh"
|
||||
# shellcheck source=scripts/easy-docker/lib/app/wizard/common/apps/catalog.sh
|
||||
source "${repo_root}/scripts/easy-docker/lib/app/wizard/common/apps/catalog.sh"
|
||||
# shellcheck source=scripts/easy-docker/lib/app/wizard/common/compose/build.sh
|
||||
source "${repo_root}/scripts/easy-docker/lib/app/wizard/common/compose/build.sh"
|
||||
}
|
||||
|
||||
easy_docker_test_source_docker_modules() {
|
||||
local repo_root=""
|
||||
|
||||
|
|
@ -87,6 +110,17 @@ easy_docker_test_source_docker_modules() {
|
|||
source "${repo_root}/scripts/easy-docker/lib/checks/docker.sh"
|
||||
}
|
||||
|
||||
easy_docker_test_source_jq_modules() {
|
||||
local repo_root=""
|
||||
|
||||
repo_root="$(easy_docker_test_repo_root)"
|
||||
|
||||
easy_docker_test_source_common_modules
|
||||
|
||||
# shellcheck source=scripts/easy-docker/lib/checks/jq.sh
|
||||
source "${repo_root}/scripts/easy-docker/lib/checks/jq.sh"
|
||||
}
|
||||
|
||||
easy_docker_test_source_gum_modules() {
|
||||
local repo_root=""
|
||||
|
||||
|
|
@ -145,3 +179,247 @@ easy_docker_test_install_docker_stub() {
|
|||
|
||||
easy_docker_test_prepend_bin_dir
|
||||
}
|
||||
|
||||
easy_docker_test_install_jq_stub() {
|
||||
# shellcheck disable=SC2016
|
||||
easy_docker_test_write_bin_command jq \
|
||||
'set -euo pipefail' \
|
||||
'raw_output=0' \
|
||||
'exit_status=0' \
|
||||
'filter_expr=""' \
|
||||
'file_path=""' \
|
||||
'arg_field_name=""' \
|
||||
'arg_key=""' \
|
||||
'arg_app_id=""' \
|
||||
'while [ "$#" -gt 0 ]; do' \
|
||||
' case "${1}" in' \
|
||||
' -r)' \
|
||||
' raw_output=1' \
|
||||
' shift' \
|
||||
' ;;' \
|
||||
' -e)' \
|
||||
' exit_status=1' \
|
||||
' shift' \
|
||||
' ;;' \
|
||||
' --arg)' \
|
||||
' case "${2}" in' \
|
||||
' field_name) arg_field_name="${3}" ;;' \
|
||||
' key) arg_key="${3}" ;;' \
|
||||
' app_id) arg_app_id="${3}" ;;' \
|
||||
' esac' \
|
||||
' shift 3' \
|
||||
' ;;' \
|
||||
' --indent)' \
|
||||
' shift 2' \
|
||||
' ;;' \
|
||||
' -*)' \
|
||||
' shift' \
|
||||
' ;;' \
|
||||
' *)' \
|
||||
' if [ -z "${filter_expr}" ]; then' \
|
||||
' filter_expr="${1}"' \
|
||||
' elif [ -z "${file_path}" ]; then' \
|
||||
' file_path="${1}"' \
|
||||
' else' \
|
||||
' echo "unsupported jq stub arguments" >&2' \
|
||||
' exit 2' \
|
||||
' fi' \
|
||||
' shift' \
|
||||
' ;;' \
|
||||
' esac' \
|
||||
'done' \
|
||||
'if [ -z "${filter_expr}" ]; then' \
|
||||
' echo "missing jq filter" >&2' \
|
||||
' exit 2' \
|
||||
'fi' \
|
||||
'cleanup_file=""' \
|
||||
'if [ -n "${file_path}" ] && [ "${file_path}" != "-" ]; then' \
|
||||
' payload_path="${file_path}"' \
|
||||
'else' \
|
||||
' payload_path="$(mktemp)"' \
|
||||
' cleanup_file="${payload_path}"' \
|
||||
' cat >"${payload_path}"' \
|
||||
'fi' \
|
||||
'jq_stub_cleanup() {' \
|
||||
' if [ -n "${cleanup_file}" ] && [ -f "${cleanup_file}" ]; then' \
|
||||
' rm -f "${cleanup_file}"' \
|
||||
' fi' \
|
||||
'}' \
|
||||
'trap jq_stub_cleanup EXIT' \
|
||||
'jq_stub_is_object() {' \
|
||||
' awk '"'"'BEGIN { found=0 } /^[[:space:]]*$/ { next } { if ($0 ~ /^[[:space:]]*{/) found=1; exit } END { exit(found ? 0 : 1) }'"'"' "${payload_path}"' \
|
||||
'}' \
|
||||
'jq_stub_first_string_field() {' \
|
||||
' local field_name="${1}"' \
|
||||
' awk -v field_name="${field_name}" '"'"'match($0, "\"" field_name "\"[[:space:]]*:[[:space:]]*\"([^\"]*)\"", parts) { print parts[1]; exit }'"'"' "${payload_path}"' \
|
||||
'}' \
|
||||
'jq_stub_array_strings() {' \
|
||||
' local key="${1}"' \
|
||||
' awk -v key="${key}" '"'"'' \
|
||||
' function emit_matches(segment, parts) {' \
|
||||
' while (match(segment, /"([^"]+)"/, parts)) {' \
|
||||
' print parts[1]' \
|
||||
' segment = substr(segment, RSTART + RLENGTH)' \
|
||||
' }' \
|
||||
' }' \
|
||||
' $0 ~ "\"" key "\"[[:space:]]*:[[:space:]]*\\[" {' \
|
||||
' segment = $0' \
|
||||
' sub(/^.*\[[[:space:]]*/, "", segment)' \
|
||||
' emit_matches(segment)' \
|
||||
' if (segment ~ /\]/) {' \
|
||||
' exit' \
|
||||
' }' \
|
||||
' in_array = 1' \
|
||||
' next' \
|
||||
' }' \
|
||||
' in_array {' \
|
||||
' emit_matches($0)' \
|
||||
' if ($0 ~ /\]/) {' \
|
||||
' exit' \
|
||||
' }' \
|
||||
' }' \
|
||||
' '"'"' "${payload_path}"' \
|
||||
'}' \
|
||||
'jq_stub_object_entries() {' \
|
||||
' local key="${1}"' \
|
||||
' awk -v key="${key}" '"'"'' \
|
||||
' $0 ~ "\"" key "\"[[:space:]]*:[[:space:]]*\\{" { in_object = 1; next }' \
|
||||
' in_object && /^[[:space:]]*}/ { exit }' \
|
||||
' in_object && match($0, /"([^"]+)"[[:space:]]*:[[:space:]]*"([^"]+)"/, parts) { print parts[1] "|" parts[2] }' \
|
||||
' '"'"' "${payload_path}"' \
|
||||
'}' \
|
||||
'jq_stub_lookup_object_value() {' \
|
||||
' local object_key="${1}"' \
|
||||
' local lookup_key="${2}"' \
|
||||
' awk -v object_key="${object_key}" -v lookup_key="${lookup_key}" '"'"'' \
|
||||
' $0 ~ "\"" object_key "\"[[:space:]]*:[[:space:]]*\\{" { in_object = 1; next }' \
|
||||
' in_object && /^[[:space:]]*}/ { exit }' \
|
||||
' in_object && match($0, /"([^"]+)"[[:space:]]*:[[:space:]]*"([^"]+)"/, parts) {' \
|
||||
' if (parts[1] == lookup_key) {' \
|
||||
' print parts[2]' \
|
||||
' exit' \
|
||||
' }' \
|
||||
' }' \
|
||||
' '"'"' "${payload_path}"' \
|
||||
'}' \
|
||||
'jq_stub_top_level_keys() {' \
|
||||
' awk '"'"'match($0, /^ "([^"]+)":/, parts) { print parts[1] }'"'"' "${payload_path}"' \
|
||||
'}' \
|
||||
'jq_stub_count_delta() {' \
|
||||
' local line="${1}"' \
|
||||
' local opens=0' \
|
||||
' local closes=0' \
|
||||
' local tmp=""' \
|
||||
' tmp="${line//[^\{]/}"' \
|
||||
' opens=$((opens + ${#tmp}))' \
|
||||
' tmp="${line//[^\[]/}"' \
|
||||
' opens=$((opens + ${#tmp}))' \
|
||||
' tmp="${line//[^\}]/}"' \
|
||||
' closes=$((closes + ${#tmp}))' \
|
||||
' tmp="${line//[^\]]/}"' \
|
||||
' closes=$((closes + ${#tmp}))' \
|
||||
' printf "%s\n" "$((opens - closes))"' \
|
||||
'}' \
|
||||
'jq_stub_top_level_value() {' \
|
||||
' local key="${1}"' \
|
||||
' local line=""' \
|
||||
' local value=""' \
|
||||
' local in_block=0' \
|
||||
' local depth=0' \
|
||||
' local delta=0' \
|
||||
' while IFS= read -r line || [ -n "${line}" ]; do' \
|
||||
' if [ "${in_block}" -eq 0 ]; then' \
|
||||
' case "${line}" in' \
|
||||
' " \"${key}\":"*)' \
|
||||
' value="${line#*: }"' \
|
||||
' if [[ "${value}" == \{* || "${value}" == \[* ]]; then' \
|
||||
' printf "%s\n" "${value}"' \
|
||||
' depth="$(jq_stub_count_delta "${value}")"' \
|
||||
' if [ "${depth}" -le 0 ]; then' \
|
||||
' return 0' \
|
||||
' fi' \
|
||||
' in_block=1' \
|
||||
' else' \
|
||||
' value="${value%,}"' \
|
||||
' printf "%s\n" "${value}"' \
|
||||
' return 0' \
|
||||
' fi' \
|
||||
' ;;' \
|
||||
' esac' \
|
||||
' else' \
|
||||
' delta="$(jq_stub_count_delta "${line}")"' \
|
||||
' if [ $((depth + delta)) -le 0 ]; then' \
|
||||
' printf "%s\n" "${line%,}"' \
|
||||
' return 0' \
|
||||
' fi' \
|
||||
' printf "%s\n" "${line}"' \
|
||||
' depth=$((depth + delta))' \
|
||||
' fi' \
|
||||
' done <"${payload_path}"' \
|
||||
'}' \
|
||||
'jq_stub_apps_custom_lines() {' \
|
||||
' local repo=""' \
|
||||
' local branch=""' \
|
||||
' awk '"'"'' \
|
||||
' /"custom"[[:space:]]*:[[:space:]]*\[/ { in_custom = 1; next }' \
|
||||
' in_custom && /\]/ { exit }' \
|
||||
' in_custom && match($0, /"repo"[[:space:]]*:[[:space:]]*"([^"]+)"/, parts) { repo = parts[1] }' \
|
||||
' in_custom && match($0, /"branch"[[:space:]]*:[[:space:]]*"([^"]+)"/, parts) { branch = parts[1] }' \
|
||||
' in_custom && repo != "" && branch != "" { print repo "|" branch; repo = ""; branch = "" }' \
|
||||
' '"'"' "${payload_path}"' \
|
||||
'}' \
|
||||
'jq_stub_apps_json_refs() {' \
|
||||
' awk '"'"'match($0, /"url"[[:space:]]*:[[:space:]]*"([^"]+)".*"branch"[[:space:]]*:[[:space:]]*"([^"]+)"/, parts) { print parts[1] "|" parts[2] }'"'"' "${payload_path}"' \
|
||||
'}' \
|
||||
'case "${filter_expr}" in' \
|
||||
' "(.apps.predefined // []) | join(\",\")")' \
|
||||
' output="$(jq_stub_array_strings "predefined" | paste -sd, -)"' \
|
||||
' [ -n "${output}" ] && printf "%s\n" "${output}"' \
|
||||
' ;;' \
|
||||
' "(.apps.custom // [])[]? | select(has(\"repo\") and has(\"branch\")) | \"\\(.repo)|\\(.branch)\"")' \
|
||||
' jq_stub_apps_custom_lines' \
|
||||
' ;;' \
|
||||
' "(.apps.predefined_branches // {}) | to_entries[]? | \"\\(.key)|\\(.value)\"")' \
|
||||
' jq_stub_object_entries "predefined_branches"' \
|
||||
' ;;' \
|
||||
' ".apps.predefined_branches[\$app_id] // empty")' \
|
||||
' jq_stub_lookup_object_value "predefined_branches" "${arg_app_id}"' \
|
||||
' ;;' \
|
||||
' "[.. | objects | .[\$field_name]? | select(type == \"string\")][0] // empty")' \
|
||||
' jq_stub_first_string_field "${arg_field_name}"' \
|
||||
' ;;' \
|
||||
' "([.. | objects | .compose_files? | select(type == \"array\")] | .[0] // [])[]?")' \
|
||||
' jq_stub_array_strings "compose_files"' \
|
||||
' ;;' \
|
||||
' ".site[\$field_name] // empty")' \
|
||||
' jq_stub_first_string_field "${arg_field_name}"' \
|
||||
' ;;' \
|
||||
' "(.site.apps_installed // [])[]? | select(type == \"string\")")' \
|
||||
' jq_stub_array_strings "apps_installed"' \
|
||||
' ;;' \
|
||||
' "type == \"object\"")' \
|
||||
' if jq_stub_is_object; then' \
|
||||
' [ "${exit_status}" -eq 0 ] && printf "true\n"' \
|
||||
' exit 0' \
|
||||
' fi' \
|
||||
' [ "${exit_status}" -eq 1 ] && exit 1' \
|
||||
' printf "false\n"' \
|
||||
' exit 0' \
|
||||
' ;;' \
|
||||
' "keys_unsorted[]")' \
|
||||
' jq_stub_top_level_keys' \
|
||||
' ;;' \
|
||||
' ".[\$key]")' \
|
||||
' jq_stub_top_level_value "${arg_key}"' \
|
||||
' ;;' \
|
||||
' ".[]? | select((.url // \"\") != \"\" and (.branch // \"\") != \"\") | \"\\(.url)|\\(.branch)\"")' \
|
||||
' jq_stub_apps_json_refs' \
|
||||
' ;;' \
|
||||
' *)' \
|
||||
' echo "unsupported jq filter in stub: ${filter_expr}" >&2' \
|
||||
' exit 2' \
|
||||
' ;;' \
|
||||
'esac'
|
||||
|
||||
easy_docker_test_prepend_bin_dir
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue