Compare commits

..

No commits in common. "main" and "v0.14.3" have entirely different histories.

151 changed files with 3477 additions and 6892 deletions

View File

@ -3,43 +3,30 @@ updates:
# Automatic upgrade for go modules. # Automatic upgrade for go modules.
- package-ecosystem: "gomod" - package-ecosystem: "gomod"
directories: directory: "/"
- "/estargz"
- "/ipfs"
- "/"
- "/cmd"
schedule: schedule:
interval: "daily" interval: "daily"
ignore: ignore:
# We upgrade this manually on each release # We upgrade this manually on each release
- dependency-name: "github.com/containerd/stargz-snapshotter/estargz" - dependency-name: "github.com/containerd/stargz-snapshotter/estargz"
groups:
golang-x: # Automatic upgrade for go modules of estargz package.
patterns: - package-ecosystem: "gomod"
- "golang.org/x/*" directory: "/estargz"
google-golang: schedule:
patterns: interval: "daily"
- "google.golang.org/*"
containerd: # Automatic upgrade for go modules of ipfs package.
patterns: - package-ecosystem: "gomod"
- "github.com/containerd/*" directory: "/ipfs"
opencontainers: schedule:
patterns: interval: "daily"
- "github.com/opencontainers/*"
k8s: # Automatic upgrade for go modules of cmd package.
patterns: - package-ecosystem: "gomod"
- "k8s.io/*" directory: "/cmd"
gomod: schedule:
# this pattern covers all go dependencies that are not in interval: "daily"
# the above groups. dependabot doesn't seem to update sub-modules if
# a dependency doesn't belong to a group, so we define this group
# explicitly.
exclude-patterns:
- "golang.org/x/*"
- "google.golang.org/*"
- "github.com/containerd/*"
- "github.com/opencontainers/*"
- "k8s.io/*"
# Automatic upgrade for base images used in the Dockerfile # Automatic upgrade for base images used in the Dockerfile
- package-ecosystem: "docker" - package-ecosystem: "docker"

View File

@ -10,7 +10,7 @@ env:
jobs: jobs:
hello-bench: hello-bench:
runs-on: ubuntu-24.04 runs-on: ubuntu-22.04
name: HelloBench name: HelloBench
env: env:
BENCHMARK_LOG_DIR: ${{ github.workspace }}/log/ BENCHMARK_LOG_DIR: ${{ github.workspace }}/log/
@ -29,9 +29,9 @@ jobs:
steps: steps:
- name: Install tools - name: Install tools
run: | run: |
sudo apt-get update && \ sudo apt-get update && sudo apt-get --no-install-recommends install -y gnuplot
sudo apt-get install -y gnuplot python3-numpy pip install numpy
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Prepare directories - name: Prepare directories
run: mkdir "${BENCHMARK_RESULT_DIR}" "${BENCHMARK_LOG_DIR}" run: mkdir "${BENCHMARK_RESULT_DIR}" "${BENCHMARK_LOG_DIR}"
- name: Get instance information - name: Get instance information
@ -43,7 +43,7 @@ jobs:
env: env:
BENCHMARK_RUNTIME_MODE: ${{ matrix.runtime }} BENCHMARK_RUNTIME_MODE: ${{ matrix.runtime }}
run: make benchmark run: make benchmark
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v3
if: ${{ always() }} if: ${{ always() }}
with: with:
name: benchmarking-result-${{ matrix.runtime }} name: benchmarking-result-${{ matrix.runtime }}

View File

@ -3,7 +3,6 @@ on:
push: push:
tags: tags:
- 'v*' - 'v*'
pull_request:
env: env:
REGISTRY: ghcr.io REGISTRY: ghcr.io
@ -11,15 +10,15 @@ env:
jobs: jobs:
kind-image: kind-image:
runs-on: ubuntu-24.04 runs-on: ubuntu-22.04
name: Kind image name: Kind image
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v4
with: with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: | tags: |
@ -27,25 +26,16 @@ jobs:
- name: Login to GHCR - name: Login to GHCR
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
uses: docker/login-action@v3 uses: docker/login-action@v2
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
with:
image: tonistiigi/binfmt:qemu-v7.0.0-28
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push - name: Build and push
uses: docker/build-push-action@v6.18.0 uses: docker/build-push-action@v4.0.0
with: with:
context: . context: .
push: ${{ github.event_name != 'pull_request' }} push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64,linux/arm64

View File

@ -21,87 +21,79 @@ env:
jobs: jobs:
integration: integration:
runs-on: ubuntu-24.04 runs-on: ubuntu-22.04
name: Integration name: Integration
steps: steps:
- name: Install htpasswd for setting up private registry - name: Install htpasswd for setting up private registry
run: sudo apt-get update -y && sudo apt-get --no-install-recommends install -y apache2-utils run: sudo apt-get update -y && sudo apt-get --no-install-recommends install -y apache2-utils
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Run integration test - name: Run integration test
run: make integration run: make integration
test-optimize: test-optimize:
runs-on: ubuntu-24.04 runs-on: ubuntu-22.04
name: Optimize name: Optimize
steps: steps:
- name: Install htpasswd for setting up private registry - name: Install htpasswd for setting up private registry
run: sudo apt-get update -y && sudo apt-get --no-install-recommends install -y apache2-utils run: sudo apt-get update -y && sudo apt-get --no-install-recommends install -y apache2-utils
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Run test for optimize subcommand of ctr-remote - name: Run test for optimize subcommand of ctr-remote
run: make test-optimize run: make test-optimize
test-kind: test-kind:
runs-on: ubuntu-24.04 runs-on: ubuntu-22.04
name: Kind name: Kind
steps: steps:
- name: Install htpasswd for setting up private registry - name: Install htpasswd for setting up private registry
run: sudo apt-get update -y && sudo apt-get --no-install-recommends install -y apache2-utils run: sudo apt-get update -y && sudo apt-get --no-install-recommends install -y apache2-utils
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Run test for pulling image from private registry on Kubernetes - name: Run test for pulling image from private registry on Kubernetes
run: make test-kind run: make test-kind
test-criauth: test-criauth:
runs-on: ubuntu-24.04 runs-on: ubuntu-22.04
name: CRIAuth name: CRIAuth
steps: steps:
- name: Install htpasswd for setting up private registry - name: Install htpasswd for setting up private registry
run: sudo apt-get update -y && sudo apt-get --no-install-recommends install -y apache2-utils run: sudo apt-get update -y && sudo apt-get --no-install-recommends install -y apache2-utils
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Run test for pulling image from private registry on Kubernetes - name: Run test for pulling image from private registry on Kubernetes
run: make test-criauth run: make test-criauth
test-cri-containerd: test-cri-containerd:
runs-on: ubuntu-24.04 runs-on: ubuntu-22.04
name: CRIValidationContainerd name: CRIValidationContainerd
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Varidate the runtime through CRI with containerd - name: Varidate the runtime through CRI with containerd
run: make test-cri-containerd run: make test-cri-containerd
test-cri-o: test-cri-o:
runs-on: ubuntu-24.04 runs-on: ubuntu-18.04
name: CRIValidationCRIO name: CRIValidationCRIO
steps: steps:
- name: Install the latest docker - uses: actions/checkout@v3
run: |
sudo apt-get remove moby-cli moby-engine
wget -O get-docker.sh https://get.docker.com
sh get-docker.sh
- uses: actions/checkout@v4
- name: Varidate the runtime through CRI with CRI-O - name: Varidate the runtime through CRI with CRI-O
env: env:
DOCKER_BUILD_ARGS: "--build-arg=RUNC_VERSION=v1.0.3" DOCKER_BUILD_ARGS: "--build-arg=RUNC_VERSION=v1.0.3"
run: | run: make test-cri-o
# needed to pass "runtime should output OOMKilled reason" test
sudo swapoff -a
make test-cri-o
test-k3s: test-k3s:
runs-on: ubuntu-24.04 runs-on: ubuntu-22.04
name: K3S name: K3S
steps: steps:
- uses: actions/setup-go@v5 - uses: actions/setup-go@v3
with: with:
go-version: '1.24.x' go-version: '1.19.x'
- name: Install k3d - name: Install k3d
run: | run: |
wget -q -O - https://raw.githubusercontent.com/rancher/k3d/v5.6.3/install.sh | bash wget -q -O - https://raw.githubusercontent.com/rancher/k3d/v5.0.0/install.sh | bash
- name: Install htpasswd for setting up private registry - name: Install htpasswd for setting up private registry
run: sudo apt-get update -y && sudo apt-get --no-install-recommends install -y apache2-utils run: sudo apt-get update -y && sudo apt-get --no-install-recommends install -y apache2-utils
- name: Install yq - name: Install yq
run: | run: |
sudo wget -O /usr/local/bin/yq https://github.com/mikefarah/yq/releases/download/v4.9.3/yq_linux_amd64 sudo wget -O /usr/local/bin/yq https://github.com/mikefarah/yq/releases/download/v4.9.3/yq_linux_amd64
sudo chmod +x /usr/local/bin/yq sudo chmod +x /usr/local/bin/yq
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Run test with k3s - name: Run test with k3s
run: make test-k3s run: make test-k3s

View File

@ -9,7 +9,7 @@ env:
jobs: jobs:
build: build:
runs-on: ubuntu-24.04 runs-on: ubuntu-22.04
name: Build name: Build
strategy: strategy:
matrix: matrix:
@ -17,7 +17,7 @@ jobs:
env: env:
OUTPUT_DIR: ${{ github.workspace }}/out OUTPUT_DIR: ${{ github.workspace }}/out
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Build Binary - name: Build Binary
env: env:
DOCKER_BUILDKIT: 1 DOCKER_BUILDKIT: 1
@ -29,28 +29,26 @@ jobs:
if [ "${ARCH_ID}" == "arm-v7" ] ; then if [ "${ARCH_ID}" == "arm-v7" ] ; then
BUILD_ARGS="--build-arg=TARGETARCH=arm --build-arg=GOARM=7" BUILD_ARGS="--build-arg=TARGETARCH=arm --build-arg=GOARM=7"
fi fi
# make binaries static
BUILD_ARGS="$BUILD_ARGS --build-arg=CGO_ENABLED=0"
TAR_FILE_NAME="stargz-snapshotter-${RELEASE_TAG}-linux-${ARCH_ID}.tar.gz" TAR_FILE_NAME="stargz-snapshotter-${RELEASE_TAG}-linux-${ARCH_ID}.tar.gz"
SHA256SUM_FILE_NAME="${TAR_FILE_NAME}.sha256sum" SHA256SUM_FILE_NAME="${TAR_FILE_NAME}.sha256sum"
docker build ${BUILD_ARGS} --target release-binaries -o - . | gzip > "${OUTPUT_DIR}/${TAR_FILE_NAME}" docker build ${BUILD_ARGS} --target release-binaries -o - . | gzip > "${OUTPUT_DIR}/${TAR_FILE_NAME}"
( cd ${OUTPUT_DIR}; sha256sum ${TAR_FILE_NAME} ) > "${OUTPUT_DIR}/${SHA256SUM_FILE_NAME}" ( cd ${OUTPUT_DIR}; sha256sum ${TAR_FILE_NAME} ) > "${OUTPUT_DIR}/${SHA256SUM_FILE_NAME}"
- name: Save Binary - name: Save Binary
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: builds-${{ matrix.arch }} name: builds-${{ matrix.arch }}
path: ${{ env.OUTPUT_DIR }}/* path: ${{ env.OUTPUT_DIR }}/*
release: release:
runs-on: ubuntu-24.04 runs-on: ubuntu-22.04
name: Release name: Release
needs: [build] needs: [build]
env: env:
OUTPUT_DIR: ${{ github.workspace }}/builds OUTPUT_DIR: ${{ github.workspace }}/builds
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Download Builds - name: Download Builds
uses: actions/download-artifact@v5 uses: actions/download-artifact@v3
with: with:
path: ${{ env.OUTPUT_DIR }} path: ${{ env.OUTPUT_DIR }}
- name: Create Release - name: Create Release
@ -59,11 +57,15 @@ jobs:
run: | run: |
RELEASE_TAG="${GITHUB_REF##*/}" RELEASE_TAG="${GITHUB_REF##*/}"
cat <<EOF > ${GITHUB_WORKSPACE}/release-note.txt cat <<EOF > ${GITHUB_WORKSPACE}/release-note.txt
${RELEASE_TAG}
(TBD) (TBD)
EOF EOF
ASSET_ARGS=() ASSET_FLAGS=()
ls -al ${OUTPUT_DIR}/ ls -al ${OUTPUT_DIR}/
for A in "amd64" "arm-v7" "arm64" "ppc64le" "s390x" ; do for A in "amd64" "arm-v7" "arm64" "ppc64le" "s390x" ; do
ASSET_ARGS+=("${OUTPUT_DIR}/builds-${A}/*") for F in ${OUTPUT_DIR}/builds-${A}/* ; do
ASSET_FLAGS+=("-a" "$F")
done
done done
gh release create -F ${GITHUB_WORKSPACE}/release-note.txt --draft --title "${RELEASE_TAG}" "${RELEASE_TAG}" ${ASSET_ARGS[@]} hub release create "${ASSET_FLAGS[@]}" -F ${GITHUB_WORKSPACE}/release-note.txt --draft "${RELEASE_TAG}"

View File

@ -10,44 +10,39 @@ env:
jobs: jobs:
build: build:
runs-on: ubuntu-24.04 runs-on: ubuntu-22.04
name: Build name: Build
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Build all - name: Build all
run: ./script/util/make.sh build -j2 run: ./script/util/make.sh build -j2
test: test:
runs-on: ubuntu-24.04 runs-on: ubuntu-22.04
name: Test name: Test
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Test all - name: Test all
run: ./script/util/make.sh test-all -j2 run: ./script/util/make.sh test-all -j2
linter: linter:
runs-on: ubuntu-24.04 runs-on: ubuntu-22.04
name: Linter name: Linter
strategy:
fail-fast: false
matrix:
targetdir: [".", "./estargz", "./cmd", "./ipfs"]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
with: with:
fetch-depth: '0' fetch-depth: '0'
- uses: actions/setup-go@v5 - uses: actions/setup-go@v3
with: with:
go-version: '1.24.x' go-version: '1.19.x'
- name: golangci-lint - name: golangci-lint
uses: golangci/golangci-lint-action@v8.0.0 uses: golangci/golangci-lint-action@v3.4.0
with: with:
version: v2.1 version: v1.49.0
args: --verbose --timeout=10m args: --verbose
working-directory: ${{ matrix.targetdir }}
integration: integration:
runs-on: ubuntu-24.04 runs-on: ubuntu-22.04
name: Integration name: Integration
strategy: strategy:
fail-fast: false fail-fast: false
@ -55,9 +50,6 @@ jobs:
buildargs: ["", "--build-arg=CONTAINERD_VERSION=main"] # released version & main version buildargs: ["", "--build-arg=CONTAINERD_VERSION=main"] # released version & main version
builtin: ["true", "false"] builtin: ["true", "false"]
metadata-store: ["memory", "db"] metadata-store: ["memory", "db"]
fuse-passthrough: ["true", "false"]
fuse-manager: ["true", "false"]
transfer-service: ["true", "false"]
exclude: exclude:
- buildargs: "" - buildargs: ""
builtin: "true" builtin: "true"
@ -65,40 +57,19 @@ jobs:
builtin: "true" builtin: "true"
- metadata-store: "db" - metadata-store: "db"
buildargs: "--build-arg=CONTAINERD_VERSION=main" buildargs: "--build-arg=CONTAINERD_VERSION=main"
- fuse-passthrough: "true"
builtin: "true"
- fuse-passthrough: "true"
buildargs: "--build-arg=CONTAINERD_VERSION=main"
- fuse-passthrough: "true"
metadata-store: "db"
- fuse-manager: "true"
builtin: "true"
- fuse-manager: "true"
buildargs: "--build-arg=CONTAINERD_VERSION=main"
- transfer-service: "true"
buildargs: "--build-arg=CONTAINERD_VERSION=main"
- transfer-service: "true"
builtin: "true"
- transfer-service: "true"
metadata-store: "db"
- transfer-service: "true"
fuse-passthrough: "true"
steps: steps:
- name: Install htpasswd for setting up private registry - name: Install htpasswd for setting up private registry
run: sudo apt-get update -y && sudo apt-get --no-install-recommends install -y apache2-utils run: sudo apt-get update -y && sudo apt-get --no-install-recommends install -y apache2-utils
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Run integration test - name: Run integration test
env: env:
DOCKER_BUILD_ARGS: ${{ matrix.buildargs }} DOCKER_BUILD_ARGS: ${{ matrix.buildargs }}
BUILTIN_SNAPSHOTTER: ${{ matrix.builtin }} BUILTIN_SNAPSHOTTER: ${{ matrix.builtin }}
METADATA_STORE: ${{ matrix.metadata-store }} METADATA_STORE: ${{ matrix.metadata-store }}
FUSE_PASSTHROUGH: ${{ matrix.fuse-passthrough }}
FUSE_MANAGER: ${{ matrix.fuse-manager }}
TRANSFER_SERVICE: ${{ matrix.transfer-service }}
run: make integration run: make integration
test-optimize: test-optimize:
runs-on: ubuntu-24.04 runs-on: ubuntu-22.04
name: Optimize name: Optimize
strategy: strategy:
fail-fast: false fail-fast: false
@ -107,14 +78,14 @@ jobs:
steps: steps:
- name: Install htpasswd for setting up private registry - name: Install htpasswd for setting up private registry
run: sudo apt-get update -y && sudo apt-get --no-install-recommends install -y apache2-utils run: sudo apt-get update -y && sudo apt-get --no-install-recommends install -y apache2-utils
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Run test for optimize subcommand of ctr-remote - name: Run test for optimize subcommand of ctr-remote
env: env:
DOCKER_BUILD_ARGS: ${{ matrix.buildargs }} DOCKER_BUILD_ARGS: ${{ matrix.buildargs }}
run: make test-optimize run: make test-optimize
test-kind: test-kind:
runs-on: ubuntu-24.04 runs-on: ubuntu-22.04
name: Kind name: Kind
strategy: strategy:
fail-fast: false fail-fast: false
@ -127,7 +98,7 @@ jobs:
steps: steps:
- name: Install htpasswd for setting up private registry - name: Install htpasswd for setting up private registry
run: sudo apt-get update -y && sudo apt-get --no-install-recommends install -y apache2-utils run: sudo apt-get update -y && sudo apt-get --no-install-recommends install -y apache2-utils
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Run test for pulling image from private registry on Kubernetes - name: Run test for pulling image from private registry on Kubernetes
env: env:
DOCKER_BUILD_ARGS: ${{ matrix.buildargs }} DOCKER_BUILD_ARGS: ${{ matrix.buildargs }}
@ -135,7 +106,7 @@ jobs:
run: make test-kind run: make test-kind
test-criauth: test-criauth:
runs-on: ubuntu-24.04 runs-on: ubuntu-22.04
name: CRIAuth name: CRIAuth
strategy: strategy:
fail-fast: false fail-fast: false
@ -148,7 +119,7 @@ jobs:
steps: steps:
- name: Install htpasswd for setting up private registry - name: Install htpasswd for setting up private registry
run: sudo apt-get update -y && sudo apt-get --no-install-recommends install -y apache2-utils run: sudo apt-get update -y && sudo apt-get --no-install-recommends install -y apache2-utils
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Run test for pulling image from private registry on Kubernetes with CRI keychain mode - name: Run test for pulling image from private registry on Kubernetes with CRI keychain mode
env: env:
DOCKER_BUILD_ARGS: ${{ matrix.buildargs }} DOCKER_BUILD_ARGS: ${{ matrix.buildargs }}
@ -156,7 +127,7 @@ jobs:
run: make test-criauth run: make test-criauth
test-cri-containerd: test-cri-containerd:
runs-on: ubuntu-24.04 runs-on: ubuntu-22.04
name: CRIValidationContainerd name: CRIValidationContainerd
strategy: strategy:
fail-fast: false fail-fast: false
@ -164,9 +135,6 @@ jobs:
buildargs: ["", "--build-arg=CONTAINERD_VERSION=main"] # released version & main version buildargs: ["", "--build-arg=CONTAINERD_VERSION=main"] # released version & main version
builtin: ["true", "false"] builtin: ["true", "false"]
metadata-store: ["memory", "db"] metadata-store: ["memory", "db"]
fuse-passthrough: ["true", "false"]
fuse-manager: ["true", "false"]
transfer-service: ["true", "false"]
exclude: exclude:
- buildargs: "" - buildargs: ""
builtin: "true" builtin: "true"
@ -174,119 +142,85 @@ jobs:
builtin: "true" builtin: "true"
- metadata-store: "db" - metadata-store: "db"
buildargs: "--build-arg=CONTAINERD_VERSION=main" buildargs: "--build-arg=CONTAINERD_VERSION=main"
- fuse-passthrough: "true"
builtin: "true"
- fuse-passthrough: "true"
buildargs: "--build-arg=CONTAINERD_VERSION=main"
- fuse-passthrough: "true"
metadata-store: "db"
- fuse-manager: "true"
builtin: "true"
- fuse-manager: "true"
buildargs: "--build-arg=CONTAINERD_VERSION=main"
- transfer-service: "true"
buildargs: "--build-arg=CONTAINERD_VERSION=main"
- transfer-service: "true"
builtin: "true"
- transfer-service: "true"
metadata-store: "db"
- transfer-service: "true"
fuse-passthrough: "true"
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Validate containerd through CRI - name: Validate containerd through CRI
env: env:
DOCKER_BUILD_ARGS: ${{ matrix.buildargs }} DOCKER_BUILD_ARGS: ${{ matrix.buildargs }}
BUILTIN_SNAPSHOTTER: ${{ matrix.builtin }} BUILTIN_SNAPSHOTTER: ${{ matrix.builtin }}
METADATA_STORE: ${{ matrix.metadata-store }} METADATA_STORE: ${{ matrix.metadata-store }}
FUSE_PASSTHROUGH: ${{ matrix.fuse-passthrough }}
FUSE_MANAGER: ${{ matrix.fuse-manager }}
TRANSFER_SERVICE: ${{ matrix.transfer-service }}
run: make test-cri-containerd run: make test-cri-containerd
test-cri-cri-o: test-cri-cri-o:
runs-on: ubuntu-24.04 runs-on: ubuntu-18.04
name: CRIValidationCRIO name: CRIValidationCRIO
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
metadata-store: ["memory", "db"] metadata-store: ["memory", "db"]
steps: steps:
- name: Install the latest docker - uses: actions/checkout@v3
run: |
sudo apt-get remove moby-cli moby-engine
wget -O get-docker.sh https://get.docker.com
sh get-docker.sh
- uses: actions/checkout@v4
- name: Validate CRI-O through CRI - name: Validate CRI-O through CRI
env: env:
DOCKER_BUILD_ARGS: "--build-arg=RUNC_VERSION=v1.0.3" DOCKER_BUILD_ARGS: "--build-arg=RUNC_VERSION=v1.0.3"
METADATA_STORE: ${{ matrix.metadata-store }} METADATA_STORE: ${{ matrix.metadata-store }}
run: | run: make test-cri-o
# needed to pass "runtime should output OOMKilled reason" test
sudo swapoff -a
make test-cri-o
test-podman: test-podman:
runs-on: ubuntu-24.04 runs-on: ubuntu-22.04
name: PodmanRootless name: PodmanRootless
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Test Podman (rootless) - name: Test Podman (rootless)
run: make test-podman run: make test-podman
test-k3s: test-k3s:
runs-on: ubuntu-24.04 runs-on: ubuntu-22.04
name: K3S name: K3S
steps: steps:
- uses: actions/setup-go@v5 - uses: actions/setup-go@v3
with: with:
go-version: '1.24.x' go-version: '1.19.x'
- name: Install k3d - name: Install k3d
run: | run: |
wget -q -O - https://raw.githubusercontent.com/rancher/k3d/v5.6.3/install.sh | bash wget -q -O - https://raw.githubusercontent.com/rancher/k3d/v5.0.0/install.sh | bash
- name: Install htpasswd for setting up private registry - name: Install htpasswd for setting up private registry
run: sudo apt-get update -y && sudo apt-get --no-install-recommends install -y apache2-utils run: sudo apt-get update -y && sudo apt-get --no-install-recommends install -y apache2-utils
- name: Install yq - name: Install yq
run: | run: |
sudo wget -O /usr/local/bin/yq https://github.com/mikefarah/yq/releases/download/v4.9.3/yq_linux_amd64 sudo wget -O /usr/local/bin/yq https://github.com/mikefarah/yq/releases/download/v4.9.3/yq_linux_amd64
sudo chmod +x /usr/local/bin/yq sudo chmod +x /usr/local/bin/yq
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Run test with k3s - name: Run test with k3s
run: make test-k3s run: make test-k3s
test-ipfs: test-ipfs:
runs-on: ubuntu-24.04 runs-on: ubuntu-22.04
name: IPFS name: IPFS
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Run test - name: Run test
run: make test-ipfs run: make test-ipfs
test-k3s-argo-workflow: test-k3s-argo-workflow:
runs-on: ubuntu-24.04 runs-on: ubuntu-22.04
name: K3SArgoWorkflow name: K3SArgoWorkflow
env: env:
RESULT_DIR: ${{ github.workspace }}/argo-workflow/ RESULT_DIR: ${{ github.workspace }}/argo-workflow/
steps: steps:
- uses: actions/setup-go@v5 - uses: actions/setup-go@v3
with: with:
go-version: '1.24.x' go-version: '1.19.x'
- name: Install k3d - name: Install k3d
run: | run: |
wget -q -O - https://raw.githubusercontent.com/rancher/k3d/v5.6.3/install.sh | bash wget -q -O - https://raw.githubusercontent.com/rancher/k3d/v5.0.0/install.sh | bash
- name: Install argo worklflow - name: Install argo worklflow
run: | run: |
wget -q https://github.com/argoproj/argo-workflows/releases/download/v3.0.10/argo-linux-amd64.gz wget -q https://github.com/argoproj/argo-workflows/releases/download/v3.0.10/argo-linux-amd64.gz
gunzip argo-linux-amd64.gz gunzip argo-linux-amd64.gz
sudo mv argo-linux-amd64 /usr/local/bin/argo sudo mv argo-linux-amd64 /usr/local/bin/argo
sudo chmod +x /usr/local/bin/argo sudo chmod +x /usr/local/bin/argo
- name: Workaround for freeing up more disk space - uses: actions/checkout@v3
# https://github.com/actions/runner-images/issues/2606
run: |
sudo rm -rf /usr/local/lib/android # will release about 10 GB if you don't need Android
sudo rm -rf /usr/share/dotnet # will release about 20GB if you don't need .NET
- uses: actions/checkout@v4
- name: Prepare directories - name: Prepare directories
run: mkdir "${RESULT_DIR}" run: mkdir "${RESULT_DIR}"
- name: Get instance information - name: Get instance information
@ -298,7 +232,7 @@ jobs:
env: env:
RESULT: ${{ env.RESULT_DIR }}/result.json RESULT: ${{ env.RESULT_DIR }}/result.json
run: make test-k3s-argo-workflow run: make test-k3s-argo-workflow
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v3
with: with:
name: k3s-argo-workflow name: k3s-argo-workflow
path: ${{ env.RESULT_DIR }} path: ${{ env.RESULT_DIR }}
@ -311,43 +245,21 @@ jobs:
project: project:
name: Project Checks name: Project Checks
runs-on: ubuntu-24.04 runs-on: ubuntu-22.04
timeout-minutes: 10 timeout-minutes: 5
steps: steps:
- uses: actions/setup-go@v5 - uses: actions/setup-go@v3
with: with:
go-version: '1.24.x' go-version: '1.19.x'
- uses: actions/checkout@v4 - uses: actions/checkout@v3
with: with:
path: src/github.com/containerd/stargz-snapshotter path: src/github.com/containerd/stargz-snapshotter
fetch-depth: 25 fetch-depth: 25
- uses: containerd/project-checks@v1.2.2 - uses: containerd/project-checks@v1
with: with:
working-directory: src/github.com/containerd/stargz-snapshotter working-directory: src/github.com/containerd/stargz-snapshotter
# go-licenses-ignore is set because go-licenses cannot correctly detect the license of the following packages:
# * estargz packages: Apache-2.0 and BSD-3-Clause dual license
# (https://github.com/containerd/stargz-snapshotter/blob/main/NOTICE.md)
#
# The list of the CNCF-approved licenses can be found here:
# https://github.com/cncf/foundation/blob/main/allowed-third-party-license-policy.md
#
# hashicorp packages: MPL-2.0
# (https://github.com/hashicorp/go-cleanhttp/blob/master/LICENSE,
# https://github.com/hashicorp/go-retryablehttp/blob/master/LICENSE)
# Note: MPL-2.0 is not in the CNCF-approved licenses list, but these packages are allowed as exceptions.
# See CNCF licensing exceptions:
# https://github.com/cncf/foundation/blob/main/license-exceptions/CNCF-licensing-exceptions.csv
go-licenses-ignore: |
github.com/containerd/stargz-snapshotter/estargz
github.com/containerd/stargz-snapshotter/estargz/errorutil
github.com/containerd/stargz-snapshotter/estargz/externaltoc
github.com/containerd/stargz-snapshotter/estargz/zstdchunked
github.com/hashicorp/go-cleanhttp
github.com/hashicorp/go-retryablehttp
- name: Check proto generated code - name: Check proto generated code
run: make validate-generated run: make validate-generated
working-directory: src/github.com/containerd/stargz-snapshotter working-directory: src/github.com/containerd/stargz-snapshotter
- run: ./script/util/verify-no-patent.sh - run: ./script/util/verify-no-patent.sh
working-directory: src/github.com/containerd/stargz-snapshotter working-directory: src/github.com/containerd/stargz-snapshotter
- run: make validate-vendor
working-directory: src/github.com/containerd/stargz-snapshotter

View File

@ -1,54 +1,26 @@
version: "2" # This is applied to `estargz` submodule as well.
# https://golangci-lint.run/usage/configuration#config-file
linters: linters:
enable: enable:
- depguard - structcheck
- misspell - varcheck
- revive - staticcheck
- unconvert - unconvert
disable:
- errcheck
settings:
depguard:
rules:
main:
deny:
- pkg: github.com/containerd/containerd/errdefs
desc: The containerd errdefs package was migrated to a separate module. Use github.com/containerd/errdefs instead.
- pkg: github.com/containerd/containerd/log
desc: The containerd log package was migrated to a separate module. Use github.com/containerd/log instead.
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
rules:
- linters:
- revive
text: unused-parameter
- linters:
- revive
text: redefines-builtin-id
paths:
- docs
- images
- out
- script
- third_party$
- builtin$
- examples$
formatters:
enable:
- gofmt - gofmt
- goimports - goimports
exclusions: - revive
generated: lax - ineffassign
paths: - vet
- docs - unused
- images - misspell
- out disable:
- script - errcheck
- third_party$
- builtin$ run:
- examples$ deadline: 4m
skip-dirs:
- docs
- images
- out
- script

View File

@ -12,45 +12,43 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
ARG CONTAINERD_VERSION=v2.1.3 ARG CONTAINERD_VERSION=v1.7.0-rc.2
ARG RUNC_VERSION=v1.3.0 ARG RUNC_VERSION=v1.1.4
ARG CNI_PLUGINS_VERSION=v1.7.1 ARG CNI_PLUGINS_VERSION=v1.1.1
ARG NERDCTL_VERSION=2.1.3 ARG NERDCTL_VERSION=1.1.0
ARG PODMAN_VERSION=v5.5.2 ARG PODMAN_VERSION=v4.3.1
ARG CRIO_VERSION=v1.33.2 ARG CRIO_VERSION=v1.26.1
ARG CONMON_VERSION=v2.1.13 ARG CONMON_VERSION=v2.1.5
ARG COMMON_VERSION=v0.63.0 ARG COMMON_VERSION=v0.49.2
ARG CRIO_TEST_PAUSE_IMAGE_NAME=registry.k8s.io/pause:3.6 ARG CRIO_TEST_PAUSE_IMAGE_NAME=registry.k8s.io/pause:3.6
ARG NETAVARK_VERSION=v1.15.2
ARG CONTAINERIZED_SYSTEMD_VERSION=v0.1.1 ARG CONTAINERIZED_SYSTEMD_VERSION=v0.1.1
ARG SLIRP4NETNS_VERSION=v1.3.3 ARG SLIRP4NETNS_VERSION=v1.1.12
ARG PAUSE_IMAGE_NAME_TEST=registry.k8s.io/pause:3.10.1
# Used in CI # Used in CI
ARG CRI_TOOLS_VERSION=v1.30.1 ARG CRI_TOOLS_VERSION=v1.26.0
# Legacy builder that doesn't support TARGETARCH should set this explicitly using --build-arg. # Legacy builder that doesn't support TARGETARCH should set this explicitly using --build-arg.
# If TARGETARCH isn't supported by the builder, the default value is "amd64". # If TARGETARCH isn't supported by the builder, the default value is "amd64".
FROM golang:1.24-bullseye AS golang-base FROM golang:1.20.1-bullseye AS golang-base
# Build containerd # Build containerd
FROM --platform=$BUILDPLATFORM golang:1.24-bullseye AS containerd-dev FROM golang-base AS containerd-dev
ARG CONTAINERD_VERSION ARG CONTAINERD_VERSION
ARG TARGETARCH RUN apt-get update -y && apt-get install -y libbtrfs-dev libseccomp-dev && \
RUN git clone -b ${CONTAINERD_VERSION} --depth 1 \ git clone -b ${CONTAINERD_VERSION} --depth 1 \
https://github.com/containerd/containerd $GOPATH/src/github.com/containerd/containerd && \ https://github.com/containerd/containerd $GOPATH/src/github.com/containerd/containerd && \
cd $GOPATH/src/github.com/containerd/containerd && \ cd $GOPATH/src/github.com/containerd/containerd && \
GOARCH=$TARGETARCH make && DESTDIR=/out/ PREFIX= make install make && DESTDIR=/out/ PREFIX= make install
# Build containerd with builtin stargz snapshotter # Build containerd with builtin stargz snapshotter
FROM --platform=$BUILDPLATFORM golang:1.24-bullseye AS containerd-snapshotter-dev FROM golang-base AS containerd-snapshotter-dev
ARG CONTAINERD_VERSION ARG CONTAINERD_VERSION
ARG TARGETARCH
COPY . $GOPATH/src/github.com/containerd/stargz-snapshotter COPY . $GOPATH/src/github.com/containerd/stargz-snapshotter
RUN git clone -b ${CONTAINERD_VERSION} --depth 1 \ RUN apt-get update -y && apt-get install -y libbtrfs-dev libseccomp-dev && \
git clone -b ${CONTAINERD_VERSION} --depth 1 \
https://github.com/containerd/containerd $GOPATH/src/github.com/containerd/containerd && \ https://github.com/containerd/containerd $GOPATH/src/github.com/containerd/containerd && \
cd $GOPATH/src/github.com/containerd/containerd && \ cd $GOPATH/src/github.com/containerd/containerd && \
echo 'require github.com/containerd/stargz-snapshotter v0.0.0' >> go.mod && \ echo 'require github.com/containerd/stargz-snapshotter v0.0.0' >> go.mod && \
@ -66,10 +64,10 @@ RUN git clone -b ${CONTAINERD_VERSION} --depth 1 \
echo 'replace github.com/containerd/stargz-snapshotter/estargz => '$GOPATH'/src/github.com/containerd/stargz-snapshotter/estargz' >> integration/client/go.mod ; \ echo 'replace github.com/containerd/stargz-snapshotter/estargz => '$GOPATH'/src/github.com/containerd/stargz-snapshotter/estargz' >> integration/client/go.mod ; \
fi && \ fi && \
echo 'package main \nimport _ "github.com/containerd/stargz-snapshotter/service/plugin"' > cmd/containerd/builtins_stargz_snapshotter.go && \ echo 'package main \nimport _ "github.com/containerd/stargz-snapshotter/service/plugin"' > cmd/containerd/builtins_stargz_snapshotter.go && \
make vendor && GOARCH=$TARGETARCH make && DESTDIR=/out/ PREFIX= make install make vendor && make && DESTDIR=/out/ PREFIX= make install
# Build runc # Build runc
FROM golang:1.24-bullseye AS runc-dev FROM golang-base AS runc-dev
ARG RUNC_VERSION ARG RUNC_VERSION
RUN apt-get update -y && apt-get install -y libseccomp-dev && \ RUN apt-get update -y && apt-get install -y libseccomp-dev && \
git clone -b ${RUNC_VERSION} --depth 1 \ git clone -b ${RUNC_VERSION} --depth 1 \
@ -78,17 +76,15 @@ RUN apt-get update -y && apt-get install -y libseccomp-dev && \
make && make install PREFIX=/out/ make && make install PREFIX=/out/
# Build stargz snapshotter # Build stargz snapshotter
FROM --platform=$BUILDPLATFORM golang:1.24-bullseye AS snapshotter-dev FROM golang-base AS snapshotter-dev
ARG TARGETARCH ARG TARGETARCH
ARG GOARM ARG GOARM
ARG SNAPSHOTTER_BUILD_FLAGS ARG SNAPSHOTTER_BUILD_FLAGS
ARG CTR_REMOTE_BUILD_FLAGS ARG CTR_REMOTE_BUILD_FLAGS
COPY . $GOPATH/src/github.com/containerd/stargz-snapshotter COPY . $GOPATH/src/github.com/containerd/stargz-snapshotter
ARG CGO_ENABLED
RUN cd $GOPATH/src/github.com/containerd/stargz-snapshotter && \ RUN cd $GOPATH/src/github.com/containerd/stargz-snapshotter && \
PREFIX=/out/ GOARCH=${TARGETARCH:-amd64} GO_BUILD_FLAGS=${SNAPSHOTTER_BUILD_FLAGS} make containerd-stargz-grpc && \ PREFIX=/out/ GOARCH=${TARGETARCH:-amd64} GO_BUILD_FLAGS=${SNAPSHOTTER_BUILD_FLAGS} make containerd-stargz-grpc && \
PREFIX=/out/ GOARCH=${TARGETARCH:-amd64} GO_BUILD_FLAGS=${CTR_REMOTE_BUILD_FLAGS} make ctr-remote && \ PREFIX=/out/ GOARCH=${TARGETARCH:-amd64} GO_BUILD_FLAGS=${CTR_REMOTE_BUILD_FLAGS} make ctr-remote
PREFIX=/out/ GOARCH=${TARGETARCH:-amd64} GO_BUILD_FLAGS=${CTR_REMOTE_BUILD_FLAGS} make stargz-fuse-manager
# Build stargz store # Build stargz store
FROM golang-base AS stargz-store-dev FROM golang-base AS stargz-store-dev
@ -97,9 +93,8 @@ ARG GOARM
ARG SNAPSHOTTER_BUILD_FLAGS ARG SNAPSHOTTER_BUILD_FLAGS
ARG CTR_REMOTE_BUILD_FLAGS ARG CTR_REMOTE_BUILD_FLAGS
COPY . $GOPATH/src/github.com/containerd/stargz-snapshotter COPY . $GOPATH/src/github.com/containerd/stargz-snapshotter
ARG CGO_ENABLED
RUN cd $GOPATH/src/github.com/containerd/stargz-snapshotter && \ RUN cd $GOPATH/src/github.com/containerd/stargz-snapshotter && \
PREFIX=/out/ GOARCH=${TARGETARCH:-amd64} GO_BUILD_FLAGS=${SNAPSHOTTER_BUILD_FLAGS} make stargz-store stargz-store-helper PREFIX=/out/ GOARCH=${TARGETARCH:-amd64} GO_BUILD_FLAGS=${SNAPSHOTTER_BUILD_FLAGS} make stargz-store
# Build podman # Build podman
FROM golang-base AS podman-dev FROM golang-base AS podman-dev
@ -111,8 +106,7 @@ RUN apt-get update -y && apt-get install -y libseccomp-dev libgpgme-dev && \
make && make install PREFIX=/out/ make && make install PREFIX=/out/
# Build CRI-O # Build CRI-O
# FROM golang-base AS cri-o-dev FROM golang-base AS cri-o-dev
FROM golang:1.24-bullseye AS cri-o-dev
ARG CRIO_VERSION ARG CRIO_VERSION
RUN apt-get update -y && apt-get install -y libseccomp-dev libgpgme-dev && \ RUN apt-get update -y && apt-get install -y libseccomp-dev libgpgme-dev && \
git clone https://github.com/cri-o/cri-o $GOPATH/src/github.com/cri-o/cri-o && \ git clone https://github.com/cri-o/cri-o $GOPATH/src/github.com/cri-o/cri-o && \
@ -172,13 +166,12 @@ COPY --from=snapshotter-dev /out/ctr-remote /usr/local/bin/
RUN ln -s /usr/local/bin/ctr-remote /usr/local/bin/ctr RUN ln -s /usr/local/bin/ctr-remote /usr/local/bin/ctr
# Base image which contains podman with stargz-store # Base image which contains podman with stargz-store
FROM ubuntu:24.04 AS podman-base FROM golang-base AS podman-base
ARG TARGETARCH ARG TARGETARCH
ARG CNI_PLUGINS_VERSION ARG CNI_PLUGINS_VERSION
ARG PODMAN_VERSION ARG PODMAN_VERSION
ARG NETAVARK_VERSION RUN apt-get update -y && apt-get --no-install-recommends install -y fuse3 libgpgme-dev \
RUN apt-get update -y && DEBIAN_FRONTEND=noninteractive apt-get install -y fuse3 libgpgme-dev \ iptables libyajl-dev && \
iptables libyajl-dev curl ca-certificates libglib2.0 libseccomp-dev wget && \
# Make CNI plugins manipulate iptables instead of nftables # Make CNI plugins manipulate iptables instead of nftables
# as this test runs in a Docker container that network is configured with iptables. # as this test runs in a Docker container that network is configured with iptables.
# c.f. https://github.com/moby/moby/issues/26824 # c.f. https://github.com/moby/moby/issues/26824
@ -187,13 +180,6 @@ RUN apt-get update -y && DEBIAN_FRONTEND=noninteractive apt-get install -y fuse3
curl -qsSL https://raw.githubusercontent.com/containers/podman/${PODMAN_VERSION}/cni/87-podman-bridge.conflist | tee /etc/cni/net.d/87-podman-bridge.conflist && \ curl -qsSL https://raw.githubusercontent.com/containers/podman/${PODMAN_VERSION}/cni/87-podman-bridge.conflist | tee /etc/cni/net.d/87-podman-bridge.conflist && \
curl -Ls https://github.com/containernetworking/plugins/releases/download/${CNI_PLUGINS_VERSION}/cni-plugins-linux-${TARGETARCH:-amd64}-${CNI_PLUGINS_VERSION}.tgz | tar xzv -C /opt/cni/bin curl -Ls https://github.com/containernetworking/plugins/releases/download/${CNI_PLUGINS_VERSION}/cni-plugins-linux-${TARGETARCH:-amd64}-${CNI_PLUGINS_VERSION}.tgz | tar xzv -C /opt/cni/bin
RUN mkdir /tmp/netavark ; \
wget -O /tmp/netavark/netavark.gz https://github.com/containers/netavark/releases/download/${NETAVARK_VERSION}/netavark.gz ; \
gunzip /tmp/netavark/netavark.gz ; \
mkdir -p /usr/local/libexec/podman ; \
mv /tmp/netavark/netavark /usr/local/libexec/podman/ ; \
chmod 0755 /usr/local/libexec/podman/netavark
COPY --from=podman-dev /out/bin/* /usr/local/bin/ COPY --from=podman-dev /out/bin/* /usr/local/bin/
COPY --from=runc-dev /out/sbin/* /usr/local/sbin/ COPY --from=runc-dev /out/sbin/* /usr/local/sbin/
COPY --from=conmon-dev /out/bin/* /usr/local/bin/ COPY --from=conmon-dev /out/bin/* /usr/local/bin/
@ -217,9 +203,6 @@ RUN curl -o /usr/local/bin/slirp4netns --fail -L https://github.com/rootless-con
COPY ./script/podman/config/storage.conf /home/rootless/.config/containers/storage.conf COPY ./script/podman/config/storage.conf /home/rootless/.config/containers/storage.conf
# Stargz Store systemd service for rootless Podman # Stargz Store systemd service for rootless Podman
COPY ./script/podman/config/podman-rootless-stargz-store.service /home/rootless/.config/systemd/user/ COPY ./script/podman/config/podman-rootless-stargz-store.service /home/rootless/.config/systemd/user/
COPY ./script/podman/config/containers.conf /home/rootless/.config/containers/containers.conf
# test-podman-rootless.sh logins to the user via SSH # test-podman-rootless.sh logins to the user via SSH
COPY ./script/podman/config/test-podman-rootless.sh /test-podman-rootless.sh COPY ./script/podman/config/test-podman-rootless.sh /test-podman-rootless.sh
RUN ssh-keygen -q -t rsa -f /root/.ssh/id_rsa -N '' && \ RUN ssh-keygen -q -t rsa -f /root/.ssh/id_rsa -N '' && \
@ -246,7 +229,7 @@ RUN apt-get update && apt-get install -y iptables && \
curl -Ls https://github.com/containernetworking/plugins/releases/download/${CNI_PLUGINS_VERSION}/cni-plugins-linux-${TARGETARCH:-amd64}-${CNI_PLUGINS_VERSION}.tgz | tar xzv -C /opt/cni/bin curl -Ls https://github.com/containernetworking/plugins/releases/download/${CNI_PLUGINS_VERSION}/cni-plugins-linux-${TARGETARCH:-amd64}-${CNI_PLUGINS_VERSION}.tgz | tar xzv -C /opt/cni/bin
# Image which can be used as a node image for KinD (containerd with builtin snapshotter) # Image which can be used as a node image for KinD (containerd with builtin snapshotter)
FROM kindest/node:v1.33.2 AS kind-builtin-snapshotter FROM kindest/node:v1.26.0 AS kind-builtin-snapshotter
COPY --from=containerd-snapshotter-dev /out/bin/containerd /out/bin/containerd-shim-runc-v2 /usr/local/bin/ COPY --from=containerd-snapshotter-dev /out/bin/containerd /out/bin/containerd-shim-runc-v2 /usr/local/bin/
COPY --from=snapshotter-dev /out/ctr-remote /usr/local/bin/ COPY --from=snapshotter-dev /out/ctr-remote /usr/local/bin/
COPY ./script/config/ / COPY ./script/config/ /
@ -255,7 +238,7 @@ ENTRYPOINT [ "/usr/local/bin/kind-entrypoint.sh", "/usr/local/bin/entrypoint", "
# Image for testing CRI-O with Stargz Store. # Image for testing CRI-O with Stargz Store.
# NOTE: This cannot be used for the node image of KinD. # NOTE: This cannot be used for the node image of KinD.
FROM ubuntu:24.04 AS crio-stargz-store FROM ubuntu:23.04 AS crio-stargz-store
ARG CNI_PLUGINS_VERSION ARG CNI_PLUGINS_VERSION
ARG CRIO_TEST_PAUSE_IMAGE_NAME ARG CRIO_TEST_PAUSE_IMAGE_NAME
ENV container docker ENV container docker
@ -287,7 +270,7 @@ COPY ./script/config-cri-o/ /
ENTRYPOINT [ "/usr/local/bin/entrypoint" ] ENTRYPOINT [ "/usr/local/bin/entrypoint" ]
# Image which can be used as a node image for KinD # Image which can be used as a node image for KinD
FROM kindest/node:v1.33.2 FROM kindest/node:v1.26.0
COPY --from=containerd-dev /out/bin/containerd /out/bin/containerd-shim-runc-v2 /usr/local/bin/ COPY --from=containerd-dev /out/bin/containerd /out/bin/containerd-shim-runc-v2 /usr/local/bin/
COPY --from=snapshotter-dev /out/* /usr/local/bin/ COPY --from=snapshotter-dev /out/* /usr/local/bin/
COPY ./script/config/ / COPY ./script/config/ /

View File

@ -24,7 +24,7 @@ REVISION=$(shell git rev-parse HEAD)$(shell if ! git diff --no-ext-diff --quiet
GO_BUILD_LDFLAGS ?= -s -w GO_BUILD_LDFLAGS ?= -s -w
GO_LD_FLAGS=-ldflags '$(GO_BUILD_LDFLAGS) -X $(PKG)/version.Version=$(VERSION) -X $(PKG)/version.Revision=$(REVISION) $(GO_EXTRA_LDFLAGS)' GO_LD_FLAGS=-ldflags '$(GO_BUILD_LDFLAGS) -X $(PKG)/version.Version=$(VERSION) -X $(PKG)/version.Revision=$(REVISION) $(GO_EXTRA_LDFLAGS)'
CMD=containerd-stargz-grpc ctr-remote stargz-store stargz-fuse-manager CMD=containerd-stargz-grpc ctr-remote stargz-store
CMD_BINARIES=$(addprefix $(PREFIX),$(CMD)) CMD_BINARIES=$(addprefix $(PREFIX),$(CMD))
@ -45,12 +45,6 @@ ctr-remote: FORCE
stargz-store: FORCE stargz-store: FORCE
cd cmd/ ; GO111MODULE=$(GO111MODULE_VALUE) go build -o $(PREFIX)$@ $(GO_BUILD_FLAGS) $(GO_LD_FLAGS) -v ./stargz-store cd cmd/ ; GO111MODULE=$(GO111MODULE_VALUE) go build -o $(PREFIX)$@ $(GO_BUILD_FLAGS) $(GO_LD_FLAGS) -v ./stargz-store
stargz-store-helper: FORCE
cd cmd/ ; GO111MODULE=$(GO111MODULE_VALUE) go build -o $(PREFIX)$@ $(GO_BUILD_FLAGS) $(GO_LD_FLAGS) -v ./stargz-store/helper
stargz-fuse-manager: FORCE
cd cmd/ ; GO111MODULE=$(GO111MODULE_VALUE) go build -o $(PREFIX)$@ $(GO_BUILD_FLAGS) $(GO_LD_FLAGS) -v ./stargz-fuse-manager
check: check:
@echo "$@" @echo "$@"
@GO111MODULE=$(GO111MODULE_VALUE) $(shell go env GOPATH)/bin/golangci-lint run @GO111MODULE=$(GO111MODULE_VALUE) $(shell go env GOPATH)/bin/golangci-lint run
@ -128,10 +122,3 @@ test-k3s-argo-workflow:
test-ipfs: test-ipfs:
@./script/ipfs/test.sh @./script/ipfs/test.sh
validate-vendor:
$(eval TMPDIR := $(shell mktemp -d))
@cp -R $(CURDIR) ${TMPDIR}
@(cd ${TMPDIR}/stargz-snapshotter && make vendor)
@diff -r -u -q $(CURDIR) ${TMPDIR}/stargz-snapshotter
@rm -rf ${TMPDIR}

View File

@ -62,8 +62,6 @@ version = 2
[proxy_plugins.stargz] [proxy_plugins.stargz]
type = "snapshot" type = "snapshot"
address = "/run/containerd-stargz-grpc/containerd-stargz-grpc.sock" address = "/run/containerd-stargz-grpc/containerd-stargz-grpc.sock"
[proxy_plugins.stargz.exports]
root = "/var/lib/containerd-stargz-grpc/"
# Use stargz snapshotter through CRI # Use stargz snapshotter through CRI
[plugins."io.containerd.grpc.v1.cri".containerd] [plugins."io.containerd.grpc.v1.cri".containerd]
@ -71,6 +69,8 @@ version = 2
disable_snapshot_annotations = false disable_snapshot_annotations = false
``` ```
**Note that `disable_snapshot_annotations = false` is required since containerd > v1.4.2**
You can try our [prebuilt](/Dockerfile) [KinD](https://github.com/kubernetes-sigs/kind) node image that contains the above configuration. You can try our [prebuilt](/Dockerfile) [KinD](https://github.com/kubernetes-sigs/kind) node image that contains the above configuration.
```console ```console
@ -147,7 +147,7 @@ $ docker buildx build -t ghcr.io/ktock/hello:esgz \
> NOTE2: Docker still does not support lazy pulling of eStargz. > NOTE2: Docker still does not support lazy pulling of eStargz.
eStargz-enabled BuildKit (v0.10) will be [included to Docker v22.XX](https://github.com/moby/moby/blob/v22.06.0-beta.0/vendor.mod#L51) however you can build eStargz images with the prior version using Buildx [driver](https://github.com/docker/buildx/blob/master/docs/reference/buildx_create.md#-set-the-builder-driver-to-use---driver) feature. eStargz-enaled BuildKit (v0.10) will be [included to Docker v22.XX](https://github.com/moby/moby/blob/v22.06.0-beta.0/vendor.mod#L51) however you can build eStargz images with the prior version using Buildx [driver](https://github.com/docker/buildx/blob/master/docs/reference/buildx_create.md#-set-the-builder-driver-to-use---driver) feature.
You can enable the specific version of BuildKit using [`docker buildx create`](https://docs.docker.com/engine/reference/commandline/buildx_create/) (this example specifies `v0.10.3`). You can enable the specific version of BuildKit using [`docker buildx create`](https://docs.docker.com/engine/reference/commandline/buildx_create/) (this example specifies `v0.10.3`).
``` ```
@ -230,6 +230,28 @@ bin boot dev etc home lib lib32 lib64 libx32 media mnt opt proc roo
> NOTE: You can perform lazy pulling from any OCI-compatible registries (e.g. docker.io, ghcr.io, etc) as long as the image is formatted as eStargz. > NOTE: You can perform lazy pulling from any OCI-compatible registries (e.g. docker.io, ghcr.io, etc) as long as the image is formatted as eStargz.
### Registry-side conversion with `estargz.kontain.me`
You can convert arbitrary images into eStargz on the registry-side, using [`estargz.kontain.me`](https://estargz.kontain.me).
`estargz.kontain.me/[image]` serves eStargz-converted version of an arbitrary public image.
For example, the following Kubernetes manifest performs lazy pulling of eStargz-formatted version of `docker.io/library/nginx:1.21.1` that is converted by `estargz.kontain.me`.
```yaml
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: estargz.kontain.me/docker.io/library/nginx:1.21.1
ports:
- containerPort: 80
```
> WARNING: Before trying this method, read [caveats from kontain.me](https://github.com/imjasonh/kontain.me#caveats). If you rely on it in production, you should copy the image to your own registry or build eStargz by your own using `ctr-remote` as described in the following.
## Importing Stargz Snapshotter as go module ## Importing Stargz Snapshotter as go module
Currently, Stargz Snapshotter repository contains two Go modules as the following and both of them need to be imported. Currently, Stargz Snapshotter repository contains two Go modules as the following and both of them need to be imported.

View File

@ -30,16 +30,16 @@ import (
"time" "time"
"github.com/containerd/console" "github.com/containerd/console"
containerd "github.com/containerd/containerd/v2/client" "github.com/containerd/containerd"
"github.com/containerd/containerd/v2/cmd/ctr/commands" "github.com/containerd/containerd/cio"
"github.com/containerd/containerd/v2/cmd/ctr/commands/tasks" "github.com/containerd/containerd/cmd/ctr/commands"
"github.com/containerd/containerd/v2/core/mount" "github.com/containerd/containerd/cmd/ctr/commands/tasks"
"github.com/containerd/containerd/v2/core/snapshots" "github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/v2/pkg/cio" "github.com/containerd/containerd/log"
"github.com/containerd/containerd/v2/pkg/oci" "github.com/containerd/containerd/mount"
"github.com/containerd/errdefs" "github.com/containerd/containerd/oci"
"github.com/containerd/log" "github.com/containerd/containerd/platforms"
"github.com/containerd/platforms" "github.com/containerd/containerd/snapshots"
"github.com/containerd/stargz-snapshotter/analyzer/fanotify" "github.com/containerd/stargz-snapshotter/analyzer/fanotify"
"github.com/containerd/stargz-snapshotter/analyzer/recorder" "github.com/containerd/stargz-snapshotter/analyzer/recorder"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
@ -225,9 +225,6 @@ func Analyze(ctx context.Context, client *containerd.Client, ref string, opts ..
successCount++ successCount++
} }
}() }()
if err := task.Start(ctx); err != nil {
return "", err
}
if aOpts.terminal { if aOpts.terminal {
if err := tasks.HandleConsoleResize(ctx, task, con); err != nil { if err := tasks.HandleConsoleResize(ctx, task, con); err != nil {
log.G(ctx).WithError(err).Error("failed to resize console") log.G(ctx).WithError(err).Error("failed to resize console")
@ -236,6 +233,9 @@ func Analyze(ctx context.Context, client *containerd.Client, ref string, opts ..
sigc := commands.ForwardAllSignals(ctx, task) sigc := commands.ForwardAllSignals(ctx, task)
defer commands.StopCatch(sigc) defer commands.StopCatch(sigc)
} }
if err := task.Start(ctx); err != nil {
return "", err
}
// Wait until the task exit // Wait until the task exit
var status containerd.ExitStatus var status containerd.ExitStatus

View File

@ -17,7 +17,6 @@
package fanotify package fanotify
import ( import (
"errors"
"fmt" "fmt"
"os/exec" "os/exec"
"sync" "sync"
@ -25,6 +24,7 @@ import (
"time" "time"
"github.com/containerd/stargz-snapshotter/analyzer/fanotify/conn" "github.com/containerd/stargz-snapshotter/analyzer/fanotify/conn"
"github.com/hashicorp/go-multierror"
) )
// Fanotifier monitors "/" mountpoint of a new mount namespace and notifies all // Fanotifier monitors "/" mountpoint of a new mount namespace and notifies all
@ -59,15 +59,14 @@ func SpawnFanotifier(fanotifierBin string) (*Fanotifier, error) {
// Connect to the spawned fanotifier over stdio // Connect to the spawned fanotifier over stdio
conn: conn.NewClient(notifyR, notifyW, cmd.Process.Pid, 5*time.Second), conn: conn.NewClient(notifyR, notifyW, cmd.Process.Pid, 5*time.Second),
closeFunc: func() error { closeFunc: func() (allErr error) {
var errs []error
if err := notifyR.Close(); err != nil { if err := notifyR.Close(); err != nil {
errs = append(errs, err) allErr = multierror.Append(allErr, err)
} }
if err := notifyW.Close(); err != nil { if err := notifyW.Close(); err != nil {
errs = append(errs, err) allErr = multierror.Append(allErr, err)
} }
return errors.Join(errs...) return
}, },
}, nil }, nil
} }

View File

@ -68,7 +68,7 @@ func Serve(target string, r io.Reader, w io.Writer) error {
return fmt.Errorf("read fanotify fd: %w", err) return fmt.Errorf("read fanotify fd: %w", err)
} }
if event.Vers != unix.FANOTIFY_METADATA_VERSION { if event.Vers != unix.FANOTIFY_METADATA_VERSION {
return fmt.Errorf("fanotify version mismatch %d(got) != %d(want)", return fmt.Errorf("Fanotify version mismatch %d(got) != %d(want)",
event.Vers, unix.FANOTIFY_METADATA_VERSION) event.Vers, unix.FANOTIFY_METADATA_VERSION)
} }
if event.Fd < 0 { if event.Fd < 0 {

View File

@ -19,8 +19,8 @@ package analyzer
import ( import (
"time" "time"
containerd "github.com/containerd/containerd/v2/client" "github.com/containerd/containerd"
"github.com/containerd/containerd/v2/pkg/oci" "github.com/containerd/containerd/oci"
) )
type analyzerOpts struct { type analyzerOpts struct {

View File

@ -26,13 +26,13 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/containerd/containerd/v2/core/content" "github.com/containerd/containerd/archive/compression"
"github.com/containerd/containerd/v2/core/images" "github.com/containerd/containerd/content"
"github.com/containerd/containerd/v2/core/images/converter/uncompress" "github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/v2/pkg/archive/compression" "github.com/containerd/containerd/images"
"github.com/containerd/errdefs" "github.com/containerd/containerd/images/converter/uncompress"
"github.com/containerd/log" "github.com/containerd/containerd/log"
"github.com/containerd/platforms" "github.com/containerd/containerd/platforms"
"github.com/containerd/stargz-snapshotter/recorder" "github.com/containerd/stargz-snapshotter/recorder"
"github.com/containerd/stargz-snapshotter/util/containerdutil" "github.com/containerd/stargz-snapshotter/util/containerdutil"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
@ -151,7 +151,7 @@ func (r *ImageRecorder) Record(name string) error {
} }
whDir := cleanEntryName(path.Join(path.Dir("/"+name), whiteoutOpaqueDir)) whDir := cleanEntryName(path.Join(path.Dir("/"+name), whiteoutOpaqueDir))
if _, ok := r.index[i][whDir]; ok { if _, ok := r.index[i][whDir]; ok {
return fmt.Errorf("parent dir of %q is a deleted directory", name) return fmt.Errorf("Parent dir of %q is a deleted directory", name)
} }
} }
if index < 0 { if index < 0 {

View File

@ -26,9 +26,9 @@ import (
"path" "path"
"testing" "testing"
"github.com/containerd/containerd/v2/core/content" "github.com/containerd/containerd/content"
"github.com/containerd/containerd/v2/plugins/content/local" "github.com/containerd/containerd/content/local"
"github.com/containerd/errdefs" "github.com/containerd/containerd/errdefs"
"github.com/containerd/stargz-snapshotter/recorder" "github.com/containerd/stargz-snapshotter/recorder"
"github.com/containerd/stargz-snapshotter/util/testutil" "github.com/containerd/stargz-snapshotter/util/testutil"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"

79
cache/cache.go vendored
View File

@ -18,7 +18,6 @@ package cache
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"io" "io"
"os" "os"
@ -27,7 +26,7 @@ import (
"github.com/containerd/stargz-snapshotter/util/cacheutil" "github.com/containerd/stargz-snapshotter/util/cacheutil"
"github.com/containerd/stargz-snapshotter/util/namedmutex" "github.com/containerd/stargz-snapshotter/util/namedmutex"
"golang.org/x/sys/unix" "github.com/hashicorp/go-multierror"
) )
const ( const (
@ -62,9 +61,6 @@ type DirectoryCacheConfig struct {
// Direct forcefully enables direct mode for all operation in cache. // Direct forcefully enables direct mode for all operation in cache.
// Thus operation won't use on-memory caches. // Thus operation won't use on-memory caches.
Direct bool Direct bool
// FadvDontNeed forcefully clean fscache pagecache for saving memory.
FadvDontNeed bool
} }
// TODO: contents validation. // TODO: contents validation.
@ -86,9 +82,6 @@ type BlobCache interface {
type Reader interface { type Reader interface {
io.ReaderAt io.ReaderAt
Close() error Close() error
// If a blob is backed by a file, it should return *os.File so that it can be used for FUSE passthrough
GetReaderAt() io.ReaderAt
} }
// Writer enables the client to cache byte data. Commit() must be // Writer enables the client to cache byte data. Commit() must be
@ -101,8 +94,7 @@ type Writer interface {
} }
type cacheOpt struct { type cacheOpt struct {
direct bool direct bool
passThrough bool
} }
type Option func(o *cacheOpt) *cacheOpt type Option func(o *cacheOpt) *cacheOpt
@ -118,15 +110,6 @@ func Direct() Option {
} }
} }
// PassThrough option indicates whether to enable FUSE passthrough mode
// to improve local file read performance.
func PassThrough() Option {
return func(o *cacheOpt) *cacheOpt {
o.passThrough = true
return o
}
}
func NewDirectoryCache(directory string, config DirectoryCacheConfig) (BlobCache, error) { func NewDirectoryCache(directory string, config DirectoryCacheConfig) (BlobCache, error) {
if !filepath.IsAbs(directory) { if !filepath.IsAbs(directory) {
return nil, fmt.Errorf("dir cache path must be an absolute path; got %q", directory) return nil, fmt.Errorf("dir cache path must be an absolute path; got %q", directory)
@ -177,7 +160,6 @@ func NewDirectoryCache(directory string, config DirectoryCacheConfig) (BlobCache
wipDirectory: wipdir, wipDirectory: wipdir,
bufPool: bufPool, bufPool: bufPool,
direct: config.Direct, direct: config.Direct,
fadvDontNeed: config.FadvDontNeed,
} }
dc.syncAdd = config.SyncAdd dc.syncAdd = config.SyncAdd
return dc, nil return dc, nil
@ -193,9 +175,8 @@ type directoryCache struct {
bufPool *sync.Pool bufPool *sync.Pool
syncAdd bool syncAdd bool
direct bool direct bool
fadvDontNeed bool
closed bool closed bool
closedMu sync.Mutex closedMu sync.Mutex
@ -248,22 +229,8 @@ func (dc *directoryCache) Get(key string, opts ...Option) (Reader, error) {
// that won't be accessed immediately. // that won't be accessed immediately.
if dc.direct || opt.direct { if dc.direct || opt.direct {
return &reader{ return &reader{
ReaderAt: file, ReaderAt: file,
closeFunc: func() error { closeFunc: func() error { return file.Close() },
if dc.fadvDontNeed {
if err := dropFilePageCache(file); err != nil {
fmt.Printf("Warning: failed to drop page cache: %v\n", err)
}
}
// In passthough model, close will be toke over by go-fuse
// If "passThrough" option is specified, "direct" option also will
// be specified, so adding this branch here is enough
if opt.passThrough {
return nil
}
return file.Close()
},
}, nil }, nil
} }
@ -306,20 +273,13 @@ func (dc *directoryCache) Add(key string, opts ...Option) (Writer, error) {
// Commit the cache contents // Commit the cache contents
c := dc.cachePath(key) c := dc.cachePath(key)
if err := os.MkdirAll(filepath.Dir(c), os.ModePerm); err != nil { if err := os.MkdirAll(filepath.Dir(c), os.ModePerm); err != nil {
var errs []error var allErr error
if err := os.Remove(wip.Name()); err != nil { if err := os.Remove(wip.Name()); err != nil {
errs = append(errs, err) allErr = multierror.Append(allErr, err)
} }
errs = append(errs, fmt.Errorf("failed to create cache directory %q: %w", c, err)) return multierror.Append(allErr,
return errors.Join(errs...) fmt.Errorf("failed to create cache directory %q: %w", c, err))
} }
if dc.fadvDontNeed {
if err := dropFilePageCache(wip); err != nil {
fmt.Printf("Warning: failed to drop page cache: %v\n", err)
}
}
return os.Rename(wip.Name(), c) return os.Rename(wip.Name(), c)
}, },
abortFunc: func() error { abortFunc: func() error {
@ -424,7 +384,7 @@ func (mc *MemoryCache) Get(key string, opts ...Option) (Reader, error) {
defer mc.mu.Unlock() defer mc.mu.Unlock()
b, ok := mc.Membuf[key] b, ok := mc.Membuf[key]
if !ok { if !ok {
return nil, fmt.Errorf("missed cache: %q", key) return nil, fmt.Errorf("Missed cache: %q", key)
} }
return &reader{bytes.NewReader(b.Bytes()), func() error { return nil }}, nil return &reader{bytes.NewReader(b.Bytes()), func() error { return nil }}, nil
} }
@ -454,10 +414,6 @@ type reader struct {
func (r *reader) Close() error { return r.closeFunc() } func (r *reader) Close() error { return r.closeFunc() }
func (r *reader) GetReaderAt() io.ReaderAt {
return r.ReaderAt
}
type writer struct { type writer struct {
io.WriteCloser io.WriteCloser
commitFunc func() error commitFunc func() error
@ -482,16 +438,3 @@ func (w *writeCloser) Close() error { return w.closeFunc() }
func nopWriteCloser(w io.Writer) io.WriteCloser { func nopWriteCloser(w io.Writer) io.WriteCloser {
return &writeCloser{w, func() error { return nil }} return &writeCloser{w, func() error { return nil }}
} }
func dropFilePageCache(file *os.File) error {
if file == nil {
return nil
}
fd := file.Fd()
err := unix.Fadvise(int(fd), 0, 0, unix.FADV_DONTNEED)
if err != nil {
return fmt.Errorf("posix_fadvise failed, ret=%d", err)
}
return nil
}

View File

@ -35,10 +35,10 @@ import (
"github.com/containerd/stargz-snapshotter/estargz" "github.com/containerd/stargz-snapshotter/estargz"
"github.com/containerd/stargz-snapshotter/metadata" "github.com/containerd/stargz-snapshotter/metadata"
"github.com/goccy/go-json" "github.com/goccy/go-json"
"github.com/hashicorp/go-multierror"
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
"github.com/rs/xid" "github.com/rs/xid"
bolt "go.etcd.io/bbolt" bolt "go.etcd.io/bbolt"
errbolt "go.etcd.io/bbolt/errors"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
) )
@ -99,7 +99,7 @@ func NewReader(db *bolt.DB, sr *io.SectionReader, opts ...metadata.Option) (meta
rOpts.Telemetry.GetFooterLatency(start) rOpts.Telemetry.GetFooterLatency(start)
} }
var errs []error var allErr error
var tocR io.ReadCloser var tocR io.ReadCloser
var decompressor metadata.Decompressor var decompressor metadata.Decompressor
for _, d := range decompressors { for _, d := range decompressors {
@ -108,7 +108,7 @@ func NewReader(db *bolt.DB, sr *io.SectionReader, opts ...metadata.Option) (meta
maybeTocBytes := footer[:fOffset] maybeTocBytes := footer[:fOffset]
_, tocOffset, tocSize, err := d.ParseFooter(footer[fOffset:]) _, tocOffset, tocSize, err := d.ParseFooter(footer[fOffset:])
if err != nil { if err != nil {
errs = append(errs, err) allErr = multierror.Append(allErr, err)
continue continue
} }
if tocOffset >= 0 && tocSize <= 0 { if tocOffset >= 0 && tocSize <= 0 {
@ -119,14 +119,12 @@ func NewReader(db *bolt.DB, sr *io.SectionReader, opts ...metadata.Option) (meta
} }
tocR, err = decompressTOC(d, sr, tocOffset, tocSize, maybeTocBytes, rOpts) tocR, err = decompressTOC(d, sr, tocOffset, tocSize, maybeTocBytes, rOpts)
if err != nil { if err != nil {
errs = append(errs, err) allErr = multierror.Append(allErr, err)
continue continue
} }
decompressor = d decompressor = d
break break
} }
allErr := errors.Join(errs...)
if tocR == nil { if tocR == nil {
if allErr == nil { if allErr == nil {
return nil, fmt.Errorf("failed to get the reader of TOC: unknown") return nil, fmt.Errorf("failed to get the reader of TOC: unknown")
@ -224,7 +222,7 @@ func (r *reader) init(decompressedR io.Reader, rOpts metadata.Options) (retErr e
for i := 0; i < 100; i++ { for i := 0; i < 100; i++ {
fsID := xid.New().String() fsID := xid.New().String()
if err := r.initRootNode(fsID); err != nil { if err := r.initRootNode(fsID); err != nil {
if errors.Is(err, errbolt.ErrBucketExists) { if errors.Is(err, bolt.ErrBucketExists) {
continue // try with another id continue // try with another id
} }
return fmt.Errorf("failed to initialize root node %q: %w", fsID, err) return fmt.Errorf("failed to initialize root node %q: %w", fsID, err)
@ -240,22 +238,20 @@ func (r *reader) init(decompressedR io.Reader, rOpts metadata.Options) (retErr e
if err != nil { if err != nil {
return err return err
} }
closeFunc := func() error { closeFunc := func() (closeErr error) {
name := f.Name() name := f.Name()
var errs []error
if err := f.Close(); err != nil { if err := f.Close(); err != nil {
errs = append(errs, err) closeErr = multierror.Append(closeErr, err)
} }
if err := os.Remove(name); err != nil { if err := os.Remove(name); err != nil {
errs = append(errs, err) closeErr = multierror.Append(closeErr, err)
} }
return errors.Join(errs...) return
} }
defer func() { defer func() {
if retErr != nil { if retErr != nil {
if err := closeFunc(); err != nil { if err := closeFunc(); err != nil {
retErr = errors.Join(retErr, err) retErr = multierror.Append(retErr, err)
return
} }
} }
}() }()

View File

@ -29,60 +29,15 @@ import (
) )
func TestReader(t *testing.T) { func TestReader(t *testing.T) {
testRunner := &testutil.TestRunner{ testutil.TestReader(t, newTestableReader)
TestingT: t,
Runner: func(testingT testutil.TestingT, name string, run func(t testutil.TestingT)) {
tt, ok := testingT.(*testing.T)
if !ok {
testingT.Fatal("TestingT is not a *testing.T")
return
}
tt.Run(name, func(t *testing.T) {
run(t)
})
},
}
testutil.TestReader(testRunner, newTestableReader)
} }
func TestFSReader(t *testing.T) { func TestFSReader(t *testing.T) {
testRunner := &fsreader.TestRunner{ fsreader.TestSuiteReader(t, newStore)
TestingT: t,
Runner: func(testingT fsreader.TestingT, name string, run func(t fsreader.TestingT)) {
tt, ok := testingT.(*testing.T)
if !ok {
testingT.Fatal("TestingT is not a *testing.T")
return
}
tt.Run(name, func(t *testing.T) {
run(t)
})
},
}
fsreader.TestSuiteReader(testRunner, newStore)
} }
func TestFSLayer(t *testing.T) { func TestFSLayer(t *testing.T) {
testRunner := &layer.TestRunner{ layer.TestSuiteLayer(t, newStore)
TestingT: t,
Runner: func(testingT layer.TestingT, name string, run func(t layer.TestingT)) {
tt, ok := testingT.(*testing.T)
if !ok {
testingT.Fatal("TestingT is not a *testing.T")
return
}
tt.Run(name, func(t *testing.T) {
run(t)
})
},
}
layer.TestSuiteLayer(testRunner, newStore)
} }
func newTestableReader(sr *io.SectionReader, opts ...metadata.Option) (testutil.TestableReader, error) { func newTestableReader(sr *io.SectionReader, opts ...metadata.Option) (testutil.TestableReader, error) {

View File

@ -1,80 +0,0 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fsopts
import (
"context"
"fmt"
"io"
"path/filepath"
"github.com/containerd/log"
dbmetadata "github.com/containerd/stargz-snapshotter/cmd/containerd-stargz-grpc/db"
ipfs "github.com/containerd/stargz-snapshotter/cmd/containerd-stargz-grpc/ipfs"
"github.com/containerd/stargz-snapshotter/fs"
"github.com/containerd/stargz-snapshotter/metadata"
memorymetadata "github.com/containerd/stargz-snapshotter/metadata/memory"
bolt "go.etcd.io/bbolt"
)
type Config struct {
EnableIpfs bool
MetadataStore string
OpenBoltDB func(string) (*bolt.DB, error)
}
const (
memoryMetadataType = "memory"
dbMetadataType = "db"
)
func ConfigFsOpts(ctx context.Context, rootDir string, config *Config) ([]fs.Option, error) {
fsOpts := []fs.Option{fs.WithMetricsLogLevel(log.InfoLevel)}
if config.EnableIpfs {
fsOpts = append(fsOpts, fs.WithResolveHandler("ipfs", new(ipfs.ResolveHandler)))
}
mt, err := getMetadataStore(rootDir, config)
if err != nil {
return nil, fmt.Errorf("failed to configure metadata store: %w", err)
}
fsOpts = append(fsOpts, fs.WithMetadataStore(mt))
return fsOpts, nil
}
func getMetadataStore(rootDir string, config *Config) (metadata.Store, error) {
switch config.MetadataStore {
case "", memoryMetadataType:
return memorymetadata.NewReader, nil
case dbMetadataType:
if config.OpenBoltDB == nil {
return nil, fmt.Errorf("bolt DB is not configured")
}
db, err := config.OpenBoltDB(filepath.Join(rootDir, "metadata.db"))
if err != nil {
return nil, err
}
return func(sr *io.SectionReader, opts ...metadata.Option) (metadata.Reader, error) {
return dbmetadata.NewReader(db, sr, opts...)
}, nil
default:
return nil, fmt.Errorf("unknown metadata store type: %v; must be %v or %v",
config.MetadataStore, memoryMetadataType, dbMetadataType)
}
}

View File

@ -77,3 +77,10 @@ func (f *fetcher) GenID(off int64, size int64) string {
sum := sha256.Sum256([]byte(fmt.Sprintf("%s-%d-%d", f.cid, off, size))) sum := sha256.Sum256([]byte(fmt.Sprintf("%s-%d-%d", f.cid, off, size)))
return fmt.Sprintf("%x", sum) return fmt.Sprintf("%x", sum)
} }
type readCloser struct {
io.Reader
closeFunc func() error
}
func (r *readCloser) Close() error { return r.closeFunc() }

View File

@ -20,43 +20,54 @@ import (
"context" "context"
"flag" "flag"
"fmt" "fmt"
"io"
golog "log" golog "log"
"math/rand" "math/rand"
"net" "net"
"net/http" "net/http"
"os" "os"
"os/exec"
"os/signal" "os/signal"
"path/filepath" "path/filepath"
"time" "time"
snapshotsapi "github.com/containerd/containerd/api/services/snapshots/v1" snapshotsapi "github.com/containerd/containerd/api/services/snapshots/v1"
"github.com/containerd/containerd/v2/contrib/snapshotservice" "github.com/containerd/containerd/contrib/snapshotservice"
"github.com/containerd/containerd/v2/core/snapshots" "github.com/containerd/containerd/defaults"
"github.com/containerd/containerd/v2/pkg/sys" "github.com/containerd/containerd/log"
"github.com/containerd/log" "github.com/containerd/containerd/pkg/dialer"
"github.com/containerd/stargz-snapshotter/cmd/containerd-stargz-grpc/fsopts" "github.com/containerd/containerd/snapshots"
"github.com/containerd/stargz-snapshotter/fusemanager" "github.com/containerd/containerd/sys"
runtime_alpha "github.com/containerd/containerd/third_party/k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
dbmetadata "github.com/containerd/stargz-snapshotter/cmd/containerd-stargz-grpc/db"
ipfs "github.com/containerd/stargz-snapshotter/cmd/containerd-stargz-grpc/ipfs"
"github.com/containerd/stargz-snapshotter/fs"
"github.com/containerd/stargz-snapshotter/metadata"
memorymetadata "github.com/containerd/stargz-snapshotter/metadata/memory"
"github.com/containerd/stargz-snapshotter/service" "github.com/containerd/stargz-snapshotter/service"
"github.com/containerd/stargz-snapshotter/service/keychain/keychainconfig" "github.com/containerd/stargz-snapshotter/service/keychain/cri"
snbase "github.com/containerd/stargz-snapshotter/snapshot" "github.com/containerd/stargz-snapshotter/service/keychain/crialpha"
"github.com/containerd/stargz-snapshotter/service/keychain/dockerconfig"
"github.com/containerd/stargz-snapshotter/service/keychain/kubeconfig"
"github.com/containerd/stargz-snapshotter/service/resolver"
"github.com/containerd/stargz-snapshotter/version" "github.com/containerd/stargz-snapshotter/version"
sddaemon "github.com/coreos/go-systemd/v22/daemon" sddaemon "github.com/coreos/go-systemd/v22/daemon"
metrics "github.com/docker/go-metrics" metrics "github.com/docker/go-metrics"
"github.com/pelletier/go-toml" "github.com/pelletier/go-toml"
"github.com/sirupsen/logrus"
bolt "go.etcd.io/bbolt" bolt "go.etcd.io/bbolt"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/backoff"
"google.golang.org/grpc/credentials/insecure"
runtime "k8s.io/cri-api/pkg/apis/runtime/v1"
) )
const ( const (
defaultAddress = "/run/containerd-stargz-grpc/containerd-stargz-grpc.sock" defaultAddress = "/run/containerd-stargz-grpc/containerd-stargz-grpc.sock"
defaultConfigPath = "/etc/containerd-stargz-grpc/config.toml" defaultConfigPath = "/etc/containerd-stargz-grpc/config.toml"
defaultLogLevel = log.InfoLevel defaultLogLevel = logrus.InfoLevel
defaultRootDir = "/var/lib/containerd-stargz-grpc" defaultRootDir = "/var/lib/containerd-stargz-grpc"
defaultImageServiceAddress = "/run/containerd/containerd.sock" defaultImageServiceAddress = "/run/containerd/containerd.sock"
defaultFuseManagerAddress = "/run/containerd-stargz-grpc/fuse-manager.sock"
fuseManagerBin = "stargz-fuse-manager"
) )
var ( var (
@ -71,40 +82,25 @@ type snapshotterConfig struct {
service.Config service.Config
// MetricsAddress is address for the metrics API // MetricsAddress is address for the metrics API
MetricsAddress string `toml:"metrics_address" json:"metrics_address"` MetricsAddress string `toml:"metrics_address"`
// NoPrometheus is a flag to disable the emission of the metrics // NoPrometheus is a flag to disable the emission of the metrics
NoPrometheus bool `toml:"no_prometheus" json:"no_prometheus"` NoPrometheus bool `toml:"no_prometheus"`
// DebugAddress is a Unix domain socket address where the snapshotter exposes /debug/ endpoints. // DebugAddress is a Unix domain socket address where the snapshotter exposes /debug/ endpoints.
DebugAddress string `toml:"debug_address" json:"debug_address"` DebugAddress string `toml:"debug_address"`
// IPFS is a flag to enbale lazy pulling from IPFS. // IPFS is a flag to enbale lazy pulling from IPFS.
IPFS bool `toml:"ipfs" json:"ipfs"` IPFS bool `toml:"ipfs"`
// MetadataStore is the type of the metadata store to use. // MetadataStore is the type of the metadata store to use.
MetadataStore string `toml:"metadata_store" default:"memory" json:"metadata_store"` MetadataStore string `toml:"metadata_store" default:"memory"`
// FuseManagerConfig is configuration for fusemanager
FuseManagerConfig `toml:"fuse_manager" json:"fuse_manager"`
}
type FuseManagerConfig struct {
// Enable is whether detach fusemanager or not
Enable bool `toml:"enable" default:"false" json:"enable"`
// Address is address for the fusemanager's GRPC server (default: "/run/containerd-stargz-grpc/fuse-manager.sock")
Address string `toml:"address" json:"address"`
// Path is path to the fusemanager's executable (default: looking for a binary "stargz-fuse-manager")
Path string `toml:"path" json:"path"`
} }
func main() { func main() {
rand.Seed(time.Now().UnixNano()) //nolint:staticcheck // Global math/rand seed is deprecated, but still used by external dependencies rand.Seed(time.Now().UnixNano())
flag.Parse() flag.Parse()
log.SetFormat(log.JSONFormat) lvl, err := logrus.ParseLevel(*logLevel)
err := log.SetLevel(*logLevel)
if err != nil { if err != nil {
log.L.WithError(err).Fatal("failed to prepare logger") log.L.WithError(err).Fatal("failed to prepare logger")
} }
@ -112,19 +108,23 @@ func main() {
fmt.Println("containerd-stargz-grpc", version.Version, version.Revision) fmt.Println("containerd-stargz-grpc", version.Version, version.Revision)
return return
} }
logrus.SetLevel(lvl)
logrus.SetFormatter(&logrus.JSONFormatter{
TimestampFormat: log.RFC3339NanoFixed,
})
var ( var (
ctx = log.WithLogger(context.Background(), log.L) ctx = log.WithLogger(context.Background(), log.L)
config snapshotterConfig config snapshotterConfig
) )
// Streams log of standard lib (go-fuse uses this) into debug log // Streams log of standard lib (go-fuse uses this) into debug log
// Snapshotter should use "github.com/containerd/log" otherwize // Snapshotter should use "github.com/containerd/containerd/log" otherwize
// logs are always printed as "debug" mode. // logs are always printed as "debug" mode.
golog.SetOutput(log.G(ctx).WriterLevel(log.DebugLevel)) golog.SetOutput(log.G(ctx).WriterLevel(logrus.DebugLevel))
// Get configuration from specified file // Get configuration from specified file
tree, err := toml.LoadFile(*configPath) tree, err := toml.LoadFile(*configPath)
if err != nil && (!os.IsNotExist(err) || *configPath != defaultConfigPath) { if err != nil && !(os.IsNotExist(err) && *configPath == defaultConfigPath) {
log.G(ctx).WithError(err).Fatalf("failed to load config file %q", *configPath) log.G(ctx).WithError(err).Fatalf("failed to load config file %q", *configPath)
} }
if err := tree.Unmarshal(&config); err != nil { if err := tree.Unmarshal(&config); err != nil {
@ -138,126 +138,54 @@ func main() {
// Create a gRPC server // Create a gRPC server
rpc := grpc.NewServer() rpc := grpc.NewServer()
// Configure FUSE passthrough
// Always set Direct to true to ensure that
// *directoryCache.Get always return *os.File instead of buffer
if config.PassThrough {
config.Direct = true
}
// Configure keychain // Configure keychain
keyChainConfig := keychainconfig.Config{ credsFuncs := []resolver.Credential{dockerconfig.NewDockerconfigKeychain(ctx)}
EnableKubeKeychain: config.KubeconfigKeychainConfig.EnableKeychain, if config.Config.KubeconfigKeychainConfig.EnableKeychain {
EnableCRIKeychain: config.CRIKeychainConfig.EnableKeychain, var opts []kubeconfig.Option
KubeconfigPath: config.KubeconfigPath, if kcp := config.Config.KubeconfigKeychainConfig.KubeconfigPath; kcp != "" {
DefaultImageServiceAddress: defaultImageServiceAddress, opts = append(opts, kubeconfig.WithKubeconfigPath(kcp))
ImageServicePath: config.ImageServicePath, }
credsFuncs = append(credsFuncs, kubeconfig.NewKubeconfigKeychain(ctx, opts...))
} }
if config.Config.CRIKeychainConfig.EnableKeychain {
var rs snapshots.Snapshotter // connects to the backend CRI service (defaults to containerd socket)
fuseManagerConfig := config.FuseManagerConfig criAddr := defaultImageServiceAddress
if fuseManagerConfig.Enable { if cp := config.CRIKeychainConfig.ImageServicePath; cp != "" {
fmPath := fuseManagerConfig.Path criAddr = cp
if fmPath == "" { }
var err error connectCRI := func() (runtime.ImageServiceClient, error) {
fmPath, err = exec.LookPath(fuseManagerBin) conn, err := newCRIConn(criAddr)
if err != nil { if err != nil {
log.G(ctx).WithError(err).Fatalf("failed to find fusemanager bin") return nil, err
} }
return runtime.NewImageServiceClient(conn), nil
} }
fmAddr := fuseManagerConfig.Address connectAlphaCRI := func() (runtime_alpha.ImageServiceClient, error) {
if fmAddr == "" { conn, err := newCRIConn(criAddr)
fmAddr = defaultFuseManagerAddress
}
if !filepath.IsAbs(fmAddr) {
log.G(ctx).WithError(err).Fatalf("fuse manager address must be an absolute path: %s", fmAddr)
}
managerNewlyStarted, err := fusemanager.StartFuseManager(ctx, fmPath, fmAddr, filepath.Join(*rootDir, "fusestore.db"), *logLevel, filepath.Join(*rootDir, "stargz-fuse-manager.log"))
if err != nil {
log.G(ctx).WithError(err).Fatalf("failed to start fusemanager")
}
fuseManagerConfig := fusemanager.Config{
Config: config.Config,
IPFS: config.IPFS,
MetadataStore: config.MetadataStore,
DefaultImageServiceAddress: defaultImageServiceAddress,
}
fs, err := fusemanager.NewManagerClient(ctx, *rootDir, fmAddr, &fuseManagerConfig)
if err != nil {
log.G(ctx).WithError(err).Fatalf("failed to configure fusemanager")
}
flags := []snbase.Opt{snbase.AsynchronousRemove}
// "managerNewlyStarted" being true indicates that the FUSE manager is newly started. To
// fully recover the snapshotter and the FUSE manager's state, we need to restore
// all snapshot mounts. If managerNewlyStarted is false, the existing FUSE manager maintains
// snapshot mounts so we don't need to restore them.
if !managerNewlyStarted {
flags = append(flags, snbase.NoRestore)
}
rs, err = snbase.NewSnapshotter(ctx, filepath.Join(*rootDir, "snapshotter"), fs, flags...)
if err != nil {
log.G(ctx).WithError(err).Fatalf("failed to configure snapshotter")
}
log.G(ctx).Infof("Start snapshotter with fusemanager mode")
} else {
crirpc := rpc
// For CRI keychain, if listening path is different from stargz-snapshotter's socket, prepare for the dedicated grpc server and the socket.
serveCRISocket := config.CRIKeychainConfig.EnableKeychain && config.ListenPath != "" && config.ListenPath != *address
if serveCRISocket {
crirpc = grpc.NewServer()
}
credsFuncs, err := keychainconfig.ConfigKeychain(ctx, crirpc, &keyChainConfig)
if err != nil {
log.G(ctx).WithError(err).Fatalf("failed to configure keychain")
}
if serveCRISocket {
addr := config.ListenPath
// Prepare the directory for the socket
if err := os.MkdirAll(filepath.Dir(addr), 0700); err != nil {
log.G(ctx).WithError(err).Fatalf("failed to create directory %q", filepath.Dir(addr))
}
// Try to remove the socket file to avoid EADDRINUSE
if err := os.RemoveAll(addr); err != nil {
log.G(ctx).WithError(err).Fatalf("failed to remove %q", addr)
}
// Listen and serve
l, err := net.Listen("unix", addr)
if err != nil { if err != nil {
log.G(ctx).WithError(err).Fatalf("error on listen socket %q", addr) return nil, err
} }
go func() { return runtime_alpha.NewImageServiceClient(conn), nil
if err := crirpc.Serve(l); err != nil {
log.G(ctx).WithError(err).Errorf("error on serving CRI via socket %q", addr)
}
}()
}
fsConfig := fsopts.Config{
EnableIpfs: config.IPFS,
MetadataStore: config.MetadataStore,
OpenBoltDB: func(p string) (*bolt.DB, error) {
return bolt.Open(p, 0600, &bolt.Options{
NoFreelistSync: true,
InitialMmapSize: 64 * 1024 * 1024,
FreelistType: bolt.FreelistMapType,
})
},
}
fsOpts, err := fsopts.ConfigFsOpts(ctx, *rootDir, &fsConfig)
if err != nil {
log.G(ctx).WithError(err).Fatalf("failed to configure fs config")
}
rs, err = service.NewStargzSnapshotterService(ctx, *rootDir, &config.Config,
service.WithCredsFuncs(credsFuncs...), service.WithFilesystemOptions(fsOpts...))
if err != nil {
log.G(ctx).WithError(err).Fatalf("failed to configure snapshotter")
} }
f, criServer := cri.NewCRIKeychain(ctx, connectCRI)
fAlpha, criAlphaServer := crialpha.NewCRIAlphaKeychain(ctx, connectAlphaCRI)
runtime.RegisterImageServiceServer(rpc, criServer)
runtime_alpha.RegisterImageServiceServer(rpc, criAlphaServer)
credsFuncs = append(credsFuncs, f, fAlpha)
}
fsOpts := []fs.Option{fs.WithMetricsLogLevel(logrus.InfoLevel)}
if config.IPFS {
fsOpts = append(fsOpts, fs.WithResolveHandler("ipfs", new(ipfs.ResolveHandler)))
}
mt, err := getMetadataStore(*rootDir, config)
if err != nil {
log.G(ctx).WithError(err).Fatalf("failed to configure metadata store")
}
fsOpts = append(fsOpts, fs.WithMetadataStore(mt))
rs, err := service.NewStargzSnapshotterService(ctx, *rootDir, &config.Config,
service.WithCredsFuncs(credsFuncs...), service.WithFilesystemOptions(fsOpts...))
if err != nil {
log.G(ctx).WithError(err).Fatalf("failed to configure snapshotter")
} }
cleanup, err := serve(ctx, rpc, *address, rs, config) cleanup, err := serve(ctx, rpc, *address, rs, config)
@ -265,18 +193,7 @@ func main() {
log.G(ctx).WithError(err).Fatalf("failed to serve snapshotter") log.G(ctx).WithError(err).Fatalf("failed to serve snapshotter")
} }
// When FUSE manager is disabled, FUSE servers are goroutines in the if cleanup {
// contaienrd-stargz-grpc process. So killing containerd-stargz-grpc will
// result in all FUSE mount becoming unavailable with leaving all resources
// (e.g. temporary cache) on the node. To ensure graceful shutdown, we
// should always cleanup mounts and associated resources here.
//
// When FUSE manager is enabled, those mounts are still under the control by
// the FUSE manager so we need to avoid cleaning them up unless explicitly
// commanded via SIGINT. The user can use SIGINT to gracefully killing the FUSE
// manager before rebooting the node for ensuring that the all snapshots are
// unmounted with cleaning up associated temporary resources.
if cleanup || !fuseManagerConfig.Enable {
log.G(ctx).Debug("Closing the snapshotter") log.G(ctx).Debug("Closing the snapshotter")
rs.Close() rs.Close()
} }
@ -366,3 +283,48 @@ func serve(ctx context.Context, rpc *grpc.Server, addr string, rs snapshots.Snap
} }
return false, nil return false, nil
} }
const (
memoryMetadataType = "memory"
dbMetadataType = "db"
)
func getMetadataStore(rootDir string, config snapshotterConfig) (metadata.Store, error) {
switch config.MetadataStore {
case "", memoryMetadataType:
return memorymetadata.NewReader, nil
case dbMetadataType:
bOpts := bolt.Options{
NoFreelistSync: true,
InitialMmapSize: 64 * 1024 * 1024,
FreelistType: bolt.FreelistMapType,
}
db, err := bolt.Open(filepath.Join(rootDir, "metadata.db"), 0600, &bOpts)
if err != nil {
return nil, err
}
return func(sr *io.SectionReader, opts ...metadata.Option) (metadata.Reader, error) {
return dbmetadata.NewReader(db, sr, opts...)
}, nil
default:
return nil, fmt.Errorf("unknown metadata store type: %v; must be %v or %v",
config.MetadataStore, memoryMetadataType, dbMetadataType)
}
}
func newCRIConn(criAddr string) (*grpc.ClientConn, error) {
// TODO: make gRPC options configurable from config.toml
backoffConfig := backoff.DefaultConfig
backoffConfig.MaxDelay = 3 * time.Second
connParams := grpc.ConnectParams{
Backoff: backoffConfig,
}
gopts := []grpc.DialOption{
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithConnectParams(connParams),
grpc.WithContextDialer(dialer.ContextDialer),
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(defaults.DefaultMaxRecvMsgSize)),
grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(defaults.DefaultMaxSendMsgSize)),
}
return grpc.Dial(dialer.DialAddress(criAddr), gopts...)
}

View File

@ -25,13 +25,12 @@ import (
"os" "os"
"os/signal" "os/signal"
"github.com/containerd/containerd/v2/cmd/ctr/commands" "github.com/containerd/containerd/cmd/ctr/commands"
"github.com/containerd/containerd/v2/core/content" "github.com/containerd/containerd/content"
"github.com/containerd/containerd/v2/core/images" "github.com/containerd/containerd/images"
"github.com/containerd/containerd/v2/core/images/converter" "github.com/containerd/containerd/images/converter"
"github.com/containerd/containerd/v2/core/images/converter/uncompress" "github.com/containerd/containerd/images/converter/uncompress"
"github.com/containerd/log" "github.com/containerd/containerd/platforms"
"github.com/containerd/platforms"
"github.com/containerd/stargz-snapshotter/estargz" "github.com/containerd/stargz-snapshotter/estargz"
estargzconvert "github.com/containerd/stargz-snapshotter/nativeconverter/estargz" estargzconvert "github.com/containerd/stargz-snapshotter/nativeconverter/estargz"
esgzexternaltocconvert "github.com/containerd/stargz-snapshotter/nativeconverter/estargz/externaltoc" esgzexternaltocconvert "github.com/containerd/stargz-snapshotter/nativeconverter/estargz/externaltoc"
@ -39,11 +38,12 @@ import (
"github.com/containerd/stargz-snapshotter/recorder" "github.com/containerd/stargz-snapshotter/recorder"
"github.com/klauspost/compress/zstd" "github.com/klauspost/compress/zstd"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/urfave/cli/v2" "github.com/sirupsen/logrus"
"github.com/urfave/cli"
) )
// ConvertCommand converts an image // ConvertCommand converts an image
var ConvertCommand = &cli.Command{ var ConvertCommand = cli.Command{
Name: "convert", Name: "convert",
Usage: "convert an image", Usage: "convert an image",
ArgsUsage: "[flags] <source_ref> <target_ref>...", ArgsUsage: "[flags] <source_ref> <target_ref>...",
@ -56,72 +56,72 @@ When '--all-platforms' is given all images in a manifest list must be available.
`, `,
Flags: []cli.Flag{ Flags: []cli.Flag{
// estargz flags // estargz flags
&cli.BoolFlag{ cli.BoolFlag{
Name: "estargz", Name: "estargz",
Usage: "convert legacy tar(.gz) layers to eStargz for lazy pulling. Should be used in conjunction with '--oci'", Usage: "convert legacy tar(.gz) layers to eStargz for lazy pulling. Should be used in conjunction with '--oci'",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "estargz-record-in", Name: "estargz-record-in",
Usage: "Read 'ctr-remote optimize --record-out=<FILE>' record file", Usage: "Read 'ctr-remote optimize --record-out=<FILE>' record file",
}, },
&cli.IntFlag{ cli.IntFlag{
Name: "estargz-compression-level", Name: "estargz-compression-level",
Usage: "eStargz compression level", Usage: "eStargz compression level",
Value: gzip.BestCompression, Value: gzip.BestCompression,
}, },
&cli.IntFlag{ cli.IntFlag{
Name: "estargz-chunk-size", Name: "estargz-chunk-size",
Usage: "eStargz chunk size", Usage: "eStargz chunk size",
Value: 0, Value: 0,
}, },
&cli.IntFlag{ cli.IntFlag{
Name: "estargz-min-chunk-size", Name: "estargz-min-chunk-size",
Usage: "The minimal number of bytes of data must be written in one gzip stream. Note that this adds a TOC property that old reader doesn't understand.", Usage: "The minimal number of bytes of data must be written in one gzip stream. Note that this adds a TOC property that old reader doesn't understand.",
Value: 0, Value: 0,
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "estargz-external-toc", Name: "estargz-external-toc",
Usage: "Separate TOC JSON into another image (called \"TOC image\"). The name of TOC image is the original + \"-esgztoc\" suffix. Both eStargz and the TOC image should be pushed to the same registry. stargz-snapshotter refers to the TOC image when it pulls the result eStargz image.", Usage: "Separate TOC JSON into another image (called \"TOC image\"). The name of TOC image is the original + \"-esgztoc\" suffix. Both eStargz and the TOC image should be pushed to the same registry. stargz-snapshotter refers to the TOC image when it pulls the result eStargz image.",
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "estargz-keep-diff-id", Name: "estargz-keep-diff-id",
Usage: "convert to esgz without changing diffID (cannot be used in conjunction with '--estargz-record-in'. must be specified with '--estargz-external-toc')", Usage: "convert to esgz without changing diffID (cannot be used in conjunction with '--estargz-record-in'. must be specified with '--estargz-external-toc')",
}, },
// zstd:chunked flags // zstd:chunked flags
&cli.BoolFlag{ cli.BoolFlag{
Name: "zstdchunked", Name: "zstdchunked",
Usage: "use zstd compression instead of gzip (a.k.a zstd:chunked). Must be used in conjunction with '--oci'.", Usage: "use zstd compression instead of gzip (a.k.a zstd:chunked). Must be used in conjunction with '--oci'.",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "zstdchunked-record-in", Name: "zstdchunked-record-in",
Usage: "Read 'ctr-remote optimize --record-out=<FILE>' record file", Usage: "Read 'ctr-remote optimize --record-out=<FILE>' record file",
}, },
&cli.IntFlag{ cli.IntFlag{
Name: "zstdchunked-compression-level", Name: "zstdchunked-compression-level",
Usage: "zstd:chunked compression level", Usage: "zstd:chunked compression level",
Value: 3, // SpeedDefault; see also https://pkg.go.dev/github.com/klauspost/compress/zstd#EncoderLevel Value: 3, // SpeedDefault; see also https://pkg.go.dev/github.com/klauspost/compress/zstd#EncoderLevel
}, },
&cli.IntFlag{ cli.IntFlag{
Name: "zstdchunked-chunk-size", Name: "zstdchunked-chunk-size",
Usage: "zstd:chunked chunk size", Usage: "zstd:chunked chunk size",
Value: 0, Value: 0,
}, },
// generic flags // generic flags
&cli.BoolFlag{ cli.BoolFlag{
Name: "uncompress", Name: "uncompress",
Usage: "convert tar.gz layers to uncompressed tar layers", Usage: "convert tar.gz layers to uncompressed tar layers",
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "oci", Name: "oci",
Usage: "convert Docker media types to OCI media types", Usage: "convert Docker media types to OCI media types",
}, },
// platform flags // platform flags
&cli.StringSliceFlag{ cli.StringSliceFlag{
Name: "platform", Name: "platform",
Usage: "Convert content for a specific platform", Usage: "Convert content for a specific platform",
Value: &cli.StringSlice{}, Value: &cli.StringSlice{},
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "all-platforms", Name: "all-platforms",
Usage: "Convert content for all platforms", Usage: "Convert content for all platforms",
}, },
@ -183,7 +183,7 @@ When '--all-platforms' is given all images in a manifest list must be available.
layerConvertFunc = estargzconvert.LayerConvertFunc(esgzOpts...) layerConvertFunc = estargzconvert.LayerConvertFunc(esgzOpts...)
} }
if !context.Bool("oci") { if !context.Bool("oci") {
log.L.Warn("option --estargz should be used in conjunction with --oci") logrus.Warn("option --estargz should be used in conjunction with --oci")
} }
if context.Bool("uncompress") { if context.Bool("uncompress") {
return errors.New("option --estargz conflicts with --uncompress") return errors.New("option --estargz conflicts with --uncompress")
@ -239,7 +239,7 @@ When '--all-platforms' is given all images in a manifest list must be available.
// Cleanly cancel conversion // Cleanly cancel conversion
select { select {
case s := <-sigCh: case s := <-sigCh:
log.G(ctx).Infof("Got %v", s) logrus.Infof("Got %v", s)
cancel() cancel()
case <-ctx.Done(): case <-ctx.Done():
} }

View File

@ -21,125 +21,106 @@ import (
gocontext "context" gocontext "context"
"encoding/csv" "encoding/csv"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strconv"
"strings" "strings"
containerd "github.com/containerd/containerd/v2/client" "github.com/containerd/containerd"
"github.com/containerd/containerd/v2/contrib/nvidia" "github.com/containerd/containerd/containers"
"github.com/containerd/containerd/v2/core/containers" "github.com/containerd/containerd/content"
"github.com/containerd/containerd/v2/core/content" "github.com/containerd/containerd/contrib/nvidia"
"github.com/containerd/containerd/v2/core/images" "github.com/containerd/containerd/images"
"github.com/containerd/containerd/v2/pkg/netns" "github.com/containerd/containerd/oci"
"github.com/containerd/containerd/v2/pkg/oci" "github.com/containerd/containerd/pkg/netns"
gocni "github.com/containerd/go-cni" gocni "github.com/containerd/go-cni"
"github.com/containerd/log" "github.com/hashicorp/go-multierror"
imagespec "github.com/opencontainers/image-spec/specs-go/v1" imagespec "github.com/opencontainers/image-spec/specs-go/v1"
runtimespec "github.com/opencontainers/runtime-spec/specs-go" runtimespec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/rs/xid" "github.com/rs/xid"
"github.com/urfave/cli/v2" "github.com/sirupsen/logrus"
"github.com/urfave/cli"
) )
const netnsMountDir = "/var/run/netns" const netnsMountDir = "/var/run/netns"
func parseGPUs(gpuStr string) ([]int, bool) {
if gpuStr == "" {
return nil, false
}
if gpuStr == "all" {
return nil, true
}
parts := strings.Split(gpuStr, ",")
var devices []int
for _, part := range parts {
part = strings.TrimSpace(part)
if device, err := strconv.Atoi(part); err == nil {
devices = append(devices, device)
}
}
return devices, false
}
var samplerFlags = []cli.Flag{ var samplerFlags = []cli.Flag{
&cli.BoolFlag{ cli.BoolFlag{
Name: "terminal,t", Name: "terminal,t",
Usage: "enable terminal for sample container. must be specified with i option", Usage: "enable terminal for sample container. must be specified with i option",
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "i", Name: "i",
Usage: "attach stdin to the container", Usage: "attach stdin to the container",
}, },
&cli.IntFlag{ cli.IntFlag{
Name: "period", Name: "period",
Usage: "time period to monitor access log", Usage: "time period to monitor access log",
Value: defaultPeriod, Value: defaultPeriod,
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "user", Name: "user",
Usage: "user/group name to override image's default config(user[:group])", Usage: "user/group name to override image's default config(user[:group])",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "cwd", Name: "cwd",
Usage: "working dir to override image's default config", Usage: "working dir to override image's default config",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "args", Name: "args",
Usage: "command arguments to override image's default config(in JSON array)", Usage: "command arguments to override image's default config(in JSON array)",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "entrypoint", Name: "entrypoint",
Usage: "entrypoint to override image's default config(in JSON array)", Usage: "entrypoint to override image's default config(in JSON array)",
}, },
&cli.StringSliceFlag{ cli.StringSliceFlag{
Name: "env", Name: "env",
Usage: "environment valulable to add or override to the image's default config", Usage: "environment valulable to add or override to the image's default config",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "env-file", Name: "env-file",
Usage: "specify additional container environment variables in a file(i.e. FOO=bar, one per line)", Usage: "specify additional container environment variables in a file(i.e. FOO=bar, one per line)",
}, },
&cli.StringSliceFlag{ cli.StringSliceFlag{
Name: "mount", Name: "mount",
Usage: "additional mounts for the container (e.g. type=foo,source=/path,destination=/target,options=bind)", Usage: "additional mounts for the container (e.g. type=foo,source=/path,destination=/target,options=bind)",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "dns-nameservers", Name: "dns-nameservers",
Usage: "comma-separated nameservers added to the container's /etc/resolv.conf", Usage: "comma-separated nameservers added to the container's /etc/resolv.conf",
Value: "8.8.8.8", Value: "8.8.8.8",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "dns-search-domains", Name: "dns-search-domains",
Usage: "comma-separated search domains added to the container's /etc/resolv.conf", Usage: "comma-separated search domains added to the container's /etc/resolv.conf",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "dns-options", Name: "dns-options",
Usage: "comma-separated options added to the container's /etc/resolv.conf", Usage: "comma-separated options added to the container's /etc/resolv.conf",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "add-hosts", Name: "add-hosts",
Usage: "comma-separated hosts configuration (host:IP) added to container's /etc/hosts", Usage: "comma-separated hosts configuration (host:IP) added to container's /etc/hosts",
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "cni", Name: "cni",
Usage: "enable CNI-based networking", Usage: "enable CNI-based networking",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "cni-plugin-conf-dir", Name: "cni-plugin-conf-dir",
Usage: "path to the CNI plugins configuration directory", Usage: "path to the CNI plugins configuration directory",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "cni-plugin-dir", Name: "cni-plugin-dir",
Usage: "path to the CNI plugins binary directory", Usage: "path to the CNI plugins binary directory",
}, },
&cli.StringFlag{ cli.IntSliceFlag{
Name: "gpus", Name: "gpus",
Usage: "add gpus to the container (comma-separated list of indices or 'all')", Usage: "add gpus to the container",
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "net-host", Name: "net-host",
Usage: "enable host networking in the container", Usage: "enable host networking in the container",
}, },
@ -148,14 +129,13 @@ var samplerFlags = []cli.Flag{
func getSpecOpts(clicontext *cli.Context) func(image containerd.Image, rootfs string) (opts []oci.SpecOpts, done func() error, rErr error) { func getSpecOpts(clicontext *cli.Context) func(image containerd.Image, rootfs string) (opts []oci.SpecOpts, done func() error, rErr error) {
return func(image containerd.Image, rootfs string) (opts []oci.SpecOpts, done func() error, rErr error) { return func(image containerd.Image, rootfs string) (opts []oci.SpecOpts, done func() error, rErr error) {
var cleanups []func() error var cleanups []func() error
done = func() error { done = func() (allErr error) {
var errs []error
for i := len(cleanups) - 1; i >= 0; i-- { for i := len(cleanups) - 1; i >= 0; i-- {
if err := cleanups[i](); err != nil { if err := cleanups[i](); err != nil {
errs = append(errs, err) allErr = multierror.Append(allErr, err)
} }
} }
return errors.Join(errs...) return
} }
defer func() { defer func() {
if rErr != nil { if rErr != nil {
@ -223,21 +203,16 @@ func getSpecOpts(clicontext *cli.Context) func(image containerd.Image, rootfs st
} }
if clicontext.Bool("net-host") { if clicontext.Bool("net-host") {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
log.L.Warn("option --net-host is not supported on Windows") logrus.Warn("option --net-host is not supported on Windows")
} else { } else {
opts = append(opts, oci.WithHostNamespace(runtimespec.NetworkNamespace), oci.WithHostHostsFile, oci.WithHostResolvconf) opts = append(opts, oci.WithHostNamespace(runtimespec.NetworkNamespace), oci.WithHostHostsFile, oci.WithHostResolvconf)
} }
} }
if clicontext.IsSet("gpus") { if clicontext.IsSet("gpus") {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
log.L.Warn("option --gpus is not supported on Windows") logrus.Warn("option --gpus is not supported on Windows")
} else { } else {
devices, useAll := parseGPUs(clicontext.String("gpus")) opts = append(opts, nvidia.WithGPUs(nvidia.WithDevices(clicontext.IntSlice("gpus")...), nvidia.WithAllCapabilities))
if useAll {
opts = append(opts, nvidia.WithGPUs(nvidia.WithAllCapabilities))
} else if len(devices) > 0 {
opts = append(opts, nvidia.WithGPUs(nvidia.WithDevices(devices...), nvidia.WithAllCapabilities))
}
} }
} }
@ -290,14 +265,13 @@ func withEntrypointArgs(clicontext *cli.Context, image containerd.Image) (oci.Sp
func withCNI(clicontext *cli.Context) (specOpt oci.SpecOpts, done func() error, rErr error) { func withCNI(clicontext *cli.Context) (specOpt oci.SpecOpts, done func() error, rErr error) {
var cleanups []func() error var cleanups []func() error
done = func() error { done = func() (allErr error) {
var errs []error
for i := len(cleanups) - 1; i >= 0; i-- { for i := len(cleanups) - 1; i >= 0; i-- {
if err := cleanups[i](); err != nil { if err := cleanups[i](); err != nil {
errs = append(errs, err) allErr = multierror.Append(allErr, err)
} }
} }
return errors.Join(errs...) return
} }
defer func() { defer func() {
if rErr != nil { if rErr != nil {

View File

@ -22,27 +22,27 @@ import (
"fmt" "fmt"
"io" "io"
"github.com/containerd/containerd/v2/cmd/ctr/commands" "github.com/containerd/containerd/cmd/ctr/commands"
"github.com/containerd/stargz-snapshotter/estargz" "github.com/containerd/stargz-snapshotter/estargz"
"github.com/containerd/stargz-snapshotter/estargz/zstdchunked" "github.com/containerd/stargz-snapshotter/estargz/zstdchunked"
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
// GetTOCDigestCommand outputs TOC info of a layer // GetTOCDigestCommand outputs TOC info of a layer
var GetTOCDigestCommand = &cli.Command{ var GetTOCDigestCommand = cli.Command{
Name: "get-toc-digest", Name: "get-toc-digest",
Usage: "get the digest of TOC of a layer", Usage: "get the digest of TOC of a layer",
ArgsUsage: "<layer digest>", ArgsUsage: "<layer digest>",
Flags: []cli.Flag{ Flags: []cli.Flag{
// zstd:chunked flags // zstd:chunked flags
&cli.BoolFlag{ cli.BoolFlag{
Name: "zstdchunked", Name: "zstdchunked",
Usage: "parse layer as zstd:chunked", Usage: "parse layer as zstd:chunked",
}, },
// other flags for debugging // other flags for debugging
&cli.BoolFlag{ cli.BoolFlag{
Name: "dump-toc", Name: "dump-toc",
Usage: "dump TOC instead of digest. Note that the dumped TOC might be formatted with indents so may have different digest against the original in the layer", Usage: "dump TOC instead of digest. Note that the dumped TOC might be formatted with indents so may have different digest against the original in the layer",
}, },

View File

@ -20,35 +20,34 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/containerd/containerd/v2/cmd/ctr/commands" "github.com/containerd/containerd/cmd/ctr/commands"
"github.com/containerd/containerd/v2/core/images/converter" "github.com/containerd/containerd/images/converter"
"github.com/containerd/log" "github.com/containerd/containerd/platforms"
"github.com/containerd/platforms"
"github.com/containerd/stargz-snapshotter/ipfs" "github.com/containerd/stargz-snapshotter/ipfs"
estargzconvert "github.com/containerd/stargz-snapshotter/nativeconverter/estargz" estargzconvert "github.com/containerd/stargz-snapshotter/nativeconverter/estargz"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/urfave/cli/v2" "github.com/sirupsen/logrus"
"github.com/urfave/cli"
) )
// IPFSPushCommand pushes an image to IPFS // IPFSPushCommand pushes an image to IPFS
var IPFSPushCommand = &cli.Command{ var IPFSPushCommand = cli.Command{
Name: "ipfs-push", Name: "ipfs-push",
Usage: "push an image to IPFS (experimental)", Usage: "push an image to IPFS (experimental)",
ArgsUsage: "[flags] <image_ref>", ArgsUsage: "[flags] <image_ref>",
Flags: []cli.Flag{ Flags: []cli.Flag{
// platform flags // platform flags
&cli.StringSliceFlag{ cli.StringSliceFlag{
Name: "platform", Name: "platform",
Usage: "Add content for a specific platform", Usage: "Add content for a specific platform",
Value: &cli.StringSlice{}, Value: &cli.StringSlice{},
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "all-platforms", Name: "all-platforms",
Usage: "Add content for all platforms", Usage: "Add content for all platforms",
}, },
&cli.BoolFlag{ cli.BoolTFlag{
Name: "estargz", Name: "estargz",
Value: true,
Usage: "Convert the image into eStargz", Usage: "Convert the image into eStargz",
}, },
}, },
@ -91,7 +90,7 @@ var IPFSPushCommand = &cli.Command{
if err != nil { if err != nil {
return err return err
} }
log.L.WithField("CID", p).Infof("Pushed") logrus.WithField("CID", p).Infof("Pushed")
fmt.Println(p) fmt.Println(p)
return nil return nil

View File

@ -21,11 +21,11 @@ import (
"os" "os"
"github.com/containerd/stargz-snapshotter/analyzer/fanotify/service" "github.com/containerd/stargz-snapshotter/analyzer/fanotify/service"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
// FanotifyCommand notifies filesystem event under the specified directory. // FanotifyCommand notifies filesystem event under the specified directory.
var FanotifyCommand = &cli.Command{ var FanotifyCommand = cli.Command{
Name: "fanotify", Name: "fanotify",
Hidden: true, Hidden: true,
Action: func(context *cli.Context) error { Action: func(context *cli.Context) error {

View File

@ -27,13 +27,12 @@ import (
"os/signal" "os/signal"
"time" "time"
containerd "github.com/containerd/containerd/v2/client" "github.com/containerd/containerd"
"github.com/containerd/containerd/v2/cmd/ctr/commands" "github.com/containerd/containerd/cmd/ctr/commands"
"github.com/containerd/containerd/v2/core/content" "github.com/containerd/containerd/content"
"github.com/containerd/containerd/v2/core/images" "github.com/containerd/containerd/images"
"github.com/containerd/containerd/v2/core/images/converter" "github.com/containerd/containerd/images/converter"
"github.com/containerd/log" "github.com/containerd/containerd/platforms"
"github.com/containerd/platforms"
"github.com/containerd/stargz-snapshotter/analyzer" "github.com/containerd/stargz-snapshotter/analyzer"
"github.com/containerd/stargz-snapshotter/estargz" "github.com/containerd/stargz-snapshotter/estargz"
"github.com/containerd/stargz-snapshotter/estargz/zstdchunked" "github.com/containerd/stargz-snapshotter/estargz/zstdchunked"
@ -45,74 +44,75 @@ import (
"github.com/klauspost/compress/zstd" "github.com/klauspost/compress/zstd"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/urfave/cli/v2" "github.com/sirupsen/logrus"
"github.com/urfave/cli"
) )
const defaultPeriod = 10 const defaultPeriod = 10
// OptimizeCommand converts and optimizes an image // OptimizeCommand converts and optimizes an image
var OptimizeCommand = &cli.Command{ var OptimizeCommand = cli.Command{
Name: "optimize", Name: "optimize",
Usage: "optimize an image with user-specified workload", Usage: "optimize an image with user-specified workload",
ArgsUsage: "[flags] <source_ref> <target_ref>...", ArgsUsage: "[flags] <source_ref> <target_ref>...",
Flags: append([]cli.Flag{ Flags: append([]cli.Flag{
&cli.BoolFlag{ cli.BoolFlag{
Name: "reuse", Name: "reuse",
Usage: "reuse eStargz (already optimized) layers without further conversion", Usage: "reuse eStargz (already optimized) layers without further conversion",
}, },
&cli.StringSliceFlag{ cli.StringSliceFlag{
Name: "platform", Name: "platform",
Usage: "Pull content from a specific platform", Usage: "Pull content from a specific platform",
Value: &cli.StringSlice{}, Value: &cli.StringSlice{},
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "all-platforms", Name: "all-platforms",
Usage: "targeting all platform of the source image", Usage: "targeting all platform of the source image",
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "wait-on-signal", Name: "wait-on-signal",
Usage: "ignore context cancel and keep the container running until it receives SIGINT (Ctrl + C) sent manually", Usage: "ignore context cancel and keep the container running until it receives SIGINT (Ctrl + C) sent manually",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "wait-on-line", Name: "wait-on-line",
Usage: "Substring of a stdout line to be waited. When this string is detected, the container will be killed.", Usage: "Substring of a stdout line to be waited. When this string is detected, the container will be killed.",
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "no-optimize", Name: "no-optimize",
Usage: "convert image without optimization", Usage: "convert image without optimization",
}, },
&cli.StringFlag{ cli.StringFlag{
Name: "record-out", Name: "record-out",
Usage: "record the monitor log to the specified file", Usage: "record the monitor log to the specified file",
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "oci", Name: "oci",
Usage: "convert Docker media types to OCI media types", Usage: "convert Docker media types to OCI media types",
}, },
&cli.IntFlag{ cli.IntFlag{
Name: "estargz-compression-level", Name: "estargz-compression-level",
Usage: "eStargz compression level", Usage: "eStargz compression level",
Value: gzip.BestCompression, Value: gzip.BestCompression,
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "estargz-external-toc", Name: "estargz-external-toc",
Usage: "Separate TOC JSON into another image (called \"TOC image\"). The name of TOC image is the original + \"-esgztoc\" suffix. Both eStargz and the TOC image should be pushed to the same registry. stargz-snapshotter refers to the TOC image when it pulls the result eStargz image.", Usage: "Separate TOC JSON into another image (called \"TOC image\"). The name of TOC image is the original + \"-esgztoc\" suffix. Both eStargz and the TOC image should be pushed to the same registry. stargz-snapshotter refers to the TOC image when it pulls the result eStargz image.",
}, },
&cli.IntFlag{ cli.IntFlag{
Name: "estargz-chunk-size", Name: "estargz-chunk-size",
Usage: "eStargz chunk size (not applied to zstd:chunked)", Usage: "eStargz chunk size (not applied to zstd:chunked)",
Value: 0, Value: 0,
}, },
&cli.IntFlag{ cli.IntFlag{
Name: "estargz-min-chunk-size", Name: "estargz-min-chunk-size",
Usage: "The minimal number of bytes of data must be written in one gzip stream. Note that this adds a TOC property that old reader doesn't understand (not applied to zstd:chunked)", Usage: "The minimal number of bytes of data must be written in one gzip stream. Note that this adds a TOC property that old reader doesn't understand (not applied to zstd:chunked)",
Value: 0, Value: 0,
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "zstdchunked", Name: "zstdchunked",
Usage: "use zstd compression instead of gzip (a.k.a zstd:chunked)", Usage: "use zstd compression instead of gzip (a.k.a zstd:chunked)",
}, },
&cli.IntFlag{ cli.IntFlag{
Name: "zstdchunked-compression-level", Name: "zstdchunked-compression-level",
Usage: "zstd:chunked compression level", Usage: "zstd:chunked compression level",
Value: 3, // SpeedDefault; see also https://pkg.go.dev/github.com/klauspost/compress/zstd#EncoderLevel Value: 3, // SpeedDefault; see also https://pkg.go.dev/github.com/klauspost/compress/zstd#EncoderLevel
@ -205,7 +205,7 @@ var OptimizeCommand = &cli.Command{
// Cleanly cancel conversion // Cleanly cancel conversion
select { select {
case s := <-sigCh: case s := <-sigCh:
log.G(ctx).Infof("Got %v", s) logrus.Infof("Got %v", s)
cancel() cancel()
case <-ctx.Done(): case <-ctx.Done():
} }
@ -389,7 +389,7 @@ func excludeWrapper(excludes []digest.Digest) func(converter.ConvertFunc) conver
return func(ctx context.Context, cs content.Store, desc ocispec.Descriptor) (*ocispec.Descriptor, error) { return func(ctx context.Context, cs content.Store, desc ocispec.Descriptor) (*ocispec.Descriptor, error) {
for _, e := range excludes { for _, e := range excludes {
if e == desc.Digest { if e == desc.Digest {
log.G(ctx).Warnf("reusing %q without conversion", e) logrus.Warnf("reusing %q without conversion", e)
return nil, nil return nil, nil
} }
} }
@ -400,7 +400,7 @@ func excludeWrapper(excludes []digest.Digest) func(converter.ConvertFunc) conver
func logWrapper(convertFunc converter.ConvertFunc) converter.ConvertFunc { func logWrapper(convertFunc converter.ConvertFunc) converter.ConvertFunc {
return func(ctx context.Context, cs content.Store, desc ocispec.Descriptor) (*ocispec.Descriptor, error) { return func(ctx context.Context, cs content.Store, desc ocispec.Descriptor) (*ocispec.Descriptor, error) {
log.G(ctx).WithField("digest", desc.Digest).Infof("converting...") logrus.WithField("digest", desc.Digest).Infof("converting...")
return convertFunc(ctx, cs, desc) return convertFunc(ctx, cs, desc)
} }
} }

View File

@ -20,18 +20,18 @@ import (
"context" "context"
"fmt" "fmt"
containerd "github.com/containerd/containerd/v2/client" "github.com/containerd/containerd"
"github.com/containerd/containerd/v2/cmd/ctr/commands" "github.com/containerd/containerd/cmd/ctr/commands"
"github.com/containerd/containerd/v2/cmd/ctr/commands/content" "github.com/containerd/containerd/cmd/ctr/commands/content"
"github.com/containerd/containerd/v2/core/images" "github.com/containerd/containerd/images"
"github.com/containerd/containerd/v2/core/snapshots" "github.com/containerd/containerd/log"
ctdsnapshotters "github.com/containerd/containerd/v2/pkg/snapshotters" ctdsnapshotters "github.com/containerd/containerd/pkg/snapshotters"
"github.com/containerd/log" "github.com/containerd/containerd/snapshots"
fsconfig "github.com/containerd/stargz-snapshotter/fs/config" fsconfig "github.com/containerd/stargz-snapshotter/fs/config"
"github.com/containerd/stargz-snapshotter/fs/source" "github.com/containerd/stargz-snapshotter/fs/source"
"github.com/containerd/stargz-snapshotter/ipfs" "github.com/containerd/stargz-snapshotter/ipfs"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
const ( const (
@ -39,26 +39,26 @@ const (
skipContentVerifyOpt = "skip-content-verify" skipContentVerifyOpt = "skip-content-verify"
) )
// RpullCommand is a subcommand to pull an image from a registry leveraging stargz snapshotter // RpullCommand is a subcommand to pull an image from a registry levaraging stargz snapshotter
var RpullCommand = &cli.Command{ var RpullCommand = cli.Command{
Name: "rpull", Name: "rpull",
Usage: "pull an image from a registry leveraging stargz snapshotter", Usage: "pull an image from a registry levaraging stargz snapshotter",
ArgsUsage: "[flags] <ref>", ArgsUsage: "[flags] <ref>",
Description: `Fetch and prepare an image for use in containerd leveraging stargz snapshotter. Description: `Fetch and prepare an image for use in containerd levaraging stargz snapshotter.
After pulling an image, it should be ready to use the same reference in a run After pulling an image, it should be ready to use the same reference in a run
command. command.
`, `,
Flags: append(append(commands.RegistryFlags, commands.LabelFlag, Flags: append(append(commands.RegistryFlags, commands.LabelFlag,
&cli.BoolFlag{ cli.BoolFlag{
Name: skipContentVerifyOpt, Name: skipContentVerifyOpt,
Usage: "Skip content verification for layers contained in this image.", Usage: "Skip content verification for layers contained in this image.",
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "ipfs", Name: "ipfs",
Usage: "Pull image from IPFS. Specify an IPFS CID as a reference. (experimental)", Usage: "Pull image from IPFS. Specify an IPFS CID as a reference. (experimental)",
}, },
&cli.BoolFlag{ cli.BoolFlag{
Name: "use-containerd-labels", Name: "use-containerd-labels",
Usage: "Use labels defined in containerd project", Usage: "Use labels defined in containerd project",
}, },
@ -115,8 +115,8 @@ command.
type rPullConfig struct { type rPullConfig struct {
*content.FetchConfig *content.FetchConfig
skipVerify bool skipVerify bool
snapshotter string snapshotter string
containerdLabels bool containerdLabels bool
} }
@ -138,7 +138,7 @@ func pull(ctx context.Context, client *containerd.Client, ref string, config *rP
} }
var labelHandler func(h images.Handler) images.Handler var labelHandler func(h images.Handler) images.Handler
prefetchSize := int64(10 * 1024 * 1024) prefetchSize := int64(10*1024*1024)
if config.containerdLabels { if config.containerdLabels {
labelHandler = source.AppendExtraLabelsHandler(prefetchSize, ctdsnapshotters.AppendInfoHandlerWrapper(ref)) labelHandler = source.AppendExtraLabelsHandler(prefetchSize, ctdsnapshotters.AppendInfoHandlerWrapper(ref))
} else { } else {
@ -151,6 +151,7 @@ func pull(ctx context.Context, client *containerd.Client, ref string, config *rP
containerd.WithPullLabels(labels), containerd.WithPullLabels(labels),
containerd.WithResolver(config.Resolver), containerd.WithResolver(config.Resolver),
containerd.WithImageHandler(h), containerd.WithImageHandler(h),
containerd.WithSchema1Conversion,
containerd.WithPullUnpack, containerd.WithPullUnpack,
containerd.WithPullSnapshotter(config.snapshotter, snOpts...), containerd.WithPullSnapshotter(config.snapshotter, snOpts...),
containerd.WithImageHandlerWrapper(labelHandler), containerd.WithImageHandlerWrapper(labelHandler),

View File

@ -20,13 +20,18 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/containerd/containerd/v2/cmd/ctr/app" "github.com/containerd/containerd/cmd/ctr/app"
"github.com/containerd/containerd/pkg/seed"
"github.com/containerd/stargz-snapshotter/cmd/ctr-remote/commands" "github.com/containerd/stargz-snapshotter/cmd/ctr-remote/commands"
"github.com/urfave/cli/v2" "github.com/urfave/cli"
) )
func init() {
seed.WithTimeAndRand()
}
func main() { func main() {
customCommands := []*cli.Command{ customCommands := []cli.Command{
commands.RpullCommand, commands.RpullCommand,
commands.OptimizeCommand, commands.OptimizeCommand,
commands.ConvertCommand, commands.ConvertCommand,
@ -36,7 +41,7 @@ func main() {
app := app.New() app := app.New()
for i := range app.Commands { for i := range app.Commands {
if app.Commands[i].Name == "images" { if app.Commands[i].Name == "images" {
sc := map[string]*cli.Command{} sc := map[string]cli.Command{}
for _, subcmd := range customCommands { for _, subcmd := range customCommands {
sc[subcmd.Name] = subcmd sc[subcmd.Name] = subcmd
} }

View File

@ -1,154 +1,140 @@
module github.com/containerd/stargz-snapshotter/cmd module github.com/containerd/stargz-snapshotter/cmd
go 1.24.0 go 1.19
toolchain go1.24.2
require ( require (
github.com/containerd/containerd/api v1.9.0 github.com/containerd/containerd v1.7.0-rc.2
github.com/containerd/containerd/v2 v2.1.4 github.com/containerd/go-cni v1.1.9
github.com/containerd/go-cni v1.1.13 github.com/containerd/stargz-snapshotter v0.14.3
github.com/containerd/log v0.1.0 github.com/containerd/stargz-snapshotter/estargz v0.14.3
github.com/containerd/platforms v1.0.0-rc.1 github.com/containerd/stargz-snapshotter/ipfs v0.14.3
github.com/containerd/stargz-snapshotter v0.15.2-0.20240622031358-6405f362966d
github.com/containerd/stargz-snapshotter/estargz v0.17.0
github.com/containerd/stargz-snapshotter/ipfs v0.15.2-0.20240622031358-6405f362966d
github.com/coreos/go-systemd/v22 v22.5.0 github.com/coreos/go-systemd/v22 v22.5.0
github.com/docker/go-metrics v0.0.1 github.com/docker/go-metrics v0.0.1
github.com/goccy/go-json v0.10.5 github.com/goccy/go-json v0.10.0
github.com/klauspost/compress v1.18.0 github.com/hashicorp/go-multierror v1.1.1
github.com/klauspost/compress v1.16.0
github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.1 github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b
github.com/opencontainers/runtime-spec v1.2.1 github.com/opencontainers/runtime-spec v1.1.0-rc.1
github.com/pelletier/go-toml v1.9.5 github.com/pelletier/go-toml v1.9.5
github.com/rs/xid v1.6.0 github.com/rs/xid v1.4.0
github.com/urfave/cli/v2 v2.27.7 github.com/sirupsen/logrus v1.9.0
go.etcd.io/bbolt v1.4.2 github.com/urfave/cli v1.22.12
golang.org/x/sync v0.16.0 go.etcd.io/bbolt v1.3.7
golang.org/x/sys v0.34.0 golang.org/x/sync v0.1.0
google.golang.org/grpc v1.74.2 golang.org/x/sys v0.6.0
google.golang.org/grpc v1.53.0
k8s.io/cri-api v0.27.0-alpha.3
) )
require ( require (
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 // indirect
github.com/Microsoft/hcsshim v0.13.0 // indirect github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20221215162035-5330a85ea652 // indirect
github.com/Microsoft/go-winio v0.6.0 // indirect
github.com/Microsoft/hcsshim v0.10.0-rc.7 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cilium/ebpf v0.16.0 // indirect github.com/cilium/ebpf v0.9.1 // indirect
github.com/containerd/cgroups/v3 v3.0.5 // indirect github.com/containerd/cgroups v1.1.0 // indirect
github.com/containerd/console v1.0.5 // indirect github.com/containerd/cgroups/v3 v3.0.1 // indirect
github.com/containerd/continuity v0.4.5 // indirect github.com/containerd/console v1.0.3 // indirect
github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/continuity v0.3.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/fifo v1.1.0 // indirect github.com/containerd/fifo v1.1.0 // indirect
github.com/containerd/go-runc v1.1.0 // indirect github.com/containerd/go-runc v1.0.0 // indirect
github.com/containerd/plugin v1.0.0 // indirect github.com/containerd/ttrpc v1.2.0 // indirect
github.com/containerd/ttrpc v1.2.7 // indirect github.com/containerd/typeurl/v2 v2.1.0 // indirect
github.com/containerd/typeurl/v2 v2.2.3 // indirect github.com/containernetworking/cni v1.1.2 // indirect
github.com/containernetworking/cni v1.3.0 // indirect github.com/containernetworking/plugins v1.2.0 // indirect
github.com/containernetworking/plugins v1.7.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/cyphar/filepath-securejoin v0.2.3 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/distribution/reference v0.6.0 // indirect github.com/docker/cli v23.0.1+incompatible // indirect
github.com/docker/cli v28.3.3+incompatible // indirect github.com/docker/docker v20.10.20+incompatible // indirect
github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
github.com/docker/go-units v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/emicklei/go-restful/v3 v3.10.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.2.3 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/jsonreference v0.20.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect github.com/go-openapi/swag v0.19.14 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/gnostic-models v0.6.9 // indirect github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.7.0 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/go-cmp v0.5.9 // indirect
github.com/hanwen/go-fuse/v2 v2.8.0 // indirect github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/hanwen/go-fuse/v2 v2.2.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect github.com/hashicorp/go-retryablehttp v0.7.2 // indirect
github.com/intel/goresctrl v0.8.0 // indirect github.com/imdario/mergo v0.3.13 // indirect
github.com/intel/goresctrl v0.3.0 // indirect
github.com/ipfs/go-cid v0.1.0 // indirect github.com/ipfs/go-cid v0.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.6 // indirect
github.com/mdlayher/socket v0.5.1 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mdlayher/vsock v1.2.1 // indirect github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect
github.com/minio/sha256-simd v1.0.1 // indirect github.com/minio/sha256-simd v1.0.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/moby/locker v1.0.1 // indirect github.com/moby/locker v1.0.1 // indirect
github.com/moby/sys/mountinfo v0.7.2 // indirect github.com/moby/sys/mountinfo v0.6.2 // indirect
github.com/moby/sys/sequential v0.6.0 // indirect github.com/moby/sys/sequential v0.5.0 // indirect
github.com/moby/sys/signal v0.7.1 // indirect github.com/moby/sys/signal v0.7.0 // indirect
github.com/moby/sys/symlink v0.3.0 // indirect github.com/moby/sys/symlink v0.2.0 // indirect
github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect github.com/mr-tron/base58 v1.2.0 // indirect
github.com/multiformats/go-base32 v0.1.0 // indirect github.com/multiformats/go-base32 v0.0.3 // indirect
github.com/multiformats/go-base36 v0.2.0 // indirect github.com/multiformats/go-base36 v0.1.0 // indirect
github.com/multiformats/go-multiaddr v0.16.1 // indirect github.com/multiformats/go-multiaddr v0.8.0 // indirect
github.com/multiformats/go-multibase v0.2.0 // indirect github.com/multiformats/go-multibase v0.0.3 // indirect
github.com/multiformats/go-multihash v0.2.3 // indirect github.com/multiformats/go-multihash v0.1.0 // indirect
github.com/multiformats/go-varint v0.0.7 // indirect github.com/multiformats/go-varint v0.0.6 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/opencontainers/runtime-tools v0.9.1-0.20221107090550-2e043c6bd626 // indirect github.com/opencontainers/runc v1.1.4 // indirect
github.com/opencontainers/selinux v1.12.0 // indirect github.com/opencontainers/selinux v1.11.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_golang v1.23.0 // indirect github.com/prometheus/client_golang v1.14.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.16.1 // indirect github.com/prometheus/procfs v0.8.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sasha-s/go-deadlock v0.3.5 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/spf13/pflag v1.0.6 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect github.com/vbatts/tar-split v0.11.2 // indirect
github.com/vbatts/tar-split v0.12.1 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
go.opencensus.io v0.24.0 // indirect go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/otel v1.12.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect go.opentelemetry.io/otel/trace v1.12.0 // indirect
go.opentelemetry.io/otel v1.36.0 // indirect golang.org/x/crypto v0.1.0 // indirect
go.opentelemetry.io/otel/metric v1.36.0 // indirect golang.org/x/mod v0.7.0 // indirect
go.opentelemetry.io/otel/trace v1.36.0 // indirect golang.org/x/net v0.7.0 // indirect
golang.org/x/crypto v0.38.0 // indirect golang.org/x/oauth2 v0.4.0 // indirect
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect golang.org/x/term v0.5.0 // indirect
golang.org/x/mod v0.24.0 // indirect golang.org/x/text v0.7.0 // indirect
golang.org/x/net v0.40.0 // indirect golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/tools v0.5.0 // indirect
golang.org/x/term v0.32.0 // indirect google.golang.org/appengine v1.6.7 // indirect
golang.org/x/text v0.25.0 // indirect google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect
golang.org/x/time v0.9.0 // indirect google.golang.org/protobuf v1.28.1 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.33.3 // indirect k8s.io/api v0.26.2 // indirect
k8s.io/apimachinery v0.33.3 // indirect k8s.io/apimachinery v0.26.2 // indirect
k8s.io/client-go v0.33.3 // indirect k8s.io/client-go v0.26.2 // indirect
k8s.io/cri-api v0.33.3 // indirect k8s.io/klog/v2 v2.90.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 // indirect
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect lukechampine.com/blake3 v1.1.6 // indirect
lukechampine.com/blake3 v1.2.1 // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/yaml v1.3.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
tags.cncf.io/container-device-interface v1.0.1 // indirect
tags.cncf.io/container-device-interface/specs-go v1.0.0 // indirect
) )
replace ( replace (

File diff suppressed because it is too large Load Diff

View File

@ -1,97 +0,0 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"fmt"
"net"
"os"
"path/filepath"
"github.com/containerd/log"
"github.com/containerd/stargz-snapshotter/cmd/containerd-stargz-grpc/fsopts"
fusemanager "github.com/containerd/stargz-snapshotter/fusemanager"
"github.com/containerd/stargz-snapshotter/service"
"github.com/containerd/stargz-snapshotter/service/keychain/keychainconfig"
"google.golang.org/grpc"
)
func init() {
fusemanager.RegisterConfigFunc(func(cc *fusemanager.ConfigContext) ([]service.Option, error) {
fsConfig := fsopts.Config{
EnableIpfs: cc.Config.IPFS,
MetadataStore: cc.Config.MetadataStore,
OpenBoltDB: cc.OpenBoltDB,
}
fsOpts, err := fsopts.ConfigFsOpts(cc.Ctx, cc.RootDir, &fsConfig)
if err != nil {
return nil, err
}
return []service.Option{service.WithFilesystemOptions(fsOpts...)}, nil
})
fusemanager.RegisterConfigFunc(func(cc *fusemanager.ConfigContext) ([]service.Option, error) {
keyChainConfig := keychainconfig.Config{
EnableKubeKeychain: cc.Config.Config.KubeconfigKeychainConfig.EnableKeychain,
EnableCRIKeychain: cc.Config.Config.CRIKeychainConfig.EnableKeychain,
KubeconfigPath: cc.Config.Config.KubeconfigPath,
DefaultImageServiceAddress: cc.Config.DefaultImageServiceAddress,
ImageServicePath: cc.Config.Config.ImageServicePath,
}
if cc.Config.Config.CRIKeychainConfig.EnableKeychain && cc.Config.Config.ListenPath == "" || cc.Config.Config.ListenPath == cc.Address {
return nil, fmt.Errorf("listen path of CRI server must be specified as a separated socket from FUSE manager server")
}
// For CRI keychain, if listening path is different from stargz-snapshotter's socket, prepare for the dedicated grpc server and the socket.
serveCRISocket := cc.Config.Config.CRIKeychainConfig.EnableKeychain && cc.Config.Config.ListenPath != "" && cc.Config.Config.ListenPath != cc.Address
if serveCRISocket {
cc.CRIServer = grpc.NewServer()
}
credsFuncs, err := keychainconfig.ConfigKeychain(cc.Ctx, cc.CRIServer, &keyChainConfig)
if err != nil {
return nil, err
}
if serveCRISocket {
addr := cc.Config.Config.ListenPath
// Prepare the directory for the socket
if err := os.MkdirAll(filepath.Dir(addr), 0700); err != nil {
return nil, fmt.Errorf("failed to create directory %q: %w", filepath.Dir(addr), err)
}
// Try to remove the socket file to avoid EADDRINUSE
if err := os.RemoveAll(addr); err != nil {
return nil, fmt.Errorf("failed to remove %q: %w", addr, err)
}
// Listen and serve
l, err := net.Listen("unix", addr)
if err != nil {
return nil, fmt.Errorf("error on listen socket %q: %w", addr, err)
}
go func() {
if err := cc.CRIServer.Serve(l); err != nil {
log.G(cc.Ctx).WithError(err).Errorf("error on serving CRI via socket %q", addr)
}
}()
}
return []service.Option{service.WithCredsFuncs(credsFuncs...)}, nil
})
}
func main() {
fusemanager.Run()
}

View File

@ -1,66 +0,0 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"context"
"io"
"os"
"time"
"github.com/containerd/containerd/v2/defaults"
"github.com/containerd/containerd/v2/pkg/dialer"
"github.com/containerd/stargz-snapshotter/store/pb"
grpc "google.golang.org/grpc"
"google.golang.org/grpc/backoff"
"google.golang.org/grpc/credentials/insecure"
)
func main() {
var addr = "/var/lib/stargz-store/store.sock" // default
if len(os.Args) >= 2 {
addr = os.Args[1]
}
data, err := io.ReadAll(os.Stdin)
if err != nil {
panic(err)
}
backoffConfig := backoff.DefaultConfig
backoffConfig.MaxDelay = 3 * time.Second
connParams := grpc.ConnectParams{
Backoff: backoffConfig,
}
gopts := []grpc.DialOption{
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithConnectParams(connParams),
grpc.WithContextDialer(dialer.ContextDialer),
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(defaults.DefaultMaxRecvMsgSize)),
grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(defaults.DefaultMaxSendMsgSize)),
}
conn, err := grpc.NewClient(dialer.DialAddress(addr), gopts...)
if err != nil {
panic(err)
}
c := pb.NewControllerClient(conn)
_, err = c.AddCredential(context.Background(), &pb.AddCredentialRequest{
Data: data,
})
if err != nil {
panic(err)
}
}

View File

@ -17,41 +17,35 @@
package main package main
import ( import (
"bytes"
"context" "context"
"encoding/json"
"errors"
"flag" "flag"
"fmt" "fmt"
"io" "io"
golog "log" golog "log"
"math/rand" "math/rand"
"net"
"os" "os"
"os/signal" "os/signal"
"path/filepath" "path/filepath"
"sync"
"syscall" "syscall"
"time" "time"
"github.com/containerd/containerd/v2/pkg/reference" "github.com/containerd/containerd/log"
"github.com/containerd/log"
dbmetadata "github.com/containerd/stargz-snapshotter/cmd/containerd-stargz-grpc/db" dbmetadata "github.com/containerd/stargz-snapshotter/cmd/containerd-stargz-grpc/db"
"github.com/containerd/stargz-snapshotter/fs/config" "github.com/containerd/stargz-snapshotter/fs/config"
"github.com/containerd/stargz-snapshotter/metadata" "github.com/containerd/stargz-snapshotter/metadata"
memorymetadata "github.com/containerd/stargz-snapshotter/metadata/memory" memorymetadata "github.com/containerd/stargz-snapshotter/metadata/memory"
"github.com/containerd/stargz-snapshotter/service/keychain/dockerconfig"
"github.com/containerd/stargz-snapshotter/service/keychain/kubeconfig" "github.com/containerd/stargz-snapshotter/service/keychain/kubeconfig"
"github.com/containerd/stargz-snapshotter/service/resolver" "github.com/containerd/stargz-snapshotter/service/resolver"
"github.com/containerd/stargz-snapshotter/store" "github.com/containerd/stargz-snapshotter/store"
"github.com/containerd/stargz-snapshotter/store/pb"
sddaemon "github.com/coreos/go-systemd/v22/daemon" sddaemon "github.com/coreos/go-systemd/v22/daemon"
"github.com/pelletier/go-toml" "github.com/pelletier/go-toml"
"github.com/sirupsen/logrus"
bolt "go.etcd.io/bbolt" bolt "go.etcd.io/bbolt"
grpc "google.golang.org/grpc"
) )
const ( const (
defaultLogLevel = log.InfoLevel defaultLogLevel = logrus.InfoLevel
defaultConfigPath = "/etc/stargz-store/config.toml" defaultConfigPath = "/etc/stargz-store/config.toml"
defaultRootDir = "/var/lib/stargz-store" defaultRootDir = "/var/lib/stargz-store"
) )
@ -60,7 +54,6 @@ var (
configPath = flag.String("config", defaultConfigPath, "path to the configuration file") configPath = flag.String("config", defaultConfigPath, "path to the configuration file")
logLevel = flag.String("log-level", defaultLogLevel.String(), "set the logging level [trace, debug, info, warn, error, fatal, panic]") logLevel = flag.String("log-level", defaultLogLevel.String(), "set the logging level [trace, debug, info, warn, error, fatal, panic]")
rootDir = flag.String("root", defaultRootDir, "path to the root directory for this snapshotter") rootDir = flag.String("root", defaultRootDir, "path to the root directory for this snapshotter")
listenaddr = flag.String("addr", filepath.Join(defaultRootDir, "store.sock"), "path to the socket listened by this snapshotter")
) )
type Config struct { type Config struct {
@ -84,22 +77,25 @@ type KubeconfigKeychainConfig struct {
type ResolverConfig resolver.Config type ResolverConfig resolver.Config
func main() { func main() {
rand.Seed(time.Now().UnixNano()) //nolint:staticcheck // Global math/rand seed is deprecated, but still used by external dependencies rand.Seed(time.Now().UnixNano())
flag.Parse() flag.Parse()
mountPoint := flag.Arg(0) mountPoint := flag.Arg(0)
err := log.SetLevel(*logLevel) lvl, err := logrus.ParseLevel(*logLevel)
if err != nil { if err != nil {
log.L.WithError(err).Fatal("failed to prepare logger") log.L.WithError(err).Fatal("failed to prepare logger")
} }
log.SetFormat(log.JSONFormat) logrus.SetLevel(lvl)
logrus.SetFormatter(&logrus.JSONFormatter{
TimestampFormat: log.RFC3339NanoFixed,
})
var ( var (
ctx = log.WithLogger(context.Background(), log.L) ctx = log.WithLogger(context.Background(), log.L)
config Config config Config
) )
// Streams log of standard lib (go-fuse uses this) into debug log // Streams log of standard lib (go-fuse uses this) into debug log
// Snapshotter should use "github.com/containerd/log" otherwise // Snapshotter should use "github.com/containerd/containerd/log" otherwise
// logs are always printed as "debug" mode. // logs are always printed as "debug" mode.
golog.SetOutput(log.G(ctx).WriterLevel(log.DebugLevel)) golog.SetOutput(log.G(ctx).WriterLevel(logrus.DebugLevel))
if mountPoint == "" { if mountPoint == "" {
log.G(ctx).Fatalf("mount point must be specified") log.G(ctx).Fatalf("mount point must be specified")
@ -108,7 +104,7 @@ func main() {
// Get configuration from specified file // Get configuration from specified file
if *configPath != "" { if *configPath != "" {
tree, err := toml.LoadFile(*configPath) tree, err := toml.LoadFile(*configPath)
if err != nil && (!os.IsNotExist(err) || *configPath != defaultConfigPath) { if err != nil && !(os.IsNotExist(err) && *configPath == defaultConfigPath) {
log.G(ctx).WithError(err).Fatalf("failed to load config file %q", *configPath) log.G(ctx).WithError(err).Fatalf("failed to load config file %q", *configPath)
} }
if err := tree.Unmarshal(&config); err != nil { if err := tree.Unmarshal(&config); err != nil {
@ -116,15 +112,11 @@ func main() {
} }
} }
sk := new(storeKeychain)
errCh := serveController(*listenaddr, sk)
// Prepare kubeconfig-based keychain if required // Prepare kubeconfig-based keychain if required
credsFuncs := []resolver.Credential{sk.credentials} credsFuncs := []resolver.Credential{dockerconfig.NewDockerconfigKeychain(ctx)}
if config.EnableKeychain { if config.KubeconfigKeychainConfig.EnableKeychain {
var opts []kubeconfig.Option var opts []kubeconfig.Option
if kcp := config.KubeconfigPath; kcp != "" { if kcp := config.KubeconfigKeychainConfig.KubeconfigPath; kcp != "" {
opts = append(opts, kubeconfig.WithKubeconfigPath(kcp)) opts = append(opts, kubeconfig.WithKubeconfigPath(kcp))
} }
credsFuncs = append(credsFuncs, kubeconfig.NewKubeconfigKeychain(ctx, opts...)) credsFuncs = append(credsFuncs, kubeconfig.NewKubeconfigKeychain(ctx, opts...))
@ -140,8 +132,9 @@ func main() {
Fatalf("failed to prepare mountpoint %q", mountPoint) Fatalf("failed to prepare mountpoint %q", mountPoint)
} }
} }
if config.DisableVerification { if !config.Config.DisableVerification {
log.G(ctx).Fatalf("content verification can't be disabled") log.G(ctx).Warnf("content verification is not supported; switching to non-verification mode")
config.Config.DisableVerification = true
} }
mt, err := getMetadataStore(*rootDir, config) mt, err := getMetadataStore(*rootDir, config)
if err != nil { if err != nil {
@ -151,7 +144,7 @@ func main() {
if err != nil { if err != nil {
log.G(ctx).WithError(err).Fatalf("failed to prepare pool") log.G(ctx).WithError(err).Fatalf("failed to prepare pool")
} }
if err := store.Mount(ctx, mountPoint, layerManager, config.Debug); err != nil { if err := store.Mount(ctx, mountPoint, layerManager, config.Config.Debug); err != nil {
log.G(ctx).WithError(err).Fatalf("failed to mount fs at %q", mountPoint) log.G(ctx).WithError(err).Fatalf("failed to mount fs at %q", mountPoint)
} }
defer func() { defer func() {
@ -170,22 +163,14 @@ func main() {
} }
}() }()
if err := waitForSignal(ctx, errCh); err != nil { waitForSIGINT()
log.G(ctx).Errorf("error: %v", err) log.G(ctx).Info("Got SIGINT")
os.Exit(1)
}
} }
func waitForSignal(ctx context.Context, errCh <-chan error) error { func waitForSIGINT() {
c := make(chan os.Signal, 1) c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt) signal.Notify(c, os.Interrupt)
select { <-c
case s := <-c:
log.G(ctx).Infof("Got %v", s)
case err := <-errCh:
return err
}
return nil
} }
const ( const (
@ -215,80 +200,3 @@ func getMetadataStore(rootDir string, config Config) (metadata.Store, error) {
config.MetadataStore, memoryMetadataType, dbMetadataType) config.MetadataStore, memoryMetadataType, dbMetadataType)
} }
} }
func newController(addCredentialFunc func(data []byte) error) *controller {
return &controller{
addCredentialFunc: addCredentialFunc,
}
}
type controller struct {
addCredentialFunc func(data []byte) error
}
func (c *controller) AddCredential(ctx context.Context, req *pb.AddCredentialRequest) (resp *pb.AddCredentialResponse, _ error) {
return &pb.AddCredentialResponse{}, c.addCredentialFunc(req.Data)
}
type authConfig struct {
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
IdentityToken string `json:"identityToken,omitempty"`
}
type storeKeychain struct {
config map[string]authConfig
configMu sync.Mutex
}
func (sk *storeKeychain) add(data []byte) error {
conf := make(map[string]authConfig)
if err := json.NewDecoder(bytes.NewReader(data)).Decode(&conf); err != nil && !errors.Is(err, io.EOF) {
return err
}
sk.configMu.Lock()
if sk.config == nil {
sk.config = make(map[string]authConfig)
}
for k, c := range conf {
sk.config[k] = c
}
sk.configMu.Unlock()
return nil
}
func (sk *storeKeychain) credentials(host string, refspec reference.Spec) (string, string, error) {
if host != refspec.Hostname() {
return "", "", nil // Do not use creds for mirrors
}
sk.configMu.Lock()
defer sk.configMu.Unlock()
if acfg, ok := sk.config[refspec.String()]; ok {
if acfg.IdentityToken != "" {
return "", acfg.IdentityToken, nil
} else if acfg.Username != "" || acfg.Password != "" {
return acfg.Username, acfg.Password, nil
}
}
return "", "", nil
}
func serveController(addr string, sk *storeKeychain) <-chan error {
// Try to remove the socket file to avoid EADDRINUSE
os.Remove(addr)
rpc := grpc.NewServer()
c := newController(sk.add)
pb.RegisterControllerServer(rpc, c)
errCh := make(chan error, 1)
go func() {
l, err := net.Listen("unix", addr)
if err != nil {
errCh <- fmt.Errorf("error on listen socket %q: %w", addr, err)
return
}
if err := rpc.Serve(l); err != nil {
errCh <- fmt.Errorf("error on serving via socket %q: %w", addr, err)
}
}()
return errCh
}

View File

@ -44,8 +44,6 @@ We assume that you are using containerd (> v1.4.2) as a CRI runtime.
[proxy_plugins.stargz] [proxy_plugins.stargz]
type = "snapshot" type = "snapshot"
address = "/run/containerd-stargz-grpc/containerd-stargz-grpc.sock" address = "/run/containerd-stargz-grpc/containerd-stargz-grpc.sock"
[proxy_plugins.stargz.exports]
root = "/var/lib/containerd-stargz-grpc/"
``` ```
@ -147,8 +145,6 @@ We assume that you are using CRI-O newer than https://github.com/cri-o/cri-o/pul
[proxy_plugins.stargz] [proxy_plugins.stargz]
type = "snapshot" type = "snapshot"
address = "/run/containerd-stargz-grpc/containerd-stargz-grpc.sock" address = "/run/containerd-stargz-grpc/containerd-stargz-grpc.sock"
[proxy_plugins.stargz.exports]
root = "/var/lib/containerd-stargz-grpc/"
``` ```
- Install fuse - Install fuse
@ -178,7 +174,3 @@ We assume that you are using CRI-O newer than https://github.com/cri-o/cri-o/pul
systemctl restart containerd systemctl restart containerd
systemctl restart docker systemctl restart docker
``` ```
## Using stargz-snapshotter on Lima
See [`./lima.md`](./lima.md)

View File

@ -74,11 +74,10 @@ You can enable host networking for the container using the `net-host` flag.
# ctr-remote i optimize -t -i --oci --entrypoint='[ "/bin/bash", "-c" ]' --net-host --args='[ "ip a && curl example.com" ]' ghcr.io/stargz-containers/centos:8-test registry2:5000/centos:8-test-esgz # ctr-remote i optimize -t -i --oci --entrypoint='[ "/bin/bash", "-c" ]' --net-host --args='[ "ip a && curl example.com" ]' ghcr.io/stargz-containers/centos:8-test registry2:5000/centos:8-test-esgz
``` ```
You can optimize GPU-based images using the `gpu` flag. The flag expects a comma separated list of integers or 'all'. You can optimize GPU-based images using the `gpu` flag. The flag expects a comma separated list of integers.
```console ```console
# ctr-remote i optimize --oci --gpus "0" <src> <target> # ctr-remote i optimize --oci --gpus "0" <src> <target>
# ctr-remote i optimize --oci --gpus "all" <src> <target>
``` ```
`--oci` option is highly recommended to add when you create eStargz image. `--oci` option is highly recommended to add when you create eStargz image.
@ -269,38 +268,3 @@ ctr-remote image optimize --oci \
By default, when the source image is a multi-platform image, `ctr-remote` converts the image corresponding to the platform where `ctr-remote` runs. By default, when the source image is a multi-platform image, `ctr-remote` converts the image corresponding to the platform where `ctr-remote` runs.
Note that though the images specified by `--all-platform` and `--platform` are converted to eStargz, images that don't correspond to the current platform aren't *optimized*. That is, these images are lazily pulled but without prefetch. Note that though the images specified by `--all-platform` and `--platform` are converted to eStargz, images that don't correspond to the current platform aren't *optimized*. That is, these images are lazily pulled but without prefetch.
### Dump log of accessed files during optimization (`--record-out`)
You can dump the information of which files are accesssed during optimization, using `--record-out` flag.
For example, the following dumps logs of files accessed during running `ls` in `ubuntu:24.04`.
```
ctr-remote image pull docker.io/library/ubuntu:24.04
ctr-remote image optimize --record-out=/tmp/log.json \
--entrypoint='[ "/bin/bash", "-c" ]' --args='[ "ls" ]' \
docker.io/library/ubuntu:24.04 registry2:5000/ubuntu:24.04
```
The following is the contents of the log (`/tmp/log.json`):
```
{"path":"usr/bin/bash","manifestDigest":"sha256:5d070ad5f7fe63623cbb99b4fc0fd997f5591303d4b03ccce50f403957d0ddc4","layerIndex":0}
{"path":"usr/bin/bash","manifestDigest":"sha256:5d070ad5f7fe63623cbb99b4fc0fd997f5591303d4b03ccce50f403957d0ddc4","layerIndex":0}
{"path":"usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2","manifestDigest":"sha256:5d070ad5f7fe63623cbb99b4fc0fd997f5591303d4b03ccce50f403957d0ddc4","layerIndex":0}
{"path":"etc/ld.so.cache","manifestDigest":"sha256:5d070ad5f7fe63623cbb99b4fc0fd997f5591303d4b03ccce50f403957d0ddc4","layerIndex":0}
{"path":"usr/lib/x86_64-linux-gnu/libtinfo.so.6.4","manifestDigest":"sha256:5d070ad5f7fe63623cbb99b4fc0fd997f5591303d4b03ccce50f403957d0ddc4","layerIndex":0}
{"path":"usr/lib/x86_64-linux-gnu/libc.so.6","manifestDigest":"sha256:5d070ad5f7fe63623cbb99b4fc0fd997f5591303d4b03ccce50f403957d0ddc4","layerIndex":0}
{"path":"etc/nsswitch.conf","manifestDigest":"sha256:5d070ad5f7fe63623cbb99b4fc0fd997f5591303d4b03ccce50f403957d0ddc4","layerIndex":0}
{"path":"etc/nsswitch.conf","manifestDigest":"sha256:5d070ad5f7fe63623cbb99b4fc0fd997f5591303d4b03ccce50f403957d0ddc4","layerIndex":0}
{"path":"etc/passwd","manifestDigest":"sha256:5d070ad5f7fe63623cbb99b4fc0fd997f5591303d4b03ccce50f403957d0ddc4","layerIndex":0}
{"path":"usr/bin/ls","manifestDigest":"sha256:5d070ad5f7fe63623cbb99b4fc0fd997f5591303d4b03ccce50f403957d0ddc4","layerIndex":0}
{"path":"usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2","manifestDigest":"sha256:5d070ad5f7fe63623cbb99b4fc0fd997f5591303d4b03ccce50f403957d0ddc4","layerIndex":0}
{"path":"etc/ld.so.cache","manifestDigest":"sha256:5d070ad5f7fe63623cbb99b4fc0fd997f5591303d4b03ccce50f403957d0ddc4","layerIndex":0}
{"path":"usr/lib/x86_64-linux-gnu/libselinux.so.1","manifestDigest":"sha256:5d070ad5f7fe63623cbb99b4fc0fd997f5591303d4b03ccce50f403957d0ddc4","layerIndex":0}
{"path":"usr/lib/x86_64-linux-gnu/libc.so.6","manifestDigest":"sha256:5d070ad5f7fe63623cbb99b4fc0fd997f5591303d4b03ccce50f403957d0ddc4","layerIndex":0}
{"path":"usr/lib/x86_64-linux-gnu/libpcre2-8.so.0.11.2","manifestDigest":"sha256:5d070ad5f7fe63623cbb99b4fc0fd997f5591303d4b03ccce50f403957d0ddc4","layerIndex":0}
```
For creating an optimized eStargz using this log, you can input this log into [`--estargz-record-in` or `--zstdchunked-record-in` of `nerdctl image convert`](https://github.com/containerd/nerdctl/blob/8b814ca7fe29cb505a02a3d85ba22860e63d15bf/docs/command-reference.md#nerd_face-nerdctl-image-convert) or the same flags for `ctr-remote image convert` .

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

View File

@ -1,52 +0,0 @@
# Getting started with Stargz Snapshotter on Lima
[Lima](https://github.com/lima-vm/lima) is a tool to manage Linux virtual machines on various hosts, including MacOS and Linux.
Lima can be used as an easy way to get started with Stargz Snapshotter as Lima provides a default VM image bundling [containerd](https://github.com/containerd/containerd), [nerdctl](https://github.com/containerd/nerdctl)(Docker-compatible CLI of containerd) and Stargz Snapshotter.
This document describes how to get started with Stargz Snapshotter on Lima.
## Enable Stargz Snapshotter using `--snapshotter=stargz` flag
nerdctl's `--snapshotter=stargz` flag enables stargz-snapshotter.
```
$ nerdctl.lima --snapshotter=stargz system info | grep stargz
Storage Driver: stargz
```
Using this flag, you can perform lazy pulling of a python eStargz image and run it.
```
$ nerdctl.lima --snapshotter=stargz run --rm -it --name python ghcr.io/stargz-containers/python:3.13-esgz
Python 3.13.2 (main, Feb 6 2025, 22:37:13) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
```
## Use Stargz Snapshotter as the default snapshotter
nerdctl recognizes an environment variable `CONTAINERD_SNAPSHOTTER` for the snapshotter to use.
You can add this environment variable to the VM by configuring Lima config as shown in the following:
```
$ cat <<EOF >> ~/.lima/_config/override.yaml
env:
CONTAINERD_SNAPSHOTTER: stargz
EOF
$ limactl stop
$ limactl start
$ nerdctl.lima system info | grep Storage
Storage Driver: stargz
```
> NOTE: `override.yaml` applies to all the instances of Lima
You can perform lazy pulling of eStargz using nerdctl, without any extra flags.
```
$ nerdctl.lima run --rm -it --name python ghcr.io/stargz-containers/python:3.13-esgz
Python 3.13.2 (main, Feb 6 2025, 22:37:13) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
```

View File

@ -1,22 +1,22 @@
# Containerd Stargz Snapshotter Plugin Overview # Containerd Stargz Snapshotter Plugin Overview
__Before reading this overview document, we recommend you read [README](../README.md).__ __Before get through this overview document, we recommend you to read [README](../README.md).__
Pulling images is one of the most time-consuming steps in the container startup process. Pulling image is one of the time-consuming steps in the container startup process.
In the containerd community, we have had a lot of discussions to address this issue at the following: In containerd community, we have had a lot of discussions to address this issue as the following,
- [#3731 Support remote snapshotter to speed up image pulling](https://github.com/containerd/containerd/issues/3731) - [#3731 Support remote snapshotter to speed up image pulling](https://github.com/containerd/containerd/issues/3731)
- [#2968 Support `Prepare` for existing snapshots in Snapshotter interface](https://github.com/containerd/containerd/issues/2968) - [#2968 Support `Prepare` for existing snapshots in Snapshotter interface](https://github.com/containerd/containerd/issues/2968)
- [#2943 remote filesystem snapshotter](https://github.com/containerd/containerd/issues/2943) - [#2943 remote filesystem snapshotter](https://github.com/containerd/containerd/issues/2943)
The solution for fast image distribution is called *Remote Snapshotter* plugin. The solution for the fast image distribution is called *Remote Snapshotter* plugin.
This prepares the container's rootfs layers by directly mounting from remote stores instead of downloading and unpacking the entire image contents. This prepares container's rootfs layers by directly mounting from remote stores instead of downloading and unpacking the entire image contents.
The actual image contents can be fetched *lazily* so runtimes can start containers before the entire image contents are locally available. The actual image contents can be fetched *lazily* so runtimes can startup containers before the entire image contents to be locally available.
We call these remotely mounted layers *remote snapshots*. We call these remotely mounted layers as *remote snapshots*.
*Stargz Snapshotter* is a remote snapshotter plugin implementation which supports standard compatible remote snapshots functionality. *Stargz Snapshotter* is a remote snapshotter plugin implementation which supports standard compatible remote snapshots functionality.
This snapshotter leverages [eStargz](/docs/stargz-estargz.md) image, which is lazily-pullable and still standard-compatible. This snapshotter leverages [eStargz](/docs/stargz-estargz.md) image, which is lazily-pullable and still standard-compatible.
Because of this compatibility, eStargz images can be pushed to and lazily pulled from [OCI](https://github.com/opencontainers/distribution-spec)/[Docker](https://docs.docker.com/registry/spec/api/) registries (e.g. ghcr.io). Because of this compatibility, eStargz image can be pushed to and lazily pulled from [OCI](https://github.com/opencontainers/distribution-spec)/[Docker](https://docs.docker.com/registry/spec/api/) registries (e.g. ghcr.io).
Furthermore, images can run even on eStargz-agnostic runtimes (e.g. Docker). Furthermore, images can run even on eStargz-agnostic runtimes (e.g. Docker).
When you run a container image and it is formatted by eStargz, stargz snapshotter prepares container's rootfs layers as remote snapshots by mounting layers from the registry to the node, instead of pulling the entire image contents. When you run a container image and it is formatted by eStargz, stargz snapshotter prepares container's rootfs layers as remote snapshots by mounting layers from the registry to the node, instead of pulling the entire image contents.
@ -27,10 +27,10 @@ This document gives you a high-level overview of stargz snapshotter.
## Stargz Snapshotter proxy plugin ## Stargz Snapshotter proxy plugin
Stargz snapshotter is implemented as a [proxy plugin](https://github.com/containerd/containerd/blob/04985039cede6aafbb7dfb3206c9c4d04e2f924d/PLUGINS.md#proxy-plugins) daemon (`containerd-stargz-grpc`) for containerd. Stargz snapshotter is implemented as a [proxy plugin](https://github.com/containerd/containerd/blob/04985039cede6aafbb7dfb3206c9c4d04e2f924d/PLUGINS.md#proxy-plugins) daemon (`containerd-stargz-grpc`) for containerd.
When containerd starts a container, it queries the rootfs snapshots to stargz snapshotter daemon through a unix socket. When containerd starts a container, it queries the rootfs snapshots to stargz snapshotter daemon through an unix socket.
This snapshotter remotely mounts queried eStargz layers from registries to the node and provides these mount points as remote snapshots to containerd. This snapshotter remotely mounts queried eStargz layers from registries to the node and provides these mount points as remote snapshots to containerd.
Containerd recognizes this plugin through a unix socket specified in the configuration file (e.g. `/etc/containerd/config.toml`). Containerd recognizes this plugin through an unix socket specified in the configuration file (e.g. `/etc/containerd/config.toml`).
Stargz snapshotter can also be used through Kubernetes CRI by specifying the snapshotter name in the CRI plugin configuration. Stargz snapshotter can also be used through Kubernetes CRI by specifying the snapshotter name in the CRI plugin configuration.
We assume that you are using containerd (> v1.4.2). We assume that you are using containerd (> v1.4.2).
@ -44,8 +44,6 @@ version = 2
[proxy_plugins.stargz] [proxy_plugins.stargz]
type = "snapshot" type = "snapshot"
address = "/run/containerd-stargz-grpc/containerd-stargz-grpc.sock" address = "/run/containerd-stargz-grpc/containerd-stargz-grpc.sock"
[proxy_plugins.stargz.exports]
root = "/var/lib/containerd-stargz-grpc/"
# Use stargz snapshotter through CRI # Use stargz snapshotter through CRI
[plugins."io.containerd.grpc.v1.cri".containerd] [plugins."io.containerd.grpc.v1.cri".containerd]
@ -53,26 +51,24 @@ version = 2
disable_snapshot_annotations = false disable_snapshot_annotations = false
``` ```
> NOTE: `root` field of `proxy_plugins` is needed for the CRI plugin to recognize stargz snapshotter's root directory.
This repo contains [a Dockerfile as a KinD node image](/Dockerfile) which includes the above configuration. This repo contains [a Dockerfile as a KinD node image](/Dockerfile) which includes the above configuration.
## State directory ## State directory
Stargz snapshotter mounts eStargz layers from registries to the node using FUSE. Stargz snapshotter mounts eStargz layers from registries to the node using FUSE.
Metadata for all files in the image are preserved on the container filesystem and the file contents are fetched from registries on demand. The all files metadata in the image are preserved on the filesystem and files contents are fetched from registries on demand.
At the root of the container filesystem, there is a *state directory* (`/.stargz-snapshotter`) for status monitoring for the filesystem. At the root of the filesystem, there is a *state directory* (`/.stargz-snapshotter`) for status monitoring for the filesystem.
This directory is hidden from `getdents(2)` so you can't see this with `ls -a /`. This directory is hidden from `getdents(2)` so you can't see this with `ls -a /`.
Instead, you can directly access the directory by specifying the path (`/.stargz-snapshotter`). Instead, you can directly access the directory by specifying the path (`/.stargz-snapshotter`).
The state directory contains JSON-formatted metadata files for each layer. State directory contains JSON-formatted metadata files for each layer.
In the following example, metadata JSON files for overlayed 7 layers are visible. In the following example, metadata JSON files for overlayed 7 layers are visible.
In each metadata JSON file, the following fields are contained: In each metadata JSON file, the following fields are contained,
- `digest` contains the layer digest. This is the same value as that in the image's manifest. - `digest` contains the layer digest. This is the same value as that in the image's manifest.
- `size` is the size bytes of the layer. - `size` is the size bytes of the layer.
- `fetchedSize` and `fetchedPercent` indicate how many bytes have been fetched for this layer. Stargz snapshotter aggressively downloads this layer in the background - unless configured otherwise - so these values gradually increase. When `fetchedPercent` reaches `100` percent, this layer has been fully downloaded on the node and no further access will occur for reading files. - `fetchedSize` and `fetchedPercent` indicate how many bytes have been fetched for this layer. Stargz snapshotter aggressively downloads this layer in the background - unless configured otherwise - so these values gradually increase. When `fetchedPercent` reaches to `100` percents, this layer has been fully downloaded on the node and no further access will occur for reading files.
Note that the state directory layout and the metadata JSON structure are subject to change. Note that the state directory layout and the metadata JSON structure are subject to change.
@ -99,59 +95,6 @@ root@1d43741b8d29:/go# cat /.stargz-snapshotter/*
{"digest":"sha256:f077511be7d385c17ba88980379c5cd0aab7068844dffa7a1cefbf68cc3daea3","size":580,"fetchedSize":580,"fetchedPercent":100} {"digest":"sha256:f077511be7d385c17ba88980379c5cd0aab7068844dffa7a1cefbf68cc3daea3","size":580,"fetchedSize":580,"fetchedPercent":100}
``` ```
## Fuse Manager
The fuse manager is designed to maintain the availability of running containers by managing the lifecycle of FUSE mountpoints independently from the stargz snapshotter.
### Fuse Manager Overview
Remote snapshots are mounted using FUSE, and its filesystem processes are attached to the stargz snapshotter. If the stargz snapshotter restarts (due to configuration changes or crashes), all filesystem processes will be killed and restarted, which causes the remount of FUSE mountpoints, making running containers unavailable.
To avoid this, we use a fuse daemon called the fuse manager to handle filesystem processes. The fuse manager is responsible for mounting and unmounting remote snapshotters. Its process is detached from the stargz snapshotter main process to an independent one in a shim-like way during the snapshotter's startup. This design ensures that the restart of the snapshotter won't affect the filesystem processes it manages, keeping mountpoints and running containers available during the restart. However, it is important to note that the restart of the fuse manager itself triggers a remount, so it is recommended to keep the fuse manager running in a good state.
You can enable the fuse manager by adding the following configuration.
```toml
[fusem_anager]
enable = true
```
## Killing and restarting Stargz Snapshotter
Stargz Snapshotter works as a FUSE server for the snapshots.
When you stop Stargz Sanpshotter on the node, it takes the following behaviour depending on the configuration.
### FUSE manager mode is disabled
killing containerd-stargz-grpc will result in unmounting all snapshot mounts managed by Stargz Snapshotter.
When containerd-stargz-grpc is restarted, all those snapshots are mounted again by lazy pulling all layers.
If the snapshotter fails to mount one of the snapshots (e.g. because of lazy pulling failure) during this step, the behaviour differs depending on `allow_invalid_mounts_on_restart` flag in the config TOML.
- `allow_invalid_mounts_on_restart = true`: containerd-stargz-grpc leaves the failed snapshots as empty directories. The user needs to manually remove those snapshot via containerd (e.g. using `ctr snapshot rm` command). The name of those snapshots can be seen in the log with `failed to restore remote snapshot` message.
- `allow_invalid_mounts_on_restart = false`: containerd-stargz-grpc doesn't start. The user needs to manually recover this (e.g. by wiping snapshotter and containerd state).
### FUSE manager mode is enabled
Killing containerd-stargz-grpc using non-SIGINT signal (e.g. using SIGTERM) doesn't affect the snapshot mounts because the FUSE manager process detached from containerd-stargz-grpc keeps on serving FUSE mounts to the kernel.
This is useful when you reload the updated config TOML to Stargz Snapshotter without unmounting existing snapshots.
FUSE manager serves FUSE mounts of the snapshots so if you kill this process, all snapshot mounts will be unavailable.
When stopping FUSE manager for upgrading the binary or restarting the node, you can use SIGINT signal to trigger the graceful exit as shown in the following steps.
1. Stop containers that use Stargz Snapshotter. Stopping FUSE manager makes all snapshot mounts unavailable so containers can't keep working.
2. Stop containerd-stargz-grpc process using SIGINT. This signal triggers unmounting of all snapshots and cleaning up of the associated resources.
3. Kill the FUSE manager process (`stargz-fuse-manager`)
4. Restart the containerd-stargz-grpc process. This restores all snapshot mounts by lazy pulling them. `allow_invalid_mounts_on_restart` (described in the above) can still be used for controlling the behaviour of the error cases.
5. Restart the containers.
### Unexpected restart handling
When Stargz Snapshotter is killed unexpectedly (e.g., by OOM killer or system crash), the process doesn't get a chance to perform graceful cleanup. In such cases, the snapshotter can successfully restart and restore remote snapshots, but this may lead to fscache duplicating cached data.
**Recommended handling:**
Since this scenario is caused by abnormal exit, users are expected to manually clean up the cache directory after an unexpected restart to avoid cache duplication issues. The cache cleanup should be performed before restarting the snapshotter service.
## Registry-related configuration ## Registry-related configuration
You can configure stargz snapshotter for accessing registries with custom configurations. You can configure stargz snapshotter for accessing registries with custom configurations.
@ -186,9 +129,6 @@ The snapshotter acquires registry creds by scanning requests.
You must specify `--image-service-endpoint=unix:///run/containerd-stargz-grpc/containerd-stargz-grpc.sock` option to kubelet. You must specify `--image-service-endpoint=unix:///run/containerd-stargz-grpc/containerd-stargz-grpc.sock` option to kubelet.
You can specify the backing image service's socket using `image_service_path`.
The default is the containerd's socket (`/run/containerd/containerd.sock`).
```toml ```toml
# Stargz Snapshotter proxies CRI Image Service into containerd socket. # Stargz Snapshotter proxies CRI Image Service into containerd socket.
[cri_keychain] [cri_keychain]
@ -196,13 +136,6 @@ enable_keychain = true
image_service_path = "/run/containerd/containerd.sock" image_service_path = "/run/containerd/containerd.sock"
``` ```
The default path where containerd-stargz-grpc serves the CRI Image Service API is `unix:///run/containerd-stargz-grpc/containerd-stargz-grpc.sock`.
You can also change this path using `listen_path` field.
> Note that if you enabled the FUSE manager and CRI-based authentication together, `listen_path` is a mandatory field with some caveats:
> - This path must be different from the FUSE manager's socket path (`/run/containerd-stargz-grpc/fuse-manager.sock`) because they have different lifecycle. Specifically, the CRI socket is recreted on each reload of the configuration to the FUSE manager.
> - containerd-stargz-grpc's socket path (`/run/containerd-stargz-grpc/containerd-stargz-grpc.sock`) can't be used as `listen_path` because the CRI socket is served by the FUSE manager process (not containerd-stargz-grpc process).
#### kubeconfig-based authentication #### kubeconfig-based authentication
This is another way to enable lazy pulling of private images on Kubernetes. This is another way to enable lazy pulling of private images on Kubernetes.

View File

@ -1,60 +0,0 @@
# Introduction
FUSE Passthrough has been introduced in the Linux kernel version 6.9 ([Linux Kernel Commit](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=6ce8b2ce0d7e3a621cdc9eb66d74436ca7d0e66e)). This feature has shown significant performance improvements, as detailed in the following articles:
[Phoronix Article on FUSE Passthrough](https://www.phoronix.com/news/FUSE-Passthrough-In-6.9-Next)<br>
FUSE Passthrough allows performing read and write (also via memory maps) on a backing file without incurring the overhead of roundtrips to userspace.
![passhthrough feature](/docs/images/passthrough01.png)
Additionally, the `go-fuse` package, which Stargz-Snapshotter depends on, has also added support for this passthrough feature:
[go-fuse Commit 1](https://github.com/hanwen/go-fuse/commit/e0641a46c6cca7e5370fc135f78caf7cb7fc3aa8#diff-f830ac3db25844bf71102b09e4e02f7213e9cdb577b32745979d61d775462bd3R157)<br>
[go-fuse Commit 2](https://github.com/hanwen/go-fuse/commit/e0a0b09ae8287249c38033a27fd69a3593c7e235#diff-1521152f1fc3600273bda897c669523dc1e9fc9cbe24046838f043a8040f0d67R749)<br>
[go-fuse Commit 3](https://github.com/hanwen/go-fuse/commit/1a7d98b0360f945fca50ac79905332b7106c049f)
When a user-defined file implements the `FilePassthroughFder` interface, `go-fuse` will attempt to register the file `fd` from the file with the kernel.
# Configuration
## Basic Configuration
To enable FUSE passthrough mode, first verify that your host's kernel supports this feature. You can check this by running the following command:
```bash
$ cat /boot/config-$(uname -r) | grep "CONFIG_FUSE_PASSTHROUGH=y"
CONFIG_FUSE_PASSTHROUGH=y
```
Once you have confirmed kernel support, you need to enable passthrough mode in your `config.toml` file with the following configuration:
```toml
[fuse]
passthrough = true
```
After updating the configuration, specify the `config.toml` file when starting `containerd-stargz-grpc` and restart the service:
```bash
$ containerd-stargz-grpc -config config.toml
```
## Advanced Configuration
In passthrough mode, the initial pull of an image requires merging chunks into a file. This process can be time-consuming, especially for large files.
To optimize the time taken for the initial image pull, you can use the `merge_buffer_size` and `merge_worker_count` configuration options. The `merge_buffer_size` specifies the size of the buffer used for reading the image, with a default value of 400MB. The `merge_worker_count` determines the level of concurrency for reading the image, with a default value of 10.
By concurrently reading chunks and caching them for batch writing, you can significantly enhance the performance of the initial image pull in passthrough mode.
# Important Considerations
When passthrough mode is enabled, the following configuration is applied by default, even if it is set to false in the configuration file:
```toml
[directory_cache]
direct = true
```
This is because, in passthrough mode, read operations after opening a file are handled directly by the kernel.

View File

@ -1,99 +0,0 @@
# Enabling Stargz Snapshotter With Transfer Service
Transfer Service is a containerd component which is used for image management in contianerd (e.g. pulling and pushing images).
For details about Transfer Service, refer to [the official document in the containerd repo](https://github.com/containerd/containerd/blob/6af7c07905a317d4c343a49255e2392f4c8569f9/docs/transfer.md).
To use Stargz Snapshotter on containerd with enabling Transfer Service, additional configurations is needed.
## Availability of Transfer Service
Transfer Service is available since v1.7.
And this is enabled in different settings depending on the containerd version.
|containerd version|`ctr`|CRI|
---|---|---
|containerd >= v1.7 and < v2.0|Disabled by default. Enabled by `--local=false`|Disabled|
|containerd >= v2.0 and < v2.1|Enabled by default. Disabled by `--local`|Disabled|
|containerd >= v2.1|Enabled by default. Disabled by `--local`|Enabled by default. Disabled when conditions described in [containerd's CRI document](https://github.com/containerd/containerd/blob/v2.1.0/docs/cri/config.md#image-pull-configuration-since-containerd-v21) are met|
### Note about containerd v2.1
Before containerd v2.1, `disable_snapshot_annotations = false` in containerd's config TOML was a mandatory field to enable Stargz Snapshotter in CRI.
In containerd v2.1, `disable_snapshot_annotations = false` field can still be used to enable Stargz Snapshotter and containerd disables Transfer Service when this field is detected.
If you want to enable Transfer Service, you need to remove `disable_snapshot_annotations = false` field and apply the configuration explaind in this document.
## How to enable Stargz Snapshotter when Transfer Service is enabled?
In containerd v2.1, Transfer Service added support for remote snapshotters like Stargz Snapshotter.
### For ctr and other non-CRI clients
To enable Stargz Snapshotter with Transfer Service, you need to start containerd-stargz-grpc on the node and add the following configuration to contianerd's config TOML file.
Note that you need to add a field `enable_remote_snapshot_annotations = "true"` in `proxy_plugins.stargz.exports` so that containerd can correctly pass image-related information to Stargz Snapshotter.
```toml
version = 2
# Enable Stargz Snapshotter in Transfer Service
[[plugins."io.containerd.transfer.v1.local".unpack_config]]
platform = "linux"
snapshotter = "stargz"
# Plugin Stargz Snapshotter
[proxy_plugins]
[proxy_plugins.stargz]
type = "snapshot"
address = "/run/containerd-stargz-grpc/containerd-stargz-grpc.sock"
[proxy_plugins.stargz.exports]
root = "/var/lib/containerd-stargz-grpc/"
enable_remote_snapshot_annotations = "true"
```
#### Example client command
When you enable Transfer Service with Stargz Snapshotter, you can perform lazy pulling using the normal `ctr` command. (of course, `ctr-remote` can still be used)
```
# ctr image pull --snapshotter=stargz ghcr.io/stargz-containers/ubuntu:24.04-esgz
```
Then `mount | grep stargz` prints stargz mounts on the node.
### For CRI
To enable Stargz Snapshotter with Transfer Service, you need to start containerd-stargz-grpc on the node and add the following configuration to contianerd's config TOML file.
```toml
version = 2
# Basic CRI configuration with enabling Stargz Snapshotter
[plugins."io.containerd.grpc.v1.cri".containerd]
default_runtime_name = "runc"
snapshotter = "stargz"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
runtime_type = "io.containerd.runc.v2"
# Enable Stargz Snapshotter in Transfer Service
[[plugins."io.containerd.transfer.v1.local".unpack_config]]
platform = "linux"
snapshotter = "stargz"
# Plugin Stargz Snapshotter
[proxy_plugins]
[proxy_plugins.stargz]
type = "snapshot"
address = "/run/containerd-stargz-grpc/containerd-stargz-grpc.sock"
[proxy_plugins.stargz.exports]
root = "/var/lib/containerd-stargz-grpc/"
enable_remote_snapshot_annotations = "true"
```
#### Example client command
You can quickly check the behaviour using `crictl` command.
```
# crictl image pull ghcr.io/stargz-containers/ubuntu:24.04-esgz
```
Then `mount | grep stargz` prints stargz mounts on the node.

View File

@ -408,11 +408,11 @@ func readerFromEntries(entries ...*entry) io.Reader {
defer tw.Close() defer tw.Close()
for _, entry := range entries { for _, entry := range entries {
if err := tw.WriteHeader(entry.header); err != nil { if err := tw.WriteHeader(entry.header); err != nil {
pw.CloseWithError(fmt.Errorf("failed to write tar header: %v", err)) pw.CloseWithError(fmt.Errorf("Failed to write tar header: %v", err))
return return
} }
if _, err := io.Copy(tw, entry.payload); err != nil { if _, err := io.Copy(tw, entry.payload); err != nil {
pw.CloseWithError(fmt.Errorf("failed to write tar payload: %v", err)) pw.CloseWithError(fmt.Errorf("Failed to write tar payload: %v", err))
return return
} }
} }
@ -436,8 +436,9 @@ func importTar(in io.ReaderAt) (*tarFile, error) {
if err != nil { if err != nil {
if err == io.EOF { if err == io.EOF {
break break
} else {
return nil, fmt.Errorf("failed to parse tar file, %w", err)
} }
return nil, fmt.Errorf("failed to parse tar file, %w", err)
} }
switch cleanEntryName(h.Name) { switch cleanEntryName(h.Name) {
case PrefetchLandmark, NoPrefetchLandmark: case PrefetchLandmark, NoPrefetchLandmark:
@ -627,12 +628,12 @@ func (cr *countReadSeeker) Seek(offset int64, whence int) (int64, error) {
switch whence { switch whence {
default: default:
return 0, fmt.Errorf("unknown whence: %v", whence) return 0, fmt.Errorf("Unknown whence: %v", whence)
case io.SeekStart: case io.SeekStart:
case io.SeekCurrent: case io.SeekCurrent:
offset += *cr.cPos offset += *cr.cPos
case io.SeekEnd: case io.SeekEnd:
return 0, fmt.Errorf("unsupported whence: %v", whence) return 0, fmt.Errorf("Unsupported whence: %v", whence)
} }
if offset < 0 { if offset < 0 {

View File

@ -412,8 +412,9 @@ func TestSort(t *testing.T) {
if err != nil { if err != nil {
if err == io.EOF { if err == io.EOF {
break break
} else {
t.Fatalf("Failed to parse tar file: %v", err)
} }
t.Fatalf("Failed to parse tar file: %v", err)
} }
if !reflect.DeepEqual(gotH, wantH) { if !reflect.DeepEqual(gotH, wantH) {

View File

@ -81,7 +81,7 @@ func TestChunkEntryForOffset(t *testing.T) {
if ok != te.wantOk { if ok != te.wantOk {
t.Errorf("ok = %v; want (%v)", ok, te.wantOk) t.Errorf("ok = %v; want (%v)", ok, te.wantOk)
} else if ok { } else if ok {
if ce.ChunkOffset != te.wantChunkOffset || ce.ChunkSize != te.wantChunkSize { if !(ce.ChunkOffset == te.wantChunkOffset && ce.ChunkSize == te.wantChunkSize) {
t.Errorf("chunkOffset = %d, ChunkSize = %d; want (chunkOffset = %d, chunkSize = %d)", t.Errorf("chunkOffset = %d, ChunkSize = %d; want (chunkOffset = %d, chunkSize = %d)",
ce.ChunkOffset, ce.ChunkSize, te.wantChunkOffset, te.wantChunkSize) ce.ChunkOffset, ce.ChunkSize, te.wantChunkOffset, te.wantChunkSize)
} }

View File

@ -135,7 +135,7 @@ func gzipFooterBytes() ([]byte, error) {
header[0], header[1] = 'S', 'G' header[0], header[1] = 'S', 'G'
subfield := "STARGZEXTERNALTOC" // len("STARGZEXTERNALTOC") = 17 subfield := "STARGZEXTERNALTOC" // len("STARGZEXTERNALTOC") = 17
binary.LittleEndian.PutUint16(header[2:4], uint16(len(subfield))) // little-endian per RFC1952 binary.LittleEndian.PutUint16(header[2:4], uint16(len(subfield))) // little-endian per RFC1952
gz.Extra = append(header, []byte(subfield)...) gz.Header.Extra = append(header, []byte(subfield)...)
if err := gz.Close(); err != nil { if err := gz.Close(); err != nil {
return nil, err return nil, err
} }
@ -204,7 +204,7 @@ func (gz *GzipDecompressor) ParseFooter(p []byte) (blobPayloadSize, tocOffset, t
return 0, 0, 0, err return 0, 0, 0, err
} }
defer zr.Close() defer zr.Close()
extra := zr.Extra extra := zr.Header.Extra
si1, si2, subfieldlen, subfield := extra[0], extra[1], extra[2:4], extra[4:] si1, si2, subfieldlen, subfield := extra[0], extra[1], extra[2:4], extra[4:]
if si1 != 'S' || si2 != 'G' { if si1 != 'S' || si2 != 'G' {
return 0, 0, 0, fmt.Errorf("invalid subfield IDs: %q, %q; want E, S", si1, si2) return 0, 0, 0, fmt.Errorf("invalid subfield IDs: %q, %q; want E, S", si1, si2)

View File

@ -27,22 +27,7 @@ import (
// TestGzipEStargz tests gzip-based external TOC eStargz // TestGzipEStargz tests gzip-based external TOC eStargz
func TestGzipEStargz(t *testing.T) { func TestGzipEStargz(t *testing.T) {
testRunner := &estargz.TestRunner{ estargz.CompressionTestSuite(t,
TestingT: t,
Runner: func(testingT estargz.TestingT, name string, run func(t estargz.TestingT)) {
tt, ok := testingT.(*testing.T)
if !ok {
testingT.Fatal("TestingT is not a *testing.T")
return
}
tt.Run(name, func(t *testing.T) {
run(t)
})
},
}
estargz.CompressionTestSuite(testRunner,
gzipControllerWithLevel(gzip.NoCompression), gzipControllerWithLevel(gzip.NoCompression),
gzipControllerWithLevel(gzip.BestSpeed), gzipControllerWithLevel(gzip.BestSpeed),
gzipControllerWithLevel(gzip.BestCompression), gzipControllerWithLevel(gzip.BestCompression),
@ -71,15 +56,15 @@ type gzipController struct {
} }
func (gc *gzipController) String() string { func (gc *gzipController) String() string {
return fmt.Sprintf("externaltoc_gzip_compression_level=%v", gc.compressionLevel) return fmt.Sprintf("externaltoc_gzip_compression_level=%v", gc.GzipCompressor.compressionLevel)
} }
// TestStream tests the passed estargz blob contains the specified list of streams. // TestStream tests the passed estargz blob contains the specified list of streams.
func (gc *gzipController) TestStreams(t estargz.TestingT, b []byte, streams []int64) { func (gc *gzipController) TestStreams(t *testing.T, b []byte, streams []int64) {
estargz.CheckGzipHasStreams(t, b, streams) estargz.CheckGzipHasStreams(t, b, streams)
} }
func (gc *gzipController) DiffIDOf(t estargz.TestingT, b []byte) string { func (gc *gzipController) DiffIDOf(t *testing.T, b []byte) string {
return estargz.GzipDiffIDOf(t, b) return estargz.GzipDiffIDOf(t, b)
} }

View File

@ -1,10 +1,10 @@
module github.com/containerd/stargz-snapshotter/estargz module github.com/containerd/stargz-snapshotter/estargz
go 1.23.0 go 1.19
require ( require (
github.com/klauspost/compress v1.18.0 github.com/klauspost/compress v1.16.0
github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/go-digest v1.0.0
github.com/vbatts/tar-split v0.12.1 github.com/vbatts/tar-split v0.11.2
golang.org/x/sync v0.16.0 golang.org/x/sync v0.1.0
) )

View File

@ -1,8 +1,20 @@
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4=
github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME=
github.com/vbatts/tar-split v0.11.2/go.mod h1:vV3ZuO2yWSVsz+pfFzDG/upWH1JhjOiEaWq6kXyQ3VI=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -109,7 +109,7 @@ func gzipFooterBytes(tocOff int64) []byte {
header[0], header[1] = 'S', 'G' header[0], header[1] = 'S', 'G'
subfield := fmt.Sprintf("%016xSTARGZ", tocOff) subfield := fmt.Sprintf("%016xSTARGZ", tocOff)
binary.LittleEndian.PutUint16(header[2:4], uint16(len(subfield))) // little-endian per RFC1952 binary.LittleEndian.PutUint16(header[2:4], uint16(len(subfield))) // little-endian per RFC1952
gz.Extra = append(header, []byte(subfield)...) gz.Header.Extra = append(header, []byte(subfield)...)
gz.Close() gz.Close()
if buf.Len() != FooterSize { if buf.Len() != FooterSize {
panic(fmt.Sprintf("footer buffer = %d, not %d", buf.Len(), FooterSize)) panic(fmt.Sprintf("footer buffer = %d, not %d", buf.Len(), FooterSize))
@ -136,7 +136,7 @@ func (gz *GzipDecompressor) ParseFooter(p []byte) (blobPayloadSize, tocOffset, t
return 0, 0, 0, err return 0, 0, 0, err
} }
defer zr.Close() defer zr.Close()
extra := zr.Extra extra := zr.Header.Extra
si1, si2, subfieldlen, subfield := extra[0], extra[1], extra[2:4], extra[4:] si1, si2, subfieldlen, subfield := extra[0], extra[1], extra[2:4], extra[4:]
if si1 != 'S' || si2 != 'G' { if si1 != 'S' || si2 != 'G' {
return 0, 0, 0, fmt.Errorf("invalid subfield IDs: %q, %q; want E, S", si1, si2) return 0, 0, 0, fmt.Errorf("invalid subfield IDs: %q, %q; want E, S", si1, si2)
@ -181,7 +181,7 @@ func (gz *LegacyGzipDecompressor) ParseFooter(p []byte) (blobPayloadSize, tocOff
return 0, 0, 0, fmt.Errorf("legacy: failed to get footer gzip reader: %w", err) return 0, 0, 0, fmt.Errorf("legacy: failed to get footer gzip reader: %w", err)
} }
defer zr.Close() defer zr.Close()
extra := zr.Extra extra := zr.Header.Extra
if len(extra) != 16+len("STARGZ") { if len(extra) != 16+len("STARGZ") {
return 0, 0, 0, fmt.Errorf("legacy: invalid stargz's extra field size") return 0, 0, 0, fmt.Errorf("legacy: invalid stargz's extra field size")
} }

View File

@ -31,22 +31,7 @@ import (
// TestGzipEStargz tests gzip-based eStargz // TestGzipEStargz tests gzip-based eStargz
func TestGzipEStargz(t *testing.T) { func TestGzipEStargz(t *testing.T) {
testRunner := &TestRunner{ CompressionTestSuite(t,
TestingT: t,
Runner: func(testingT TestingT, name string, run func(t TestingT)) {
tt, ok := testingT.(*testing.T)
if !ok {
testingT.Fatal("TestingT is not a *testing.T")
return
}
tt.Run(name, func(t *testing.T) {
run(t)
})
},
}
CompressionTestSuite(testRunner,
gzipControllerWithLevel(gzip.NoCompression), gzipControllerWithLevel(gzip.NoCompression),
gzipControllerWithLevel(gzip.BestSpeed), gzipControllerWithLevel(gzip.BestSpeed),
gzipControllerWithLevel(gzip.BestCompression), gzipControllerWithLevel(gzip.BestCompression),
@ -67,15 +52,15 @@ type gzipController struct {
} }
func (gc *gzipController) String() string { func (gc *gzipController) String() string {
return fmt.Sprintf("gzip_compression_level=%v", gc.compressionLevel) return fmt.Sprintf("gzip_compression_level=%v", gc.GzipCompressor.compressionLevel)
} }
// TestStream tests the passed estargz blob contains the specified list of streams. // TestStream tests the passed estargz blob contains the specified list of streams.
func (gc *gzipController) TestStreams(t TestingT, b []byte, streams []int64) { func (gc *gzipController) TestStreams(t *testing.T, b []byte, streams []int64) {
CheckGzipHasStreams(t, b, streams) CheckGzipHasStreams(t, b, streams)
} }
func (gc *gzipController) DiffIDOf(t TestingT, b []byte) string { func (gc *gzipController) DiffIDOf(t *testing.T, b []byte) string {
return GzipDiffIDOf(t, b) return GzipDiffIDOf(t, b)
} }
@ -121,7 +106,7 @@ func checkLegacyFooter(t *testing.T, off int64) {
func legacyFooterBytes(tocOff int64) []byte { func legacyFooterBytes(tocOff int64) []byte {
buf := bytes.NewBuffer(make([]byte, 0, legacyFooterSize)) buf := bytes.NewBuffer(make([]byte, 0, legacyFooterSize))
gz, _ := gzip.NewWriterLevel(buf, gzip.NoCompression) gz, _ := gzip.NewWriterLevel(buf, gzip.NoCompression)
gz.Extra = []byte(fmt.Sprintf("%016xSTARGZ", tocOff)) gz.Header.Extra = []byte(fmt.Sprintf("%016xSTARGZ", tocOff))
gz.Close() gz.Close()
if buf.Len() != legacyFooterSize { if buf.Len() != legacyFooterSize {
panic(fmt.Sprintf("footer buffer = %d, not %d", buf.Len(), legacyFooterSize)) panic(fmt.Sprintf("footer buffer = %d, not %d", buf.Len(), legacyFooterSize))

View File

@ -26,18 +26,18 @@ import (
"archive/tar" "archive/tar"
"bytes" "bytes"
"compress/gzip" "compress/gzip"
"crypto/rand"
"crypto/sha256" "crypto/sha256"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"math/big" "math/rand"
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
"sort" "sort"
"strings" "strings"
"testing"
"time" "time"
"github.com/containerd/stargz-snapshotter/estargz/errorutil" "github.com/containerd/stargz-snapshotter/estargz/errorutil"
@ -45,51 +45,23 @@ import (
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
) )
func init() {
rand.Seed(time.Now().UnixNano())
}
// TestingController is Compression with some helper methods necessary for testing. // TestingController is Compression with some helper methods necessary for testing.
type TestingController interface { type TestingController interface {
Compression Compression
TestStreams(t TestingT, b []byte, streams []int64) TestStreams(t *testing.T, b []byte, streams []int64)
DiffIDOf(TestingT, []byte) string DiffIDOf(*testing.T, []byte) string
String() string String() string
} }
// TestingT is the minimal set of testing.T required to run the
// tests defined in CompressionTestSuite. This interface exists to prevent
// leaking the testing package from being exposed outside tests.
type TestingT interface {
Errorf(format string, args ...any)
FailNow()
Failed() bool
Fatal(args ...any)
Fatalf(format string, args ...any)
Logf(format string, args ...any)
Parallel()
}
// Runner allows running subtests of TestingT. This exists instead of adding
// a Run method to TestingT interface because the Run implementation of
// testing.T would not satisfy the interface.
type Runner func(t TestingT, name string, fn func(t TestingT))
type TestRunner struct {
TestingT
Runner Runner
}
func (r *TestRunner) Run(name string, run func(*TestRunner)) {
r.Runner(r.TestingT, name, func(t TestingT) {
run(&TestRunner{TestingT: t, Runner: r.Runner})
})
}
// CompressionTestSuite tests this pkg with controllers can build valid eStargz blobs and parse them. // CompressionTestSuite tests this pkg with controllers can build valid eStargz blobs and parse them.
func CompressionTestSuite(t *TestRunner, controllers ...TestingControllerFactory) { func CompressionTestSuite(t *testing.T, controllers ...TestingControllerFactory) {
t.Run("testBuild", func(t *TestRunner) { t.Parallel(); testBuild(t, controllers...) }) t.Run("testBuild", func(t *testing.T) { t.Parallel(); testBuild(t, controllers...) })
t.Run("testDigestAndVerify", func(t *TestRunner) { t.Run("testDigestAndVerify", func(t *testing.T) { t.Parallel(); testDigestAndVerify(t, controllers...) })
t.Parallel() t.Run("testWriteAndOpen", func(t *testing.T) { t.Parallel(); testWriteAndOpen(t, controllers...) })
testDigestAndVerify(t, controllers...)
})
t.Run("testWriteAndOpen", func(t *TestRunner) { t.Parallel(); testWriteAndOpen(t, controllers...) })
} }
type TestingControllerFactory func() TestingController type TestingControllerFactory func() TestingController
@ -110,7 +82,7 @@ var allowedPrefix = [4]string{"", "./", "/", "../"}
// testBuild tests the resulting stargz blob built by this pkg has the same // testBuild tests the resulting stargz blob built by this pkg has the same
// contents as the normal stargz blob. // contents as the normal stargz blob.
func testBuild(t *TestRunner, controllers ...TestingControllerFactory) { func testBuild(t *testing.T, controllers ...TestingControllerFactory) {
tests := []struct { tests := []struct {
name string name string
chunkSize int chunkSize int
@ -196,7 +168,7 @@ func testBuild(t *TestRunner, controllers ...TestingControllerFactory) {
prefix := prefix prefix := prefix
for _, minChunkSize := range tt.minChunkSize { for _, minChunkSize := range tt.minChunkSize {
minChunkSize := minChunkSize minChunkSize := minChunkSize
t.Run(tt.name+"-"+fmt.Sprintf("compression=%v,prefix=%q,src=%d,format=%s,minChunkSize=%d", newCL(), prefix, srcCompression, srcTarFormat, minChunkSize), func(t *TestRunner) { t.Run(tt.name+"-"+fmt.Sprintf("compression=%v,prefix=%q,src=%d,format=%s,minChunkSize=%d", newCL(), prefix, srcCompression, srcTarFormat, minChunkSize), func(t *testing.T) {
tarBlob := buildTar(t, tt.in, prefix, srcTarFormat) tarBlob := buildTar(t, tt.in, prefix, srcTarFormat)
// Test divideEntries() // Test divideEntries()
entries, err := sortEntries(tarBlob, nil, nil) // identical order entries, err := sortEntries(tarBlob, nil, nil) // identical order
@ -296,7 +268,7 @@ func testBuild(t *TestRunner, controllers ...TestingControllerFactory) {
} }
} }
func isSameTarGz(t TestingT, cla TestingController, a []byte, clb TestingController, b []byte) bool { func isSameTarGz(t *testing.T, cla TestingController, a []byte, clb TestingController, b []byte) bool {
aGz, err := cla.Reader(bytes.NewReader(a)) aGz, err := cla.Reader(bytes.NewReader(a))
if err != nil { if err != nil {
t.Fatalf("failed to read A") t.Fatalf("failed to read A")
@ -356,7 +328,7 @@ func isSameTarGz(t TestingT, cla TestingController, a []byte, clb TestingControl
return true return true
} }
func isSameVersion(t TestingT, cla TestingController, a []byte, clb TestingController, b []byte) bool { func isSameVersion(t *testing.T, cla TestingController, a []byte, clb TestingController, b []byte) bool {
aJTOC, _, err := parseStargz(io.NewSectionReader(bytes.NewReader(a), 0, int64(len(a))), cla) aJTOC, _, err := parseStargz(io.NewSectionReader(bytes.NewReader(a), 0, int64(len(a))), cla)
if err != nil { if err != nil {
t.Fatalf("failed to parse A: %v", err) t.Fatalf("failed to parse A: %v", err)
@ -370,7 +342,7 @@ func isSameVersion(t TestingT, cla TestingController, a []byte, clb TestingContr
return aJTOC.Version == bJTOC.Version return aJTOC.Version == bJTOC.Version
} }
func isSameEntries(t TestingT, a, b *Reader) bool { func isSameEntries(t *testing.T, a, b *Reader) bool {
aroot, ok := a.Lookup("") aroot, ok := a.Lookup("")
if !ok { if !ok {
t.Fatalf("failed to get root of A") t.Fatalf("failed to get root of A")
@ -384,19 +356,18 @@ func isSameEntries(t TestingT, a, b *Reader) bool {
return contains(t, aEntry, bEntry) && contains(t, bEntry, aEntry) return contains(t, aEntry, bEntry) && contains(t, bEntry, aEntry)
} }
func compressBlob(t TestingT, src *io.SectionReader, srcCompression int) *io.SectionReader { func compressBlob(t *testing.T, src *io.SectionReader, srcCompression int) *io.SectionReader {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
var w io.WriteCloser var w io.WriteCloser
var err error var err error
switch srcCompression { if srcCompression == gzipType {
case gzipType:
w = gzip.NewWriter(buf) w = gzip.NewWriter(buf)
case zstdType: } else if srcCompression == zstdType {
w, err = zstd.NewWriter(buf) w, err = zstd.NewWriter(buf)
if err != nil { if err != nil {
t.Fatalf("failed to init zstd writer: %v", err) t.Fatalf("failed to init zstd writer: %v", err)
} }
default: } else {
return src return src
} }
src.Seek(0, io.SeekStart) src.Seek(0, io.SeekStart)
@ -418,7 +389,7 @@ type stargzEntry struct {
// contains checks if all child entries in "b" are also contained in "a". // contains checks if all child entries in "b" are also contained in "a".
// This function also checks if the files/chunks contain the same contents among "a" and "b". // This function also checks if the files/chunks contain the same contents among "a" and "b".
func contains(t TestingT, a, b stargzEntry) bool { func contains(t *testing.T, a, b stargzEntry) bool {
ae, ar := a.e, a.r ae, ar := a.e, a.r
be, br := b.e, b.r be, br := b.e, b.r
t.Logf("Comparing: %q vs %q", ae.Name, be.Name) t.Logf("Comparing: %q vs %q", ae.Name, be.Name)
@ -477,7 +448,7 @@ func contains(t TestingT, a, b stargzEntry) bool {
bbytes, bnext, bok := readOffset(t, bf, nr, b) bbytes, bnext, bok := readOffset(t, bf, nr, b)
if !aok && !bok { if !aok && !bok {
break break
} else if !aok || !bok || anext != bnext { } else if !(aok && bok) || anext != bnext {
t.Logf("%q != %q (offset=%d): chunk existence a=%v vs b=%v, anext=%v vs bnext=%v", t.Logf("%q != %q (offset=%d): chunk existence a=%v vs b=%v, anext=%v vs bnext=%v",
ae.Name, be.Name, nr, aok, bok, anext, bnext) ae.Name, be.Name, nr, aok, bok, anext, bnext)
return false return false
@ -529,7 +500,7 @@ func equalEntry(a, b *TOCEntry) bool {
a.Digest == b.Digest a.Digest == b.Digest
} }
func readOffset(t TestingT, r *io.SectionReader, offset int64, e stargzEntry) ([]byte, int64, bool) { func readOffset(t *testing.T, r *io.SectionReader, offset int64, e stargzEntry) ([]byte, int64, bool) {
ce, ok := e.r.ChunkEntryForOffset(e.e.Name, offset) ce, ok := e.r.ChunkEntryForOffset(e.e.Name, offset)
if !ok { if !ok {
return nil, 0, false return nil, 0, false
@ -548,7 +519,7 @@ func readOffset(t TestingT, r *io.SectionReader, offset int64, e stargzEntry) ([
return data[:n], offset + ce.ChunkSize, true return data[:n], offset + ce.ChunkSize, true
} }
func dumpTOCJSON(t TestingT, tocJSON *JTOC) string { func dumpTOCJSON(t *testing.T, tocJSON *JTOC) string {
jtocData, err := json.Marshal(*tocJSON) jtocData, err := json.Marshal(*tocJSON)
if err != nil { if err != nil {
t.Fatalf("failed to marshal TOC JSON: %v", err) t.Fatalf("failed to marshal TOC JSON: %v", err)
@ -562,19 +533,20 @@ func dumpTOCJSON(t TestingT, tocJSON *JTOC) string {
const chunkSize = 3 const chunkSize = 3
type check func(t *TestRunner, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) // type check func(t *testing.T, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, compressionLevel int)
type check func(t *testing.T, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory)
// testDigestAndVerify runs specified checks against sample stargz blobs. // testDigestAndVerify runs specified checks against sample stargz blobs.
func testDigestAndVerify(t *TestRunner, controllers ...TestingControllerFactory) { func testDigestAndVerify(t *testing.T, controllers ...TestingControllerFactory) {
tests := []struct { tests := []struct {
name string name string
tarInit func(t TestingT, dgstMap map[string]digest.Digest) (blob []tarEntry) tarInit func(t *testing.T, dgstMap map[string]digest.Digest) (blob []tarEntry)
checks []check checks []check
minChunkSize []int minChunkSize []int
}{ }{
{ {
name: "no-regfile", name: "no-regfile",
tarInit: func(t TestingT, dgstMap map[string]digest.Digest) (blob []tarEntry) { tarInit: func(t *testing.T, dgstMap map[string]digest.Digest) (blob []tarEntry) {
return tarOf( return tarOf(
dir("test/"), dir("test/"),
) )
@ -589,7 +561,7 @@ func testDigestAndVerify(t *TestRunner, controllers ...TestingControllerFactory)
}, },
{ {
name: "small-files", name: "small-files",
tarInit: func(t TestingT, dgstMap map[string]digest.Digest) (blob []tarEntry) { tarInit: func(t *testing.T, dgstMap map[string]digest.Digest) (blob []tarEntry) {
return tarOf( return tarOf(
regDigest(t, "baz.txt", "", dgstMap), regDigest(t, "baz.txt", "", dgstMap),
regDigest(t, "foo.txt", "a", dgstMap), regDigest(t, "foo.txt", "a", dgstMap),
@ -613,7 +585,7 @@ func testDigestAndVerify(t *TestRunner, controllers ...TestingControllerFactory)
}, },
{ {
name: "big-files", name: "big-files",
tarInit: func(t TestingT, dgstMap map[string]digest.Digest) (blob []tarEntry) { tarInit: func(t *testing.T, dgstMap map[string]digest.Digest) (blob []tarEntry) {
return tarOf( return tarOf(
regDigest(t, "baz.txt", "bazbazbazbazbazbazbaz", dgstMap), regDigest(t, "baz.txt", "bazbazbazbazbazbazbaz", dgstMap),
regDigest(t, "foo.txt", "a", dgstMap), regDigest(t, "foo.txt", "a", dgstMap),
@ -637,7 +609,7 @@ func testDigestAndVerify(t *TestRunner, controllers ...TestingControllerFactory)
{ {
name: "with-non-regfiles", name: "with-non-regfiles",
minChunkSize: []int{0, 64000}, minChunkSize: []int{0, 64000},
tarInit: func(t TestingT, dgstMap map[string]digest.Digest) (blob []tarEntry) { tarInit: func(t *testing.T, dgstMap map[string]digest.Digest) (blob []tarEntry) {
return tarOf( return tarOf(
regDigest(t, "baz.txt", "bazbazbazbazbazbazbaz", dgstMap), regDigest(t, "baz.txt", "bazbazbazbazbazbazbaz", dgstMap),
regDigest(t, "foo.txt", "a", dgstMap), regDigest(t, "foo.txt", "a", dgstMap),
@ -684,7 +656,7 @@ func testDigestAndVerify(t *TestRunner, controllers ...TestingControllerFactory)
srcTarFormat := srcTarFormat srcTarFormat := srcTarFormat
for _, minChunkSize := range tt.minChunkSize { for _, minChunkSize := range tt.minChunkSize {
minChunkSize := minChunkSize minChunkSize := minChunkSize
t.Run(tt.name+"-"+fmt.Sprintf("compression=%v,prefix=%q,format=%s,minChunkSize=%d", newCL(), prefix, srcTarFormat, minChunkSize), func(t *TestRunner) { t.Run(tt.name+"-"+fmt.Sprintf("compression=%v,prefix=%q,format=%s,minChunkSize=%d", newCL(), prefix, srcTarFormat, minChunkSize), func(t *testing.T) {
// Get original tar file and chunk digests // Get original tar file and chunk digests
dgstMap := make(map[string]digest.Digest) dgstMap := make(map[string]digest.Digest)
tarBlob := buildTar(t, tt.tarInit(t, dgstMap), prefix, srcTarFormat) tarBlob := buildTar(t, tt.tarInit(t, dgstMap), prefix, srcTarFormat)
@ -720,7 +692,7 @@ func testDigestAndVerify(t *TestRunner, controllers ...TestingControllerFactory)
// checkStargzTOC checks the TOC JSON of the passed stargz has the expected // checkStargzTOC checks the TOC JSON of the passed stargz has the expected
// digest and contains valid chunks. It walks all entries in the stargz and // digest and contains valid chunks. It walks all entries in the stargz and
// checks all chunk digests stored to the TOC JSON match the actual contents. // checks all chunk digests stored to the TOC JSON match the actual contents.
func checkStargzTOC(t *TestRunner, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) { func checkStargzTOC(t *testing.T, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) {
sgz, err := Open( sgz, err := Open(
io.NewSectionReader(bytes.NewReader(sgzData), 0, int64(len(sgzData))), io.NewSectionReader(bytes.NewReader(sgzData), 0, int64(len(sgzData))),
WithDecompressors(controller), WithDecompressors(controller),
@ -831,7 +803,7 @@ func checkStargzTOC(t *TestRunner, sgzData []byte, tocDigest digest.Digest, dgst
// checkVerifyTOC checks the verification works for the TOC JSON of the passed // checkVerifyTOC checks the verification works for the TOC JSON of the passed
// stargz. It walks all entries in the stargz and checks the verifications for // stargz. It walks all entries in the stargz and checks the verifications for
// all chunks work. // all chunks work.
func checkVerifyTOC(t *TestRunner, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) { func checkVerifyTOC(t *testing.T, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) {
sgz, err := Open( sgz, err := Open(
io.NewSectionReader(bytes.NewReader(sgzData), 0, int64(len(sgzData))), io.NewSectionReader(bytes.NewReader(sgzData), 0, int64(len(sgzData))),
WithDecompressors(controller), WithDecompressors(controller),
@ -912,9 +884,9 @@ func checkVerifyTOC(t *TestRunner, sgzData []byte, tocDigest digest.Digest, dgst
// checkVerifyInvalidTOCEntryFail checks if misconfigured TOC JSON can be // checkVerifyInvalidTOCEntryFail checks if misconfigured TOC JSON can be
// detected during the verification and the verification returns an error. // detected during the verification and the verification returns an error.
func checkVerifyInvalidTOCEntryFail(filename string) check { func checkVerifyInvalidTOCEntryFail(filename string) check {
return func(t *TestRunner, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) { return func(t *testing.T, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) {
funcs := map[string]rewriteFunc{ funcs := map[string]rewriteFunc{
"lost digest in a entry": func(t TestingT, toc *JTOC, sgz *io.SectionReader) { "lost digest in a entry": func(t *testing.T, toc *JTOC, sgz *io.SectionReader) {
var found bool var found bool
for _, e := range toc.Entries { for _, e := range toc.Entries {
if cleanEntryName(e.Name) == filename { if cleanEntryName(e.Name) == filename {
@ -932,7 +904,7 @@ func checkVerifyInvalidTOCEntryFail(filename string) check {
t.Fatalf("rewrite target not found") t.Fatalf("rewrite target not found")
} }
}, },
"duplicated entry offset": func(t TestingT, toc *JTOC, sgz *io.SectionReader) { "duplicated entry offset": func(t *testing.T, toc *JTOC, sgz *io.SectionReader) {
var ( var (
sampleEntry *TOCEntry sampleEntry *TOCEntry
targetEntry *TOCEntry targetEntry *TOCEntry
@ -948,18 +920,16 @@ func checkVerifyInvalidTOCEntryFail(filename string) check {
} }
if sampleEntry == nil { if sampleEntry == nil {
t.Fatalf("TOC must contain at least one regfile or chunk entry other than the rewrite target") t.Fatalf("TOC must contain at least one regfile or chunk entry other than the rewrite target")
return
} }
if targetEntry == nil { if targetEntry == nil {
t.Fatalf("rewrite target not found") t.Fatalf("rewrite target not found")
return
} }
targetEntry.Offset = sampleEntry.Offset targetEntry.Offset = sampleEntry.Offset
}, },
} }
for name, rFunc := range funcs { for name, rFunc := range funcs {
t.Run(name, func(t *TestRunner) { t.Run(name, func(t *testing.T) {
newSgz, newTocDigest := rewriteTOCJSON(t, io.NewSectionReader(bytes.NewReader(sgzData), 0, int64(len(sgzData))), rFunc, controller) newSgz, newTocDigest := rewriteTOCJSON(t, io.NewSectionReader(bytes.NewReader(sgzData), 0, int64(len(sgzData))), rFunc, controller)
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
if _, err := io.Copy(buf, newSgz); err != nil { if _, err := io.Copy(buf, newSgz); err != nil {
@ -988,7 +958,7 @@ func checkVerifyInvalidTOCEntryFail(filename string) check {
// checkVerifyInvalidStargzFail checks if the verification detects that the // checkVerifyInvalidStargzFail checks if the verification detects that the
// given stargz file doesn't match to the expected digest and returns error. // given stargz file doesn't match to the expected digest and returns error.
func checkVerifyInvalidStargzFail(invalid *io.SectionReader) check { func checkVerifyInvalidStargzFail(invalid *io.SectionReader) check {
return func(t *TestRunner, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) { return func(t *testing.T, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) {
cl := newController() cl := newController()
rc, err := Build(invalid, WithChunkSize(chunkSize), WithCompression(cl)) rc, err := Build(invalid, WithChunkSize(chunkSize), WithCompression(cl))
if err != nil { if err != nil {
@ -1020,7 +990,7 @@ func checkVerifyInvalidStargzFail(invalid *io.SectionReader) check {
// checkVerifyBrokenContentFail checks if the verifier detects broken contents // checkVerifyBrokenContentFail checks if the verifier detects broken contents
// that doesn't match to the expected digest and returns error. // that doesn't match to the expected digest and returns error.
func checkVerifyBrokenContentFail(filename string) check { func checkVerifyBrokenContentFail(filename string) check {
return func(t *TestRunner, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) { return func(t *testing.T, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController, newController TestingControllerFactory) {
// Parse stargz file // Parse stargz file
sgz, err := Open( sgz, err := Open(
io.NewSectionReader(bytes.NewReader(sgzData), 0, int64(len(sgzData))), io.NewSectionReader(bytes.NewReader(sgzData), 0, int64(len(sgzData))),
@ -1077,9 +1047,9 @@ func chunkID(name string, offset, size int64) string {
return fmt.Sprintf("%s-%d-%d", cleanEntryName(name), offset, size) return fmt.Sprintf("%s-%d-%d", cleanEntryName(name), offset, size)
} }
type rewriteFunc func(t TestingT, toc *JTOC, sgz *io.SectionReader) type rewriteFunc func(t *testing.T, toc *JTOC, sgz *io.SectionReader)
func rewriteTOCJSON(t TestingT, sgz *io.SectionReader, rewrite rewriteFunc, controller TestingController) (newSgz io.Reader, tocDigest digest.Digest) { func rewriteTOCJSON(t *testing.T, sgz *io.SectionReader, rewrite rewriteFunc, controller TestingController) (newSgz io.Reader, tocDigest digest.Digest) {
decodedJTOC, jtocOffset, err := parseStargz(sgz, controller) decodedJTOC, jtocOffset, err := parseStargz(sgz, controller)
if err != nil { if err != nil {
t.Fatalf("failed to extract TOC JSON: %v", err) t.Fatalf("failed to extract TOC JSON: %v", err)
@ -1150,7 +1120,7 @@ func parseStargz(sgz *io.SectionReader, controller TestingController) (decodedJT
return decodedJTOC, tocOffset, nil return decodedJTOC, tocOffset, nil
} }
func testWriteAndOpen(t *TestRunner, controllers ...TestingControllerFactory) { func testWriteAndOpen(t *testing.T, controllers ...TestingControllerFactory) {
const content = "Some contents" const content = "Some contents"
invalidUtf8 := "\xff\xfe\xfd" invalidUtf8 := "\xff\xfe\xfd"
@ -1494,7 +1464,7 @@ func testWriteAndOpen(t *TestRunner, controllers ...TestingControllerFactory) {
for _, srcTarFormat := range []tar.Format{tar.FormatUSTAR, tar.FormatPAX, tar.FormatGNU} { for _, srcTarFormat := range []tar.Format{tar.FormatUSTAR, tar.FormatPAX, tar.FormatGNU} {
srcTarFormat := srcTarFormat srcTarFormat := srcTarFormat
for _, lossless := range []bool{true, false} { for _, lossless := range []bool{true, false} {
t.Run(tt.name+"-"+fmt.Sprintf("compression=%v,prefix=%q,lossless=%v,format=%s", newCL(), prefix, lossless, srcTarFormat), func(t *TestRunner) { t.Run(tt.name+"-"+fmt.Sprintf("compression=%v,prefix=%q,lossless=%v,format=%s", newCL(), prefix, lossless, srcTarFormat), func(t *testing.T) {
var tr io.Reader = buildTar(t, tt.in, prefix, srcTarFormat) var tr io.Reader = buildTar(t, tt.in, prefix, srcTarFormat)
origTarDgstr := digest.Canonical.Digester() origTarDgstr := digest.Canonical.Digester()
tr = io.TeeReader(tr, origTarDgstr.Hash()) tr = io.TeeReader(tr, origTarDgstr.Hash())
@ -1658,7 +1628,7 @@ func digestFor(content string) string {
type numTOCEntries int type numTOCEntries int
func (n numTOCEntries) check(t TestingT, r *Reader) { func (n numTOCEntries) check(t *testing.T, r *Reader) {
if r.toc == nil { if r.toc == nil {
t.Fatal("nil TOC") t.Fatal("nil TOC")
} }
@ -1678,15 +1648,15 @@ func (n numTOCEntries) check(t TestingT, r *Reader) {
func checks(s ...stargzCheck) []stargzCheck { return s } func checks(s ...stargzCheck) []stargzCheck { return s }
type stargzCheck interface { type stargzCheck interface {
check(t TestingT, r *Reader) check(t *testing.T, r *Reader)
} }
type stargzCheckFn func(TestingT, *Reader) type stargzCheckFn func(*testing.T, *Reader)
func (f stargzCheckFn) check(t TestingT, r *Reader) { f(t, r) } func (f stargzCheckFn) check(t *testing.T, r *Reader) { f(t, r) }
func maxDepth(max int) stargzCheck { func maxDepth(max int) stargzCheck {
return stargzCheckFn(func(t TestingT, r *Reader) { return stargzCheckFn(func(t *testing.T, r *Reader) {
e, ok := r.Lookup("") e, ok := r.Lookup("")
if !ok { if !ok {
t.Fatal("root directory not found") t.Fatal("root directory not found")
@ -1703,7 +1673,7 @@ func maxDepth(max int) stargzCheck {
}) })
} }
func getMaxDepth(t TestingT, e *TOCEntry, current, limit int) (max int, rErr error) { func getMaxDepth(t *testing.T, e *TOCEntry, current, limit int) (max int, rErr error) {
if current > limit { if current > limit {
return -1, fmt.Errorf("walkMaxDepth: exceeds limit: current:%d > limit:%d", return -1, fmt.Errorf("walkMaxDepth: exceeds limit: current:%d > limit:%d",
current, limit) current, limit)
@ -1725,7 +1695,7 @@ func getMaxDepth(t TestingT, e *TOCEntry, current, limit int) (max int, rErr err
} }
func hasFileLen(file string, wantLen int) stargzCheck { func hasFileLen(file string, wantLen int) stargzCheck {
return stargzCheckFn(func(t TestingT, r *Reader) { return stargzCheckFn(func(t *testing.T, r *Reader) {
for _, ent := range r.toc.Entries { for _, ent := range r.toc.Entries {
if ent.Name == file { if ent.Name == file {
if ent.Type != "reg" { if ent.Type != "reg" {
@ -1741,7 +1711,7 @@ func hasFileLen(file string, wantLen int) stargzCheck {
} }
func hasFileXattrs(file, name, value string) stargzCheck { func hasFileXattrs(file, name, value string) stargzCheck {
return stargzCheckFn(func(t TestingT, r *Reader) { return stargzCheckFn(func(t *testing.T, r *Reader) {
for _, ent := range r.toc.Entries { for _, ent := range r.toc.Entries {
if ent.Name == file { if ent.Name == file {
if ent.Type != "reg" { if ent.Type != "reg" {
@ -1768,7 +1738,7 @@ func hasFileXattrs(file, name, value string) stargzCheck {
} }
func hasFileDigest(file string, digest string) stargzCheck { func hasFileDigest(file string, digest string) stargzCheck {
return stargzCheckFn(func(t TestingT, r *Reader) { return stargzCheckFn(func(t *testing.T, r *Reader) {
ent, ok := r.Lookup(file) ent, ok := r.Lookup(file)
if !ok { if !ok {
t.Fatalf("didn't find TOCEntry for file %q", file) t.Fatalf("didn't find TOCEntry for file %q", file)
@ -1780,7 +1750,7 @@ func hasFileDigest(file string, digest string) stargzCheck {
} }
func hasFileContentsWithPreRead(file string, offset int, want string, extra ...chunkInfo) stargzCheck { func hasFileContentsWithPreRead(file string, offset int, want string, extra ...chunkInfo) stargzCheck {
return stargzCheckFn(func(t TestingT, r *Reader) { return stargzCheckFn(func(t *testing.T, r *Reader) {
extraMap := make(map[string]chunkInfo) extraMap := make(map[string]chunkInfo)
for _, e := range extra { for _, e := range extra {
extraMap[e.name] = e extraMap[e.name] = e
@ -1827,7 +1797,7 @@ func hasFileContentsWithPreRead(file string, offset int, want string, extra ...c
} }
func hasFileContentsRange(file string, offset int, want string) stargzCheck { func hasFileContentsRange(file string, offset int, want string) stargzCheck {
return stargzCheckFn(func(t TestingT, r *Reader) { return stargzCheckFn(func(t *testing.T, r *Reader) {
f, err := r.OpenFile(file) f, err := r.OpenFile(file)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -1844,7 +1814,7 @@ func hasFileContentsRange(file string, offset int, want string) stargzCheck {
} }
func hasChunkEntries(file string, wantChunks int) stargzCheck { func hasChunkEntries(file string, wantChunks int) stargzCheck {
return stargzCheckFn(func(t TestingT, r *Reader) { return stargzCheckFn(func(t *testing.T, r *Reader) {
ent, ok := r.Lookup(file) ent, ok := r.Lookup(file)
if !ok { if !ok {
t.Fatalf("no file for %q", file) t.Fatalf("no file for %q", file)
@ -1888,7 +1858,7 @@ func hasChunkEntries(file string, wantChunks int) stargzCheck {
} }
func entryHasChildren(dir string, want ...string) stargzCheck { func entryHasChildren(dir string, want ...string) stargzCheck {
return stargzCheckFn(func(t TestingT, r *Reader) { return stargzCheckFn(func(t *testing.T, r *Reader) {
want := append([]string(nil), want...) want := append([]string(nil), want...)
var got []string var got []string
ent, ok := r.Lookup(dir) ent, ok := r.Lookup(dir)
@ -1907,7 +1877,7 @@ func entryHasChildren(dir string, want ...string) stargzCheck {
} }
func hasDir(file string) stargzCheck { func hasDir(file string) stargzCheck {
return stargzCheckFn(func(t TestingT, r *Reader) { return stargzCheckFn(func(t *testing.T, r *Reader) {
for _, ent := range r.toc.Entries { for _, ent := range r.toc.Entries {
if ent.Name == cleanEntryName(file) { if ent.Name == cleanEntryName(file) {
if ent.Type != "dir" { if ent.Type != "dir" {
@ -1921,7 +1891,7 @@ func hasDir(file string) stargzCheck {
} }
func hasDirLinkCount(file string, count int) stargzCheck { func hasDirLinkCount(file string, count int) stargzCheck {
return stargzCheckFn(func(t TestingT, r *Reader) { return stargzCheckFn(func(t *testing.T, r *Reader) {
for _, ent := range r.toc.Entries { for _, ent := range r.toc.Entries {
if ent.Name == cleanEntryName(file) { if ent.Name == cleanEntryName(file) {
if ent.Type != "dir" { if ent.Type != "dir" {
@ -1939,7 +1909,7 @@ func hasDirLinkCount(file string, count int) stargzCheck {
} }
func hasMode(file string, mode os.FileMode) stargzCheck { func hasMode(file string, mode os.FileMode) stargzCheck {
return stargzCheckFn(func(t TestingT, r *Reader) { return stargzCheckFn(func(t *testing.T, r *Reader) {
for _, ent := range r.toc.Entries { for _, ent := range r.toc.Entries {
if ent.Name == cleanEntryName(file) { if ent.Name == cleanEntryName(file) {
if ent.Stat().Mode() != mode { if ent.Stat().Mode() != mode {
@ -1954,7 +1924,7 @@ func hasMode(file string, mode os.FileMode) stargzCheck {
} }
func hasSymlink(file, target string) stargzCheck { func hasSymlink(file, target string) stargzCheck {
return stargzCheckFn(func(t TestingT, r *Reader) { return stargzCheckFn(func(t *testing.T, r *Reader) {
for _, ent := range r.toc.Entries { for _, ent := range r.toc.Entries {
if ent.Name == file { if ent.Name == file {
if ent.Type != "symlink" { if ent.Type != "symlink" {
@ -1970,7 +1940,7 @@ func hasSymlink(file, target string) stargzCheck {
} }
func lookupMatch(name string, want *TOCEntry) stargzCheck { func lookupMatch(name string, want *TOCEntry) stargzCheck {
return stargzCheckFn(func(t TestingT, r *Reader) { return stargzCheckFn(func(t *testing.T, r *Reader) {
e, ok := r.Lookup(name) e, ok := r.Lookup(name)
if !ok { if !ok {
t.Fatalf("failed to Lookup entry %q", name) t.Fatalf("failed to Lookup entry %q", name)
@ -1983,7 +1953,7 @@ func lookupMatch(name string, want *TOCEntry) stargzCheck {
} }
func hasEntryOwner(entry string, owner owner) stargzCheck { func hasEntryOwner(entry string, owner owner) stargzCheck {
return stargzCheckFn(func(t TestingT, r *Reader) { return stargzCheckFn(func(t *testing.T, r *Reader) {
ent, ok := r.Lookup(strings.TrimSuffix(entry, "/")) ent, ok := r.Lookup(strings.TrimSuffix(entry, "/"))
if !ok { if !ok {
t.Errorf("entry %q not found", entry) t.Errorf("entry %q not found", entry)
@ -1997,7 +1967,7 @@ func hasEntryOwner(entry string, owner owner) stargzCheck {
} }
func mustSameEntry(files ...string) stargzCheck { func mustSameEntry(files ...string) stargzCheck {
return stargzCheckFn(func(t TestingT, r *Reader) { return stargzCheckFn(func(t *testing.T, r *Reader) {
var first *TOCEntry var first *TOCEntry
for _, f := range files { for _, f := range files {
if first == nil { if first == nil {
@ -2069,7 +2039,7 @@ func (f tarEntryFunc) appendTar(tw *tar.Writer, prefix string, format tar.Format
return f(tw, prefix, format) return f(tw, prefix, format)
} }
func buildTar(t TestingT, ents []tarEntry, prefix string, opts ...interface{}) *io.SectionReader { func buildTar(t *testing.T, ents []tarEntry, prefix string, opts ...interface{}) *io.SectionReader {
format := tar.FormatUnknown format := tar.FormatUnknown
for _, opt := range opts { for _, opt := range opts {
switch v := opt.(type) { switch v := opt.(type) {
@ -2278,7 +2248,7 @@ func noPrefetchLandmark() tarEntry {
}) })
} }
func regDigest(t TestingT, name string, contentStr string, digestMap map[string]digest.Digest) tarEntry { func regDigest(t *testing.T, name string, contentStr string, digestMap map[string]digest.Digest) tarEntry {
if digestMap == nil { if digestMap == nil {
t.Fatalf("digest map mustn't be nil") t.Fatalf("digest map mustn't be nil")
} }
@ -2321,11 +2291,7 @@ var runes = []rune("1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX
func randomContents(n int) string { func randomContents(n int) string {
b := make([]rune, n) b := make([]rune, n)
for i := range b { for i := range b {
bi, err := rand.Int(rand.Reader, big.NewInt(int64(len(runes)))) b[i] = runes[rand.Intn(len(runes))]
if err != nil {
panic(err)
}
b[i] = runes[int(bi.Int64())]
} }
return string(b) return string(b)
} }
@ -2348,7 +2314,7 @@ func (f fileInfoOnlyMode) ModTime() time.Time { return time.Now() }
func (f fileInfoOnlyMode) IsDir() bool { return os.FileMode(f).IsDir() } func (f fileInfoOnlyMode) IsDir() bool { return os.FileMode(f).IsDir() }
func (f fileInfoOnlyMode) Sys() interface{} { return nil } func (f fileInfoOnlyMode) Sys() interface{} { return nil }
func CheckGzipHasStreams(t TestingT, b []byte, streams []int64) { func CheckGzipHasStreams(t *testing.T, b []byte, streams []int64) {
if len(streams) == 0 { if len(streams) == 0 {
return // nop return // nop
} }
@ -2377,8 +2343,8 @@ func CheckGzipHasStreams(t TestingT, b []byte, streams []int64) {
t.Fatalf("countStreams(gzip), Copy: %v", err) t.Fatalf("countStreams(gzip), Copy: %v", err)
} }
var extra string var extra string
if len(zr.Extra) > 0 { if len(zr.Header.Extra) > 0 {
extra = fmt.Sprintf("; extra=%q", zr.Extra) extra = fmt.Sprintf("; extra=%q", zr.Header.Extra)
} }
t.Logf(" [%d] at %d in stargz, uncompressed length %d%s", numStreams, zoff, n, extra) t.Logf(" [%d] at %d in stargz, uncompressed length %d%s", numStreams, zoff, n, extra)
delete(wants, int64(zoff)) delete(wants, int64(zoff))
@ -2386,7 +2352,7 @@ func CheckGzipHasStreams(t TestingT, b []byte, streams []int64) {
} }
} }
func GzipDiffIDOf(t TestingT, b []byte) string { func GzipDiffIDOf(t *testing.T, b []byte) string {
h := sha256.New() h := sha256.New()
zr, err := gzip.NewReader(bytes.NewReader(b)) zr, err := gzip.NewReader(bytes.NewReader(b))
if err != nil { if err != nil {

View File

@ -30,22 +30,7 @@ import (
// TestZstdChunked tests zstd:chunked // TestZstdChunked tests zstd:chunked
func TestZstdChunked(t *testing.T) { func TestZstdChunked(t *testing.T) {
testRunner := &estargz.TestRunner{ estargz.CompressionTestSuite(t,
TestingT: t,
Runner: func(testingT estargz.TestingT, name string, run func(t estargz.TestingT)) {
tt, ok := testingT.(*testing.T)
if !ok {
testingT.Fatal("TestingT is not a *testing.T")
return
}
tt.Run(name, func(t *testing.T) {
run(t)
})
},
}
estargz.CompressionTestSuite(testRunner,
zstdControllerWithLevel(zstd.SpeedFastest), zstdControllerWithLevel(zstd.SpeedFastest),
zstdControllerWithLevel(zstd.SpeedDefault), zstdControllerWithLevel(zstd.SpeedDefault),
zstdControllerWithLevel(zstd.SpeedBetterCompression), zstdControllerWithLevel(zstd.SpeedBetterCompression),
@ -65,12 +50,12 @@ type zstdController struct {
} }
func (zc *zstdController) String() string { func (zc *zstdController) String() string {
return fmt.Sprintf("zstd_compression_level=%v", zc.CompressionLevel) return fmt.Sprintf("zstd_compression_level=%v", zc.Compressor.CompressionLevel)
} }
// TestStream tests the passed zstdchunked blob contains the specified list of streams. // TestStream tests the passed zstdchunked blob contains the specified list of streams.
// The last entry of streams must be the offset of footer (len(b) - footerSize). // The last entry of streams must be the offset of footer (len(b) - footerSize).
func (zc *zstdController) TestStreams(t estargz.TestingT, b []byte, streams []int64) { func (zc *zstdController) TestStreams(t *testing.T, b []byte, streams []int64) {
t.Logf("got zstd streams (compressed size: %d):", len(b)) t.Logf("got zstd streams (compressed size: %d):", len(b))
if len(streams) == 0 { if len(streams) == 0 {
@ -137,7 +122,7 @@ func nextIndex(s1, sub []byte) int {
return -1 return -1
} }
func (zc *zstdController) DiffIDOf(t estargz.TestingT, b []byte) string { func (zc *zstdController) DiffIDOf(t *testing.T, b []byte) string {
h := sha256.New() h := sha256.New()
zr, err := zstd.NewReader(bytes.NewReader(b)) zr, err := zstd.NewReader(bytes.NewReader(b))
if err != nil { if err != nil {

View File

@ -33,131 +33,62 @@ const (
TargetPrefetchSizeLabel = "containerd.io/snapshot/remote/stargz.prefetch" TargetPrefetchSizeLabel = "containerd.io/snapshot/remote/stargz.prefetch"
) )
// Config is configuration for stargz snapshotter filesystem.
type Config struct { type Config struct {
// Type of cache for compressed contents fetched from the registry. "memory" stores them on memory. HTTPCacheType string `toml:"http_cache_type"`
// Other values default to cache them on disk. FSCacheType string `toml:"filesystem_cache_type"`
HTTPCacheType string `toml:"http_cache_type" json:"http_cache_type"`
// Type of cache for uncompressed files contents. "memory" stores them on memory. Other values
// default to cache them on disk.
FSCacheType string `toml:"filesystem_cache_type" json:"filesystem_cache_type"`
// ResolveResultEntryTTLSec is TTL (in sec) to cache resolved layers for // ResolveResultEntryTTLSec is TTL (in sec) to cache resolved layers for
// future use. (default 120s) // future use. (default 120s)
ResolveResultEntryTTLSec int `toml:"resolve_result_entry_ttl_sec" json:"resolve_result_entry_ttl_sec"` ResolveResultEntryTTLSec int `toml:"resolve_result_entry_ttl_sec"`
ResolveResultEntry int `toml:"resolve_result_entry"` // deprecated
// PrefetchSize is the default size (in bytes) to prefetch when mounting a layer. Default is 0. Stargz-snapshotter still PrefetchSize int64 `toml:"prefetch_size"`
// uses the value specified by the image using "containerd.io/snapshot/remote/stargz.prefetch" or the landmark file. PrefetchTimeoutSec int64 `toml:"prefetch_timeout_sec"`
PrefetchSize int64 `toml:"prefetch_size" json:"prefetch_size"` NoPrefetch bool `toml:"noprefetch"`
NoBackgroundFetch bool `toml:"no_background_fetch"`
// PrefetchTimeoutSec is the default timeout (in seconds) when the prefetching takes long. Default is 10s. Debug bool `toml:"debug"`
PrefetchTimeoutSec int64 `toml:"prefetch_timeout_sec" json:"prefetch_timeout_sec"` AllowNoVerification bool `toml:"allow_no_verification"`
DisableVerification bool `toml:"disable_verification"`
// NoPrefetch disables prefetching. Default is false. MaxConcurrency int64 `toml:"max_concurrency"`
NoPrefetch bool `toml:"noprefetch" json:"noprefetch"` NoPrometheus bool `toml:"no_prometheus"`
// NoBackgroundFetch disables the behaviour of fetching the entire layer contents in background. Default is false.
NoBackgroundFetch bool `toml:"no_background_fetch" json:"no_background_fetch"`
// Debug enables filesystem debug log.
Debug bool `toml:"debug" json:"debug"`
// AllowNoVerification allows mouting images without verification. Default is false.
AllowNoVerification bool `toml:"allow_no_verification" json:"allow_no_verification"`
// DisableVerification disables verifying layer contents. Default is false.
DisableVerification bool `toml:"disable_verification" json:"disable_verification"`
// MaxConcurrency is max number of concurrent background tasks for fetching layer contents. Default is 2.
MaxConcurrency int64 `toml:"max_concurrency" json:"max_concurrency"`
// NoPrometheus disables exposing filesystem-related metrics. Default is false.
NoPrometheus bool `toml:"no_prometheus" json:"no_prometheus"`
// BlobConfig is config for layer blob management. // BlobConfig is config for layer blob management.
BlobConfig `toml:"blob" json:"blob"` BlobConfig `toml:"blob"`
// DirectoryCacheConfig is config for directory-based cache. // DirectoryCacheConfig is config for directory-based cache.
DirectoryCacheConfig `toml:"directory_cache" json:"directory_cache"` DirectoryCacheConfig `toml:"directory_cache"`
// FuseConfig is configurations for FUSE fs. FuseConfig `toml:"fuse"`
FuseConfig `toml:"fuse" json:"fuse"`
// ResolveResultEntry is a deprecated field.
ResolveResultEntry int `toml:"resolve_result_entry" json:"resolve_result_entry"` // deprecated
} }
// BlobConfig is configuration for the logic to fetching blobs.
type BlobConfig struct { type BlobConfig struct {
// ValidInterval specifies a duration (in seconds) during which the layer can be reused without ValidInterval int64 `toml:"valid_interval"`
// checking the connection to the registry. Default is 60. CheckAlways bool `toml:"check_always"`
ValidInterval int64 `toml:"valid_interval" json:"valid_interval"` // ChunkSize is the granularity at which background fetch and on-demand reads
// are fetched from the remote registry.
// CheckAlways overwrites ValidInterval to 0 if it's true. Default is false. ChunkSize int64 `toml:"chunk_size"`
CheckAlways bool `toml:"check_always" json:"check_always"` FetchTimeoutSec int64 `toml:"fetching_timeout_sec"`
ForceSingleRangeMode bool `toml:"force_single_range_mode"`
// ChunkSize is the granularity (in bytes) at which background fetch and on-demand reads
// are fetched from the remote registry. Default is 50000.
ChunkSize int64 `toml:"chunk_size" json:"chunk_size"`
// FetchTimeoutSec is a timeout duration (in seconds) for fetching chunks from the registry. Default is 300.
FetchTimeoutSec int64 `toml:"fetching_timeout_sec" json:"fetching_tieout_sec"`
// ForceSingleRangeMode disables using of multiple ranges in a Range Request and always specifies one larger
// region that covers them. Default is false.
ForceSingleRangeMode bool `toml:"force_single_range_mode" json:"force_single_range_mode"`
// PrefetchChunkSize is the maximum bytes transferred per http GET from remote registry // PrefetchChunkSize is the maximum bytes transferred per http GET from remote registry
// during prefetch. It is recommended to have PrefetchChunkSize > ChunkSize. // during prefetch. It is recommended to have PrefetchChunkSize > ChunkSize.
// If PrefetchChunkSize < ChunkSize prefetch bytes will be fetched as a single http GET, // If PrefetchChunkSize < ChunkSize prefetch bytes will be fetched as a single http GET,
// else total GET requests for prefetch = ceil(PrefetchSize / PrefetchChunkSize). // else total GET requests for prefetch = ceil(PrefetchSize / PrefetchChunkSize).
// Default is 0. PrefetchChunkSize int64 `toml:"prefetch_chunk_size"`
PrefetchChunkSize int64 `toml:"prefetch_chunk_size" json:"prefetch_chunk_size"`
// MaxRetries is a max number of reries of a HTTP request. Default is 5. MaxRetries int `toml:"max_retries"`
MaxRetries int `toml:"max_retries" json:"max_retries"` MinWaitMSec int `toml:"min_wait_msec"`
MaxWaitMSec int `toml:"max_wait_msec"`
// MinWaitMSec is minimal delay (in seconds) for the next retrying after a request failure. Default is 30.
MinWaitMSec int `toml:"min_wait_msec" json:"min_wait_msec"`
// MinWaitMSec is maximum delay (in seconds) for the next retrying after a request failure. Default is 30.
MaxWaitMSec int `toml:"max_wait_msec" json:"max_wait_msec"`
} }
// DirectoryCacheConfig is configuration for the disk-based cache.
type DirectoryCacheConfig struct { type DirectoryCacheConfig struct {
// MaxLRUCacheEntry is the number of entries of LRU cache to cache data on memory. Default is 10. MaxLRUCacheEntry int `toml:"max_lru_cache_entry"`
MaxLRUCacheEntry int `toml:"max_lru_cache_entry" json:"max_lru_cache_entry"` MaxCacheFds int `toml:"max_cache_fds"`
SyncAdd bool `toml:"sync_add"`
// MaxCacheFds is the number of entries of LRU cache to hold fds of files of cached contents. Default is 10. Direct bool `toml:"direct" default:"true"`
MaxCacheFds int `toml:"max_cache_fds" json:"max_cache_fds"`
// SyncAdd being true means that each adding of data to the cache blocks until the data is fully written to the
// cache directory. Default is false.
SyncAdd bool `toml:"sync_add" json:"sync_add"`
// Direct disables on-memory data cache. Default is true for saving memory usage.
Direct bool `toml:"direct" default:"true" json:"direct"`
// FadvDontNeed forcefully clean fscache pagecache for saving memory. Default is false.
FadvDontNeed bool `toml:"fadv_dontneed" json:"fadv_dontneed"`
} }
// FuseConfig is configuration for FUSE fs.
type FuseConfig struct { type FuseConfig struct {
// AttrTimeout defines overall timeout attribute for a file system in seconds. // AttrTimeout defines overall timeout attribute for a file system in seconds.
AttrTimeout int64 `toml:"attr_timeout" json:"attr_timeout"` AttrTimeout int64 `toml:"attr_timeout"`
// EntryTimeout defines TTL for directory, name lookup in seconds. // EntryTimeout defines TTL for directory, name lookup in seconds.
EntryTimeout int64 `toml:"entry_timeout" json:"entry_timeout"` EntryTimeout int64 `toml:"entry_timeout"`
// PassThrough indicates whether to enable FUSE passthrough mode to improve local file read performance. Default is false.
PassThrough bool `toml:"passthrough" default:"false" json:"passthrough"`
// MergeBufferSize is the size of the buffer to merge chunks (in bytes) for passthrough mode. Default is 400MB.
MergeBufferSize int64 `toml:"merge_buffer_size" default:"419430400" json:"merge_buffer_size"`
// MergeWorkerCount is the number of workers to merge chunks for passthrough mode. Default is 10.
MergeWorkerCount int `toml:"merge_worker_count" default:"10" json:"merge_worker_count"`
} }

View File

@ -44,9 +44,9 @@ import (
"sync" "sync"
"time" "time"
"github.com/containerd/containerd/v2/core/remotes/docker" "github.com/containerd/containerd/log"
"github.com/containerd/containerd/v2/pkg/reference" "github.com/containerd/containerd/reference"
"github.com/containerd/log" "github.com/containerd/containerd/remotes/docker"
"github.com/containerd/stargz-snapshotter/estargz" "github.com/containerd/stargz-snapshotter/estargz"
"github.com/containerd/stargz-snapshotter/fs/config" "github.com/containerd/stargz-snapshotter/fs/config"
"github.com/containerd/stargz-snapshotter/fs/layer" "github.com/containerd/stargz-snapshotter/fs/layer"
@ -63,6 +63,7 @@ import (
"github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse"
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
@ -72,12 +73,6 @@ const (
) )
var fusermountBin = []string{"fusermount", "fusermount3"} var fusermountBin = []string{"fusermount", "fusermount3"}
var (
nsLock = sync.Mutex{}
ns *metrics.Namespace
metricsCtr *layermetrics.Controller
)
type Option func(*options) type Option func(*options)
@ -85,7 +80,7 @@ type options struct {
getSources source.GetSources getSources source.GetSources
resolveHandlers map[string]remote.Handler resolveHandlers map[string]remote.Handler
metadataStore metadata.Store metadataStore metadata.Store
metricsLogLevel *log.Level metricsLogLevel *logrus.Level
overlayOpaqueType layer.OverlayOpaqueType overlayOpaqueType layer.OverlayOpaqueType
additionalDecompressors func(context.Context, source.RegistryHosts, reference.Spec, ocispec.Descriptor) []metadata.Decompressor additionalDecompressors func(context.Context, source.RegistryHosts, reference.Spec, ocispec.Descriptor) []metadata.Decompressor
} }
@ -111,7 +106,7 @@ func WithMetadataStore(metadataStore metadata.Store) Option {
} }
} }
func WithMetricsLogLevel(logLevel log.Level) Option { func WithMetricsLogLevel(logLevel logrus.Level) Option {
return func(opts *options) { return func(opts *options) {
opts.metricsLogLevel = &logLevel opts.metricsLogLevel = &logLevel
} }
@ -139,12 +134,12 @@ func NewFilesystem(root string, cfg config.Config, opts ...Option) (_ snapshot.F
maxConcurrency = defaultMaxConcurrency maxConcurrency = defaultMaxConcurrency
} }
attrTimeout := time.Duration(cfg.AttrTimeout) * time.Second attrTimeout := time.Duration(cfg.FuseConfig.AttrTimeout) * time.Second
if attrTimeout == 0 { if attrTimeout == 0 {
attrTimeout = defaultFuseTimeout attrTimeout = defaultFuseTimeout
} }
entryTimeout := time.Duration(cfg.EntryTimeout) * time.Second entryTimeout := time.Duration(cfg.FuseConfig.EntryTimeout) * time.Second
if entryTimeout == 0 { if entryTimeout == 0 {
entryTimeout = defaultFuseTimeout entryTimeout = defaultFuseTimeout
} }
@ -166,20 +161,18 @@ func NewFilesystem(root string, cfg config.Config, opts ...Option) (_ snapshot.F
return nil, fmt.Errorf("failed to setup resolver: %w", err) return nil, fmt.Errorf("failed to setup resolver: %w", err)
} }
nsLock.Lock() var ns *metrics.Namespace
defer nsLock.Unlock() if !cfg.NoPrometheus {
if !cfg.NoPrometheus && ns == nil {
ns = metrics.NewNamespace("stargz", "fs", nil) ns = metrics.NewNamespace("stargz", "fs", nil)
logLevel := log.DebugLevel logLevel := logrus.DebugLevel
if fsOpts.metricsLogLevel != nil { if fsOpts.metricsLogLevel != nil {
logLevel = *fsOpts.metricsLogLevel logLevel = *fsOpts.metricsLogLevel
} }
commonmetrics.Register(logLevel) // Register common metrics. This will happen only once. commonmetrics.Register(logLevel) // Register common metrics. This will happen only once.
metrics.Register(ns) // Register layer metrics.
} }
if metricsCtr == nil { c := layermetrics.NewLayerMetrics(ns)
metricsCtr = layermetrics.NewLayerMetrics(ns) if ns != nil {
metrics.Register(ns) // Register layer metrics.
} }
return &filesystem{ return &filesystem{
@ -193,7 +186,7 @@ func NewFilesystem(root string, cfg config.Config, opts ...Option) (_ snapshot.F
backgroundTaskManager: tm, backgroundTaskManager: tm,
allowNoVerification: cfg.AllowNoVerification, allowNoVerification: cfg.AllowNoVerification,
disableVerification: cfg.DisableVerification, disableVerification: cfg.DisableVerification,
metricsController: metricsCtr, metricsController: c,
attrTimeout: attrTimeout, attrTimeout: attrTimeout,
entryTimeout: entryTimeout, entryTimeout: entryTimeout,
}, nil }, nil
@ -389,13 +382,10 @@ func (fs *filesystem) Check(ctx context.Context, mountpoint string, labels map[s
return fmt.Errorf("layer not registered") return fmt.Errorf("layer not registered")
} }
if l.Info().FetchedSize < l.Info().Size { // Check the blob connectivity and try to refresh the connection on failure
// Image contents hasn't fully cached yet. if err := fs.check(ctx, l, labels); err != nil {
// Check the blob connectivity and try to refresh the connection on failure log.G(ctx).WithError(err).Warn("check failed")
if err := fs.check(ctx, l, labels); err != nil { return err
log.G(ctx).WithError(err).Warn("check failed")
return err
}
} }
// Wait for prefetch compeletion // Wait for prefetch compeletion
@ -450,10 +440,8 @@ func (fs *filesystem) Unmount(ctx context.Context, mountpoint string) error {
fs.layerMu.Unlock() fs.layerMu.Unlock()
return fmt.Errorf("specified path %q isn't a mountpoint", mountpoint) return fmt.Errorf("specified path %q isn't a mountpoint", mountpoint)
} }
delete(fs.layer, mountpoint) // unregisters the corresponding layer delete(fs.layer, mountpoint) // unregisters the corresponding layer
if err := l.Close(); err != nil { // Cleanup associated resources l.Done()
log.G(ctx).WithError(err).Warn("failed to release resources of the layer")
}
fs.layerMu.Unlock() fs.layerMu.Unlock()
fs.metricsController.Remove(mountpoint) fs.metricsController.Remove(mountpoint)

View File

@ -28,8 +28,8 @@ import (
"testing" "testing"
"time" "time"
"github.com/containerd/containerd/v2/core/remotes/docker" "github.com/containerd/containerd/reference"
"github.com/containerd/containerd/v2/pkg/reference" "github.com/containerd/containerd/remotes/docker"
"github.com/containerd/stargz-snapshotter/fs/layer" "github.com/containerd/stargz-snapshotter/fs/layer"
"github.com/containerd/stargz-snapshotter/fs/remote" "github.com/containerd/stargz-snapshotter/fs/remote"
"github.com/containerd/stargz-snapshotter/fs/source" "github.com/containerd/stargz-snapshotter/fs/source"
@ -65,20 +65,14 @@ type breakableLayer struct {
success bool success bool
} }
func (l *breakableLayer) Info() layer.Info { func (l *breakableLayer) Info() layer.Info { return layer.Info{} }
return layer.Info{ func (l *breakableLayer) RootNode(uint32) (fusefs.InodeEmbedder, error) { return nil, nil }
Size: 1, func (l *breakableLayer) Verify(tocDigest digest.Digest) error { return nil }
} func (l *breakableLayer) SkipVerify() {}
} func (l *breakableLayer) Prefetch(prefetchSize int64) error { return fmt.Errorf("fail") }
func (l *breakableLayer) RootNode(uint32) (fusefs.InodeEmbedder, error) { return nil, nil } func (l *breakableLayer) ReadAt([]byte, int64, ...remote.Option) (int, error) { return 0, nil }
func (l *breakableLayer) Verify(tocDigest digest.Digest) error { return nil } func (l *breakableLayer) WaitForPrefetchCompletion() error { return fmt.Errorf("fail") }
func (l *breakableLayer) SkipVerify() {} func (l *breakableLayer) BackgroundFetch() error { return fmt.Errorf("fail") }
func (l *breakableLayer) Prefetch(prefetchSize int64) error { return fmt.Errorf("fail") }
func (l *breakableLayer) ReadAt([]byte, int64, ...remote.Option) (int, error) {
return 0, fmt.Errorf("fail")
}
func (l *breakableLayer) WaitForPrefetchCompletion() error { return fmt.Errorf("fail") }
func (l *breakableLayer) BackgroundFetch() error { return fmt.Errorf("fail") }
func (l *breakableLayer) Check() error { func (l *breakableLayer) Check() error {
if !l.success { if !l.success {
return fmt.Errorf("failed") return fmt.Errorf("failed")
@ -91,5 +85,4 @@ func (l *breakableLayer) Refresh(ctx context.Context, hosts source.RegistryHosts
} }
return nil return nil
} }
func (l *breakableLayer) Done() {} func (l *breakableLayer) Done() {}
func (l *breakableLayer) Close() error { return nil }

View File

@ -32,8 +32,8 @@ import (
"sync" "sync"
"time" "time"
"github.com/containerd/containerd/v2/pkg/reference" "github.com/containerd/containerd/log"
"github.com/containerd/log" "github.com/containerd/containerd/reference"
"github.com/containerd/stargz-snapshotter/cache" "github.com/containerd/stargz-snapshotter/cache"
"github.com/containerd/stargz-snapshotter/estargz" "github.com/containerd/stargz-snapshotter/estargz"
"github.com/containerd/stargz-snapshotter/estargz/zstdchunked" "github.com/containerd/stargz-snapshotter/estargz/zstdchunked"
@ -49,6 +49,7 @@ import (
fusefs "github.com/hanwen/go-fuse/v2/fs" fusefs "github.com/hanwen/go-fuse/v2/fs"
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sirupsen/logrus"
) )
const ( const (
@ -59,18 +60,6 @@ const (
memoryCacheType = "memory" memoryCacheType = "memory"
) )
// passThroughConfig contains configuration for FUSE passthrough mode
type passThroughConfig struct {
// enable indicates whether to enable FUSE passthrough mode
enable bool
// mergeBufferSize is the size of the buffer to merge chunks (in bytes)
mergeBufferSize int64
// mergeWorkerCount is the number of workers to merge chunks
mergeWorkerCount int
}
// Layer represents a layer. // Layer represents a layer.
type Layer interface { type Layer interface {
// Info returns the information of this layer. // Info returns the information of this layer.
@ -110,10 +99,6 @@ type Layer interface {
// Done releases the reference to this layer. The resources related to this layer will be // Done releases the reference to this layer. The resources related to this layer will be
// discarded sooner or later. Queries after calling this function won't be serviced. // discarded sooner or later. Queries after calling this function won't be serviced.
Done() Done()
// Close is the same as Done. But this evicts the resources related to this Layer immediately.
// This can be used for cleaning up resources on unmount.
Close() error
} }
// Info is the current status of a layer. // Info is the current status of a layer.
@ -123,7 +108,6 @@ type Info struct {
FetchedSize int64 // layer fetched size in bytes FetchedSize int64 // layer fetched size in bytes
PrefetchSize int64 // layer prefetch size in bytes PrefetchSize int64 // layer prefetch size in bytes
ReadTime time.Time // last time the layer was read ReadTime time.Time // last time the layer was read
TOCDigest digest.Digest
} }
// Resolver resolves the layer location and provieds the handler of that layer. // Resolver resolves the layer location and provieds the handler of that layer.
@ -160,10 +144,10 @@ func NewResolver(root string, backgroundTaskManager *task.BackgroundTaskManager,
layerCache := cacheutil.NewTTLCache(resolveResultEntryTTL) layerCache := cacheutil.NewTTLCache(resolveResultEntryTTL)
layerCache.OnEvicted = func(key string, value interface{}) { layerCache.OnEvicted = func(key string, value interface{}) {
if err := value.(*layer).close(); err != nil { if err := value.(*layer).close(); err != nil {
log.L.WithField("key", key).WithError(err).Warnf("failed to clean up layer") logrus.WithField("key", key).WithError(err).Warnf("failed to clean up layer")
return return
} }
log.L.WithField("key", key).Debugf("cleaned up layer") logrus.WithField("key", key).Debugf("cleaned up layer")
} }
// blobCache caches resolved blobs for futural use. This is especially useful when a layer // blobCache caches resolved blobs for futural use. This is especially useful when a layer
@ -171,10 +155,10 @@ func NewResolver(root string, backgroundTaskManager *task.BackgroundTaskManager,
blobCache := cacheutil.NewTTLCache(resolveResultEntryTTL) blobCache := cacheutil.NewTTLCache(resolveResultEntryTTL)
blobCache.OnEvicted = func(key string, value interface{}) { blobCache.OnEvicted = func(key string, value interface{}) {
if err := value.(remote.Blob).Close(); err != nil { if err := value.(remote.Blob).Close(); err != nil {
log.L.WithField("key", key).WithError(err).Warnf("failed to clean up blob") logrus.WithField("key", key).WithError(err).Warnf("failed to clean up blob")
return return
} }
log.L.WithField("key", key).Debugf("cleaned up blob") logrus.WithField("key", key).Debugf("cleaned up blob")
} }
if err := os.MkdirAll(root, 0700); err != nil { if err := os.MkdirAll(root, 0700); err != nil {
@ -235,12 +219,11 @@ func newCache(root string, cacheType string, cfg config.Config) (cache.BlobCache
return cache.NewDirectoryCache( return cache.NewDirectoryCache(
cachePath, cachePath,
cache.DirectoryCacheConfig{ cache.DirectoryCacheConfig{
SyncAdd: dcc.SyncAdd, SyncAdd: dcc.SyncAdd,
DataCache: dCache, DataCache: dCache,
FdCache: fCache, FdCache: fCache,
BufPool: bufPool, BufPool: bufPool,
Direct: dcc.Direct, Direct: dcc.Direct,
FadvDontNeed: dcc.FadvDontNeed,
}, },
) )
} }
@ -266,7 +249,7 @@ func (r *Resolver) Resolve(ctx context.Context, hosts source.RegistryHosts, refs
return &layerRef{l, done}, nil return &layerRef{l, done}, nil
} }
// Cached layer is invalid // Cached layer is invalid
done(true) done()
r.layerCacheMu.Lock() r.layerCacheMu.Lock()
r.layerCache.Remove(name) r.layerCache.Remove(name)
r.layerCacheMu.Unlock() r.layerCacheMu.Unlock()
@ -281,7 +264,7 @@ func (r *Resolver) Resolve(ctx context.Context, hosts source.RegistryHosts, refs
} }
defer func() { defer func() {
if retErr != nil { if retErr != nil {
blobR.done(true) blobR.done()
} }
}() }()
@ -332,11 +315,7 @@ func (r *Resolver) Resolve(ctx context.Context, hosts source.RegistryHosts, refs
} }
// Combine layer information together and cache it. // Combine layer information together and cache it.
l := newLayer(r, desc, blobR, vr, passThroughConfig{ l := newLayer(r, desc, blobR, vr)
enable: r.config.PassThrough,
mergeBufferSize: r.config.MergeBufferSize,
mergeWorkerCount: r.config.MergeWorkerCount,
})
r.layerCacheMu.Lock() r.layerCacheMu.Lock()
cachedL, done2, added := r.layerCache.Add(name, l) cachedL, done2, added := r.layerCache.Add(name, l)
r.layerCacheMu.Unlock() r.layerCacheMu.Unlock()
@ -361,7 +340,7 @@ func (r *Resolver) resolveBlob(ctx context.Context, hosts source.RegistryHosts,
return &blobRef{blob, done}, nil return &blobRef{blob, done}, nil
} }
// invalid blob. discard this. // invalid blob. discard this.
done(true) done()
r.blobCacheMu.Lock() r.blobCacheMu.Lock()
r.blobCache.Remove(name) r.blobCache.Remove(name)
r.blobCacheMu.Unlock() r.blobCacheMu.Unlock()
@ -396,7 +375,6 @@ func newLayer(
desc ocispec.Descriptor, desc ocispec.Descriptor,
blob *blobRef, blob *blobRef,
vr *reader.VerifiableReader, vr *reader.VerifiableReader,
pth passThroughConfig,
) *layer { ) *layer {
return &layer{ return &layer{
resolver: resolver, resolver: resolver,
@ -404,7 +382,6 @@ func newLayer(
blob: blob, blob: blob,
verifiableReader: vr, verifiableReader: vr,
prefetchWaiter: newWaiter(), prefetchWaiter: newWaiter(),
passThrough: pth,
} }
} }
@ -425,7 +402,6 @@ type layer struct {
prefetchOnce sync.Once prefetchOnce sync.Once
backgroundFetchOnce sync.Once backgroundFetchOnce sync.Once
passThrough passThroughConfig
} }
func (l *layer) Info() Info { func (l *layer) Info() Info {
@ -439,7 +415,6 @@ func (l *layer) Info() Info {
FetchedSize: l.blob.FetchedSize(), FetchedSize: l.blob.FetchedSize(),
PrefetchSize: l.prefetchedSize(), PrefetchSize: l.prefetchedSize(),
ReadTime: readTime, ReadTime: readTime,
TOCDigest: l.verifiableReader.Metadata().TOCDigest(),
} }
} }
@ -597,12 +572,7 @@ func (l *layer) backgroundFetch(ctx context.Context) error {
} }
func (l *layerRef) Done() { func (l *layerRef) Done() {
l.done(false) // leave chances to reuse this l.done()
}
func (l *layerRef) Close() error {
l.done(true) // evict this from the cache
return nil
} }
func (l *layer) RootNode(baseInode uint32) (fusefs.InodeEmbedder, error) { func (l *layer) RootNode(baseInode uint32) (fusefs.InodeEmbedder, error) {
@ -612,7 +582,7 @@ func (l *layer) RootNode(baseInode uint32) (fusefs.InodeEmbedder, error) {
if l.r == nil { if l.r == nil {
return nil, fmt.Errorf("layer hasn't been verified yet") return nil, fmt.Errorf("layer hasn't been verified yet")
} }
return newNode(l.desc.Digest, l.r, l.blob, baseInode, l.resolver.overlayOpaqueType, l.passThrough) return newNode(l.desc.Digest, l.r, l.blob, baseInode, l.resolver.overlayOpaqueType)
} }
func (l *layer) ReadAt(p []byte, offset int64, opts ...remote.Option) (int, error) { func (l *layer) ReadAt(p []byte, offset int64, opts ...remote.Option) (int, error) {
@ -626,7 +596,7 @@ func (l *layer) close() error {
return nil return nil
} }
l.closed = true l.closed = true
defer l.blob.done(true) // Close reader first, then close the blob defer l.blob.done() // Close reader first, then close the blob
l.verifiableReader.Close() l.verifiableReader.Close()
if l.r != nil { if l.r != nil {
return l.r.Close() return l.r.Close()
@ -646,7 +616,7 @@ func (l *layer) isClosed() bool {
// to this blob will be discarded. // to this blob will be discarded.
type blobRef struct { type blobRef struct {
remote.Blob remote.Blob
done func(bool) done func()
} }
// layerRef is a reference to the layer in the cache. Calling `Done` or `done` decreases the // layerRef is a reference to the layer in the cache. Calling `Done` or `done` decreases the
@ -654,7 +624,7 @@ type blobRef struct {
// cache, resources bound to this layer will be discarded. // cache, resources bound to this layer will be discarded.
type layerRef struct { type layerRef struct {
*layer *layer
done func(bool) done func()
} }
func newWaiter() *waiter { func newWaiter() *waiter {

View File

@ -30,22 +30,7 @@ import (
) )
func TestLayer(t *testing.T) { func TestLayer(t *testing.T) {
testRunner := &TestRunner{ TestSuiteLayer(t, memorymetadata.NewReader)
TestingT: t,
Runner: func(testingT TestingT, name string, run func(t TestingT)) {
tt, ok := testingT.(*testing.T)
if !ok {
testingT.Fatal("TestingT is not a *testing.T")
return
}
tt.Run(name, func(t *testing.T) {
run(t)
})
},
}
TestSuiteLayer(testRunner, memorymetadata.NewReader)
} }
func TestWaiter(t *testing.T) { func TestWaiter(t *testing.T) {

View File

@ -36,7 +36,7 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/containerd/log" "github.com/containerd/containerd/log"
"github.com/containerd/stargz-snapshotter/estargz" "github.com/containerd/stargz-snapshotter/estargz"
commonmetrics "github.com/containerd/stargz-snapshotter/fs/metrics/common" commonmetrics "github.com/containerd/stargz-snapshotter/fs/metrics/common"
"github.com/containerd/stargz-snapshotter/fs/reader" "github.com/containerd/stargz-snapshotter/fs/reader"
@ -45,21 +45,18 @@ import (
fusefs "github.com/hanwen/go-fuse/v2/fs" fusefs "github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse"
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
const ( const (
blockSize = 4096 blockSize = 4096
physicalBlockSize = 512 whiteoutPrefix = ".wh."
// physicalBlockRatio is the ratio of blockSize to physicalBlockSize. whiteoutOpaqueDir = whiteoutPrefix + whiteoutPrefix + ".opq"
// It can be used to convert from # blockSize-byte blocks to # physicalBlockSize-byte blocks opaqueXattrValue = "y"
physicalBlockRatio = blockSize / physicalBlockSize stateDirName = ".stargz-snapshotter"
whiteoutPrefix = ".wh." statFileMode = syscall.S_IFREG | 0400 // -r--------
whiteoutOpaqueDir = whiteoutPrefix + whiteoutPrefix + ".opq" stateDirMode = syscall.S_IFDIR | 0500 // dr-x------
opaqueXattrValue = "y"
stateDirName = ".stargz-snapshotter"
statFileMode = syscall.S_IFREG | 0400 // -r--------
stateDirMode = syscall.S_IFDIR | 0500 // dr-x------
) )
type OverlayOpaqueType int type OverlayOpaqueType int
@ -76,7 +73,7 @@ var opaqueXattrs = map[OverlayOpaqueType][]string{
OverlayOpaqueUser: {"user.overlay.opaque"}, OverlayOpaqueUser: {"user.overlay.opaque"},
} }
func newNode(layerDgst digest.Digest, r reader.Reader, blob remote.Blob, baseInode uint32, opaque OverlayOpaqueType, pth passThroughConfig) (fusefs.InodeEmbedder, error) { func newNode(layerDgst digest.Digest, r reader.Reader, blob remote.Blob, baseInode uint32, opaque OverlayOpaqueType) (fusefs.InodeEmbedder, error) {
rootID := r.Metadata().RootID() rootID := r.Metadata().RootID()
rootAttr, err := r.Metadata().GetAttr(rootID) rootAttr, err := r.Metadata().GetAttr(rootID)
if err != nil { if err != nil {
@ -84,7 +81,7 @@ func newNode(layerDgst digest.Digest, r reader.Reader, blob remote.Blob, baseIno
} }
opq, ok := opaqueXattrs[opaque] opq, ok := opaqueXattrs[opaque]
if !ok { if !ok {
return nil, fmt.Errorf("unknown overlay opaque type") return nil, fmt.Errorf("Unknown overlay opaque type")
} }
ffs := &fs{ ffs := &fs{
r: r, r: r,
@ -92,7 +89,6 @@ func newNode(layerDgst digest.Digest, r reader.Reader, blob remote.Blob, baseIno
baseInode: baseInode, baseInode: baseInode,
rootID: rootID, rootID: rootID,
opaqueXattrs: opq, opaqueXattrs: opq,
passThrough: pth,
} }
ffs.s = ffs.newState(layerDgst, blob) ffs.s = ffs.newState(layerDgst, blob)
return &node{ return &node{
@ -110,7 +106,6 @@ type fs struct {
baseInode uint32 baseInode uint32
rootID uint32 rootID uint32
opaqueXattrs []string opaqueXattrs []string
passThrough passThroughConfig
} }
func (fs *fs) inodeOfState() uint64 { func (fs *fs) inodeOfState() uint64 {
@ -132,13 +127,11 @@ func (fs *fs) inodeOfID(id uint32) (uint64, error) {
// node is a filesystem inode abstraction. // node is a filesystem inode abstraction.
type node struct { type node struct {
fusefs.Inode fusefs.Inode
fs *fs fs *fs
id uint32 id uint32
attr metadata.Attr attr metadata.Attr
ents []fuse.DirEntry ents []fuse.DirEntry
entsCached bool entsCached bool
entsMu sync.Mutex
} }
func (n *node) isRootNode() bool { func (n *node) isRootNode() bool {
@ -169,13 +162,9 @@ func (n *node) readdir() ([]fuse.DirEntry, syscall.Errno) {
start := time.Now() // set start time start := time.Now() // set start time
defer commonmetrics.MeasureLatencyInMicroseconds(commonmetrics.NodeReaddir, n.fs.layerDigest, start) defer commonmetrics.MeasureLatencyInMicroseconds(commonmetrics.NodeReaddir, n.fs.layerDigest, start)
n.entsMu.Lock()
if n.entsCached { if n.entsCached {
ents := n.ents return n.ents, 0
n.entsMu.Unlock()
return ents, 0
} }
n.entsMu.Unlock()
isRoot := n.isRootNode() isRoot := n.isRootNode()
@ -239,8 +228,6 @@ func (n *node) readdir() ([]fuse.DirEntry, syscall.Errno) {
sort.Slice(ents, func(i, j int) bool { sort.Slice(ents, func(i, j int) bool {
return ents[i].Name < ents[j].Name return ents[i].Name < ents[j].Name
}) })
n.entsMu.Lock()
defer n.entsMu.Unlock()
n.ents, n.entsCached = ents, true // cache it n.ents, n.entsCached = ents, true // cache it
return ents, 0 return ents, 0
@ -292,7 +279,6 @@ func (n *node) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fu
} }
// early return if this entry doesn't exist // early return if this entry doesn't exist
n.entsMu.Lock()
if n.entsCached { if n.entsCached {
var found bool var found bool
for _, e := range n.ents { for _, e := range n.ents {
@ -301,11 +287,9 @@ func (n *node) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fu
} }
} }
if !found { if !found {
n.entsMu.Unlock()
return nil, syscall.ENOENT return nil, syscall.ENOENT
} }
} }
n.entsMu.Unlock()
id, ce, err := n.fs.r.Metadata().GetChild(n.id, name) id, ce, err := n.fs.r.Metadata().GetChild(n.id, name)
if err != nil { if err != nil {
@ -346,26 +330,10 @@ func (n *node) Open(ctx context.Context, flags uint32) (fh fusefs.FileHandle, fu
n.fs.s.report(fmt.Errorf("node.Open: %v", err)) n.fs.s.report(fmt.Errorf("node.Open: %v", err))
return nil, 0, syscall.EIO return nil, 0, syscall.EIO
} }
return &file{
f := &file{
n: n, n: n,
ra: ra, ra: ra,
fd: -1, }, fuse.FOPEN_KEEP_CACHE, 0
}
if n.fs.passThrough.enable {
if getter, ok := ra.(reader.PassthroughFdGetter); ok {
fd, err := getter.GetPassthroughFd(n.fs.passThrough.mergeBufferSize, n.fs.passThrough.mergeWorkerCount)
if err != nil {
n.fs.s.report(fmt.Errorf("passThrough model failed due to node.Open: %v", err))
n.fs.passThrough.enable = false
} else {
f.InitFd(int(fd))
}
}
}
return f, fuse.FOPEN_KEEP_CACHE, 0
} }
var _ = (fusefs.NodeGetattrer)((*node)(nil)) var _ = (fusefs.NodeGetattrer)((*node)(nil))
@ -442,7 +410,6 @@ func (n *node) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno {
type file struct { type file struct {
n *node n *node
ra io.ReaderAt ra io.ReaderAt
fd int
} }
var _ = (fusefs.FileReader)((*file)(nil)) var _ = (fusefs.FileReader)((*file)(nil))
@ -470,20 +437,6 @@ func (f *file) Getattr(ctx context.Context, out *fuse.AttrOut) syscall.Errno {
return 0 return 0
} }
// Implement PassthroughFd to enable go-fuse passthrough
var _ = (fusefs.FilePassthroughFder)((*file)(nil))
func (f *file) PassthroughFd() (int, bool) {
if f.fd <= 0 {
return -1, false
}
return f.fd, true
}
func (f *file) InitFd(fd int) {
f.fd = fd
}
// whiteout is a whiteout abstraction compliant to overlayfs. // whiteout is a whiteout abstraction compliant to overlayfs.
type whiteout struct { type whiteout struct {
fusefs.Inode fusefs.Inode
@ -644,7 +597,7 @@ func (sf *statFile) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Err
// The entries naming is kept to be consistend with the field naming in statJSON. // The entries naming is kept to be consistend with the field naming in statJSON.
func (sf *statFile) logContents() { func (sf *statFile) logContents() {
ctx := context.Background() ctx := context.Background()
log.G(ctx).WithFields(log.Fields{ log.G(ctx).WithFields(logrus.Fields{
"digest": sf.statJSON.Digest, "size": sf.statJSON.Size, "digest": sf.statJSON.Digest, "size": sf.statJSON.Size,
"fetchedSize": sf.statJSON.FetchedSize, "fetchedPercent": sf.statJSON.FetchedPercent, "fetchedSize": sf.statJSON.FetchedSize, "fetchedPercent": sf.statJSON.FetchedPercent,
}).WithError(errors.New(sf.statJSON.Error)).Error("statFile error") }).WithError(errors.New(sf.statJSON.Error)).Error("statFile error")
@ -688,7 +641,10 @@ func entryToAttr(ino uint64, e metadata.Attr, out *fuse.Attr) fusefs.StableAttr
out.Size = uint64(len(e.LinkName)) out.Size = uint64(len(e.LinkName))
} }
out.Blksize = blockSize out.Blksize = blockSize
out.Blocks = (out.Size + uint64(out.Blksize) - 1) / uint64(out.Blksize) * physicalBlockRatio out.Blocks = out.Size / uint64(out.Blksize)
if out.Size%uint64(out.Blksize) > 0 {
out.Blocks++
}
mtime := e.ModTime mtime := e.ModTime
out.SetTimes(nil, &mtime, nil) out.SetTimes(nil, &mtime, nil)
out.Mode = fileModeToSystemMode(e.Mode) out.Mode = fileModeToSystemMode(e.Mode)
@ -765,7 +721,7 @@ func (fs *fs) statFileToAttr(size uint64, out *fuse.Attr) fusefs.StableAttr {
out.Ino = fs.inodeOfStatFile() out.Ino = fs.inodeOfStatFile()
out.Size = size out.Size = size
out.Blksize = blockSize out.Blksize = blockSize
out.Blocks = (out.Size + uint64(out.Blksize) - 1) / uint64(out.Blksize) * physicalBlockRatio out.Blocks = out.Size / uint64(out.Blksize)
out.Nlink = 1 out.Nlink = 1
// Root can read it ("-r-------- root root"). // Root can read it ("-r-------- root root").
@ -832,7 +788,7 @@ func defaultStatfs(stat *fuse.StatfsOut) {
stat.Files = 0 // dummy stat.Files = 0 // dummy
stat.Ffree = 0 stat.Ffree = 0
stat.Bsize = blockSize stat.Bsize = blockSize
stat.NameLen = 255 // Standard max filename length for most filesystems (ext4, etc.) for compatibility stat.NameLen = 1<<32 - 1
stat.Frsize = blockSize stat.Frsize = blockSize
stat.Padding = 0 stat.Padding = 0
stat.Spare = [6]uint32{} stat.Spare = [6]uint32{}

View File

@ -26,22 +26,21 @@ import (
"bytes" "bytes"
"compress/gzip" "compress/gzip"
"context" "context"
"crypto/rand"
"crypto/sha256" "crypto/sha256"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"math" "math/rand"
"math/big"
"net/http" "net/http"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
"syscall" "syscall"
"testing"
"time" "time"
"github.com/containerd/containerd/v2/pkg/reference" "github.com/containerd/containerd/reference"
"github.com/containerd/stargz-snapshotter/cache" "github.com/containerd/stargz-snapshotter/cache"
"github.com/containerd/stargz-snapshotter/estargz" "github.com/containerd/stargz-snapshotter/estargz"
"github.com/containerd/stargz-snapshotter/fs/reader" "github.com/containerd/stargz-snapshotter/fs/reader"
@ -70,86 +69,18 @@ var srcCompressions = map[string]tutil.CompressionFactory{
"externaltoc-gzip-bestspeed": tutil.ExternalTOCGzipCompressionWithLevel(gzip.BestSpeed), "externaltoc-gzip-bestspeed": tutil.ExternalTOCGzipCompressionWithLevel(gzip.BestSpeed),
} }
type layerConfig struct { func TestSuiteLayer(t *testing.T, store metadata.Store) {
name string testPrefetch(t, store)
passThroughConfig passThroughConfig testNodeRead(t, store)
} testNodes(t, store)
// TestingT is the minimal set of testing.T required to run the
// tests defined in TestSuiteLayer. This interface exists to prevent
// leaking the testing package from being exposed outside tests.
type TestingT interface {
Errorf(format string, args ...any)
Fatal(args ...any)
Fatalf(format string, args ...any)
Logf(format string, args ...any)
}
// Runner allows running subtests of TestingT. This exists instead of adding
// a Run method to TestingT interface because the Run implementation of
// testing.T would not satisfy the interface.
type Runner func(t TestingT, name string, fn func(t TestingT))
type TestRunner struct {
TestingT
Runner Runner
}
func (r *TestRunner) Run(name string, run func(*TestRunner)) {
r.Runner(r.TestingT, name, func(t TestingT) {
run(&TestRunner{TestingT: t, Runner: r.Runner})
})
}
func TestSuiteLayer(t *TestRunner, store metadata.Store) {
for _, lc := range []layerConfig{
{
name: "default",
passThroughConfig: passThroughConfig{
enable: false,
},
},
{
name: "passthrough",
passThroughConfig: passThroughConfig{
enable: true,
mergeBufferSize: 419430400,
mergeWorkerCount: 10,
},
},
{
name: "passthorough without concorrency",
passThroughConfig: passThroughConfig{
enable: true,
mergeBufferSize: 419430400,
mergeWorkerCount: 1,
},
},
{
name: "passthorough with small buffer",
passThroughConfig: passThroughConfig{
enable: true,
mergeBufferSize: 1,
mergeWorkerCount: 10,
},
},
} {
testPrefetch(t, store, lc)
testNodeRead(t, store, lc)
testNodes(t, store, lc)
}
} }
var testStateLayerDigest = digest.FromString("dummy") var testStateLayerDigest = digest.FromString("dummy")
func testPrefetch(t *TestRunner, factory metadata.Store, lc layerConfig) { func testPrefetch(t *testing.T, factory metadata.Store) {
randomData, err := tutil.RandomBytes(64000) data64KB := string(tutil.RandomBytes(t, 64000))
if err != nil {
t.Fatalf("failed rand.Read: %v", err)
}
data64KB := string(randomData)
defaultPrefetchSize := int64(10000) defaultPrefetchSize := int64(10000)
landmarkPosition := func(t TestingT, l *layer) int64 { landmarkPosition := func(t *testing.T, l *layer) int64 {
if l.r == nil { if l.r == nil {
t.Fatalf("layer hasn't been verified yet") t.Fatalf("layer hasn't been verified yet")
} }
@ -169,7 +100,7 @@ func testPrefetch(t *TestRunner, factory metadata.Store, lc layerConfig) {
in []tutil.TarEntry in []tutil.TarEntry
wantNum int // number of chunks wanted in the cache wantNum int // number of chunks wanted in the cache
wants []string // filenames to compare wants []string // filenames to compare
prefetchSize func(TestingT, *layer) int64 prefetchSize func(*testing.T, *layer) int64
prioritizedFiles []string prioritizedFiles []string
}{ }{
{ {
@ -247,7 +178,7 @@ func testPrefetch(t *TestRunner, factory metadata.Store, lc layerConfig) {
for _, tt := range tests { for _, tt := range tests {
for srcCompressionName, srcCompression := range srcCompressions { for srcCompressionName, srcCompression := range srcCompressions {
cl := srcCompression() cl := srcCompression()
t.Run("testPrefetch-"+tt.name+"-"+srcCompressionName+"-"+lc.name, func(t *TestRunner) { t.Run("testPrefetch-"+tt.name+"-"+srcCompressionName, func(t *testing.T) {
chunkSize := sampleChunkSize chunkSize := sampleChunkSize
if tt.chunkSize > 0 { if tt.chunkSize > 0 {
chunkSize = tt.chunkSize chunkSize = tt.chunkSize
@ -283,9 +214,8 @@ func testPrefetch(t *TestRunner, factory metadata.Store, lc layerConfig) {
backgroundTaskManager: task.NewBackgroundTaskManager(10, 5*time.Second), backgroundTaskManager: task.NewBackgroundTaskManager(10, 5*time.Second),
}, },
ocispec.Descriptor{Digest: testStateLayerDigest}, ocispec.Descriptor{Digest: testStateLayerDigest},
&blobRef{blob, func(bool) {}}, &blobRef{blob, func() {}},
vr, vr,
lc.passThroughConfig,
) )
if err := l.Verify(dgst); err != nil { if err := l.Verify(dgst); err != nil {
t.Errorf("failed to verify reader: %v", err) t.Errorf("failed to verify reader: %v", err)
@ -374,7 +304,7 @@ func isDup(a, b region) bool {
return b.end >= a.begin return b.end >= a.begin
} }
func newBlob(t TestingT, sr *io.SectionReader) *sampleBlob { func newBlob(t *testing.T, sr *io.SectionReader) *sampleBlob {
return &sampleBlob{ return &sampleBlob{
t: t, t: t,
r: sr, r: sr,
@ -382,7 +312,7 @@ func newBlob(t TestingT, sr *io.SectionReader) *sampleBlob {
} }
type sampleBlob struct { type sampleBlob struct {
t TestingT t *testing.T
r *io.SectionReader r *io.SectionReader
readCalled bool readCalled bool
@ -447,7 +377,7 @@ const (
lastChunkOffset1 = sampleChunkSize * (int64(len(sampleData1)) / sampleChunkSize) lastChunkOffset1 = sampleChunkSize * (int64(len(sampleData1)) / sampleChunkSize)
) )
func testNodeRead(t *TestRunner, factory metadata.Store, lc layerConfig) { func testNodeRead(t *testing.T, factory metadata.Store) {
sizeCond := map[string]int64{ sizeCond := map[string]int64{
"single_chunk": sampleChunkSize - sampleMiddleOffset, "single_chunk": sampleChunkSize - sampleMiddleOffset,
"multi_chunks": sampleChunkSize + sampleMiddleOffset, "multi_chunks": sampleChunkSize + sampleMiddleOffset,
@ -472,7 +402,7 @@ func testNodeRead(t *TestRunner, factory metadata.Store, lc layerConfig) {
for fn, filesize := range fileSizeCond { for fn, filesize := range fileSizeCond {
for _, srcCompression := range srcCompressions { for _, srcCompression := range srcCompressions {
cl := srcCompression() cl := srcCompression()
t.Run(fmt.Sprintf("reading_%s_%s_%s_%s_%s", sn, in, bo, fn, lc.name), func(t *TestRunner) { t.Run(fmt.Sprintf("reading_%s_%s_%s_%s", sn, in, bo, fn), func(t *testing.T) {
if filesize > int64(len(sampleData1)) { if filesize > int64(len(sampleData1)) {
t.Fatal("sample file size is larger than sample data") t.Fatal("sample file size is larger than sample data")
} }
@ -496,7 +426,7 @@ func testNodeRead(t *TestRunner, factory metadata.Store, lc layerConfig) {
} }
// data we get from the file node. // data we get from the file node.
f, closeFn := makeNodeReader(t, []byte(sampleData1)[:filesize], sampleChunkSize, factory, cl, lc) f, closeFn := makeNodeReader(t, []byte(sampleData1)[:filesize], sampleChunkSize, factory, cl)
defer closeFn() defer closeFn()
tmpbuf := make([]byte, size) // fuse library can request bigger than remain tmpbuf := make([]byte, size) // fuse library can request bigger than remain
rr, errno := f.Read(context.Background(), tmpbuf, offset) rr, errno := f.Read(context.Background(), tmpbuf, offset)
@ -527,7 +457,7 @@ func testNodeRead(t *TestRunner, factory metadata.Store, lc layerConfig) {
} }
} }
func makeNodeReader(t TestingT, contents []byte, chunkSize int, factory metadata.Store, cl tutil.Compression, lc layerConfig) (_ *file, closeFn func() error) { func makeNodeReader(t *testing.T, contents []byte, chunkSize int, factory metadata.Store, cl tutil.Compression) (_ *file, closeFn func() error) {
testName := "test" testName := "test"
sr, tocDgst, err := tutil.BuildEStargz( sr, tocDgst, err := tutil.BuildEStargz(
[]tutil.TarEntry{tutil.File(testName, string(contents))}, []tutil.TarEntry{tutil.File(testName, string(contents))},
@ -540,7 +470,7 @@ func makeNodeReader(t TestingT, contents []byte, chunkSize int, factory metadata
if err != nil { if err != nil {
t.Fatalf("failed to create reader: %v", err) t.Fatalf("failed to create reader: %v", err)
} }
rootNode := getRootNode(t, r, OverlayOpaqueAll, tocDgst, cache.NewMemoryCache(), lc) rootNode := getRootNode(t, r, OverlayOpaqueAll, tocDgst, cache.NewMemoryCache())
var eo fuse.EntryOut var eo fuse.EntryOut
inode, errno := rootNode.Lookup(context.Background(), testName, &eo) inode, errno := rootNode.Lookup(context.Background(), testName, &eo)
if errno != 0 { if errno != 0 {
@ -555,20 +485,16 @@ func makeNodeReader(t TestingT, contents []byte, chunkSize int, factory metadata
return f.(*file), r.Close return f.(*file), r.Close
} }
func testNodes(t *TestRunner, factory metadata.Store, lc layerConfig) { func testNodes(t *testing.T, factory metadata.Store) {
for _, o := range []OverlayOpaqueType{OverlayOpaqueAll, OverlayOpaqueTrusted, OverlayOpaqueUser} { for _, o := range []OverlayOpaqueType{OverlayOpaqueAll, OverlayOpaqueTrusted, OverlayOpaqueUser} {
testNodesWithOpaque(t, factory, o, lc) testNodesWithOpaque(t, factory, o)
} }
} }
func testNodesWithOpaque(t *TestRunner, factory metadata.Store, opaque OverlayOpaqueType, lc layerConfig) { func testNodesWithOpaque(t *testing.T, factory metadata.Store, opaque OverlayOpaqueType) {
randomData, err := tutil.RandomBytes(64000) data64KB := string(tutil.RandomBytes(t, 64000))
if err != nil {
t.Fatalf("failed rand.Read: %v", err)
}
data64KB := string(randomData)
hasOpaque := func(entry string) check { hasOpaque := func(entry string) check {
return func(t TestingT, root *node, cc cache.BlobCache, cr *calledReaderAt) { return func(t *testing.T, root *node, cc cache.BlobCache, cr *calledReaderAt) {
for _, k := range opaqueXattrs[opaque] { for _, k := range opaqueXattrs[opaque] {
hasNodeXattrs(entry, k, opaqueXattrValue)(t, root, cc, cr) hasNodeXattrs(entry, k, opaqueXattrValue)(t, root, cc, cr)
} }
@ -774,7 +700,7 @@ func testNodesWithOpaque(t *TestRunner, factory metadata.Store, opaque OverlayOp
for _, tt := range tests { for _, tt := range tests {
for _, srcCompression := range srcCompressions { for _, srcCompression := range srcCompressions {
cl := srcCompression() cl := srcCompression()
t.Run(tt.name+"-"+lc.name, func(t *TestRunner) { t.Run(tt.name, func(t *testing.T) {
opts := []tutil.BuildEStargzOption{ opts := []tutil.BuildEStargzOption{
tutil.WithEStargzOptions(estargz.WithCompression(cl)), tutil.WithEStargzOptions(estargz.WithCompression(cl)),
} }
@ -796,7 +722,7 @@ func testNodesWithOpaque(t *TestRunner, factory metadata.Store, opaque OverlayOp
} }
defer r.Close() defer r.Close()
mcache := cache.NewMemoryCache() mcache := cache.NewMemoryCache()
rootNode := getRootNode(t, r, opaque, tocDgst, mcache, lc) rootNode := getRootNode(t, r, opaque, tocDgst, mcache)
for _, want := range tt.want { for _, want := range tt.want {
want(t, rootNode, mcache, testR) want(t, rootNode, mcache, testR)
} }
@ -805,7 +731,7 @@ func testNodesWithOpaque(t *TestRunner, factory metadata.Store, opaque OverlayOp
} }
} }
func getRootNode(t TestingT, r metadata.Reader, opaque OverlayOpaqueType, tocDgst digest.Digest, cc cache.BlobCache, lc layerConfig) *node { func getRootNode(t *testing.T, r metadata.Reader, opaque OverlayOpaqueType, tocDgst digest.Digest, cc cache.BlobCache) *node {
vr, err := reader.NewReader(r, cc, digest.FromString("")) vr, err := reader.NewReader(r, cc, digest.FromString(""))
if err != nil { if err != nil {
t.Fatalf("failed to create reader: %v", err) t.Fatalf("failed to create reader: %v", err)
@ -814,7 +740,7 @@ func getRootNode(t TestingT, r metadata.Reader, opaque OverlayOpaqueType, tocDgs
if err != nil { if err != nil {
t.Fatalf("failed to verify reader: %v", err) t.Fatalf("failed to verify reader: %v", err)
} }
rootNode, err := newNode(testStateLayerDigest, rr, &testBlobState{10, 5}, 100, opaque, lc.passThroughConfig) rootNode, err := newNode(testStateLayerDigest, rr, &testBlobState{10, 5}, 100, opaque)
if err != nil { if err != nil {
t.Fatalf("failed to get root node: %v", err) t.Fatalf("failed to get root node: %v", err)
} }
@ -839,10 +765,10 @@ func (tb *testBlobState) Refresh(ctx context.Context, host source.RegistryHosts,
} }
func (tb *testBlobState) Close() error { return nil } func (tb *testBlobState) Close() error { return nil }
type check func(TestingT, *node, cache.BlobCache, *calledReaderAt) type check func(*testing.T, *node, cache.BlobCache, *calledReaderAt)
func fileNotExist(file string) check { func fileNotExist(file string) check {
return func(t TestingT, root *node, cc cache.BlobCache, cr *calledReaderAt) { return func(t *testing.T, root *node, cc cache.BlobCache, cr *calledReaderAt) {
if _, _, err := getDirentAndNode(t, root, file); err == nil { if _, _, err := getDirentAndNode(t, root, file); err == nil {
t.Errorf("Node %q exists", file) t.Errorf("Node %q exists", file)
} }
@ -850,7 +776,7 @@ func fileNotExist(file string) check {
} }
func hasFileDigest(filename string, digest string) check { func hasFileDigest(filename string, digest string) check {
return func(t TestingT, root *node, cc cache.BlobCache, cr *calledReaderAt) { return func(t *testing.T, root *node, cc cache.BlobCache, cr *calledReaderAt) {
_, n, err := getDirentAndNode(t, root, filename) _, n, err := getDirentAndNode(t, root, filename)
if err != nil { if err != nil {
t.Fatalf("failed to get node %q: %v", filename, err) t.Fatalf("failed to get node %q: %v", filename, err)
@ -879,7 +805,7 @@ func hasFileDigest(filename string, digest string) check {
} }
func hasSize(name string, size int) check { func hasSize(name string, size int) check {
return func(t TestingT, root *node, cc cache.BlobCache, cr *calledReaderAt) { return func(t *testing.T, root *node, cc cache.BlobCache, cr *calledReaderAt) {
_, n, err := getDirentAndNode(t, root, name) _, n, err := getDirentAndNode(t, root, name)
if err != nil { if err != nil {
t.Fatalf("failed to get node %q: %v", name, err) t.Fatalf("failed to get node %q: %v", name, err)
@ -888,14 +814,14 @@ func hasSize(name string, size int) check {
if errno := n.Operations().(fusefs.NodeGetattrer).Getattr(context.Background(), nil, &ao); errno != 0 { if errno := n.Operations().(fusefs.NodeGetattrer).Getattr(context.Background(), nil, &ao); errno != 0 {
t.Fatalf("failed to get attributes of node %q: %v", name, errno) t.Fatalf("failed to get attributes of node %q: %v", name, errno)
} }
if ao.Size != uint64(size) { if ao.Attr.Size != uint64(size) {
t.Fatalf("got size = %d, want %d", ao.Size, size) t.Fatalf("got size = %d, want %d", ao.Attr.Size, size)
} }
} }
} }
func hasExtraMode(name string, mode os.FileMode) check { func hasExtraMode(name string, mode os.FileMode) check {
return func(t TestingT, root *node, cc cache.BlobCache, cr *calledReaderAt) { return func(t *testing.T, root *node, cc cache.BlobCache, cr *calledReaderAt) {
_, n, err := getDirentAndNode(t, root, name) _, n, err := getDirentAndNode(t, root, name)
if err != nil { if err != nil {
t.Fatalf("failed to get node %q: %v", name, err) t.Fatalf("failed to get node %q: %v", name, err)
@ -914,7 +840,7 @@ func hasExtraMode(name string, mode os.FileMode) check {
} }
func hasValidWhiteout(name string) check { func hasValidWhiteout(name string) check {
return func(t TestingT, root *node, cc cache.BlobCache, cr *calledReaderAt) { return func(t *testing.T, root *node, cc cache.BlobCache, cr *calledReaderAt) {
ent, n, err := getDirentAndNode(t, root, name) ent, n, err := getDirentAndNode(t, root, name)
if err != nil { if err != nil {
t.Fatalf("failed to get node %q: %v", name, err) t.Fatalf("failed to get node %q: %v", name, err)
@ -950,7 +876,7 @@ func hasValidWhiteout(name string) check {
} }
func hasNodeXattrs(entry, name, value string) check { func hasNodeXattrs(entry, name, value string) check {
return func(t TestingT, root *node, cc cache.BlobCache, cr *calledReaderAt) { return func(t *testing.T, root *node, cc cache.BlobCache, cr *calledReaderAt) {
_, n, err := getDirentAndNode(t, root, entry) _, n, err := getDirentAndNode(t, root, entry)
if err != nil { if err != nil {
t.Fatalf("failed to get node %q: %v", entry, err) t.Fatalf("failed to get node %q: %v", entry, err)
@ -991,7 +917,7 @@ func hasNodeXattrs(entry, name, value string) check {
} }
} }
func hasEntry(t TestingT, name string, ents fusefs.DirStream) (fuse.DirEntry, bool) { func hasEntry(t *testing.T, name string, ents fusefs.DirStream) (fuse.DirEntry, bool) {
for ents.HasNext() { for ents.HasNext() {
de, errno := ents.Next() de, errno := ents.Next()
if errno != 0 { if errno != 0 {
@ -1004,8 +930,8 @@ func hasEntry(t TestingT, name string, ents fusefs.DirStream) (fuse.DirEntry, bo
return fuse.DirEntry{}, false return fuse.DirEntry{}, false
} }
func hasStateFile(t TestingT, id string) check { func hasStateFile(t *testing.T, id string) check {
return func(t TestingT, root *node, cc cache.BlobCache, cr *calledReaderAt) { return func(t *testing.T, root *node, cc cache.BlobCache, cr *calledReaderAt) {
// Check the state dir is hidden on OpenDir for "/" // Check the state dir is hidden on OpenDir for "/"
ents, errno := root.Readdir(context.Background()) ents, errno := root.Readdir(context.Background())
@ -1053,11 +979,8 @@ func hasStateFile(t TestingT, id string) check {
} }
// wanted data // wanted data
b, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64)) rand.Seed(time.Now().UnixNano())
if err != nil { wantErr := fmt.Errorf("test-%d", rand.Int63())
panic(err)
}
wantErr := fmt.Errorf("test-%d", b.Int64())
// report the data // report the data
root.fs.s.report(wantErr) root.fs.s.report(wantErr)
@ -1102,7 +1025,7 @@ func hasStateFile(t TestingT, id string) check {
// getDirentAndNode gets dirent and node at the specified path at once and makes // getDirentAndNode gets dirent and node at the specified path at once and makes
// sure that the both of them exist. // sure that the both of them exist.
func getDirentAndNode(t TestingT, root *node, path string) (ent fuse.DirEntry, n *fusefs.Inode, err error) { func getDirentAndNode(t *testing.T, root *node, path string) (ent fuse.DirEntry, n *fusefs.Inode, err error) {
dir, base := filepath.Split(filepath.Clean(path)) dir, base := filepath.Split(filepath.Clean(path))
// get the target's parent directory. // get the target's parent directory.
@ -1178,7 +1101,7 @@ type chunkInfo struct {
} }
func hasFileContentsWithPreCached(name string, off int64, contents string, extra ...chunkInfo) check { func hasFileContentsWithPreCached(name string, off int64, contents string, extra ...chunkInfo) check {
return func(t TestingT, root *node, cc cache.BlobCache, cr *calledReaderAt) { return func(t *testing.T, root *node, cc cache.BlobCache, cr *calledReaderAt) {
buf := readFile(t, root, name, int64(len(contents)), off) buf := readFile(t, root, name, int64(len(contents)), off)
if len(buf) != len(contents) { if len(buf) != len(contents) {
t.Fatalf("failed to read contents %q (off:%d, want:%q) got %q", name, off, longBytesView([]byte(contents)), longBytesView(buf)) t.Fatalf("failed to read contents %q (off:%d, want:%q) got %q", name, off, longBytesView([]byte(contents)), longBytesView(buf))
@ -1200,7 +1123,7 @@ func hasFileContentsWithPreCached(name string, off int64, contents string, extra
} }
func hasFileContentsOffset(name string, off int64, contents string, fromCache bool) check { func hasFileContentsOffset(name string, off int64, contents string, fromCache bool) check {
return func(t TestingT, root *node, cc cache.BlobCache, cr *calledReaderAt) { return func(t *testing.T, root *node, cc cache.BlobCache, cr *calledReaderAt) {
cr.called = nil // reset test cr.called = nil // reset test
buf := readFile(t, root, name, int64(len(contents)), off) buf := readFile(t, root, name, int64(len(contents)), off)
if len(buf) != len(contents) { if len(buf) != len(contents) {
@ -1222,7 +1145,7 @@ func hasFileContentsOffset(name string, off int64, contents string, fromCache bo
} }
} }
func readFile(t TestingT, root *node, filename string, size, off int64) []byte { func readFile(t *testing.T, root *node, filename string, size, off int64) []byte {
_, n, err := getDirentAndNode(t, root, filename) _, n, err := getDirentAndNode(t, root, filename)
if err != nil { if err != nil {
t.Fatalf("failed to get node %q: %v", filename, err) t.Fatalf("failed to get node %q: %v", filename, err)

View File

@ -21,9 +21,10 @@ import (
"sync" "sync"
"time" "time"
"github.com/containerd/log" "github.com/containerd/containerd/log"
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/sirupsen/logrus"
) )
const ( const (
@ -129,7 +130,7 @@ var (
) )
var register sync.Once var register sync.Once
var logLevel = log.DebugLevel var logLevel logrus.Level = logrus.DebugLevel
// sinceInMilliseconds gets the time since the specified start in milliseconds. // sinceInMilliseconds gets the time since the specified start in milliseconds.
// The division by 1e6 is made to have the milliseconds value as floating point number, since the native method // The division by 1e6 is made to have the milliseconds value as floating point number, since the native method
@ -146,7 +147,7 @@ func sinceInMicroseconds(start time.Time) float64 {
} }
// Register registers metrics. This is always called only once. // Register registers metrics. This is always called only once.
func Register(l log.Level) { func Register(l logrus.Level) {
register.Do(func() { register.Do(func() {
logLevel = l logLevel = l
prometheus.MustRegister(operationLatencyMilliseconds) prometheus.MustRegister(operationLatencyMilliseconds)

View File

@ -27,12 +27,10 @@ import (
"bytes" "bytes"
"context" "context"
"crypto/sha256" "crypto/sha256"
"errors"
"fmt" "fmt"
"io" "io"
"os" "os"
"runtime" "runtime"
"sort"
"sync" "sync"
"time" "time"
@ -40,6 +38,7 @@ import (
"github.com/containerd/stargz-snapshotter/estargz" "github.com/containerd/stargz-snapshotter/estargz"
commonmetrics "github.com/containerd/stargz-snapshotter/fs/metrics/common" commonmetrics "github.com/containerd/stargz-snapshotter/fs/metrics/common"
"github.com/containerd/stargz-snapshotter/metadata" "github.com/containerd/stargz-snapshotter/metadata"
"github.com/hashicorp/go-multierror"
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
"golang.org/x/sync/semaphore" "golang.org/x/sync/semaphore"
@ -54,10 +53,6 @@ type Reader interface {
LastOnDemandReadTime() time.Time LastOnDemandReadTime() time.Time
} }
type PassthroughFdGetter interface {
GetPassthroughFd(mergeBufferSize int64, mergeWorkerCount int) (uintptr, error)
}
// VerifiableReader produces a Reader with a given verifier. // VerifiableReader produces a Reader with a given verifier.
type VerifiableReader struct { type VerifiableReader struct {
r *reader r *reader
@ -391,21 +386,20 @@ func (gr *reader) OpenFile(id uint32) (io.ReaderAt, error) {
}, nil }, nil
} }
func (gr *reader) Close() error { func (gr *reader) Close() (retErr error) {
gr.closedMu.Lock() gr.closedMu.Lock()
defer gr.closedMu.Unlock() defer gr.closedMu.Unlock()
if gr.closed { if gr.closed {
return nil return nil
} }
gr.closed = true gr.closed = true
var errs []error
if err := gr.cache.Close(); err != nil { if err := gr.cache.Close(); err != nil {
errs = append(errs, err) retErr = multierror.Append(retErr, err)
} }
if err := gr.r.Close(); err != nil { if err := gr.r.Close(); err != nil {
errs = append(errs, err) retErr = multierror.Append(retErr, err)
} }
return errors.Join(errs...) return
} }
func (gr *reader) isClosed() bool { func (gr *reader) isClosed() bool {
@ -496,316 +490,18 @@ func (sf *file) ReadAt(p []byte, offset int64) (int, error) {
return nr, nil return nr, nil
} }
type chunkData struct { func (gr *reader) verifyAndCache(entryID uint32, ip []byte, chunkDigestStr string, cacheID string) error {
offset int64
size int64
digestStr string
bufferPos int64
}
func (sf *file) GetPassthroughFd(mergeBufferSize int64, mergeWorkerCount int) (uintptr, error) {
var (
offset int64
firstChunkOffset int64
totalSize int64
hasLargeChunk bool
)
var chunks []chunkData
for {
chunkOffset, chunkSize, digestStr, ok := sf.fr.ChunkEntryForOffset(offset)
if !ok {
break
}
// Check if any chunk size exceeds merge buffer size to avoid bounds out of range
if chunkSize > mergeBufferSize {
hasLargeChunk = true
}
chunks = append(chunks, chunkData{
offset: chunkOffset,
size: chunkSize,
digestStr: digestStr,
})
totalSize += chunkSize
offset = chunkOffset + chunkSize
}
id := genID(sf.id, firstChunkOffset, totalSize)
// cache.PassThrough() is necessary to take over files
r, err := sf.gr.cache.Get(id, cache.PassThrough())
if err != nil {
if hasLargeChunk {
if err := sf.prefetchEntireFileSequential(id); err != nil {
return 0, err
}
} else {
if err := sf.prefetchEntireFile(id, chunks, totalSize, mergeBufferSize, mergeWorkerCount); err != nil {
return 0, err
}
}
// just retry once to avoid exception stuck
r, err = sf.gr.cache.Get(id, cache.PassThrough())
if err != nil {
return 0, err
}
}
readerAt := r.GetReaderAt()
file, ok := readerAt.(*os.File)
if !ok {
r.Close()
return 0, fmt.Errorf("the cached ReaderAt is not of type *os.File, fd obtain failed")
}
fd := file.Fd()
r.Close()
return fd, nil
}
// prefetchEntireFileSequential uses the legacy sequential approach for processing chunks
// when chunk size exceeds merge buffer size to avoid slice bounds out of range panic
func (sf *file) prefetchEntireFileSequential(entireCacheID string) error {
w, err := sf.gr.cache.Add(entireCacheID)
if err != nil {
return fmt.Errorf("failed to create cache writer: %w", err)
}
defer w.Close()
var offset int64
for {
chunkOffset, chunkSize, chunkDigestStr, ok := sf.fr.ChunkEntryForOffset(offset)
if !ok {
break
}
id := genID(sf.id, chunkOffset, chunkSize)
b := sf.gr.bufPool.Get().(*bytes.Buffer)
b.Reset()
b.Grow(int(chunkSize))
ip := b.Bytes()[:chunkSize]
if r, err := sf.gr.cache.Get(id); err == nil {
n, err := r.ReadAt(ip, 0)
if (err == nil || err == io.EOF) && int64(n) == chunkSize {
if _, err := w.Write(ip[:n]); err != nil {
r.Close()
sf.gr.putBuffer(b)
w.Abort()
return fmt.Errorf("failed to write cached data: %w", err)
}
offset = chunkOffset + int64(n)
r.Close()
sf.gr.putBuffer(b)
continue
}
r.Close()
}
if _, err := sf.fr.ReadAt(ip, chunkOffset); err != nil && err != io.EOF {
sf.gr.putBuffer(b)
w.Abort()
return fmt.Errorf("failed to read data: %w", err)
}
if err := sf.gr.verifyOneChunk(sf.id, ip, chunkDigestStr); err != nil {
sf.gr.putBuffer(b)
w.Abort()
return err
}
if _, err := w.Write(ip); err != nil {
sf.gr.putBuffer(b)
w.Abort()
return fmt.Errorf("failed to write fetched data: %w", err)
}
offset = chunkOffset + chunkSize
sf.gr.putBuffer(b)
}
return w.Commit()
}
type batchWorkerArgs struct {
workerID int
chunks []chunkData
buffer []byte
workerCount int
readInfos []chunkReadInfo
}
func (sf *file) prefetchEntireFile(entireCacheID string, chunks []chunkData, totalSize int64, bufferSize int64, workerCount int) error {
w, err := sf.gr.cache.Add(entireCacheID)
if err != nil {
return fmt.Errorf("failed to create cache writer: %w", err)
}
defer w.Close()
batchCount := (totalSize + bufferSize - 1) / bufferSize
for batchIdx := int64(0); batchIdx < batchCount; batchIdx++ {
batchStart := batchIdx * bufferSize
batchEnd := (batchIdx + 1) * bufferSize
if batchEnd > totalSize {
batchEnd = totalSize
}
var batchChunks []chunkData
var batchOffset int64
for i := range chunks {
chunkStart := chunks[i].offset
chunkEnd := chunkStart + chunks[i].size
if chunkEnd <= batchStart {
continue
}
if chunkStart >= batchEnd {
break
}
chunks[i].bufferPos = batchOffset
batchOffset += chunks[i].size
batchChunks = append(batchChunks, chunks[i])
}
batchSize := batchEnd - batchStart
buffer := make([]byte, batchSize)
eg := errgroup.Group{}
allReadInfos := make([][]chunkReadInfo, workerCount)
for i := 0; i < workerCount && i < len(batchChunks); i++ {
workerID := i
args := &batchWorkerArgs{
workerID: workerID,
chunks: batchChunks,
buffer: buffer,
workerCount: workerCount,
}
eg.Go(func() error {
err := sf.processBatchChunks(args)
if err == nil && len(args.readInfos) > 0 {
allReadInfos[args.workerID] = args.readInfos
}
return err
})
}
if err := eg.Wait(); err != nil {
w.Abort()
return err
}
var mergedReadInfos []chunkReadInfo
for _, infos := range allReadInfos {
mergedReadInfos = append(mergedReadInfos, infos...)
}
if err := sf.checkHoles(mergedReadInfos, batchSize); err != nil {
w.Abort()
return fmt.Errorf("hole check failed: %w", err)
}
n, err := w.Write(buffer)
if err != nil {
w.Abort()
return fmt.Errorf("failed to write batch data: %w", err)
}
if int64(n) != batchSize {
w.Abort()
return fmt.Errorf("incomplete write: expected %d bytes, wrote %d bytes", batchSize, n)
}
}
return w.Commit()
}
type chunkReadInfo struct {
offset int64
size int64
}
func (sf *file) checkHoles(readInfos []chunkReadInfo, totalSize int64) error {
if len(readInfos) == 0 {
return nil
}
sort.Slice(readInfos, func(i, j int) bool {
return readInfos[i].offset < readInfos[j].offset
})
end := readInfos[0].offset
for _, info := range readInfos {
if info.offset < end {
return fmt.Errorf("overlapping read detected: previous end %d, current start %d", end, info.offset)
} else if info.offset > end {
return fmt.Errorf("hole detected in read: previous end %d, current start %d", end, info.offset)
}
end = info.offset + info.size
}
if end != totalSize {
return fmt.Errorf("incomplete read: expected total size %d, actual end %d", totalSize, end)
}
return nil
}
func (sf *file) processBatchChunks(args *batchWorkerArgs) error {
var readInfos []chunkReadInfo
for chunkIdx := args.workerID; chunkIdx < len(args.chunks); chunkIdx += args.workerCount {
chunk := args.chunks[chunkIdx]
bufStart := args.buffer[chunk.bufferPos : chunk.bufferPos+chunk.size]
id := genID(sf.id, chunk.offset, chunk.size)
if r, err := sf.gr.cache.Get(id); err == nil {
n, err := r.ReadAt(bufStart, 0)
r.Close()
if err == nil || err == io.EOF {
if int64(n) == chunk.size {
readInfos = append(readInfos, chunkReadInfo{
offset: chunk.bufferPos,
size: int64(n),
})
continue
}
}
}
n, err := sf.fr.ReadAt(bufStart, chunk.offset)
if err != nil && err != io.EOF {
return fmt.Errorf("failed to read data at offset %d: %w", chunk.offset, err)
}
readInfos = append(readInfos, chunkReadInfo{
offset: chunk.bufferPos,
size: int64(n),
})
if err := sf.gr.verifyOneChunk(sf.id, bufStart, chunk.digestStr); err != nil {
return fmt.Errorf("chunk verification failed at offset %d: %w", chunk.offset, err)
}
}
args.readInfos = readInfos
return nil
}
func (gr *reader) verifyOneChunk(entryID uint32, ip []byte, chunkDigestStr string) error {
// We can end up doing on demand registry fetch when aligning the chunk // We can end up doing on demand registry fetch when aligning the chunk
commonmetrics.IncOperationCount(commonmetrics.OnDemandRemoteRegistryFetchCount, gr.layerSha) commonmetrics.IncOperationCount(commonmetrics.OnDemandRemoteRegistryFetchCount, gr.layerSha) // increment the number of on demand file fetches from remote registry
commonmetrics.AddBytesCount(commonmetrics.OnDemandBytesFetched, gr.layerSha, int64(len(ip))) commonmetrics.AddBytesCount(commonmetrics.OnDemandBytesFetched, gr.layerSha, int64(len(ip))) // record total bytes fetched
gr.setLastReadTime(time.Now()) gr.setLastReadTime(time.Now())
// Verify this chunk
if err := gr.verifyChunk(entryID, ip, chunkDigestStr); err != nil { if err := gr.verifyChunk(entryID, ip, chunkDigestStr); err != nil {
return fmt.Errorf("invalid chunk: %w", err) return fmt.Errorf("invalid chunk: %w", err)
} }
return nil
}
func (gr *reader) cacheData(ip []byte, cacheID string) { // Cache this chunk
if w, err := gr.cache.Add(cacheID); err == nil { if w, err := gr.cache.Add(cacheID); err == nil {
if cn, err := w.Write(ip); err != nil || cn != len(ip) { if cn, err := w.Write(ip); err != nil || cn != len(ip) {
w.Abort() w.Abort()
@ -814,13 +510,7 @@ func (gr *reader) cacheData(ip []byte, cacheID string) {
} }
w.Close() w.Close()
} }
}
func (gr *reader) verifyAndCache(entryID uint32, ip []byte, chunkDigestStr string, cacheID string) error {
if err := gr.verifyOneChunk(entryID, ip, chunkDigestStr); err != nil {
return err
}
gr.cacheData(ip, cacheID)
return nil return nil
} }

View File

@ -29,20 +29,5 @@ import (
) )
func TestReader(t *testing.T) { func TestReader(t *testing.T) {
testRunner := &TestRunner{ TestSuiteReader(t, memorymetadata.NewReader)
TestingT: t,
Runner: func(testingT TestingT, name string, run func(t TestingT)) {
tt, ok := testingT.(*testing.T)
if !ok {
testingT.Fatal("TestingT is not a *testing.T")
return
}
tt.Run(name, func(t *testing.T) {
run(t)
})
},
}
TestSuiteReader(testRunner, memorymetadata.NewReader)
} }

View File

@ -32,11 +32,13 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"sync" "sync"
"testing"
"time" "time"
"github.com/containerd/stargz-snapshotter/cache" "github.com/containerd/stargz-snapshotter/cache"
"github.com/containerd/stargz-snapshotter/estargz" "github.com/containerd/stargz-snapshotter/estargz"
"github.com/containerd/stargz-snapshotter/metadata" "github.com/containerd/stargz-snapshotter/metadata"
"github.com/containerd/stargz-snapshotter/util/testutil"
tutil "github.com/containerd/stargz-snapshotter/util/testutil" tutil "github.com/containerd/stargz-snapshotter/util/testutil"
"github.com/klauspost/compress/zstd" "github.com/klauspost/compress/zstd"
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
@ -58,47 +60,14 @@ var srcCompressions = map[string]tutil.CompressionFactory{
"externaltoc-gzip-bestspeed": tutil.ExternalTOCGzipCompressionWithLevel(gzip.BestSpeed), "externaltoc-gzip-bestspeed": tutil.ExternalTOCGzipCompressionWithLevel(gzip.BestSpeed),
} }
// MockReadAtOutput defines the default output size for mocked read operations func TestSuiteReader(t *testing.T, store metadata.Store) {
var (
MockReadAtOutput = 4194304
)
// TestingT is the minimal set of testing.T required to run the
// tests defined in TestSuiteReader. This interface exists to prevent
// leaking the testing package from being exposed outside tests.
type TestingT interface {
Cleanup(func())
Errorf(format string, args ...any)
Fatal(args ...any)
Fatalf(format string, args ...any)
Logf(format string, args ...any)
}
// Runner allows running subtests of TestingT. This exists instead of adding
// a Run method to TestingT interface because the Run implementation of
// testing.T would not satisfy the interface.
type Runner func(t TestingT, name string, fn func(t TestingT))
type TestRunner struct {
TestingT
Runner Runner
}
func (r *TestRunner) Run(name string, run func(*TestRunner)) {
r.Runner(r.TestingT, name, func(t TestingT) {
run(&TestRunner{TestingT: t, Runner: r.Runner})
})
}
func TestSuiteReader(t *TestRunner, store metadata.Store) {
testFileReadAt(t, store) testFileReadAt(t, store)
testCacheVerify(t, store) testCacheVerify(t, store)
testFailReader(t, store) testFailReader(t, store)
testPreReader(t, store) testPreReader(t, store)
testProcessBatchChunks(t)
} }
func testFileReadAt(t *TestRunner, factory metadata.Store) { func testFileReadAt(t *testing.T, factory metadata.Store) {
sizeCond := map[string]int64{ sizeCond := map[string]int64{
"single_chunk": sampleChunkSize - sampleMiddleOffset, "single_chunk": sampleChunkSize - sampleMiddleOffset,
"multi_chunks": sampleChunkSize + sampleMiddleOffset, "multi_chunks": sampleChunkSize + sampleMiddleOffset,
@ -135,7 +104,7 @@ func testFileReadAt(t *TestRunner, factory metadata.Store) {
for cc, cacheExcept := range cacheCond { for cc, cacheExcept := range cacheCond {
for srcCompressionName, srcCompression := range srcCompressions { for srcCompressionName, srcCompression := range srcCompressions {
srcCompression := srcCompression() srcCompression := srcCompression()
t.Run(fmt.Sprintf("reading_%s_%s_%s_%s_%s_%s", sn, in, bo, fn, cc, srcCompressionName), func(t *TestRunner) { t.Run(fmt.Sprintf("reading_%s_%s_%s_%s_%s_%s", sn, in, bo, fn, cc, srcCompressionName), func(t *testing.T) {
if filesize > int64(len(sampleData1)) { if filesize > int64(len(sampleData1)) {
t.Fatal("sample file size is larger than sample data") t.Fatal("sample file size is larger than sample data")
} }
@ -225,7 +194,7 @@ func testFileReadAt(t *TestRunner, factory metadata.Store) {
} }
} }
func newExceptFile(t TestingT, fr metadata.File, except ...region) metadata.File { func newExceptFile(t *testing.T, fr metadata.File, except ...region) metadata.File {
er := exceptFile{fr: fr, t: t} er := exceptFile{fr: fr, t: t}
er.except = map[region]bool{} er.except = map[region]bool{}
for _, reg := range except { for _, reg := range except {
@ -237,7 +206,7 @@ func newExceptFile(t TestingT, fr metadata.File, except ...region) metadata.File
type exceptFile struct { type exceptFile struct {
fr metadata.File fr metadata.File
except map[region]bool except map[region]bool
t TestingT t *testing.T
} }
func (er *exceptFile) ReadAt(p []byte, offset int64) (int, error) { func (er *exceptFile) ReadAt(p []byte, offset int64) (int, error) {
@ -251,11 +220,11 @@ func (er *exceptFile) ChunkEntryForOffset(offset int64) (off int64, size int64,
return er.fr.ChunkEntryForOffset(offset) return er.fr.ChunkEntryForOffset(offset)
} }
func makeFile(t TestingT, contents []byte, chunkSize int, factory metadata.Store, comp tutil.Compression) (*file, func() error) { func makeFile(t *testing.T, contents []byte, chunkSize int, factory metadata.Store, comp tutil.Compression) (*file, func() error) {
testName := "test" testName := "test"
sr, dgst, err := tutil.BuildEStargz([]tutil.TarEntry{ sr, dgst, err := testutil.BuildEStargz([]testutil.TarEntry{
tutil.File(testName, string(contents)), testutil.File(testName, string(contents)),
}, tutil.WithEStargzOptions(estargz.WithChunkSize(chunkSize), estargz.WithCompression(comp))) }, testutil.WithEStargzOptions(estargz.WithChunkSize(chunkSize), estargz.WithCompression(comp)))
if err != nil { if err != nil {
t.Fatalf("failed to build sample estargz") t.Fatalf("failed to build sample estargz")
} }
@ -291,7 +260,7 @@ func makeFile(t TestingT, contents []byte, chunkSize int, factory metadata.Store
return f, vr.Close return f, vr.Close
} }
func testCacheVerify(t *TestRunner, factory metadata.Store) { func testCacheVerify(t *testing.T, factory metadata.Store) {
for _, skipVerify := range [2]bool{true, false} { for _, skipVerify := range [2]bool{true, false} {
for _, invalidChunkBeforeVerify := range [2]bool{true, false} { for _, invalidChunkBeforeVerify := range [2]bool{true, false} {
for _, invalidChunkAfterVerify := range [2]bool{true, false} { for _, invalidChunkAfterVerify := range [2]bool{true, false} {
@ -299,11 +268,11 @@ func testCacheVerify(t *TestRunner, factory metadata.Store) {
srcCompression := srcCompression() srcCompression := srcCompression()
name := fmt.Sprintf("test_cache_verify_%v_%v_%v_%v", name := fmt.Sprintf("test_cache_verify_%v_%v_%v_%v",
skipVerify, invalidChunkBeforeVerify, invalidChunkAfterVerify, srcCompressionName) skipVerify, invalidChunkBeforeVerify, invalidChunkAfterVerify, srcCompressionName)
t.Run(name, func(t *TestRunner) { t.Run(name, func(t *testing.T) {
sr, tocDgst, err := tutil.BuildEStargz([]tutil.TarEntry{ sr, tocDgst, err := testutil.BuildEStargz([]testutil.TarEntry{
tutil.File("a", sampleData1+"a"), testutil.File("a", sampleData1+"a"),
tutil.File("b", sampleData1+"b"), testutil.File("b", sampleData1+"b"),
}, tutil.WithEStargzOptions(estargz.WithChunkSize(sampleChunkSize), estargz.WithCompression(srcCompression))) }, testutil.WithEStargzOptions(estargz.WithChunkSize(sampleChunkSize), estargz.WithCompression(srcCompression)))
if err != nil { if err != nil {
t.Fatalf("failed to build sample estargz") t.Fatalf("failed to build sample estargz")
} }
@ -335,8 +304,10 @@ func testCacheVerify(t *TestRunner, factory metadata.Store) {
if err != nil { if err != nil {
t.Fatalf("failed to make new reader: %v", err) t.Fatalf("failed to make new reader: %v", err)
} }
vr.verifier = verifier.verifier if verifier != nil {
vr.r.verifier = verifier.verifier vr.verifier = verifier.verifier
vr.r.verifier = verifier.verifier
}
off2id, id2path, err := prepareMap(vr.Metadata(), vr.Metadata().RootID(), "") off2id, id2path, err := prepareMap(vr.Metadata(), vr.Metadata().RootID(), "")
if err != nil || off2id == nil || id2path == nil { if err != nil || off2id == nil || id2path == nil {
@ -509,16 +480,16 @@ func prepareMap(mr metadata.Reader, id uint32, p string) (off2id map[int64]uint3
return off2id, id2path, nil return off2id, id2path, nil
} }
func testFailReader(t *TestRunner, factory metadata.Store) { func testFailReader(t *testing.T, factory metadata.Store) {
testFileName := "test" testFileName := "test"
for srcCompressionName, srcCompression := range srcCompressions { for srcCompressionName, srcCompression := range srcCompressions {
srcCompression := srcCompression() srcCompression := srcCompression()
t.Run(fmt.Sprintf("%v", srcCompressionName), func(t *TestRunner) { t.Run(fmt.Sprintf("%v", srcCompressionName), func(t *testing.T) {
for _, rs := range []bool{true, false} { for _, rs := range []bool{true, false} {
for _, vs := range []bool{true, false} { for _, vs := range []bool{true, false} {
stargzFile, tocDigest, err := tutil.BuildEStargz([]tutil.TarEntry{ stargzFile, tocDigest, err := testutil.BuildEStargz([]testutil.TarEntry{
tutil.File(testFileName, sampleData1), testutil.File(testFileName, sampleData1),
}, tutil.WithEStargzOptions(estargz.WithChunkSize(sampleChunkSize), estargz.WithCompression(srcCompression))) }, testutil.WithEStargzOptions(estargz.WithChunkSize(sampleChunkSize), estargz.WithCompression(srcCompression)))
if err != nil { if err != nil {
t.Fatalf("failed to build sample estargz") t.Fatalf("failed to build sample estargz")
} }
@ -621,12 +592,8 @@ func (bev *testChunkVerifier) verifier(id uint32, chunkDigest string) (digest.Ve
return &testVerifier{bev.success}, nil return &testVerifier{bev.success}, nil
} }
func testPreReader(t *TestRunner, factory metadata.Store) { func testPreReader(t *testing.T, factory metadata.Store) {
randomData, err := tutil.RandomBytes(64000) data64KB := string(tutil.RandomBytes(t, 64000))
if err != nil {
t.Fatalf("failed rand.Read: %v", err)
}
data64KB := string(randomData)
tests := []struct { tests := []struct {
name string name string
chunkSize int chunkSize int
@ -696,7 +663,7 @@ func testPreReader(t *TestRunner, factory metadata.Store) {
for _, tt := range tests { for _, tt := range tests {
for srcCompresionName, srcCompression := range srcCompressions { for srcCompresionName, srcCompression := range srcCompressions {
srcCompression := srcCompression() srcCompression := srcCompression()
t.Run(tt.name+"-"+srcCompresionName, func(t *TestRunner) { t.Run(tt.name+"-"+srcCompresionName, func(t *testing.T) {
opts := []tutil.BuildEStargzOption{ opts := []tutil.BuildEStargzOption{
tutil.WithEStargzOptions(estargz.WithCompression(srcCompression)), tutil.WithEStargzOptions(estargz.WithCompression(srcCompression)),
} }
@ -735,7 +702,7 @@ func testPreReader(t *TestRunner, factory metadata.Store) {
} }
} }
type check func(TestingT, *reader, *calledReaderAt) type check func(*testing.T, *reader, *calledReaderAt)
type chunkInfo struct { type chunkInfo struct {
name string name string
@ -745,7 +712,7 @@ type chunkInfo struct {
} }
func hasFileContentsOffset(name string, off int64, contents string, fromCache bool) check { func hasFileContentsOffset(name string, off int64, contents string, fromCache bool) check {
return func(t TestingT, r *reader, cr *calledReaderAt) { return func(t *testing.T, r *reader, cr *calledReaderAt) {
tid, err := lookup(r, name) tid, err := lookup(r, name)
if err != nil { if err != nil {
t.Fatalf("failed to lookup %q", name) t.Fatalf("failed to lookup %q", name)
@ -780,7 +747,7 @@ func hasFileContentsOffset(name string, off int64, contents string, fromCache bo
} }
func hasFileContentsWithPreCached(name string, off int64, contents string, extra ...chunkInfo) check { func hasFileContentsWithPreCached(name string, off int64, contents string, extra ...chunkInfo) check {
return func(t TestingT, r *reader, cr *calledReaderAt) { return func(t *testing.T, r *reader, cr *calledReaderAt) {
tid, err := lookup(r, name) tid, err := lookup(r, name)
if err != nil { if err != nil {
t.Fatalf("failed to lookup %q", name) t.Fatalf("failed to lookup %q", name)
@ -855,183 +822,3 @@ func (b longBytesView) String() string {
} }
return string(b[:50]) + "...(omit)..." + string(b[len(b)-50:]) return string(b[:50]) + "...(omit)..." + string(b[len(b)-50:])
} }
func makeMockFile(id uint32) *file {
mockCache := &mockCache{
getError: fmt.Errorf("mock cache get error"),
}
mockFile := &mockFile{}
gr := &reader{
cache: mockCache,
}
return &file{
id: id,
fr: mockFile,
gr: gr,
}
}
type mockCache struct {
getError error
}
func (c *mockCache) Add(key string, opts ...cache.Option) (cache.Writer, error) {
return nil, fmt.Errorf("not implemented")
}
func (c *mockCache) Get(key string, opts ...cache.Option) (cache.Reader, error) {
return nil, c.getError
}
func (c *mockCache) Close() error {
return nil
}
type mockFile struct{}
func (f *mockFile) ChunkEntryForOffset(offset int64) (off int64, size int64, dgst string, ok bool) {
return 0, 0, "", true
}
func (f *mockFile) ReadAt(p []byte, offset int64) (int, error) {
return MockReadAtOutput, nil
}
func testProcessBatchChunks(t *TestRunner) {
type testCase struct {
name string
setupMock func()
createChunks func(chunkSize int64, totalChunks int) []chunkData
expectErrorInHoles bool
}
runTest := func(t TestingT, tc testCase) {
if tc.setupMock != nil {
tc.setupMock()
}
sf := makeMockFile(1)
const (
bufferSize int64 = 400 * 1024 * 1024
chunkSize int64 = 4 * 1024 * 1024
workerCount int = 10
totalChunks int = 100
)
chunks := tc.createChunks(chunkSize, totalChunks)
buffer := make([]byte, bufferSize)
allReadInfos := make([][]chunkReadInfo, workerCount)
eg := errgroup.Group{}
for i := 0; i < workerCount && i < len(chunks); i++ {
workerID := i
args := &batchWorkerArgs{
workerID: workerID,
chunks: chunks,
buffer: buffer,
workerCount: workerCount,
}
eg.Go(func() error {
err := sf.processBatchChunks(args)
if err == nil && len(args.readInfos) > 0 {
allReadInfos[args.workerID] = args.readInfos
}
return err
})
}
if err := eg.Wait(); err != nil {
t.Fatalf("processBatchChunks failed: %v", err)
}
var mergedReadInfos []chunkReadInfo
for _, infos := range allReadInfos {
mergedReadInfos = append(mergedReadInfos, infos...)
}
err := sf.checkHoles(mergedReadInfos, bufferSize)
if tc.expectErrorInHoles {
if err == nil {
t.Fatalf("checkHoles should have detected issues but didn't")
}
t.Logf("Expected error detected: %v", err)
} else {
if err != nil {
t.Fatalf("checkHoles failed: %v", err)
}
}
}
createNormalChunks := func(chunkSize int64, totalChunks int) []chunkData {
var chunks []chunkData
for i := 0; i < totalChunks; i++ {
chunks = append(chunks, chunkData{
offset: int64(i) * chunkSize,
size: chunkSize,
digestStr: fmt.Sprintf("sha256:%d", i),
bufferPos: int64(i) * chunkSize,
})
}
return chunks
}
createOverlappingChunks := func(chunkSize int64, totalChunks int) []chunkData {
chunks := createNormalChunks(chunkSize, totalChunks)
for i := 0; i < totalChunks; i++ {
if i > 0 && i%10 == 0 {
chunks = append(chunks, chunkData{
offset: int64(i)*chunkSize - chunkSize/2,
size: chunkSize,
digestStr: fmt.Sprintf("sha256:overlap-%d", i),
bufferPos: int64(i) * chunkSize,
})
if i < totalChunks-1 {
chunks = append(chunks, chunkData{
offset: int64(i+1)*chunkSize + chunkSize/2,
size: chunkSize,
digestStr: fmt.Sprintf("sha256:gap-%d", i),
bufferPos: int64(i+2) * chunkSize,
})
}
}
}
return chunks
}
tests := []testCase{
{
name: "test_process_batch_chunks_and_check_holes",
createChunks: createNormalChunks,
expectErrorInHoles: false,
},
{
name: "test_process_batch_chunks_with_holes",
setupMock: func() {
originalMockReadAtOutput := MockReadAtOutput
MockReadAtOutput = MockReadAtOutput / 2
t.Cleanup(func() {
MockReadAtOutput = originalMockReadAtOutput
})
},
createChunks: createNormalChunks,
expectErrorInHoles: true,
},
{
name: "test_process_batch_chunks_with_overlapping",
createChunks: createOverlappingChunks,
expectErrorInHoles: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *TestRunner) {
runTest(t, tc)
})
}
}

View File

@ -32,7 +32,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/containerd/containerd/v2/pkg/reference" "github.com/containerd/containerd/reference"
"github.com/containerd/stargz-snapshotter/cache" "github.com/containerd/stargz-snapshotter/cache"
"github.com/containerd/stargz-snapshotter/fs/source" "github.com/containerd/stargz-snapshotter/fs/source"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
@ -120,7 +120,7 @@ func (b *blob) Refresh(ctx context.Context, hosts source.RegistryHosts, refspec
return err return err
} }
if newSize != b.size { if newSize != b.size {
return fmt.Errorf("invalid size of new blob %d; want %d", newSize, b.size) return fmt.Errorf("Invalid size of new blob %d; want %d", newSize, b.size)
} }
// update the blob's fetcher with new one // update the blob's fetcher with new one
@ -259,23 +259,13 @@ func (b *blob) ReadAt(p []byte, offset int64, opts ...Option) (int, error) {
o(&readAtOpts) o(&readAtOpts)
} }
fr := b.getFetcher() // Fetcher can be suddenly updated so we take and use the snapshot of it for
// consistency.
b.fetcherMu.Lock()
fr := b.fetcher
b.fetcherMu.Unlock()
if err := b.prepareChunksForRead(allRegion, offset, p, fr, allData, &readAtOpts); err != nil { b.walkChunks(allRegion, func(chunk region) error {
return 0, err
}
// Read required data
if err := b.fetchRange(allData, &readAtOpts); err != nil {
return 0, err
}
return b.adjustBufferSize(p, offset), nil
}
// prepareChunksForRead prepares chunks for reading by checking cache and setting up writers
func (b *blob) prepareChunksForRead(allRegion region, offset int64, p []byte, fr fetcher, allData map[region]io.Writer, opts *options) error {
return b.walkChunks(allRegion, func(chunk region) error {
var ( var (
base = positive(chunk.b - offset) base = positive(chunk.b - offset)
lowerUnread = positive(offset - chunk.b) lowerUnread = positive(offset - chunk.b)
@ -283,9 +273,14 @@ func (b *blob) prepareChunksForRead(allRegion region, offset int64, p []byte, fr
expectedSize = chunk.size() - upperUnread - lowerUnread expectedSize = chunk.size() - upperUnread - lowerUnread
) )
// Try to read from cache first // Check if the content exists in the cache
if err := b.readFromCache(chunk, p[base:base+expectedSize], lowerUnread, fr, opts); err == nil { r, err := b.cache.Get(fr.genID(chunk), readAtOpts.cacheOpts...)
return nil if err == nil {
defer r.Close()
n, err := r.ReadAt(p[base:base+expectedSize], lowerUnread)
if (err == nil || err == io.EOF) && int64(n) == expectedSize {
return nil
}
} }
// We missed cache. Take it from remote registry. // We missed cache. Take it from remote registry.
@ -294,23 +289,21 @@ func (b *blob) prepareChunksForRead(allRegion region, offset int64, p []byte, fr
allData[chunk] = newBytesWriter(p[base:base+expectedSize], lowerUnread) allData[chunk] = newBytesWriter(p[base:base+expectedSize], lowerUnread)
return nil return nil
}) })
}
// readFromCache attempts to read chunk data from cache // Read required data
func (b *blob) readFromCache(chunk region, dest []byte, offset int64, fr fetcher, opts *options) error { if err := b.fetchRange(allData, &readAtOpts); err != nil {
r, err := b.cache.Get(fr.genID(chunk), opts.cacheOpts...) return 0, err
if err != nil {
return err
} }
defer r.Close()
n, err := r.ReadAt(dest, offset) // Adjust the buffer size according to the blob size
if err != nil && err != io.EOF { if remain := b.size - offset; int64(len(p)) >= remain {
return err if remain < 0 {
remain = 0
}
p = p[:remain]
} }
if n != len(dest) {
return fmt.Errorf("incomplete read from cache: read %d bytes, expected %d bytes", n, len(dest)) return len(p), nil
}
return nil
} }
// fetchRegions fetches all specified chunks from remote blob and puts it in the local cache. // fetchRegions fetches all specified chunks from remote blob and puts it in the local cache.
@ -320,7 +313,11 @@ func (b *blob) fetchRegions(allData map[region]io.Writer, fetched map[region]boo
return nil return nil
} }
fr := b.getFetcher() // Fetcher can be suddenly updated so we take and use the snapshot of it for
// consistency.
b.fetcherMu.Lock()
fr := b.fetcher
b.fetcherMu.Unlock()
// request missed regions // request missed regions
var req []region var req []region
@ -335,6 +332,7 @@ func (b *blob) fetchRegions(allData map[region]io.Writer, fetched map[region]boo
fetchCtx = opts.ctx fetchCtx = opts.ctx
} }
mr, err := fr.fetch(fetchCtx, req, true) mr, err := fr.fetch(fetchCtx, req, true)
if err != nil { if err != nil {
return err return err
} }
@ -355,9 +353,35 @@ func (b *blob) fetchRegions(allData map[region]io.Writer, fetched map[region]boo
return fmt.Errorf("failed to read multipart resp: %w", err) return fmt.Errorf("failed to read multipart resp: %w", err)
} }
if err := b.walkChunks(reg, func(chunk region) (retErr error) { if err := b.walkChunks(reg, func(chunk region) (retErr error) {
if err := b.cacheChunkData(chunk, p, fr, allData, fetched, opts); err != nil { id := fr.genID(chunk)
cw, err := b.cache.Add(id, opts.cacheOpts...)
if err != nil {
return err return err
} }
defer cw.Close()
w := io.Writer(cw)
// If this chunk is one of the targets, write the content to the
// passed reader too.
if _, ok := fetched[chunk]; ok {
w = io.MultiWriter(w, allData[chunk])
}
// Copy the target chunk
if _, err := io.CopyN(w, p, chunk.size()); err != nil {
cw.Abort()
return err
}
// Add the target chunk to the cache
if err := cw.Commit(); err != nil {
return err
}
b.fetchedRegionSetMu.Lock()
b.fetchedRegionSet.add(chunk)
b.fetchedRegionSetMu.Unlock()
fetched[chunk] = true
return nil return nil
}); err != nil { }); err != nil {
return fmt.Errorf("failed to get chunks: %w", err) return fmt.Errorf("failed to get chunks: %w", err)
@ -384,6 +408,9 @@ func (b *blob) fetchRange(allData map[region]io.Writer, opts *options) error {
return nil return nil
} }
// We build a key based on regions we need to fetch and pass it to singleflightGroup.Do(...)
// to block simultaneous same requests. Once the request is finished and the data is ready,
// all blocked callers will be unblocked and that same data will be returned by all blocked callers.
key := makeSyncKey(allData) key := makeSyncKey(allData)
fetched := make(map[region]bool) fetched := make(map[region]bool)
_, err, shared := b.fetchedRegionGroup.Do(key, func() (interface{}, error) { _, err, shared := b.fetchedRegionGroup.Do(key, func() (interface{}, error) {
@ -393,66 +420,46 @@ func (b *blob) fetchRange(allData map[region]io.Writer, opts *options) error {
// When unblocked try to read from cache in case if there were no errors // When unblocked try to read from cache in case if there were no errors
// If we fail reading from cache, fetch from remote registry again // If we fail reading from cache, fetch from remote registry again
if err == nil && shared { if err == nil && shared {
if err := b.handleSharedFetch(allData, fetched, opts); err != nil { for reg := range allData {
return b.fetchRange(allData, opts) // retry on error if _, ok := fetched[reg]; ok {
continue
}
err = b.walkChunks(reg, func(chunk region) error {
b.fetcherMu.Lock()
fr := b.fetcher
b.fetcherMu.Unlock()
// Check if the content exists in the cache
// And if exists, read from cache
r, err := b.cache.Get(fr.genID(chunk), opts.cacheOpts...)
if err != nil {
return err
}
defer r.Close()
rr := io.NewSectionReader(r, 0, chunk.size())
// Copy the target chunk
b.fetchedRegionCopyMu.Lock()
defer b.fetchedRegionCopyMu.Unlock()
if _, err := io.CopyN(allData[chunk], rr, chunk.size()); err != nil {
return err
}
return nil
})
if err != nil {
break
}
}
// if we cannot read the data from cache, do fetch again
if err != nil {
return b.fetchRange(allData, opts)
} }
} }
return err return err
} }
// handleSharedFetch handles the case when multiple goroutines share the same fetch result
func (b *blob) handleSharedFetch(allData map[region]io.Writer, fetched map[region]bool, opts *options) error {
for reg := range allData {
if _, ok := fetched[reg]; ok {
continue
}
if err := b.copyFetchedChunks(reg, allData, opts); err != nil {
return err
}
}
return nil
}
// copyFetchedChunks copies fetched chunks from cache to target writer
func (b *blob) copyFetchedChunks(reg region, allData map[region]io.Writer, opts *options) error {
return b.walkChunks(reg, func(chunk region) error {
fr := b.getFetcher()
r, err := b.cache.Get(fr.genID(chunk), opts.cacheOpts...)
if err != nil {
return err
}
defer r.Close()
b.fetchedRegionCopyMu.Lock()
defer b.fetchedRegionCopyMu.Unlock()
if _, err := io.CopyN(allData[chunk], io.NewSectionReader(r, 0, chunk.size()), chunk.size()); err != nil {
return err
}
return nil
})
}
// getFetcher safely gets the current fetcher
// Fetcher can be suddenly updated so we take and use the snapshot of it for consistency.
func (b *blob) getFetcher() fetcher {
b.fetcherMu.Lock()
defer b.fetcherMu.Unlock()
return b.fetcher
}
// adjustBufferSize adjusts buffer size according to the blob size
func (b *blob) adjustBufferSize(p []byte, offset int64) int {
if remain := b.size - offset; int64(len(p)) >= remain {
if remain < 0 {
remain = 0
}
p = p[:remain]
}
return len(p)
}
type walkFunc func(reg region) error type walkFunc func(reg region) error
// walkChunks walks chunks from begin to end in order in the specified region. // walkChunks walks chunks from begin to end in order in the specified region.
@ -526,34 +533,3 @@ func positive(n int64) int64 {
} }
return n return n
} }
// cacheChunkData handles caching of chunk data
func (b *blob) cacheChunkData(chunk region, r io.Reader, fr fetcher, allData map[region]io.Writer, fetched map[region]bool, opts *options) error {
id := fr.genID(chunk)
cw, err := b.cache.Add(id, opts.cacheOpts...)
if err != nil {
return fmt.Errorf("failed to create cache writer: %w", err)
}
defer cw.Close()
w := io.Writer(cw)
if _, ok := fetched[chunk]; ok {
w = io.MultiWriter(w, allData[chunk])
}
if _, err := io.CopyN(w, r, chunk.size()); err != nil {
cw.Abort()
return fmt.Errorf("failed to write chunk data: %w", err)
}
if err := cw.Commit(); err != nil {
return fmt.Errorf("failed to commit chunk: %w", err)
}
b.fetchedRegionSetMu.Lock()
b.fetchedRegionSet.add(chunk)
b.fetchedRegionSetMu.Unlock()
fetched[chunk] = true
return nil
}

View File

@ -609,7 +609,7 @@ func TestCheckInterval(t *testing.T) {
if !tr.called { if !tr.called {
return b.lastCheck, false return b.lastCheck, false
} }
if !b.lastCheck.After(beforeUpdate) || !b.lastCheck.Before(afterUpdate) { if !(b.lastCheck.After(beforeUpdate) && b.lastCheck.Before(afterUpdate)) {
t.Errorf("%q: updated time must be after %q and before %q but %q", name, beforeUpdate, afterUpdate, b.lastCheck) t.Errorf("%q: updated time must be after %q and before %q but %q", name, beforeUpdate, afterUpdate, b.lastCheck)
} }

View File

@ -24,12 +24,10 @@ package remote
import ( import (
"context" "context"
"crypto/rand"
"crypto/sha256" "crypto/sha256"
"errors"
"fmt" "fmt"
"io" "io"
"math/big" "math/rand"
"mime" "mime"
"mime/multipart" "mime/multipart"
"net/http" "net/http"
@ -39,14 +37,15 @@ import (
"sync" "sync"
"time" "time"
"github.com/containerd/containerd/v2/core/remotes/docker" "github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/v2/pkg/reference" "github.com/containerd/containerd/log"
"github.com/containerd/errdefs" "github.com/containerd/containerd/reference"
"github.com/containerd/log" "github.com/containerd/containerd/remotes/docker"
"github.com/containerd/stargz-snapshotter/cache" "github.com/containerd/stargz-snapshotter/cache"
"github.com/containerd/stargz-snapshotter/fs/config" "github.com/containerd/stargz-snapshotter/fs/config"
commonmetrics "github.com/containerd/stargz-snapshotter/fs/metrics/common" commonmetrics "github.com/containerd/stargz-snapshotter/fs/metrics/common"
"github.com/containerd/stargz-snapshotter/fs/source" "github.com/containerd/stargz-snapshotter/fs/source"
"github.com/hashicorp/go-multierror"
rhttp "github.com/hashicorp/go-retryablehttp" rhttp "github.com/hashicorp/go-retryablehttp"
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
@ -122,19 +121,19 @@ func (r *Resolver) Resolve(ctx context.Context, hosts source.RegistryHosts, refs
func (r *Resolver) resolveFetcher(ctx context.Context, hosts source.RegistryHosts, refspec reference.Spec, desc ocispec.Descriptor) (f fetcher, size int64, err error) { func (r *Resolver) resolveFetcher(ctx context.Context, hosts source.RegistryHosts, refspec reference.Spec, desc ocispec.Descriptor) (f fetcher, size int64, err error) {
blobConfig := &r.blobConfig blobConfig := &r.blobConfig
fc := &fetcherConfig{ fc := &fetcherConfig{
hosts: hosts, hosts: hosts,
refspec: refspec, refspec: refspec,
desc: desc, desc: desc,
maxRetries: blobConfig.MaxRetries, maxRetries: blobConfig.MaxRetries,
minWait: time.Duration(blobConfig.MinWaitMSec) * time.Millisecond, minWaitMSec: time.Duration(blobConfig.MinWaitMSec) * time.Millisecond,
maxWait: time.Duration(blobConfig.MaxWaitMSec) * time.Millisecond, maxWaitMSec: time.Duration(blobConfig.MaxWaitMSec) * time.Millisecond,
} }
var errs []error var handlersErr error
for name, p := range r.handlers { for name, p := range r.handlers {
// TODO: allow to configure the selection of readers based on the hostname in refspec // TODO: allow to configure the selection of readers based on the hostname in refspec
r, size, err := p.Handle(ctx, desc) r, size, err := p.Handle(ctx, desc)
if err != nil { if err != nil {
errs = append(errs, err) handlersErr = multierror.Append(handlersErr, err)
continue continue
} }
log.G(ctx).WithField("handler name", name).WithField("ref", refspec.String()).WithField("digest", desc.Digest). log.G(ctx).WithField("handler name", name).WithField("ref", refspec.String()).WithField("digest", desc.Digest).
@ -142,8 +141,6 @@ func (r *Resolver) resolveFetcher(ctx context.Context, hosts source.RegistryHost
return &remoteFetcher{r}, size, nil return &remoteFetcher{r}, size, nil
} }
handlersErr := errors.Join(errs...)
log.G(ctx).WithError(handlersErr).WithField("ref", refspec.String()).WithField("digest", desc.Digest).Debugf("using default handler") log.G(ctx).WithError(handlersErr).WithField("ref", refspec.String()).WithField("digest", desc.Digest).Debugf("using default handler")
hf, size, err := newHTTPFetcher(ctx, fc) hf, size, err := newHTTPFetcher(ctx, fc)
if err != nil { if err != nil {
@ -156,23 +153,19 @@ func (r *Resolver) resolveFetcher(ctx context.Context, hosts source.RegistryHost
} }
type fetcherConfig struct { type fetcherConfig struct {
hosts source.RegistryHosts hosts source.RegistryHosts
refspec reference.Spec refspec reference.Spec
desc ocispec.Descriptor desc ocispec.Descriptor
maxRetries int maxRetries int
minWait time.Duration minWaitMSec time.Duration
maxWait time.Duration maxWaitMSec time.Duration
} }
func jitter(duration time.Duration) time.Duration { func jitter(duration time.Duration) time.Duration {
if duration <= 0 { if duration <= 0 {
return duration return duration
} }
b, err := rand.Int(rand.Reader, big.NewInt(int64(duration))) return time.Duration(rand.Int63n(int64(duration)) + int64(duration))
if err != nil {
panic(err)
}
return time.Duration(b.Int64() + int64(duration))
} }
// backoffStrategy extends retryablehttp's DefaultBackoff to add a random jitter to avoid overwhelming the repository // backoffStrategy extends retryablehttp's DefaultBackoff to add a random jitter to avoid overwhelming the repository
@ -202,7 +195,7 @@ func newHTTPFetcher(ctx context.Context, fc *fetcherConfig) (*httpFetcher, int64
} }
desc := fc.desc desc := fc.desc
if desc.Digest.String() == "" { if desc.Digest.String() == "" {
return nil, 0, fmt.Errorf("digest is mandatory in layer descriptor") return nil, 0, fmt.Errorf("Digest is mandatory in layer descriptor")
} }
digest := desc.Digest digest := desc.Digest
pullScope, err := docker.RepositoryScope(fc.refspec, false) pullScope, err := docker.RepositoryScope(fc.refspec, false)
@ -222,16 +215,15 @@ func newHTTPFetcher(ctx context.Context, fc *fetcherConfig) (*httpFetcher, int64
// Prepare transport with authorization functionality // Prepare transport with authorization functionality
tr := host.Client.Transport tr := host.Client.Transport
timeout := host.Client.Timeout
if rt, ok := tr.(*rhttp.RoundTripper); ok { if rt, ok := tr.(*rhttp.RoundTripper); ok {
rt.Client.RetryMax = fc.maxRetries rt.Client.RetryMax = fc.maxRetries
rt.Client.RetryWaitMin = fc.minWait rt.Client.RetryWaitMin = fc.minWaitMSec
rt.Client.RetryWaitMax = fc.maxWait rt.Client.RetryWaitMax = fc.maxWaitMSec
rt.Client.Backoff = backoffStrategy rt.Client.Backoff = backoffStrategy
rt.Client.CheckRetry = retryStrategy rt.Client.CheckRetry = retryStrategy
timeout = rt.Client.HTTPClient.Timeout
} }
timeout := host.Client.Timeout
if host.Authorizer != nil { if host.Authorizer != nil {
tr = &transport{ tr = &transport{
inner: tr, inner: tr,
@ -408,10 +400,9 @@ func getSize(ctx context.Context, url string, tr http.RoundTripper, timeout time
res.Body.Close() res.Body.Close()
}() }()
switch res.StatusCode { if res.StatusCode == http.StatusOK {
case http.StatusOK:
return strconv.ParseInt(res.Header.Get("Content-Length"), 10, 64) return strconv.ParseInt(res.Header.Get("Content-Length"), 10, 64)
case http.StatusPartialContent: } else if res.StatusCode == http.StatusPartialContent {
_, size, err := parseRange(res.Header.Get("Content-Range")) _, size, err := parseRange(res.Header.Get("Content-Range"))
return size, err return size, err
} }
@ -559,10 +550,9 @@ func (f *httpFetcher) check() error {
io.Copy(io.Discard, res.Body) io.Copy(io.Discard, res.Body)
res.Body.Close() res.Body.Close()
}() }()
switch res.StatusCode { if res.StatusCode == http.StatusOK || res.StatusCode == http.StatusPartialContent {
case http.StatusOK, http.StatusPartialContent:
return nil return nil
case http.StatusForbidden: } else if res.StatusCode == http.StatusForbidden {
// Try to re-redirect this blob // Try to re-redirect this blob
rCtx := context.Background() rCtx := context.Background()
if f.timeout > 0 { if f.timeout > 0 {

View File

@ -33,8 +33,8 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/containerd/containerd/v2/core/remotes/docker" "github.com/containerd/containerd/reference"
"github.com/containerd/containerd/v2/pkg/reference" "github.com/containerd/containerd/remotes/docker"
"github.com/containerd/stargz-snapshotter/fs/source" "github.com/containerd/stargz-snapshotter/fs/source"
rhttp "github.com/hashicorp/go-retryablehttp" rhttp "github.com/hashicorp/go-retryablehttp"
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"

View File

@ -21,10 +21,10 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/containerd/containerd/v2/core/images" "github.com/containerd/containerd/images"
"github.com/containerd/containerd/v2/core/remotes/docker" "github.com/containerd/containerd/labels"
"github.com/containerd/containerd/v2/pkg/labels" "github.com/containerd/containerd/reference"
"github.com/containerd/containerd/v2/pkg/reference" "github.com/containerd/containerd/remotes/docker"
"github.com/containerd/stargz-snapshotter/fs/config" "github.com/containerd/stargz-snapshotter/fs/config"
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"

View File

@ -1,566 +0,0 @@
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: api.proto
package api
import (
context "context"
fmt "fmt"
proto "github.com/gogo/protobuf/proto"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
type StatusRequest struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *StatusRequest) Reset() { *m = StatusRequest{} }
func (m *StatusRequest) String() string { return proto.CompactTextString(m) }
func (*StatusRequest) ProtoMessage() {}
func (*StatusRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_00212fb1f9d3bf1c, []int{0}
}
func (m *StatusRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_StatusRequest.Unmarshal(m, b)
}
func (m *StatusRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_StatusRequest.Marshal(b, m, deterministic)
}
func (m *StatusRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_StatusRequest.Merge(m, src)
}
func (m *StatusRequest) XXX_Size() int {
return xxx_messageInfo_StatusRequest.Size(m)
}
func (m *StatusRequest) XXX_DiscardUnknown() {
xxx_messageInfo_StatusRequest.DiscardUnknown(m)
}
var xxx_messageInfo_StatusRequest proto.InternalMessageInfo
type InitRequest struct {
Root string `protobuf:"bytes,1,opt,name=root,proto3" json:"root,omitempty"`
Config []byte `protobuf:"bytes,2,opt,name=config,proto3" json:"config,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *InitRequest) Reset() { *m = InitRequest{} }
func (m *InitRequest) String() string { return proto.CompactTextString(m) }
func (*InitRequest) ProtoMessage() {}
func (*InitRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_00212fb1f9d3bf1c, []int{1}
}
func (m *InitRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_InitRequest.Unmarshal(m, b)
}
func (m *InitRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_InitRequest.Marshal(b, m, deterministic)
}
func (m *InitRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_InitRequest.Merge(m, src)
}
func (m *InitRequest) XXX_Size() int {
return xxx_messageInfo_InitRequest.Size(m)
}
func (m *InitRequest) XXX_DiscardUnknown() {
xxx_messageInfo_InitRequest.DiscardUnknown(m)
}
var xxx_messageInfo_InitRequest proto.InternalMessageInfo
func (m *InitRequest) GetRoot() string {
if m != nil {
return m.Root
}
return ""
}
func (m *InitRequest) GetConfig() []byte {
if m != nil {
return m.Config
}
return nil
}
type MountRequest struct {
Mountpoint string `protobuf:"bytes,1,opt,name=mountpoint,proto3" json:"mountpoint,omitempty"`
Labels map[string]string `protobuf:"bytes,2,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *MountRequest) Reset() { *m = MountRequest{} }
func (m *MountRequest) String() string { return proto.CompactTextString(m) }
func (*MountRequest) ProtoMessage() {}
func (*MountRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_00212fb1f9d3bf1c, []int{2}
}
func (m *MountRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_MountRequest.Unmarshal(m, b)
}
func (m *MountRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_MountRequest.Marshal(b, m, deterministic)
}
func (m *MountRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_MountRequest.Merge(m, src)
}
func (m *MountRequest) XXX_Size() int {
return xxx_messageInfo_MountRequest.Size(m)
}
func (m *MountRequest) XXX_DiscardUnknown() {
xxx_messageInfo_MountRequest.DiscardUnknown(m)
}
var xxx_messageInfo_MountRequest proto.InternalMessageInfo
func (m *MountRequest) GetMountpoint() string {
if m != nil {
return m.Mountpoint
}
return ""
}
func (m *MountRequest) GetLabels() map[string]string {
if m != nil {
return m.Labels
}
return nil
}
type CheckRequest struct {
Mountpoint string `protobuf:"bytes,1,opt,name=mountpoint,proto3" json:"mountpoint,omitempty"`
Labels map[string]string `protobuf:"bytes,2,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *CheckRequest) Reset() { *m = CheckRequest{} }
func (m *CheckRequest) String() string { return proto.CompactTextString(m) }
func (*CheckRequest) ProtoMessage() {}
func (*CheckRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_00212fb1f9d3bf1c, []int{3}
}
func (m *CheckRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CheckRequest.Unmarshal(m, b)
}
func (m *CheckRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_CheckRequest.Marshal(b, m, deterministic)
}
func (m *CheckRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_CheckRequest.Merge(m, src)
}
func (m *CheckRequest) XXX_Size() int {
return xxx_messageInfo_CheckRequest.Size(m)
}
func (m *CheckRequest) XXX_DiscardUnknown() {
xxx_messageInfo_CheckRequest.DiscardUnknown(m)
}
var xxx_messageInfo_CheckRequest proto.InternalMessageInfo
func (m *CheckRequest) GetMountpoint() string {
if m != nil {
return m.Mountpoint
}
return ""
}
func (m *CheckRequest) GetLabels() map[string]string {
if m != nil {
return m.Labels
}
return nil
}
type UnmountRequest struct {
Mountpoint string `protobuf:"bytes,1,opt,name=mountpoint,proto3" json:"mountpoint,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *UnmountRequest) Reset() { *m = UnmountRequest{} }
func (m *UnmountRequest) String() string { return proto.CompactTextString(m) }
func (*UnmountRequest) ProtoMessage() {}
func (*UnmountRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_00212fb1f9d3bf1c, []int{4}
}
func (m *UnmountRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_UnmountRequest.Unmarshal(m, b)
}
func (m *UnmountRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_UnmountRequest.Marshal(b, m, deterministic)
}
func (m *UnmountRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_UnmountRequest.Merge(m, src)
}
func (m *UnmountRequest) XXX_Size() int {
return xxx_messageInfo_UnmountRequest.Size(m)
}
func (m *UnmountRequest) XXX_DiscardUnknown() {
xxx_messageInfo_UnmountRequest.DiscardUnknown(m)
}
var xxx_messageInfo_UnmountRequest proto.InternalMessageInfo
func (m *UnmountRequest) GetMountpoint() string {
if m != nil {
return m.Mountpoint
}
return ""
}
type StatusResponse struct {
Status int32 `protobuf:"varint,1,opt,name=status,proto3" json:"status,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *StatusResponse) Reset() { *m = StatusResponse{} }
func (m *StatusResponse) String() string { return proto.CompactTextString(m) }
func (*StatusResponse) ProtoMessage() {}
func (*StatusResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_00212fb1f9d3bf1c, []int{5}
}
func (m *StatusResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_StatusResponse.Unmarshal(m, b)
}
func (m *StatusResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_StatusResponse.Marshal(b, m, deterministic)
}
func (m *StatusResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_StatusResponse.Merge(m, src)
}
func (m *StatusResponse) XXX_Size() int {
return xxx_messageInfo_StatusResponse.Size(m)
}
func (m *StatusResponse) XXX_DiscardUnknown() {
xxx_messageInfo_StatusResponse.DiscardUnknown(m)
}
var xxx_messageInfo_StatusResponse proto.InternalMessageInfo
func (m *StatusResponse) GetStatus() int32 {
if m != nil {
return m.Status
}
return 0
}
type Response struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Response) Reset() { *m = Response{} }
func (m *Response) String() string { return proto.CompactTextString(m) }
func (*Response) ProtoMessage() {}
func (*Response) Descriptor() ([]byte, []int) {
return fileDescriptor_00212fb1f9d3bf1c, []int{6}
}
func (m *Response) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Response.Unmarshal(m, b)
}
func (m *Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Response.Marshal(b, m, deterministic)
}
func (m *Response) XXX_Merge(src proto.Message) {
xxx_messageInfo_Response.Merge(m, src)
}
func (m *Response) XXX_Size() int {
return xxx_messageInfo_Response.Size(m)
}
func (m *Response) XXX_DiscardUnknown() {
xxx_messageInfo_Response.DiscardUnknown(m)
}
var xxx_messageInfo_Response proto.InternalMessageInfo
func init() {
proto.RegisterType((*StatusRequest)(nil), "fusemanager.StatusRequest")
proto.RegisterType((*InitRequest)(nil), "fusemanager.InitRequest")
proto.RegisterType((*MountRequest)(nil), "fusemanager.MountRequest")
proto.RegisterMapType((map[string]string)(nil), "fusemanager.MountRequest.LabelsEntry")
proto.RegisterType((*CheckRequest)(nil), "fusemanager.CheckRequest")
proto.RegisterMapType((map[string]string)(nil), "fusemanager.CheckRequest.LabelsEntry")
proto.RegisterType((*UnmountRequest)(nil), "fusemanager.UnmountRequest")
proto.RegisterType((*StatusResponse)(nil), "fusemanager.StatusResponse")
proto.RegisterType((*Response)(nil), "fusemanager.Response")
}
func init() { proto.RegisterFile("api.proto", fileDescriptor_00212fb1f9d3bf1c) }
var fileDescriptor_00212fb1f9d3bf1c = []byte{
// 386 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x53, 0x51, 0x4b, 0xf3, 0x30,
0x14, 0xa5, 0xdd, 0xd6, 0xef, 0xdb, 0xed, 0x9c, 0x12, 0x54, 0x6a, 0x05, 0x19, 0x05, 0xa1, 0x2f,
0x6b, 0x65, 0x3e, 0xe8, 0x84, 0x3d, 0xa8, 0x28, 0x08, 0xee, 0xa5, 0xc3, 0x17, 0xdf, 0xb2, 0x92,
0x75, 0x65, 0x6b, 0x52, 0x9b, 0x74, 0x30, 0x7f, 0x91, 0xff, 0xc5, 0x3f, 0x25, 0xcd, 0xba, 0x91,
0x8a, 0x13, 0x84, 0xbd, 0xe5, 0x24, 0xf7, 0xdc, 0x9e, 0x7b, 0xcf, 0x29, 0x34, 0x71, 0x1a, 0x7b,
0x69, 0xc6, 0x04, 0x43, 0xe6, 0x24, 0xe7, 0x24, 0xc1, 0x14, 0x47, 0x24, 0x73, 0xf6, 0x61, 0x6f,
0x24, 0xb0, 0xc8, 0x79, 0x40, 0xde, 0x72, 0xc2, 0x85, 0xd3, 0x07, 0xf3, 0x89, 0xc6, 0xa2, 0x84,
0x08, 0x41, 0x3d, 0x63, 0x4c, 0x58, 0x5a, 0x47, 0x73, 0x9b, 0x81, 0x3c, 0xa3, 0x63, 0x30, 0x42,
0x46, 0x27, 0x71, 0x64, 0xe9, 0x1d, 0xcd, 0x6d, 0x05, 0x25, 0x72, 0x3e, 0x34, 0x68, 0x0d, 0x59,
0x4e, 0x37, 0xe4, 0x33, 0x80, 0xa4, 0xc0, 0x29, 0x8b, 0xe9, 0xba, 0x85, 0x72, 0x83, 0x06, 0x60,
0xcc, 0xf1, 0x98, 0xcc, 0xb9, 0xa5, 0x77, 0x6a, 0xae, 0xd9, 0x3b, 0xf7, 0x14, 0x69, 0x9e, 0xda,
0xca, 0x7b, 0x96, 0x75, 0x0f, 0x54, 0x64, 0xcb, 0xa0, 0x24, 0xd9, 0x7d, 0x30, 0x95, 0x6b, 0x74,
0x00, 0xb5, 0x19, 0x59, 0x96, 0x9f, 0x29, 0x8e, 0xe8, 0x10, 0x1a, 0x0b, 0x3c, 0xcf, 0x89, 0xd4,
0xd9, 0x0c, 0x56, 0xe0, 0x46, 0xbf, 0xd6, 0xa4, 0xd4, 0xfb, 0x29, 0x09, 0x67, 0xbb, 0x91, 0xaa,
0xb6, 0xda, 0xb5, 0xd4, 0x0b, 0x68, 0xbf, 0xd0, 0xe4, 0x0f, 0x6b, 0x75, 0x5c, 0x68, 0xaf, 0x3d,
0xe5, 0x29, 0xa3, 0x9c, 0x14, 0x8e, 0x71, 0x79, 0x23, 0xab, 0x1b, 0x41, 0x89, 0x1c, 0x80, 0xff,
0xeb, 0x9a, 0xde, 0xa7, 0x0e, 0xd6, 0x48, 0xe0, 0x2c, 0x7a, 0x7f, 0xcc, 0x39, 0x19, 0xae, 0x26,
0x1b, 0x91, 0x6c, 0x11, 0x87, 0x04, 0xdd, 0x82, 0xb1, 0x6a, 0x89, 0xec, 0xca, 0xe0, 0x95, 0xec,
0xd8, 0xa7, 0x3f, 0xbe, 0x95, 0x1a, 0xae, 0xa0, 0x5e, 0x04, 0x0b, 0x59, 0x95, 0x22, 0x25, 0x6b,
0xf6, 0x51, 0xe5, 0x65, 0x43, 0xec, 0x43, 0x43, 0x46, 0x01, 0x9d, 0x6c, 0x8d, 0xc7, 0x2f, 0x54,
0x69, 0xcd, 0x37, 0xaa, 0x6a, 0xd7, 0x36, 0xea, 0x00, 0xfe, 0x95, 0x6b, 0x47, 0xd5, 0xb1, 0xaa,
0x66, 0x6c, 0xa1, 0xdf, 0xf9, 0xaf, 0xdd, 0x28, 0x16, 0xd3, 0x7c, 0xec, 0x85, 0x2c, 0xf1, 0xb9,
0xdc, 0x6b, 0x97, 0x53, 0x9c, 0xf2, 0x29, 0x13, 0x82, 0x64, 0xbe, 0xc2, 0xf2, 0x71, 0x1a, 0x8f,
0x0d, 0xf9, 0x73, 0x5e, 0x7e, 0x05, 0x00, 0x00, 0xff, 0xff, 0x9d, 0x24, 0xe1, 0x41, 0xa9, 0x03,
0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// StargzFuseManagerServiceClient is the client API for StargzFuseManagerService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type StargzFuseManagerServiceClient interface {
Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error)
Init(ctx context.Context, in *InitRequest, opts ...grpc.CallOption) (*Response, error)
Mount(ctx context.Context, in *MountRequest, opts ...grpc.CallOption) (*Response, error)
Check(ctx context.Context, in *CheckRequest, opts ...grpc.CallOption) (*Response, error)
Unmount(ctx context.Context, in *UnmountRequest, opts ...grpc.CallOption) (*Response, error)
}
type stargzFuseManagerServiceClient struct {
cc *grpc.ClientConn
}
func NewStargzFuseManagerServiceClient(cc *grpc.ClientConn) StargzFuseManagerServiceClient {
return &stargzFuseManagerServiceClient{cc}
}
func (c *stargzFuseManagerServiceClient) Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) {
out := new(StatusResponse)
err := c.cc.Invoke(ctx, "/fusemanager.StargzFuseManagerService/Status", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *stargzFuseManagerServiceClient) Init(ctx context.Context, in *InitRequest, opts ...grpc.CallOption) (*Response, error) {
out := new(Response)
err := c.cc.Invoke(ctx, "/fusemanager.StargzFuseManagerService/Init", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *stargzFuseManagerServiceClient) Mount(ctx context.Context, in *MountRequest, opts ...grpc.CallOption) (*Response, error) {
out := new(Response)
err := c.cc.Invoke(ctx, "/fusemanager.StargzFuseManagerService/Mount", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *stargzFuseManagerServiceClient) Check(ctx context.Context, in *CheckRequest, opts ...grpc.CallOption) (*Response, error) {
out := new(Response)
err := c.cc.Invoke(ctx, "/fusemanager.StargzFuseManagerService/Check", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *stargzFuseManagerServiceClient) Unmount(ctx context.Context, in *UnmountRequest, opts ...grpc.CallOption) (*Response, error) {
out := new(Response)
err := c.cc.Invoke(ctx, "/fusemanager.StargzFuseManagerService/Unmount", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// StargzFuseManagerServiceServer is the server API for StargzFuseManagerService service.
type StargzFuseManagerServiceServer interface {
Status(context.Context, *StatusRequest) (*StatusResponse, error)
Init(context.Context, *InitRequest) (*Response, error)
Mount(context.Context, *MountRequest) (*Response, error)
Check(context.Context, *CheckRequest) (*Response, error)
Unmount(context.Context, *UnmountRequest) (*Response, error)
}
// UnimplementedStargzFuseManagerServiceServer can be embedded to have forward compatible implementations.
type UnimplementedStargzFuseManagerServiceServer struct {
}
func (*UnimplementedStargzFuseManagerServiceServer) Status(ctx context.Context, req *StatusRequest) (*StatusResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Status not implemented")
}
func (*UnimplementedStargzFuseManagerServiceServer) Init(ctx context.Context, req *InitRequest) (*Response, error) {
return nil, status.Errorf(codes.Unimplemented, "method Init not implemented")
}
func (*UnimplementedStargzFuseManagerServiceServer) Mount(ctx context.Context, req *MountRequest) (*Response, error) {
return nil, status.Errorf(codes.Unimplemented, "method Mount not implemented")
}
func (*UnimplementedStargzFuseManagerServiceServer) Check(ctx context.Context, req *CheckRequest) (*Response, error) {
return nil, status.Errorf(codes.Unimplemented, "method Check not implemented")
}
func (*UnimplementedStargzFuseManagerServiceServer) Unmount(ctx context.Context, req *UnmountRequest) (*Response, error) {
return nil, status.Errorf(codes.Unimplemented, "method Unmount not implemented")
}
func RegisterStargzFuseManagerServiceServer(s *grpc.Server, srv StargzFuseManagerServiceServer) {
s.RegisterService(&_StargzFuseManagerService_serviceDesc, srv)
}
func _StargzFuseManagerService_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(StatusRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(StargzFuseManagerServiceServer).Status(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/fusemanager.StargzFuseManagerService/Status",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(StargzFuseManagerServiceServer).Status(ctx, req.(*StatusRequest))
}
return interceptor(ctx, in, info, handler)
}
func _StargzFuseManagerService_Init_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(InitRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(StargzFuseManagerServiceServer).Init(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/fusemanager.StargzFuseManagerService/Init",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(StargzFuseManagerServiceServer).Init(ctx, req.(*InitRequest))
}
return interceptor(ctx, in, info, handler)
}
func _StargzFuseManagerService_Mount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(MountRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(StargzFuseManagerServiceServer).Mount(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/fusemanager.StargzFuseManagerService/Mount",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(StargzFuseManagerServiceServer).Mount(ctx, req.(*MountRequest))
}
return interceptor(ctx, in, info, handler)
}
func _StargzFuseManagerService_Check_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CheckRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(StargzFuseManagerServiceServer).Check(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/fusemanager.StargzFuseManagerService/Check",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(StargzFuseManagerServiceServer).Check(ctx, req.(*CheckRequest))
}
return interceptor(ctx, in, info, handler)
}
func _StargzFuseManagerService_Unmount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UnmountRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(StargzFuseManagerServiceServer).Unmount(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/fusemanager.StargzFuseManagerService/Unmount",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(StargzFuseManagerServiceServer).Unmount(ctx, req.(*UnmountRequest))
}
return interceptor(ctx, in, info, handler)
}
var _StargzFuseManagerService_serviceDesc = grpc.ServiceDesc{
ServiceName: "fusemanager.StargzFuseManagerService",
HandlerType: (*StargzFuseManagerServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Status",
Handler: _StargzFuseManagerService_Status_Handler,
},
{
MethodName: "Init",
Handler: _StargzFuseManagerService_Init_Handler,
},
{
MethodName: "Mount",
Handler: _StargzFuseManagerService_Mount_Handler,
},
{
MethodName: "Check",
Handler: _StargzFuseManagerService_Check_Handler,
},
{
MethodName: "Unmount",
Handler: _StargzFuseManagerService_Unmount_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "api.proto",
}

View File

@ -1,58 +0,0 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
syntax = "proto3";
option go_package = "github.com/stargz-snapshotter/fusemanager/api";
package fusemanager;
service StargzFuseManagerService {
rpc Status (StatusRequest) returns (StatusResponse);
rpc Init (InitRequest) returns (Response);
rpc Mount (MountRequest) returns (Response);
rpc Check (CheckRequest) returns (Response);
rpc Unmount (UnmountRequest) returns (Response);
}
message StatusRequest {
}
message InitRequest {
string root = 1;
bytes config = 2;
}
message MountRequest {
string mountpoint = 1;
map<string, string> labels = 2;
}
message CheckRequest {
string mountpoint = 1;
map<string, string> labels = 2;
}
message UnmountRequest {
string mountpoint = 1;
}
message StatusResponse {
int32 status = 1;
}
message Response {
}

View File

@ -1,19 +0,0 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package api
//go:generate protoc --gogo_out=paths=source_relative,plugins=grpc:. api.proto

View File

@ -1,141 +0,0 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fusemanager
import (
"context"
"encoding/json"
"fmt"
"github.com/containerd/containerd/v2/defaults"
"github.com/containerd/containerd/v2/pkg/dialer"
"github.com/containerd/log"
"google.golang.org/grpc"
"google.golang.org/grpc/backoff"
"google.golang.org/grpc/credentials/insecure"
pb "github.com/containerd/stargz-snapshotter/fusemanager/api"
"github.com/containerd/stargz-snapshotter/snapshot"
)
type Client struct {
client pb.StargzFuseManagerServiceClient
}
func NewManagerClient(ctx context.Context, root, socket string, config *Config) (snapshot.FileSystem, error) {
grpcCli, err := newClient(socket)
if err != nil {
return nil, err
}
client := &Client{
client: grpcCli,
}
err = client.init(ctx, root, config)
if err != nil {
return nil, err
}
return client, nil
}
func newClient(socket string) (pb.StargzFuseManagerServiceClient, error) {
connParams := grpc.ConnectParams{
Backoff: backoff.DefaultConfig,
}
gopts := []grpc.DialOption{
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithConnectParams(connParams),
grpc.WithContextDialer(dialer.ContextDialer),
grpc.WithDefaultCallOptions(
grpc.MaxCallRecvMsgSize(defaults.DefaultMaxRecvMsgSize),
grpc.MaxCallSendMsgSize(defaults.DefaultMaxSendMsgSize),
),
}
conn, err := grpc.NewClient(fmt.Sprintf("unix://%s", socket), gopts...)
if err != nil {
return nil, err
}
return pb.NewStargzFuseManagerServiceClient(conn), nil
}
func (cli *Client) init(ctx context.Context, root string, config *Config) error {
configBytes, err := json.Marshal(config)
if err != nil {
return err
}
req := &pb.InitRequest{
Root: root,
Config: configBytes,
}
_, err = cli.client.Init(ctx, req)
if err != nil {
log.G(ctx).WithError(err).Errorf("failed to call Init")
return err
}
return nil
}
func (cli *Client) Mount(ctx context.Context, mountpoint string, labels map[string]string) error {
req := &pb.MountRequest{
Mountpoint: mountpoint,
Labels: labels,
}
_, err := cli.client.Mount(ctx, req)
if err != nil {
log.G(ctx).WithError(err).Errorf("failed to call Mount")
return err
}
return nil
}
func (cli *Client) Check(ctx context.Context, mountpoint string, labels map[string]string) error {
req := &pb.CheckRequest{
Mountpoint: mountpoint,
Labels: labels,
}
_, err := cli.client.Check(ctx, req)
if err != nil {
log.G(ctx).WithError(err).Errorf("failed to call Check")
return err
}
return nil
}
func (cli *Client) Unmount(ctx context.Context, mountpoint string) error {
req := &pb.UnmountRequest{
Mountpoint: mountpoint,
}
_, err := cli.client.Unmount(ctx, req)
if err != nil {
log.G(ctx).WithError(err).Errorf("failed to call Unmount")
return err
}
return nil
}

View File

@ -1,259 +0,0 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fusemanager
import (
"context"
"flag"
"fmt"
golog "log"
"net"
"os"
"os/exec"
"os/signal"
"path/filepath"
"syscall"
"github.com/containerd/log"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
"google.golang.org/grpc"
pb "github.com/containerd/stargz-snapshotter/fusemanager/api"
"github.com/containerd/stargz-snapshotter/version"
)
var (
debugFlag bool
versionFlag bool
fuseStoreAddr string
address string
logLevel string
logPath string
action string
)
func parseFlags() {
flag.BoolVar(&debugFlag, "debug", false, "enable debug output in logs")
flag.BoolVar(&versionFlag, "v", false, "show the fusemanager version and exit")
flag.StringVar(&action, "action", "", "action of fusemanager")
flag.StringVar(&fuseStoreAddr, "fusestore-path", "/var/lib/containerd-stargz-grpc/fusestore.db", "address for the fusemanager's store")
flag.StringVar(&address, "address", "/run/containerd-stargz-grpc/fuse-manager.sock", "address for the fusemanager's gRPC socket")
flag.StringVar(&logLevel, "log-level", logrus.InfoLevel.String(), "set the logging level [trace, debug, info, warn, error, fatal, panic]")
flag.StringVar(&logPath, "log-path", "", "path to fusemanager's logs, no log recorded if empty")
flag.Parse()
}
func Run() {
if err := run(); err != nil {
fmt.Fprintf(os.Stderr, "failed to run fusemanager: %v", err)
os.Exit(1)
}
}
func run() error {
parseFlags()
if versionFlag {
fmt.Printf("%s:\n", os.Args[0])
fmt.Println(" Version: ", version.Version)
fmt.Println(" Revision:", version.Revision)
fmt.Println("")
return nil
}
if fuseStoreAddr == "" || address == "" {
return fmt.Errorf("fusemanager fusestore and socket path cannot be empty")
}
ctx := log.WithLogger(context.Background(), log.L)
switch action {
case "start":
return startNew(ctx, logPath, address, fuseStoreAddr, logLevel)
default:
return runFuseManager(ctx)
}
}
func startNew(ctx context.Context, logPath, address, fusestore, logLevel string) error {
self, err := os.Executable()
if err != nil {
return err
}
cwd, err := os.Getwd()
if err != nil {
return err
}
args := []string{
"-address", address,
"-fusestore-path", fusestore,
"-log-level", logLevel,
}
// we use shim-like approach to start new fusemanager process by self-invoking in the background
// and detach it from parent
cmd := exec.CommandContext(ctx, self, args...)
cmd.Dir = cwd
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
}
if logPath != "" {
err := os.Remove(logPath)
if err != nil && !os.IsNotExist(err) {
return err
}
file, err := os.Create(logPath)
if err != nil {
return err
}
cmd.Stdout = file
cmd.Stderr = file
}
if err := cmd.Start(); err != nil {
return err
}
go cmd.Wait()
if ready, err := waitUntilReady(ctx); err != nil || !ready {
if err != nil {
return fmt.Errorf("failed to start new fusemanager: %w", err)
}
if !ready {
return fmt.Errorf("failed to start new fusemanager, fusemanager not ready")
}
}
return nil
}
// waitUntilReady waits until fusemanager is ready to accept requests
func waitUntilReady(ctx context.Context) (bool, error) {
grpcCli, err := newClient(address)
if err != nil {
return false, err
}
resp, err := grpcCli.Status(ctx, &pb.StatusRequest{})
if err != nil {
log.G(ctx).WithError(err).Errorf("failed to call Status")
return false, err
}
if resp.Status == FuseManagerNotReady {
return false, nil
}
return true, nil
}
func runFuseManager(ctx context.Context) error {
lvl, err := logrus.ParseLevel(logLevel)
if err != nil {
return fmt.Errorf("failed to prepare logger: %w", err)
}
logrus.SetLevel(lvl)
logrus.SetFormatter(&logrus.JSONFormatter{
TimestampFormat: log.RFC3339NanoFixed,
})
golog.SetOutput(log.G(ctx).WriterLevel(logrus.DebugLevel))
// Prepare the directory for the socket
if err := os.MkdirAll(filepath.Dir(address), 0700); err != nil {
return fmt.Errorf("failed to create directory %s: %w", filepath.Dir(address), err)
}
// Try to remove the socket file to avoid EADDRINUSE
if err := os.Remove(address); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("failed to remove old socket file: %w", err)
}
l, err := net.Listen("unix", address)
if err != nil {
return fmt.Errorf("failed to listen socket: %w", err)
}
server := grpc.NewServer()
fm, err := NewFuseManager(ctx, l, server, fuseStoreAddr, address)
if err != nil {
return fmt.Errorf("failed to configure manager server: %w", err)
}
pb.RegisterStargzFuseManagerServiceServer(server, fm)
errCh := make(chan error, 1)
go func() {
if err := server.Serve(l); err != nil {
errCh <- fmt.Errorf("error on serving via socket %q: %w", address, err)
}
}()
var s os.Signal
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, unix.SIGINT, unix.SIGTERM)
select {
case s = <-sigCh:
log.G(ctx).Infof("Got %v", s)
case err := <-errCh:
log.G(ctx).WithError(err).Warnf("error during running the server")
}
server.Stop()
if err = fm.Close(ctx); err != nil {
return fmt.Errorf("failed to close fuse manager: %w", err)
}
return nil
}
func StartFuseManager(ctx context.Context, executable, address, fusestore, logLevel, logPath string) (newlyStarted bool, err error) {
// if socket exists, do not start it
if _, err := os.Stat(address); err == nil {
return false, nil
} else if !os.IsNotExist(err) {
return false, err
}
if _, err := os.Stat(executable); err != nil {
return false, fmt.Errorf("failed to stat fusemanager binary: %q", executable)
}
args := []string{
"-action", "start",
"-address", address,
"-fusestore-path", fusestore,
"-log-level", logLevel,
"-log-path", logPath,
}
cmd := exec.Command(executable, args...)
if err := cmd.Start(); err != nil {
return false, err
}
if err := cmd.Wait(); err != nil {
return false, err
}
return true, nil
}

View File

@ -1,235 +0,0 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fusemanager
import (
"context"
"encoding/json"
"fmt"
"net"
"os"
"path/filepath"
"testing"
pb "github.com/containerd/stargz-snapshotter/fusemanager/api"
"github.com/containerd/stargz-snapshotter/service"
"google.golang.org/grpc"
)
// mockFileSystem implements snapshot.FileSystem for testing
type mockFileSystem struct {
t *testing.T
mountErr error
checkErr error
unmountErr error
mountPoints map[string]bool
checkCalled bool
mountCalled bool
unmountCalled bool
}
func newMockFileSystem(t *testing.T) *mockFileSystem {
return &mockFileSystem{
t: t,
mountPoints: make(map[string]bool),
}
}
func (fs *mockFileSystem) Mount(ctx context.Context, mountpoint string, labels map[string]string) error {
fs.mountCalled = true
if fs.mountErr != nil {
return fs.mountErr
}
fs.mountPoints[mountpoint] = true
return nil
}
func (fs *mockFileSystem) Check(ctx context.Context, mountpoint string, labels map[string]string) error {
fs.checkCalled = true
if fs.checkErr != nil {
return fs.checkErr
}
if _, ok := fs.mountPoints[mountpoint]; !ok {
return fmt.Errorf("mountpoint %s not found", mountpoint)
}
return nil
}
func (fs *mockFileSystem) Unmount(ctx context.Context, mountpoint string) error {
fs.unmountCalled = true
if fs.unmountErr != nil {
return fs.unmountErr
}
delete(fs.mountPoints, mountpoint)
return nil
}
// mockServer embeds Server struct and overrides Init method
type mockServer struct {
*Server
initCalled bool
initErr error
}
func newMockServer(ctx context.Context, listener net.Listener, server *grpc.Server, fuseStoreAddr, serverAddr string) (*mockServer, error) {
s, err := NewFuseManager(ctx, listener, server, fuseStoreAddr, serverAddr)
if err != nil {
return nil, err
}
return &mockServer{Server: s}, nil
}
// Init overrides Server.Init to avoid actual initialization
func (s *mockServer) Init(ctx context.Context, req *pb.InitRequest) (*pb.Response, error) {
s.initCalled = true
if s.initErr != nil {
return nil, s.initErr
}
// Set only required fields
s.root = req.Root
config := &Config{}
if err := json.Unmarshal(req.Config, config); err != nil {
return nil, err
}
s.config = config
s.status = FuseManagerReady
return &pb.Response{}, nil
}
func TestFuseManager(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "fusemanager-test")
if err != nil {
t.Fatalf("failed to create temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
socketPath := filepath.Join(tmpDir, "test.sock")
fuseStorePath := filepath.Join(tmpDir, "fusestore.db")
fuseManagerSocketPath := filepath.Join(tmpDir, "test-fusemanager.sock")
l, err := net.Listen("unix", socketPath)
if err != nil {
t.Fatalf("failed to listen: %v", err)
}
defer l.Close()
// Create server with mock
grpcServer := grpc.NewServer()
mockFs := newMockFileSystem(t)
fm, err := newMockServer(context.Background(), l, grpcServer, fuseStorePath, fuseManagerSocketPath)
if err != nil {
t.Fatalf("failed to create fuse manager: %v", err)
}
defer fm.Close(context.Background())
pb.RegisterStargzFuseManagerServiceServer(grpcServer, fm)
// Set mock filesystem
fm.curFs = mockFs
go grpcServer.Serve(l)
defer grpcServer.Stop()
// Test cases to verify Init, Mount, Check and Unmount operations
testCases := []struct {
name string
mountpoint string
labels map[string]string
initErr error
mountErr error
checkErr error
unmountErr error
wantErr bool
}{
{
name: "successful init and mount",
mountpoint: filepath.Join(tmpDir, "mount1"),
labels: map[string]string{"key": "value"},
},
{
name: "init error",
mountpoint: filepath.Join(tmpDir, "mount2"),
initErr: fmt.Errorf("init error"),
wantErr: true,
},
{
name: "mount error",
mountpoint: filepath.Join(tmpDir, "mount3"),
mountErr: fmt.Errorf("mount error"),
wantErr: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
mockFs.mountErr = tc.mountErr
mockFs.checkErr = tc.checkErr
mockFs.unmountErr = tc.unmountErr
mockFs.mountCalled = false
mockFs.checkCalled = false
mockFs.unmountCalled = false
fm.initErr = tc.initErr
fm.initCalled = false
config := &Config{
Config: service.Config{},
}
client, err := NewManagerClient(context.Background(), tmpDir, socketPath, config)
if err != nil {
if !tc.wantErr {
t.Fatalf("failed to create client: %v", err)
}
return
}
if !fm.initCalled {
t.Error("Init() was not called")
}
if !tc.wantErr {
// Test Mount
err = client.Mount(context.Background(), tc.mountpoint, tc.labels)
if err != nil {
t.Errorf("Mount() error = %v", err)
}
if !mockFs.mountCalled {
t.Error("Mount() was not called on filesystem")
}
// Test Check
err = client.Check(context.Background(), tc.mountpoint, tc.labels)
if err != nil {
t.Errorf("Check() error = %v", err)
}
if !mockFs.checkCalled {
t.Error("Check() was not called on filesystem")
}
// Test Unmount
err = client.Unmount(context.Background(), tc.mountpoint)
if err != nil {
t.Errorf("Unmount() error = %v", err)
}
if !mockFs.unmountCalled {
t.Error("Unmount() was not called on filesystem")
}
}
})
}
}

View File

@ -1,99 +0,0 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fusemanager
import (
"context"
"encoding/json"
bolt "go.etcd.io/bbolt"
"github.com/containerd/stargz-snapshotter/service"
)
var (
fuseInfoBucket = []byte("fuse-info-bucket")
)
type fuseInfo struct {
Root string
Mountpoint string
Labels map[string]string
Config service.Config
}
func (fm *Server) storeFuseInfo(fuseInfo *fuseInfo) error {
return fm.ms.Update(func(tx *bolt.Tx) error {
bucket, err := tx.CreateBucketIfNotExists(fuseInfoBucket)
if err != nil {
return err
}
key := []byte(fuseInfo.Mountpoint)
val, err := json.Marshal(fuseInfo)
if err != nil {
return err
}
err = bucket.Put(key, val)
if err != nil {
return err
}
return nil
})
}
func (fm *Server) removeFuseInfo(fuseInfo *fuseInfo) error {
return fm.ms.Update(func(tx *bolt.Tx) error {
bucket, err := tx.CreateBucketIfNotExists(fuseInfoBucket)
if err != nil {
return err
}
key := []byte(fuseInfo.Mountpoint)
err = bucket.Delete(key)
if err != nil {
return err
}
return nil
})
}
// restoreFuseInfo restores fuseInfo when Init is called, it will skip mounted
// layers whose mountpoint can be found in fsMap
func (fm *Server) restoreFuseInfo(ctx context.Context) error {
return fm.ms.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket(fuseInfoBucket)
if bucket == nil {
return nil
}
return bucket.ForEach(func(_, v []byte) error {
mi := &fuseInfo{}
err := json.Unmarshal(v, mi)
if err != nil {
return err
}
return fm.mount(ctx, mi.Mountpoint, mi.Labels)
})
})
}

View File

@ -1,358 +0,0 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fusemanager
import (
"context"
"encoding/json"
"fmt"
"net"
"os"
"path/filepath"
"sync"
"time"
"github.com/containerd/log"
"github.com/moby/sys/mountinfo"
bolt "go.etcd.io/bbolt"
"google.golang.org/grpc"
pb "github.com/containerd/stargz-snapshotter/fusemanager/api"
"github.com/containerd/stargz-snapshotter/service"
"github.com/containerd/stargz-snapshotter/snapshot"
)
const (
FuseManagerNotReady = iota
FuseManagerWaitInit
FuseManagerReady
)
type Config struct {
Config service.Config
IPFS bool `toml:"ipfs" json:"ipfs"`
MetadataStore string `toml:"metadata_store" default:"memory" json:"metadata_store"`
DefaultImageServiceAddress string `json:"default_image_service_address"`
}
type ConfigContext struct {
Ctx context.Context
Config *Config
RootDir string
Server *grpc.Server
OpenBoltDB func(string) (*bolt.DB, error)
Address string
CRIServer *grpc.Server
}
var (
configFuncs []ConfigFunc
configMu sync.Mutex
)
type ConfigFunc func(cc *ConfigContext) ([]service.Option, error)
func RegisterConfigFunc(f ConfigFunc) {
configMu.Lock()
defer configMu.Unlock()
configFuncs = append(configFuncs, f)
}
// Opens bolt DB with avoiding opening the same DB multiple times
type dbOpener struct {
mu sync.Mutex
handles map[string]*bolt.DB
}
func (o *dbOpener) openBoltDB(p string) (*bolt.DB, error) {
o.mu.Lock()
defer o.mu.Unlock()
if db, ok := o.handles[p]; ok && db != nil {
// we opened it before. avoid trying to open this again.
return db, nil
}
db, err := bolt.Open(p, 0600, &bolt.Options{
NoFreelistSync: true,
InitialMmapSize: 64 * 1024 * 1024,
FreelistType: bolt.FreelistMapType,
})
if err != nil {
return nil, err
}
if o.handles == nil {
o.handles = make(map[string]*bolt.DB)
}
o.handles[p] = db
return db, nil
}
type Server struct {
pb.UnimplementedStargzFuseManagerServiceServer
lock sync.RWMutex
status int32
listener net.Listener
server *grpc.Server
// root is the latest root passed from containerd-stargz-grpc
root string
// config is the latest config passed from containerd-stargz-grpc
config *Config
// fsMap maps mountpoint to its filesystem instance to ensure Mount/Check/Unmount
// call the proper filesystem
fsMap sync.Map
// curFs is filesystem created by latest config
curFs snapshot.FileSystem
ms *bolt.DB
fuseStoreAddr string
dbOpener *dbOpener
serverAddr string
curCRIServer *grpc.Server
}
func NewFuseManager(ctx context.Context, listener net.Listener, server *grpc.Server, fuseStoreAddr string, serverAddr string) (*Server, error) {
if err := os.MkdirAll(filepath.Dir(fuseStoreAddr), 0700); err != nil {
return nil, fmt.Errorf("failed to create directory %q: %w", filepath.Dir(fuseStoreAddr), err)
}
db, err := bolt.Open(fuseStoreAddr, 0666, &bolt.Options{Timeout: 10 * time.Second, ReadOnly: false})
if err != nil {
return nil, fmt.Errorf("failed to configure fusestore: %w", err)
}
fm := &Server{
status: FuseManagerWaitInit,
lock: sync.RWMutex{},
fsMap: sync.Map{},
ms: db,
listener: listener,
server: server,
fuseStoreAddr: fuseStoreAddr,
dbOpener: &dbOpener{},
serverAddr: serverAddr,
}
return fm, nil
}
func (fm *Server) Status(ctx context.Context, _ *pb.StatusRequest) (*pb.StatusResponse, error) {
fm.lock.RLock()
defer fm.lock.RUnlock()
return &pb.StatusResponse{
Status: fm.status,
}, nil
}
func (fm *Server) Init(ctx context.Context, req *pb.InitRequest) (*pb.Response, error) {
fm.lock.Lock()
fm.status = FuseManagerWaitInit
defer func() {
fm.status = FuseManagerReady
fm.lock.Unlock()
}()
ctx = log.WithLogger(ctx, log.G(ctx))
config := &Config{}
err := json.Unmarshal(req.Config, config)
if err != nil {
log.G(ctx).WithError(err).Errorf("failed to get config")
return &pb.Response{}, err
}
fm.root = req.Root
fm.config = config
if fm.curCRIServer != nil {
fm.curCRIServer.Stop()
fm.curCRIServer = nil
}
cc := &ConfigContext{
Ctx: ctx,
Config: fm.config,
RootDir: fm.root,
Server: fm.server,
OpenBoltDB: fm.dbOpener.openBoltDB,
Address: fm.serverAddr,
}
var opts []service.Option
for _, configFunc := range configFuncs {
funcOpts, err := configFunc(cc)
if err != nil {
log.G(ctx).WithError(err).Errorf("failed to apply config function")
return &pb.Response{}, err
}
opts = append(opts, funcOpts...)
}
fm.curCRIServer = cc.CRIServer
fs, err := service.NewFileSystem(ctx, fm.root, &fm.config.Config, opts...)
if err != nil {
return &pb.Response{}, err
}
fm.curFs = fs
err = fm.restoreFuseInfo(ctx)
if err != nil {
log.G(ctx).WithError(err).Errorf("failed to restore fuse info")
return &pb.Response{}, err
}
return &pb.Response{}, nil
}
func (fm *Server) Mount(ctx context.Context, req *pb.MountRequest) (*pb.Response, error) {
fm.lock.RLock()
defer fm.lock.RUnlock()
if fm.status != FuseManagerReady {
return &pb.Response{}, fmt.Errorf("fuse manager not ready")
}
ctx = log.WithLogger(ctx, log.G(ctx).WithField("mountpoint", req.Mountpoint))
err := fm.mount(ctx, req.Mountpoint, req.Labels)
if err != nil {
log.G(ctx).WithError(err).Errorf("failed to mount stargz")
return &pb.Response{}, err
}
fm.storeFuseInfo(&fuseInfo{
Root: fm.root,
Mountpoint: req.Mountpoint,
Labels: req.Labels,
Config: fm.config.Config,
})
return &pb.Response{}, nil
}
func (fm *Server) Check(ctx context.Context, req *pb.CheckRequest) (*pb.Response, error) {
fm.lock.RLock()
defer fm.lock.RUnlock()
if fm.status != FuseManagerReady {
return &pb.Response{}, fmt.Errorf("fuse manager not ready")
}
ctx = log.WithLogger(ctx, log.G(ctx).WithField("mountpoint", req.Mountpoint))
obj, found := fm.fsMap.Load(req.Mountpoint)
if !found {
err := fmt.Errorf("failed to find filesystem of mountpoint %s", req.Mountpoint)
log.G(ctx).WithError(err).Errorf("failed to check filesystem")
return &pb.Response{}, err
}
fs := obj.(snapshot.FileSystem)
err := fs.Check(ctx, req.Mountpoint, req.Labels)
if err != nil {
log.G(ctx).WithError(err).Errorf("failed to check filesystem")
return &pb.Response{}, err
}
return &pb.Response{}, nil
}
func (fm *Server) Unmount(ctx context.Context, req *pb.UnmountRequest) (*pb.Response, error) {
fm.lock.RLock()
defer fm.lock.RUnlock()
if fm.status != FuseManagerReady {
return &pb.Response{}, fmt.Errorf("fuse manager not ready")
}
ctx = log.WithLogger(ctx, log.G(ctx).WithField("mountpoint", req.Mountpoint))
obj, found := fm.fsMap.Load(req.Mountpoint)
if !found {
// check whether already unmounted
mounts, err := mountinfo.GetMounts(func(info *mountinfo.Info) (skip, stop bool) {
if info.Mountpoint == req.Mountpoint {
return false, true
}
return true, false
})
if err != nil {
log.G(ctx).WithError(err).Errorf("failed to get mount info")
return &pb.Response{}, err
}
if len(mounts) <= 0 {
return &pb.Response{}, nil
}
err = fmt.Errorf("failed to find filesystem of mountpoint %s", req.Mountpoint)
log.G(ctx).WithError(err).Errorf("failed to unmount filesystem")
return &pb.Response{}, err
}
fs := obj.(snapshot.FileSystem)
err := fs.Unmount(ctx, req.Mountpoint)
if err != nil {
log.G(ctx).WithError(err).Errorf("failed to unmount filesystem")
return &pb.Response{}, err
}
fm.fsMap.Delete(req.Mountpoint)
fm.removeFuseInfo(&fuseInfo{
Mountpoint: req.Mountpoint,
})
return &pb.Response{}, nil
}
func (fm *Server) Close(ctx context.Context) error {
fm.lock.Lock()
defer fm.lock.Unlock()
fm.status = FuseManagerNotReady
err := fm.ms.Close()
if err != nil {
log.G(ctx).WithError(err).Errorf("failed to close fusestore")
return err
}
if err := os.Remove(fm.fuseStoreAddr); err != nil {
log.G(ctx).WithError(err).Errorf("failed to remove fusestore file %s", fm.fuseStoreAddr)
return err
}
return nil
}
func (fm *Server) mount(ctx context.Context, mountpoint string, labels map[string]string) error {
// mountpoint in fsMap means layer is already mounted, skip it
if _, found := fm.fsMap.Load(mountpoint); found {
return nil
}
err := fm.curFs.Mount(ctx, mountpoint, labels)
if err != nil {
log.G(ctx).WithError(err).Errorf("failed to mount stargz")
return err
}
fm.fsMap.Store(mountpoint, fm.curFs)
return nil
}

193
go.mod
View File

@ -1,138 +1,119 @@
module github.com/containerd/stargz-snapshotter module github.com/containerd/stargz-snapshotter
go 1.24.0 go 1.19
toolchain go1.24.2
require ( require (
github.com/containerd/console v1.0.5 github.com/containerd/console v1.0.3
github.com/containerd/containerd/v2 v2.1.4 github.com/containerd/containerd v1.7.0-rc.2
github.com/containerd/continuity v0.4.5 github.com/containerd/continuity v0.3.0
github.com/containerd/errdefs v1.0.0 github.com/containerd/stargz-snapshotter/estargz v0.14.3
github.com/containerd/log v0.1.0 github.com/docker/cli v23.0.1+incompatible
github.com/containerd/platforms v1.0.0-rc.1
github.com/containerd/plugin v1.0.0
github.com/containerd/stargz-snapshotter/estargz v0.17.0
github.com/distribution/reference v0.6.0
github.com/docker/cli v28.3.3+incompatible
github.com/docker/go-metrics v0.0.1 github.com/docker/go-metrics v0.0.1
github.com/gogo/protobuf v1.3.2
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da
github.com/hanwen/go-fuse/v2 v2.8.0 github.com/hanwen/go-fuse/v2 v2.2.0
github.com/hashicorp/go-retryablehttp v0.7.8 github.com/hashicorp/go-multierror v1.1.1
github.com/klauspost/compress v1.18.0 github.com/hashicorp/go-retryablehttp v0.7.2
github.com/moby/sys/mountinfo v0.7.2 github.com/klauspost/compress v1.16.0
github.com/moby/sys/mountinfo v0.6.2
github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.1 github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b
github.com/opencontainers/runtime-spec v1.2.1 github.com/opencontainers/runtime-spec v1.1.0-rc.1
github.com/prometheus/client_golang v1.23.0 github.com/prometheus/client_golang v1.14.0
github.com/rs/xid v1.6.0 github.com/rs/xid v1.4.0
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.0
go.etcd.io/bbolt v1.4.2 golang.org/x/sync v0.1.0
golang.org/x/sync v0.16.0 golang.org/x/sys v0.6.0
golang.org/x/sys v0.34.0 google.golang.org/grpc v1.53.0
google.golang.org/grpc v1.74.2 google.golang.org/protobuf v1.28.1
k8s.io/api v0.33.3 k8s.io/api v0.26.2
k8s.io/apimachinery v0.33.3 k8s.io/apimachinery v0.26.2
k8s.io/client-go v0.33.3 k8s.io/client-go v0.26.2
k8s.io/cri-api v0.33.3 k8s.io/cri-api v0.27.0-alpha.3
) )
require ( require (
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 // indirect
github.com/Microsoft/hcsshim v0.13.0 // indirect github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20221215162035-5330a85ea652 // indirect
github.com/Microsoft/go-winio v0.6.0 // indirect
github.com/Microsoft/hcsshim v0.10.0-rc.7 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/containerd/cgroups/v3 v3.0.5 // indirect github.com/containerd/cgroups v1.1.0 // indirect
github.com/containerd/containerd/api v1.9.0 // indirect github.com/containerd/cgroups/v3 v3.0.1 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/fifo v1.1.0 // indirect github.com/containerd/fifo v1.1.0 // indirect
github.com/containerd/go-cni v1.1.13 // indirect github.com/containerd/go-cni v1.1.9 // indirect
github.com/containerd/ttrpc v1.2.7 // indirect github.com/containerd/ttrpc v1.2.0 // indirect
github.com/containerd/typeurl/v2 v2.2.3 // indirect github.com/containerd/typeurl/v2 v2.1.0 // indirect
github.com/containernetworking/cni v1.3.0 // indirect github.com/containernetworking/cni v1.1.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/docker v20.10.20+incompatible // indirect
github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/emicklei/go-restful/v3 v3.10.1 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-logr/logr v1.2.3 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/jsonreference v0.20.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect github.com/go-openapi/swag v0.19.14 // indirect
github.com/google/gnostic-models v0.6.9 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/go-cmp v0.7.0 // indirect github.com/golang/protobuf v1.5.2 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.6 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/moby/locker v1.0.1 // indirect github.com/moby/locker v1.0.1 // indirect
github.com/moby/sys/sequential v0.6.0 // indirect github.com/moby/sys/sequential v0.5.0 // indirect
github.com/moby/sys/signal v0.7.1 // indirect github.com/moby/sys/signal v0.7.0 // indirect
github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/opencontainers/selinux v1.12.0 // indirect github.com/opencontainers/runc v1.1.4 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/opencontainers/selinux v1.11.0 // indirect
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.16.1 // indirect github.com/prometheus/procfs v0.8.0 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sasha-s/go-deadlock v0.3.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/pflag v1.0.6 // indirect github.com/stretchr/testify v1.8.2 // indirect
github.com/stretchr/testify v1.10.0 // indirect github.com/urfave/cli v1.22.12 // indirect
github.com/urfave/cli/v2 v2.27.7 // indirect github.com/vbatts/tar-split v0.11.2 // indirect
github.com/vbatts/tar-split v0.12.1 // indirect go.etcd.io/bbolt v1.3.7 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
go.opencensus.io v0.24.0 // indirect go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/otel v1.12.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect go.opentelemetry.io/otel/trace v1.12.0 // indirect
go.opentelemetry.io/otel v1.36.0 // indirect golang.org/x/mod v0.7.0 // indirect
go.opentelemetry.io/otel/metric v1.36.0 // indirect golang.org/x/net v0.7.0 // indirect
go.opentelemetry.io/otel/trace v1.36.0 // indirect golang.org/x/oauth2 v0.4.0 // indirect
golang.org/x/net v0.40.0 // indirect golang.org/x/term v0.5.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/text v0.7.0 // indirect
golang.org/x/term v0.32.0 // indirect golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
golang.org/x/text v0.25.0 // indirect golang.org/x/tools v0.5.0 // indirect
golang.org/x/time v0.9.0 // indirect google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
gotest.tools/v3 v3.5.0 // indirect gotest.tools/v3 v3.4.0 // indirect
k8s.io/klog/v2 v2.130.1 // indirect k8s.io/klog/v2 v2.90.1 // indirect
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect sigs.k8s.io/yaml v1.3.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
) )
// Import local package for estargz. // Import local package for estargz.
replace github.com/containerd/stargz-snapshotter/estargz => ./estargz replace github.com/containerd/stargz-snapshotter/estargz => ./estargz
exclude (
// These dependencies were updated to "master" in some modules we depend on,
// but have no code-changes since their last release. Unfortunately, this also
// causes a ripple effect, forcing all users of the containerd module to also
// update these dependencies to an unrelease / un-tagged version.
//
// Both these dependencies will unlikely do a new release in the near future,
// so exclude these versions so that we can downgrade to the current release.
//
// For additional details, see this PR and links mentioned in that PR:
// https://github.com/kubernetes-sigs/kustomize/pull/5830#issuecomment-2569960859
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2
)

789
go.sum
View File

@ -1,183 +1,282 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 h1:EKPd1INOIyr5hWOWhvpmQpY6tKjeG0hT1s3AMC/9fic=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1/go.mod h1:VzwV+t+dZ9j/H867F1M2ziD+yLHtB46oM35FxxMJ4d0=
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20221215162035-5330a85ea652 h1:+vTEFqeoeur6XSq06bs+roX3YiT49gUniJK7Zky7Xjg=
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20221215162035-5330a85ea652/go.mod h1:OahwfttHWG6eJ0clwcfBAHoDI6X/LV/15hx/wlMZSrU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Microsoft/hcsshim v0.13.0 h1:/BcXOiS6Qi7N9XqUcv27vkIuVOkBEcWstd2pMlWSeaA= github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
github.com/Microsoft/hcsshim v0.13.0/go.mod h1:9KWJ/8DgU+QzYGupX4tzMhRQE8h6w90lH6HAaclpEok= github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
github.com/Microsoft/hcsshim v0.10.0-rc.7 h1:HBytQPxcv8Oy4244zbQbe6hnOnx544eL5QPUqhJldz8=
github.com/Microsoft/hcsshim v0.10.0-rc.7/go.mod h1:ILuwjA+kNW+MrN/w5un7n3mTqkwsFu4Bp05/okFUZlE=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
github.com/containerd/console v1.0.5 h1:R0ymNeydRqH2DmakFNdmjR2k0t7UPuiOV/N/27/qqsc= github.com/containerd/cgroups/v3 v3.0.1 h1:4hfGvu8rfGIwVIDd+nLzn/B9ZXx4BcCjzt5ToenJRaE=
github.com/containerd/console v1.0.5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= github.com/containerd/cgroups/v3 v3.0.1/go.mod h1:/vtwk1VXrtoa5AaZLkypuOJgA/6DyPMZHJPGQNtlHnw=
github.com/containerd/containerd/api v1.9.0 h1:HZ/licowTRazus+wt9fM6r/9BQO7S0vD5lMcWspGIg0= github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
github.com/containerd/containerd/api v1.9.0/go.mod h1:GhghKFmTR3hNtyznBoQ0EMWr9ju5AqHjcZPsSpTKutI= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
github.com/containerd/containerd/v2 v2.1.4 h1:/hXWjiSFd6ftrBOBGfAZ6T30LJcx1dBjdKEeI8xucKQ= github.com/containerd/containerd v1.7.0-rc.2 h1:Me1LnvqNQPl56LrVpVkNiQPPLomEKgZi2wKbe6ixx6w=
github.com/containerd/containerd/v2 v2.1.4/go.mod h1:8C5QV9djwsYDNhxfTCFjWtTBZrqjditQ4/ghHSYjnHM= github.com/containerd/containerd v1.7.0-rc.2/go.mod h1:N8hwWslQgbkbPIQulOL9Qgsw5x7VWZppXQLZburjSso=
github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg=
github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY= github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY=
github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o= github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o=
github.com/containerd/go-cni v1.1.13 h1:eFSGOKlhoYNxpJ51KRIMHZNlg5UgocXEIEBGkY7Hnis= github.com/containerd/go-cni v1.1.9 h1:ORi7P1dYzCwVM6XPN4n3CbkuOx/NZ2DOqy+SHRdo9rU=
github.com/containerd/go-cni v1.1.13/go.mod h1:nTieub0XDRmvCZ9VI/SBG6PyqT95N4FIhxsauF1vSBI= github.com/containerd/go-cni v1.1.9/go.mod h1:XYrZJ1d5W6E2VOvjffL3IZq0Dz6bsVlERHbekNK90PM=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/ttrpc v1.2.0 h1:we4O9wzVsBA1HUVRGU8CWFsbjy2P/U2g9raVu5XXNI0=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/ttrpc v1.2.0/go.mod h1:YYyNVhZrTMiaf51Vj6WhAJqJw+vl/nzABhj8pWrzle4=
github.com/containerd/platforms v1.0.0-rc.1 h1:83KIq4yy1erSRgOVHNk1HYdPvzdJ5CnsWaRoJX4C41E= github.com/containerd/typeurl/v2 v2.1.0 h1:yNAhJvbNEANt7ck48IlEGOxP7YAp6LLpGn5jZACDNIE=
github.com/containerd/platforms v1.0.0-rc.1/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4= github.com/containerd/typeurl/v2 v2.1.0/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0=
github.com/containerd/plugin v1.0.0 h1:c8Kf1TNl6+e2TtMHZt+39yAPDbouRH9WAToRjex483Y= github.com/containernetworking/cni v1.1.2 h1:wtRGZVv7olUHMOqouPpn3cXJWpJgM6+EUl31EQbXALQ=
github.com/containerd/plugin v1.0.0/go.mod h1:hQfJe5nmWfImiqT1q8Si3jLv3ynMUIBB47bQ+KexvO8= github.com/containernetworking/cni v1.1.2/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw=
github.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRqQ= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/containernetworking/cni v1.3.0 h1:v6EpN8RznAZj9765HhXQrtXgX+ECGebEYEmnuFjskwo=
github.com/containernetworking/cni v1.3.0/go.mod h1:Bs8glZjjFfGPHMw6hQu82RUgEPNGEaBb9KS5KtNMnJ4=
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI=
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/docker/cli v23.0.1+incompatible h1:LRyWITpGzl2C9e9uGxzisptnxAn1zfZKXy13Ul2Q5oM=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/cli v23.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v28.3.3+incompatible h1:fp9ZHAr1WWPGdIWBM1b3zLtgCF+83gRdVMTJsUeiyAo= github.com/docker/docker v20.10.20+incompatible h1:kH9tx6XO+359d+iAkumyKDc5Q1kOwPuAUaeri48nD6E=
github.com/docker/cli v28.3.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/docker v20.10.20+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A=
github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0=
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8=
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=
github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ=
github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54=
github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hanwen/go-fuse/v2 v2.8.0 h1:wV8rG7rmCz8XHSOwBZhG5YcVqcYjkzivjmbaMafPlAs= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/hanwen/go-fuse/v2 v2.8.0/go.mod h1:yE6D2PqWwm3CbYRxFXV9xUd8Md5d6NG0WBs5spCswmI= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/hanwen/go-fuse/v2 v2.2.0 h1:jo5QZYmBLNcl9ovypWaQ5yXMSSV+Ch68xoC3rtZvvBM=
github.com/hanwen/go-fuse/v2 v2.2.0/go.mod h1:B1nGE/6RBFyBRC1RRnf23UpwCdyJ31eukw34oAKukAc=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0=
github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU=
github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
github.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0= github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
github.com/moby/sys/signal v0.7.1/go.mod h1:Se1VGehYokAkrSQwL4tDzHvETwUZlnY7S5XtQ50mQp8= github.com/moby/sys/signal v0.7.0 h1:25RW3d5TnQEoKvRbEKUGay6DCQ46IxAVTT9CUMgmsSI=
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= github.com/moby/sys/signal v0.7.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg=
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -185,26 +284,40 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1:YWuSjZCQAPM8UUBLkYUk1e+rZcvWHJmFb6i6rM44Xs8=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ=
github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww= github.com/opencontainers/runc v1.1.4 h1:nRCz/8sKg6K6jgYAFLDlXzPeITBZJyX28DBVhWD+5dg=
github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runc v1.1.4/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg=
github.com/opencontainers/selinux v1.12.0 h1:6n5JV4Cf+4y0KNXW48TLj5DwfXpvWlxXplUkdTrmPb8= github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/selinux v1.12.0/go.mod h1:BTPX+bjVbWGXw7ZZWUbdENt8w0htPSrlgOOysQaU62U= github.com/opencontainers/runtime-spec v1.1.0-rc.1 h1:wHa9jroFfKGQqFHj0I1fMRKLl0pfj+ynAqBxo3v6u9w=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/opencontainers/runtime-spec v1.1.0-rc.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 h1:Dx7Ovyv/SFnMFw3fD4oEoeorXc6saIiQ23LrGLth0Gw= github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU=
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@ -212,162 +325,387 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE=
github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sasha-s/go-deadlock v0.3.5 h1:tNCOEEDG6tBqrNDOX35j/7hL5FcFViG6awUGROb2NsU= github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
github.com/sasha-s/go-deadlock v0.3.5/go.mod h1:bugP6EGbdGYObIlx7pUZtWqlvo8k9H6vCBBsiChJQ5U= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo= github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA= github.com/urfave/cli v1.22.12 h1:igJgVw1JdKH+trcLWLeLwZjU9fEfPesQ+9/e4MQ44S8=
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8=
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/vbatts/tar-split v0.11.2/go.mod h1:vV3ZuO2yWSVsz+pfFzDG/upWH1JhjOiEaWq6kXyQ3VI=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I= go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM= go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/otel v1.12.0 h1:IgfC7kqQrRccIKuB7Cl+SRUmsKbEwSGPr0Eu+/ht1SQ=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/otel v1.12.0/go.mod h1:geaoz0L0r1BEOR81k7/n9W4TCXYCJ7bPO7K374jQHG0=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= go.opentelemetry.io/otel/trace v1.12.0 h1:p28in++7Kd0r2d8gSt931O57fdjUyWxkVbESuILAeUc=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= go.opentelemetry.io/otel/trace v1.12.0/go.mod h1:pHlgBynn6s25qJ2szD+Bv+iwKJttjHSI3lUAyf0GNuQ=
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M=
golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/tools v0.5.0 h1:+bSpV5HIeWkuvgaMfI3UmKRThoTA5ODJTUd8T17NO+4=
golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA=
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -376,45 +714,66 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.33.3 h1:SRd5t//hhkI1buzxb288fy2xvjubstenEKL9K51KBI8= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
k8s.io/api v0.33.3/go.mod h1:01Y/iLUjNBM3TAvypct7DIj0M0NIZc+PzAHCIo0CYGE= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/apimachinery v0.33.3 h1:4ZSrmNa0c/ZpZJhAgRdcsFcZOw1PQU1bALVQ0B3I5LA= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/apimachinery v0.33.3/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= k8s.io/api v0.26.2 h1:dM3cinp3PGB6asOySalOZxEG4CZ0IAdJsrYZXE/ovGQ=
k8s.io/client-go v0.33.3 h1:M5AfDnKfYmVJif92ngN532gFqakcGi6RvaOF16efrpA= k8s.io/api v0.26.2/go.mod h1:1kjMQsFE+QHPfskEcVNgL3+Hp88B80uj0QtSOlj8itU=
k8s.io/client-go v0.33.3/go.mod h1:luqKBQggEf3shbxHY4uVENAxrDISLOarxpTKMiUuujg= k8s.io/apimachinery v0.26.2 h1:da1u3D5wfR5u2RpLhE/ZtZS2P7QvDgLZTi9wrNZl/tQ=
k8s.io/cri-api v0.33.3 h1:aQvK3UxsaVMul4z71lOiblMHdhw9ROaw3Cgg15xDrD4= k8s.io/apimachinery v0.26.2/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I=
k8s.io/cri-api v0.33.3/go.mod h1:OLQvT45OpIA+tv91ZrpuFIGY+Y2Ho23poS7n115Aocs= k8s.io/client-go v0.26.2 h1:s1WkVujHX3kTp4Zn4yGNFK+dlDXy1bAAkIl+cFAiuYI=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/client-go v0.26.2/go.mod h1:u5EjOuSyBa09yqqyY7m3abZeovO/7D/WehVVlZ2qcqU=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/cri-api v0.27.0-alpha.3 h1:RT3W8OnLFpk9MUod99GwYeNPnzhEqVH0aQE8zBKZ9ds=
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= k8s.io/cri-api v0.27.0-alpha.3/go.mod h1:dwMiSnrLiMmrAsTo/fObV8+efaoI9hHa1IlL++hdDIs=
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 h1:kmDqav+P+/5e1i9tFfHq1qcF3sOrDp+YEkVDAHu7Jwk=
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=

View File

@ -50,9 +50,9 @@ func New(ipfsAPIAddress string) *Client {
// Please see details at: https://docs.ipfs.tech/reference/kubo/rpc/#api-v0-files-stat // Please see details at: https://docs.ipfs.tech/reference/kubo/rpc/#api-v0-files-stat
type FileInfo struct { type FileInfo struct {
Blocks int `json:"Blocks"` Blocks int `json:"Blocks"`
CumulativeSize uint64 `json:"CumulativeSize"` CumulativeSize uint64 `json:"CumulativeSize": "<uint64>"`
Hash string `json:"Hash"` Hash string `json:"Hash": "<string>"`
Local bool `json:"Local"` Local bool `json:"Local": "<bool>"`
Size uint64 `json:"Size"` Size uint64 `json:"Size"`
SizeLocal uint64 `json:"SizeLocal"` SizeLocal uint64 `json:"SizeLocal"`
Type string `json:"Type"` Type string `json:"Type"`

View File

@ -21,13 +21,13 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"os"
"strings" "strings"
"os"
containerd "github.com/containerd/containerd/v2/client" "github.com/containerd/containerd"
"github.com/containerd/containerd/v2/core/content" "github.com/containerd/containerd/content"
"github.com/containerd/containerd/v2/core/images/converter" "github.com/containerd/containerd/images/converter"
"github.com/containerd/platforms" "github.com/containerd/containerd/platforms"
ipfsclient "github.com/containerd/stargz-snapshotter/ipfs/client" ipfsclient "github.com/containerd/stargz-snapshotter/ipfs/client"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
) )

View File

@ -1,73 +1,67 @@
module github.com/containerd/stargz-snapshotter/ipfs module github.com/containerd/stargz-snapshotter/ipfs
go 1.23.0 go 1.19
toolchain go1.24.1
require ( require (
github.com/containerd/containerd/v2 v2.1.4 github.com/containerd/containerd v1.7.0-rc.2
github.com/containerd/platforms v1.0.0-rc.1
github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-homedir v1.1.0
github.com/multiformats/go-multiaddr v0.16.1 github.com/multiformats/go-multiaddr v0.8.0
github.com/opencontainers/image-spec v1.1.1 github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b
) )
require ( require (
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 // indirect
github.com/Microsoft/hcsshim v0.13.0 // indirect github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20221215162035-5330a85ea652 // indirect
github.com/containerd/cgroups/v3 v3.0.5 // indirect github.com/Microsoft/go-winio v0.6.0 // indirect
github.com/containerd/containerd/api v1.9.0 // indirect github.com/Microsoft/hcsshim v0.10.0-rc.7 // indirect
github.com/containerd/continuity v0.4.5 // indirect github.com/containerd/cgroups v1.1.0 // indirect
github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/continuity v0.3.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/fifo v1.1.0 // indirect github.com/containerd/fifo v1.1.0 // indirect
github.com/containerd/log v0.1.0 // indirect github.com/containerd/ttrpc v1.2.0 // indirect
github.com/containerd/plugin v1.0.0 // indirect github.com/containerd/typeurl/v2 v2.1.0 // indirect
github.com/containerd/ttrpc v1.2.7 // indirect github.com/cyphar/filepath-securejoin v0.2.3 // indirect
github.com/containerd/typeurl/v2 v2.2.3 // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
github.com/distribution/reference v0.6.0 // indirect github.com/go-logr/logr v1.2.3 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/go-cmp v0.7.0 // indirect github.com/golang/protobuf v1.5.2 // indirect
github.com/ipfs/go-cid v0.0.7 // indirect github.com/google/go-cmp v0.5.9 // indirect
github.com/klauspost/compress v1.18.0 // indirect github.com/google/uuid v1.3.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect github.com/ipfs/go-cid v0.1.0 // indirect
github.com/minio/sha256-simd v1.0.1 // indirect github.com/klauspost/compress v1.16.0 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect
github.com/minio/sha256-simd v1.0.0 // indirect
github.com/moby/locker v1.0.1 // indirect github.com/moby/locker v1.0.1 // indirect
github.com/moby/sys/mountinfo v0.7.2 // indirect github.com/moby/sys/mountinfo v0.6.2 // indirect
github.com/moby/sys/sequential v0.6.0 // indirect github.com/moby/sys/sequential v0.5.0 // indirect
github.com/moby/sys/signal v0.7.1 // indirect github.com/moby/sys/signal v0.7.0 // indirect
github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect github.com/mr-tron/base58 v1.2.0 // indirect
github.com/multiformats/go-base32 v0.1.0 // indirect github.com/multiformats/go-base32 v0.0.3 // indirect
github.com/multiformats/go-base36 v0.2.0 // indirect github.com/multiformats/go-base36 v0.1.0 // indirect
github.com/multiformats/go-multibase v0.2.0 // indirect github.com/multiformats/go-multibase v0.0.3 // indirect
github.com/multiformats/go-multihash v0.2.3 // indirect github.com/multiformats/go-multihash v0.1.0 // indirect
github.com/multiformats/go-varint v0.0.7 // indirect github.com/multiformats/go-varint v0.0.6 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/runtime-spec v1.2.1 // indirect github.com/opencontainers/runc v1.1.4 // indirect
github.com/opencontainers/selinux v1.12.0 // indirect github.com/opencontainers/runtime-spec v1.1.0-rc.1 // indirect
github.com/opencontainers/selinux v1.11.0 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect github.com/sirupsen/logrus v1.9.0 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect
go.opencensus.io v0.24.0 // indirect go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/otel v1.12.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect go.opentelemetry.io/otel/trace v1.12.0 // indirect
go.opentelemetry.io/otel v1.36.0 // indirect golang.org/x/crypto v0.1.0 // indirect
go.opentelemetry.io/otel/metric v1.36.0 // indirect golang.org/x/mod v0.7.0 // indirect
go.opentelemetry.io/otel/trace v1.36.0 // indirect golang.org/x/net v0.7.0 // indirect
golang.org/x/crypto v0.38.0 // indirect golang.org/x/sync v0.1.0 // indirect
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect golang.org/x/sys v0.6.0 // indirect
golang.org/x/net v0.40.0 // indirect golang.org/x/text v0.7.0 // indirect
golang.org/x/sync v0.16.0 // indirect golang.org/x/tools v0.5.0 // indirect
golang.org/x/sys v0.34.0 // indirect google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect
golang.org/x/text v0.25.0 // indirect google.golang.org/grpc v1.53.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect google.golang.org/protobuf v1.28.1 // indirect
google.golang.org/grpc v1.74.2 // indirect lukechampine.com/blake3 v1.1.6 // indirect
google.golang.org/protobuf v1.36.6 // indirect
lukechampine.com/blake3 v1.2.1 // indirect
) )

View File

@ -1,54 +1,53 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 h1:EKPd1INOIyr5hWOWhvpmQpY6tKjeG0hT1s3AMC/9fic=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1/go.mod h1:VzwV+t+dZ9j/H867F1M2ziD+yLHtB46oM35FxxMJ4d0=
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20221215162035-5330a85ea652 h1:+vTEFqeoeur6XSq06bs+roX3YiT49gUniJK7Zky7Xjg=
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20221215162035-5330a85ea652/go.mod h1:OahwfttHWG6eJ0clwcfBAHoDI6X/LV/15hx/wlMZSrU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
github.com/Microsoft/hcsshim v0.13.0 h1:/BcXOiS6Qi7N9XqUcv27vkIuVOkBEcWstd2pMlWSeaA= github.com/Microsoft/hcsshim v0.10.0-rc.7 h1:HBytQPxcv8Oy4244zbQbe6hnOnx544eL5QPUqhJldz8=
github.com/Microsoft/hcsshim v0.13.0/go.mod h1:9KWJ/8DgU+QzYGupX4tzMhRQE8h6w90lH6HAaclpEok= github.com/Microsoft/hcsshim v0.10.0-rc.7/go.mod h1:ILuwjA+kNW+MrN/w5un7n3mTqkwsFu4Bp05/okFUZlE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
github.com/containerd/containerd/api v1.9.0 h1:HZ/licowTRazus+wt9fM6r/9BQO7S0vD5lMcWspGIg0= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
github.com/containerd/containerd/api v1.9.0/go.mod h1:GhghKFmTR3hNtyznBoQ0EMWr9ju5AqHjcZPsSpTKutI= github.com/containerd/containerd v1.7.0-rc.2 h1:Me1LnvqNQPl56LrVpVkNiQPPLomEKgZi2wKbe6ixx6w=
github.com/containerd/containerd/v2 v2.1.4 h1:/hXWjiSFd6ftrBOBGfAZ6T30LJcx1dBjdKEeI8xucKQ= github.com/containerd/containerd v1.7.0-rc.2/go.mod h1:N8hwWslQgbkbPIQulOL9Qgsw5x7VWZppXQLZburjSso=
github.com/containerd/containerd/v2 v2.1.4/go.mod h1:8C5QV9djwsYDNhxfTCFjWtTBZrqjditQ4/ghHSYjnHM= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg=
github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4= github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM=
github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY= github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY=
github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o= github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/ttrpc v1.2.0 h1:we4O9wzVsBA1HUVRGU8CWFsbjy2P/U2g9raVu5XXNI0=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/ttrpc v1.2.0/go.mod h1:YYyNVhZrTMiaf51Vj6WhAJqJw+vl/nzABhj8pWrzle4=
github.com/containerd/platforms v1.0.0-rc.1 h1:83KIq4yy1erSRgOVHNk1HYdPvzdJ5CnsWaRoJX4C41E= github.com/containerd/typeurl/v2 v2.1.0 h1:yNAhJvbNEANt7ck48IlEGOxP7YAp6LLpGn5jZACDNIE=
github.com/containerd/platforms v1.0.0-rc.1/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4= github.com/containerd/typeurl/v2 v2.1.0/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0=
github.com/containerd/plugin v1.0.0 h1:c8Kf1TNl6+e2TtMHZt+39yAPDbouRH9WAToRjex483Y= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/containerd/plugin v1.0.0/go.mod h1:hQfJe5nmWfImiqT1q8Si3jLv3ynMUIBB47bQ+KexvO8= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRqQ= github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI=
github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40=
github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@ -58,6 +57,7 @@ github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4er
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
@ -65,127 +65,129 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/ipfs/go-cid v0.0.7 h1:ysQJVJA3fNDF1qigJbsSQOdjhVLsOEoPdh0+R97k3jY= github.com/ipfs/go-cid v0.1.0 h1:YN33LQulcRHjfom/i25yoOZR4Telp1Hr/2RU3d0PnC0=
github.com/ipfs/go-cid v0.0.7/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= github.com/ipfs/go-cid v0.1.0/go.mod h1:rH5/Xv83Rfy8Rw6xG+id3DYAMUVmem1MowoKwdXmN2o=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g=
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=
github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU=
github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
github.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0= github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
github.com/moby/sys/signal v0.7.1/go.mod h1:Se1VGehYokAkrSQwL4tDzHvETwUZlnY7S5XtQ50mQp8= github.com/moby/sys/signal v0.7.0 h1:25RW3d5TnQEoKvRbEKUGay6DCQ46IxAVTT9CUMgmsSI=
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= github.com/moby/sys/signal v0.7.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg=
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8=
github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
github.com/multiformats/go-base32 v0.0.3 h1:tw5+NhuwaOjJCC5Pp82QuXbrmLzWg7uxlMFp8Nq/kkI=
github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA= github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA=
github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= github.com/multiformats/go-base36 v0.1.0 h1:JR6TyF7JjGd3m6FbLU2cOxhC0Li8z8dLNGQ89tUg4F4=
github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=
github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM=
github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= github.com/multiformats/go-multiaddr v0.8.0 h1:aqjksEcqK+iD/Foe1RRFsGZh8+XFiGo7FgUCZlpv3LU=
github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= github.com/multiformats/go-multiaddr v0.8.0/go.mod h1:Fs50eBDWvZu+l3/9S6xAE7ZYj6yhxlvaVZjakWN7xRs=
github.com/multiformats/go-multiaddr v0.16.1 h1:fgJ0Pitow+wWXzN9do+1b8Pyjmo8m5WhGfzpL82MpCw= github.com/multiformats/go-multibase v0.0.3 h1:l/B6bJDQjvQ5G52jw4QGSYeOTZoAwIO77RblWplfIqk=
github.com/multiformats/go-multiaddr v0.16.1/go.mod h1:JSVUmXDjsVFiW7RjIFMP7+Ev+h1DTbiJgVeTV/tcmP0=
github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc=
github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= github.com/multiformats/go-multihash v0.0.15/go.mod h1:D6aZrWNLFTV/ynMpKsNtB40mJzmCl4jb1alC0OvHiHg=
github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= github.com/multiformats/go-multihash v0.1.0 h1:CgAgwqk3//SVEw3T+6DqI4mWMyRuDwZtOWcJT0q9+EA=
github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= github.com/multiformats/go-multihash v0.1.0/go.mod h1:RJlXsxt6vHGaia+S8We0ErjhojtKzPP2AH4+kYM7k84=
github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= github.com/multiformats/go-varint v0.0.6 h1:gk85QWKxh3TazbLxED/NlDVv8+q+ReFJk7Y2W/KhfNY=
github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
github.com/multiformats/go-varint v0.0.5/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=
github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1:YWuSjZCQAPM8UUBLkYUk1e+rZcvWHJmFb6i6rM44Xs8=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ=
github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww= github.com/opencontainers/runc v1.1.4 h1:nRCz/8sKg6K6jgYAFLDlXzPeITBZJyX28DBVhWD+5dg=
github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runc v1.1.4/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg=
github.com/opencontainers/selinux v1.12.0 h1:6n5JV4Cf+4y0KNXW48TLj5DwfXpvWlxXplUkdTrmPb8= github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/selinux v1.12.0/go.mod h1:BTPX+bjVbWGXw7ZZWUbdENt8w0htPSrlgOOysQaU62U= github.com/opencontainers/runtime-spec v1.1.0-rc.1 h1:wHa9jroFfKGQqFHj0I1fMRKLl0pfj+ynAqBxo3v6u9w=
github.com/opencontainers/runtime-spec v1.1.0-rc.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU=
github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/otel v1.12.0 h1:IgfC7kqQrRccIKuB7Cl+SRUmsKbEwSGPr0Eu+/ht1SQ=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/otel v1.12.0/go.mod h1:geaoz0L0r1BEOR81k7/n9W4TCXYCJ7bPO7K374jQHG0=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= go.opentelemetry.io/otel/trace v1.12.0 h1:p28in++7Kd0r2d8gSt931O57fdjUyWxkVbESuILAeUc=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= go.opentelemetry.io/otel/trace v1.12.0/go.mod h1:pHlgBynn6s25qJ2szD+Bv+iwKJttjHSI3lUAyf0GNuQ=
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo=
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -195,28 +197,42 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@ -225,6 +241,8 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.5.0 h1:+bSpV5HIeWkuvgaMfI3UmKRThoTA5ODJTUd8T17NO+4=
golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -233,16 +251,18 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE= google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -252,13 +272,17 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= lukechampine.com/blake3 v1.1.6 h1:H3cROdztr7RCfoaTpGZFQsrqvweFLrqS73j7L7cmR5c=
lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= lukechampine.com/blake3 v1.1.6/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=

View File

@ -21,10 +21,10 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"os"
"path" "path"
"os"
"github.com/containerd/containerd/v2/core/remotes" "github.com/containerd/containerd/remotes"
ipfsclient "github.com/containerd/stargz-snapshotter/ipfs/client" ipfsclient "github.com/containerd/stargz-snapshotter/ipfs/client"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
) )

View File

@ -25,21 +25,7 @@ import (
) )
func TestReader(t *testing.T) { func TestReader(t *testing.T) {
testRunner := &testutil.TestRunner{ testutil.TestReader(t, readerFactory)
TestingT: t,
Runner: func(testingT testutil.TestingT, name string, run func(t testutil.TestingT)) {
tt, ok := testingT.(*testing.T)
if !ok {
testingT.Fatal("TestingT is not a *testing.T")
return
}
tt.Run(name, func(t *testing.T) {
run(t)
})
},
}
testutil.TestReader(testRunner, readerFactory)
} }
func readerFactory(sr *io.SectionReader, opts ...metadata.Option) (testutil.TestableReader, error) { func readerFactory(sr *io.SectionReader, opts ...metadata.Option) (testutil.TestableReader, error) {

View File

@ -18,23 +18,29 @@ package testutil
import ( import (
"compress/gzip" "compress/gzip"
"errors"
"fmt" "fmt"
"io" "io"
"math/rand"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"reflect" "reflect"
"strings" "strings"
"testing"
"time" "time"
"github.com/containerd/stargz-snapshotter/estargz" "github.com/containerd/stargz-snapshotter/estargz"
"github.com/containerd/stargz-snapshotter/metadata" "github.com/containerd/stargz-snapshotter/metadata"
tutil "github.com/containerd/stargz-snapshotter/util/testutil" tutil "github.com/containerd/stargz-snapshotter/util/testutil"
"github.com/hashicorp/go-multierror"
"github.com/klauspost/compress/zstd" "github.com/klauspost/compress/zstd"
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
) )
func init() {
rand.Seed(time.Now().UnixNano())
}
var allowedPrefix = [4]string{"", "./", "/", "../"} var allowedPrefix = [4]string{"", "./", "/", "../"}
var srcCompressions = map[string]tutil.CompressionFactory{ var srcCompressions = map[string]tutil.CompressionFactory{
@ -59,41 +65,11 @@ type TestableReader interface {
NumOfNodes() (i int, _ error) NumOfNodes() (i int, _ error)
} }
// TestingT is the minimal set of testing.T required to run the
// tests defined in TestReader. This interface exists to prevent
// leaking the testing package from being exposed outside tests.
type TestingT interface {
Errorf(format string, args ...any)
Fatal(args ...any)
Fatalf(format string, args ...any)
Logf(format string, args ...any)
}
// Runner allows running subtests of TestingT. This exists instead of adding
// a Run method to TestingT interface because the Run implementation of
// testing.T would not satisfy the interface.
type Runner func(t TestingT, name string, fn func(t TestingT))
type TestRunner struct {
TestingT
Runner Runner
}
func (r *TestRunner) Run(name string, run func(*TestRunner)) {
r.Runner(r.TestingT, name, func(t TestingT) {
run(&TestRunner{TestingT: t, Runner: r.Runner})
})
}
// TestReader tests Reader returns correct file metadata. // TestReader tests Reader returns correct file metadata.
func TestReader(t *TestRunner, factory ReaderFactory) { func TestReader(t *testing.T, factory ReaderFactory) {
sampleTime := time.Now().Truncate(time.Second) sampleTime := time.Now().Truncate(time.Second)
sampleText := "qwer" + "tyui" + "opas" + "dfgh" + "jk" sampleText := "qwer" + "tyui" + "opas" + "dfgh" + "jk"
randomData, err := tutil.RandomBytes(64000) data64KB := string(tutil.RandomBytes(t, 64000))
if err != nil {
t.Fatalf("failed rand.Read: %v", err)
}
data64KB := string(randomData)
tests := []struct { tests := []struct {
name string name string
chunkSize int chunkSize int
@ -315,8 +291,7 @@ func TestReader(t *TestRunner, factory ReaderFactory) {
prefix := prefix prefix := prefix
for srcCompresionName, srcCompression := range srcCompressions { for srcCompresionName, srcCompression := range srcCompressions {
srcCompression := srcCompression() srcCompression := srcCompression()
t.Run(tt.name+"-"+srcCompresionName, func(t *testing.T) {
t.Run(tt.name+"-"+srcCompresionName, func(t *TestRunner) {
opts := []tutil.BuildEStargzOption{ opts := []tutil.BuildEStargzOption{
tutil.WithBuildTarOptions(tutil.WithPrefix(prefix)), tutil.WithBuildTarOptions(tutil.WithPrefix(prefix)),
tutil.WithEStargzOptions(estargz.WithCompression(srcCompression)), tutil.WithEStargzOptions(estargz.WithCompression(srcCompression)),
@ -373,7 +348,7 @@ func TestReader(t *TestRunner, factory ReaderFactory) {
} }
} }
t.Run("clone-id-stability", func(t *TestRunner) { t.Run("clone-id-stability", func(t *testing.T) {
var mapEntries func(r TestableReader, id uint32, m map[string]uint32) (map[string]uint32, error) var mapEntries func(r TestableReader, id uint32, m map[string]uint32) (map[string]uint32, error)
mapEntries = func(r TestableReader, id uint32, m map[string]uint32) (map[string]uint32, error) { mapEntries = func(r TestableReader, id uint32, m map[string]uint32) (map[string]uint32, error) {
if m == nil { if m == nil {
@ -442,21 +417,21 @@ func newCalledTelemetry() (telemetry *metadata.Telemetry, check func() error) {
GetTocLatency: func(time.Time) { getTocLatencyCalled = true }, GetTocLatency: func(time.Time) { getTocLatencyCalled = true },
DeserializeTocLatency: func(time.Time) { deserializeTocLatencyCalled = true }, DeserializeTocLatency: func(time.Time) { deserializeTocLatencyCalled = true },
}, func() error { }, func() error {
var errs []error var allErr error
if !getFooterLatencyCalled { if !getFooterLatencyCalled {
errs = append(errs, fmt.Errorf("metrics GetFooterLatency isn't called")) allErr = multierror.Append(allErr, fmt.Errorf("metrics GetFooterLatency isn't called"))
} }
if !getTocLatencyCalled { if !getTocLatencyCalled {
errs = append(errs, fmt.Errorf("metrics GetTocLatency isn't called")) allErr = multierror.Append(allErr, fmt.Errorf("metrics GetTocLatency isn't called"))
} }
if !deserializeTocLatencyCalled { if !deserializeTocLatencyCalled {
errs = append(errs, fmt.Errorf("metrics DeserializeTocLatency isn't called")) allErr = multierror.Append(allErr, fmt.Errorf("metrics DeserializeTocLatency isn't called"))
} }
return errors.Join(errs...) return allErr
} }
} }
func dumpNodes(t TestingT, r TestableReader, id uint32, level int) { func dumpNodes(t *testing.T, r TestableReader, id uint32, level int) {
if err := r.ForeachChild(id, func(name string, id uint32, mode os.FileMode) bool { if err := r.ForeachChild(id, func(name string, id uint32, mode os.FileMode) bool {
ind := "" ind := ""
for i := 0; i < level; i++ { for i := 0; i < level; i++ {
@ -470,10 +445,10 @@ func dumpNodes(t TestingT, r TestableReader, id uint32, level int) {
} }
} }
type check func(TestingT, TestableReader) type check func(*testing.T, TestableReader)
func numOfNodes(want int) check { func numOfNodes(want int) check {
return func(t TestingT, r TestableReader) { return func(t *testing.T, r TestableReader) {
i, err := r.NumOfNodes() i, err := r.NumOfNodes()
if err != nil { if err != nil {
t.Errorf("num of nodes: %v", err) t.Errorf("num of nodes: %v", err)
@ -485,7 +460,7 @@ func numOfNodes(want int) check {
} }
func numOfChunks(name string, num int) check { func numOfChunks(name string, num int) check {
return func(t TestingT, r TestableReader) { return func(t *testing.T, r TestableReader) {
nr, ok := r.(interface { nr, ok := r.(interface {
NumOfChunks(id uint32) (i int, _ error) NumOfChunks(id uint32) (i int, _ error)
}) })
@ -509,7 +484,7 @@ func numOfChunks(name string, num int) check {
} }
func sameNodes(n string, nodes ...string) check { func sameNodes(n string, nodes ...string) check {
return func(t TestingT, r TestableReader) { return func(t *testing.T, r TestableReader) {
id, err := lookup(r, n) id, err := lookup(r, n)
if err != nil { if err != nil {
t.Errorf("failed to lookup %q: %v", n, err) t.Errorf("failed to lookup %q: %v", n, err)
@ -529,7 +504,7 @@ func sameNodes(n string, nodes ...string) check {
} }
func linkName(name string, linkName string) check { func linkName(name string, linkName string) check {
return func(t TestingT, r TestableReader) { return func(t *testing.T, r TestableReader) {
id, err := lookup(r, name) id, err := lookup(r, name)
if err != nil { if err != nil {
t.Errorf("failed to lookup %q: %v", name, err) t.Errorf("failed to lookup %q: %v", name, err)
@ -552,7 +527,7 @@ func linkName(name string, linkName string) check {
} }
func hasNumLink(name string, numLink int) check { func hasNumLink(name string, numLink int) check {
return func(t TestingT, r TestableReader) { return func(t *testing.T, r TestableReader) {
id, err := lookup(r, name) id, err := lookup(r, name)
if err != nil { if err != nil {
t.Errorf("failed to lookup %q: %v", name, err) t.Errorf("failed to lookup %q: %v", name, err)
@ -571,7 +546,7 @@ func hasNumLink(name string, numLink int) check {
} }
func hasDirChildren(name string, children ...string) check { func hasDirChildren(name string, children ...string) check {
return func(t TestingT, r TestableReader) { return func(t *testing.T, r TestableReader) {
id, err := lookup(r, name) id, err := lookup(r, name)
if err != nil { if err != nil {
t.Errorf("failed to lookup %q: %v", name, err) t.Errorf("failed to lookup %q: %v", name, err)
@ -606,7 +581,7 @@ func hasDirChildren(name string, children ...string) check {
} }
func hasChardev(name string, maj, min int) check { func hasChardev(name string, maj, min int) check {
return func(t TestingT, r TestableReader) { return func(t *testing.T, r TestableReader) {
id, err := lookup(r, name) id, err := lookup(r, name)
if err != nil { if err != nil {
t.Errorf("cannot find chardev %q: %v", name, err) t.Errorf("cannot find chardev %q: %v", name, err)
@ -629,7 +604,7 @@ func hasChardev(name string, maj, min int) check {
} }
func hasBlockdev(name string, maj, min int) check { func hasBlockdev(name string, maj, min int) check {
return func(t TestingT, r TestableReader) { return func(t *testing.T, r TestableReader) {
id, err := lookup(r, name) id, err := lookup(r, name)
if err != nil { if err != nil {
t.Errorf("cannot find blockdev %q: %v", name, err) t.Errorf("cannot find blockdev %q: %v", name, err)
@ -652,7 +627,7 @@ func hasBlockdev(name string, maj, min int) check {
} }
func hasFifo(name string) check { func hasFifo(name string) check {
return func(t TestingT, r TestableReader) { return func(t *testing.T, r TestableReader) {
id, err := lookup(r, name) id, err := lookup(r, name)
if err != nil { if err != nil {
t.Errorf("cannot find blockdev %q: %v", name, err) t.Errorf("cannot find blockdev %q: %v", name, err)
@ -671,7 +646,7 @@ func hasFifo(name string) check {
} }
func hasFile(name, content string, size int64) check { func hasFile(name, content string, size int64) check {
return func(t TestingT, r TestableReader) { return func(t *testing.T, r TestableReader) {
id, err := lookup(r, name) id, err := lookup(r, name)
if err != nil { if err != nil {
t.Errorf("cannot find file %q: %v", name, err) t.Errorf("cannot find file %q: %v", name, err)
@ -716,7 +691,7 @@ type chunkInfo struct {
} }
func hasFileContentsWithPreRead(name string, off int64, contents string, extra ...chunkInfo) check { func hasFileContentsWithPreRead(name string, off int64, contents string, extra ...chunkInfo) check {
return func(t TestingT, r TestableReader) { return func(t *testing.T, r TestableReader) {
extraMap := make(map[uint32]chunkInfo) extraMap := make(map[uint32]chunkInfo)
for _, e := range extra { for _, e := range extra {
id, err := lookup(r, e.name) id, err := lookup(r, e.name)
@ -783,7 +758,7 @@ func hasFileContentsWithPreRead(name string, off int64, contents string, extra .
} }
func hasFileContentsOffset(name string, off int64, contents string) check { func hasFileContentsOffset(name string, off int64, contents string) check {
return func(t TestingT, r TestableReader) { return func(t *testing.T, r TestableReader) {
id, err := lookup(r, name) id, err := lookup(r, name)
if err != nil { if err != nil {
t.Errorf("failed to lookup %q: %v", name, err) t.Errorf("failed to lookup %q: %v", name, err)
@ -812,7 +787,7 @@ func hasFileContentsOffset(name string, off int64, contents string) check {
} }
func hasMode(name string, mode os.FileMode) check { func hasMode(name string, mode os.FileMode) check {
return func(t TestingT, r TestableReader) { return func(t *testing.T, r TestableReader) {
id, err := lookup(r, name) id, err := lookup(r, name)
if err != nil { if err != nil {
t.Errorf("cannot find file %q: %v", name, err) t.Errorf("cannot find file %q: %v", name, err)
@ -831,7 +806,7 @@ func hasMode(name string, mode os.FileMode) check {
} }
func hasOwner(name string, uid, gid int) check { func hasOwner(name string, uid, gid int) check {
return func(t TestingT, r TestableReader) { return func(t *testing.T, r TestableReader) {
id, err := lookup(r, name) id, err := lookup(r, name)
if err != nil { if err != nil {
t.Errorf("cannot find file %q: %v", name, err) t.Errorf("cannot find file %q: %v", name, err)
@ -850,7 +825,7 @@ func hasOwner(name string, uid, gid int) check {
} }
func hasModTime(name string, modTime time.Time) check { func hasModTime(name string, modTime time.Time) check {
return func(t TestingT, r TestableReader) { return func(t *testing.T, r TestableReader) {
id, err := lookup(r, name) id, err := lookup(r, name)
if err != nil { if err != nil {
t.Errorf("cannot find file %q: %v", name, err) t.Errorf("cannot find file %q: %v", name, err)
@ -870,7 +845,7 @@ func hasModTime(name string, modTime time.Time) check {
} }
func hasXattrs(name string, xattrs map[string]string) check { func hasXattrs(name string, xattrs map[string]string) check {
return func(t TestingT, r TestableReader) { return func(t *testing.T, r TestableReader) {
id, err := lookup(r, name) id, err := lookup(r, name)
if err != nil { if err != nil {
t.Errorf("cannot find file %q: %v", name, err) t.Errorf("cannot find file %q: %v", name, err)

View File

@ -21,13 +21,13 @@ import (
"fmt" "fmt"
"io" "io"
"github.com/containerd/containerd/v2/core/content" "github.com/containerd/containerd/archive/compression"
"github.com/containerd/containerd/v2/core/images" "github.com/containerd/containerd/content"
"github.com/containerd/containerd/v2/core/images/converter" "github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/v2/core/images/converter/uncompress" "github.com/containerd/containerd/images"
"github.com/containerd/containerd/v2/pkg/archive/compression" "github.com/containerd/containerd/images/converter"
"github.com/containerd/containerd/v2/pkg/labels" "github.com/containerd/containerd/images/converter/uncompress"
"github.com/containerd/errdefs" "github.com/containerd/containerd/labels"
"github.com/containerd/stargz-snapshotter/estargz" "github.com/containerd/stargz-snapshotter/estargz"
"github.com/containerd/stargz-snapshotter/util/ioutils" "github.com/containerd/stargz-snapshotter/util/ioutils"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"

View File

@ -20,9 +20,9 @@ import (
"context" "context"
"testing" "testing"
"github.com/containerd/containerd/v2/core/images" "github.com/containerd/containerd/images"
"github.com/containerd/containerd/v2/core/images/converter" "github.com/containerd/containerd/images/converter"
"github.com/containerd/platforms" "github.com/containerd/containerd/platforms"
"github.com/containerd/stargz-snapshotter/estargz" "github.com/containerd/stargz-snapshotter/estargz"
"github.com/containerd/stargz-snapshotter/util/testutil" "github.com/containerd/stargz-snapshotter/util/testutil"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"

View File

@ -24,14 +24,14 @@ import (
"sort" "sort"
"time" "time"
"github.com/containerd/containerd/v2/core/content" "github.com/containerd/containerd/archive/compression"
"github.com/containerd/containerd/v2/core/images" "github.com/containerd/containerd/content"
"github.com/containerd/containerd/v2/core/images/converter" "github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/v2/core/images/converter/uncompress" "github.com/containerd/containerd/images"
"github.com/containerd/containerd/v2/pkg/archive/compression" "github.com/containerd/containerd/images/converter"
"github.com/containerd/containerd/v2/pkg/labels" "github.com/containerd/containerd/images/converter/uncompress"
"github.com/containerd/containerd/v2/pkg/reference" "github.com/containerd/containerd/labels"
"github.com/containerd/errdefs" "github.com/containerd/containerd/reference"
"github.com/containerd/stargz-snapshotter/estargz" "github.com/containerd/stargz-snapshotter/estargz"
esgzexternaltoc "github.com/containerd/stargz-snapshotter/estargz/externaltoc" esgzexternaltoc "github.com/containerd/stargz-snapshotter/estargz/externaltoc"
estargzconvert "github.com/containerd/stargz-snapshotter/nativeconverter/estargz" estargzconvert "github.com/containerd/stargz-snapshotter/nativeconverter/estargz"

View File

@ -23,10 +23,10 @@ import (
"io" "io"
"testing" "testing"
"github.com/containerd/containerd/v2/core/content" "github.com/containerd/containerd/content"
"github.com/containerd/containerd/v2/core/images" "github.com/containerd/containerd/images"
"github.com/containerd/containerd/v2/core/images/converter" "github.com/containerd/containerd/images/converter"
"github.com/containerd/platforms" "github.com/containerd/containerd/platforms"
"github.com/containerd/stargz-snapshotter/estargz" "github.com/containerd/stargz-snapshotter/estargz"
"github.com/containerd/stargz-snapshotter/util/testutil" "github.com/containerd/stargz-snapshotter/util/testutil"
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"

View File

@ -21,10 +21,10 @@ import (
"fmt" "fmt"
"io" "io"
"github.com/containerd/containerd/v2/core/remotes" "github.com/containerd/containerd/platforms"
"github.com/containerd/containerd/v2/core/remotes/docker" "github.com/containerd/containerd/reference"
"github.com/containerd/containerd/v2/pkg/reference" "github.com/containerd/containerd/remotes"
"github.com/containerd/platforms" "github.com/containerd/containerd/remotes/docker"
esgzexternaltoc "github.com/containerd/stargz-snapshotter/estargz/externaltoc" esgzexternaltoc "github.com/containerd/stargz-snapshotter/estargz/externaltoc"
"github.com/containerd/stargz-snapshotter/fs/source" "github.com/containerd/stargz-snapshotter/fs/source"
"github.com/containerd/stargz-snapshotter/util/containerdutil" "github.com/containerd/stargz-snapshotter/util/containerdutil"

View File

@ -21,20 +21,20 @@ import (
"fmt" "fmt"
"io" "io"
"github.com/containerd/containerd/v2/core/content" "github.com/containerd/containerd/archive/compression"
"github.com/containerd/containerd/v2/core/images" "github.com/containerd/containerd/content"
"github.com/containerd/containerd/v2/core/images/converter" "github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/v2/core/images/converter/uncompress" "github.com/containerd/containerd/images"
"github.com/containerd/containerd/v2/pkg/archive/compression" "github.com/containerd/containerd/images/converter"
"github.com/containerd/containerd/v2/pkg/labels" "github.com/containerd/containerd/images/converter/uncompress"
"github.com/containerd/errdefs" "github.com/containerd/containerd/labels"
"github.com/containerd/log"
"github.com/containerd/stargz-snapshotter/estargz" "github.com/containerd/stargz-snapshotter/estargz"
"github.com/containerd/stargz-snapshotter/estargz/zstdchunked" "github.com/containerd/stargz-snapshotter/estargz/zstdchunked"
"github.com/containerd/stargz-snapshotter/util/ioutils" "github.com/containerd/stargz-snapshotter/util/ioutils"
"github.com/klauspost/compress/zstd" "github.com/klauspost/compress/zstd"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sirupsen/logrus"
) )
type zstdCompression struct { type zstdCompression struct {
@ -109,7 +109,12 @@ func LayerConvertFuncWithCompressionLevel(compressionLevel zstd.EncoderLevel, op
if uncompressedDesc == nil { if uncompressedDesc == nil {
return nil, fmt.Errorf("unexpectedly got the same blob after compression (%s, %q)", desc.Digest, desc.MediaType) return nil, fmt.Errorf("unexpectedly got the same blob after compression (%s, %q)", desc.Digest, desc.MediaType)
} }
log.G(ctx).Debugf("zstdchunked: uncompressed %s into %s", desc.Digest, uncompressedDesc.Digest) defer func() {
if err := cs.Delete(ctx, uncompressedDesc.Digest); err != nil {
logrus.WithError(err).WithField("uncompressedDesc", uncompressedDesc).Warn("failed to remove tmp uncompressed layer")
}
}()
logrus.Debugf("zstdchunked: uncompressed %s into %s", desc.Digest, uncompressedDesc.Digest)
} }
info, err := cs.Info(ctx, desc.Digest) info, err := cs.Info(ctx, desc.Digest)
@ -141,7 +146,7 @@ func LayerConvertFuncWithCompressionLevel(compressionLevel zstd.EncoderLevel, op
} }
defer blob.Close() defer blob.Close()
ref := fmt.Sprintf("convert-zstdchunked-from-%s", desc.Digest) ref := fmt.Sprintf("convert-zstdchunked-from-%s", desc.Digest)
w, err := content.OpenWriter(ctx, cs, content.WithRef(ref)) w, err := cs.Writer(ctx, content.WithRef(ref))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -216,8 +221,8 @@ func convertMediaTypeToZstd(mt string) (string, error) {
switch ociMediaType { switch ociMediaType {
case ocispec.MediaTypeImageLayer, ocispec.MediaTypeImageLayerGzip, ocispec.MediaTypeImageLayerZstd: case ocispec.MediaTypeImageLayer, ocispec.MediaTypeImageLayerGzip, ocispec.MediaTypeImageLayerZstd:
return ocispec.MediaTypeImageLayerZstd, nil return ocispec.MediaTypeImageLayerZstd, nil
case ocispec.MediaTypeImageLayerNonDistributable, ocispec.MediaTypeImageLayerNonDistributableGzip, ocispec.MediaTypeImageLayerNonDistributableZstd: //nolint:staticcheck // deprecated case ocispec.MediaTypeImageLayerNonDistributable, ocispec.MediaTypeImageLayerNonDistributableGzip, ocispec.MediaTypeImageLayerNonDistributableZstd:
return ocispec.MediaTypeImageLayerNonDistributableZstd, nil //nolint:staticcheck // deprecated return ocispec.MediaTypeImageLayerNonDistributableZstd, nil
default: default:
return "", fmt.Errorf("unknown mediatype %q", mt) return "", fmt.Errorf("unknown mediatype %q", mt)
} }

View File

@ -22,9 +22,9 @@ import (
"runtime/debug" "runtime/debug"
"github.com/containerd/containerd/v2/core/images" "github.com/containerd/containerd/images"
"github.com/containerd/containerd/v2/core/images/converter" "github.com/containerd/containerd/images/converter"
"github.com/containerd/platforms" "github.com/containerd/containerd/platforms"
"github.com/containerd/stargz-snapshotter/estargz" "github.com/containerd/stargz-snapshotter/estargz"
"github.com/containerd/stargz-snapshotter/estargz/zstdchunked" "github.com/containerd/stargz-snapshotter/estargz/zstdchunked"
"github.com/containerd/stargz-snapshotter/util/testutil" "github.com/containerd/stargz-snapshotter/util/testutil"

View File

@ -4,5 +4,3 @@ version = 2
[proxy_plugins.stargz] [proxy_plugins.stargz]
type = "snapshot" type = "snapshot"
address = "/run/containerd-stargz-grpc/containerd-stargz-grpc.sock" address = "/run/containerd-stargz-grpc/containerd-stargz-grpc.sock"
[proxy_plugins.stargz.exports]
root = "/var/lib/containerd-stargz-grpc/"

View File

@ -55,7 +55,7 @@ function kill_all {
function cleanup { function cleanup {
rm -rf "${CONTAINERD_ROOT}"* rm -rf "${CONTAINERD_ROOT}"*
if [ -e "${REMOTE_SNAPSHOTTER_SOCKET}" ] ; then if [ -f "${REMOTE_SNAPSHOTTER_SOCKET}" ] ; then
rm "${REMOTE_SNAPSHOTTER_SOCKET}" rm "${REMOTE_SNAPSHOTTER_SOCKET}"
fi fi
if [ -d "${REMOTE_SNAPSHOTTER_ROOT}snapshotter/snapshots/" ] ; then if [ -d "${REMOTE_SNAPSHOTTER_ROOT}snapshotter/snapshots/" ] ; then

View File

@ -36,7 +36,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE. # SOFTWARE.
import os, sys, subprocess, select, random, time, json, tempfile, shutil, itertools import os, sys, subprocess, select, random, urllib2, time, json, tempfile, shutil, itertools
TMP_DIR = tempfile.mkdtemp() TMP_DIR = tempfile.mkdtemp()
LEGACY_MODE = "legacy" LEGACY_MODE = "legacy"
@ -176,7 +176,7 @@ class BenchRunner:
elif runtime == "podman": elif runtime == "podman":
self.controller = PodmanController() self.controller = PodmanController()
else: else:
print ('Unknown runtime mode: '+runtime) print 'Unknown runtime mode: '+runtime
exit(1) exit(1)
self.repository = repository self.repository = repository
self.srcrepository = srcrepository self.srcrepository = srcrepository
@ -236,15 +236,15 @@ class BenchRunner:
stderr=subprocess.STDOUT, stderr=subprocess.STDOUT,
stdout=subprocess.PIPE) stdout=subprocess.PIPE)
while True: while True:
l = p.stdout.readline().decode("utf-8") l = p.stdout.readline()
if l == '': if l == '':
continue continue
print ('out: ' + l.strip()) print 'out: ' + l.strip()
# are we done? # are we done?
if l.find(runargs.waitline) >= 0: if l.find(runargs.waitline) >= 0:
runtime = time.time() - startrun runtime = time.time() - startrun
# cleanup # cleanup
print ('DONE') print 'DONE'
cmd = self.controller.task_kill_cmd(cid) cmd = self.controller.task_kill_cmd(cid)
rc = os.system(cmd) rc = os.system(cmd)
assert(rc == 0) assert(rc == 0)
@ -262,10 +262,10 @@ class BenchRunner:
cmd = self.controller.task_start_cmd(cid) cmd = self.controller.task_start_cmd(cid)
startrun = time.time() startrun = time.time()
p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
print (runargs.stdin) print runargs.stdin
out, _ = p.communicate(runargs.stdin.encode()) out, _ = p.communicate(runargs.stdin)
runtime = time.time() - startrun runtime = time.time() - startrun
print (out) print out
assert(p.returncode == 0) assert(p.returncode == 0)
return createtime, runtime return createtime, runtime
@ -273,7 +273,7 @@ class BenchRunner:
name = bench.name name = bench.name
image = self.fully_qualify(bench.name) image = self.fully_qualify(bench.name)
print ("Pulling the image...") print "Pulling the image..."
pullcmd = self.controller.pull_cmd(image) pullcmd = self.controller.pull_cmd(image)
startpull = time.time() startpull = time.time()
rc = os.system(pullcmd) rc = os.system(pullcmd)
@ -291,7 +291,7 @@ class BenchRunner:
elif name in BenchRunner.CMD_STDIN: elif name in BenchRunner.CMD_STDIN:
createtime, runtime = self.run_cmd_stdin(image=image, cid=cid, runargs=BenchRunner.CMD_STDIN[name]) createtime, runtime = self.run_cmd_stdin(image=image, cid=cid, runargs=BenchRunner.CMD_STDIN[name])
else: else:
print ('Unknown bench: '+name) print 'Unknown bench: '+name
exit(1) exit(1)
return pulltime, createtime, runtime return pulltime, createtime, runtime
@ -300,7 +300,7 @@ class BenchRunner:
period=10 period=10
cmd = ('%s %s -cni -period %s -entrypoint \'["/bin/sh", "-c"]\' -args \'["echo hello"]\' %s %s' % cmd = ('%s %s -cni -period %s -entrypoint \'["/bin/sh", "-c"]\' -args \'["echo hello"]\' %s %s' %
(self.optimizer, option, period, src, dest)) (self.optimizer, option, period, src, dest))
print (cmd) print cmd
rc = os.system(cmd) rc = os.system(cmd)
assert(rc == 0) assert(rc == 0)
@ -312,7 +312,7 @@ class BenchRunner:
entry = '-entrypoint \'["/bin/sh", "-c"]\'' entry = '-entrypoint \'["/bin/sh", "-c"]\''
cmd = ('%s %s -cni -period %s %s %s %s %s' % cmd = ('%s %s -cni -period %s %s %s %s %s' %
(self.optimizer, option, period, entry, genargs_for_optimization(runargs.arg), src, dest)) (self.optimizer, option, period, entry, genargs_for_optimization(runargs.arg), src, dest))
print (cmd) print cmd
rc = os.system(cmd) rc = os.system(cmd)
assert(rc == 0) assert(rc == 0)
@ -323,10 +323,10 @@ class BenchRunner:
a = tmp_copy(a) a = tmp_copy(a)
mounts += '--mount type=bind,src=%s,dst=%s,options=rbind ' % (a,b) mounts += '--mount type=bind,src=%s,dst=%s,options=rbind ' % (a,b)
period = 90 period = 90
env = ' '.join(['-env %s=%s' % (k,v) for k,v in runargs.env.items()]) env = ' '.join(['-env %s=%s' % (k,v) for k,v in runargs.env.iteritems()])
cmd = ('%s %s -cni -period %s %s %s %s %s %s' % cmd = ('%s %s -cni -period %s %s %s %s %s %s' %
(self.optimizer, option, period, mounts, env, genargs_for_optimization(runargs.arg), src, dest)) (self.optimizer, option, period, mounts, env, genargs_for_optimization(runargs.arg), src, dest))
print (cmd) print cmd
rc = os.system(cmd) rc = os.system(cmd)
assert(rc == 0) assert(rc == 0)
@ -339,36 +339,36 @@ class BenchRunner:
period = 60 period = 60
cmd = ('%s %s -i -cni -period %s %s -entrypoint \'["/bin/sh", "-c"]\' %s %s %s' % cmd = ('%s %s -i -cni -period %s %s -entrypoint \'["/bin/sh", "-c"]\' %s %s %s' %
(self.optimizer, option, period, mounts, genargs_for_optimization(runargs.stdin_sh), src, dest)) (self.optimizer, option, period, mounts, genargs_for_optimization(runargs.stdin_sh), src, dest))
print (cmd) print cmd
p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE) p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
print (runargs.stdin) print runargs.stdin
out,_ = p.communicate(runargs.stdin.encode()) out,_ = p.communicate(runargs.stdin)
print (out) print out
p.wait() p.wait()
assert(p.returncode == 0) assert(p.returncode == 0)
def push_img(self, dest): def push_img(self, dest):
cmd = '%s %s' % (self.pusher, dest) cmd = '%s %s' % (self.pusher, dest)
print (cmd) print cmd
rc = os.system(cmd) rc = os.system(cmd)
assert(rc == 0) assert(rc == 0)
def pull_img(self, src): def pull_img(self, src):
cmd = '%s %s' % (self.puller, src) cmd = '%s %s' % (self.puller, src)
print (cmd) print cmd
rc = os.system(cmd) rc = os.system(cmd)
assert(rc == 0) assert(rc == 0)
def copy_img(self, src, dest): def copy_img(self, src, dest):
cmd = 'crane copy %s %s' % (src, dest) cmd = 'crane copy %s %s' % (src, dest)
print (cmd) print cmd
rc = os.system(cmd) rc = os.system(cmd)
assert(rc == 0) assert(rc == 0)
def convert_and_push_img(self, src, dest): def convert_and_push_img(self, src, dest):
self.pull_img(src) self.pull_img(src)
cmd = '%s --no-optimize %s %s' % (self.optimizer, src, dest) cmd = '%s --no-optimize %s %s' % (self.optimizer, src, dest)
print (cmd) print cmd
rc = os.system(cmd) rc = os.system(cmd)
assert(rc == 0) assert(rc == 0)
self.push_img(dest) self.push_img(dest)
@ -384,7 +384,7 @@ class BenchRunner:
elif name in BenchRunner.CMD_STDIN: elif name in BenchRunner.CMD_STDIN:
self.convert_cmd_stdin(src=src, dest=dest, runargs=BenchRunner.CMD_STDIN[name], option=option) self.convert_cmd_stdin(src=src, dest=dest, runargs=BenchRunner.CMD_STDIN[name], option=option)
else: else:
print ('Unknown bench: '+name) print 'Unknown bench: '+name
exit(1) exit(1)
self.push_img(dest) self.push_img(dest)
@ -403,7 +403,7 @@ class BenchRunner:
self.prepare(bench) self.prepare(bench)
return 0, 0, 0 return 0, 0, 0
else: else:
print ('Unknown operation: '+op) print 'Unknown operation: '+op
exit(1) exit(1)
class ContainerdController: class ContainerdController:
@ -415,7 +415,7 @@ class ContainerdController:
if self.is_lazypull: if self.is_lazypull:
base_cmd = "ctr-remote i rpull" base_cmd = "ctr-remote i rpull"
cmd = '%s %s' % (base_cmd, image) cmd = '%s %s' % (base_cmd, image)
print (cmd) print cmd
return cmd return cmd
def create_echo_hello_cmd(self, image, cid): def create_echo_hello_cmd(self, image, cid):
@ -423,7 +423,7 @@ class ContainerdController:
if self.is_lazypull: if self.is_lazypull:
snapshotter_opt = "--snapshotter=stargz" snapshotter_opt = "--snapshotter=stargz"
cmd = '%s c create --net-host %s -- %s %s echo hello' % (CTR, snapshotter_opt, image, cid) cmd = '%s c create --net-host %s -- %s %s echo hello' % (CTR, snapshotter_opt, image, cid)
print (cmd) print cmd
return cmd return cmd
def create_cmd_arg_cmd(self, image, cid, runargs): def create_cmd_arg_cmd(self, image, cid, runargs):
@ -433,21 +433,21 @@ class ContainerdController:
cmd = '%s c create --net-host %s ' % (CTR, snapshotter_opt) cmd = '%s c create --net-host %s ' % (CTR, snapshotter_opt)
cmd += '-- %s %s ' % (image, cid) cmd += '-- %s %s ' % (image, cid)
cmd += runargs.arg cmd += runargs.arg
print (cmd) print cmd
return cmd return cmd
def create_cmd_arg_wait_cmd(self, image, cid, runargs): def create_cmd_arg_wait_cmd(self, image, cid, runargs):
snapshotter_opt = "" snapshotter_opt = ""
if self.is_lazypull: if self.is_lazypull:
snapshotter_opt = "--snapshotter=stargz" snapshotter_opt = "--snapshotter=stargz"
env = ' '.join(['--env %s=%s' % (k,v) for k,v in runargs.env.items()]) env = ' '.join(['--env %s=%s' % (k,v) for k,v in runargs.env.iteritems()])
cmd = '%s c create --net-host %s %s '% (CTR, snapshotter_opt, env) cmd = '%s c create --net-host %s %s '% (CTR, snapshotter_opt, env)
for a,b in runargs.mount: for a,b in runargs.mount:
a = os.path.join(os.path.dirname(os.path.abspath(__file__)), a) a = os.path.join(os.path.dirname(os.path.abspath(__file__)), a)
a = tmp_copy(a) a = tmp_copy(a)
cmd += '--mount type=bind,src=%s,dst=%s,options=rbind ' % (a,b) cmd += '--mount type=bind,src=%s,dst=%s,options=rbind ' % (a,b)
cmd += '-- %s %s %s' % (image, cid, runargs.arg) cmd += '-- %s %s %s' % (image, cid, runargs.arg)
print (cmd) print cmd
return cmd return cmd
def create_cmd_stdin_cmd(self, image, cid, runargs): def create_cmd_stdin_cmd(self, image, cid, runargs):
@ -463,30 +463,30 @@ class ContainerdController:
if runargs.stdin_sh: if runargs.stdin_sh:
cmd += runargs.stdin_sh # e.g., sh -c cmd += runargs.stdin_sh # e.g., sh -c
print (cmd) print cmd
return cmd return cmd
def task_start_cmd(self, cid): def task_start_cmd(self, cid):
cmd = '%s t start %s' % (CTR, cid) cmd = '%s t start %s' % (CTR, cid)
print (cmd) print cmd
return cmd return cmd
def task_kill_cmd(self, cid): def task_kill_cmd(self, cid):
cmd = '%s t kill -s 9 %s' % (CTR, cid) cmd = '%s t kill -s 9 %s' % (CTR, cid)
print (cmd) print cmd
return cmd return cmd
def cleanup(self, name, image): def cleanup(self, name, image):
print ("Cleaning up environment...") print "Cleaning up environment..."
cmd = '%s t kill -s 9 %s' % (CTR, name) cmd = '%s t kill -s 9 %s' % (CTR, name)
print (cmd) print cmd
rc = os.system(cmd) # sometimes containers already exit. we ignore the failure. rc = os.system(cmd) # sometimes containers already exit. we ignore the failure.
cmd = '%s c rm %s' % (CTR, name) cmd = '%s c rm %s' % (CTR, name)
print (cmd) print cmd
rc = os.system(cmd) rc = os.system(cmd)
assert(rc == 0) assert(rc == 0)
cmd = '%s image rm %s' % (CTR, image) cmd = '%s image rm %s' % (CTR, image)
print (cmd) print cmd
rc = os.system(cmd) rc = os.system(cmd)
assert(rc == 0) assert(rc == 0)
@ -500,29 +500,29 @@ class ContainerdController:
class PodmanController: class PodmanController:
def pull_cmd(self, image): def pull_cmd(self, image):
cmd = '%s pull docker://%s' % (PODMAN, image) cmd = '%s pull docker://%s' % (PODMAN, image)
print (cmd) print cmd
return cmd return cmd
def create_echo_hello_cmd(self, image, cid): def create_echo_hello_cmd(self, image, cid):
cmd = '%s create --name %s docker://%s echo hello' % (PODMAN, cid, image) cmd = '%s create --name %s docker://%s echo hello' % (PODMAN, cid, image)
print (cmd) print cmd
return cmd return cmd
def create_cmd_arg_cmd(self, image, cid, runargs): def create_cmd_arg_cmd(self, image, cid, runargs):
cmd = '%s create --name %s docker://%s ' % (PODMAN, cid, image) cmd = '%s create --name %s docker://%s ' % (PODMAN, cid, image)
cmd += runargs.arg cmd += runargs.arg
print (cmd) print cmd
return cmd return cmd
def create_cmd_arg_wait_cmd(self, image, cid, runargs): def create_cmd_arg_wait_cmd(self, image, cid, runargs):
env = ' '.join(['--env %s=%s' % (k,v) for k,v in runargs.env.items()]) env = ' '.join(['--env %s=%s' % (k,v) for k,v in runargs.env.iteritems()])
cmd = '%s create %s --name %s '% (PODMAN, env, cid) cmd = '%s create %s --name %s '% (PODMAN, env, cid)
for a,b in runargs.mount: for a,b in runargs.mount:
a = os.path.join(os.path.dirname(os.path.abspath(__file__)), a) a = os.path.join(os.path.dirname(os.path.abspath(__file__)), a)
a = tmp_copy(a) a = tmp_copy(a)
cmd += '--mount type=bind,src=%s,dst=%s ' % (a,b) cmd += '--mount type=bind,src=%s,dst=%s ' % (a,b)
cmd += ' docker://%s %s ' % (image, runargs.arg) cmd += ' docker://%s %s ' % (image, runargs.arg)
print (cmd) print cmd
return cmd return cmd
def create_cmd_stdin_cmd(self, image, cid, runargs): def create_cmd_stdin_cmd(self, image, cid, runargs):
@ -535,51 +535,51 @@ class PodmanController:
if runargs.stdin_sh: if runargs.stdin_sh:
cmd += runargs.stdin_sh # e.g., sh -c cmd += runargs.stdin_sh # e.g., sh -c
print (cmd) print cmd
return cmd return cmd
def task_start_cmd(self, cid): def task_start_cmd(self, cid):
cmd = '%s start -a %s' % (PODMAN, cid) cmd = '%s start -a %s' % (PODMAN, cid)
print (cmd) print cmd
return cmd return cmd
def task_kill_cmd(self, cid): def task_kill_cmd(self, cid):
cmd = '%s kill -s 9 %s' % (PODMAN, cid) cmd = '%s kill -s 9 %s' % (PODMAN, cid)
print (cmd) print cmd
return cmd return cmd
def cleanup(self, name, image): def cleanup(self, name, image):
print ("Cleaning up environment...") print "Cleaning up environment..."
cmd = '%s kill -s 9 %s' % (PODMAN, name) cmd = '%s kill -s 9 %s' % (PODMAN, name)
print (cmd) print cmd
rc = os.system(cmd) # sometimes containers already exit. we ignore the failure. rc = os.system(cmd) # sometimes containers already exit. we ignore the failure.
cmd = '%s rm %s' % (PODMAN, name) cmd = '%s rm %s' % (PODMAN, name)
print (cmd) print cmd
rc = os.system(cmd) rc = os.system(cmd)
assert(rc == 0) assert(rc == 0)
cmd = '%s image rm %s' % (PODMAN, image) cmd = '%s image rm %s' % (PODMAN, image)
print (cmd) print cmd
rc = os.system(cmd) rc = os.system(cmd)
assert(rc == 0) assert(rc == 0)
cmd = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../reboot_store.sh') cmd = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../reboot_store.sh')
print (cmd) print cmd
rc = os.system(cmd) rc = os.system(cmd)
assert(rc == 0) assert(rc == 0)
def main(): def main():
if len(sys.argv) == 1: if len(sys.argv) == 1:
print ('Usage: bench.py [OPTIONS] [BENCHMARKS]') print 'Usage: bench.py [OPTIONS] [BENCHMARKS]'
print ('OPTIONS:') print 'OPTIONS:'
print ('--repository=<repository>') print '--repository=<repository>'
print ('--srcrepository=<repository>') print '--srcrepository=<repository>'
print ('--all') print '--all'
print ('--list') print '--list'
print ('--list-json') print '--list-json'
print ('--experiments') print '--experiments'
print ('--profile=<seconds>') print '--profile=<seconds>'
print ('--runtime') print '--runtime'
print ('--op=(prepare|run)') print '--op=(prepare|run)'
print ('--mode=(%s|%s|%s)' % (LEGACY_MODE, ESTARGZ_NOOPT_MODE, ESTARGZ_MODE, ZSTDCHUNKED_MODE)) print '--mode=(%s|%s|%s)' % (LEGACY_MODE, ESTARGZ_NOOPT_MODE, ESTARGZ_MODE, ZSTDCHUNKED_MODE)
exit(1) exit(1)
benches = [] benches = []
@ -594,11 +594,11 @@ def main():
benches.extend(BenchRunner.ALL.values()) benches.extend(BenchRunner.ALL.values())
elif parts[0] == 'list': elif parts[0] == 'list':
template = '%-16s\t%-20s' template = '%-16s\t%-20s'
print (template % ('CATEGORY', 'NAME')) print template % ('CATEGORY', 'NAME')
for b in sorted(BenchRunner.ALL.values(), key=lambda b:(b.category, b.name)): for b in sorted(BenchRunner.ALL.values(), key=lambda b:(b.category, b.name)):
print (template % (b.category, b.name)) print template % (b.category, b.name)
elif parts[0] == 'list-json': elif parts[0] == 'list-json':
print (json.dumps([b.__dict__ for b in BenchRunner.ALL.values()])) print json.dumps([b.__dict__ for b in BenchRunner.ALL.values()])
else: else:
benches.append(BenchRunner.ALL[arg]) benches.append(BenchRunner.ALL[arg])
@ -633,18 +633,18 @@ def main():
pull_times.append(pulltime) pull_times.append(pulltime)
create_times.append(createtime) create_times.append(createtime)
run_times.append(runtime) run_times.append(runtime)
print ('ITERATION %s:' % i) print 'ITERATION %s:' % i
print ('elapsed %s' % elapsed) print 'elapsed %s' % elapsed
print ('pull %s' % pulltime) print 'pull %s' % pulltime
print ('create %s' % createtime) print 'create %s' % createtime
print ('run %s' % runtime) print 'run %s' % runtime
if profile > 0 and time.time() - for_start > profile: if profile > 0 and time.time() - for_start > profile:
break break
row = {'mode':'%s' % runner.mode, 'repo':bench.name, 'bench':bench.name, 'elapsed':sum(elapsed_times) / len(elapsed_times), 'elapsed_pull':sum(pull_times) / len(pull_times), 'elapsed_create':sum(create_times) / len(create_times), 'elapsed_run':sum(run_times) / len(run_times)} row = {'mode':'%s' % runner.mode, 'repo':bench.name, 'bench':bench.name, 'elapsed':sum(elapsed_times) / len(elapsed_times), 'elapsed_pull':sum(pull_times) / len(pull_times), 'elapsed_create':sum(create_times) / len(create_times), 'elapsed_run':sum(run_times) / len(run_times)}
js = json.dumps(row) js = json.dumps(row)
print ('%s%s,' % (BENCHMARKOUT_MARK, js)) print '%s%s,' % (BENCHMARKOUT_MARK, js)
sys.stdout.flush() sys.stdout.flush()
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -70,11 +70,8 @@ cat <<EOF > "${TMP_CONTEXT}/Dockerfile"
FROM ${BENCHMARKING_BASE_IMAGE_NAME} FROM ${BENCHMARKING_BASE_IMAGE_NAME}
RUN apt-get update -y && \ RUN apt-get update -y && \
apt-get install -y python3 jq wget && \ apt-get --no-install-recommends install -y python jq && \
ln -s /usr/bin/python3 /usr/bin/python && \ go install github.com/google/go-containerregistry/cmd/crane@v0.8.0
mkdir -p /tmp/crane && \
wget -O - https://github.com/google/go-containerregistry/releases/download/v0.19.1/go-containerregistry_Linux_x86_64.tar.gz | tar -C /tmp/crane/ -zxf - && \
mv /tmp/crane/crane /usr/local/bin/
COPY ./config / COPY ./config /
@ -133,9 +130,9 @@ echo "Logging to >>> ${LOG_FILE} (will finally be stored under ${OUTPUTDIR})"
echo "Benchmarking..." echo "Benchmarking..."
FAIL= FAIL=
if ! ( cd "${CONTEXT}" && \ if ! ( cd "${CONTEXT}" && \
docker compose -f "${DOCKER_COMPOSE_YAML}" build ${DOCKER_BUILD_ARGS:-} \ docker-compose -f "${DOCKER_COMPOSE_YAML}" build ${DOCKER_BUILD_ARGS:-} \
"${BENCHMARKING_NODE}" && \ "${BENCHMARKING_NODE}" && \
docker compose -f "${DOCKER_COMPOSE_YAML}" up -d --force-recreate && \ docker-compose -f "${DOCKER_COMPOSE_YAML}" up -d --force-recreate && \
docker exec \ docker exec \
-e BENCHMARK_RUNTIME_MODE -e BENCHMARK_SAMPLES_NUM -e BENCHMARK_PROFILE \ -e BENCHMARK_RUNTIME_MODE -e BENCHMARK_SAMPLES_NUM -e BENCHMARK_PROFILE \
-i "${BENCHMARKING_CONTAINER}" \ -i "${BENCHMARKING_CONTAINER}" \
@ -165,7 +162,7 @@ if [ "${FAIL}" != "true" ] ; then
fi fi
echo "Cleaning up environment..." echo "Cleaning up environment..."
docker compose -f "${DOCKER_COMPOSE_YAML}" down -v docker-compose -f "${DOCKER_COMPOSE_YAML}" down -v
if [ "${FAIL}" == "true" ] ; then if [ "${FAIL}" == "true" ] ; then
exit 1 exit 1
fi fi

View File

@ -47,7 +47,7 @@ done
cat <<EOF cat <<EOF
# Benchmarking Result (${PERCENTILE} pctl.,samples=${MINSAMPLES}) # Benchmarking Result (${PERCENTILE} pctl.,samples=${MINSAMPLES})
Runs on the ubuntu-24.04 runner on Github Actions. Runs on the ubuntu-22.04 runner on Github Actions.
EOF EOF
for IMGNAME in "${IMAGES[@]}" ; do for IMGNAME in "${IMAGES[@]}" ; do

View File

@ -1,2 +1 @@
unqualified-search-registries = ['docker.io'] unqualified-search-registries = ['docker.io']
additional-layer-store-auth-helper = "stargz-store-helper"

View File

@ -22,5 +22,3 @@ version = 2
[proxy_plugins.stargz] [proxy_plugins.stargz]
type = "snapshot" type = "snapshot"
address = "/run/containerd-stargz-grpc/containerd-stargz-grpc.sock" address = "/run/containerd-stargz-grpc/containerd-stargz-grpc.sock"
[proxy_plugins.stargz.exports]
root = "/var/lib/containerd-stargz-grpc/"

Some files were not shown because too many files have changed in this diff Show More