build/lib/functions/cli/cli-jsoninfo.sh

277 lines
15 KiB
Bash

#!/usr/bin/env bash
#
# SPDX-License-Identifier: GPL-2.0
#
# Copyright (c) 2013-2023 Igor Pecovnik, igor@armbian.com
#
# This file is a part of the Armbian Build Framework
# https://github.com/armbian/build/
function cli_json_info_pre_run() {
# "gimme root on a Linux machine"
cli_standard_relaunch_docker_or_sudo
}
function cli_json_info_run() {
display_alert "Generating JSON info" "for all boards; wait" "info"
prep_conf_main_minimal_ni
function json_info_logged() { # logging wrapper
LOG_SECTION="json_info" do_with_logging json_info_only
}
function json_info_only() {
prepare_python_and_pip # requires HOSTRELEASE
declare INFO_TOOLS_DIR="${SRC}"/lib/tools/info
display_alert "Here we go" "generating JSON info :: ${ARMBIAN_COMMAND} " "info"
# Targets inventory. Will do all-by-all if no targets file is provided.
declare TARGETS_FILE="${TARGETS_FILE-"${USERPATCHES_PATH}/${TARGETS_FILENAME:-"targets.yaml"}"}"
declare BASE_INFO_OUTPUT_DIR="${SRC}/output/info" # Output dir for info
if [[ "${CLEAN_INFO:-"yes"}" != "no" ]]; then
display_alert "Cleaning info output dir" "${BASE_INFO_OUTPUT_DIR}" "info"
rm -rf "${BASE_INFO_OUTPUT_DIR}"
fi
mkdir -p "${BASE_INFO_OUTPUT_DIR}"
# `gha-template` does not depend on the rest of the info-gatherer, so we can run it first and return.
if [[ "${ARMBIAN_COMMAND}" == "gha-template" ]]; then
# If we have userpatches/gha/chunks, run the workflow template utility
declare user_gha_dir="${USERPATCHES_PATH}/gha"
declare wf_template_dir="${user_gha_dir}/chunks"
declare GHA_CONFIG_YAML_FILE="${user_gha_dir}/gha_config.yaml"
if [[ ! -d "${wf_template_dir}" ]]; then
exit_with_error "output-gha-workflow-template :: no ${wf_template_dir} directory found"
fi
if [[ ! -f "${GHA_CONFIG_YAML_FILE}" ]]; then
exit_with_error "output-gha-workflow-template :: no ${GHA_CONFIG_YAML_FILE} file found"
fi
display_alert "Generating GHA workflow template" "output-gha-workflow-template :: ${wf_template_dir}" "info"
declare GHA_WORKFLOW_TEMPLATE_OUT_FILE_default="${BASE_INFO_OUTPUT_DIR}/artifact-image-complete-matrix.yml"
declare GHA_WORKFLOW_TEMPLATE_OUT_FILE="${GHA_WORKFLOW_TEMPLATE_OUT_FILE:-"${GHA_WORKFLOW_TEMPLATE_OUT_FILE_default}"}"
run_host_command_logged "${PYTHON3_VARS[@]}" "${PYTHON3_INFO[BIN]}" "${INFO_TOOLS_DIR}"/output-gha-workflow-template.py "${GHA_WORKFLOW_TEMPLATE_OUT_FILE}" "${GHA_CONFIG_YAML_FILE}" "${wf_template_dir}" "${MATRIX_ARTIFACT_CHUNKS:-"17"}" "${MATRIX_IMAGE_CHUNKS:-"16"}"
display_alert "Done with" "gha-template" "info"
run_tool_batcat "${GHA_WORKFLOW_TEMPLATE_OUT_FILE}"
display_alert "Templated workflow file" "${GHA_WORKFLOW_TEMPLATE_OUT_FILE}" "ext"
return 0 # stop here.
fi
# debs-to-repo-download is also isolated from the rest. It does depend on the debs-to-repo-info, but that's prepared beforehand in a standard pipeline run.
if [[ "${ARMBIAN_COMMAND}" == "debs-to-repo-download" ]]; then
display_alert "Downloading debs" "debs-to-repo-download" "info"
declare DEBS_TO_REPO_INFO_FILE="${BASE_INFO_OUTPUT_DIR}/debs-to-repo-info.json"
if [[ ! -f "${DEBS_TO_REPO_INFO_FILE}" ]]; then
exit_with_error "debs-to-repo-download :: no ${DEBS_TO_REPO_INFO_FILE} file found; did you restore the pipeline artifacts correctly?"
fi
declare DEBS_OUTPUT_DIR="${DEB_STORAGE}" # this is different depending if BETA=yes (output/debs-beta) or not (output/debs)
display_alert "Downloading debs to" "${DEBS_OUTPUT_DIR}" "info"
export PARALLEL_DOWNLOADS_WORKERS="${PARALLEL_DOWNLOADS_WORKERS}"
run_host_command_logged mkdir -pv "${DEBS_OUTPUT_DIR}"
run_host_command_logged "${PYTHON3_VARS[@]}" "${PYTHON3_INFO[BIN]}" "${INFO_TOOLS_DIR}"/download-debs.py "${DEBS_TO_REPO_INFO_FILE}" "${DEBS_OUTPUT_DIR}"
display_alert "Done with" "debs-to-repo-download" "ext"
return 0 # stop here.
fi
# debs-to-repo-download is also isolated from the rest. It does depend on the debs-to-repo-info, but that's prepared beforehand in a standard pipeline run.
if [[ "${ARMBIAN_COMMAND}" == "debs-to-repo-reprepro" ]]; then
display_alert "Generating rerepro publishing script" "debs-to-repo-reprepro" "info"
declare DEBS_TO_REPO_INFO_FILE="${BASE_INFO_OUTPUT_DIR}/debs-to-repo-info.json"
if [[ ! -f "${DEBS_TO_REPO_INFO_FILE}" ]]; then
exit_with_error "debs-to-repo-reprepro :: no ${DEBS_TO_REPO_INFO_FILE} file found; did you restore the pipeline artifacts correctly?"
fi
declare OUTPUT_INFO_REPREPRO_DIR="${BASE_INFO_OUTPUT_DIR}/reprepro"
declare OUTPUT_INFO_REPREPRO_CONF_DIR="${OUTPUT_INFO_REPREPRO_DIR}/conf"
run_host_command_logged mkdir -pv "${OUTPUT_INFO_REPREPRO_DIR}" "${OUTPUT_INFO_REPREPRO_CONF_DIR}"
# Export params so Python can see them
export REPO_GPG_KEYID="${REPO_GPG_KEYID}"
run_host_command_logged "${PYTHON3_VARS[@]}" "${PYTHON3_INFO[BIN]}" "${INFO_TOOLS_DIR}"/repo-reprepro.py "${DEBS_TO_REPO_INFO_FILE}" "${OUTPUT_INFO_REPREPRO_DIR}" "${OUTPUT_INFO_REPREPRO_CONF_DIR}"
display_alert "Done with" "debs-to-repo-reprepro" "ext"
return 0 # stop here.
fi
### --- inventory --- ###
declare ALL_USERSPACE_INVENTORY_FILE="${BASE_INFO_OUTPUT_DIR}/all_userspace_inventory.json"
declare ALL_BOARDS_ALL_BRANCHES_INVENTORY_FILE="${BASE_INFO_OUTPUT_DIR}/all_boards_all_branches.json"
declare TARGETS_OUTPUT_FILE="${BASE_INFO_OUTPUT_DIR}/all-targets.json"
declare IMAGE_INFO_FILE="${BASE_INFO_OUTPUT_DIR}/image-info.json"
declare IMAGE_INFO_CSV_FILE="${BASE_INFO_OUTPUT_DIR}/image-info.csv"
declare REDUCED_ARTIFACTS_FILE="${BASE_INFO_OUTPUT_DIR}/artifacts-reduced.json"
declare ARTIFACTS_INFO_FILE="${BASE_INFO_OUTPUT_DIR}/artifacts-info.json"
declare ARTIFACTS_INFO_UPTODATE_FILE="${BASE_INFO_OUTPUT_DIR}/artifacts-info-uptodate.json"
declare OUTDATED_ARTIFACTS_IMAGES_FILE="${BASE_INFO_OUTPUT_DIR}/outdated-artifacts-images.json"
# Userspace inventory: RELEASES, and DESKTOPS and their possible ARCH'es, names, and support status.
if [[ ! -f "${ALL_USERSPACE_INVENTORY_FILE}" ]]; then
display_alert "Generating userspace inventory" "all_userspace_inventory.json" "info"
run_host_command_logged "${PYTHON3_VARS[@]}" "${PYTHON3_INFO[BIN]}" "${INFO_TOOLS_DIR}"/userspace-inventory.py ">" "${ALL_USERSPACE_INVENTORY_FILE}"
fi
# Board/branch inventory.
if [[ ! -f "${ALL_BOARDS_ALL_BRANCHES_INVENTORY_FILE}" ]]; then
display_alert "Generating board/branch inventory" "all_boards_all_branches.json" "info"
run_host_command_logged "${PYTHON3_VARS[@]}" "${PYTHON3_INFO[BIN]}" "${INFO_TOOLS_DIR}"/board-inventory.py ">" "${ALL_BOARDS_ALL_BRANCHES_INVENTORY_FILE}"
fi
if [[ "${ARMBIAN_COMMAND}" == "inventory" ]]; then
display_alert "Done with" "inventory" "info"
return 0
fi
# if TARGETS_FILE does not exist, one will be provided for you, from a template.
if [[ ! -f "${TARGETS_FILE}" ]]; then
declare TARGETS_TEMPLATE="${TARGETS_TEMPLATE:-"targets-default.yaml"}"
display_alert "No targets file found" "using default targets template ${TARGETS_TEMPLATE}" "warn"
TARGETS_FILE="${SRC}/config/templates/${TARGETS_TEMPLATE}"
else
display_alert "Using targets file" "${TARGETS_FILE}" "warn"
fi
if [[ ! -f "${TARGETS_OUTPUT_FILE}" ]]; then
display_alert "Generating targets inventory" "targets-compositor" "info"
export TARGETS_BETA="${BETA}" # Read by the Python script, and injected into every target as "BETA=" param.
export TARGETS_REVISION="${REVISION}" # Read by the Python script, and injected into every target as "REVISION=" param.
export TARGETS_FILTER_INCLUDE="${TARGETS_FILTER_INCLUDE}" # Read by the Python script; used to "only include" targets that match the given string.
run_host_command_logged "${PYTHON3_VARS[@]}" "${PYTHON3_INFO[BIN]}" "${INFO_TOOLS_DIR}"/targets-compositor.py "${ALL_BOARDS_ALL_BRANCHES_INVENTORY_FILE}" "${ALL_USERSPACE_INVENTORY_FILE}" "${TARGETS_FILE}" ">" "${TARGETS_OUTPUT_FILE}"
unset TARGETS_BETA
unset TARGETS_REVISION
unset TARGETS_FILTER_INCLUDE
fi
if [[ "${ARMBIAN_COMMAND}" == "targets-composed" ]]; then
display_alert "Done with" "targets-dashboard" "info"
return 0
fi
### Images.
# The image info extractor.
if [[ ! -f "${IMAGE_INFO_FILE}" ]]; then
display_alert "Generating image info" "info-gatherer-image" "info"
run_host_command_logged "${PYTHON3_VARS[@]}" "${PYTHON3_INFO[BIN]}" "${INFO_TOOLS_DIR}"/info-gatherer-image.py "${TARGETS_OUTPUT_FILE}" ">" "${IMAGE_INFO_FILE}"
fi
# convert image info output to CSV for easy import into Google Sheets etc
if [[ ! -f "${IMAGE_INFO_CSV_FILE}" ]]; then
display_alert "Generating CSV info" "info.csv" "info"
run_host_command_logged "${PYTHON3_VARS[@]}" "${PYTHON3_INFO[BIN]}" "${INFO_TOOLS_DIR}"/json2csv.py "<" "${IMAGE_INFO_FILE}" ">" ${IMAGE_INFO_CSV_FILE}
fi
if [[ "${ARMBIAN_COMMAND}" == "targets-dashboard" ]]; then
display_alert "To load the OpenSearch dashboards:" "
pip3 install opensearch-py # install needed lib to talk to OpenSearch
sysctl -w vm.max_map_count=262144 # raise limited needed by OpenSearch
docker-compose --file tools/dashboards/docker-compose-opensearch.yaml up -d # start up OS in docker-compose
python3 lib/tools/index-opensearch.py < output/info/image-info.json # index the JSON into OpenSearch
# go check out http://localhost:5601
docker-compose --file tools/dashboards/docker-compose-opensearch.yaml down # shut down OpenSearch when you're done
" "info"
display_alert "Done with" "targets-dashboard" "info"
return 0
fi
### Artifacts.
# Reducer: artifacts.
if [[ ! -f "${REDUCED_ARTIFACTS_FILE}" ]]; then
display_alert "Reducing info into artifacts" "artifact-reducer" "info"
run_host_command_logged "${PYTHON3_VARS[@]}" "${PYTHON3_INFO[BIN]}" "${INFO_TOOLS_DIR}"/artifact-reducer.py "${IMAGE_INFO_FILE}" ">" "${REDUCED_ARTIFACTS_FILE}"
fi
# The artifact info extractor.
if [[ ! -f "${ARTIFACTS_INFO_FILE}" ]]; then
display_alert "Generating artifact info" "info-gatherer-artifact" "info"
run_host_command_logged "${PYTHON3_VARS[@]}" "${PYTHON3_INFO[BIN]}" "${INFO_TOOLS_DIR}"/info-gatherer-artifact.py "${REDUCED_ARTIFACTS_FILE}" ">" "${ARTIFACTS_INFO_FILE}"
fi
# Now a mapper, check each OCI coordinate to see if it's up-to-date or not. _cache_ (eternally) the positives, but _never_ cache the negatives.
# This should ideally use the authentication info and other stuff that ORAS.land would.
# this is controlled by "CHECK_OCI=yes". most people are not interested in what is or not in the cache when generating a build plan, and it is slow to do.
if [[ ! -f "${ARTIFACTS_INFO_UPTODATE_FILE}" ]]; then
display_alert "Gathering OCI info" "mapper-oci-uptodate :: real lookups (CHECK_OCI): ${CHECK_OCI:-"no"}" "info"
run_host_command_logged "${PYTHON3_VARS[@]}" "${PYTHON3_INFO[BIN]}" "${INFO_TOOLS_DIR}"/mapper-oci-uptodate.py "${ARTIFACTS_INFO_FILE}" "${CHECK_OCI:-"no"}" ">" "${ARTIFACTS_INFO_UPTODATE_FILE}"
fi
# A combinator/reducer: image + artifact; outdated artifacts plus the images that depend on them.
if [[ ! -f "${OUTDATED_ARTIFACTS_IMAGES_FILE}" ]]; then
display_alert "Combining image and artifact info" "outdated-artifact-image-reducer" "info"
run_host_command_logged "${PYTHON3_VARS[@]}" "${PYTHON3_INFO[BIN]}" "${INFO_TOOLS_DIR}"/outdated-artifact-image-reducer.py "${ARTIFACTS_INFO_UPTODATE_FILE}" "${IMAGE_INFO_FILE}" ">" "${OUTDATED_ARTIFACTS_IMAGES_FILE}"
fi
if [[ "${ARMBIAN_COMMAND}" == "targets" ]]; then
display_alert "Done with" "targets" "info"
return 0
fi
### CI/CD Outputs.
# output stage: deploy debs to repo.
# Artifacts-to-repo output. Takes all artifacts, and produces info necessary for:
# 1) getting the artifact from OCI only (not build it)
# 2) getting the list of .deb's to be published to the repo for that artifact
display_alert "Generating deb-to-repo JSON output" "output-debs-to-repo-json" "info"
# This produces debs-to-repo-info.json
run_host_command_logged "${PYTHON3_VARS[@]}" "${PYTHON3_INFO[BIN]}" "${INFO_TOOLS_DIR}"/output-debs-to-repo-json.py "${BASE_INFO_OUTPUT_DIR}" "${OUTDATED_ARTIFACTS_IMAGES_FILE}"
if [[ "${ARMBIAN_COMMAND}" == "debs-to-repo-json" ]]; then
display_alert "Done with" "output-debs-to-repo-json" "ext"
return 0
fi
# Output stage: GHA simplest possible two-matrix worflow.
# A prepare job running this, prepares two matrixes:
# One for artifacts. One for images.
# If the image or artifact is up-to-date, it is still included in matrix, but the job is skipped.
# If any of the matrixes is bigger than 255 items, an error is generated.
if [[ "${ARMBIAN_COMMAND}" == "gha-matrix" ]]; then
if [[ "${CLEAN_MATRIX:-"yes"}" != "no" ]]; then
display_alert "Cleaning GHA matrix output" "clean-matrix" "info"
run_host_command_logged rm -fv "${BASE_INFO_OUTPUT_DIR}"/gha-*-matrix.json
fi
display_alert "Generating GHA matrix for artifacts" "output-gha-matrix :: artifacts" "info"
declare GHA_ALL_ARTIFACTS_JSON_MATRIX_FILE="${BASE_INFO_OUTPUT_DIR}/gha-all-artifacts-matrix.json"
if [[ ! -f "${GHA_ALL_ARTIFACTS_JSON_MATRIX_FILE}" ]]; then
run_host_command_logged "${PYTHON3_VARS[@]}" "${PYTHON3_INFO[BIN]}" "${INFO_TOOLS_DIR}"/output-gha-matrix.py artifacts "${OUTDATED_ARTIFACTS_IMAGES_FILE}" "${MATRIX_ARTIFACT_CHUNKS}" ">" "${GHA_ALL_ARTIFACTS_JSON_MATRIX_FILE}"
fi
github_actions_add_output "artifact-matrix" "$(cat "${GHA_ALL_ARTIFACTS_JSON_MATRIX_FILE}")"
display_alert "Generating GHA matrix for images" "output-gha-matrix :: images" "info"
declare GHA_ALL_IMAGES_JSON_MATRIX_FILE="${BASE_INFO_OUTPUT_DIR}/gha-all-images-matrix.json"
if [[ ! -f "${GHA_ALL_IMAGES_JSON_MATRIX_FILE}" ]]; then
# export env vars used by the Python script.
export SKIP_IMAGES="${SKIP_IMAGES:-"no"}"
export IMAGES_ONLY_OUTDATED_ARTIFACTS="${IMAGES_ONLY_OUTDATED_ARTIFACTS:-"no"}"
run_host_command_logged "${PYTHON3_VARS[@]}" "${PYTHON3_INFO[BIN]}" "${INFO_TOOLS_DIR}"/output-gha-matrix.py images "${OUTDATED_ARTIFACTS_IMAGES_FILE}" "${MATRIX_IMAGE_CHUNKS}" ">" "${GHA_ALL_IMAGES_JSON_MATRIX_FILE}"
fi
github_actions_add_output "image-matrix" "$(cat "${GHA_ALL_IMAGES_JSON_MATRIX_FILE}")"
fi
### a secondary stage, which only makes sense to be run inside GHA, and as such should be split in a different CLI or under a flag.
if [[ "${ARMBIAN_COMMAND}" == "gha-workflow" ]]; then
# GHA Workflow output. A delusion. Maybe.
display_alert "Generating GHA workflow" "output-gha-workflow :: complete" "info"
declare GHA_WORKFLOW_FILE="${BASE_INFO_OUTPUT_DIR}/gha-workflow.yaml"
run_host_command_logged "${PYTHON3_VARS[@]}" "${PYTHON3_INFO[BIN]}" "${INFO_TOOLS_DIR}"/output-gha-workflow.py "${OUTDATED_ARTIFACTS_IMAGES_FILE}" "${GHA_WORKFLOW_FILE}"
fi
}
do_with_default_build json_info_logged
}