diff --git a/GEMINI.md b/GEMINI.md index 99891b31be..591fa810af 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -35,7 +35,6 @@ Configurations are declared using custom resources like `RootSync` and `RepoSync * Make * Docker * gcloud CLI -* gsutil **Common Commands (from Makefile):** diff --git a/Makefile b/Makefile index a9d43a3d6f..c290e32eef 100644 --- a/Makefile +++ b/Makefile @@ -234,7 +234,6 @@ DOCKER_RUN_ARGS = \ -v $(GO_DIR)/std/windows_amd64_static:/usr/local/go/pkg/windows_amd64_static \ -v $(TEMP_OUTPUT_DIR):/tmp \ -v $${HOME}/.config:/.config \ - -v $${HOME}/.gsutil:/.gsutil \ -w /go/src/$(REPO) \ --rm \ $(BUILDENV_IMAGE) \ diff --git a/Makefile.e2e.ci b/Makefile.e2e.ci index 7b1919caa1..ca1d58498e 100644 --- a/Makefile.e2e.ci +++ b/Makefile.e2e.ci @@ -49,21 +49,21 @@ GCS_BINARIES := $(GCS_BUCKET)/binaries .PHONY: publish-gcs publish-gcs: - gsutil cp $(OSS_MANIFEST_STAGING_DIR)/* $(GCS_MANIFESTS)/oss/ - gsutil cp $(NOMOS_MANIFEST_STAGING_DIR)/* $(GCS_MANIFESTS)/operator/ - gsutil cp $(BIN_DIR)/darwin_amd64/nomos $(GCS_BINARIES)/darwin_amd64/nomos - gsutil cp $(BIN_DIR)/darwin_arm64/nomos $(GCS_BINARIES)/darwin_arm64/nomos - gsutil cp $(BIN_DIR)/linux_amd64/nomos $(GCS_BINARIES)/linux_amd64/nomos - gsutil cp $(BIN_DIR)/linux_arm64/nomos $(GCS_BINARIES)/linux_arm64/nomos + gcloud storage cp $(OSS_MANIFEST_STAGING_DIR)/* $(GCS_MANIFESTS)/oss/ + gcloud storage cp $(NOMOS_MANIFEST_STAGING_DIR)/* $(GCS_MANIFESTS)/operator/ + gcloud storage cp $(BIN_DIR)/darwin_amd64/nomos $(GCS_BINARIES)/darwin_amd64/nomos + gcloud storage cp $(BIN_DIR)/darwin_arm64/nomos $(GCS_BINARIES)/darwin_arm64/nomos + gcloud storage cp $(BIN_DIR)/linux_amd64/nomos $(GCS_BINARIES)/linux_amd64/nomos + gcloud storage cp $(BIN_DIR)/linux_arm64/nomos $(GCS_BINARIES)/linux_arm64/nomos .PHONY: pull-gcs pull-gcs: clean $(OUTPUT_DIR) - gsutil cp $(GCS_MANIFESTS)/oss/*.yaml $(OSS_MANIFEST_STAGING_DIR)/ - gsutil cp $(GCS_MANIFESTS)/operator/*.yaml $(NOMOS_MANIFEST_STAGING_DIR)/ - gsutil cp $(GCS_BINARIES)/darwin_amd64/nomos $(BIN_DIR)/darwin_amd64/nomos - gsutil cp $(GCS_BINARIES)/darwin_arm64/nomos $(BIN_DIR)/darwin_arm64/nomos - gsutil cp $(GCS_BINARIES)/linux_amd64/nomos $(BIN_DIR)/linux_amd64/nomos - gsutil cp $(GCS_BINARIES)/linux_arm64/nomos $(BIN_DIR)/linux_arm64/nomos + gcloud storage cp '$(GCS_MANIFESTS)/oss/*.yaml' $(OSS_MANIFEST_STAGING_DIR)/ + gcloud storage cp '$(GCS_MANIFESTS)/operator/*.yaml' $(NOMOS_MANIFEST_STAGING_DIR)/ + gcloud storage cp $(GCS_BINARIES)/darwin_amd64/nomos $(BIN_DIR)/darwin_amd64/nomos + gcloud storage cp $(GCS_BINARIES)/darwin_arm64/nomos $(BIN_DIR)/darwin_arm64/nomos + gcloud storage cp $(GCS_BINARIES)/linux_amd64/nomos $(BIN_DIR)/linux_amd64/nomos + gcloud storage cp $(GCS_BINARIES)/linux_arm64/nomos $(BIN_DIR)/linux_arm64/nomos $(MAKE) copy-cli .PHONY: pull-gcs-postsubmit diff --git a/build/buildenv/Dockerfile b/build/buildenv/Dockerfile index b816f3cf47..b1ff22ed6f 100644 --- a/build/buildenv/Dockerfile +++ b/build/buildenv/Dockerfile @@ -88,8 +88,8 @@ ENV GOCACHE=/go/cache COPY --from=tools-base /go/bin/* /bin/ -# Install gcloud & gsutil -# Requires /.config/ & /.gsutil/ volumes mounted for authentication +# Install gcloud & gcloud storage components +# Requires /.config/ volume mounted for authentication COPY --from=gcloud-install /usr/lib/google-cloud-sdk /usr/lib/google-cloud-sdk ENV PATH "$PATH:/usr/lib/google-cloud-sdk/bin" diff --git a/docs/development.md b/docs/development.md index 83e98ad2c1..d3db0bcb28 100644 --- a/docs/development.md +++ b/docs/development.md @@ -8,11 +8,10 @@ You must have the following tools: * [make] * [docker] * [gcloud] -* [gsutil] ### Login to gcloud -In order to download some build artifacts and dependencies from Google Cloud Storage (GCS) with `gsutil`, you may need to be authenticated with `gcloud`. +In order to download some build artifacts and dependencies from Google Cloud Storage (GCS) with `gcloud storage`, you may need to be authenticated with `gcloud`. One way to do this is with `gcloud auth login`. For other options, see https://cloud.google.com/docs/authentication/gcloud @@ -157,5 +156,4 @@ make run-oss [make]: https://www.gnu.org/software/make/ [docker]: https://www.docker.com/get-started [gcloud]: https://cloud.google.com/sdk/docs/install -[gsutil]: https://cloud.google.com/storage/docs/gsutil_install [create your own fork]: https://docs.github.com/en/get-started/quickstart/fork-a-repo diff --git a/e2e/testdata/configmanagement/uninstall/uninstall_configmanagement.sh b/e2e/testdata/configmanagement/uninstall/uninstall_configmanagement.sh index 8d0181803c..b63d768111 100755 --- a/e2e/testdata/configmanagement/uninstall/uninstall_configmanagement.sh +++ b/e2e/testdata/configmanagement/uninstall/uninstall_configmanagement.sh @@ -27,7 +27,7 @@ fi kubectl delete deployment -n config-management-system config-management-operator --ignore-not-found --cascade=foreground -if kubectl get configmanagement config-management &> /dev/null ; then +if kubectl get configmanagement config-management &>/dev/null; then kubectl patch configmanagement config-management --type="merge" -p '{"metadata":{"finalizers":[]}}' kubectl delete configmanagement config-management --cascade=orphan --ignore-not-found fi diff --git a/examples/post-sync/README.md b/examples/post-sync/README.md index fecd400b73..1ed2978085 100644 --- a/examples/post-sync/README.md +++ b/examples/post-sync/README.md @@ -173,7 +173,7 @@ export SINK_NAME="sync-status-errors" export BUCKET_NAME="sync-status-logs-${PROJECT_ID}" # Create the storage bucket -gsutil mb -l ${REGION} gs://${BUCKET_NAME} +gcloud storage buckets create gs://${BUCKET_NAME} --location=${REGION} # Create the log sink gcloud logging sinks create ${SINK_NAME} storage.googleapis.com/${BUCKET_NAME} \ @@ -183,7 +183,7 @@ gcloud logging sinks create ${SINK_NAME} storage.googleapis.com/${BUCKET_NAME} \ export SINK_SA=$(gcloud logging sinks describe ${SINK_NAME} --format='value(writerIdentity)') # Grant permissions to write to the bucket -gsutil iam ch ${SINK_SA}:roles/storage.objectCreator gs://${BUCKET_NAME} +gcloud storage buckets add-iam-policy-binding gs://${BUCKET_NAME} --member=${SINK_SA} --role=roles/storage.objectCreator ``` ## Setting Up Alerting with Pub/Sub and Cloud Functions @@ -502,7 +502,7 @@ gcloud iam service-accounts delete ${LOG_GSA_NAME}@${PROJECT_ID}.iam.gserviceacc gcloud iam service-accounts delete ${SA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com # Delete the storage bucket (if created) -gsutil rm -r gs://${BUCKET_NAME} +gcloud storage rm --recursive gs://${BUCKET_NAME} # Delete GAR repository (optional) gcloud artifacts repositories delete ${GAR_REPO_NAME} --location=${REGION} diff --git a/scripts/build-status.sh b/scripts/build-status.sh index 71315edfdf..3d6e899297 100755 --- a/scripts/build-status.sh +++ b/scripts/build-status.sh @@ -29,7 +29,7 @@ function pretty_print { function local_image_exists { image="$1" - docker image inspect "${image}" &> /dev/null + docker image inspect "${image}" &>/dev/null } function remote_image_exists { @@ -37,12 +37,12 @@ function remote_image_exists { flags=() # must pass --insecure flag for local registry (e.g. localhost:5000) [[ "${image}" == "localhost"* ]] && flags+=("--insecure") - docker manifest inspect "${flags[@]}" "${image}" &> /dev/null + docker manifest inspect "${flags[@]}" "${image}" &>/dev/null } pretty_print "Current commit" "$(git describe --tags --always --dirty --long)" -read -r -a images <<< "$(config_sync_images)" +read -r -a images <<<"$(config_sync_images)" [[ ${#images[@]} -eq 0 ]] && exit 1 declare -A status_map cs_tag="" diff --git a/scripts/e2e.sh b/scripts/e2e.sh index d0ffb41f09..844f797604 100755 --- a/scripts/e2e.sh +++ b/scripts/e2e.sh @@ -13,7 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. - # # golang e2e test launcher. # This wraps the e2e test execution and creates a junit report. @@ -27,7 +26,7 @@ start_time=$(date +%s) go test ./e2e/testcases/... --p 1 --e2e --test.v "$@" | tee test_results.txt exit_code=$? end_time=$(date +%s) -echo "Tests took $(( end_time - start_time )) seconds" +echo "Tests took $((end_time - start_time)) seconds" # Save test results to ARTIFACTS directory. The ARTIFACTS env var is set by prow. # The containerized entry points mount the ARTIFACTS directory to a path inside @@ -35,7 +34,7 @@ echo "Tests took $(( end_time - start_time )) seconds" # enables running this script more flexibly, e.g. without docker in docker. if [[ -n "${ARTIFACTS}" && -d "${ARTIFACTS}" ]]; then echo "Creating junit xml report" - go-junit-report --subtest-mode=exclude-parents < test_results.txt > "${ARTIFACTS}/junit_report.xml" + go-junit-report --subtest-mode=exclude-parents "${ARTIFACTS}/junit_report.xml" if [ "$exit_code" -eq 0 ]; then echo "Running junit-report post processor" # build our in-repo junit report post-processor binary diff --git a/scripts/generate-clientset.sh b/scripts/generate-clientset.sh index 10e94bf292..3246183e16 100755 --- a/scripts/generate-clientset.sh +++ b/scripts/generate-clientset.sh @@ -45,12 +45,12 @@ source "${CODEGEN_PKG}/kube_codegen.sh" GOMOD_NAME="$(grep "^module" "${SCRIPT_ROOT}/go.mod" | cut -d' ' -f2)" kube::codegen::gen_helpers \ - --boilerplate "${SCRIPT_ROOT}/hack/boilerplate.txt" \ - "${SCRIPT_ROOT}/pkg/api" + --boilerplate "${SCRIPT_ROOT}/hack/boilerplate.txt" \ + "${SCRIPT_ROOT}/pkg/api" kube::codegen::gen_client \ - --with-watch \ - --output-dir "${SCRIPT_ROOT}/pkg/generated" \ - --output-pkg "${GOMOD_NAME}/pkg/generated" \ - --boilerplate "${SCRIPT_ROOT}/hack/boilerplate.txt" \ - "${SCRIPT_ROOT}/pkg/api" + --with-watch \ + --output-dir "${SCRIPT_ROOT}/pkg/generated" \ + --output-pkg "${GOMOD_NAME}/pkg/generated" \ + --boilerplate "${SCRIPT_ROOT}/hack/boilerplate.txt" \ + "${SCRIPT_ROOT}/pkg/api" diff --git a/scripts/generate-test-loggers.sh b/scripts/generate-test-loggers.sh index 4d60c05e2a..8537d12425 100755 --- a/scripts/generate-test-loggers.sh +++ b/scripts/generate-test-loggers.sh @@ -28,7 +28,7 @@ GO_MODULE="$(grep "^module" "go.mod" | cut -d' ' -f2)" source_paths=("pkg" "cmd") function render_main_test() { - cat << EOF + cat < "${file_name}" + render_main_test >"${file_name}" "addlicense" -c "Google LLC" -f LICENSE_TEMPLATE "${file_name}" - done <<< "$(find_test_dirs "${source_path}")" + done <<<"$(find_test_dirs "${source_path}")" done diff --git a/scripts/install-helm.sh b/scripts/install-helm.sh index 9057fbc5b6..3c0dd3526b 100755 --- a/scripts/install-helm.sh +++ b/scripts/install-helm.sh @@ -30,17 +30,17 @@ OUTPUT_DIR=${OUTPUT_DIR:-${REPO_ROOT}/.output} STAGING_DIR=${STAGING_DIR:-${OUTPUT_DIR}/third_party/helm} function helm_version_installed() { - local version - version=$("${INSTALL_DIR}/helm" version --short) - echo "${version%+*}" # remove commit suffix + local version + version=$("${INSTALL_DIR}/helm" version --short) + echo "${version%+*}" # remove commit suffix } # Check installed version if [[ -f "${INSTALL_DIR}/helm" ]] && [[ -x "${INSTALL_DIR}/helm" ]]; then - if [[ "$(helm_version_installed)" == "${HELM_VERSION}" ]]; then - echo "helm version: ${HELM_VERSION} (already installed)" - exit 0 - fi + if [[ "$(helm_version_installed)" == "${HELM_VERSION}" ]]; then + echo "helm version: ${HELM_VERSION} (already installed)" + exit 0 + fi fi HELM_TARBALL_URL=gs://config-management-release/config-sync/helm/tag/${HELM_VERSION}/helm-${HELM_VERSION}-linux-amd64.tar.gz @@ -49,14 +49,14 @@ HELM_TARBALL=${TMPDIR}/helm-${HELM_VERSION}-linux-amd64.tar.gz HELM_CHECKSUM=${HELM_TARBALL}.sha256 function cleanup() { - rm -f "${HELM_TARBALL}" - rm -f "${HELM_CHECKSUM}" + rm -f "${HELM_TARBALL}" + rm -f "${HELM_CHECKSUM}" } trap cleanup EXIT echo "Downloading helm ${HELM_VERSION}" -gsutil cp "${HELM_TARBALL_URL}" "${HELM_TARBALL}" -gsutil cp "${HELM_CHECKSUM_URL}" "${HELM_CHECKSUM}" +gcloud storage cp "${HELM_TARBALL_URL}" "${HELM_TARBALL}" +gcloud storage cp "${HELM_CHECKSUM_URL}" "${HELM_CHECKSUM}" echo "Verifying helm checksum" echo "$(cat "${HELM_CHECKSUM}") ${HELM_TARBALL}" | sha256sum -c diff --git a/scripts/install-kustomize.sh b/scripts/install-kustomize.sh index 7e4ac29fd2..516f16c274 100755 --- a/scripts/install-kustomize.sh +++ b/scripts/install-kustomize.sh @@ -30,15 +30,15 @@ OUTPUT_DIR=${OUTPUT_DIR:-${REPO_ROOT}/.output} STAGING_DIR=${STAGING_DIR:-${OUTPUT_DIR}/third_party/kustomize} function helm_version_installed() { - "${INSTALL_DIR}/kustomize" version + "${INSTALL_DIR}/kustomize" version } # Check installed version if [[ -f "${INSTALL_DIR}/kustomize" ]] && [[ -x "${INSTALL_DIR}/kustomize" ]]; then - if [[ "$(helm_version_installed)" == "${KUSTOMIZE_VERSION}" ]]; then - echo "kustomize version: ${KUSTOMIZE_VERSION} (already installed)" - exit 0 - fi + if [[ "$(helm_version_installed)" == "${KUSTOMIZE_VERSION}" ]]; then + echo "kustomize version: ${KUSTOMIZE_VERSION} (already installed)" + exit 0 + fi fi KUSTOMIZE_TARBALL_URL=gs://config-management-release/config-sync/kustomize/tag/${KUSTOMIZE_VERSION}/kustomize-${KUSTOMIZE_VERSION}-linux-amd64.tar.gz @@ -47,14 +47,14 @@ KUSTOMIZE_TARBALL=${TMPDIR}/kustomize-${KUSTOMIZE_VERSION}-linux-amd64.tar.gz KUSTOMIZE_CHECKSUM=${KUSTOMIZE_TARBALL}.sha256 function cleanup() { - rm -f "${KUSTOMIZE_TARBALL}" - rm -f "${KUSTOMIZE_CHECKSUM}" + rm -f "${KUSTOMIZE_TARBALL}" + rm -f "${KUSTOMIZE_CHECKSUM}" } trap cleanup EXIT echo "Downloading kustomize ${KUSTOMIZE_VERSION}" -gsutil cp "${KUSTOMIZE_TARBALL_URL}" "${KUSTOMIZE_TARBALL}" -gsutil cp "${KUSTOMIZE_CHECKSUM_URL}" "${KUSTOMIZE_CHECKSUM}" +gcloud storage cp "${KUSTOMIZE_TARBALL_URL}" "${KUSTOMIZE_TARBALL}" +gcloud storage cp "${KUSTOMIZE_CHECKSUM_URL}" "${KUSTOMIZE_CHECKSUM}" echo "Verifying kustomize checksum" echo "$(cat "${KUSTOMIZE_CHECKSUM}") ${KUSTOMIZE_TARBALL}" | sha256sum -c diff --git a/scripts/lib/manifests.sh b/scripts/lib/manifests.sh index c7c0fd4f90..2b137a4455 100644 --- a/scripts/lib/manifests.sh +++ b/scripts/lib/manifests.sh @@ -52,7 +52,3 @@ config_sync_images() { echo "${images[@]}" return 0 } - - - - diff --git a/scripts/package-test-helm-chart.sh b/scripts/package-test-helm-chart.sh index e0431d5fcb..1b126b70e1 100755 --- a/scripts/package-test-helm-chart.sh +++ b/scripts/package-test-helm-chart.sh @@ -24,7 +24,7 @@ CHART_SRC_DIR="${REPO_ROOT}/e2e/testdata/helm-charts/${CHART_NAME}" TMP_DIR=$(mktemp -d) function cleanup() { - rm -rf -- "$TMP_DIR" + rm -rf -- "$TMP_DIR" } # Ensures the temporary directory is cleaned up on exit @@ -42,4 +42,4 @@ helm repo index . echo "Uploading charts and index to ${HELM_CHARTS_BUCKET}" gcloud storage cp -n ./*.tgz "${HELM_CHARTS_BUCKET}/" -gcloud storage cp ./index.yaml "${HELM_CHARTS_BUCKET}/" \ No newline at end of file +gcloud storage cp ./index.yaml "${HELM_CHARTS_BUCKET}/" diff --git a/scripts/pull-postsubmit-retry.sh b/scripts/pull-postsubmit-retry.sh index 3698465c59..bdc8e993f5 100755 --- a/scripts/pull-postsubmit-retry.sh +++ b/scripts/pull-postsubmit-retry.sh @@ -25,10 +25,10 @@ num_intervals=80 interval=15 SECONDS=0 until [[ "$n" -ge $num_intervals ]]; do - make pull-gcs-postsubmit && exit 0 - echo "++++ Failed to pull postsubmit artifacts. Waiting ${interval} seconds to retry." - n=$((n+1)) - sleep "${interval}" + make pull-gcs-postsubmit && exit 0 + echo "++++ Failed to pull postsubmit artifacts. Waiting ${interval} seconds to retry." + n=$((n + 1)) + sleep "${interval}" done echo "++++ Postsubmit artifacts not found after retrying for ${SECONDS} seconds" diff --git a/scripts/tag-release-candidate.sh b/scripts/tag-release-candidate.sh index 9be5176b79..21c9993a2f 100755 --- a/scripts/tag-release-candidate.sh +++ b/scripts/tag-release-candidate.sh @@ -106,7 +106,7 @@ echo "+++ Incremented RC. NEXT_RC: $NEXT_RC" if [[ "${branch}" == "main" ]]; then git log --oneline --graph -30 "${remote_sha}" else - git fetch "${REMOTE}" main > /dev/null + git fetch "${REMOTE}" main >/dev/null main_sha=$(git rev-parse FETCH_HEAD) git log --oneline --graph -30 "${main_sha}" "${remote_sha}" fi diff --git a/scripts/test-kustomization.sh b/scripts/test-kustomization.sh index a731221891..0699e51e4c 100755 --- a/scripts/test-kustomization.sh +++ b/scripts/test-kustomization.sh @@ -22,8 +22,8 @@ out=$(kustomize build --load-restrictor=LoadRestrictionsNone "${REPO_ROOT}/test/ expected_file="${REPO_ROOT}/test/kustomization/expected.yaml" if [[ "${UPDATE_EXPECTED_OUTPUT:-}" == "true" ]]; then - echo "${out}" > "${expected_file}" + echo "${out}" >"${expected_file}" exit 0 fi -diff "${expected_file}" <( echo "${out}" ) +diff "${expected_file}" <(echo "${out}") diff --git a/scripts/update-component-image.sh b/scripts/update-component-image.sh index 20fcf968e0..46badb4e48 100755 --- a/scripts/update-component-image.sh +++ b/scripts/update-component-image.sh @@ -1,5 +1,5 @@ #!/bin/bash -# +# # Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -56,13 +56,13 @@ case "${COMPONENT}" in VAR_NAME="KUSTOMIZE_VERSION" GCS_PATH="gs://config-management-release/config-sync/kustomize/tag/" STRIP_BASE_PATTERN="-gke.*" - QUERY_TYPE="gsutil" + QUERY_TYPE="gcloud-storage" ;; helm) VAR_NAME="HELM_VERSION" GCS_PATH="gs://config-management-release/config-sync/helm/tag/" STRIP_BASE_PATTERN="-gke.*" - QUERY_TYPE="gsutil" + QUERY_TYPE="gcloud-storage" ;; *) echo "Unknown component: ${COMPONENT}" >&2 @@ -101,8 +101,8 @@ elif [[ "${UPDATE_TYPE}" == "latest-build" ]]; then fi FILTER="tags:${BASE_VERSION}*" GREP_PATTERN="^${BASE_VERSION}" - if [ "${QUERY_TYPE}" == "gsutil" ]; then - FILTER="${BASE_VERSION%-gke.*}*" + if [ "${QUERY_TYPE}" == "gcloud-storage" ]; then + FILTER="" GREP_PATTERN="^${BASE_VERSION%-gke.*}" fi else @@ -117,7 +117,7 @@ if [ "${QUERY_TYPE}" == "gcloud" ]; then --format="value(tags)" | tr ',' '\n' | grep "${GREP_PATTERN}" | sort -V | tail -n 1) else # Query GCS and strip path/trailing slash - LATEST_TAG=$(gsutil ls -d "${GCS_PATH}${FILTER}/" | sed "s|${GCS_PATH}||; s|/||" | grep "${GREP_PATTERN}" | sort -V | tail -n 1) + LATEST_TAG=$(gcloud storage ls "${GCS_PATH}" | sed "s|${GCS_PATH}||; s|/||" | grep "${GREP_PATTERN}" | sort -V | tail -n 1) fi if [ -z "${LATEST_TAG}" ]; then diff --git a/scripts/vulnerabilities.sh b/scripts/vulnerabilities.sh index 18880bc8a4..8ea1c5933c 100755 --- a/scripts/vulnerabilities.sh +++ b/scripts/vulnerabilities.sh @@ -34,7 +34,7 @@ scripts_dir="$(dirname "$(realpath "$0")")" # shellcheck source=scripts/lib/manifests.sh source "${scripts_dir}/lib/manifests.sh" -read -r -a images <<< "$(config_sync_images)" +read -r -a images <<<"$(config_sync_images)" [[ ${#images[@]} -eq 0 ]] && exit 1 fixable_total=0 scan_failure_total=0