docker pull & push

#!/bin/bash
set -euo pipefail
 
# -----------------------------
# 配置区
# -----------------------------
REGISTRY="${REGISTRY:-harbor.ops.qianxin-inc.cn}" # Harbor 地址
REPO="${REPO:-qde/qde-grpc-server-oversea}"      # 仓库名
TAG="${TAG:-20251103}"                             # 镜像标签
 
USERNAME='robot$x'
PASSWORD='x'
 
TLS_SKIP_VERIFY=true # 自签证书可设 true
 
MANIFEST_JSON="/tmp/manifest.json"
DIGESTS_FILE="/tmp/layers.digests"
 
# -----------------------------
# 构建 curl 命令
# -----------------------------
CURL_CMD="curl -sS -u $USERNAME:$PASSWORD"
$TLS_SKIP_VERIFY && CURL_CMD="$CURL_CMD -k"
 
# -----------------------------
# 拉取 manifest
# -----------------------------
echo "[INFO] 拉取 manifest: $REGISTRY/v2/$REPO/manifests/$TAG"
$CURL_CMD -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
"https://$REGISTRY/v2/$REPO/manifests/$TAG" -o "$MANIFEST_JSON" || {
    echo "[ERROR] 拉取 manifest 失败,请检查账号/密码/网络/registry"
    exit 1
}
 
# -----------------------------
# 检查 JSON 是否有效
# -----------------------------
if ! jq . "$MANIFEST_JSON" &>/dev/null; then
    echo "[ERROR] manifest.json 无效或为空,请检查 registry 地址/认证/网络"
    cat "$MANIFEST_JSON"
    exit 1
fi
 
# -----------------------------
# 判断 schemaVersion 并提取 blob digest + size
# -----------------------------
SCHEMA=$(jq -r '.schemaVersion' "$MANIFEST_JSON")
if [ "$SCHEMA" = "2" ]; then
    CONFIG_DIGEST=$(jq -r '.config.digest' "$MANIFEST_JSON")
    CONFIG_SIZE=$(jq -r '.config.size' "$MANIFEST_JSON")
    jq -r '.layers[].digest' "$MANIFEST_JSON" > "$DIGESTS_FILE"
    mapfile -t LAYERS_SIZES < <(jq -r '.layers[].size' "$MANIFEST_JSON")
elif [ "$SCHEMA" = "1" ]; then
    CONFIG_DIGEST="N/A"
    CONFIG_SIZE="N/A"
    jq -r '.fsLayers[].blobSum' "$MANIFEST_JSON" > "$DIGESTS_FILE"
    mapfile -t LAYERS_SIZES < <(for _ in $(cat "$DIGESTS_FILE"); do echo "N/A"; done)
else
    echo "[ERROR] 不支持的 manifest schemaVersion: $SCHEMA"
    exit 1
fi
 
echo "[INFO] schemaVersion=$SCHEMA"
echo "[INFO] Config digest: $CONFIG_DIGEST"
echo "[INFO] Config size: $CONFIG_SIZE"
echo "[INFO] 共找到 $(wc -l < "$DIGESTS_FILE") 层 blob"
echo
 
# -----------------------------
# 遍历每个 blob 请求 HEAD,打印重定向 URL(支持 Ceph RGW/S3)及 size,并检查 HTTP 状态
# -----------------------------
i=0
ALL_OK=true
while read -r digest; do
    size="${LAYERS_SIZES[i]}"
    echo "=== Blob $((i+1)) ==="
    echo "Digest: $digest"
    echo "Size:   $size"
 
    # -I HEAD 请求,-L 跟随重定向
    STATUS=$($CURL_CMD -I -L -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
        "https://$REGISTRY/v2/$REPO/blobs/$digest" 2>&1 | tee /tmp/blob_head.txt | grep -E "HTTP/|Location:")
 
    echo "$STATUS"
     
    # 如果 HTTP 最终状态不是 2xx,就标记失败
    if ! grep -q "HTTP/.* 2[0-9][0-9]" /tmp/blob_head.txt; then
        ALL_OK=false
    fi
    echo
    i=$((i+1))
done < "$DIGESTS_FILE"
 
# -----------------------------
# 输出最终结果
# -----------------------------
if $ALL_OK; then
    echo "[INFO] 所有 blob 拉取检查通过,pull 可能成功"
else
    echo "[ERROR] 存在 blob 拉取失败,pull 可能失败"
fi
 
echo "[INFO] 完成"

 

 

#!/bin/bash
set -euo pipefail
 
# -----------------------------
# 配置区
# -----------------------------
REGISTRY="${REGISTRY:-harbor.ops.qianxin-inc.cn}" # Harbor 地址
REPO="${REPO:-qde/qde-grpc-server-oversea}"       # 仓库名
TAG="${TAG:-20251103}"                             # 镜像 tag
 
USERNAME='robot$x'                                 # 账号
PASSWORD='x'                                       # 密码
 
TLS_SKIP_VERIFY=true                               # 自签证书可设 true
LOCAL_IMAGE="${LOCAL_IMAGE:-$REPO:$TAG}"          # 本地镜像名
 
TMPDIR=$(mktemp -d)
echo "[INFO] 临时目录: $TMPDIR"
 
# -----------------------------
# 构建 curl 命令
# -----------------------------
CURL_CMD="curl -sS -u $USERNAME:$PASSWORD"
$TLS_SKIP_VERIFY && CURL_CMD="$CURL_CMD -k"
 
# -----------------------------
# 1. docker save 导出镜像 tar
# -----------------------------
IMAGE_TAR="$TMPDIR/image.tar"
echo "[INFO] docker save 导出镜像 $LOCAL_IMAGE -> $IMAGE_TAR"
docker pull "$LOCAL_IMAGE" >/dev/null 2>&1 || true
docker save -o "$IMAGE_TAR" "$LOCAL_IMAGE"
 
# -----------------------------
# 2. 解压 tar 并解析 manifest.json
# -----------------------------
tar -xf "$IMAGE_TAR" -C "$TMPDIR"
MANIFEST_JSON="$TMPDIR/manifest.json"
 
if [ ! -f "$MANIFEST_JSON" ]; then
    echo "[ERROR] 找不到 manifest.json"
    exit 1
fi
 
CONFIG_FILE=$(jq -r '.[0].Config' "$MANIFEST_JSON")
mapfile -t LAYERS_ARRAY < <(jq -r '.[0].Layers[]' "$MANIFEST_JSON")
 
echo "[INFO] 解析 manifest.json 成功:"
echo "       Config: $CONFIG_FILE"
echo "       Layers 数量: ${#LAYERS_ARRAY[@]}"
echo
 
# -----------------------------
# 工具函数
# -----------------------------
calc_digest_size() {
    local file="$1"
    local sha
    sha=$(sha256sum "$file" | awk '{print $1}')
    local size
    size=$(wc -c < "$file" | tr -d ' ')
    echo "$sha $size"
}
 
start_upload() {
    local name="$1"
    $CURL_CMD -i -X POST "https://$REGISTRY/v2/$name/blobs/uploads/" \
        | grep -i '^Location:' | sed -E 's/Location: *//I' | tr -d '\r'
}
 
upload_blob() {
    local url="$1"
    local file="$2"
    local digest="$3"
    if [[ "$url" =~ ^/ ]]; then
        url="https://$REGISTRY$url"
    fi
    echo "[INFO] 上传 blob -> $file"
    echo "       digest=$digest"
    echo "       size=$(wc -c < "$file")"
    echo "       url=$url"
 
    # -L 跟随重定向,输出 HTTP 状态和 Location
    RESPONSE=$($CURL_CMD -i -X PUT --data-binary @"$file" "$url?digest=$digest" -w "\n%{http_code}")
    echo "$RESPONSE" | grep -E "HTTP/|Location:" || true
 
    # 检查 HTTP 状态码
    STATUS_CODE=$(echo "$RESPONSE" | tail -n1)
    if [[ ! "$STATUS_CODE" =~ ^2[0-9][0-9]$ ]]; then
        echo "[ERROR] 上传失败 HTTP status=$STATUS_CODE"
        return 1
    fi
    echo
    return 0
}
 
# -----------------------------
# 3. 上传 config
# -----------------------------
CONFIG_PATH="$TMPDIR/$CONFIG_FILE"
read CONFIG_DIGEST CONFIG_SIZE < <(calc_digest_size "$CONFIG_PATH")
 
echo "[INFO] Config 信息:"
echo "       路径: $CONFIG_PATH"
echo "       大小: $CONFIG_SIZE"
echo "       Digest: sha256:$CONFIG_DIGEST"
echo
 
CONFIG_LOC=$(start_upload "$REPO")
upload_blob "$CONFIG_LOC" "$CONFIG_PATH" "sha256:$CONFIG_DIGEST" || exit 1
 
# -----------------------------
# 4. 上传 layers
# -----------------------------
declare -a LAYER_DIGESTS
declare -a LAYER_SIZES
 
for layer in "${LAYERS_ARRAY[@]}"; do
    LAYER_PATH="$TMPDIR/$layer"
    read DIGEST SIZE < <(calc_digest_size "$LAYER_PATH")
    LAYER_DIGESTS+=("sha256:$DIGEST")
    LAYER_SIZES+=("$SIZE")
     
    echo "[INFO] Layer 上传信息:"
    echo "       路径: $LAYER_PATH"
    echo "       大小: $SIZE"
    echo "       Digest: sha256:$DIGEST"
    echo
 
    UPLOC=$(start_upload "$REPO")
    upload_blob "$UPLOC" "$LAYER_PATH" "sha256:$DIGEST" || exit 1
done
 
# -----------------------------
# 5. 构造 manifest_final.json
# -----------------------------
MANIFEST_FINAL="$TMPDIR/manifest_final.json"
 
{
  echo "{"
  echo "  \"schemaVersion\": 2,"
  echo "  \"mediaType\": \"application/vnd.docker.distribution.manifest.v2+json\","
  echo "  \"config\": {"
  echo "    \"mediaType\": \"application/vnd.docker.container.image.v1+json\","
  echo "    \"size\": $CONFIG_SIZE,"
  echo "    \"digest\": \"sha256:$CONFIG_DIGEST\""
  echo "  },"
  echo "  \"layers\": ["
  for i in "${!LAYER_DIGESTS[@]}"; do
    echo "    {"
    echo "      \"mediaType\": \"application/vnd.docker.image.rootfs.diff.tar.gzip\","
    echo "      \"size\": ${LAYER_SIZES[i]},"
    echo "      \"digest\": \"${LAYER_DIGESTS[i]}\""
    if [ $i -lt $((${#LAYER_DIGESTS[@]} - 1)) ]; then
      echo "    },"
    else
      echo "    }"
    fi
  done
  echo "  ]"
  echo "}"
} > "$MANIFEST_FINAL"
 
echo "[INFO] 生成 manifest_final.json 内容如下:"
cat "$MANIFEST_FINAL" | jq .
echo
 
# -----------------------------
# 6. 上传 manifest
# -----------------------------
echo "[INFO] 上传 manifest -> $TAG"
RESPONSE=$($CURL_CMD -i -X PUT "https://$REGISTRY/v2/$REPO/manifests/$TAG" \
    -H "Content-Type: application/vnd.docker.distribution.manifest.v2+json" \
    --data-binary @"$MANIFEST_FINAL")
 
echo "$RESPONSE" | grep -E "HTTP/|Location:" || true
HTTP_STATUS=$(echo "$RESPONSE" | grep HTTP | tail -n1 | awk '{print $2}')
 
if [[ ! "$HTTP_STATUS" =~ ^2[0-9][0-9]$ ]]; then
    echo "[ERROR] manifest 上传失败 HTTP status=$HTTP_STATUS"
    exit 1
fi
 
echo
echo "[INFO] 镜像 push 完成!临时目录: $TMPDIR"
echo "[DEBUG] 你可以手动检查:"
echo "         manifest.json: $MANIFEST_JSON"
echo "         config.json: $CONFIG_PATH"
echo "         layers: ${#LAYERS_ARRAY[@]} 个文件"
# rm -rf "$TMPDIR"  # 调试完再打开删除

 

posted on 2025-12-16 19:20  吃草的青蛙  阅读(1)  评论(0)    收藏  举报

导航