#!/usr/bin/env bash set -Eeuo pipefail # There is no button in the interface of GitHub (and as it seems also no API) with which workflows can be deleted. # They are eventually removed automatically, but in case you want to clean up your Actions UI it is sometimes nice # to have the possibility to get rid of workflows that are not required anymore. This interactive script allows you # to achieve exactly that by deleting each individual run of a workflow, after which GitHub deletes the workflow. declare -ir CONCURRENCY=${CONCURRENCY:-8} # region https://no-color.org/ if [[ "${NO_COLOR:-}" == '' ]]; then readonly RESET='\033[0m' readonly DIM='\033[2m' readonly RESET_DIM='\033[22m' readonly BOLD='\033[1m' readonly RESET_BOLD='\033[22m' readonly CYAN='\033[36m' readonly RED='\033[31m' readonly GREEN='\033[32m' else readonly RESET= readonly DIM= readonly RESET_DIM= readonly BOLD= readonly RESET_BOLD= readonly CYAN= readonly RED= readonly GREEN= fi # endregion dry_run=false yes=false while (($# > 0)); do case "$1" in -d | --dry-run) dry_run=true ;; -h | --help) cat </dev/null; then printf -- '%bCould not find gh in PATH, make sure it is available: https://cli.github.com/%b\n' "$RED" "$RESET" >&2 exit 1 fi workflow_names=() workflow_paths=() workflow_ids=() while read -r workflow; do eval "$workflow" done < <(gh api '/repos/{owner}/{repo}/actions/workflows' --jq '.workflows |= sort_by(.name) | .workflows[] | "workflow_names+=('"'"'\(.name)'"'"');workflow_paths+=('"'"'\(.path)'"'"');workflow_ids+=(\(.id))"') case ${#workflow_names[@]} in 0) echo 'Nothing to delete, this repository does not have any workflows.' exit 0 ;; 1) readonly workflow_name=${workflow_names[0]} readonly workflow_path=${workflow_paths[0]} readonly workflow_id=${workflow_ids[0]} printf 'There is only one workflow %b%s %b(%s)%b...\n' "$CYAN" "$workflow_name" "$DIM" "$workflow_id" "$RESET" ;; *) for ((i = 0; i < ${#workflow_names[@]}; i++)); do printf -- '%b[%i]%b %s %b%s (%s)%b\n' "$CYAN" $((i + 1)) "$RESET" "${workflow_names[$i]}" "$DIM" "${workflow_paths[$i]}" "${workflow_ids[$i]}" "$RESET_DIM" done while :; do printf -- '%bWhich workflow [1..%i]? ' "$CYAN" ${#workflow_names[@]} read -r selection if ((1 <= selection && selection <= ${#workflow_names[@]})); then printf -- "%b" "$RESET" ((selection--)) readonly selection break else # shellcheck disable=SC2016 printf -- '%bSelection `%s` is invalid, %bMUST%b be one of [1..%i], try again...%b\n' "$RED" "$selection" "$BOLD" "$RESET_BOLD" "${#workflow_names[@]}" "$RESET" >&2 fi done readonly workflow_name=${workflow_names[$selection]} readonly workflow_path=${workflow_paths[$selection]} readonly workflow_id=${workflow_ids[$selection]} ;; esac unset workflow_names workflow_ids if [[ "$dry_run" == false && "$yes" == false ]]; then # shellcheck disable=SC2016 printf -- 'Are you sure you want to delete workflow %b%s %b%s (%s)%b? %bYou cannot recover from this!%b [y/n] ' "$CYAN" "$workflow_name" "$DIM" "$workflow_path" "$workflow_id" "$RESET" "$RED" "$RESET" read -rN1 reply if [[ "$reply" != y ]]; then case "$reply" in n) response='\nOK' ;; $'\n') response='You did not enter anything' ;; *) response='\nAssuming you meant n' ;; esac printf -- '%b%b, aborting...%b\n' "$RED" "$response" "$RESET" exit 0 fi unset reply echo fi i=0 while read -r run_id; do if [[ "$dry_run" == true ]]; then printf -- '%b💀 run %s%b\n' "$RED" "$run_id" "$RESET" else gh api -X DELETE "/repos/{owner}/{repo}/actions/runs/$run_id" & fi if ((++i == CONCURRENCY)); then i=0 wait fi done < <(gh api --paginate "/repos/{owner}/{repo}/actions/workflows/$workflow_id/runs" --jq '.workflow_runs[] | .id') wait printf -- '%bDone%b\n' "$GREEN" "$RESET"