Browse Source

new: [compose-core] make ``compose status`` resolve all cell in parallel

Valentin Lab 4 weeks ago
parent
commit
26a597a33f
  1. 461
      bin/compose-core

461
bin/compose-core

@ -2737,31 +2737,72 @@ export -f service:state
charm:upstream-version() { charm:upstream-version() {
local charm="$1" version cache_file="$state_tmpdir/$FUNCNAME.cache.$1" path local charm="$1" version cache_file="$state_tmpdir/$FUNCNAME.cache.$1" path
if [ -e "$cache_file" ]; then if [ -e "$cache_file" ]; then
cat "$cache_file"
return 0
fi
if ! path=$(charm.has_direct_action "$charm" "upstream-versions"); then
return 0
{
read-0 errlvl
cat
} <"$cache_file"
return $errlvl
fi fi
version=$("$path" -l 1) || {
err "Failed to get upstream version for ${DARKYELLOW}$charm${NORMAL}."
return 1
}
if path=$(charm.has_direct_action "$charm" "upstream-version-normalize"); then
version=$("$path" "$version") || {
err "Failed to normalize upstream version for ${DARKYELLOW}$charm${NORMAL}."
(
if ! mkdir "$cache_file.lock" 2>/dev/null; then
while true; do
sleep 0.1
[ -d "${cache_file}.lock" ] || break
done
if [ -e "$cache_file" ]; then
{
read-0 errlvl
if [ "$errlvl" == 0 ]; then
cat
else
cat >&2
fi
} <"$cache_file"
return $errlvl
fi
return 1 return 1
}
fi
echo "$version" > "$cache_file"
e "$version"
fi
trap_add EXIT,ERR "rmdir \"${cache_file}\".lock"
if ! path=$(charm.has_direct_action "$charm" "upstream-versions"); then
touch "$cache_file"
return 0
fi
rm -f "${cache_file}.wip"
touch "${cache_file}.wip"
(
version=$("$path" -l 1)
errlvl=$?
if [ "$errlvl" != 0 ]; then
err "Action ${WHITE}upstream-versions${NORMAL} failed for ${DARKPINK}$charm${NORMAL}."
return $errlvl
fi
if path=$(charm.has_direct_action "$charm" "upstream-version-normalize"); then
version=$("$path" "$version")
errlvl=$?
if [ "$errlvl" != 0 ]; then
err "Failed to normalize upstream version for ${DARKPINK}$charm${NORMAL}."
return $errlvl
fi
fi
echo "$version"
) > "${cache_file}.wip" 2>&1
errlvl=$?
p0 "$errlvl" > "${cache_file}"
if [ "$errlvl" != 0 ]; then
cat "${cache_file}.wip" | tee -a "${cache_file}" >&2
rm "${cache_file}.wip"
return $errlvl
fi
cat "${cache_file}.wip" | tee -a "${cache_file}"
rm "${cache_file}.wip"
)
} }
service:upstream-version() { service:upstream-version() {
local service="$1" version local service="$1" version
charm=$(get_service_charm "$service") || return 1
version=$(charm:upstream-version "$charm") || return 1
charm=$(get_service_charm "$service") || return $?
version=$(charm:upstream-version "$charm") || return $?
e "$version" e "$version"
} }
export -f service:upstream-version export -f service:upstream-version
@ -5703,14 +5744,81 @@ if [ "${PIPESTATUS[0]}" != 0 ]; then
fi fi
[ "$action" == "build" ] && exit 0 [ "$action" == "build" ] && exit 0
state:fields:resolve-parallel() {
local cols=("$@") service jobs state_msg out errlvl col
first_job=1
tick_pid=
concurrent_jobs=0
MAX_CONCURRENT_JOBS=$(nproc)
for col in "${cols[@]}"; do
for service in "${services_args[@]}"; do
if [ "$concurrent_jobs" -ge "$MAX_CONCURRENT_JOBS" ]; then
wait -n # -p job_id ## not supported in this version of bash
## job list is not accurate, but the number of elt is
((concurrent_jobs--))
fi
(
out=$(
case "${col//_/-}" in
root)
if [[ " ${compose_yml_services[*]} " == *" ${service} "* ]]; then
echo "1"
else
echo "0"
fi
;;
name) e "$service" ;;
charm) get_service_charm "$service" ;;
state) service:state "$service" ;;
type) get_service_type "$service" ;;
upstream-version) service:upstream-version "$service" ;;
*)
if has_service_action "$service" "get-$col" >/dev/null; then
state_msg=$(run_service_action "$service" "get-$col") || exit 1
if [[ "$state_msg" == *$'\n'* ]]; then
e "${state_msg%%$'\n'*}"
else
e "${state_msg}"
fi
fi
;;
esac 2>&1
)
errlvl="$?"
p0 "$service" "$col" "$errlvl" "$out"
) &
jobs=("${jobs[@]}" $!)
((concurrent_jobs++))
if [ -n "$first_job" ]; then
## launch tick
(
while true; do
sleep 0.1
p0 "" "" "" ""
done
) &
tick_pid=$!
first_job=
fi
done
done
wait "${jobs[@]}"
if [ -n "$tick_pid" ]; then
kill "$tick_pid"
fi
}
if [ "$action" == "status" ]; then if [ "$action" == "status" ]; then
if ! [ -t 1 ]; then
state_raw_output=1
fi
if [[ -n "${state_all_services}" ]] || [[ "${#state_filters[@]}" -gt 0 ]]; then if [[ -n "${state_all_services}" ]] || [[ "${#state_filters[@]}" -gt 0 ]]; then
compose_yml_services=($(compose:yml:root:services)) || exit 1 compose_yml_services=($(compose:yml:root:services)) || exit 1
fi fi
if [[ -n "${state_all_services}" ]]; then if [[ -n "${state_all_services}" ]]; then
state_columns=("root" ${state_columns[@]}) state_columns=("root" ${state_columns[@]})
fi fi
state_columns_raw=() state_columns_raw=()
for col in "${state_columns[@]}"; do for col in "${state_columns[@]}"; do
if [[ "$col" =~ ^[+-] ]]; then if [[ "$col" =~ ^[+-] ]]; then
@ -5731,13 +5839,136 @@ if [ "$action" == "status" ]; then
esac esac
fi fi
done done
declare -A state_columns_idx=()
declare -A filter_idx=()
filter_cols=()
non_filter_cols=("${state_columns_raw[@]}")
for filter in "${state_filters[@]}"; do
IFS="=" read -r key value <<<"$filter"
if [[ " ${non_filter_cols[*]} " == *" $key "* ]]; then
## remove from non_filter_cols
non_filter_cols=(${non_filter_cols[*]/$key})
fi
state_columns_idx["$col"]="${#filter_cols[@]}"
filter_cols+=("${key}")
done
tot_nb_cols=$(( ${#non_filter_cols[@]} + ${#filter_cols[@]} ))
while read-0-err E "${state_columns_raw[@]}"; do
values=()
for col in "${state_columns_raw[@]}"; do
color=
value="${!col}"
if [ -z "$state_raw_output" ]; then
## make services_idx
declare -A services_idx=()
idx=0
for service in "${services_args[@]}"; do
services_idx["$service"]=$((idx++))
done
## make state_columns_idx
idx=0
for col in "${non_filter_cols[@]}"; do
state_columns_idx["$col"]=$((${#filter_cols[@]} + idx++))
done
values=() ## all values
new_service_args=("${services_args[@]}") ## will remove service not satisfying filters
while read-0 service col E out; do
if [[ " ${new_service_args[*]} " != *" $service "* ]]; then
continue
fi
col_index="${state_columns_idx[$col]}"
service_index="${services_idx[$service]}"
values[$((service_index * tot_nb_cols + col_index))]="$out"
## check if all filter are valuated and satisfied
for filter in "${state_filters[@]}"; do
IFS="=" read -r key value <<<"$filter"
col_index="${state_columns_idx[$key]}"
if [ -z "${values[$((service_index * tot_nb_cols + col_index))]}" ]; then
break
fi
if [ "${values[$((service_index * tot_nb_cols + col_index))]}" != "$value" ]; then
new_service_args=(${new_service_args[*]/"$service"})
break
fi
done
done < <(state:fields:resolve-parallel "${filter_cols[@]}")
services_args=("${new_service_args[@]}")
if [ "${#services_args[@]}" == 0 ]; then
warn "No services found matching the filters." >&2
exit 0
fi
spinner_chars="⣷⣯⣟⡿⢿⣻⣽⣾"
spinner_idx=0
spinner_bg_steps=4
spinner_bg_dir=1
first_draw=1
last_draw=
if [ -z "$state_raw_output" ]; then
echo -en "\e[?25l"; stty -echo 2>/dev/null
trap_add EXIT,ERR "echo -en '\e[?25h'; stty echo 2>/dev/null"
fi
errors=()
declare -A errors_hash_idx=()
error_idx=0
values_valued=0
values_total=$(( ${#services_args[@]} * ${#state_columns_raw[@]} ))
while read-0 service col E out; do
if [ -n "$service" ]; then
col_index="${state_columns_idx[$col]}"
service_index="${services_idx[$service]}"
if [[ "$E" != 0 ]]; then
error_hash=$(H "$col" "$E" "$out")
matching_error_idx="${errors_hash_idx[$error_hash]}"
if [[ -z "${matching_error_idx}" ]]; then
errors+=("$error_idx:$service:$col:$E:$out")
out="!Err[$((error_idx))]"
errors_hash_idx["$error_hash"]="$error_idx"
error_idx=$((error_idx + 1))
else
## find the error to add the service
error="${errors[$matching_error_idx]}"
error="${error#*:}"
error_service="${error%%:*}"
error_tail="${error#*:}"
errors[$matching_error_idx]="$matching_error_idx:$error_service,$service:$error_tail"
out="!Err[$((matching_error_idx))]"
fi
fi
values[$((service_index * tot_nb_cols + col_index))]="$out"
values_valued=$((values_valued + 1))
if [[ "$values_valued" != "$values_total" ]]; then
last_draw=1
continue
fi
fi
[ -n "$state_raw_output" ] && continue
## Draw table
if [ -n "$first_draw" ]; then
first_draw=
else
## move up one line per service
printf "\033[%dA" "${#services_args[@]}"
fi
if [[ "$spinner_bg_dir" == "1" ]]; then
spinner_bg_step=$((spinner_bg_step + 1))
if [ "$spinner_bg_step" -ge "$spinner_bg_steps" ]; then
spinner_idx=$(( (spinner_idx + 1) % ${#spinner_chars} ))
spinner_bg_dir=0
fi
else
spinner_bg_step=$((spinner_bg_step - 1))
if [ "$spinner_bg_step" -le 0 ]; then
spinner_idx=$(( (spinner_idx + 1) % ${#spinner_chars} ))
spinner_bg_dir=1
fi
fi
SPINNERGRAY=$'\e[38;5;28;48;5;'"$((232 + spinner_bg_step))"'m'
while read-0-err E "${state_columns_raw[@]}"; do
line_values=()
for col in "${state_columns_raw[@]}"; do
color=
value="${!col}"
read -r -- value_trim <<<"${!col}" read -r -- value_trim <<<"${!col}"
case "${col//_/-}" in case "${col//_/-}" in
root) root)
@ -5767,107 +5998,115 @@ if [ "$action" == "status" ]; then
if [[ "${value_trim}" == "N/A" ]]; then if [[ "${value_trim}" == "N/A" ]]; then
color=gray color=gray
fi fi
if [[ "$value_trim" == "!Err"* ]]; then
color=darkred
fi
if [[ "$spinner_chars" == *"$value_trim"* ]]; then
color=spinnergray
fi
;; ;;
esac esac
color="${color^^}" color="${color^^}"
fi
if [ -n "$color" ]; then
values+=("${!color}$value${NORMAL}")
else
values+=("$value")
fi
done
first=1
for value in "${values[@]}"; do
if [ -n "$first" ]; then
first=
else
if [ -n "$state_raw_output_nul" ]; then
printf "\0"
if [ -n "$color" ]; then
line_values+=("${!color}$value${NORMAL}")
else
line_values+=("$value")
fi
done
first=1
for value in "${line_values[@]}"; do
if [ -n "$first" ]; then
first=
else else
printf " " printf " "
fi fi
fi
printf "%s" "$value"
done
if [ -n "$state_raw_output_nul" ]; then
printf "\0"
else
printf "%s" "$value"
done
printf "\n" printf "\n"
done < <(
set -o pipefail
for service in "${services_args[@]}"; do
for col in "${state_columns_raw[@]}"; do
col_index="${state_columns_idx[$col]}"
service_index="${services_idx[$service]}"
value_idx="$((service_index * tot_nb_cols + col_index))"
if ! [[ -v "values[$value_idx]" ]]; then
p0 " ${spinner_chars:$spinner_idx:1} "
elif [ -z "${values[$((service_index * tot_nb_cols + col_index))]}" ]; then
p0 "N/A"
else
p0 "${values[$((service_index * tot_nb_cols + col_index))]}"
fi
done
done | {
if [ -z "$state_raw_output" ]; then
col-0:normalize:size "${state_columns_align}"
else
cat
fi
}
echo 0
)
if [ "$E" != 0 ]; then
err "Unexpected failure"
exit $E
fi fi
done < <(
set -o pipefail
filter_cols=()
for filter in "${state_filters[@]}"; do
IFS="=" read -r key value <<<"$filter"
## if not already in state_columns_raw
[[ " ${state_columns_raw[*]} " == *" $key "* ]] ||
filter_cols+=("${key//-/_}")
done < <(state:fields:resolve-parallel "${non_filter_cols[@]}")
for error in "${errors[@]}"; do
echo "" >&2
idx=${error%%:*}; error=${error#*:}
service=${error%%:*}; error=${error#*:}
col=${error%%:*}; error=${error#*:}
E=${error%%:*}; error=${error#*:}
service_list_str=""
services=(${service//,/ })
first=1
for service in "${services[@]}"; do
if [ -n "$first" ]; then
first=
else
service_list_str+=", "
fi
service_list_str+="${DARKYELLOW}$service${NORMAL}"
done done
echo "${RED}Error${DARKRED}[$idx]:${NORMAL} while computing" \
"${WHITE}$col${NORMAL} for $service_list_str" >&2
echo "$error" | prefix " ${GRAY}|${NORMAL} " >&2
echo " ${GRAY}..${NORMAL} ${WHITE}Exited${NORMAL} with errorlevel ${DARKRED}$E${NORMAL}" >&2
done
if [[ "${#errors[@]}" -gt 0 ]]; then
exit 1
fi
if [ -n "$state_raw_output" ]; then
for service in "${services_args[@]}"; do for service in "${services_args[@]}"; do
declare -A values=()
for col in "${state_columns_raw[@]}" "${filter_cols[@]}"; do
case "${col//_/-}" in
root)
if [[ " ${compose_yml_services[*]} " == *" ${service} "* ]]; then
value="1"
else
value="0"
fi
;;
name) value="$service" ;;
charm)
value=$(get_service_charm "$service") || { echo 1; exit 1; }
;;
state)
value=$(service:state "$service") || { echo 1; exit 1; }
;;
type)
value=$(get_service_type "$service") || { echo 1; exit 1; }
;;
upstream-version)
value=$(service:upstream-version "$service") || { echo 1; exit 1; }
value=${value:-N/A}
;;
*)
if has_service_action "$service" "get-$col" >/dev/null; then
state_msg=$(run_service_action "$service" "get-$col") || { echo 1; exit 1 ; }
if [[ "$state_msg" == *$'\n'* ]]; then
value="${state_msg%%$'\n'*}"
## XXXvlab: For now, these are not used, but we could
## display them in additional lines (in same "cell")
msgs="${state_msg#*$'\n'}"
else
value=${state_msg}
fi
else
value="N/A"
fi
;;
esac
values["$col"]="$value"
done
for filter in "${state_filters[@]}"; do
IFS="=" read -r key value <<<"$filter"
[[ "${values[$key]}" != "$value" ]] &&
continue 2
done
first=1
for col in "${state_columns_raw[@]}"; do for col in "${state_columns_raw[@]}"; do
p0 "${values[$col]}"
col_index="${state_columns_idx[$col]}"
service_index="${services_idx[$service]}"
value_idx="$((service_index * tot_nb_cols + col_index))"
value="${values[$value_idx]}"
if [ -n "$first" ]; then
first=
else
if [ -n "$state_raw_output_nul" ]; then
printf "\0"
else
printf " "
fi
fi
printf "%s" "$value"
done done
done | {
if [ -z "$state_raw_output" ]; then
col-0:normalize:size "${state_columns_align}"
if [ -n "$state_raw_output_nul" ]; then
printf "\0"
else else
cat
printf "\n"
fi fi
}
echo 0
)
if [ "$E" != 0 ]; then
echo "E: '$E'" >&2
exit 1
done
fi fi
exit 0 exit 0
fi fi

Loading…
Cancel
Save