Docker Registry clean up

اضافه کردن delete به تنظیمات registry

 در ابتدا تنظیمات اولیه registry را مشاهده کنیم تا از بودن مورد delete در آن مطمئن شویم.

docker exec registry cat /etc/docker/registry/config.yml
و وجود مورد زیر را تایید میکنیم:
storage:
delete:
enabled: true

در صورتی که این مورد وحود نداشت باید در قسمت تنظیمات کانتینر رجیستری قرار بگیرد. برای این کار باید موارد زیر را انجام دهیم.

docker exec -it registry sh
vi /etc/docker/registry/config.yml

در تنظیمات قسمت زیر را اضافه کنید:

storage:
  delete:
    enabled: true
    
# EXAMPLE
version: 0.1

storage:
  filesystem:
    rootdirectory: /var/lib/registry
  delete:
    enabled: true

http:
  addr: :5000
docker restart registry

 

اجرای اسکریپت چک کردن ایمیج های قابل حذف

ابتدا فایل های مورد نیاز را ایجاد میکنیم:

mkdir -p /opt/registry-retention

cat > /opt/registry-retention/registry-retention.conf <<'EOF'
# Registry API protocol
REGISTRY_SCHEME="http"

# Registry address
REGISTRY_ADDRESS="10.10.10.99:5000"

# Local Docker Registry container name
REGISTRY_CONTAINER="registry"

# Repository storage path INSIDE the Registry container
REGISTRY_REPOSITORIES_PATH="/var/lib/registry/docker/registry/v2/repositories"

# Keep the latest N unique image digests per repository
KEEP_LAST=2

# Detailed dry-run report
REPORT_FILE="/opt/registry-retention/registry-retention-report.tsv"

# Optional Registry Basic Authentication
# Leave empty if authentication is not enabled
REGISTRY_USERNAME=""
REGISTRY_PASSWORD=""

# Used by Registry garbage collection
REGISTRY_IMAGE="registry:latest"

# Registry storage source mounted at /var/lib/registry
# For a named Docker volume:
REGISTRY_STORAGE_SOURCE="rke-registry"

# Registry config file on the HOST
REGISTRY_CONFIG_HOST="/etc/docker/registry/config.yml"
EOF

chmod 600 /opt/registry-retention/registry-retention.conf

این موارد را بر اساس اطلاعات رجیستری خود تغییر میدهیم.

برای گرفتن گزارش کامل ایمیج های قابل حذف در رجیستری ابتدا اسکریپت زیر را اجرا میکنیم:

cat > /opt/registry-retention/registry-retention-dry-run.sh <<'EOF'
#!/usr/bin/env bash

set -euo pipefail

SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_FILE="${CONFIG_FILE:-${SCRIPT_DIR}/registry-retention.conf}"

# -------------------------------------------------------------------
# Load and validate configuration
# -------------------------------------------------------------------

if [[ ! -f "$CONFIG_FILE" ]]; then
    echo "ERROR: Configuration file not found: $CONFIG_FILE" >&2
    exit 1
fi

# shellcheck source=/dev/null
source "$CONFIG_FILE"

required_commands=(
    curl
    jq
    docker
    sort
    awk
    mktemp
    head
    tail
    wc
)

for command_name in "${required_commands[@]}"; do
    if ! command -v "$command_name" >/dev/null 2>&1; then
        echo "ERROR: Required command not found: $command_name" >&2
        exit 1
    fi
done

required_variables=(
    REGISTRY_SCHEME
    REGISTRY_ADDRESS
    REGISTRY_CONTAINER
    REGISTRY_REPOSITORIES_PATH
    KEEP_LAST
    REPORT_FILE
)

for variable_name in "${required_variables[@]}"; do
    if [[ -z "${!variable_name:-}" ]]; then
        echo "ERROR: Required configuration is empty: $variable_name" >&2
        exit 1
    fi
done

if ! [[ "$KEEP_LAST" =~ ^[1-9][0-9]*$ ]]; then
    echo "ERROR: KEEP_LAST must be a positive integer." >&2
    exit 1
fi

if ! docker inspect "$REGISTRY_CONTAINER" >/dev/null 2>&1; then
    echo "ERROR: Registry container not found: $REGISTRY_CONTAINER" >&2
    exit 1
fi

REGISTRY_URL="${REGISTRY_SCHEME}://${REGISTRY_ADDRESS}"

DETAIL_REPORT="$REPORT_FILE"
SUMMARY_REPORT="${SCRIPT_DIR}/registry-retention-summary.tsv"

AUTH_ARGS=()

if [[ -n "${REGISTRY_USERNAME:-}" ]]; then
    AUTH_ARGS=(
        --user "${REGISTRY_USERNAME}:${REGISTRY_PASSWORD:-}"
    )
fi

# -------------------------------------------------------------------
# Registry API functions
# -------------------------------------------------------------------

api_get() {
    local url="$1"
    local response_file
    local http_status

    response_file="$(mktemp)"

    http_status="$(
        curl \
            "${AUTH_ARGS[@]}" \
            --silent \
            --show-error \
            --output "$response_file" \
            --write-out '%{http_code}' \
            "$url" ||
        true
    )"

    if [[ "$http_status" != "200" ]]; then
        echo "ERROR: Registry API request failed." >&2
        echo "URL: $url" >&2
        echo "HTTP status: ${http_status:-curl-error}" >&2
        echo "Response:" >&2
        cat "$response_file" >&2
        echo >&2

        rm -f "$response_file"
        return 1
    fi

    cat "$response_file"
    rm -f "$response_file"
}

get_digest() {
    local repository="$1"
    local tag="$2"
    local response_headers
    local digest

    response_headers="$(mktemp)"

    if ! curl \
        "${AUTH_ARGS[@]}" \
        --fail \
        --silent \
        --show-error \
        --head \
        -H 'Accept: application/vnd.oci.image.index.v1+json' \
        -H 'Accept: application/vnd.oci.image.manifest.v1+json' \
        -H 'Accept: application/vnd.docker.distribution.manifest.list.v2+json' \
        -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' \
        --output "$response_headers" \
        "${REGISTRY_URL}/v2/${repository}/manifests/${tag}"
    then
        rm -f "$response_headers"
        return 1
    fi

    digest="$(
        awk -F': ' '
            BEGIN {
                IGNORECASE = 1
            }

            tolower($1) == "docker-content-digest" {
                gsub("\r", "", $2)
                print $2
            }
        ' "$response_headers" |
        tail -n 1
    )"

    rm -f "$response_headers"

    printf '%s\n' "$digest"
}

get_tag_time() {
    local repository="$1"
    local tag="$2"
    local link_file

    link_file="${REGISTRY_REPOSITORIES_PATH}/${repository}/_manifests/tags/${tag}/current/link"

    docker exec "$REGISTRY_CONTAINER" \
        stat -c $'%Y\t%y' "$link_file" 2>/dev/null ||
    true
}

# -------------------------------------------------------------------
# Initial information
# -------------------------------------------------------------------

echo "Registry: ${REGISTRY_URL}"
echo "Container: ${REGISTRY_CONTAINER}"
echo "Keeping latest ${KEEP_LAST} unique digest(s) per repository"
echo
echo "Reading Registry catalog..." >&2

# Do not use ?n=10000 because some Registry versions reject it.
catalog_json="$(
    api_get "${REGISTRY_URL}/v2/_catalog"
)"

mapfile -t repositories < <(
    jq -r '
        .repositories[]?
        | select(type == "string")
        | select(length > 0)
    ' <<< "$catalog_json" |
    sort -u
)

repository_count="${#repositories[@]}"

if [[ "$repository_count" -eq 0 ]]; then
    echo "No repositories found."
    exit 0
fi

mkdir -p "$(dirname "$DETAIL_REPORT")"
mkdir -p "$(dirname "$SUMMARY_REPORT")"

printf "action\trepository\ttag\tpushed_epoch\tpushed_at\tdigest\n" \
    > "$DETAIL_REPORT"

printf "repository\ttotal_tags\tunique_digests\tkeep_tags\tdelete_tags\treview_tags\n" \
    > "$SUMMARY_REPORT"

# -------------------------------------------------------------------
# Global counters
# -------------------------------------------------------------------

total_tags=0
total_unique_digests=0
total_keep_latest=0
total_keep_previous=0
total_delete_candidates=0
total_review=0

# -------------------------------------------------------------------
# Scan repositories
# -------------------------------------------------------------------

for repository in "${repositories[@]}"; do
    echo "Scanning: ${repository}" >&2

    tags_json="$(
        api_get "${REGISTRY_URL}/v2/${repository}/tags/list" ||
        true
    )"

    if [[ -z "$tags_json" ]]; then
        echo "WARNING: Unable to retrieve tags for ${repository}" >&2

        printf "%s\t0\t0\t0\t0\t0\n" \
            "$repository" >> "$SUMMARY_REPORT"

        continue
    fi

    mapfile -t tags < <(
        jq -r '
            .tags[]?
            | select(type == "string")
            | select(length > 0)
        ' <<< "$tags_json" |
        sort -u
    )

    repository_tag_count="${#tags[@]}"

    if [[ "$repository_tag_count" -eq 0 ]]; then
        printf "%s\t0\t0\t0\t0\t0\n" \
            "$repository" >> "$SUMMARY_REPORT"

        continue
    fi

    temp_file="$(mktemp)"
    sorted_file="${temp_file}.sorted"

    for tag in "${tags[@]}"; do
        total_tags=$((total_tags + 1))

        stat_result="$(get_tag_time "$repository" "$tag")"

        pushed_epoch=""
        pushed_at=""

        if [[ -n "$stat_result" ]]; then
            IFS=$'\t' read -r pushed_epoch pushed_at <<< "$stat_result"
        fi

        digest="$(get_digest "$repository" "$tag" || true)"

        if [[ -z "$pushed_epoch" ]]; then
            pushed_epoch="0"
        fi

        if [[ -z "$pushed_at" ]]; then
            pushed_at="unknown"
        fi

        if [[ -z "$digest" ]]; then
            digest="unknown"
        fi

        printf "%s\t%s\t%s\t%s\n" \
            "$pushed_epoch" \
            "$tag" \
            "$pushed_at" \
            "$digest" \
            >> "$temp_file"
    done

    # Newest tag reference first.
    sort \
        -t $'\t' \
        -k1,1nr \
        -k2,2 \
        "$temp_file" \
        > "$sorted_file"

    declare -A digest_rank=()

    repository_unique_digests=0
    repository_keep_tags=0
    repository_delete_tags=0
    repository_review_tags=0

    while IFS=$'\t' read -r pushed_epoch tag pushed_at digest; do
        action=""

        if [[ "$digest" == "unknown" || "$pushed_epoch" == "0" ]]; then
            action="REVIEW"

            repository_review_tags=$((repository_review_tags + 1))
            total_review=$((total_review + 1))
        else
            if [[ -z "${digest_rank[$digest]:-}" ]]; then
                repository_unique_digests=$((repository_unique_digests + 1))
                digest_rank["$digest"]="$repository_unique_digests"
            fi

            rank="${digest_rank[$digest]}"

            if [[ "$rank" -eq 1 ]]; then
                action="KEEP-LATEST"

                repository_keep_tags=$((repository_keep_tags + 1))
                total_keep_latest=$((total_keep_latest + 1))
            elif [[ "$rank" -le "$KEEP_LAST" ]]; then
                action="KEEP-PREVIOUS"

                repository_keep_tags=$((repository_keep_tags + 1))
                total_keep_previous=$((total_keep_previous + 1))
            else
                action="DELETE-CANDIDATE"

                repository_delete_tags=$((repository_delete_tags + 1))
                total_delete_candidates=$((total_delete_candidates + 1))
            fi
        fi

        printf "%s\t%s\t%s\t%s\t%s\t%s\n" \
            "$action" \
            "$repository" \
            "$tag" \
            "$pushed_epoch" \
            "$pushed_at" \
            "$digest" \
            >> "$DETAIL_REPORT"

    done < "$sorted_file"

    # Correct Bash arithmetic syntax.
    total_unique_digests=$((total_unique_digests + repository_unique_digests))

    printf "%s\t%s\t%s\t%s\t%s\t%s\n" \
        "$repository" \
        "$repository_tag_count" \
        "$repository_unique_digests" \
        "$repository_keep_tags" \
        "$repository_delete_tags" \
        "$repository_review_tags" \
        >> "$SUMMARY_REPORT"

    unset digest_rank

    rm -f "$temp_file" "$sorted_file"
done

# -------------------------------------------------------------------
# Calculate final statistics
# -------------------------------------------------------------------

unique_delete_digests="$(
    awk -F'\t' '
        NR > 1 &&
        $1 == "DELETE-CANDIDATE" &&
        $6 != "" &&
        $6 != "unknown" {
            print $2 "\t" $6
        }
    ' "$DETAIL_REPORT" |
    sort -u |
    wc -l
)"

repositories_with_delete_candidates="$(
    awk -F'\t' '
        NR > 1 && ($5 + 0) > 0 {
            print $1
        }
    ' "$SUMMARY_REPORT" |
    wc -l
)"

total_kept_tags=$((total_keep_latest + total_keep_previous))

# -------------------------------------------------------------------
# Print overall summary
# -------------------------------------------------------------------

echo
echo "============================================================"
echo "Registry retention dry-run completed"
echo "============================================================"

printf "%-35s %s\n" "Registry repositories:" "$repository_count"
printf "%-35s %s\n" "Total tags scanned:" "$total_tags"
printf "%-35s %s\n" "Total unique digests:" "$total_unique_digests"
printf "%-35s %s\n" "KEEP-LATEST tags:" "$total_keep_latest"
printf "%-35s %s\n" "KEEP-PREVIOUS tags:" "$total_keep_previous"
printf "%-35s %s\n" "Total kept tags:" "$total_kept_tags"
printf "%-35s %s\n" "DELETE candidate tags:" "$total_delete_candidates"
printf "%-35s %s\n" "Unique delete digests:" "$unique_delete_digests"
printf "%-35s %s\n" \
    "Repositories with deletions:" \
    "$repositories_with_delete_candidates"
printf "%-35s %s\n" "REVIEW required:" "$total_review"

# -------------------------------------------------------------------
# Per-repository summary
# -------------------------------------------------------------------

echo
echo "============================================================"
echo "Repository tag summary"
echo "============================================================"

printf "%-62s %8s %9s %8s %8s %8s\n" \
    "REPOSITORY" \
    "TAGS" \
    "DIGESTS" \
    "KEEP" \
    "DELETE" \
    "REVIEW"

printf "%-62s %8s %9s %8s %8s %8s\n" \
    "--------------------------------------------------------------" \
    "--------" \
    "---------" \
    "--------" \
    "--------" \
    "--------"

tail -n +2 "$SUMMARY_REPORT" |
sort \
    -t $'\t' \
    -k2,2nr \
    -k1,1 |
awk -F'\t' '
    {
        printf "%-62s %8s %9s %8s %8s %8s\n",
               $1, $2, $3, $4, $5, $6
    }
'

# -------------------------------------------------------------------
# Repositories with deletion candidates
# -------------------------------------------------------------------

echo
echo "============================================================"
echo "Repositories with more than ${KEEP_LAST} unique digests"
echo "============================================================"

found_large_repository=0

while IFS=$'\t' read -r \
    repository \
    tag_count \
    digest_count \
    keep_count \
    delete_count \
    review_count
do
    if (( digest_count > KEEP_LAST )); then
        found_large_repository=1

        printf "%-62s tags=%-5s digests=%-5s delete=%s\n" \
            "$repository" \
            "$tag_count" \
            "$digest_count" \
            "$delete_count"
    fi
done < <(
    tail -n +2 "$SUMMARY_REPORT" |
    sort \
        -t $'\t' \
        -k3,3nr \
        -k1,1
)

if [[ "$found_large_repository" -eq 0 ]]; then
    echo "No repository has more than ${KEEP_LAST} unique digests."
fi

# -------------------------------------------------------------------
# Exact delete candidates
# -------------------------------------------------------------------

echo
echo "============================================================"
echo "Delete candidates"
echo "============================================================"

if [[ "$total_delete_candidates" -eq 0 ]]; then
    echo "No delete candidates found."
else
    awk -F'\t' '
        NR > 1 && $1 == "DELETE-CANDIDATE" {
            printf "Repository : %s\n", $2
            printf "Image      : %s:%s\n", $2, $3
            printf "Tag        : %s\n", $3
            printf "Pushed at  : %s\n", $5
            printf "Digest     : %s\n", $6
            printf "%s\n",
                   "------------------------------------------------------------"
        }
    ' "$DETAIL_REPORT"
fi

# -------------------------------------------------------------------
# Exact kept images
# -------------------------------------------------------------------

echo
echo "============================================================"
echo "Images that will be kept"
echo "============================================================"

awk -F'\t' '
    NR > 1 &&
    ($1 == "KEEP-LATEST" || $1 == "KEEP-PREVIOUS") {
        printf "%-14s %-75s %s\n",
               $1,
               $2 ":" $3,
               $6
    }
' "$DETAIL_REPORT"

# -------------------------------------------------------------------
# Review items
# -------------------------------------------------------------------

if [[ "$total_review" -gt 0 ]]; then
    echo
    echo "============================================================"
    echo "Items requiring manual review"
    echo "============================================================"

    awk -F'\t' '
        NR > 1 && $1 == "REVIEW" {
            printf "Repository : %s\n", $2
            printf "Image      : %s:%s\n", $2, $3
            printf "Pushed at  : %s\n", $5
            printf "Digest     : %s\n", $6
            printf "%s\n",
                   "------------------------------------------------------------"
        }
    ' "$DETAIL_REPORT"
fi

# -------------------------------------------------------------------
# Report paths
# -------------------------------------------------------------------

echo
echo "============================================================"
echo "Report files"
echo "============================================================"
echo "Detailed report : ${DETAIL_REPORT}"
echo "Summary report  : ${SUMMARY_REPORT}"
echo
echo "No manifest, tag, digest, blob, image, or repository was deleted."
EOF

chmod +x /opt/registry-retention/registry-retention-dry-run.sh

برای اجرا دستور زیر را وارد میکنیم:

./registry-retention-dry-run.sh

مثال خروجی:

[root@node1 registry-retention]# ./registry-retention-dry-run.sh
Registry: http://10.10.10.99:5000
Container: registry
Keeping latest 2 unique digest(s) per repository

Reading Registry catalog...
Scanning: appsan-back-crm
Scanning: appsan-back-panel
Scanning: appsan-panel-ui
Scanning: auth-server
Scanning: bitnami/os-shell
Scanning: bitnami/postgresql
Scanning: bitnami/rabbitmq-cluster-operator
Scanning: bitnami/redis
Scanning: bitnami/redis-sentinel
Scanning: busybox
Scanning: customer-service-counter-miniapp
Scanning: data-foundation-panel-backend
Scanning: data-foundation-panel-backend-with-path
Scanning: data-foundation-panel-ui
Scanning: data-quality-control
Scanning: devops-fileserver
Scanning: energy-man
Scanning: engine
Scanning: esb-sum-tester
Scanning: fileserver
Scanning: flyway
Scanning: flyway/flyway
Scanning: form-man-miniapp
Scanning: fx-obligation-manager-diba-miniapp
Scanning: haj-multi-messanger
Scanning: haj-pilgrim-search
Scanning: keycloak
Scanning: log-correlator
Scanning: log-management
Scanning: mavaracrm-engine
Scanning: ms-common
Scanning: ms-datatools
Scanning: ms-system
Scanning: newfxs
Scanning: newfxsamd
Scanning: noiro/aci-containers-controller
Scanning: noiro/aci-containers-host
Scanning: noiro/cnideploy
Scanning: noiro/openvswitch
Scanning: noiro/opflex
Scanning: openapi-parser
Scanning: panel-ui
Scanning: pilgrim-search-miniapp
Scanning: pilgrim_search-miniapp
Scanning: pricing-panel-ui
Scanning: pricing-with-feign
Scanning: pricing-with-feign-dev
Scanning: quay.io/frrouting/frr
Scanning: quay.io/jetstack/cert-manager-cainjector
Scanning: quay.io/jetstack/cert-manager-controller
Scanning: quay.io/jetstack/cert-manager-startupapicheck
Scanning: quay.io/jetstack/cert-manager-webhook
Scanning: quay.io/metallb/controller
Scanning: quay.io/metallb/speaker
Scanning: rabbitmq
Scanning: rancher/calico-cni
Scanning: rancher/flannel-cni
Scanning: rancher/hyperkube
Scanning: rancher/local-path-provisioner
Scanning: rancher/mirrored-calico-ctl
Scanning: rancher/mirrored-calico-kube-controllers
Scanning: rancher/mirrored-calico-node
Scanning: rancher/mirrored-calico-pod2daemon-flexvol
Scanning: rancher/mirrored-cluster-proportional-autoscaler
Scanning: rancher/mirrored-coredns-coredns
Scanning: rancher/mirrored-coreos-etcd
Scanning: rancher/mirrored-flannel-flannel
Scanning: rancher/mirrored-ingress-nginx-kube-webhook-certgen
Scanning: rancher/mirrored-k8s-dns-dnsmasq-nanny
Scanning: rancher/mirrored-k8s-dns-kube-dns
Scanning: rancher/mirrored-k8s-dns-node-cache
Scanning: rancher/mirrored-k8s-dns-sidecar
Scanning: rancher/mirrored-metrics-server
Scanning: rancher/mirrored-nginx-ingress-controller-defaultbackend
Scanning: rancher/mirrored-pause
Scanning: rancher/nginx-ingress-controller
Scanning: rancher/rke-tools
Scanning: redis
Scanning: registry.k8s.io/sig-storage/nfs-subdir-external-provisioner
Scanning: rest-interface
Scanning: rest-invoker
Scanning: service-desk
Scanning: soap-invoker
Scanning: soap-parser
Scanning: soft-asset-man-miniapp
Scanning: software-identity-miniapp
Scanning: sso_keycloak
Scanning: timberio/vector

============================================================
Registry retention dry-run completed
============================================================
Registry repositories:           88
Total tags scanned:              112
Total unique digests:            110
KEEP-LATEST tags:                88
KEEP-PREVIOUS tags:              23
DELETE candidate tags:           1
Unique delete digests:           1
Repositories with deletions:     1
REVIEW required:                 0

============================================================
Repository tag summary
============================================================
REPOSITORY                                                  TAGS  DIGESTS     KEEP   DELETE   REVIEW
------------------------------------------------------- -------- -------- -------- -------- --------
data-foundation-panel-backend                                  3        3        2        1        0
bitnami/os-shell                                               2        2        2        0        0
bitnami/redis                                                  2        2        2        0        0
data-foundation-panel-backend-with-path                        2        2        2        0        0
data-foundation-panel-ui                                       2        2        2        0        0
data-quality-control                                           2        2        2        0        0
devops-fileserver                                              2        2        2        0        0
haj-multi-messanger                                            2        2        2        0        0
haj-pilgrim-search                                             2        2        2        0        0
keycloak                                                       2        2        2        0        0
ms-system                                                      2        2        2        0        0
openapi-parser                                                 2        2        2        0        0
panel-ui                                                       2        2        2        0        0
pricing-panel-ui                                               2        2        2        0        0
pricing-with-feign-dev                                         2        2        2        0        0
rest-interface                                                 2        2        2        0        0
rest-invoker                                                   2        2        2        0        0
service-desk                                                   2        2        2        0        0
software-identity-miniapp                                      2        2        2        0        0
timberio/vector                                                2        2        2        0        0
appsan-back-crm                                                1        1        1        0        0
appsan-back-panel                                              1        1        1        0        0
appsan-panel-ui                                                1        1        1        0        0
auth-server                                                    1        1        1        0        0
bitnami/postgresql                                             1        1        1        0        0
bitnami/rabbitmq-cluster-operator                              1        1        1        0        0
bitnami/redis-sentinel                                         1        1        1        0        0
busybox                                                        1        1        1        0        0
customer-service-counter-miniapp                               1        1        1        0        0
energy-man                                                     1        1        1        0        0
engine                                                         1        1        1        0        0
esb-sum-tester                                                 1        1        1        0        0
fileserver                                                     1        1        1        0        0
flyway                                                         1        1        1        0        0
flyway/flyway                                                  1        1        1        0        0
form-man-miniapp                                               1        1        1        0        0
fx-obligation-manager-diba-miniapp                             1        1        1        0        0
log-correlator                                                 1        1        1        0        0
log-management                                                 1        1        1        0        0
mavaracrm-engine                                               1        1        1        0        0
ms-common                                                      1        1        1        0        0
ms-datatools                                                   1        1        1        0        0
newfxs                                                         1        1        1        0        0
newfxsamd                                                      1        1        1        0        0
noiro/aci-containers-controller                                1        1        1        0        0
noiro/aci-containers-host                                      1        1        1        0        0
noiro/cnideploy                                                1        1        1        0        0
noiro/openvswitch                                              1        1        1        0        0
noiro/opflex                                                   1        1        1        0        0
pilgrim-search-miniapp                                         1        1        1        0        0
pilgrim_search-miniapp                                         1        1        1        0        0
quay.io/frrouting/frr                                          1        1        1        0        0
quay.io/jetstack/cert-manager-cainjector                       1        1        1        0        0
quay.io/jetstack/cert-manager-controller                       1        1        1        0        0
quay.io/jetstack/cert-manager-startupapicheck                  1        1        1        0        0
quay.io/jetstack/cert-manager-webhook                          1        1        1        0        0
quay.io/metallb/controller                                     1        1        1        0        0
quay.io/metallb/speaker                                        1        1        1        0        0
rabbitmq                                                       1        1        1        0        0
rancher/calico-cni                                             1        1        1        0        0
rancher/flannel-cni                                            1        1        1        0        0
rancher/hyperkube                                              1        1        1        0        0
rancher/local-path-provisioner                                 1        1        1        0        0
rancher/mirrored-calico-ctl                                    1        1        1        0        0
rancher/mirrored-calico-kube-controllers                       1        1        1        0        0
rancher/mirrored-calico-node                                   1        1        1        0        0
rancher/mirrored-calico-pod2daemon-flexvol                     1        1        1        0        0
rancher/mirrored-cluster-proportional-autoscaler               1        1        1        0        0
rancher/mirrored-coredns-coredns                               1        1        1        0        0
rancher/mirrored-coreos-etcd                                   1        1        1        0        0
rancher/mirrored-flannel-flannel                               1        1        1        0        0
rancher/mirrored-ingress-nginx-kube-webhook-certgen            1        1        1        0        0
rancher/mirrored-k8s-dns-dnsmasq-nanny                         1        1        1        0        0
rancher/mirrored-k8s-dns-kube-dns                              1        1        1        0        0
rancher/mirrored-k8s-dns-node-cache                            1        1        1        0        0
rancher/mirrored-k8s-dns-sidecar                               1        1        1        0        0
rancher/mirrored-metrics-server                                1        1        1        0        0
rancher/mirrored-nginx-ingress-controller-defaultbackend        1        1        1        0        0
rancher/mirrored-pause                                         1        1        1        0        0
rancher/nginx-ingress-controller                               1        1        1        0        0
rancher/rke-tools                                              1        1        1        0        0
redis                                                          1        1        1        0        0
registry.k8s.io/sig-storage/nfs-subdir-external-provisioner        1        1        1        0        0
soap-invoker                                                   1        1        1        0        0
soap-parser                                                    1        1        1        0        0
soft-asset-man-miniapp                                         1        1        1        0        0
sso_keycloak                                                   1        1        1        0        0
repository                                              total_tags unique_digests keep_tags delete_tags review_tags

============================================================
Repositories with more than 2 unique digests
============================================================
data-foundation-panel-backend                           tags=3     digests=3     delete=1

============================================================
Delete candidates
============================================================
Repository : data-foundation-panel-backend
Image      : data-foundation-panel-backend:null
Tag        : null
Pushed at  : 2026-03-28 21:28:29.616459450 +0000
Digest     : sha256:e5d8350338ebb63631b2c2c5bfa781b3b59981c73e03352a592f8f794b651e3b
------------------------------------------------------------

============================================================
Images that will be kept
============================================================
KEEP-LATEST    appsan-back-crm:5f9c84e                                           sha256:1070a842b50327dec4bc247bd4b4ddecd7e616d3e32e82a5028534d550ddf61d
KEEP-LATEST    appsan-back-panel:5f9c84e                                         sha256:cae08d283dfd2eebb9613ec0cf9440935f2b9917b7eba3ed013c1433322a4a01
KEEP-LATEST    appsan-panel-ui:4eb4a27                                           sha256:5d57ac29ccb0ecdf00eb71911afea357ef0ff8a8f5afaac7a7b8fbe5c1fed801
KEEP-LATEST    auth-server:c5d2277                                               sha256:3f16471e046bd284329f3b0b713054c8c45d4838c562054a4d1ce83e95f71190
KEEP-LATEST    bitnami/os-shell:12-debian-12-r40                                 sha256:86282b491360476e192fbd68a25b1f92ca9282e637f432599057d0a311340e56
KEEP-PREVIOUS  bitnami/os-shell:12-debian-12-r24                                 sha256:399baf4d3dc5d2967f7ed95b07a6e48e8faf856c9a8ccf83601ccc4f5221d7e6
KEEP-LATEST    bitnami/postgresql:16.3.0-debian-12-r19                           sha256:33d97c701ceaf71641532e1fce98dcf3b12f7aacbba68aa4da5dc103edd4ec33
KEEP-LATEST    bitnami/rabbitmq-cluster-operator:latest                          sha256:8d9f0ce53c7518327e870a9d6ca2aa3f9faa56c7c9692533a733a753a04868d6
KEEP-LATEST    bitnami/redis:7.4.2-debian-12-r6                                  sha256:233e80ba5fc68b959f34ba5c91ef47caf5a678bf6d0ff19f416539f0122ec304
KEEP-PREVIOUS  bitnami/redis:7.4.1-debian-12-r2                                  sha256:9ea3d45afc7a1bf95192a9591f2debe2ab5e37712a0e456d859e8f450a9cb7ee
KEEP-LATEST    bitnami/redis-sentinel:7.4.2-debian-12-r6                         sha256:8379ae0ba492ebe8d119adf085d5adad29ad7942687d97e581a8c8e36d6d1724
KEEP-LATEST    busybox:latest                                                    sha256:d319b0e3e1745e504544e931cde012fc5470eba649acc8a7b3607402942e5db7
KEEP-LATEST    customer-service-counter-miniapp:ebc7f15                          sha256:fb25defb7f3649eae6bdca6f295b4823eba41dc8c4918be35a8a803e9c2b4799
KEEP-LATEST    data-foundation-panel-backend:d0f5602-fakher                      sha256:bb4481c0c5440b1ddf84f77117c0abfeaa905580abb30aae9de4773ff63fdb08
KEEP-PREVIOUS  data-foundation-panel-backend:61952da-fakher                      sha256:cc2ffe7233376b5619ba7752bdf61afd9c79ba449b112342f33a8218ceae7159
KEEP-LATEST    data-foundation-panel-backend-with-path:9b4bda5-fakher            sha256:86586eafad027c848f7c0091a0db1dee2baba5b9d0addd137c9d356c1fcc51e8
KEEP-PREVIOUS  data-foundation-panel-backend-with-path:b6eb421-fakher            sha256:af2459a1a3c9e81ce4d7eafaa6768988b654ff79d4663998afd6d1080ecd9594
KEEP-LATEST    data-foundation-panel-ui:dfp-ec38808                              sha256:9193cef90b772104f6980c5c62436df4c7967693dd80c2060745840b3df0486d
KEEP-PREVIOUS  data-foundation-panel-ui:dfp-fdc20dd                              sha256:6baddf1a722912681ae77dacbac8e9ce81814b30b4f20191f9dbb768511fed40
KEEP-LATEST    data-quality-control:0826d3b-fakher                               sha256:5b6e5be9a7e761043390f39e2c6d4a12077d5e75a997e77c9fd102a33580ac1a
KEEP-PREVIOUS  data-quality-control:3e3dd03-fakher                               sha256:7869e7beea0428f93735f3fd037a26d73517c0a716d24e3868fd46cf0dc216b9
KEEP-LATEST    devops-fileserver:V12.2                                           sha256:794a758b44bcd7eb4ab1b59e3791232f37b759d2201f1eef67eacdb92bceedd5
KEEP-PREVIOUS  devops-fileserver:V12.1                                           sha256:4bbf0fc9a84d4a3f82004812a9aa8741469a5e98f2c783844548007038a080c9
KEEP-LATEST    energy-man:e89a0b0                                                sha256:e78567dbdbbe25e22ee347ce94b9936ffb16fdeb63b47e632b2b8e5c06516e1e
KEEP-LATEST    engine:5bb50e9                                                    sha256:7970b1b72c7723843f391bb84d716857ce6a783d7647c2b73c67c4b3399a421f
KEEP-LATEST    esb-sum-tester:v1.0                                               sha256:e080d70e1d65225266c0ced477392c682c2e9366118f5a178c61444b2d9303b1
KEEP-LATEST    fileserver:v12.3                                                  sha256:cf9f363e807f110b347d567613e687235bbb3f6d60e40947a93460ea4def8100
KEEP-LATEST    flyway:latest                                                     sha256:fb8e1d3f838527e4b0e9d11228797ddcec4955c4d9b9540ceb521e4e2122dfea
KEEP-LATEST    flyway/flyway:latest                                              sha256:fb8e1d3f838527e4b0e9d11228797ddcec4955c4d9b9540ceb521e4e2122dfea
KEEP-LATEST    form-man-miniapp:65c9e4f                                          sha256:03c86dd369397172405895c617aca2a6630fafcdef8edbecbb97b603f026b7a5
KEEP-LATEST    fx-obligation-manager-diba-miniapp:509c7eb                        sha256:bd6b3f1b6d71930eac9a771f4a5e52b0b636c0efee092413df1b9de4066cf1e9
KEEP-LATEST    haj-multi-messanger:3688422                                       sha256:2f373cbe74dffb363a97f6d185f6938193fb1ebe554d0ee3c46be9ae7f081e93
KEEP-PREVIOUS  haj-multi-messanger:78c0297                                       sha256:43e47661e2a0e4722194779bc228b1948ab184e38d437160169179c69fe26a29
KEEP-LATEST    haj-pilgrim-search:integropia-42                                  sha256:5b0139ca99514d4f68c5babbcd2b346b2c4fe82cecba297981ddb45e0c16f34e
KEEP-PREVIOUS  haj-pilgrim-search:integropia-41                                  sha256:5831ad7934ffe4e630504991d52332fd462d1b0a03b1480d4aec2961fd4f2c1e
KEEP-LATEST    keycloak:itg-26                                                   sha256:e0c89dfbd34ad42e5724e2b7a2e8e6dce008f2cd5438db55601901f187bbc56f
KEEP-PREVIOUS  keycloak:itg-23                                                   sha256:775bb2314f81a0586580a4e85934bd59c0442aa171c2441345f5870a042d1461
KEEP-LATEST    log-correlator:latest                                             sha256:4f0a59dd39b8b56dcbea138eed048c1c281f27b042b315bf8910436d8aef3ce6
KEEP-LATEST    log-management:442ee29                                            sha256:d1540fd13c41316271882e68cd7ba1d8b3997a53af33a7bb799ee4f23cf44732
KEEP-LATEST    mavaracrm-engine:363389e                                          sha256:bdcbafad6b3f431cd0ef61244668dde77f835bc497402549064673d307a64a83
KEEP-LATEST    ms-common:e1a4646                                                 sha256:6c84b8603830f9c02de2ca6e458f970d27b7a7fb844a8aebe25c1bf531aee4f1
KEEP-LATEST    ms-datatools:96b6805                                              sha256:9565b9c4307ce6751962683f5a28f85abf042ffa2e193afce704cb095e50feeb
KEEP-LATEST    ms-system:itg-4                                                   sha256:912e07235b6f7d6c088d20bd727f2a85c58dc641c4ae31bed4398c0542ea277a
KEEP-PREVIOUS  ms-system:itg-3                                                   sha256:a6a29af68391ba67c99741c12674db88e062d2b6092e2610c1c26623d6208780
KEEP-LATEST    newfxs:latest                                                     sha256:93e548ab1a984799db74acbf0b0486019d0281867263e29b59bd5ff48fec01f8
KEEP-LATEST    newfxsamd:latest                                                  sha256:66a03d56c6c90ce836ed82365dce82c27c873e4f4636af0ad9b6bf88b9fb29d0
KEEP-LATEST    noiro/aci-containers-controller:6.1.1.1.81c2369                   sha256:b7f1ebf49d1c681b8001213f86b39649cd5dc118981b3f6b83771af9ad10d472
KEEP-LATEST    noiro/aci-containers-host:6.1.1.1.81c2369                         sha256:eadeb41433bea5c9461d1a3f70c90be31f7b25517e26fe74b43f95bd68695f40
KEEP-LATEST    noiro/cnideploy:6.1.1.1.81c2369                                   sha256:de30bae131703ad03b9cbbe325543bcced695a5cdb0e0700196046c766ba7823
KEEP-LATEST    noiro/openvswitch:6.1.1.1.81c2369                                 sha256:c07a4073eb3de76beb6271d6408b5d0ce6e59ed85be3b2c36e2bb1aa88f720d4
KEEP-LATEST    noiro/opflex:6.1.1.1.81c2369                                      sha256:eb8f7fb50773dfc09f7a7f55fad80683a7e4f96dec0bc10d8802bf575ebd7527
KEEP-LATEST    openapi-parser:itg-5                                              sha256:621a14ba28154487369d0c36a4c7c66f51a593125831ebb56c5fb9afd2895314
KEEP-PREVIOUS  openapi-parser:integropia-4                                       sha256:44a793f146341d67df35c1974a25447346454659c5388514f8b44bff32838600
KEEP-LATEST    panel-ui:itg-16                                                   sha256:c2750dc4130d830a728855c6e3806b165f82aa577a410cefdf3248f097ea8823
KEEP-PREVIOUS  panel-ui:itg-15                                                   sha256:1aa1b29a1bee82e06cacb282eac26bb5b310bdba063f8fedfcc6084fa78edce9
KEEP-LATEST    pilgrim-search-miniapp:eb71481                                    sha256:fcdf6a33799a925adb6645a2ff3ff468d105a72cf6b56a3c92e86037afd711b1
KEEP-LATEST    pilgrim_search-miniapp:eb71481                                    sha256:fcdf6a33799a925adb6645a2ff3ff468d105a72cf6b56a3c92e86037afd711b1
KEEP-LATEST    pricing-panel-ui:dfp-72-internal                                  sha256:d38bbf554176dfcd210addbfd6e559610b8dcab23875d42677342411cf0a8511
KEEP-PREVIOUS  pricing-panel-ui:dfp-71-internal                                  sha256:86b4d37d87b265a89c871c64b53dd4ede90411623d1b5366bd46e606df9c7c64
KEEP-LATEST    pricing-with-feign:296d81f-fakher                                 sha256:1f9fffd92889bede2ff9d6b1c25542db2d439cdc878b829407ea6516f156a3d2
KEEP-PREVIOUS  pricing-with-feign:b068181-fakher                                 sha256:4b4dc316210fa15e49b07ef88912071b45c11ac11265e849cccc8dc9d2bcec85
KEEP-PREVIOUS  pricing-with-feign:4641768-fakher                                 sha256:4b4dc316210fa15e49b07ef88912071b45c11ac11265e849cccc8dc9d2bcec85
KEEP-PREVIOUS  pricing-with-feign:0c5882d-fakher                                 sha256:4b4dc316210fa15e49b07ef88912071b45c11ac11265e849cccc8dc9d2bcec85
KEEP-LATEST    pricing-with-feign-dev:ac6d1ed-fakher                             sha256:020f8cb6170891c85299031a2a58c1679cedc8c1f2000031e49c30797f8266ab
KEEP-PREVIOUS  pricing-with-feign-dev:fa1a824-fakher                             sha256:06fb7831f3172f96265088a83e74783fcff4f6ac0f065dc1e70aa0b864206e2f
KEEP-LATEST    quay.io/frrouting/frr:9.1.0                                       sha256:e9eef6f1be059274fc1280f54eea41ebf1db36c9e1a22d454c72ee08583c1f07
KEEP-LATEST    quay.io/jetstack/cert-manager-cainjector:v1.16.1                  sha256:171f9a9a7affd5905e509a52f13a7cae75b2303510e0455fda30a49a4b8847c0
KEEP-LATEST    quay.io/jetstack/cert-manager-controller:v1.16.1                  sha256:8f82029c681ec7812c3dcae27136412d67cb8334e78c3f61a93b301c14802fa8
KEEP-LATEST    quay.io/jetstack/cert-manager-startupapicheck:v1.16.1             sha256:c00bc206b040b4563b72d73a022607e79f73df26de94749e2ec1725d8d9d9b15
KEEP-LATEST    quay.io/jetstack/cert-manager-webhook:v1.16.1                     sha256:71606ab773978c8f0172759bf06b17d268f67cb4fea5303e39d1d29033cc4bb0
KEEP-LATEST    quay.io/metallb/controller:v0.14.8                                sha256:ecf533ca0b175a88f91a767f675c1cc1e5058e675d678d4c2ac9dff45d355c9c
KEEP-LATEST    quay.io/metallb/speaker:v0.14.8                                   sha256:87f1be308b8d42b227a989cf6c13b5d11d4e0611cee06d4f335ca1ff3ab8638b
KEEP-LATEST    rabbitmq:4.0.3-management-alpine                                  sha256:63c260b34b6c657c1273b98a8a5adcf57fde85534fda0fa6698d6865f15847aa
KEEP-LATEST    rancher/calico-cni:v3.28.1-rancher1                               sha256:6270ed548cf1402e1e337f7c5a57a4ee7131386c31e37909cb83aa8e663c8dcd
KEEP-LATEST    rancher/flannel-cni:v1.4.1-rancher1                               sha256:6525affdfa48affc550487d9c7c1f02beed36427854a6424191a0a1bdd7627d2
KEEP-LATEST    rancher/hyperkube:v1.30.5-rancher1                                sha256:a6bdbda952a78af0978ee0b5b6f228dbcba34025a0d0f1581152cc67d8617d88
KEEP-LATEST    rancher/local-path-provisioner:v0.0.26                            sha256:9325057706239e408ed417b19356cd892ee67b046ee08ff11798777d67288bd5
KEEP-LATEST    rancher/mirrored-calico-ctl:v3.28.1                               sha256:deea3c3b5931520f6b77654626053bd265504645b4fa33348c0a2b08ca918dd0
KEEP-LATEST    rancher/mirrored-calico-kube-controllers:v3.28.1                  sha256:8579fad4baca75ce79644db84d6a1e776a3c3f5674521163e960ccebd7206669
KEEP-LATEST    rancher/mirrored-calico-node:v3.28.1                              sha256:f72bd42a299e280eed13231cc499b2d9d228ca2f51f6fd599d2f4176049d7880
KEEP-LATEST    rancher/mirrored-calico-pod2daemon-flexvol:v3.28.1                sha256:72be5fbe1cef7a60245bdc0c9f37f9f92800d115c63760955382d6597ea58e23
KEEP-LATEST    rancher/mirrored-cluster-proportional-autoscaler:v1.8.9           sha256:f7ed92f1e5c511a4ffcf266851db8ddedb45d87d861ced63f3bcfe62b1731051
KEEP-LATEST    rancher/mirrored-coredns-coredns:1.11.1                           sha256:2169b3b96af988cf69d7dd69efbcc59433eb027320eb185c6110e0850b997870
KEEP-LATEST    rancher/mirrored-coreos-etcd:v3.5.12                              sha256:162fe639721588329bfea28d8b5e1286423cebd2b48c789ce7facfe7e0db0e1d
KEEP-LATEST    rancher/mirrored-flannel-flannel:v0.25.1                          sha256:707bb127c71edb8c619a0485d98796fc849eff1618a1214f335df88ed0b5615d
KEEP-LATEST    rancher/mirrored-ingress-nginx-kube-webhook-certgen:v1.4.1        sha256:887b7f4495677473f1bef5bfb48200a1070e526183b515682a7e78e43c7d7da4
KEEP-LATEST    rancher/mirrored-k8s-dns-dnsmasq-nanny:1.23.0                     sha256:5f420006a6da5263570e2c8c35ad133c1f29ea562092af24f0b655be6202d2bb
KEEP-LATEST    rancher/mirrored-k8s-dns-kube-dns:1.23.0                          sha256:32b101995e0742f18af3528b1da23be0e5bb9353f8ed9667ca74cd85d3cc968f
KEEP-LATEST    rancher/mirrored-k8s-dns-node-cache:1.23.0                        sha256:6177e8cdbafa04b5a33df4f3b3bb1d826584e34cfe1dfdb0b773eb8f566b8047
KEEP-LATEST    rancher/mirrored-k8s-dns-sidecar:1.23.0                           sha256:33a513ad5eba3bed82e073c14c778b4951fab04a45d44be00bda1c7354f0a38f
KEEP-LATEST    rancher/mirrored-metrics-server:v0.7.1                            sha256:799bbdcdd394890e7e4564ff692d3e7506e34cbe2117f7e8db867257f6bc6e08
KEEP-LATEST    rancher/mirrored-nginx-ingress-controller-defaultbackend:1.5-rancher1 sha256:4dc5e07c8ca4e23bddb3153737d7b8c556e5fb2f29c4558b7cd6e6df99c512c7
KEEP-LATEST    rancher/mirrored-pause:3.7                                        sha256:445a99db22e9add9bfb15ddb1980861a329e5dff5c88d7eec9cbf08b6b2f4eb1
KEEP-LATEST    rancher/nginx-ingress-controller:nginx-1.11.2-rancher1            sha256:f0402daaa8551afd3342d7cdbe2096e94988bfe4b6519367f676592e475ac9d6
KEEP-LATEST    rancher/rke-tools:v0.1.103                                        sha256:d4d2f8a11901ba950fc61b8e2d0a849b1797796f495a683cd84ca8b521dcb33b
KEEP-LATEST    redis:8.0-M03-alpine3.21                                          sha256:64a6d11ec570f253bf47392d70820cef20ff859fd1f7b150232561d97ddf912b
KEEP-LATEST    registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2 sha256:f741e403b3ca161e784163de3ebde9190905fdbf7dfaa463620ab8f16c0f6423
KEEP-LATEST    rest-interface:be5cb08                                            sha256:d285b34b0fbba68955fe379ed7ff578374bee3b9252bed65838ac48609e47919
KEEP-PREVIOUS  rest-interface:d2ade9b                                            sha256:209cb8b8ddd69dcc92129fb4fdd5784fd75f572e18a8a785135b47f45d5c0c75
KEEP-LATEST    rest-invoker:92290fb                                              sha256:0b112c4e309ccd29b9d45baa3c89a9aaa57d5f5955045ea04231518554a328f2
KEEP-PREVIOUS  rest-invoker:1d409fe                                              sha256:0fc206944ed9d8da616b66ed1f33cd8eca6b8f209baab722166af7dd0ac32c3a
KEEP-LATEST    service-desk:itg-37                                               sha256:b393a8a7246bd7bbabbe2dc3390cf690163d408ee243c048b6a816bd576c858c
KEEP-PREVIOUS  service-desk:itg-36                                               sha256:dd3daa69c29e91bdf78bbbf6f2c649110c940d11d3f521c6ec4be76c1d0763e4
KEEP-LATEST    soap-invoker:223a6b4                                              sha256:de37965e3ae8e684fcbc960706cc198c9ed3045e423f19e79d2b8ef7d8ab9104
KEEP-LATEST    soap-parser:8ad244a                                               sha256:13b974f0c0a65bf0189ee1972be4090e16e021615c5ccd34ff9de946ccf875ad
KEEP-LATEST    soft-asset-man-miniapp:4fd828a                                    sha256:923744bcfd45283425ec52071054e59e03c60802fd51f821e841a908a53c9378
KEEP-LATEST    software-identity-miniapp:1bf69da                                 sha256:b5de488c28110d7affe3a47cb3e1e18733e6740ccb2fe794e228d600f29e99d4
KEEP-PREVIOUS  software-identity-miniapp:1af723e                                 sha256:d17a71413d55e0ffde43f5b965aee62357b67ecd5c454b84b1de1066777460c0
KEEP-LATEST    sso_keycloak:23.0.3-1.0.53                                        sha256:b18ad3dce1631ad3bb8e7747a501dea03c363015e8027c7584d1ae2ebc347fa8
KEEP-LATEST    timberio/vector:0.46.1-debian                                     sha256:b69744ff85f898279be707312546cf6f117192659e5080ffc988c7dca341dfdd
KEEP-PREVIOUS  timberio/vector:0.42.0-debian                                     sha256:e3537b4d454b6711e10c7643268fe5e8f0cc8489d462dc6a46cd01ccea9c89dd

============================================================
Report files
============================================================
Detailed report : /opt/registry-retention/registry-retention-report.tsv
Summary report  : /opt/registry-retention/registry-retention-summary.tsv

No data was deleted.

توجه کنید که این دستور ایمیجی را پاک نکرده و صرفا برای اطمینان و گزارش‌گیری ساخته شده است.

پاک کردن ایمیج های اضافی

برای پاک کردن ایمیج‌های قابل حذف ابتدا دستور زیر را اجرا کنید:

cat > /opt/registry-retention/delete-registry-candidates.sh <<'EOF'
#!/usr/bin/env bash

set -euo pipefail

SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_FILE="${CONFIG_FILE:-${SCRIPT_DIR}/registry-retention.conf}"

if [[ ! -f "$CONFIG_FILE" ]]; then
    echo "ERROR: Configuration file not found: $CONFIG_FILE" >&2
    exit 1
fi

# shellcheck source=/dev/null
source "$CONFIG_FILE"

required_variables=(
    REGISTRY_SCHEME
    REGISTRY_ADDRESS
    REPORT_FILE
)

for variable_name in "${required_variables[@]}"; do
    if [[ -z "${!variable_name:-}" ]]; then
        echo "ERROR: Required configuration is empty: ${variable_name}" >&2
        exit 1
    fi
done

for command_name in curl awk sort; do
    if ! command -v "$command_name" >/dev/null 2>&1; then
        echo "ERROR: Required command not found: ${command_name}" >&2
        exit 1
    fi
done

REGISTRY_URL="${REGISTRY_SCHEME}://${REGISTRY_ADDRESS}"
REPORT="$REPORT_FILE"

if [[ ! -f "$REPORT" ]]; then
    echo "ERROR: Report file not found: $REPORT" >&2
    echo "Run registry-retention-dry-run.sh first." >&2
    exit 1
fi

expected_header=$'action\trepository\ttag\tpushed_epoch\tpushed_at\tdigest'
actual_header="$(head -n 1 "$REPORT")"

if [[ "$actual_header" != "$expected_header" ]]; then
    echo "ERROR: Unexpected report format." >&2
    echo "Expected:" >&2
    printf '%s\n' "$expected_header" >&2
    echo "Found:" >&2
    printf '%s\n' "$actual_header" >&2
    exit 1
fi

AUTH_ARGS=()

if [[ -n "${REGISTRY_USERNAME:-}" ]]; then
    AUTH_ARGS=(
        --user "${REGISTRY_USERNAME}:${REGISTRY_PASSWORD:-}"
    )
fi

candidate_tag_count="$(
    awk -F'\t' '
        NR > 1 && $1 == "DELETE-CANDIDATE" {
            count++
        }
        END {
            print count + 0
        }
    ' "$REPORT"
)"

candidate_digest_count="$(
    awk -F'\t' '
        NR > 1 &&
        $1 == "DELETE-CANDIDATE" &&
        $6 != "" &&
        $6 != "unknown" {
            print $2 "\t" $6
        }
    ' "$REPORT" |
    sort -u |
    wc -l
)"

if [[ "$candidate_tag_count" -eq 0 ]]; then
    echo "No DELETE-CANDIDATE rows were found."
    echo "Nothing was deleted."
    exit 0
fi

echo "Registry             : ${REGISTRY_URL}"
echo "Report               : ${REPORT}"
echo "Candidate tags       : ${candidate_tag_count}"
echo "Unique delete digests: ${candidate_digest_count}"
echo

echo "The following images are candidates for deletion:"
echo

awk -F'\t' '
    NR > 1 && $1 == "DELETE-CANDIDATE" {
        printf "  %-65s %s\n", $2 ":" $3, $6
    }
' "$REPORT"

echo

if [[ "${CONFIRM:-}" != "YES" ]]; then
    echo "Deletion is disabled."
    echo
    echo "Review the list above, then run:"
    echo "CONFIRM=YES ./delete-registry-candidates.sh"
    exit 1
fi

# Additional confirmation value to prevent accidental execution.
if [[ "${CONFIRM_REGISTRY:-}" != "$REGISTRY_ADDRESS" ]]; then
    echo "ERROR: Registry confirmation does not match." >&2
    echo
    echo "Run with both confirmation variables:" >&2
    echo "CONFIRM=YES CONFIRM_REGISTRY=${REGISTRY_ADDRESS} ./delete-registry-candidates.sh" >&2
    exit 1
fi

declare -A processed=()

deleted_count=0
failed_count=0
duplicate_count=0
invalid_count=0
protected_count=0

while IFS=$'\t' read -r \
    action \
    repository \
    tag \
    pushed_epoch \
    pushed_at \
    digest
do
    [[ "$action" != "DELETE-CANDIDATE" ]] && continue

    if [[ -z "$repository" || -z "$tag" ]]; then
        echo "SKIP invalid row: missing repository or tag"
        invalid_count=$((invalid_count + 1))
        continue
    fi

    if [[ -z "$digest" || "$digest" == "unknown" ]]; then
        echo "SKIP missing digest: ${repository}:${tag}"
        invalid_count=$((invalid_count + 1))
        continue
    fi

    if [[ ! "$digest" =~ ^sha256:[0-9a-fA-F]{64}$ ]]; then
        echo "SKIP invalid digest: ${repository}:${tag} ${digest}"
        invalid_count=$((invalid_count + 1))
        continue
    fi

    key="${repository}|${digest}"

    if [[ -n "${processed[$key]:-}" ]]; then
        echo "SKIP duplicate digest: ${repository}:${tag} ${digest}"
        duplicate_count=$((duplicate_count + 1))
        continue
    fi

    # Safety check:
    # Never delete a digest if the report also marks the same
    # repository+digest as KEEP-LATEST or KEEP-PREVIOUS.
    if awk -F'\t' \
        -v repository="$repository" \
        -v digest="$digest" '
            NR > 1 &&
            $2 == repository &&
            $6 == digest &&
            ($1 == "KEEP-LATEST" || $1 == "KEEP-PREVIOUS") {
                found = 1
                exit
            }
            END {
                exit(found ? 0 : 1)
            }
        ' "$REPORT"
    then
        echo "PROTECTED: ${repository}:${tag} ${digest}"
        echo "Reason: the same digest is marked as KEEP in the report."
        protected_count=$((protected_count + 1))
        processed["$key"]=1
        continue
    fi

    processed["$key"]=1

    status="$(
        curl \
            "${AUTH_ARGS[@]}" \
            --silent \
            --show-error \
            --output /dev/null \
            --write-out '%{http_code}' \
            --request DELETE \
            "${REGISTRY_URL}/v2/${repository}/manifests/${digest}" ||
        true
    )"

    case "$status" in
        202)
            echo "DELETED: ${repository}:${tag} ${digest}"
            deleted_count=$((deleted_count + 1))
            ;;

        404)
            echo "NOT FOUND: ${repository}:${tag} ${digest}"
            echo "The manifest may already have been deleted."
            failed_count=$((failed_count + 1))
            ;;

        405)
            echo "FAILED: ${repository}:${tag} HTTP=405"
            echo "Manifest deletion is not enabled in Registry configuration."
            failed_count=$((failed_count + 1))
            ;;

        *)
            echo "FAILED: ${repository}:${tag} HTTP=${status:-curl-error} digest=${digest}"
            failed_count=$((failed_count + 1))
            ;;
    esac

done < <(tail -n +2 "$REPORT")

echo
echo "============================================================"
echo "Registry manifest deletion completed"
echo "============================================================"
echo "Deleted unique digests : ${deleted_count}"
echo "Failed deletions       : ${failed_count}"
echo "Duplicate report rows  : ${duplicate_count}"
echo "Invalid report rows    : ${invalid_count}"
echo "Protected digests      : ${protected_count}"
echo
echo "Manifest deletion does not immediately release disk space."
echo "Run Registry garbage collection after reviewing the result."
EOF

chmod +x /opt/registry-retention/delete-registry-candidates.sh

برای تست ابتدا به صورت امتحانی این دستور را اجرا میکنیم:

./delete-registry-candidates.sh

خروجی قابل انتظار:

[root@node1 registry-retention]# ./delete-registry-candidates.sh
Registry             : http://10.10.10.99:5000
Report               : /opt/registry-retention/registry-retention-report.tsv
Candidate tags       : 1
Unique delete digests: 1

The following images are candidates for deletion:

  data-foundation-panel-backend:null                                sha256:e5d8350338ebb63631b2c2c5bfa781b3b59981c73e03352a592f8f794b651e3b

Deletion is disabled.

Review the list above, then run:
CONFIRM=YES ./delete-registry-candidates.sh

سپس پس از اطمینان از خروجی دستور زیر را برای پاک کردن نهایی ایمیج ها وارد کنید:

CONFIRM=YES \
CONFIRM_REGISTRY=10.10.10.99:5000 \
./delete-registry-candidates.sh

این دستور فقط manifest هارا پاک میکند. برای پاک کردن blob های بدون manifest باید از garbage collector استفاده کنیم:

docker run --rm \
  -v rke-registry:/var/lib/registry \
  registry:latest \
  garbage-collect --delete-untagged \
  /etc/distribution/config.yml

توجه کنید که مورد اخر این دستور را باید با توجه به تنظیمات registry خود قرار دهید.