pax_global_header00006660000000000000000000000064142630152740014516gustar00rootroot0000000000000052 comment=461aaf7075bd3f771d6a379eff2db071dd54d222 stargz-snapshotter-0.12.0/000077500000000000000000000000001426301527400154605ustar00rootroot00000000000000stargz-snapshotter-0.12.0/.dockerignore000066400000000000000000000000041426301527400201260ustar00rootroot00000000000000/outstargz-snapshotter-0.12.0/.github/000077500000000000000000000000001426301527400170205ustar00rootroot00000000000000stargz-snapshotter-0.12.0/.github/dependabot.yml000066400000000000000000000021771426301527400216570ustar00rootroot00000000000000version: 2 updates: # Automatic upgrade for go modules. - package-ecosystem: "gomod" directory: "/" schedule: interval: "daily" ignore: # We upgrade this manually on each release - dependency-name: "github.com/containerd/stargz-snapshotter/estargz" # This forcefully points to v1.22.1. See go.mod. - dependency-name: "github.com/urfave/cli" # Automatic upgrade for go modules of estargz package. - package-ecosystem: "gomod" directory: "/estargz" schedule: interval: "daily" # Automatic upgrade for go modules of ipfs package. - package-ecosystem: "gomod" directory: "/ipfs" schedule: interval: "daily" # Automatic upgrade for go modules of cmd package. - package-ecosystem: "gomod" directory: "/cmd" schedule: interval: "daily" # Automatic upgrade for base images used in the Dockerfile - package-ecosystem: "docker" directory: "/" schedule: interval: "daily" # Automatic upgrade for Github Actions - package-ecosystem: "github-actions" directory: "/" # means ".github/workflows" schedule: interval: "daily" stargz-snapshotter-0.12.0/.github/workflows/000077500000000000000000000000001426301527400210555ustar00rootroot00000000000000stargz-snapshotter-0.12.0/.github/workflows/benchmark.yml000066400000000000000000000030441426301527400235330ustar00rootroot00000000000000name: Benchmark on: push: branches: - main pull_request: env: DOCKER_BUILDKIT: 1 jobs: hello-bench: runs-on: ubuntu-20.04 name: HelloBench env: BENCHMARK_LOG_DIR: ${{ github.workspace }}/log/ BENCHMARK_RESULT_DIR: ${{ github.workspace }}/benchmark/ BENCHMARK_REGISTRY: ghcr.io BENCHMARK_USER: stargz-containers BENCHMARK_TARGETS: python:3.10 gcc:11.2.0 postgres:14.2 tomcat:10.1.0-jdk17-openjdk-bullseye BENCHMARK_SAMPLES_NUM: 5 BENCHMARK_PERCENTILE: 95 BENCHMARK_PERCENTILES_GRANULARITY: 25 strategy: fail-fast: false max-parallel: 1 matrix: runtime: ["podman", "containerd"] steps: - name: Install tools run: | sudo apt-get update && sudo apt-get --no-install-recommends install -y gnuplot pip install numpy - uses: actions/checkout@v3 - name: Prepare directories run: mkdir "${BENCHMARK_RESULT_DIR}" "${BENCHMARK_LOG_DIR}" - name: Get instance information run: | curl -H "Metadata:true" "http://169.254.169.254/metadata/instance?api-version=2019-11-01" | \ jq '{ location : .compute.location, vmSize : .compute.vmSize }' | \ tee ${{ env.BENCHMARK_RESULT_DIR }}/instance.json - name: Run benchmark env: BENCHMARK_RUNTIME_MODE: ${{ matrix.runtime }} run: make benchmark - uses: actions/upload-artifact@v3 if: ${{ always() }} with: name: benchmarking-result-${{ matrix.runtime }} path: ${{ env.BENCHMARK_RESULT_DIR }} stargz-snapshotter-0.12.0/.github/workflows/nightly.yml000066400000000000000000000064431426301527400232650ustar00rootroot00000000000000name: Nightly on: schedule: - cron: '0 0 * * *' # Every day at midnight pull_request: paths: - '.github/workflows/nightly.yml' # This nightly test helps us to track changes on containerd on daily basis # and enable us to quickly fix snapshotter when some of recent changes on # containerd cause incompatibility with this snapshotter. # # TODO1(ktock): Output binaries if needed. # TODO2(ktock): Ideally, this test should be invoked in containerd/containerd's CI on each PR. # This will make sure that each commit merged into containerd/containerd safely # works with stargz snapshotter. env: DOCKER_BUILDKIT: 1 DOCKER_BUILD_ARGS: --build-arg=CONTAINERD_VERSION=main # do tests with the latest containerd jobs: integration: runs-on: ubuntu-20.04 name: Integration steps: - name: Install htpasswd for setting up private registry run: sudo apt-get update -y && sudo apt-get --no-install-recommends install -y apache2-utils - uses: actions/checkout@v3 - name: Run integration test run: make integration test-optimize: runs-on: ubuntu-20.04 name: Optimize steps: - name: Install htpasswd for setting up private registry run: sudo apt-get update -y && sudo apt-get --no-install-recommends install -y apache2-utils - uses: actions/checkout@v3 - name: Run test for optimize subcommand of ctr-remote run: make test-optimize test-kind: runs-on: ubuntu-20.04 name: Kind steps: - name: Install htpasswd for setting up private registry run: sudo apt-get update -y && sudo apt-get --no-install-recommends install -y apache2-utils - uses: actions/checkout@v3 - name: Run test for pulling image from private registry on Kubernetes run: make test-kind test-criauth: runs-on: ubuntu-20.04 name: CRIAuth steps: - name: Install htpasswd for setting up private registry run: sudo apt-get update -y && sudo apt-get --no-install-recommends install -y apache2-utils - uses: actions/checkout@v3 - name: Run test for pulling image from private registry on Kubernetes run: make test-criauth test-cri-containerd: runs-on: ubuntu-20.04 name: CRIValidationContainerd steps: - uses: actions/checkout@v3 - name: Varidate the runtime through CRI with containerd run: make test-cri-containerd test-cri-o: runs-on: ubuntu-18.04 name: CRIValidationCRIO steps: - uses: actions/checkout@v3 - name: Varidate the runtime through CRI with CRI-O env: DOCKER_BUILD_ARGS: "--build-arg=RUNC_VERSION=v1.0.3" run: make test-cri-o test-k3s: runs-on: ubuntu-20.04 name: K3S steps: - uses: actions/setup-go@v3 with: go-version: '1.17.x' - name: Install k3d run: | wget -q -O - https://raw.githubusercontent.com/rancher/k3d/v5.0.0/install.sh | bash - name: Install htpasswd for setting up private registry run: sudo apt-get update -y && sudo apt-get --no-install-recommends install -y apache2-utils - name: Install yq run: | 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 - uses: actions/checkout@v3 - name: Run test with k3s run: make test-k3s stargz-snapshotter-0.12.0/.github/workflows/release.yml000066400000000000000000000040311426301527400232160ustar00rootroot00000000000000name: Release on: push: tags: - 'v*' env: DOCKER_BUILDKIT: 1 jobs: build: runs-on: ubuntu-20.04 name: Build strategy: matrix: arch: ["amd64", "arm-v7", "arm64", "ppc64le", "s390x"] env: OUTPUT_DIR: ${{ github.workspace }}/out steps: - uses: actions/checkout@v3 - name: Build Binary env: DOCKER_BUILDKIT: 1 run: | mkdir ${OUTPUT_DIR} RELEASE_TAG="${GITHUB_REF##*/}" ARCH_ID="${{ matrix.arch }}" BUILD_ARGS=--build-arg=TARGETARCH=${ARCH_ID} if [ "${ARCH_ID}" == "arm-v7" ] ; then BUILD_ARGS="--build-arg=TARGETARCH=arm --build-arg=GOARM=7" fi TAR_FILE_NAME="stargz-snapshotter-${RELEASE_TAG}-linux-${ARCH_ID}.tar.gz" SHA256SUM_FILE_NAME="${TAR_FILE_NAME}.sha256sum" 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}" - name: Save Binary uses: actions/upload-artifact@v3 with: name: builds-${{ matrix.arch }} path: ${{ env.OUTPUT_DIR }}/* release: runs-on: ubuntu-20.04 name: Release needs: [build] env: OUTPUT_DIR: ${{ github.workspace }}/builds steps: - uses: actions/checkout@v3 - name: Download Builds uses: actions/download-artifact@v3 with: path: ${{ env.OUTPUT_DIR }} - name: Create Release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | RELEASE_TAG="${GITHUB_REF##*/}" cat < ${GITHUB_WORKSPACE}/release-note.txt ${RELEASE_TAG} (TBD) EOF ASSET_FLAGS=() ls -al ${OUTPUT_DIR}/ for A in "amd64" "arm-v7" "arm64" "ppc64le" "s390x" ; do for F in ${OUTPUT_DIR}/builds-${A}/* ; do ASSET_FLAGS+=("-a" "$F") done done hub release create "${ASSET_FLAGS[@]}" -F ${GITHUB_WORKSPACE}/release-note.txt --draft "${RELEASE_TAG}" stargz-snapshotter-0.12.0/.github/workflows/tests.yml000066400000000000000000000165541426301527400227550ustar00rootroot00000000000000name: Tests on: push: branches: - main pull_request: env: DOCKER_BUILDKIT: 1 jobs: build: runs-on: ubuntu-20.04 name: Build steps: - uses: actions/checkout@v3 - name: Build all run: ./script/util/make.sh build -j2 test: runs-on: ubuntu-20.04 name: Test steps: - uses: actions/checkout@v3 - name: Test all run: ./script/util/make.sh test-all -j2 linter: runs-on: ubuntu-20.04 name: Linter steps: - uses: actions/checkout@v3 with: fetch-depth: '0' - name: Run Linter run: ./script/util/make.sh install-check-tools check integration: runs-on: ubuntu-20.04 name: Integration strategy: fail-fast: false matrix: buildargs: ["", "--build-arg=CONTAINERD_VERSION=main"] # released version & main version builtin: ["true", "false"] metadata-store: ["memory", "db"] exclude: - buildargs: "" builtin: "true" - metadata-store: "db" builtin: "true" - metadata-store: "db" buildargs: "--build-arg=CONTAINERD_VERSION=main" steps: - name: Install htpasswd for setting up private registry run: sudo apt-get update -y && sudo apt-get --no-install-recommends install -y apache2-utils - uses: actions/checkout@v3 - name: Run integration test env: DOCKER_BUILD_ARGS: ${{ matrix.buildargs }} BUILTIN_SNAPSHOTTER: ${{ matrix.builtin }} METADATA_STORE: ${{ matrix.metadata-store }} run: make integration test-optimize: runs-on: ubuntu-20.04 name: Optimize strategy: fail-fast: false matrix: buildargs: ["", "--build-arg=CONTAINERD_VERSION=main"] # released version & main version steps: - name: Install htpasswd for setting up private registry run: sudo apt-get update -y && sudo apt-get --no-install-recommends install -y apache2-utils - uses: actions/checkout@v3 - name: Run test for optimize subcommand of ctr-remote env: DOCKER_BUILD_ARGS: ${{ matrix.buildargs }} run: make test-optimize test-kind: runs-on: ubuntu-20.04 name: Kind strategy: fail-fast: false matrix: buildargs: ["", "--build-arg=CONTAINERD_VERSION=main"] # released version & main version builtin: ["true", "false"] exclude: - buildargs: "" builtin: "true" steps: - name: Install htpasswd for setting up private registry run: sudo apt-get update -y && sudo apt-get --no-install-recommends install -y apache2-utils - uses: actions/checkout@v3 - name: Run test for pulling image from private registry on Kubernetes env: DOCKER_BUILD_ARGS: ${{ matrix.buildargs }} BUILTIN_SNAPSHOTTER: ${{ matrix.builtin }} run: make test-kind test-criauth: runs-on: ubuntu-20.04 name: CRIAuth strategy: fail-fast: false matrix: buildargs: ["", "--build-arg=CONTAINERD_VERSION=main"] # released version & main version builtin: ["true", "false"] exclude: - buildargs: "" builtin: "true" steps: - name: Install htpasswd for setting up private registry run: sudo apt-get update -y && sudo apt-get --no-install-recommends install -y apache2-utils - uses: actions/checkout@v3 - name: Run test for pulling image from private registry on Kubernetes with CRI keychain mode env: DOCKER_BUILD_ARGS: ${{ matrix.buildargs }} BUILTIN_SNAPSHOTTER: ${{ matrix.builtin }} run: make test-criauth test-cri-containerd: runs-on: ubuntu-20.04 name: CRIValidationContainerd strategy: fail-fast: false matrix: buildargs: ["", "--build-arg=CONTAINERD_VERSION=main"] # released version & main version builtin: ["true", "false"] metadata-store: ["memory", "db"] exclude: - buildargs: "" builtin: "true" - metadata-store: "db" builtin: "true" - metadata-store: "db" buildargs: "--build-arg=CONTAINERD_VERSION=main" steps: - uses: actions/checkout@v3 - name: Validate containerd through CRI env: DOCKER_BUILD_ARGS: ${{ matrix.buildargs }} BUILTIN_SNAPSHOTTER: ${{ matrix.builtin }} METADATA_STORE: ${{ matrix.metadata-store }} run: make test-cri-containerd test-cri-cri-o: runs-on: ubuntu-18.04 name: CRIValidationCRIO strategy: fail-fast: false matrix: metadata-store: ["memory", "db"] steps: - uses: actions/checkout@v3 - name: Validate CRI-O through CRI env: DOCKER_BUILD_ARGS: "--build-arg=RUNC_VERSION=v1.0.3" METADATA_STORE: ${{ matrix.metadata-store }} run: make test-cri-o test-k3s: runs-on: ubuntu-20.04 name: K3S steps: - uses: actions/setup-go@v3 with: go-version: '1.17.x' - name: Install k3d run: | wget -q -O - https://raw.githubusercontent.com/rancher/k3d/v5.0.0/install.sh | bash - name: Install htpasswd for setting up private registry run: sudo apt-get update -y && sudo apt-get --no-install-recommends install -y apache2-utils - name: Install yq run: | 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 - uses: actions/checkout@v3 - name: Run test with k3s run: make test-k3s test-k3s-argo-workflow: runs-on: ubuntu-20.04 name: K3SArgoWorkflow env: RESULT_DIR: ${{ github.workspace }}/argo-workflow/ steps: - uses: actions/setup-go@v3 with: go-version: '1.17.x' - name: Install k3d run: | wget -q -O - https://raw.githubusercontent.com/rancher/k3d/v5.0.0/install.sh | bash - name: Install argo worklflow run: | wget -q https://github.com/argoproj/argo-workflows/releases/download/v3.0.10/argo-linux-amd64.gz gunzip argo-linux-amd64.gz sudo mv argo-linux-amd64 /usr/local/bin/argo sudo chmod +x /usr/local/bin/argo - uses: actions/checkout@v3 - name: Prepare directories run: mkdir "${RESULT_DIR}" - name: Get instance information run: | curl -H "Metadata:true" "http://169.254.169.254/metadata/instance?api-version=2019-11-01" | \ jq '{ location : .compute.location, vmSize : .compute.vmSize }' | \ tee ${{ env.RESULT_DIR }}/instance.json - name: Run argo workflow env: RESULT: ${{ env.RESULT_DIR }}/result.json run: make test-k3s-argo-workflow - uses: actions/upload-artifact@v3 with: name: k3s-argo-workflow path: ${{ env.RESULT_DIR }} # # Project checks # NOTE: Jobs for project checks commonly used in containerd projects # See https://github.com/containerd/project-checks # project: name: Project Checks runs-on: ubuntu-20.04 timeout-minutes: 5 steps: - uses: actions/setup-go@v3 with: go-version: '1.18.x' - uses: actions/checkout@v3 with: path: src/github.com/containerd/stargz-snapshotter fetch-depth: 25 - uses: containerd/project-checks@v1 with: working-directory: src/github.com/containerd/stargz-snapshotter - name: Check proto generated code run: make validate-generated working-directory: src/github.com/containerd/stargz-snapshotter stargz-snapshotter-0.12.0/.gitignore000066400000000000000000000000041426301527400174420ustar00rootroot00000000000000/outstargz-snapshotter-0.12.0/.golangci.yml000066400000000000000000000006211426301527400200430ustar00rootroot00000000000000# This is applied to `estargz` submodule as well. # https://golangci-lint.run/usage/configuration#config-file linters: enable: - structcheck - varcheck - staticcheck - unconvert - gofmt - goimports - revive - ineffassign - vet - unused - misspell disable: - errcheck run: deadline: 4m skip-dirs: - docs - images - out - script stargz-snapshotter-0.12.0/Dockerfile000066400000000000000000000317201426301527400174550ustar00rootroot00000000000000# 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. ARG CONTAINERD_VERSION=v1.6.6 ARG RUNC_VERSION=v1.1.3 ARG CNI_PLUGINS_VERSION=v1.1.1 ARG NERDCTL_VERSION=0.21.0 ARG PODMAN_VERSION=v4.1.1 ARG CRIO_VERSION=v1.24.1 ARG CONMON_VERSION=v2.1.2 ARG COMMON_VERSION=v0.48.0 ARG CRIO_TEST_PAUSE_IMAGE_NAME=k8s.gcr.io/pause:3.6 # Used in CI ARG CRI_TOOLS_VERSION=v1.24.2 # 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". FROM golang:1.18-bullseye AS golang-base # Build containerd FROM golang-base AS containerd-dev ARG CONTAINERD_VERSION 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 && \ cd $GOPATH/src/github.com/containerd/containerd && \ make && DESTDIR=/out/ PREFIX= make install # Build containerd with builtin stargz snapshotter FROM golang-base AS containerd-snapshotter-dev ARG CONTAINERD_VERSION COPY . $GOPATH/src/github.com/containerd/stargz-snapshotter 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 && \ cd $GOPATH/src/github.com/containerd/containerd && \ echo 'require github.com/containerd/stargz-snapshotter v0.0.0' >> go.mod && \ echo 'replace github.com/containerd/stargz-snapshotter => '$GOPATH'/src/github.com/containerd/stargz-snapshotter' >> go.mod && \ echo 'replace github.com/containerd/stargz-snapshotter/estargz => '$GOPATH'/src/github.com/containerd/stargz-snapshotter/estargz' >> go.mod && \ # recent containerd requires to update api/go.mod and integration/client/go.mod as well. if [ -f api/go.mod ] ; then \ echo 'replace github.com/containerd/stargz-snapshotter => '$GOPATH'/src/github.com/containerd/stargz-snapshotter' >> api/go.mod && \ echo 'replace github.com/containerd/stargz-snapshotter/estargz => '$GOPATH'/src/github.com/containerd/stargz-snapshotter/estargz' >> api/go.mod ; \ fi && \ if [ -f integration/client/go.mod ] ; then \ echo 'replace github.com/containerd/stargz-snapshotter => '$GOPATH'/src/github.com/containerd/stargz-snapshotter' >> 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 && \ echo 'package main \nimport _ "github.com/containerd/stargz-snapshotter/service/plugin"' > cmd/containerd/builtins_stargz_snapshotter.go && \ make vendor && make && DESTDIR=/out/ PREFIX= make install # Build runc FROM golang-base AS runc-dev ARG RUNC_VERSION RUN apt-get update -y && apt-get install -y libseccomp-dev && \ git clone -b ${RUNC_VERSION} --depth 1 \ https://github.com/opencontainers/runc $GOPATH/src/github.com/opencontainers/runc && \ cd $GOPATH/src/github.com/opencontainers/runc && \ make && make install PREFIX=/out/ # Build stargz snapshotter FROM golang-base AS snapshotter-dev ARG TARGETARCH ARG GOARM ARG SNAPSHOTTER_BUILD_FLAGS ARG CTR_REMOTE_BUILD_FLAGS COPY . $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=${CTR_REMOTE_BUILD_FLAGS} make ctr-remote # Build stargz store FROM golang-base AS stargz-store-dev ARG TARGETARCH ARG GOARM ARG SNAPSHOTTER_BUILD_FLAGS ARG CTR_REMOTE_BUILD_FLAGS COPY . $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 # Build podman FROM golang-base AS podman-dev ARG PODMAN_VERSION RUN apt-get update -y && apt-get install -y libseccomp-dev libgpgme-dev && \ git clone https://github.com/containers/podman $GOPATH/src/github.com/containers/podman && \ cd $GOPATH/src/github.com/containers/podman && \ git checkout ${PODMAN_VERSION} && \ make && make install PREFIX=/out/ # Build CRI-O FROM golang-base AS cri-o-dev ARG CRIO_VERSION 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 && \ cd $GOPATH/src/github.com/cri-o/cri-o && \ git checkout ${CRIO_VERSION} && \ make && make install PREFIX=/out/ && \ curl -sSL --output /out/crio.service https://raw.githubusercontent.com/cri-o/cri-o/${CRIO_VERSION}/contrib/systemd/crio.service # Build conmon FROM golang-base AS conmon-dev ARG CONMON_VERSION RUN apt-get update -y && apt-get install -y gcc git libc6-dev libglib2.0-dev pkg-config make libseccomp-dev && \ git clone -b ${CONMON_VERSION} --depth 1 \ https://github.com/containers/conmon $GOPATH/src/github.com/containers/conmon && \ cd $GOPATH/src/github.com/containers/conmon && \ mkdir /out/ && make && make install PREFIX=/out/ # Get seccomp.json for Podman/CRI-O FROM golang-base AS containers-common-dev ARG COMMON_VERSION RUN git clone https://github.com/containers/common $GOPATH/src/github.com/containers/common && \ cd $GOPATH/src/github.com/containers/common && \ git checkout ${COMMON_VERSION} && mkdir /out/ && cp pkg/seccomp/seccomp.json /out/ # Binaries for release FROM scratch AS release-binaries COPY --from=snapshotter-dev /out/* / COPY --from=stargz-store-dev /out/* / # Base image which contains containerd with default snapshotter FROM golang-base AS containerd-base ARG TARGETARCH ARG NERDCTL_VERSION RUN apt-get update -y && apt-get --no-install-recommends install -y fuse && \ curl -sSL --output /tmp/nerdctl.tgz https://github.com/containerd/nerdctl/releases/download/v${NERDCTL_VERSION}/nerdctl-${NERDCTL_VERSION}-linux-${TARGETARCH:-amd64}.tar.gz && \ tar zxvf /tmp/nerdctl.tgz -C /usr/local/bin && \ rm -f /tmp/nerdctl.tgz COPY --from=containerd-dev /out/bin/containerd /out/bin/containerd-shim-runc-v2 /usr/local/bin/ COPY --from=runc-dev /out/sbin/* /usr/local/sbin/ # Base image which contains containerd with stargz snapshotter FROM containerd-base AS snapshotter-base COPY --from=snapshotter-dev /out/* /usr/local/bin/ RUN ln -s /usr/local/bin/ctr-remote /usr/local/bin/ctr # Base image which contains containerd with builtin stargz snapshotter FROM golang-base AS containerd-snapshotter-base ARG TARGETARCH ARG NERDCTL_VERSION RUN apt-get update -y && apt-get --no-install-recommends install -y fuse && \ curl -sSL --output /tmp/nerdctl.tgz https://github.com/containerd/nerdctl/releases/download/v${NERDCTL_VERSION}/nerdctl-${NERDCTL_VERSION}-linux-${TARGETARCH:-amd64}.tar.gz && \ tar zxvf /tmp/nerdctl.tgz -C /usr/local/bin && \ rm -f /tmp/nerdctl.tgz COPY --from=containerd-snapshotter-dev /out/bin/containerd /out/bin/containerd-shim-runc-v2 /usr/local/bin/ COPY --from=runc-dev /out/sbin/* /usr/local/sbin/ COPY --from=snapshotter-dev /out/ctr-remote /usr/local/bin/ RUN ln -s /usr/local/bin/ctr-remote /usr/local/bin/ctr # Base image which contains podman with stargz-store FROM golang-base AS podman-base ARG TARGETARCH ARG CNI_PLUGINS_VERSION ARG PODMAN_VERSION RUN apt-get update -y && apt-get --no-install-recommends install -y fuse libgpgme-dev \ iptables libyajl-dev && \ # Make CNI plugins manipulate iptables instead of nftables # as this test runs in a Docker container that network is configured with iptables. # c.f. https://github.com/moby/moby/issues/26824 update-alternatives --set iptables /usr/sbin/iptables-legacy && \ mkdir -p /etc/containers /etc/cni/net.d /opt/cni/bin && \ 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 COPY --from=podman-dev /out/bin/* /usr/local/bin/ COPY --from=runc-dev /out/sbin/* /usr/local/sbin/ COPY --from=conmon-dev /out/bin/* /usr/local/bin/ COPY --from=containers-common-dev /out/seccomp.json /usr/share/containers/ COPY --from=stargz-store-dev /out/* /usr/local/bin/ # Image which can be used for interactive demo environment FROM containerd-base AS demo ARG CNI_PLUGINS_VERSION ARG TARGETARCH RUN apt-get update && apt-get install -y iptables && \ # Make CNI plugins manipulate iptables instead of nftables # as this test runs in a Docker container that network is configured with iptables. # c.f. https://github.com/moby/moby/issues/26824 update-alternatives --set iptables /usr/sbin/iptables-legacy && \ mkdir -p /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) FROM kindest/node:v1.24.2 AS kind-builtin-snapshotter # see https://medium.com/nttlabs/ubuntu-21-10-and-fedora-35-do-not-work-on-docker-20-10-9-1cd439d9921 ADD https://github.com/AkihiroSuda/clone3-workaround/releases/download/v1.0.0/clone3-workaround.x86_64 /clone3-workaround RUN chmod 755 /clone3-workaround 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 ./script/config/ / RUN /clone3-workaround apt-get update -y && /clone3-workaround apt-get install --no-install-recommends -y fuse ENTRYPOINT [ "/usr/local/bin/entrypoint", "/sbin/init" ] # Image for testing CRI-O with Stargz Store. # NOTE: This cannot be used for the node image of KinD. FROM ubuntu:22.04 AS crio-stargz-store ARG CNI_PLUGINS_VERSION ARG CRIO_TEST_PAUSE_IMAGE_NAME ENV container docker RUN apt-get update -y && apt-get install --no-install-recommends -y \ ca-certificates fuse libgpgme-dev libglib2.0-dev curl \ iptables conntrack systemd systemd-sysv && \ DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y tzdata && \ # Make CNI plugins manipulate iptables instead of nftables # as this test runs in a Docker container that network is configured with iptables. # c.f. https://github.com/moby/moby/issues/26824 update-alternatives --set iptables /usr/sbin/iptables-legacy && \ mkdir -p /opt/cni/bin && \ curl -sSL 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 && \ echo ${CRIO_TEST_PAUSE_IMAGE_NAME} > /pause_name && \ mkdir -p /etc/sysconfig && \ echo CRIO_RUNTIME_OPTIONS=--pause-image=${CRIO_TEST_PAUSE_IMAGE_NAME} > /etc/sysconfig/crio && \ # Necessary to pass CRI tests: https://github.com/kubernetes-sigs/cri-tools/pull/905 mkdir -p /etc/crio/crio.conf.d && \ printf '[crio.runtime]\nseccomp_use_default_when_empty = false\n' > /etc/crio/crio.conf.d/02-seccomp.conf COPY --from=stargz-store-dev /out/* /usr/local/bin/ COPY --from=cri-o-dev /out/bin/* /usr/local/bin/ COPY --from=cri-o-dev /out/crio.service /etc/systemd/system/ COPY --from=runc-dev /out/sbin/* /usr/local/sbin/ COPY --from=conmon-dev /out/bin/* /usr/local/bin/ COPY --from=containers-common-dev /out/seccomp.json /usr/share/containers/ COPY ./script/config-cri-o/ / ENTRYPOINT [ "/usr/local/bin/entrypoint" ] # Image which can be used as a node image for KinD FROM kindest/node:v1.24.2 # see https://medium.com/nttlabs/ubuntu-21-10-and-fedora-35-do-not-work-on-docker-20-10-9-1cd439d9921 ADD https://github.com/AkihiroSuda/clone3-workaround/releases/download/v1.0.0/clone3-workaround.x86_64 /clone3-workaround RUN chmod 755 /clone3-workaround 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 ./script/config/ / RUN /clone3-workaround apt-get update -y && /clone3-workaround apt-get install --no-install-recommends -y fuse && \ systemctl enable stargz-snapshotter ENTRYPOINT [ "/usr/local/bin/entrypoint", "/sbin/init" ] stargz-snapshotter-0.12.0/LICENSE000066400000000000000000000261361426301527400164750ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. stargz-snapshotter-0.12.0/MAINTAINERS000066400000000000000000000005641426301527400171620ustar00rootroot00000000000000# stargz-snapshotter maintainers # # As a containerd sub-project, containerd maintainers are also included from https://github.com/containerd/project/blob/main/MAINTAINERS. # See https://github.com/containerd/project/blob/main/GOVERNANCE.md for description of maintainer role # # COMMITTERS # GitHub ID, Name, Email address ktock, Kohei Tokunaga, ktokunaga.mail@gmail.com stargz-snapshotter-0.12.0/Makefile000066400000000000000000000074051426301527400171260ustar00rootroot00000000000000# 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. # Base path used to install. CMD_DESTDIR ?= /usr/local GO111MODULE_VALUE=auto PREFIX ?= $(CURDIR)/out/ PKG=github.com/containerd/stargz-snapshotter VERSION=$(shell git describe --match 'v[0-9]*' --dirty='.m' --always --tags) REVISION=$(shell git rev-parse HEAD)$(shell if ! git diff --no-ext-diff --quiet --exit-code; then echo .m; fi) GO_LD_FLAGS=-ldflags '-s -w -X $(PKG)/version.Version=$(VERSION) -X $(PKG)/version.Revision=$(REVISION) $(GO_EXTRA_LDFLAGS)' CMD=containerd-stargz-grpc ctr-remote stargz-store CMD_BINARIES=$(addprefix $(PREFIX),$(CMD)) .PHONY: all build check install-check-tools install uninstall clean test test-root test-all integration test-optimize benchmark test-kind test-cri-containerd test-cri-o test-criauth generate validate-generated test-k3s test-k3s-argo-workflow vendor all: build build: $(CMD) FORCE: containerd-stargz-grpc: FORCE cd cmd/ ; GO111MODULE=$(GO111MODULE_VALUE) go build -o $(PREFIX)$@ $(GO_BUILD_FLAGS) $(GO_LD_FLAGS) -v ./containerd-stargz-grpc ctr-remote: FORCE cd cmd/ ; GO111MODULE=$(GO111MODULE_VALUE) go build -o $(PREFIX)$@ $(GO_BUILD_FLAGS) $(GO_LD_FLAGS) -v ./ctr-remote stargz-store: FORCE cd cmd/ ; GO111MODULE=$(GO111MODULE_VALUE) go build -o $(PREFIX)$@ $(GO_BUILD_FLAGS) $(GO_LD_FLAGS) -v ./stargz-store check: @echo "$@" @GO111MODULE=$(GO111MODULE_VALUE) $(shell go env GOPATH)/bin/golangci-lint run @cd ./estargz ; GO111MODULE=$(GO111MODULE_VALUE) $(shell go env GOPATH)/bin/golangci-lint run @cd ./cmd ; GO111MODULE=$(GO111MODULE_VALUE) $(shell go env GOPATH)/bin/golangci-lint run @cd ./ipfs ; GO111MODULE=$(GO111MODULE_VALUE) $(shell go env GOPATH)/bin/golangci-lint run install-check-tools: @curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/v1.46.2/install.sh | sh -s -- -b $(shell go env GOPATH)/bin v1.46.2 install: @echo "$@" @mkdir -p $(CMD_DESTDIR)/bin @install $(CMD_BINARIES) $(CMD_DESTDIR)/bin uninstall: @echo "$@" @rm -f $(addprefix $(CMD_DESTDIR)/bin/,$(notdir $(CMD_BINARIES))) clean: @echo "$@" @rm -f $(CMD_BINARIES) generate: @./script/generated-files/generate.sh update validate-generated: @./script/generated-files/generate.sh validate vendor: @cd ./estargz ; GO111MODULE=$(GO111MODULE_VALUE) go mod tidy @cd ./ipfs ; GO111MODULE=$(GO111MODULE_VALUE) go mod tidy @GO111MODULE=$(GO111MODULE_VALUE) go mod tidy @cd ./cmd ; GO111MODULE=$(GO111MODULE_VALUE) go mod tidy test: @echo "$@" @GO111MODULE=$(GO111MODULE_VALUE) go test -race ./... @cd ./estargz ; GO111MODULE=$(GO111MODULE_VALUE) go test -timeout 30m -race ./... @cd ./cmd ; GO111MODULE=$(GO111MODULE_VALUE) go test -timeout 20m -race ./... @cd ./ipfs ; GO111MODULE=$(GO111MODULE_VALUE) go test -timeout 20m -race ./... test-root: @echo "$@" @GO111MODULE=$(GO111MODULE_VALUE) go test -race ./snapshot -test.root test-all: test-root test integration: @./script/integration/test.sh test-optimize: @./script/optimize/test.sh benchmark: @./script/benchmark/test.sh test-kind: @./script/kind/test.sh test-cri-containerd: @./script/cri-containerd/test.sh test-cri-o: @./script/cri-o/test.sh test-criauth: @./script/criauth/test.sh test-k3s: @./script/k3s/test.sh test-k3s-argo-workflow: @./script/k3s-argo-workflow/run.sh stargz-snapshotter-0.12.0/NOTICE.md000066400000000000000000000072661426301527400167760ustar00rootroot00000000000000The source code developed under the Stargz Snapshotter Project is licensed under Apache License 2.0. However, the Stargz Snapshotter project contains modified subcomponents from Container Registry Filesystem Project with separate copyright notices and license terms. Your use of the source code for the subcomponent is subject to the terms and conditions as defined by the source project. Files in these subcomponents contain following file header. ``` Copyright 2019 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the NOTICE.md file. ``` These source code is governed by a 3-Clause BSD license. The copyright notice, list of conditions and disclaimer are the following. ``` Copyright (c) 2019 Google LLC. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ``` The Stargz Snapshotter project also contains modified benchmarking code from HelloBench Project with separate copyright notices and license terms. Your use of the source code for the benchmarking code is subject to the terms and conditions as defined by the source project. These source code is governed by a MIT license. The copyright notice, condition and disclaimer are the following. The file in the benchmarking code contains it as the file header. ``` The MIT License (MIT) Copyright (c) 2015 Tintri Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` stargz-snapshotter-0.12.0/README.md000066400000000000000000000361231426301527400167440ustar00rootroot00000000000000[[⬇️ **Download**]](https://github.com/containerd/stargz-snapshotter/releases) [[📔**Browse images**]](./docs/pre-converted-images.md) [[☸**Quick Start (Kubernetes)**]](#quick-start-with-kubernetes) [[🤓**Quick Start (nerdctl)**]](https://github.com/containerd/nerdctl/blob/master/docs/stargz.md) [[🔆**Install**]](./docs/INSTALL.md) # Stargz Snapshotter [![Tests Status](https://github.com/containerd/stargz-snapshotter/workflows/Tests/badge.svg)](https://github.com/containerd/stargz-snapshotter/actions?query=workflow%3ATests+branch%3Amain) [![Benchmarking](https://github.com/containerd/stargz-snapshotter/workflows/Benchmark/badge.svg)](https://github.com/containerd/stargz-snapshotter/actions?query=workflow%3ABenchmark+branch%3Amain) [![Nightly](https://github.com/containerd/stargz-snapshotter/workflows/Nightly/badge.svg)](https://github.com/containerd/stargz-snapshotter/actions?query=workflow%3ANightly+branch%3Amain) Read also introductory blog: [Startup Containers in Lightning Speed with Lazy Image Distribution on Containerd](https://medium.com/nttlabs/startup-containers-in-lightning-speed-with-lazy-image-distribution-on-containerd-243d94522361) Pulling image is one of the time-consuming steps in the container lifecycle. Research shows that time to take for pull operation accounts for 76% of container startup time[[FAST '16]](https://www.usenix.org/node/194431). *Stargz Snapshotter* is an implementation of snapshotter which aims to solve this problem by *lazy pulling*. *Lazy pulling* here means a container can run without waiting for the pull completion of the image and necessary chunks of the image are fetched *on-demand*. [*eStargz*](/docs/stargz-estargz.md) is a lazily-pullable image format proposed by this project. This is compatible to [OCI](https://github.com/opencontainers/image-spec/)/[Docker](https://github.com/moby/moby/blob/master/image/spec/v1.2.md) images so this can be pushed to standard container registries (e.g. ghcr.io) as well as this is *still runnable* even on eStargz-agnostic runtimes including Docker. eStargz format is based on [stargz image format by CRFS](https://github.com/google/crfs) but comes with additional features like runtime optimization and content verification. The following histogram is the benchmarking result for startup time of several containers measured on Github Actions, using GitHub Container Registry. The benchmarking result on ecdb227 `legacy` shows the startup performance when we use containerd's default snapshotter (`overlayfs`) with images copied from `docker.io/library` without optimization. For this configuration, containerd pulls entire image contents and `pull` operation takes accordingly. When we use stargz snapshotter with eStargz-converted images but without any optimization (`estargz-noopt`) we are seeing performance improvement on the `pull` operation because containerd can start the container without waiting for the `pull` completion and fetch necessary chunks of the image on-demand. But at the same time, we see the performance drawback for `run` operation because each access to files takes extra time for fetching them from the registry. When we use [eStargz with optimization](/docs/ctr-remote.md) (`estargz`), we can mitigate the performance drawback observed in `estargz-noopt` images. This is because [stargz snapshotter prefetches and caches *likely accessed files* during running the container](/docs/stargz-estargz.md). On the first container creation, stargz snapshotter waits for the prefetch completion so `create` sometimes takes longer than other types of image. But it's still shorter than waiting for downloading all files of all layers. The above histogram is [the benchmarking result on the commit `ecdb227`](https://github.com/containerd/stargz-snapshotter/actions/runs/398606060). We are constantly measuring the performance of this snapshotter so you can get the latest one through the badge shown top of this doc. Please note that we sometimes see dispersion among the results because of the NW condition on the internet and the location of the instance in the Github Actions, etc. Our benchmarking method is based on [HelloBench](https://github.com/Tintri/hello-bench). :nerd_face: You can also run containers on IPFS with lazy pulling. This is an experimental feature. See [`./docs/ipfs.md`](./docs/ipfs.md) for more details. Stargz Snapshotter is a **non-core** sub-project of containerd. ## Quick Start with Kubernetes - For more details about stargz snapshotter plugin and its configuration, refer to [Containerd Stargz Snapshotter Plugin Overview](/docs/overview.md). - For more details about setup lazy pulling of eStargz with containerd, CRI-O, Podman, systemd, etc., refer to [Install Stargz Snapshotter and Stargz Store](./docs/INSTALL.md). For using stargz snapshotter on kubernetes nodes, you need the following configuration to containerd as well as run stargz snapshotter daemon on the node. We assume that you are using containerd (> v1.4.2) as a CRI runtime. ```toml version = 2 # Plug stargz snapshotter into containerd # Containerd recognizes stargz snapshotter through specified socket address. # The specified address below is the default which stargz snapshotter listen to. [proxy_plugins] [proxy_plugins.stargz] type = "snapshot" address = "/run/containerd-stargz-grpc/containerd-stargz-grpc.sock" # Use stargz snapshotter through CRI [plugins."io.containerd.grpc.v1.cri".containerd] snapshotter = "stargz" 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. ```console $ kind create cluster --name stargz-demo --image ghcr.io/stargz-containers/estargz-kind-node:0.11.3 ``` :information_source: kind binary v0.11.x or newer is recommended. :information_source: You can get latest node images from [`ghcr.io/stargz-containers/estargz-kind-node`](https://github.com/orgs/stargz-containers/packages/container/package/estargz-kind-node). Then you can create eStargz pods on the cluster. In this example, we create a stargz-converted Node.js pod (`ghcr.io/stargz-containers/node:17.8.0-esgz`) as a demo. ```yaml apiVersion: v1 kind: Pod metadata: name: nodejs spec: containers: - name: nodejs-stargz image: ghcr.io/stargz-containers/node:17.8.0-esgz command: ["node"] args: - -e - var http = require('http'); http.createServer(function(req, res) { res.writeHead(200); res.end('Hello World!\n'); }).listen(80); ports: - containerPort: 80 ``` The following command lazily pulls `ghcr.io/stargz-containers/node:17.8.0-esgz` from Github Container Registry and creates the pod so the time to take for it is shorter than the original image `library/node:13.13`. ```console $ kubectl --context kind-stargz-demo apply -f stargz-pod.yaml && kubectl --context kind-stargz-demo get po nodejs -w $ kubectl --context kind-stargz-demo port-forward nodejs 8080:80 & $ curl 127.0.0.1:8080 Hello World! ``` Stargz snapshotter also supports [further configuration](/docs/overview.md) including private registry authentication, mirror registries, etc. ## Getting eStargz images - For more examples and details about the image converter `ctr-remote`, refer to [Optimize Images with `ctr-remote image optimize`](/docs/ctr-remote.md). - For more details about eStargz format, refer to [eStargz: Standard-Compatible Extensions to Tar.gz Layers for Lazy Pulling Container Images](/docs/stargz-estargz.md) For lazy pulling images, you need to prepare eStargz images first. There are several ways to achieve that. This section describes some of them. ### Trying pre-built eStargz images You can try our pre-converted eStargz images on ghcr.io listed in [Trying pre-converted images](/docs/pre-converted-images.md). ### Building eStargz images using BuildKit BuildKit supports building eStargz image since v0.10. You can try it using [Docker Buildx](https://docs.docker.com/buildx/working-with-buildx/). The following command builds an eStargz image and push it to `ghcr.io/ktock/hello:esgz`. Flags `oci-mediatypes=true,compression=estargz` enable to build eStargz. ``` $ docker buildx build -t ghcr.io/ktock/hello:esgz \ -o type=registry,oci-mediatypes=true,compression=estargz,force-compression=true \ /tmp/buildctx/ ``` > NOTE1: `force-compression=true` isn't needed if the base image is already eStargz. > NOTE2: Docker still does not support lazy pulling of eStargz. 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`). ``` $ docker buildx create --use --name v0.10.3 --driver docker-container --driver-opt image=moby/buildkit:v0.10.3 $ docker buildx inspect --bootstrap v0.10.3 ``` ### Building eStargz images using Kaniko [Kaniko](https://github.com/GoogleContainerTools/kaniko) is an image builder runnable in containers and Kubernetes. Since v1.5.0, it experimentally supports building eStargz. `GGCR_EXPERIMENT_ESTARGZ=1` is needed. ```console $ docker run --rm -e GGCR_EXPERIMENT_ESTARGZ=1 \ -v /tmp/buildctx:/workspace -v ~/.docker/config.json:/kaniko/.docker/config.json:ro \ gcr.io/kaniko-project/executor:v1.8.1 --destination ghcr.io/ktock/hello:esgz ``` ### Building eStargz images using nerdctl [nerdctl](https://github.com/containerd/nerdctl), Docker-compatible CLI of containerd, supports building eStargz images. ```console $ nerdctl build -t ghcr.io/ktock/hello:1 /tmp/buildctx $ nerdctl image convert --estargz --oci ghcr.io/ktock/hello:1 ghcr.io/ktock/hello:esgz $ nerdctl push ghcr.io/ktock/hello:esgz ``` > NOTE: `--estargz` should be specified in conjunction with `--oci` Please refer to nerdctl document for details for further information (e.g. lazy pulling): https://github.com/containerd/nerdctl/blob/master/docs/stargz.md ### Creating eStargz images using `ctr-remote` [`ctr-remote`](/docs/ctr-remote.md) allows converting an image into eStargz with optimizing it. As shown in the above benchmarking result, on-demand lazy pulling improves the performance of pull but causes runtime performance penalty because reading files induce remotely downloading contents. For solving this, `ctr-remote` has *workload-based* optimization for images. For trying the examples described in this section, you can also use the docker-compose-based demo environment. You can setup this environment as the following commands (put this repo on `${GOPATH}/src/github.com/containerd/stargz-snapshotter`). *Note that this runs privileged containers on your host.* ```console $ cd ${GOPATH}/src/github.com/containerd/stargz-snapshotter/script/demo $ docker-compose build containerd_demo $ docker-compose up -d $ docker exec -it containerd_demo /bin/bash (inside container) # ./script/demo/run.sh ``` Generally, container images are built with purpose and the *workloads* are defined in the Dockerfile with some parameters (e.g. entrypoint, envvars and user). By default, `ctr-remote` optimizes the performance of reading files that are most likely accessed in the workload defined in the Dockerfile. [You can also specify the custom workload using options if needed](/docs/ctr-remote.md). The following example converts the legacy `library/ubuntu:20.04` image into eStargz. The command also optimizes the image for the workload of executing `ls` on `/bin/bash`. The thing actually done is it runs the specified workload in a temporary container and profiles all file accesses with marking them as *likely accessed* also during runtime. The converted image is still **docker-compatible** so you can run it with eStargz-agnostic runtimes (e.g. Docker). ```console # ctr-remote image pull docker.io/library/ubuntu:20.04 # ctr-remote image optimize --oci --entrypoint='[ "/bin/bash", "-c" ]' --args='[ "ls" ]' docker.io/library/ubuntu:20.04 registry2:5000/ubuntu:20.04 # ctr-remote image push --plain-http registry2:5000/ubuntu:20.04 ``` Finally, the following commands clear the local cache then pull the eStargz image lazily. Stargz snapshotter prefetches files that are most likely accessed in the optimized workload, which hopefully increases the cache hit rate for that workload and mitigates runtime overheads as shown in the benchmarking result shown top of this doc. ```console # ctr-remote image rm --sync registry2:5000/ubuntu:20.04 # ctr-remote images rpull --plain-http registry2:5000/ubuntu:20.04 fetching sha256:610399d1... application/vnd.oci.image.index.v1+json fetching sha256:0b4a26b4... application/vnd.oci.image.manifest.v1+json fetching sha256:8d8d9dbe... application/vnd.oci.image.config.v1+json # ctr-remote run --rm -t --snapshotter=stargz registry2:5000/ubuntu:20.04 test /bin/bash root@8eabb871a9bd:/# ls bin boot dev etc home lib lib32 lib64 libx32 media mnt opt proc root run sbin srv sys tmp usr var ``` > 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 Currently, Stargz Snapshotter repository contains two Go modules as the following and both of them need to be imported. - `github.com/containerd/stargz-snapshotter` - `github.com/containerd/stargz-snapshotter/estargz` Please make sure you import the both of them and they point to *the same commit version*. ## Project details Stargz Snapshotter is a containerd **non-core** sub-project, licensed under the [Apache 2.0 license](./LICENSE). As a containerd non-core sub-project, you will find the: * [Project governance](https://github.com/containerd/project/blob/main/GOVERNANCE.md), * [Maintainers](./MAINTAINERS), * and [Contributing guidelines](https://github.com/containerd/project/blob/main/CONTRIBUTING.md) information in our [`containerd/project`](https://github.com/containerd/project) repository. stargz-snapshotter-0.12.0/analyzer/000077500000000000000000000000001426301527400173055ustar00rootroot00000000000000stargz-snapshotter-0.12.0/analyzer/analyzer.go000066400000000000000000000274111426301527400214660ustar00rootroot00000000000000/* 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 analyzer import ( "context" "fmt" "io" "os" "os/signal" "sync" "sync/atomic" "syscall" "time" "github.com/containerd/console" "github.com/containerd/containerd" "github.com/containerd/containerd/cio" "github.com/containerd/containerd/cmd/ctr/commands" "github.com/containerd/containerd/cmd/ctr/commands/tasks" "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/log" "github.com/containerd/containerd/mount" "github.com/containerd/containerd/oci" "github.com/containerd/containerd/platforms" "github.com/containerd/containerd/snapshots" "github.com/containerd/stargz-snapshotter/analyzer/fanotify" "github.com/containerd/stargz-snapshotter/analyzer/recorder" "github.com/opencontainers/go-digest" "github.com/opencontainers/image-spec/identity" runtimespec "github.com/opencontainers/runtime-spec/specs-go" "github.com/rs/xid" ) var defaultPeriod = 10 * time.Second // Analyze analyzes the passed image then store the record of prioritized files into // containerd's content store. This function returns the digest of that record file. // This digest can be used to read record from the content store. func Analyze(ctx context.Context, client *containerd.Client, ref string, opts ...Option) (digest.Digest, error) { var aOpts analyzerOpts for _, o := range opts { o(&aOpts) } if aOpts.terminal && aOpts.waitOnSignal { return "", fmt.Errorf("wait-on-signal option cannot be used with terminal option") } target, err := os.MkdirTemp("", "target") if err != nil { return "", err } defer os.RemoveAll(target) cs := client.ContentStore() is := client.ImageService() ss := client.SnapshotService(aOpts.snapshotter) img, err := is.Get(ctx, ref) if err != nil { return "", err } platformImg := containerd.NewImageWithPlatform(client, img, platforms.Default()) // Mount the target image // NOTE: We cannot let containerd prepare the rootfs. We create mount namespace *before* // creating the container so containerd's bundle preparation (mounting snapshots to // the bundle directory, etc.) is invisible inside the pre-unshared mount namespace. // This leads to runc using empty directory as the rootfs. if unpacked, err := platformImg.IsUnpacked(ctx, aOpts.snapshotter); err != nil { return "", err } else if !unpacked { if err := platformImg.Unpack(ctx, aOpts.snapshotter); err != nil { return "", err } } cleanup, err := mountImage(ctx, ss, platformImg, target) if err != nil { return "", err } defer cleanup() // Spawn a fanotifier process in a new mount namespace and setup recorder. fanotifier, err := fanotify.SpawnFanotifier("/proc/self/exe") if err != nil { return "", fmt.Errorf("failed to spawn fanotifier: %w", err) } defer func() { if err := fanotifier.Close(); err != nil { log.G(ctx).WithError(err).Warnf("failed to close fanotifier") } }() // Prepare the spec based on the specified image and runtime options. var sOpts []oci.SpecOpts if aOpts.specOpts != nil { gotOpts, done, err := aOpts.specOpts(platformImg, target) if err != nil { return "", err } defer func() { if err := done(); err != nil { log.G(ctx).WithError(err).Warnf("failed to cleanup container") return } }() sOpts = append(sOpts, gotOpts...) } else { sOpts = append(sOpts, oci.WithDefaultSpec(), oci.WithDefaultUnixDevices, oci.WithRootFSPath(target), oci.WithImageConfig(platformImg), ) } sOpts = append(sOpts, oci.WithLinuxNamespace(runtimespec.LinuxNamespace{ Type: runtimespec.MountNamespace, Path: fanotifier.MountNamespacePath(), // use mount namespace that the fanotifier created })) // Create the container and the task var container containerd.Container for i := 0; i < 3; i++ { id := xid.New().String() var s runtimespec.Spec container, err = client.NewContainer(ctx, id, containerd.WithImage(platformImg), containerd.WithSnapshotter(aOpts.snapshotter), containerd.WithImageStopSignal(platformImg, "SIGKILL"), // WithImageConfig depends on WithImage and WithSnapshotter for resolving // username (accesses to /etc/{passwd,group} files on the rootfs) containerd.WithSpec(&s, sOpts...), ) if err != nil { if errdefs.IsAlreadyExists(err) { log.G(ctx).WithError(err).Warnf("failed to create container") continue } return "", err } break } if container == nil { return "", fmt.Errorf("failed to create container") } defer container.Delete(ctx, containerd.WithSnapshotCleanup) var ioCreator cio.Creator var con console.Console stdinC := newLazyReadCloser(os.Stdin) if aOpts.terminal { if !aOpts.stdin { return "", fmt.Errorf("terminal cannot be used if stdin isn't enabled") } con = console.Current() defer con.Reset() if err := con.SetRaw(); err != nil { return "", err } // On terminal mode, the "stderr" field is unused. ioCreator = cio.NewCreator(cio.WithStreams(con, con, nil), cio.WithTerminal) } else if aOpts.stdin { ioCreator = cio.NewCreator(cio.WithStreams(stdinC, os.Stdout, os.Stderr)) } else { ioCreator = cio.NewCreator(cio.WithStreams(nil, os.Stdout, os.Stderr)) } task, err := container.NewTask(ctx, ioCreator) if err != nil { return "", err } stdinC.registerCloser(func() { // Ensure to close IO when stdin get EOF task.CloseIO(ctx, containerd.WithStdinCloser) }) // Start to monitor "/" and run the task. rc, err := recorder.NewImageRecorder(ctx, cs, img, platforms.Default()) if err != nil { return "", err } defer rc.Close() if err := fanotifier.Start(); err != nil { return "", fmt.Errorf("failed to start fanotifier: %w", err) } var fanotifierClosed bool var fanotifierClosedMu sync.Mutex go func() { var successCount int defer func() { log.G(ctx).Debugf("success record %d path", successCount) }() for { path, err := fanotifier.GetPath() if err != nil { if err == io.EOF { fanotifierClosedMu.Lock() isFanotifierClosed := fanotifierClosed fanotifierClosedMu.Unlock() if isFanotifierClosed { break } } log.G(ctx).WithError(err).Error("failed to get notified path") break } if err := rc.Record(path); err != nil { log.G(ctx).WithError(err).Debugf("failed to record %q", path) } successCount++ } }() if aOpts.terminal { if err := tasks.HandleConsoleResize(ctx, task, con); err != nil { log.G(ctx).WithError(err).Error("failed to resize console") } } else { sigc := commands.ForwardAllSignals(ctx, task) defer commands.StopCatch(sigc) } if err := task.Start(ctx); err != nil { return "", err } // Wait until the task exit var status containerd.ExitStatus var killOk bool if aOpts.waitOnSignal { // NOTE: not functional with `terminal` option log.G(ctx).Infof("press Ctrl+C to terminate the container") status, killOk, err = waitOnSignal(ctx, container, task) if err != nil { return "", err } } else { if aOpts.period <= 0 { aOpts.period = defaultPeriod } log.G(ctx).Infof("waiting for %v ...", aOpts.period) status, killOk, err = waitOnTimeout(ctx, container, task, aOpts.period) if err != nil { return "", err } } if !killOk { log.G(ctx).Warnf("failed to exit task %v; manually kill it", task.ID()) } else { code, _, err := status.Result() if err != nil { return "", err } log.G(ctx).Infof("container exit with code %v", code) if _, err := task.Delete(ctx); err != nil { return "", err } } // ensure no record comes in fanotifierClosedMu.Lock() fanotifierClosed = true fanotifierClosedMu.Unlock() if err := fanotifier.Close(); err != nil { log.G(ctx).WithError(err).Warnf("failed to cleanup fanotifier") } // Finish recording return rc.Commit(ctx) } func mountImage(ctx context.Context, ss snapshots.Snapshotter, image containerd.Image, mountpoint string) (func(), error) { diffIDs, err := image.RootFS(ctx) if err != nil { return nil, err } mounts, err := ss.Prepare(ctx, mountpoint, identity.ChainID(diffIDs).String()) if err != nil { return nil, err } if err := mount.All(mounts, mountpoint); err != nil { if err := ss.Remove(ctx, mountpoint); err != nil && !errdefs.IsNotFound(err) { log.G(ctx).WithError(err).Warnf("failed to cleanup snapshot after mount error") } return nil, fmt.Errorf("failed to mount rootfs at %q: %w", mountpoint, err) } return func() { if err := mount.UnmountAll(mountpoint, 0); err != nil { log.G(ctx).WithError(err).Warnf("failed to unmount snapshot") } if err := ss.Remove(ctx, mountpoint); err != nil && !errdefs.IsNotFound(err) { log.G(ctx).WithError(err).Warnf("failed to cleanup snapshot") } }, nil } func waitOnSignal(ctx context.Context, container containerd.Container, task containerd.Task) (containerd.ExitStatus, bool, error) { statusC, err := task.Wait(ctx) if err != nil { return containerd.ExitStatus{}, false, err } sc := make(chan os.Signal, 1) signal.Notify(sc, syscall.SIGINT) defer signal.Stop(sc) select { case status := <-statusC: return status, true, nil case <-sc: log.G(ctx).Info("signal detected") status, err := killTask(ctx, container, task, statusC) if err != nil { log.G(ctx).WithError(err).Warnf("failed to kill container") return containerd.ExitStatus{}, false, nil } return status, true, nil } } func waitOnTimeout(ctx context.Context, container containerd.Container, task containerd.Task, period time.Duration) (containerd.ExitStatus, bool, error) { statusC, err := task.Wait(ctx) if err != nil { return containerd.ExitStatus{}, false, err } select { case status := <-statusC: return status, true, nil case <-time.After(period): log.G(ctx).Warnf("killing task. the time period to monitor access log (%s) has timed out", period.String()) status, err := killTask(ctx, container, task, statusC) if err != nil { log.G(ctx).WithError(err).Warnf("failed to kill container") return containerd.ExitStatus{}, false, nil } return status, true, nil } } func killTask(ctx context.Context, container containerd.Container, task containerd.Task, statusC <-chan containerd.ExitStatus) (containerd.ExitStatus, error) { sig, err := containerd.GetStopSignal(ctx, container, syscall.SIGKILL) if err != nil { return containerd.ExitStatus{}, err } if err := task.Kill(ctx, sig, containerd.WithKillAll); err != nil && !errdefs.IsNotFound(err) { return containerd.ExitStatus{}, fmt.Errorf("forward SIGKILL: %w", err) } select { case status := <-statusC: return status, nil case <-time.After(5 * time.Second): return containerd.ExitStatus{}, fmt.Errorf("timeout") } } type lazyReadCloser struct { reader io.Reader closer func() closerMu sync.Mutex initCond *sync.Cond initialized int64 } func newLazyReadCloser(r io.Reader) *lazyReadCloser { rc := &lazyReadCloser{reader: r, initCond: sync.NewCond(&sync.Mutex{})} return rc } func (s *lazyReadCloser) registerCloser(closer func()) { s.closerMu.Lock() s.closer = closer s.closerMu.Unlock() atomic.AddInt64(&s.initialized, 1) s.initCond.Broadcast() } func (s *lazyReadCloser) Read(p []byte) (int, error) { if atomic.LoadInt64(&s.initialized) <= 0 { // wait until initialized s.initCond.L.Lock() if atomic.LoadInt64(&s.initialized) <= 0 { s.initCond.Wait() } s.initCond.L.Unlock() } n, err := s.reader.Read(p) if err == io.EOF { s.closerMu.Lock() s.closer() s.closerMu.Unlock() } return n, err } stargz-snapshotter-0.12.0/analyzer/fanotify/000077500000000000000000000000001426301527400211245ustar00rootroot00000000000000stargz-snapshotter-0.12.0/analyzer/fanotify/conn/000077500000000000000000000000001426301527400220615ustar00rootroot00000000000000stargz-snapshotter-0.12.0/analyzer/fanotify/conn/conn.go000066400000000000000000000075661426301527400233630ustar00rootroot00000000000000/* 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 conn import ( "bufio" "fmt" "io" "os" "strconv" "strings" "time" ) const ( mesStart = "start" mesStarted = "started" mesAck = "ack" mesFdPrefix = "fd:" ) // Client is the client to talk to the fanotifier. type Client struct { r io.Reader w io.Writer notification *bufio.Scanner servicePid int timeout time.Duration } // NewClient returns the client to talk to the fanotifier. func NewClient(r io.Reader, w io.Writer, servicePid int, timeout time.Duration) *Client { return &Client{r, w, bufio.NewScanner(r), servicePid, timeout} } // Start lets the notirier start to monitor the filesystem. func (nc *Client) Start() error { if err := writeMessage(nc.w, mesStart); err != nil { return err } if mes, err := scanWithTimeout(nc.notification, nc.timeout); err != nil { return err } else if mes != mesStarted { return fmt.Errorf("non-started message got from fanotifier service: %q", mes) } return nil } // GetPath returns notified path from the fanotifier. This blocks until new path is notified // from the service func (nc *Client) GetPath() (string, error) { if !nc.notification.Scan() { // NOTE: no timeout return "", io.EOF } mes := nc.notification.Text() if !strings.HasPrefix(mes, mesFdPrefix) { return "", fmt.Errorf("unexpected prefix for message %q", mes) } fd, err := strconv.ParseInt(mes[len(mesFdPrefix):], 10, 32) if err != nil { return "", fmt.Errorf("invalid fd %q: %w", mes, err) } path, err := os.Readlink(fmt.Sprintf("/proc/%d/fd/%d", nc.servicePid, fd)) if err != nil { return "", fmt.Errorf("failed to get link from fd %q: %w", mes, err) } return path, writeMessage(nc.w, mesAck) } // Service is the service end of the fanotifier. type Service struct { r io.Reader w io.Writer client *bufio.Scanner timeout time.Duration } // NewService is the service end of the fanotifier. func NewService(r io.Reader, w io.Writer, timeout time.Duration) *Service { return &Service{r, w, bufio.NewScanner(r), timeout} } // WaitStart waits "start" message from the client. func (ns *Service) WaitStart() error { if !ns.client.Scan() { // NOTE: no timeout return io.EOF } if mes := ns.client.Text(); mes != mesStart { return fmt.Errorf("non-start message got from fanotifier client: %q", mes) } return nil } // SendStarted tells client that the service has been started. func (ns *Service) SendStarted() error { return writeMessage(ns.w, mesStarted) } // SendFd send file descriptor to the client. func (ns *Service) SendFd(fd int) error { if err := writeMessage(ns.w, fmt.Sprintf("%s%d", mesFdPrefix, fd)); err != nil { return err } if mes, err := scanWithTimeout(ns.client, ns.timeout); err != nil { return err } else if mes != mesAck { return fmt.Errorf("non-ack message got from fanotifier client: %q", mes) } return nil } func scanWithTimeout(sc *bufio.Scanner, timeout time.Duration) (string, error) { notifyCh := make(chan string) errCh := make(chan error) go func() { if !sc.Scan() { errCh <- io.EOF } notifyCh <- sc.Text() }() select { case mes := <-notifyCh: return mes, nil case err := <-errCh: return "", err case <-time.After(timeout): return "", fmt.Errorf("timeout") } } func writeMessage(w io.Writer, mes string) error { _, err := io.WriteString(w, mes+"\n") return err } stargz-snapshotter-0.12.0/analyzer/fanotify/fanotify.go000066400000000000000000000047551426301527400233050ustar00rootroot00000000000000/* 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 fanotify import ( "fmt" "os/exec" "sync" "syscall" "time" "github.com/containerd/stargz-snapshotter/analyzer/fanotify/conn" "github.com/hashicorp/go-multierror" ) // Fanotifier monitors "/" mountpoint of a new mount namespace and notifies all // accessed files. type Fanotifier struct { cmd *exec.Cmd conn *conn.Client closeOnce sync.Once closeFunc func() error } func SpawnFanotifier(fanotifierBin string) (*Fanotifier, error) { // Run fanotifier that monitor "/" in a new mount namespace cmd := exec.Command(fanotifierBin, "fanotify", "/") cmd.SysProcAttr = &syscall.SysProcAttr{ Cloneflags: syscall.CLONE_NEWNS, } notifyR, err := cmd.StdoutPipe() if err != nil { return nil, err } notifyW, err := cmd.StdinPipe() if err != nil { return nil, err } if err := cmd.Start(); err != nil { return nil, err } return &Fanotifier{ cmd: cmd, // Connect to the spawned fanotifier over stdio conn: conn.NewClient(notifyR, notifyW, cmd.Process.Pid, 5*time.Second), closeFunc: func() (allErr error) { if err := notifyR.Close(); err != nil { allErr = multierror.Append(allErr, err) } if err := notifyW.Close(); err != nil { allErr = multierror.Append(allErr, err) } return }, }, nil } // Start starts fanotifier in the new mount namespace func (n *Fanotifier) Start() error { return n.conn.Start() } // GetPath gets path notified by the fanotifier. func (n *Fanotifier) GetPath() (string, error) { return n.conn.GetPath() } // MountNamespacePath returns the path to the mount namespace where // the fanotifier is monitoring. func (n *Fanotifier) MountNamespacePath() string { return fmt.Sprintf("/proc/%d/ns/mnt", n.cmd.Process.Pid) } // Close kills fanotifier process and closes the connection. func (n *Fanotifier) Close() (err error) { n.closeOnce.Do(func() { if err = n.cmd.Process.Kill(); err != nil { return } err = n.closeFunc() }) return } stargz-snapshotter-0.12.0/analyzer/fanotify/service/000077500000000000000000000000001426301527400225645ustar00rootroot00000000000000stargz-snapshotter-0.12.0/analyzer/fanotify/service/service.go000066400000000000000000000054671426301527400245670ustar00rootroot00000000000000/* 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 service import ( "bufio" "encoding/binary" "fmt" "io" "os" "time" "github.com/containerd/stargz-snapshotter/analyzer/fanotify/conn" "golang.org/x/sys/unix" ) // Serve starts fanotify at target directory and notifies all accessed files. // Passed io.Reader an io.Writer are used for communicating with the client. func Serve(target string, r io.Reader, w io.Writer) error { sConn := conn.NewService(r, w, 5*time.Second) fd, err := unix.FanotifyInit(unix.FAN_CLASS_NOTIF, unix.O_RDONLY) if err != nil { return fmt.Errorf("fanotify_init: %w", err) } // This blocks until the client tells us to start monitoring the target mountpoint. if err := sConn.WaitStart(); err != nil { return fmt.Errorf("waiting for start inst: %w", err) } // Start monitoring the target mountpoint. if err := unix.FanotifyMark(fd, unix.FAN_MARK_ADD|unix.FAN_MARK_MOUNT, unix.FAN_ACCESS|unix.FAN_OPEN, unix.AT_FDCWD, target, ); err != nil { return fmt.Errorf("fanotify_mark: %w", err) } // Notify "started" state to the client. if err := sConn.SendStarted(); err != nil { return fmt.Errorf("failed to send started message: %w", err) } nr := bufio.NewReader(os.NewFile(uintptr(fd), "")) for { event := &unix.FanotifyEventMetadata{} if err := binary.Read(nr, binary.LittleEndian, event); err != nil { if err == io.EOF { break } return fmt.Errorf("read fanotify fd: %w", err) } if event.Vers != unix.FANOTIFY_METADATA_VERSION { return fmt.Errorf("Fanotify version mismatch %d(got) != %d(want)", event.Vers, unix.FANOTIFY_METADATA_VERSION) } if event.Fd < 0 { // queue overflow // TODO: do we need something special? fmt.Fprintf(os.Stderr, "Warn: queue overflow") continue } // Notify file descriptor. // NOTE: There is no guarantee that we have /proc in this mount namespace // (the target container's rootfs is mounted on "/") so we send file // descriptor and let the client resolve the path of this file using /proc of // this process. if err := sConn.SendFd(int(event.Fd)); err != nil { return fmt.Errorf("failed to send fd %d to client: %w", fd, err) } if err := unix.Close(int(event.Fd)); err != nil { return fmt.Errorf("Close(fd): %w", err) } continue } return nil } stargz-snapshotter-0.12.0/analyzer/option.go000066400000000000000000000040611426301527400211450ustar00rootroot00000000000000/* 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 analyzer import ( "time" "github.com/containerd/containerd" "github.com/containerd/containerd/oci" ) type analyzerOpts struct { period time.Duration waitOnSignal bool snapshotter string specOpts SpecOpts terminal bool stdin bool } // Option is runtime configuration of analyzer container type Option func(opts *analyzerOpts) // SpecOpts returns runtime configuration based on the passed image and rootfs path. type SpecOpts func(image containerd.Image, rootfs string) (opts []oci.SpecOpts, done func() error, err error) // WithSpecOpts is the runtime configuration func WithSpecOpts(specOpts SpecOpts) Option { return func(opts *analyzerOpts) { opts.specOpts = specOpts } } // WithTerminal enable terminal for the container. This must be specified with WithStdin(). func WithTerminal() Option { return func(opts *analyzerOpts) { opts.terminal = true } } // WithStdin attaches stdin to the container func WithStdin() Option { return func(opts *analyzerOpts) { opts.stdin = true } } // WithPeriod is the period to run runtime func WithPeriod(period time.Duration) Option { return func(opts *analyzerOpts) { opts.period = period } } // WithWaitOnSignal disables timeout func WithWaitOnSignal() Option { return func(opts *analyzerOpts) { opts.waitOnSignal = true } } // WithSnapshotter is the snapshotter to use func WithSnapshotter(snapshotter string) Option { return func(opts *analyzerOpts) { opts.snapshotter = snapshotter } } stargz-snapshotter-0.12.0/analyzer/recorder/000077500000000000000000000000001426301527400211125ustar00rootroot00000000000000stargz-snapshotter-0.12.0/analyzer/recorder/recorder.go000066400000000000000000000127601426301527400232540ustar00rootroot00000000000000/* 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 recorder import ( "archive/tar" "context" "encoding/json" "fmt" "io" "path" "strings" "sync" "github.com/containerd/containerd/archive/compression" "github.com/containerd/containerd/content" "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/images" "github.com/containerd/containerd/images/converter/uncompress" "github.com/containerd/containerd/log" "github.com/containerd/containerd/platforms" "github.com/containerd/stargz-snapshotter/recorder" "github.com/containerd/stargz-snapshotter/util/containerdutil" "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/rs/xid" "golang.org/x/sync/errgroup" ) const ( whiteoutPrefix = ".wh." whiteoutOpaqueDir = whiteoutPrefix + whiteoutPrefix + ".opq" ) // ImageRecorder is a wrapper of recorder.Recroder. This holds the relationship // between files and layer index in the specified image. So the client can record // files without knowing about which layer this file belongs to. type ImageRecorder struct { r *recorder.Recorder index []map[string]struct{} manifestDigest digest.Digest recordW content.Writer recordWMu sync.Mutex } func NewImageRecorder(ctx context.Context, cs content.Store, img images.Image, platformMC platforms.MatchComparer) (*ImageRecorder, error) { manifestDesc, err := containerdutil.ManifestDesc(ctx, cs, img.Target, platformMC) if err != nil { return nil, err } p, err := content.ReadBlob(ctx, cs, manifestDesc) if err != nil { return nil, err } var manifest ocispec.Manifest if err := json.Unmarshal(p, &manifest); err != nil { return nil, err } return imageRecorderFromManifest(ctx, cs, manifestDesc, manifest) } func imageRecorderFromManifest(ctx context.Context, cs content.Store, manifestDesc ocispec.Descriptor, manifest ocispec.Manifest) (*ImageRecorder, error) { var eg errgroup.Group filesMap := make([]map[string]struct{}, len(manifest.Layers)) for i, desc := range manifest.Layers { i, desc := i, desc filesMap[i] = make(map[string]struct{}) // Create the index from the layer blob. // TODO: During optimization, we uncompress the blob several times (here and during // creating eStargz layer). We should unify this process for better optimization // performance. log.G(ctx).Infof("analyzing blob %q", desc.Digest) readerAt, err := cs.ReaderAt(ctx, desc) if err != nil { return nil, fmt.Errorf("failed to get reader blob %v: %w", desc.Digest, err) } defer readerAt.Close() r := io.Reader(io.NewSectionReader(readerAt, 0, desc.Size)) if !uncompress.IsUncompressedType(desc.MediaType) { r, err = compression.DecompressStream(r) if err != nil { return nil, fmt.Errorf("cannot decompress layer %v: %w", desc.Digest, err) } } eg.Go(func() error { tr := tar.NewReader(r) for { h, err := tr.Next() if err != nil { if err == io.EOF { break } return err } filesMap[i][cleanEntryName(h.Name)] = struct{}{} } return nil }) } if err := eg.Wait(); err != nil { return nil, err } recordW, err := content.OpenWriter(ctx, cs, content.WithRef(fmt.Sprintf("recorder-%v", xid.New().String()))) if err != nil { return nil, fmt.Errorf("failed to open writer for recorder: %w", err) } return &ImageRecorder{ r: recorder.New(recordW), index: filesMap, recordW: recordW, manifestDigest: manifestDesc.Digest, }, nil } func (r *ImageRecorder) Record(name string) error { if name == "" { return nil } r.recordWMu.Lock() defer r.recordWMu.Unlock() name = cleanEntryName(name) index := -1 for i := len(r.index) - 1; i >= 0; i-- { if _, ok := r.index[i][name]; ok { index = i break } // If this is deleted by a whiteout file or directory, return error. wh := cleanEntryName(path.Join(path.Dir("/"+name), whiteoutPrefix+path.Base("/"+name))) if _, ok := r.index[i][wh]; ok { return fmt.Errorf("%q is a deleted file", name) } whDir := cleanEntryName(path.Join(path.Dir("/"+name), whiteoutOpaqueDir)) if _, ok := r.index[i][whDir]; ok { return fmt.Errorf("Parent dir of %q is a deleted directory", name) } } if index < 0 { return fmt.Errorf("file %q not found in index", name) } return r.r.Record(&recorder.Entry{ Path: name, ManifestDigest: r.manifestDigest.String(), LayerIndex: &index, }) } func (r *ImageRecorder) Commit(ctx context.Context) (digest.Digest, error) { r.recordWMu.Lock() defer r.recordWMu.Unlock() if err := r.recordW.Commit(ctx, 0, ""); err != nil && !errdefs.IsAlreadyExists(err) { return "", err } return r.recordW.Digest(), nil } func (r *ImageRecorder) Close() error { r.recordWMu.Lock() defer r.recordWMu.Unlock() return r.recordW.Close() } func cleanEntryName(name string) string { // Use path.Clean to consistently deal with path separators across platforms. return strings.TrimPrefix(path.Clean("/"+name), "/") } stargz-snapshotter-0.12.0/analyzer/recorder/recorder_test.go000066400000000000000000000206051426301527400243100ustar00rootroot00000000000000/* 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 recorder import ( "compress/gzip" "context" "encoding/json" "fmt" "io" "os" "path" "testing" "github.com/containerd/containerd/content" "github.com/containerd/containerd/content/local" "github.com/containerd/containerd/errdefs" "github.com/containerd/stargz-snapshotter/recorder" "github.com/containerd/stargz-snapshotter/util/testutil" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/rs/xid" ) var allowedPrefix = [4]string{"", "./", "/", "../"} func TestNodeIndex(t *testing.T) { type recordEntry struct { name string index int } tests := []struct { name string in [][]testutil.TarEntry record []string want []recordEntry wantFail bool }{ { name: "single layer", in: [][]testutil.TarEntry{ { testutil.File("foo", "foo"), testutil.File("bar", "bar"), }, }, record: []string{"bar", "foo"}, want: []recordEntry{ {"bar", 0}, {"foo", 0}, }, }, { name: "overlay", in: [][]testutil.TarEntry{ { testutil.File("foo", "foo"), testutil.Dir("bar/"), testutil.Dir("bar/barbar/"), testutil.File("bar/barbar/bar2", "bar2"), testutil.File("bar/barbar/foo2", "foo2"), testutil.File("x", "x"), testutil.File("y", "y"), }, { testutil.File("foo", "foo2"), testutil.File("baz", "baz"), testutil.Dir("bar/"), testutil.Dir("bar/barbar/"), testutil.File("bar/barbar/foo2", "foo2-upper"), }, { testutil.File("foo", "foo3"), testutil.Dir("a/"), testutil.File("a/aaaaaa", "a"), testutil.File("y", "y"), }, }, record: []string{ "baz", "foo", "bar/barbar/foo2", "a/aaaaaa", "bar/barbar/bar2", "y"}, want: []recordEntry{ {"baz", 1}, {"foo", 2}, {"bar/barbar/foo2", 1}, {"a/aaaaaa", 2}, {"bar/barbar/bar2", 0}, {"y", 2}, }, }, { name: "various files", in: [][]testutil.TarEntry{ // All file path should be interpreted as the path relative to root. { testutil.File("foo", "foo"), testutil.Dir("bar/"), testutil.Symlink("bar/foosym", "foo"), testutil.Dir("bar/barbar/"), testutil.File("./bar/barbar/bar2", "bar2"), testutil.File("../bar/barbar/foo2", "foo2"), testutil.File("/x", "x"), testutil.Chardev("chardev", 1, 10), testutil.Fifo("bar/fifo1"), }, { testutil.File("./foo", "foo2"), testutil.File("baz", "baz"), testutil.Dir("bar/"), testutil.Dir("./bar/barbar/"), testutil.File("bar/barbar/bazlink", "baz"), testutil.File("../bar/barbar/foo2", "foo2-upper"), testutil.Chardev("chardev", 10, 100), testutil.Chardev("./blockdev", 100, 1), }, }, record: []string{ // All file path should be interpreted as the path relative to root. "./bar/foosym", "bar/barbar/bazlink", "/bar/barbar/foo2", "chardev", "../blockdev", "bar/fifo1", "./bar/barbar/bar2", }, want: []recordEntry{ {"bar/foosym", 0}, {"bar/barbar/bazlink", 1}, {"bar/barbar/foo2", 1}, {"chardev", 1}, {"blockdev", 1}, {"bar/fifo1", 0}, {"bar/barbar/bar2", 0}, }, }, { name: "whiteout file", in: [][]testutil.TarEntry{ { testutil.Dir("bar/"), testutil.File("bar/barfile", "bar"), }, { testutil.Dir("bar/"), testutil.File(path.Join("bar", whiteoutPrefix+"barfile"), "bar"), }, }, record: []string{"bar/barfile"}, want: []recordEntry{}, wantFail: true, }, { name: "whiteout dir", in: [][]testutil.TarEntry{ { testutil.Dir("bar/"), testutil.File("bar/barfile", "bar"), }, { testutil.Dir("bar/"), testutil.Dir(path.Join("bar", whiteoutOpaqueDir) + "/"), }, }, record: []string{"bar/barfile"}, want: []recordEntry{}, wantFail: true, }, { name: "whiteout dir", in: [][]testutil.TarEntry{ { testutil.Dir("bar/"), testutil.File("bar/barfile", "bar"), }, { testutil.Dir("bar/"), testutil.Dir(path.Join("bar", whiteoutOpaqueDir) + "/"), }, }, record: []string{"bar/barfile"}, want: []recordEntry{}, wantFail: true, }, } tempDir, err := os.MkdirTemp("", "test-recorder") if err != nil { t.Fatalf("failed to prepare content store dir: %v", err) } defer os.RemoveAll(tempDir) cs, err := local.NewStore(tempDir) if err != nil { t.Fatalf("failed to prepare content store: %v", err) } compressWrappers := map[string]func(r io.Reader) io.Reader{ ocispec.MediaTypeImageLayer: func(r io.Reader) io.Reader { return r }, // nop (uncompressed) ocispec.MediaTypeImageLayerGzip: gzipCompress, // gzip compression } ctx := context.Background() for _, tt := range tests { for _, prefix := range allowedPrefix { prefix := prefix for mediatype, cWrapper := range compressWrappers { t.Run(tt.name+":"+mediatype+",prefix="+prefix, func(t *testing.T) { var layers []ocispec.Descriptor for _, es := range tt.in { ref := fmt.Sprintf("recorder-test-%v", xid.New().String()) lw, err := content.OpenWriter(ctx, cs, content.WithRef(ref)) if err != nil { t.Errorf("failed to open writer: %v", err) return } tarR := testutil.BuildTar(es, testutil.WithPrefix(prefix)) if _, err := io.Copy(lw, cWrapper(tarR)); err != nil { t.Errorf("failed to copy layer: %v", err) return } if err := lw.Commit(ctx, 0, ""); err != nil && !errdefs.IsAlreadyExists(err) { t.Errorf("failed to commit layer: %v", err) return } info, err := cs.Info(ctx, lw.Digest()) if err != nil { t.Errorf("failed to get layer info: %v", err) return } // TODO: check compressed version as well layers = append(layers, ocispec.Descriptor{ Digest: info.Digest, Size: info.Size, MediaType: mediatype, }) } ir, err := imageRecorderFromManifest(ctx, cs, ocispec.Descriptor{}, ocispec.Manifest{Layers: layers}) if err != nil { t.Errorf("failed to get recorder: %v", err) return } defer ir.Close() fail := false for _, name := range tt.record { if err := ir.Record(name); err != nil { fail = true t.Logf("failed to record: %q: %v", name, err) break } } if tt.wantFail != fail { t.Errorf("unexpected record result: fail = %v; wantFail = %v", fail, tt.wantFail) return } if tt.wantFail { return // no need to check the record out } recordOut, err := ir.Commit(ctx) if err != nil { t.Errorf("failed to commit record: %v", err) return } ra, err := cs.ReaderAt(ctx, ocispec.Descriptor{Digest: recordOut}) if err != nil { t.Errorf("failed to get record out: %v", err) return } defer ra.Close() dec := json.NewDecoder(io.NewSectionReader(ra, 0, ra.Size())) i := 0 for dec.More() { var e recorder.Entry if err := dec.Decode(&e); err != nil { t.Errorf("failed to decord record: %v", err) return } if len(tt.want) <= i { t.Errorf("too many records: %d th but want %d", i, len(tt.want)) return } if e.Path != tt.want[i].name || *e.LayerIndex != tt.want[i].index { t.Errorf("unexpected entry { name = %q, index = %d }; want { name = %q, index = %d }", e.Path, *e.LayerIndex, tt.want[i].name, tt.want[i].index) return } i++ } if i < len(tt.want) { t.Errorf("record is too short: %d; but want %d", i, len(tt.want)) return } }) } } } } func gzipCompress(r io.Reader) io.Reader { pr, pw := io.Pipe() go func() { gw := gzip.NewWriter(pw) if _, err := io.Copy(gw, r); err != nil { gw.Close() pw.CloseWithError(err) return } gw.Close() pw.Close() }() return pr } stargz-snapshotter-0.12.0/cache/000077500000000000000000000000001426301527400165235ustar00rootroot00000000000000stargz-snapshotter-0.12.0/cache/cache.go000066400000000000000000000246531426301527400201270ustar00rootroot00000000000000/* 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 cache import ( "bytes" "fmt" "io" "os" "path/filepath" "sync" "github.com/containerd/stargz-snapshotter/util/cacheutil" "github.com/containerd/stargz-snapshotter/util/namedmutex" "github.com/hashicorp/go-multierror" ) const ( defaultMaxLRUCacheEntry = 10 defaultMaxCacheFds = 10 ) type DirectoryCacheConfig struct { // Number of entries of LRU cache (default: 10). // This won't be used when DataCache is specified. MaxLRUCacheEntry int // Number of file descriptors to cache (default: 10). // This won't be used when FdCache is specified. MaxCacheFds int // On Add, wait until the data is fully written to the cache directory. SyncAdd bool // DataCache is an on-memory cache of the data. // OnEvicted will be overridden and replaced for internal use. DataCache *cacheutil.LRUCache // FdCache is a cache for opened file descriptors. // OnEvicted will be overridden and replaced for internal use. FdCache *cacheutil.LRUCache // BufPool will be used for pooling bytes.Buffer. BufPool *sync.Pool // Direct forcefully enables direct mode for all operation in cache. // Thus operation won't use on-memory caches. Direct bool } // TODO: contents validation. // BlobCache represents a cache for bytes data type BlobCache interface { // Add returns a writer to add contents to cache Add(key string, opts ...Option) (Writer, error) // Get returns a reader to read the specified contents // from cache Get(key string, opts ...Option) (Reader, error) // Close closes the cache Close() error } // Reader provides the data cached. type Reader interface { io.ReaderAt Close() error } // Writer enables the client to cache byte data. Commit() must be // called after data is fully written to Write(). To abort the written // data, Abort() must be called. type Writer interface { io.WriteCloser Commit() error Abort() error } type cacheOpt struct { direct bool } type Option func(o *cacheOpt) *cacheOpt // Direct option lets FetchAt and Add methods not to use on-memory caches. When // you know that the targeting value won't be used immediately, you can prevent // the limited space of on-memory caches from being polluted by these unimportant // values. func Direct() Option { return func(o *cacheOpt) *cacheOpt { o.direct = true return o } } func NewDirectoryCache(directory string, config DirectoryCacheConfig) (BlobCache, error) { if !filepath.IsAbs(directory) { return nil, fmt.Errorf("dir cache path must be an absolute path; got %q", directory) } bufPool := config.BufPool if bufPool == nil { bufPool = &sync.Pool{ New: func() interface{} { return new(bytes.Buffer) }, } } dataCache := config.DataCache if dataCache == nil { maxEntry := config.MaxLRUCacheEntry if maxEntry == 0 { maxEntry = defaultMaxLRUCacheEntry } dataCache = cacheutil.NewLRUCache(maxEntry) dataCache.OnEvicted = func(key string, value interface{}) { value.(*bytes.Buffer).Reset() bufPool.Put(value) } } fdCache := config.FdCache if fdCache == nil { maxEntry := config.MaxCacheFds if maxEntry == 0 { maxEntry = defaultMaxCacheFds } fdCache = cacheutil.NewLRUCache(maxEntry) fdCache.OnEvicted = func(key string, value interface{}) { value.(*os.File).Close() } } if err := os.MkdirAll(directory, 0700); err != nil { return nil, err } wipdir := filepath.Join(directory, "wip") if err := os.MkdirAll(wipdir, 0700); err != nil { return nil, err } dc := &directoryCache{ cache: dataCache, fileCache: fdCache, wipLock: new(namedmutex.NamedMutex), directory: directory, wipDirectory: wipdir, bufPool: bufPool, direct: config.Direct, } dc.syncAdd = config.SyncAdd return dc, nil } // directoryCache is a cache implementation which backend is a directory. type directoryCache struct { cache *cacheutil.LRUCache fileCache *cacheutil.LRUCache wipDirectory string directory string wipLock *namedmutex.NamedMutex bufPool *sync.Pool syncAdd bool direct bool closed bool closedMu sync.Mutex } func (dc *directoryCache) Get(key string, opts ...Option) (Reader, error) { if dc.isClosed() { return nil, fmt.Errorf("cache is already closed") } opt := &cacheOpt{} for _, o := range opts { opt = o(opt) } if !dc.direct && !opt.direct { // Get data from memory if b, done, ok := dc.cache.Get(key); ok { return &reader{ ReaderAt: bytes.NewReader(b.(*bytes.Buffer).Bytes()), closeFunc: func() error { done() return nil }, }, nil } // Get data from disk. If the file is already opened, use it. if f, done, ok := dc.fileCache.Get(key); ok { return &reader{ ReaderAt: f.(*os.File), closeFunc: func() error { done() // file will be closed when it's evicted from the cache return nil }, }, nil } } // Open the cache file and read the target region // TODO: If the target cache is write-in-progress, should we wait for the completion // or simply report the cache miss? file, err := os.Open(dc.cachePath(key)) if err != nil { return nil, fmt.Errorf("failed to open blob file for %q: %w", key, err) } // If "direct" option is specified, do not cache the file on memory. // This option is useful for preventing memory cache from being polluted by data // that won't be accessed immediately. if dc.direct || opt.direct { return &reader{ ReaderAt: file, closeFunc: func() error { return file.Close() }, }, nil } // TODO: should we cache the entire file data on memory? // but making I/O (possibly huge) on every fetching // might be costly. return &reader{ ReaderAt: file, closeFunc: func() error { _, done, added := dc.fileCache.Add(key, file) defer done() // Release it immediately. Cleaned up on eviction. if !added { return file.Close() // file already exists in the cache. close it. } return nil }, }, nil } func (dc *directoryCache) Add(key string, opts ...Option) (Writer, error) { if dc.isClosed() { return nil, fmt.Errorf("cache is already closed") } opt := &cacheOpt{} for _, o := range opts { opt = o(opt) } wip, err := dc.wipFile(key) if err != nil { return nil, err } w := &writer{ WriteCloser: wip, commitFunc: func() error { if dc.isClosed() { return fmt.Errorf("cache is already closed") } // Commit the cache contents c := dc.cachePath(key) if err := os.MkdirAll(filepath.Dir(c), os.ModePerm); err != nil { var allErr error if err := os.Remove(wip.Name()); err != nil { allErr = multierror.Append(allErr, err) } return multierror.Append(allErr, fmt.Errorf("failed to create cache directory %q: %w", c, err)) } return os.Rename(wip.Name(), c) }, abortFunc: func() error { return os.Remove(wip.Name()) }, } // If "direct" option is specified, do not cache the passed data on memory. // This option is useful for preventing memory cache from being polluted by data // that won't be accessed immediately. if dc.direct || opt.direct { return w, nil } b := dc.bufPool.Get().(*bytes.Buffer) memW := &writer{ WriteCloser: nopWriteCloser(io.Writer(b)), commitFunc: func() error { if dc.isClosed() { w.Close() return fmt.Errorf("cache is already closed") } cached, done, added := dc.cache.Add(key, b) if !added { dc.putBuffer(b) // already exists in the cache. abort it. } commit := func() error { defer done() defer w.Close() n, err := w.Write(cached.(*bytes.Buffer).Bytes()) if err != nil || n != cached.(*bytes.Buffer).Len() { w.Abort() return err } return w.Commit() } if dc.syncAdd { return commit() } go func() { if err := commit(); err != nil { fmt.Println("failed to commit to file:", err) } }() return nil }, abortFunc: func() error { defer w.Close() defer w.Abort() dc.putBuffer(b) // abort it. return nil }, } return memW, nil } func (dc *directoryCache) putBuffer(b *bytes.Buffer) { b.Reset() dc.bufPool.Put(b) } func (dc *directoryCache) Close() error { dc.closedMu.Lock() defer dc.closedMu.Unlock() if dc.closed { return nil } dc.closed = true return os.RemoveAll(dc.directory) } func (dc *directoryCache) isClosed() bool { dc.closedMu.Lock() closed := dc.closed dc.closedMu.Unlock() return closed } func (dc *directoryCache) cachePath(key string) string { return filepath.Join(dc.directory, key[:2], key) } func (dc *directoryCache) wipFile(key string) (*os.File, error) { return os.CreateTemp(dc.wipDirectory, key+"-*") } func NewMemoryCache() BlobCache { return &MemoryCache{ Membuf: map[string]*bytes.Buffer{}, } } // MemoryCache is a cache implementation which backend is a memory. type MemoryCache struct { Membuf map[string]*bytes.Buffer mu sync.Mutex } func (mc *MemoryCache) Get(key string, opts ...Option) (Reader, error) { mc.mu.Lock() defer mc.mu.Unlock() b, ok := mc.Membuf[key] if !ok { return nil, fmt.Errorf("Missed cache: %q", key) } return &reader{bytes.NewReader(b.Bytes()), func() error { return nil }}, nil } func (mc *MemoryCache) Add(key string, opts ...Option) (Writer, error) { b := new(bytes.Buffer) return &writer{ WriteCloser: nopWriteCloser(io.Writer(b)), commitFunc: func() error { mc.mu.Lock() defer mc.mu.Unlock() mc.Membuf[key] = b return nil }, abortFunc: func() error { return nil }, }, nil } func (mc *MemoryCache) Close() error { return nil } type reader struct { io.ReaderAt closeFunc func() error } func (r *reader) Close() error { return r.closeFunc() } type writer struct { io.WriteCloser commitFunc func() error abortFunc func() error } func (w *writer) Commit() error { return w.commitFunc() } func (w *writer) Abort() error { return w.abortFunc() } type writeCloser struct { io.Writer closeFunc func() error } func (w *writeCloser) Close() error { return w.closeFunc() } func nopWriteCloser(w io.Writer) io.WriteCloser { return &writeCloser{w, func() error { return nil }} } stargz-snapshotter-0.12.0/cache/cache_test.go000066400000000000000000000106751426301527400211650ustar00rootroot00000000000000/* 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. */ /* Copyright 2019 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the NOTICE.md file. */ package cache import ( "crypto/sha256" "fmt" "io" "os" "testing" ) const ( sampleData = "0123456789" ) func TestDirectoryCache(t *testing.T) { // with enough memory cache newCache := func() (BlobCache, cleanFunc) { tmp, err := os.MkdirTemp("", "testcache") if err != nil { t.Fatalf("failed to make tempdir: %v", err) } c, err := NewDirectoryCache(tmp, DirectoryCacheConfig{ MaxLRUCacheEntry: 10, SyncAdd: true, }) if err != nil { t.Fatalf("failed to make cache: %v", err) } return c, func() { os.RemoveAll(tmp) } } testCache(t, "dir-with-enough-mem", newCache) // with smaller memory cache newCache = func() (BlobCache, cleanFunc) { tmp, err := os.MkdirTemp("", "testcache") if err != nil { t.Fatalf("failed to make tempdir: %v", err) } c, err := NewDirectoryCache(tmp, DirectoryCacheConfig{ MaxLRUCacheEntry: 1, SyncAdd: true, }) if err != nil { t.Fatalf("failed to make cache: %v", err) } return c, func() { os.RemoveAll(tmp) } } testCache(t, "dir-with-small-mem", newCache) } func TestMemoryCache(t *testing.T) { testCache(t, "memory", func() (BlobCache, cleanFunc) { return NewMemoryCache(), func() {} }) } type cleanFunc func() func testCache(t *testing.T, name string, newCache func() (BlobCache, cleanFunc)) { tests := []struct { name string blobs []string checks []check }{ { name: "empty_data", blobs: []string{ "", }, checks: []check{ hit(""), miss(sampleData), }, }, { name: "data", blobs: []string{ sampleData, }, checks: []check{ hit(sampleData), miss("dummy"), }, }, { name: "manydata", blobs: []string{ sampleData, "test", }, checks: []check{ hit(sampleData), miss("dummy"), }, }, { name: "dup_data", blobs: []string{ sampleData, sampleData, }, checks: []check{ hit(sampleData), }, }, } for _, tt := range tests { t.Run(fmt.Sprintf("%s-%s", name, tt.name), func(t *testing.T) { c, clean := newCache() defer clean() for _, blob := range tt.blobs { d := digestFor(blob) w, err := c.Add(d) if err != nil { t.Fatalf("failed to add %v: %v", d, err) } if n, err := w.Write([]byte(blob)); err != nil || n != len(blob) { w.Close() t.Fatalf("failed to write %v (len:%d): %v", d, len(blob), err) } if err := w.Commit(); err != nil { w.Close() t.Fatalf("failed to commit %v (len:%d): %v", d, len(blob), err) } w.Close() } for _, check := range tt.checks { check(t, c) } }) } } type check func(*testing.T, BlobCache) func digestFor(content string) string { sum := sha256.Sum256([]byte(content)) return fmt.Sprintf("%x", sum) } func hit(sample string) check { return func(t *testing.T, c BlobCache) { // test whole blob key := digestFor(sample) testChunk(t, c, key, 0, sample) // test a chunk chunk := len(sample) / 3 testChunk(t, c, key, int64(chunk), sample[chunk:2*chunk]) } } func testChunk(t *testing.T, c BlobCache, key string, offset int64, sample string) { p := make([]byte, len(sample)) r, err := c.Get(key) if err != nil { t.Errorf("missed %v", key) return } if n, err := r.ReadAt(p, offset); err != nil && err != io.EOF { t.Errorf("failed to fetch blob %q: %v", key, err) return } else if n != len(sample) { t.Errorf("fetched size %d; want %d", len(p), len(sample)) return } if digestFor(sample) != digestFor(string(p)) { t.Errorf("fetched %q; want %q", string(p), sample) } } func miss(sample string) check { return func(t *testing.T, c BlobCache) { d := digestFor(sample) if _, err := c.Get(d); err == nil { t.Errorf("hit blob %q but must be missed: %v", d, err) return } } } stargz-snapshotter-0.12.0/cmd/000077500000000000000000000000001426301527400162235ustar00rootroot00000000000000stargz-snapshotter-0.12.0/cmd/containerd-stargz-grpc/000077500000000000000000000000001426301527400226125ustar00rootroot00000000000000stargz-snapshotter-0.12.0/cmd/containerd-stargz-grpc/db/000077500000000000000000000000001426301527400231775ustar00rootroot00000000000000stargz-snapshotter-0.12.0/cmd/containerd-stargz-grpc/db/db.go000066400000000000000000000324671426301527400241270ustar00rootroot00000000000000/* 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 db import ( "encoding/binary" "fmt" "os" "sort" "github.com/containerd/stargz-snapshotter/metadata" bolt "go.etcd.io/bbolt" ) // Metadata package stores filesystem metadata in the following schema. // // - filesystems // - *filesystem id* : bucket for each filesystem keyed by a unique string. // - nodes // - *node id* : bucket for each node keyed by a uniqe uint64. // - size : : size of the regular node. // - modtime : : modification time of the node. // - linkName : : link target of symlink // - mode : : permission and mode bits (os.FileMode). // - uid : : uid of the owner. // - gid : : gid of the owner. // - devMajor : : the major device number for device // - devMinor : : the minor device number for device // - xattrKey : : key of the first extended attribute. // - xattrValue : : value of the first extended attribute // - xattrsExtra : 2nd and the following extended attribute. // - *key* : : map of key to value string // - numLink : : the number of links pointing to this node. // - metadata // - *node id* : bucket for each node keyed by a uniqe uint64. // - childName : : base name of the first child // - childID : : id of the first child // - childrenExtra : 2nd and following child nodes of directory. // - *basename* : : map of basename string to the child node id // - chunk : : information of the first chunkn // - chunksExtra : 2nd and following chunks (this is rarely used so we can avoid the cost of creating the bucket) // - *offset* : : keyed by gzip header offset (varint) in the estargz file to the chunk. // - nextOffset : : the offset of the next node with a non-zero offset. var ( bucketKeyFilesystems = []byte("filesystems") bucketKeyNodes = []byte("nodes") bucketKeySize = []byte("size") bucketKeyModTime = []byte("modtime") bucketKeyLinkName = []byte("linkName") bucketKeyMode = []byte("mode") bucketKeyUID = []byte("uid") bucketKeyGID = []byte("gid") bucketKeyDevMajor = []byte("devMajor") bucketKeyDevMinor = []byte("devMinor") bucketKeyXattrKey = []byte("xattrKey") bucketKeyXattrValue = []byte("xattrValue") bucketKeyXattrsExtra = []byte("xattrsExtra") bucketKeyNumLink = []byte("numLink") bucketKeyMetadata = []byte("metadata") bucketKeyChildName = []byte("childName") bucketKeyChildID = []byte("childID") bucketKeyChildrenExtra = []byte("childrenExtra") bucketKeyChunk = []byte("chunk") bucketKeyChunksExtra = []byte("chunksExtra") bucketKeyNextOffset = []byte("nextOffset") ) type childEntry struct { base string id uint32 } type chunkEntry struct { offset int64 chunkOffset int64 chunkSize int64 chunkDigest string } type metadataEntry struct { children map[string]childEntry chunks []chunkEntry nextOffset int64 } func getNodes(tx *bolt.Tx, fsID string) (*bolt.Bucket, error) { filesystems := tx.Bucket(bucketKeyFilesystems) if filesystems == nil { return nil, fmt.Errorf("fs %q not found: no fs is registered", fsID) } lbkt := filesystems.Bucket([]byte(fsID)) if lbkt == nil { return nil, fmt.Errorf("fs bucket for %q not found", fsID) } nodes := lbkt.Bucket(bucketKeyNodes) if nodes == nil { return nil, fmt.Errorf("nodes bucket for %q not found", fsID) } return nodes, nil } func getMetadata(tx *bolt.Tx, fsID string) (*bolt.Bucket, error) { filesystems := tx.Bucket(bucketKeyFilesystems) if filesystems == nil { return nil, fmt.Errorf("fs %q not found: no fs is registered", fsID) } lbkt := filesystems.Bucket([]byte(fsID)) if lbkt == nil { return nil, fmt.Errorf("fs bucket for %q not found", fsID) } md := lbkt.Bucket(bucketKeyMetadata) if md == nil { return nil, fmt.Errorf("metadata bucket for fs %q not found", fsID) } return md, nil } func getNodeBucketByID(nodes *bolt.Bucket, id uint32) (*bolt.Bucket, error) { b := nodes.Bucket(encodeID(id)) if b == nil { return nil, fmt.Errorf("node bucket for %d not found", id) } return b, nil } func getMetadataBucketByID(md *bolt.Bucket, id uint32) (*bolt.Bucket, error) { b := md.Bucket(encodeID(id)) if b == nil { return nil, fmt.Errorf("metadata bucket for %d not found", id) } return b, nil } func writeAttr(b *bolt.Bucket, attr *metadata.Attr) error { for _, v := range []struct { key []byte val int64 }{ {bucketKeySize, attr.Size}, {bucketKeyUID, int64(attr.UID)}, {bucketKeyGID, int64(attr.GID)}, {bucketKeyDevMajor, int64(attr.DevMajor)}, {bucketKeyDevMinor, int64(attr.DevMinor)}, {bucketKeyNumLink, int64(attr.NumLink - 1)}, // numLink = 0 means num link = 1 in DB } { if v.val != 0 { val, err := encodeInt(v.val) if err != nil { return err } if err := b.Put(v.key, val); err != nil { return err } } } if !attr.ModTime.IsZero() { te, err := attr.ModTime.GobEncode() if err != nil { return err } if err := b.Put(bucketKeyModTime, te); err != nil { return err } } if len(attr.LinkName) > 0 { if err := b.Put(bucketKeyLinkName, []byte(attr.LinkName)); err != nil { return err } } if attr.Mode != 0 { val, err := encodeUint(uint64(attr.Mode)) if err != nil { return err } if err := b.Put(bucketKeyMode, val); err != nil { return err } } if len(attr.Xattrs) > 0 { var firstK string var firstV []byte for k, v := range attr.Xattrs { firstK, firstV = k, v break } if err := b.Put(bucketKeyXattrKey, []byte(firstK)); err != nil { return err } if err := b.Put(bucketKeyXattrValue, firstV); err != nil { return err } var xbkt *bolt.Bucket for k, v := range attr.Xattrs { if k == firstK || len(v) == 0 { continue } if xbkt == nil { if xbkt := b.Bucket(bucketKeyXattrsExtra); xbkt != nil { // Reset if err := b.DeleteBucket(bucketKeyXattrsExtra); err != nil { return err } } var err error xbkt, err = b.CreateBucket(bucketKeyXattrsExtra) if err != nil { return err } } if err := xbkt.Put([]byte(k), v); err != nil { return fmt.Errorf("failed to set xattr %q=%q: %w", k, string(v), err) } } } return nil } func readAttr(b *bolt.Bucket, attr *metadata.Attr) error { return b.ForEach(func(k, v []byte) error { switch string(k) { case string(bucketKeySize): attr.Size, _ = binary.Varint(v) case string(bucketKeyModTime): if err := (&attr.ModTime).GobDecode(v); err != nil { return err } case string(bucketKeyLinkName): attr.LinkName = string(v) case string(bucketKeyMode): mode, _ := binary.Uvarint(v) attr.Mode = os.FileMode(uint32(mode)) case string(bucketKeyUID): i, _ := binary.Varint(v) attr.UID = int(i) case string(bucketKeyGID): i, _ := binary.Varint(v) attr.GID = int(i) case string(bucketKeyDevMajor): i, _ := binary.Varint(v) attr.DevMajor = int(i) case string(bucketKeyDevMinor): i, _ := binary.Varint(v) attr.DevMinor = int(i) case string(bucketKeyNumLink): i, _ := binary.Varint(v) attr.NumLink = int(i) + 1 // numLink = 0 means num link = 1 in DB case string(bucketKeyXattrKey): if attr.Xattrs == nil { attr.Xattrs = make(map[string][]byte) } attr.Xattrs[string(v)] = b.Get(bucketKeyXattrValue) case string(bucketKeyXattrsExtra): if err := b.Bucket(k).ForEach(func(k, v []byte) error { if attr.Xattrs == nil { attr.Xattrs = make(map[string][]byte) } attr.Xattrs[string(k)] = v return nil }); err != nil { return err } } return nil }) } func readNumLink(b *bolt.Bucket) int { // numLink = 0 means num link = 1 in BD numLink, _ := binary.Varint(b.Get(bucketKeyNumLink)) return int(numLink) + 1 } func readChunks(b *bolt.Bucket, size int64) (chunks []chunkEntry, err error) { if chunk := b.Get(bucketKeyChunk); len(chunk) > 0 { e, err := decodeChunkEntry(chunk) if err != nil { return nil, err } chunks = append(chunks, e) } if chbkt := b.Bucket(bucketKeyChunksExtra); chbkt != nil { if err := chbkt.ForEach(func(_, v []byte) error { e, err := decodeChunkEntry(v) if err != nil { return err } chunks = append(chunks, e) return nil }); err != nil { return nil, err } sort.Slice(chunks, func(i, j int) bool { return chunks[i].chunkOffset < chunks[j].chunkOffset }) } nextOffset := size for i := len(chunks) - 1; i >= 0; i-- { chunks[i].chunkSize = nextOffset - chunks[i].chunkOffset nextOffset = chunks[i].chunkOffset } return } func readChild(md *bolt.Bucket, base string) (uint32, error) { if base == string(md.Get(bucketKeyChildName)) { return decodeID(md.Get(bucketKeyChildID)), nil } cbkt := md.Bucket(bucketKeyChildrenExtra) if cbkt == nil { return 0, fmt.Errorf("extra children not found") } eid := cbkt.Get([]byte(base)) if len(eid) == 0 { return 0, fmt.Errorf("children %q not found", base) } return decodeID(eid), nil } func writeMetadataEntry(md *bolt.Bucket, m *metadataEntry) error { if len(m.children) > 0 { var firstChildName string var firstChild childEntry for name, child := range m.children { firstChildName, firstChild = name, child break } if err := md.Put(bucketKeyChildID, encodeID(firstChild.id)); err != nil { return fmt.Errorf("failed to put id of first child %q: %w", firstChildName, err) } if err := md.Put(bucketKeyChildName, []byte(firstChildName)); err != nil { return fmt.Errorf("failed to put name first child %q: %w", firstChildName, err) } if len(m.children) > 1 { var cbkt *bolt.Bucket for k, c := range m.children { if k == firstChildName { continue } if cbkt == nil { if cbkt := md.Bucket(bucketKeyChildrenExtra); cbkt != nil { // Reset if err := md.DeleteBucket(bucketKeyChildrenExtra); err != nil { return err } } var err error cbkt, err = md.CreateBucket(bucketKeyChildrenExtra) if err != nil { return err } } if err := cbkt.Put([]byte(c.base), encodeID(c.id)); err != nil { return fmt.Errorf("failed to add child ID %q: %w", c.id, err) } } } } if len(m.chunks) > 0 { first := m.chunks[0] if err := md.Put(bucketKeyChunk, encodeChunkEntry(first)); err != nil { return fmt.Errorf("failed to set chunk %q: %w", first.offset, err) } var cbkt *bolt.Bucket for _, e := range m.chunks[1:] { if cbkt == nil { if cbkt := md.Bucket(bucketKeyChunksExtra); cbkt != nil { // Reset if err := md.DeleteBucket(bucketKeyChunksExtra); err != nil { return err } } var err error cbkt, err = md.CreateBucket(bucketKeyChunksExtra) if err != nil { return err } } eoff, err := encodeInt(e.offset) if err != nil { return err } if err := cbkt.Put(eoff, encodeChunkEntry(e)); err != nil { return err } } } if m.nextOffset > 0 { if err := putInt(md, bucketKeyNextOffset, m.nextOffset); err != nil { return fmt.Errorf("failed to set next offset value %d: %w", m.nextOffset, err) } } return nil } func encodeChunkEntry(e chunkEntry) []byte { eb := make([]byte, 16+len([]byte(e.chunkDigest))) binary.BigEndian.PutUint64(eb[0:8], uint64(e.chunkOffset)) binary.BigEndian.PutUint64(eb[8:16], uint64(e.offset)) copy(eb[16:], []byte(e.chunkDigest)) return eb } func decodeChunkEntry(d []byte) (e chunkEntry, _ error) { if len(d) < 16 { return e, fmt.Errorf("mulformed chunk entry (len:%d)", len(d)) } e.chunkOffset = int64(binary.BigEndian.Uint64(d[0:8])) e.offset = int64(binary.BigEndian.Uint64(d[8:16])) if len(d) > 16 { e.chunkDigest = string(d[16:]) } return e, nil } func putInt(b *bolt.Bucket, k []byte, v int64) error { i, err := encodeInt(v) if err != nil { return err } return b.Put(k, i) } func encodeID(id uint32) []byte { b := [4]byte{} binary.BigEndian.PutUint32(b[:], id) return b[:] } func decodeID(b []byte) uint32 { return binary.BigEndian.Uint32(b) } func encodeInt(i int64) ([]byte, error) { var ( buf [binary.MaxVarintLen64]byte iEncoded = buf[:] ) iEncoded = iEncoded[:binary.PutVarint(iEncoded, i)] if len(iEncoded) == 0 { return nil, fmt.Errorf("failed encoding integer = %v", i) } return iEncoded, nil } func encodeUint(i uint64) ([]byte, error) { var ( buf [binary.MaxVarintLen64]byte iEncoded = buf[:] ) iEncoded = iEncoded[:binary.PutUvarint(iEncoded, i)] if len(iEncoded) == 0 { return nil, fmt.Errorf("failed encoding integer = %v", i) } return iEncoded, nil } stargz-snapshotter-0.12.0/cmd/containerd-stargz-grpc/db/reader.go000066400000000000000000000637441426301527400250060ustar00rootroot00000000000000/* 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 db import ( "bufio" "bytes" "encoding/binary" "errors" "fmt" "io" "math" "os" "path" "path/filepath" "sort" "strings" "sync" "time" "github.com/containerd/stargz-snapshotter/estargz" "github.com/containerd/stargz-snapshotter/metadata" "github.com/goccy/go-json" "github.com/hashicorp/go-multierror" digest "github.com/opencontainers/go-digest" "github.com/rs/xid" bolt "go.etcd.io/bbolt" "golang.org/x/sync/errgroup" ) // reader stores filesystem metadata parsed from eStargz to metadata DB // and provides methods to read them. type reader struct { db *bolt.DB fsID string rootID uint32 tocDigest digest.Digest sr *io.SectionReader curID uint32 curIDMu sync.Mutex initG *errgroup.Group decompressor metadata.Decompressor } func (r *reader) nextID() (uint32, error) { r.curIDMu.Lock() defer r.curIDMu.Unlock() if r.curID == math.MaxUint32 { return 0, fmt.Errorf("sequence id too large") } r.curID++ return r.curID, nil } // NewReader parses an eStargz and stores filesystem metadata to // the provided DB. func NewReader(db *bolt.DB, sr *io.SectionReader, opts ...metadata.Option) (metadata.Reader, error) { var rOpts metadata.Options for _, o := range opts { if err := o(&rOpts); err != nil { return nil, fmt.Errorf("failed to apply option: %w", err) } } gzipCompressors := []metadata.Decompressor{new(estargz.GzipDecompressor), new(estargz.LegacyGzipDecompressor)} decompressors := append(gzipCompressors, rOpts.Decompressors...) // Determine the size to fetch. Try to fetch as many bytes as possible. fetchSize := maxFooterSize(sr.Size(), decompressors...) if maybeTocOffset := rOpts.TOCOffset; maybeTocOffset > fetchSize { if maybeTocOffset > sr.Size() { return nil, fmt.Errorf("blob size %d is smaller than the toc offset", sr.Size()) } fetchSize = sr.Size() - maybeTocOffset } start := time.Now() // before getting layer footer footer := make([]byte, fetchSize) if _, err := sr.ReadAt(footer, sr.Size()-fetchSize); err != nil { return nil, fmt.Errorf("error reading footer: %v", err) } if rOpts.Telemetry != nil && rOpts.Telemetry.GetFooterLatency != nil { rOpts.Telemetry.GetFooterLatency(start) } var allErr error var tocR io.ReadCloser var decompressor metadata.Decompressor for _, d := range decompressors { fSize := d.FooterSize() fOffset := positive(int64(len(footer)) - fSize) maybeTocBytes := footer[:fOffset] _, tocOffset, tocSize, err := d.ParseFooter(footer[fOffset:]) if err != nil { allErr = multierror.Append(allErr, err) continue } if tocSize <= 0 { tocSize = sr.Size() - tocOffset - fSize } if tocSize < int64(len(maybeTocBytes)) { maybeTocBytes = maybeTocBytes[:tocSize] } tocR, err = decompressTOC(d, sr, tocOffset, tocSize, maybeTocBytes, rOpts) if err != nil { allErr = multierror.Append(allErr, err) continue } decompressor = d break } if tocR == 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: %w", allErr) } defer tocR.Close() r := &reader{sr: sr, db: db, initG: new(errgroup.Group), decompressor: decompressor} if err := r.init(tocR, rOpts); err != nil { return nil, fmt.Errorf("failed to initialize matadata: %w", err) } return r, nil } func maxFooterSize(blobSize int64, decompressors ...metadata.Decompressor) (res int64) { for _, d := range decompressors { if s := d.FooterSize(); res < s && s <= blobSize { res = s } } return } func decompressTOC(d metadata.Decompressor, sr *io.SectionReader, tocOff, tocSize int64, tocBytes []byte, opts metadata.Options) (io.ReadCloser, error) { if len(tocBytes) > 0 { start := time.Now() // before getting TOC tocR, err := d.DecompressTOC(bytes.NewReader(tocBytes)) if err == nil { if opts.Telemetry != nil && opts.Telemetry.GetTocLatency != nil { opts.Telemetry.GetTocLatency(start) } return tocR, nil } } start := time.Now() // before getting TOC tocBytes = make([]byte, tocSize) if _, err := sr.ReadAt(tocBytes, tocOff); err != nil { return nil, fmt.Errorf("error reading %d byte TOC targz: %v", len(tocBytes), err) } r, err := d.DecompressTOC(bytes.NewReader(tocBytes)) if err != nil { return nil, err } if opts.Telemetry != nil && opts.Telemetry.GetTocLatency != nil { opts.Telemetry.GetTocLatency(start) } return r, nil } // RootID returns ID of the root node. func (r *reader) RootID() uint32 { return r.rootID } func (r *reader) TOCDigest() digest.Digest { return r.tocDigest } // Clone returns a new reader identical to the current reader // but uses the provided section reader for retrieving file paylaods. func (r *reader) Clone(sr *io.SectionReader) (metadata.Reader, error) { if err := r.waitInit(); err != nil { return nil, err } return &reader{ db: r.db, fsID: r.fsID, rootID: r.rootID, sr: sr, initG: new(errgroup.Group), decompressor: r.decompressor, }, nil } func (r *reader) init(decompressedR io.Reader, rOpts metadata.Options) (retErr error) { start := time.Now() // before parsing TOC JSON // Initialize root node var ok bool for i := 0; i < 100; i++ { fsID := xid.New().String() if err := r.initRootNode(fsID); err != nil { if errors.Is(err, bolt.ErrBucketExists) { continue // try with another id } return fmt.Errorf("failed to initialize root node %q: %w", fsID, err) } ok = true break } if !ok { return fmt.Errorf("failed to get a unique id for metadata reader") } f, err := os.CreateTemp("", "") if err != nil { return err } closeFunc := func() (closeErr error) { name := f.Name() if err := f.Close(); err != nil { closeErr = multierror.Append(closeErr, err) } if err := os.Remove(name); err != nil { closeErr = multierror.Append(closeErr, err) } return } defer func() { if retErr != nil { if err := closeFunc(); err != nil { retErr = multierror.Append(retErr, err) } } }() if err := f.Chmod(0600); err != nil { return err } dgstr := digest.Canonical.Digester() if _, err := io.Copy(f, io.TeeReader(decompressedR, dgstr.Hash())); err != nil { return fmt.Errorf("failed to read TOC: %w", err) } r.tocDigest = dgstr.Digest() // Initialize file metadata in background. All operations refer to these metadata must wait // until this initialization ends. r.initG.Go(func() error { defer closeFunc() if _, err := f.Seek(0, io.SeekStart); err != nil { return err } if err := r.initNodes(f); err != nil { return err } if rOpts.Telemetry != nil && rOpts.Telemetry.DeserializeTocLatency != nil { rOpts.Telemetry.DeserializeTocLatency(start) } return nil }) return nil } func (r *reader) initRootNode(fsID string) error { return r.db.Batch(func(tx *bolt.Tx) (err error) { filesystems, err := tx.CreateBucketIfNotExists(bucketKeyFilesystems) if err != nil { return err } lbkt, err := filesystems.CreateBucket([]byte(fsID)) if err != nil { return err } r.fsID = fsID if _, err := lbkt.CreateBucket(bucketKeyMetadata); err != nil { return err } nodes, err := lbkt.CreateBucket(bucketKeyNodes) if err != nil { return err } rootID, err := r.nextID() if err != nil { return err } rootBucket, err := nodes.CreateBucket(encodeID(rootID)) if err != nil { return err } if err := writeAttr(rootBucket, &metadata.Attr{ Mode: os.ModeDir | 0755, NumLink: 2, // The directory itself(.) and the parent link to this directory. }); err != nil { return err } r.rootID = rootID return err }) } func (r *reader) initNodes(tr io.Reader) error { dec := json.NewDecoder(tr) for { t, err := dec.Token() if err != nil { return fmt.Errorf("failed to get JSON token: %w", err) } if ele, ok := t.(string); ok { if ele == "version" { continue } if ele == "entries" { continue } } if de, ok := t.(json.Delim); ok { if de.String() == "[" { break } } } md := make(map[uint32]*metadataEntry) if err := r.db.Batch(func(tx *bolt.Tx) (err error) { nodes, err := getNodes(tx, r.fsID) if err != nil { return err } nodes.FillPercent = 1.0 // we only do sequential write to this bucket var wantNextOffsetID uint32 var lastEntBucketID uint32 var lastEntSize int64 var attr metadata.Attr var ent estargz.TOCEntry for dec.More() { resetEnt(&ent) if err := dec.Decode(&ent); err != nil { return err } ent.Name = cleanEntryName(ent.Name) if ent.Type == "chunk" { if lastEntBucketID == 0 { return fmt.Errorf("chunk entry must not be the topmost") } if ent.ChunkSize == 0 { // last chunk in this file ent.ChunkSize = lastEntSize - ent.ChunkOffset } } if ent.ChunkSize == 0 && ent.Size != 0 { ent.ChunkSize = ent.Size } if ent.Type != "chunk" { var id uint32 var b *bolt.Bucket if ent.Type == "hardlink" { id, err = getIDByName(md, ent.LinkName, r.rootID) if err != nil { return fmt.Errorf("%q is a hardlink but cannot get link destination %q: %w", ent.Name, ent.LinkName, err) } b, err = getNodeBucketByID(nodes, id) if err != nil { return fmt.Errorf("cannot get hardlink destination %q ==> %q (%d): %w", ent.Name, ent.LinkName, id, err) } numLink, _ := binary.Varint(b.Get(bucketKeyNumLink)) if err := putInt(b, bucketKeyNumLink, numLink+1); err != nil { return fmt.Errorf("cannot put NumLink of %q ==> %q: %w", ent.Name, ent.LinkName, err) } } else { // Write node bucket var found bool if ent.Type == "dir" { // Check if this directory is already created, if so overwrite it. id, err = getIDByName(md, ent.Name, r.rootID) if err == nil { b, err = getNodeBucketByID(nodes, id) if err != nil { return fmt.Errorf("failed to get directory bucket %d: %w", id, err) } found = true ent.NumLink = readNumLink(b) } } if !found { // No existing node. Create a new one. id, err = r.nextID() if err != nil { return err } b, err = nodes.CreateBucket(encodeID(id)) if err != nil { return err } ent.NumLink = 1 // at least the parent dir references this directory. if ent.Type == "dir" { ent.NumLink++ // at least "." references this directory. } } if err := writeAttr(b, attrFromTOCEntry(&ent, &attr)); err != nil { return fmt.Errorf("failed to set attr to %d(%q): %w", id, ent.Name, err) } } pdirName := parentDir(ent.Name) pid, pb, err := r.getOrCreateDir(nodes, md, pdirName, r.rootID) if err != nil { return fmt.Errorf("failed to create parent directory %q of %q: %w", pdirName, ent.Name, err) } if err := setChild(md, pb, pid, path.Base(ent.Name), id, ent.Type == "dir"); err != nil { return err } if ent.Offset > 0 && wantNextOffsetID > 0 { if md[wantNextOffsetID] == nil { md[wantNextOffsetID] = &metadataEntry{} } md[wantNextOffsetID].nextOffset = ent.Offset } if ent.Type == "reg" && ent.Size > 0 { wantNextOffsetID = id } lastEntSize = ent.Size lastEntBucketID = id } if (ent.Type == "reg" && ent.Size > 0) || (ent.Type == "chunk" && ent.ChunkSize > 0) { if md[lastEntBucketID] == nil { md[lastEntBucketID] = &metadataEntry{} } ce := chunkEntry{ent.Offset, ent.ChunkOffset, ent.ChunkSize, ent.ChunkDigest} md[lastEntBucketID].chunks = append(md[lastEntBucketID].chunks, ce) } } if wantNextOffsetID > 0 { if md[wantNextOffsetID] == nil { md[wantNextOffsetID] = &metadataEntry{} } md[wantNextOffsetID].nextOffset = r.sr.Size() } return nil }); err != nil { return err } addendum := make([]struct { id []byte md *metadataEntry }, len(md)) i := 0 for id, d := range md { addendum[i].id, addendum[i].md = encodeID(id), d i++ } sort.Slice(addendum, func(i, j int) bool { return bytes.Compare(addendum[i].id, addendum[j].id) < 0 }) if err := r.db.Batch(func(tx *bolt.Tx) (err error) { meta, err := getMetadata(tx, r.fsID) if err != nil { return err } meta.FillPercent = 1.0 // we only do sequential write to this bucket for _, m := range addendum { md, err := meta.CreateBucket(m.id) if err != nil { return err } if err := writeMetadataEntry(md, m.md); err != nil { return err } } return nil }); err != nil { return err } return nil } func (r *reader) getOrCreateDir(nodes *bolt.Bucket, md map[uint32]*metadataEntry, d string, rootID uint32) (id uint32, b *bolt.Bucket, err error) { id, err = getIDByName(md, d, rootID) if err != nil { id, err = r.nextID() if err != nil { return 0, nil, err } b, err = nodes.CreateBucket(encodeID(id)) if err != nil { return 0, nil, err } attr := &metadata.Attr{ Mode: os.ModeDir | 0755, NumLink: 2, // The directory itself(.) and the parent link to this directory. } if err := writeAttr(b, attr); err != nil { return 0, nil, err } if d != "" { pid, pb, err := r.getOrCreateDir(nodes, md, parentDir(d), rootID) if err != nil { return 0, nil, err } if err := setChild(md, pb, pid, path.Base(d), id, true); err != nil { return 0, nil, err } } } else { b, err = getNodeBucketByID(nodes, id) if err != nil { return 0, nil, fmt.Errorf("failed to get dir bucket %d: %w", id, err) } } return id, b, nil } func (r *reader) waitInit() error { // TODO: add timeout if err := r.initG.Wait(); err != nil { return fmt.Errorf("initialization failed: %w", err) } return nil } func (r *reader) view(fn func(tx *bolt.Tx) error) error { if err := r.waitInit(); err != nil { return err } return r.db.View(func(tx *bolt.Tx) error { return fn(tx) }) } func (r *reader) update(fn func(tx *bolt.Tx) error) error { if err := r.waitInit(); err != nil { return err } return r.db.Batch(func(tx *bolt.Tx) error { return fn(tx) }) } // Close closes this reader. This removes underlying filesystem metadata as well. func (r *reader) Close() error { return r.update(func(tx *bolt.Tx) (err error) { filesystems := tx.Bucket(bucketKeyFilesystems) if filesystems == nil { return nil } return filesystems.DeleteBucket([]byte(r.fsID)) }) } // GetOffset returns an offset of a node. func (r *reader) GetOffset(id uint32) (offset int64, _ error) { if err := r.view(func(tx *bolt.Tx) error { metadataEntries, err := getMetadata(tx, r.fsID) if err != nil { return fmt.Errorf("metadata bucket of %q not found for searching offset of %d: %w", r.fsID, id, err) } nodes, err := getNodes(tx, r.fsID) if err != nil { return err } b, err := getNodeBucketByID(nodes, id) if err != nil { return err } size, _ := binary.Varint(b.Get(bucketKeySize)) if md, err := getMetadataBucketByID(metadataEntries, id); err == nil { chunks, err := readChunks(md, size) if err != nil { return err } if len(chunks) > 0 { offset = chunks[0].offset } } return nil }); err != nil { return 0, err } return } // GetAttr returns file attribute of specified node. func (r *reader) GetAttr(id uint32) (attr metadata.Attr, _ error) { if r.rootID == id { // no need to wait for root dir if err := r.db.View(func(tx *bolt.Tx) error { nodes, err := getNodes(tx, r.fsID) if err != nil { return fmt.Errorf("nodes bucket of %q not found for sarching attr %d: %w", r.fsID, id, err) } b, err := getNodeBucketByID(nodes, id) if err != nil { return fmt.Errorf("failed to get attr bucket %d: %w", id, err) } return readAttr(b, &attr) }); err != nil { return metadata.Attr{}, err } return attr, nil } if err := r.view(func(tx *bolt.Tx) error { nodes, err := getNodes(tx, r.fsID) if err != nil { return fmt.Errorf("nodes bucket of %q not found for sarching attr %d: %w", r.fsID, id, err) } b, err := getNodeBucketByID(nodes, id) if err != nil { return fmt.Errorf("failed to get attr bucket %d: %w", id, err) } return readAttr(b, &attr) }); err != nil { return metadata.Attr{}, err } return } // GetChild returns a child node that has the specified base name. func (r *reader) GetChild(pid uint32, base string) (id uint32, attr metadata.Attr, _ error) { if err := r.view(func(tx *bolt.Tx) error { metadataEntries, err := getMetadata(tx, r.fsID) if err != nil { return fmt.Errorf("metadata bucket of %q not found for getting child of %d: %w", r.fsID, pid, err) } md, err := getMetadataBucketByID(metadataEntries, pid) if err != nil { return fmt.Errorf("failed to get parent metadata %d: %w", pid, err) } id, err = readChild(md, base) if err != nil { return fmt.Errorf("failed to read child %q of %d: %w", base, pid, err) } nodes, err := getNodes(tx, r.fsID) if err != nil { return fmt.Errorf("nodes bucket of %q not found for getting child of %d: %w", r.fsID, pid, err) } child, err := getNodeBucketByID(nodes, id) if err != nil { return fmt.Errorf("failed to get child bucket %d: %w", id, err) } return readAttr(child, &attr) }); err != nil { return 0, metadata.Attr{}, err } return } // ForeachChild calls the specified callback function for each child node. // When the callback returns non-nil error, this stops the iteration. func (r *reader) ForeachChild(id uint32, f func(name string, id uint32, mode os.FileMode) bool) error { type childInfo struct { id uint32 mode os.FileMode } children := make(map[string]childInfo) if err := r.view(func(tx *bolt.Tx) error { metadataEntries, err := getMetadata(tx, r.fsID) if err != nil { return fmt.Errorf("nodes bucket of %q not found for getting child of %d: %w", r.fsID, id, err) } md, err := getMetadataBucketByID(metadataEntries, id) if err != nil { return nil // no child } var nodes *bolt.Bucket firstName := md.Get(bucketKeyChildName) if len(firstName) != 0 { firstID := decodeID(md.Get(bucketKeyChildID)) if nodes == nil { nodes, err = getNodes(tx, r.fsID) if err != nil { return fmt.Errorf("nodes bucket of %q not found for getting children of %d: %w", r.fsID, id, err) } } firstChild, err := getNodeBucketByID(nodes, firstID) if err != nil { return fmt.Errorf("failed to get first child bucket %d: %w", firstID, err) } mode, _ := binary.Uvarint(firstChild.Get(bucketKeyMode)) children[string(firstName)] = childInfo{firstID, os.FileMode(uint32(mode))} } cbkt := md.Bucket(bucketKeyChildrenExtra) if cbkt == nil { return nil // no child } if nodes == nil { nodes, err = getNodes(tx, r.fsID) if err != nil { return fmt.Errorf("nodes bucket of %q not found for getting children of %d: %w", r.fsID, id, err) } } return cbkt.ForEach(func(k, v []byte) error { id := decodeID(v) child, err := getNodeBucketByID(nodes, id) if err != nil { return fmt.Errorf("failed to get child bucket %d: %w", id, err) } mode, _ := binary.Uvarint(child.Get(bucketKeyMode)) children[string(k)] = childInfo{id, os.FileMode(uint32(mode))} return nil }) }); err != nil { return err } for k, e := range children { if !f(k, e.id, e.mode) { break } } return nil } // OpenFile returns a section reader of the specified node. func (r *reader) OpenFile(id uint32) (metadata.File, error) { var chunks []chunkEntry var size int64 var nextOffset int64 if err := r.view(func(tx *bolt.Tx) error { nodes, err := getNodes(tx, r.fsID) if err != nil { return fmt.Errorf("nodes bucket of %q not found for opening %d: %w", r.fsID, id, err) } b, err := getNodeBucketByID(nodes, id) if err != nil { return fmt.Errorf("failed to get file bucket %d: %w", id, err) } size, _ = binary.Varint(b.Get(bucketKeySize)) m, _ := binary.Uvarint(b.Get(bucketKeyMode)) if !os.FileMode(uint32(m)).IsRegular() { return fmt.Errorf("%q is not a regular file", id) } metadataEntries, err := getMetadata(tx, r.fsID) if err != nil { return fmt.Errorf("metadata bucket of %q not found for opening %d: %w", r.fsID, id, err) } if md, err := getMetadataBucketByID(metadataEntries, id); err == nil { chunks, err = readChunks(md, size) if err != nil { return fmt.Errorf("failed to get chunks: %w", err) } nextOffset, _ = binary.Varint(md.Get(bucketKeyNextOffset)) } return nil }); err != nil { return nil, err } fr := &fileReader{ r: r, size: size, ents: chunks, nextOffset: nextOffset, } return &file{io.NewSectionReader(fr, 0, size), chunks}, nil } type file struct { io.ReaderAt ents []chunkEntry } func (fr *file) ChunkEntryForOffset(offset int64) (off int64, size int64, dgst string, ok bool) { i := sort.Search(len(fr.ents), func(i int) bool { e := fr.ents[i] return e.chunkOffset >= offset || (offset > e.chunkOffset && offset < e.chunkOffset+e.chunkSize) }) if i == len(fr.ents) { return 0, 0, "", false } ci := fr.ents[i] return ci.chunkOffset, ci.chunkSize, ci.chunkDigest, true } type fileReader struct { r *reader size int64 ents []chunkEntry nextOffset int64 } // ReadAt reads file payload of this file. func (fr *fileReader) ReadAt(p []byte, off int64) (n int, err error) { if off >= fr.size { return 0, io.EOF } if off < 0 { return 0, errors.New("invalid offset") } var ent chunkEntry switch len(fr.ents) { case 0: return 0, errors.New("no chunk is registered") case 1: ent = fr.ents[0] if ent.chunkOffset > off { return 0, fmt.Errorf("no chunk coveres offset %d", off) } default: i := sort.Search(len(fr.ents), func(i int) bool { return fr.ents[i].chunkOffset > off }) if i == 0 { return 0, fmt.Errorf("no chunk coveres offset %d", off) } ent = fr.ents[i-1] } compressedBytesRemain := fr.nextOffset - ent.offset bufSize := int(2 << 20) if bufSize > int(compressedBytesRemain) { bufSize = int(compressedBytesRemain) } br := bufio.NewReaderSize(io.NewSectionReader(fr.r.sr, ent.offset, compressedBytesRemain), bufSize) if _, err := br.Peek(bufSize); err != nil { return 0, fmt.Errorf("failed to peek read file payload: %v", err) } dr, err := fr.r.decompressor.Reader(br) if err != nil { return 0, fmt.Errorf("fileReader.ReadAt.decompressor.Reader: %v", err) } defer dr.Close() base := off - ent.chunkOffset if n, err := io.CopyN(io.Discard, dr, base); n != base || err != nil { return 0, fmt.Errorf("discard of %d bytes = %v, %v", base, n, err) } return io.ReadFull(dr, p) } // TODO: share it with memory pkg func attrFromTOCEntry(src *estargz.TOCEntry, dst *metadata.Attr) *metadata.Attr { dst.Size = src.Size dst.ModTime, _ = time.Parse(time.RFC3339, src.ModTime3339) dst.LinkName = src.LinkName dst.Mode = src.Stat().Mode() dst.UID = src.UID dst.GID = src.GID dst.DevMajor = src.DevMajor dst.DevMinor = src.DevMinor dst.Xattrs = src.Xattrs dst.NumLink = src.NumLink return dst } func getIDByName(md map[uint32]*metadataEntry, name string, rootID uint32) (uint32, error) { name = cleanEntryName(name) if name == "" { return rootID, nil } dir, base := filepath.Split(name) pid, err := getIDByName(md, dir, rootID) if err != nil { return 0, err } if md[pid] == nil { return 0, fmt.Errorf("not found metadata of %d", pid) } if md[pid].children == nil { return 0, fmt.Errorf("not found children of %q", pid) } c, ok := md[pid].children[base] if !ok { return 0, fmt.Errorf("not found child %q in %d", base, pid) } return c.id, nil } func setChild(md map[uint32]*metadataEntry, pb *bolt.Bucket, pid uint32, base string, id uint32, isDir bool) error { if md[pid] == nil { md[pid] = &metadataEntry{} } if md[pid].children == nil { md[pid].children = make(map[string]childEntry) } md[pid].children[base] = childEntry{base, id} if isDir { numLink, _ := binary.Varint(pb.Get(bucketKeyNumLink)) if err := putInt(pb, bucketKeyNumLink, numLink+1); err != nil { return fmt.Errorf("cannot add numlink for children: %w", err) } } return nil } func parentDir(p string) string { dir, _ := path.Split(p) return strings.TrimSuffix(dir, "/") } func cleanEntryName(name string) string { // Use path.Clean to consistently deal with path separators across platforms. return strings.TrimPrefix(path.Clean("/"+name), "/") } func resetEnt(ent *estargz.TOCEntry) { ent.Name = "" ent.Type = "" ent.Size = 0 ent.ModTime3339 = "" ent.LinkName = "" ent.Mode = 0 ent.UID = 0 ent.GID = 0 ent.Uname = "" ent.Gname = "" ent.Offset = 0 ent.DevMajor = 0 ent.DevMinor = 0 ent.NumLink = 0 ent.Xattrs = nil ent.Digest = "" ent.ChunkOffset = 0 ent.ChunkSize = 0 ent.ChunkDigest = "" } func positive(n int64) int64 { if n < 0 { return 0 } return n } func (r *reader) NumOfNodes() (i int, _ error) { if err := r.view(func(tx *bolt.Tx) error { nodes, err := getNodes(tx, r.fsID) if err != nil { return err } return nodes.ForEach(func(k, v []byte) error { b := nodes.Bucket(k) if b == nil { return fmt.Errorf("entry bucket for %q not found", string(k)) } var attr metadata.Attr if err := readAttr(b, &attr); err != nil { return err } i++ return nil }) }); err != nil { return 0, err } return } func (r *reader) NumOfChunks(id uint32) (i int, _ error) { if err := r.view(func(tx *bolt.Tx) error { metadataEntries, err := getMetadata(tx, r.fsID) if err != nil { return err } md, err := getMetadataBucketByID(metadataEntries, id) if err != nil { return err } nodes, err := getNodes(tx, r.fsID) if err != nil { return err } b, err := getNodeBucketByID(nodes, id) if err != nil { return err } size, _ := binary.Varint(b.Get(bucketKeySize)) chunks, err := readChunks(md, size) if err != nil { return err } i = len(chunks) return nil }); err != nil { return 0, err } return } stargz-snapshotter-0.12.0/cmd/containerd-stargz-grpc/db/reader_test.go000066400000000000000000000045201426301527400260300ustar00rootroot00000000000000/* 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 db import ( "io" "os" "testing" "github.com/containerd/stargz-snapshotter/fs/layer" fsreader "github.com/containerd/stargz-snapshotter/fs/reader" "github.com/containerd/stargz-snapshotter/metadata" bolt "go.etcd.io/bbolt" ) func TestReader(t *testing.T) { metadata.TestReader(t, newTestableReader) } func TestFSReader(t *testing.T) { fsreader.TestSuiteReader(t, newStore) } func TestFSLayer(t *testing.T) { layer.TestSuiteLayer(t, newStore) } func newTestableReader(sr *io.SectionReader, opts ...metadata.Option) (metadata.TestableReader, error) { f, err := os.CreateTemp("", "readertestdb") if err != nil { return nil, err } defer f.Close() defer os.Remove(f.Name()) db, err := bolt.Open(f.Name(), 0600, nil) if err != nil { return nil, err } r, err := NewReader(db, sr, opts...) if err != nil { return nil, err } return &testableReadCloser{ TestableReader: r.(*reader), closeFn: func() error { db.Close() return os.Remove(f.Name()) }, }, nil } func newStore(sr *io.SectionReader, opts ...metadata.Option) (metadata.Reader, error) { f, err := os.CreateTemp("", "readertestdb") if err != nil { return nil, err } defer f.Close() db, err := bolt.Open(f.Name(), 0600, nil) if err != nil { return nil, err } r, err := NewReader(db, sr, opts...) if err != nil { return nil, err } return &readCloser{ Reader: r, closeFn: func() error { db.Close() return os.Remove(f.Name()) }, }, nil } type readCloser struct { metadata.Reader closeFn func() error } func (r *readCloser) Close() error { r.closeFn() return r.Reader.Close() } type testableReadCloser struct { metadata.TestableReader closeFn func() error } func (r *testableReadCloser) Close() error { r.closeFn() return r.TestableReader.Close() } stargz-snapshotter-0.12.0/cmd/containerd-stargz-grpc/ipfs/000077500000000000000000000000001426301527400235535ustar00rootroot00000000000000stargz-snapshotter-0.12.0/cmd/containerd-stargz-grpc/ipfs/resolvehandler.go000066400000000000000000000050251426301527400271210ustar00rootroot00000000000000/* 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 ipfs import ( "context" "crypto/sha256" "fmt" "io" "github.com/containerd/stargz-snapshotter/fs/remote" "github.com/containerd/stargz-snapshotter/ipfs" httpapi "github.com/ipfs/go-ipfs-http-client" iface "github.com/ipfs/interface-go-ipfs-core" ipath "github.com/ipfs/interface-go-ipfs-core/path" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) type ResolveHandler struct{} func (r *ResolveHandler) Handle(ctx context.Context, desc ocispec.Descriptor) (remote.Fetcher, int64, error) { p, err := ipfs.GetPath(desc) if err != nil { return nil, 0, err } client, err := httpapi.NewLocalApi() if err != nil { return nil, 0, err } n, err := client.Unixfs().Get(ctx, p) if err != nil { return nil, 0, err } if _, ok := n.(interface { io.ReaderAt }); !ok { return nil, 0, fmt.Errorf("ReaderAt is not implemented") } defer n.Close() s, err := n.Size() if err != nil { return nil, 0, err } return &fetcher{client, p}, s, nil } type fetcher struct { api iface.CoreAPI path ipath.Path } func (f *fetcher) Fetch(ctx context.Context, off int64, size int64) (io.ReadCloser, error) { n, err := f.api.Unixfs().Get(ctx, f.path) if err != nil { return nil, err } ra, ok := n.(interface { io.ReaderAt }) if !ok { return nil, fmt.Errorf("ReaderAt is not implemented") } return &readCloser{ Reader: io.NewSectionReader(ra, off, size), closeFunc: n.Close, }, nil } func (f *fetcher) Check() error { n, err := f.api.Unixfs().Get(context.Background(), f.path) if err != nil { return err } if _, ok := n.(interface { io.ReaderAt }); !ok { return fmt.Errorf("ReaderAt is not implemented") } return n.Close() } func (f *fetcher) GenID(off int64, size int64) string { sum := sha256.Sum256([]byte(fmt.Sprintf("%s-%d-%d", f.path.String(), off, size))) return fmt.Sprintf("%x", sum) } type readCloser struct { io.Reader closeFunc func() error } func (r *readCloser) Close() error { return r.closeFunc() } stargz-snapshotter-0.12.0/cmd/containerd-stargz-grpc/main.go000066400000000000000000000253071426301527400240740ustar00rootroot00000000000000/* 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" "flag" "fmt" "io" golog "log" "math/rand" "net" "net/http" "os" "os/signal" "path/filepath" "time" snapshotsapi "github.com/containerd/containerd/api/services/snapshots/v1" "github.com/containerd/containerd/contrib/snapshotservice" "github.com/containerd/containerd/defaults" "github.com/containerd/containerd/log" "github.com/containerd/containerd/pkg/dialer" "github.com/containerd/containerd/snapshots" "github.com/containerd/containerd/sys" 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/keychain/cri" "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" sddaemon "github.com/coreos/go-systemd/v22/daemon" metrics "github.com/docker/go-metrics" "github.com/pelletier/go-toml" "github.com/sirupsen/logrus" bolt "go.etcd.io/bbolt" "golang.org/x/sys/unix" "google.golang.org/grpc" "google.golang.org/grpc/backoff" "google.golang.org/grpc/credentials/insecure" runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2" ) const ( defaultAddress = "/run/containerd-stargz-grpc/containerd-stargz-grpc.sock" defaultConfigPath = "/etc/containerd-stargz-grpc/config.toml" defaultLogLevel = logrus.InfoLevel defaultRootDir = "/var/lib/containerd-stargz-grpc" defaultImageServiceAddress = "/run/containerd/containerd.sock" ) var ( address = flag.String("address", defaultAddress, "address for the snapshotter's GRPC server") 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]") rootDir = flag.String("root", defaultRootDir, "path to the root directory for this snapshotter") printVersion = flag.Bool("version", false, "print the version") ) type snapshotterConfig struct { service.Config // MetricsAddress is address for the metrics API MetricsAddress string `toml:"metrics_address"` // NoPrometheus is a flag to disable the emission of the metrics NoPrometheus bool `toml:"no_prometheus"` // DebugAddress is a Unix domain socket address where the snapshotter exposes /debug/ endpoints. DebugAddress string `toml:"debug_address"` // IPFS is a flag to enbale lazy pulling from IPFS. IPFS bool `toml:"ipfs"` // MetadataStore is the type of the metadata store to use. MetadataStore string `toml:"metadata_store" default:"memory"` } func main() { rand.Seed(time.Now().UnixNano()) flag.Parse() lvl, err := logrus.ParseLevel(*logLevel) if err != nil { log.L.WithError(err).Fatal("failed to prepare logger") } if *printVersion { fmt.Println("containerd-stargz-grpc", version.Version, version.Revision) return } logrus.SetLevel(lvl) logrus.SetFormatter(&logrus.JSONFormatter{ TimestampFormat: log.RFC3339NanoFixed, }) var ( ctx = log.WithLogger(context.Background(), log.L) config snapshotterConfig ) // Streams log of standard lib (go-fuse uses this) into debug log // Snapshotter should use "github.com/containerd/containerd/log" otherwize // logs are always printed as "debug" mode. golog.SetOutput(log.G(ctx).WriterLevel(logrus.DebugLevel)) // Get configuration from specified file tree, err := toml.LoadFile(*configPath) if err != nil && !(os.IsNotExist(err) && *configPath == defaultConfigPath) { log.G(ctx).WithError(err).Fatalf("failed to load config file %q", *configPath) } if err := tree.Unmarshal(&config); err != nil { log.G(ctx).WithError(err).Fatalf("failed to unmarshal config file %q", *configPath) } if err := service.Supported(*rootDir); err != nil { log.G(ctx).WithError(err).Fatalf("snapshotter is not supported") } // Create a gRPC server rpc := grpc.NewServer() // Configure keychain credsFuncs := []resolver.Credential{dockerconfig.NewDockerconfigKeychain(ctx)} if config.Config.KubeconfigKeychainConfig.EnableKeychain { var opts []kubeconfig.Option if kcp := config.Config.KubeconfigKeychainConfig.KubeconfigPath; kcp != "" { opts = append(opts, kubeconfig.WithKubeconfigPath(kcp)) } credsFuncs = append(credsFuncs, kubeconfig.NewKubeconfigKeychain(ctx, opts...)) } if config.Config.CRIKeychainConfig.EnableKeychain { // connects to the backend CRI service (defaults to containerd socket) criAddr := defaultImageServiceAddress if cp := config.CRIKeychainConfig.ImageServicePath; cp != "" { criAddr = cp } connectCRI := func() (runtime.ImageServiceClient, 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)), } conn, err := grpc.Dial(dialer.DialAddress(criAddr), gopts...) if err != nil { return nil, err } return runtime.NewImageServiceClient(conn), nil } f, criServer := cri.NewCRIKeychain(ctx, connectCRI) runtime.RegisterImageServiceServer(rpc, criServer) credsFuncs = append(credsFuncs, f) } 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) if err != nil { log.G(ctx).WithError(err).Fatalf("failed to serve snapshotter") } if cleanup { log.G(ctx).Debug("Closing the snapshotter") rs.Close() } log.G(ctx).Info("Exiting") } func serve(ctx context.Context, rpc *grpc.Server, addr string, rs snapshots.Snapshotter, config snapshotterConfig) (bool, error) { // Convert the snapshotter to a gRPC service, snsvc := snapshotservice.FromSnapshotter(rs) // Register the service with the gRPC server snapshotsapi.RegisterSnapshotsServer(rpc, snsvc) // Prepare the directory for the socket if err := os.MkdirAll(filepath.Dir(addr), 0700); err != nil { return false, 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 false, fmt.Errorf("failed to remove %q: %w", addr, err) } errCh := make(chan error, 1) // We need to consider both the existence of MetricsAddress as well as NoPrometheus flag not set if config.MetricsAddress != "" && !config.NoPrometheus { l, err := net.Listen("tcp", config.MetricsAddress) if err != nil { return false, fmt.Errorf("failed to get listener for metrics endpoint: %w", err) } m := http.NewServeMux() m.Handle("/metrics", metrics.Handler()) go func() { if err := http.Serve(l, m); err != nil { errCh <- fmt.Errorf("error on serving metrics via socket %q: %w", addr, err) } }() } if config.DebugAddress != "" { log.G(ctx).Infof("listen %q for debugging", config.DebugAddress) l, err := sys.GetLocalListener(config.DebugAddress, 0, 0) if err != nil { return false, fmt.Errorf("failed to listen %q: %w", config.DebugAddress, err) } go func() { if err := http.Serve(l, debugServerMux()); err != nil { errCh <- fmt.Errorf("error on serving a debug endpoint via socket %q: %w", addr, err) } }() } // Listen and serve l, err := net.Listen("unix", addr) if err != nil { return false, fmt.Errorf("error on listen socket %q: %w", addr, err) } go func() { if err := rpc.Serve(l); err != nil { errCh <- fmt.Errorf("error on serving via socket %q: %w", addr, err) } }() if os.Getenv("NOTIFY_SOCKET") != "" { notified, notifyErr := sddaemon.SdNotify(false, sddaemon.SdNotifyReady) log.G(ctx).Debugf("SdNotifyReady notified=%v, err=%v", notified, notifyErr) } defer func() { if os.Getenv("NOTIFY_SOCKET") != "" { notified, notifyErr := sddaemon.SdNotify(false, sddaemon.SdNotifyStopping) log.G(ctx).Debugf("SdNotifyStopping notified=%v, err=%v", notified, notifyErr) } }() 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: return false, err } if s == unix.SIGINT { return true, nil // do cleanup on SIGINT } 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) } } stargz-snapshotter-0.12.0/cmd/containerd-stargz-grpc/server.go000066400000000000000000000021151426301527400244460ustar00rootroot00000000000000/* 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 ( "expvar" "net/http" "net/http/pprof" ) func debugServerMux() *http.ServeMux { m := http.NewServeMux() m.Handle("/debug/vars", expvar.Handler()) m.Handle("/debug/pprof/", http.HandlerFunc(pprof.Index)) m.Handle("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline)) m.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile)) m.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol)) m.Handle("/debug/pprof/trace", http.HandlerFunc(pprof.Trace)) return m } stargz-snapshotter-0.12.0/cmd/ctr-remote/000077500000000000000000000000001426301527400203045ustar00rootroot00000000000000stargz-snapshotter-0.12.0/cmd/ctr-remote/commands/000077500000000000000000000000001426301527400221055ustar00rootroot00000000000000stargz-snapshotter-0.12.0/cmd/ctr-remote/commands/convert.go000066400000000000000000000152031426301527400241150ustar00rootroot00000000000000/* 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 commands import ( "compress/gzip" "encoding/json" "errors" "fmt" "os" "os/signal" "github.com/containerd/containerd/cmd/ctr/commands" "github.com/containerd/containerd/images/converter" "github.com/containerd/containerd/images/converter/uncompress" "github.com/containerd/containerd/platforms" "github.com/containerd/stargz-snapshotter/estargz" estargzconvert "github.com/containerd/stargz-snapshotter/nativeconverter/estargz" zstdchunkedconvert "github.com/containerd/stargz-snapshotter/nativeconverter/zstdchunked" "github.com/containerd/stargz-snapshotter/recorder" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/sirupsen/logrus" "github.com/urfave/cli" ) // ConvertCommand converts an image var ConvertCommand = cli.Command{ Name: "convert", Usage: "convert an image", ArgsUsage: "[flags] ...", Description: `Convert an image format. e.g., 'ctr-remote convert --estargz --oci example.com/foo:orig example.com/foo:esgz' Use '--platform' to define the output platform. When '--all-platforms' is given all images in a manifest list must be available. `, Flags: []cli.Flag{ // estargz flags cli.BoolFlag{ Name: "estargz", Usage: "convert legacy tar(.gz) layers to eStargz for lazy pulling. Should be used in conjunction with '--oci'", }, cli.StringFlag{ Name: "estargz-record-in", Usage: "Read 'ctr-remote optimize --record-out=' record file", }, cli.IntFlag{ Name: "estargz-compression-level", Usage: "eStargz compression level", Value: gzip.BestCompression, }, cli.IntFlag{ Name: "estargz-chunk-size", Usage: "eStargz chunk size", Value: 0, }, // zstd:chunked flags cli.BoolFlag{ Name: "zstdchunked", Usage: "use zstd compression instead of gzip (a.k.a zstd:chunked). Must be used in conjunction with '--oci'.", }, // generic flags cli.BoolFlag{ Name: "uncompress", Usage: "convert tar.gz layers to uncompressed tar layers", }, cli.BoolFlag{ Name: "oci", Usage: "convert Docker media types to OCI media types", }, // platform flags cli.StringSliceFlag{ Name: "platform", Usage: "Convert content for a specific platform", Value: &cli.StringSlice{}, }, cli.BoolFlag{ Name: "all-platforms", Usage: "Convert content for all platforms", }, }, Action: func(context *cli.Context) error { var ( convertOpts = []converter.Opt{} ) srcRef := context.Args().Get(0) targetRef := context.Args().Get(1) if srcRef == "" || targetRef == "" { return errors.New("src and target image need to be specified") } var platformMC platforms.MatchComparer if context.Bool("all-platforms") { platformMC = platforms.All } else { if pss := context.StringSlice("platform"); len(pss) > 0 { var all []ocispec.Platform for _, ps := range pss { p, err := platforms.Parse(ps) if err != nil { return fmt.Errorf("invalid platform %q: %w", ps, err) } all = append(all, p) } platformMC = platforms.Ordered(all...) } else { platformMC = platforms.DefaultStrict() } } convertOpts = append(convertOpts, converter.WithPlatform(platformMC)) var layerConvertFunc converter.ConvertFunc if context.Bool("estargz") { esgzOpts, err := getESGZConvertOpts(context) if err != nil { return err } layerConvertFunc = estargzconvert.LayerConvertFunc(esgzOpts...) if !context.Bool("oci") { logrus.Warn("option --estargz should be used in conjunction with --oci") } if context.Bool("uncompress") { return errors.New("option --estargz conflicts with --uncompress") } if context.Bool("zstdchunked") { return errors.New("option --estargz conflicts with --zstdchunked") } } if context.Bool("zstdchunked") { esgzOpts, err := getESGZConvertOpts(context) if err != nil { return err } layerConvertFunc = zstdchunkedconvert.LayerConvertFunc(esgzOpts...) if !context.Bool("oci") { return errors.New("option --zstdchunked must be used in conjunction with --oci") } if context.Bool("uncompress") { return errors.New("option --zstdchunked conflicts with --uncompress") } } if context.Bool("uncompress") { layerConvertFunc = uncompress.LayerConvertFunc } if layerConvertFunc == nil { return errors.New("specify layer converter") } convertOpts = append(convertOpts, converter.WithLayerConvertFunc(layerConvertFunc)) if context.Bool("oci") { convertOpts = append(convertOpts, converter.WithDockerToOCI(true)) } client, ctx, cancel, err := commands.NewClient(context) if err != nil { return err } defer cancel() sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, os.Interrupt) go func() { // Cleanly cancel conversion select { case s := <-sigCh: logrus.Infof("Got %v", s) cancel() case <-ctx.Done(): } }() newImg, err := converter.Convert(ctx, client, targetRef, srcRef, convertOpts...) if err != nil { return err } fmt.Fprintln(context.App.Writer, newImg.Target.Digest.String()) return nil }, } func getESGZConvertOpts(context *cli.Context) ([]estargz.Option, error) { esgzOpts := []estargz.Option{ estargz.WithCompressionLevel(context.Int("estargz-compression-level")), estargz.WithChunkSize(context.Int("estargz-chunk-size")), } if estargzRecordIn := context.String("estargz-record-in"); estargzRecordIn != "" { paths, err := readPathsFromRecordFile(estargzRecordIn) if err != nil { return nil, err } esgzOpts = append(esgzOpts, estargz.WithPrioritizedFiles(paths)) var ignored []string esgzOpts = append(esgzOpts, estargz.WithAllowPrioritizeNotFound(&ignored)) } return esgzOpts, nil } func readPathsFromRecordFile(filename string) ([]string, error) { r, err := os.Open(filename) if err != nil { return nil, err } defer r.Close() dec := json.NewDecoder(r) var paths []string added := make(map[string]struct{}) for dec.More() { var e recorder.Entry if err := dec.Decode(&e); err != nil { return nil, err } if _, ok := added[e.Path]; !ok { paths = append(paths, e.Path) added[e.Path] = struct{}{} } } return paths, nil } stargz-snapshotter-0.12.0/cmd/ctr-remote/commands/flags.go000066400000000000000000000342601426301527400235350ustar00rootroot00000000000000/* 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 commands import ( "bytes" gocontext "context" "encoding/csv" "encoding/json" "fmt" "os" "path/filepath" "runtime" "strings" "github.com/containerd/containerd" "github.com/containerd/containerd/containers" "github.com/containerd/containerd/content" "github.com/containerd/containerd/contrib/nvidia" "github.com/containerd/containerd/images" "github.com/containerd/containerd/oci" "github.com/containerd/containerd/pkg/netns" gocni "github.com/containerd/go-cni" "github.com/hashicorp/go-multierror" imagespec "github.com/opencontainers/image-spec/specs-go/v1" runtimespec "github.com/opencontainers/runtime-spec/specs-go" "github.com/rs/xid" "github.com/sirupsen/logrus" "github.com/urfave/cli" ) const netnsMountDir = "/var/run/netns" var samplerFlags = []cli.Flag{ cli.BoolFlag{ Name: "terminal,t", Usage: "enable terminal for sample container. must be specified with i option", }, cli.BoolFlag{ Name: "i", Usage: "attach stdin to the container", }, cli.IntFlag{ Name: "period", Usage: "time period to monitor access log", Value: defaultPeriod, }, cli.StringFlag{ Name: "user", Usage: "user/group name to override image's default config(user[:group])", }, cli.StringFlag{ Name: "cwd", Usage: "working dir to override image's default config", }, cli.StringFlag{ Name: "args", Usage: "command arguments to override image's default config(in JSON array)", }, cli.StringFlag{ Name: "entrypoint", Usage: "entrypoint to override image's default config(in JSON array)", }, cli.StringSliceFlag{ Name: "env", Usage: "environment valulable to add or override to the image's default config", }, cli.StringFlag{ Name: "env-file", Usage: "specify additional container environment variables in a file(i.e. FOO=bar, one per line)", }, cli.StringSliceFlag{ Name: "mount", Usage: "additional mounts for the container (e.g. type=foo,source=/path,destination=/target,options=bind)", }, cli.StringFlag{ Name: "dns-nameservers", Usage: "comma-separated nameservers added to the container's /etc/resolv.conf", Value: "8.8.8.8", }, cli.StringFlag{ Name: "dns-search-domains", Usage: "comma-separated search domains added to the container's /etc/resolv.conf", }, cli.StringFlag{ Name: "dns-options", Usage: "comma-separated options added to the container's /etc/resolv.conf", }, cli.StringFlag{ Name: "add-hosts", Usage: "comma-separated hosts configuration (host:IP) added to container's /etc/hosts", }, cli.BoolFlag{ Name: "cni", Usage: "enable CNI-based networking", }, cli.StringFlag{ Name: "cni-plugin-conf-dir", Usage: "path to the CNI plugins configuration directory", }, cli.StringFlag{ Name: "cni-plugin-dir", Usage: "path to the CNI plugins binary directory", }, cli.IntSliceFlag{ Name: "gpus", Usage: "add gpus to the container", }, cli.BoolFlag{ Name: "net-host", Usage: "enable host networking in the container", }, } 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) { var cleanups []func() error done = func() (allErr error) { for i := len(cleanups) - 1; i >= 0; i-- { if err := cleanups[i](); err != nil { allErr = multierror.Append(allErr, err) } } return } defer func() { if rErr != nil { if err := done(); err != nil { rErr = fmt.Errorf("failed to cleanup: %w", rErr) } } }() entrypointOpt, err := withEntrypointArgs(clicontext, image) if err != nil { rErr = fmt.Errorf("failed to parse entrypoint and arg flags: %w", err) return } resolverOpt, cleanup, err := withResolveConfig(clicontext) if err != nil { rErr = fmt.Errorf("failed to parse DNS-related flags: %w", err) return } cleanups = append(cleanups, cleanup) var mounts []runtimespec.Mount for _, mount := range clicontext.StringSlice("mount") { m, err := parseMountFlag(mount) if err != nil { rErr = fmt.Errorf("failed to parse mount flag %q: %w", mount, err) return } mounts = append(mounts, m) } opts = append(opts, oci.WithDefaultSpec(), oci.WithDefaultUnixDevices, oci.WithRootFSPath(rootfs), oci.WithImageConfig(image), oci.WithEnv(clicontext.StringSlice("env")), oci.WithMounts(mounts), resolverOpt, entrypointOpt, ) if envFile := clicontext.String("env-file"); envFile != "" { opts = append(opts, oci.WithEnvFile(envFile)) } if username := clicontext.String("user"); username != "" { opts = append(opts, oci.WithUser(username)) } if cwd := clicontext.String("cwd"); cwd != "" { opts = append(opts, oci.WithProcessCwd(cwd)) } if clicontext.Bool("terminal") { if !clicontext.Bool("i") { rErr = fmt.Errorf("terminal flag must be specified with \"-i\"") return } opts = append(opts, oci.WithTTY) } if clicontext.Bool("cni") { var nOpt oci.SpecOpts nOpt, cleanup, err = withCNI(clicontext) if err != nil { rErr = fmt.Errorf("failed to parse CNI-related flags: %w", err) return } cleanups = append(cleanups, cleanup) opts = append(opts, nOpt) } if clicontext.Bool("net-host") { if runtime.GOOS == "windows" { logrus.Warn("option --net-host is not supported on Windows") } else { opts = append(opts, oci.WithHostNamespace(runtimespec.NetworkNamespace), oci.WithHostHostsFile, oci.WithHostResolvconf) } } if clicontext.IsSet("gpus") { if runtime.GOOS == "windows" { logrus.Warn("option --gpus is not supported on Windows") } else { opts = append(opts, nvidia.WithGPUs(nvidia.WithDevices(clicontext.IntSlice("gpus")...), nvidia.WithAllCapabilities)) } } return } } func withEntrypointArgs(clicontext *cli.Context, image containerd.Image) (oci.SpecOpts, error) { var eFlag []string if eStr := clicontext.String("entrypoint"); eStr != "" { if err := json.Unmarshal([]byte(eStr), &eFlag); err != nil { return nil, fmt.Errorf("invalid option \"entrypoint\": %w", err) } } var aFlag []string if aStr := clicontext.String("args"); aStr != "" { if err := json.Unmarshal([]byte(aStr), &aFlag); err != nil { return nil, fmt.Errorf("invalid option \"args\": %w", err) } } return func(ctx gocontext.Context, client oci.Client, container *containers.Container, s *runtimespec.Spec) error { configDesc, err := image.Config(ctx) if err != nil { return err } var ociimage imagespec.Image switch configDesc.MediaType { case imagespec.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config: p, err := content.ReadBlob(ctx, image.ContentStore(), configDesc) if err != nil { return err } if err := json.Unmarshal(p, &ociimage); err != nil { return err } default: return fmt.Errorf("unknown image config media type %s", configDesc.MediaType) } entrypoint := ociimage.Config.Entrypoint if len(eFlag) > 0 { entrypoint = eFlag } args := ociimage.Config.Cmd if len(aFlag) > 0 { args = aFlag } return oci.WithProcessArgs(append(entrypoint, args...)...)(ctx, client, container, s) }, nil } func withCNI(clicontext *cli.Context) (specOpt oci.SpecOpts, done func() error, rErr error) { var cleanups []func() error done = func() (allErr error) { for i := len(cleanups) - 1; i >= 0; i-- { if err := cleanups[i](); err != nil { allErr = multierror.Append(allErr, err) } } return } defer func() { if rErr != nil { if err := done(); err != nil { rErr = fmt.Errorf("failed to cleanup: %w", rErr) } } }() // Create a new network namespace for configuring it with CNI plugins ns, err := netns.NewNetNS(netnsMountDir) if err != nil { rErr = fmt.Errorf("failed to prepare netns: %w", err) return } cleanups = append(cleanups, ns.Remove) // Configure the namespace with CNI plugins var cniopts []gocni.Opt if cdir := clicontext.String("cni-plugin-conf-dir"); cdir != "" { cniopts = append(cniopts, gocni.WithPluginConfDir(cdir)) } if pdir := clicontext.String("cni-plugin-dir"); pdir != "" { cniopts = append(cniopts, gocni.WithPluginDir([]string{pdir})) } // The first-found configration file will be effective // TODO: Should we make the number of reading files configurable? cniopts = append(cniopts, gocni.WithDefaultConf) network, err := gocni.New(cniopts...) if err != nil { rErr = fmt.Errorf("failed to prepare CNI plugins: %w", err) return } id := xid.New().String() ctx := gocontext.Background() if _, err := network.Setup(ctx, id, ns.GetPath()); err != nil { rErr = fmt.Errorf("failed to setup netns with CNI plugins: %w", err) return } cleanups = append(cleanups, func() error { return network.Remove(ctx, id, ns.GetPath()) }) // Make the container use this network namespace return oci.WithLinuxNamespace(runtimespec.LinuxNamespace{ Type: runtimespec.NetworkNamespace, Path: ns.GetPath(), }), done, nil } func withResolveConfig(clicontext *cli.Context) (specOpt oci.SpecOpts, cleanup func() error, rErr error) { defer func() { if rErr != nil { if err := cleanup(); err != nil { rErr = fmt.Errorf("failed to cleanup: %w", rErr) } } }() extrahosts, nameservers, searches, dnsopts, err := parseResolveFlag(clicontext) if err != nil { return nil, nil, err } // Generate /etc/hosts and /etc/resolv.conf resolvDir, err := os.MkdirTemp("", "tmpetc") if err != nil { return nil, nil, err } cleanup = func() error { return os.RemoveAll(resolvDir) } var ( etcHostsPath = filepath.Join(resolvDir, "hosts") etcResolvConfPath = filepath.Join(resolvDir, "resolv.conf") buf = new(bytes.Buffer) ) for _, n := range nameservers { if _, err := fmt.Fprintf(buf, "nameserver %s\n", n); err != nil { rErr = fmt.Errorf("failed to prepare nameserver of /etc/resolv.conf: %w", err) return } } if len(searches) > 0 { _, err := fmt.Fprintf(buf, "search %s\n", strings.Join(searches, " ")) if err != nil { rErr = fmt.Errorf("failed to prepare search contents of /etc/resolv.conf: %w", err) return } } if len(dnsopts) > 0 { _, err := fmt.Fprintf(buf, "options %s\n", strings.Join(dnsopts, " ")) if err != nil { rErr = fmt.Errorf("failed to prepare options contents of /etc/resolv.conf: %w", err) return } } if err := os.WriteFile(etcResolvConfPath, buf.Bytes(), 0644); err != nil { rErr = fmt.Errorf("failed to write contents to /etc/resolv.conf: %w", err) return } buf.Reset() // Reusing for /etc/hosts for _, h := range []struct { host string ip string }{ // Configuration compatible to docker's config // https://github.com/moby/libnetwork/blob/535ef365dc1dd82a5135803a58bc6198a3b9aa27/etchosts/etchosts.go#L28-L36 {"localhost", "127.0.0.1"}, {"localhost ip6-localhost ip6-loopback", "::1"}, {"ip6-localnet", "fe00::0"}, {"ip6-mcastprefix", "ff00::0"}, {"ip6-allnodes", "ff02::1"}, {"ip6-allrouters", "ff02::2"}, } { if _, err := fmt.Fprintf(buf, "%s\t%s\n", h.ip, h.host); err != nil { rErr = fmt.Errorf("failed to write default hosts to /etc/hosts: %w", err) return } } for _, h := range extrahosts { parts := strings.SplitN(h, ":", 2) // host:IP if len(parts) != 2 { rErr = fmt.Errorf("cannot parse %q as extra host; must be \"host:IP\"", h) return } // TODO: Validate them if _, err := fmt.Fprintf(buf, "%s\t%s\n", parts[1], parts[0]); err != nil { rErr = fmt.Errorf("failed to write extra hosts to /etc/hosts: %w", err) return } } if err := os.WriteFile(etcHostsPath, buf.Bytes(), 0644); err != nil { rErr = fmt.Errorf("failed to write contents to /etc/hosts: %w", err) return } return oci.WithMounts([]runtimespec.Mount{ { Destination: "/etc/resolv.conf", Source: etcResolvConfPath, Options: []string{"bind"}, }, { Destination: "/etc/hosts", Source: etcHostsPath, Options: []string{"bind"}, }, }), cleanup, nil } // parseMountFlag parses a mount string in the form "type=foo,source=/path,destination=/target,options=rbind:rw" func parseMountFlag(m string) (runtimespec.Mount, error) { mount := runtimespec.Mount{} r := csv.NewReader(strings.NewReader(m)) fields, err := r.Read() if err != nil { return mount, err } for _, field := range fields { v := strings.Split(field, "=") if len(v) != 2 { return mount, fmt.Errorf("invalid mount specification: expected key=val") } key := v[0] val := v[1] switch key { case "type": mount.Type = val case "source", "src": mount.Source = val case "destination", "dst": mount.Destination = val case "options": mount.Options = strings.Split(val, ":") default: return mount, fmt.Errorf("mount option %q not supported", key) } } return mount, nil } func parseResolveFlag(clicontext *cli.Context) (hosts []string, nameservers []string, searches []string, dnsopts []string, _ error) { if nFlag := clicontext.String("dns-nameservers"); nFlag != "" { fields, err := csv.NewReader(strings.NewReader(nFlag)).Read() if err != nil { return nil, nil, nil, nil, err } nameservers = append(nameservers, fields...) } if sFlag := clicontext.String("dns-search-domains"); sFlag != "" { fields, err := csv.NewReader(strings.NewReader(sFlag)).Read() if err != nil { return nil, nil, nil, nil, err } searches = append(searches, fields...) } if oFlag := clicontext.String("dns-options"); oFlag != "" { fields, err := csv.NewReader(strings.NewReader(oFlag)).Read() if err != nil { return nil, nil, nil, nil, err } dnsopts = append(dnsopts, fields...) } if hFlag := clicontext.String("add-hosts"); hFlag != "" { fields, err := csv.NewReader(strings.NewReader(hFlag)).Read() if err != nil { return nil, nil, nil, nil, err } hosts = append(hosts, fields...) } return } stargz-snapshotter-0.12.0/cmd/ctr-remote/commands/get-toc-digest.go000066400000000000000000000061361426301527400252610ustar00rootroot00000000000000/* 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 commands import ( "encoding/json" "errors" "fmt" "io" "github.com/containerd/containerd/cmd/ctr/commands" "github.com/containerd/stargz-snapshotter/estargz" "github.com/containerd/stargz-snapshotter/estargz/zstdchunked" digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/urfave/cli" ) // GetTOCDigestCommand outputs TOC info of a layer var GetTOCDigestCommand = cli.Command{ Name: "get-toc-digest", Usage: "get the digest of TOC of a layer", ArgsUsage: "", Flags: []cli.Flag{ // zstd:chunked flags cli.BoolFlag{ Name: "zstdchunked", Usage: "parse layer as zstd:chunked", }, // other flags for debugging cli.BoolFlag{ 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", }, }, Action: func(clicontext *cli.Context) error { layerDgstStr := clicontext.Args().Get(0) if layerDgstStr == "" { return errors.New("layer digest need to be specified") } client, ctx, cancel, err := commands.NewClient(clicontext) if err != nil { return err } defer cancel() layerDgst, err := digest.Parse(layerDgstStr) if err != nil { return err } ra, err := client.ContentStore().ReaderAt(ctx, ocispec.Descriptor{Digest: layerDgst}) if err != nil { return err } defer ra.Close() footerSize := estargz.FooterSize if clicontext.Bool("zstdchunked") { footerSize = zstdchunked.FooterSize } footer := make([]byte, footerSize) if _, err := ra.ReadAt(footer, ra.Size()-int64(footerSize)); err != nil { return fmt.Errorf("error reading footer: %w", err) } var decompressor estargz.Decompressor decompressor = new(estargz.GzipDecompressor) if clicontext.Bool("zstdchunked") { decompressor = new(zstdchunked.Decompressor) } _, tocOff, tocSize, err := decompressor.ParseFooter(footer) if err != nil { return fmt.Errorf("error parsing footer: %w", err) } if tocSize <= 0 { tocSize = ra.Size() - tocOff - int64(footerSize) } toc, tocDgst, err := decompressor.ParseTOC(io.NewSectionReader(ra, tocOff, tocSize)) if err != nil { return fmt.Errorf("error parsing TOC: %w", err) } if clicontext.Bool("dump-toc") { tocJSON, err := json.MarshalIndent(toc, "", "\t") if err != nil { return fmt.Errorf("failed to marshal toc: %w", err) } fmt.Println(string(tocJSON)) return nil } fmt.Println(tocDgst.String()) return nil }, } stargz-snapshotter-0.12.0/cmd/ctr-remote/commands/ipfs-push.go000066400000000000000000000054251426301527400243600ustar00rootroot00000000000000/* 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 commands import ( "errors" "fmt" "github.com/containerd/containerd/cmd/ctr/commands" "github.com/containerd/containerd/images/converter" "github.com/containerd/containerd/platforms" "github.com/containerd/stargz-snapshotter/ipfs" estargzconvert "github.com/containerd/stargz-snapshotter/nativeconverter/estargz" httpapi "github.com/ipfs/go-ipfs-http-client" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/sirupsen/logrus" "github.com/urfave/cli" ) // IPFSPushCommand pushes an image to IPFS var IPFSPushCommand = cli.Command{ Name: "ipfs-push", Usage: "push an image to IPFS (experimental)", ArgsUsage: "[flags] ", Flags: []cli.Flag{ // platform flags cli.StringSliceFlag{ Name: "platform", Usage: "Add content for a specific platform", Value: &cli.StringSlice{}, }, cli.BoolFlag{ Name: "all-platforms", Usage: "Add content for all platforms", }, cli.BoolTFlag{ Name: "estargz", Usage: "Convert the image into eStargz", }, }, Action: func(context *cli.Context) error { srcRef := context.Args().Get(0) if srcRef == "" { return errors.New("image need to be specified") } var platformMC platforms.MatchComparer if context.Bool("all-platforms") { platformMC = platforms.All } else { if pss := context.StringSlice("platform"); len(pss) > 0 { var all []ocispec.Platform for _, ps := range pss { p, err := platforms.Parse(ps) if err != nil { return fmt.Errorf("invalid platform %q: %w", ps, err) } all = append(all, p) } platformMC = platforms.Ordered(all...) } else { platformMC = platforms.DefaultStrict() } } client, ctx, cancel, err := commands.NewClient(context) if err != nil { return err } defer cancel() ipfsClient, err := httpapi.NewLocalApi() if err != nil { return err } var layerConvert converter.ConvertFunc if context.Bool("estargz") { layerConvert = estargzconvert.LayerConvertFunc() } p, err := ipfs.Push(ctx, client, ipfsClient, srcRef, layerConvert, platformMC) if err != nil { return err } logrus.WithField("CID", p.Cid().String()).Infof("Pushed") fmt.Println(p.Cid().String()) return nil }, } stargz-snapshotter-0.12.0/cmd/ctr-remote/commands/notify.go000066400000000000000000000021031426301527400237400ustar00rootroot00000000000000/* 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 commands import ( "fmt" "os" "github.com/containerd/stargz-snapshotter/analyzer/fanotify/service" "github.com/urfave/cli" ) // FanotifyCommand notifies filesystem event under the specified directory. var FanotifyCommand = cli.Command{ Name: "fanotify", Hidden: true, Action: func(context *cli.Context) error { target := context.Args().Get(0) if target == "" { return fmt.Errorf("target must be specified") } return service.Serve(target, os.Stdin, os.Stdout) }, } stargz-snapshotter-0.12.0/cmd/ctr-remote/commands/optimize.go000066400000000000000000000255701426301527400243050ustar00rootroot00000000000000/* 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 commands import ( "compress/gzip" "context" "encoding/json" "errors" "fmt" "io" "os" "os/signal" "time" "github.com/containerd/containerd" "github.com/containerd/containerd/cmd/ctr/commands" "github.com/containerd/containerd/content" "github.com/containerd/containerd/images/converter" "github.com/containerd/containerd/platforms" "github.com/containerd/stargz-snapshotter/analyzer" "github.com/containerd/stargz-snapshotter/estargz" "github.com/containerd/stargz-snapshotter/estargz/zstdchunked" estargzconvert "github.com/containerd/stargz-snapshotter/nativeconverter/estargz" zstdchunkedconvert "github.com/containerd/stargz-snapshotter/nativeconverter/zstdchunked" "github.com/containerd/stargz-snapshotter/recorder" "github.com/containerd/stargz-snapshotter/util/containerdutil" "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/sirupsen/logrus" "github.com/urfave/cli" ) const defaultPeriod = 10 // OptimizeCommand converts and optimizes an image var OptimizeCommand = cli.Command{ Name: "optimize", Usage: "optimize an image with user-specified workload", ArgsUsage: "[flags] ...", Flags: append([]cli.Flag{ cli.BoolFlag{ Name: "reuse", Usage: "reuse eStargz (already optimized) layers without further conversion", }, cli.StringSliceFlag{ Name: "platform", Usage: "Pull content from a specific platform", Value: &cli.StringSlice{}, }, cli.BoolFlag{ Name: "all-platforms", Usage: "targeting all platform of the source image", }, cli.BoolFlag{ Name: "wait-on-signal", Usage: "ignore context cancel and keep the container running until it receives SIGINT (Ctrl + C) sent manually", }, cli.BoolFlag{ Name: "no-optimize", Usage: "convert image without optimization", }, cli.StringFlag{ Name: "record-out", Usage: "record the monitor log to the specified file", }, cli.BoolFlag{ Name: "oci", Usage: "convert Docker media types to OCI media types", }, cli.IntFlag{ Name: "estargz-compression-level", Usage: "eStargz compression level (only applied to gzip as of now)", Value: gzip.BestCompression, }, cli.BoolFlag{ Name: "zstdchunked", Usage: "use zstd compression instead of gzip (a.k.a zstd:chunked)", }, }, samplerFlags...), Action: func(clicontext *cli.Context) error { convertOpts := []converter.Opt{} srcRef := clicontext.Args().Get(0) targetRef := clicontext.Args().Get(1) if srcRef == "" || targetRef == "" { return errors.New("src and target image need to be specified") } var platformMC platforms.MatchComparer if clicontext.Bool("all-platforms") { platformMC = platforms.All } else { if pss := clicontext.StringSlice("platform"); len(pss) > 0 { var all []ocispec.Platform for _, ps := range pss { p, err := platforms.Parse(ps) if err != nil { return fmt.Errorf("invalid platform %q: %w", ps, err) } all = append(all, p) } platformMC = platforms.Ordered(all...) } else { platformMC = platforms.DefaultStrict() } } convertOpts = append(convertOpts, converter.WithPlatform(platformMC)) if clicontext.Bool("oci") { convertOpts = append(convertOpts, converter.WithDockerToOCI(true)) } else if clicontext.Bool("zstdchunked") { return errors.New("option --zstdchunked must be used in conjunction with --oci") } client, ctx, cancel, err := commands.NewClient(clicontext) if err != nil { return err } defer cancel() ctx, done, err := client.WithLease(ctx) if err != nil { return err } defer done(ctx) recordOut, esgzOptsPerLayer, wrapper, err := analyze(ctx, clicontext, client, srcRef) if err != nil { return err } if recordOutFile := clicontext.String("record-out"); recordOutFile != "" { if err := writeContentFile(ctx, client, recordOut, recordOutFile); err != nil { return fmt.Errorf("failed output record file: %w", err) } } var f converter.ConvertFunc if clicontext.Bool("zstdchunked") { f = zstdchunkedconvert.LayerConvertWithLayerOptsFunc(esgzOptsPerLayer) } else { f = estargzconvert.LayerConvertWithLayerAndCommonOptsFunc(esgzOptsPerLayer, estargz.WithCompressionLevel(clicontext.Int("estargz-compression-level"))) } if wrapper != nil { f = wrapper(f) } layerConvertFunc := logWrapper(f) sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, os.Interrupt) go func() { // Cleanly cancel conversion select { case s := <-sigCh: logrus.Infof("Got %v", s) cancel() case <-ctx.Done(): } }() convertOpts = append(convertOpts, converter.WithLayerConvertFunc(layerConvertFunc)) newImg, err := converter.Convert(ctx, client, targetRef, srcRef, convertOpts...) if err != nil { return err } fmt.Fprintln(clicontext.App.Writer, newImg.Target.Digest.String()) return nil }, } func writeContentFile(ctx context.Context, client *containerd.Client, dgst digest.Digest, targetFile string) error { fw, err := os.Create(targetFile) if err != nil { return err } defer fw.Close() ra, err := client.ContentStore().ReaderAt(ctx, ocispec.Descriptor{Digest: dgst}) if err != nil { return err } defer ra.Close() _, err = io.Copy(fw, io.NewSectionReader(ra, 0, ra.Size())) return err } func analyze(ctx context.Context, clicontext *cli.Context, client *containerd.Client, srcRef string) (digest.Digest, map[digest.Digest][]estargz.Option, func(converter.ConvertFunc) converter.ConvertFunc, error) { if clicontext.Bool("no-optimize") { return "", nil, nil, nil } // Do analysis only when the target platforms contain the current platform if !clicontext.Bool("all-platforms") { if pss := clicontext.StringSlice("platform"); len(pss) > 0 { containsDefault := false for _, ps := range pss { p, err := platforms.Parse(ps) if err != nil { return "", nil, nil, fmt.Errorf("invalid platform %q: %w", ps, err) } if platforms.DefaultStrict().Match(p) { containsDefault = true } } if !containsDefault { return "", nil, nil, nil // do not run analyzer } } } cs := client.ContentStore() is := client.ImageService() // Analyze layers and get prioritized files aOpts := []analyzer.Option{analyzer.WithSpecOpts(getSpecOpts(clicontext))} if clicontext.Bool("wait-on-signal") && clicontext.Bool("terminal") { return "", nil, nil, fmt.Errorf("wait-on-signal can't be used with terminal flag") } if clicontext.Bool("wait-on-signal") { aOpts = append(aOpts, analyzer.WithWaitOnSignal()) } else { aOpts = append(aOpts, analyzer.WithPeriod(time.Duration(clicontext.Int("period"))*time.Second)) } if clicontext.Bool("terminal") { if !clicontext.Bool("i") { return "", nil, nil, fmt.Errorf("terminal flag must be specified with \"-i\"") } aOpts = append(aOpts, analyzer.WithTerminal()) } if clicontext.Bool("i") { aOpts = append(aOpts, analyzer.WithStdin()) } recordOut, err := analyzer.Analyze(ctx, client, srcRef, aOpts...) if err != nil { return "", nil, nil, err } // Parse record file srcImg, err := is.Get(ctx, srcRef) if err != nil { return "", nil, nil, err } manifestDesc, err := containerdutil.ManifestDesc(ctx, cs, srcImg.Target, platforms.DefaultStrict()) if err != nil { return "", nil, nil, err } p, err := content.ReadBlob(ctx, cs, manifestDesc) if err != nil { return "", nil, nil, err } var manifest ocispec.Manifest if err := json.Unmarshal(p, &manifest); err != nil { return "", nil, nil, err } // TODO: this should be indexed by layer "index" (not "digest") layerLogs := make(map[digest.Digest][]string, len(manifest.Layers)) ra, err := cs.ReaderAt(ctx, ocispec.Descriptor{Digest: recordOut}) if err != nil { return "", nil, nil, err } defer ra.Close() dec := json.NewDecoder(io.NewSectionReader(ra, 0, ra.Size())) added := make(map[digest.Digest]map[string]struct{}, len(manifest.Layers)) for dec.More() { var e recorder.Entry if err := dec.Decode(&e); err != nil { return "", nil, nil, err } if *e.LayerIndex < len(manifest.Layers) && e.ManifestDigest == manifestDesc.Digest.String() { dgst := manifest.Layers[*e.LayerIndex].Digest if added[dgst] == nil { added[dgst] = map[string]struct{}{} } if _, ok := added[dgst][e.Path]; !ok { added[dgst][e.Path] = struct{}{} layerLogs[dgst] = append(layerLogs[dgst], e.Path) } } } // Create a converter wrapper for skipping layer conversion. This skip occurs // if "reuse" option is specified, the source layer is already valid estargz // and no access occur to that layer. var excludes []digest.Digest layerOpts := make(map[digest.Digest][]estargz.Option, len(manifest.Layers)) for _, desc := range manifest.Layers { if layerLog, ok := layerLogs[desc.Digest]; ok && len(layerLog) > 0 { layerOpts[desc.Digest] = []estargz.Option{estargz.WithPrioritizedFiles(layerLog)} } else if clicontext.Bool("reuse") && isReusableESGZLayer(ctx, desc, cs) { excludes = append(excludes, desc.Digest) // reuse layer without conversion } } return recordOut, layerOpts, excludeWrapper(excludes), nil } func isReusableESGZLayer(ctx context.Context, desc ocispec.Descriptor, cs content.Store) bool { dgstStr, ok := desc.Annotations[estargz.TOCJSONDigestAnnotation] if !ok { return false } tocdgst, err := digest.Parse(dgstStr) if err != nil { return false } ra, err := cs.ReaderAt(ctx, desc) if err != nil { return false } defer ra.Close() r, err := estargz.Open(io.NewSectionReader(ra, 0, desc.Size), estargz.WithDecompressors(new(zstdchunked.Decompressor))) if err != nil { return false } if _, err := r.VerifyTOC(tocdgst); err != nil { return false } return true } func excludeWrapper(excludes []digest.Digest) func(converter.ConvertFunc) converter.ConvertFunc { return func(convertFunc converter.ConvertFunc) converter.ConvertFunc { return func(ctx context.Context, cs content.Store, desc ocispec.Descriptor) (*ocispec.Descriptor, error) { for _, e := range excludes { if e == desc.Digest { logrus.Warnf("reusing %q without conversion", e) return nil, nil } } return convertFunc(ctx, cs, desc) } } } func logWrapper(convertFunc converter.ConvertFunc) converter.ConvertFunc { return func(ctx context.Context, cs content.Store, desc ocispec.Descriptor) (*ocispec.Descriptor, error) { logrus.WithField("digest", desc.Digest).Infof("converting...") return convertFunc(ctx, cs, desc) } } stargz-snapshotter-0.12.0/cmd/ctr-remote/commands/rpull.go000066400000000000000000000105251426301527400235750ustar00rootroot00000000000000/* 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 commands import ( "context" "fmt" "github.com/containerd/containerd" "github.com/containerd/containerd/cmd/ctr/commands" "github.com/containerd/containerd/cmd/ctr/commands/content" "github.com/containerd/containerd/images" "github.com/containerd/containerd/log" "github.com/containerd/containerd/snapshots" fsconfig "github.com/containerd/stargz-snapshotter/fs/config" "github.com/containerd/stargz-snapshotter/fs/source" "github.com/containerd/stargz-snapshotter/ipfs" httpapi "github.com/ipfs/go-ipfs-http-client" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/urfave/cli" ) const ( remoteSnapshotterName = "stargz" skipContentVerifyOpt = "skip-content-verify" ) // RpullCommand is a subcommand to pull an image from a registry levaraging stargz snapshotter var RpullCommand = cli.Command{ Name: "rpull", Usage: "pull an image from a registry levaraging stargz snapshotter", ArgsUsage: "[flags] ", 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 command. `, Flags: append(append(commands.RegistryFlags, commands.LabelFlag, cli.BoolFlag{ Name: skipContentVerifyOpt, Usage: "Skip content verification for layers contained in this image.", }, cli.BoolFlag{ Name: "ipfs", Usage: "Pull image from IPFS. Specify an IPFS CID as a reference. (experimental)", }, ), commands.SnapshotterFlags...), Action: func(context *cli.Context) error { var ( ref = context.Args().First() config = &rPullConfig{} ) if ref == "" { return fmt.Errorf("please provide an image reference to pull") } client, ctx, cancel, err := commands.NewClient(context) if err != nil { return err } defer cancel() ctx, done, err := client.WithLease(ctx) if err != nil { return err } defer done(ctx) fc, err := content.NewFetchConfig(ctx, context) if err != nil { return err } config.FetchConfig = fc if context.Bool(skipContentVerifyOpt) { config.skipVerify = true } if context.Bool("ipfs") { ipfsClient, err := httpapi.NewLocalApi() if err != nil { return err } r, err := ipfs.NewResolver(ipfsClient, ipfs.ResolverOptions{ Scheme: "ipfs", }) if err != nil { return err } config.Resolver = r } config.snapshotter = remoteSnapshotterName if sn := context.String("snapshotter"); sn != "" { config.snapshotter = sn } return pull(ctx, client, ref, config) }, } type rPullConfig struct { *content.FetchConfig skipVerify bool snapshotter string } func pull(ctx context.Context, client *containerd.Client, ref string, config *rPullConfig) error { pCtx := ctx h := images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { if desc.MediaType != images.MediaTypeDockerSchema1Manifest { fmt.Printf("fetching %v... %v\n", desc.Digest.String()[:15], desc.MediaType) } return nil, nil }) var snOpts []snapshots.Opt if config.skipVerify { log.G(pCtx).WithField("image", ref).Warn("content verification disabled") snOpts = append(snOpts, snapshots.WithLabels(map[string]string{ fsconfig.TargetSkipVerifyLabel: "true", })) } log.G(pCtx).WithField("image", ref).Debug("fetching") labels := commands.LabelArgs(config.Labels) if _, err := client.Pull(pCtx, ref, []containerd.RemoteOpt{ containerd.WithPullLabels(labels), containerd.WithResolver(config.Resolver), containerd.WithImageHandler(h), containerd.WithSchema1Conversion, containerd.WithPullUnpack, containerd.WithPullSnapshotter(config.snapshotter, snOpts...), containerd.WithImageHandlerWrapper(source.AppendDefaultLabelsHandlerWrapper(ref, 10*1024*1024)), }...); err != nil { return err } return nil } stargz-snapshotter-0.12.0/cmd/ctr-remote/main.go000066400000000000000000000035131426301527400215610ustar00rootroot00000000000000/* 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" "os" "github.com/containerd/containerd/cmd/ctr/app" "github.com/containerd/containerd/pkg/seed" "github.com/containerd/stargz-snapshotter/cmd/ctr-remote/commands" "github.com/urfave/cli" ) func init() { seed.WithTimeAndRand() } func main() { customCommands := []cli.Command{ commands.RpullCommand, commands.OptimizeCommand, commands.ConvertCommand, commands.GetTOCDigestCommand, commands.IPFSPushCommand, } app := app.New() for i := range app.Commands { if app.Commands[i].Name == "images" { sc := map[string]cli.Command{} for _, subcmd := range customCommands { sc[subcmd.Name] = subcmd } // First, replace duplicated subcommands for j := range app.Commands[i].Subcommands { for name, subcmd := range sc { if name == app.Commands[i].Subcommands[j].Name { app.Commands[i].Subcommands[j] = subcmd delete(sc, name) } } } // Next, append all new sub commands for _, subcmd := range sc { app.Commands[i].Subcommands = append(app.Commands[i].Subcommands, subcmd) } break } } app.Commands = append(app.Commands, commands.FanotifyCommand) if err := app.Run(os.Args); err != nil { fmt.Fprintf(os.Stderr, "ctr-remote: %v\n", err) os.Exit(1) } } stargz-snapshotter-0.12.0/cmd/go.mod000066400000000000000000000027461426301527400173420ustar00rootroot00000000000000module github.com/containerd/stargz-snapshotter/cmd go 1.16 require ( github.com/containerd/containerd v1.6.6 github.com/containerd/go-cni v1.1.6 github.com/containerd/stargz-snapshotter v0.12.0 github.com/containerd/stargz-snapshotter/estargz v0.12.0 github.com/containerd/stargz-snapshotter/ipfs v0.12.0 github.com/coreos/go-systemd/v22 v22.3.2 github.com/docker/go-metrics v0.0.1 github.com/goccy/go-json v0.9.8 github.com/hashicorp/go-multierror v1.1.1 github.com/ipfs/go-ipfs-http-client v0.4.0 github.com/ipfs/interface-go-ipfs-core v0.7.0 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 github.com/pelletier/go-toml v1.9.5 github.com/rs/xid v1.4.0 github.com/sirupsen/logrus v1.8.1 github.com/urfave/cli v1.22.5 go.etcd.io/bbolt v1.3.6 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a google.golang.org/grpc v1.47.0 k8s.io/cri-api v0.25.0-alpha.2 ) replace ( // Import local packages. github.com/containerd/stargz-snapshotter => ../ github.com/containerd/stargz-snapshotter/estargz => ../estargz github.com/containerd/stargz-snapshotter/ipfs => ../ipfs // Temporary fork for avoiding importing patent-protected code: https://github.com/hashicorp/golang-lru/issues/73 github.com/hashicorp/golang-lru => github.com/ktock/golang-lru v0.5.5-0.20211029085301-ec551be6f75c ) stargz-snapshotter-0.12.0/cmd/go.sum000066400000000000000000007270511426301527400173720ustar00rootroot00000000000000bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo= 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 v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= 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/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= 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/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8/go.mod h1:CzsSbkDixRphAF5hS6wbMKq0eI6ccJRb7/A0M6JBnwg= github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= github.com/Microsoft/hcsshim v0.8.20/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= github.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= github.com/Microsoft/hcsshim v0.8.23/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01nnU2M8jKDg= github.com/Microsoft/hcsshim v0.9.2/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= github.com/Microsoft/hcsshim v0.9.3 h1:k371PzBuRrz2b+ebGuI2nVgVhgsVX60jMfSw80NECxo= github.com/Microsoft/hcsshim v0.9.3/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/Stebalien/go-bitfield v0.0.1/go.mod h1:GNjFpasyUVkHMsfEOk8EFLJ9syQ6SI+XWrX9Wf2XH0s= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= 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-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alecthomas/units v0.0.0-20210927113745-59d0afb8317a/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= github.com/alexflint/go-filemutex v1.1.0/go.mod h1:7P4iRhttt/nUvUOrYIhcpMzv2G6CY9UnI16Z+UJqRyk= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/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.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= github.com/btcsuite/btcd v0.0.0-20190523000118-16327141da8c/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.0.0-20190605094302-a0d1e3e36d50/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.21.0-beta h1:At9hIZdJW0s9E/fAz28nrz6AmcNlSVucCH796ZteX1M= github.com/btcsuite/btcd v0.21.0-beta/go.mod h1:ZSWyehm27aAuS9bvkATT+Xte3hjHZ+MRgMY/8NJ7K94= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= 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.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/cilium/ebpf v0.7.0 h1:1k/q3ATgxSXRdrmPfH8d7YK0GfqVsEKZAX9dQZvs56k= github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= 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-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= github.com/containerd/aufs v1.0.0/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E= github.com/containerd/btrfs v0.0.0-20210316141732-918d888fb676/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= github.com/containerd/btrfs v1.0.0/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI= github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= github.com/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBdAP4= github.com/containerd/cgroups v1.0.3/go.mod h1:/ofk34relqNjSGyqPrmEULrO4Sc8LJhvJmWbUCUKqj8= github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.9/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c= github.com/containerd/containerd v1.5.8/go.mod h1:YdFSv5bTFLpG2HIYmfqDpSYYTDX+mc5qtSuYx1YUb/s= github.com/containerd/containerd v1.6.1/go.mod h1:1nJz5xCZPusx6jJU8Frfct988y0NpumIq9ODB0kLtoE= github.com/containerd/containerd v1.6.6 h1:xJNPhbrmz8xAMDNoVjHy9YHtWwEQNS+CDkcIRh7t8Y0= github.com/containerd/containerd v1.6.6/go.mod h1:ZoP1geJldzCVY3Tonoz7b1IXk8rIX0Nltt5QE4OMNk0= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= github.com/containerd/continuity v0.2.2/go.mod h1:pWygW9u7LtS1o4N/Tn0FoCFDIXZ7rxcMX7HX1Dmibvk= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= github.com/containerd/fifo v1.0.0 h1:6PirWBr9/L7GDamKr+XM0IeUFXu5mf3M/BPpH9gaLBU= github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU= github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk= github.com/containerd/go-cni v1.1.0/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA= github.com/containerd/go-cni v1.1.3/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA= github.com/containerd/go-cni v1.1.6 h1:el5WPymG5nRRLQF1EfB97FWob4Tdc8INg8RZMaXWZlo= github.com/containerd/go-cni v1.1.6/go.mod h1:BWtoWl5ghVymxu6MBjg79W9NZrCRyHIdUtk4cauMe34= github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= github.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= github.com/containerd/go-runc v1.0.0 h1:oU+lLv1ULm5taqgV/CJivypVODI4SUz1znWjv3nNYS0= github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0= github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA= github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow= github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms= github.com/containerd/imgcrypt v1.1.3/go.mod h1:/TPA1GIDXMzbj01yd8pIbQiLdQxed5ue1wb8bP7PQu4= github.com/containerd/imgcrypt v1.1.4/go.mod h1:LorQnPtzL/T0IyCeftcsMEO7AqxUDbdO8j/tSUpgxvo= github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= github.com/containerd/ttrpc v1.1.0 h1:GbtyLRxb0gOLR0TYQWt3O6B0NvT8tMdorEHqIQo/lWI= github.com/containerd/ttrpc v1.1.0/go.mod h1:XX4ZTnoOId4HklF4edwc4DcqskFZuvXB1Evzy5KFQpQ= github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= github.com/containerd/typeurl v1.0.2 h1:Chlt8zIieDbzQFzXzAeBEF92KhExuE4p9p92/QmY7aY= github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s= github.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2/go.mod h1:8IgZOBdv8fAgXddBT4dBXJPtxyRsejFIpXoklgxgEjw= github.com/containerd/zfs v0.0.0-20210301145711-11e8f1707f62/go.mod h1:A9zfAbMlQwE+/is6hi0Xw8ktpL+6glmqZYtevJgaB8Y= github.com/containerd/zfs v0.0.0-20210315114300-dde8f0fda960/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= github.com/containerd/zfs v0.0.0-20210324211415-d5c4544f0433/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/cni v1.0.1/go.mod h1:AKuhXbN5EzmD4yTNtfSsX3tPcmtrBI6QcRV0NiNt15Y= github.com/containernetworking/cni v1.1.1 h1:ky20T7c0MvKvbMOwS/FrlbNwjEoqJEUUYfsL4b0mc4k= github.com/containernetworking/cni v1.1.1/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw= github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= github.com/containernetworking/plugins v1.0.1/go.mod h1:QHCfGpaTwYTbbH+nZXKVTxNBDZcxSOplJT5ico8/FLE= github.com/containernetworking/plugins v1.1.1 h1:+AGfFigZ5TiQH00vhR8qPeSatj53eNGz0C1d3wVYlHE= github.com/containernetworking/plugins v1.1.1/go.mod h1:Sr5TH/eBsGLXK/h71HeLfX19sZPp3ry5uHSkI4LPxV8= github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= github.com/containers/ocicrypt v1.1.2/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= github.com/containers/ocicrypt v1.1.3/go.mod h1:xpdkbVAuaH3WzbEabUd5yDsl9SwJA5pABH85425Es2g= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3 h1:HVTnpeuvF6Owjd5mniCL8DEXo7uYXdQEmOP4FJbV5tg= github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3/go.mod h1:p1d6YEZWvFzEh4KLyvBcVSnrfNDDvK2zfK/4x2v/4pE= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cskr/pubsub v1.0.2 h1:vlOzMhl6PFn60gRlTQQsIfVwaPB/B/8MziK8FhEPt/0= github.com/cskr/pubsub v1.0.2/go.mod h1:/8MzYXk/NJAz782G8RPkFzXTZVu63VotefPnR9TIRis= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4= github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= github.com/dgraph-io/badger v1.5.5-0.20190226225317-8115aed38f8f/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ= github.com/dgraph-io/badger v1.6.0-rc1/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgraph-io/badger v1.6.1/go.mod h1:FRmFw3uxvcpa8zG3Rxs0th+hCLIuaQg8HlNV5bjgnuU= github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-farm v0.0.0-20190104051053-3adb47b1fb0f/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v20.10.17+incompatible h1:eO2KS7ZFeov5UJeaDmIs1NFEDRf32PaqRpvoEkKBy5M= github.com/docker/cli v20.10.17+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.7+incompatible h1:Z6O9Nhsjv+ayUEeI1IojKbYcsGdgYSNqxe1s2MYzUhQ= github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= github.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56WZ2Pwu/fKWtKuZB0o= github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= 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.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= 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-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= 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.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5 h1:BBso6MBKW8ncyZLv37o+KNyy0HrrHgfnOaGQC2qvN+A= github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5/go.mod h1:JpoxHjuQauoxiFMl1ie8Xc/7TfLuMZ5eOCONd1sUBHg= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/flynn/noise v0.0.0-20180327030543-2492fe189ae6/go.mod h1:1i71OnUq3iUe1ma7Lr6yG6/rjvM3emb6yoL7xLFzcVQ= github.com/flynn/noise v1.0.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= 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/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= 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.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/goccy/go-json v0.9.8 h1:DxXB6MLd6yyel7CLph8EwNIonUtVZd3Ue5iRcL4DQCE= github.com/goccy/go-json v0.9.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e h1:BWhy2j3IXJhjCbC68FptL43tDKIq8FladmaTs3Xs7Z8= github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro= github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= github.com/gogo/googleapis v1.4.0 h1:zgVt4UpGxcqVOw97aRGxT4svlcmdK35fynLNctY32zI= github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 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/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/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-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= 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/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= 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.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.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.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.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.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= 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/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 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/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= 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.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.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.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/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.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= 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/martian/v3 v3.1.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-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 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.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/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.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4= github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/gxed/go-shellwords v1.0.3/go.mod h1:N7paucT91ByIjmVJHhvoarjoQnmsi3Jd3vH7VqgtMxQ= github.com/gxed/hashland/keccakpg v0.0.1/go.mod h1:kRzw3HkwxFU1mpmPP8v1WyQzwdGfmKFJ6tItnhQ67kU= github.com/gxed/hashland/murmur3 v0.0.1/go.mod h1:KjXop02n4/ckmZSnY2+HKcLud/tcmvhST0bie/0lS48= github.com/hanwen/go-fuse/v2 v2.1.1-0.20220112183258-f57e95bda82d h1:ibbzF2InxMOS+lLCphY9PHNKPURDUBNKaG6ErSq8gJQ= github.com/hanwen/go-fuse/v2 v2.1.1-0.20220112183258-f57e95bda82d/go.mod h1:B1nGE/6RBFyBRC1RRnf23UpwCdyJ31eukw34oAKukAc= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 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.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ= github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/huin/goupnp v1.0.0 h1:wg75sLpL6DZqwHQN6E1Cfk6mtfzS45z8OV+ic+DtHRo= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= 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.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9FCCAVRQ= github.com/ipfs/bbloom v0.0.1/go.mod h1:oqo8CVWsJFMOZqTglBG4wydCE4IQA/G2/SEofB0rjUI= github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= github.com/ipfs/go-bitfield v1.0.0/go.mod h1:N/UiujQy+K+ceU1EF5EkVd1TNqevLrCQMIcAEPrdtus= github.com/ipfs/go-bitswap v0.1.0/go.mod h1:FFJEf18E9izuCqUtHxbWEvq+reg7o4CW5wSAE1wsxj0= github.com/ipfs/go-bitswap v0.1.2/go.mod h1:qxSWS4NXGs7jQ6zQvoPY3+NmOfHHG47mhkiLzBpJQIs= github.com/ipfs/go-bitswap v0.1.8/go.mod h1:TOWoxllhccevbWFUR2N7B1MTSVVge1s6XSMiCSA4MzM= github.com/ipfs/go-bitswap v0.3.4/go.mod h1:4T7fvNv/LmOys+21tnLzGKncMeeXUYUd1nUiJ2teMvI= github.com/ipfs/go-bitswap v0.5.1/go.mod h1:P+ckC87ri1xFLvk74NlXdP0Kj9RmWAh4+H78sC6Qopo= github.com/ipfs/go-bitswap v0.6.0 h1:f2rc6GZtoSFhEIzQmddgGiel9xntj02Dg0ZNf2hSC+w= github.com/ipfs/go-bitswap v0.6.0/go.mod h1:Hj3ZXdOC5wBJvENtdqsixmzzRukqd8EHLxZLZc3mzRA= github.com/ipfs/go-block-format v0.0.1/go.mod h1:DK/YYcsSUIVAFNwo/KZCdIIbpN0ROH/baNLgayt4pFc= github.com/ipfs/go-block-format v0.0.2/go.mod h1:AWR46JfpcObNfg3ok2JHDUfdiHRgWhJgCQF+KIgOPJY= github.com/ipfs/go-block-format v0.0.3 h1:r8t66QstRp/pd/or4dpnbVfXT5Gt7lOqRvC+/dDTpMc= github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk= github.com/ipfs/go-blockservice v0.1.0/go.mod h1:hzmMScl1kXHg3M2BjTymbVPjv627N7sYcvYaKbop39M= github.com/ipfs/go-blockservice v0.1.4/go.mod h1:OTZhFpkgY48kNzbgyvcexW9cHrpjBYIjSR0KoDOFOLU= github.com/ipfs/go-blockservice v0.2.1/go.mod h1:k6SiwmgyYgs4M/qt+ww6amPeUH9EISLRBnvUurKJhi8= github.com/ipfs/go-blockservice v0.3.0 h1:cDgcZ+0P0Ih3sl8+qjFr2sVaMdysg/YZpLj5WJ8kiiw= github.com/ipfs/go-blockservice v0.3.0/go.mod h1:P5ppi8IHDC7O+pA0AlGTF09jruB2h+oP3wVVaZl8sfk= github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.4/go.mod h1:4LLaPOQwmk5z9LBgQnpkivrx8BJjUyGwTXCd5Xfj6+M= github.com/ipfs/go-cid v0.0.5/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67FexhXog= github.com/ipfs/go-cid v0.0.6/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= github.com/ipfs/go-cid v0.0.7/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= github.com/ipfs/go-cid v0.1.0 h1:YN33LQulcRHjfom/i25yoOZR4Telp1Hr/2RU3d0PnC0= github.com/ipfs/go-cid v0.1.0/go.mod h1:rH5/Xv83Rfy8Rw6xG+id3DYAMUVmem1MowoKwdXmN2o= github.com/ipfs/go-datastore v0.0.1/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= github.com/ipfs/go-datastore v0.0.5/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= github.com/ipfs/go-datastore v0.1.0/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= github.com/ipfs/go-datastore v0.1.1/go.mod h1:w38XXW9kVFNp57Zj5knbKWM2T+KOZCGDRVNdgPHtbHw= github.com/ipfs/go-datastore v0.3.1/go.mod h1:w38XXW9kVFNp57Zj5knbKWM2T+KOZCGDRVNdgPHtbHw= github.com/ipfs/go-datastore v0.4.0/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= github.com/ipfs/go-datastore v0.4.1/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= github.com/ipfs/go-datastore v0.4.4/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= github.com/ipfs/go-datastore v0.4.5/go.mod h1:eXTcaaiN6uOlVCLS9GjJUJtlvJfM3xk23w3fyfrmmJs= github.com/ipfs/go-datastore v0.5.0 h1:rQicVCEacWyk4JZ6G5bD9TKR7lZEG1MWcG7UdWYrFAU= github.com/ipfs/go-datastore v0.5.0/go.mod h1:9zhEApYMTl17C8YDp7JmU7sQZi2/wqiYh73hakZ90Bk= github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk= github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps= github.com/ipfs/go-ds-badger v0.0.2/go.mod h1:Y3QpeSFWQf6MopLTiZD+VT6IC1yZqaGmjvRcKeSGij8= github.com/ipfs/go-ds-badger v0.0.5/go.mod h1:g5AuuCGmr7efyzQhLL8MzwqcauPojGPUaHzfGTzuE3s= github.com/ipfs/go-ds-badger v0.2.1/go.mod h1:Tx7l3aTph3FMFrRS838dcSJh+jjA7cX9DrGVwx/NOwE= github.com/ipfs/go-ds-badger v0.2.3/go.mod h1:pEYw0rgg3FIrywKKnL+Snr+w/LjJZVMTBRn4FS6UHUk= github.com/ipfs/go-ds-leveldb v0.0.1/go.mod h1:feO8V3kubwsEF22n0YRQCffeb79OOYIykR4L04tMOYc= github.com/ipfs/go-ds-leveldb v0.4.1/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s= github.com/ipfs/go-ds-leveldb v0.4.2/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s= github.com/ipfs/go-fetcher v1.5.0/go.mod h1:5pDZ0393oRF/fHiLmtFZtpMNBQfHOYNPtryWedVuSWE= github.com/ipfs/go-fetcher v1.6.1/go.mod h1:27d/xMV8bodjVs9pugh/RCjjK2OZ68UgAMspMdingNo= github.com/ipfs/go-ipfs-blockstore v0.0.1/go.mod h1:d3WClOmRQKFnJ0Jz/jj/zmksX0ma1gROTlovZKBmN08= github.com/ipfs/go-ipfs-blockstore v0.1.0/go.mod h1:5aD0AvHPi7mZc6Ci1WCAhiBQu2IsfTduLl+422H6Rqw= github.com/ipfs/go-ipfs-blockstore v0.1.4/go.mod h1:Jxm3XMVjh6R17WvxFEiyKBLUGr86HgIYJW/D/MwqeYQ= github.com/ipfs/go-ipfs-blockstore v0.2.1/go.mod h1:jGesd8EtCM3/zPgx+qr0/feTXGUeRai6adgwC+Q+JvE= github.com/ipfs/go-ipfs-blockstore v1.2.0 h1:n3WTeJ4LdICWs/0VSfjHrlqpPpl6MZ+ySd3j8qz0ykw= github.com/ipfs/go-ipfs-blockstore v1.2.0/go.mod h1:eh8eTFLiINYNSNawfZOC7HOxNTxpB1PFuA5E1m/7exE= github.com/ipfs/go-ipfs-blocksutil v0.0.1 h1:Eh/H4pc1hsvhzsQoMEP3Bke/aW5P5rVM1IWFJMcGIPQ= github.com/ipfs/go-ipfs-blocksutil v0.0.1/go.mod h1:Yq4M86uIOmxmGPUHv/uI7uKqZNtLb449gwKqXjIsnRk= github.com/ipfs/go-ipfs-chunker v0.0.1 h1:cHUUxKFQ99pozdahi+uSC/3Y6HeRpi9oTeUHbE27SEw= github.com/ipfs/go-ipfs-chunker v0.0.1/go.mod h1:tWewYK0we3+rMbOh7pPFGDyypCtvGcBFymgY4rSDLAw= github.com/ipfs/go-ipfs-cmds v0.7.0 h1:0lEldmB7C83RxIOer38Sv1ob6wIoCAIEOaxiYgcv7wA= github.com/ipfs/go-ipfs-cmds v0.7.0/go.mod h1:y0bflH6m4g6ary4HniYt98UqbrVnRxmRarzeMdLIUn0= github.com/ipfs/go-ipfs-config v0.5.3 h1:3GpI/xR9FoJNTjU6YvCMRbYyEi0dBVY5UtlUTcNRlSA= github.com/ipfs/go-ipfs-config v0.5.3/go.mod h1:nSLCFtlaL+2rbl3F+9D4gQZQbT1LjRKx7TJg/IHz6oM= github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= github.com/ipfs/go-ipfs-delay v0.0.1 h1:r/UXYyRcddO6thwOnhiznIAiSvxMECGgtv35Xs1IeRQ= github.com/ipfs/go-ipfs-delay v0.0.1/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= github.com/ipfs/go-ipfs-ds-help v0.0.1/go.mod h1:gtP9xRaZXqIQRh1HRpp595KbBEdgqWFxefeVKOV8sxo= github.com/ipfs/go-ipfs-ds-help v0.1.1/go.mod h1:SbBafGJuGsPI/QL3j9Fc5YPLeAu+SzOkI0gFwAg+mOs= github.com/ipfs/go-ipfs-ds-help v1.1.0 h1:yLE2w9RAsl31LtfMt91tRZcrx+e61O5mDxFRR994w4Q= github.com/ipfs/go-ipfs-ds-help v1.1.0/go.mod h1:YR5+6EaebOhfcqVCyqemItCLthrpVNot+rsOU/5IatU= github.com/ipfs/go-ipfs-exchange-interface v0.0.1/go.mod h1:c8MwfHjtQjPoDyiy9cFquVtVHkO9b9Ob3FG91qJnWCM= github.com/ipfs/go-ipfs-exchange-interface v0.1.0 h1:TiMekCrOGQuWYtZO3mf4YJXDIdNgnKWZ9IE3fGlnWfo= github.com/ipfs/go-ipfs-exchange-interface v0.1.0/go.mod h1:ych7WPlyHqFvCi/uQI48zLZuAWVP5iTQPXEfVaw5WEI= github.com/ipfs/go-ipfs-exchange-offline v0.0.1/go.mod h1:WhHSFCVYX36H/anEKQboAzpUws3x7UeEGkzQc3iNkM0= github.com/ipfs/go-ipfs-exchange-offline v0.1.1/go.mod h1:vTiBRIbzSwDD0OWm+i3xeT0mO7jG2cbJYatp3HPk5XY= github.com/ipfs/go-ipfs-exchange-offline v0.2.0 h1:2PF4o4A7W656rC0RxuhUace997FTcDTcIQ6NoEtyjAI= github.com/ipfs/go-ipfs-exchange-offline v0.2.0/go.mod h1:HjwBeW0dvZvfOMwDP0TSKXIHf2s+ksdP4E3MLDRtLKY= github.com/ipfs/go-ipfs-files v0.0.3/go.mod h1:INEFm0LL2LWXBhNJ2PMIIb2w45hpXgPjNoE7yA8Y1d4= github.com/ipfs/go-ipfs-files v0.0.8/go.mod h1:wiN/jSG8FKyk7N0WyctKSvq3ljIa2NNTiZB55kpTdOs= github.com/ipfs/go-ipfs-files v0.1.1 h1:/MbEowmpLo9PJTEQk16m9rKzUHjeP4KRU9nWJyJO324= github.com/ipfs/go-ipfs-files v0.1.1/go.mod h1:8xkIrMWH+Y5P7HvJ4Yc5XWwIW2e52dyXUiC0tZyjDbM= github.com/ipfs/go-ipfs-http-client v0.4.0 h1:LNuVbFoKfCohCmcNImml3byM3PpTxTT7RPrv/UoDFkI= github.com/ipfs/go-ipfs-http-client v0.4.0/go.mod h1:NXzPUKt/QVCuR74a8angJCGOSLPImNi5LqaTxIep/70= github.com/ipfs/go-ipfs-posinfo v0.0.1 h1:Esoxj+1JgSjX0+ylc0hUmJCOv6V2vFoZiETLR6OtpRs= github.com/ipfs/go-ipfs-posinfo v0.0.1/go.mod h1:SwyeVP+jCwiDu0C313l/8jg6ZxM0qqtlt2a0vILTc1A= github.com/ipfs/go-ipfs-pq v0.0.1/go.mod h1:LWIqQpqfRG3fNc5XsnIhz/wQ2XXGyugQwls7BgUmUfY= github.com/ipfs/go-ipfs-pq v0.0.2 h1:e1vOOW6MuOwG2lqxcLA+wEn93i/9laCY8sXAw76jFOY= github.com/ipfs/go-ipfs-pq v0.0.2/go.mod h1:LWIqQpqfRG3fNc5XsnIhz/wQ2XXGyugQwls7BgUmUfY= github.com/ipfs/go-ipfs-routing v0.1.0/go.mod h1:hYoUkJLyAUKhF58tysKpids8RNDPO42BVMgK5dNsoqY= github.com/ipfs/go-ipfs-routing v0.2.1 h1:E+whHWhJkdN9YeoHZNj5itzc+OR292AJ2uE9FFiW0BY= github.com/ipfs/go-ipfs-routing v0.2.1/go.mod h1:xiNNiwgjmLqPS1cimvAw6EyB9rkVDbiocA4yY+wRNLM= github.com/ipfs/go-ipfs-util v0.0.1/go.mod h1:spsl5z8KUnrve+73pOhSVZND1SIxPW5RyBCNzQxlJBc= github.com/ipfs/go-ipfs-util v0.0.2 h1:59Sswnk1MFaiq+VcaknX7aYEyGyGDAA73ilhEK2POp8= github.com/ipfs/go-ipfs-util v0.0.2/go.mod h1:CbPtkWJzjLdEcezDns2XYaehFVNXG9zrdrtMecczcsQ= github.com/ipfs/go-ipld-cbor v0.0.2/go.mod h1:wTBtrQZA3SoFKMVkp6cn6HMRteIB1VsmHA0AQFOn7Nc= github.com/ipfs/go-ipld-cbor v0.0.3/go.mod h1:wTBtrQZA3SoFKMVkp6cn6HMRteIB1VsmHA0AQFOn7Nc= github.com/ipfs/go-ipld-cbor v0.0.5 h1:ovz4CHKogtG2KB/h1zUp5U0c/IzZrL435rCh5+K/5G8= github.com/ipfs/go-ipld-cbor v0.0.5/go.mod h1:BkCduEx3XBCO6t2Sfo5BaHzuok7hbhdMm9Oh8B2Ftq4= github.com/ipfs/go-ipld-format v0.0.1/go.mod h1:kyJtbkDALmFHv3QR6et67i35QzO3S0dCDnkOJhcZkms= github.com/ipfs/go-ipld-format v0.0.2/go.mod h1:4B6+FM2u9OJ9zCV+kSbgFAZlOrv1Hqbf0INGQgiKf9k= github.com/ipfs/go-ipld-format v0.2.0/go.mod h1:3l3C1uKoadTPbeNfrDi+xMInYKlx2Cvg1BuydPSdzQs= github.com/ipfs/go-ipld-format v0.3.0/go.mod h1:co/SdBE8h99968X0hViiw1MNlh6fvxxnHpvVLnH7jSM= github.com/ipfs/go-ipld-format v0.4.0 h1:yqJSaJftjmjc9jEOFYlpkwOLVKv68OD27jFLlSghBlQ= github.com/ipfs/go-ipld-format v0.4.0/go.mod h1:co/SdBE8h99968X0hViiw1MNlh6fvxxnHpvVLnH7jSM= github.com/ipfs/go-ipld-legacy v0.1.0 h1:wxkkc4k8cnvIGIjPO0waJCe7SHEyFgl+yQdafdjGrpA= github.com/ipfs/go-ipld-legacy v0.1.0/go.mod h1:86f5P/srAmh9GcIcWQR9lfFLZPrIyyXQeVlOWeeWEuI= github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM= github.com/ipfs/go-log v1.0.2/go.mod h1:1MNjMxe0u6xvJZgeqbJ8vdo2TKaGwZ1a0Bpza+sr2Sk= github.com/ipfs/go-log v1.0.3/go.mod h1:OsLySYkwIbiSUR/yBTdv1qPtcE4FW3WPWk/ewz9Ru+A= github.com/ipfs/go-log v1.0.4/go.mod h1:oDCg2FkjogeFOhqqb+N39l2RpTNPL6F/StPkB3kPgcs= github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8= github.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo= github.com/ipfs/go-log/v2 v2.0.2/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= github.com/ipfs/go-log/v2 v2.0.3/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= github.com/ipfs/go-log/v2 v2.0.5/go.mod h1:eZs4Xt4ZUJQFM3DlanGhy7TkwwawCZcSByscwkWG+dw= github.com/ipfs/go-log/v2 v2.1.1/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHntrv9KM= github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g= github.com/ipfs/go-log/v2 v2.3.0 h1:31Re/cPqFHpsRHgyVwjWADPoF0otB1WrjTy8ZFYwEZU= github.com/ipfs/go-log/v2 v2.3.0/go.mod h1:QqGoj30OTpnKaG/LKTGTxoP2mmQtjVMEnK72gynbe/g= github.com/ipfs/go-merkledag v0.2.3/go.mod h1:SQiXrtSts3KGNmgOzMICy5c0POOpUNQLvB3ClKnBAlk= github.com/ipfs/go-merkledag v0.3.2/go.mod h1:fvkZNNZixVW6cKSZ/JfLlON5OlgTXNdRLz0p6QG/I2M= github.com/ipfs/go-merkledag v0.5.1/go.mod h1:cLMZXx8J08idkp5+id62iVftUQV+HlYJ3PIhDfZsjA4= github.com/ipfs/go-merkledag v0.6.0 h1:oV5WT2321tS4YQVOPgIrWHvJ0lJobRTerU+i9nmUCuA= github.com/ipfs/go-merkledag v0.6.0/go.mod h1:9HSEwRd5sV+lbykiYP+2NC/3o6MZbKNaa4hfNcH5iH0= github.com/ipfs/go-metrics-interface v0.0.1 h1:j+cpbjYvu4R8zbleSs36gvB7jR+wsL2fGD6n0jO4kdg= github.com/ipfs/go-metrics-interface v0.0.1/go.mod h1:6s6euYU4zowdslK0GKHmqaIZ3j/b/tL7HTWtJ4VPgWY= github.com/ipfs/go-path v0.1.1/go.mod h1:vC8q4AKOtrjJz2NnllIrmr2ZbGlF5fW2OKKyhV9ggb0= github.com/ipfs/go-path v0.3.0 h1:tkjga3MtpXyM5v+3EbRvOHEoo+frwi4oumw5K+KYWyA= github.com/ipfs/go-path v0.3.0/go.mod h1:NOScsVgxfC/eIw4nz6OiGwK42PjaSJ4Y/ZFPn1Xe07I= github.com/ipfs/go-peertaskqueue v0.1.0/go.mod h1:Jmk3IyCcfl1W3jTW3YpghSwSEC6IJ3Vzz/jUmWw8Z0U= github.com/ipfs/go-peertaskqueue v0.1.1/go.mod h1:Jmk3IyCcfl1W3jTW3YpghSwSEC6IJ3Vzz/jUmWw8Z0U= github.com/ipfs/go-peertaskqueue v0.2.0/go.mod h1:5/eNrBEbtSKWCG+kQK8K8fGNixoYUnr+P7jivavs9lY= github.com/ipfs/go-peertaskqueue v0.7.0 h1:VyO6G4sbzX80K58N60cCaHsSsypbUNs1GjO5seGNsQ0= github.com/ipfs/go-peertaskqueue v0.7.0/go.mod h1:M/akTIE/z1jGNXMU7kFB4TeSEFvj68ow0Rrb04donIU= github.com/ipfs/go-todocounter v0.0.1/go.mod h1:l5aErvQc8qKE2r7NDMjmq5UNAvuZy0rC8BHOplkWvZ4= github.com/ipfs/go-unixfs v0.2.4/go.mod h1:SUdisfUjNoSDzzhGVxvCL9QO/nKdwXdr+gbMUdqcbYw= github.com/ipfs/go-unixfs v0.3.1 h1:LrfED0OGfG98ZEegO4/xiprx2O+yS+krCMQSp7zLVv8= github.com/ipfs/go-unixfs v0.3.1/go.mod h1:h4qfQYzghiIc8ZNFKiLMFWOTzrWIAtzYQ59W/pCFf1o= github.com/ipfs/go-unixfsnode v1.1.2/go.mod h1:5dcE2x03pyjHk4JjamXmunTMzz+VUtqvPwZjIEkfV6s= github.com/ipfs/go-verifcid v0.0.1 h1:m2HI7zIuR5TFyQ1b79Da5N9dnnCP1vcu2QqawmWlK2E= github.com/ipfs/go-verifcid v0.0.1/go.mod h1:5Hrva5KBeIog4A+UpqlaIU+DEstipcJYQQZc0g37pY0= github.com/ipfs/interface-go-ipfs-core v0.7.0 h1:7tb+2upz8oCcjIyjo1atdMk+P+u7wPmI+GksBlLE8js= github.com/ipfs/interface-go-ipfs-core v0.7.0/go.mod h1:lF27E/nnSPbylPqKVXGZghal2hzifs3MmjyiEjnc9FY= github.com/ipfs/iptb v1.4.0 h1:YFYTrCkLMRwk/35IMyC6+yjoQSHTEcNcefBStLJzgvo= github.com/ipfs/iptb v1.4.0/go.mod h1:1rzHpCYtNp87/+hTxG5TfCVn/yMY3dKnLn8tBiMfdmg= github.com/ipfs/iptb-plugins v0.3.0 h1:C1rpq1o5lUZtaAOkLIox5akh6ba4uk/3RwWc6ttVxw0= github.com/ipfs/iptb-plugins v0.3.0/go.mod h1:5QtOvckeIw4bY86gSH4fgh3p3gCSMn3FmIKr4gaBncA= github.com/ipld/go-codec-dagpb v1.3.0/go.mod h1:ga4JTU3abYApDC3pZ00BC2RSvC3qfBb9MSJkMLSwnhA= github.com/ipld/go-codec-dagpb v1.3.2 h1:MZQUIjanHXXfDuYmtWYT8nFbqfFsZuyHClj6VDmSXr4= github.com/ipld/go-codec-dagpb v1.3.2/go.mod h1:ga4JTU3abYApDC3pZ00BC2RSvC3qfBb9MSJkMLSwnhA= github.com/ipld/go-ipld-prime v0.9.1-0.20210324083106-dc342a9917db/go.mod h1:KvBLMr4PX1gWptgkzRjVZCrLmSGcZCb/jioOQwCqZN8= github.com/ipld/go-ipld-prime v0.11.0 h1:jD/b/22R7CSL+F9xNffcexs+wO0Ji/TfwXO/TWck+70= github.com/ipld/go-ipld-prime v0.11.0/go.mod h1:+WIAkokurHmZ/KwzDOMUuoeJgaRQktHtEaLglS3ZeV8= github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= github.com/j-keck/arping v1.0.2/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw= github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= github.com/jackpal/go-nat-pmp v1.0.1/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jbenet/go-cienv v0.0.0-20150120210510-1bb1476777ec/go.mod h1:rGaEvXB4uRSZMmzKNLoXvTu1sfx+1kv/DojUlPrSZGs= github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= github.com/jbenet/go-temp-err-catcher v0.0.0-20150120210811-aac704a3f4f2/go.mod h1:8GXXJV31xl8whumTzdZsTt3RnUIiPqzkyf7mxToRCMs= github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk= github.com/jbenet/goprocess v0.0.0-20160826012719-b497e2f366b8/go.mod h1:Ly/wlsjFq/qrU3Rar62tu1gASgGw6chQbSh/XgIIXCY= github.com/jbenet/goprocess v0.1.3/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o= github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= 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/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= 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.8/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/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/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 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/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 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/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.15.7 h1:7cgTQxJCU/vy+oP/E3B9RGbQTgbiVzIJWIKOLoAsPok= github.com/klauspost/compress v1.15.7/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 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/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.2/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/koron/go-ssdp v0.0.0-20180514024734-4a0ed625a78b/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d h1:68u9r4wEvL3gYg2jvAOgROwZ3H+Y3hIDk4tbbmIjcYQ= github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= 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 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= 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/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= 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/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/ktock/golang-lru v0.5.5-0.20211029085301-ec551be6f75c h1:xNyENjfLL0OOOIOLCPnDTLN4whgVMcak4Ep9CUEwbtI= github.com/ktock/golang-lru v0.5.5-0.20211029085301-ec551be6f75c/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/libp2p/go-addr-util v0.0.1/go.mod h1:4ac6O7n9rIAKB1dnd+s8IbbMXkt+oBpzX4/+RACcnlQ= github.com/libp2p/go-addr-util v0.0.2 h1:7cWK5cdA5x72jX0g8iLrQWm5TRJZ6CzGdPEhWj7plWU= github.com/libp2p/go-addr-util v0.0.2/go.mod h1:Ecd6Fb3yIuLzq4bD7VcywcVSBtefcAwnUISBM3WG15E= github.com/libp2p/go-buffer-pool v0.0.1/go.mod h1:xtyIz9PMobb13WaxR6Zo1Pd1zXJKYg0a8KiIvDp3TzQ= github.com/libp2p/go-buffer-pool v0.0.2 h1:QNK2iAFa8gjAe1SPz6mHSMuCcjs+X1wlHzeOSqcmlfs= github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= github.com/libp2p/go-conn-security-multistream v0.1.0/go.mod h1:aw6eD7LOsHEX7+2hJkDxw1MteijaVcI+/eP2/x3J1xc= github.com/libp2p/go-conn-security-multistream v0.2.0/go.mod h1:hZN4MjlNetKD3Rq5Jb/P5ohUnFLNzEAR4DLSzpn2QLU= github.com/libp2p/go-conn-security-multistream v0.2.1/go.mod h1:cR1d8gA0Hr59Fj6NhaTpFhJZrjSYuNmhpT2r25zYR70= github.com/libp2p/go-eventbus v0.0.2/go.mod h1:Hr/yGlwxA/stuLnpMiu82lpNKpvRy3EaJxPu40XYOwk= github.com/libp2p/go-eventbus v0.1.0/go.mod h1:vROgu5cs5T7cv7POWlWxBaVLxfSegC5UGQf8A2eEmx4= github.com/libp2p/go-eventbus v0.2.1 h1:VanAdErQnpTioN2TowqNcOijf6YwhuODe4pPKSDpxGc= github.com/libp2p/go-eventbus v0.2.1/go.mod h1:jc2S4SoEVPP48H9Wpzm5aiGwUCBMfGhVhhBjyhhCJs8= github.com/libp2p/go-flow-metrics v0.0.1/go.mod h1:Iv1GH0sG8DtYN3SVJ2eG221wMiNpZxBdp967ls1g+k8= github.com/libp2p/go-flow-metrics v0.0.3/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs= github.com/libp2p/go-libp2p v0.1.0/go.mod h1:6D/2OBauqLUoqcADOJpn9WbKqvaM07tDw68qHM0BxUM= github.com/libp2p/go-libp2p v0.1.1/go.mod h1:I00BRo1UuUSdpuc8Q2mN7yDF/oTUTRAX6JWpTiK9Rp8= github.com/libp2p/go-libp2p v0.3.1/go.mod h1:e6bwxbdYH1HqWTz8faTChKGR0BjPc8p+6SyP8GTTR7Y= github.com/libp2p/go-libp2p v0.4.0/go.mod h1:9EsEIf9p2UDuwtPd0DwJsAl0qXVxgAnuDGRvHbfATfI= github.com/libp2p/go-libp2p v0.6.1/go.mod h1:CTFnWXogryAHjXAKEbOf1OWY+VeAP3lDMZkfEI5sT54= github.com/libp2p/go-libp2p v0.7.0/go.mod h1:hZJf8txWeCduQRDC/WSqBGMxaTHCOYHt2xSU1ivxn0k= github.com/libp2p/go-libp2p v0.7.4/go.mod h1:oXsBlTLF1q7pxr+9w6lqzS1ILpyHsaBPniVO7zIHGMw= github.com/libp2p/go-libp2p v0.8.1/go.mod h1:QRNH9pwdbEBpx5DTJYg+qxcVaDMAz3Ee/qDKwXujH5o= github.com/libp2p/go-libp2p v0.13.0/go.mod h1:pM0beYdACRfHO1WcJlp65WXyG2A6NqYM+t2DTVAJxMo= github.com/libp2p/go-libp2p v0.14.3 h1:NST/bkwGSyaOt+stT7GBHY1+OqaANZ+QUOjpsmjXVC4= github.com/libp2p/go-libp2p v0.14.3/go.mod h1:d12V4PdKbpL0T1/gsUNN8DfgMuRPDX8bS2QxCZlwRH0= github.com/libp2p/go-libp2p-autonat v0.1.0/go.mod h1:1tLf2yXxiE/oKGtDwPYWTSYG3PtvYlJmg7NeVtPRqH8= github.com/libp2p/go-libp2p-autonat v0.1.1/go.mod h1:OXqkeGOY2xJVWKAGV2inNF5aKN/djNA3fdpCWloIudE= github.com/libp2p/go-libp2p-autonat v0.2.0/go.mod h1:DX+9teU4pEEoZUqR1PiMlqliONQdNbfzE1C718tcViI= github.com/libp2p/go-libp2p-autonat v0.2.1/go.mod h1:MWtAhV5Ko1l6QBsHQNSuM6b1sRkXrpk0/LqCr+vCVxI= github.com/libp2p/go-libp2p-autonat v0.2.2/go.mod h1:HsM62HkqZmHR2k1xgX34WuWDzk/nBwNHoeyyT4IWV6A= github.com/libp2p/go-libp2p-autonat v0.4.0/go.mod h1:YxaJlpr81FhdOv3W3BTconZPfhaYivRdf53g+S2wobk= github.com/libp2p/go-libp2p-autonat v0.4.2 h1:YMp7StMi2dof+baaxkbxaizXjY1RPvU71CXfxExzcUU= github.com/libp2p/go-libp2p-autonat v0.4.2/go.mod h1:YxaJlpr81FhdOv3W3BTconZPfhaYivRdf53g+S2wobk= github.com/libp2p/go-libp2p-autonat-svc v0.1.0/go.mod h1:fqi8Obl/z3R4PFVLm8xFtZ6PBL9MlV/xumymRFkKq5A= github.com/libp2p/go-libp2p-blankhost v0.1.1/go.mod h1:pf2fvdLJPsC1FsVrNP3DUUvMzUts2dsLLBEpo1vW1ro= github.com/libp2p/go-libp2p-blankhost v0.1.3/go.mod h1:KML1//wiKR8vuuJO0y3LUd1uLv+tlkGTAr3jC0S5cLg= github.com/libp2p/go-libp2p-blankhost v0.1.4/go.mod h1:oJF0saYsAXQCSfDq254GMNmLNz6ZTHTOvtF4ZydUvwU= github.com/libp2p/go-libp2p-blankhost v0.2.0/go.mod h1:eduNKXGTioTuQAUcZ5epXi9vMl+t4d8ugUBRQ4SqaNQ= github.com/libp2p/go-libp2p-circuit v0.1.0/go.mod h1:Ahq4cY3V9VJcHcn1SBXjr78AbFkZeIRmfunbA7pmFh8= github.com/libp2p/go-libp2p-circuit v0.1.1/go.mod h1:Ahq4cY3V9VJcHcn1SBXjr78AbFkZeIRmfunbA7pmFh8= github.com/libp2p/go-libp2p-circuit v0.1.3/go.mod h1:Xqh2TjSy8DD5iV2cCOMzdynd6h8OTBGoV1AWbWor3qM= github.com/libp2p/go-libp2p-circuit v0.1.4/go.mod h1:CY67BrEjKNDhdTk8UgBX1Y/H5c3xkAcs3gnksxY7osU= github.com/libp2p/go-libp2p-circuit v0.2.1/go.mod h1:BXPwYDN5A8z4OEY9sOfr2DUQMLQvKt/6oku45YUmjIo= github.com/libp2p/go-libp2p-circuit v0.4.0/go.mod h1:t/ktoFIUzM6uLQ+o1G6NuBl2ANhBKN9Bc8jRIk31MoA= github.com/libp2p/go-libp2p-connmgr v0.1.1/go.mod h1:wZxh8veAmU5qdrfJ0ZBLcU8oJe9L82ciVP/fl1VHjXk= github.com/libp2p/go-libp2p-core v0.0.1/go.mod h1:g/VxnTZ/1ygHxH3dKok7Vno1VfpvGcGip57wjTU4fco= github.com/libp2p/go-libp2p-core v0.0.2/go.mod h1:9dAcntw/n46XycV4RnlBq3BpgrmyUi9LuoTNdPrbUco= github.com/libp2p/go-libp2p-core v0.0.3/go.mod h1:j+YQMNz9WNSkNezXOsahp9kwZBKBvxLpKD316QWSJXE= github.com/libp2p/go-libp2p-core v0.0.4/go.mod h1:jyuCQP356gzfCFtRKyvAbNkyeuxb7OlyhWZ3nls5d2I= github.com/libp2p/go-libp2p-core v0.0.6/go.mod h1:0d9xmaYAVY5qmbp/fcgxHT3ZJsLjYeYPMJAUKpaCHrE= github.com/libp2p/go-libp2p-core v0.2.0/go.mod h1:X0eyB0Gy93v0DZtSYbEM7RnMChm9Uv3j7yRXjO77xSI= github.com/libp2p/go-libp2p-core v0.2.2/go.mod h1:8fcwTbsG2B+lTgRJ1ICZtiM5GWCWZVoVrLaDRvIRng0= github.com/libp2p/go-libp2p-core v0.2.3/go.mod h1:GqhyQqyIAPsxFYXHMjfXgMv03lxsvM0mFzuYA9Ib42A= github.com/libp2p/go-libp2p-core v0.2.4/go.mod h1:STh4fdfa5vDYr0/SzYYeqnt+E6KfEV5VxfIrm0bcI0g= github.com/libp2p/go-libp2p-core v0.3.0/go.mod h1:ACp3DmS3/N64c2jDzcV429ukDpicbL6+TrrxANBjPGw= github.com/libp2p/go-libp2p-core v0.3.1/go.mod h1:thvWy0hvaSBhnVBaW37BvzgVV68OUhgJJLAa6almrII= github.com/libp2p/go-libp2p-core v0.4.0/go.mod h1:49XGI+kc38oGVwqSBhDEwytaAxgZasHhFfQKibzTls0= github.com/libp2p/go-libp2p-core v0.5.0/go.mod h1:49XGI+kc38oGVwqSBhDEwytaAxgZasHhFfQKibzTls0= github.com/libp2p/go-libp2p-core v0.5.1/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= github.com/libp2p/go-libp2p-core v0.5.2/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= github.com/libp2p/go-libp2p-core v0.5.4/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= github.com/libp2p/go-libp2p-core v0.5.5/go.mod h1:vj3awlOr9+GMZJFH9s4mpt9RHHgGqeHCopzbYKZdRjM= github.com/libp2p/go-libp2p-core v0.5.6/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= github.com/libp2p/go-libp2p-core v0.5.7/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= github.com/libp2p/go-libp2p-core v0.6.0/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= github.com/libp2p/go-libp2p-core v0.7.0/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= github.com/libp2p/go-libp2p-core v0.8.0/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= github.com/libp2p/go-libp2p-core v0.8.1/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= github.com/libp2p/go-libp2p-core v0.8.2/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= github.com/libp2p/go-libp2p-core v0.8.5/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= github.com/libp2p/go-libp2p-core v0.8.6 h1:3S8g006qG6Tjpj1JdRK2S+TWc2DJQKX/RG9fdLeiLSU= github.com/libp2p/go-libp2p-core v0.8.6/go.mod h1:dgHr0l0hIKfWpGpqAMbpo19pen9wJfdCGv51mTmdpmM= github.com/libp2p/go-libp2p-crypto v0.1.0/go.mod h1:sPUokVISZiy+nNuTTH/TY+leRSxnFj/2GLjtOTW90hI= github.com/libp2p/go-libp2p-daemon v0.2.2/go.mod h1:kyrpsLB2JeNYR2rvXSVWyY0iZuRIMhqzWR3im9BV6NQ= github.com/libp2p/go-libp2p-discovery v0.1.0/go.mod h1:4F/x+aldVHjHDHuX85x1zWoFTGElt8HnoDzwkFZm29g= github.com/libp2p/go-libp2p-discovery v0.2.0/go.mod h1:s4VGaxYMbw4+4+tsoQTqh7wfxg97AEdo4GYBt6BadWg= github.com/libp2p/go-libp2p-discovery v0.3.0/go.mod h1:o03drFnz9BVAZdzC/QUQ+NeQOu38Fu7LJGEOK2gQltw= github.com/libp2p/go-libp2p-discovery v0.5.0/go.mod h1:+srtPIU9gDaBNu//UHvcdliKBIcr4SfDcm0/PfPJLug= github.com/libp2p/go-libp2p-kad-dht v0.2.1/go.mod h1:k7ONOlup7HKzQ68dE6lSnp07cdxdkmnRa+6B4Fh9/w0= github.com/libp2p/go-libp2p-kbucket v0.2.1/go.mod h1:/Rtu8tqbJ4WQ2KTCOMJhggMukOLNLNPY1EtEWWLxUvc= github.com/libp2p/go-libp2p-loggables v0.1.0 h1:h3w8QFfCt2UJl/0/NW4K829HX/0S4KD31PQ7m8UXXO8= github.com/libp2p/go-libp2p-loggables v0.1.0/go.mod h1:EyumB2Y6PrYjr55Q3/tiJ/o3xoDasoRYM7nOzEpoa90= github.com/libp2p/go-libp2p-mplex v0.2.0/go.mod h1:Ejl9IyjvXJ0T9iqUTE1jpYATQ9NM3g+OtR+EMMODbKo= github.com/libp2p/go-libp2p-mplex v0.2.1/go.mod h1:SC99Rxs8Vuzrf/6WhmH41kNn13TiYdAWNYHrwImKLnE= github.com/libp2p/go-libp2p-mplex v0.2.2/go.mod h1:74S9eum0tVQdAfFiKxAyKzNdSuLqw5oadDq7+L/FELo= github.com/libp2p/go-libp2p-mplex v0.2.3/go.mod h1:CK3p2+9qH9x+7ER/gWWDYJ3QW5ZxWDkm+dVvjfuG3ek= github.com/libp2p/go-libp2p-mplex v0.4.0/go.mod h1:yCyWJE2sc6TBTnFpjvLuEJgTSw/u+MamvzILKdX7asw= github.com/libp2p/go-libp2p-mplex v0.4.1/go.mod h1:cmy+3GfqfM1PceHTLL7zQzAAYaryDu6iPSC+CIb094g= github.com/libp2p/go-libp2p-nat v0.0.4/go.mod h1:N9Js/zVtAXqaeT99cXgTV9e75KpnWCvVOiGzlcHmBbY= github.com/libp2p/go-libp2p-nat v0.0.5/go.mod h1:1qubaE5bTZMJE+E/uu2URroMbzdubFz1ChgiN79yKPE= github.com/libp2p/go-libp2p-nat v0.0.6 h1:wMWis3kYynCbHoyKLPBEMu4YRLltbm8Mk08HGSfvTkU= github.com/libp2p/go-libp2p-nat v0.0.6/go.mod h1:iV59LVhB3IkFvS6S6sauVTSOrNEANnINbI/fkaLimiw= github.com/libp2p/go-libp2p-netutil v0.1.0 h1:zscYDNVEcGxyUpMd0JReUZTrpMfia8PmLKcKF72EAMQ= github.com/libp2p/go-libp2p-netutil v0.1.0/go.mod h1:3Qv/aDqtMLTUyQeundkKsA+YCThNdbQD54k3TqjpbFU= github.com/libp2p/go-libp2p-noise v0.1.1/go.mod h1:QDFLdKX7nluB7DEnlVPbz7xlLHdwHFA9HiohJRr3vwM= github.com/libp2p/go-libp2p-noise v0.2.0/go.mod h1:IEbYhBBzGyvdLBoxxULL/SGbJARhUeqlO8lVSREYu2Q= github.com/libp2p/go-libp2p-peer v0.2.0/go.mod h1:RCffaCvUyW2CJmG2gAWVqwePwW7JMgxjsHm7+J5kjWY= github.com/libp2p/go-libp2p-peerstore v0.1.0/go.mod h1:2CeHkQsr8svp4fZ+Oi9ykN1HBb6u0MOvdJ7YIsmcwtY= github.com/libp2p/go-libp2p-peerstore v0.1.3/go.mod h1:BJ9sHlm59/80oSkpWgr1MyY1ciXAXV397W6h1GH/uKI= github.com/libp2p/go-libp2p-peerstore v0.2.0/go.mod h1:N2l3eVIeAitSg3Pi2ipSrJYnqhVnMNQZo9nkSCuAbnQ= github.com/libp2p/go-libp2p-peerstore v0.2.1/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRjwRLBr4TYKfNgrUkOPA= github.com/libp2p/go-libp2p-peerstore v0.2.2/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRjwRLBr4TYKfNgrUkOPA= github.com/libp2p/go-libp2p-peerstore v0.2.6/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= github.com/libp2p/go-libp2p-peerstore v0.2.7 h1:83JoLxyR9OYTnNfB5vvFqvMUv/xDNa6NoPHnENhBsGw= github.com/libp2p/go-libp2p-peerstore v0.2.7/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= github.com/libp2p/go-libp2p-pnet v0.2.0/go.mod h1:Qqvq6JH/oMZGwqs3N1Fqhv8NVhrdYcO0BW4wssv21LA= github.com/libp2p/go-libp2p-pubsub v0.1.1/go.mod h1:ZwlKzRSe1eGvSIdU5bD7+8RZN/Uzw0t1Bp9R1znpR/Q= github.com/libp2p/go-libp2p-quic-transport v0.1.1/go.mod h1:wqG/jzhF3Pu2NrhJEvE+IE0NTHNXslOPn9JQzyCAxzU= github.com/libp2p/go-libp2p-quic-transport v0.10.0/go.mod h1:RfJbZ8IqXIhxBRm5hqUEJqjiiY8xmEuq3HUDS993MkA= github.com/libp2p/go-libp2p-record v0.1.0/go.mod h1:ujNc8iuE5dlKWVy6wuL6dd58t0n7xI4hAIl8pE6wu5Q= github.com/libp2p/go-libp2p-record v0.1.1 h1:ZJK2bHXYUBqObHX+rHLSNrM3M8fmJUlUHrodDPPATmY= github.com/libp2p/go-libp2p-record v0.1.1/go.mod h1:VRgKajOyMVgP/F0L5g3kH7SVskp17vFi2xheb5uMJtg= github.com/libp2p/go-libp2p-routing v0.1.0/go.mod h1:zfLhI1RI8RLEzmEaaPwzonRvXeeSHddONWkcTcB54nE= github.com/libp2p/go-libp2p-secio v0.1.0/go.mod h1:tMJo2w7h3+wN4pgU2LSYeiKPrfqBgkOsdiKK77hE7c8= github.com/libp2p/go-libp2p-secio v0.2.0/go.mod h1:2JdZepB8J5V9mBp79BmwsaPQhRPNN2NrnB2lKQcdy6g= github.com/libp2p/go-libp2p-secio v0.2.1/go.mod h1:cWtZpILJqkqrSkiYcDBh5lA3wbT2Q+hz3rJQq3iftD8= github.com/libp2p/go-libp2p-secio v0.2.2/go.mod h1:wP3bS+m5AUnFA+OFO7Er03uO1mncHG0uVwGrwvjYlNY= github.com/libp2p/go-libp2p-swarm v0.1.0/go.mod h1:wQVsCdjsuZoc730CgOvh5ox6K8evllckjebkdiY5ta4= github.com/libp2p/go-libp2p-swarm v0.2.1/go.mod h1:x07b4zkMFo2EvgPV2bMTlNmdQc8i+74Jjio7xGvsTgU= github.com/libp2p/go-libp2p-swarm v0.2.2/go.mod h1:fvmtQ0T1nErXym1/aa1uJEyN7JzaTNyBcHImCxRpPKU= github.com/libp2p/go-libp2p-swarm v0.2.3/go.mod h1:P2VO/EpxRyDxtChXz/VPVXyTnszHvokHKRhfkEgFKNM= github.com/libp2p/go-libp2p-swarm v0.2.8/go.mod h1:JQKMGSth4SMqonruY0a8yjlPVIkb0mdNSwckW7OYziM= github.com/libp2p/go-libp2p-swarm v0.3.0/go.mod h1:hdv95GWCTmzkgeJpP+GK/9D9puJegb7H57B5hWQR5Kk= github.com/libp2p/go-libp2p-swarm v0.4.0/go.mod h1:XVFcO52VoLoo0eitSxNQWYq4D6sydGOweTOAjJNraCw= github.com/libp2p/go-libp2p-swarm v0.5.0/go.mod h1:sU9i6BoHE0Ve5SKz3y9WfKrh8dUat6JknzUehFx8xW4= github.com/libp2p/go-libp2p-testing v0.0.2/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= github.com/libp2p/go-libp2p-testing v0.0.3/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= github.com/libp2p/go-libp2p-testing v0.0.4/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= github.com/libp2p/go-libp2p-testing v0.1.0/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0= github.com/libp2p/go-libp2p-testing v0.1.1/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0= github.com/libp2p/go-libp2p-testing v0.1.2-0.20200422005655-8775583591d8/go.mod h1:Qy8sAncLKpwXtS2dSnDOP8ktexIAHKu+J+pnZOFZLTc= github.com/libp2p/go-libp2p-testing v0.3.0/go.mod h1:efZkql4UZ7OVsEfaxNHZPzIehtsBXMrXnCfJIgDti5g= github.com/libp2p/go-libp2p-testing v0.4.0 h1:PrwHRi0IGqOwVQWR3xzgigSlhlLfxgfXgkHxr77EghQ= github.com/libp2p/go-libp2p-testing v0.4.0/go.mod h1:Q+PFXYoiYFN5CAEG2w3gLPEzotlKsNSbKQ/lImlOWF0= github.com/libp2p/go-libp2p-tls v0.1.3/go.mod h1:wZfuewxOndz5RTnCAxFliGjvYSDA40sKitV4c50uI1M= github.com/libp2p/go-libp2p-transport-upgrader v0.1.1/go.mod h1:IEtA6or8JUbsV07qPW4r01GnTenLW4oi3lOPbUMGJJA= github.com/libp2p/go-libp2p-transport-upgrader v0.2.0/go.mod h1:mQcrHj4asu6ArfSoMuyojOdjx73Q47cYD7s5+gZOlns= github.com/libp2p/go-libp2p-transport-upgrader v0.3.0/go.mod h1:i+SKzbRnvXdVbU3D1dwydnTmKRPXiAR/fyvi1dXuL4o= github.com/libp2p/go-libp2p-transport-upgrader v0.4.0/go.mod h1:J4ko0ObtZSmgn5BX5AmegP+dK3CSnU2lMCKsSq/EY0s= github.com/libp2p/go-libp2p-transport-upgrader v0.4.2/go.mod h1:NR8ne1VwfreD5VIWIU62Agt/J18ekORFU/j1i2y8zvk= github.com/libp2p/go-libp2p-yamux v0.2.0/go.mod h1:Db2gU+XfLpm6E4rG5uGCFX6uXA8MEXOxFcRoXUODaK8= github.com/libp2p/go-libp2p-yamux v0.2.1/go.mod h1:1FBXiHDk1VyRM1C0aez2bCfHQ4vMZKkAQzZbkSQt5fI= github.com/libp2p/go-libp2p-yamux v0.2.2/go.mod h1:lIohaR0pT6mOt0AZ0L2dFze9hds9Req3OfS+B+dv4qw= github.com/libp2p/go-libp2p-yamux v0.2.5/go.mod h1:Zpgj6arbyQrmZ3wxSZxfBmbdnWtbZ48OpsfmQVTErwA= github.com/libp2p/go-libp2p-yamux v0.2.7/go.mod h1:X28ENrBMU/nm4I3Nx4sZ4dgjZ6VhLEn0XhIoZ5viCwU= github.com/libp2p/go-libp2p-yamux v0.2.8/go.mod h1:/t6tDqeuZf0INZMTgd0WxIRbtK2EzI2h7HbFm9eAKI4= github.com/libp2p/go-libp2p-yamux v0.4.0/go.mod h1:+DWDjtFMzoAwYLVkNZftoucn7PelNoy5nm3tZ3/Zw30= github.com/libp2p/go-libp2p-yamux v0.5.0/go.mod h1:AyR8k5EzyM2QN9Bbdg6X1SkVVuqLwTGf0L4DFq9g6po= github.com/libp2p/go-libp2p-yamux v0.5.1/go.mod h1:dowuvDu8CRWmr0iqySMiSxK+W0iL5cMVO9S94Y6gkv4= github.com/libp2p/go-libp2p-yamux v0.5.4/go.mod h1:tfrXbyaTqqSU654GTvK3ocnSZL3BuHoeTSqhcel1wsE= github.com/libp2p/go-maddr-filter v0.0.4/go.mod h1:6eT12kSQMA9x2pvFQa+xesMKUBlj9VImZbj3B9FBH/Q= github.com/libp2p/go-maddr-filter v0.0.5/go.mod h1:Jk+36PMfIqCJhAnaASRH83bdAvfDRp/w6ENFaC9bG+M= github.com/libp2p/go-maddr-filter v0.1.0/go.mod h1:VzZhTXkMucEGGEOSKddrwGiOv0tUhgnKqNEmIAz/bPU= github.com/libp2p/go-mplex v0.0.3/go.mod h1:pK5yMLmOoBR1pNCqDlA2GQrdAVTMkqFalaTWe7l4Yd0= github.com/libp2p/go-mplex v0.1.0/go.mod h1:SXgmdki2kwCUlCCbfGLEgHjC4pFqhTp0ZoV6aiKgxDU= github.com/libp2p/go-mplex v0.1.1/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3SFXI1lk= github.com/libp2p/go-mplex v0.1.2/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3SFXI1lk= github.com/libp2p/go-mplex v0.2.0/go.mod h1:0Oy/A9PQlwBytDRp4wSkFnzHYDKcpLot35JQ6msjvYQ= github.com/libp2p/go-mplex v0.3.0/go.mod h1:0Oy/A9PQlwBytDRp4wSkFnzHYDKcpLot35JQ6msjvYQ= github.com/libp2p/go-msgio v0.0.2/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= github.com/libp2p/go-msgio v0.0.3/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= github.com/libp2p/go-msgio v0.0.4/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= github.com/libp2p/go-msgio v0.0.6 h1:lQ7Uc0kS1wb1EfRxO2Eir/RJoHkHn7t6o+EiwsYIKJA= github.com/libp2p/go-msgio v0.0.6/go.mod h1:4ecVB6d9f4BDSL5fqvPiC4A3KivjWn+Venn/1ALLMWA= github.com/libp2p/go-nat v0.0.3/go.mod h1:88nUEt0k0JD45Bk93NIwDqjlhiOwOoV36GchpcVc1yI= github.com/libp2p/go-nat v0.0.4/go.mod h1:Nmw50VAvKuk38jUBcmNh6p9lUJLoODbJRvYAa/+KSDo= github.com/libp2p/go-nat v0.0.5 h1:qxnwkco8RLKqVh1NmjQ+tJ8p8khNLFxuElYG/TwqW4Q= github.com/libp2p/go-nat v0.0.5/go.mod h1:B7NxsVNPZmRLvMOwiEO1scOSyjA56zxYAGv1yQgRkEU= github.com/libp2p/go-netroute v0.1.2/go.mod h1:jZLDV+1PE8y5XxBySEBgbuVAXbhtuHSdmLPL2n9MKbk= github.com/libp2p/go-netroute v0.1.3/go.mod h1:jZLDV+1PE8y5XxBySEBgbuVAXbhtuHSdmLPL2n9MKbk= github.com/libp2p/go-netroute v0.1.5/go.mod h1:V1SR3AaECRkEQCoFFzYwVYWvYIEtlxx89+O3qcpCl4A= github.com/libp2p/go-netroute v0.1.6 h1:ruPJStbYyXVYGQ81uzEDzuvbYRLKRrLvTYd33yomC38= github.com/libp2p/go-netroute v0.1.6/go.mod h1:AqhkMh0VuWmfgtxKPp3Oc1LdU5QSWS7wl0QLhSZqXxQ= github.com/libp2p/go-openssl v0.0.2/go.mod h1:v8Zw2ijCSWBQi8Pq5GAixw6DbFfa9u6VIYDXnvOXkc0= github.com/libp2p/go-openssl v0.0.3/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= github.com/libp2p/go-openssl v0.0.4/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= github.com/libp2p/go-openssl v0.0.5/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= github.com/libp2p/go-openssl v0.0.7 h1:eCAzdLejcNVBzP/iZM9vqHnQm+XyCEbSSIheIPRGNsw= github.com/libp2p/go-openssl v0.0.7/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= github.com/libp2p/go-reuseport v0.0.1/go.mod h1:jn6RmB1ufnQwl0Q1f+YxAj8isJgDCQzaaxIFYDhcYEA= github.com/libp2p/go-reuseport v0.0.2/go.mod h1:SPD+5RwGC7rcnzngoYC86GjPzjSywuQyMVAheVBD9nQ= github.com/libp2p/go-reuseport-transport v0.0.2/go.mod h1:YkbSDrvjUVDL6b8XqriyA20obEtsW9BLkuOUyQAOCbs= github.com/libp2p/go-reuseport-transport v0.0.3/go.mod h1:Spv+MPft1exxARzP2Sruj2Wb5JSyHNncjf1Oi2dEbzM= github.com/libp2p/go-reuseport-transport v0.0.4/go.mod h1:trPa7r/7TJK/d+0hdBLOCGvpQQVOU74OXbNCIMkufGw= github.com/libp2p/go-sockaddr v0.0.2/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= github.com/libp2p/go-sockaddr v0.1.0/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= github.com/libp2p/go-sockaddr v0.1.1 h1:yD80l2ZOdGksnOyHrhxDdTDFrf7Oy+v3FMVArIRgZxQ= github.com/libp2p/go-sockaddr v0.1.1/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= github.com/libp2p/go-stream-muxer v0.0.1/go.mod h1:bAo8x7YkSpadMTbtTaxGVHWUQsR/l5MEaHbKaliuT14= github.com/libp2p/go-stream-muxer-multistream v0.2.0/go.mod h1:j9eyPol/LLRqT+GPLSxvimPhNph4sfYfMoDPd7HkzIc= github.com/libp2p/go-stream-muxer-multistream v0.3.0/go.mod h1:yDh8abSIzmZtqtOt64gFJUXEryejzNb0lisTt+fAMJA= github.com/libp2p/go-tcp-transport v0.1.0/go.mod h1:oJ8I5VXryj493DEJ7OsBieu8fcg2nHGctwtInJVpipc= github.com/libp2p/go-tcp-transport v0.1.1/go.mod h1:3HzGvLbx6etZjnFlERyakbaYPdfjg2pWP97dFZworkY= github.com/libp2p/go-tcp-transport v0.2.0/go.mod h1:vX2U0CnWimU4h0SGSEsg++AzvBcroCGYw28kh94oLe0= github.com/libp2p/go-tcp-transport v0.2.1/go.mod h1:zskiJ70MEfWz2MKxvFB/Pv+tPIB1PpPUrHIWQ8aFw7M= github.com/libp2p/go-tcp-transport v0.2.3/go.mod h1:9dvr03yqrPyYGIEN6Dy5UvdJZjyPFvl1S/igQ5QD1SU= github.com/libp2p/go-testutil v0.1.0/go.mod h1:81b2n5HypcVyrCg/MJx4Wgfp/VHojytjVe/gLzZ2Ehc= github.com/libp2p/go-ws-transport v0.1.0/go.mod h1:rjw1MG1LU9YDC6gzmwObkPd/Sqwhw7yT74kj3raBFuo= github.com/libp2p/go-ws-transport v0.1.2/go.mod h1:dsh2Ld8F+XNmzpkaAijmg5Is+e9l6/1tK/6VFOdN69Y= github.com/libp2p/go-ws-transport v0.2.0/go.mod h1:9BHJz/4Q5A9ludYWKoGCFC5gUElzlHoKzu0yY9p/klM= github.com/libp2p/go-ws-transport v0.3.0/go.mod h1:bpgTJmRZAvVHrgHybCVyqoBmyLQ1fiZuEaBYusP5zsk= github.com/libp2p/go-ws-transport v0.4.0/go.mod h1:EcIEKqf/7GDjth6ksuS/6p7R49V4CBY6/E7R/iyhYUA= github.com/libp2p/go-yamux v1.2.2/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/libp2p/go-yamux v1.2.3/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/libp2p/go-yamux v1.3.0/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/libp2p/go-yamux v1.3.3/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/libp2p/go-yamux v1.3.5/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/libp2p/go-yamux v1.3.7/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= github.com/libp2p/go-yamux v1.4.0/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= github.com/libp2p/go-yamux v1.4.1/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= github.com/libp2p/go-yamux/v2 v2.0.0/go.mod h1:NVWira5+sVUIU6tu1JWvaRn1dRnG+cawOJiflsAM+7U= github.com/libp2p/go-yamux/v2 v2.2.0/go.mod h1:3So6P6TV6r75R9jiBpiIKgU/66lOarCZjqROGxzPpPQ= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo= github.com/lucas-clemente/quic-go v0.11.2/go.mod h1:PpMmPfPKO9nKJ/psF49ESTAGQSdfXxlg1otPbEB2nOw= github.com/lucas-clemente/quic-go v0.19.3/go.mod h1:ADXpNbTQjq1hIzCpB+y/k5iz4n4z4IwqoLb94Kh5Hu8= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk= github.com/marten-seemann/qtls v0.10.0/go.mod h1:UvMd1oaYDACI99/oZUYLzMCkBXQVT0aGm99sJhbT8hs= github.com/marten-seemann/qtls-go1-15 v0.1.1/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.12/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.28/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c/go.mod h1:0SQS9kMwD2VsyFEB++InYyBJroV/FRmBgcydeSUcJms= github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b/go.mod h1:lxPUiZwKoFL8DUUmalo2yJJUCxbPKtm8OKfqr2/FTNU= github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc/go.mod h1:cGKTAVKx4SxOuR/czcZ/E2RSJ3sfHs8FpHhQ5CWMf9s= 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/sha256-simd v0.0.0-20190131020904-2d45a736cd16/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= github.com/minio/sha256-simd v0.0.0-20190328051042-05b4dd3047e5/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= github.com/minio/sha256-simd v0.1.0/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 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-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= 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/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= github.com/moby/sys/signal v0.6.0 h1:aDpY94H8VlhTGa9sNYUFCFsMZIUh5wm0B6XkIoJj/iY= github.com/moby/sys/signal v0.6.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg= github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= github.com/moby/sys/symlink v0.2.0 h1:tk1rOM+Ljp0nFmfOIBtlV3rTDlWOwFRhjEeAhZB0nZc= github.com/moby/sys/symlink v0.2.0/go.mod h1:7uZVF2dqJjG/NsClqul95CqKOBRQyYSNnJ6BMgR/gFs= github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= 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/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/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/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= github.com/mr-tron/base58 v1.1.1/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= 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/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-base36 v0.1.0 h1:JR6TyF7JjGd3m6FbLU2cOxhC0Li8z8dLNGQ89tUg4F4= github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= github.com/multiformats/go-multiaddr v0.0.1/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.0.2/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.0.4/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.1.0/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo= github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4= github.com/multiformats/go-multiaddr v0.2.1/go.mod h1:s/Apk6IyxfvMjDafnhJgJ3/46z7tZ04iMk5wP4QMGGE= github.com/multiformats/go-multiaddr v0.2.2/go.mod h1:NtfXiOtHvghW9KojvtySjH5y0u0xW5UouOmQQrn6a3Y= github.com/multiformats/go-multiaddr v0.3.0/go.mod h1:dF9kph9wfJ+3VLAaeBqo9Of8x4fJxp6ggJGteB8HQTI= github.com/multiformats/go-multiaddr v0.3.1/go.mod h1:uPbspcUPd5AfaP6ql3ujFY+QWzmBD8uLLL4bXW0XfGc= github.com/multiformats/go-multiaddr v0.3.3/go.mod h1:lCKNGP1EQ1eZ35Za2wlqnabm9xQkib3fyB+nZXHLag0= github.com/multiformats/go-multiaddr v0.5.0 h1:i/JuOoVg4szYQ4YEzDGtb2h0o8M7CG/Yq6cGlcjWZpM= github.com/multiformats/go-multiaddr v0.5.0/go.mod h1:3KAxNkUqLTJ20AAwN4XVX4kZar+bR+gh4zgbfr3SNug= github.com/multiformats/go-multiaddr-dns v0.0.1/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= github.com/multiformats/go-multiaddr-dns v0.0.2/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= github.com/multiformats/go-multiaddr-dns v0.0.3/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= github.com/multiformats/go-multiaddr-dns v0.1.0/go.mod h1:01k2RAqtoXIuPa3DCavAE9/6jc6nM0H3EgZyfUhN2oY= github.com/multiformats/go-multiaddr-dns v0.2.0/go.mod h1:TJ5pr5bBO7Y1B18djPuRsVkduhQH2YqYSbxWJzYGdK0= github.com/multiformats/go-multiaddr-dns v0.3.1 h1:QgQgR+LQVt3NPTjbrLLpsaT2ufAA2y0Mkk+QRVJbW3A= github.com/multiformats/go-multiaddr-dns v0.3.1/go.mod h1:G/245BRQ6FJGmryJCrOuTdB37AMA5AMOVuO6NY3JwTk= github.com/multiformats/go-multiaddr-fmt v0.0.1/go.mod h1:aBYjqL4T/7j4Qx+R73XSv/8JsgnRFlf0w2KGLCmXl3Q= github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E= github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo= github.com/multiformats/go-multiaddr-net v0.0.1/go.mod h1:nw6HSxNmCIQH27XPGBuX+d1tnvM7ihcFwHMSstNAVUU= github.com/multiformats/go-multiaddr-net v0.1.0/go.mod h1:5JNbcfBOP4dnhoZOv10JJVkJO0pCCEf8mTnipAo2UZQ= github.com/multiformats/go-multiaddr-net v0.1.1/go.mod h1:5JNbcfBOP4dnhoZOv10JJVkJO0pCCEf8mTnipAo2UZQ= github.com/multiformats/go-multiaddr-net v0.1.2/go.mod h1:QsWt3XK/3hwvNxZJp92iMQKME1qHfpYmyIjFVsSOY6Y= github.com/multiformats/go-multiaddr-net v0.1.3/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= github.com/multiformats/go-multiaddr-net v0.1.4/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= github.com/multiformats/go-multiaddr-net v0.1.5/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= github.com/multiformats/go-multiaddr-net v0.2.0 h1:MSXRGN0mFymt6B1yo/6BPnIRpLPEnKgQNvVfCX5VDJk= github.com/multiformats/go-multiaddr-net v0.2.0/go.mod h1:gGdH3UXny6U3cKKYCvpXI5rnK7YaOIEOPVDI9tsJbEA= github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs= github.com/multiformats/go-multibase v0.0.3 h1:l/B6bJDQjvQ5G52jw4QGSYeOTZoAwIO77RblWplfIqk= github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= github.com/multiformats/go-multicodec v0.3.0/go.mod h1:qGGaQmioCDh+TeFOnxrbU0DaIPw8yFgAZgFG0V7p1qQ= github.com/multiformats/go-multicodec v0.4.1 h1:BSJbf+zpghcZMZrwTYBGwy0CPcVZGWiC72Cp8bBd4R4= github.com/multiformats/go-multicodec v0.4.1/go.mod h1:1Hj/eHRaVWSXiSNNfcEPcwZleTmdNP81xlxDLnWU9GQ= github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U= github.com/multiformats/go-multihash v0.0.5/go.mod h1:lt/HCbqlQwlPBz7lv0sQCdtfcMtlJvakRUn/0Ual8po= github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= github.com/multiformats/go-multihash v0.0.10/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= github.com/multiformats/go-multihash v0.0.14/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= github.com/multiformats/go-multihash v0.0.15/go.mod h1:D6aZrWNLFTV/ynMpKsNtB40mJzmCl4jb1alC0OvHiHg= github.com/multiformats/go-multihash v0.1.0 h1:CgAgwqk3//SVEw3T+6DqI4mWMyRuDwZtOWcJT0q9+EA= github.com/multiformats/go-multihash v0.1.0/go.mod h1:RJlXsxt6vHGaia+S8We0ErjhojtKzPP2AH4+kYM7k84= github.com/multiformats/go-multistream v0.1.0/go.mod h1:fJTiDfXJVmItycydCnNx4+wSzZ5NwG2FEVAI30fiovg= github.com/multiformats/go-multistream v0.1.1/go.mod h1:KmHZ40hzVxiaiwlj3MEbYgK9JFk2/9UktWZAF54Du38= github.com/multiformats/go-multistream v0.2.0/go.mod h1:5GZPQZbkWOLOn3J2y4Y99vVW7vOfsAflxARk3x14o6k= github.com/multiformats/go-multistream v0.2.1/go.mod h1:5GZPQZbkWOLOn3J2y4Y99vVW7vOfsAflxARk3x14o6k= github.com/multiformats/go-multistream v0.2.2 h1:TCYu1BHTDr1F/Qm75qwYISQdzGcRdC21nFgQW7l7GBo= github.com/multiformats/go-multistream v0.2.2/go.mod h1:UIcnm7Zuo8HKG+HkWgfQsGL+/MIEhyTqbODbIUwSXKs= github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.2/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.6 h1:gk85QWKxh3TazbLxED/NlDVv8+q+ReFJk7Y2W/KhfNY= github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 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/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/networkplumbing/go-nft v0.2.0/go.mod h1:HnnM+tYvlGAsMU7yoYwXEVLLiDW9gdMmb5HoGcwpuQs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= 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 h1:e/3Cwtogj0HA+25nMP1jCMDIf8RtRYbGwGGuBIFztkc= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= 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/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec= github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= github.com/opencontainers/runc v1.1.0/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= github.com/opencontainers/runc v1.1.2 h1:2VSZwLx5k/BfsBxMMipG/LYUnmqOD/BPkIVgQUcTlLw= github.com/opencontainers/runc v1.1.2/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 h1:3snG66yBm59tKhhSPQrQ/0bCrv1LQbKt40LnUPiUxdc= github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/opencontainers/selinux v1.10.1 h1:09LIPVRP3uuZGQvgR+SgMSNBd1Eb3vlRbGqQpoHsF8w= github.com/opencontainers/selinux v1.10.1/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= 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/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/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/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= 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/polydawn/refmt v0.0.0-20190221155625-df39d6c2d992/go.mod h1:uIp+gprXxxrWSjjklXD+mN4wed/tMfjMMmN/9+JsA9o= github.com/polydawn/refmt v0.0.0-20190408063855-01bf1e26dd14/go.mod h1:uIp+gprXxxrWSjjklXD+mN4wed/tMfjMMmN/9+JsA9o= github.com/polydawn/refmt v0.0.0-20190807091052-3d65705ee9f1/go.mod h1:uIp+gprXxxrWSjjklXD+mN4wed/tMfjMMmN/9+JsA9o= github.com/polydawn/refmt v0.0.0-20201211092308-30ac6d18308e h1:ZOcivgkkFRnjfoTcGsDq3UQYiBmekwLA+qg0OjyB/ls= github.com/polydawn/refmt v0.0.0-20201211092308-30ac6d18308e/go.mod h1:uIp+gprXxxrWSjjklXD+mN4wed/tMfjMMmN/9+JsA9o= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.8.0/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 v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 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.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34= github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/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-20190115171406-56726106282f/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-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.0/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.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/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.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/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.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw= github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI= github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg= github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw= github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y= github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q= github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ= github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I= github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0= github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk= github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8= github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= github.com/smartystreets/goconvey v0.0.0-20190222223459-a17d461953aa/go.mod h1:2RVY1rIf+2J2o/IM9+vPq9RzmHDSseB7FoXiSNIUsoU= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smola/gocompat v0.2.0/go.mod h1:1B0MlxbmoZNo3h8guHp8HztB3BSYR5itql9qtVc0ypY= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/spacemonkeygo/openssl v0.0.0-20181017203307-c2dcc5cca94a/go.mod h1:7AyxJNCJ7SBZ1MfVQCWD6Uqo2oubI2Eq2y2eqf+A5r0= github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 h1:RC6RW7j+1+HkWaX/Yh71Ee5ZHaHYt7ZP4sQgUrm6cDU= github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w0SWMsp6j9O/dk4/ZpIhL+3CkG8ofA2vuv7k+ltqUMc= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 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/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/R4aaNBc= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/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.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/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.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 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= github.com/texttheater/golang-levenshtein v0.0.0-20180516184445-d188e65d659e/go.mod h1:XDKHRm5ThF8YJjx001LtgelzsoaEcvnA7lVWz9EeX3g= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU= github.com/urfave/cli v1.22.5/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= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/warpfork/go-wish v0.0.0-20180510122957-5ad1f5abf436/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= github.com/warpfork/go-wish v0.0.0-20190328234359-8b3e70f8e830/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= github.com/warpfork/go-wish v0.0.0-20200122115046-b9ea61034e4a h1:G++j5e0OC488te356JvdhaM8YS6nMsjLAYF7JxCv07w= github.com/warpfork/go-wish v0.0.0-20200122115046-b9ea61034e4a/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= github.com/whyrusleeping/base32 v0.0.0-20170828182744-c30ac30633cc/go.mod h1:r45hJU7yEoA81k6MWNhpMj/kms0n14dkzkxYHoB96UM= github.com/whyrusleeping/cbor-gen v0.0.0-20200123233031-1cdf64d27158 h1:WXhVOwj2USAXB5oMDwRl3piOux2XMV9TANaYxXHdkoE= github.com/whyrusleeping/cbor-gen v0.0.0-20200123233031-1cdf64d27158/go.mod h1:Xj/M2wWU+QdTdRbu/L/1dIZY8/Wb2K9pAhtroQuxJJI= github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f h1:jQa4QT2UP9WYv2nzyawpKMOCl+Z/jW7djv2/J50lj9E= github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f/go.mod h1:p9UJB6dDgdPgMJZs7UjUOdulKyRr9fqkS+6JKAInPy8= github.com/whyrusleeping/go-ctrlnet v0.0.0-20180313164037-f564fbbdaa95/go.mod h1:SJqKCCPXRfBFCwXjfNT/skfsceF7+MBFLI2OrvuRA7g= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc/go.mod h1:bopw91TMyo8J3tvftk8xmU2kPmlrt4nScJQZU2hE5EM= github.com/whyrusleeping/go-logging v0.0.1/go.mod h1:lDPYj54zutzG1XYfHAhcc7oNXEburHQBn+Iqd4yS4vE= github.com/whyrusleeping/go-notifier v0.0.0-20170827234753-097c5d47330f/go.mod h1:cZNvX9cFybI01GriPRMXDtczuvUhgbcYr9iCGaNlRv8= github.com/whyrusleeping/mafmt v1.2.8/go.mod h1:faQJFPbLSxzD9xpA02ttW/tS9vZykNvXwGvqIpk20FA= github.com/whyrusleeping/mdns v0.0.0-20180901202407-ef14215e6b30/go.mod h1:j4l84WPFclQPj320J9gp0XwNKBb3U0zt5CBqjPp22G4= github.com/whyrusleeping/mdns v0.0.0-20190826153040-b9b60ed33aa9/go.mod h1:j4l84WPFclQPj320J9gp0XwNKBb3U0zt5CBqjPp22G4= github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7/go.mod h1:X2c0RVCI1eSUFI8eLcY3c0423ykwiUdxLJtkDvruhjI= github.com/whyrusleeping/timecache v0.0.0-20160911033111-cfcb2f1abfee/go.mod h1:m2aV4LZI4Aez7dP5PMyVKEHhUyEJ/RjmPEDOpDvudHg= github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 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.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= 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.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= 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.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0/go.mod h1:vEhqr0m4eTc+DWxfsXoXue2GBgV2uUwVznkGIHW/e5w= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0/go.mod h1:hO1KLR7jcKaDDKDkvI9dP/FIhpmna5lkqPUQdEjFAM8= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0/go.mod h1:keUU7UfnwWTWpJ+FWnyqmogPa82nuU5VUANFq49hlMY= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.3.0/go.mod h1:QNX1aly8ehqqX1LEa6YniTU7VY9I6R3X/oPxhGdTceE= go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs= go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.11.0/go.mod h1:QpEjXPrNQzrFDZgoTo49dgHR9RYRSrg3NAKnUGl9YpQ= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec= go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190225124518-7f87c0fbb88b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/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-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 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-20180702182130-06c8688daad7/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-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-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/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/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.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181106065722-10aee1819953/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-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/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-20190125091013-d26f9f9a57f3/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-20190227160552-c95aed5357e7/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-20190313220215-9f648a60d977/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-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/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-20190619014844-b5b0513f8c1b/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-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/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-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-20201006153459-a7d1128ccaa0/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-20201031054903-ff519b6c9102/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-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 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-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= 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-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-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-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/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-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/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-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/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-20190219092855-153ac476189d/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190302025703-b6889370fb10/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-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/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-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190524122548-abf6ff778158/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190526052359-791d8a0f4d09/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/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-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/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-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/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-20191022100944-742c48ecaeb7/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-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/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-20200217220822-9197077df867/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-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/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-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210903071746-97244b99971b/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-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220405210540-1e041c57c461/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 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.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.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/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-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/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-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181130052023-1c3d964395ce/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-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-20190328211700-ab21143f2384/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-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/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-20190624222133-a101b041ded4/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-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= 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-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/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-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-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 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-20200103221440-774c71fcf114/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-20200505023115-26f46d2f7ef8/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-20200616133436-c1934b75d054/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-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-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20200916195026-c9a70fc28ce3/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= 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/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.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/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 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-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= 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-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-20200117163144-32f20d992d24/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-20200423170343-7949de9c1215/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-20200513103714-09dca8ec2884/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-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 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-20200904004341-0bd0a958aa1d/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-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6 h1:FglFEfyj61zP3c6LgjmVHxYxZWXYul9oiS1EZqD5gLc= google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= 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.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.28.1/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.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8= google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= 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-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 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.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.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= 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 v1.0.0-20141024133853-64131543e789/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/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/src-d/go-cli.v0 v0.0.0-20181105080154-d492247bbc0d/go.mod h1:z+K8VcOYVYcSwSjGebuDL6176A1XskgbtNl64NSg+n8= gopkg.in/src-d/go-log.v1 v1.0.1/go.mod h1:GN34hKP0g305ysm2/hctJ0Y8nWP3zxXXJ8GFabTyABE= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 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.3/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-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/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.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= k8s.io/api v0.22.5/go.mod h1:mEhXyLaSD1qTOf40rRiKXkc+2iCem09rWLlFwhCEiAs= k8s.io/api v0.24.2 h1:g518dPU/L7VRLxWfcadQn2OnsiGWVOadTLpdnqgY2OI= k8s.io/api v0.24.2/go.mod h1:AHqbSkTm6YrQ0ObxjO3Pmp/ubFF/KuM7jU+3khoBsOg= k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= k8s.io/apimachinery v0.22.5/go.mod h1:xziclGKwuuJ2RM5/rSFQSYAj0zdbci3DH8kj+WvyN0U= k8s.io/apimachinery v0.24.2 h1:5QlH9SL2C8KMcrNJPor+LbXVTaZRReml7svPEh4OKDM= k8s.io/apimachinery v0.24.2/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= k8s.io/apiserver v0.22.5/go.mod h1:s2WbtgZAkTKt679sYtSudEQrTGWUSQAPe6MupLnlmaQ= k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= k8s.io/client-go v0.22.5/go.mod h1:cs6yf/61q2T1SdQL5Rdcjg9J1ElXSwbjSrW2vFImM4Y= k8s.io/client-go v0.24.2 h1:CoXFSf8if+bLEbinDqN9ePIDGzcLtqhfd6jpfnwGOFA= k8s.io/client-go v0.24.2/go.mod h1:zg4Xaoo+umDsfCWr4fCnmLEtQXyCNXCvJuSsglNcV30= k8s.io/code-generator v0.19.7/go.mod h1:lwEq3YnLYb/7uVXLorOJfxg+cUu2oihFhHZ0n9NIla0= k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= k8s.io/component-base v0.22.5/go.mod h1:VK3I+TjuF9eaa+Ln67dKxhGar5ynVbwnGrUiNF4MqCI= k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= k8s.io/cri-api v0.23.1/go.mod h1:REJE3PSU0h/LOV1APBrupxrEJqnoxZC8KWzkBUHwrK4= k8s.io/cri-api v0.25.0-alpha.2 h1:KSB1Untl+/iXXPuoqWtiW0YZbjqnnYGzhz4BbaLd3pg= k8s.io/cri-api v0.25.0-alpha.2/go.mod h1:bKbUiy31Ex/ogNMxLEikgk+5kPv1vevtbiLN+xWEXr8= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20201113003025-83324d819ded/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/klog/v2 v2.60.1 h1:VW25q3bZx9uE3vvdL6M8ezOX79vA2Aq1nEWLqNQclHc= k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 h1:Gii5eqf+GmIEwGNKQYQClCayuJCe2/4fZUvF7VG99sU= k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 h1:HNSDgDCrr/6Ly3WEGKZftiE7IY19Vz2GdbOCyI4qqhc= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= lukechampine.com/blake3 v1.1.6 h1:H3cROdztr7RCfoaTpGZFQsrqvweFLrqS73j7L7cmR5c= lukechampine.com/blake3 v1.1.6/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 h1:kDi4JBNAsJWfz1aEXhO8Jg87JJaPNLh5tIzYHgStQ9Y= sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY= sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= stargz-snapshotter-0.12.0/cmd/stargz-store/000077500000000000000000000000001426301527400206675ustar00rootroot00000000000000stargz-snapshotter-0.12.0/cmd/stargz-store/main.go000066400000000000000000000147271426301527400221550ustar00rootroot00000000000000/* 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" "flag" "fmt" "io" golog "log" "math/rand" "os" "os/signal" "path/filepath" "syscall" "time" "github.com/containerd/containerd/log" dbmetadata "github.com/containerd/stargz-snapshotter/cmd/containerd-stargz-grpc/db" "github.com/containerd/stargz-snapshotter/fs/config" "github.com/containerd/stargz-snapshotter/metadata" 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/resolver" "github.com/containerd/stargz-snapshotter/store" sddaemon "github.com/coreos/go-systemd/v22/daemon" "github.com/pelletier/go-toml" "github.com/sirupsen/logrus" bolt "go.etcd.io/bbolt" ) const ( defaultLogLevel = logrus.InfoLevel defaultConfigPath = "/etc/stargz-store/config.toml" defaultRootDir = "/var/lib/stargz-store" ) var ( 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]") rootDir = flag.String("root", defaultRootDir, "path to the root directory for this snapshotter") ) type Config struct { config.Config // KubeconfigKeychainConfig is config for kubeconfig-based keychain. KubeconfigKeychainConfig `toml:"kubeconfig_keychain"` // ResolverConfig is config for resolving registries. ResolverConfig `toml:"resolver"` // MetadataStore is the type of the metadata store to use. MetadataStore string `toml:"metadata_store" default:"memory"` } type KubeconfigKeychainConfig struct { EnableKeychain bool `toml:"enable_keychain"` KubeconfigPath string `toml:"kubeconfig_path"` } type ResolverConfig resolver.Config func main() { rand.Seed(time.Now().UnixNano()) flag.Parse() mountPoint := flag.Arg(0) lvl, err := logrus.ParseLevel(*logLevel) if err != nil { log.L.WithError(err).Fatal("failed to prepare logger") } logrus.SetLevel(lvl) logrus.SetFormatter(&logrus.JSONFormatter{ TimestampFormat: log.RFC3339NanoFixed, }) var ( ctx = log.WithLogger(context.Background(), log.L) config Config ) // Streams log of standard lib (go-fuse uses this) into debug log // Snapshotter should use "github.com/containerd/containerd/log" otherwise // logs are always printed as "debug" mode. golog.SetOutput(log.G(ctx).WriterLevel(logrus.DebugLevel)) if mountPoint == "" { log.G(ctx).Fatalf("mount point must be specified") } // Get configuration from specified file if *configPath != "" { tree, err := toml.LoadFile(*configPath) if err != nil && !(os.IsNotExist(err) && *configPath == defaultConfigPath) { log.G(ctx).WithError(err).Fatalf("failed to load config file %q", *configPath) } if err := tree.Unmarshal(&config); err != nil { log.G(ctx).WithError(err).Fatalf("failed to unmarshal config file %q", *configPath) } } // Prepare kubeconfig-based keychain if required credsFuncs := []resolver.Credential{dockerconfig.NewDockerconfigKeychain(ctx)} if config.KubeconfigKeychainConfig.EnableKeychain { var opts []kubeconfig.Option if kcp := config.KubeconfigKeychainConfig.KubeconfigPath; kcp != "" { opts = append(opts, kubeconfig.WithKubeconfigPath(kcp)) } credsFuncs = append(credsFuncs, kubeconfig.NewKubeconfigKeychain(ctx, opts...)) } // Use RegistryHosts based on ResolverConfig and keychain hosts := resolver.RegistryHostsFromConfig(resolver.Config(config.ResolverConfig), credsFuncs...) // Configure and mount filesystem if _, err := os.Stat(mountPoint); err != nil { if err2 := os.MkdirAll(mountPoint, 0755); err2 != nil && !os.IsExist(err2) { log.G(ctx).WithError(err).WithError(err2). Fatalf("failed to prepare mountpoint %q", mountPoint) } } if !config.Config.DisableVerification { log.G(ctx).Warnf("content verification is not supported; switching to non-verification mode") config.Config.DisableVerification = true } mt, err := getMetadataStore(*rootDir, config) if err != nil { log.G(ctx).WithError(err).Fatalf("failed to configure metadata store") } layerManager, err := store.NewLayerManager(ctx, *rootDir, hosts, mt, config.Config) if err != nil { log.G(ctx).WithError(err).Fatalf("failed to prepare pool") } 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) } defer func() { syscall.Unmount(mountPoint, 0) log.G(ctx).Info("Exiting") }() if os.Getenv("NOTIFY_SOCKET") != "" { notified, notifyErr := sddaemon.SdNotify(false, sddaemon.SdNotifyReady) log.G(ctx).Debugf("SdNotifyReady notified=%v, err=%v", notified, notifyErr) } defer func() { if os.Getenv("NOTIFY_SOCKET") != "" { notified, notifyErr := sddaemon.SdNotify(false, sddaemon.SdNotifyStopping) log.G(ctx).Debugf("SdNotifyStopping notified=%v, err=%v", notified, notifyErr) } }() waitForSIGINT() log.G(ctx).Info("Got SIGINT") } func waitForSIGINT() { c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt) <-c } const ( memoryMetadataType = "memory" dbMetadataType = "db" ) func getMetadataStore(rootDir string, config Config) (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) } } stargz-snapshotter-0.12.0/docs/000077500000000000000000000000001426301527400164105ustar00rootroot00000000000000stargz-snapshotter-0.12.0/docs/INSTALL.md000066400000000000000000000104241426301527400200410ustar00rootroot00000000000000# Install Stargz Snapshotter and Stargz Store ## What's Stargz Snapshotter and Stargz Store? *Stargz Snapshotter* is a plugin for containerd, which enables it to perform lazy pulling of eStargz. This is an implementation of *remote snapshotter* plugin and provides remotely-mounted eStargz layers to containerd. Communication between containerd and Stargz Snapshotter is done with gRPC over unix socket. For more details about Stargz Snapshotter and the relationship with containerd, [please refer to the doc](./overview.md). If you are using CRI-O/Podman, you can't use Stargz Snapshotter for enabling lazy pulling of eStargz. Instead, use *Stargz Store* plugin. This is an implementation of *additional layer store* plugin of CRI-O/Podman. Stargz Store provides remotely-mounted eStargz layers to CRI-O/Podman. Stargz Store exposes mounted filesystem structured like the following. CRI-O/Podman access to this filesystem to acquire eStargz layers. ``` /base64(imageref)// - diff : exposes the extracted eStargz layer - info : contains JSON-formatted metadata of this layer - use : files to notify the use of this layer (used for GC) ``` ## Install Stargz Snapshotter for containerd with Systemd To enable lazy pulling of eStargz on containerd, you need to install *Stargz Snapshotter* plugin. This section shows the step to install Stargz Snapshotter with systemd. We assume that you are using containerd (> v1.4.2) as a CRI runtime. - Download release tarball from [the release page](https://github.com/containerd/stargz-snapshotter/releases). - Add the following configuration to containerd's configuration file (typically: /etc/containerd/config.toml). Please see also [an example configuration file](../script/config/etc/containerd/config.toml). ```toml version = 2 # Enable stargz snapshotter for CRI [plugins."io.containerd.grpc.v1.cri".containerd] snapshotter = "stargz" disable_snapshot_annotations = false # Plug stargz snapshotter into containerd [proxy_plugins] [proxy_plugins.stargz] type = "snapshot" address = "/run/containerd-stargz-grpc/containerd-stargz-grpc.sock" ``` - Install fuse ###### centos ``` # centos 7 yum install fuse # centos 8 dnf install fuse modprobe fuse ``` ###### ubuntu ``` apt-get install fuse modprobe fuse ``` - Start stargz-snapshotter and restart containerd ``` tar -C /usr/local/bin -xvf stargz-snapshotter-${version}-linux-${arch}.tar.gz containerd-stargz-grpc ctr-remote wget -O /etc/systemd/system/stargz-snapshotter.service https://raw.githubusercontent.com/containerd/stargz-snapshotter/main/script/config/etc/systemd/system/stargz-snapshotter.service systemctl enable --now stargz-snapshotter systemctl restart containerd ``` ## Install Stargz Store for CRI-O/Podman with Systemd To enable lazy pulling of eStargz on CRI-O/Podman, you need to install *Stargz Store* plugin. This section shows the step to install Stargz Store with systemd. We assume that you are using CRI-O newer than https://github.com/cri-o/cri-o/pull/4850 or Podman newer than https://github.com/containers/podman/pull/10214 . - Download release tarball from [the release page](https://github.com/containerd/stargz-snapshotter/releases). - Add the following configuration to the storage configuration file of CRI-O/Podman (typically: /etc/containers/storage.conf). Please see also [an example configuration file](../script/config-cri-o/etc/containers/storage.conf). ```toml [storage] driver = "overlay" graphroot = "/var/lib/containers/storage" runroot = "/run/containers/storage" [storage.options] additionallayerstores = ["/var/lib/stargz-store/store:ref"] ``` - Install fuse ###### centos ``` # centos 7 yum install fuse # centos 8 dnf install fuse modprobe fuse ``` ###### ubuntu ``` apt-get install fuse modprobe fuse ``` - Start stargz-store (CRI-O also needs to be restarted if you are using) ``` tar -C /usr/local/bin -xvf stargz-snapshotter-${version}-linux-${arch}.tar.gz stargz-store wget -O /etc/systemd/system/stargz-store.service https://raw.githubusercontent.com/containerd/stargz-snapshotter/main/script/config-cri-o/etc/systemd/system/stargz-store.service systemctl enable --now stargz-store systemctl restart cri-o # if you are using CRI-O ``` stargz-snapshotter-0.12.0/docs/ctr-remote.md000066400000000000000000000333671426301527400210270ustar00rootroot00000000000000# Optimize Images with `ctr-remote image optimize` This doc describes example usages of `ctr-remote image optimize` command for converting images into eStargz. `ctr-remote images optimize` command (call `ctr-remote` in this doc) enables users to convert an image into eStargz. This command works on containerd so containerd needs to run on your environment. So this converts an image stored in containerd and stores the resulting image to containerd. Because the resulting image is stored to containerd, you can use `ctr-remote image pull` and `ctr-remote image push` commands for pulling/pushing images from/to regstries. [nerdctl](https://github.com/containerd/nerdctl), Docker-compatible CLI for containerd, allows you to pull/push images using `~/.docker/config.json`. Various other containerd-based commands like `ctr-remote content get`, `ctr-remote images export` and other `ctr-remote` and `nerdctl` commands can also be used for debugging and inspecting the resulting eStargz image. The converted eStargz image can be lazily pulled by Stargz Snapshotter which can speed up the container startup. Because this image is backward compatible to OCI/Docker image, this can be also run by other runtimes that don't support lazy pull (e.g. Docker). Though lazy pull speeds up the container's startup, it's possible, especially with slow network, that the runtime performance becomes lower because reading files can induce remotely downloading file contents. For mitigating this, `ctr-remote` also allows to *optimize* the image against the *workload* the image runs. Here, workload means the configuration of the container that runs from that image, including the entrypoint program, environment variables, user etc. This optimization is done by baking the information about files that are likely accessed during runtime (called *prioritized files*), to the image. On runtime, Stargz Snapshotter prefetches these prioritized files before mounting the layer for making sure these files are locally accessible. This can avoid downloading chunks on every file read and mitigate the runtime performance drawbacks. For more details about eStargz and its optimization, refer also to [eStargz: Standard-Compatible Extensions to Tar.gz Layers for Lazy Pulling Container Images](/docs/stargz-estargz.md). ## Requirements - containerd: Release binaries are available on https://github.com/containerd/containerd/releases. - CNI plugins (if network connection is needed during optimization): Release binaries are available on https://github.com/containernetworking/plugins. `ctr-remote` requires CAP_SYS_ADMIN. Rootless execution of this command is still WIP. For trying the examples described in this doc, you can also use the docker-compose-based demo environment. You can setup this environment as the following commands. *Note that this runs privileged containers on your host.* ``` $ cd ${GOPATH}/src/github.com/containerd/stargz-snapshotter/script/demo $ docker-compose build containerd_demo $ docker-compose up -d $ docker exec -it containerd_demo /bin/bash (inside container) # ./script/demo/run.sh ``` ## Optimizing an image The following command optimizes an (non-eStargz) image `ghcr.io/stargz-containers/golang:1.15.3-buster-org` (this is a copy of `golang:1.15.3-buster`) and pushes the result eStargz image into `registry2:5000/golang:1.15.3-esgz`. This doesn't append workload-related configuration options (e.g. `--entrypoint`) so this optimizes the image against the default configurations baked to the image e.g. through Dockefile instructions (`ENTRYPOINT`, etc) when building the original image. ``` ctr-remote image pull ghcr.io/stargz-containers/golang:1.15.3-buster-org ctr-remote image optimize --oci ghcr.io/stargz-containers/golang:1.15.3-buster-org registry2:5000/golang:1.15.3-esgz ctr-remote image push --plain-http registry2:5000/golang:1.15.3-esgz ``` When you run `ctr-remote image optimize`, this runs the source image (`ghcr.io/stargz-containers/golang:1.15.3-buster-org`) as a container and profiles all file accesses during the execution. Then these accessed files are marked as "prioritized" files and will be prefetched on runtime. You can specify the GZIP compression level the converter should use using the `--estargz-compression-level` flag. The values range from 1-9. If the flag isn't provided, the compression level will default to 9. A value of 9 indicates the archive will be gzipped with max compression. This will reduce the bytes transferred over the network but increase the CPU cycles required to decompress the payload. Whereas gzip compression value 1 indicates archive will be gzipped with least compression. This will increase the bytes transferred over the network but decreases the CPU cycles required to decompress the payload. This value should be chosen based on the workload and host characteristics. The following example optimizes an image with a compression level of 1. ```console # ctr-remote image optimize --oci --estargz-compression-level 1 ghcr.io/stargz-containers/golang:1.15.3-buster-org registry2:5000/golang:1.15.3-esgz ``` You can enable host networking for the container using the `net-host` flag. ```console # 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. ```console # ctr-remote i optimize --oci --gpus "0" ``` `--oci` option is highly recommended to add when you create eStargz image. If the source image is [Docker image](https://github.com/moby/moby/blob/master/image/spec/v1.2.md) that doesn't allow us [content verification of eStargz](/docs/verification.md), `ctr-remote` converts this image into the [OCI starndard compliant image](https://github.com/opencontainers/image-spec/). OCI image also can run on most of modern container runtimes. You can lazy-pull this image into other hosts with Stargz Snapshotter. The following example lazily pulls this image to containerd, using `ctr-remote image rpull` command. ```console # ctr-remote image rpull --plain-http registry2:5000/golang:1.15.3-esgz fetching sha256:9f9b5a43... application/vnd.oci.image.index.v1+json fetching sha256:16debc17... application/vnd.oci.image.manifest.v1+json fetching sha256:a610ec55... application/vnd.oci.image.config.v1+json # ctr-remote run --rm -t --snapshotter=stargz registry2:5000/golang:1.15.3-esgz test echo hello hello ``` In the following examples, we omit `ctr-remote image pull` and `ctr-remote image push` from the example. ## Optimizing an image with custom configuration You can also specify the custom workload configuration that the image is optimized against. The following example optimizes the image against the workload running `go version` on `/bin/bash`. ``` ctr-remote image optimize --oci \ --entrypoint='[ "/bin/bash", "-c" ]' --args='[ "go version" ]' \ ghcr.io/stargz-containers/golang:1.15.3-buster-org \ registry2:5000/golang:1.15.3-esgz-go-version ``` Other options are also available for configuring the workload. |Option|Description| ---|--- |`--entrypoint`|Entrypoint of the container (in JSON array)| |`--args`|Arguments for the entrypoint (in JSON array)| |`--env`|Environment variables in the container| |`--user`|User name to run the process in the container| |`--cwd`|Working directory| |`--period`|The time seconds during profiling the file accesses| |`-t`or`--terminal`|Attach terminal to the container. This flag must be specified with `-i`| |`-i`|Attach stdin to the container| ## Mounting files from the host There are several cases where sharing files from host to the container during optimization is useful. This includes when we want to optimize an image against building a binary using compilers. For these use-cases, you can mount files on the hosts to the container using `--mount` option. The following example optimizes the image against the workload where Go compiler compiles "hello world" program. First, create the following Go source file at `/tmp/hello.go`. ```golang package main import "fmt" func main() { fmt.Println("hello world") } ``` Then you can build it inside the container by bind-mounting the file `/tmp/hello.go` on the host to this container. ``` ctr-remote image optimize --oci \ --mount=type=bind,source=/tmp/hello.go,destination=/hello.go,options=bind:ro \ --entrypoint='[ "/bin/bash", "-c" ]' --args='[ "go build -o /hello /hello.go && /hello" ]' \ ghcr.io/stargz-containers/golang:1.15.3-buster-org \ registry2:5000/golang:1.15.3-esgz-hello-world ``` The syntax of the `--mount` option is compatible to containerd's `ctr` tool and [corresponds to the OCI Runtime Spec](https://github.com/opencontainers/runtime-spec/blob/v1.0.2/config.md#mounts). You need to specify the following key-value pairs with comma separators. |Field|Description| ---|--- |`type`|The type of the filesystem to be mounted| |`destination`|The absolute path to the destination mount point in the container| |`source`|The source of the mount| |`options`|Mount options (separated by `:`) of the filesystem| ## Enabling CNI-based Networking You can also gain network connection in the container during optimization, using CNI plugins. Once you configure CNI plugins on the host, CNI-based networking can be enabled using `--cni` option. The following example accesses to https://example.com from the container, using `bridge` CNI plugin installed in the demo environment. ``` ctr-remote image optimize --oci \ --cni \ --entrypoint='[ "/bin/bash", "-c" ]' \ --args='[ "curl example.com" ]' \ ghcr.io/stargz-containers/golang:1.15.3-buster-org \ registry2:5000/golang:1.15.3-esgz-curl ``` If CNI plugins and configurations are installed to locations other than well-known paths (/opt/cni/bin and /etc/cni/net.d), you can tell it to `ctr-remote` using the following options. |Option|Description| ---|--- |`--cni-plugin-config-dir`|Path to the directory where CNI plugins configurations are stored| |`--cni-plugin-dir`|Path to the directory where CNI plugins binaries are installed| By default, `/etc/hosts` and `/etc/resolv.conf` are configured as the following. /etc/hosts ``` 127.0.0.1 localhost ::1 localhost ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters ``` /etc/resolv.conf ``` nameserver 8.8.8.8 ``` If you want to customize the configuration, the following options are useful. |Option|Description| ---|--- |`--add-hosts`|Commma-separated list of hosts configuration (formatted as `hostname:IP`) for `/etc/hosts`| |`--dns-nameservers`|Comma-separated `nameserver` configs added to the container's `/etc/resolv.conf`| |`--dns-search-domains`|Comma-separated `search` configs added to the container's `/etc/resolv.conf`| |`--dns-options`|Comma-separated `options` configs added to the container's `/etc/resolv.conf`| ## Other useful features ### Reusing already-converted layers If the source image is large, its conversion takes accordingly long time. But you can skip conversion for layers that are already converted to eStargz. For enabling this feature, add `--reuse` option to `ctr-remote`. The following example re-converts already converted eStargz image (`ghcr.io/stargz-containers/golang:1.15.3-buster-esgz`) with `--reuse` feature. ``` ctr-remote image optimize --oci \ --reuse \ ghcr.io/stargz-containers/golang:1.15.3-buster-esgz \ registry2:5000/golang:1.15.3-esgz ``` You will see `ctr-remote` skips converting some layers with printing `copying without conversion` log messages as the following. ``` (... omit ...) WARN[0036] reusing "sha256:4416ecf7e2787af750fe3b1988f36a2c47edc2d3162739c6eedd739d6d5a14d1" without conversion WARN[0036] reusing "sha256:976cc0da952505fede3abe08c0ff0c5277416828c4dff8bd01b306c5b4e5c6f5" without conversion WARN[0036] reusing "sha256:59cf7266511a915072804370a3083a1007c4fb757d800ceef848032ac4a5b605" without conversion WARN[0036] reusing "sha256:ca2a1da2dee341a3b87a14d56603e9c29c66721056a47bec156f9b04ee0b1e5e" without conversion WARN[0036] reusing "sha256:79d28aed10b15d548b63eea4cc59e518c4939f9c8fb8498100ec658fc7e0baca" without conversion WARN[0036] reusing "sha256:6aedf0c74720e30b9093dc0d2b39c2dd88f35ead14e2087bb49c1608bb151e61" without conversion (... omit ...) ``` When optimizing an image, `ctr-remote` tries to avoid layer conversion as much as possible. The layers that meet the following conditions are skipped to converting. - layers that are already formatted as eStargz - layers that no file access occurred during optimization ### Converting multi-platform images You can also convert multi-platform images. If you want to convert all images contained in a multi-platform image, use `--all-platform` option. If you want to convert an image corresponding to a specific platform, tell it using `--platform` option. The format of the `--platform` option is `||/[/]`, please refer to containerd's [godoc](https://godoc.org/github.com/containerd/containerd/platforms#hdr-Platform_Specifiers) for more details. The following example converts all images contained in `ghcr.io/stargz-containers/golang:1.15.3-buster-org`. ``` ctr-remote image optimize --oci \ --all-platforms \ ghcr.io/stargz-containers/golang:1.15.3-buster-org \ registry2:5000/golang:1.15.3-esgz-fat ``` 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. stargz-snapshotter-0.12.0/docs/estargz.md000066400000000000000000000405441426301527400204200ustar00rootroot00000000000000# eStargz: Standard-Compatible Extension to Container Image Layers for Lazy Pulling This doc describes the extension to gzip layers of container images (`application/vnd.oci.image.layer.v1.tar+gzip` of [OCI Image Specification](https://github.com/opencontainers/image-spec/) and `application/vnd.docker.image.rootfs.diff.tar.gzip` of [Docker Image Specification](https://github.com/moby/moby/blob/master/image/spec/v1.2.md)) for *lazy pulling*. The extension is called *eStargz*. eStargz is a *backward-compatible extension* which means that images can be pushed to the extension-agnostic registry and can run on extension-agnostic runtimes. This extension is based on stargz (stands for *seekable tar.gz*) proposed by [Google CRFS](https://github.com/google/crfs) project (initially [discussed in Go community](https://github.com/golang/go/issues/30829)). eStargz extends stargz for chunk-level verification and runtime performance optimization. Notational convention follows [OCI Image Specification](https://github.com/opencontainers/image-spec/blob/v1.0.1/spec.md#notational-conventions). ## Overview Lazy pulling is a technique of pulling container images aiming at the faster cold start. This allows a container to startup without waiting for the entire image layer contents to be locally available. Instead, necessary files (or chunks for large files) in the layer are fetched *on-demand* during running the container. For achieving this, runtimes need to fetch and extract each file in a layer independently. However, layer without eStargz extension doesn't allow this because of the following reasons, 1. The entire layer blob needs to be extracted even for getting a single file entry. 2. Digests aren't provided for each file so it cannot be verified independently. eStargz solves these issues and enables lazy pulling. Additionally, it supports prefetching of files. This can be used to mitigate runtime performance drawbacks caused by the on-demand fetching of each file. This extension is a backward-compatible so the eStargz-formatted image can be pushed to the registry and can run even on eStargz-agnostic runtimes. ## The structure ![The structure of eStargz](/docs/images/estargz-structure.png) eStargz is a gzip-compressed tar archive of files and a metadata component called *TOC* (described in the later section). In an eStargz-formatted blob, each non-empty regular file and each metadata component MUST be separately compressed as gzip. This structure is inherited from [stargz](https://github.com/google/crfs). Therefore, the gzip headers MUST locate at the following locations. - The top of the blob - The top of the payload of each non-empty regular file tar entry except *TOC* - The top of *TOC* tar header - The top of *footer* (described in the later section) Large regular files in an eStargz blob MAY be chunked into several smaller gzip members. Each chunked member is called *chunk* in this doc. Therefore, gzip headers MAY locate at the following locations. - Arbitrary location within the payload of non-empty regular file entry An eStargz-formatted blob is the concatenation of these gzip members, which is a still valid gzip blob. ## TOC, TOCEntries and Footer ### TOC and TOCEntries eStargz contains a regular file called *TOC* which records metadata (e.g. name, file type, owners, offset etc) of all file entries in eStargz, except TOC itself. Container runtimes MAY use TOC to mount the container's filesystem without downloading the entire layer contents. TOC MUST be a JSON file contained as the last tar entry and MUST be named `stargz.index.json`. The following fields contain the primary properties that constitute a TOC. - **`version`** *int* This REQUIRED property contains the version of the TOC. This value MUST be `1`. - **`entries`** *array of objects* This property MUST contain an array of *TOCEntry* of all tar entries and chunks in the blob, except `stargz.index.json`. *TOCEntry* consists of metadata of a file or chunk in eStargz. If metadata in a TOCEntry of a file differs from the corresponding tar entry, TOCEntry SHOULD be respected. The following fields contain the primary properties that constitute a TOCEntry. Properties other than `chunkDigest` are inherited from [stargz](https://github.com/google/crfs). - **`name`** *string* This REQUIRED property contains the name of the tar entry. This MUST be the complete path stored in the tar file. - **`type`** *string* This REQUIRED property contains the type of tar entry. This MUST be either of the following. - `dir`: directory - `reg`: regular file - `symlink`: symbolic link - `hardlink`: hard link - `char`: character device - `block`: block device - `fifo`: fifo - `chunk`: a chunk of regular file data As described in the above section, a regular file can be divided into several chunks. TOCEntry MUST be created for each chunk. TOCEntry of the first chunk of that file MUST be typed as `reg`. TOCEntry of each chunk after 2nd MUST be typed as `chunk`. `chunk` TOCEntry MUST set *offset*, *chunkOffset* and *chunkSize* properties. - **`size`** *uint64* This OPTIONAL property contains the uncompressed size of the regular file. Non-empty `reg` file MUST set this property. - **`modtime`** *string* This OPTIONAL property contains the modification time of the tar entry. Empty means zero or unknown. Otherwise, the value is in UTC RFC3339 format. - **`linkName`** *string* This OPTIONAL property contains the link target. `symlink` and `hardlink` MUST set this property. - **`mode`** *int64* This REQUIRED property contains the permission and mode bits. - **`uid`** *uint* This REQUIRED property contains the user ID of the owner of this file. - **`gid`** *uint* This REQUIRED property contains the group ID of the owner of this file. - **`userName`** *string* This OPTIONAL property contains the username of the owner. - **`groupName`** *string* This OPTIONAL property contains the groupname of the owner. - **`devMajor`** *int* This OPTIONAL property contains the major device number of device files. `char` and `block` files MUST set this property. - **`devMinor`** *int* This OPTIONAL property contains the minor device number of device files. `char` and `block` files MUST set this property. - **`xattrs`** *string-bytes map* This OPTIONAL property contains the extended attribute for the tar entry. - **`digest`** *string* This OPTIONAL property contains the digest of the regular file contents. - **`offset`** *int64* This OPTIONAL property contains the offset of the gzip header of the regular file or chunk in the blob. TOCEntries of non-empty `reg` and `chunk` MUST set this property. - **`chunkOffset`** *int64* This OPTIONAL property contains the offset of this chunk in the decompressed regular file payload. TOCEntries of `chunk` type MUST set this property. - **`chunkSize`** *int64* This OPTIONAL property contains the decompressed size of this chunk. The last `chunk` in a `reg` file or `reg` file that isn't chunked MUST set this property to zero. Other `reg` and `chunk` MUST set this property. - **`chunkDigest`** *string* This OPTIONAL property contains a digest of this chunk. TOCEntries of non-empty `reg` and `chunk` MUST set this property. This MAY be used for verifying the data of the chunk. ### Footer At the end of the blob, a *footer* MUST be appended. This MUST be an empty gzip member whose [Extra field](https://tools.ietf.org/html/rfc1952#section-2.3.1.1) contains the offset of TOC in the blob. The footer MUST be the following 51 bytes (1 byte = 8 bits in gzip). ``` - 10 bytes gzip header - 2 bytes XLEN (length of Extra field) = 26 (4 bytes header + 16 hex digits + len("STARGZ")) - 2 bytes Extra: SI1 = 'S', SI2 = 'G' - 2 bytes Extra: LEN = 22 (16 hex digits + len("STARGZ")) - 22 bytes Extra: subfield = fmt.Sprintf("%016xSTARGZ", offsetOfTOC) - 5 bytes flate header: BFINAL = 1(last block), BTYPE = 0(non-compressed block), LEN = 0 - 8 bytes gzip footer (End of eStargz) ``` Runtimes MAY first read and parse the footer to get the offset of TOC. Each file's metadata is recorded in the TOC so runtimes don't need to extract other parts of the archive as long as it only uses file metadata. If runtime needs to get a regular file's content, it can get the size and offset of that content from the TOC and extract that range without scanning the entire blob. By combining this with HTTP Range Request supported by [OCI Distribution Spec](https://github.com/opencontainers/distribution-spec/blob/ef28f81727c3b5e98ab941ae050098ea664c0960/detail.md#fetch-blob-part), runtimes can selectively download file entries from the registry. ### Notes on compatibility with stargz eStargz is designed aiming to compatibility with gzip layers. For achieving this, eStargz's footer structure is incompatible with [stargz's one](https://github.com/google/crfs/blob/71d77da419c90be7b05d12e59945ac7a8c94a543/stargz/stargz.go#L36-L49). eStargz adds SI1, SI2 and LEN fields to the footer to make it compliant to [Extra field definition in RFC1952](https://tools.ietf.org/html/rfc1952#section-2.3.1.1). TOC, TOCEntry and the position of gzip headers are still compatible with stargz. ## Prioritized Files and Landmark Files ![Prioritized files and landmark files](/docs/images/estargz-landmark.png) Lazy pulling can cause runtime performance overhead by on-demand fetching of each file. eStargz mitigates this by supporting prefetching of important files called *prioritized files*. eStargz encodes the information about prioritized files to the *order* of file entries with some *landmark* file entries. File entries in eStargz are grouped into the following groups, - A. *prioritized files* - B. non *prioritized files* If no files are belonging to A, a landmark file *no-prefetch landmark* MUST be contained in the archive. If one or more files are belonging to A, eStargz MUST consist of two separated areas corresponding to these groups and a landmark file *prefetch landmark* MUST be contained at the boundary between these two areas. The Landmark file MUST be a regular file entry with 4 bits contents 0xf in eStargz. It MUST be recorded to TOC as a TOCEntry. Prefetch landmark MUST be named `.prefetch.landmark`. No-prefetch landmark MUST be named `.no.prefetch.landmark`. ### Example use-case of prioritized files: workload-based image optimization in Stargz Snapshotter Stargz Snapshotter makes use of eStargz's prioritized files for *workload-based* optimization to mitigate the overhead of reading files. The *workload* of the image is the runtime configuration defined in the Dockerfile, including entrypoint command, environment variables and user. Stargz snapshotter provides an image converter command `ctr-remote images optimize` to create optimized eStargz images. When converting the image, this command runs the specified workload in a sandboxed environment and profiles all file accesses. This command treats all accessed files as prioritized files. Then it constructs eStargz by - putting prioritized files from the top of the archive, sorting them by the accessed order, - putting *prefetch landmark* file entry at the end of this range, and - putting all other files (non-prioritized files) after the prefetch landmark. Before running the container, stargz snapshotter prefetches and pre-caches the range where prioritized files are contained, by a single HTTP Range Request supported by the registry. This can increase the cache hit rate for the specified workload and can mitigate runtime overheads. ## Content Verification in eStargz The goal of the content verification in eStargz is to ensure the downloaded metadata and contents of all files are the expected ones, based on the calculated digests. The verification of other components in the image including image manifests is out-of-scope of eStargz. On the verification step of an eStargz layer, we assume that the manifest that references this eStargz layer is already verified (using digest tag, etc). ![the overview of the verification](/docs/images/estargz-verification.png) A non-eStargz layer can be verified by recalculating the digest and comparing it with the one written in the layer descriptor referencing that layer in the verified manifest. However, an eStargz layer is *lazily* pulled from the registry in file (or chunk if that file is large) granularity so each one needs to be independently verified every time fetched. The following describes how the verification of eStargz is done using the verified manifest. eStargz consists of the following components to be verified: - TOC (a set of metadata of all files contained in the layer) - chunks of contents of each regular file TOC contains metadata (name, type, mode, etc.) of all files and chunks in the blob. On mounting eStargz, filesystem fetches the TOC from the registry. For making the TOC verifiable using the verified manifest, we define an annotation `containerd.io/snapshot/stargz/toc.digest`. The value of this annotation is the digest of the TOC and this MUST be contained in the descriptor that references this eStargz layer. Using this annotation, filesystem can verify the TOC by recalculating the digest and comparing it to the annotation value. Each file's metadata is encoded to a TOCEntry in the TOC. TOCEntry is created also for each chunk of regular files. For making the contents of each file and chunk verifiable using the verified manifest, TOCEntry has a property *chunkDigest*. *chunkDigest* contains the digest of the content of the `reg` or `chunk` entry. As mentioned above, the TOC is verifiable using the special annotation. Using *chunkDigest* fields written in the verified TOC, each file and chunk can be independently verified by recalculating the digest and comparing it to the property. As the conclusion, eStargz MUST contain the following metadata: - `containerd.io/snapshot/stargz/toc.digest` annotation in the descriptor that references eStargz layer: The value is the digest of the TOC. - *chunkDigest* properties of non-empty `reg` or `chunk` TOCEntry: The value is the digest of the contents of the file or chunk. ### Example usecase: Content verification in Stargz Snapshotter Stargz Snapshotter verifies eStargz layers leveraging the above metadata. As mentioned above, the verification of other image components including the manifests is out-of-scope of the snapshotter. When this snapshotter mounts an eStargz layer, the manifest that references this layer must be verified in advance and the TOC digest annotation written in the verified manifest must be passed down to this snapshotter. On mounting a layer, stargz snapshotter fetches the TOC from the registry. Then it verifies the TOC by recalculating the digest and comparing it with the one written in the manifest. After the TOC is verified, the snapshotter mounts this layer using the metadata recorded in the TOC. During runtime of the container, this snapshotter fetches chunks of regular file contents lazily. Before providing a chunk to the filesystem user, snapshotter recalculates the digest and checks it matches the one recorded in the corresponding TOCEntry. ## Example of TOC Here is an example TOC JSON: ```json { "version": 1, "entries": [ { "name": "bin/", "type": "dir", "modtime": "2019-08-20T10:30:43Z", "mode": 16877, "NumLink": 0 }, { "name": "bin/busybox", "type": "reg", "size": 833104, "modtime": "2019-06-12T17:52:45Z", "mode": 33261, "offset": 126, "NumLink": 0, "digest": "sha256:8b7c559b8cccca0d30d01bc4b5dc944766208a53d18a03aa8afe97252207521f", "chunkDigest": "sha256:8b7c559b8cccca0d30d01bc4b5dc944766208a53d18a03aa8afe97252207521f" }, { "name": "lib/", "type": "dir", "modtime": "2019-08-20T10:30:43Z", "mode": 16877, "NumLink": 0 }, { "name": "lib/ld-musl-x86_64.so.1", "type": "reg", "size": 580144, "modtime": "2019-08-07T07:15:30Z", "mode": 33261, "offset": 512427, "NumLink": 0, "digest": "sha256:45c6ee3bd1862697eab8058ec0e462f5a760927331c709d7d233da8ffee40e9e", "chunkDigest": "sha256:45c6ee3bd1862697eab8058ec0e462f5a760927331c709d7d233da8ffee40e9e" }, { "name": ".prefetch.landmark", "type": "reg", "size": 1, "offset": 886633, "NumLink": 0, "digest": "sha256:dc0e9c3658a1a3ed1ec94274d8b19925c93e1abb7ddba294923ad9bde30f8cb8", "chunkDigest": "sha256:dc0e9c3658a1a3ed1ec94274d8b19925c93e1abb7ddba294923ad9bde30f8cb8" }, ... (omit) ... ``` stargz-snapshotter-0.12.0/docs/images/000077500000000000000000000000001426301527400176555ustar00rootroot00000000000000stargz-snapshotter-0.12.0/docs/images/benchmarking-result-ecdb227.png000066400000000000000000000340271426301527400254630ustar00rootroot00000000000000PNG  IHDR S2PLTE@Ai @0`@ԥ**@333MMMfff22U݂d"".Wp͇PErz挽k ܠ ݠݐP@Uk/@@``@@`pͷ|@ ___???eL pHYs+ IDATx an_*j% &>p4ر~<޺-I6RxbЦko<=_Cu֏qx!2ߞ#<mφwg872_!B(יvg^6:A9]S]3N omrh3zlVS>HM?\v-g N;w+5Z[!nt yô˵όqrXEg<nm\@gҭr>㰦nڻ&hnmܺgs?>FW>V;ſqΚ]$ fp6y"c3mm?GWяĿqnÚ~5GVfVW#3"5!}5:x/FoT8JS.%?6Y~E>S+ۿVcWAKg.h~t6˸uիm~Xu_ǂUw0_ת;q}M8U.4[ċ1MWxW{U`_Gx}mAKz {ќD~̹)`:փяm=^/{]'&.3;!s:S| "yH)' L>#6}R{0huMB xv0:px^.Lh;ޡŭ=Pk]F,2 +V+ud}ߊtՊ]oeýzߊtՊ]/oƁsժEtjUqd*8p]9G[Ul6: 6b3 ֿ$qtat4~;b~5wӭ}q6b3!xW_4 N3J6Zo/FFInatoFGFсl\38&oja1e\]!e9I6x6v@F`}M5:+ohL?ߌ.5:!יv2i|o8~l쀌~m'ѣߚ_x jtΧmwClߚ,5:2:ExQ[pjtkW]'l[Fov?vя}O/@'wí{vos2:jt MVFoa08~}m5:2:zYWcYg:\GVQp 6v@Flr2:`c(;FN?7 o{6v@FN?]cz(5:uvgu\~߬ 2:B>|igFG~ё/F'=(Ǩ?Knj~7%nY?ft>|{jtPP6v@Flr2:o9LX|wnYZ~7AMjۡ r"AF&0ero6X|(Ge; Y/{ԵCxNE]; kfCFNXV[F[\Go=q; P6v@@9rjtPP6v@Flr2:`c(;Fl쀌@95:`cdt(; P6v@@9rjtPP6v@Fl5D6?atP 60:F`sؠFA960:(5:AFA96atP6 2:AZuMxB.Ѱ /jt5ЪK4l‹ZuMxQ1 "1 Fct҃.Ѱ /2+/Ѱ /jt=FW:_a^dt=FW:_a^zNz%6EFct%6EJK4l‹JK4l‹]ID&ztD&]|Mx]|MxFjHDS9kopb/dm8jΏuco:;9}>rfC/jN ӗMo]ёFx1k>F'= mm7;`Uy-GёQ= S.QsZs:( @хo7aSkt҃.rw7zpxzΏFw4Mo7ո3r|QktbNθokAFftdt~ 5|jNz%6Ū#MxRQV]aޟ:3V]a_I؄y BkӪK4l{fC/ZkYD&j2V]a^&)ggse+Z3V]adFl}Ek-Uh؄]MӪK4l‹@9؄y BkӪK4l{fC/ZkYD&j2V]a^&)ggse+Z3V]adFl}Ek-Uh؄]MӪK4l‹@9؄y BkӪK4l{fC/ZkYD&j2V]a^&)ggse+Z3V]adFl}Ek-Uh؄]MӪK4l‹@9؄y BkӪK4l{fC/ZkYD&j2V]a^&)ggse+Z3V]adFl}Ek-Uh؄]MӪK4l‹@9؄y BkӪK4l{fC/ZkYD&j2V]a^ztx{غaޔѳ߲QO|MxRQ "#r /jt`ecd>}nD&gF;nF߾j/Ѱ /2:2:(F.P6M]*ڌtD&ɌN(]|MxёA9؄5:trTo?8]3}^df ڌ>Q7_^7y7}s[ѷK4ӗMiq5C Q%7;S2o:_֚d m'OuWmFW:_ʼ uΗh^odt2\>[.Yd-;;8%3K?pfMe^uэ}iDS9qOxg2a_h؄k>蒥K4l›2U\]|MxRQ5 "#r /jt`ecd>}nD&gF;nF߾j/Ѱ /2:2:(F.P6M]*ڌtD&ɌN(]|MxёA9؄5:tr 1b2ƌ>Q7_a3r|[o_5͗h؄MxQC(𦌮tWmFW:_adF{l}EosD&l‹@9؄y1OcF/Ѱ [ѷK4l‹&ѡ MxSFW6+/Ѱ o2S=6ʾF9_a^dFm]]%6E~UtU&'Fƈe5f = Fat%6EFGF`^Wu0 𦌮tWmFW:_adF{l}EosD&l‹.F8MxOKMqD&gF;nB% K4l}KΗh؄5U]"qD&)+]%ot%6MftJFW]|MxёA9؄5U]NUq?=/;#aK5Ηh؄q-_d]|MxёA9؄5U]"qD&)+]Ū &3:c+.kD&κff Gpi Zk>K4;qˉ1VqD]|rx~28cBZ8{!w.FW8_y#$+F|vѯat%}*8Xu5_ލW,˚/oԚ!e͗LwR7Q_4]G<Ӄk|PVNP2*A%K4娛L>:}!at%g?|x<5jtY%.'\tK4w);@^utY%9 Dk@F5_y>y6۸vc v%8D]"qD|xp\}W@a-_d]|DxoQ$..F8_y>GՐkѕb]|6v7,˚/<^/@fdtY%û/֚i*y>wI;@%3K?pD]|6l>C/D]2pD|xp\DxKaFc͇]| ]ѕb]||ܤzK4wlqi]Dk@F5_yK4%‹OSQk/Ѱ [u05C Q%,Wu0uM?@b]|R0)e͗h@Xq #r<~sZF5_y< :1z^v0F*.Qk/<_7N= Q +/t Z325_y<'~Z^7ƚ5;FeM>JWq.kDpx3_^wv7F~@j.K&1~tXuq<iK4?{_Goqi*~9w_2K5Ηh؄q-_d]|MxёA9؄5U]"qD&)+]Ū &3:c+.kD&l‹.F8MxOK%K4l{fC/D]2pD&l‹.F8_aޔѕb]|MxRQ\G5_a^dtdtP6E~UtU&'FHX%at%6=3z!w.FW8_a^dtdtP6E~Uk/Ѱ oJWq.kD&ɌN( ˚/Ѱ /2:2:(Fi*aec$u0 = Q +/Ѱ /2:2:(FK5Ηh؄7etXu5_adF{l}e͗h؄MxQ_4]G 1VqD]|MxόwKΗh؄MxQ_%K4l›2U\˚/Ѱ o2S=6ʾK4l‹&ѯot#A؄y D]"qD&gF;nB% K4l‹&ѯat%6M]*.Ve͗h؄7)e_utY%6EFGF`^Wu7:MW l{b`U\.F8_a3r|!at%6EFGF`^Wu0 𦌮tK4op ,˚/To?F>Q9mgf@|e͗hꇗf`U\.F8_yvgF;nB% K4o}zӹqFe@F7K4o=@|e͗h^5z7R*.Ve͗L^8уÛ]ɌN8.kD{jj\J32Mo^w65jtY%6=1z^v0F*.Qk/Ѱ [uդ.R_"l‹^66 FK5Ηh؄7etUWk]|MxRQk]|MxёA9؄5U]NE \MxOK%K4l{fC/D]2pD&l‹.F8_aޔѕb]|MxRQ\G5_a^dtdtP6E~Utaec$u0 = Q +/Ѱ /2:2:(FK5Ηh؄7etXu5_adF{l}e͗h؄MxQ_4]E؄y D]"qD&gF;nB% K4l‹&ѯat%6M]*.Ve͗h؄7)e_utY%6EFGF`^Wu7:M/p6=1z^v0F*.Qk/Ѱ [u0 "#r /jD]|MxSFWUwY%6MftJFWp]|MxёA9؄5U]NE \MxOK%K4l{fC/D]2pD&l‹.F8_aޔѕb]|MxRQ\G5_a^dtdtP6E~Utaec$u0IDAT = Q +/Ѱ /2zn&ѯ"Z1:_aޔѕ]uˀhl3oulg-' "k"U:q́ѯvf6_wi Zk>.V+j F[]_df &N@E=et.R_5zFogv򅨋sZWM`go kdYԌH*k>҃WjPKF-k/ttK\FOp۪vNپs>jH}դf --#oH}DfF_w u}^// y6gD]3:M ?[u}NItI>a # 2]lmz1|1digge+Xu8_7Iftӕ-e_!""U1|&@FEF d'HftQ.Pa=/;LF"Ud'83z!w.i9.R_5a2 dtdtR_5AFu}^// #ml} &Ɍn?ve+D]drR5]jR3d藷oɌ.6J@Eybb2Sec؜Iu4]jgF;nBE9-'UE&5c8LN&OpbtQ.Pa`]|!$NWl}sZNWMjq -џ F >CC]Lqj`n$zTU3t 2=py+tQE]jR3d褾j'F'F"^SH}D.&H=;;U3t 2$3Vѫ壊H}դf 0KBF 2$3:6oʺ}E&Lqj`n$zTU3th]Lqn WE˳uh]LN&u1Ʃ2^SH}D.&H=,^SW͌NE&u1IF;] 0zU]|TQdu!F.&8蔇WE諦.R_50Nw57_=ꪙiH}Dq&gF;\ph.R_5} /2:2:sDl{b'WM]j"8dޔi<]j"8ddF;]2 ph.R_5} /2:2:sDl›'WM]j"8dE͍Wjft.R_5} <.Z>WMDl‹!=$ j"%BW-DzcP3\Xu|Uч_$3ʖRU1UѺ2k>~%WdtO.F'=8-Uuuet;`EMN7D=]^F4BueqM..v )Uu]-ZdtO.iѻѿ "z }C3j!S zOe쳨\3A}]%6ոs ]뭌ލκ@뻬z~?[F1?"E ]uբ!ygQsDje>躯4Buh1z*y.GU]OGCёOY@F1?"E ]uբ!gQ%Ǫ^惮H#!zD=1gK/M ]uC|03iJ2t= /F'=/d躩 h}ς1fM@]KTuK1u==_b.Lz&.)FPK,.F>-153tAW-]"ǗvH.誤K%IS jat҃_R_d]UtI1zTqyK]t1z_ڹ*.1Fv&M Y ]tI1z~NELxܰEkGF5]]߶atg]t0:ah}Q%CtUE\12_ @tU%q[Kjw[[躨KѥI@tU%KlG*.H=~R.誦Ks6s}O.}\K>&.誦KIKlG*.1얹 M.1F;ZRtBE]b.L׌8k%E.43m/ Djg肮j2kwlg| m;ۅ>&.誦%ObIKlGtAW5]4ɷiCu[K7Գ6肮j^3zfFv(v7躵^2lv_U; :zP; E]Ъ so% 0ŷ?K% @ov;=ԊtZAVŷֹ`Zр=ul||oz  ][7:MV_V4kto%_mq 8duFkE@VK9ُM$ӵ;iwYkh ][ gBil2"w9Zр/.6B5}{Z]]_ZAWU]|^ĴDka,܊t 譤,ٯjMwԡ];>˹Ԋp^Z+ nN3Vin5RY ]Vu|d_hT}@J:Qjcޚ%Ry "];~+k&}ȭZBVimxfHg "]@V‰ov (ފtMm߀ km][i`*dͻ +Z;@ to|nWcEku n+4c׎v}ܵ5Z+еN揭#7oZZр=uoڱiΆV5jU/h7}ߪ&ZuAk~hŇ9VmGoЄ0.Ky8]V]x'.蒅V]|zF}dUV߻KZuqaCO@,z'<.Yhņ%UvB蒅V]lhY蒅V]muVBKZuqǷCKZuq 74} tB.>L/=I&A,B,bCX2yB,bčιNۚ'tB.>4ٱ׷ ]Ъ}۩\.Yh k% ؠudUg]}]Ъ 뚧@,ü14Jh@,K&$v;$wi{7 ߑeE~= iu띱aOoϷBsn"y8ĚCޥ>az;i]ʫ[ĉяn3M"\]tv;'NOh/KSA4Chbtw9P~<:m:8@}!#FAtNck# s?Tn6nKEB6 mgvOX1V Dƿ}݇2wi&3=Eq!F_|u(|[q?.Z7 ܎O .fz孕0G?}Nǻڷ 1 L'Y@IDATxO7M1&DMaAAQE*@ "݀ tTQ@Dؐk-'}Cafv|Μ򝳻sy;(NED@D@D@D@D@D@D@D@D@D@D@JwKh, " " " " " " " " " " " %G@H]R HD@D@D@D@D@D@D@D@D@D@D@@RrT9 " " " " " " " " " " "Pr$%ՀD@D@D@D@D@D@D@D@D@D@D@$h %wI5  " " " " " " " " " " " %G ߺGy5oׯ h@" "PV^7nzQTD@D@D@D@D@D@D@D@DOt7ntK.uҜcD@D@ D?wK,)PmFD@D@D@D@D@D@D@D@ʃ@*@Q Tȿ/w:Th" " " " " " " " " " " Ր@j5"" " " " " " " " " " " L@H1_MD@D@D@D@D@D@D@D@D@D@D 'B`Q+':ID@D@D@D@D@D@D@D@D@D@D @ ȋ@*?$/:YD@D@D@D@D@D@D@D@D@D@D @<@j RDD@D@D@D@D@D@D@D@D@D@DO( zZR:ND@D@K믿vWvVrݯkרQ#~j;ƍn;שS'}FPmU B3qﻖ-[+tDiq/X}7I&nw/kt" " " " " " [^ :?Ѝ9=?֬YZ裏<{oסCJ@_| /;NOܼy3RU3O툀@H-XD@D@r'ݭ\G?֭[vmݮ{1g⩹qFtRܵj*_?>MN5Pmp+c<)/U"J!:|;M6zE@D@D `f?[h;<_Ҽ%0*jns͚5T᫃" " " " " " " "PA )D "@2o3o |F~;a]_m:[D@D@D@D@D@D@D@?sTk!o}6a%(m 9}{MD+IyO,(Vt@@x'_m9\[oqjղ$+VOSkG^zn<̈́z'|b!>s?ٽ:>oZuwP`Bΐ_~>6FU2-A|{կN7/z7l{j֬p% l2^d;caw5l,TO\̵UVc zQ=\w:ycɒ%*0?0#ZiӦ \7⩏fLSLqǏs;?O '>~FxxV߃>h }ATҥ;c9}e^#~ky)N:9Q 3?x;}CƵlҝ}&Epgqsx{A6eŊD=ުUlNױ" " " " " " "`R 6؋GD@D@ L#/F5kָg} CS} >Ûwfw׃g#7|1;>\0x&`dƈ!ŋݤIl=m x=ѬY3>-[fIk׮8<0L "?n3c^ό-b:tl'<?pۡO;l} 1b_zX~A#<#F/poX3p@rJG;΄%㏛PT^*~[yFlB"s\;Xz衑mR /`!1!.č ="xO DBEJpή]Y̛!}ƈõ4hA51y o[Z* *p].U5*BvƌµBlA=a0.cp!lׇyC+ڇ ׁ9u''FR?|xB]xq|B \skI<X 泀؀ sk\bkȤk ?7s(#M!w a7o.W#" " " " " " OB" " EI>T (@aT(q|"8.'60^ FFvǨwC>̙3ͳa{DTM8!< 5kN !6x\s5<| gϞ֯0O 'GcT^c> w} Opa`=\%0=z4&*&'xfm2wHsR?xyh,/yp.cޣ0rz0pALڵG- ZxfTҎ),ፂ7^1r5*ğ5>ct1xFgB›fh31 x1 ( Hd⒆Mp J\}^=s$$$?9>?Bp!-T'NWK<Ou8H%/xY@1L( Ĉ1r`0G`; '' k=6 p7/8os&z'"^uOykuc=lN0Nƌ=;cnD1Y#m>:t{ǍQ 5^@|DrLL™ԩSڵkm^# `{uc," " " " " " H-R 4ƓID`M2뮻.2Px<ʥD,]g*xfc`cDAGL~kl2קOۨѩS'K  "7 }< *p]h{TV;&wsqճ$DgL.xqE2w#`͚5ծ]^/tOD@D@D@D@D@D@|hc. ъ@u !|JJv {3* y*PwGeFx<S*'L<`ɽ/R׾}<*p$~ej+a.fJz瞳$s,XU!`լY3?_#԰aL{;@AlBV"PB0D{n]vq]vkd|^?|;QX;vLTpHva>g3ݻ櫯SiܸCp[.;B^r)х '^xcgyf/(_x}MAL78Wlұ" " " " &JaKoh_vw.ZœiӦ[֯_o<1v`p!1m.u~30|p Aq7ߴdUGL|;w.FqY=zH)V=YA9 c…f<%r>;O:$.u70(?nHƒ>ժU-^0O0Hmۺ 6cǚn,sP%!`PϮ/"\W k{";gʔ)f'Xd' "":ICXB< 8*xቂw o믿n97vy›kCج0 裏'RO[D@/m"@_~5{'x3ڄt,>/T t/ zn`1_íM;1?1%-vEv;M]'Y`D6[ O{Ӓ90Kzjl8Xs=-\n& uqǙw*]#F0qmDhҤ 9n.]\͚5!w6mk$<&<NEZnm`~r=\7`޽@"'FgBa g*"C1{&#wy3R2 + \{}w(B _0]POh 2 c` Grvi_d& ߽:쳭]]sр :ꨣs1W^y4I!\KR[ހd@qERoc8Nj^JR;3dxw0n8X$jg'-yI(?͜79C(IW?|ŖN:Y]x`2ǩ|¸#Qlb!L#[g,pb/iED@D@D@D@D@D@Dw~aH@ѥ sݹ<-+߾|^>VLWK}IC9!BR`cGhA^z< D("|9&ȓ@?phpݓ!SAHA(`؁N{Bbqfc]wU-Z03}Ƙ>ȵƈH@{1GB"^1O׮]cE"__sj׮mt[1 v1 &fvi|D<@tc2wh!}2` /C v[S/qe0Wc㣞;cbbz7, wB(-v6ىA.nyz:Vύ=7G?1:s|8ڵ C,י>N2eG[ `xerQ11g,w5l F;w\{.{M᷄Gt<+zK׉pq%M;vĹi#ed~l7PORLpߒtli~;i3qTwlPÑxkTxS^7ί2|B>`mw_m!^YC6(o(&qs[oe~+Y'q/{8Xrc|f;> >d(NT(|ٽќdd'6aNHKLrnc0Uފ:߿%#a@cƌ;+x6=Ö_nDMsUWE&ޥ>c']\Mi7)7΄  7ܼ?DŽf8!?|9M?=2nE pp뮻_2BEL=*ryg!ZaAsee\0 7!+Ö]tm: asYyB6ވKȘ;Zء]a" " " " "Pp|rǺ{cB!av,h&1cԩ3k: .7侚p! "kA?6n㊼eqsqBa#Fݻ;mB@`Ks9!CK/uds?[D@D@D@R  pį&c8G"7'O6LJ[5B}O?%fqEH8`)Xn, 7Wݻ )xpM SzM}"9^*I'Hfs ;7߈ pW;EDܨVH@@p8㏻>}`@rav!ᝀ n=O_~vf .0O_|O8W`'>znԢ`<&!nx &&qkKs9X<#0`MO{c̀~Ta"܅bʢEMQ9k)y {РA&-W_~ask2'F_~ټ>|Mbl‹:'\/" " " IR PpIUɏ7&M =0]ӷo_PU0c3]1zyd6 ̝;׼+h0pc&.w+Ę]IaC_}Mcĉvӎw/q5&^~ۍ?Ƃ0twX,~7?pc g1؟x*o5j]s)»EG{[nܴ/aS\R>TՁ Hᝰ"}g1:돧><|c ,p-[lo!F &݋BVb+Q[/9&zEq"p~Wؘ"AdVxa 4:;ɥPX-M+%Š_ph\0}O0#{nic?ygF*" " " "Plg]wzFºsO>d׾}{ڂRX6a />E4  Xӱa 8^{mR؜Ńrk cq B]=}'Η| qxo#ְaP,lP*Zni-u^l@T&M&Mlgn21s2qS 3pSpxP{PZ wٱc(N)Dy Kh%\u< EN ܾ;[ ., 8}^bG b3,Ŝxm*JġM >QuA (X]ob;>> M,I&Sa QGRQNb]HuB~?`" " " $ZQBb\;c> TYFOlnx9pNu*gP~ܹrNKaB17 ˄kL8Ya.u9‚]wx} đw=VG}?MՔ{ĭ#F;VQ" [C!bS xu% b6T!{z^+~cuDmN;/:!bXw$i^\_D@D@D@w4FJHC,c _ =C6?DqFK\ew^1.) $ 01+:-`X罤y|R*P|x|3jrw"9+NՄB 2mv&=W7E@4h`Ǐ"WW(>'^l6+#lX>A  SkBl΢L!u&$͒ k%28'" " " A BEHV~&6p9g%Xon5v7ǩ3gΌu}b%0O&[oغ{W,8xs3zh׵kWov|6mjc>}\O^lYzunm"8F<1vXn]M):YK4@ ^2t^su׹K/4qÝ?^" " " "Ph@ M4E}x>f(¨,xcƌ^xaN{v`|#m-R'ҦΝ;[ۃ ]EbKn _Ƅ X(@sV;'[>C$x Ns=&E c-D.u^&`>!r+x}w_n8q} 0WׂHθKѥcv l 0ır+z7o5k/,?[dI6X}… +E$/s;C6;Ajzy5F P aM4,?O6%GnR"}896 o[WyzMD@D@D@ E w`VɏXck71O2]vev#پ}sO!7ęK,'M7Fe$s# +I"ޭ[7 \_ĵen@D_q;!4?71 0^,ny/XI #yъE\JؽJn‰ 粐`wn #k֬1(YT'h|ڴicl o-Zis"gڈ,7o^{hcc nݺ&9Ҿ Fg|Ig W ~'KcUԇxq?~ʭ a-76~ =v6@u!@H+VZ+Ww(B ]{)*\(t'Zh*?巟U.aXGXf Fas$6}`z1;3xN{5\~oTIYSn% zYqB۝Ş={an,+`f77xC Ac ƕa@nqǻ:t>V Ci1\q}3c61z ߄1_o7|1`7 3fGpmݺ1|~1?>,x92m4F o^#| 6{a٨o@$|c{aa*3" /4.zh̥Cp~0ꞵtF"Na{b VRr@w:uD`zF\ANIu6l7vi[xJ9]x6m*k7mF#cca0vǛPѳTHUugvg>xQ.ܠ1En"n!?Ԇ 1#=.og71*Ԯ];MJ};ixw Hp`QNl2N7վEX d*#*7}#. D+= =ׇkŎ-uc*\o_\Ya"0Y\ӧOl=QuGF[7p4Q⸥}ჰ.,Ԙ_W_}Ϳe˖B]\c֨QRnB7Fr-m>)~<req6rnq L~Q"5=q뙴mSk6UcmȚiSuP~B`A^𡳸 EC*<@Xk2`MK{1֕䚤￿X k=zXYǨ@ @pwe767,Bwcz ` 8M+I7 "7I%c  g$ƕlڡsU``qܶm[$X/quq ĤLzCT;찃CXW\;7!pF  0egJ%煼xuYw{N >#:BD -_7"'F8 i8\(K]=5lbB_f6q@SI,Sa=Ѹqc.XNa%,b]wKGy~mI-XOW~9_>RzDM>Lj^=IEDwc#"" AAG! lϺ5&"\ }@I[4.A\=x@eH*RY@@i |X,7|` e(g$a3mN|=,F !aעDf~ iǎHccPK؈7g8$`p%>#k c{mQ;*Oq M<`ep|0 R)6 MAIJ@8;ݲQZC2<;XىZ"B/>c$?o2+!ۦ]BpbaZÒTtI~헊[ 0~8#ƀ,!@H-ЦT 0ohz2}Q7bGoՈ[h?XQzzmF}w_ 8`ʯ@r߽[&b`ƨ@@X?|'yD> ![۷`Aԍ}ԨQnΜ9&!Dp/s+7{l}F@!qi-by#)b9D>dOrÇ7!3qlҝ}ٛp<;38H {A6eŊ>G#ZTA D —(JzMD@D#܏D0`垉~W@"Za1LIcz!b|޵XPk" "P5tDt< @IDATf z1b|W-}gAϞ=#=+|=x_ns?j_~aww!2\s=׌x,^M4D 1~ ׌c7Ecǎ5#3:M@4ˀBp3<3Z–c0# x0#!Dy9dz Wr/po %.Ⴀ0xmvڙ'Ϙ1cÜ@@Ȣ =|/"ԏ(W|fYv qf2ogN1rA|ൃkxp o\*Q3s夓N2#ב u "1G#ưD3vw \6A/}CWi ֭[ cSaG?TD@D@D@D@D@D@ʛ@g#',F UD@D 6 P Q@0֮]ۄvc8FDڸqc3z &FguC>pNvn0bJ&ML$a8mA(wcxOa;{Ao fO=<"3]i`-]Ԍ~j|ݴyۄWMqƙQр]A0eȓhU9a8`' sQ͵-#&\/~ 4W%" aCp+בkJ}x닀_/ +ԅB`թSǮl(HBGCA %A,x\{sGl'p.7 >i |"͝;5o‘ѯ$ DED@D@D@D@D@D @XRvܕ7F^D@D ;1Bb&MT] Ͱ|1~b%͉*pAtU0@"x,mbp@Ύ|bJ#, oxQD ר0>h.r-oHvn~ xmDODvU0.BwUWٳg[,С" "P{? >^~v;lF_|[o>83L{֖! ;l ΅na 4%lS!I)qv2c3cC1%sIMrn|ɑmxV^q}{ꫯ~ .+r 8߅*:S,ND=[`y$aKx>>P'" " " " " "PR AH\ F7tҥ05vik.jur O>}u=Rx$˗//Hx#w#… ~nݺ=O޽sd Ğ- JTa/;ȑ#E ew}TPO2ptȽjժLh9sxǨB-. HQms񍹃! W$U%Fs8_H븧*Z\_W.rs1^xS |>|!2t>[QbYA:@#!:uSOw[1@$-0хݏ+mM!F,"Pؑ6۸;̌7n7o{Yg͛y`XLɓ'ێnߍAckrB aLA 7C?V`~eN\F`~W`l2Kp'!0`{/6FΧP/_KkDx,6Vf͚$!I~guOׄ`w 7Ab\#Co{ոB<3L|#4g8L7 =#sI'`sDB% p܂1~:#3 u!sy,s/"u˵@;o C;\)) .1g0f^ED@D@po=vtQq].EJk" "PSW&,xN磎:*v>#sp`% =,Y䪈@+P-$F ߱{`w3ƻkɒ%fh\] c@ QA\1B>3c=FY hә bx%LJΥ]<*ilHwqxNSgO}EU0F2F ܀ ׌ Q7Z.Xxv)2p 7m1‹yAuf ^FYv2?Xϕ61ÁB_?A?[w<rnnz.?`|=\'NTx1n<X z0`Ff|6TD@D@MB8p,6.q(q@HE)%5 b&\aA6nh!v˸,竈@;C}w$;BO?1`l,iX# xc%;3`č3C i>>|$|$ozMD@3^>|$*98UD@D@D@D%#ݼćxa3\dc@)'@yNcO%/]ԑ(| *\Km& HHᒰD*" "-|-Zd4CCS77|BTԄdCrT>w " " "P|R >CK`a<@֯_oE@D@r'-2xb z&OlO& ᣼F/"M{GŒ>3لxDED@D@r%0dK/4D _GRص۴iSK:~x7m4/mB(IE狀#oֽK gKk׮ԩc^w$yi˽.bb$b X䙆 *~1^vWv:uRI]ے@jć)aIK͛Ç;-\}7sΎ/-n„ n鶣 $mݺ*" " " " "D焾ڰay̲YQH1'" L4Bzر@Ih|П~4 -(*p =Bv!dԩ-Zp{m§" " !gٳg;<Ot۷wo~ D$>_`7oY/_M'MJx뭷mUD@DR xCxX$%D 1E=P>qD7tP i׮;3,Z;D@Dgu#GtO=C9͕AP_]~T1^x?0">4~*t>hfw1I:JD@ J> $\x/?/1cƘ مB|f͚)Z/"PVX9g oEX.B ֨Qr- VD@D@D@D /l!9ى')Sl"UD@D\@B" ^vmW*UlF /? y|a>X֭[{1ץKۍѳ \{Y>C^s%ر;S{mBr8E@D@D@D@ G笻=}qYnNrr!J^8̪IʚB+ad;sL~_]*$B`Q!*h<֭f͚eIfrJצMסC!D@J_~.\h߃|~WI&sqճB)9x@"b ?Q4k V";ӌcN>M qF^7~3f@_kKM_כ1Gis?sꫯ?=&lMcF=~[[ _zjoZ8Aoi7|¹//KK7ߘ@uc̙ꈪW&~d_RS 1lԨ%1c}A-Yĝ}&hT=zMD@+Qr#0j(OXf~)sQGٮ W׫~'ьs5ӟtwT=(zGmdֲ?O,1_b֭mtU}w/R1lu UVrl: wYg̙3M0@nm1)߷ٷEc6,"0.-Dq}`'ŋM~JgϞ& $mxDT v6?{בWV-w-5kZqƹI&CDaN;믿n9M#}FH'*i7t \N;!Xp_|EwMXÞ3F|>}8~!F9ˏ@*,لˆbя/ >wÆ LTة@u'޽{;)7$u;3컏>f_D@D@D@Dj ڄ<$>?clywI~n~m321qf 챰]x] _k.dVxyaC{۶mM@R x9YE=QC>ao֬fFBa>`ŵ^0c˵E@FH& .?7O(k|&uW^y#QGz)m*A`xǰ<`EzyAvm͖ K.—oqx`@[G&y"60ƫ~k  'nvB>9x/\ O0N s v>1(|nFH/l(|xBK UED@J.,Hyn\_ D@D@D@D@e, }E aOxqF"躺 0,yqB'M<0J#u̘8zmB|xc3"kvY^K}@FxD,"`>YU 7y?|"4€=s-x8 \tBX0L?<@ץK ܉~Ch'Dj h==sGu"DyT`:" B &g  qR\\CϐAi$ _OBWsWB;vhD({D^6>cs9o¦aO*K.<&x0臊 |7B 0IqNED@J7./n"Q WVc @Ux`\cc!F0a-Yw@u#@B`£<̪.k OBAUvZm}BaFxwNZ0#ǻ!ׂ !(@A& ȵ tqjT9q?8QexEy`'T#wA[C4S$n8<}g ϴE1?І-Su9= A{ X h¢dr,׳}6_oM\!;b $~Q[EN/3(Ӎ@`H#L %bchɈ1M8QƩBC@e ZHPàaP>f,FYɄ?uj1PO{xx6Q_# 20Lcx; (zЏX%:mI+62"vʜK#nnc`w !h+X;9T'D+ D.1Yq"E?3>3? #bŋ\^CK1Iz†+$+0<~IC`'fZD@D@D@D@D@D@'j*7k, ҲeK шTG8xdr(TC"hvckP6 0iC;w'Vhf5k,O# 93%X! '\>x^()Xc7 bb a_><6aoౌ?=zH??xO#>xX"!? .?JܟL| F," " "/;%97oo:_D@D@JI)O(biU$jEE_@û]iv/0{RvWqukGYgg-&X-C 1B<% DlUQ2AOM$6si1qAA$p A\_ҾAA{ȸYz-P 3\F>N/oyw/Kы@y^NgvSNu6l} 4KsyN ZD@D D'>(Q D0*"P] `5 tB)m5xL^_G%9#K.wq$&DQXU~ൃᬘ3̻!1oE:BaؖY>/w=e3bQֿ5nY\cڴihck׶xIa/" "PV&7|vkɣ1Zb,'g$܋/\rR Z "(ڵs$ 'E\ _?&NhhxN* ԄB!pU-هnxܔRaS\pkx/1HE*@jV*_D@D@D4 xc1`_}UGhW= a";bՖ&JD@D@6'w;͛giN8-V *" "Çw׷| #B xq D,7|SN&/D AIpQH)K/dPrᵜ`I@OD H Rm[D@D@D4pOAΧ/|QAV(UWv<‰}~OSѳ@&+Wt%|0Ol "a g}/x{U%6!tD)f%zx^lɒ%f͚!-Zf̘7nlvAǓ8x6sLbaoD;"MM>uUV-׻wo'r&\9" " "P] [Is{M4U~c _TD@D vc0a~g!!(=ћN:t0#=bET\SOl~&q7`KҎ<ؿ~k8;vtw}k]MW^q\细 : UF\߾}sa~= r:9 7u/sk׮us@H{>gyacUC ն-@! pAIJ8aW> IZu*¹70#P8k1R"(w'gLO!s$ Ͻ<OB!b#|͘C5O6k/w 7عqĽNh6mXurx/X-Zp]t}ݷ0չ5_' k `"b7> Db֨Q87΄' ]L ^C:e7-Fes~q< " xrzΕ@jD "y" " "P^KPW\hbVX@+bWlI4, {È cW$H@,+b`oJ{bxcʟe>>kN3O=g\B@! 2 @L},>fJ +p =Kt%#: %,%*IIBMN^(ZgMdVNz# ;ߩy>@QC-<'f͚e] 6zG%򣈰եVX^…yO?X%م#s#NCꫛٳg[1xIh"4֦njH*o߾6$s9ǖ@b)I[:((3ʥyg 70 HaÆپ伈㍶>gϞV!T=z38N$j! H-tB@!Z6ȸGKN]("De3°$,IgH!τ`w4gH27B0瞳'8 ?#$()!+9PtGO ayJHw4uA9CZ JR^P@؏W^(V_~aÁNW($PZo1@{x'B1A?pYvrQVI\CPH'%#kԥ=yHB@! @=\'^x,G=B@/@5?#IІp!cK$H{/VP/9nr;3$y:P++(h'y)#4uQ/P6SP:Pfr=]PlpER(++/9o^"!T 4ph6DB@! "Y"bbFZ,!ŇƊB@l :\veְ |e>y@yo mLjH! VBcTG! B\$1czmC]IQnt! h_N|Pp IF+CEB@AG(3W̙3mR>,X*_Qo=*P]ˇoiB@V̈EH! \ S|?8lqhs V! ##|ӧOpN{&wR*QFOdw6(dV%f_\p-0#G9?VYe 5NrHQr:@ ! hfp9sXb}ZXR|4snB@!*|'Ϸv[^r$#mVDB:&.5t(e/T )?{uB@! 9>|ǠAl+)>!  wm}{W?}*@ I]! ͎@*m|ۤ ! 9@1eʔovjhC]IX&Bp>`.o|5'g5(EB@! "Z"/EB@!  ۬LJS|uQR|SG-B@!tg2'O6ݻwlslv6H5NB@! @HH{J셀B@AU#P| %pVB@! jW$>'V[meD^N+XB@! @< P ! hEc瞳.], ٠bbB@!*?fm ?%>wmӟd п{WޅB@!!IcA B@N_PWx|jV3 Ɲ`0B@!pqc喳ʏuY'uP~;6](B@t2R)@P~B"H'!jB@(>x3vX+>?oV#Bp_z%%k @vm7)@(zB@! @T QrW! @  /[ hTB׿eNjwn6!G}d[yS߫ B@!ЙH?7|_LBmXï#̪HHz|qǙ~{C^G{o]w]sW^1{n_HcJ-o_"-z2׹sF|[2{6't"Z`U >|z͔)S̘1c /`wA+nƋx|t&|W\цEYd! X~^ O?uHՕ};$Xr%OzB@! Hz,Tx=(@+!ef 5ky7)O>b8 [h3fӒ,O饗€hcx;N3N7nyglh aԀh΀?&ڹ(EB@!;co3w>}zGeY&GH$B@!@jHV! hP~n<¢"! B9 wm_~y{K7gEO !  )@:SoB@!ЖzʆCr)zSIҖݮF ! @ "{5$>?ͮjodj 9 5|6KL:'p0bW$B{XB@G/0Ys5'luu T^{o(B K|yxyΛ7 @EaB *E! @3! H3"B K,իK ߃"! B9 W=czj~_\?OO'x}~+_\nB@! @gAdUV1cǎm*p۷bujƺ.VI}V$EFɯ9[@IDATMJ[nY,AzK'b7Q%,ZhK4h 74#FS}`%4}m7^ o}[?YguGۺk_3]v5n k sOl4$ʂ ,=z~^!i9B_&14OmiӦ\x Ds (?b߄B@!(pХ護ފN35mH4&Qtlju\߳gϺ PUspnuv ;})G~ڼVswݬfVl8>>.97ӧOm$!cvBSٗʠ.Ϩ8Zm՚f%1e}/r6Us"ǟg_V0F! B PWKÇ^;5~>&LەW^i卾npQ}|Ƣo~`MuA|Vw1^xa9וz}ٺD8$ڐ{I8ik LQGeN=T09}%fmfRϙfi7'\[u=Xΐ{.3^~lKޅB@! G4o1cYveM>},̚5Z)[x+oiya=MaԨQѣG>v} š`/CK-T7<^{m۰N>hן^oA?-qY )(8pΜ9njn^y}j37x^k̋/h ~Wmtmm\UW]ps.b}뭷g#,b|f=!GL|e^0>Co~3fa;/yG׏'xz,L˲0X&Ol(#EgM|ٳ &<{ PI˺˾-k9q#Ԥn_>qq;_?s'YǕ ۉ'}l󟸮k'ދ`> # G*w=%IzL4Ɇd_s /1?xuVBȺS슄B@ M[bSȡwQ?a^z%#~ZqPBP߱|8q3gb.ԟ[GFs?f|f\9?BS6s1?B$B舰A s ֭ =(98IrtM@A5XZyJQ Whrk{FZKX"Jn z_i[#.tM6z뭗ױҞZX.L65e䏱2*u\}GeTmDɅNaԨ4^.!bf[Ec֝B@! @+!-ZOG뿬O>7v}s9"j>8Xf1w1^n`yht~%A$B@!@af${ Agmo@A<^IVA4D A91ǣPaIfj1690sS~I@@n^&#kJsj&F_7_~a׮]͉'ؔ0ZB5 6BϤwaHf헩SV[meDyD"Q2  2*8_aaK^/왇rœ__*Y~g6?#!h̾s*811Q|7?яwcDHr~;esdxmv\0O2 CyUP,%8D֣*#r VxEA`ܹ6ܰ,DRB7xS_Ç)<':!d ;y@=]v[)@,B8 g}];0>H%ߎQYAc\(\KhKvG%Bm"ٰ\0^ _híu:F@{_|z1@kSV+#SL1'O6EQ QM/B@t+@8Bȗ"HfRmt\$.jQonFz~Qޞ߭c(_k"lNKx /^#@A~b@)>YN~!9%u)$ϼ`< !4{6/v($\;Xw'<a"+^+FMB|mZK apw[yaP³11JB@! @}+@saSHXX~ s<ߠ wa^xᅺ*#ǂ :aMVz0G{{{"ڠAl]VZi5~QVcV]KGث_~*Ykm޳4f"ֽ @HXSO=eXk:c~߮es'NzmJI&Ykg֔Z rvvlA?[}TZe+A)oHBk!^f婿GaLG kiUWM}.B@! @gF ǵ^k1%\2,*xtXWq<:4q<ݣ1\tEqEURVnMaհ&12֯XRaÆYkrK/ Ǒw㏷^PX#,CyTvǃ>hs@XF??)>!kEy?\KhKvnLs00.O _"V^RF1}^ָ"49cgHBl(@XQ~ -[>}ETB@! h6+@Pz H.QflkzTE׿nȩ<E)G2uk"b Ry*sMWjβսO8=Z<>ɂ0[+,&nJYhꫛZJV스*uAn[o*K#K6r5f!QhrP}Czexoi9_|E3j(C"2Bc뭷w ,xB@! !p"t!"@>/7xYP}c7poEQU_AD,~FY;,4e偵_"$\XE cO<Xc|ヘ]3|4]ݲAw_<(A>;7Z^iy)+xV2'PР u$Ie%}_X-!= <ܯa^xUo#Ϭ&M2 yiB*#{>WZi%.B@!+@"r 7^F|5.8r)TyG̙3ʹiQQU_@4D>B^q$xh{>}c=fWu '&!`AAd*}Y)>淫y9Jɉ'Z%@HY_  ʚkTO %PĂƌ wh|ysyy T^q~@Yg O]a&DH! B  9a1$q2<@B9}9C.W^{<ҥ袋,#9cN:$CpBSVl-*@([ݡj@q 7t(>@ O I̘5?I<teymY=RwZx\}kt/!8@KXO< ! [}s6t3`GSQN Dafvu x`]4ބB@!+@Pz!!{StG(4"h^$ԛnMۭ[E؄Ha+Br-7R+S-+g2.mEW(>wB!EiLJUH~Iەus]faaThbx6,Ckwh@ 6س^B@! h~uE ulьw ,X5Lc,agu]$ :&L` E"S|wqV6hݑJe anFM o=<@;6m=a/}Ỵ>jw1D?s!IC'>J,<ȅľc Q8kC*)W^^,xF:ɫI c8M;>YQ1®qYӵB@! Z ؜~7iUh+lǎkzJREXo9̃M0 ̝;לuY!(@Q ByM\I<諮Òy$ b. fZ{ݝMڇ\`X`.GҨjCo9JIj2ڒ)"iWxA*t>%G4DeB+^@{NB5ʎwjS&Ol69;yjс?ͯX^aDJQ㏭>}+gB:-]'BL+@8#H J!2d}I.׎*#7n]w}<vJָQ5qUV &2n:rpA9">q;Gy*|DZ`=ڹB-vu+k3?Qw}6 Tk m4|A\ kG}+0r}y$=qR<_^k*zEZF sdE5ߞ{i]wE/vDB@! ^W7#Xb}w*r!XMkiӦ5m=bA+)>9 9]u˜%0f_ڲ,c-@ L!D{f kuxE9~"Ӛ}^ָNCe+wu+mn/OyEɸ'v{g̙c>k?p #eb$9X;٣!yLf_^ aR> "PLSWvTTx߫l^z4*# M78Er-'& c1a1VXa8K"s=g:ꉺ!Ex|gРA6 9Bq =o;XoH(ꪫr-~\k֔ZqV<д~5weޡ=*?-bv-ۯ򊽇(N.2s.9*ҍg<0ӜܽzB@!  -C*xDyv]v*#+$'vኊQGtk""P$9 }u moN;00a-Cv$AGcPWx| ÅuˆÍ9Zes}V[YfyR=o;}tA6X:%y<1XeEA8>SVz땒?ޙɍjׇ~h;C>0#G~ (pD@N=DYD-Fx[YT o~*9?ǍO?ݐS92y`oB@!Nۍ$qqm֜Yײ'\ y#ds>OC`ܹ3ϴ <:sI/9?aЊe𪫮 bɌVW0G sCXŮ樣R|-QXv:}Y@)I~^YiydwZx\s5v7BK/d]W[m58H^zYRȺ0C 1ܯ]qaf9~~7mXJw5X'&lewyg/GB\>_UXa/C/Qq\se&>w5Ѕ;DydzgoQBёk-B@!h/; \X~^xMZB*Ta0[nax*xC0egϞmXLHДEC^J(>@PGyک4sݗ=-7Jb͓+)cJ#MYI*BU5}tZk T^X%&I @2|e_Ӽ<2X!  [V] aOPy_~+CGy*QYI|NΔa H! B ; WPel gQc%G[s?8͜9L6mu^۫Q=<~}?q%(Дa%8^xB!o(CSr)nF @'XlB' ]$o״sݗ= x&$ ,ubB03`N~PĞyG)P )Q/aD}cw\1y^oNfmfPؓk<]igX묳߿ {zB(=-rc<'Md6pCQfspe1.0U+[gS! yhW!) 6 O;tOIp M ^Na+/ q|]tҥ#s%4֖BRiVs1c2.<(BɅ+>NiC]!ヘ(><@w`tm$$_GUG&eY^5ǃ1,|̝;לy晶$\aq9 K[@-Q@_z%[';a`Fa,=~OMB;P Y,!P|{VJ/y|ӟߎMY$y5Bw9Ҳ,c-@`WOYA:^{ &S{z,_>7W9_|EsM7J"  w l'/QvP ֡ZIsjɚ3`"B@! ȇ@pV\p v- lHJյd773rbIk98R!a%BEdE[~*@Vx1hرcǚ] B+9ꫯ_E͜9s:IxfƅZ%<*9XQ*љ冏*,B@4 lCXέTϛP8)@8%iӦխg>Qޞ`]}QGƭZխ(*B.9 ʼD}8q"C~ SO=v9vG{߮es:egk m4{U&V[YwBq^xqEk#B!+l=zcp5Xݻg9^*7@<'Ia(H|Κտ^8Z3mx됽!EB@!  Me˯G>8.'M#H?x/4;q<ݣW_\|Ŷq&a-ܚhhc,h%GQu?Ѳ﭂,XK(8o;  Z@e M>_ ,CZnˆe+A(!%{キyW y/_0«O0C"dxM:Za;'>GE?TI.F-RTx ! @p³4B"`VHZ^QǃXcIJǣh5 q]we+HteG^uZ<u =HM;bUIB:_1h @+-Uyw ߽m BLpeF߉g_/Qz<,j9{l E{5V"E(7_(B om=1W;@bw7A#OSӧOJ=h>{cXx牄B!s}Ĉf6%b( .?.BktSF\!īGDŽfU̢!f{̘1k\C{su #'[ߗGsgX&1F EAH%W^Ē;e+@h>k:kJy4R|Q*+@ve,iytiydwZxvlh@]ޱfg%_Qbm%j>koiqx^@uN>#>#Ǹq:BX5+.?z뭧q'|Gߒ{^U&>wUb|zyU$"0c Vn.m)mo߾ tR쳏/ړY`|#v9҆`aB=0n:g[s=7xC97 ^nE"FG,*rIaMr+®W3MYyg:n?/D"HX⹅emvro)_磬^&oH9AQ畖OZiˋױcz,o /X;͓O>xD͇@Y}4/Ҍ{/.g2daGȁ|"_~/iV;QzH|ZARPB`qviSN9_a̫jz!eV9!CC#"]uUSO>i1cq׎1"h{wm_؎BXX'^P}CHH GxޫWߕwߏjp/6Qyjv͐_ò, ;8k}l!r[0` 1?RzB\!4ݵY砻/{WxǷD0OYy2cʘ  ,~"8ڄ2ef'f5֐(Ⱥ?5IugA\V,<ݿa>sA)"$$]\C‚0e猩}.ϸ"Yge ʯݻwn@=P$|9AP\%gbo>)>|4}~G l`ۈA2nFj! @'A#Y71 #Q1|@~T\[(t ʙ&~+(+@X:l[ʵ4B:(oD Xr8S(~U o#PJߪ!~B@ <*jHrİhTb0x$/j^*!fDZ@i V6U>;%_fs<<5pv$#PTD[!nC9)>v/@vf,iy |ҥիp *֫W\ /q3ճ(wGhTÔWVÃxt֭1/^gI2"w7{ ׈x$Y47^)h6PKIW7_̚5ˆ3Cʩp}!pH2 Ÿqlq$63aH)КZn83lűt fl[oe~_dhwVB]A>*>?pkM)fAN4QJ!X!wJ*Qv^"!/xB`ވ9 _2K.\i;1:rOU,U~yO?nWB@oyۭzf׮iA^[E XxVʕܲł9cJ_~U.00%D1_DWtnNYFԙ K"oQ@/ӟ%/ORDy@. +@4έT"5w=BǃzDWz}ǣ==@7~nM\eUXnyKK{65NC z/@(u acؿŲAz(9]˜ÉSvЖ@XG5ljk)@PH5kkT66 Pk}yY D8bIZZ\',oa993CIW\a=S8,wB2e "HkaJo(Qn@gG=XmeABFUM47K`1~'ZoYG:j(%5( Xx>"K⹌e QVɿff"cGl`o \pr^3#9{x8%B\YuO[6Ƈwm7[GN s7v%ׂQ0d>c a)JFO\r% ǔO^wufu׵s 0XJ2d0zӟQ6/aРA}c`1 >v;/Z` qp?e./'zȣ< 0aOAt-(ªGshꀌ}߁\q0(;B㏷c9"ꡇj>y|~_n0͍{k?,,L 8&Tu٬^r87 d,hhmxxcC-P|<%Grh ^;jfޡ7v^RBo5OQX_bya/ԉC5+E 90ZZƕљ㐏`–Jln 0C$NRe}K4/2+Fow`Y7 @IDATX.#D 0!˦FF~w[n8w,a.h:!,gCxpӧUha>^3⹆3IKQ,`m2BE}\̽ꫭ b}R.S_ӣG6'|rGN*gy6݀ 6#dB^j(|Ebay{Y0Gp>l,7f⮭uCr0ea|8&%xޢ4p^}~UWhF Bn.˘PL"_+B 4!緫[YK <EHr󶃺H@hGZDx" C/nu|XBHD큀e+hmx MX#2Vv­#?DZPsEHCb\B<_YIcP%X,cŏ` a9/BM(Yz`-FPڏܡQ O.ȆBKB2!FB=`=xbptGu ^3$Bo, Mٕ:c'G` oڂ@OYua,xv 9w/xr"XpG9۵Օ5-BOB³P~]x+@F(,LHG @ϟq Bwk{x} AA;!(~qAC?<# iq31|Q9J$:g.>Fk\aO >#93g,x# ayI0ȕKpv(`I->v?!Ox `&U-^9 k[ii$o(HR6YӶ3u!緫CY$Xrǵ@@ƜZzvxy$b)j>/EwP^΂rNR.PyΕYÐÐ=xr]21z ]Em伎W|=F>ẕgG% Imއ?!QxN kؑEEںFN9I(9?/J$'x"[aE9o$ <4\$,VQ|IfN2nH~λ˜Oy晎P~93{.߲R b8O8gy]BR>gR 7$: Õ^8qa!̙3CqeS#y3V@A@ec>w%8qSdcHZ,BRV: Sh⃵Z~6sR|ģ]ָ跍MM8<ډ&6 %oWsݗ=-0䐅s.7ZZY*`Fxz -Ywy_m0#\{^CfOWx]bB!EV` \M<8cW (aNk0x(G&Fc},'iu!P&xs3 Bñkp+1FPžQQV֠ZBK.ȲDw=BRE~47(3ɷ|BArA|~Gi"YzrAF4y!0%SdNΘr`ʞ)IHIz1>k% 3<⩅)a{ :ףZԪozײ>q{WWJkځW:D'yq%ᵔo+@Af4:.,Br@v!: P}΂Ba+@Bp,"hRFi/D]؈rWD: `Hh[1{d--BlxJ4,kÇ.k\lPƜHÛkXy~0!2,h&J߮Y砻/{0asEGĜ9s׬#! x9r(s#R^*ر^FW|K<1A!㬜12 oqk84u]OÄ@d}NՙG !DUBjk!9f(>Xn!]bcM]\'~4k+ng)ϖZP\72$BF{giN*?(iH(OX#BG։1| (ja~Bd#'0U^PŠ"sp`Յ> jʰ WVUy⌊Eg<rY2M-ɛJ7ߴDȂfugiVO,<ݿ|Y!Q$: `wBWeG>k2<3()ܞC c`gQ~m>]B*a hP缨9@‹9H_I\ʮ Zeoo=Ⱦg 玨`?v87kFݦ!l3*DUo B-@zOFq~i$ojթj-PJ5$i~;Aw_!F>SJc!Ӑ[7~vm |XLtq=X؃ʷfBDa|y)8>v؏5) W hv$h+%% KZP}%(UbIC\ 呥`}|p̞&! V!2D>Ãx a5Y} (fXQFcћՋKEAgT:H!P%(^9G#'ERN$(lfήyyDZaz57ʑ5$ă駟n__}촓 8%ʃM.oCKWvFnA4Y砻/{ZXwc0Biyd)3z-<8Di3k#$7.*vuC-%Csyx$Wѣm"29I=E*gb"jN\?d= ! D(L<*d)SXC9Hm]u-;\Itx㍋B ZxO4ɼkauҩao2i?xGQs5%W@L>]0`Bf7SNxu]6qR\G&LJ:')ُO$/}h\{M~l] tQl=9VUYV+ K3$qm?x! !ƌSquU䗧ϭq8]¦xh0#!_GQrGmq `1bxG-@`(k3ƺm+Pk m>vf0XX6h#<$<h 7CWe+Z[oY<@`Q` 9,R$(zNukBqBy=p rT)Gx|UXma^ɝwz pve=MD(%| W^ ure"贈I{-!Qn`00@@7lMf }r{y#(&4'/AdE I3x“ߓ=xg$<`}{ߎ_'teAkNJ헭#yYV !Z7 1H9rvt53 G`U * Qs"ʋ= Ghwxʔ0<y0gqg6x͈d"~'ly֢ <ZE'k$ǠO r&T,FQ+yuQ"s7 kDݫFBhM39'E#CAx կ~eC7'l.{^? L~ۼ"e/rX!18/TxAՄPBpgīWD(5&;y ;w{`TO\3FZqG"~fCnqrc97L*]80= 8g`Fhʄ"^>lq >"Yn(اpE3tPAeJZ2-(P~(UQ! &,I!6p0yGz=h `f^E  , 3ǣh5`fx5K:򠚷0N6o%4׳FS^62XDB9>es6yAŅ+c--yvmZ`}qkbY\>\]?;BXҊߕ yY +7<$=PR>c<K[ڃ$<] $P0OBqBH9E͉Xj6jܜVhG8W]$vXcͳ'xb3> dI3O;4$@D)gogNx^ѫW/?eqoZr j /ŔA! SH=_yրa֬Y6zsQ 8qVI#FEn-'h7ق rŃ>hFx`ԾN;YOUGxFqEM$ڊ-:cn6{ J4wuAq!w Dv_I|CZ/* &D?Q.Ij !CbgϞF356p7$'p^8܇ cJ-2%D&P e6l3PWκI髤I>N!6DpeG`{aÆ٦لWq<8,TQQLߚ LzbQ.[y0:Ppp;Ee#D?c.RLK. x@!r~;lʚXɰAfȆ+(r C9#1$KA(4<)@8ćOS7]S~5>')2{xWW_my`=sK9\g{\pS|P(>LjrSԜ83)k>R@gB H CgA͡rx`G5C(0C.lYsC-`U--5^;w\qf!$IC+@8!wm “w'N][ (Pt]u,V"! z a0p&n q;PbAX,&(㜽]weCQe"NoL>}leC!j^Pp/\B~d&mVfB@!, ɨ/"{ Jru`HI~ kVAk"' vB#M#$#rnp eY)w>^Ca(~AIj JДj g.E E8ow6kB{uCiM+Ø$[OM t,53ƻ>"^R'L!Ъ j Sx #3fسKxV9CC*x!2( +@dg㳨pׯ ?"D5DXS(~U <Q"LsXBSV\φ!MzuZ롤[ڕuQ[ϴw}"ϸb e^cO΋9@-7(!g\Kzl}B%@mdścggUVx!_R]4\N0Gq5$BF0PY>þkhx,Rh*xDXߊxw=cYpq|9瘓N: )+rZv`Yzw&a${W5I-teyO˃H׮]mN. Y(-,eFW_m.b3sLw [IXX#ˠyΆsyc!s={yg ńBBW#㼔!5!x!a%B@!P]B_B/(+@_feƪtJg„ ֻ)ds8fg _ >?}viV[mաB2$7oK\(JRET7#I5teyO~;+*O#KcWZN9r %1Ã,آBzkLr*(@s{ eڣjB@! ek~hZ[s5#Fr^eP|/b@(p %62'_u#(n?/r3@;"j￿+IgթYh*W4f;CV9ng@~J$B@!P lV[Hq5έTg$?~KVG1#GLUz Qޞe_m)!3ܚG!Q$a46l*^^kN@T aS{5]va\Ax 2|'.σq==qR(P,X11l /B@! @y[oek! *xykL\Z׿֭pǃ:~ǣ== )JEnM$,$!DOjƍgE12/-Q=g[f2th%Bo29F \y}ek m S{Zڭ}QGBZdt/ ^YC%e+ZأG:-d"'BExE$B@!P${K%7;c XXotMw]ӭ[kȃJ[.ϭC )$d!ˬ15hUhF m>I HdAAAdYӵBG6 |Vg! B@WpƢB@]Ɔ qn:|~*#m} 80q< Z_ЈH6ڨcMDńI̚kix $t]->鍊nq`>`Ü>}!wܡ0XA{J5 !kSY}T׮]MϞ= +ͳ,XK(8o;C1;cҟ;Z1j(+JX#JyY t\9IȋbCqz>! B@t+@8lGG:>L8`ʼn0UI6~x2﫯2oU&3xO;`|֪UӴj*C%YIkcR!Cst+ ,a #?&5xG?95ʻK<{K 7ٳgq .,BV?̺u̪UJƜ6_. JFeCF2bvmV ܯLލ_(ىAN&voUxI:VF t#I \߱^#mGf͚eǺ တ1i$ӬY3/1~0B h[GXcme:28W.r[/M}C|k"%|/!"" " " ".@d([$$߿O,0ZĘe˖6xƍif24fE6dĈԿY hwFF77d_ů r!BlQB&v}{ |*4od2SҶ!oL31!D> D c}R፹m:j׮]( \G-Q6O{C+}]+B_aQ&}2x&/7lFe1-S++w r*" " "P rn!!dB͛H%ߙE3ze:,N-*~lp /6m HvkM[e_ +$Çt\AI3%H tLU m~>JUvAY,\O=TsG(ѽ{wSjU{ͨ035?w914j6lZÆ SRz~a.4iaI%7 Iem#ە#k} J078c&BKtѧf970L*f|S,kך^zɴh†vc_s C{K6ΫlI߳G`ܹK/>:t!X,͠_7sO ͛g~誾(Ht}q~>S󩧞ITeiw8c'|b{)W\nL`~PIbk̽=MG^C䚤&}Zw@2J,Gq0" " " yF x]P$D.^{ҥM̌KcN[jFUt Aۃ'@87Sw#-?a2P zۘIsWLH(Ht}6q~>Sgք y"+UԕhdWԩSm `aڨBhTs>1,]~g 2x۩E`{O<̀$T6VJ"dx 'ARˆ ۛouPdۄϥ@1dA3fzk_dpf˗R~0{̘1%K /yرy7̏?hpG .;.a:_$:tYtM;i"H :z?Fdq/tlf?zh$3F^#,^NVl$Ag;L~+`@eDEf͚#Jn >7e祜[x@aj*s[:Đ?ꨣ7dCFa o u K+x2h#IQYRigQZ=IJaSXVZ6s=g]weC q`亡E ͫWnNAlT߮Z2rʥ6=Og( ۸qRYdv&$ tDBU}O䥍{- ~ 锖\(#@!v|ºν*$˂ƽz ڷooy 'r +fk<2.dg;AOK 4%@K/ !9x.rsym ga -rk* B&u@ naE^l_Q/?28Xj_7=x2w~x22S'=Zj=8At_/^(y"q{29\;s17%PM4h`gpng̙c=6;_@׷ukm۶6˅ƽaׯ9LXpω͛7,WW\?>,~/+DdMRJrxV#|)chs,x.%A\=Ay K6d| /`IhJ6# /Agdwm/Ls5^zƄL qDf= #c=V~v[o5ժUK88ovk!W .&0ݻ +SA^AX9ʲzRjaKK@!%YƱ$3`eA+  zhТU_x<󊮑'ga4 4K9A -C$04I[ED@D@D@D ;7`@fɆ0tW.].%AxēueYx2J;F a`j&#{"Gx$n*= HQ[["PsE &XC&XdeFWjpI IiFdmLeWF" @wLNm1/zg 4`pF%ssW7B_"" " " "=@6{>h^UrGYK,1? a!'A l3f$v%#!+O߳GWXqŅ/ 3H^fM9O2xX9mڴ4Yg}=;*"P]߮~AwO?20{O?F#O۽"o1~O2~x*{|*W^m \?Dɘ2e5N$K` z̽H€¹d#HѬ >y{」HJD@D@D@ @ 1>|6l}߿/~lM`A2HT]qvso+ґ;#۵kg0@0Pmx.vh?(V97DDFBHS%ƍ7hFaA(1'ւ L^YgU3U1dCFH%# +ܯHbI_(ċn7/pX$GWB#v{ |* 64m۶5͛7#† yqGjgi;w ;Tf4Θ{{yEIF@IDATq]Y 49@xP@sڇA^UrKYoرi֬Y r1[bŭ* qEĴ?a%$sf_s5/6$[*W=Ly晫2=/ ]߮m~AwTe4{ꩧ ITeiwx7l` @dQ 5ooC5,*#;{{yE{̙cI %x+G2R&" " " ȹױ pZS!rJsӧO*OPcu6 9 JFNeCF2օ⍻ϬmT+Vm̄ FE @5 o<|M䌱JUF*u%UT1oO2tN Ex?d]x`Qte~gM:ud.\htI6N { 3&@Lobm?f̙[2< ) C^HbTU$~qNsf'Aggm@s(Dt}c,F-jrW^ޣX V_Lb =7?`@eQ(q_l0 Q " " " Q&]Ѝ#\CZBŋ-Π [Fγ In:jժR[1ׯISigiF?JkGMovB ~uqY%:O={4| eVJd:O~cƌT[v8qmXdrM@׷cuΤg&L(øЗtB6[̳C2tВ~ 2'?m^|W@Rcy)x.P8o QfM9p rBAlTy  `\rR'-[4 oԨQ,mx2J;F 3w}@:,QwOd9Yir'Ec^ʅ">#Im~"& oΰsJl%%~>KH˗/7x.^ꦯ,*A;aWg}I'd? z6 I~΢"" " " "}@I _fSHՂX̽zN+*vo1v8o dwޱ=zQHȧ۴icjXI1w# ~Eqx:a|THO$e+H$H(@7ydUV c6dJayXϚ:u(Ha:7\xަlٲS" " " "  [|? KP.YCW_~Փl=m7o!_~iT/|+TED@D@ ^zqF3{l3n8;څEQs$vqcϬvPsr̴l҆!I%[glȈC? b 3g^֭KW !g2-Î2D׷k3U[4hG5Dd~'_|a"_2&Md5kHWWXcma:2Y^=2OHFZ(Y:wl@m#0|p;C M>ݼfu ::uT;/;@"(%[(~ob E\^z:khAئҘِ#Rf?`#  ˬsgK ~͚i!V5hJ5=S:>]߮~AwTevi޼&U*s/ۭ[7-Aω|U`f#S֘{[iӦٰi#?KS{ UȓobM. &3J &Aۄq{JgQ>aÆ6Gm\rT㻞dr2;'|b=Z-ضm[y/Y#" "2x((W.(HuСC$#1V=#YHoE3ϔ]75\cW+.WB"TSr7;ws?'o?k,?akp^Weذav@Ht}Oc'wc<}߇z\{~ w~Wr/rmMv##Y=imEU)#~1+'SO:׺DQ.Wh=cۅ+!?(x0j}`ِP[n;lt+?fEzOao8gG޽9Sa{PX-G[ sϵK͚5у1pUrekĢMCbN3+w9ퟨd1~ ~BO:ryywlt`?" " "Cy 2$dKŊSZLg e, Kcm#9\Lm_Te;cq<@ӴiS҂1H"πls+ȣ,~w]{]_ڹkO^|e첋K,\](8w5FaƘ'/.*U6^Ɯ3gM\((x۹$2Ƶ?\|%?\39IXE- QA\D1F߿5v פO֧*ퟨ֣iݺ2c ̊azY, s1xZ?㙨vr᰼7o oq珓Wt+=9'i#2i}8ݽ2v47m[ s.^2~8n?IZB1i Oȋ &D!q<}%" "+@rp(` dRGidⷅDλ˻0tʋ~LT@. )" " " " " "Pp\)ch 1I< F ᳃:U520иvx@!FQ})Ƅ" th0`@I`Ÿpa/KEQaZliMo]ND@D u2J{@\K.9!E~O+CV*VLdׯ$GcلFD@D@@Mq-K lLh!"P(ps=mB_oƙFlo rŋ݅^." "@JL&-5m6c s{:ӣG];,ZȶpX5jH?Փ<])[lǧcHGD@D@"$,WD@D@D@D@D@D@ 1cÉX*>tL2W^1|p5j;l;p nݺ)ʎٖK_DD8,Je@ (VGH<&oކ|@C+WZYn駟̒%KllKzf3mڴ$A>" " ' HH 2gq& g^m#UӦM-?8ppRjm 52f0 :A0Q" " " ~iXpM3UO`%y&֭9CmBrOƑ˶ ¸aȣ(};@}~@i̲{+UNGŸpFo˗7,al EF2 ӧ`:QٯjƏo[ӳgϼ4҇u֙=?J & H&" " " " " ".<\U4]?lH`Oq|_ Ġ i,X`~0{ҦM@,j*q;FLH P>" " " " " " "?!9@Tk4ib/bA8Okb͚5sh]' H:(" " " " " "?X7oaAށ~w2( 40;f֯^VE'p9m"" " @@BEAD@D@D@D@D@D pBtM!> $+͚5+ {E2eʸM}6n82_eU$" " 9 HK@ N>x bŊߩ"7jԨj1]2!Gأ/^lß6 ܹsMrl˗/ok/(ќ9sn ;}{_qvkܫR=vfڴi϶mۚw=nظqꫯ̌3qs3$W*{_|ac5jdvmTGoƲc=L:ur=(c\f͚euZt-Y̞=0v*T00iذ)[l>d~r_mvywwrǔVGR(!$RD@D@D@D@D@D B 5Ps{L26f~ 'N4cƌ1(9Ou|*ζ _(u 0XRas^x*/_n=9F1Cz8 q({m;@֮]krze$ ]HQFx0 q`O\s9#RcĈu믶g_|M: N+VwD;û{=fҥ>aĀ cwyiݺuVoڜr)欳β+|ggϞh%H,5k2z$_D@D@D@D@D@D / `aAQJ~`ʕ+m].T{k„ fv3Ke~(fQF81<f˥l@=#0JuL~JkxTZu+x`@>}=_C yyOyOeDXjD@״luQy8SOYCoLT A0z~ڪs! <_~91sL裏.Ȯ`XR* ; 6̎qg ;xs7ߴ\qUV؊㥗^ Rq0~ }:رc- .8C[o1!s5k֘O?\xv"F,,1ڤm" 2ddgAY,H /a4ibP$4$L3itɶ O(u)'x3x`6(R6}/wߵF믿t.t~6m1%;AXQT8 C=dNj:wlPׯ_*1i}5vC5}g-=PkD' ,]vС5Pж>T^r-Zl?~ݎM6-Qs <נAl=xNa5Ra6zhƅ.]XXb?lƍgg۶ضP!nf6]yY N"4U{Gkz|`T?}:蠃L>}Q~3Dlزgyzɘœu"@:D@D@D@D@D@D@T}'3Q02o f_euڵ*wӥm(Q̢X J`VR6 YlU@@$aƜ:cJdpAbC\,yD[i3a޻&MM7dCJuxC}g;GxੂOlw6xq=2d> D!PUD93LM<c69F- 0\0ۭ cE3}߿=bDw}v.RS^ 6ԃsϵ}"t붽cǵ)|\$%[D@D@D@D@D@D / h"Dў{igѢR/(YQb`7cB>2\.ۙ , D aB4(mw.eY,$B^R^ y8uA @7~쏑o(:.I5~.c ڄqCGv?G+N:ix +!A *0汅<)ϷlHT@P! +sꩧZjmepӧ}< @ݟ|M-թO@0 j@^* ^D E'vvfMvm"o_BQKXȱ D Yw\M;0;y(^ N(ZBON<˒Ufb`&K<Sc^rE@\\%@B`1cy~s qLo_m\ Ad3f=g*/iIsmnuPG|;f9NԕK'a78?@ t 1.'O3f0e> txF ^$ܶt?1p[vM)^K.x/ݔǃXF|rqW0RiJ3F^w(L.$9؂.^DgpZ6@D BArY,*GPJ(")CJdJtzKgp L`hȵv4h\|VLnJ.e=zXQ;׾\~6';h;8'IԦҿR&<&="1;#yl}kF,HԞ8 ƆL9"ͺ7n\I8,B$ReT%2h3)BdtE)^Bt\ʦ(YXW1x`{ٜCe>D4ddžNᬳβGBK9fx'=cDA~70(?&t +_i&2DmJ닶7@{{KfcQO~zB䚠0(CjժA+ Pa?$Wƍ޽{ . aȸ|pxK`Kr [|0JfL ' '$0B.lɫF%@rit R^HO_vWx饗C)hs&fpGQFKcI@Kf!Wm"uEjD ,̨bb(r[{CمYڕ+WK*nd,mdIN:&i )!0Y$^lU'[wq jرf 7p4hf̙~08dÊ}1"8)RsFXNoMO?$FYB~p իمso4_|ݖH́Ya裏E2f3qDFC"&R'IQO?Mǎq^) HBbs O?m֬YwWϘ1öY*"Pt&(" " " " " " Y!W%!WᕕHH`IͪN$6wq=} 7<Og}ex8ZRt-Ϗ!>'lR&MJ<{w5w5PW$F{'aP Aإ|)3gf͚ewb s盦M0R ιng7`xnp`:3Ϙ_~zZ`PXm9S'|b^uS34ڵaĈaÆ~GB=wСC[0>IF. "@@BEAD@D@D@D@D@D +\]J a64)5j%%# rʒ*)el6EJ/n<5ڷoo!:u r FnBɡHg;9o6tRC^|*xu]ֳb?^MHN) wyҥ|K.2.&L``!Fx}O?z` M_n]k+iڴi126ݺu_zw9*sٱM!h,s\;q[O2yDyt6 !Ol|L*K`ܸqfѢE3,aB&#x}VP$*P'*)MDQR+xty}O<.8+$ H.K@p!Ay3l :l03ep4~ + 9 93w:x,t-UD@D@D@D@D@D@D@p+z"JIIܫW/SkS<}_~ن '#F믿.OD@D@rB@ 9." " " " " " D E5kS8ƍ-Z`Nb]~H j*m@!FQ}ҥKmbJ|P3&0p@CB_ʧ~Z$^p 񖧃flC@mhlM$@!J~z!zjۡ $L2IH8䓍u]3骎$2CD?0D% jѢE#ݻw75߷~P \r(CF?m -ڵS?=2e@fB7lad"cG 0Xa@AY2D@D@D@D@D@D@0p*U/jk֬1+V+\.-[8q3fM jԨ [-7˗/lժ >KD JD@D@D@D@D@D "7!2e.xA> & u֙={zm6l0dT䂀 ." " " " " " yCE4͐ЁM~W~zHTɶZjۛ=C<@"?Dj@. ;v =OD.Yl=|uW£ծ]TZջ9ٖAxmۚJ*%DdU@APD@D@D@D@D@D@I`ƍ614?5kFjoƍ37O[!VlYK; W2ʔ)SZӴ]D@D@ y3Tj@ ,^؆}ݭ$ Ew$y i&OllR9(j2" " " " " " `!zƍ#p¹p~OW_}e``XredejIED@D@ 0@/_n? :y@T nVҙ#8^A{PqLժU#+SN1c]ww)@Hu!{_~rٙ3SN5;~3hҤIf[/׮]k^z%sWl]v/17|Vfⳙ·ժU3w(^xaIV\5jXH2emyFEVG0F/$2dï?" " F %3,XP`]WwD <@j֬i F.ׯ*qlٲ|1z(t>l&aAbn)" N:H&bC ГmÆ +ģ>}XXʕ ˇjC c!B m۶NSȷj@\tm*ĸ[>$PfS"?Jk:q kժn$#6d 179䐸Mb{6T+@2e;l\UMp'jl)լ]v" ޢ7t>|!7Oboܸы_E $f-$fQ0xuי'=bsכ6mmРA}AD@D@D@D 2 s}f* ZU`0tyfQVʖuⳅEoʖuⳅEo։@5zϳQ:.\XqT2QcpHxt5knw餓|`wC=Դo޾CL>ݶOż>vDFb?^D`?XV?yݬX&Y>$>&M*YfK'vtcx֟l8ε߶m+[y hsiկm" " " " " X~ !ʻEݺuͯj+c%K{;5kaS*''ٶeٲev%N?ضD;9#?CˈDLUV>)9@T?@bC=d #%vLG-RBj}G W0mw-u=!x!H'" " " " "3s)1L-I_*?y' ~q[yOA"" " " "2JNL׹s xPlvfʕv8sO/5jVPD^z&D K.\p/VE@D@D@D@D( 0)ұ.3]tUͻLZ( >Og=;C3ڵ۪m1*mŰ6zkP+9soWjռ)" " " "5=v}wptjɓmEゾ;Tg=%Ic=֜s96ÍW^vyka` `@^6%6ݻwk/DUTr6l 1+xK/4$b;+h9/H~N,"0.s*=U+D@D@D@D o, GP+u,7tm2Ď y?kn"{5@/bFm^uީS'sI'"+8<@TD@D@D@DU@IDAT@D@%矛!C>׳gOӪU@P1c|@!>j(S~}@UX»Go|"C 7ٳgqƙ]v٥}O'/y?.B+'*" " " " " Xl 55i$KVҕHvR?ZaTD@D@D@D@@ ~zQ,x} <6M1`8Bj@4|bt#}kMJXh5OrPg" " " EB e˚=z؇w4o<1zTT)mSD@D@D@D@DᆭsΦ{jժiӦw  %?0$[#^zIF,"@~.yMv̙gi `ڶm&ŕzj3tPPED@D@D@D@D X{9cA޽m`%rׯ߼6' /*Kw};I{F … Ŷ>)$m<#sxUV믿/N;<j5WD@D@D@D@M`7ʕ y E- urV9>$?Gݭ[7%@HdOرcKw9|A. _HO pCbv3'p3Ȓ b G֓#YVXQ#G_$@ L>ݰPQ*_ PZO?Yv?*TUÇ7SL Q{\2e,>}X'/" B pSF樣 ӦM33gL:rOdVV>~ }oޑ+.OD@D@D@D@D^|O =u+cFeׯ/H#ʔ#V >L2ŊmѢE@q-P0X9@p5cذa%fún .+藰uV 5kԩSMӦMwӵk@[;÷~ _mOʐz*{],}=.|{K?@{+Wھp{yɓ-mR'/" B \y k֬$Z\wumRƍ͊+^{esn+td %W709̠AL#.(,{m5kJ2"*vQ Jq VJUV$GD@D@D ¬& ӟߣX0zDmQ6_(+T` HBFndTZ՗ P^=Ji)O݅ :u$cҥO?a|>#CN>,a#|1| G[hQQFu>}ȃ̒|'C9r!q /` d"aÆ1c$mĄիuwoӦ9#J@ :԰P&.. `$2qDktՆm.Ҽ7WQn]]6>Y~:uyU% ^6ywv')X$v].l {駭1](H д@ np@Z ^CBP42իj{:ʺz3lEf͚WAzlܸѶDVZ38N(#Je 6lhN=TG lP{Z]Q{&լY@`R<{մiSb B/(arl#" " Y&$Ϫ8bƌvDD@D@D@D@D@| 7D߾}M˖-}lg)۵kg:t`?Aڶmk~m;!  ^R[c,H[ӷ>+W'bW(l@sIܵV8Ld+W{챥zqW4>y睶_[U*" " " " " %@ثN:Fz7,%/b ;s`Ba^N}/aXtE%>AR\º_+~d&_GQ΄C=d>sZeʔɤKK}G駟nCeژ3f{z%GM tMn0իWϼ@_رMfmB;.$G:2FWӹѳCNZ̹k8c@]D@D@D@D@rC9ȳ:KeܹfĈ`ׅ 2|0uzUng"_a bB Tk_|Eh"E&J9 G![*&Ol^}Us 'XH*De~.R&.b*Vhs66mg֤IӼymN@d?ݺuФB1̞=ی=6|k2Ľ['6tepv|v^P@FoP9ԫWnK{*UJ<׭[g=~ǒuԋR%^$Bz,~Z{13eTn5j5 O>J'(_{&Zyȕ1a{k FjժH ۖ-[f;n8ףRJ믷 ˭>j}V{D@D@D@D@D lI&f͚>8p8CUW]e ^ABBosBS- _[oe%a }V{"rR'앻tAhժUv1ʪ\X=iРܽvZ!EC^-97 #@=FpDz! \:2P"" Lݺu`PO^x$`v|iQZ{p>o=c6lh!}KX/\~ѣ 218pb_l{ղeKKI1'  SG9h^lp!Nۄ oWE@ \l86H#Š``H}k+ '6[%O?ܼ;ֳ]vn1驄B|W85'+rnM2zIXDT|'C<իg\zǽML 0|I˗/ۼُYoJ6BȻ7q Sm3t9։'#"ned\b= y`BwL :F*Bl PO[9/3!><11 xy^ N`9s n#?]*<4/"h'5p{x𨜫plժzj[O97an\7l(M7YED@D@D@D@D XL9K5o-]9rUieUV5}/ aEx`Kʥ^jb?9/`,*Iq`*vN8AI-C۩S'|Ҵh>|U?q.PR|6V(QbXK deڴi6 n(o*oQs=gڴiWh˜9s̈#D;q<)sε3/t13M:5[n) d;EGaG;\!& w}*z_~͏qE.I'dCPgRhW_}eۊ"+'&~`@){P3`8>8O9\x&Bv)(+3ρiԨQyvwy}c|?x!0(1G1't1$3pp31:í x6#-O>y+/"<ȩYØˈkx`{n '+l\HFc6Yg6SD@D@D@D@D xc$t^:Si>ѳڐ?((\xP>֣qa;F*~P <؉'h v3QzǍFqtS%D@D@D@D@D \<׻^x(UI耰 {yQE!Y#5'(@q‹>8N#"q7[_M;_ Lr\C^AxJ?L>*P<}Vb0̒K  W<P❃!{؅Q(: 6;z .1[| ([Q̳דY8B+]..x0sz8Gsi#E? 5[}*%&2q8W ): <Pkp@g cT0`2fpq_(1 q?ܤ $Guƌl(1aXph x?~U3Ļ1Xa<,K0\wu|~]ip=1'w ς 0ZB=129)o"r &׭+{[o)]}Qw /shx! Wd%-={tXs c(P#h| (b=7L+b3αK6es'Rp-!ggG3oOrOA[Ɖ?,wK aQ.M\hb#~=N*WYi." " " " "R]0"d@T A ]X>aOH奒BL |W&tBGyB :S]6cCo=OG,Q6EҸqBc}ܓCŽp@,x @{E x'jo,꣠hyݽ% %*pNt|:CgDJt- *Ǡ,M)Yk߯X^ |y湹獾VXs!>+ EzP1XPJo~sGw8kSڟ9G aPeEsdqc/~0tWxF;NϪ<Ta.ԪUk9vT~NJc *@Py.ʢ\ }zLW lX oۅU:a2X_|N=8 lb! XPS} ]V6 ̄Qp%K_"0wcXۢ{yI3<%g/0yCtC_WԳ(l~O /EߋR` @\?6O*M`Xd3Q2Uɀb; O~UD@D@D@D@D@#@Xટ am`MZ6LX,9>Z,X>B$BsyРA. xEKD`)=6 X0?b11,OT0<@m1(xkժuY^=WQB[>`,DL_1@(@hSNuw\{%Egqu]c9̱XB2IG_+_jո-^5N- I97tۗc~L6g+@g*7aBgRРp1KiSs_$'`AQϽ OJrTE[cv۹G= /6mG?_~e{W"gQ^5S,`#4G$"B[nNAtԨQ.D/ӝG*Og۩t_]c:m۶N֘Y8X<@ KFP |^g/n>;v,^XbI%u`akM4> A.^D*GD;}t'`vׯ;찃 G ʗXo'YαK  E^ J ?7%F!3fpd}SN2Vۘgx003g s>3Qϱ\sw2=之;$xy {G'5΢38'xC7J,l;R" "/DRRD,NZeO XX a=O.H,Zږ оYlqgREw! K3̣X,xP%^xXN>:G.`X'?B W'i'j'اd?3DcAa~(bEn [ܟp TxR'kD+}78s1Ǜ~({J6nߋξ% q} ~vTusEE~WdO&}]B>4U@v)Mhp#Z Vam*=q꺝GD@DVX`]A p" }6.l;cS" "N“{?(˖-sXu .dYP,j]بM^(@@ED`Sx\}A8|WJɷ-܃JrSc10cXه0>_)CYՇV~ߊՎ^2Eׁ:Q\u>9/WT}_%Ju^?ȃ1DNT%_q%DmŨ8'b^e]Y_0Yp}K2i0 1VAC@FAKC=bƓ /aU Kc`e˖κ{,Ih#%mzl_D@IE:!'(,y|6awKB,uH*[IƁ\= $'(*I:Fr<,ְy<0XPIBEpXB^>N@0*ׯ Åq=*+ 4 GJچ *qq#fA}Y\rڿ1nXѢd.ˇ@I_h߿7:NTe;= ށxFr/ª{ꩧ:;/ihe(>B#bkc=qDI ,zAHիW[ݍ$ pa^JAК5k&UGcR]"-\k2WxR8-)EW % 8dn#2)>C67X9nP:^rJa W@J\fv8(Bx"dȐ![Ҹ?qa EYnݜ{РArA]D@D@D@D@@u並@.I BWnݺFeH2cʔ)N DLN:)HP55k_|a"xN(- -Z=1.Rzl|&Hl_j PBjQYG 9]tj_C ~p;e! gd!ׂ]޽]XB lD])>|ٳ?sѢE.VrT5!o۶NEXYڸꪫ>=)fJcaݺuk[x1#@ٳmܹnաo 15/w :3""IlʕKKJD[E@D@@ `iwzgu-؎;p}'" " " " yIT.ix\ zZuErv B"jղ:9 w}w_KxO,O Q,fΜlԨ e _m@ UVȑ#]Qox pH8x]/<knyp bw!bEգE@D@D@D@D@ L/ ^lvrJ$SHLWz)߽?8s$&oE@D@D Dү_?7rg HIN'<;ի;ʕ+GuTc/YtTR-Fj֬|!1 7Q(^{y]sW^؄!$/bvryARQ|"@ |0 m`{m;I&avGuaԩzUB~ ?H3E@D@@ k׮uInK xE +WSO=#/I?|ܹ]s5) !VUI޳뮻΅9CgϞ.,Tm 8Xm`yw0D@HSN^W!U Q4NL5-" "*+@ V$G[:*[z WEU*U;P*xb7vX;\[nz-:tKZcHD@D@D@D@2Ѱ6_Pa1{lzVFD@D@J_x{~l@H95.5Ru*~']5L_X몫{Ζ.]j-UT=.b[ouI{m]6KѣG a&AQ}!"r&M/µC8|%GʧH lа$_-ZHNاL,XA`+^*+VO<ѦLb-Zŋ[W^ .٨O" " " " "?|A6@0٬Y0ݨ_ߨ0oݚa>?aUu@.np:.r#7Ej@)O ףGBp5&vc+ ݻ.K 0{m.D*" " " " "kb PBJU6 oTyg~?z\}Θ*[aUu@q 0wVrF\Bu ;߶coQ˖-\qj44(GWJuvXԫWϺufӦM֭[$~zE@D@D@D@D@<M6~9M4I+> _>Tr^^pStJ8~x@JX!wS՛> 6٭Zj|򍶯X¹Îx} v맭*i`ڃѶn{em*7uPy~>}~m-" " " " a@зoM6Mv l _/ _=I?[.;lBǢ qh"$҃Wth"_D@D@@ Đ\ ymɒ%ewx.Zڵ]HW_}ՅB!Ү]D@D@D@D@D@M`ٲe6f`!$V*LxIGX}T=" " " " "*+@vm7{\`"Eإ4mTr5/N8n6 HqGQfk֬M{wSYOP\r%.|׮]m֬YviD~xdB?˒?lqOD@D@D@D@D S ]|`SN_~}DXWCרQ2*((;n׷Gy%H?~FUx~Goa|M]K$m̞=qm-(q:PD@D@D@D@Dl ]p"3O֫W/"!zY뮳/"" " " " BgXSYJ}uⳊ@v]j*9r=^۷7,iyv~Yfs=;[F U37zk&N̹馛 eȐ!C 8V$(Eṗu,+Vpp1.ԐaWu@ݧD~kСzY  s);ߌهRF*.>:`w_yBrI'٫ CPE@D@D@D@D &^{_,snOՏ?%;jժ)tCɷn#" " 9B t`p?ixجJaS ɓ'… tKH6m؁̚5=\֭{EED@D@D@D@c):e˖٘1c\J fU*eN\7o?mʕ%Qn a|~QGYƍre~>Ԡ@ m坠m6iիWC=4a~g{g>xL0m4cQo%<6r-N:6~xC;ﴁ:6\cL&" " " " ZTz,q9! #YK`6sL?w_ ?{F }ƌNqWZjղܪ" " "]A{إ4mП͛۠AvZK^fMw gs!$?nF_yg>nZhSE@D@D@D@@֭e˖n;JEA0 A@n Do%k=z-{wO$7F!" yO J<&RYJDz(*ĉCa.\ZO9Q]wu6}t +pBy 4!J?J<>OE":,z_OIl$+x"" " @ tHqps/t.(gVQz")o !b<1† @~m"+EQ3" " " " eL㝰{fn}iԨavGu@/v._qbiP ljm%lڵv} '`z3իmذa},)7믷-2yJh,( 3^s ْ1k,C믿vF[wJ˗7Dl dΝJBD@D gd]$_n]aL#O?YϞ=[oʹҟ+ZVlʔ)nѹdKW^܄҈*8ȱ믿/b1V%iKژ1c*" A`„ 6uT#*(b 5WBjviWpS%8.{Pb$-Y>arAD7|YNkӦK^u#@:B6e]\lxt{I lָql%mZljd;<䓆xP*" " " ҞRJF襫5 & Jvɺufuu$Hoݺ)Cry@z~BҠƃ>K#Q DX~}n &Qh"WHѳI&LUT*tm͛ɓ]veܹsO>F,W*TVu1)IM4qϿTg pYgߜ7xCI>(yyCED@D@r@F(@$J*j˕+g￿KTߠAׯ{カ`>| jLD@D@D@D@2k,5jdoN():kŋK7D}.aI8$!6.>`/BoiXvu]}M/_~ Sa;8z-^{5c UD@D@D@D  <پG)Ifl6v1ǸXm۶e˖Y׮]QW>1XE@D@D@D@#@q?8't[Co/\l?tPghx$N8~>l;S\HXF=c kٲc>oo&([&LSߐm9s[CK| gB=@B`  ?s<#\믅>%ݎ:(k֬"X<8ӴiS;C3gδ?9 ~㵓qg}\+Bby,\І 56 9/ ?[C,o;k;0B{Sѭw֭[Q_h |oF`ANذ=>3Ófʕβ<SPdYۏ1ºt0XǢO" " YLT D&_ʕ+aoѢ>_ZN: )(@ݎeԨQI;a _l5kִnݺ駟n{.H"<46 25nъpH* [*#kSpҵx@P#>QnZ iGL& _<: ?=$ҵ=p3%^Rx) Wb ZBý:Xx&d%WǂsĉNa/\gqySp|~O6p(;'|0^(z5^Xdγ_F}c;vi_4&۷'z#hyš@(U⠡ʗ/oo.шk׮B'wgYRE@D@D@D@D@D@D@B%@S1@~ 'ĵPB`AD@D@D@D@D@D@D =uA; H>bҥ` /hͳFSD5򘊆."3~3 ,0 [X?`W^yS[ζv[RyAyD#E ]V^]n^6!ϝo[3u]7?6+WВ$.XJЛػXDl|(@ O@.j\\Ɣ֨'O9sH Ϙ&MDmտ@;!C8W\asUXѶzk0˯j=}vg[j2@$ISlXjM2ŮZv7f͚(5l!A?l+VdKSC9Ď:(޽)mKg&.&Nhs=תV om+\u |" N@I}+WlgyS4l}]k߾r-C%<˗/"eVڵ]LB_pWP>lvIɆ!" !$D*1.ԩcƍky 8ڵkgyH'iӦ T6 і[nim)Bu3Pj$аE Hc'4Ӈ曻={tJ*ٳ>kZr 2@n5jM4~P ]wݥ`!T" " A\ftC7:䓭z./ /.n%#̷ @Cq܂ %+΅GW֮]?CvC! WVCEK栃m6NotRc};0n_|buֹ0X$B' {G~ϧ5kƺʚ5k쫯ri\r/ٳ]?ȣv%B߉!%A/CjjSfFuwۭnRs 66=B[D@D@D@D@D@D@D}ثGCۼ>l#Hj69[n.a;Mjq|_ >)c/;v~gS7H%(Pb_|YfvW;V޽_N8/V,p&i" 4WOrwv^ ճ^xFٺuk \r%.W*fXcIXۓewkɟsζxQAHTr6l)0H]l]y啶~&sO!XH8~׺5jp=6mT$ڥ^j7|m۶Л$XgΜi]w}G.G,!{o=z=S2"ia- :*K3 `ABG})APQo9]t1oުV<}Fa?^+x7<?}a69FD@HW YD/^\m#ލ'ݨ"=zx7:!" " " " " "[BSl@?Lױfm"7F6OٱI'Nxqy뭷"2 )܋͋ԭ[7yD<%] cz 0xB"{5\ g#ESlT+VDm6rH#! -E&ARU8dxpP6>?`xg޽{w] i /rpx Ϋ駟6D`}ꩧډ'| }\y/yI<1ja5~.EO8ύΝ;Xُxta&Mr}6_˗ᯎ?xR#j7+"fR pEwCϚv}w{饗\(,W0`|!tgQv$ Dl%S]6czF8L)ByJ0Y rbq@P*L2ž;oOB86x.GoBml>FR(cve$;W/#<ó듶lJ@ MhK %78,<!/wtT@ xn\_^*r H XvaNHU>e3aeζcq~'q890ԩ||xg'xxÅ(?U].O?0y}f͚λ$\ iO~a>:f~,"^/j]6"79\?Ѧ^{w;tɯ6:@@I}H(ĩ$m%BG17gϞmjr $q^&Qhp>x Bb͛78Ui_<#pg:hJl:AsJ-[:?n_~&X>#oxoDvڹ|3OBf[>}Grs9yDxz&LPLAT!C Yf{6B Fat.gxMm岧'@ ؓO>i r={t7nvi#ew auԱ]:a@" _̍7hO==ӆ7aPE¹rĪkדּr_vUWx֭[:wVn݄* MU*}ԩF5kָ|0ȇPr 2vX{]x.($G_zU^݆ Btd>wu(dQ&'<,d5 :CrbۍvBw% H8Xp3g.׹sgw+Es:TD@D@D@D@D@D 4of̘a_eիWY_q e9Hzc"8e>Ӧ_ϫ82rp=D A;;?mp /D;$@=HnF_}K~]w9'hUSED@D@D@D@D@D  4m^{cyvGCD@D@D (ev/}v r|+&*" " " " " "kڴicx&Ge,wyhB -c.9:  `sX"ԶT@`Hx2us׮] U i&C]wzaƍ*Uؔ)SUV /SjUD@D@D@D@D@D -&MdӦMիWLl $&ʅ1!o?kѢ-]Νk$x#r>," " " " " "}uf?[.:FVrjE@D@DX "s͚5]rn{:tm> ꩺ"" " " " " " $&H$IEYY lذa|:-" " &.;Su&L`; ?Sp@E@D@D@D@D@D  yVR|~1矆bHJxQD@D (O4u9\+VO<ѹ4y7\ƪMD@D@D@D@D@2@N\>@y@>9jղƍ^{iKDtP(_pv}ҕ-ΰ#" " " " "  42kAր*ԩSȍRPP $" " "ɉӨAAG ¨Jud8I&ٴi8O$Ar-e`" " M@f}{ %ȏ?Xs ȑ#mŊ9@JD@D Hw\ ;0,X`Æ ˗d/s@6m< o`?4^HTud)@2|7" " " " " " " e@SN.Ň~(߼kժe7ګ ΀P3V " " " " " " " FK4⟚:uXnݬߤwjgSE@D@D@D@D@D@D@JBӧOG[lb6lV}%Q" " " " " " " % мys1cy7=N?tsJdɼsIׯ_oϷ+W ٳmժUy}ʕ6rHQO@U/" "P&)jDD@D@D@D@D@D@D \xᅮ;˖-s _tժUfcǎuJ GL&MTED@DTTt@@믿:?IѷrK=s.v[#d;)@ " " " " " " " " " " " |- " " " " " " " " " " " YN@ ,?ٔd9)@Hľ>|u.b?LGwBiw'OvcygC3^%eV>b_|a{'g|K.N:Y~Zx$I\i7pPr,Zڶmk+Vp۪VjxtMYK?>)A>,J@[f=SV~7￷)S`={-"@$OJ&wiժUvʕ+O.]8曧ƙ~7xfϞmvլY3ex/C9Q,eVʀŨxar~J _'aj" " " " " " " " ")@BOUi޼yVR%ꪫCMڵkmԨQ6k,kԨQ>XE@D@D@D@D@D@D@D@D@D@JA 5flxHv)U~$~w {H P>PB)_{~" " " " " " " " " " "AɠC r`O>1nרQ ~jjժY / ;c˗/իg쳏k}1[o}6llVzuc֦M*Vk֬w}}[nA8Uy|K\ .ts>n;u]c>`l2c=\GL~'jիW>p~6˕+mVч㏶`7FM[̋]vem9HVZvQGYvSHW_k^PP`pH Õ/XÆ CIb ǎ1Po{9% uϘӳgv: ;mwtuCK.up߅pc+x:}wnVre||.As_::TD@D@D@D@D@2رc[ouw]wu{Zmό^@8JOv;a|>}'4FIݭ[D_r3;s| {uB믿VZ6osUR%f ӺukC;g'޽=N$ |9s]}Np1#lF/!0o)TPb? ~Rm.曃;x`=z;gaȑ#7QV!~.sqnkΝQd<ӆ0v ?ᔎwyg\߯EٹkuԱqƹyۻwo{ >e `;k6e7k();ʨx3֮]넘c?|K/#8=L>Fe~Px1Ogu-Y؇5kt=@;a  7-[t |A'A,  3ƍ[-6mĉN9Ba,Q|wΛfȐ!y8^>o&dB(LA }?xky0/y.7uTC{#A6s~죏>;i@7#FA9OA=x WuaxE<|AR<^{NyB*7|c:t3f8mZ&MO?'<3G0\/( @;ϋ%ы/AT O5(98OxBYµx*Vh۷w 8'p<#<]2~x7oQСL;cK 9\A1xpx㍶;'RDBsy5AAGED@D@D@D@D }Xo,Փ)_8Mw$ }cO"" xl/Yp{>kGHxaCED@D@'˚Y/D}N"=rϚ%)"cMBO"ȱO99ݘ:,y+kS^ >z({9<&1cDXkN֓~ggܱw\S$ܟC@]X9kؼygSL?[ ū^T6n=ƚ5ce::̇zK|^X*zœE ߒ vB`aͱXb,=4iRt5>cQW!z}qQj#V&L X`Oy.B ?cK^ @єz!=}!gLR|G]^_"K2}}5$?xVNuh.[vөSi8}(utG"sZABd!A6CDD@D@O Eu!rp<_7|, Ce, ڋ;=zp72 } X pwsP'99a`H҇iIL%~JQs]8g$ci@8O(>SQƔϾrɟ_67>G >(£$$GgϏCHD $ç^z }." " " " "^&L(\o'Zwe:J e$" " H, W(`!+O샥:  (8}FGI moMXM" %v< @WKۯ~~d,9Ey2E auiO>_ ɆBs9IHTآKzIe?[7J5/Y0݅5#P~PzE]_\7E)ܑ͂f̙%w&ϷG} " " " " " #0zh5k@QZƐK4QŲZa&ʋYe`iO%} n3VWBqawI 3^tI.t2uG]& ?C>E) P%JfLz<2"_yG8'pb(IH¹d!\T%}I~<^|Eǐ0tvP%Kֲ-(LرK~믻E#h)@41D@D@r@)@P>_|"!s;UTqVx (>XOϴ&#<#dV&BsrU?1Q2dJ>dBǡjS@D QDoKEQDWiRѿXum̘1O=Ԙ9]P$!^HKdOu#>܆3Ih@uY.of-/^^H'~_~?"׮]fZb0BD@D@D  8qs%Iq<@΀Zj+Nz=txa{JZP FЋ;+4~v8&>s<}+;jwh3YKM*6y28wcڢOp@}bWVŋ9 i}^⿧>[9ѯVZP\zK<`q=&5ju]g(?(;ӜWk<*?H@VhdFFIK)o /#oB ;Z(^_[>b}ꩧLHr>s=VEԖ0zA,sαyobکk! f;hG*i6AqQ>Xw} 6|q4vXK兰 B0NBCLLaʉ.e]&c Ga mځ'^nBg[!zVx Mf~ЇNKb ! @GEC De~UW[<uLPٽAPtM6~!RGↂvxn=tgv?bူP"~<3V'C =䓎މuB J|Nۊp~饗OV D~R;c2 f q3 O}Ǜ wp{G(iCVܽٳͻB!0z>`B :B H~9RzaNlf-O{.,O8 .ӰPAP޴Խ{wsY@GL%,'e&XpBj-ƍy`W2+-,RY DF,X!5:B@! mG/Ϗ}صyir8Pq LO;Wv"V\ }yG %ic88HϜE܈Qg<S>1T6_/[*h-az(;G*Y/ s5)@{IYwM5~>o7PnhSV9hK⫼1 B@! BɳMV"o.gC#縴fs|AҌuq lJB@! ::fMڳhR@pًǦyZ! 'TE!B@! B@E9sf۫ d؄WoQ_gBX<\wu32t>cui~ۍ=ڭU$.cpUW Xpc>q} 'TsR!t9:g?>]NA.R!}5u饗cƌiaw}hhZY1^1~xr>ﶶ[yFVx"k;4wcǎ=[H.<L8%! B@! B1@A)}E]d{% NGؕ)ߓ_eƈ>3!&ksښsO?tMa6|s([jZ|ݱk!z[{[ Ur>TbA{̓ZɡOEk[HV}V2sBʑOjB@! B@! چn@V]uUX,X |~gG^개KZn: Ȃ%L}B@O=O݁\rV <ZךV@RP$2 JB@! B@! @9rmL$>@?hM7nxa2\pMڵ8Y䍢B [#U<,>:(B<~Y@j B@! B@! : xelTm YC*u/ӌuп 7Wrݺuꮎ ! R@|%{?p}O^%!Po$a/B@! B@43gδ@w{?k[o8G0^sV^ye[߼Nw}wm:_}o}+%N!_|,N 5T ! B@! ͎_|A h/K;f>};MYwu.?n@K.qCUԗ!ke]֭gϞb 죏>rZ\%!B>W_5mY$őHO\=v6ļd޴ilOs*oq~pm;~a J ݽ{R1'ŗ_~y #[J.\h@}|k_[tθTs={=$9s ;,(6@L_A02e -)K66lя~dV8o 8Jp<Ʉ02p@k6lcb.X;X9r!yhJbJ}{ǬTb\?>g71VpSq֒Yn"!>Yq0=P7c J+έ:o&+uwpm@HC$` _P~QG XKYysh-)hAovB@@D9?jj*UB@! x0򄑽[@D^;ca~6&Y{Ɩqb͚5+bJ7[Xpc,(5Ɲ@yc6?p|饗8yFM^(8PF{.~6ߒxaî[]\%C/\j$"o̓Pk 0h_H?[غ}KozbP䅏 `c=f,^\+4a[G9\c܋EyYyc4 bz9~\vh(XfʄY&o('tRb". &s(Hԭ}UO:Q0>{o33ҩ >+^o;04MJB@! F]╶Kkٵ1w '?Y;=mفIrsC.zFM߰2*FX&M6ͬA_W΋'vg7k +\(c۩ήz6a;\P;J`Ǽ'<[u nšL{( vg?+҉yυV$O[xyʧX\`qN;Ch3g19@Iks-/n}=eX,=̏r*MY(`%Xq/mkĽY|}W|€D"UɱzE:m&c@HbxPg`#ñw8I^%b=_p[VɄ]hZkxMĠ̇Ed$CejD>Aڋ;& .D45UMBf|nh Bm\7mBs/*׻xb|q7Gb,hKztY;S+E [ ZTɄHL\e0A\ݕ%Iywb[([g~r7J~6ڱ9(FyAxJ~ySPMPY(~jYg{S6)+(ƔuÏ!7Eh2QؕB@! =Ru'tBw>6 b;|s&҈Zu| M"6$6 ;- ]ul0`x6%AQ 䵇~f8ʹ6?26&%i3}  Cֿf8F{,* AZWM[$ảS)V3 qO*X}/4|R\EweAq@ex4!.sElu07Ƥ W|q`OAb1e.J5ZT>,*~piznN $:+p#FzΊ%B@! @V  A .F&Hf3C1N<'A2& zuN?6FAGWTkىNm69e6- `9HJ((OV躒y D w^H>p톀qd{/q_PWϸfxX :6" я@`F87M<,?qR#O =ٖJ[K\цnޓIZ~2߂%}\ӳxC46Bx1S0YAlJ{ʅ K,͊p~@Ý=ƿj!x&eS O?p# :hd݈h]"3iaW_%-3a=vNU"e˦+6\SOu^zY#"x1k~bQ"&A NX`0zhhwX7\k1ƒdžYbȲW&7-X]J;XO`6+qqغwnBpwmZRԖ6&7TALnCp:ΏO?mj~*B@! @E X@-*IX퇬"g7o$"K)ifqIĩ"7^f5CPrvNo5mFXALmý[oxٻaJ3/\ 9Nq0YoQl*I^#ptckZ*v0 p,_!bV<)L`". 7l*oum%PNQ?6DqkxmP5`(;e|u 0PBb_9{l3|gϞ-?gu\dl[} wv^oL 6AX8y#@]#11ZVAyo(LtL( )_ #G34%{| rG}\ek|T̰x_`]LP'0JH^Q'a0G`"|p+&޴9ʅw0ftֲWv\?%*뇕q'jœO>x2/(3>1c>#ό#;0+;ŘS]s6+1F\3\ȺᇧĘHGivqa-3߹( ! B@!P; saW/9,&B >fx^NA~s{Z~7âva]Ue*)ᆨ-9̸q,̄ Ӌ8J[;;\&P^gwϥ=p]I B84ڲ.dr/pQY >s.Bѣq.-p-DC~d=-G[')2nvwFг/aKbpA$h %iӦś\ښԥcƌi E]v"}irX?(wq  Ʉ?:LD }cB>_tE"Q p.`ȑ#I'H=$?Yc 1?P188r.$b !ec=p6lNLh $ ! B@6#pevl4iyNaÚ VX'D"'V^kdzwd>1p,Sѩ%y6.>ب̉2pTȲzZ E/9qpɄw7ҷoV7I3Yf96Çe%k3@4=dȐ0]wYMýQRy0'}b(O|~}qm u4b kӎfBAƎ{∏8DvaLA}W,Pa7!wv%q-X |GagBٍ)`YLxlfA]܀aI>} [LLDv '`0 km_/CD# |`},t|C_ƋkLħm&AB ` OY\y+~ ! jT{D)VnrYpy?2+<| {~M(x]uU6Gh0nv]^Q^ۘ+U Z",Xk%Dƒ9|[#2èW#0ȋͅ}̙&!0Xg$0Y.i3,]Ĵ8< ?rqg&?O|I#&"NVB 鿒B@! E{tɓ "Mvd\2{φdk:&Póghޅxo> G43,룞k1P<79ͤ"͆c G?fTxzp\ɹR;̵ɓ'ɵ7I "Kƣu[e#9s.(Ns1ǘxIz8f~7(#_ E >*+! ć8 u~qڈ4uT;2swy'`6s>cY5b nÆjEgІ$OE~wIU'AH!Q'#/D~G~Wͻ<yn?x3-P7P/wE1H,y;?Dض>, 4hE$OH>@N/ S!hE\<Nkyg=!n{r:‡ lB"E+"a-oZ/!'NlucÜ^gZyE@gjz_L}'b]("hE2i8G!փpZ^ȓ|Why'B@! BJoֳ''a7C^ĉ<lyf1w<4sm[Q·@jpCq 34 l|32\o͚'g#OXF>83_Jy3Si_vqȋC"8⋭G8$;oC=yo5{дD^:ol^$O-u|'O{qwIwޑߌR?ƂqGpi{7Fp,T!1n >ީ)^{މ Wxx}~3[8yˑo%pIڊ7"[^l1~3kNskC< c|a< ȇ0L- ލ5 -Dl2Ip~b3ß}%Ξ=0a]QwegZ9X,@@;v7H&ɓk+0+v.ZhH&ڄb~l\`#$Bb8AbmMgW=V | `2z ; diXe8EH6q0@xǚ%/ 7TC,.PRQI=Kx *p\͌ż+P̌+"KYX;LpEKʢ]O83iub !+;*2dVJs-wO>6K&R h3a@~wviZº (Z#pG\m 'סB;s0vMI! B@"s8$ ʉ]繙g.%!Ggc9ƞ9'xX=|ny.gşeVy 7%' *؁Ԍ\7le7~.5i3CDy󁛂?a>258V^cx9 /->{rƚ"+]Q.Oy N k+88w^w<|f E <8\K!sʡLk 0ǃ /I_8m;QY^H1k$F[t5'`73ڊRH{&F}t%#:' yVIDAT4c`@^blRygLpS;8sy6_Ù G?*/D ,t` ύ |ŗ6n0Mk:Bid-w738A${NFgrC;j- V&2 z fԕ2q&!mG;7~Hc!M };o&ig?c|7-q?is.c]'Q*/ڀ]-oJEs9~e]qb ƨ armqAufgv ~0_D~xyk'޿_.CsepNxfB^ ! B@!P9W-~q22Rټ]YueNޢ|E߇xWrNJ?gٍɲ̳2co7\?> (2m yދ0|6k|E_IzS1:!@3 2жszPo7rz;ȲćP"Y^}RJ꤬"sR600^G馛:5^ .̺g;*-`rAGIw4"'XY۴v1vX%3kLO2 aue-SB@! B SO?#nkV[mU7! ! O7\XX N)  X.m\w1ˋ|vg3ɪ!ǩ"7!xO4?2/. ߇8{,29YX{oЈwab|{K ! 2O>O+ Ya' dsV>˓un8ƏX j&i!*7R͖_6< {=pŝ AQW\`gγCZb37y%:ꨣL^veJ!P)jK eT`u}'B@! ,Νkq/n\‹6ogSlfO<N%! @WF_z`X{}g3p\p H+fۡQA={v;Ԙ_$%s=Td0(+-ABsck}e+u rРA׿uYkdFjK+?=([o<E{vs|DHyW]uU&puqǹ-}iE5Vn<ѧD?P&[˺b…nƌ-Mej3$hX(`GEGHZJ!Ed!b)n86lS U ! B@!np]w B第<C8'+o8u] ִ8 !Еm5,0H|F6>>77i&D:t1bD-8tֳXJL6̊qS㏻믿\davO0`D n'C0n85L<'l$(t  !K/v?XɶR+~kkk"f͚:,}xFW^1"x!Y}D `npH0idn=z )nf8ܤb\bW=潵NW0E}}ꩧ:ys=ZI<º`>csα9obz?*H/\1cd96lUF24vT(<:&F#`77\U;Io,퐬t ynn 7睊"K q&Z`馛:Li gD:c/Hx0?~ dvkL2~„ &P_RIcW n 1GϚxA#ץuD|D2t-;LfxAh8s1 S;A$Dw]r0Z׋8à ׄ5?GB@! 6m癓gs3ϲ <Wy Ai:o+7N! :  @} >`s[4|p + n^̴U*`̙FtIF޳Xc 7yd׫WG Y1a¿&7+4˄0}n:ke*qe'<NH3|ö_~FC"W q:#LGq5qD#V񺱠& c0{ a3~P"r xP'D72q?&Mvmx5\|vai80{ʔ)&~`O2 ˇz$pF@L\BV $<$4œZ2oyBfX6'b B@vB  Cr)trW&/u\7!`J ć̬h'O?mA!7d> XI&k\+Aԓ'&Ot{ByN%L,l, K/dv 7GXm 8dI%wȐ!ƒ$;>p|EX ЭZ6رP(6^Q`|b x'='be(#(PoZGaNd%b1EvPYL`q'V+q 7s;+l]8yeø<>hk淒B@! AMsa"ύAOm_*tl(E?^jB@CK^"jWuvv\p oMIp%9] iv{#C~$?z9/l:i 2"^3::W6whOx 7|`J}[&q.LvPo΍'Hʠ6/yy1yG8/w|;gd}j"/}!<yΏJerm+>^R֘][5VNjɗB[ok(7)K0x(y }}KzJXh剧ԡ$B@! @m7ĆZL:62bu+k6)/5{l2}59=<3I ! hvM%ڹg=q[nkvwT|#C9g<3d%+#K_i*dm[(zy`-},3hG^8T&DQˎS%BY:T&OYJ}Emd' buK`]! B@ ۫n$5cT:T^! hfḍ ! B@! uA`ssqc|* ?pˬSB@!,6ާT ! B@! B|f1|sg]wM8ѭnܹn`]v7nwk8n6r_J߿7G}Y~>}x뭷mjwqG׫W/2˸w}ѮgϞn%CyjQB@! j\`F"B@! B@t$n^|E.zFw9bbZj)+Ap<'x[֭#ߞ[>x 裏?=;b?m4 /?076lc/ߒ&! V'DuA! B@! >b_,̼+EĔ TB@!<HiPKB@! B@! B@!jTB@! B@! B@! @ iB B@! B@! B@! jZ!rB@! B@! B@A@H "B@! B@! B@  BR! B@! B@! M 5D! B@! B@! @jB@! B@! B@! hPr}~GIENDB`stargz-snapshotter-0.12.0/docs/images/estargz-structure.png000066400000000000000000004002361426301527400241050ustar00rootroot00000000000000PNG  IHDR(}VgAMA a cHRMz&u0`:pQ< pHYs""*YiTXtXML:com.adobe.xmp 1 L'Y@IDATxESbs@DL`FQ0 (Ą#zzz~3;;;;癝ٙOZ,)@ e(0RP#EGE@D@D@D@D@D@D@D@D@D@D@P(:)(\HA> " " " " " " " " " " "PtRP B}@D@D@D@D@D@D@D@D@D@D@D(:r(" " " " " " " " " " " @ HAQt*PD@D@D@D@D@D@D@D@D@D@D@ U" " " " " " " " " " " E' Eё@)(D@D@D@D@D@D@D@D@D@D@D@N@ #W" " " " " " " " " " " RPEGE@D@D@D@D@D@D@D@D@D@D@(-bn%tK,^|-W=/{Q?OnX|͘~hc+/򗿤 e#?ώ>96|}'uA:wփY|M;}=uvXosaڇ| cGc:ߴ'/LLI.^D_U]~D]s9?[ɼͫyg+X˷\&|uԖ@~=Snƌk޼;蠃ܞ{Y|r?#7qDﻟ٭K."D1WʭzM6?OۻVo+"/t3gtO?ꫯܚk<@׮]J9҉GO?}Y[~g&0?]nݪ9]٣?a^{ͽ;&^k܎;n;+8WEE>mϱe*:uΜ9't~wߜL'X|757m&CB=?C;}oeumy]V(_e_~-ZȽW_WZi%Fm147S> E.[%(5%oyF6mjUW]UXsl [*5g`,z5z  ܄ K/YfK9!Ca KaIwW]u9r7L۷"hREf V"Ѕ_o0DܗSR[jXpkY'Nu1Fr#B/͛7JC?=GuYsΩv89J+=CPgڻ\y#2țr˙[߾}k޹_cwVC.˻!C^\O&ϸbrVY" " " #ۊxT I,knfnJ96yn}B/pv-Ǧ_xp#iCu[neccn@tqǹo>ǦfP/OKueW{UW/8>E|1)/Y ϻӧ;=pnmM6UgW\q)mӟ{̍7Δ (::uꔪκe]~m[m۶gH!=|#<ۉ'h}hUV82h@!7.@2%m?KMvGO?Ss"ŭj(_V Ω` hGu0iʵYTo~dZSpdGu[)M>Ş'<eSN@!c65]]M :qVȪ |i}t^!@AIh|m8t%@w(;ol)cӐ0A f˸r"\? V=﷽ gJA>f*O; ">X}v)ʉxzkS6r!QV3%Jg8†cPtOǽkz%KŕARˍ@S@u:cˍ[!R|͖~kkL#JI6GI-ЮB6+'lNOslg&yڪ1RL^A_{ꩧ,4(%B\wu1%'ɄyloIWs 60exJ㭯$" " " "P#SxDِMH0;)p96މWJ‚z-Ry-&r6Xg|V>о)B[wjɱ'y-`߲)B{p6BЇ x!lX(sogd;Ϲ&3#2[8'\[>uTVC>>i'n|=_>&YӦ/Cy>J|of9:k yg1z/.7C;gЦ3)ۙF>96y F~au~OrmwDz80 pm!S9)f= PLf2%O?m ^xRȸ| c/항ב?9WfLI|y|;`*!";;F'y74 S&|nxt09:-Aчyeꃡ'c0fJԙi\֧!}|uH8V" " " uK dP&d VZ,N?s;fQ A8cQC|S_m4 u F6=\e6fЅ YfVꫯ^zpx;_[\o6­x5ְPTm] db/;11C>XQ>qXjG7wc[yx$h(U ~<@c^om N&ځzgm뱡E_fxm޼p mD, 4iNI}A#nc.'sr.oQ.s,4?üKͷjD_Ÿ{ւ 07aU ;=Owl8X'\"}/oΦ٤\Ek1Juϒ/"&T;ԍ86tS[3G˯gu\M=JOa`!LG?"38?cC9G_ӟ{+lu2>g|(/x1}&ܹsm1^;s'{;j`xH#_iK_ZrЇ2S^kxe'ˣtCآrͱɵHg \s9$ֵܗY3dzo\̹ay~Ϛ(X+>bt"syeM˽b7B_c\QdžBacF1 Ӻkˑ_D#G")R/"?/"/CI"["'5kVPK;vhyʑ6|UE~sd^j}~ϭ}´}^xϱǻۮk6\//P]@ "/h.9}+Jn^yჱOQty ]4' ۜ|\7>4|g<")ml6W21{G7͛We.zvmA`~o078k_ \ks%D|M̃kìpm^  :4X}Wd^y|tqYo0?r_fޣ'Oy}qJPkW֒B}HG9S}Qtg[_^WTh֕\}u/ǻAJxo߾v-a/D25F5 00av-u'4h9qv˧ 9K{77coRշrK1\X }{ T|<+lgqw$?Uԇc'#uy/衇rQ~q1D^YSo!C-^s/9%a le_ ӟo+a_zZa~Z"c֪{DX -s/֫o!^o:+:ln {DuD@D@D@CN<(Cy,8 u^e@X8!_^z9/r;w[6 jn^`VZe vsXê+5p~j +܏rĝs9f&jI+ ņ_gVݻW\u >clV~gfl=,nN9 dh̪ +}@,3*|X6`Ѽ u:O'@fc+z ̓ *9.殐4>|YbjHIַ|x)?ڦMŽݺu3E~Sxv+?Vx9`팥 ܮ];+?WX's,'cLGc=<}&8山)֔i팵8s+s̷X1rq?foH^켲,YSpE1T⋝WdZٳxTч'L`]YDLy/,lY~-0/|\q9/q!^xN88<_OX c>α^Xf9)s;߳Φ.iuu,}'!@ԅu/$0aÆEXI<f=| 304,\7fEVɄu_Xy^jaE򩧞jXql!LzkLTVX)\+/1>xhĈU~_!f U\-ѣY9cT3Xo) f{eTҥy.xaX̞=,6u\Œ?E,/yTtUW:iXuQWfT˽Ѭ?-a=s_vK!w  x%=ʽO?{}`Z"/+4iR!z37 c+ụ.pK_tE=I5้B/mV6^xsW8xCl^B{~FOKyVɔXo<`̘1U[5 I$" " "P|wr/R*1,w\,!u_\ 4Qmqӓ H^ $n\VXd`gH yaiAX40X m[6H9UAh)FR+A~d}XoP bG ò u5qv ^?F`μOX{{$*k|9lG^h^BkAH5v:Ǿ&Xuы˰^\2~/ֈx`MLM1L^yacfkNCלWdE-1H' p~U%//qkP6s7OYH1ڗvm'>cB\ĥKԟ9:曰hYi xg#Yk<Ox ̝sW4z9IǞoxp!ºu<=y~g풖xfs%{BqND@D@Dn \AP6 P%rXH \x=L, DYHFm$D\EY(vquXh%ɅP!ٌ3+wGZPOmcLU%@A[VxXc  JЏp"$*TaЬYL6)  O{- $IR>+"b$"! BR`A"D+MXpywN6&( 3kVF_Mܘ!l<v3?7nq.Y07fK&[D&׳J'B*eRs)$[ \I;<;!Wc9c\,jRoΧ%Z!I}J`!0&I{.cR{2œ`FyޛLJ(*a*QPefYuhZbs7d9JpE@D@D@.X ` )PC i|(SY?d:&SVzBg  >, Z/gQ^6Lı#fB-S=,Tixa6S PūIW,oj!-\Ê8R͇231V(|IJPE ls S;9g}8)dls$ ;{X{>6˨׼@ov<#5#1H!ڕ~zo>DԍP3ͻWy/j2Yp_'Z%/Y7Ў(1a PQ;Om<)~J" " " G (yK vib dxl7oXb11.r=2i{XqbB2qm}-q϶dBo&L K&}s#rSY+Rւy8"ʳXe-$#1,=\ۜc1=sT2q}( cי( QR hQ/<=+jWxgc_ܬ6\#!uE9aEE}=SO`!} mMs>#? ļ|>} e9<\FӔ>,sYfSfЇiKcPPP((1Bb^a0> v}&mV6J+뢏 #n7sGZz;g}0Z6?c4mK>#9s,ߴ{챇VBA & "$F$S3!gfU `0WJZ V؏#/?%KNj1ɺ7S3,x+F9($5b|Ї vbڶ' 1żQIGCX_klmf;˷'iꞍix>|kSc]@D@~H3 eW ]D@D@D\A PTЖ)!fABb%!,TN8 K=)z)ۿUbcW|1̵?ÇnYkP9U pz }c5%WZ&nx8˖8y&xRd;п1nFb?o b_|q*DPf(.o[&{~EL)/G8)|G&\;v3MkxY>!з8<AxX!˖ŜZL7uxb,5%O>dwꩧz1.3/xb;~#f;s:밇C.}gxgM\@2<9!Bb|Rc$^|DҞWھDiV1&r?CxCjP`$ƊϠ\.c̏{T83q_g-1>BgZXUeNu=~r mJXJEC-BٰE\1Xg#c̵7~x k㉺Vu)x\^@e wW<#$HTͽn0`;F[s\[խꦦʵX[aLm&@xzᇻcں5*h/r%l!B9,zKٮs ;BF±FĄ%aBxF}ro/m@ΓTHMڛ97 dc(pki^tl}WM9.PEw}mSJ3|kYְIYpLRg((X?P\R:{!Pp `|x72>"wb pd^i),:(s]ăV'g*5y? XaIͪ-XjU[%XZwc^@ SB`sO{t^mGC}z1ޣGsg>';ŏ3r X81 |S… 3r=aZPbMX{)xbJ;(C c9~r{)Bx#]}+J} ڏh7,6qVsUG@&i?XrpM7uoy_jf5]}uõ$t 믛'<ʜLD!}:Jb=տ>˷ᵰ;2(^]w ZXR2d 3ENkQ֊wnlڴi#6{nj`'YW/" " "P? `aBbv3,yg첋yQΘ1{ȣ`u_2mB~xb@NA!FEaÆ|u/#Gj.=Πc~"HW3F2FCxf#q9 6X'p?M7ŝw.{°na~a}˘)c?tGvw)=tR4L{6C:e|q1 ]Q|<ǽ~X+ҿw)0nd4uT/0/ۦbbH+_߉"BEy*vƳD8eСfЁ:`XeJ " b,zY"`B6mj3 FXWU"X fm6WĢ,X,k, ;"/!6*ډ9A< fsx`}3pn<0_~Yrl) @DmL}6+F#܆XXyb.+<>k1X2vaiT|Wݱ<y<3/1+]+N!y`σ%c e3y !Vl؄~L<41p[9<~:$ |ܳxH )@ h履~ڄ5NV6_ݱ^-^_>żNa`Cic=֔uuc$?όٳgsKGxdϡ\0zY0xى^^}a(/zs-~gɇW37sq뭷^j1< ɗϷybĽkcm9xXE/p& es/ U2aA}wWD+#0_' "K{"nxEb%BeF703 "2yk?F~yN\_g y qm)ӧ]_ !/q|VWYyr=T{:|?8)11D^J$orH*zEA?K`xa9|{PD'tRU_x!I-;#/9y{E(+[!]Q43^mNk{;ӷ½W^l%Px r=~ vRX+sZF}\{>Y+WUc{9\(zx+IJC O<1IygXz%UY+㕽6xU1c0>ʯ5 aR?/(eDW 92D^\m_B[3[fL=>lJ =ko8dkޙ^yaĜKʵ +cKxv`Ρ#A#̅an06Fw[.M1ٳTAV="wxas^0ۜ&"^0y q}C_XqWܗDˍv̥J?[F֜a\>}E:Ny+$867< 'N~tG^AZbWb ,qK).VXa@(n3|%ŰH ,9Åv{"\n$/t3l7X`ylX)Վ_䚫< ֢/ƆIk޴ +AbfKK>f1F8fMXfk~ESkoXU"$srs9z`/s ̽XQNX!r]a.g\x02'\E1ȥ~z md? ‹1’0[b~BWBakߴs99zaK?c?s,^aVLr.1ɬZn]# )PBɄ;?BR(<ዘ#Y1aOӇ9+H3ZX,ՙwH%d0Fc0=\_XҟKs֒xQp ;ֿS0Tg2@9#/x9_." " "PRPr%0|׫W/wg~e|nذaPyMkQ,Pxرc݂ wŭnr쳏n\&M2faĉs|[gu܀!'N駟6%J۶m݄ (dPp5QQ@xc3c 3ʼAU}CNApO?uXrhѢ*kH'>Eߊ@}@9W_ǻ믿5oNu/'x͘1 y5nԩ8ܱFnv s5d{a L Gٞ{v=zpn[a*C 믛3pmڴ1eFE@]Lb!UWQ"PFaAޕJڦCxwܹsm/.n.25F爀4NAHU)" "'.\ƍgkOxmK.1k1cƸ>,~}f xW$s kֻ4q9`ݺusl[bFA.BD@D@E@ bV9" "P?c_d- w~Oܽkc7͆9.?qd^\+C\yўxsyyc(;|ǏKk?K;W߉4\SOꫯp/VW&" " "P2e(Fe"" A :v:tf͚UR okYUo<Gw5olaR6mZ,p}u׭ Dwޓ믶W{7,r9Y_\ B^^3~$N}=;-dAV·qM-ZdK-[c5?R omLM@^GXveVbvlN~wM)Q⊕5>6F gbl꒶FYX}hXL`wj ;ìQHaP:Q{Ӧ(h#<\y䑊pNxHuYzi;>(GZn^E@D@D@D 'c0jAcM@' F@D1 ܄'|)f3p&Lpw^%$_}Y}NU?}1{uQTJS& ~>3#$<(arI)@AF,/ &LI^͛7;ֆ($t'V %~PU). &$<=PȠ@ACVXQlҍ ahժUP[!@ur}^3Yk(@.A %#" eNA<{-4kBj,9v{!kwP;J CK!~Bd+}rM&MpI(:3"~( طEaE%PR2 a/&ׄj5jy@yOX8(s%=z(|J=qʹ]9$oֹ6͹IϚd}6e$" " "':-Zvڙ1Fw}LAD7!xhB?2?-!# Oz9.B ef=ꪫl<{ksE\@'9 ;l{C\} D)WrC\xdжaL0cZ?!Tf[B))XA@ κJFN+{,pO<"!5Bu#אL)bY(c.e&ˈꎧB8.?l+[Z~M c=?xZ3>?( Q\Q(*7Iȏ"sG?$%^;+J0 Ž5(u ^S'l[\,K@c$xAgo$̌xq,"{k޺ b]刀)(J-TSZ} .B47^z>}T#$D  ~'1S-T2 F9ڴhbߟPNA뮻P#G7[X?.םLK\٤cO`'q ;c4<\zuH\JFu؈cQfqmo6qm*7eyw,mO;4Ǽ$c e"cLx3E9xq̛ܗWZi%02x煒{Q|66{[MI^U@C# ECkQ]d y3f̰ tQ Ļ/@lo^gwx|,#yM}_0@ߡw(|Ú[n8!L7vX@I s I)\s'NTO 7*DB7e'\!c/ ,D!yWHfX#Ǻ$'4yRAA$'Gu)SjR& x줓N2_8cǎUڵ 2@Q' (a ,ZH(PD!XeʣS>/ڎ:كNJ" O'<S(Qc)>cό{L5Wxor"( {A\)+ Ԑ&s1okN':@ 0s dB0m9X#h㻐P@VAyxQA!sx]eq)!'{ {Fįe{0@K.XGՀ !Īmaၢ"(4ن\ɄPGA vwLEJ ;Jj"Ex H(7('Pf>X9Fʊ9O? tGMR?@Msy" " " " #L ~ K&bp@k]ج;~;uBqD `409PJ Aр?ıXID m: 7 ((x%X ږ}1sh,jD1JH"=JI9#M9[g:1יּ"$ #㖐(J;0ce ]ԫש6B rg,q|P5(7RP[" " "PA\COD,y>9g6/7w}1pSI%k}]e. y!}\aJT3(RPKK" " "P3.ˠ5"zj@PРAnԨQ+2:TJl QvpNxM5鬳2o ʦ] 7 q%hP4'` =sml?v!pn℅;^z;6~'ݔ)Sȑ#-D{mUHzR*t p/{~0eRhn{f" " "P.(R=E@D@D  f/ K Ġ"a!C8 ĕg 6gim1K_~9 m3Ν;'C]ԟj_'" " "Pg':*魷޲pHX"HLk:ce 쳏y;x㍎i5ְMP; !( BG#gSA bJNzas]IЮxA?;1DЧ v[3=a"]9 ҥ;vݮ@y1l0G83k __ɓmPlOID@D@D@jK@ " " " p= ajr)& PEBe}'XuPą3xpmf* 9j6>( PuƃMly{)g +T<"djもvŪ7!f]Zy߮TOכJ(1I<_~nС֗yG1-c]vn4PTGmJ>|Yאo=c6&D$" " "  O;kiߛ5kf{!'IZiԩ& GOÇ7?~}i6mj^ɽZhzm rj*63u]g|rNB=38#sBpm(!g2%<(9Foh"SNqI'U:e˖v_n{Sl&E_ؠ])7؈k} 60J"PJPEE8pBשS''1_Ju/d]>6W/HPB`& 6.$΋xqo8pr,v" " " D@ rj-UD@D@ʀ-,g̘a"@pw^I[l]"5dBxg _};wn#$5-y涱,{. 'Yy+A 0w1 Qbd|%Q״ҠGm۶^mDa%ݱcT;wC-ڳ IZ"&lOn!Ivb?iʶ1cƘ}`؇9$x+F}J b BPţwߵ1\ǚR'ois@ID@D@D@ E`1Uڭ?vX@`7fϞm:Yf5YP:"w>louTh 4`#[n1Q؃`ܸqzxT>&N*ŬÏ0`O<ѱ/@eB|D@D@D@D@D@D ^7t^)~^zҩj3'oe)(ꂪ(Wa%|_7Jz};yF:8]lŊVW$" "P||mZjeaڵk{@%%c,pAw*Fk)X`ի[s5Thhk2&@;!!g}IF!A؎M[mf%o,TU]D@r%_#GqƹmۺO?mVr$=0c6о馛ܲ.{o.ltX xWǯJM6֎%P-UAD@D@D6.KD@?ü#@A&b!@|~w lb-Lf%B5@~'7vX7a{N;B䞃̗@~ot-С#R+ @qKҪ@YOC3c62+!T?lxFjѢ[+xI-BID@D@=ewwھC'tE ݥ^Fen(W>kxOm:_D@D@D@"+(" uG<#@!BcHe^3+EV{o/LрWݦnj4p :ZJ9N>X}]߾}]֭2ʶK,,W^y:Fm?u5sϹ_|B;M[]'" " O@ o@D &6x׻wowW\Ѽ(!{gʈwĢElfxC@A&B5j GD@D `Cl~޳gO3=zugZQi9O,4B;S& E]V" "^Q} :u_~ZC5!/cPFw5;( ⡚_AB+P16J" uCĉ:ʭ*uSrvZ1 2Xc QH$xmݶx_|yx5zZFID@D@ꂀuAUy@ ,N?j(@QAا'lB&,?PB#~mwUxF;B5e֐믿yN~KJ@2O0v>#ۻ//Cu%|gݽk ;,hY77/($m ˕ gϞ]Q55kСCN(wRP{ "  K/uڴi&Ln7}t7g tG&W7@ b4Ap@&C" 𚶂+GiPF P?<BbM7u( PFQfm kyղ0ʼMD2o%0iӦY曛wQwEc0m\D N|a݋u{TP ~&|A.RCUlT?Pい.1(ԕD@D@D@D(&m%" y l`ajk3<>l7m4w)vɱv|_}e)awyǔAZke{GC54(aߕ?NC(P<Þ+Ieޚk)!ic8z#Wxo\>*87(k'_vO=wpWZ䖓-gsw;vtm۶m:_D@D@D@& EtBۛ7g=/wL~Lpսzr ,pllBx7($xb9GBx!P_n$M`%/ͯʼ 0~lBYqԥkxcGWTk.\h4hfmJҪ`s}"04X!FB"(#ݴꪫ3=GAp;:6mժ'Ҙ EsRowW^qof2e/y2E@`L?'t:u2a7^JC.]1cƘB(Cg'0xA9qoԢKYmUİuŤD FO?u-[}"Ps( P`֭-0~ɍ7=㦤8C{4iҤABAp<#x{=3"5| <!=J" uGQ<=,P*Ӿ ;{Px!ycRg5((X3*U%0qDסC?4o5{zu,R% U `D% ~K!+iV( COJ?4'MFa aÆ9„ qVjBIH>BBb…=<#PF D2O#RkOP˕[onV۷oNoԩt'+n~NJ ]vuW\qy0R`8g{ F%RPu) ,xD c[o}|bI* +"v آ$6DcoDcA[45XXPP5y3;wvwvݽwggssSԩS!-FU<ֈ"!"޿կMQA~.a5rGAB6a%uaG}wk*TS*IB@<~a1։N(` JrmߙwH%?S&Qj֬Yn mf2TB@! (%OB@zAYZwykA1bYuU]/VX~ݰa1 &Gaf W_}uMQFeVxCʈٳg+xG`I-Xᆹ#_}SRMT! Z֭xwܢEZ4ӦM3A<[ jl0vms=gS-\c7kϚ뭷 qU5! B{XB@HE~;w[~㏷5 H5fO/ݽ.27СC-;ZR4T8<ȭ*#^{5[oE7O`l&B@! @! Eu#@ >sCLI۷~{7vX7x`oiK#Կ ͫ>;-;)j+O!c3믿/(!?Rb޼yg^{e$W1^ !2! y/3:@PK.1%T߾}nϩkQcN[k׮9qRB@! HpUB@!poަgϞCqrůYw45#;D",y*PT`<|pwQG6"fh#P<`2ɗ"# 6@c?k'>!PT'FB=v1:E<ܴ[vDrm3x+a@DB@! hoh! @6X>nҤIl"ǍgWxA`VpFD$_[ݯkbw(G׿, J5_-|XQ @馛^zUg5ruB@AIQJAŬy`iOh$ݲdSzb!N٢@GַW\qyʐOŵsmBBWH! 퍀/@B C٘SNn]w<bVC%O_$?~|KاG}M8TbvjR+Pr$#"o"TDauދ7ydwwZx'Gqg}V~*~XBX`X$ J+ǖ'4(H[K.xE21Exg ʾdB   p6''xO(9s渋/ؔzy督@O-q6]'![py!وEB@! ^P:,('waۯGgaGVq|K|@CHاO+<~xKm)$Be¥.]TPw!С@1o|#QAAR_z)PN0;v=z)H\um͞=<Ю0/$<(>z\UB@!)(R@&BdY75<lM6$BSWYej7a2 V(d1wm7kgD޽흆Q$<(D(1%|p%'|]xᅖSNqt95GPn.ϼb!jWr]#DB@! $AP=:$lquL?n=P6jИ9s0`m-Y@xb`%6l;c,|7_}wga{ӭ[7S !5X= _~rNOЈ(E4('?(9! 袋ww~u2.%>B37ްzi *܄ LxpZs|"D'(e3_*G0h CBm喕;Èv脔,? 7V]&ZE G! @AK,qӧOwW\qY){Yv4R! Ѓ @A{a!~?+7mvOֻ:.:zy@あ=UV^B@ru>裶tM]Ԯ\eUܿB!E@ (_! @#5/)&PP,79 jJRPsw\=Bj݃}@IDATw),N8:c3T'OZhC ʀxP@8c0zhM$E)*G@ 1B@! q&I&oGxѫW/wᇻ<}mvB@4 C! lqQs9wƍu< WK% u[lEu:[oe"Ժ뮛| ! o|1 +e]֑J$@" EZ&B nŋ<&MdJe=ts B 96 'Ov/7>#dI_Oc]F@Q:Ps=y;6;2tJ:A*ޒ~#DB@4/RP4oߪeB@! ,VǗ]v +1tPwq9"! @="+;ݡ*OzM.]̃?l #˦(K.{nvB@'_}yj׮s6\ș~USB:KUB@!P pzꩧ,n+!0f^{Q<]+%?,I'FYfZ:@{pExQ9Jju[Fiw#36ģF|UWsB! ֐T~^qQ XBxP B M$ u!d - =x#Kݽk-_~Yqرm ~a_/سR>nZsˡC<>m4w=X=~~۬#_]׮]weއBp;w|AkĉfXA+0LAً!<o$\8bw]w): !mznyXd.v7Δ6_~[hQK9^xÀ3ϴ?P_Z%mdMJ*)O;s#X<Z < AH!RX`Ax-A -dq + A?+K.za(Zxvna!PrkB!Pδ?|(e G!P凵# T!Ybu xMD/Ř B7AB( 뭷^ꫯ. ku~u*Z! B@! ,rRu뭷6ۜYx!PS^~ew]wY@fv+OZXwyof޼OnUW]MS zv a梄 $ABxbPE:b)~YW^yޏՌo"jO@<&!)bVahmn x7ĉpX7t=z嚈_CΈ#<ҼNZrR(0P^!ᄏyWį\PL U7P EܳНOɓ'3/XS,:(P+O Ou+ fC*I|QJ&aZ 8n/a?.ojkԷx»$$u&r(G'4 A>KbmL0>&2S7'xy]R#S[n1Cӂ>Nr! *(K(HB?nܸ #,dՑMArUl@zo<h,6O_cKfΜ PjlVg̘Qw  Pli|AOArM$M 'Pt+F /D?uYө?s.rm;餓P+TGb5K <(rKȄ:" Q;$bv!߸ omer "<02r ӃA-EE, kQ4<} !F!Ht >zdr*^~2Pr',G7E4I5B@! B@fAkg& 5d^Ń"0: Hn} ! !cnfP/ŋGm,#r PIQpq5b}KI({Xb6y0xߠ*I&Uu_! B@!PCP gĀo] ! xsףGKLNrp`NTlS *hN*!x[,]]r%g@R՜?J{$@1 D(bPK#y<턂">nxLu<x} ! B@! @#@^B;be̘1O> "U_zA7gfe(W^\nw/'%wZk^K[n9[o/h~4څeъ+X2gCp}]* 5!!((Pd/ ŮҎW;k! B@! B $%;(b%CE`$`&Bc'Bqͣ>K}(wo%{v\ 8չ_~u+ >;wnQo0P7oCac_=Ό_6&7M[M1u]vygw}YtL5B@! B@Ccϙ3mn-hB@F+άO>dwqǹ/r-txM)@$r< ӻk!Pj;֭vƏ$Bb~~;&,ҞR{(Vɓ^{Y='ivH$7r9lu)8BB_3fXոqDv3( __[)^(W_u7t, Qs 5 n;<(yw$ jb?PSo_xy^3i-5;# /o`$UKR8ke}1F}~vѸΧ!ĒG\6檽χKȻzSO9>ic$,8{$e%lñ#S&; g# h… 9R!xP F߂RaVv l!pS(xT!x@~pp '?oCh [?G_ q SLI#u6Vt( A9xo{X``8|6c+=~oU?_ʈ-m , , {u!ju{r>^6lknvV~Xcj.c#pO/EkG-3k_A-\>N;xBDPIǏz\:_4cK!< [`lPOJtt>*<@qcn6[{-?F2 AB2$mjug6:OG"Бzt[|aÇ\ AX9ɇAta/ 5s(nV<R\sM!PT .t͞Cq{ )0 *|,CyFW]xn <ѓ c|oYOx'Bb>6mYSO-}e;N:]}f9Oc+/!+$<ao?ˆ0"9&p 60ɗ.Գ5OBY#8qyk1[OpOL"ҷo_ mwx6*sex']9<8c*?$y0]yywٳg[$ȓ, e#5X([YCaVyAkw9}M6ШZ™\p‡1Gi8g)8+W<ԏs:0"8񢠯goyV>uҌhܙH0ʓrKh[MAclEg"\&L4H> o&@#n;dPg$Jq!ѱQ$܏~# eȜ;w1b;xMX`i?_<|X[{- /C|],ȳ|\QP0f-(Z8PR6 +m9O{V&J7 )zA|R_?3_>O3(l QnL-^ؑe 1؉S~>L RbԩѮ|4 >ø 9i"! O0}m\l?v[[r{dH`!B5UB ;*/͵iL9Xv@S E9=kklJ{((GVB.xYQi=5 :p@s 3UZ\o=5w020`e.Z^zijwK>?aaDzځ%NM@;X][$E8koO=#Zz'/HXa1guVb}S/+l5FKU$l9c!x5G+i2v$K<|ַ",h{6KUZGrhkb{$.ur=B@%X1{ܭ DB@!PP:%D 6pXo(zG,7y"DBYYZ"!7ASNAZQDB@<@GZrӧ^uG02ɣI\L<"NjNV/r.oB@4,,y9av</s" &X$LE$SN1MX$ʒH! j+pOgCOu >;sbȃGYx;-p-@bGx'_X۪ͅ@Wx =_+Ss l B;nԨQn-zRB@4A8;e]tQUIB #6lB|yZ]F+MNăE%Kj1FRx>ox~`}!B@!pBw{:Jb'vaUWPQ!N.pMF7x%kP/3}"3Ү5W_\rDO#@1>$sB@!#($ o\;mZY<ʕSwxD4EEFxL}LD͋}駟Yaثp@̞=ۼ'zk= ! BY/_ !GQuq('fs=]zʔ)U#fذan֬Y>mc'bhf@DlEB@!P;tInСw#Fh>r_~KF6T{" B&I,W4<ʕQwx̙3͝;~>nw?ӭ?e}"jnjYx$ ڏ~#7dȐ] O'^z%LCA!B@! @3!̓_56$[Ąƅ8CI^z{'MGs,[WYe43hx*4%+ԗ,/cB@!:묒VϬKcvےg1 uYǝwynM7u=zM$/!҆U+ҥK^Zy첋ݻ=-   _<J ! B] .Նy"| ˏ 0 r3EAq[ ȣ?|9JSN9m&mƢ/2+u]dRPBX ! "b$֩aRx$}KȯG(4g0G1hv_+:+oB@!<6=\GlXƓd#?C݀!]DPZ-r7p&4U*G2,{d GnG~>$.+;} >2|A30Çh'SP|6)!{#B@@.ZAol+K.&!L}|9J &X4Exj{VtT]B@GyB"$Q\ϻy-#g^v۵g:QrãgϞu 7XwƒPcxBw _~d'B@;"|^ŸOmp?3MA6D-#G+Ic1=gc N$E0/$wFG EK}(//g;}'"|ׄ IQ!/嫬C y+Zq "!O?ԔA;vӧo*O! u7GݦQ}Yלp *NԂGk[R1RHA1p@VEIKn%:u2E+ka$T&_#^W(/Ds1c1S$B@fEJ} mrk>PP6;i$w)6dıXi%񨴌4W|HB@Akdޅᓪ- 0 Aj %(}.-ZLbLnGEYځ5CNL+&ʃdL57>k\ao:tA^ EO<ᮼJTc<[+='yX/- Wo㮺*ߑGi(NE^W^1s R$B@䋀W.q {^xa\PDžcZ QHeKa>x$]WΝ;6"!@k7xËpL>9nz LZ{뭷.8( RPv}j_}寡0 կ~~ߘT(& G.<#XcZtÓ V(8>n8{XH ПA_wO?)P}߯"B@! o h8j+Ҕ Q +8aox}Gb>y,X:/9!?r9IQLKu pOJ˃us\R='-rx2I\~<|SOֈyyF51R>ABE=cwY}-zmyA~bs7oly ;8U!%E1D裏,~^\n|PYu}N,<<˸swƐ!C6r˰־Mwᇻ;]W}[Lo֒x?r((ro4B@!  xx,8ᢈJ\b)( r`5sL?ƌ _׏>5kVG)t`Mh'w*~B@G`…'F{ Gي!PXtuڵ>h"u׳\=(WF\g)wOz[(A]w]K[i SggW10%>d1EI-փH}!xϩ~#~_3 ֊Wt.B@! !UAAXj<#i`+[ cz>GI@xj|Vl'MT}B@Fk[.z7~\e\3;zmU)(Xow)6 GXk:!_ěo8 ?! B@47,Њ & 9yg%90`>IQ!/嫬C y+ZM q$E+}[O a/FȺW_=)g*~y^ 6BBcw"[C3)(ZҮ_PL<3?B](B@! (7i-I?XydAr'W_}e1W :PMr( B=O"O7y7uT¶PQSU@5!믿POՔ{;&c#wODVB@!  o 6]w] {B9a^m$pk#oG:&m^ZGd|6m5kVXi%񨴌4ׇVXa4! … -ZdJxFYu-õj )SX1(#^,BOw#"qB@! @6)(X&m*Yd?ZHcuvgO>^(ţoR *ţ}b"TzB@YR3aE3/Q4㏭-3?a8(yh+Q ,2^;"#<^[*Zzf`?O{NmQlTB@! o X+UK8~A E4J( 4hP+|hUpKVl*IzB GwnxNtmm&1yOXn\W͉4<.bYݷoߖ\Lã򒮅?OYZ7VOJ%%E>Os, O vmvMN>wu{_H(Qx[o={Ç+ sN;~o 1>0ׯ_K;VKB@!.xSP`iElf'OnvSN 6{Y= N3?yW#8*l=pcƌiz_?-p^}e\n 742Z ?͝;B8=wu( C"FE{v￿:t[eUŸ,]r-X\i=۹M~ɒ%f(Q#oWB]/_ܱa򮖘\xlrE1 ?KI&x'&>}(Yy`vw|TM! <#{lk8Nx2u Bq4<5x`׵kzS(;;vÐ*(QNax_+pw}I9w/|̓rOM̂FxWxnѢEVwa}SO=etM7d4 RoV|YYh} cV$B OyP°Z/ քO q0ȾC f3{8y>g'iӦERt,QC; oB@!6x _s5-IXl}KHMIߔB)Skg:QramOD+ŠXO >2:uOΈJF5ַlK8o=B-,5(t֒2sim+@IDAT5@sϙP$Y.|0y]!F" x 3cWUB@4X_zU+(H馛Z2ZPmYq]=,q^{u18~x›R! nlEB@!P;_s5- ,=P7 o~IeJ8mvmY.ix+ &?9X7lnwhaVc.jnsr,JXCC <"+iƌ/P ^Ziܶn;0SP&/x'j"~i o  q싙T<Egf닕S#x;]! :&ۏ= % XZYF* yԢ?xP7,* T%=vQeS<|ַBD.(rE$`s%{ƌSeO{R $:zPO"!  F8qwf pL&Mr5jMB_gϞ7x% s%I|CxyGT5:Gy\[DáăҥK-^/I Q %񨦼bީB:/E\p%DI2! kX G:s&!!O%7xC]Yg*ŃF Qy$;졮]Ĵi܃>h*gJ 7Ѝ9Ņs^%ړģYC1OȖ,'@Ѐ2n_bXby񈢗ag[6ve;|}qgKA:,/yV x>ySe/xэ6Oa׾lt?A ;\%bV<J]i.Y 01 }ąfK@5[nwAb:nXT4M}hSpD|(; )N ! @t)7o[eU\"փo 4<ꫛw®Z1*ixT\hxk7do侱˪ʜIɼIaZU%uWkyyqEjV2%?]ij棸6e Q 2 ~yQ}"@jsa(`^Iqwzʪ>{Re ! @o ,O)ʅEW]u2eyYqG/46Q}lҥno3}(,<|"! .h"4"8"adM'<zc!MgzQ4Vo F>W<<˸z衇ԩS |B${[ꫯZx'y '_nv(JwUt޴֚G~G!ϟ넀B X Z:oX"IZF  jrt%A,)!Mt,Q Ь<:udJW ]&B``]BX]\42I2cL0?G^ =[s¿w/aV[mվ1 ޮRZq-yYG-yD'DB@! @ N42;8;hʬ  RzVVZվI|,Ԓ:'@`l&!d8(]Z_`Tr(WN7݀J]7y+b&&'eW>E2^(t|OwkQr"d[?c/E#Q}#pA9,50X-y\~n.2d{C߅BX~r6;f{P1A~=&0lɒ%%6&ԋ~&5xX>+GG=#Av߾}ar,Z!ֈs=-!{o'Ek|M! !MA'ئf,X@C,X mAN[. T ZGs=ݘ1cU/$.!oƸq M=P!P58qb @0B`HjK:s͛y }Q-Qe?sGF-ċyٜDڄ( *{]pf`eGh'xnfId-9;x~P9{PWXF=5j{= "!H0g%DK>#s՛==5,gBI f2N TϣKuB@p盧' t!eSQtMV7%6v-k,-%ў >3ȑ#O|%P?ryⓃu$ +8<;Sw/hzGXpc=r'#pm43tS^{O*U_~]}Ֆyh.z/O'5uovzSP YwP"N!$6 Hă1&BpS"G}ީS' D|`B@E%8w\/ Gx'ꇠ+D'X0h STo|v Z2JǼ 7}+s6NS䯲j}p$>2vi'Fڵ7`oZy8wgzf?H K,[UP (eCD ݉   *! 1(g2%u=4Zxr8w! /)(Φ/ɓnʉaÆc=--Gy8 x(6x6cRPg}w,yR,B@xDxb&tCJU?wߵ҆g~g?O"/Mx><>3|Ee ! `Oj#,6?p{=(P<_l5}(ޒdcQ-$ {"ZBR430cMnp|E'5O,nczփ\}:!,bQ#,(OʠjzfRn%kV}QJ=eP VJ lGLpm7=Z߱@0L|j ^Iŏ0 +o=O\T_}`xB0+9߄Es̺/+?ix 836*UPSƈunݺe)=l9I>a%jnpoWgWX zgy$!!CDB@!P[X#@`qI hz0`Yn 2-ZqI,\Z); YpnL- ' ȯ}Pk:u3_c#۷W9!pO<+%S? 7Ւ%N$@<,k+ ;g-yx!6c$׶b wEZ̸++zx! b#PtIn]vǏ7A>nBzfUP'Tyg "L=rneRCg?)A(cw?MZ=l@Y!|k^#Gt[}KX>;aj !@psGXExq污缜, E|(+YyZId] ! @v>YnƁG^,C5hРGŅnɂ9&n~薯lL}h)\%,<`H_@%"{2`B!֧((<蠃mKB@Y upI uh@E>CVQ+EV(C)&换n@`s5ۉ'h^ 9$%bĉ.g}a'!pLnf'nS @فLr3UH7[h]P]n`(<աY.rknѰI\(ƃ{ƌSz㑺?rBSު˄B YM5冞ծ}pf曛u<8d။7kӧ\9e0 6DymB y+'.$ " +F2P18?nhEC 80c< 5&X&3A!\ ӯ優YZYo<90/Y#h潎8 ~qw1k,_z%^+Pi#mN2䡾O= /UYCE @ 7ܰ#f0!g Hg X#"u^xp*ǘC!֫DaOwu].n$L멧 ^zٞdHA)uuk» O><`PM;yd83CG*,e(oēڽO|"MA5naC@ߓH-x4(2 c^NE$`|gs4KI<|]2> RPB@B!XZe ݎ`1ӭ^}U7zhSBʉu(aqET\At,@'9QT sy<\!d=9@&FX&4(Fax`]gQvr|AS(]p-C(7믿,!ؾ'Z(d΋1|*ZGx31ROL)'=vKmt)4׉B/0c2B?6A&NpClTJL!GvdF'wa;0vaK鳹y AK{キPh$5\c8Xms [nC%`<$ gJBJ䱞 tO 4]wu7.hx4z{1 C {}Ij]<|ת'#p3Z}wާkf筠hOie^!v9 >%117S"Qs A)bg/E]D Qvکh)xQ3!p9묳 (NQ(;ʸZG`ړģYcs"M>! @"un!`3}`>f8p#^,`NC)Cr?>l|`AeY]zUiP)9>?` yO c uJwT=4uQn6ʲ)u^3ְ E ƒVNxp B&6Qm\L+ -p?4h{و6~^G|ϞQK\BĻgP}X12ǩg[Og_~D-bJwL )64iI??)͍Q #_6$WjyӁiԙQvu#kgv0p_EмR 'ا~z# 2Q68dS5?)(XhjF7hj WPG?U:l-sMo^4-rվaxp$ 0dB8@ »c9kl*uIiOÃdʸJx lgAǪ< R_olm}>f\EM[)e.$Y xI0vW&KYjwS,PdR ZKV6ۤpkEГ P6e'yO|y0B@̇9Fode;$eq=H'V;,ݠg7~x >d+D?=|W ~w} 9T!cjȰ@6 ,G!s=8餓ՂĹo.k,wsJ2o.~򓟼b,ѭagm᝺@ ȏ+dl)Y| R%9$fF'96#\瑺٫>UFx OЖ[n5Ivl.э+z*w^04hv @Aq-|XA^)c$!$.V:e#gDBRuZSv+] !>UN{m H/d`㳤lX2*=V<BtNyMTmh6psޠt]-lF9ȃQ}se 6#G<>b0ZA@ CKg}v#ss`[BwL{ 09:ճ{\:G(B=&WOs@]wݕ{jB $fg+ }A^:G z?׏^P-?"4 Dl{to' _76,n{f33Io,( Ж2Zez%$EhSy77tSsZ\mlutYg뮻nqUWu?l&QɄ2,Wi=r駟n?rXGS Cۮ-@E^ ),9-oyKZ[Zk ~y*y@ .8NmO}S騟6s6bPǠe֯CX sn"u4wGqD f\QĹB(6HeY&^o~Sp _q.P?k& f)P{#@ $rO+((F!G4NBJ9vPPٱӰlr{`i_bC(N] ?c8!'!;O?=J%T$G]|Hʄ6(~"#W\-+8(g^qIi}/y0B@K/6&,)FxxUw\sM-YPbtMr|k|J/~)c߃6ȦU]X hKetl {tbclMlA@ Afj&k)/>mL jM}/YN=ms{ "AsnJ,IQ_~%AW_',^s>Sv !C`YSNBIrZJ#"ֹJk%%r0NI1Q\kҸ 4gQtSRn|E?k[o5UWgZ^ggCɷu ]`3,N9l~`ДLF;yiێz뭋8xg.EygӾ@X)9|[{)0;T5:aM_׋C9$!`S/9*arEy."MAĄ ^bGa]{rj26?hAκi,w|cO,  z/@ C{"2e K0-Y3#ej|M6$]nc]y 96!& {$wݔ#r*ON$ǐ3<ƋE)]oMăp6*Y[v+K_RR DLB }0GMaMqYg\bT/$M-wVH j77ڧ96XPVJƦ\o@,Dy[ߚ! & (Y7!J̢Xz'6_>((3BnI 儖ӕV[mQ E<׍XkӦk)"}~ok\Ar"MQzmo^ U" 1v(ie-?B2$Æn<(QļILaz%%q⊙䳼$PhYPә'ra%>@ Ȧ NB l{nu)剳YƝ8aOcRQlU,R @ B%I'*7pʄrC-GҨD\6x8 [k9(~(9¾ o*޾zn}ָS8ss<#;$#/ČyGoB9w#o q)(^|ʼn/FGI'"Gof=<ܜ3aSE<@ 6Ȫm&BnԸ˓ 05s"FF3L϶?xn품~#Y^4u0%.<&'@`L=蠃->uQI1Q*sjv`-iS\*!#mRGۼlG?>\kRO H2h>c KAQ(]W/ 3y睅A{n" :rә/4dN @ B Wka߭1" %i /ˉєP#WҤ\<°<"a@ 0>$oH=u e2E8OH +y'ճjUFx PnkrO!W^qш_AHv)$N‹٫ UFx<Ƀܖ+FO?{)w{WkV/ϳ }~ǶÌom]Ar'e%ֽ=BUzJe { ʉ+[uU {"'ʐFrk_ZOZ-7}ݷr-7+@`v#UAqgB(< to!4ucIUMڔUopTxtk< UZu@ !3$DqV~7G u227CңqWT^uG2zǃځ' ʗ3D jf7axFX"!UyLy)&X1yyO6{ń1Mח<-9;qI6ۤ<3B -1Oki<~_w; i6Ey@ @6h? {An-|8I|:l>O976#g{͝>s򠘨'd#~@ ȇ <Y;bºF~yXk5~ySVk թɜIx?XFNOg>v\ ;A:wo{R9Ozew}IqAᨓ ]~Nkӻ~\N(yPN#hv#;dZ9L]!( /(nj8y0ʸ{䳟l$@ dSPX{a7Ol]AStń9-Vs5Xc?'_b\{ >1"ry`F`Q/('I[/~x,x,2POS &Z&XZ{ w4;C}≯7YYou'! kJ bͅ1uFiW֖rccw%PTwEAN뮭쁺 N?" Oq>V?MG?PPLŠ @ *($Pi ASI'*b#&vnaD"YvrY<,B>8-`9 @ tA]Q>/z'AxQG%%0~xЄt+<'Xj?cI[J B84wGÃ"!{Iٷv[ d-΋Q'JSJ В>ꪫ wN9٩D@X2,> B&m8yH ql[@ 0wJvD Xw1T$T#MGz2uԞqGa/(&Xl6N{co 0n*S@ " !?!6|RK:D;rXBB.  ꚛG~ôC|d>zNRiv ok\\B_Lw0O }CJ^#Q֤u&Ļd{iϮDB#:wu)gߋ,/'|b-B): &ɳ<,yyE9\>]y.믟{՜e@ Ȧ >3^3#8"eO:{qH :u8zIWdwkߛxM6a۶](/'Gyd{=8t$3}!˵s=YAÐ"7z{i޲.=EEe;(66` *^j5^x?qsįP9%?B0ZA&#QPP£.%;!&TS.BN7pĂRnta?1Aa7S s l zx'`dLMtr?1Bf,>OpZ\?xPP@kvDAzf@ ‡7IgM xka_r}Kj!h ajzX ц ʃ#hv#P<9;U{կ~uzyL^{%$B<F'HsxPNDޘͦ+L[{S1$+(6̃AAra0{%i@6? :g0q]Rz뭗o^qG.MkfqQlGMI|Tf@ ‡< "畐xk G%c-Sok\dxޗ#5 y;q s('uj"ɢ.x<,G qB B:qmoOŻ=S";hA'oɲG&t?kC9$yyMo@ - 78 g_-oI2x3Wôu]BAJpIU·MG>oF bXbx@@n"U^+ֳъ{n"aZi:="$"S#!@`n#MA!N @ //hjҗT|fB|]Xţ[/ Cq5Dnƹ@ Z@Z׿uFxGa׿uɟɺ$&B.E?zu(_}T^ ^e:!Exu}jrÌ+~4syRkCG!FXQ6{׻ޕ'rxMF&# )=v$wb-6.X+#7P0%qlRtk^A@ @[dSPd?=R]?s?Oӆ 頩G {Փ;xXţ[/ 5lwC7@ G]<,˿K/Tu]C= -\JZFnO=mo{ۤzB?9RyA,ŸPD eEwXƙ np  FsBC3291@ f>Ygu+b+,N:餔BrPP d{X'oN,:{ ,næҭq.@(y時dIu0|]tQ!Q$A| +ByK /=W_}IU~x*y<N({O>7o^gPPꙙ}~}> c 3ѻl B:ӊoy2)#7^-2 M)/.vV.TnN7[f(w}i޷$aY[}≯R^42&( ?-_,.$ϊq+0 K^ٌ읭x!hxl,.+~,>MަW]uUR @ @ bwI(`d,#V%)Wq˶G>jBk_a|!P@ 7 K7{w:]7ʹ~x-o)<zev?ekռskZLΤ(ww`@ i5f+&V?'a_ɒuyP 0`e!V=#a]r uۊ38#崰|ǒ.AjP LF`/u@ @ ٬l~:H`qYߙV B~O&d;cX&Ǯ:Բ wA{(@`4G}t.f1YO= 2{͟XBDzꩩI~x7L;Rz> (,X5/*W@W7xcqov)6tBB^ ax[ "^~tLF]@ @ 6vgu$0C e9i -< @ @  YvQJml:۰0I #?u'֊c[18Lx;7G>򑦪 [ b֦|p<.A@ Aon(>r2JrIl?eQ0x^<(ڔ@Wmv¨5yMMU4*n}ָz_,)٨! ʉw1=k -P,@ @ @o)(璐ʝRE8xT4>SP1YG4m rMcq~k1h\k)@ 0>;%fo!(%r}\\iR^xT<x\(VڽBV9ڤ*yَ(}]]Iiȋge#?p&-'0Uz嗓ӫ_,Jj9@ @ l 38/v rAf 6- 7v%΍zO??r9%qwb, ayݾq.{qG'%E$кz8S[zO??c[a#< }=2֑=P!T/<4F.Rpf!̝6A@ @ T"MAAâ,zgSϜ-?+7A\訏Ŝ<:XlIb@ `O@NzYfM<٫RUNxwKacióA_=?E2ٍ8|XpQ;=@”U*%vک炲vO (' ߿Xyg@ @ L{)(6s=Gnث'Hv,ASl3'9i5לHY"GYf<$2S@ rtAOJnꪫRo7.obQ@ @ @ 0*YIan^*mj(h6Ik9AV\qbM7^}nؼF_\f\@iK'(xveT9S?.nZ(&)<_G־+xIPJd|G?Q򂶖_xᅓ"d%Q(#@ @ l m{Iu;t zo\ |+`B_<;\}n=Ee},ѭۼ Wƹ@ :PxS2GNhZ{VJUx&ճWѫ^?iT}j^ ta8|ozӛ MʔAoЇ2s- BZkR.ɣ˵[x%Z5_W[=BpOP @ @ 0]Ȧ8Omq&B9'Gδ:uԎqG_qwL&.dk@ 0>X}I,|ҠɧjK&_%᎞zѨP^y2L;W!A\OCؚok\x)ovŶn koUB t\ppF֖Cż&kr-E\ 0o"O% ?s\}h @dPPuY6GqDjk_t>Q 8x V_}?h__ŗTG ʺW.j{b?>YV@  t &@PPP{챩ܷ) {sxc0L;fV(V]uzϛ7/ _Xwu6۬x_=Q~ | Tq#$Xh M? y$~Oc ׿5P ʑYA1J6e8D~m)9昴$" ?<)ӱN@ l l:I&*b(en!8x?,?O2,&*7G5=v@ $c }_ r³\c=5e[kQ>ţZaxȫd.wUV ĊwnEGc%Y@W@<>v =ܳxS~>wz8$UَPՂ@ @`<dSP`ʹN#۾rH6^Ry:1h!$W6ڨSG7p=DnK@ F@É'x}&ne\}/s}ĺC 6xL4> úgc0O.%V֒mXyWyPF9f7>ok\y>x)lz76@O!DhP C}$<2GQ{[lߜ+ß~>AHP @ Y: tnu)0p e^uG4 aY&GzU&=o֦rHR@ 0l>xksX2y)YЋBMQbvܦdk4r]~p)۱+N~9WytMFj5Zj-D3viB"mᆅNs>O/x@ dA Vž[a Nge~:?:񠘰lU'?W_^@@ 2# <ì)yx)'˿ 7Ł@  l 3<3+ .6,OMABd#;c1n,cY@ f?ϥ|)V^yb~E$- b];A?̸"Knm >Z!ܓsPr>sh{ dBDžT6K/tor㗱 `ςRx.sOd'xb…%QX,?P5@ "'? uK/Tu]C=B5-r +0qO9nWT^G2z??Sn[^ |HC8)^%6A}~>f\=# A@ 08G}45D^9'2l  (@ h l lIG%W^yeqI'% +@|:ΥyM |ۊ%X"{sBtbNXS6tN@ -!2RKu; rEsN֜[A/k- MQ䓋y.l~6Af卣χa yNlDu!&Bs^Az d@  @6 =c09 ;4mXM=Vscf5(6hI1V+POc @?蠃'x"Y/䒓Q|WչEJ+묳UnRHT %VJuFC@S|qňG_"h-@`Id_9׉H4GD?ȶΥ gQ^nk~ aN9H9(7u>WۼHcG*Q-yX,N83@ F· $]tnub@y)0!1T+7a./dޕY>V'~k_[, ٍ>_jZaZk,jG`v*]wF so?pᓟd$ɞ"@dSP{&~w9G@_j*"knB;QLXb.>,"Ѝs@ G ցÄ*s /E=7EQ?ÃrD /Lg:ãWP/.~O?t[>/>}qE`}> 7MɘZJyc,jloq"n @ !MAaq~g"1qkNJ MyZ>Q=;gX2F7(@`|H}o~Bʭ*"FiSO=Uvim*LF<!(]r%ynm >[0^@s#8"gkT味(k"Pƕ[yo[ZBkZϢqP u]7Hh㹮Xo. @ FD "WgFkO:df8&B6(Ky(/Kc%Q@ ƃo:x' \r0 J+묳%J!"9G@ʤm@SL{|?SWVr,(*^xDՋ+8u̠6ݒFJ ^Hdqק7K׼5#7UyUu 'fM3}ָ2]vHh{^v[qꩧ7tSB=9'?ƃ>X:(eUP(JF$9h6SNx)q馛QGay=48z=@%@ Ì+wŻʞ 9gy&;cH9Q:T6A@ 0wܱXgu !:}_Op'?I((A$:=A.d!)N|'#=al,w'-lqugEAկ~5ICr9m0W"Xţ[ۇT XwC7@ Ąu,W#ISGma'^^ãWAy|\Xwvء>f\٣xoX+S9b}DrˍdsJ9a(((dg ;7xDȫv-&><R)ַ06  l\fQa1y/+KBu]xtk<]A@ CeG6YJO@ )WMeNK,m"ɼ{եuYWIFᓛSA`}> }{)vmS<B`_N2דd &n=oZ =s 4! !t?՛ѷ_җRx6iKFуuTj1jMKTO|x^W|k_k$~ @ hy'G a@IDAT9%[ؗknA8qi$Gn so/K۲֒ @N|pn}Z~OmK66,.򴡵ϲyWhbf .(oqRל#)Z7q B$d\o|pv__ͻ`^NiK(ثaΏ7>#$?{-d}X2$Jvj+g}vRKO2*dBpLz'wqG&0S6;5^{(u, ?@S;)((9< SzI `ljw .RΓ9e~ж?ӺX~ _  RN(B?"VAadQo}kEko[mU7aw~ueY8餓ONžzMC7xcZ[!ȧT0^?+Dapkfj3Fp@௽/A^{+@Jh~#ӽ+|+I ScJ9ک `';sKu(^|I~kQ(ƒda vd;vʗGk7˘uGt᥿!NX:^{X*@ ?'-6Olr0zlvV^yj<<ـRP0)ү^n߫)ᥠ2("'cOrJo~3yxN%i$ϱ}o@g-a{z /LH<XSʄ^meKhJ+cA=MZyRFY< E׌Bx>yIƬ<&s$ e k]UW‰RHx MB:8c7ϒBtUP m'0$S~'cL8k/Cu饗\rIq衇_ZDzJFkF.† ;E}:Ycðe,4(<[ 5,eSP:k=a6lo<,LhAM/PO8k<xr   ?cF'L3dgM`^ "<(u8$^;CR.S^8iqWmoP@ػx]ve?S_I:bİbB ߄Ǣ$ɾQxxOV։ ^BBf4N9Qb$S'B*:2RPT(x?&1J&)2a'9Znҳ/RVg1PMуv`}r%a\}#ayXz䰰{mS @/(Yؔ* .adc]R_,KFŻuX6M(.yEy}-4@}≯,NI6oy؄Cֺ;8քA@ Ќ5by.ӮrCnIXQ"!w?F_ `*%8Sn$cS*J^ZEo3^lȡBQ/CYUR։W_3_H&% %@(n햔:7F;4R64,:/Fr0'.e]9(Xy.l 4<-ƆCCV4mZZձ#'N=1, Р@ "g3ur5bn{guf;lF=l;wM1l GXٌ7 )3 f3eH#œ:u=?@"[qH(U0,㠩}g4VέjEm |zHC\Vq&7$*b4D>VDŽ@[7^>.=0k2[Jh}Y^EAE=ډҁ¬.H/ym?oOȪCI0~oeA[sNHmȝ7EƠ 4=#꠭yY 4oA6U k@^֏նjOdΗm:^"7:Յ3R6 ͨY#IvXMq.oqkyMx& UɣZnf\P-WM<}{u`ƦFԡM0"<"H$ \XD=IxMƉ@-^aJZ !_hv`<* B06.-g@wK<9aC-~S:F*SLdNK@Nֿ0nhr5ndsOG9 ҎND@]7Kr 9z3d/vo݈wVƫ(2^ `lqHP''?IRyQdCXGOd]g:ãW%svZ4hv#YͶÌ+yƼw5( z !efj6`/S)Ֆ8LgX[e+SP3Z ۩"SASɛ\ۢ)n3kZ<}x|뭷Nn۳:+BO-u2cҊg #(k|w)eL#^#& gdM 6i~U%'uu*Yt# (\Ǔ‚GLVN:u bK/Mq>Q[O13ϬW#}a\k^ui,`)(,>YKu!#hz `^JrS>$#hrHʣ\<æ :(@`|sKY䉟Z%6qXs>Ã0R4Ŷ^^uG2z@ECn9sMc`fӇ# _JGP Ee-:ו}AqP Yk?k:hded26A3a8>d.7Tgšp9:BY3 դӁtUDqǥ[neUWeC?a빇gq<3 ,xjo6WQJ7:]d y C+a9oT"pUaH)g(e}eaxM{(LSUMބQQ[19j䥠b$|&yP4Dz$&(#f<:S]n.{Uݨ =vN וm,$ϚC)&eSPx94Yz %.Oݴ34vܝQ1"`$ ShNk_BWQ/x)˪R}"nA 9̏&2Vja>&EIQ!M.b/LHא7 N$10[嵏?x]M eަo22B[?mlL;,+u t9GqDbϽ{Gk@m+H(>g-l, jxx~ PN,2UWYe"7@Y ôL0O3W_F4S$ַ!8_7euLtJ403y.ur-u eH}Z_s5I_„ 6k~463D$^m]p[k3a9,NƱ&"瞛:Ȩ*}`\ԅuƍкPu_ƾd_}RnS\ k$b *WP7a?CżyR_W槨?7u&-(@`<za6"EO@ObP&췗VJ0P"kR|XұDαs @ڼS0lN@`! k%d\'^aڻn<[ϰ?y)XpaK(cMlk`<3)hN;FUX<(dChpG}tzn{l ZƳD:4/{쑄d.u~=mSQ'B)YSPfފ_g*a2_җR~#Le-\#a*ouI!P&+vw/eVI6O99S =Nqe/0(&PwFagzl/.#LƏpW'EQGUI8Dݭ;yR(ǸR߶Gr!)_-+Ɨz^~婽N5Ƣ犽w\4UbC^&s?O'ŵPxqثR>)( =̽xW8x3W&s?4ha 49hCcWiQ=ZP  L>Bx[H>m[}l,F%BuȆEPn:ӎ[;m6 M]gMU'xbq 'fM3}ָoL-8dxNkMNJ1gϚmp51a.fhlCi:Q"} "zYԾ=gorB:g UpI'%<%DL*4lՉww$ϿM6$7:%!8SYd?%%F 1WZwޙ/e̷U",PFPiuN8e F`^ ]xƜ`(n}G}uu,0NySh\/yS\_;x?X)_o喍J7L@n}qgjw aiBahOaaMU7B},S5Mp#<`MQR +Ba[yOE9D׉f *[+2nYO^Pc\HQ4Qn3aϓZ“bk2ԟ<ך^ss`]I&IM.i>XnXbxe9 Φ:mT򢑌Ƀ%id9#g{ix(c1'N=1,/pP xf1*0˓uEBrVZ⯩xˣW9+EBP yˢX5m(3,9!G}>2=JCGz(a)(<{nC}bX /LʍD̶AƚM^0V)'ja|dE@ 0鸙{ U^cXQ%M26' 2uqJB2&E2P$"R)Cd8}XjZ~>^^us5 nc4A,O0Yi+^N!Vi#ql XMzdKR 8\뉧se9穬+qgrNƛ1b8cGx2㞏46}\/࡚ CGVbsDTؒ<,?)IvݱkR @` ٱ>hY2FͥteG )Xm7#ZL}bڥ=`"" "YsOs%G(P E]ԋf5ԁW Si46qI 9$֭[gI]mxm/}sֺJ9Ǡ5OK.Dv}dEH./N੧*i$\9B." "Ai p:3JtÇv,/@o8m sR:ÑA؄FcǎXmuK6"eGEE@D |X2Į( T$)/;8S7pg>=6J7 ]~/+ӌ3ܤIiuG /vX9 j$RmX?A~jAZn޸X x7HΈWfs_t/=P%mqE'J`yf.裏\:u# jt)VllA@MV [O" " N 0ln-yhZ40;vC#=*O((x1 ,ps̱^8'۴icuL""P +M*jn?;@ D+(xH0̒66׍xIdb>bAoѺ7BZL}b>X^B B+%KQP%{,8r-H bIe(%Ce˖Uܺuk+oaΎsVP[V=J Q%&~QB~]Y5jcf}U]"OxS<2 /dyO+]c#Av pQ=H,Y|xB8իWya0Ms#6=6ѣGj]D@D $HHK=7bh_æ"L PgYmzJ6>𨣎2+O廟`.kr9穮+Q8S9'˚&M"饗^r_KpnfYg:udaC@!@!H''\ O9QHk@c()(p>3SΝ~m{lذ;ҮWF9n{]#l²cǎ[ 86!6x(^x=0z.jVD@D $x\pO>1# ~ ˄J !1܃, ~K:["m:.mA|0oWM<~&1xx֬Y3nj#?쳡y*J>K$ZpVRŒ\`ҥc. :S[nw"\m"^g}G^MzC!SW@ Qs'ܨQJ:Hf[䦛nr c>sb CijcJ>\r ZQU@q-v„ nܸqpN{ذaf MdK"m$[g;%脚뭷3fƍ)(g">gbSi]vNAv.s={N(8*8+T`(O0@i""P6 qB9-裏a%֭[6D!" " )LAAb wdnі~ !((Fm5V"bIj?'!NDeM B D PipKSOW:`` M6mnA 7`j_6/~}'" ",?x"ZAggm/o'ˋKϲHeQHf͚-[uHߓ+L821穴pB`S?<;8wV*O`vǐ!C >I6^^ly_7FD@D })(d`_ںKM@l/G.E=zv,#ɓ@'6)@’:II>(/$U9=8<'QN<ٽnv2.)(25j p͜90tI.4~ O/c\$IFݿiӦJf+ IG!LG6 &<=>_b?lIEPOOGZj .pxNaÆU~?}"D"iJZ+oty.6J7 %D i3~F<.(:$A^[A֯ZWN:کCA=HaP8z8s`3b!m'1F{キ<%t@,H%^5< Eܖcwӏ|<6b YtkMh~晜H|}Q$JB,kϿ#œEQ$\GY]"3J\,&B%oi}mIϩEE[§`G-ZhFJ1cEBo+>}zt%>>5xH߆~ذa6 oR]a={-X Q<4b]"bV4bĈ;e+ʥϬ%OEME:8*O رc! i{Z}ᇅ04r fFD-#?*9B6T#7f}KkqÆ 8rÛmHkXAbqFw$P-A#_20JZHw"2~; ;}+;"ocʕ*M?a+Λ7EG]u֙{ʕ ;vXmlQCm RoE@D@B$PZ5w%KJ*ts/Auc <,AzYhT:$Fc 7mdV_~eP&1 Q0)D33x79穬]vŵjt!KSLWP AU=" " " " TA-؃ ܃>h'p ҮWF"7x[`#g| bծ]ubGT:ߧڵk-Bo=Z}e=&OlFrW K0qذaoZj%|"m$]i 7Fr߿^}!E|MKIHۧWj90ÜTUv5jTVO:'" " " " LAAbbyJC?I*Gȑ#37AdBDŠ(m쩶bsPѬ7nhFDjټy#6%m/`"mu6,SQVe}Ok֬1x9,]C+ŋ|p~t: dbSic})9,QNӠJ/#" " " "PSP 3fKҢM3JuM : ZHԜ ܹsXAQ $ƛTB;UZ5F$" "^{ aآE wYgmhܗb`|H+VjQF~Ʃxs"m@CxH7B S"hWsNH06 o5h X'Y͚5=T(O>y*j… ֆ B{3<3tV@SP ºK!@R)$Ad 0zfZ x Rm?>^." "m֑dG]P>#| KI{ω}\2ixivA޼yU}YL @IDATsjy"" " " " "X0]CX քI@dDA7BxީS'rt^x|j iӦ;u/zҙiS7*TD@D@2CZjnnɒ%?嶠mvN݄Zxv:tpaݿT@`ke`"ĺui veG W]G?a+I<:wldQPD@D@D@D@D 7)@Aqg5Ç턲2D%V 4jԨM  %DX,eY0,֮]j1¤d@*Uܥ^xcK#ɡ G!_yHL6l D GGFtR(~iSTL)̘1M4v9#]nyod*O?a+i-[4/:u?TPD@D@D@D@B"kXXύ7κO!CD!jK󙘏Xm$߿y!Zo{6PPlذb<ش]D@D{!lܸфXZq_zu!#֭[۴}7ۻ 58H'8?PZʒxqШD@D@D@D@Dt)(d3f4u=!vTS%^^x DAٻ}˗ƵmQ 7N'͚5srHt%>Xq0(o^p?|M,#CFhɋ*x|pOvKPdUMw3gδ\p#Wĉѣʕ+݉'޻` ]TD@D@D ,)("pt&aLfnAdqwL"O6PP$QZ(mTCpճgTq" " )@N~AhAƽZzϰL OOrIx!uFgD>.6H-E2rs_9chԵ_Zwm޼,|9 ن^TŋC={ zɒ%fd`tAH} d-ωA3Ʋ:L"m:.mrך5k,L i.R_5aœTI[jH}G6\g$ HF QHLA9fk6heYPmF16u+ }'" " *U*3g,J&M^AD ta̛VZI7HIWump?lq]t+_|99$N͚5ӫTGg5guay*Pkuֵsˣ 92 <|ISy@\y5\(" "PSPW_ x}']'@<((nÀ++2A+s?z%bPmRmcÆ GDlկD@D@%@իWE>VOBȢ &qY2g<.VP$B!t?"HeQkٳg xz뭎76eLn9O <Ļ#s2gE$бcG{|}ŋ)(skT" " SP 3fLډܛ嶟 Y \B)5A9%K7}t‰NDeF*mREIˢE@D X$"%EʕڢMaăO .[5NH[ <Ax?hK% 0' šT ˝p =QH7%׎;iEI Pߞ ]ڏ Uʏ ɱ˜%I)ٯf"6k-tHi;` \ҍ;R,]"1Ĝ'`%6PxqgY}J({~ׁ==PWhf%gRXsʺ"XkrF GwSN-N]xJݺuooED@D@!A6-$e>Sn]:>}$?iIF rα#z-F<q={WDOMb;&{B lFm`+ UzmLiVrCuڵK2_+O;LymCqڑ?3@@91sp#kGvED@D@R")He;w>c$!`jӦ#~h\}zʕ~" "gd5VZGydv!~z͏р#u! (̙c'n,.,h#R]n2bIƑ~Bo`,VA]]]zj@7ߺ[p{t fkժh#^<)*:t]-[.]jۘB{dd+bk-oz ,1|UD@D@2GE4M6YޫT'EwӢE 7iҤs%Bu7|,XxA+KBdPEQbܸqn=AA֫ZW) wi'y堞@9^wjŵT vHVIww,nj{}l.EEEvCsZRkXYeRD@!C8m6x4fc?zk{wR$[oY^x!jU@9/L1A^[;iEI 0 ,o!i KtCĂ6b[@b,b^m6!gb>bGiFiZG@?v-Z:ϩA }ϣ#FFtSǐ˃rHt%>]BQ}GsbŊP}YF?a+L0^2 @~$o4iw.c:-P JxW -;xV\pYfMK:vbȃ(l瘋.!L{!R?b:sLsr_eZJ@SN9ń *U*107|GȿqF =ֺbX}-O(mꏷačWM!T(K?,m ['wz&Zo_Ϻr,#{( E}^{e%\A5kָCժUc?裤 o<o;KcJ*- K@~zk ЯBy"JT $WDn*Hg"E$JVgϷ}k{i}CEѽkf̘auDANF*kzUԠAXO?-߿Vl={vQDp_kgk?(݋"g'מH,oHnrv?}EL6(bp>gZoӦM_^tWrx EXsɶR(-("ϢαHN;0 p?PbŢ1Qy>5kVQ0F-" "zPD~UD`L87|JXy"" "YBŚM6!o5⭕ !=vΛX%+XZ=oa`  .b)D;ZʈǟB/q4}NrWP0FZE !Y./o &G;%DOH6,w1@ ěKC󋹌.lg.J?W|NǏwY%1ZhɦՖ@̃i" " " " " " " yDp$^n]*ֽ{FլE@D@DRPBD@D@D@D@D@D@D@ )S* dw=\$ND@D@& E@U@n@_bE_r!w˗/7T ?;l"" Е$]:^D@D@D@D@D@D@D RgrnĈkQ^u~Ֆ#rk믮sOt*UJr-" )(~ǬtBdkcBʷ~ﲏ@uKC.9VBhҥq;B{F:@`b7ίxwm+:ur_ b׬YS8/e/v,E)͛7ύ=-QZ5;mmVY;t<=8Iǟ:&"ܟ"%U^=Cd5ÇaÆeuչ|nnԨQnŊ5C5#e]ܣ>ZhC=WW"O>nɒ%;^{ ƎR|ĉm۶cu;SPHT@oRD@D@D@D@D@D@D@B'SZˑ4iw.#0F:UR%fs'0~)U@j1SLɪPdـf͚G$;>d TF WBw@- W5H&{+*Vo"{|ri&V-$DEl9QHs@>"gUcH@߾}]Ν-Bw#Gt$W)۰a#?h ${O믿~Wv}%wLxn?w}x>mܸڦK^?;>䣡_})I݋cUe"u혾 tm֒.sqCɵgr~4Zɫ\mVE"@|P$vyg׸q㠪U="`a}#l2=w}_;8)פH %ЩS'Θ1íYpAFN^w}m>@@Ouֹ_I:rMPjUW^=wXۮT|QH\pB',N:6lhNm̘5k[vҀ~֭[ AAyr|Avڮ~L{a do;݁(,?p([Q_^{9~sSzu4o 8?~N:$w 7טj.83gtڵ3K/? 8<ɇ17ÅW/7|p׿_xkĚF BYYXKL39zK36qƙbOS{o{(4yAXN;͝;d M65ea7ʉ>s=9F)g´ia&KAA?=\FY/ ;}gc|<ی1ŒIه~{Mrs6*xxF~zw7gyƞ;vXдM|VP[ xna" " " " " " ixԩSj<ͪ&Mnݺg6 EL~e;Lݟ~Yf_@V?2vݺuݥ^j!^|ESV4oܝxnwaF{8BpUW9 <x!4i0a`Q], ,k裏vݻww(6o1cƸ[oS@Q\ry|L8=K.c5e AdxM.׾}{%ׄc^>yeҲeKW~rQycRP," " " " " " " 2eyZ@VL8?ޑ@ VGvǏ-Z͚5ˈA^{<‹;3\JBGXhժ/4<6ml"^a^yc)^&U/x衇1^(I<4b Ste޼y !+V?&fmL¾s9?cBQAqAyyx!hTD@Jتm<(BGD@D@D@D@D@D abf">N'kF]} 5>ao&5[oeIB(3QʳDB3:CWz\ Bb>}/b(+/3'p *)(DsA>X9% r{qBsήw:u@lRP梭"nx@BìE@D@D@D@D@D a}1"9T~#@#G:˻6:34B.c9C/bÆ v(\ʳDƇ`W^1%y+IѶm[@RiY Q8@h(o㎳Q;&BQJxxT=(Ƣ" BJc" [P-yd J܌tB9O\8xb{wy8$ ޼yY׷nݺ8@}`}`O *X>ʕ+ݬ_m'2iT(+/aGh%<ƍm`;^ q"17)R|"_+W$ +VX퇷TD@# ErB@!JD@D@D@D@D@D XbZ7ǣċ_\򗿸C:5;ժUKI08ȿk@w1>KP,=jɷ v!UBED yRP$LG@H*C@ `8%ЧOG2¼R-.]:0I˖-";~w9NN:cZ-E5J D^O;{$(Jݛoi)OGPE 8RPR5@<;^*" " " " " " KD۶m3|GX/v> ;B{"Uwr9<㦤d~_~@$[0Kg6E'x<(=X^{EbN-յkWS?3(.~a7`G(8}{,Y5"Va;v޼yz=hٲ{ݔ)Sl_rdx^{ϏvZE@"$T5" " " " " " " I`&$o7nuG@iH4}g[#FXXvڹw rҥ{UJBq>^}նׯ~G{v >iv% ckך!o(C (~k߾%'<Ի~83]ӦMSy&")(Dxq$I"u̙Q[ cZ\Ϟ=MX[$ w A(%&V?p 󄠟ss e_ ԣG7p@Sīj^O>[{Z,CСCWBxոF޽{ &8N= ]tEn}->CSFr6mvm-'$2dիW|>P'z+"E@ ( (aAիݪU,1ɩTXel.d}R[jոnp_~K(<AY~ ?м|~PCւ"nlk֬׭[tMO>q:J +Z]2 HA<3!i@AAIGAA}#Xs=~*7W\q)4l*<}޲`rطol@]&(b7uU vG@ I@+N;:^JA@2n^<^@$;uv:RR&~p{iƎ R% uֲe%\.bKrʬb}b96w\,Ox [c3'k, " " " Fsήm۶o$6~G<>." " Eu@ ? _{5SN\ 8 ׺uzC2MTH5qD7k,7{ly 'u (6m`/܂ \5l7L@6FE@D@D@D 3E:\*_jFpm@h *@W_gN'BuM1Aoz9#<<`Nƍ{97`sc%qWl F._ݱ^x3کS' tQGe],T,B@ <2e[n] uCƍY8aU"P D=(Pd'O66G) ;xtK_eq5:|pSHs=W_5gyƝ~ Fa-rM f(zr+V iՙٰ20L5!" " " i ̙3Ӯ'*Uyc"" " a",WH{tK,q3fpw}[pYѣ;묳LI m1ok׮B E ( ;< t)|i%VB.Eg}a1FN:uc9(Zc9K(@~f\lK(9d/_PP(SI>"" " )(ZD )))-A1!On7;vG}*Z%O> 5i$G>/ vyyf{[w2Z;+\&Zci!ڃ3Ʋ:&" " " q«If͚8{[ndN;Qu9''a1j(7m4K޾}{>$uּyC=xvmvH\cMNg;w{e<إm/^x 4Bkl" " G@ cEjo˒jc<Xͫm5kf 4oM@MګLo_} #hРAq)*E 5VX4Z|$_oƑM7/ၫNZ" " a"L[dO>qs̱<ک^%";3vT x6h=#nĉn֬Ynٮwޖwk*6mr~%;v[`CᅅXM 8m+鮱!3rã!oȑ׭[7aU"켰,c=6nvuWw["l̜93N-\ׯKZz{,7 啟~ɭX½1Ax(k<&O;1C5#Ÿx˙Uᵭ"" " a",W aǍ+䠸;mG >?]~Żw7t)*/PZ1^6xaoWm&{W~뼜9=:Aݞ{0OT" "  HA@/q{キ%%^rZjfN"[󆇄e˖Y?O~z;[:-_#~,k b\z;d֘@>ӧPÓBI[n]uU/[cRPd\'D17s;#~ha:(w)8 TTh <ҥK/l yX@Ю];%ѼysuգGתU+C΢Eotb$b'@ 5S|$=23f̐ ^x5h )(qkL" "%ȒP7 Vʔw{ݓO>&LOHM"ϔEHy`ݐG|@R;k$䢸-wŀrWī#Vږ_YcEB(d_~)(7R_W.(@*b+u~W^17کSC=>=*A5B I&ĦM\&M3w);Sh #aLaqi-Z2}U @k0i" " " L`iݺu<̤FnݺjRjgH$QIҮ"{vAaÆnʔ)fs9<)v;rDGs ?[8'׮]u95h A#tM65:ox8-ŴRB3r:*" " " Q&OfΜ?֪UB^ BU"@<%{#D>\۶m-7?lV/;S;n _~'b 믛bb֬Y}-zXCE ZGq/EڵqmJgL,#" " " ec=<(w '90n|@yȫ݂& ^pcuwyYvm駟vg};]ݶn m!Ż}w,!?;ʩ{+-VfMw饗}"!7}YˁBfmr@yX6WV\8תV5;1 r7]*" "֯_00 ӝڵk:_H =L$;W-" 9@@ $'իWUVYvfCټy扤Xd/"0:<o4w͝;]tEaqg:Ȅ!Ur뀇 gyƼHW~}GBlPa\rIrB>]xR ^0 s&©;X8#S"~G'|M56mdGCyn<,eݺuE@D e]tq/v ń?EPњ5k n; "E+KE@ =B} Bls= 4(/0g\R8 תU+|VpZ{ ~Guk޼?曖 {rvMҥKBkرnZjSNpN#ɤgvr];z![g/{I'dթS V+&mkcz#y@ڽۦ6m >FXPZnh=z7:x Ii\"Jf9W!WyaXTۙ?ΥU@"9zbѢEb,ع)}]vI"yY?8L/J"PN s鬹O?{v;<t'xw})G\SbjqGPy XxwyP?{\:,"#|Jvk{%JW%ߺ>aQJʌwFv KjONN=zX ([ lذꪫm̘1{ȑ&hB Zc\{Yȯ&މ@6n/_n9Əo51k [)/.y|'ܣ>^z%ooa,2#YB⨱@!"cݎ5!Wx ~\~\nBl*X> 7qIQSO=UTz%U.++Iv"=DB<gc^^D>LIͺIo*~r!eChf͚u䭷޲AUG 5A($J &XN(y䑦z us›{3f̰ЅXDbYGK&JiǍgq&$F)1`{Ⱦ$U7|yPs=TV(X0.d+ BA!2^=Tr{0+VqS a ^@ x'݆ 0\ R܃>9ɗ5k,wW ^&vXFFD@D@D@D@D# $H&d"T… R+]+ ~IJžx̞=WݺuGH6Iv=ܼyC+ (tf@xWAʝ(ͼ[k&^C<"@"T yXt A;2=xcCȫ)S':<kȽi޽]ƍ4~#bR[E@D@D@D@D@"P _~}e`@ر#ksi]w! !ޣxa$7 _%sSAs=KR~ :+ML\ʕ+ꕐ$&P8""\Ok}ܹN:VZN8Yg̠֙` _6eKaBVZe]` WF0z;CMI=(aSɛE2Ν; d<w$" " " " F`֮]{=0 0B-[CrTqըQ\QHxIIte{}q$U k@N~bz}ŋݳ>"mӦMw&M\,(8}vsmڴq'4œO>PTh"ŐTr饗f"k)_n%z۠AG]%oy&~mC!=B0^:MmPT_r%#p}"Gѣ-)(~##P~-Zȼ%.zj  QFvرn@k׮fF(+*@PN)E#+ -ﺫ xn.QkYqbZwAw%rp= ޸DHgQG}d9]ݣg @BuŔz_I(d ;R7_]q0~)R[" " " " " Y@7n4jL ^%>3Lsz*T`ڵsw{G̓_4²t>[IDAT<*T# خY͙3ǼN8-J6r|ᇶ  !%ě L%#5kt#GBLED|XX '/@17s %!P ʿ[g-G8;^Y3#B;*ua((Pđvۙuv!y `?{$$=J@%>RJTP'p'DcK1g S(I{%!ɉ!K׸kKiV\}։yV]>w" " " " " yF  y B@qxZjL>G~zdڃ80!DJ<,BXd[D>駟p{z\ݺuXus,[! >4mڴD.K.\., m۶B W_5qιK'}%FJa?zDV2.(# :u+;Ƈol7,vF%,?4|17} 'i(9r0/sǵENmNK6Ah<1~b6(XCX YGo͛oƘۺeY_/6O(ÆyBqTvm[oA\gJD W vMSN9żx]%{饗E~ +nĈ4ZD@D@D@D@D@r@A(("p 9MCQGeD$CLF8[F'ކC&#!pկPN 7n{M"J=^zjL}3f{7M؋)a.-2(KzA] ԩSm-]ve}{V.}>lwYg[F̹ѣGz=EŊMMLkvI's?B~K/ԝs9Ňm#ob#*~ذa ܐeڴi=/y9L/޳N`#)^a<~ƏZ|ꫯ51c Wv7td/~O߿?a ߔveY\yԛ08> Ovq %1ǎkSþI&v^!%TD >Bv=n֬Yv-z\>}ܡ*W:F}' )A( 6~G0AѬ"" " " " "PX BAR,ky{ݓO>&LOnΝ;p1-ƭ5f~=c)醎@}wZ>-y[7p=lr | a ,ّGZjq|8wºC AaVLekk{){lxLx&Gξ|e= (-x pDX70 Bhr `4@ LPd(Pb!G2` 'RoeDF k c-Hp 2Q,FED >5n=7o?l:6}'yyq=8Fx(b@p'8 c{ A EW\q=p"xÝҁun3_g6a+clݺ=$K34-)9n3N]~2dHU^{e.Mxg\pm0 y=J y;B6?"D5j)Z8yn`# Ap$тGCQk J ^yy+:1x j4 4bPL^Vx5 cAyui2, 1xvi)+k 61S/m֬+ƂK;TD@ XxMxꩧq wXE#ł=iOX9s@|Xi!|ʫb' Kc޷>#/byy OXh~.Xǥk3z]̗5B5D B[crPH!'DG1NRg5<ȧDRg%J~yѢEy  '"~ew~^H@+(ƃ;4 9wcE,b 8XFfҤId!se,;>" źK!c{!\k!(P*)+,^VHZYiBc xIy0J$Z6#i8I Eȳue"SHJ?{8$ G}dmD2dBdOKh>B6Cx!噰ZD+1$ r 4ay' 7vxW ψXah&$x&4 OPV2Pc 8V=ߧ 3 X`2 5 37pǃJX}6f\P>Tg͚宺*  v-H߸/X5x=z+op !Uތ$8csĘϖЖpT_|2d#0Imo&K(Do@JAPj!4VGV$9s1B_ Nf6bg6x,RQ<$)0¡qƙ2;eJ hP=VR @Id3<9Y*d5jpń~X",ܲ03BwD9u1}땹Fɵ5Ox7n,Ο?f3rK9󦞉@A [={+f7&NXљdr.$gީhA((!ѱcG~HjX=%={yP'myyLiWф>B˜QPhk6(:41Q@wq=J:1"G6O% DZڈw'@Rg@SV`EnD9\[@9E@HD<0RQc*!jŔE&$QAU\ ȍr,ėVofggzZvnw?yz9Æ &M{oF2Yڗ1cjRk[9fp }rpzN-! 膿n+pwygYEYŏ>֭ Jz :42\ <+3|?w.Vj+o{<>c޳(1QƜG/E" RGq    @E @Q ۔%MZݨ-##˼({K.>]34t_%=ƍ6mzZQJH)L (E7ݕa}vo\,bSv@ErqQPFjXU#a~Go-ϝ!*`>s<}tTfOuV,X,gLA1RfΆ <"}b?t    @y T:vV{ff2+o     @H @?"o=o    T0     $:mS9l    r獓YE2}@ԨQRSSf͚ez;8I}y@@$8.Y㏷vYFex/ȗ@(u!CX-,--ߋ7_㑿K   hIHII43^g 5֠Ivqy- P`D Kd]  C i L\y8*`+*@plY3    G9Vli P)&    E @Q$6^@PϏW#    @P     NEdP$Qd@@@@@8(k(dRJUU@@@@@ (Bف0 (@ @@@@HFxr @Q @@@@@r @QONzP$qg@@@@@(g(3eF!    @ @QA      @" H˾UHЗd$B"6 @@@@@E D P)Z@@@@@ Kfʠ( ޖ/__g}uҥ"*ۀK`׮]ZڵgϞZb@@@8(*α`KH$G7omfͲիWmF">O*,i?4h]tE֭[7^z"6   @RHNV4( Ǟ={ܹsMhA" (cBAӧۄ lɒ%iiiVJ""   @ (_= XWqd:tϟ7fϞm[l^zeߴk۶mʲ 7;cŋmȐ!vyY&Mq'@@@^E)b͐ǚݩ' 4(wca(jȑ#j*1cߨSωVZw]vUE (,+³cG̙c˖-@2=\SNaP/m۶/5jd 1@@@D @QJGQ ?c{VZ6rHKJXm  a9sߘ7oլYFY\>l/J㎳N;nFS0VA y饗lѢEMqZJ pB8q}"^ 8T\ٿ%طo4QMv`o֬Y~y@ aUM[n~mϦP#m}R6OO}fGؼy w   0(JPn03(6mZJj( .7tRỏիW[N즛n` @ B>edd'|⍴՟w^*}y%Aqꩧ&α7   (J@=R0˽CڼAӽJr)gjԨ=g@PV=T.RkA 0m֭[7^zɽ1kB5ѰaC9^C@@R @QJi41,%VX=(؞={LeTdܹ~#BK4ϷO>9{Φ##Iڵ-Zd}zwgΦPI38t6"Pj/   h( pD5[m֭{noLׯ)|5޽{9r$OD@jxCvav2[elxb>AC󳛾nٲz3uSm۶l PR=ҲiC=H;[wKP|Mm*פ:GԄZjkN:r C.}7th]ssNys^W֫W:\F@@ #@"-]n۶z- JHEfw.;묳b嫯GyT{Z0X>u*4|\J/xT}b͸MDF@74t WX %Yh}wDAW_}l6wAH@V_=m4[l5o<] &… 2PRA5MHСCC h^s瘸Yfyf|j׷i&" qƍy'.Ukٲ%畹x@@#@"^pC VZy]"? O?Np^.^|Eϸs<'XzJh9[3/_nO=iv%[L:u-Xnf/  G}{9۴iSٳ+A Pd]ny%" h/lT*IVvmۿO25x[|rIy[VVi"ԩS)4&y&<]V^ϵUyI*BNA}5jO!)!   @kNTrgַo_;|v+@g /32O8{' ܸ8O6oď>9VB ƏAgg*_ۧ>魲kt@Ce=ڳ(tB=}t e|xGnjy/,FЗF2(KSj24yK.> EAeK8=VN :t.1p@_ghij>%z%J#9IFF7*K[o )bPfTf' (S  8 (@#Fݻg/٤IkР_X*PCpS9{|J*rJO(0ٳ fcǎuf/Ƒ#Gz]wEkdG >/ FhꦚJssFr9D)T@_5\7SoKׯV!=!lWTryeT@u.s@e*0Cz=TntΜ9^IAK/38"S9'3U1cG~_[5k2|G@@*@"f̒nd]v?~Ѧ ʒ x<^⮻lŦj`Ymnlnh[탧^Ap"sr ݺul PPŋ{l=l @*ߙ3f`fo!C|&h8}dW@!r1MRSLQEI <˃Ç u$&(O`N;+*>ZNͳ   @qPTnO8ѦL[JWRc32KjhƜj~CZEfJGe$7&@ȔSM ݠHMMo馚M~tM uLK* Vl P|U &fΜ#6mt{S_~oߙ5M@ZhZUrsΞ=Ф(ɡ~i&M2MlQY&A";^X~79e, -QcSĆ l 0@@@(Ff) ֬Y>M)~_i&n&ҥKm„ X}lm_~k6JV!oh p[^ V"؊&,fBpM )4 .l :$~}T"OL]tӧDV')XI%ۭ}A3o޼э%}/  D/TM*1yi&k~~F@@ @GH7~u8n8[l ʨ d̈́+΍b]effz묬,ol`H@}*PuVzկB-Ϩ8:6iokڵ eS̞=|AS3df32@d8pQ`B%vfʘPwly+jJ$쒆2+Ym۶;;묳gqVEs L^y>֩@7)"PC硚@[@@@ CJ* >̔*͛7ťJ1) ?.ǔǪs<^ve].um;3mС9mJ]0*@pa_gI*dT|>+rɒ%vz1-@]@ك魠|M6v5 u]VFI@m]t` Re;PPyaÆyOqzN?O?MAxCz\"HZj$7   PTN޽{D(sA 5Tm_]\SY[UI7_|E/٣u7hذ92ޢ<^AЗ>[nDo2wnl ~R)x i+ @ tUݗb]wQD;ꉷ?:Rk׮ϳzN=䓦b_~ysmZ*UxC/UӤb   e)@jh:s> Pv22? oymʆk7mڔ};k43N7 z#}M:g^Z~׮]Q= UzG\W_}?@Q3("7YS~ZN^ @ ׄ ql̘1^W?Dg!233mĉ?kIc:S_240C,:Mйܹ͛7Ͽ;zh'Xѯg@@@ dPQE]GJhfnbNjj=m4L :^5?~ P_sǎV5M)Ef9o;ӼVzۭjtR_@b|/E':Zv*3, v`  ШQ#S D&իW%kaهy&(s@2ʈU(SΟ?߃꣢dO(#7֫fꙦs[PHKSHDs={lդKSD:=@@-@"z뭞b{.4CMuwM7yc8.RUXZ={\]BP -5kfog +Vx҅꧟~Am |`R膨 f=DW@rhР7.M^n׮]I lÇ~=Լyso`,!M(QY#FxFt[{ܨ&(*ˤ`&/vҤI%7>f*ʢk}YY   @IT9vXQCAQ+4{LϫغT뮻ΔXh,F"Oiڵk=_S|/C3pk,Q}an۽{PO-[xi5GT)(aTUgB [[S@AbP֭}^aP@R2>b*4+::GTٍ<9S20:ė6mX-r*)ܰaC%~l7eY(xr饗2v   @I T:VǒZYGT@]@4PpA tQf;zg` b~CU-bb5tHQ U"JʢPpE@@(e)濲tNwc@*!2g2eRk^/@@@@@  Pյkڌ3,==3)-    D (  jzۀ#_u۷\h)))֨Q#;餓RJo@@@@ Ŀb@r (aot'XVV>|تW}%>l;M6_|5z)_o@E޽{mԩrࡸ[jez}ʕs-̙3͛g;vx`iӦֵkWz//<"1 /; RXΝ:bkܸqu     @xPX @T*)==fϞm\p3Znm*ҥKMɓ'[ڵmܸqYQ7W)pp饗zVxZliW]uU 7y{饗<0q 7XN<[BO?&Nh&L+C@@@@;(zG,^jԨƩj 4yP9(~*ZJ5-[3!nF4hPŔݱvZ@h;~_M&UfO<6mdӦMRT;wα~@@@@p/(#GСC~?". $PP$27{={뭷G6rHoneO̟?֯_]tN˩1N :>{7Ew@@@@ ;s?j@<TIͯwmg,RHII ʈ(PJ3tѦleX(CO>9z[D.7C)/_nʼ`    W ;(I'd>ؿu{Bhu- z[⋶eկ~ez25޺u2O6l𬄹sz]mȐ!y ΃5oK;r)q_RM* #lb    WEx[Z@ͦп;oLk֬><PM;q;vgn䵞N8ꉡ2TTzC@@@p7(@J< 34eUtg϶?{TMܘ2e-^س!.|31Wn76I< (B    @Wd<#I#L%KlT VRΝM%vH?ɵ0Nj۷QFY:u}x?V|G1O+QpBMǏo*I@@@@@ ({r@@yf{GK| *TjUW^e=2 yo=zhk۶msA ?x @@@@ (yj@P*t!S&e ,ZY `۷ϟ͚5s9PW/l+Viiicǎ_z_53ΰaÆYff 222þ2%o&Ol 6\?a!@@@@0 Ѓ"GmF !M͛7_z b޽ֺukkԨ)P5 B۫_n=CqiӦv[&MQ=[oٽkݺuuz0Cھjժ7\J@@@@ #@ 6(=5jX>}VZ6c /, eO((ѿπPQ~}Lfַ͚o|J:EZli/=`[l,`ۦﱶ/}@@@@b T:vcNJl @I W_mrصkQRB *ԸqoK[nU Zmۂ`4{&6/++TJ w    @bH^      *:p     $8      @"     (8     JE     @bH^      *:\l,     !@"1#{     @Pp     $8      @"     \iIENDB`stargz-snapshotter-0.12.0/docs/images/estargz-verification.png000066400000000000000000005544551426301527400245440ustar00rootroot00000000000000PNG  IHDRvE;gAMA a cHRMz&u0`:pQ< pHYs""*YiTXtXML:com.adobe.xmp 1 L'Y@IDATx]U7(%$Hh!ޥX8cyDZ 8*mAzB$ ";ss}=gU޵:{}뷾_  H@$  H@$  H@$  H@$  !hG$  H@$  H@$  H@$  H@H$  H@$  H@$  H@$  H@$P;ݠy*_$  H@$  H@$  H@z +P,bqs, H@@WXd/J~uǓ$  H@$  H@$  H@%pI''Nlr$  H@[A$  H@$  H@$  H@$  H@$Pš*PH$  H@$  H@$  H@$  H@ kl$  H@$  H@$  H@$  H@B@aM(~$ H@$  H@$  H@$  H@$ wC"JSN$  H@$  H@$  H@CF^(z!K߄%  H@ k\r3,$  H@$  H@$  H@@g>S}=3. H@@wp+s' H@$  H@$  H@$  H@$0D x$  H@$  H@$  H@$  H@n k~̝$  H@$  H@$  H@$  H@PX3DMV$  H@$  H@$  H@$  H (1w$  H@$  H@$  H@$  H@CD@a7Y H@$  H@$  H@$  H@$ &I@$  H@$  H@$  H@$  5Cd%  H@$  H@$  H@$  H@šs' H@$  H@$  H@$  H@$0D x$  H@$  H@$  H@$  H@n k~̝$  H@$  H@$  H@$  H@PX3DMV$  H@$  H@$  H@$  H (1w$  H@$  H@$  H@$  H@CD@a7Y H@$  H@$  H@$  H@$ &I@$  H@$  H@$  H@$  5Cd%  H@$  H@$  H@$  H@šs' H@$  H@$  H@$  H@$0D x$  H@$  H@$  H@$  H@n k~̝$  H@$  H@$  H@$  H@PX3DMV$  H@$  H@$  H@$  H (1w$  H@$  H@$  H@$  H@CD@a7Y H@$  H@$  H@$  H@$ &I@$  H@$  H@$  H@$  5Cd%  H@$  H@$  H@$  H@šs' H@$  H@$  H@$  H@$0D x$  H@$  H@$  H@$  H@n k~̝$  H@$  H@$  H@$  H@PX3DMV$  H@$  H@$  H@$  H (1w$  H@$  H@$  H@$  H@CD@a7Y H@$  H@$  H@$  H@$ &I@$  H@$  H@$  H@$  5Cd%  H@$  H@$  H@$  H@šs' H@$  H@$  H@$  H@$0D9D隬$  H@$  2/o].w.h" rF/ H@$  H@$  H`PX3|ҒH@$  H@HBW^yx 5$  H@$ ({Lk<o~"AP^{w\bU8ӟYf]˕S.Q& ;ovI$  H@$  H@$ D$  H@>EЉ'X<} bԨQG>MP+?=.OO_q}s{MHzj(;} +_}QK,u„ jh/R+֍/%  H@$  H@$  H$  H@$0k?G-"[cl >K/U   :U=~Y{RۖC$  H@-[ ^Mf̘ F\ /7|s1eʔD|nr mf =T.o!i%0=ְnqƾVy$  H@$  /VE\V{nOT&WD|믿~q1$;J@`o|xӢW^?_lU+pY~6 خ~O,{챔UW]WR3LaMף9$  H@@ıJ+[mUq4^{bܹsѣG.b"fͪāem)fΜY7'UuBX{$  H@$ &o1X/20cuVVb{9sE',";vl +m/N=u@ݰmE]&Y\CotFW뭷ހ kFnc4Xx#׫qx6t5?x1xv>^ktO} H@$  tɓ']vYe5o1 qL>'T]wݴk 50 /IA1^L3HH;{n]t5yO#D`(#TCܸ4͛I@$  H@k6yaBq%en]z׀$ϸnKk_}4֚0aBVxla +ذhlstI'o8"lb\xx&.MaF a>BKדxwm E@aMo՗$  H@H4jԨb 6xa[;8 Vz0D[*bvmD(1 /ayk FG>7 `0wLjj_<0i,&'RKUtq=J,Oyy+(H&y͓O> LA,!~ 'ru)XcmU;^yfP䗭h1zҠH+e(3{nba\Q$  H@P`rgsx\ aQE>j7!lqoyJ8>C=T<}ƄsOr5_yi؞R?}Wjn/i9 @xk&aFPQ+ @r!$<4BS>Ǹo;w6*/z'\< )0SL);bNB9G |'# ">XZ@`r뭷GqD_e]wCSwޚ>jN;프* \"_Zv;{bgvWik]zUv^{'_$  H@$ `#f\sʹ".6 ,BɯB{0vdaF|O^ore 5I7E>!@ (cNU޷FԬ--CG@aб7e H@$  t?>-B(oNܐL" @x xAqYg%K3q̈́eU}Hy]uap` dX+0hJ~ŦnڔP'ؔ2,_3Ov9眔wҮ~֬YD.{Ҋ>\ ~p{~{;/g8$6<W;AxƈY[FEsOy8)u>W.Vvc: H@$  Hg:Y'O.⊔o+ŋ[4(}blg6Ŀ-}̜93 dr* Q^xGC( 7ܐ>Q0ah6mZqg"VE|D='|rqǦmU^_I/7R>R_?ϒx)\(` k|\G}\|IrG}W-Oӯ>|/AiOGԄvJ0S9Ї`0H@$  H@l;D+Ƹ1%cQ3b|n N<x̊Wn~:bRkMcUƁc(_iq5c{n]c5C=yCla#/2H@$  H@,6`dgـfYg~y[hjqՑQGU|LQ^hR+oˋTtC|IV~Fɵ?|N> H"K`xLaOK# H@$ fuM"a *p@K7A+mݶ("k :GRH&ʃQ1j~iU ~veVh6$XAA@m:8]tQ**^宻מ<}o#DrJEXՇ_җ*z0A+g" nlGyd2O;x կ~VꑺGۯj}ҲN;T|UE:PF}SַUib'pBH@$  H@flb֐Whw}ְ88YH":eΫ&qlxIGڹi5>2kfxԣ$  H@@ fz+VmZ;,~-.j:E'`-6B |VՀXAIx;+ʁvAz> sϸģ$  H@$ aK`M6IFg? fͪEFzO~Va%  T'S H@$  HG {a\Պh$7tSk{L_`b'Y#^My# aVYǻ*2s+%WJ1\С7] V[m=h=#-m @lD[ mm $  H@$Wo),v=0d1CqVն«,%5oR#Ƒ1.Ǘ6*lW9nE5NL4#-XtS>ókKn6-v7!/;P>KF7A'/`ĽRʯN뛹vB~vuo`!.A)kENK5ѲS$  H@=H-V_}+g.Ν[WP{I[AE^eP< ab_v5pa)< iҗ8G0ϡ\Ib[F{ƫSteX1mM74CPe N{We~y<y>lVx(J}`g"(3[( ؟p*o^?횺n}70αq@a>k>)rĽ<`s/NUcV[m$ qӟꪉ}U+D蟸6r^IEzB'm6Mq(+[ӮiG<"-?)-]7nEJWN};ҽ,߁Fvo ehO^59 K@$  H``p[7xcEyja~ 7T1(aRɃc!\Xà -ƌe  i-!75Ƞbk0Dcme{1ƻ믿9i$  H@$ ^#&D?Ƣ$0bKV&mzb"Z>"dN;Txi,<?AZ8׿$a<}EIc=LnG?*$!ͽ+ye<wղp&aO}*q|;IvMQMX,퉉+hcyS-3cNvxjG5efPv#,&Lв(6C7]wUY'}7 hϵ"odCQ<ͤ ĩVM}Q>G[BcW_}uo NH?lw!$OsK㳓O>6mZ迎?7?tIɋ7NK|嗧߆jyF|я~4ܪ "oS-8RZZā8k]v)[Uy3viI4K?jD yއ~~};MLU{*4f9`klƖxK/M<6cI6q=; >zctIpF|5kV]駟 ;cS,Y;<#&)O$`"Ϝ "\E=`^\{!X|d 1?䱣~gqF$=V;$~hSXN9t!,iV7{ku|+H=$8j$'oNa"n>痿ev[ {Nؙ\o 8uNj~4 GC=Eխ0Z=u]WW$7F'o۩]Uo&p_ܧr_Dn=K_RO~2ِM3E&) hU09&mIo^<ex4k-";M2n08<i:2Y͸= p1g8> l5&S4fK0m:I\-fMYp('b!b 9OG%, 6^ خX/6|Ԗ('l6" ^:ujk w瞉g&Vi"=GD!&~Ǎ^u6Ȋ2} pߵ*2 cƌr/0kmYs1Q-= ObhV]v sIDC[m~M7YY2i k(3ʀ #$TVp.bc+d+{t> ZL$  H@@`Q =cIB<2QYmRk"01v͵& 0nc6lX:J@;2>P_7bV92x` ӟtk9}4k3ΎIrC9?$e_' m(afvF0(B[}\" # T@m/ |hK@49/ŞW 2 /tl`B$rTԑ]v٥y睓'Oh+Ie׶z%&e/f_A^C\p o7 ָF/( 'Ĺ`x3Lb:sqWsΩoyK 7"b岰jϜuxRU9 &r+0d+||xiBLb CMb1yADF؅X0O|"j0]ziΈ7kćhSTԲ 9{[ЮpG}7KDnzI\oGQ5DƾZ^>E_/5eEk-v !L'Us ?SF׿ާ!^W: x6ޖW)X" ,}m/@,~}<|r M~n|5Vo/N;$&Ũ5A£$  H@$P!U RC FX% 2{竿11~V VVJn„ Kjj ܠ׶_Ήo0btb`Ԥ? qUՇXB`@c5 / H@$  H`haM7M1Qrl+{S0!Y-2aCC{dqrlxE0L"`B2P=-jjkw5[jx1񎨠lY"`D^`3`!'x@ ZhǴa.>[/3=D5yx!ڽ-Kqs-bD1D5e?gQ׈1Lz#ndyˏ0i5=I޷Vri5|Rgz1xaX,9gwG=糭S pЇ>T`뭗^[zƴc9vGD9{YOUŵU69qK@$  H@l锯`01 c[2ŀj سR/>CŴB& Yi&^ʃfniو TV<gp݋p+_C°Ky4= H@$  H@&QIF"!#cu{&s8ldz82'\ |.sO7[/nӬ%o/Gva"pbfB7_fe#<2وr{Dް}' kZv\=sZZ"6 )}qog ^ƾ^^#ܮdJjyG/ۊA|H^RZW ͫxZ[UHbiER-N~6f̘TF')x^@:il>h{-8jGn$ƾ͞0'e26ERzy`HqBh;>q6<l5]nF}R/m:&,%,M.hT^Ɛ aBѵ=<䓲p8v5uDz^xjŋI/ro | 7,¶n&.W/V Wn97 6F7 `n%Q|U~}2i;C;ʿGD52+Gˆwn=~)x_s{TX_^/ H@$ aL"\I@ { P11l@Q`@c`8Ř3sT,1NM+Ɣ 0R!ޢ.kdeI>xC040m H@$  H@!vPl瓋Tؒy6:Yh8i-3^v13c;j&< ,ivB u5x[m eQ.Sg~>ۨbX)?vghHZ;V>E]erPOtñr糚g9 <7ih;;5 B7n\z(b5D3xUaF97 H^{mqg.4Ao?EXx%jXɓ'(aсU;4SxCE!B(S+`@D PTr~h#"n$  H@:E.ۘ o,z={v[t/w<[lYm!_t/!<碉JU&|AZ|.Ѱzmpm7ӕNK[G9'~,BճEF%6նv:, k p,qK@$  H@A|1 &$ Q"Ǔ fW䐇z ѣGЇҖTVD`pM7M}ܖVK?#qq^#ip Q>U>ueka϶H{l{'.0W'q=XW^eXW rCȍ0iSlu'a 0nŖ[nY8p]$  H@$  Vb,Ɩxɷb؋I|K=4k@6V kBo%5(XǶy}䣎:0?Ri+l=˿cO>d@M5Ex$  H@$0x@0nܸͥl#jXs5߿"i6 1n18c^{i"zRK22B$%lA'F_4/""8ad 1c٩ Hbm'& K>1`D0@<0h% Ρ~>bw.MybW}(#uO dIZx[=aa&ꏸi 8zdEoXVyZN8*nЩ9vu/ H@$  Hk 53Fso2.8 e1֫X, 1ML--f@Y3n{gцFms36GO"Wݗ_~9- kkY]xfwꙴ[{MYXCevfCx7Î;p1~$g}sk1& /1 f餃8C)wFw䏁 ċk_ZJ/W;STEBc 1&:SKvXq$LEBPj6SWW0;Ҁ"'I1` #⦍Rɀ<{Vk~ a !j^,$  H@$mw%tٕO&4a1&{mU-xY*c@[Κ5+";͚U?pnS,i4Y3"; p!N9s3fhe!`;vln;vfk&|3nHQ\|aKl2kNY0Gq#Қ2eJӶ*7+1Fީg1l^ :o۰~#w$  H@Ft"nH-$K>j7`īQh'=; 2HD2^݅@0'A d  H@$  H 9sOB0p{4;Τ)JIƋ.(M"`|efoyR <Բ芺%&`rO~$ꁡ0baH<"dXguc,-v=""`x{[4ޮ Xtu~Ij'VE^3Ȱܧ!Dj$ATC P.`G;`1%&J7>^uU#9^ue>hvX~1{$  H@$  H@$  H@:N`ԨQ'L۷ o+81x)KD#ʍld=u#Nfhy~gXK?H87S_ԩSy= ⋓P^8⤓N*~$!]9Ҧs1u_:]_oWJ~#n< x<">Ah([3(oOO'/}#AN -r![6jy gxT+XX hw]]--^rJk0V59 K@$  H@$  H@$  H0av%Cxa]&cB c<4 E<0ybD;y{Ҷx\&g/71آIT&Ν[|Mb|pIv:͚b;nʽ-2l+[l$J`/9眊h O=K9묳iJbs!B,¤%\kju؎K[9xA–x!xu%~^^["/[i~xED6oyELlCN?'N81} o<}wܑDJx΁G[{ &p?OtA4[@u3o',~x@^ctBӳ>:!<}s @|vg>QﴙZAaM-2~. H@$  H@$  H@$ .! !![!=lAJa/|䙀p馛 <0!͋g&.d2 |2Ca5iֈgHǍpFXq駧ǎ&iS% < `{BpS#%jZٛoY9+Li{.O?t%po eo+/Ob;L7c<!CX2o޼%O[ɱn lwDWnoB0D!^^K: ?pF"xmAp;p˙u#v.<@Ve "-[l}΃C=E@lөVTUezjqcƌI4DW#D|F9(G $  H@$  H@$  H@RLf2qg8̳6$gͼg$.{;{wN{bC <+8|px㍕zb^"<׾uʨZF;GL{.yXj&3;ňN8%/C=w^@K㎩m$  H@$  H@$  H@@+b&jʋĊ:*y#꫋'x"mTM_GNVX LT6Leyޗ-_yx T|}8|4Z8 <]@)wL{+Njו)_3/Cq{7i-NckܻGZ<,ɓy  juqPvAH0oO2Fc D£ϐ& mgEuGmNᥩW΅ Ig^GU>N\q$?8L6Ҏj1kIY VM <5æ^|xd?]G?Jۜ-Duoʉ(-Րrp59EK@$  H@$  H@$  HK L<`{9sT&dܸqņn&:x`1eʔ4V*xa6L3HzL2̤< l͒n S*%[0Nh7.ࡃL3 KdMz,#Oृz>y1.HtᎠN4(^q/PפAq£瓷fJ/J+H0} e|_xA4r)g`C6zG.؆6x  &|7ΥktsM^FL4)ݷѮ\s͆F0{{ߛkR; W-`8,FjrQJĝ[&@;{G]~ni\ȍ@]F@aMUّ$0 `?~1mڴKbV[fmɓ'Ε_la}٧Xk⣞:j;c1a„J 4@swzk#ЪZ kѴO'M"6t~ +, 6馛~8 3ѭZaͰ/+O^<li|a曋Ŷn[3gvz$  &gΜD5dm&M4p Yetax_x衇br6W5\ by@jvz]PXӭ5c$ A^60 p)+m^pir=*c=bʿG?C# ]v%cvV "i.1A;Pհr &ȏ/B<U2ne>Tmt;O6ߞgǏ0(?5 yǒ=Nފ[>Ҿ=gv.Av+Ǝr^W~̝$?q\rILY*#??MU<o$.<&,@`}޼yg]veo5H(Z0!C+Y!w8͊3I_}vۥܣz@vn+P3IW7&M&No7#e@'J6~1uYmN )τ" ^IމVoef>`m=0(8]=sQM#;TzHME ngo֧`Kl3; {[r}*~M 5':iLOgNcǁ"i4<nbܹiA[e k̃$0b01Ŋxqd>W:aw+)9:d<25kVe1UVY` F* o1*dY89昮N "y1! kzv 1,.!5Xr⥒vNǨV[4귅ԋbSBG:^5B<@zf^P8∴-7SThW_Ǧ`k' } 0lp"g{'^{Owr c \ozn.>sAÅå&-$xA#g< Ly;:#Ml3 PciLʱI pG$]0B^uUw]X= ~ K/4@rozִNg 6j,Ri{P'"s*~ƌiU4 t 3lxFǛn)M34xW^x!y[z~E5|vA@@;wygen(9l>mڇI@zrc䡊I& úr-\@vJ+nk/rZMʪ<,Ə_ H@_iJ5l56Pc뮛k-&ϖ@83z%<b2+5$XW"tƶԮM>.D^{m并qwjdq„ chwx l?V^dݱ kQJ@0^rU\pA~VXlFio z[{6o gΜY)6+`+($ V دJ%<&lհ]Sd5qD H@_iJ5l6P/bBİ<#L0HWcԩi^x>_c5VXW`>%  H@oQu]88A4 d?b;`D{x⡭:&.apl5|[-S:O?O{6f@9=Ҡm`U"ihpgҟ|EZxPҋӾ;Zzmsmy>>m7Ckc@zSy_i~wF H@F5 6 FW>3~Y իzV+RaS%8y$ 1(Ny`2.lKF硈ɏ9?<#ifLzїҟ+uQ{oRʏ!>rAۡ ޹bk7Cnog~(CU>+Z=+ oF)VIIꖺ8uM{ Mŋr?ӿpáP q9Mm,vG;DT'-ioj|?i9FWKx6 ce{ͤ>uI}MܔoMam>ҧF _Qv37ý)FYXsl_Qno xϒe㞦/mgs}gi$ n#o'}3xNi$~Ac+y$;҈#5;szfwSN)J}{|#M7*c~+4iR}{IT,>kϑ;0~0جO=[n,Z(1쯀v.4㙷Z ]mvebʔ)yygx` yUWt'xγt:uEqW'|rK﫵cۮ8/ڪw}ػq7ϳUʪ$0 uӔ@C)<⒍fBwqb=htEu]N?oj9;8m6CԩSV+=g y70jG\D $®y3YC8{6U;zvP|N;!@kHI~fʁf< 57J?}yc҈@ 0'DF{if2&&rI a&07 B*]W18`oN,F0t)N;NNdP^OW73$L^E ]='ONa&1 r30ej>n wmf&guyO_(yQהcFU=;qoӆ7#/Kc{ӗc:OeoQV_ װ^yMJAqcԴ[gj`9\na0cdLӟE5%Ⱥv6:@_461_V~9vyek鳛 )IqqvZ-z7&ԣƒ.!!+AUCg-o)c-_ĥ 8#o|jmqb7-Wfm]yz0l2kv:u&O@l) e/{ #:uɑ"9&~x+ZYLX^ 9ښlYbλC QV˜hwÂs{Z yc&-&y?~Ok"t}@Ǣ+89GKC`E[}`"I!!'1_neޮ"/8!؝ 7 گ ~y ʰew[[_YsutTɁPٸ|5v댗`V 5׾]2eNȿh<ռʱxűD*l?ѶkZ-_rK^+++;$Uqװ&oSqۮ OPvHҗt^䢭$~>ʃ)vyғ4{6ʳ667r8Xs[/e5  z' X2'ul;O)k2,Z .c׹'aT n2{Ur} ,JR`׿C 2zr\ƅ:}'cI{~YemSk,Ur_zK6R1]42XƵȓl3mʎ!#ld\3Xɍ6(䑡UzհXehvUg|'m͙@-K9M^i^W~mCcvڇRe\weUBޗq)kKrElU]?W+L.{\ɢnUyOX|K˲UpۉmQr-A,~Aʬt#d ՞tkQ[g.jSƒky>vN}RL{mN;ck9usL+?zyuQ}jLtz eɵK.dhV1fkC_ͫmɾ@&m%ؤէʡ ɒtk9`u8}Ey~V,5W٦WWZ~yN;֏V`"mQOFhcs=+:MdqxIT:=,?-[_^k̽ qm(mEY)kV]MOȷ6o֫|9KQV~\ߵE.cw!!p6딲o|Zk`C 柰nuŁ_;@VscpC@Qڝ v5MQV/MB^f\p "C< T2E6=hhvy < is1Nݧ=s0W1~cj@mR>:0|jG%Z^~~oMkc7I$2_;o+y:͵9.Sem^ ʟ8Xs-Et`0hlXFg0vq fap391i_Fx^s[ʻaQd7[dLQ.+;jtY/Ġ`xe`l8c E')n}C^ScILR*))IyMz&˛o>{Pb2tښp]xzEX~NzY;0m[6Y2#0eh^"}cVJm]e~n27*0mBZ~]3MLz%+6I"oݺ;!O>\1M~q9NūMɥI_.Zoخe.`cI?oM2:#Y=6l7ֿw-9r&zzqeBY MWMm Vw>@I}J_s9*J #z\=5'H;\*I[v5*r-[v=%&ɹҮcѩj~Ǝ֜]mM0ã*?zAx!S~q^7K9X۲]O鉩3l"hpe^'7BץW!!pgk]͹2?gx2eTͯhZtt(Uc) _9ؽd\Y(Ykj+?glx36>o2?77a)ZHuw*>Ų&U>cc7{TOקl>/+XkVO"[1U̿Ȇyjk/aeG|7 ~gNIɼU۔\*O臕gmk#8_- qfsPY3!ӯ,u˗WI:r\y_^eUX 癨E:EfԗD)}%1y;~:b,Z9v;nzY%՛Nu:Ep+k;=!U_]ioƘ-q̰9P/ۻ%ozXl{h^|~l>s6bY |?ߘ0ڎі- OQQ6%9' ceW0a/+aKlyԧ>KZa.'^W mAQ\ٶ:>sXv%K+}':sS{.f桎ߵm-N8)HA|x35Wic4./l0s,+)w*~L4f;_^6S%[zֳ9 ;iO{`>]k#6W;cuSab+V?T8;M~< ?|85y]ܸ>3ϝYsL*ݾeW\!pB 09-vC\U"䱏} 9g,R_\1Y8df4<_Hè/rs~2&IDSa]3ԥ5 ]zW~Xݼ g"Us(5ԛMOZv[s*[,oǰwdC_p|%6@~N>I982eL&} dh5, //A]Qj>q(l'E,J"dcKk6d]I;r)ImR_ d,oBL^؝k_ &ڧ]<ӯ8L&aOП,HL)YܱpA6~ҧq.&c65T^o]khQoţJSq&r|Q#LCS8C|%c^_xcޤ8?gtzǵI Ey5د"E:"9wcyy^{zǶjJ,yk[-җ|ظjG}-#{q@xIu~%hKcl0d[N}f,]508w2*0P]SՑ}$}2ؤ'H2FvM/^pYWn:~y/-OOXW:/ҧ};ˣ_ĩ{@V}gŀ_Ew3|-kYb?#};gweOk*7?vM+#9:gkl1Ss??zhȍ ~ժn/O}Sj}Z[Ϫ]}Sߜ[W\dWlKm`vS{n,<|NM+p6ܕMGXЪD,RyxWmR)ngNŷh6=F`K[>׵D Ow%s,J+5 l w.wq@hyz,i^: }{^ُ{i+G^Zݫ_glu4q~'ֶ5-r;y\_eL.jw) Qx۩ƸuўzWմ+ .=iܫNpreO;3~׏ cesIXoed]iouKy̾֋x=Gi+GrS`+=@kq'gEN3hm5霾*ycSEu;ۛ 9֗cQ~S~c_q,R|Ǚ>@pJ^peXd̶-{{s^26ўț6<\{fc:;ӮJV׾jwT9cS(ټU5|*[]O|7$]w!!?&*[xYggEp_pgP^/zd+t,]_XtvXaRkXo7>>eyyFl1{g~"ޯ:ZZW];+#}?@k|GLekΖgpi&U(e;oZe@60?ͷl;Mn,sb8 'ޛ^H&ãhkI1 c?\TWII8(rvԘڿ- _H: ^P_%ڡݝK[Yjrvjkwʎu{ܼΣu8k>g^@9,@җ48-f1ģ^ҞG;ˆm'6WMb{GRLJ{[$HV//6՗z^TmvʶYn2v>&@k-,Hm/u.:^k=o `.(8?=zSt ~ \hox aU2^J]~Wwm;w PNWIl,3,@<$/ rxX,}fjؠBE27%ѣg<`*75ꜱwrɎm(=ԶعtQ?ص []@8kXcç$s~>6;>)8-~>ecOqu&| _8{[:tevM%m.}>cZM k66p~k?_0?ԗ:GmF_ױN v)_8j8ƢmC9 ^L[iW˿\G8㺫:G`&2JboQ;t9g+ͻ.g_ڤ~j[ߣLL*ХSvѨslp·G㪉<|_\ctGۿsR-}UzհK #wZ'}~A5J1T: =kkܲk~:S0ɢ+]}eQY<{$~G4镣f;zmۇߔ}QAMM=Jeۼif>>=>;UVy?O [s5ckqeu8ijko#? z/[i|+ {=Lm>aK{g}mW/|[=3~NSlP ^%(c.^7(W}^RVA4l:IK\* i,sOwc,Xƀi7/zыf{7y-X5G|[mZ5x=md~Uz͚ozӛViɵ5ٗ]{߁ZI!IY!pN[ÄSc59.گrM$Z+Uqccӗ!30~t8uWmk9'mx-ZH?w}W)<?W1&F u &.5) vG9G9/oqw:>ϝ_6I/X$ʭ&5)pB^lms;hǫ~&8JڍIyEwOj{'omsG mzN/ W|GF{Gg.L\D_%2{l1[E @<<ҹM7nۣa=&="KVMv c噃]е}<ۺNId2JX6 JYǮj16Zuş/ 0gG7ElhY!A/]O/m3YiEUԣӟ`V)t\S`ʚB B4(n8j{}x4İ﬍O}S#_`/%n`S+?vJ}ۭs-1;ύ^I6*yUcC`XM;väw39p 5 (2; qwI%(>8>_g ~m sN nu&T]Qzn Wo#Izm|.Vd0 5Vd6~[|TɄO"|;aގ/N}}Cv򱣁wR`eqk>9|O 33:lLA'^G^fNԏi^&1TJd]<|+gL=6ͲSW.k{Æiߏ3x G?:xԈET&+NN_K:a 11hLݾ](?IzE0$pg>VoGGq ; q场w9Gqv ie S&P8E㳠лn[cS`MqNvV7ߧCz`>ڛx[q^t^οc_٨}(fjy]#]k'D1; mM@mްh?.J7nY/FUϐp֧zֳs a{Wx_n['|}w.vUU6w~=[$r`NM'㈿袋@|&B?., mŦ˾N~U-k@x)=. ylmBWL{{r޶l,?]8o7~׻uNe+ ~^r T#@*w¸^NIGgS0XhaXz.n6.Fhy7l#˂jȮapRzym2ivujc =-ݱ6ռȀ}nX%{>~c C[-*UW? ^x$yL&q7M͋liL##mxSQw=:#C B413Ɯc Ƣy?% ͣN]c}<"wӝBO~N1%/yd~>rwX{zڈᵳ{ff~Gwt6~ <@kxDca@VrMwj6[s8 IGтhqJ&Uwz;)[k|>o;&Yv-]wi{]+I+IR-f[ȯZ'~W%u|EWlM98,[3ߣL[-*t% }CmxnկRyQ; h=Ǒؘk{r>yk4"};, $]~ 9~G@ B?y^yVG>2ravQt.UuXѬ\cR,6!{}dTym]wզA;˽ƒ~&׾%R̫ڷ:_-x^(C7 ocp$tE}zgU7z%޴CvZú&ܜ'X0pVy}#0.՛/X4{8 Soh>ͺ|Cyi/\mP|Lm? ~ݺۇ6bOwFlsy輱5wT)G L{'0ÄY;f_̘oCބ#0Tm]|" ]r۽ss 2$#o5 ~~iܯOX6G_N wĮSvlje[kʹ#s7}3799l2lp*|Mc,}]>LܑBy3kQrw>juawo/mvju{\/QI+NMz-Mӗn 3+}yo|;@ <o}[[_s&XPm͝|N~?s]۫m=szz9qo6ȌXoSe+W>nU5lKE mPNaSBT[}^]sT_W/6<8^gwUN6{!8I0a_K!@kliq4%Yp7qeNnm", d3j\tԲO)*TV9gc*e=cG>rۼ/}8ogǏc/læ}h#s.?X՗t~vw0N9&8wt%&Au[5- "dI av1q ߄K[Y8p@T}τm{N;yΐY.`2zj_Xe1O@kmo{,q:?,Zҗ4,&>c{ZwZdQBiB=>&܇MZc}T6lT6 VXVn̯8aLP Y$@NNcu~KGp‰kN0I~Ixorz׻nv]^<ݵ:SmuoEB`zG*~>m׵1;]D"yߗ3[]:?眤dw0##C4u$3Sps'u np^aѯwA]v٠C6*M|B B87j׉L|HME*~}cDmgElַ+b}^^ۘ\ c7qD3׮AU9D.{R{M뜩F5G`M~僚 z plL%K9߬r{9e:yg)! =DɓGJ d89c}@Aé$1\SaNbl~䧜 us*[E]tbXU}v$DF{Y{cwd2%aI_ض(s-}QLwwTV9&T.5y5FBuK :]ǝLmgoo"ַuկ~lL&8X`򪌭AXxXJ;jg!w餶m*Q,ܝdnP-&zt~v5vG[MG1XUF ݵk,0eaL!Y9u7eev kn eF7Zš~75uz2n.Q:d]O)*Ld[7l.딡{;&e]} z"/HFD&jǩ weӚ=}ycQ?y]zLʆd<$Eotn0.%cXtܼ`ͱY+7ce[K6_| @,v~n~1e ;qu>6/HK}έu~}n%ʵw4sФ8"'SHUsyǘaT_p`nΉ.EYU'Ø^a-Alj<kCy.KQ;JVf캇Hv+٩ѩvFrkf&dyЃ4,Wm\o_U:OesW\!!;N5nb  `}?2.Y6G|l{z/_e7n~C|lO6@˞ۢmAo~UǚEi;Nm93V) :1BcǮ-5u&<{^6L6):pԻˣM&Z~2vwQIc8/0V,ڵ ڟŤJ(ccq訓[!-c B&tNZ{ZtBMwԝ']{]&ril9z~S̍⵶nurLxEo ]bc.3 ĻnƘcydf>s>Dyȷ M%j: .rۼ97UR~h7Ki7byo{WUw/=CA7+Ey jX)bz$1eF>݇1҇]j_2Nfj@b\el~%)K@B֏/|9{Q~wgѕnz+^1GaC-6 [Kd0V;mzwNotݬ_7/+ڱ!{,?Nʴٵv%psH8c5Օܜft5o-xWrv^_WkmD|vq,H{ӛ4tSLpwmYj;A:|]G;*P~_nN_cL-#vX'G]rP-iw{p׻u ;xB?Y`pGjɉo3wc%ȘEtɧ%\2,bϘcwI@ZM,{;iJ}`mC.B'?tCc &KΏquͩ&r&59ssW oҘ {mGL[%㟻n{W[y?iK}|qFI v+k[>w~OaaXƫ^Lɳ?Fz=/؆~o1v e\lS5V}{g;v[X R60`k,;b -'Mr=v|"X_z]7\/vèv'_16omE?O %Veێ-yTRwzڜh{ϮowٞV$mUԕ=LVmQ_2[lV oc}u.&{,!%/yY[Qy ;fߒE2F>y lً.h[d|(?0싲LRa|/~V7`!7} h0\G |7v= 3/z~5+>A}<18{ܡiu|C$H`Ij5Ie>J}5;(k63n ȋv(T2H3R e0zD-s|(Ǻ^㺒1]ݥwF%C$a`_[/?V~<_0bh6ҹ&C";j4 Qk >%8ŋEFigAMi%QdmAȝȍ +QZ&U&dL:+u'm@ǒMb$:ϛCȘmQuwczvX8z\PصbѶd՘L{l֡9w'31η}&]"} icMzǏq!bnJTw^}[ ]"%Ǫ}AF޻Է]-c~R2a@<}~j{rZAqIXWmk.s5x Zpu}ƀ1禲8FP*ߐU^Ug%n(O9Y1s具>Z} a>,}.[]VjL_o;wڨgP1/ߏɸ1K60?2f'k;rSu٥ l$ɃLyhs[;s-+}[< Ϣ1Z_i@`Q7gJ,CQú6 C=JS5*r"Ctֶզ™P\j*DN]+&_ YK^^zd^&tD*)Y2o9XiQY'v^_m_k{P ;٦I¸iW\٭\S[5a/@a tE6Nͳoq{kHاlb2.}_ӕv0Io͂ z]GGBQsԸήc.^(_eY.}seOd' 6 {kY~?.ʵK0} ^0ة|le1{mg/{1yW.ְ#XX?::ԶA5"}ڕ62ק9'}gڤgY:>oOK @Oȵ&1۪/Kk'O"֜VKC8dmf5T<_-09 m ED`SAַu0,`zc0w&cNcFRΈLdѱ$F6ds ^ SYN+F&#۵q&3Lv,U>ySʫm\&1&a,) I.&F!67TF >C]3+/i~oz#q%0_sݒuf| (<[?.fkvHXG5YSۺi]9'uしXcRdXWOy-Jy6o]NUtɘrPc}b6:*$ӫdK=}y1ƬJw;v5)vTv&v,壟ulb-ڵ d5-UzmhI,ޕ]mMk!">%8~祽8zFPtq΢1mᙃ@UaۂUflk{lC쑱\ޠKW }Oj^['K/0PLغzK;)̼ k|Q9˭v°aQ^oʫtd>U}1<B R!/l;G`sqA|pgC1W~d 5v|oЍWՇ[7R8)K'5/r`o}2EI3|ٙlN wz[dP:D -$\__Y%aնعSrPלDK.ǒG1%1E^/rz ߣכWYpb_#?kjN xm&ڋjGM+C>h1_KSIGƴW2n&XI][=&@̿fGƑ)'?y89ȓ-|5'=-pYz`sJ>0ܚM,?N'=iZ'']_ehcQεsu[mfY?(kYb븡X>/iO{ڐ1-Ǔ/XbQf+mwAn:/;R7?Jݞg .Ky8~&Ob[Sy0]UyjZ=vLrK@MQnTpݫMRrOMnQ]t=NmN*ȤقKdw6 kw9Ydtf^MNq;JY *ݡ^pmk+8ⰉN3VsZ"lykvFps޻зvѾ,sT"c([Ej#!w{&j']T ٨&6<ؾ£-Co[]9;eAb^g8r] ]Ot:S e(zy†CИ'8-%vXIcҶ;~01pd}Vox93q*6Oi'*Iېs});d GKݙ?oB`UEtgɄ kL[|j̖]sw~U\ƒ6"\pp: Vcy{ رxsUPM-ݷ6K98,)8sgvä8Y\13ݸ6olrmMAe`9蝻'Pc6edzw]2; kU~ՎMSpޢsa,O}H,sd"oNOϢE a?iϕl6n2.:uS[nȞ뙋>q۶9WC|&;<ۜ{jb <)O@nʪo*? $O/6c09 {ԜW9~^Nktr$>H`>bk0qs)22ei`7Vc\ZA\8o٠n07ˠ< QP7"(ձxhen;vmWĉї4^~=L2EַW`a1hk6?+/mK;f(s)<N^I#EBw sÜ3yi' D&ؙ'LFwXx7dsIzh[0)IHrɳqb,U;GY\EXL;X7NWL?lP˹j?}\}{^+9&f ȋ,rMi}+L+888w3Oy3~cөeF۶+î&c#eHȫq}?YwwW2_'ȸ2bҮWrgTb[9pǑJv0->"F/5:xk5v+ Z}[y'b?L[ [@٭D~-P ;L= .uK-|`v;,ԏ\WLΝÖmU*ڞ@>\OYl@L|+T^2z/-K,Xu[v91Oxm"Ӹ;;,PW)D9G@C*x8=GֽOt&I~wsyc&悃`e6uMcߠسllʾ|z7G&WͳƢz]J6+j/vY2bjoJvzm[$;^i6yUsS(h^CDv6T?cSrj.>v"-h1,J*8% z7+o)WqTc1dܷ&cdmD0$^ٜrW{w[uwc >ddN;˞ XyT>{ϑ땜;Nk_y'9q̢$jg巀F]K=꽮IkO{vk-*oxT+5/*{9\Lښ/etǸ*;=> +)DuX(Gq"0U-*줣Hk<ǼV "pϏLxEIg 5̚_|ŻV[NΑۉ#!t,zb9z3Mۋcbg5G 2ǞUy2Yh]6$>30-ܽ1pn[uU+їM9TMV*g]2q)'kWzloj|g^ל;<9[ck)~>Χ:}bhlLѳ+k;f#[ +I?ܕa}I/[&S+ƶ"ȮЇ Q`̵V1'^zUU! XrI!Kv}N 'CI1ѵI!M$;Mrn$pljnV0؝|u:9>p| s+8Vs]kI?럓k k.?#=CrʝGW\I]9@8= m$Nrw^qp{%Pv=X_y ܭN|3~ɂa_ܧqv^-X {`J]^{tl5Qd џ!(tYc.% 9 -RmvB%v( ]Je; VmUl=<Ў^Lkc6˸nC> A1zg5lJPȼ;hԎJt]g{&r~Ts}Lì۸{v!!pߋɲC-^_p#]kC6@l;n2o$׍6=![ujsB B B B JY,"X8^Zd!plW 2fJiXQ}Pq<׾!+2wI%Ov²#K%ARNPA)}@u!!p <v+ _if("y8nvg?{Vf>\首CS$f !!!G;g/%`gwp6|8WNmY=ǫMdj;M=V=m-*-ۭF@%5g AuݴW!sB B` V8;wڧf!'sqzy mk+:@kNg!!!!0I|_(m/_۾6t~ @JO$o>F`ws^Nv!حbJչ|m;^lg97`=o7\D`M|s\@~kE]tN@G?ᱤƔ8NX9,;m;gsm*!!!!EYʙ9xGA%@:W֡sV!`'/BHvˇ[n| }Mo۳|##ۅ p1.ˣs۱׾q]0I X\i} ,@蕈 \W,Pۥg>.ٟ9BRp,Tx s/9lo|ג2bvSֵ5<IrWwpB&.6v`&A5fwg“B N;8~_^]l}|XPE6=Fn!-rv׽5-n;{ JC B D>F~y W:0_@ {C5`v>?M^s{W}jpXs8~9;B B B B B B B`&7q CY=ɢ5x__yTR"`WioxGTT*rsmXdITr9.B N']L 89.™>'d7$i ʩc@@@@@@"& fw7=a Yĝv]GGw6^t^~ Ozc/'@@@@@@@@@@@@@@@"y":-B B B B B B B B B B B B B B B BȎ5Ovůzիnw۝J!!!!{I/_n׸5fw&jR@@@@& 9MGuկ>FJ@@@f}s׮غkˉ!!!!!!!!!!QyFq&}!}i#B B B B B B B B B B B B B B B B`XQ,B B B B B B B B B B B B B B B B`_$f_Z2(lg2  ٗL=B B B B B B B B B B B B B B B B 6J 5řB B B B B B B B B B B B B B B B @k%SH`Fq&}!}i#B B B B B B B B B B B B B B B B`XQ,B B B B B B B B B B B B B B B B`_$f_Z2(-?.[tH~ h\~wyrF@@@@)&`!!!"p$m]#@@@@@@@@@@@@gX@@ȣMS H` &#k(B B B B B B B B B B B B B B B B`X"B B B B B B B B B B B B B B B B`\Mzիnw_R8bwg_~W51;>?'@@@@@@UR@@@U~tI1.l>wmnz>?'@@@@@@@@@@l@9)B B B B B B B B B B B B B B B B`$f3U ler # ٣LUB B B B B B B B B B B B B B B B 6G 5cB B B B B B B B B B B B B B B B @k1SH`X&="=jT%B B B B B B B B B B B B B B B B`sX9)B B B B B B B B B B B B B B B B`$f3U ler # ٣LUB B B B B B B B B B B B B B B B 6G 5cB B B B B B B B B B B B B B B B @k1SH`X&="=jT%B B B B B B B B B B B B B B B B`sX9)B B B B B B B B B B B B B B B B`$f3U ler # ٣LUB B B B B B B B B B B B B B B B 6G 5cB B B B B B B B B B B B B B B B @k1SH`X&="=jT%B B B B B B B B B B B B B B B B`sX9)B B B B B B B B B B B B B B B B`$f3U ler # ٣LUB B B B B B B B B B B B B B B B 6Gj*9@@@@@@~~OOήvͮzܣ_ڬKG?7?fB B B B B B B B` $f' 8jg/2w3ٿۿ|wk]kv]ov^wv_9z!+?so|c]u3эn4])* o}[!PMNTS!!!!!!{H 5{بR@@@@@J#.]r%/CЀ+]לw=O7'>ً_׾Ur-oy3:@m73 ,@ !@41(P+u]E}n_^؁j:$  H@$  šq* H@$  H@d>p}]ڵkĉT%B`Ϟ=FI(F>T4iyJ?~㏫ k`N6M6 [BƠ &aÆ55ے$  H@$  y k$  H@$  |aݺua1[D"BJp1+ħ~`$ "@yK= QFo1|$  H@$0 (j$  H@$  H!w+Vj(mC٧)S38#uQ1#k3Jx Il5jܗRdb" & H@$  H@@s(iG[$  H@$ .%W_۷UVUzo}+̚5+_u8c*9r7d$  "^E#/y*Q*> _-QnϚ$  H@$   k8K H@$  H@A`o34 .!7Gyd:w H@B4Ç0qM{lٲcǎ3g+r`$  H@$ 8 $  H@$  H`H/b6G&N$ !@Ɣ9sĽ;SgmF&ڵkիc-&y睧ɜmN$  H@@'PXɣo$  H@$ry{܇ROk4 H@H!Ô)S{Fk$  H@$  4š5 H@$  H@2dɅ57AK~tYWtWb(Og!6vW$  H@Nm7$  H@$  t*|'֐F$  H@$  H@$`$  H@$  _vȠ8%7+cJC p@~Z_uOx/G U C;[3ʩ)g_|OF|=ú+|S9l8 G!NpclRF5҇ >,ޏ$At*u#X55+q?$7K3Q)HmVjpO*ߌyWlBifZU39Lrfԯ4~)3r, H@$  H@C@1ts H@$  H@(A+v>)M}ݰu/?0qp駇;];ݻ믿v#I uQᤓN #G ÇGyd^{h!J2g}6Mcƌ)>(;7|3{^1FFe۶mN8cg!Aq~?3¸qآֽw|b 3>p#衇FVG=kշt[[o_Ia\q ݫ S?0o7>#HbYN8!{o>yz7zO#(4|}-_yc 1F\4bEIAam(_o->KY_>ä/\f0؟kd:cxg1aoeG6,` |g?6FƼi}sNW\bn%9$ 'cK?XzޜN. H@$  H@C@aMJ@$  H@HH?#lڴ)Xw_XlY ,̝;7?\L?|X|yl?a*'cdž  {z@__b zLuԩ⎢~:<1إDӅ^>@p:VcW7|sB0 ?Obwݺu 3U6oƹ?>ÜKX7"@,7'x"ދ`y LKa6k֬x/ƫ7kC|@PmŊj!Bvy>Zծ1D 16lb{XOgs3<#[nb~g߸a 5oG馛xl;ǜbn=3w̍1L1}⹵ OfϞ]x-[G}4\2 IX'g̘I_j6UV9>YvoDSvacyϟ s6/B`Tx.ۿ[`Ä A H@$  H@N ksZ$  H@@ H ^F'ŋcZ6e߆z*fv^:g1Zp'W2k˾p%hK";cƘj>"N32gg0KiíUcMf Ϋf;< R#BZwC"L*q|0,Y$ޯ=8\"0E]MVUUz$q;j"~ˍ==P\1Xj 'LȦO<׽s/\eGZŴ>9YKTS8lٗ5 H@$  H@\ 7$  H@BȆl ղBT%8 OeG`&{r1HP&_*y{)CS,5EPd H DC9ڢ}% p`wpꩧ}h - $# sW0Âq&ʚ5k" ^1"@(@?08õd੗%#wx"Ƙ1^h; Kps=q*Ro!P@11^~Z0dPA\EVI?R)*'8'QDa`oFezED6ZcR#ms>ْnq=:FԱt ,6b2oS01ؿZm=jw2gHK_Ӕrc2F8 {c{#J1L= 0p2~qƚ~q=s1H~m>gms^25ϲ |4 H@$  H@\ 7$  H@{, p@,JD(_R &Q Pғ'O{nK@!N &K  YH^}x`ڵk+BwS -чd)UE$!笳 3gΌO ?@ 9;G8D501bD9rdd '@Ёl= Jvi1/ƒL)R7V"E2XP!l0oɸ" c_^լ _Ҽe3-Z18sX?V 1x288Nx0=\!"bJW1Ɗk>VU7k;\`W  Ykd}CÜ "&QCĆ rHub`đN}b(Ÿ`hJꑽ(Ew/g߃k/퍈 !]{X~`\/X|?c9,91iWs9q߈'dS홓G H@$  H@RW.$  H@$wd# ~5r:/ G\n2 xMF0_58ybFHC` 5&Ayk.J3VX2$TΩ2(WB !h窫坊B _:efϞ'0%!ro$ "=e]sJ}ߎ|Y@7$'Mwgs /U'E#C89spӦMW2DXmʈ`ɋl._|qr 6O c<!t@,_*ocbx5r+0@yßf sO5jToj.2,؛!_q<v$~o؇ˬ\2rĉcv/c _`_2W=sjYA+8/oqԩլ{q}i`gf dE~IX}(]$  H@$  . kx H@$  H@N+BIo@d) ܸLcbdS __IJ!sD%#N 2` BhG0R D; *&HL SB$2$J 0^Bfdd՛WFƉW-+ eo,?F$7V[d@$  H@$  t'}e;$  H@$!#/!A_2OP@,AFud`نd 3C2Pb&?j ؂d&H)@8Q%gYd!&! K `"Xߛxj͐5E+WHyj@L6Y]eX9hx2\\7jԨ(xah1,"7sG\ao-jF)]vU~nȾXm^p/ !z';f}T`A$  H@9QrtZ$  H@he|2ݷ"d苑Ճl yk(ׂ ^i=7'B/+޹>3~a<0\F/cM:tƠp2D" x. H[ ȳI)~'@ ÊWYcn2_ZkX1_ǰjdN{T{P^q|0 X eO|Dglb)V4Ə 0 9sup,C`y)u'Zv'XCo(b j=gؿ$3'͋Z{#c1>.?>%  H@$  H@]:$  H@$ !L=#A#8\} 0lA2je̮]6q믿^9+ 5U8'0w(enݺ{5 !ae2}3`z@߿gHEq'^EC87a2VZ 6?|8ìYu 6QmL1X& kXg#eҵ2P%ҹ}}g1L0{u!#ؑ9cy-ZEGn_h[y״>^xpYgU"-W& H@$  H@(C@aMJ# H@$  <+Vv.s/_ާX^`Ah}Ѧg.iFWt x#KF^2DA;l EY5o_d@\_iB&%KH&ODBNЧbDŽ  Ap,gnݺ1<{$œZ"tF1\n5^f#Fts9SOEl\'^~jժJ ¤Ze5kms<<@,EX\SL,:y} , H@$  H@K@a$  H@@ٳ#<5%'N ӦM@X%QB J%14Mn~_;#ZFtw^H))^Ͱjc3\wuQ԰lٲm۶9) Aׇ{␽ :YDX8?Әea Ȣu b\8E+}a \iՄ@Cͼg_BTtgM6E .Z(rMcD* Y+b#8"޳ވkcҥ^>(5gΜ(/;U_$  H@$ M@a{/ H@$  %íZ W .y_ "ؤ/F !cَ)Q/%K#i/vK [3!6.0iҤO5kĬ,I"dq![ " 8t! kzP qM 䠯P26f* R|OلұNx-dxxlGgrJ[lQrKs'PQz{uodN0ވK.|=\Cƍõ^N?$  H@$  tb7$  H@%@b %3:Rӧw5CHR,B0ncQSfAN6Vr%Mr#ICye>4CCd^3fLJx`QAOP֎8 2m91/}Ƹ.?G20Z4Jѿ>,jf A5EDμ@06g]"n.X Vjnm{#6:;v CD"!CǼy† C=}FG(;^UE忓ى+ ^~b"5}Q,@g"Ahl&A;__C@C˗xev>(HJ"'!JٿCڨV.^A>?>/J&]qAQ) Rzp6mZ&!"g5i>;p]wX ClSf}g@{K@$  H@@{ (i/o& H@$  t ] jJ2 NXʩQ`iYwo \/:we&x`d`>5c|:QG.W6I+%(9?! 2MU7ï^{-=N-#LBIC[k8HNW+C[7(eΝ_0)w1~U'3)# Yƍ3[ȴHL57jjGrXܟ5`.U!gA?ǁ$  H@$ OMw$  H@$QR֚J_ Ӄ%[MX@o2/R cμ F2D5/b ^cN[}/˓>"M2'e bTR(] ;c \Z]3<lRJ׭[z+ab.f<)&K{֭ fI*jFNm;B5$ =;D_+V7;|x%DxOaS7 佑bz^.BW#l>"2v ͹ jQT`T3zH- M$  H@M=4K H@$  H 5>hؾ}{x;ز̙3Ò%K ʕ+c`ΊYHrD`gqQ멧K QUg qRȢdB Ⱦ1Z"Hyti}0'X߹*?v/ C2b}$1b\g H@$  H@^ kw\$  H@h 4g\9!@0> hds gώk:?s'I'Ҍ5/5|1" 7b;vDғO>c?E _" ^IXC&[E|JcTXp{}v8W1Ebyj#B\"DIX1*dAD;I5 `푥&W^e]g^νw!b"K/}[b}bvAi+Rnmy XbpoM$  H@5g,$  H@$"f0fICGaͶmb/FFw1M@@9N@a֭1cE L A+5EP /͛7*o=P {L4K/QRIںlUs9A6mA{' G. @^K. x?c^!aސRW'1>(@`*#3 E(DfrE k5 !#1 U:/^K2ലF%*1C8|k1" p3Έb (s a,_q){aIH-.S cd`z NbCP:19@g}~3q}HVa.1n8On?W??L. H@$  H@Oul$  H@$ HӲ ljG?Q̄@*FF<*c` i,V?` *cdDWL;'s(3㼪|D)Y25~ $CL«%ƥ^;a#{KQ Y հ!PCԈ!Jo#WCDu]QD!DSL wb b?{0/qV= Ƒ9LV1[],D?3xwVD'p 2[F5Q ׳#3!!;zRAyT1Gɠmh2+!^IB%  H@$  H ({ H@$  HM:%k A;2PfSjQ?@6g1@MiUz XAXdCMGy Ο??w,Y3u)[dD_2V5!zMUx~o߻wƅceqDž: } ~bL!ʹ b Z,p„ agn 6K@Oq!tܚiEgD dޔzB9c3w#/x=O9;dMAp.c#Ҁ=3$]g/7H\3nܸ*14k_8.^"y/qC^\HLH% Y0)e,cՌ>e$j{9F2y1YO6yC2#![0*E,& H@$  H@š5F$  H@hf z5-}YftZ2L6-AP`w*/2v EqAA@ AX=y@ |" K\znݺL(d †7  cu\Ǻwcfa1{`z3]w]:ujxcicC0Ƙ @ܡlxoj8dy饗b8G0APk[aXgd ްbRS!{챘( zeqǤ?BƋhH2{cNoMX0pB{{i2f)w=a cf?=w}Q ?]1I 0K+D|m 1 ”7&. eg~zgj?uWXIC`ž:~F(cP7mQH@$  H@@gs?LJ$  H@@@aÆ1E#`_mͿoO~/0$bf"){ \܋& Jm |E!F(r#Fih@qS}g @ٳ'0Ò1`1&m*A> Dm.R8"s1Dq 'ϯv2Ȏs-TD#,E7xc_6Zq8clv0תeJLqLn-fa07tSC|ᑽ!"\eyc/'WD^N{#>Rg}7q=~׻I@$  H@Ϯ?. H@$  Hc&p6PYk:1[M[i\F@n3&!h^_;w r^1di!>#B@PVЊ~"@ƫfG_qDV6myR[(Hc q ""^e]|MdIe]x善nz7[dNXj_>xҥK͛u(ER$  H@$  H`0ѵo$  H@@KYknֺAf8`fP H`زeK {|ɖ* G3PiϞ=׍7ŋ۷S:* PO/bXbE$Yj6mڴ* H@$  H@| 1G$  H@@ڵkAVGf͚fΜ٪[خ$ #@#W_};Ib&A?D… ç~{He۶m!9!!Cƹq,'2`nN8pWK/2P# H@$  H@š>O$  H@h d/)_7۸lj'afӵ= H kq%J1cƄSN9%~&K.%( qpW=yN gs d$  H@$  H@Cš0Q$  H@h C=4L>=L0%Yk?[⿍J@V}QbsL5vnRFy嗇O?=P Q[o>X" (/b4\;nܸpgr[)M˝$  H@$  A$  H@$ $ʬ5f9@{ û<(9S=t]xyQ@fΝ=5d/c8 kx"Z" fOqc H@$  H@?5gh $  H@&Ъ5f“ʮK@ /o#92ւWI@$  H@oS H@$  H@'g/afiEې$  H@$  H@$ k$  H@$ !N Z fiEې$  H@$  H@$ k$  H@$ !NYkV3'ݗ$  H@$  H@:š$  H@@֘[g~K@$  H@$  H@š8I$  H@h;fd1[MۇJ@$  H@$  H@K@aM]<( H@$  HgϞ_>8zaksv_o|o~5g?8CcP2~ ts~߇͛7;vy0z0nܸo}ٷ= H@$  H@$0(Rmg%  H@@w’%K?'xbt0_}UxO?>ӰN:)uY1?@[ >X~}ؾ}{!8)L5s_k֬ v k'gΜFQk_X? a-nܸ1&Ᏸ ;>iҤp'<{:PE!ys=5ӧOwĉw$  H@$  H@šA4vE$  &!|ͰlٲoÆ Ozd%AHq-Da ING` gS/_V\)[J"@fɓ'Gn_~9~᭷ފzᨣ+5eAvP;w bFDVyk6fZIObf#!P=]!C{pq;$  H@$  H@šA7vH$ #JUFpD)Bދ~+eH(APmmWe§>(W|"ȉ/)82M6|Zvm,iAR$1vئo#C 003SCbhL)/O=Th+aӦM1Ks:>JFV&^/kx Qȭ:% HJqy#GYfEHhqy晸DZQJcƌi;O$  H@$   kP H@S<FZe^hpWz jn?IXCpg:Y:(`AXCI$gqF8zO#'xl>!'~KšC=4f?QF &Ā&~;'@0BVv% #@w~(+AI}˖-/oI<BsG.riaZd8 s60qy;py믿G9?9IZs ܼys|5]|aѢE_:X'!nV˲±jS;C%|yf3 k̰$  H@$  Zz^+ H@@G _S⨷,, "2Ky%K!@d̘11!hߐ={W`#A1-gy& s@ ȎCi>8 @^;]w2їI&Ug*E9[wyg 8c`^VjpLjqcJ<{ R!ƞƞ5 Ē<㒝r)1Ϛx~'Q LJ l(xm a 31,QJNj$  H@$  H@PX/ϖ$  H  x9sb@ ȯ@9dd2馛ךpFW\/^@{g~z,A0 kX 7Qڵ+{a wKiL5zk||l%SNJrJPv+EЇ6 {33pO2K3""|ӚCX?9J8&}V"+!$D{yxfyn0Ns΍I(E3 %wqA< j7n <" H@$  H@$PšxQ$ n$ XzA%KzF @^_ !bn` eo;\p~V3]v&L/_֯__9?Ǝ͛W9^>!;zj 5\rI5kV TݰaC,kjժ'eu'NdV{>]hš{[@TBLY2㈑ѦL{C'tR/^{RX3f}$  H@$  H4I@$0 <$ W$wpX)V= q.처,8WK. 'N$e8k׮\QCz?ÇBlQ*k͚51Moke(ꫯV~"iV H@@Afdg:š NCY5Zykx%a V5d)+j*7ϔ$  H@$  H@@$  H@7D!7o"< ᢋ.*%9g"^[>Q a $ӥ^ZJT&;rC,2c\xǸߑG34# -d*f_rFaS6C4͑!Y^w5&b8$  H@$  H@3 K@$ #@jŊ=2#PbҤI1SM_KQdƌaʔ)ܐL4>lXhQ2Idi@|_' / }6C('y+?裓D'CDWJAJma O7M#zEÆ ~63`0w }Q` & %Wz/-_ 07aDf WfM4C2ʇq/>Ʋ֊%s:&E~-ߏi r!qL%v>'hxrM4ΜeW*fbIkك@6?Xϟv kxfOY? g!k~rc9&>\7ވ*G$  H@$  H@PX $  H@eCSO8@ j _z\ ff͚'dܶm[|U+O*DnN~"+T'z5![.lܸ1 Hq0^'OvYu(gǏSN 4 6aQXC(ͅ1c*̪}`n>#*G2/Rӡ;BJy晕ck׮i1'a Apx1A$F&Mh݌Ad*M0! ȸ_UlcƌsD&z"bN&19rd\c >{u5Y܏yȼmW7J񎨏( ke5`R{cOF#K1IFիW3==80V>5~ߵ߻g/<^AxՌ$hJ.(V& J` { 98@3<E@[lRb;|O^ښ3gN 4.k\&00bL 6a3o޼((Vo0IokT1O=0sp9V /~v"`M&ŽF39Jf1DkZ`c:SZn}y~~ʾ+7ꫯEE̓k0amqb!b<{$Uį _/@IDAT. c saPk8>Zk6)d TcΒg }a6ض%  H@$  H@P"f(}$  HeYE BA:@"\XCq5C@FȊf2DY\B@%;#J 1h`;=cQ0;/G;ժ%!H>h'bƿ{w}Q,s1b/8̝;wc0,^K_FP`LƄ|0 At嗗*}~{x'bZ s8 $̯֎ 1)˳xiʕq#Zl1EŘ2DEcZs;z—z7xg}1kM믿>(镹W΁_1-ޟ1f,<_s!#mj}GC˗}gX177bri/]4>; 6쳈bFV#%=}9s¤w H@$  H@$mtۈ$  H@I Cs+fdImك ,$GЍhύr.7}+D/Eš?${s=_p""%gL[ƒ{oZov[do21pɲћ1VO>dO΀*mP^ +c@>@)m=\d!GAq 9Sw ʢqke`H0x~;"0Ng>ĘO20yYku˘噟u$`|gx!dux1G?߱Wuc:>#@7n\IO""h7@yڵ_|!-ܬe$K rQ sRb kRg>&eY1qʵƞ9K%tEa cGfJ ,PF~7ҽ?90k&]s3S"{IZC1s5D#idZ983rEiKuJɼbF/ D%cݳo c`Hhrxd{c/9̹C&bƌ {s Dt d~#tm"2>0w Kš2{$  H@$  H@(q&H@$ ~ J`!KnrK[_>%/|F6@4 _Bpm{O p^za…/lC܂#A$e]ԩSc0;dp!K VI 2Fd _}/^[/"bd!8K?&<Op@v2D #PK)ui(q W/ؐ\S&% b5jT{X&O s"?>Ϙ1##<+vǤ,<ի,g>#ꪫ`X4D c!`c`% 1$Mo ȅ5Ų=;rUCGnFX Wlx1nأ "j{"aa2=x;wW˶)Vb3G:1LS0j!_[`!#S5CZB֨!l╄5Ad| H@$  H@$0X Wc% H@L f !|h&AT%G@:s@})~6S@aXKXRw2'\qQ8SKCpd(Zl7DI:R2"y (-wXɂH ^xz dAtT68K&d;AZ#D54 1d!H=#Y ʘM޹տ}}f.,x2d!/<Yp!j$?ed%a 2EB0rF:`t_gse=&;D4t!22$Yih3)9-׬wۼܟ{fv$  H@$  H@`%f$  Hm(P(X6l- .Sd|/Dp{o3-R)#xe' h1ĸVƏ?^Y-uA&|,0L |墒1)3st;s~޼y(z{QKx].c줒\ܗL.g.'I2i0kd(˳ ~vZnx}]d*cpUrKd*"7+ۤγ?3T{$  H@$  H@  Q$  V"%@] U۴B7DXS̰CcUNzycm2^O@v$A[oN:(+gXeiBWJV-4$#SYx5|oi6ٿvd\z衇в(2-{Z(7sJaM-b$  H@$  H@7H@$  @<%&e*)lSۭņc@ƒMe_ ;ȵ*^6WA,i 6w٣\aiӦtZO2>S+LJm H>"ɃGK57?dK?g}~\8R*) ex' CFT§Lts}㜧 {~.4Ümd/FOG;Ʒ(l0aB,Ǽes.~'O^}=.}׶N$  H@$   kHO H@ZFpQBp-+yctȀk kR" o>``}}'!Y,"@Mٶ$A gTq/^QQF cToVkvN}qȍ 6}\Ah~UmN2g̘{_~щv,1bD8cS҄ۈ1D#ϫF$  H@$  H`0PX3G>I@$V(kXaD2B{2'8}G1' @$Xx '$7;c?y阎pN,Nt2fN[\Xl'jK,]4^:l۶Y`ӎLF_ޯf|Tc5_E]FRf?o#kֽl;>^W sQ)RSȊ8sLx'+s$|x> T|7s=fΜϟE6,k=hY=O$  H@$  t:5>B' H@@W@TCW^KXBnwԨQ1 dGHFK,f!D 0&;[ۑG=0rpi8}.HGsO|YeH?1p,jժR"ѧ5/ѿ_ܩ>3r8md߯ݟ;O=ƍë#Z[lYg]s5aΜ9Qvψj"N59!?K@$  H@$ H@$ *q֐m駟A*4|r"9̞=;fi8!pg~غukћq"U0[i_3>37ȬC2z b. >/M_={aʕQ {ɓc š eG! b9;&29¢E0xwD6LGׯ2q 87͙z>$  H@$  H@()ɳ$  H@@]&LD`>l uQpB6-[T'FzjXQctA OP>6^Md_祵qNj s#JFN6dbBT /< 1M-bZjUQ˧x;߷?;gEK_Ǒ"8E'߬`t-/JP'2#9ZoeH6<^cS,EH|og"$$s YȘGB_|13ݫ kj$  H@$  H@̫^ϒ$  H@( :iҤ} DlΝa͚5PN.4A@rՌĉc;92)I 2M6hApsMy1VYȦ}z%? A YofPxZ'7j}b6lXh^vzc\c/KR>:BdG?~8j\5݉5p+W_}u~eDXYgc_&J. H@$  H@$/52$  H@hN:)y~1+Vj志؂9EU2$f(Eka4|O6"C+x䙃ؠե)1~9!O`zʔ)5b:/urFAF^ ^S8*eK>uӘ$˾#Cg@4ǘ޽l3=ɧQF8_w7{ "|~"Zy뭷nFR^Oa*cg ʴ9$  H@$  H`(PX3G߾K@$Tw,XC\@ 6G7F{FB^ ܹs{d^z)}a׮].B22 ZdIfOQj[pAJ^ q %֭[ׂ_^j?c[,!x&R r(e>K9K>uӘ$ygކpaڵ>7XCQHxƞMf,e*+t=O<1ٻg٪:*KỴ 2 BVwEtt~G0R*KeAD"u\g<;ɓy"d̽^ƽ~ݖ&FA!:5>VH>׼+!B B B B B B B B?kʑ!!!!0:#Xvֿ˿4wqGo}檫jE {WsW_Ic9fovs-4m}J7k_Z~jc%K)A0hQ_wXMm0%vR6晷;:Qo]3,SWW*'F`[|ͽ=-[tv3,?b#J#{wY\{y.qyg:S㭷˛ 7޸Lg+:kqҵ2R \C[ik!y|Zd}nkm1}٧omWF;R߾/7(_߭ΩZړװkKSwqa"%_@@@$!T5qYN{ncfA뮻Z <8_y*?unEÄmD`z'|r[Jzю6'S=?If;6f¬WҲHeRZq[8HqnֈMQAAaanE5PL+0*2:3j 5}ΟV:fC?֨~B zcWѾF]\>_Q[ao|x;S5@Q~z[zT}٧W]Sw MUYOLpthAP)oWn/2y ?HpA0Dy <wqKHTQJm `l#픟D%߫Ĉ\s"r_ןs+⿕4<8y^O+!!!!!!!!@5ym{ \xᅭG[|!<}>,^O{z~j}ѭ`y%l RGWPaPٌq 1~Km3w .š",1JCZK?i0~U\qG;:/꫺›-#5O9fӖmuc[\?]2q֯;,/ _@@@F#[D } Ϯ+''J12nH41" ߛU_m=8FIlqr\z_X]WWhn C5/!ERȸxבmn y75^FAFR^v0IעI_WeL}VVʰsW9[ZN|׾7Cū>6C B B B`/4>Vo~!!!!0+cDCC B B B`0\y"0*1OюqkQ/EXÈ+Ipc|g4uy>Z^z3IV9AX`T:ġ)Ք'V&k䕷 ʞ+`YuU_8ӷ'8asƉ>e![}ϓus-ekZxGoC B B B B B B B`[ G[Jc@@@Jخx9~YrXT#ܼGOl=GԵyNC B B B B B B B`- DXtw@@@<{.sLX۸aNm}^X1Q *P !!!!!!!!f|f9#B B B B` N'mHK/}@@,._3<Ӽ[m&lwa5f*)u&a:@.!!!!5}Ć>a_~e,˿yxZaM8švءsd K šr\@@@L ͩ|h;4?xo4X@@,w?iܿnE5z޷YHC B B B B B B B`. DX3ŒD@@@@|#i=f}maOj ~ 0F ͻn;92@@@@@@@@CcC B B B B`V<]7W\qEsw6vکnf\'B B fnP~xWg$a\K!!!!oߜr)~׼{͇?Q !!! E]:6PĨ;_@@@@@@@"YN%bO'@@@ '~WB@@@@@@@X& D š TJ@@@@@@@@@@@@@@@DX3=)B B B B B B B B B B B B B B B B`ff!!!!!!!!!!!!!!!!0=Leb @"@@@@@@@@@@@@@@@@@L@5cB B B B B B B B B B B B B B B B 6k6Pa&+!!!!!!!!!!!!!!!!#EB B B B B B B B B B B B B B` ۿ[~|l~}sԇ@@LJ šI A'h6ol~%a?f=h;擟doCO&7h?"SO=uЇU ظ"ٸe@@@@@@@@@@@@'@[o5nv5'o"{[tvyCs7ЇV{ݥ ^{7ټ+ &;м15q%i!S$0@}IOT!!!8l|_Z4W]uKD!!!Hvu .Z_}믟Z|(B B 1ؚ!!x؊veGR^{_uS"bnyltA5gu֒fTxꫛkS jyGm<6^=֢MwiE6/B E`f׾i;@@Bp:Ma W $!!S$pGLUX黌S,D!!P:kĒHדo /Њ]s?Sɲ]gY|mկ6Gr`Z~E6vXOzm{ki3B> l7|q:u\!!0f&o I]@@@@@@@@@@@@= )O<ќzSn_nnR?O-]oSD5;b=أ3,[ι_Wqy:uNg_͛M6[M9n5u~B@Xa.š@@~߷.H'8uS>7sXX4D5OAO:5N.!!IjlM{#zI$R[w WÇzS?C+ToﶇlvЁp\w)|U g(޶{3iڦO_aKi|@'޾k>d\]kȉ?B B` pW>+aMW׽5&OGXƌ}@KOl#*|K_jV+[JO,|p~pE`,d~_7I0tP#J"W_}=l駟nUj|ϓMVs:|[oOӏ?6D.>w]5D E%P< [8D>~5/b' ?O/x'?!<0sQG-9䐆Gҕ#[e~衇~{w3h]wݵ9V%' :ֶOB}E1CP5I;N96B B` DXe@@@@@@@@@@@@l>O72aod& JK/-y1|@M¤>}?y+[n!!5$!!!!!!!!!!!!!0O473ED-lvM5D3(?.2R.O2OߋkX|}+QaX&Vb'?fvϰ4ڶ;XIoIjbU1oR;Iԧ>zV7ɠcjSz{G>no\ݕB|[m OW}_)*W%ҿ'mkC}J\W1/x_J(wwԏm^O k"B B B B B B B B B B B B B`};mb;wy5nc'!a\/a=l 2c/<͋/zSEXhLX^{J|u/pMSa gx˼nc~ͱ`nk~,yY<}UWn@\ ]!Qx5=\OeV+ӽ޻9Crw[ZIk"/ PvmfӦMzH2NU 3}ٖQ^iCe7•q8i,ǎJ+^O=T6o܊ҰG?ھ}|9䐭Ձr>WW ]yzE^{K\[T"\sM[N]|+'lY~饗..3ϴ^\C]"q,a e'xb bW?"y嗗 _Vv}]϶-"YRKC B B B B B B B B B B B 688-0c bj;ÆÂk{ュ}c~לuYͩڊonH᠃j /B};[v Є ]M7?ԫva[ k_я~z58OsG.vv+ςu֯{iRc8SO?}hٗxeDa>`NK"Aדf[qI'Ԧ[Gau ;KYDH#Y&ҲJ=i7U!On.emR7 \rI[7x7̪\o5D='(>l/W9;H_[9D4تKDm֠cBAB-} +"t uT_ݷ} c[.šm"JKM0s1c kxD(Qfض(#4wZ㰭TV ?^{ݺo ŒʣQ"o~L7Vx~,6L $qx/=4^_g5ꫯn `(z`F_4w^y.Aבfnxz,tHXq 74rK+lU753(?O ƈ2xP˰auE1,U1?NσUg0W}~ tu'neG}E7~%LF šɸ:^lox]+ؒdn"rx`-3L5SO4û@ CmlucF].zyh_]/zu(^"l%~,O]66ђbϮ]ve]gg͚hv+)uJmm{^j x!X".R.QmHs^bKɰޞ0L`FTS-tAm@IDATܮMn A-O6iW l%Ҭ*Zh"ڪrRP?ϋ0<8VeruP;^xmN߆:\ xu?t=FcRqJ(A}e2[k65XjIs@@@@@@@@@@@$@kF12Bv& )xa\G]:CP{iy\z03}V1}YRH)߿CCpLAq^ʵg%B]"VT/ :OS꽺[B ~X_r+*Ad;R|+ jYkEX3o%@@@@@@@@@@@@lN9{Yրho+>Fmù# öS[ ë^.VPvACh#q#.+R{Ƿ^7D ]}?_U3<Ыu'R6ɳ>{+o7OF`řB B B B B B B B B B B B B &$h@-h!>r šbw,̠m[[<8/^B4߽s\kg\/i-"" O Eu <-g5YY[o]n\yE5~9}+E~_@Dp5,;c=JTSw!+b"⠷~>g„}[ǻuxuUmwyP+5QaT-?b8smt˻km޲(Zkٺjtj%j .ݸ N=V@P=(u;%~#(CLVB=s14>>Vg6oQlײ\mTyu^3DMڑ՟v/oEJK5kE6@@@@@@@@@@@@ 2xn O  ~2-0X`Tl;#N퉁c$}ۍc5kBZ-z5q,P- G?nr KW=Ghxx"%xA[o5GI Umm^t튺բ.,'|r䵻iY!]veFԥc9f uשYփϢ]stohIzC B B B B B B B B B B B B`-Rc5 3QouA5@91Ë/Ls8mS{x2z؎b`mZ+ /+~1g籦M ;v'?ɲ:{7atkx[W_}urw"Îc!fMEZ>i%<Үk)my ksU*YQ3uA^ml7*6=^T)B B B B B B B B B B B B B`LlCj/zJK/-)|[ Do(^xᅡsw^Qj#o'?ڋy֬C-!VzYo~߯egt ʶPg%++(o[y睖Z \HCԉq'ELυGy_r}׆^a+OsaW(yaͶPc@@@@@@@@@@@[ȀN9唁[0v`A[ |GoQq|ݎ8A71Ļg͚X ǿ\yk>ȣN;4"t)šq%73[{7<Ǚ:>;8ֺ\_ʸ"^Ȝ'M{5y!!!!!!!!!!!!!wfjz%̏~VDm/@S>k yg<7{25?Ϛ͛7/y`?餓Fn L[9w#z]?蠃]veDu:gy֬䵔mmo2Hd'DЇtYr]K/D%6 k6JI&!!!!!!!!!!!! '|{[=Ӝ~˄5cKt[K+Ҽ{=-1gB@,qM\ Tp@M.%[Mkm9xJǸ](ę5 $ !!ޓ%Xdu]Y;毾R0q?ߙ1DY7dh[ s@@,#|o~Ӧʏ~ӟtc6P6ۨGw˝7xc0a?Y~-[f&2ԧ>stG׿^v.FW`Կrֺ\cD%]!SC`bLnN?YRgь9šBd髯z)blm?m5 \N}ٍ4!B 杀C=xI6 ' z\?q۴w]裏6wuWk5Z9ÛSO=5W!;n^[at@>q駟n㎥x@@@<8c^H:V<.xvLFf7 ɜ)af͚kkuxqEkuuz?ߦS LcDfyU땺KlU>cFsxIDiEn5TZ+U#կ~ ʡșk;H :m+K7pCcVtO]veoʜAd$fd3Y |z2.{aIJYvc_3rgm5tMG879u7enn'(daO5}sɄ#`[`&VO'?nx6dVؐOB B 6b`^_v=Vz!<@m 87+kpbOzm1HWczm#]?šT6CuԋׄɵIZjrfb_jJ:Ky梋.$@Y"ԂJpO9vYJC q l1ՍZSڭOg,^XHR>3 g7GV/u㎋ׄ*$5BЧ^~H,3nբr-['qy 1ǨUO|+n19H*B B B`8vb^ {vbMJy\i(qW\B {ml-`3<$'>bٚ[OnEXb Ì}1 ̚m0P{[[OvЇG|l=ެg݊Kݶ V{p{^ZIz{v`y俵$qX;s A ְ׿xaטﱨ8a G ELKus4&`RIE˝w޹uiU̴6:p z"/&1=+M&WΛ  ?{mdB@,*FIU7&nP<̘}ZFFcwST-j鶰Ž죏>!J@6ѽ)a sϡ 22fw1vD1ikxlLs\E&`]=b2!lF!zAe' ᝤk<7ќ{mV\ [=s| .h.2HYoڲ :%Bַծ_N(u]PQeQ⴦#wݥ50"&Vջ뮻ƹvە}ֲ\GݻeD3O.w}w:HEj-a-/"aW\5 WdIpÜI1O>dL>nfro2}7 njlTw^$fԢ,8Vk<桇ZZq鳣>:j!!`/\'zYmxk$V glȚ!!!N" lI1XpJx|ekp_6~zy`Cc+kg}q85k6:x[&I-v}1#[=o5:AS%G.^\Z52v=ɏ;{Xĥ^T2,Q!(s/y7/ ԕl=#9q:u^o7<,Bp//4tMK9hŽҗ5'(k&SB`x2ΑڻIQ8g]7yvڲ8J\㾛4S l[Z0!B B 晀>O+jȘFVe׾u%qߍ`P|W>[|ƗC B B`AyvqДq՘jluj-wэ3=QQݽS^w ;Ky'`b]̟ʻau衇6_~y!A̎(X`>GQ{qҵgC`D+Bg m <ƮS5ʻ68ֻkQf\rI[էR mW֔urtFyV%5^Qw/V0*O55lWz^~\h<$$Ҏ ja f<^9:撀@*]:XX$*\^K0c tVh2 wyS:W'1N(Y-X&7%ov zV$8:,by$@@<>˜,[W-[`n;nc\x5]6mB'B B B ֐2?KNψ:n0/,5\ӊA^+0Adb0Y6%17FA=0'J?3'x{Ț9^{˲ wEFO?U A`=C2c-s.^tEgўW>ϟ Ȋ1f-bSxkRO7$Tf r=X/){{`M2mb#_)M9!!@f\i #L%T5~>#>f–qдo,iZ3DC4{(xyZA[k;J|}UI;K*Swe"Y7xxږy]<)uD@JZ_AiPZ9;;8I˰\珺OUjӺK{)k}zr#1"4HX~KW}+K}_:G)hk^\K:1ޥz=u>^_}0>PY%4651|} ?[W'x3 YpHKoon W˸s p/IL,zy]wmѹ\=UhCރ#ʷ!!!! Wz!80bDa(k!^XmZy N'mwhg]FVweB_~G+~+ǽ-e%\{_W׈6mh_m1BtA4}mlyշoj& f|fS9C3K MER5 :u T K5Nv{J0pi}8 8|S,p#H m7`g1Z<ؔͻᄏ5 :tiړoP' JXO;H'?b+IYtC!&tm7&ڔ6 ڰ6~b: gҟK^!y. ң +nu׺`idTO Zڠ`L\øjLfcu=kӧH>D-0vq]IK5^["u-|We\w.\>MW2MyFC93,Nk<gJ1^!} }}=[mwbYӮgPtmח'/ЗxAa]nо}kkX-]B;(sK} l}q=6O} KmL?)&v8K߰н6^Ω#򭿓o0gZiv~&}b7`(/+VC9ec1kke-uW8> -yɏ~څk껻Oc1Ț6gڕ945emҟMҧțrc:8c\k'y0W3?5oSܧ4o"@bW^kS H*+s5(ii_mj_~y6PkgiW>N}=1>IU2?uW[/l=cnKi7xR}|t\yKkDa{&}v#jO>Y,wa~Ae.g}&JWYr}}GԝeQECՠJE׷(;ڴ,ʵ\˻빗g}xy51Fa͌J ̺7ÒhXܙTXcpxAXbaIeQ\⦎q@`ςWa{rAVO8,bPՍ k[()iv}B"f¡~W68,vYla8FTs7y/Q +="Q6ƣ5[˫?]B%C2fSo)<;i}TPWl]P`jQ- ju܀v*J]cPe1uM5)RF}6qe`un~ 2^XP', gЯw~V2_SW 3&P[cⱈ:7+06_vVc+iel&}u=d%8{Ī} *Ez‰A+{Q uy 0ұ+~1ۤ+(痺].}œϨ㔝qԵ/m1pP0wblW0N<%7INԛ>{v+#/Ay ڙ90akH8eJ k[eQ{mc*O|%?u.W~2'x^[?y&j ) ]s)JPDH59.rwsI~xjl5WU'Hߨ{>2m8;裏n'1,GU+Œz^[3Na4lMy8e+% ս:}Î!)-^4uN<̶[>v5isn} DŽ@@@@L@5Ulë.Xlu _0,PGgS?AEJn)HQ m!BJygFf&Q1`qB} ʱx iѭ _ꀅ> YKޫ]d.W[WFD\xU~L7hsu),=:j1k[ZOAuC``цƪqvd\+pƭ}KOm6t-31pk\^öc aHb'71\I=\8㌡‡r4= )ZXc/䳺i|⩌16CD+ ܤ씝LR z̏5&9UO ^#/Dž"Ўy>L6,oڰ~n@XCu}GO0矦p<{>7b<2*.}>[%tM{Ur_cLpzia‡AqN^0sYcIu<,Az9K^;ɻ͜\eP2 WN<<8DIL@{fo|2 _hco5\~]X?֬!{7&cJY*宗)F +bA >p,ԆJSɨR,Y賘2Ӈ}ҷVHTb(i^rmI-h >[U`ؓǫM&װT." .} ps>ߦiYR'\ \ꅧ-uo:IT[$Xp(Aq>X5k< GOOvGOoj\EUY`P"=ţ/~Im(R%ȃnH+=8Oʂo¨xymB8қ߶4#qWz\Uk^e\VCƼЦb,ɷs؅@La-O5uKox +ubX<8>`R(_uf=EJGخvn8(^]qmӖE/y>iЎ<0y.!^O}p`\0 9W[7W5F_.2S0/h[~3yX$cjW;b8ȏ{NW>꘲4PQAzԉa|SyW\W7圼8uڿڂ߬.&]~u}Ri -3to,f3†|7J; 7e!e˷ZE5nƈ˷ݍ',Yd|o#Ţ N]D5 "2] qܬZRFu-yK/z-1Nx Re kD -D[d[ -- ֨KA,vF;! d]!S@[A~M]/?ҎI~꿽#Ę>k/{н5jyy7.gA_'^ʘjNQ{}qY[c }lk=V*uCk<|c:5@Xm ZJ ,ʗwaz]1<u3I9j\-scdیجM,:zB_H5ak%ޝxivi–rNU^e%D{*1L_ĿmGia)&&m>l1vP.[U=uB B B B`DXF%f"gQbq.X"ϓE׵F`PX|g'iƒE cJOly $u`o7̷̈́& NcǀU/gYYI'.b^uUK )e5m %XӦX0UǴ/iTe1Â`yj}WUJPo{ ]>/ S\ҭG25|&}J,QÉEQ Es$"EM,R'#vז3}AsA˘%5 y-:pg$ԘVx@`LEsiT644{̵,Ƞk[0aepǸ)'3sҮ99 Cu3RA])q+<̶~u\^[i`t#?me; Xi5cX/e\Ub3`HVc9&!?ajԹ;53Du=N6"' 㹾e.MP !aU}<}Cy8nIlA7N(1|DoyN ךl|ԝ"t}ذAz;aǍ󽱞~If|7Kv.ݺWB ܇+;LWկ~<|weP|uQ3e;|2OG-<5v6*c:wKE-?~@@@+ɬJ9II3CTY`x&`.jtxGNjkɓxb")G V, ʷE8dI^l*gJO<8k]ɨ8-(OEQu <}XpmBeVN;].s+Dvuf=?LSo`1z\aoCjn*u[D5%o}>S!b-J2?5ԶF32~+V e嵡11OY'OtE5%tm+ok0Z-dX,tT瞻̸-njcA{ϨeaBe :%ߵa7Cjեi5u~&Feq^$޵8Ǣy`gQ"$Cۨ峻E8'š jJ`xcOk kz+ N<9mv}{mlV-۟ @!-VidByЩQ}1.]Ѵ]D5< X -FQe֜1.W)yK5W3:@SVVnV_%mpxf;JaI+{x{,m?}6&,"neiۿ!Hy_ fion )qf\%t[[. iT6nf:W̛ʘV+Uv83<ݱ^u_7YJR*;ޚ~$9 !5;Abr/n߷uLM7* ';5(; JP)z)}Ẽ zk5~Z =gOYt|QQLO LQMc[OMz՞W8զ{}\pб}-f*uƢ'}JO"*=B=y%fH苴z\ed2̠ND%m;h﵇r<0^t+CWS#!s=Lj-P8fn1:MX#=]twǛ:AXx`mі}C4Z\߼u%c:j[FyeBvz'cﶝkwAqz $X|Zއ ʷ<wQ :wU‰[jlEDl瞺&=}9U^Q깉yx/AǪ+3/²eBQ̥ז/c0o iiT>[̊~M~ɳurO/|ac4-vM.$H`ʥiaB:X$t4' \@]Jgw A^G HSo1t?0v*gޗSyQm""6!yr`qxzaSܣ nFXc) c6@IDAT!&!66Ov]󔡟6fAw_^)~+5"Ja^$=HìWW3*εM/¸j|8 :8R4~1@"5J4/iz0FܣQNw[<n[Xd\D=-2N>T=OقMzwM ~XU}oe5i_Qm=}gъIE菍o^0hSM.͏[P\r]mU<1W0FcnWج!O8w! |#1&?oZw^F^|\%bx:7qX_w]uƤ>f=>KWiVi}@6(} ݧ-62  'iFvWGFi?O#`[ni6o.V5yE#1vĐ>ݮU>]{T[^']}E5賺۹ᄏck ET3N; ky>qKSwާGk+KCXSr+2oQѢlߪo_lսF~C`xD5T}(a(c}Icy7u{n[w+i[g~um֢gQ8i\KYԿgiѦvܟ9pVjE:gAwn ^ڪqRy'? ú5tQ!C`["zpC?'omL^B B B B "r51O9)G'bf-T{nX&`l*y nOyԚ1 Z>zB܃⬯iAGM[,>30-$l?cN'Iz ;XY^/>1wڮu7cs/+My䑖ֈljhy?~-[d,O?Va^mPy!)A=d4gJ bA8KU8WamD:B B B B B B fA š)Sh5V[M#a!ئ /lӊP]`څ)Ow` R?k[<ټih,2q1a%?~|,TZPn,6Xݴeq`S1-@2cr1H{BDŽgzXws`Xa`9[#oc11jzlGjcqg 9jo7m֝*eERAض {X1ߔ#`=~98fH@`Iɥ֑,m >$GCsk^ZݯuU?Owz__y̘ZMٌ%3x!fW eǶ^<6Glnʸ^=yh<%@@@@@@f Jy'-,"]pg5 k{=,f󝱬 R+ N|ς߾dXgĒVOeZp8kou/ {/w,2$g6DBf[X?|d ,tsӘOC`VoƂ:A`]>o| 4p@+N 41n+ caJAGt2%3=Eop̠-8qYbSchWX l=CB"𸌫rx *A9u]0!3!#}~W=yHڦC=A{&[캏lKLv0ǢJ 0sfnL ܋VD$Ƿ:]n!&!1O0W޻gWjJSN%NCp[{@@@@@@@57\wq/ 7?Эy?qm|;K+ub5]vٺâ#< ?`AGm[E3<]#(( hQO,LZHiAOGwAO]n'5)LB`gmAXS3F3Nnq}mq@lUTV#瞶oQ_$ur9D("{yx{w4;x359=x"cP0]S(Bcbc\c(^Uly=m>|d\\ (ߩ3>\-Xs.x4E~zdWvV[LsNy5\5Eܲ%\2ki䟡ӈ g%(KWMUISyW,qK,=:DXc qY.o .YM-ԏZ8N;YǢ-/5%E85Plj+džJqjN=81FwNWSr+ŁA#F p}~=|mc{̥T+KŒMS2N<'S=Ü9 sX7񯌫F!]a ilNt/eݼw&zT$,l Y &,o8$*I6R).jJm("JۄTmHBBIa5 bl̎1f36`%,ߛs߾Hs73l3{:e^h\ v@Nq CJ@|G+3ǗM'O [o;SR_H5DktM6eϼx7qoDN/u34rҽ?Q01.6\>0㾤_yGtk&"mVp˘=Kh\H##*FztwsOëQh1sL#r`@; `>{oQo9s$cK AB_+Uy!{a U.\b, ~qqHaX,mE<$18"~DKxgN )9Y㞆㹏=uSca,(?}ϣwGKq}үa_y!PLxʽxlGԙs^yx[gO>9jyK<+!xƓ/,c)}1g\5 FFDP$GMi9UvvXmV %g3 GyxޮH'[3B{LhCx^ҽ!O(Fʠz$  H@$  g'}Ry !&"0Q#L<τ<3C<}Lb0цI~֐O<1#LJ1j1QQ2Qq`y<&:ôg279TeKbIZ^zٕW^_>aNF+ sέkk0j49siSIԩSc9&w&טd-^LıhKLs$%#&"sA\;ыv, vN=g*1J|䗼3GyPi~݊"=ojIG8pe:yMy7C`+Ҥ#!w_2D3NZ9QFHaQ GD !Sc\6k;V9L&;[9#`bI"OJ`@~ik̛ݸ {rE OCw\*+12β8O}y-qa\z 2(#=qiAK:*8rT7\"y1\#KclQ0>SkPO=9d%!|7+3Fs*9<}3q> c'>eMJb\%l3ʝxϡ"p餽6`p0 Жi:EMy}E a4MG5 A})l^~6B P/{w{+È=glxn@O_[O\r.?i%oWDL?;Yܫ"Y?K^~-Wl\#URT, jm{G*ʽ3D= (!5QW1ҾOx7SI?qij*#)#x3GeA) $]y`C]ngp<y/AQ~n2%Drї;q† FE"Q $  H@$ 0Sy)L$ -(FP|7{&#hbq/FG^*#&D8቉'ɸd27'bN Lf!ߘkMz&馾Ǥ}7W1  M ą M&xs>&*]u ?&0 1{u} Vu~OOOyߵJM b1b0^aW&<" Os01#+-a/c@a\EHl0 a*u*}+F|\L5Vx*y6r-x .E 0UwǼu߆#5:`-# c jD3,cq;aRVzNVf]Dzo%p_H=a_ 澐NzV!0`@zֹ`C=BFZG1d &܏gqѮ@ߊQϨLRW 덎1fƾ'׫"HBJވ8 /n1B]U Cdɋ(pDtN93d]e]:Ge\eVG0ok B+ 0sye|ߘHgE&(D%&12'jJ&ة缙5\wָ&ńa-#.h/ԕr㺕10)"}h5nY0gr2q>y[&ޒ4HQ;Xh? a&Ϋ| ];=Fbѓ91F&W!>>|A~%fi1kI+'߳1xʞs1b$?+DZ=c澗qȘWϸq7ZFE0yc*H/d"Uv;^ҿzqN 4JaW?DxJ]w[?v̒W>hW/WimʕSHQ'|BxYϽ c+¸*ϭq0mŘ!_cl3 ˲uMXz)8c e/21F7F9Ԫqjq /{VQy|KY䓕LQn%V&KC3ԛjGބGp#0DCd 1\7/9v!s1t#0BЮ2dx3ɇ1>^ - ޴װ'+{a3&Va@bc&\aJ>P$=K}IZ$ E2y>;uQ m&*c6qp=30/v>PS)m'Ve16aߠRN՛r>8X҉zL/ec+i"`@?u  H@$  H@y~Iq&ˁ+uSq0a IL1Ad L.0ľ ~BBX cr !r%me˖zUT^RT$/3.Vxy6٦W8> K3xrfm%yeO%p^8,W\k{$  H@$  H@PX<;ϔHԩSx/6mڔ֝$ꩧ_i3gV$00ʱb|⦛nJ||7ׯ__xŝw9ff#81<$ !' Ϭ#xbժUeZ*_p IXbp@%  H@$  H@"v1F% HxarڴiźuF0a~xydx#(x 5l5pÆ cp^wq$  H` `k`#yk-yd /na,16[$ɽőG{ H@`2eJ1U,7ɆwQ9Qͳ>[rA$0>$~G*F8X #c/W*6;,c15U9  H@$  H@@o( wc $o!a’%+X)d'g}I<3fsH@sqq*ܘ yjr8 H@=<i  H@$  H@@({c =P=\D]X*Y:j̙a<$  X F,֬YSk)?.\Ɋ /~{va>fU5e˖⥗^*&MTL<@h$  H@8L6$  H@$  H@)4xK7&k6oޜ _@3GahKj׿e Oߩ`饗v[756ggJ$  H@PX3|ej$  H@$  H@$З~;,؇8SL)f̘}ںukd2&Jayb>SĠc.%  H@!f┵9$  H@$  H@@ k׎wiSO->$AH5  HWXBH:XW^I$  H@w}_P$  H@$  H@='!zŖ-[FrA/.=1}zd$  H`šQ~$  H@$  H@:E_OKs==#;\|S*,Yqɓ'&Cb@"f۶mC;! H@PX3%h%  H@$  H@$0ְE`ySPm$ЯL4N_j$  H@:D@aMzY H@$  H@$  Hx㍂-˨( % ~&1A$  H`bnbf\K@$  H@$  H@$[oohm]Q~$  H@$ >%ǚ>-% H@$  H@$ [׿N^_~b]wAeJ{bÆ +RNw9T7n,nZkivۭЇ>TL:5[H$>~6MqW_}o~e]ƈHO5o 59gMÜ{kzPOSQWU*k!RZ\ǹm۶TN/B*3ʟDeEu.=3ݒ>?}A@(oW_jε;'Nc6mJ'Q>ӦMK}af>[zpyS:lْNc9W o⮻*z$ޡLywD5?b…^RU/s=Q!br|̚5i {I@` Vq=,8∔G= q__}ݩM lc#DmbH{<GP ?裣KF]B\7ONbJQ/.T^ws1?$tNS_U0,_.~k_Zz`3̸Z$  H@@Ҧ$  H@$  H@$8!DkĞ8f– !,C:<>#:_X#L~ 1᠃J+ kV^Q>`*3ZpF'QpROvDnv|7m $"BPS"b|#gQ78"@HA@W^1{쮈k "JRp^y@@DXC7Ǐ'(_ 1piUk{3{VHk#`#ԛ .j9o$ CRKX~h=Xj7D\sWX3Hm!BjL(Jaiq9~' H@Z!z+ H@$  H@$ .}#1(#LkJIAw.,8Y"?" ,rB+ "j]+n9i3t71?,JCp?u8sjB8= ʕ+ /,̙q>WJ;FhWQNc~0f[Ju 9B9|FDW_\MPu guVU=HKSyUj;BC9gV>#F5D`3^yRh?O7ǔk+֬YWF&>b#ùxAҮkj1UnI(_>_~y>5g_OĶANs<ҲYw H@$,5͒< H@$  H@$  Kp  f%X*d]vI aIJQ0# `^7 ;<0?N9<˿)yx Q q A),/鴦=~.3xA\70 80 ! 'ă!ZWR~)qX A e7znz%'D? 8QWrc9S,HW;iDPM-xZ] Q u:CP` )/®qwԮfpf%67\, u%G>^{³hѢeg{O3O<=ߚ }b}7)nў;6ӁH#),KE3RVQH۔)S7ʓ>SOM˧UJ ?ǂr!$  H@@(i㑀$  H@$  H@@ `8'@OO +1btDoQ&`dD/`D8W]uU] @D/9gΜG E05QkD K?S~'t#y>0=*ӦMKDC X aSg9!D!Aŋ'Үጐ12sf 'x6jL<G"^a% iKC>Xne<5Ke #Hr>HmGaR^/~iY,D?#>hxDTv&!! <K|p*#D0FeMA0ޭhԣ\W0*ʇHoy'Xu|4vsgR^!=QC$_2U!9GP!j£?vJ9!(An GBDwE~o8|K@$ vPXN^K$  H@$  H@]"< xxa xzay֬Yi!=p4_"xj# 0bpOW^D[5`- rw#W=`xm0 #."y"& >%!ހraRN9aǍ7ޘ\ 勸#?Z^V=p)#=%\R_i?wܪǛ0#YlYqy%a@^/"S!$xq1I{IT)c# < B@u"m1x 1"*'i>!.T %k)g2)6,ERޛ"%y?5[؇' . r߅h \^q~=P{1ԓ~.ʆHw~wa*/rZmv"6! 5F%  H@@' ($]- H@$  H@FH!*h4;TINzKikqQ&xI mnF|QԐlu]p`qh#x$˫`% P&#zM% '?$4 , w @DJ @b< =$7FWF;Db v k xE !e9c!f89KQQwPQH>;_" &_,}w'JQ˧>r_b * 6;$rʅdxQ-a (xg.WrSKXg!ld\zF=?~dzRR\ڞeբrG\T h8Q"\Nqm#H$  H@'A$  H@$  , ,i?#X^%:!Ҋ1C5~9߈`{%9dm  xbF5,61^Sh/ + 8^ıJ1'N+Mx=F0<UED>+DRԥjH᱅b)01&'WԹ #V³ ^S{PqN@.R;BTC_ew*j"m7ex SqL;, KUB4G?!DӶQI@$ PX^UMfN߂h|޼j%~Pe{$  &ZFsʹELVz|x$  H0cD`ӋQqϩIdɒ䙧|<Z^9x^(X(ǵc/iEpH x<r&Z|A?qi$ O4DqM1ex`'\]nݘ{_ҌF<'&GX#._KtIkH99n}Yv@ߏS*yքA73lW`2<'5Zi=ó( DvkCW U˟m#$  H@!;E5 0?i55#d% H@@ܹs+Wlp39$6 H@@? yjwK ᑃ}‑| \lcRZyC bQ F1,sK#)onYXSF|e]J4G<=e6Q C|+Vx|{br?U)p _?*W^Ge9䐴Y#}Jt!+]ԵvƑX-D2AP{WM/u ƹ/<yGlA½$  H@5l,o-??u|?[{nՇ~Hi$ Gm&/^ZqJ|N H@>fxi}#_ `ak% xAJk<lD,GUIr<7xY݌|M~n={|I'<0*@A(G}OOE'<YB],HR3gL¢/ /|H5xɁXƊ>!*Ap_2.Ku;=3~$-?gk%  H@@ (w\[eeO}}S.Q/Z9!3gNZk)Z;-obeN)|c / 0Q5#CG9iJK ! ksl/9".k>S|.|$a)*! *bI%m/%ud(6<ʌND\˗//{t^,7ʝ0pG$=ܦ*ymtrOY*QׯO{Qb&S&SO=UtMŭZ<nYK/MbS`٧rr_/ӌGD2O5}z1$fx6U$  H@š8yJEkjZLZ$^kVb{$  t@z[M}U4$0" `,y}WQ2#yb-[GP>o.57nL۴#/ȑy1rJ*oy&'L4iR;}^R֔=#[5e@Pbi9(52PG}tDOTw!^A\H~s9>"vai)18"Ƣ^'բ !ڪ%v1-<~$  H@ ;^8%0dt 55c& H@FZS^~^7n H@h@x={v3wԏC:Km"s=+ךAJk=sE'ׯ/XF$O3{ X!%~odD8yɅ$>lۄEXQ6,;/TA=SUB?/)s1㸇~8-E}~G˙~S@7gyM%D7|sȭSɅ`KE'>lN>bG< qxKs;m~$oKoFt$  HqJך:!Ljʱ۵8H$*Z3cƌV/VV^LH υ!9s$w?ӀqL[ߟ(a?Hi-"̟&Ttڨ_NG7g#` [[W5?|nݺRcx32>~bڵu$̚58T"P/Ɣ1>X'A " Zz9[yϱ˗//V\=c[:x;Cf540 \@ߖj<^$ !fxҜ k s=`  H@굆DO;N>JN H@,&}r`njgΜYzc,s-x0Hi-scoRG#~ŪUR0ݠ.\; ˿K|{R9t#2>崔w\4fYy㦕G?JeSf2?tAŌrO1ٍ78 F2f~F>ϝ;=n$ʽĔ?iq6m#r)>Qo$a}[Oec;N~/6nX 4!D>qg{{=_DD7r&G"xBt҂ezrAI6+ƈVx^\sNJ+zǠK.$ rx9o;'i!|i`# jOɽsF!k;/R:8yEMSjy47w#DDz'aD5»{O`N8!yO0:lm(7#]QIP$  H kc<,v0ȼet$  HL^EDl=t&L,YNB$Qxe8z%9+Qxq0s_0P#SO%/NBGѲ|? RZUG>dTPx]@rW9$@h! }x?\xq rt_ve N&MDF49!0(a`GI!B- b@dCzi#xJ+>8D3e$?L@DHαdSٓK%CjyW]uըH4mڴ1v}&ogXjF=#Цo<KW^waDBYc+k>g= ^l:uvr 7$љgð <"BGhC9 _(fr2 H@š)Ks2xb-c,z&A$000)̛z&qtK@s}D\뭦 }f0N_}գ=PWox@m۶dXF`D( /L{g 68Fx8͝q4r-[H(! AhsD$GYW HYF2O, H@:J@aMGzq N~ZÃjZ/O  H@%D8o(k zm]0v H@h^zyYo5#$ ,bŊd1\ Ì#2F:)/< 6Bx[I%餓NJ?(֯_?b?%0 cy3ҍ"?.G|&yɗfw}ӳm:w,J .`cK}Gŝwa jjl{򂐇w$Jҏs=7h6CD`1!fI@8Ϟ#XKx{}w}$+aiɈh7e $  H[tHLLk G}tZ+lx$  H/0K5zj`"$  H0ݴ^k"^%Մ-mfFI<" x{`Y0hV6OZ.b "Q}PFncH3^>ϥn)-F@|^.~y(x`67'-/ FiFlN9S 1Gb_2O,akxmAezEpNuYi%B3gLK18uk2K?O/0#=z10`??LKToKCI_!l$o#+&B~<njPg~%PG12TA!4D?,}K.]aj,1gr JuKR9~$  H`bxȍ-D:1o%00xsǾot=ͼ遥$  Hx;yñہ ¯|+qO$1,zv,xIxEp@P,[`m5`09aF4^0h/na4~' ˜N#ie XNkwa fDh53 N18hox:aeUI'^sὪZ>#W.6m $  Hyo/l2bs{w~(w߽X`Ayb֭ /P\uUI,3}O?o]Ki{ggy&-.dɒbҥK@$  􎀳coh@Gm:N[M52~/ H@H xE'5zZc$  H`<Uo&)o8$  LTO=T#$*ࠃ*<1oy3,X:kK@3,'MġD}b۶mo~7ц@N8O  H@$; kzޘ%4N{[ME$0:Fo5X)L$  DS^kVRx$ Zx$.;ukn{-^{t<ۜviʼn'>m$xP3gN'$  =Zr'h9UgϞ8lb^@$0L^k:zJ`% 'O.|1"ay(6$  H@npn% Zlٲx禷#rA/" H@ yi7q=! H@4ᵆ8Z ziJ@$  H@$*5| @警z+ H@B L>,魦%|, H@CDUo3?D(͊$  H@$  H@="GV".5ziWx H@dZA]IU5zigix- H@$  H@!j#>#ЪY H@)V警g$Ї:y}$I@$  H@$0 pt V&H$  E^kVc-$  %ЬՌ$  H@$  šp7V @^kV$0Z!(|  H@!ШFH$  H@$  H@!j !!Ь ˖-+vy!!a6$  H@hk j,YRL4Dx H@hkj͊$  H@$  H` (4 4&wqe, H@Fք#Oկ[e__ZZ<!0SO=u@@a!@ !!!!!! c77:9͕!!!#>my|ru@@@@@@@@@@@@@@@c͞Tt!!!!!!!!!!!!!!!!08+W@@@@@@@@@@@@@@@@ 8IE!!!!!!!!!!!!!!!!c|ru@@@@@@@@@@@@@@@c͞Tt!!!!!!!!!!!!!!!!08+W@@@@@@@@@@@@@@@@ 8IE!!!!!!!!!!!!!!!!c|ru@@@@@@@@@@@@@@@c͞Tt!!!!!!!!!!!!!!!!08+W@@@@@@@@@@@@@@@@ 8IE!!!!!!!!!!!!!!!!c|ru@@@@@@@@@@@@@@@c͞Tt!!!!!!!!!!!!!!!!08+W@@@@@@@@@@@@@@@@ 8IE!!!!!!!!!!!!!!!!c|ru@@@@@@@@@@@@@@@c͞Tt!!!!!!!!!!!!!!!!08+W@@@@@@@@@@@@@@@@ 8IE!!!!!!!!!!!!!!!!c|ru@@@@@@@@@@@@@@@c͞Tt!!!!!!!!!!!!!!!!08+W@@@@@@@@@@@@@@@@ 8IE!!!!!!!!!!!!!!!!c|ru@@@@@@@@@@@@@@@c͞Tt!!!!!!!!!!!!!!!!08+W@@@@@@@@@@@@@@@@ 8IE!!!!!!!!!!!!!!!!c|ru@@@@@@@@@@@@@@@c͞Tt!!!!!!!!!!!!!!!!08+W@@@@@@@@@@@@@@@@ 8IE!!!!!!!!!!!!!!!!c|ru@@@@@@@@@@@@@@@c͞Tt!!!!!!!!!!!!!!!!08+W@@@@@@@@@@@@@@@@ 8IE!!!!!!!!!!!!!!!!c|ru@@@@@@@@@@@@@@@c͞Tt!!!!!!!!!!!!!!!!08+W@@@@@@@@@@@@@@@@ 8IE!!!!!!!!!!!!!!!!c|ru@@@@@@@@@@@@@@@c͞Tt!!!!!!!!!!!!!!!!08+W@@@@@@@@@@@@@@@@ 8IE!!!!!!!!!!!!!!!!c|ru@@@@@@@@@@@@@@@c͞Tt!!!!!!!!!!!!!!!!08+W@@@@@@@@@@@@@@@@ oݓr;H30o~?;Xd9B B ??կ/ujWvKB B B` }w[5d9B B B B B B B g\iF17}7N://~o:+I/B B vſ}Cg=Y" ՛.h==;]d>B B 6M׿oo;G?t׻uN 8l$;}_rVSqB_Go{F~G'|7oekr |k_svMB0>_N_!'V9oo:Vi\oo; E>n0.65GlfxsȟUŻ-'?;џɟt2My<̍uX=,t'z殷uJ!!!!p͓JLjp'mvz8֬-}} jO?}<[ \4v,1vw_4O7saڛ M+_/>ffw(:ӟt/)ݎ7z6_le׽;}\쒫:Ȉ>Knv&Hw}wnxn$K/ttWt׸F7ߕ<|##Nbi6U&Lя~s?Ṕ@?s=wt9,%/8U}3ntusیgjޮʑv\޸#YIT_~5yM7Xsꩧq{]NYz[ڽ|f80o].Z!!!+!qeukngGt&s%v~s%ѵu;ׯ F¹}ST7dt}k>᱾~ȑѽ}i-oyKwΘc02[$$t 6_W;GՖwgt?rf?Pc__vH.Xc?4ڮ@G7`8T߭q]7Ekg};S=ld#sPc"-%@@@lK6Xf<\r%w]ϩ ̡lpCҥem쫞4Ol3orn[TmmM@cdch E4V OxB+ۂָ@ukN0[Dz[ztE%c[.b؛K&klnwm<LJ HP[mǮ*ramumC ѓч?2ksMF=)Od-bB}޶;AWONx9$;c o-ns:BV{Q\)[>ũ ,CW򕝁AD;nu[koҍEcL{n4LJF LL;Ź®*k^eS.W!!!p0>u|@Ľ]l`%^;ei=ӟΉ~<G?3adW#l7cއN@-n> HC B`PbMh?<\[ Wy[`'b9g{{k/䊉nu'|0*lvC B 9< \.O9z栌|g['{=f7R:x*rj8TpQD!!!!(泞cĜ?3l04M6Ұ(/ '=]jWV^ r .>Tm:;M~TCsGvFat+mC A 5`O^[Eٟ0>Nc9NsGc +er=Bm@@ 2rV%LfGubYNf8\˩/\5Avat E8T`q^j{@r/s/8=mal#9_W:Z\+Ca譡7aDOB B B B`^8e~}}߱dilE mţ|xmbSζh3ͱMi??98P[zk]LVC`^˴y! ǚoXlc*cg wy{k+A\zkgFZ* ϔ#&,x<9I8v5_,ӝ4coJzg=0{| %W!Q8%@Ú0;۸ũ刮DLe9rdtg;oqU2ī8a;5TZՔSa(c@)_I׸5!{C@@P6 tw]ݣˆ??( ܻXzX>nA6!~n(|<_ı0U/}X((@B VK@3Vі&SզB`qv&`?+š#U;Jfű˿sV?x*S{[jVp9B`8! ì`W #_v d,#w??kZc;$0V5K} /viݣj&5yͥe3p@`CGt:!t }'qIw\|.X+;Yiơv*J\!nqY!a}6 !" SN9T!5./g#!B B`? PNz ^dIf~1\X!!8մ5$0tw*j5R>񏏼JtIX:mo{ΦStw *<(oCuusyh3yϐhC5!0mv\.B`6o}IJ;e*|+nW1}(WC B v$3oo\Q / Boq[ ^<!!px8իy竌T+9SiރEB@@@@9s)+}{;Y9zϛ=1} hCOe7~5yuE=HNI{ 6>@kV̽ULD=]qvv":ݓx;D`,6D4Ǟ mbk\ctg!߆@@J>쓃̱g׎a̜wѓfB B B B B`__K=՘B < jVG{%a<{8q33<=bӊ O~ Μ\t\u`?cRZ(HtUWu97]W}w *//F[XyE:6-ȑa7f'|hX=Dz7M1ؗ?gQB=xJjl5>0r2":%!yLܧ䍱P*(]vɨq')BXEYV˔SS7/%ӟ1S4Lն)S'ԏz?_'꽽q߻f\]u~ke]֫gs-}+ە~˕r<2\DV# (﩯yB^MCN N63Nwe3cϚg}72+?d,8<+y\]GZE~w=L}C]c,jߌt~yt}C= O>iPoo>2ьd [q{Se}&{D?*]6#/b?ȩqd+z{ /\*G.^zLo}qcX`!='GOJ|0I㩸'{it'Y߯u77GWf-n:l?/VoMG4gibVܳڢ]cQ9:y52vImrVCElUE*?9cy6''r_ˤcv/z}~Xӏ'68,QScF6J//Wݻe'< ?׽uFoxD2EzD*r*wȑx<^<ʀ31,y t|ַtpt ~G~Cos~ C}Q4uʭ?я]qcc.Q jR:۸(M/zыF\\tEq,W򕮮Md8T]upxht;i%Ah8I@H3[U{iӌ\p3ϜXL̞'w\s~qQ' O /ˏ{_G~K oowrA{j}j<2?d]oz*ݫwnwK^o|@Ў\ wla M2+wխ撫!7wQu7Io}mns,!*Cɩj3KC ]Vgv}s-oyB3^F<~V_??>U/N1f\Qʠm(+GԕUuN-Ȍe/{YOJo7zC}3ng6z} N;mt䨮}@vj :b9mIӫ1D;Mn21iF/]dP3ב߱ ˽ 5f(5V06ec+_N?gY׬]>$cgg1\-Yq߼[l0o]Eq1A<7/1wr8T㟸-K%N㏹C/C|N 5on!=qnr?]ⵯ}m/e;nm"p.;VGʇ-M(+#^]=E4ʇr=7I+sUAUuMHD`, ڮyzWJ{ ټuRз1(UAy ^0z^W_{W>l:;.|0_} _y}7GG!ѠMZ X`!w¼y]~ |&:]l1nV:g^:OO[Ns?sjҗKWE#H@fWR6X} @"ѣ*];=ct۸ͺM{.LʯUrJP8q^X'NUwڄyuIuZ[y3*#^ڀ6(m}AzPخo:V[M>]W~WIsG<ǭY6mB\A{0XNjW(;Žc?czLC~oCE3IAgA^Ad?}]c2MoST 'Ї:"!aq` } , vJُQQ2s_22\YdxjM3>Q]_ Ȍ?C?ԿtK d{) vB+2;f,ePV'~'FHWU;UBIJZ)q Kf~'= & $坔fEȨ ƌ6hNO5H;֬+myRI:L9>T0Oy8aNǚ*ë_nxnl%,Y\vGW),ov '/~ kz`db9Jť|K^g5MT$ePB{˯9)d)+]w"eYP]eM@g?xYL~>څ/dzEjj&'m;{qOntqk~֫XEUs6 tJe\g|3 vNƳw㹾Wqqyd1L7ow'q~O 373?\i]9%~zwN(NEZG&1jN!^Q{&!Ŏi5H1= ʑHY~PN~Cڏcꔼ1V*79\quMw҆q7W2-t\岙I|!yfS}.ueqm@?e^ZV9"F߱fZ?/;C8 Ìs\ +?Ӄ=6|]Ԗ9/glvE!yWqBYl͑/ykSOԜ#n?K}n'^E"^pVˤ^"o@{ڼwCWouEΰH#@vFw:ui´|ufc]MF&sR[4˜/z`ޡOdgʰ6+ohw?N+mNp&%:)$m;6syŘdgLR5^S?SoU4Xm>2h:L1>vJqOSVa+V2( )j^4 5FDޤCӟNL[(D<p7 ے5Ntׂ|L'L'}n|6zel%pJ\[] 'G0B5mp? ʑhnϬxs:axhaa{XΊcP7S&)KEq;Py0Vm8=5~SMHY'AלּO@Z$}U\ˌCj̤Ks6Ɲ>'8MM|x1nbphi'h-2<˸D<|7k<_/\ܶˊ]?oYyB9N'-X>XanjxF7VxO zۦE<)kH[v-ʫ6-|#3Lo_g~ty? cPuOmy֘84u`ѓS|<8D(w-+z7E AN=AzEe0&͚S(1~zw9[Ly~3^zqUzwdzm/+>O mq1s*s‘e 7R~Cg%Wͣ|7옝dw: RNviA?Ԯ$e觧HCirWJT٭-^mwjiF m9;Ar#GODZƘ8LEϢ镕[cGAvų]`#~ tZ_׵5ݐw~:'ŽI`]z{xuDZ.k>|x%t]COיGlldM&]'>;q޽J>-i|ǵu;C߷ڲ[ ˇ6P/6v>1lj՜w`>W_uctś߶@kJO:`02! Z208z7W^wJ.< +EiY`a$=񏟨U j.*N`4$5 Ub/'2HGP A]x*qQ Ox: KqgqF ' !Mva gʶVfC'(eŴ80&:leeSN5fVbg锣'g80d7$oۊi,)e@2^/M;ږ mOqp:rtru c"$HP|qM(mXw~۴y3W]& L&'O'Tq?Vǎӝ3^-p(o?ۙȘFurj -{Ze;q#q&73ڕ?ıP~d JPXc1 ڂ\GZ] ʪA-2JЛ8M۝W2Ѷ8PY$/9'NY=rHssyU!e/:#e옣h?4Usm#YAQ';ڿS._,̸E@~qA'#jώF&+ڠoxۼNmm~6xL/ =O9c/݁~Gr:08Ÿ;o0Z=<'K^92+į:*J+SZ3.=FӍd /S ׸Qu,m{P/C*N$Kq׭*T{©I{sD>y~>/VdV׽޵/K9+9is:"ۀw/ 8U'ϕO: 5-ȯ~k!SeEWꗞsܰߦOv6^dIG#j#ZP8({ߓt?>Jv"$O .NbM^+]SyЭsAO>@ޟG(}aӬfhSmSqCǔ-NsUfGWjҠK_yi کNͻmӺy"bcћ\w^J!ux*ͮ[̌UM]Gk>D?׸~^q~gm]ڏ9v'փ_MCcPҴu3yI`~v@rE; .'aHЁͿ7UX*ep:$o 0V,:6=q<`:o0q1YvX3N5G2Ƥdp~"Zt}c FJ Ya‚͐* N^ʪRnQʪ(+cUֈSKq /ju"YR y ~ijKFR(F2'hSҒ> sV{QNu'0bR8hB!V/'?G?l`w;'d"_ }QRnGrb\5r,РOpxVN5dZ;t[rU%/, ǘ#}âG+5&M*ŋIqYCM@IDAT\5eD {@\_::}3ҩ&8Qv' (4L8VJEv5&CViԻ33y eկ$/ԻS cø ԣ3IYv]ŗf2ƨAjǾ'IeQO]䃓k;vU6C.# ~}Ɖz@u緟? LP_A[soTrE^Ax^Zt'XFκViʐ :KYCz&é eu)C ʪ,cN.^rQKN{mT68Gyc0ΩϴcmF{}bQ\}m:vʫz'{<6"zy΀NB`)cٴ}v1|wWoa3n~[O:ʹ81+8Tm44McSl} E.ξew/~<yH>#  }ݼ'#N).:[A̳I7ڂ?oc12w`ӕDۡ^k9<^AsqY8djW8?Y*.ur }-ɵ;ݸ6h5nmV;̥'on9yUXVև~~nEgZV6ݨZ9mv i޸A6ڸ}f'_׫sNԿ>a\6w1~H[4ٹݛpıf$<- x :% mLA|6LZAKKBbi2y1V0(x 4Mkj?J ,LR:1`{E?epR~0CЀ' OqDd9( /x wR 5+Lq\mY)e*Xs8 j}Y2&+&-V+~J#0 ھ91se.^ =e[gq`m])NikXS~V}/$muSkܰp!l&n@?6G,0c )T ⑮EvS2cy'~ /,\:t*MunCnႌm=+C?8YZdd2myʪ2aaNAI5^E%uN0}dkoCʏ% 82<5JOU\7}nOm[sYAlVcպ[П>MNF=?n wc@ e7s7rh=c6aܲ #H2AYwuc}^੬ج ʆZPTCl*ؒN)8}B:-Xl0e oۚk\߮Uʯwm*_yeq~ֿvOvMs8j{gيؤ56L 6rߋEs2='ϭN:>骂 O9zcE6W}7-I*q+!j_Y[\|yŶ~`1jnu7L߶Aiq_k{Ҿ9qN99h7CCJ`&m澣6snPX~UM;LھCnc~~yof_6Ai׽mMt׵o~cӿֈ =ks vGb/@àj)3 C_M€A2T!ed@hQ;K7+#o' N+exAV< <1:n6xP$vyQcc(LU{RpųV1BX0!3 M-բ{-hE2]h11)] EN 0y~ ƾ(h|ũ6} eն,T3pfVpb>Ag#~g|/y,I|?N폮}aN;lH0Vp" y0(iL1qf`i j۴w2ka\YbLJ: 2sSk[F SAZha씦 skwo|_e~X52 u24~w1|]s9ВxP\e!8{8[?b9 م83m6lZ%eM3t4Es 8HLJ 4k>6 ږuqN5#Ǧ*e9mP ;rZ 5zSQf<o}6Z!.- 7VUVsz־*9ŇɜÕy`cJ;T\2N:{P菫lhӿ'6[n'A@Gf1V,oȀBp00*VH.î $q2 OGs &Cvd8KcT2qʋ keU߶3eu _2ϧz4,*'A:iO{ZgЮxT# smͻz?rԉGw튿"s~,[oVgZ&vv֭;Z~#/7;dcTUrD䬂 &"+S5 i*Ɠq 4&3^2 B~'M-VW]߶JW[~vwFڪz[S4,*3ܴD#sVuOC?5l8/:1wVy{,JqScqF~:k1܄Au3s ,r|dVv d;Ldй13cEvq30V0֑޷=* ?CAQaeʎ_qENQڇ9Rפ~m}Cڰ{mX;uU^g~0fYtʕ>_͑igL,涵y#{ 6Ǧ3ϒQtkkZlkKnZiel`lxH3Kp~tiwM+k帄gIN5Ŕ^Ŧen]䝎͉m6(0d>(tcU;n2O[y:4Ťwrޖ7ݪqKe#8m.:Q? z'968k d0mt69ߘn.ͤt(3PAGgVPuB=QNWAY" ?Bɚ2P \k!:m~뫎ˡc7n:`>rT`LR,1R)=3 Czמ4R~{Q纡s(rqܡ.9540t/95`14^yqP@z<82ռlȜcȇ hZcpMUV3C*k봼LYio:zիA6al5Gl;n޴W-q@kvbfۆ2SA& ؄E !Y7xfZt Ҳx,jYWf]dlCjjgyW֡ ܶ=ȫz,0b,TÛ(/zSS E|GjU ط[}-ﻖm|l']0Qbxև\yN0IsI/JfYTCcVp7[Be?e5_()N59cN5R-ӡRqa,]bV}kxuw] s=cHYnޝbk` 8=X 킄uSͅmj*]۟ۚCor̭:N@ -r7|("e%<–EtM8`da@{miܪ&qu^=uaUx5.]b$(ov:~Bwj3e9H(=m9JGMr#sNNlk}'Va@w?W;,f밼kwm=pa;~inCvhq" WʱF\i-M ^˸}>lsݭ%'b"0#3sƄE 5>FficPjwO[xPfܬ 1P}Gqȯ9Ec4x3:馜jL;N]i }7)bL->TTC]uuqHRƿy1Sp_G>=&}Gq20r+)E#ťm2yWV׼eUvxAmʩFt6CöUy,9UqF,S޻k]鶭Stږ:4dJC?cəڌ2D&LoǍ2p >pZ/`a ʩmPy˪] dQB4#7T#}mfbi˾ϘVQm~uiZC-yVv8l~.)ߺ&}!@(t'Ü2%Xmo#z4{O.@1d,;O7c͐tk۸a˽~ӟ7VUx׹qe׃Qo8svcvZr;g80E,)*;Aޱ@X Y<6.OH|l"UդO/OzqS9^ 1d/S^u73Tے ?"%V\޵2Jq7Xj9v՛UV j iwo%+MM;laUeepT6R 䪺xw]5Gmjӝ,ʛOcU6\ݵ9%  u饗eMXØkɮ]kB^g>1Mog2i]}ڡU߮wVy{@K.= .w8do5]BٚG>P8Ǘ HClN^7:rt[Uyfmk^st$%p;Fp^C8A>mW/"X;R(dG?z.y9Sr/wpEfc8VBlm`sZ&2{7ښGo5m t~>-r8O쫚w:_ߦ];flhX6?W47|??8lgЉF>a`\t2XMe c yyf嚡w~׎-Lk3cϘ?x=U=bUO(,-S[Ew-(s8j̰~QZcf-Ic,T`$o>/ !mPI;y hޞԧ31MouhgۺEp2U@s<[B駟>Mnҵ}3\7;/U}.w }j1} j|;=!nyIr)$#~-mNyի^=#:Etmkٓ'nhG쑣4DSO=X/>qPC]]}!0w_u{uլosC䪓HVCkh0uICuCYVl"S6Hp=iq8>f8U/|agө<ӈ7s 8rTصnK }k؁[}+}]:n̦~)Lu*hEgXm;g Q4|ͦY ͣ;~;w6h ~?sQ.oιanUW.Vijw:'<CY`pĠRPڞG~i N`GgF} &GmK_Ү̧?nQZ׺†i-~ -,#e]|J'<#(x򗿼 q?] (B`,F!w6!W9:ڷ97 wܗq|f,,{o6}7=Ɖ3_:dGG/zы:ʢت? bNrR֩~DA'Þ<\׾eo:k]AE%@&y&*c`TӟvOatX>?y`aʉowEk+O X_䴵SN9_qv[8rt?Ýs\W0Wa2U:bEb-@e7g2@z8=Mozt%,N`Ӭمuw:Y4Ӽψsv=MɺIk7]~nUqkPobU+/8Q3`7A`!qۤw @֟t|x\1SJ|j~O|}] g=Y=q'8-. ֺXwyqdώN93ͣ6a4w-':I}u U6F'̳ȵ}8%GúûvjQ|3,U:2K/=K>cz8֬aU$:F^nq[tu;utrug> 0YotU&+8X±t-Xk=ݵws/c`ϻVeKNAc'[׽u#?'etMRR\ɺetK8{׻޵[/Դqi3 MIZ!!Oh%'ɕ\SYEOEgv *is`ߢ%Y[dSkcM焹Vן'mَXՆ!:.렯椖#GOlׯ~!./\m'-s5gs)G7xԤʼn^6w]o@@ ౔6J/YkB}NJ>Mvw8I-YɇmQmfh \]>}|ĚMniKCeC>nsC 5Re #{ rݨ[3&1VPl}l)P"ڇ!|\*Tv{D[SH3;W%p7qe& u7-+Ykȴ2 B !/ٱu!oݧc*]dN>jлq:Lw12D9vXG!yDE9)'\}{޳J= N S- >Bb}ps+WzxI6.OAwZ~[$cY;2s[c8{U0vy T}һ]P;7 4)_^rGvR:E NwݺP:Qkީm#)ɾ}i׵YoM/yZG5$D~+ׄ@@C9=ꪫ}0SŚKyŝ'it6k~e2skǗx46'GNNl;m؅5a:w!66 ǚUPܡ8n{cHz׻^FGsơLmN9z"ݽQ4íE ۤwmrr;u0Z17i^W ް0-maX]_pVˢ\U npY[aEf:EX=,{2Iica:ٴ_8.uCםߡu!!! plG̑xٷ 6 kkm;bc6}l ?L۶z}v guFPnA*1ݭƩfڻk t yڅ9;-Om ıf#ˑ#GQXŢ8%AwxqN{PAYq1?=Rhݢgq\vS`&T|3;]yE{^9aAAv-S6ebTI™ۿ=)Ÿ 3ӂ4-8o GZȳ@`eʄD Yy!pQro Y@ L3c9,ь+[AIq8\'vMoUۑ崁600OOS~Go{tȤB-\/\ dSrZewX[N{'= $giA/>a,>.OJ ;)/>g9pqry)ԘSW2apݬӵN9tYN(><)S m{rmO4Vs4Nf7W/c57Rw];4!CˤMҭ4)n/xA'@2}d״e1¤R{g/@ws|=B B 6E[9~sg ͭil4}O~vPfm2Mfiل&:ܯ|+V9)!ӫ٭6k%!^0ys:;llvξMm-s3:/] e!/>˪R08,n7o$8Vawj`F'?y:Q(u 7;対P1Gx> O8.6^I22nS` H cd-oJ9'u+Է}C{vEpZfp6՞>9H7~7:ik4:'[q-kv#;Vh̼կ>!.G[&dqs>fEY(`p3XȰ㸂0 /8u1۞ԧVы^nfB{?3}=p6SM;;8<#ϗ q/':յC9]?iXkƵ17NpTU<&ܓa^sg|V~(~\V[o??@m>F7~2^|0ɡR[GR#k_yl1t C^V{_9(/ƭM}6g:Kl}tile7ucNw:n!J} cbVGmv^-ik/Nyի^mJcM=;0ڦy$]I>d9Wζѕ}zm\׮2D~'!!!6@No2뷾9ZghUgeK{r]9ǜ(++۳r4k':69ͨ[WvGZ3Gq#kA>mkۭM.nh 6K?Iz駟~܆uc[t+No-6EM^9uqu5[vnߟV|yen P~~ WGpbg֑3Q&2l@oPiASsZy@-q8 Oӻ Ʀ: 㪼Xh#v07븞!ש)@` o1Lp`U_xg8:8Փǔ<#CAjw8om_n)G))lڷ#o9㟝eC?nw&7Qn O;iyG_٘@ʤR2 |f07NhGg_EyV@"nG}S?6I`\g7~k&Ez[{xQhO]:yZ%֢ /l/c֙|[}^FZ0O`T6c2v^)G ??y`f'<ū 1 (K(i#t]ϐC[&P;9q*%Ov1 z PZj奻m!w{g 0L@XT.e&ԅ>>ݩ<ݗ菶^{U.*NG]{/-k?u{)xC /vԚڪ~Ȁw[u`I ,Ov Twy7hP,M,v&Fmt~۴9byqi3ˉCUrW32J?7)Nr .Nv//8Ŝy晝 PWcE c/j,?VL)pR;lcqVٕ~SɞE=r9s&/}K}խE] ݲd#_tmGXtImJ=h_6"< qqE;ꎼ@&Yg,V%W E}NΌўUe*絊tJ@eM? mc2MGGgcC&J^uPVrs_w ƅ@̙gܑq3 2nS0F{" <ysVv *#*zMsta,vͲnè?4q&99UU}3ǘְ}9揲sm=PzY'wIGz]t)u1sWaN(HW"ʓC B B`et5t,VH4C gڋڷaSkWZ].l "|Mf)˜HZ'0?_~G=Q]6%̥~'~['6Qu^5T'됏|#;tsO`NҴvl"|ڝ Y# 9:G=h xMihƥ6XB,C 2v^ǭo}nDKHtjpUD#, )@qZ X($;(DR*ۏp~+7STuޖvdU ,q1~X>kgծ(VݿaPuBoՁ1 3iцu)Xiy<)!cM{ݤȸmCP> WpmFhRg7^~:}8pi\^ݢ[8#LЌ81~;`p@W0!iC[_qX@ʥ-N"kN??ɴڂ*2v%trKtot^փJy*UT\Yryk'c͸1p^t{P6D~9>=sFNnc׬`,|oneqqRNq5Cޥ-]%UF=c4݀` s:6z9;,^m0v`Cꛎ<=ʱB0& _eۖt,Two^g\ }U,iCI'Gڂ?NN#S˱fHk1Pƪ};L&B B VMN.e 9Hp:cΎ℈cK(s؋8LIlڅ )rZױ_xF77,l\>f:@7|SaM8&۝/9{ƔSVC[4\|ݚ 'waZW]JxлrX: |[ \: {*?)#vͽo=-O 6 hu5NkZq;S!'h7`qʎ? -'Vy_kCq*'?C䐀1¡6~?hϭw|ZdY䞪 x1?Eލ7S"eҨ_ZA<& k$'gl,k`Ҥĸ#䕗ioieqqҵʢ^5/g2/6 yQiq:69i IʺH\r6&8[ U;f8mX~٤ٞ]{Zk`Ly/gyXK{W^ONl8k3w"޶Qz姺c!@K踹1:f^'vN]yzKwh X\+~Oקt`}1sWtiWƫɺ{qȶl W9ϭkw\YgcƖnd{ե//VضXwOwM{IDATG󔧍qzzzo:0ƹcKc)C]VSwZ:/x!&tlҹ/R.EF϶esBO>mq8&:emlۮ'[O:9v٫Dٝ%fR'kHɋ)K'AMN#62gm^'ukݞwkd+NOkhzn[hG4 H'|r&'M1w˺`Ib(:nŌ,\;1̪dL]Wy^uqmOI7#ɴ1Ptҩu 7ջʽuN]'Mrwʻo&]!n'&4*:NXN{}=rseP9Vj *.Vh o.d^uUNywNdWZ2}~vO?=X3:X:P܌wkzWۗh*ډyͶW9Vg嵃ΩKmS{twlmn~>hVZ\_vq]9__S~KsfngU=5bʡ4+1^[7ˡ_mƓVZ9+ѯ~oiTWZ]l+Z؊qEucj]-WۏSYWcm˱.`\:Yo^MYwj˛4о~>>9lR~YS_=omP[PT߲/ۖ&g'Ym;s2ٰvڕkW[qvM߮Pbc kUOsy*UeѶt1ʠr g41)e޵^nSbqf^xZ^I7Qq`wJsJ~?Sϲ, X+Z0[g˕M6= te_R\EuNk7FKwN_:7욄vHc"O1X~}֧m.}Ud;S>%7~OnQ߸$6OnpJd7hP?ƏSb\?ugtc?o;cLWju rh,Cc4*zgֱ8j<رH1ձ\;mYlth66\Ruc۾L;Ύ4YWգQk? ilߣc;]bmSwڠڑڤ[SR;ԣcڢqnjUhz~ub\-kΡO笳+:P^j8Gjp6ec~N]}m-~<0),L-}b=oump>>tBFr):o[>^T^Iwk:mFʳGjW)ʮ(ʼ춃Hqu/ŝgO=-%_퟉5gWc_jFzZ>j;S۷d0>Ó>߁o}pwZ6_~vS֭j#Sm]ƜvX!׶3ڻmyq.Et$,@M>r}(]P|qNWwF:sEW{)ǎw9<}l)vkӶ3ήjڿڷvyhQ2em&;w Dw}?{>czSgZ mg1}Z7զvnPYl^Vmz=o}퟊kx++ꔼn}j-ժSC͉8>_sʯƾNl[i6'X:Unq5Ԗ_`WR}>c}똺#Gǜ~s_meSc*]榻-L<|ߣNbs5yݘHF edvbl=ϡuv3Ǫwy^cpcڤn{x_,?#px/u8/U*=.3g]ur!K~ca6]罜E#C]HN93d76MzHkj.=$o՝qbrJ|c1>cǻW[}}ξ8gmоqRߘ鶻}#c,Ͷ?[AMC,u=+S޷On6NZgy/>m;g/&ym(ڨu71&5ns?K,\ms۷=ӾYgs63ҭ|zc)}[՝Sƈs-s>:fW @9d91Y?w{tٜfZyYslstgurWz=^˔itVi{חcx9?^~&|DOv @`?nلձt 9~p/ 0MI5},MVmRM @ @0fSWk7V3^|ţ} vž @K.,~vN};ޘ' @$oק;ciOgѯ,k @ @+`b- YwyO~vo_XiH.>zV/E&{w=['@"㏯zՍ7NU\n5R%@ @\k.MQ]@;1*3 @2z}< @뮻|H  @ @(A @ @ @ @`a&,@C @ @ @ kQ @ @ @ @X5 + @ @ @ @,CĚe( @ @ @ @&`b D8 @ @ @ @0f  @ @ @ @ X @ @ @ @2LYF9 @ @ @ @`a&,@C @ @ @ kQ @ @ @ @X5 + @ @ @ @,CĚe( @ @ @ @&`b D8 @ @ @ @0f  @ @ @ @ X @ @ @ @2LYF9 @ @ @ @`a&,@C @ @ @ kQ @ @ @ @X5 + @ @ @ @,CĚe( @ @ @ @&`b D8 @ @ @ @0f  @ @ @ @ X @ @ @ @2LYF9 @ @ @ @`a&,@C @ @ @ kQ @ @ @ @X5 + @ @ @ @,CĚe( @ @ @ @&`b D8 @ @ @ @0f  @ @ @ @ X @ @ @ @2LYF9 @ @ @ @`a&,@C @ @ @ kQ @ @ @ @X5 + @ @ @ @,CĚe( @ @ @ @&`b D8 @ @ @ @0f  @ @ @ @ X @ @ @ @2LYF9 @ @ @ @`ax՛oyQK͛7mۻ6믿Wwx.7_=  @1 @MW?” pQMѯ^t  @ 4GzJU~ @ @SPܬE @ @ @ pL,{ @ @ @ @Lf- @ @ @ @+.`b/`#@ @ @ @'o-VV_}ا @v͛_mX\{C7n~ae pS[- @ >濓 @ @ @ @ )嗑  @ @ @ @.@Yݜ&sIENDB`stargz-snapshotter-0.12.0/docs/images/overview01.png000066400000000000000000017155651426301527400224160ustar00rootroot00000000000000PNG  IHDR[fUgAMA a cHRMz&u0`:pQ< pHYs""*YiTXtXML:com.adobe.xmp 1 L'Y@IDATxw՝'CF(s E u6NXVmVMmnkֆmlA$!"d˷g}zp^^|:wJQ @ @ @ @6?  @ @ @ @7 @ @ @ Q@RAG  @ @ @ @I @ @ @ @,6 @ @ @ @  @ @ @ @@GIYl$@ @ @ @$ @ @ @ @ :H @ @ @H*p @ @ @ @$td @L=5k֔^{]vu^  @ @ :H @W\QoT @ @T> @ @ @ @: H*b# @ @ @ =@ @ @ @tTБF @ @ @ @@R{ @ @ @( # @ @ @ @ @ @ @ Q@RAG  @ @ @ @I @ @ @ @,6 @ @ @ @  @ @ @ @@GIYl$@ @ @ @$ @ @ @ @ :H @ @ @H*p @ @ @ @$td @ @ @T @ @ @ @: H*b# @ @ @ =@ @ @ @tTБF @ @ @ @@R{ @ @ @( # @ @ @ @ @ @ @ Q@RAG  @ @ @ @I @ @ @ @,6 @ @ @ @  @ @ @ @@GIYl$@ @ @ @$ @ @ @ @ :H @ @ @H*p @ @ @ @$td @ @ @T @ @ @ @: H*b# @ @ @ =@ @ @ @tTБF @ @ @ @@R{ @ @ @(iǭ6 @ @ho[>~ioqtMV[mU6l|E @A@RDJH @~W>Ò $W @~Ypa;K/T֬YS%$`m){g9QGU%='@ @ H*蟭  @ 0jI H+/.+V(WFϜ9p#(k\0j'V:}zOZ /[<孷*I0XN;]v٥̘1cijq$y_/O>dyY@`mݶ.mĂ @ 0%$Lˮ @SI nM' s9s< e|k5!$ $IITR_5@OeE=V6lj?%IN-yF.܃Rgu /q @ kd@ @E Ani r}嗫ϯ[:A57kh+'g WIO җ,Sc,38c3UM\5a͚5 & @ WI}U9 @ /x㍾6$WSs93,f2k(gNkw>kuͽOSRҷz$d{ej$驗Ih;\8q{l  @ @DT0 @A A:#eժUK/-/._|q9Co<:iӦU3Bt KPdXVXQ͚qwWӗ$K$^> )sLb-ci`ħ'xeս3O @.neO @z-*?KF!+#;|Jց1#}z5M7 ȲI& ~x5?cɌfe7m{C8̮&@EYCP3i @ @O @` d7߼ /|. pw+`ŊR=#hѢ4pw  @  @(0cƌr9|;eMO~Y~!(Ւo.^{jIZ @ @/9H @vm1SnSO>(+Wlݧ/?G?>䓒QaM6Itb-fmVmsf, OڑO]M{҆]wM?Ӈ|PsX䓒cR[]mK]_Y+/d|{[Iu;_ʶR.iC>݌r\݇ԗOi3ʽ{5?>} )+m6e\O ?g͚U_Nl=P}ݻޗ/ol˧(irҮnWΝOwj>9Ko`;k5`մ~f{ @. = @ 0s2o޼I ]C 7%`׻[^}ՒO>Y%)^ڞ/$:dګ̝;p[{Kq= pU[^x[oU}L[j[ۮ'KE$Vo_vy* 6xߜ;YNW^)NL -}qnV9^K e>[2;_r9餓`@O~:;+4?\tE%?;$A]u$h:+_)w$8,]\q%5X8O &(ӟ1{+\pA1^~OEڒ\pioUB/uN}rsɒ%UbAX3Fyr^|7\=7I.h*I>;,$Ĥ/}KO,K5cH #TI.\<C__% 4Ns<v[;MWZU%<s$ $JFgm%A,XP~_v ތN~5y`؁J@?IBFgl?J"DT~G>2=tK*HL_}y?M`pM'h2A g~J|ԛ6%dDxfXP.*U )Xr?f:4~*##Z u=  @ 0$L @ g[>eF 2AO+m u3 \sM~PJbAS=Ny{iCdp08hulN^~&$InKzF|r>裕AJ$dFДؑzxQGUVw}]i+=R\-iQU?MNꪫL\0%3~f0{,};nl8~ށ g 1܄&ԗDs3;h̚:LbA @,*V @$u߻L9`lY7T\rɨ$L,ƿ뿮FۻqrKPP% $G#ncWՇ=d+jm?knw߽m*"AьJIKfH$č:yiɴp@5DD܋y֓\0~UIP0 x!$PJ6\9o;K$^~Z'A)y?FBA}p#m?  @ @ H*~#@ @GRJ;cݪ)x޽3rYu c nu f-a,u~$ag?J -QiKF8ʶN fiPGQ=?~ωs>od0o2sPJFSVf>3z7ɨn.vکt#0n["/| u{̙3x$pc3>$3#>ݒکn\{S_)߳;ij[fBHRqW%c48,mwG>m*'ůN:蠃Ă$… }9显{9ϒwu:5M J:I2y^6}r̚q9tR_-vgz5hv P#?Sx{@ڒ$N-kr)UnITČLqO/3%ɨ*DF|:y3PN%GR6IH;oFBT$$g4ckTTsYs}R}$(G%K2,]<%[ɗi[Жhs9{1E$~scU6-'dƂ, 1gΜ.$! [Isc<㙭%i.1|`T뙄$<]_kgCGuTտu @: H*b+ @)%KX  ,u@/I-0]O^)V2{@F.^q2_\N>eg]x@ ,(_~"?F= j}{8 CF&^Z\HE]Tm;Χ$93Ψ> u*;C\N=%``w|r,{{oҶ_򕯔vm3#{YNc[6ԁԛ6'v=ofH-jL\~ $`hkjNιvtg2ns2.GydI@M%VuMt۞D$/dwF]f' .w^}Ĩ[bA :üc%>~˗rgVB%/]w];2Z%wkJ$q]x%@4FIRKrYY!r'  @tT @&@W&\FOg߽2Op}_W 6% bo1蛾$u}K 5(׌4n*1H`=on{G߂s28¦̞ДP0`>C &@O]/_udpuƢ$hd$Sd,A?} RiӦUm2OSIBBf+wF~K4: L*HK \C=|g?d&~$c]yOdƎmܨc.3pV]tG|>[FQm=1>gL?IV2׿䆁3$A,,+fHp~6ca޵y> v$&I$-Xg@$j~ @ @F @ 0J4?jMrfۦO-3zR`U͈|:-xTly\ LL:9Ҏ̈hl(%qL=O{6s}$|߬VN8i.0e J2KAi*iKf)<Dm`syNLS3UhGc{^O"WA$t+iV}C=#FSRAfHFmIKf  3l o5N<Į3:$03d9\6,kDK{)1KWqJ; @ @@wIݍA @@@8 $p_EI@Q 2~[I"CFw* $ FICqԑkLn* ~vmK/K֬I8ӪO {4:?d VPfϞ]%h^hQ5ch˹m3L '`$i)+fXO$53CAY >L'\;>Φߓd$ $-+uߖ6ٗ @]@RdG @ Tg /? ʌ M{'R|g-4ktkh}# ;?#(N]M%I_hHiFKg4ySIC 63ٶl$%V&nضY&~LU$u*Ϻ$Zn$yf̘ѱN^Do$%פ[RAG˻'YtI$䱑˱ @lN @c'[$8kIRA$J)ZҞ []Goݛm#p3ew> Oи)0ffbZcQvy*m]9'wq r&Y#^cѧ# Fx9:&#ȓTgLJ }ol$ s6$$x;oS]m{$<]%K V#szM*h2.Iݒr{zhĥc]^%!3=ڶටϝL۲#; @~? @Hp^;o p \r%/?Jֿ<T?灇9 g>\iӽ[MO2jz`YZϯ]N>A( {UQmNUW]UZgqF5|fh^u[^IbDMRAJ[đ rttl' MY_ ^f+Hښ(,3~IywwIYS?nu$\uz&!no83)4is/Gfϩ> @LTuDv @ 0"ų>G]Փ`TΙE #]MPUbAI &iSzY^rJ R=**ӯzr-U m{Q l 2r>K uOH_W vq*ƪ?EySN)7tS ~U@9$$ gSBΗ}35o$ov_r5x#4meUп {,'d/IH۟{ra}>oQ%4͖ޟTжG9xc`x^ 3@!@ @/vڽ.{ @ 0\7o^wߕ|+A >;l eTP%AYTvir9Tݦ}=k?嗿ewQ-[֚P@AT5Ǻ$OOYdORos[suOPe;STDTV, ] KuQ=͐cU%$`?^ \{c{9SbPzI*r u}jWS1_%@ @`r H*W @@w^;wnבuוNH`@VK`n4?9l*9oIGN;Q{(L {^5;EFzg4|/V>"Sdi$t N)K $$4,MBn=;%Md$uw3W$ 7y*I{2 yg&1[P3|$K>d) z @Q`Qc/ @ dLuhrM%2bFcBEL 6۔C9kж='p뮻N8~ 'A"#G$0=jN:W/K_R5ł y晲vjJ^4Ffk.˩'>Y!K6+Q~*`Ս>g}ʶn۸/@’%KJᦒSf+Ȓ# ^mvf{fȱm˨dS[|AƜ%m$=uj[ΙZN?_ @. = @L >S˕W^Yny뭷i ^ RٟY5k@F{{+V(կʳ>^m %Rb9sg]P)K]gڴiUN*O?t… 믿^Ԍ_-%#j~u<ֹVFM{W2[AUV\u$$.YEi3KO$$_nliZHN=[?޸o$όY%)ĀNmIBA/IwӌlyW5̜PKz m;9Q( @ a$lwg%@ 0!LpOl KFw a kKPz߫Vk;M< C=TQ <}jLٝOgM@_2{5\S*HL<$s %XJŋW(mKiXnR) yw46cn=wWI|j*IYlY0<^}ՒO[IR{ 2[A[RAژ9昞g㏫;eIh*i{[ @FO@RY @H𪞭-h|ѢEch`^{UZ;ƌO06A]r$hTvm2{W&s^xaeeNI*u];TLmUNJ,W$ ^Tүᄏkzۖzf+xG[X?rLA@fPוsW$keˬ ڒc3:9m%׳-|JOaVv}-IިEHۮQ[. $țpךs <ꨣ|(y~r5)I<7pCՄo|nyO%Н]@u5TK;ϳ2k֬M`O,W\qE(VWWK_R9@ZI): c=VNrA?%M*u*#uө$H ^F'Ȟ>&VbFc[Cf;__8-wڒOKrIF4M%du3 {R/ViGڐ9WЛڲz' L%??K*P?Vdɒ2gy#{ߒ끹,s9ԑyΒ-{%31m%%I%d`h+I{! DrJ5BO >2wuר/Cg#m%˖-N:]Վ96ć'-7Yh޿m @&p @8أ>6627%$9!i-;2kJ *A$dj>D9~*%$tJ*H;IIȨ'V]Rwv_`_ \gI[**#=#@g>$Do Hf[SI@a8R &!%':~W%ueJ"JyhJdXquI8{J@I>Ka.Xo~;"3Ҟu \oѢE뼣:ՓUEx&$9#߀߀O[o4 @ @ I@I @@o =C%O?}LO /߮'8 $P4}wړ WF^zUoi:.W^ye5:&gyfgbA:Ȭg/H{37d$"t:fp|L2S M2C)IIRAF+'ħc9Zdj]$$ȟ: *2SO-vڐf;wn,$U|z=/Wj~mpAu~ @ @`* H*JW[_  @@Fcr!%An%K<C3r5v&i郁}hLY2*7sC*Om9gQ% L2SwojKF53Kf*HRPsgjH8K{$` K$YF`yz?,guVַU-q>IWR%#\Tgڒ,rVm-gu{XW??O:ڮFI&9+<֓`a P{`ps/lS})A/׿}jN?war)9sNI2AfP{ Ÿ韖yUx) dpi6e~X2[.??f /P>%nj{K3裏M 9Gӟ{ͱ etri[>u ?w}Uիmm?_5\O,n? M8d~ח+VT/؎2k_Z9C{ZVa`=p@%?~,#.{W#uΑ~AU6Y֢[zH26#[3>va3wI>ssz$)$4=OړcsnrW_}zΚNd[9wޕyd̜TGWޯ;S[g/J;gϮ N8ϳgӿ/eﵤ0u]v{Α i#N>!'45o; @j*Z  @L!>r 7~@Z>) $x@pJ\ {キN5ytF w+U36z窀x LDuDΓQ~&0v% A22>n7ˍ7ؘP3}j _IPk˝wY}k0ЯZIz`2m, ceSL/~WK/yrA]SGXuI2AMC nS~T]\s5U;4̧nGO` >N:sJSO=ZŮZS_Z|]'qVK%pγW P!&@ @`" $QH`>(N@z4JՌO': n,ae XKFާO2>r'pZeprD}|K+ 3wWewO.j ױ8/^&Y!Mߒܐ\>wYWρ}k=_jDrfP'#sf&1$״h:e˖'URL~fn ]ґ^-7#3G붴W^)>h3$[2y$D_J~Wĉ$!}"ߍ@FlgdzFO{6CHfhw @ @ H*  @4Y!4Li3ePJUXre4Dq;e dR^IBAf*P @ @U@Rx2E @ $f͚^z,_J.hq9fɒ%n]V"=S+[ 'gm툥Zy|I @MI;4 @\ l7ll˂ ̙39S~j,kY O$%#,\|vەۯ̘1u?_W_}J*Xzukc}f)} @ @ƫr1^v @ 0[oumIફ͛W9䐲;H%X}IRܹsZwxꩧ56 {B @ 6 @T 3}/qs=W}ꝒPOln',+8oڵ,˖-km[nY]$( @ @,xn @ @`,1SfϞ]% ܟ|IɒCI(H.R:묒Y/dK&{& @ 0%$Lˬ @"Yw}]~-^xa93V[mK3Xy普AW"; @ @DBh @C`7f+P~'||%㑖$-lfeڴi#,{n9蠃&l2Ҫ?^{*`խz뭫vi|I @ U @ƕnZ;媫*sOI>>q$a L>J"83ʜ9sJXre'#M`$i.pds @LjI @#vm?tҲhѢ+ʻ[^P` &l';cXΝ[vy껑˱S :I@?"E>[ne={v9묳{a|B @ @ H*X_ @XO`m)GqDɗ SܿޫșŠg|fΜYBl^6L*]v٥i7YVZUjƂN{h֬YfTW\g @ @T05^ @ 0Y ( d @ @ @]`'@ @ @ @# ?j%@ @ @ @^@R:@ @ @ @# ?j%@ @ @ @^@R:@ @ @ @# ?j%@ @ @ @^@R:@ @ @ @# ?j%@ @ @ @^@R:@ @ @ @# ?j%@ @ @ @^@R:@ @ @ @# ?j%@ @ @ @^@R:@ @ @ @# ?j%@ @ @ @^@R:@ @ @ @# ?j%@ @ @ @^@R:@ @ @ @# ?j%@ @ @ @^@R:@ @ @ @# ?j%@ @ @ @^@R:@ @ @ @# ?j%@ @ @ @^@R:@ @ @ @# ?j%@ @ @ @^@R:@ @ @ @# ?j%@ @ @ @^@R:@ @ @ @# ?j%@ @ @ @^@R:@ @ @ @# ?j%@ @ @ @^@R:@ @ @ @# ?j%@ @ @ @^@R:@ @ @ @# ?j%@ @ @ @^@R:@ @ @ @# ?j%@ @ @ @^@R:@ @ @ @# ?j%@ @ @ @^@R:@ @ @ @# ?j%@ @ @ @^@R:@ @ @ @# ?j%@ @ @ @^@R:@ @ @ @# ?j%@ @ @ @^@R:@ @ @ @# ?j%@ @ @ @^@R:@ @ @ @# ?j%@ @ @ @^@R:@ @ @ @# ?j%@ @ @ @^`  @ @LP^xM @MओN*l:ݒT? @ @c'D @Z~2}u: @ @ @ @j3~ @ @6{]k &@ @(pw? i|A @;/_؝Й @ @g3f(Ni|A @ @ @} @ @ @4 H*h @ @ @ { @ @ @ (  @ @ @ @`j H*_  @ @ @ @@F_ @ @ @ @- `j_'@ @ @ @ i|A @ @ @} @ @ @4 H*h @ @ @ { @ @ @ (  @ @ @ @`j H*_  @ @ @ @@F_ @ @ @ @- `j_'@ @ @ @ i|A @ @ @} @ @ @4 H*h @ @ @N=+{_~|O>)oqz;\M6~e @ @ @LIRd~W,YR~^{M7ݴ墋.*zdꮾ @ @ @ 0N&SF"0o˳>[^y* '`ŊUMm @ @ @,0.g*xw˚5kJ ~Urj[lQfj:  @ @ @ 0z*`eeѢEeҥ_~OrA=K&lRv2k֬>ۯ̞=Zg~+[m騉 @ @ @LaqTY LoK$oV7|sHpGN8zev+I>P @ @ @ @ lФO?J& 7Pya//g޼y;N3gΰr @ @ @ , ˖-+?ϪFd kז_khz6y7o]~Umee; @ @ @ @" ^ښPVKle7/ G>(nI"']+`HRBʴiGQOr mx#@ @ @ @I)A 2wYnƎ[ou}ˑGYcN;U$a7ި>Zy'ʫZ0P'$ '+,~mJݯJw[fϷ @ @ @L1O*Hʕ/ N>ryo}gϷeE%K+VldMJ.jIBҴӾ @ @ @ @"0{ |jlʩZͿ7=-7e=2o޼㏗_駟.;Cb-֫ @ @ @ @w1O*wz-hnV.ž VC=|g)K!@ @ @ @F&0Ioˣ>^\'XvquC=8{~ @ @ @ @@-w}{՝w@ @ @ @l1O?-NǞ&`vd>di,ᐙ6l[T vY)Ub>]j"u}O>7VF`cYbOLyܳ3M3O]!@ @ @cT`XVcQV^]^}*x__z\y晲[כۮZ/] 3iGfnXbEYjU`{ M6ev*ӧO/f*lMj{޶v/WAi̙3N m/beoWAmݶofs=vs`oʕ%koM״'ۺ}\PNEIu/mjcu%Y$믿^ӷ9<%׸FC=G?y|뭷C^?O;a߾=76dΙ03oTyL}g<3I @ @ݣ[pJO?t9;}=jP뮻jr]qXF%'P/ſ('tR]]ǟ %|hѢO \v<6-tP3gΜ{ kD^xq*`iǖL.]w]yG> Lw[=zsǟ9G|rM7U?m*;nI/~z^u _uޗؐX~èa|L-[Vyu^կ~U*Y&@I7Y<cdd_?U@<SN9sc=V.\XS[IrP?r1TIINg2Ny󘤩lk*wmrQG>SvV&#  @ @ @Y`̓ 25rYgU#~7IJO&٠[1ϟߘTjx 槏 [c҆x|ye?}J9ՌNAF.y?i5rSҦ$s=I'#ꬷ` ~[ߪugF'H<%}{'TRA, r܇r_sM*{m$%GJsd}ݭ>˾Igg%-Yz_zPX=[$x$n% _/Rɳ}zd @ @ @` iRA8r* ㏯ /P.*@iU͒'٩$hߗ;TO-KGv9+L 7P.䒮 w[p:u%X{W06>>)Ҷ~C~Mufsė84w*$ע.9fxGI=yz衭U$#\}km*J=IW9]tQkFs3z_J x^O"GfϞ]կz @ @ @&'dT 'PM?0@3]z0?G6Sg逌F('ߩ7߼!?I@u<.of-0ئs̆p饗vM(g?Ur$of4 @g?ީ25;PͰ0x{ּg(%A$,Xcu+IZj:! $䡇ZSgiD,wp嗗_e{(垜1cFuzLsL{,I/忬h:P3v+}pN㹌gߘ3fd%1QB};:pS'$.~F l(3sXNhp }@\ 'J] ;O" " " " " " " " " " " " GE8q*_yE#\e_CLmc !:6Q|еmOOY=48hs/-B ΏA20G}h8 qr FcVDu5jइĩs5IL yiӦ"!e]N?,!ap ,!>OE >492 c7{'yʹτ(?!Z{I  5ĉ F5_~}ֱDe_$?n<}u_~/7Bd޼yGd(~aqO<9rEfɕT/& I|ٳgg8n %C?p sP8c8 =xNDA؆޽{}صkTľ+|Ik5ZT7{ԯ_?w50p00`?T҅/=[8~_;=px#Wt(3q,?W7@{cSƍu6CѳgO@x?ְ\D͜gq}-s,'*p u{½}3畯 E 3|wfELQ aKCX$"UVe뮻.x&Q:,zj7bĈAD@D@D@D@D@D@D@D@D@D@D@D6 dz91Vq8!qp'J78qf҃PR82|Ɍ{m]7pC^}%Kz[Y>a„Kg6=׭[͂> cLpz!4tGW2 O.{hL OFr,;|N{DqꅉR`q3d+i1+6,"^`(ʈ֘g3=PZ ԟԡP:#M\6_~q-NW^y?p?OGYzp,\a,X"+a>P ! ˊ+0$~'JC ̈́eӦMDc1$V< ۷7kֲ@UT`Enժ讽Z; ao; ?zn<>|xA'ŁIx(7޸c3L!E8b_|yF8Żt{nGD(D 3P(qzj|sݺ6uEt֩:tHpέ[:4>ܳN:)x=ڵ+U9HD@D@D@D@D@D@D@D@D@D@D@DQ Nu\rp/ۿ}T~[*?fm/dߎ;aY+r'Osq<oݺuʇԧ^v=k_sc|ذaQ6_24:th:AzaxQ #$mȩ$e|s=N/Ad 8neX IHӧ?͙3f&L:N:Ic~7fN:L3!C*ŶR̓ ;wv;>|wqu[@(m۶dY SljVH}W^ye,8}E&)En1¬Y20"S߾},Z(+ Be1 auwuL 5 6&/o9|=tgv7ovgu!GD&" " " " " " " " " " " " )*ۂS]vnڴi80ܹ3;N2a^Ɏ12`"|lɉf+LTrʌb9r-_;73vX>h^~}F  58Sn7cug}T84U#C>} aaG>Q |#@"F.rIv"4?) #*hLcK/uodwy/ \P QX-T(O6"0@¬/}K>s=4~ab/ߩ3<Ӎ9G@t#h>;Ds7W~88CÉMdzd#D(Q6*<駟.)]vя~vsDh 3ϸ aus pn6pb3ă>M⋶"D$?* DLٱz鮺,s}ő:s.`FP=9w^w}% Q=~povc̘1Y#zeחGSիWzȅUHgeZSL&> {n„ IV6zS"dsM]pG /1  pVe#P71@}7~/A=*^bʐ<m6/"T}B&" " " " " " " " " " " "P^T0Ge̘1#+8Nξ}/}K[n*^IKuz37ÁÞk;>)4Di`ѣwz.ӗH8׬Yҥ+H%cg} Tdz襗^PXcQ aDn)⚹s܋ B;wt>ܱE@D@D@D@D@D@D@D@D@D@D@D T;DOcqIsLq 7Psz36;!CA*8qeO/hLJr8vW_ݻw'NLkzGSNu]vuŖ-[~חe`C9B&rŋ }lٲ0+F= D!4 gHTRq~X0<Q`c@֭[yD@D@D@D@D@D@D@D@D@D@D@D@j@'.'!iqÉ1>nCPY~8g -Zq8EHf(38IaA{w^?$ 2ĝuY_6GBߦMԏku" spg"s9~8 6={d{&+Dg{֭Z#̳Vc ݭޚ!Bđ%jCD@D@D@D@D@D@D@D@D@D@D@Dd8qb$6#2M~8uϼ ]2.P;E-J"(4 1'#>;iӦyACRuE8]O>رcGmEsqz-,Xi>9_^lnN`H1l&Y; ֑6&lh,)tĬp$2v|CЪUjGy:$Ne(:#GddOf_'|9"F06|ܸiZkƽa7D RDHbOBL@]_|y%HHAoիWgG+#p1a%~'mM**طo;^xIC .tg: #=v횵/i9+pر#uIa-BE8XUV{r%aG37t<[fg}AMp8 ɓh>hV}z80c({|@IDAT4yVKY4ByQ>@uc޼yogF0zg+#al"#$ z̘1#KˢjWyƸ 5} /؉'lu@XSz#{.!  'isaxo:~dz>;u0q->+e>}k֭KPƍW_S:u /R?G;a;GI2ȳBC450J!5O EyNn̥5 /u!(w[&" " " " " " " " " " " "PLT`IpXwyu?aw)xGo-'s!C5fWXz)"U8qb|WuIr&rN\@pHDt3ʀs}zY7B#G$G<*B>}4)F˖-rl~n֭N: ÁV+_Jٝ뿼 ׏g,q |d;tz:PD@D@D@D@D@D@D@D@D@D@D@D@@ǁwbܴipʏ1‹p&줳eMhtwIyڵk箼JOJD8O~>y6Q˘8 ?jԨ׽{w׳gO~SƉ'< w1NN]9sV ̐\sMٳ'ˡ\;\Moz3s@#h"lLujc4O#@-r Yf-[cܹY⛰,SO“40AHBԑ{?gqGq#(D`8M6F0@=g"+n۶ 2$)0G-B4@a2D{7(+ACC ~x^F}Y.!&wDI ^x/0@p55"<Y|w .e}}:@ 4я3/t']&N0[Li齎/:!M81 8'-[QAp! :^8/3"9{Tԗgx %mvz#<e :̓9N^иq<Rz/*=AJ('C22zy'xcy 7__O-bʥF@} wc3848q&lҍ9WB_}OwO?oy||M'a>zs `Cl#,{^=k1s^yOC N9}_L 3{u3fȈlA9'=͉V`^2Xq/fQJ+}'k3ygڵksSֿ\x/RM!g N\8+Bl?s㽈ʶmۼrcCKYx3QGt 0 Y{X1uyy.-^?3aB.~G`/锟!&~!1AYm&F{m`*}m;/6K䷿m>LLD@D|.r$5,v L/|Th@'5s{(4Ma8<{>pw*⨳X,Ը6$s'%"8s ?SzqT`D9q(|͌YAгgϬP=:cΜ9JaJH\~bTRh/_f͚{cF/B׺u늻mFt1:jJQp+n@" " J@J3UV.ƃ'D=/f}W_u'Ov) Jxgƍ3'/~خ!ݿ[paVr1n8'|''> ?ljQڳg۶m#AA^UW]Ǿ izOr[nxQ ?^ch]D@D@D@D@D@D@D@D@D@D@D@D@DM VO(M6s6oޜ(6lm+dgϞꫯvcǎ-P0H^~e 4d=:ܬe( UѱTt~9ꨣu^-Q96y1<3~wckk칓՘mD5V~Sc" /;e~qr5Gjl5v)S&X]mh]'F "SwӚO+3iϯt" " ' QA*eb_~6n84n0^-?C ,U/^:^{rm۶YAD@D@D@D@D@D@D@D@EG'QSe I':w۩Xf[Z(WFvrt sq:vVZ֭[{gt!׷{n}v1dl˖- o۲eK]k׮~*8ׂ#O !|ɹr9q4~^s8=,v9qqSN:a/̄3k0`@]]Zݡe&ovvҥp7ƨ/pw޽ׂZ۷wDҎKT|;ح\Q`=ѣ;S Fgme̗ORMD@Di ۴٪/}uַ+C`8^Ԍ] $喗D @u^dMa^VvZY" " " " " " " " "P48q^͝;׽)F٧Ow駻Ç{Gt}kǎnnޡjN'38wɚaOg}֋hSG7qDTLP< E]g4y !+uTݻo^&<N8)E(2 &O~~\8)9sg~޹myo=;.z1bs֬Y '(|\ve]G>цߴi?ײe˼!r/ߞ Ny>Ҙ^#}]r%B W_} E\: l" " G@ʻ'5Q"Ԙw4i}֭%j^yY^Bx1cN/xkeC^jf ^~yɘ1cܨQjuI" " " " " " " " &vJ8ۉ聏`ժU~8֫ʋ r 蕍#vƌWw ̟?4:uTOӹ\ʹO+~af͚:GzR p]wyŔ)S|zB#Daڤe:;=4lOh\GQX;'OP8tjs9-eN}X$2pqȧ=3>DRmD3 -"iӦy ;gu멧IҽA@{a9Pr[.EƖ)?x w5hDv" " ! QAy7ۿ?qѼB/K8PBt5PBE4‹#uO2>{mft" " " " " " " " E"@;;=jF;&^h;햴_":M1=?}t cmN~b '=lȟ60ھ+f^!⯻:/^HEDYOA'0 '0!嚯ڬaI9t3':ˆ0;4_|y]K9imƔsQ^Bq?CnaX|HA-<u0ceW_}iʆK/ߎqnp?h}880EwyΜ9=c;Rg'~"Do #rYB?Q(`%<T=8zAK'!՘d" " " " " " " " "Pj8yǨs"j2N<`pzғ9ϱ8%pҳ<%?ywO8QɛMTl-ZE l= ;oe&,}ajapHc8SOmg?p{luSG=pIO(z/n aBq#=!*pmqAx~􄇇 pDF`LT@W\qw[|sҥ?Ç{<(å;G` O/w DJ(!*`S  Gwᇞ0a{#kg5&! zБO9 '(p_G8ԣF +7b\puˆ #~g>Y@@AtU:! QApYD@D@D@D@D@D@D@D@D@D@D3'$==s9zq^z>Zq'2uv >8s`!K?Dcӟ?}8EqDb8@8Cb38?sD?MvyG5Nfϸɓ'^81#wXח_Cv罣?t[~g?{GmGrEk#2OJEȖ!n xg/w'NL?\xnAD\D@D*T(ŋ0ÒH%(K|z0ox&J@>AAO/x3^=ߜ^L}$A߻wo_Nf l{\' iv~D( /U wp7P'm!Eun"H0@8b,a^&h8`(´Db'?(H:v~%fȑ^ֱDKOuuvAjժ ?vԅ7xPsu{쩻8ֈ@0 =׬YS7}p3|ذazcp.d߯ rCYOB -7}C ƍˈ['%̙337uh„ .S>̃  qNZƩϳAT|DEE&I,0 Ik_" " % QAi*w2ao=z! 4,~? c Gv z=r.]P i;q{bйR0BqGS:ӯ_ ACk^2şp_>=$=62}3/ii9|JǼ9^ʗe&rE>;FD@D@ǚT:@54sLI]>Kڲc ,is+)}Ҷ0=kBndz?L+oѳt6 lp[涜m2(&;w\P/\A@ЙڳgOw ':$v·o0|^U!=}3Ss~6/-u ~ k': ~ŏ# pB)ݺus;v*{<:DĈڈfh0-T. *ިd" ͔Aĺm9wK۱kN:~;>8Mev&.[ZYx4l>Tv0-˖ g?jtKcY}tMlǬ,,ۼ-򗉀T?q'9ߴ=F@ m'}}8η9 Tf|Fd]p|' =r䈟[ "rPq>smo޽um߾=䓾mŶ7瘰f3@TN+Mq\TАyk]D@D2HTPAdNmlHf떇Ǐ&>X&/9VL>ixs^rU-bڇ+T8>8 h\aly|3H!۞ *,椷$"ߊ$˖-3ƴ/^慜 [qJOέ[˗9H*݅B_ <ccS<?6Ш˅2ײ@eJ#"PD(a/,rIJζ1O:ҒwT|17Lmcl$x=<2>dB[ftGO,mgv&4|XbnC@&HXZƜt2Lq翷m YNИ we-ZЫ~~ڸqoCkonzj eAC۶m]vJGBjh Qku6>-@s#𷷓v^&K(Йo )):qng?+}Eք iL8֙P 4Dlv>-MT׬|E@D@D@D@D@DDشcߏ|!j^v1j9 ;v7nѢEܮ! $7/߬LٳZݻ6m4XñcǺVZURÔ\D@DHTdu"h>B?/qq韴3s֙Bao7D@DR bFpmL|;4YOjk58(|/囌F mzw|邏N[[ox:A߮:ur tB=կ~fϞ>Zᯃot3I&/ ^{/KpSנ' QA~>+͚^"&  8zBQlN8LD@D0, Q6'q&He"$ӛh&D9 ,ۺ-}C˭D@D@D@D@D@D $?b[n%*!yƱ :hd\3… }'" a=Mzs]3irca;fI T⵴l2+LD@D6HTPQW!01/rPuY1eb#G8& s~(ۡ" "P'؋FBD 1}ƶ&4l2*@" ?pXIyݻ}!߿߷]fV- 2[sq\ (, B+'C0` ߹Q'ht#D<(VJ!RJr%K:[>&`f/B 1 &@X&2TL`<߄VzViE@D@D@D@D9uVcǎY a ֭[C ^u_0}r-2}Ү2ر#CP@ÇveY_>{}':VOw&8+{쩫< PK@y HTP^:H&@K,Aln >q*J'@&]Lk4!2ϦplP% D{,[gi׮]ݪU|2"}§1,Xgl^ڶUۻ+:U6h$*hw^$%7.0s^KlnsLD@D@D?4 heh7!B0B(80mӔCiD@D@D@D@D@Q܊+DW^qS}AJݸq\֭.uD8EEܐ!Cܰaut߰aCݰ|?ދ 4WzX.J{h},M\i#gMw>zꩧ9O<߿ܹsrߙgQv/C) ɸ_nTID@Dd简"P BlCa21&`6!tע(" "м g&‘g4!8@d`k+duabʤ" " " " " 9r3g᠞9sw3Ingݎgv?qݻw Qcև!0xGA _r{yl=q҆W sBQL]&!.4:Q#B[záVR|p8uk׮p<#̯Z ~P&Mbk߾Dh(tqС/]=S/wD[V݀kӺ@HTP>:sр eM,`L8 *@D #4N4nsD?04ő'" " " " "L'6NYp>~ȁɓ';zg7B`0_to߆c49s[fw|%KSNbz=~; GΝ;={Y=鷶kEyܹ>=u"?C 8L& l2߫eF!Vxw`Ϝ>Aẩ/T#{n>Oy>%-ߔAV8~Exbcwmwe8q="\p\=:`8Z E@D HTPwOe//%| 0` K^zxYe6[GPP/AUbOp~m?rG hPApNl#*D ,A>'" " " " FN9߃ق |oۻ.]wݻw{9IߪUqӞa.0(B5h۶MOn&w8ͱN8fsUkA X;C6 8 0C[։)`p{VGCCK0|`|kN;4gmV$A#`CCxXъȣԢ·p^sD(C}v" GlBYy6؏!:9}=Bl>>Deů/h0·€p2/ER^" " " "H#> J=4:m!N hXce 0ȣqE<u vQC]fEq]uE/i.zsы.ʦ}vE9lqŀ)pE]D, FIW""`1p}ӟJ^^۶m:taIBk vDO,N>@vXS8˭M1M63#ːLi3l*FK_;=cVV~0ō15\~œh]D@DHTP7AE/&@  XGy@5&^Dl} Y)D@D@D@* F`O dit#2S(8u5UR5hT,nέXᢁ]ԽE/j - rEqɝ͹hlqu9wsg⽻(}U\n!:Moj ~wyǦ&L#ӱ(kҗ_~G"]\0")L2%2*թ`_/ +Jȑ#7₤$V", \ w@pYMtاz.Ixz]fB: R?<.Лl *xy6EDYm4zO_ ! '3h h4!>`qɄ48Ȁ9 tNcoj@ @x|܌͛)5Be_> Wk–{'Ovs> .H,>aol2+~kE%q~]8ć úӛc=j(HB} 8/λ9! ߷o_.^u2TžHBV=h 3=mw "C&8Q^x_&. /D%`ȂsC0k,/xڅ#W8nj aۿ/h !HC3N}Lʁ5`ÝJc:uS RYjZ[]> 9/5MdzBĂ3gƂrS^&U($#Z ufEtނKzNeL R L4n2rbc2%GI|v{y|?%mapmIl_}s2/ۺ-͋ylӲ\:4IF/Lf[ޖ8I#hӐCC&#8`}44d:@S뮻ӧO87|[<oD/'@ݻxQ oy?1%fQ\oy?@V1hszNipټP?NU~hލscSʍH| }O*hE(d2iED%T?p}8qtjo9>8a^۷Ih8͘kNs?3+k1a9ajlUy2&>'C| ʎ7ou˛{I$ 9{ăt ,_OD@D xN`h  sѲ/s&@@ LRhps, G(ग़ALlɶLaZKglp>˜ y煞0-LVVҲ}?|`1Dz;~m9LǶf!DZLfI4c;{\ƽ9>l92i=9i8qmc)?vspgiyL&CYm2s0=>lA,9-;쾆?@3 »SرT&z0a7V~3!#}D~;_:.nx|;,TYKػn2q0ŝ:uS${9SC2HhGc8r<L}(|wCHT50: -gx|Lb[c3_yV$ G!V}*$oh ƭYȄp'(&#`nf N_3a/f붟9 8Q![pqćYo8T0 qH,p֐kJINv,Ϫ-s.&<#6A=sO2mƘe3h$%BXw'o7@ܢEzG]}W^qnu箼EQ4LD@D@D@D@M@b',NQ[qBX)--Í2$8'NRHljTaD|>}2d6lXC>DF0v[ĠqmD\ ϶tu۱M&%@=`BOxfDaBp`Dṋ㙖@IDn2n(ڧ]g>"%\D@D@D@D@*DwKJSP<@8!Yf={x8[q8D̩9m9 q8X \4& $~ۛzO i\K.]cQKHOX1T=pqHb;ΑW2+ C^ z4x7с-b[FL1>s&Y'-LR#@IDAT.1t:+D2G70<-4m.P",t8]x@)." " " " A@ -D4eD#NT&D/X\XX8mL& } G d=me)?"b 2!˵Ċ=AHɽX{̄1fQ{l2 L`lr̺LC;vSH  4`bPl`D@D@D@D "r͛"EnC͹#=P앙DŠX0.28Ba-3[&" "P}p2!wIm31hPl`pβV7 Kw uoSW 0@h@lD,V'!=-xܓO: Y62 ,5D@D@D@D@DHTPiw$GyGLd! ߾{nʜih> L - L`u~뒮PD@I7L [S!09L0Y61" לshxBJdƻ G:uP/M&@3%}s PNs;,ZvQrF,zCBR [i4G,@(XLWh i -&tΜ:msX N9T*DrcJkD)0!KD u:˶ P/x޼y cFz6!6hժ}w* `4f@˖us/܀.RG<*$*`c1<` @DPU'&7GHg !m9> d" " "М #Lmڴf?!2x?fb#ش}G`Pl`ۘ3!>^eO=ڷo/_CD'ޭtuOx2uLC'" " "P\H~E oͷ1=s]ѻLD@D@D@D@*DMx'hȴ^s۷GQֶyeOXTIL@kbX*" " KNB;"~lGjذ IzOMF0y?^ڟi |ɮ{Okƻie" " " UJ`^VR$U۰]Z`UlZ$ QA *MHp!5 e!',4XelBL`9VYW0d;" 4@`2~ B 8e!{ x#F׮]ԭ[(*q{&LD@D@DJ (uQ41'0Ùgj)*Uz@8] FH "زe@V^8M@/ L28f": >" " "Pi>hD|fzǛeLĩCHw]vi>sf` jЪUhTS{/Q*)J]#m\D@D@D@D@N@qm]4 `^D@ci ":@c.=lL\Dd-[zaY +m۶Q t4w4b%V [6c"!CNtχXUS=g"VHW&@`n"&2ནkb"JG{n:?qDs2TB= ڵkE uȀy JwOD $ttH$r5$" " " " J@wDƍݦM|%Xlm `HW\:tԃ|gPW]uo4.Vs)VbU -wjԩ֟R稟+#Q\gd) e(w &z'  <}ta $އH{!>9,Ѷd!)x@$^z={;[ ]@ ^+\%`e," " " "P $*s@`~NS' * E K@Zp`ֻBszO:餢 ,%v+f%zNp&:`@wlјdEDL *"ޅe" " " MD j^xdRU%(@ 4""^_[lqv Eq0<dؚ6\,AC`b;0#KFJĒQ$nV8 bΑXU-=_) ΋xs6сE;0s6jm b ?^.C%G2A-{oasP{D@D@DrR̪.WD@D@D@D E[6)n"zj޽ۭ]֋րSYS,Ӱ0QYn߾}60- c4Rb; twJJ`:S1~,Α|Xʼn^l{i~PpeaYVރ۵kݻh[&2}:3+d뮻wO̝Z+ or->oo馒GLW^({9ƏX" " " " "P:nc]3fcue`Bfosp1maڶmC8:"5JRΑXUzSJԿW:Gz%Vb@Z)nD#Ä&:`ȑ#u"pwqĆl0E4{lm w x^D@D@D@A Ӭҁ#009 Lf 0x`׻woN {MDW/tIA<1jH%@D@D@D@DfԔ\0݊+믿Ck䅄`L@1wM&f_  ̉tHآ,6{g~:Czl|"yV4HFĄeD2 U/*׎E$ ˽>Vq9 :t'Ynݺu4\4LD@D@D@D@@ਣr8Ǚyo^P&z볌D7a}d?h 6  1EZ@uꢱ\QXr^*<*@Պ >\.K.u6mjvC8hP<@癩;N:Չ(}j!.=zʍ؀޽ k."b;[hx4h>|ٳBx-#е@z;4E<P?)O?#C4LD@D@D@D@*@U ? uM"&ի}JZЃ M42tL@! %Z$"bQ h X> iAݜ9sܼyȑ#}O>9ʋ5@&p=򈋔. wT^LqQC_yˡ@HC Xkгs>s47tDTbΑXuVUËbGw.tu.V3be$ꟗU6mӐ!C|!)D40 9!6@h-v᧙3gaƌo|Ut*N 6ȝws;o_SOu㊙hQ"Xva;ԢtE Fc}K.uAZB["0j1MHOSJ`:PKCBP@oPBvpx鈊U:N++D^t{'ۈ>Ν;|Ϟ=~XGQ+u-[1|cǎ#p[Fc8BX+t" " 5Lꫝ{UfrQϜ\h$t__gzTA S$lNx-=hؤEPн{w7j(1P $WWZ{qb t$*KVJM$mm*V 5XED9p6fkX"]R鬈֭[7?5 $y~Bl@!0@{G3^‚; Ud" " ",Dmr\E㭺}^vNMs]!ٴD@D@D@D@D %а4n-X jeKEDC ' ƍKFC B˜^6#=Q+=@-=g~w7U7ltt*'RXXlz1 :omٲ?HZwq/[p&93\0k>D@D@D \rs68_(s;@`0BMrJDD@D@D@D@BLL0o޼(,6Z*☣: DC1JТnX嘶mD:`ux*?pX4/U~>Zb<m׮]xEY&ΑXD*+^% Kvzspذa!0͛}T5 >\7ddaC 8M8ы NA\ &s+_qQ#sܾ}g)P4b]@ul]o{Wa7_mf""01)⁞={9NRN6N*"" &ӦMpoD8!A h**)Q8 '$իWєQ<,s )S[v{"MeZz99h9ƍd8prnUc(Ze^]G&" " " " N /ݻw^{w}nMKC""A=}fرe" " " " " "PXV^p޿{fɒ%3Q В[wf˖-ꫯ6Ç7D_@".+R& ܹ6o-}.|NgN?t'$߿Pwfa0Gayf3k4(7b HJU6pa}vN"Dݷ+x"lx".VD)WKm9q_uUnywDǎK]]y\Z3~xLD@D@ʎ@ $*( @LTS?n~60>"j9>w}Yn5/}t51;GuiG <馛eQiXLt~$g N߷0̚5.)~VXLt~$gҥK믿0a:uj:t=s9Ed#+WO> 2` e DL8zhAsk]~,;ALD@D@ E:sQE u@D"*سgy' *( |hݝ`̘1w$Zmi&Ҥ[<0@Huxl<*'+R 0{9{P1#xDS|UFbOralNOGu߮<-'gJ'Xi,V-VD`y4uΩ{6/v1$&-H[ocǎӍ3q}u $ OLL^:Q^΃H呈߉L}#VȜX$7btw߮-l<^tEn+V%6ol ȇ.ϊ<|N|씱S:ElYv/iߟ7D&@LPSSc.R7ۅH2prvgqƙիW_~ټqs\@^{\}զK. D@D@D d}(&" " " "P֚G}Ԭ_>/$Æ 3]v9yADE@D@D@D@D@*FmFaVZe^z%;-[ĖZa׿CS L_i@A (RAAq+3!exzRΝ;;1iӜ@3ZYT(cƌ1z9s_xb}vsH|GW_ux4m%&" " 'i(RA2@V]v~ .X`РAfʔ)s0`@+-mۚ &#G7xÉ V\iْ,h"!_~lxamڴ1:t[F"C#Gr}1# mJt&o:?1Cޮ]MrֻVQUj>_*H#gJ'k9©γ+<f…n9\L+V"i\ij! SxL" " " " "D,} /D79g76z͚5k\4ƍ { Ǻuڵk) UqG8Xb[XJ8?>#i&tb'3=4Do#Q ljJ_%o˖-3v2,pꩧ~%!_ŔµǛnݺ]^z޽;5:1 s_" " %B^;j ̙i":-ʹ Fao3<3edg+jQAؼy3x=:#6fHGw ljJ_%o F1˳SN p</bXU4R.'Vٰaӧ[-jQL1x`su׹g}ٳ'O'|DBXYD@DڬT (ME':@S 4|_XPrg ℨJ2sԃwZy*V8Xm_||~QṉXq]'ZNX"~e.q5ŜQfΜn{ "le\HC&" " %G>elT12 " " " "P\B }\zg|*-,y %| D*f0M~}͊GTMNHI:X`S9"8 ߣ Gt#]j]qb++W˶ w|,aYbσg3f;w GfY[cm^x˺PQD@D@6RDaj"&PeC7)k[K3!wIȝ/yڵkNpfΜ9 /: ?s5;FrK2R1w͝wwMD@gw _o7یQ~m)" " " " &D{\Y@A3e.FaRT@ R&Mm3d" " "PrlQ:5b1UnNWfjk( Тf2[#J9c mOD@D@D@D@D@D 7n[.b[*ͭ[>0>" " "Pp (?LPD@D@D@D zNIQ>D4[nM~(V7D]֭[Q PE*P" " " " #PHa3tb@X6m"L<;慕ń3 ljJhQ[m޽{kVԬGxb%VQU嵫Ν;v<Em-;fڷouul3˙D@D@D@D@D T5X S L_uUa6M ?z qu!/rȃCuAIV%bIŠb]]}:⻨v[MwJo!VDwJoQ.=jpc,$jaAKҳgOss:f-b̙ͮ'8_Iw͝w2wܑL@v~3c}c6l7a ~m)" " " " &н{wl QHd{o0}5 ^;W.^y0z(`IYR *VA?Uj>_˅|#rR|UFbOraE6^B恀aNLD@D@*@,)RA6U\D@D@D@ʉ]FA;v4RP(GdCrH$`'dlfvB(x_1 `6nhcXT&" " " " " "PX,ဠH2Xv?~~3Q" Yz9|ش -6 v9X+i"l` jxh&5se޽{at1ú2("<СC':Fx3x@Pyٳga D ̝;𒉀'[mվfP3f_2G@hQQ02l0'v}mb;-Y~G[n5zf͚5f۶mfn0%˧E@D@D@D@*"c̈g#GN;Ԙ^z)Y 2>tرڵkrV6" " "PzӐa1ł E@D@D@D@D Z\T@@dΙݿ,%J7Q"^u׿-E$&g6͛7~ 83fK̄ L߾}_HeD.ÈN(r9cU"⩋/8ԶHDe ڲŴ_gcX]X߯9מC۷! x8ShߏLD@Z:{?G!CZ䣑. Q/jNEe]klD=s n߾ݼs'Oh/z\4-Df2cǎUԂlg"dN!`%%!̙3𒉀4;ЅqfƝw5>PD@ʅ]we9 7`nv\z__8ҿۿ9tդ%.*Q'j5nnذ}GMaЋ\uD_!`J12 x'̓O>?|DE{ ٳg / D7x^QM-M= unuٙ+JJKD@D@ʆ@6ku^&" " " " ND(\Ç7wK" 0 x'4Ś5k܀͸qny^? N"=zt$4KL0G1O?D]D@D@D@D@pD ӺukG"!(:t{l2k.ӥKsꩧQھ}ի Ν;]48Xݻ:*-&`@)(ZQڹsgޔPp‚>ZBbFRԢ,5Җ,YʃbEty0@(^{5 |ӻ@Iضmy衇z &&GiD؆8Q|payN:ufDKi@YP>@rE/*Eg /"`DhcgFR?≨z7xCA+=ؽ{yM^LϞ=};  6u-O@^Q/@y+"- _D@D@D@D@Z[W^y|_͸=jCD0~x(wy!QQ͔)S̘1cyQWC鉀@c HTPmC52'Pr555űa Qv!7x̛\ɃYOeڥe.Hy={͊}X#Djh߾VQ@g.OR{Mkך5k֘Yf6?hР\H//QpO\y }SNI[m " " "L)RPi{(&%'*` 0Dp3FQj $ "`qĉsu3 2'D̮߼yYl]rs'˗{~L>=/ˉ%˷'b3@T@7w1We&YdZ /" " " "HExt?;wA6B~m6KfTygSc#AD@D@D@D@Dӏ K='pqQi&OOY}: >~2<fUE*P" " " " $PV0bwlŋ'L*AA2B^D@D@D@D?a^0 fŊEA!Ƽh;vԒe W&fVD@D@D@D@@Y DР~-K.uFgfo}) $Q j %'(" " " "P8 ƎjҌ}0ȠܳCmۂN̒@1|ܸq&\,wV.3]u?K+"AeCD@Dd (RA:\D@D@D@D 7%*bPW^+D؀0lӵkג^&8pW-;4hPK ܨg6g[6mLΝwhiF*-}䔿@*VTFݻǾBTG1`F?2];-r| lj*M&" " " " D;‚T=;R2D#โe{㾜d" " " EHQAめ|HEPZ\T@z83gtqB>FAXQ^| 1X>| 2p 0^pgvmh"M76RJ(mQߧ۷,]Ay?jԨ#mܸlذ1`g_'1L {}{njc:Q&LpD@D@D@D@D@~SæNHxg h=\6aK(>a]!Sm#JD&"Pd쵿*"5إU#cՇE@@𞿘ʥ@9sb͇`رc͖Kn>3RanJvԩLOB" " " " @IDAT"=5ݻs=9t)V'38MoGHX)*D xp7{f Y%4â?D$Nd'ZU^h"ʑzafKѺukӳgfE$A&DT%V%;g >@`/=BPWW %r'B`~GN,F ط~" A (H.ZDqU ȑ@PD5_%0v(zE!*(zJ!&7" d"4``\&gСCnc _20H޽{OP[@:/l@8cNJ6" 'a1۶eo{1}f6(o͘c‚?Gb  @2PnR<C.JݨATD@D@D@D@K߿>|;#D# K$D@D r 3h@ȩ+AhqUVP@&"P|]Yxq|MVX<'/[lܸRUW̌?kРA.{*rMʋDEz< kEL$4`F)'ARE@D@D@D@D 0}1"oCR-cPT?r`&ls;?(Rk֬1?裏R |O$*2d5j(9D@ʗD%tl N: р۷P UTȜĻwv@@=4~-" &DMD鋀;WyE w5/CFD୷2/vLD((-(2G& " 2qbc^&@`Ĉ泟رcex!*u>+Ve˖gXڸ+W{-ھ'w(%'*;ީ=\suEz4 … M۶mofΜi=e" " " " "Pp?/v8FYWfb<3ffРA+0~zY8AA9)%&" " " " EBYǚDHDpٛK_'L`t钴Ȝk֬1yꩧ࠰7ꪫL퓦D@J@ɉ >{﹋)9B3ԃO555׃E*(28T0zKPA䃨9j#r#j.JOʁ`]sሪ" " H/ߙw~8hLu._ڵ3cƌ15֧ńxE05&K.DE"P&JNTaXP4j+T{vsMKݣNV鉀@Q"=zD^.fMN"σCQ"PF$*(Tff G KGbyQaŁR+ϱW^yy뭷̢E bo֭3{q˗sD@ʃ@ɉ p~R(T&Mr䗏zC1&r&Z7p8p`ܹ[|FXQ~U(@9@t?yd 7B|o nj[&w%Pr56 |Y>|/O2(gݺus#QN?|$KC D+ ( |~%J"pꩧ:M|l%38(;`sxƲ uA7!cǎn2o'Q۶m3{u/ 'oǎfn[L(+Ù@u+gsXpV" b# O>+r$ХKtΗ๐k֬1ӟ9~ӦMXXpB+ZQ>Ϙqƥ@ޏ>$P^={7n4/6˗/7~i8A  "_xᅦk׮D HTPPD%h/w^HtwHLJSu{1k?lvV2/BLo_ 2V[oCŲ QR 6ol^z%{~,]Hswu.Zȉyl>0o'\y.JEAB\$*(:DN (:X;9#?Jlذn2>`{:m͹<'ن⿋762 }Y!LX~(\s5fС%M@>|*@]]y](6mژN;\tEɬS"?@*@ ڵ˅۴iС?~A-jsUS x$F[yq{ D`&ÇUgϞWF@P0o<ڲeKLߣG/H s1;wnF@).y ʕ+T tWT@,@I`;»qM k] EQ|2=PZL}&m ~_-" "1bpԆt=RgꏛmMUNTJݮٯThLɷTlǏmcnfiUnmꡳ}Qv srmkZIMyUӃsOݣcMr`TߧIJ?@YxWŋ cAf[v>kn%m#G:˜r)n| ;/7lѣG/lO&l [ Re""PLϻ5(WN*VM'2U }.mۚ@yzjDt0yd3qDI$'k2GӵT7 *ppiؿ4;jw1"vI3k8~4n73Tut{~ٳ݌$LD@I~PV{ӧAmo .4 ~[o5rQA ^ [tۅi" " " " " "P.dGxcǎƣƘ~^۷o7ڸqcmY`֬YQJ}$-4hI96l={~S+%*@^Z\T zȁd3g6SDA\@ O;#:n.Pk֬qfg`p3EË СC{ 飨\w lc6˅ФQ_cǎ:fq[bE4b3a„f&G}i޽{c߿B(coa~DSLqN;څLD@E,q;̸qbYW@ɶ(Bi\#a@}{2<+"ԧ"A 31cƸD%((-UmcSe6BAs ƽeE4n}.}G:tU6tK#+⯀<emlZ,!agFWunlvjˬ\[ХmeU\D;;Vyڕ%Ov0quGsmpzAAs.ij_(FbyӮ݉(Xyo'? ;/G}"RMre /_<& 2CcK@ pΝ;1Q#8rɃN۳b/7bl2`ơ~zwBTp!P/*fa8 DLxӦMNT@\ ֏8+%WX" " " " " " "PHg駟&z2/VP@KB8_NIEt2C\gڋYbtp30ACַo_zDZ+F@-:[n%LrA#3y58Y0f\d쏣8h\y%3$N>Ba(PʈP?ٶm[,[Y>*9.@eQ.V ,|iW:2zH<{&>@cB>xf9]K@9=5ڈ8묳\%5'Kᘍ"t |q|,{;WjXoIo9e\isQKƐD&tCWBU2eA4nm8ܸ,_6Vf6^3|vqGبScv83;_y":ڕè/ڕ~?Z26X}q/@/BX0F.OI|>rA}< 9sbCTP.y $pK?5/BåZ>ʭ-Aq"(ؽ{[jq3񔢂Xk@cgSҿhR>rEZ[b4C#F$9ﹸ=1<>OIDR_ 8\'L`8 '-rVrB`wr-/p֊/; ){m." Qؾ}yc".p.(!Pm!a?GB8<KQsQz"P aw5lZu3\~ޱu7X vjր.`V,1t&,g0n ȣSPջp.!q.!H^#Z;F࠺C7P:-L՝mOmoLء<6Raa%ך&~ݦk] d"  0I? ;0z~M̩ZV‚Nނx! =7"PZZ\TBRyd-D= ل-yiotk032,2:cD +Vp3*z-2L~ue[l[.,+I ptMnT7npγF[Á)D98YOȑ#hndĜ~Ă2袋`n :v" "_ve…nSocyYe(6@=5~&_\+eM[Uqчr{nloN+!Oh0upBh;q6x(ʞjWQh~nΏ'7"  0_f&1^3+W4>smmyiRʗeq7ީ *8%*(C^q+ QAQW]{_ӧOw.,eD&gSL1ofޗ  >!_G6a\01cs^M6 č 96 7.?b溺:',l,@ 06h D6555TGիW;Q\@;fc]tqQcr?,jj-]@Xn{.F 0n&`Ǐl \0vIƿ]GӸMFò\8j)tq9RmSl">t2>1l[Vm v!Y 1N=cvVlPyڕԗOdy ުmԘvOF9gE$%oiƂɝD#`b 7(W@8tT5TΓ?nwg9県ʎ pH׭( ܘO=T\9r/r' sb%'リGlTb`ӦMW_uכo E" eM7}1" .X%KC3/blփ#rM C 2X@U&'-0BHYv[(^ëMC;+Bh rl8|І oB+!DV{:u`Y2^,"Xkսcmg+J"kU,!q`;.U:bvÞ KRykw..Gކ o϶9 LLݻ[zҥe3\s3sƭD( x:*PJG)`p[!@FHAe{G:uj3lnjzpУGͶLi a. D >{"ZAXXLc;vD+a ²g0 : 8,q$2 ܼt2NlrD\Bԋ+EzN{v5Q;ۏ5(K"j҄?P@y9~|ܴݻիW/9[菤bwGB; .=#Ip!jal pЕ`CѶ#DŽ' 6|lØqsQo!G S*o3Vr9F'-BQnݺl3+Ӧ,!Cs(+1¡O /МP@ŋjRس>;ڡ昮[ 8i?H0tn9xc]&wѷN.ѷwTSn2o۵kev<3K>@&k[)@Q $6CŅgf1{MVyPo^VH; V!AM{iU4XPԮrsNΏ|DE;c°=e"  Z^e3‚QF1rTJ" B@b9Vϟo,Y9Yf/!pL1̐ !N jfhaNd>sؽK\d O6 e% ndqaMNW^y9 EDo'o_|~sm񛺵>Cs=8֯~Nsfs,'N(qy7sQnzwB|&!^ذh"ыC ,>EL0σt}]׶q>7F;3c1rHsm'.gqguPL)SVzcƌqП{&Rs@}z-_16lceٲeNDSQ?,; &*mv 5k8{l41Β*AC(p< qرc]>(뗱 "޼Ci_8ħ_H0?'4"?Ol}s=߽'t'=;1 0@x2s̔'';bf/OӮ@hsl,7D7>a ;B yNfaYy6fs8r<7;;sx7&~_5ꆓ[oM\~~.!>Z>O'/ҽ0l*Zm lND@D 6~]v6s'lN]MU{P=ráZoyޱaךs77GUgy85XS]c8H;vNt~Â++{ClcvwM״~:t@+Xclذa3D"2j1Q:ND0$*( g ~̜ YƙLR "b=  fO09 1O?JTMGp/ NfƩ7ꀓ> &{XM xfe&>,-Aقw7ϝ;w}nF97N38X3kY{sr”+P!Ïc??NˉSg;\D8gi/8 +O:ĽۛBiw*~ĖM0 eF˾~_S-pSiY8EO=p s#ÞeN1s=jXsY8A cϹW_?c4ɖPڅ`89`;1NX&I Bā`'i6 k5ǁ~ ï~+&a^\h3lCpw,ؗ2Poi;#;7 ҂' S~ N?ET; Dv`LLfÐ~719Ox{|E.p?Fq,xIr^` k׮u&U7bG1D{o.OrE!O&(ڀD@D@JǷ9vΨ.9@u@y6@ ŠUn}*#F_3VnmMf?\Tf|wɗEPbuM*9$~>{d4OM_dM1#"0FTHq+&1n1maH&"P|N~r,26+fΆ`TB;8"Qɠo2>a/6\pbg`pȱΑ/O<npR`8qX4K7cRA&-3As188іI7>Dŵf g2Xc}ryʊ;sYo8# ?aq3vŊ p<);_9[qV12#| ]D7 <Kd39 )ch.\TgE](y, Ki\#H1G,A\?5!FAHAp@ߴcf\?tg? }8}97e Lq#wf,V~g;C"f ˜Hd \[P$#Bb ";g:u#EAcyvczpLӆɓ:Ӯ8><0'9lj}8pв\QDdx"LJf8_z% HDg״iӜs!/`.8O F{#""G(9F3D?+Bssѓy]SSD,A}*墜Rho>yJY|=8 p> T^Od26I~Qnyc$|ij6/O]yȍBֹ N _oֹ]L̘gnּ !nXQPyg dZz6@1r Bqg YAC[{<9I3c!vvsv:?w5]5Eq}{Lp?0ف0YƳ-d#K7Vb8)9%fjٌdJ'Prvf@ Yt8p0h_lF~W\ppKg8 pqJո23gU:%[΀) 9Sr֬Y4}{3@Z8/#]FFZ8e"QڞYFy?cԂr8~:uqpnFsW;2{8Po1CcЄp(s=)˾/>Q¸q\MzoA8뮘sldn87 -f?s덲rËߝ 94#* X@P7D8i1Lc>aiL'ϜۨqήZ'88\kwDHtOG96lc!)jeD*^XЀÊ syІ Na i{"88渶 H?r6'_BfԓL>eΜ9G3~\}}" . p\G|ίD..s{-~7u?;}73pF1_O \y>߃;O/'}qo9'aBۀ3m?8' #}طDi^G?֢jaz;p#JO (CQ2QZ" )no;:am @}> O*7@`Ю 4 ;m:Z!NJc]怐miB@a{vy'C C=lDUޖiGʮ]eya=? q!VjW:?ԗOܿU:v){ gd" !1?0n1xQ0%gƵC@]Q$f|8 SSSQ3-6ás fqLg2SqL)8 |A\i80 ' ',ND;q_?Np }Sv`'{ϔE00̖=F?׷ mp!(WjoY}x1k_sV'3}k>4-He!|~sh_cTYS8Ps|3fpg&\sshrKfQsafFޤ1H΅LxwsҷS2 7/̞=Y]rюs8_#/ r qczPV0 hS'Ovmq|y1A LJA]P1f"H D򡺦 xvQpXF`yACJf=Bp k<"cpz<&;U)X!NJ8' 86A#?{ r|mtuiɔ #2ǘ kQyFѣL֥E|8'6-xoo&6}@'eE 2Wt6t,Q[`z0tDUs UY0ȍ. rP 23<Pp,8f֏:D|Y3{8QHflW/*d;n} J%}8gr1n.X+ϰ̱NLQٰpG8uǨ'NLfs Tcx8~8mh?8qN_XZ#^g#of cGZrsF^H:3yR~e1c:^TobZ*ay~7ִ 8[Syf-eAؓ.S<;v1D8yPk$q9 ,d/*`/}~7Ha(}L=$q:$?v d">rH#.@P)LS]܆ ぐ~GKdԇD9;I&!ϠCH0:GM_[fmP @94c*?ڞߢlﴩ+I'7u¨CQsQz" IXgi%woi~7(m3uoθne B Xq)<ZTY~~n0 @#ʲ D00mhm f6B}ӌdiPɉڕ=t~4tKehl_X" Q` h~ -$3Pc\ˏux-Dc,=ƕd" %{|NREt6 6BAf/-c;H6\p6q Da n/J3ǀ~V7Tc 4.\qBTgz"9,Zlg'b7ǬLy6,'ޏ~#W'N<pC,08LP;qvm){s3?W] xQ£ueK47XsoTpHwLg8N;4pHSNp9/r.@AW.eS0#*<(!ߘU4PP 8ݗ6F>|XKe;CN͇ G{7D-ϩmu6N_6}K%(8n\S8#Gdc0KX9\Y5(1FTfPF5=6MQ D>)ɋ+|(95'"kujAUG{GKG=pn4>Q:m7 (l/&p"&V yTwGD2Š/Be :(YM;vo[E~hdUoijo2c8]0~ʔ)Ig \g`c?s^r. NdtӏϤ&1xsx*c99^@zkN_8-*งq8r—(E^A  c60߆<9i8} 4Z9a,vG[d<Z ,OCRFDeVz6Gʅ5]3G!~Goc?`@O/=!Og?ߔI)upbQ\ʑ˾\ qOG>)m\V$p쨱.q mO7-` VlЪ wUvl_qVy;9L}5v9D VlPDlƶa#H[Bu8Sbv#[p/o۸O"ND WI06Dq&GL2W ?.v0GcKqF r2" 9(9QANEA dzwH&+ .LCS1phH7ñDf3tt۾~!l8,w4B?Xp&d'8pq RA-,m߈-;7Y?}ҥx:7~2!9(P#x FѠ}&Poͱ:ooީ+"o^PWԸl1}; ^To9~1]fL_ ^TC(0}ﳂe:#B܄*kaH y"J`[" &8^ڥoDQI] F *ӳ)Cab0G4Y\H;xI+6a돪[oulPSQ oDJg}[NwZKv" " 'Tio z7:Ӥo`c6t%FT[T'f9!j;s?hVXPm#5%$XTU2N`-[4E|lX"66Cq Kyڕ%MO:k]Q,Izq'"5&A1ؙ%ca $1$F68̤^[3Jڌ1E2f tY%,6&p.ˀs֭j GL]&#8ƘC}3(ʩ0*p81/c.8<y}Ƀ{ZκLkMg˂6̌o~f.749w~Ԩr𒪮 nq',8 yҾ?p7D!jYnnqz롇E A-84p('3Q`B[(O<>鶡 ZcH pf"M4L)s<즾8Udgh>u8b1WdFI o%&K'1E&t9V~y4B+ȀiG;SNpEA|q$bǝЁ)kKlJ'P۾Nz°' ?|Љ ۉpr `}mݮ !ƒ ;Vbb 'HlXր @bI )X]}B]}I^`VF3Еnrڟ>@ L6 8nTLRL5~gn7.Y䤱b??W/"|Sr>7.";]Qsg2$w93g8EΛ7/:Lga|f8rssYw398v2s=1'0vmY nN%o}˜wynMJ K͋Dw/(^ R Zmv@%!2K͏cc_o(d'Nl N=_WNxc{ֲ?묳W9I.B[O={vD~>p.Hgy'|2FhCxq#1G<%*`;<!0iЇ#& p./Zwښ?yP3'XF iAx K>B}],_I&7xÉIqZ/| K), bhQ3Uz" "Plm]tʂ&WdF Ï5ۇ~۰=YZyzW5ؗad,[ ު;4۾9[%ByxيvsП!:?IVs׷{z`Sp̈{A|!ƾ+cK0cL"IeL`)c&|O_ JhӿݷMOLLϨSt߻=ȵ W1CPAΔLfml <HwHΩATmذA{u£! c4՛1$Oƒ˿O_&=)%xA>0?DO={-8<|~_Lٗ`Z2!$;償q,Ǿ~g(!y/*` ^Ej (Sd`s= F_b1Y3OݻE { Ow2~dzQe&< %S1+(f͈g֟6ET@T~l'f"$ ^oKȆ>{k!&>ngk^[Z"7D3V :g> LjsR%PFVq"| !rAO~>P ACgECAMS&ı׶ШcF*)Ҩs}yՑhSv|x2₭[.H_f caqו)Q"?5.z ^N|4k۫! [(Gɟn-[!r97R.!#tV% 5rZ &D!1n^S! Z%]\ 4 kƂd59)RAgJ?]pu>Q*cK_Ҙ|ӽNԘOp@C$/* QHXwY"G9'>xB;1s6 uc 9*]A"0!`$B%~N׍؉c+xNsS1D> qH%PIpDcr^p2nkjj}\/b.D"V #0 C0 lDG:r=oX<&SೱvM!`T`vBXaT T㈠ L3q]vC wQD}b8 `LZO~ $aqp;{NJe,sAe qs#DU)Ϻ>@Rx~NP ^~e??od2E(Oxq>r[oIJtd shSd`@$Sk)"!CiTWG_GyyBD֯_. Y}*>4FD9S6ԙ0;v Pdc(SӜDً{B*gbraGO}zMς7o?C\:!dwi:|G?Q;sرqв a˜ڶm[R/Tg."H`+ez&Wʘ v!PFBOOHv}"Ã"=W,LjR _#'thd]Ԃ"!V! %!"m; :AZBÚ£r]0V 袳 q,"YEg6h.-1/j:,=D[qQG4W*H6>l~s"chBufN9{!`!` .*hjB7ߜqQAAc xW2bv u$<:2,3@,<Ϋ3wH_:]c"@TAHO}{?aA.;m?S ,> Eh}<LPOs<#+??uBs>JD8qD}څdNXvn@8O{8`%ܟH;ؐGa 6%H8C1NSl~gj-B'Τ<2EߛlΜjyRTm>K t#EPG UT,WQ‚Dsi:Z%Up HW`4AB )8@,ңz"P*\ak.纥?*-^Bj,A=_*BFtE0+Υdž7 C0 !0=ejeކ@RXԆ`~HVORx9Y@_u̔]zLk?#u)En)C{pD0Hyp뭷Θ-,k2kH[nʼn H#JGxsVT=FꟌ/*^D $⫯J}{NT@D4\Q}4тK%r هT 'RY@>CMGQmI힩 x93^S!xCyr634*I 9n# sD*w~@zҦL( 00&34 ۽'>B j& \~w_\X2؂31:r#jBXcXRQ&I}%tSd__y'jHL}D"*ܗ,bl8x0 C0 yp@ڊ% [obAjp{o!`!`2]T8 dϙ\)ld !ʿկ:?#XommuxBz@! ib8(L T-9'szO"u+0վs YA =crpCAaOG_hwvdƼ(Xѷ'KH'd;B/. Ov=}vF;0.|+p`?[\WGDcԸv o~W5qCINd"_nDwɄ1.>w^V"Z' @Ce2鷼ǘ[lݺ=Q//>Ƶ3$DqtJ<7H;ƹ xQ'97$%\2LNU&$-  y6+"AM,z﯍]w]<-9^੎3CZB &7- !9bBF }"c?p^Pa"C&"` ku:FG\BX"*1.J01'D(dltp:35/cN3R\)#Ӹ @dOñBK^ kR2)njNMС%qQAW"g^ۅPF,;J7@6@k,C fz N"9D#L Sxω2?h>L~tCA^EE:Վ/X"\5OBuD!t⍹R Q"%c=&8<|׾6No[~HOËj(A m0zGcoܸQU!:s;̅u!xAS_b!8!rs[}Hvr@j?C"n na> Sa\++H@ts=N{1>} l{l?Sӷfrm,N4Bl' Al,FcvwǾ3ʹtoBJ'50uXLRFRsǎq=e)VJgW/gE*.(Rэ[}zDqn}׵@]}w<!`!`LLmDp<~υ҆#d8 ੌc $O2iڂX̅Dd1~:˞ʱÏSt r6N #[nM4?A<9Ӧ 1!@ lc,+ :m+D;l*&K?=}kq~? urNHCx']TCE _h$ҏdr?x*x#"L) _H@~OB#L"DШ+D/BDP'O/6r Rv&B \ )~xJSrM;x }ÑAӇ|8}٭:߀oCONfy(,sQ?L?@X@Hj/?&~@rMZiSt )c5DY衇߃|8#iP&}:}c{[~ӟxA_*DM\DZ$= 0FH'R$_dL}g!0;8 iqg!qi 4s0/*^h$"iB|"BzȠ[HSEHa,L6JcY<b+SI9'D+ʐ%$*mA@q/ˮ<0CTA?\QL7l6t2I=7ͮ@ s=}_ԧr'ܶnЃ)p|+ט7c?$ARо q_OpϟhrrD<:E?SO˘̹\W2B>\Mf#.2m7)k@3 ԠlDLoC0 9@@!v*EB MޣzҼeu(0*NveF{44UXZ9t%a=)$=/AIPXBZXD1n״шyK Z%BE,i*ETXXoj޾Ѩw% Fd#`mnƹy0't6N)edN=: 33 C0 #0hƧAA)^l ,CCPB\xq*W9XgK"/;u t +>;uɤq^Ή.\HSkiO +,2} H.` *TMN7H6ڂ~99hg-`r3D} hCNGfR /n?u$g?YjuKĄ1b#ǂ=}>ח7s)Ga\mCdZӷ/c1>tڈk 3>\lCDkzhG͞Ok6Od3o2p=̹vODlp03 C[q;0d}R Ã*4ߘJ8Pqa$?c;ۂ(z0 fc{lS{ ɰsZY-q鉥hX6>ƹ5[DH>lnf!`3G` 1yǖ+a1y@Hx at6\cI &x.pFylufKf;v8R!߈r1 qyW9!Ηqԝm S j!&2-sϤT͓S=.n lhDQ D4RL7YabQv1rU"S6Wz~t#VvC0  "hhx~JUHh?N UҜ>t<k}PFH(Δ@`Xiy>C[+2GM ).2Fab.u7x/p(* 6Wtl|8sbX]w,еO!`0Q!ى^x̒- nq"~Ɠ7]CpBxvosėml!8!Q=0J nٺ.;!`}` Cu49a%ċ0V TX]R)b%T Rb>D:uRR\ D9Ǫ9<_@r7;/VRHD%F4e +N*pxGpF*Gskj%Uh8+CrN32 =ªzksbƇ4']~n!`!0eLT0eC`~ ]ȑ#!J f͚Q ^aA0Tx~"G}Tl27/F,xG䩧rC~٫!*rIW吶f9V]Ci e_=ճ6F:@8* + Bkh9$FQ½"PA*/a/"/b 1X@F1XYcG5AϘ& \tIk1D6>lǺE֎dQ+syB? C0 C` ;Q$Й3gS[[+{ r9םw9uMKr&S=@B/|ӦM@ʑMŔw;hҫZ> ) w} /E, !L7O $-2Ovv/Ӱ]|g}vȥVl,TUUWR\vekhn(*d`dn N7>E55]aCȼ|r'( 2)2m\.i\|!%K4pp_ԵI%Sޓ(*.-؏wz 寧rb_(CThAO}@ɻDb< `)r n8y,D ![a_xfbݑx#GwޕCHSStˠF B)-rdbYxjYz =L2A֯l 7m.ڼ 83G!`wyA/Yd $`ؼy[<٨iD43 C`! \JR yDl==ꉤ cD^x B:@N#@${yfD<" x Bg H_#Ú TZ%!'Iy@Ho=$߭F!(}Q Bߋ \ZDj pņhBE* $cT6[`b5Fswh RX+#:z&>@KM˃?{@^|i;ABRyBâzxZٰ } W6m.I=':o# QO|,C0 C>NTFM9We\tE6[Ə!3C0x^y VWC04Dٲeˬ`@T/|VOB>j ߯"ACDt\k]Z"DTlWacWP[e<t*"*6F %Wh!AԁC -2+Dσrʸ3 C0 C0 C0 C`A ɇԙ44*A >B.bFD+[(V.zA #-q/.ت\{D?<ح 9Ja5 # *(-)TYwu"i `WN:%ca&kהktKW Rx,^T*$+_ec$XDcnkLd!`40Q4 C0 C0 C0 CkBRz'|4hi4l~&Ł1%TD4rkB,f#"L"| v \ XP,Т$o^_'!FH@>ȫh V VF4:F6a7$BoD\lY^N;+ݾkMM|ͯxK`ѢEB$ JW!Uo->┡!yWdˆRa]TWбrw~.G~X`}7AEFKw|AbHIJ* ь"d C0 C``!!`!`!`!`a  ݅G@dx{s Ǿ#*9|N?`MH+NGFGoV?[%&`{~:z쨦'D^|?'osRTY/8xЯWuuu}PJ45?;'Ѫ:&5U?Ha!Fqy@tiW01{~U m.q#֯XNf6tFY s{0{c!`EDEΎ3 C0 C0 C0 C` uUH5KZ"7DkU:D.B[#pHcBZfĥ)$=P4jI12AMDZ׶uB ~KF-@>VFbuqKiZh9m~uUrrIF+gI_HbBZT^(Emnִ Z7w{Gꡲ8OJt;9mnsIĥB9ǡ|0,ft$;C0 C&*jv!`!`!`!``Dz\x}B!PAA`,Q@ ͮ;? ݯm!*U@!ԈiZ.;FLT&vQQKsΆ =Q6OP+C=X k'NIoAvACFt_]tbYZ:FTЯ]'cY F˦_Օ* *[{eB Z:t O?BY^W%l\/KV<*̲q91#$^m!`i#` C0 C0 C0 C0 C`>#pNS $|,H%E ~LH~^ D?#r@ [KKKq@T࿛u߾}裏޽{5HFPL]MVUJH`f!`!`D་ sXPP wyɥ}+etwwO~WRU5qcsM 0 C0 CXp9sF{1Wzٶm[$x޽۝riӦ193QV ,BC Dd i.=FFF4t{¤6  := F#D W""hP)!H A5e BeVv!=.Qr^wX"Eh{XjD;pi}ݥZ[\̨lyHXF"UT.4B iJ@~dr)ڳ_tM[zZ*iݞyȑ#GsEφu(n>8+? ?8$E}zN0XdZ1#Ts5ZǒC^ C0 C`wQ?tQ~7g\T+et [n5Q4;f!`!08}t@E s?iK3.*ȕ2f/Z3Cߑ"P#J UT \ZfqLs A PF 0B BdQl\l(I.V2VͶ@IDATƚK!aeD`au3Jr+Wڥ11ږSisȀ͘;dph4%i 3v5IbjXE$[SzJF4A}^7vyp7KѰ}g@: Dxu ,,dqX8kbƠWͻ#:! !siFbګ!`!`L.*%!`!`!`!`!`Lc'OIOo/)"A @adQ7) ZIYe)^ q.ZFl-(HDxb)JHpDfϓٖsK;I_vZT]]8OsPwHlV,Ȁ?$ks/KKK@H!KmKIYR^ x}xE.Nբb).|!ynkMwrdρ#Z!-[ozAS{$wO/,/%Sb8GG<";,-qQ$4ı6>|[%W@ksFy7F4!`!`O3>O{8\)!* &*w{c!`!`BLw]_Z'd'<{$Ӗ+ed;! I5\ʉN~}-R|;: Q۩4gy J}alj PY,́wxHw4 =" 4pI8zSbRHu'ܶvXlY9;۾@/Q`XS hoT߽ygW_u |]*.`& r%ygJKKT|׍WUUUhRTDS/-Ǫ$,sTgWF %R}L:tHuirTH/*gvگu^y]rQ9Xqӵ\12l|Āfoލ4=b!`E་ pٶ\(!lcn;!`!`!>Rb}wPyy xκekK.Ki" Wʀ N8!ڵ[^ttF씧^xI.XXAHc|۷_^znkښjd+]\6_7HF)9LAG!0k{3gyիWˊ+la&{@}R^z>@Duq,֞P9D13*s 䢋.)`\{!t?!5>w P9P;*BtJFxgR(qluȠ#W /-]-c>p&Y\]&!m4թ 9cFˤ&{-~V#!8ٶvQ4I=v┦1xz\ā6_-W]ʢ2 B׭Y|ĐXټ8|LjlppP~ zF.$@xA 3^v)oLvhz&TA*7|SeK5Fi14Y5bN ׼!`i" ս M mDD9٬W\!O< xG ȧ>)$c!`!`-,hrwo'*B.䒉v C-49a%ċ4sZ(VR)b%T Rbu֥zD*YQαj0&PFHܨb ,ӈ,@|IXUm<\nTCUFϧYVauK׌ڢ:(PGSU$iRd2%ՔKm~{5Z#7sYR WG?OUU{Akim~wzd`hH=c9-ʢ%IBU?#MMM~wzac\*;>.|T>d\T< :-K9>N8!cSSk^rbSώ>mrڵNTM֪i*Z%_FTR(EDM5x9QS&&ҧq-ml!@Rm#|_>np~/-(pѩ>bo GD9Ĉ ֭[B¢JeL/S`ƒ;eثSl!`!`3Dﮮ.{ )VZ5~!`Ğ&Wz%G!!cN0KJ)"X 'OKO d{iA^6~8Bu&ڶሎ#Tm~I-{4 'JMmw~USU) 8|N}fW;{+NN㌵AB6_^I@ _Fk9q>;ZIf,bR^"D9ҪzIG0.5^A>Jd @`~ "$8q9rD8_K'{$//$**%\ C4FcuNFߧD=bc>? C`6@EfLjjT9O쪫rkmm*BFK}|GT3isUgϞu=XXͤncқHT@L :4FT ks!`!` @8A/*y4>BRE!,rsm]x[lRWճ C  hXgB{ %H@x1CᱫaǮL:P bQ BJj0zNJ"pJ"f <1p} RpL=c#>- 9X=zXzzFm.j TiƇ/cۼ (Տ׭IkG HyFG8^gF;^S~U^5n9S@6~)*0g_ʻl%r+X#*JJ軇Ns$xye%񨮺RjTPZXt~I1> T4<S?>}H%dͪF'vaIJ BJuc]#Ksn9x{RGuO.e/bp٥qåR]HDt|]KHݓwλcfܞ oD~i߾}mii߻gzM7{ R.-u32H8۷4O:g>Q\檌;vs='(?ʝwޙz0Q3<e:>Nx63C0 C0 C [`a}ҥk?7|sF/s˗/nM.򌖁 ((v2CH@~˔8D>0!e\T dwzFzݧ(KC[ pz!P*!xQFI#`yW i#䈑#Cޣaed VMg+V6JY2 h&iUW]5I4^A.Fdm~yet4Jk؅#m@ D bu !~ u{Y~ɒJ%d4ih|=pI:{FQzXSկT½Vp׭Bjk^_ hSBscqifiqT \-F7]Vj\b_Z@E~yJh7T4zU.1r}\~I9jDuHfpb۳g{BnYz Wunqt|dإňJ$\!>3 Ef̞}snNj f8;!0w&!HNLG) rQ<3qx ؀Յ^B}M.,b'4 C0 C02? 7[`FگLxQD͙6Λ ed;!`$G Ue̔z=G~$#*7\dTL?zG9#JEsNk~y?Pp7tPaTCGydDCֻ܊l:_ : U2_ Ve*)BIIaYh#l -oPɫT1 jkb ೲR(5'zZ|tŭѱZ)4 Μs{ӭ=8yGUhBy:a1_b`!`!o C`,NT1<%imʨׇnږ6e\veΛ뗿e!`!`s?m&C=S<ypx(dX΅220R dD+Y~&xc@9sB4Co#F4@[ k*(ekP51^{Wm7~H.Ys4vJI+%|L^l{7ߎG hooW/'\B"#v,`wkkc'q:g NRSN_~àIV'޽{~8}_ Xb֖1DZ=@;[SFD!E&mZy!YB_|ig:d`ɢzC_4)vU x/ʭE#ɕqp#)ܘPg3C0 C1NT@rzp*cni#B ? ܮ]2]0 C0 Cu@я~ԉC˳/a3mn1GXi(`|i\|!zOu:;) BJ9BқD(JuIۭ4x8$x\_eԣ QY(<ܭtru.Uo5%/G׈\y"=C T1?@Dж9,M5u.Š|MQ\ R\sqWԨ`-RPV)$ŲZuNZa¥OK哟 p7ވv)Z zw7oG8~r[ TSU)wq|pDbIU1+ș3Mc0{4rCQGbm>_.']W_}յ1bR3H` y1]8 *@ef!`3F`މ xklltیksUE]$leGn&5[`5 C0 C02x}#;\B޲͖(ԝM#o.1ٹ C`_ /02j\dB|N0GAq|=v|r%oc9#C%JG˜"K/T u5n0>3v<9Q<:#Ɵ{翅`h(PoӧU(JǼ V>1/Rid(ӗ܂0 asND555e"G"pADE$~ӹ0)d%Ѹ.OwD^ܟԄ\3gu{,V|`ɵn3Œ{NE ^PsI.!GGgԏ;y? idMVs _D_j{켳o-?я\;f9pnM6Ɇ NT kqW!VXQU!`!`!@Xv֭n(_Zw.C0!ɇX!gu8Ɲh;^y7}Xh97p1VbQ\>i\}qb&D[pu!㳷sGޕc|PZbĽ7#5Aȉ ꤴFEE*.X":'4()QF(*b别7l`6gc e#GASϋ._B֬]#7nsqQksG,ti29 Ny!ou5rۭ,OTƐ$KRMWui{ȶdQ4HG <-Mɠ9*h='͈,tPBBڸA*5@m>pRS)@5QR}TA|Aq;y~$>*y?lP@ha!MQsÈ52013O1ҺuS:w@#*`neØؘߗ.]ʤ,XsS181~w'}Is' 2t֯_/7p;Lt O=q! 2h\O~=/E="$_;1׍!!z,ױff{6Qܽ0S|\3mE80Q6HQ13z-7;vՋv3 ꅸQ}q"k0٣Z}N;0 C0 C4, k։cQ! {H=;1$2<`0)t_uCP 6 ]8EDJp{,ذ¢ <!`(-8zD="W1%S"Jp;q$ Of]HO4${D !D@xʐ )$$z+Lx;aE`.HAPӟԑ\CcccR1$NWZx{Tˁ@AsoHǒa1Lt|f R]"&x]t _~e'!/ꏠ`.#WTs"Yoh$~L':/g˱WC0 C0~zQ{D( LE&Ȗ0 AxW Lvfi<םݫ;Ǥ<B'aaDXvgl|#b9;>!t_=ݢ^bs$(d6>v TTPE*>(] ̳=X[#ꑡ^08/:m҈KU*08kn]rnq};~E>g<:SYii=EVi41DeԨ c'd%klS3/ ~c8v+4xYb ,։0Fq'Zݬfq'zʎiCNsU<ɮ֮]+N\_‘n~MqIHr!!cL4;3AB.CP :vZ͇8F 7 Pxoݺ5%9 G 8}s@h#C@| .A2@*{ENsݾ_on6x֗ksM.B bM7Otgfώ?Їh/^ٸ߁= )XcU#c/.C_^=}~IG og?sSԴ"oSǵU|tͧWR0%c!>1$LTʍ- 5\2Q7ZΓXlח3ث!`!`!`B+Y]05AX=5d(aH>@{F.BEQҏJеlAaߝ .AGuX,( :{ 9O(V.J`JL ͢2Z+_|PZ{];ۦ%((.R4> " BQh& th݂VXYD^hpJ-sRdizKGQa\r+ Kk_$!F(H4TW*aP0&E'NKho@{lj3 lAB={QbGk7]*EK<6bLꑧU?D"#=Ľ x& '$8k8vmn# K&M_=8A$9'+-OyR x!HWHdx CcH\jWMdժU|[zeq p_aT WN{ci ȵ\-DQBobHw9szqM&J`BWThd;rH\u7TވAd */xA|' r]wMO)9mbO? x }yEHC>I Bpe(?#"F;ҟ5Pos.QQsQ`` E ~]I!A60ʹ ӈ C0 C0 C0 C%"C]= !,( qѰ#z D[  㮱](T'OM 0 #" \oT *HP)^9e4!_{J)C`:V^W/+*x3,]2A=_CݥˀnC*6@h$F&lUcN# ~waQCJX5%4 Os81~%ޙ-uG>E )MJ4G}eR9?yCrlԯ\#1bv6;Ԭ* ERi (3z 7H`˜wɃWM[6ɲc87tUWRa"ff. tӌ$%8!;B '> -TI_t{Z^K$9dCF{@͛ Ɍy9DNhFyصkWx*xrM7A>|cHI  |D9\7j5)F 3˝u lڎAa"0H$`b "8m4_&1 !"6h#?M'оGRI!!$ p#\c= C0 C0 C0 C0 y@4|[t5Vt*dYr:WR(\‹!&eD HHE=)l >}2,Lit!}pmRZ Q Ś2^4Aq)5uBF(zTx0 3H`jB7t\zڤ޳*p;#Nd;DěpՓ!K!<þJ-%Cx_t$*bݑx?C]{S&gS!UH R 3 !Q!xgGX'Ft@p\uщ<y yab*DR03'RDRsE{q.yh~KS0W vx]՝{1~@'64m@;v鋴?"]Y1dAʖ-[ _˜< a3K pO`җ!bl=;w}z@vI+eƧ ^3iVFPIr\洍{ P xݶm[UVFU'~oX9jG H.E-Sbٲ~~LDGLtwt߸myymKBjEIq@@ke%(B 7KU9fʍ̯ V3~T=>XnobRsV LS);"5($w5́ڞ#>0ʸ $+!4 h8=<:rg0 VrD~$ V-tB,`#:v "1?yH핱 `A.'bd3HI3+C.}`ƘL&R v14!4 gѩBtWP_|tPr>Ba[`PQ-a RTHHmA ;QP0Ke*j;R!𙦟9kd^3wuFPCzs /Fg/4tU.-LB"m+&$IV=G“$u.){vk;0E__jq_7Sa5K?FD3Ns5%\ٮm9wQиc*rI"|ck{$(;^+)t?IPnvTP_#_!$OS5mZɯPEa?ؿ+Xzst(6>fK.*K -R r+~޽ <`RcJyd<_}//XTf1#4 V+3f~LM; tvH⍨b Jl'}=",=,|'Y^VSP:ƴE^5: 0ܧ)$ɛ+-PєbA'X:{.ʵ{g21W \U-Mk;dݶ@WRP4O* AzpTZ^ &&Q%mL B7Hcq#ċ&4ae{v_D-`Q9 y*}UbO)߹& |Y>Љ'AN׆xi9QGa ˹xAޅ)ϳ 6("ػw:$"(&b䢂b5Ĝ `0 A `0 A ~C9 Rp(T7HҴa@T[i_`sJBTebdL7C3uɔ jHv^G>-4y"۶J" k%T bb,t ؿч\og_;#: NzT8{t6ԼyM1L3ٱݚb)@X"lxi=S!щؔ]H3rD1/~R6lOPrF\:M{x?z1ٸD,漧k~%:tH-ioOܑ3=)K~S6s c@IDAT\ـ'#}_?xAACL@)PʹϷ`|IG߻'&󽆥ڏmΎ'݆y'}Æ # 9 %륰bMWjNL}ohY(:o(&`ET,\:ߜC U*,TŎyK~vr},VsĔ(>gKqXQ9:g{m nc1+l~`u^ Vt&pvbRظVV"[qE7wG'V4 ?; cA> X<<gY uxHajDS+~l7X1؟3D{L`: 偺D`?=&F Wm83M^ .2s w}3[7n .G$'R_xV"=3F0|F#nNgu<V^^ |UN6Jc4p$8"أC7%T `M@pXt%m"4AZ7 !<ʪAX0B-\ʑRb\]%2o$}ߑO~%#N!KN%Qeb<&賑Ѩ~ygs/:\`\냘.$``K[{o_'{+.\aԪIQO寤׆e6KeZv [됓4A_wB_BɝU:\pAYS#DgL1Jno( \+5$?nRk0EP$%Vﴗixɳ?}3(#([^>XtQgHiellÓFW8Ki;qtٷoaĚbW^yE]'(XZ{Lb~?ޅ\RR+$~/v):z{{`ARj}1s>o9k;3ңԑ?"b/#w ^ cCp ,5ȓvԵopeʹ;0Xɴ+  ,Rcڎ2o "`%!3IΐJFNu-2d<'mA'pO\le5pr)͸ǢV*Iw x8bEQEvL@,l.?yP|RKSZqT-TP$Ĕ|+34 aQ۝:2H 2T@񅗞(579! 6AuLXyn㻰kIAq]8_$K+z gϪ[I) ^[6a<~j*$i\܅)Ă\xz|>6﹈`2FU$ KC.sއJ`~߽}9.8E1uE!F ~G>Pe9B^ADAcZU(ZN!iMB^ ^rQBRxQju#gJ8Bbn]UXKB#38ɮ8(~ gȭK2kY#+RS/u6˚mgd],#꘺g:`?V!BRc!126, T(J:ө7Pkq6gcUQ4N&V,%$29ɳ*֓>sr7g21؇kuϭMn,FDގО" pPאwAs4#'[bk׍@&V&^F T $ <"] &F : x=T IBx\O5MF ^7PeGtYU#}T=˸:r !:]H&>8'EW QqftWWpw"גb]v7g׮uKT#P__'5pja`Kwţ 9uƆ=o/Ȏ{wʚH 9{crܾUxwtsFx7e=RD&E$ +WeQ8M$ԅd)iFg!ss1)>G ( :p0%3<#>Phk"b@du}}atw6륃Lv]hG9re ͮ/߿y ק61\8Xe}y v4"a]ȸ Ϲ8r yt`*?O*c_s1ʆKS\\e!*XN16 A `X<hQ{?7{~-=@H6WGGd`%~>yXS$H.S A `[Hs@4(rڟ#d[mйM߃%Dlњ>S,M`VӲè` QedkD~&́޸˸(#51oi` acRPu\-TP*d "1Tq87( |u<^8+x^aQG%EaIShxB\ pvqN}ܰe*$o)2",Z WAQQAJ~ *-QGmU^ysbșR '5bݐ$dv&oɵD:vM9#pKD. $^̍_MkGbʅs.lǺuユZ9 WɎݽ? brDP_A w,<_u[7Ǖ>E4>kw~8P\@ &]$HyYX*ꚤ BJ++L*a@T1 =և~N.NOSa5,}رc”&裏.x`36uӹz>31W=LQ0ҕ'y}>K!J8(}L!󠸠\n;DN**,.E`WzTqnN4N?h)a2Dm0 A@Qx+gqF$ě!'o`6*df\;/nȥ#@/"ٟ̒t,(!LS A 0ohcz%ymyC@ZӾĐD пXGb]S)FӶ7WCc}M>rڇr CDMa ?xC Iv}‚ G%ŜuҶu%XYK\ 68I: @x{l-:PCvgzĉ"2Qx ]:0Ԣ_Y1MQt R@bF䌊 tM񹽽]*!!"^MskM"as߃R) CRH14(.<_]:SǼ;wo -iTHb2(B5뤹u477Ie U\$B B1ֆPnٲeڳg7ɓ'%iEm:l߾]ɫRe:c(EH~FϧG9"q\Ϧ$ɿKuܹ͡sP0ȷ8JOj|SQ[o3'|w֭/kz,ʦA `0 %@ kR/n.VGRW*T#eg0 y"@V$bvMg $hmmAhm|P?60$eԡ" 1&oؓКVHI$V`Kh_{@|_HZJHuzNPA@\a|'M;|cc:!9$a[ltHS'$=7H"*B.ADZqicHoPj+-Ib8ACP">Wcb$WT}x꿬"%nq1H@sWbSjaٕ䞃TZF>#O >]?^[)1M;#*Yk*ro -v] I}cXf J:;;em|:-ɞt_JI&cix@#` ^@ÇN<"W:891h~qӝ/`.y3BPHSC\ k S8aNz?틕uXqB#snڴI׌+:Km?ڻw NT,-H9sϩf9i~)-K6f0 A y]N[p6W?pōp3֦ kӇWҲe'n$2BA `0r!Iq$i=$k`}vF%L80] ϓVEb%q%-Uȶ7mm[h%o!wBXq>Ec~cn_^\mjkuVge |wӶ`ht8Dvفh~{hS!:t9֡b%Ë}LÀ~ aHœHALI1 >@ڄ$"F|@hLsCGqYIT5 I ^1\{[pGOe:vur7 wȕkƚ$Z[[$517oސS'OȩoOk큁~MK<ϝ>%p^pF8-.[:$w_m5uי0pCU]_[[g?;w E&9_R;ԢR($rs\'ψbq AGvd>5ߴ ^bGg a?O)$`D7G7nbdO!MqM *cq>q=zT6(&`*$ڵK/~ޭZ:|̱A `0 88w7XԥoM 5ۤ~umS~߈ l7  V$I3@ Qfv I lUQG-%3Xy3v s:@%O?W3I7:h w+!#hKlj%X: ΃pCU `LaQ>Sf@ܸp⎹+dSEc$G!$x ey!&HJQhaL'Ѐі$p6p#[O&\$!'anh)j$R AAdԅ{]GMo7p ׫<`SwH҉tFLYvq#G3.;_*`=5)3#x:tH_}UuXIW.lId.$.'BܽO' ȷ^Gޟ}T\~*W^W,5oAs:7L-Xh;Swp} ϵ^xA}Y0i'*̉AmtYnDeJ]]P9XB%붍JÍ t5RsW`5 @ +Q@u(s 1G`1K\Mj#gb XT VL` |S' ?|XzsSޟmG>u:Aާr劌$M#cH1 ͵}QLL̹ 0ng۾ `.^Q{NTy3$ZgԑUN ^Q(r FB8!:Gd; `xadwGd`Lv0h K3MlQGf{@|uv*ĝҹIn!#HAh~a H@l3}דcO'-:|#X81+ FT, & @>G% Ka @(EZ^9XU/ *n'9!e;A.,K>Ç9X4:`Sy'% ׈W S)9xrdI/"XDz;&DӵÃC;ZW;C/nΡAlreq+9Ic{n0-.>8O>#P\ތ%Rs$#~SBbkmmU P % Ϝ97Wbs2;;6y#(pI)rcG{db,2_w^ٳg:8npݡtߟ\bnʼn 8+͇BIEUǏ?X{D9mJD`Çܠ^Jv:uYrXX1G7E-0u:wiɱyXy5>lGK`m5YS":$7qc3uI"@v0ϳ,)':d soȧ:SG>(V&ێ;"=iX uTA PH{iP /i\ D! 1|~f+>&^7\2:R%qO-׺Mmw S:䷗E m[^kcKxl޲ :T4_M<p-l !"nCM`x 8y1NԖ!"ݤ UVyGuRlADsX#"!A9G<* \8,4>xq_5 )oH:jz^=pϿT54˧c_ "k!2(H+ S/<ߕ*?"}2X$:v %y}ʺN̈UK^ysk'^,Ic я~$>n3%9 Zw{H|9+씓'ON!TO:A"BӚbؑÚKa*v]wݕ}Rf[ !8&gzǑN(~}-jbncz}M>:2\OuN0[l)3ϱ+NTŇEAW,VTx(v3pCTf` 1s)aE3:Z؅_Mj'5_E9GN]Z&A`H2;+ҘCIB籺88 ?Nq9Aͫ$G״‚Ŀ|y[RAEn5]A;#VW a"aZ\Y8upqqc9y~7;~ a!~( r:B+ty7AM6ʋ6.Uj~Ѿ؅l)ƅm2XTܴcjj&tXXv*@),(vqnz:fG`5;F*\7MH`[=2}A!B7w2%cyhQَ12wn/%PtHcTK ܗBŜ `ȍdj%Y ք$p0CsWEe=H,sFrKH<0G%u\խlo:մC6e=Q\?@ϐkM/yv?QExe$YOu/׎~$CWF0eMso.mϸ0do滘]@Z'CplԸl/s98ιd^ 1hhrʐVј`ߥ^Ċ#%X^& @0R^K?:2wR|5fv|s s1eB0R 5EHq푦uT e5MHWE3ATԹ^֯_ӧE؟#ȾX;@V%QFYudSoN]ٞ,nDL(Z2: e<LJ~~zPnsV!Ufˬ˨IR&ѱ{De7N~n'NPC 1(۹F/UcIq^;soN8 Bap@!1KqŮ]to9à)|駺?#8´$&IK{{˝9.nBKEKpn'_1ms3PAqVSNuquĐv}jJq`}w.I,Ls5J䷚-sINbJUetZ`} .ҥP{Qi/v)L;+Bpe~؅_Mj'5xCٮ/ K'Ju7el1dJǦyocvߣ7hY(b^#(:fOA`^VߚHLBT+yBNW~ _|,Ls@KsO(پ5d뙛sCOhjxxBmh#C@o#I A4CF$@54)܄ࠢ~G[m&S Ct"I+ A5:QKٟe-pcMv=Rf>X8L5˶ ]dp`9` 0>? 7H p 3F(Vs"=tLAQA9"Cx@c2x)uΒ] svDXXutvvrhAB)Ƈ TR*Xv?2 VO+:P65X?VEKFT0|T CnLxqoLWkg#gRjN7Jǜ `#X 7] 2.$4 'Fy탙Ll2:>/^k)I#$/lߚWt尲5/I; vfU(^Lɉ-޳&a '!PWs:ֹ7d@AC:gza@仕;4 ,'zgڅSXdnwB #Pqvg(4$& }T:z#(j9CР H1q+H 0_1a4Nu4@0:DGG$ Q=wqR$WjBi;ztOAYuFhAl$RU/e-W~é9XVY+kp`=- 2:˽E.g ewg.f2l$W-I`$ȏ;yì ɗs!4v/_$62"PGH$$r)Tx,Y?Sr!%"kÇ5=I{WP~ߍ)O<IJO}\388p;qs JHիW0,[@ln*ĚS)o޼[fv+q2͇"1EAP2}CA tQ1wwʑ}Ƀi $oI7 'W^yEEo[R5N_W2 ѕSHxx&i$?)*`;B?e[|0IuF. r&y7n+~8믿5+$IRS(D~7-MD܏F8Cf0eSHH/8׶ˏ~#ŁĂ8(XcFt*tpxG>Gp nE խ9JWK~µ$|ψ 惞9 PAa7DJѦyA 0g_V VčҶ{ zq G [. JK8 CnۑAs`0 Ո)`=N]#dd4BQ^F#Y pa X)u?a=-cHuý^]ſnAۊ + Cwly)&`*/rv)8"0> R0]~/+n0_TPc˵C uqHP@@ٺM[s*I*Af۞ׄb,ӱC(0<>#Y5uX"](B)d$>V (zHYCO9 :- f*wD$cQ *IHY@"+T I8ɰ隖v ehkUj)oZ'ANzMpg<Ԕ, <u,:l̺O.>&A bLC -zeIj.}EK)Fq 5>hϨqF3:/ˍlnt൏Bd.gΜ>X( Gn#Lg='퐹܇N |͏) !˵j?0rw5D;RF$08l; x$IEe 'N09W)?SC1{r8/19>*emx=P u:ST9ꔿ뿞vpYFTR{\A`d|dH*칄z0GUjNl0 +8"nu!,J 7&R^8٢ |aOƭ$T|֝A `0LVAi1EAJ(Ϝ]m @HΥi/47@b{rL] |MS&>>k8LP%;]jH2 >AZ$DeR-4pDpM#5x]!Rƴ V^-(փesp1JCxS3i(!>ota1`HW Nբcg}6nw#Y~~i;:2ɂ I8xkW!ޖXK|p1U4]v(NhbjFoW|xS^!hU?u@ݸ\:k & -IL\ v_cf̀s7s uȥ!tw")  ThF|R ǒD v8"@ 7'{]4H4Ŀ8/%>2p!qA$bOL@4{j%>:`8?dDJ0T NH#(y\KsPX O=&;l@:E3WKal$񜠣^cL`DJlEzOv|_̸rf3^:c77Jx?JN/RP_.YL?kUtI{TPAL?"V2i$Q˴JR8?4B:.׶ V\(`Jl8ܗXg Q1H;ē0D61K'0ubυbYq\xږXnX?PX@b) rjCx,dgg3ճQCz-dЍ"cĄ㊸/XB^r9,0aXbRPw}|s_T""scW"?H1?fxD<;g,/ Vz{CDD滷`rJz9q挷|}]‡SZT5HUykjNukJNkޅ+^yKk EX "5z H P.I,ɦ$Ei5#7rDRGId9L̴*9KW(VL'4 3=iHޓ'O*iId:#). ~v*+![jJ$MbfH K$ng 5rsupEkJGGlڴq@>00%( JR6͵iM<Sa)܏6(^#⇰6p 1> ;vl<<%(J`:&%6}1{9Ŗ|P(Y0@IDATclܸQŋ5}-##^PA\{|q1۟ F] эp!Pz׼a)j7u xJC.|߹aE]"=-͛f8'Akr𛒼! 9'5SILLy7ߕOlSjm\I\r˙|״Qo" R6;ـ7֕# s&߾cDy hA `02X@Ykڤ ߽쨙)`w;cHVGT@ ir=:^,H^;)ɞӒ8v!ri;?R%,sm,( ,XSTALdxѾ΀]+Q1MOMSqs}8%!D(Tf{i6)qksEzGE!J|:Z7'֧٥DF1nHrv 7)VCP0"/\mT%aܱ@ %YF&הS'LhB A$0n]Ç\;?yOWI@E-nVLDJ=ۜ/ 8'/j֡Pl@Ѣ̙^xMe$[C200 _}FOg3?i;ybE\/H2Ν;'$⺺?V#|_|EwG/|L$ U$CRT@{>!r / Na Ǿ[T̺SxPT҆Z)㵱Z \׳| YWt}PAqAv[Xp!ͳզB,\P>wt}q1Em}ȅ:\K1?׺?SuݻWӖP$E|89>w>g>ײ5K  ذӿ/?QԶm SkB}oI2t$#"E&Ub5Pq=oL7e&,lCeUtFNv9JGz_Ι:&*uwya[zg5 ܨ| v3 ochߒ2<ѶqO 3"Ia?*U>a4}ATC,:Y0\ ^PGz>w_mzNIjݽ[s2,$\N {\о(2׵Ki&{tW:\51.;pE[SsMtgΜQqPHЙB9N=8W,-J7|éկ~9IOkwryn(:{wx+v[.uiӟk׿۳78u0syǸ_z3{D$RW?PhI4cg.Oߑ'`'Z42ya0 |t^^[w_۱A~_{ %B9(0bSNߚ>*N8w\ox>FV|v!ZB,ELwfԝFvk95] #wA}-Z{ײU-[۸i:W ܸMlEJ`0ڊ@^a^ K I&#{mYQh|F~yT"^%hԍ1X<{T~ݸR:v`x)`zLv3nc;BJCn׈*dgu0P v홸T0|HwÙ"ixYӆ ki̸T,>6G)Fb홈"xtLXW71: ǁQa~&db rߤ 8p")mpfShD DBUG:x;ϭcgYTx?˿7r/Wͺ?ϥbuWqc?VsM:q~:wH 6쌚f4/9M$,k'#ZIL`\UPO7 Fbvāiy ' @7|Ԟ={dfkt(hk 9R,KT1og-T A`EKP{`b8q٦qt4l J.I #r\đ[?ʫ4. .xoL"y?\;;~/˦g.e1uf,5"/v}˙|?y {ޚ5:?L!AXÕآuMvA 4ඬl^7o})9C ɾ5}0?Ry"aąFTrc^xN[DBL!Q,D7WZ)'d< +Bot?}/WUKiqD[lZCb:ሊ v#(@?aRuhtCPpTdض͆` e6Wp-IF^!@D]ovd.DSp(HCN$`&] 8A?xY) |/" G}N۶I9>r(cg`-ZRRup -`-}uX5.&<"@W+qF={V'O#GhJ_~Y6o<}qМ `X\rE J0'Aj+W\T6A`~\jv>Ǡ++%@YAV |cxPn]:!W /Q9_ϲMMA `0 A `@$tn^dqCgZVKhEp O`Rh@Ay_"-ڟ%\kmf֫0qvnv;mk%SbgQWVb7Dv)?y}믿.ǎ/BHDA PLbO9B 0U hv !3] K,}}!#CSݍy]+\oKvs!9v#*` ɒ! @؏ps^ j &74uʺIuZtז5Q_Alf@zٮՎX#b]C4>ߦj(0K)ul* ,7e-:gĺ5]J6:W LM=1g'|<10v: H& " A Nt64 Ip4 A'q\(rO"MB$XY/f 6wb-"AHb߆m-:S^JRsMB82jՒY̺w;44sE`֭qFM@?rQ9}?^S#ꫯ)A`:uJ~m}۷oR4-/0 ?t2c"\|/\T@`CV u]vZcc .DS0 ⁉xJaFM^KfPǦ`A `0 A `0 j HR#|vMx@|H[gGIzI1Kwr 4 D+s|J imO" vtCp}0S`r5l3~Is"ݷ r86TmAX[;< \Ķi XFЎԑ1}Xgew/JPP~Xe:Ej&?1@0@(&KIt"i:':!A )H>AE H|x@M:mB Kp)kX+!̷FM ɔld gw[z$uLi-s}YVs_Th1~Wo.>F 3jŋVp 9t$ѡ9͋zd@Gݫb>߼i;0?&hawC{}[}ޔ, QAK3[ F #=ROwCW]4 A `0 A`X ؤ_?#g!28qR" -o^ TϤ9`ЅRd1NKr ǥm|ZZo_E}ٸMZ;7 ʙ78IvS`Gjj?iTlaĆX:{(k#.\DD>DSt8R Vp/#U|1>Nʹ&N`+ TL.E!V8|#2rs* 2x7vo}8͜HXcqPW,ĪW%*8H ǒ txA, O dSHB0ec8r4CL$Lʏka[U* ?T|) Uv_8YpS*`8) wc3..z2Vo d_0{)GJ<<쳚\?cQ9 `Xʧ~n(t(ht衇kk&tOfq `bDcd01"u)bx'TS=s7A `0 A `0,Wp1}H\|M ,"n_:D')8'@VxAB&r)viۺ]ֵK  IỲJ4}E@J*Q7΋\9 ARBkGϏdisT26k<51mv OT׈/$U=LAl^SZo_-- %H2eQ`)ޖJ38EjqRC?HzCR])Ҁ͘DGF2#2""6N TpWO, 5v2"H2Y4}D0* VVu!]ഡ7Q 0q,Ś0zTbOB^Xt.XX+`u0m`Yfl("R)жfyVk/"Lvڥ7|S 嗿q" tcNe0,!)|^>|X (Tkns(O?޽[v!--HY%(N['c F#*%A` NCrgHijoߨn~?0.|#?#A `0 A `0@;y$}#5mgp1#jHr ,c]r JNtn> -m-+S wK2AHd΂$܏9=t66pMUFGf z'^ "Y2T5y2 î$H5ᴛS QDx\#ٟH(bѥ4wB!^8g-byy"I댫^Q5b]TTRdFh@ģ#p1#p6` C$ւ)F:IZThPT$~ `HUu WH9Rc}g \ŵ^Z'%iIpB?)b֡"ɕ$וmv 6h'fECߖ-[w}W# r%iSNw{vŦ"A`9!py0ʇ~((ˡ_^O=<#RRd;PP?Yˤ<ȳc*'Pf7jA ]CPs Kj򇄍@ ?ΝePv A `0 RG|V ^7ܥѻ 7N%7L# &OR[d}He \ yyMDL=F)\ TT鞳?Db0˾$xk;}$] 0Mu+Ūjv<@N2Z&gj6N rNzl"p =`C8QxƶD3> ]"П Ŋ42@j\#OHwPsgJ} XeR> nc|{*H,I$:<$a[NXLs nBhh8QrD p4hpu#2E (bsmR](0 3M|0(ũ0`"]0Y C]Mre\LPp6Ay'eΝ A>?cǎ_^zI֮]{G_ `XJe޽g;A_\(?Xٽ{)TA *uvE9w|<+=#*Xɽg 'ƒu-12w,xR._QE6XrmS A `0 A P {Ap/8.@XдA= FVAAE]iN wy&`)V G"9m"=F!>~]2Sj.HZ "+2u#p7`UgLI &J8Ȥ;B&j`(" C:тegJȀB]@${B3xi_ 2nsv,V1>,0D i08"'H06CQQ@||\I$Q>X.gMqb$[TA}ն [IUKKI&0~ї{P˹qtKA=.[/Rm?\f8g:2ui:)<TWW~u-w<?*,8xΜgG/us q|߿_ Ξ= mmZ<裺>لbGP!(՗W<{g|^NFTPjd(R_Tbɔ\¯ޱDJzGAܸ.C6A `0 A `0Jo[p/[ɍXHFyYUڷZDC+I c&LCTdA >84i0 6EE 8KJ Cgw'Tv Hr`$ScrK6kYq_w[@{{F;C˗# NԾP۲^{z33{~s0̙3tݯ.[]%ڭ%j'}ID{# H${D"q#Ȉ~7ruT6NFā7o*UJp"9@ wyG)߿_NLl BA' O$>|D^>t@?GM*sҢdnrsseʕI-\Рd9]˓cEG$(S}}*4vK=$Z"`|1Gn~Y++7NE31[įDZ{ޑx{Mv΃[X.V7jk|PrU[_X;X֨,F@#h4spАC yCV%k*r;, ^JxCp! J`2[! kF@dB-@eIB]C{Mz  4ܹS6mp k׮ݻw!~mY~ D'F`!PWW'$0Ao/~hgX_]2^l4'?Hc y0J(8(iiZt,7-d}xw '&U? D)C%Pθ_K!8Wj"A؟i;cU1iilF|b 3%N ݒꯓg5(Cg!D.Ui019;GNi|#DZyLj#|ZXZ/>C#h4#00vS:2*@;S\RTZ!kGR+Rрn'8P28@2aKEX#S -C}Z y[@(X C.{pFp!wilDz\aB6J}XZ!*}ʘYKMlYþ"%VU&c(cz=Bl~"fcEA z/da7Àf&x(c0Z{8wJǷEVP$`j =]X!5:D? X:\lɇ<)ASYP7G C : F 99B)8"B; } cت #2<É)u<SK߱h?GG/|B@c!=ۧ$CYmz)R\\Q~t 5`N >3lŬ,9pկ!BToOߤ[)LdE!K@ llG# ?b's=YXzua{/<"y,};V3Tê*lx D|8ȝ11`ơoA.N$vbHOsTmG$P*0DfXM^c5Xn:&Zsq*yLhqUJU iuh4F@#:A*NC`eljπfAam*E%0NA-):L2s4L3lz(fE֤'/ +űjXYy GdV$аtbG1\=рI#0 E3=y eVj2 I&68_(VOX"y[3 c|-_0_1V9 Yyc r`1i ʁ}ExwW2 q< IPzR4r #E4[OuusĔngXJK > 3NK%QhwcL ()x2T\!pßǵ3UKy߹qOW 䗿"!Ξ=+mp裏paet5\GǏɓ'ƍ*PW_W^yEv!+VP7Ctdo) v#y F~=8ϗJTPTTU1d=x -,3ɉ2AK!HXX'U$"cg]` :i4F@#h4F`"u OmvKVIU˷4nq.۰F$ֺT%21B1KI+R7^y(ogem*U3CBr6yB4 V U`ݤ .GSe\}ByLICAic*P% E\cJpnHKЇn23ap7<<1R(oa@ Ó Yf"JAC}}P2nFaJ  H#3 ,^֑IL4F@#,!+] 4dO+vKI[r>q6J%bH)_>{xȭA/w%ӍLTa'r3;a|qOLz^C|ǵ}|栎:;dlW.EcKRLyEP$Pq򄦁rVL̺P'ćiL{[Dx˹Iň4h @=JSG@beA|&^N d#7߃fx-2 4Cz0ι'^ZS@<7chؘfvU-eUVqb'ō#fx=b+&bVOՏ2,@[iYF@#hzB!$8PpKE^M⹩N-btՋQ  ^N~qi춓k@:qbmVnx-4G%IP?,IUhT|QRz$V*c1^:dMng{ugeb4A:<ǚ 0wX ~8L2$gZ: 5ZgĀvB㩘P)l!^} $lQ,8q\adWFq2> F!Z{GP,H~+5)y-oϨe܄"0$D(qbx5 P\%T3@igq'a8U*HDynH9aa~q#!$ PJUH_~]ǎSzbq.C<ϟW >t u^/B &DqTF@#00ywVt8TF@#,Ub YX(O@X(I#h4"E7D& 84FxMuP"^x |~x:H''//gmdhH`@Lz[x4Vr4؏G x&Ty ٳa 4z‹<‚AoZ>,E00a5` U@fPoϸ6W:a35VfG#PGV01xaY!8"e"w,mH2-?BE]"ry7r8"yU{"Р܎_^T+`AB k~B_XKf*UGy`KF2S8_~!' ˿0!.\ ϊ\p%yds4yE 0?7nPd*P]U:SzK IWW|DA NNL &L54`j|< w<䯳h40Xcؾ\eM4F@#,2:u)}xMܕA)BgJ{`f3@ ̾V1Y;=CFFxm0$@:"μQZ[]BۖV4 2Y#=Khs1 AЀd: ,큎| #k`G9ZRI"@ kTbȆXꁊV1 B ,496  0HЊd O]A_ gIQ8dBY;܊ 2c[6 p2\g>H$M OE~a8 ԅ *s%P1 F  f/[0BUU _*c[Ν;Cz֭[rWIO#$C0tə3g?Hk֬QcGJV~H*' 7g'KOW[Lzӳeǎ åIch<LFm.h4PHa5Lפҟj4F@#] D! &)(3UrRa5O[Wz>hH?NC_z8z),ba -K"'#F1J7(p[1~!JˏE$Շآq)U'@-1d=ǹE4&aAv2NU*#qup6Jv:qVAs(˴ކ:i#ϟ`? $咿fz$c" kx Dï3(rLJB?$s *`lK*QHTy-r RAA,Ӳ@ 55U^߾}p&˺:͛#V\8~AMN>-T&$A</TIt\PgA*".Bޗ˦ kŝCLT0BsF`n"4)یunF@#BFFk&t}F@#h49Big%-P(JPtwBy遟xḾZ 2B#͆>fo}xg^a̛/A1Wn\yc0|@&^ &"-đᕓ /!$X>4\FvH`yUWY"> +u$,<1;|vcy J",i'99OJ㽛 CP'fG(pKt72Ox8n CR.p_? FYqfxHc t$aE* )H4~F`\'F@#X<VF@#XBA7\HJ!S%՝DB˜$ ;ՀT/VY%⤑ 4Ӄ$ !!1ͳl'zQՖǵPalTe(Ɍ-QqAɟàI_ %Ƞ4y<@o/ J`5iE[aCEueȝcK텒cv/+AYBx)TaڥUݻ%}&;TR}T/k G,*"p(0 U2>o|[ $H*+ Q.Ty0dʄDRD  KFT@#ݻ!?.<ׯ˽{ի*coذAfb\Jhf;_ ?r)繊 9p+*UEU&(aP斞su M* &}F@#h4F@#h4bD {: M>0'F(((3UrRŕ2>{SV_@IDAT/HA^H4d*P2k^a4j 8C,~a\Ay%G|L0 8+#K\Q,Qwg#|r}a{!oKIULDT;,DRĢ;FEօI6<x {[ci lmIko(&9h2T!8v+$`4z޸qC._,R\\<rt4 nouu ⷐ!FH$ qcQfDBg8) yB">($N*Aw*u*}ȈӜTO>D6==]5iߏIN1#iϧտoX\ϋ~<%=du%n|YB^ܹ-M2Pm(}" т:aLy x2SvqLYw(Ơ $WN䥈@~~ >s9{ H.`H#FoN{E!֭[UX={([[o%۶mDbh'_>q ] "yIXoU[,٤ 6VrQI6`)h'bQwZRE(O9BƔvz'|qUlj,x7&I1aOh4 NGI;ߟPlgqL &h4F@#0#ڱ $F(:A(H|x dQf|1  MsiJ;``@adlDh )8`T``Q2aL4aHtT4V``!|; P>>P~$?LKZ74fb!H ( Cz~`v|(2ҘNCfX=Phy<`@*_ L`oЕYd eyqe=TeÝȃ+"@A?'M,V ّ##@Fu+>nTWP8p #=E0;.%*c{™Ї HDK¬s<%3L$=LV <^IŸ :i!PYY)k֬]vW_}%_|\vM=z$O>Uo2:EWc$ X 9B>| &) rrvܕxS'/M(>wRTӟi4F@#h4F@#hE@&;DR:HwPg U@ B"od@VqP|ۘc(ἔzC*&p ƅĹ 5hLvTQ? \ɐI)#HK*0JUP" $~#qlz]/TFū(X B0edU{&O_.jp%Gv8^ie-_g(-[a}Iy\tIn߾aMڵM#0;466!3$P$"y?s VJBQH(8u;`j9o7 zO,o1車%7C"V[RɃLJ0Q́qZdE!A9| 5$=gpIB I|I#h4rB 9! M> ; ^yP'(@8URS ?a~ c$=G;Y  *$lh#- PK6K?W渿:`Q/=CϞav5IwP'0 ,]2گ2V[$fTTB^ Jq%P0AD'{n<~@zZ: cnC 0%eXjM~D)S8>if+A(93ZߨUFjS]m+TAJфuT}7{`Bj T'q;&hO$gz(eذLJ!eA8sNE2j7|#---hpuC";vLŃOtykT`s)uq1 #s^}UP0 aB  b $lߺ6Uvt>i2T@"p-/G%Š\)X-%BEGC<~NF`OgmRZe¹8>8-5w v0]XfH~Z)߸NJ%+Mh4F@#P7┶a wtw<!zgP1fU( `z@ I\y: \x0,Jº0T`a$GAsg0Ll 1~6 I'$jDizp>nIWsXRfy䗭A c$u#sCpeGyK3t8V0-2h4"`"B#)qnLClOKt4A^ ikB)'j(bGM^<9QHtP~8^TLΆZ^zC:FŘ NQ|ĭ]*AL{;[f1{, ac=jHq i$ >{?Fŋ8AY]M*}F#h4%@?HwpvSYN83U s,B~3Z@rU K>7*/KAb9SApR&/Bx^9p Bp f#  WNˉǥ-,ٲ}GlߺY4 &:I Khfztȝᅭ?C,;{{|Au?)-jdc1P)'!^?1"^_'dc%PN^u2DNS[$I1>i9!@`M;J?W.6~.'ht]5F@#X&v3┶a w@ 2*a2ݰ |OR/ȖH;zA8o 1ņA&Q a Fʐ ,yxćincޠA 4㜤!c!#]dRQQVy4z<@<6>":pǘ{,7lrq`tXpm>j1`5E!u\ARE0H&h =4JKG"$ 2b'4@{46JY#@yyڵK(NC ŋF]&/_~[}xF`!@rU88 /^aX]]9'n[zz@DBǿi\Cqkl4Fu6Ph4 @GWʥC˓Vq'  y§AsohKLoV|zVRd!ڠ`)QB_'F`, Qf`Fnl{}YeOXV>uh4F@#wJDJ>;Z@ᖊByapqi \lxg\2a'5Qw0bhPz$$ʎ &I94lZ V1wTr*?6<(Ut$886 ,j F*q-:SMN3G4QZyq" r4(Z^2J,*%IAe@PcMy[e w`LS@3 A@#ZYρ0*Dx<na҃*nGFܡ{֢J.Bۥ]Ka Pʣo7ntP\E^tuuɷ~B8qBoP?qQ.P")tvS$X~lB+GJz-r&,uhxt=R^MVW`RJvaC*yeꉏߤ>A!^?/;^}L +_Ӟk}B},+U|lx,Fx2ewd?U, dX4Q郷oH@IM ?th4F@# 8nl~3^B?CG;+ysS$R2'k&Gim|'rԗKQQdffJFFX@vOr%yȑ#{;O4sPY 9ۏ}PrbjM7ոH4sLALR˵>A(LI(sDZeכ"%Sf`6th"Ĥu%BCg24<?Ƨ@{F@#h4a([OBA%=>GBYVZuK^V*LYY.&BhB9`i_>kb uIqɂ8 Y4xR &҇ c"@1dh~FohJ$ 9*欣L[ڡtAy/#" omS?O/z`Nl~"f%VTz~-N]"tGC3j)fo/<#,nH'2uD܋ʪ#Uj?  m0@AeK;):~&C#@\.QMC,A".hSedbaP*H +hf@$`hh44U  MRAoo<~X={&׮] Dؽ{2ά$j@𷉡:+9~;wN}J H&سgx<3+I(8u|&Ă|H4_+c<7M*<(CY2:tC9YEK!1̫Wj"A؟iGq.Y?Й?bר81`"C酊4߲Z~||{€esaq|.ί$=th@dT])\)#D(vBA}M-F@#hpА :@(=[3@xgy1H.xd!7LģR(@#ᡑN8>$p]Jj8`LI+.+SL"`NH<'2h OOk~ÎCeg;12|@*B)()'IY#J=`vB vUg5#W5*9Gs0βcizX3Y^%+v;_Oa96G:B`(Odp AV@pdr|);VI8`LLYåd8 |//4q!0 &5otvvJ_twu+ݻwo-֭[[}l#@GSNPg:諒 eP߱OIϖ`,!HdѰ㏘_+5-]2 K({?,7DO{3#&%R<ɴy%T풮z-Ou 1Yy\<o1)1ӆWVh"ڄ!  g{ >}oVxeXrl>^y}NGt$\i4F@#L1:. E4!AV N` ;L釭m6* O 0$daϡnIq1H0:A:paK3E9.x]+C$_9[D&P|呌;04<8 OPy!})ER5Rqy Mz1!Oo\DyH$XHqAN*]rU,C_)_]&a~[_<ˠF3eKv +oT%qw1钧?^G[ k $x'qZyܒvã!i[= eJI6Ym䗮Q>Y4muz¶& EF$M>s1dʦCoHAٚ1F@#h4;h *: +Rǩ  " C[&47ԋ+JnTGt.sbtD. d4 FHQ1K(H*x8M9u.x c̓G@  PPP.U;Л=@hYm=/ ~'Nm> + AI1L$"=lGdpϰaBJ3 \g8ʃ$!V  bºy0J(3}ѲG B VuK{ɋ:ٹZΜ9 wRt2R:==q ;_@k qITN)k.uŋl0>&8~B:۩Pz"C;w|n"QXt2ewzMU/RZ`~O*,<"qa Rj.L6~p a̵$a"9ECѕ߹2ADz.,d Tr˦3opː{%CvOGڻפra|-Ʌ7w{~r25c}q_i~q&8Y#kvR$$fn6l+' 6+>:ow\niQ>Z}E_oG3`b`<}U=!n#|&K~e˖#Ⱥ]$Gc 5 小>,q#ۢ5Y+~k e q_J'sŃE*G;%cU7]^\:K'$7.I{cϤxUl.68"WgT$A'žpL9Ee ֤HPF@#h4 ڃI(hB#!uV2+-EA&Y,8CI;g)[dr@0:A2p8A4'LOejP$9ߌV!D *.9Wdt0EN]G7 8><^t^dž@nI|n<e`H~9Ja\Ǭ>$Mo##(d^8(T9FqjT@d_7BXp#3 A Po4̰8@2? Iê@ }1J* B eD+׬7ݳUܰ0UdBo޼b?zH\ ߶m2.[uœ1N@0]VwyG^~eVԌcYPuB}5B bO3~ s4WyYFVk)C^fK7tGG }Gƙpu/ɹ*}6L2N9uШHP_#Xo*ЫFo&NzrډqMӿS (xKg§.o ?vq|loQfS^AyO^o];0 {NTI uϤJI CwH`ρ$~?H/?n\AJ'`0qRI\WL!w)}=D2ca=i*dA*7s@ZWly=8JR +Ya4A9@^C`X?Ǚ aJE;˵?~ i#\`E Ǯ!KA*@+>Dj\{0?Zu*sD GbGȝoBJ+_mT`(D:i4F@#X,aqJ۰KzFB7HyGrR:snCX 60(:< qj@ A'<2R Qږ$` xUa^d\#RUJsI vǶko<8sƜ*4cH$ИKi=Kj$b`~8_Xqf=f%E m!܈fEh4JVzMVTO~{[џqK]tC#Z_DYL,+E1- Wj F ;űDꁁ7G$J(_l(6 :JD;͕_݃|Cqފ "$<@yq(Kgs޺$P3`LcͥodRV59jwΝ;PtAz)j EFP%YP:G/{#Pb}ƦɱSYBMՀRz*!| 8`wA!TOӐaJvg;xꁡaz3:X0WqM8#fܔ 2FJqP&&,c@@)Cy~s.HYUVz_ H*79a9L]IbQk5lrH4^;(S*Ao{K2E⺖uLĊLǎV5>,O*߷ooO/?C"{kInn5HDA`$7T?555߯=zT6l %%%ַ4 J-CTKU#|rHFPsB .wN=;SH3ZyVF@󨇻e|zHʵ=Q1ۧ+2-OFl:* [1j m0ʼnxh_cjnخbrA*cRi^fG$|wBA*OߞF;}΃ۘ^Ih}$>'E"7$9*4D᪪)ڙ!,PFkmK IC!.a*BE]#^&Cq`ll~M),_7"*. r *40Ru50Jʰ/$d6C bܴSP~\TPXF~Q1(4?c D Vm#S)ZY)i0 -p}Sg!Wb Nf~l dWT7 Pvi|xW/E8AT>21E&T yHQT(Urh;hKj~,;'0$GA.1^rP ^"h4F` pmN8DRjCtH P0)efT'BMKjo_Q;X C&Tlم#Xy@>J}0ޢiH~Od0ְJπYʛdUHzt瘈LM9HhB(x1Vu};@IDATm'Xd?]0G!^ @W O`quh4F`v`)M>t %0nL+ `7&r%_cx7f Ϛnx@(P`J1ͼExx GH@1`]fA `|gmT/vp0 q+pJ(Pzu?9?u9ۈD(3 CB˃}/B'`6À4A6 OR9:"DHLH M9-lP 6t1rJUQ'(Qd#BiC8G}MD(d-l&?ѱB >Cw'#unyKDI $``q}8Z;6VAvHpx Zt7ן/kP`u۷+q~?s9}<|Pn޼)t4A.P$LVm;L w?^ q`CPgcm쀱+e@Ȕ +6 qIspw B;f!)BHРa'ppS Qa=bM 0C$ 7I:Tx{dʝGTȄX(Z̽ͧJb}c|ŵ4CbDK.wa(6 ݓv*RhpeN< ?ʽ~[`a-0e,zwcAăfld7G)ē>W#h48A `>c!|{|))NqC"p[NDrA+ h#/^7 ㆭ9gߗ`Hɀ\`'c>T7P$=lnWj:H$x`{w&F !:KYp#+}}sS熮 _55יtRueON5e(\ yHbnX qKE=svG0$H0E]fPZyQL\_=a1n>⤻U_Y-+~O]blDdc48x>˂+z/;{,]P NY#qM +}A`. zam۶ɷ~E2+W䭷RRy3g'Nzː s~| :6M[c-@gzl 5`A4.F 9#V>ٚpeՈ?9qLw,,| ɶ7~<$CCw~/'%AheUT@ z:ye\h!y!=P x P~2CĻ=p o`eW}ǦIX3i|| ')XP7 @1qafOBAkcsl`o\<ʓUP.8f;:_J,(<0׋uxCʋ{2)oж95 r{=?fXۇO?bȅrF١}w J*7ʎ?vx3+HxJd_$,6 wbF"\m6Ĭe`6']$8tɠkt`OcubAh5F@#'xntb:!Oo{79~`wXe#C8<1pA [$A =2ƹ d簑<,  JD򦫽  AZ ͐rH.gF䬑YK/⽈ ff8jI  Hxwu|̬nTFw^:29fU|33"K|xP a~X?SzQONh҄ s= s[87gДb%H9J  jFxHn( nTHF`heYB51P[xTGpQQVC ҡd@] 1`k0y5[I+2GYYF 5N*9t(؀ x OE˩P%R$ 8z;b@:)i9 ֌ڠa3Nr]? ;ŏu01؉;k1sP@.kq䢌?Аα/-$E,F<2J[cI탨4`U+B{P={Dye+ !4X+cyb2!YYY) w@{G jo/RSӝJހDN:ߧ `@xE&լ\n#T ԙ $tOP)6Bm#0j[F`"clƉBLԸ4~ӓO>9ѩ3̃ oݪ@t#yY+5CwkZ2i<7R[?1i`-eќ!DxmgW! +Óc$/{x q|,d1IRz{0E3rI~KW$E]oT.igaL3LЈ>_e K@bAyM2psMA 9=  NA'$ۙ-,#Rμ29Va]\5KVYOF1BQCaT54+g~GHU]^A2LI,Jv$@VV Z&MXPcI*Ju˪[:*xW*B!0$Ǎ'iT^δ?94.tHp2ڥT=V\[geC$'^F.Ǥy=R!h4, 9C0օ=2HZi h9q:Z$9T.S}s bw (o:/sT <䊬N~EC̙F="Dv5 0faϠ$sN4볔xb.P7b8ߟn,R: pPa Hav;p3,U_Xzz+ĭc݄}y%!8hnnW_}Ul"w|'Ppe~I6xǎʘ^/N,tL@uC)I 2I) w@ufJ(IΈ:4`"n)i2?NJ*g4T`'ZPFR&-pvS*j I˴I*R ظ[cyP}4P1B㗤;:,aSZTw &ÃCT,>,. &pL*ϥd~:cn<+Z=u3/C&4"i[F@#hJ[?>#x#!xKt))L^Z"$MEuMhT/{MHƳ|$p=xr$M}8 z .( ̝ ޜpN#rOcd2I8|s&Tf}C G)s ghÄ+\# aO0Z8wsb:tukHqU[}F@#PE(G#~ gm߮>?T TZ%K_TZXx:d  H(pF>n; ka^Hnd%L )uTVV(-ee//3nr[.gg9˹rtS:.^xc{8$r-ņǟ;jSzlSGzk 'VXpȇrdѾtVČ߬2(,'~~-=[yFHNA[zUme5'o{`MwmCnm&7.Fl ~}#I`)I}㚻e'7{P0a}Y+_zQ~)X];%RIJ=¤LXQYEޖЯ#Qd{}'c#ȩ 10ǍS uLOo6I}ojߏ'{ߏ=\{6b\f Z`Y/ł\=-ynHKrBBG* bk4F` pd0&1<~aă@lIaO |h' "P_EMP^I4pBAܧA$"7toW p^CQb nBh[u &k CL|aqH&{,s0`N  G"0ywdbλykYGhɍCɌE 0s5h@dB@~8cT3pECtE8?v2\tαH .zc}^ĵ0Ͷࢼح(]tNA9xw\v :Ye?S޽{e-uLeTȃ7E<Є'k5Qn6ay'JN*X,e8 uuu/R KuP/iO%XnNM2 ?$^xz8~ >VXDcS3CΕ5}2Q'Iġ?,7+#= /u.8)Ah|VگOt+^k *$bɗ zlB,\'k}0O*0 T[2~8Ҍ~R ?$XL1U[ȓ 8d`ğb6Oc`'(PԉuB^'v8= %\< q ">Lǣ|`0υ@~0nɎLOoVKP ؏$A+ +C?)|~4nKsQilCE.DrTgjס"zk2Ic15F@#0OY\zaHv($2`lMOo vyrĈI hM\ X7x\ൈ0#j0D4磰9'PF8+ (i|+ AbēHvl4ӊ Vs!<_{5 zyA6`HL dtP,р$E('A(ij78GiUd,T7|$zɂT Td`;@S2 .ٳg"PuPCė nGa[*A RҴ@ \;~``bK L{&U$1g3K pZOV;@̉ b4c{?T <]uY;kg+zSb>^vEh'[Z 0-XwPha JIfxX_HJZl $ tR"%I3{A4<;U ]+^3D;,-][m佒0Ơ2Bl&:&Ǽ6.77c!.uύ+r)/PP&k>)_ c}qf0&QcMTuY!\9 1㝟+ wE" /|b.hqXl+@ q'渟-L_(PЌ= ݻZl"8p@Q>,OV!Z@ʕ+'{vǨ:G)2I4Рٟ着*|= 3P@%i m$28i/֭z ZΟ/2H$⤓RR@x h{+:1 (Q\N/tDpr>m~D)'ߩP#;{[ɏ` pR`ܽ x7qi؝n"_ r&<oOvPdrFA&0)4~U(9H!L$wbjd各qGOQabCX_EQ,尠}^.~XB-'-eMx^y-T2ޘ_F@#h˝O$^`m.#&Ly#S:[E*9bpB8 mIRi0(!p=Ǐy ՑC P4ٱ{  2,5 RE)S5*n@mqC^JnH$-(@9/"@Yl(6jY<uzC) O-$ 1ep.As4rT317[ xf8I49*LjL#P n0Hr/Q//Z5 HpLo-8gn|f]-74bdNpńs|Ze>*-^$ba͞NY6$%+1Iz AX.ODc3H*H` WY&}A~݅g,3L k?`* K^zEٺe~/ݹMЎP44o۶M/~y7d#G矗\jjjT_]lab791ŋJzH'֭6Ynљ &_-ԥ̋oBg;-28v̗2~W*++@+bDQo{˕V})|?xJ)6yAogʗqU0jk/_t|W22u@n\'z._>hJ_c48Kg-۰MxHӆ\! grcR1۟RUߔ'C=]׾eا? /s!K(Pθ=98D/˟ %2OH͈Q/>x`R$E\GvȦXPb=x%-)LU|ԷŦEͫI ǙC8ۣ@B7>JwƂa^j!>/9=ißziLF@#h !@ 0$a*,BH.!|4ȅ.D4ol~ dр*.I& `Isi>c<Ă C*x:c/ cq'.20R4fUi^AOcDUy”;`4|n\_MRnz ;xIjN »| ?IP/E4hU#%:`\HB%1WQ+^}0R/Z$;e| qpSt^q|x6&S~_(SC1y4y^<,8A*/gEc #$5XרRvB,Cn `5 b VI໹Xlب$l߾]<(_|ܼyS~_(r0̊lq1O'گZ'$=zTLO 4P( wJ_yA*(}t̨))D* V}(׮KF _vVކ p=|ƙpN;\@#]?*>H|)yYvHTr.".>,􄂌 p\n?_${^3YegsƃMoEr!&}E**-_'dRI$m `R^?eNk1?8; RQhL%N.q8Wy@GY$2:)6H b )] ĀPt蓋|Ga(:bKڻKB(`>#`cE刁Vu\J|vWfc>1'㦐DT\ qH(k?1aY=wCj~3er̍^G咆P[#$L9?E9f}F@#h+.ONqBW;!E, GA_{wLMXȚJi(0 \< x1r$K dAR4 .Qc.a 8l]x5aN` =< {xu)0& OS\xJ!)' {a>bKD[Y;/˧NыW\/پWxJjnC?xH J={T VJU0 s!*@=`@HPEћ) * 1eL 3!aRG1,8ZqQ'^.Lhꀗzg|Dm; GPpc7&0FXUvc!eY#'؉7<*$~rr}jN#17@b혳G?K?W-l ( X4 p^HV/ie+0EG?icZ (T Bw~W(t}ʆ%Rl% Wl.'XU3{&77%f6+mg(k'Hc#'>]RdS~/},@\QgbgaQ.u2#Ľv*nL<_HHb`<\U-e}16 X:rs$bv<bCm5QpZ"XI#h4YDsO7B2^``F`x#lcgMB 4ND40Imv&3ܪ !lELr$ `, xAh@{/Io W|L+ip*`FM({T&u%ܙh'Uկi,Uskx2 並ehSX+ѐOYϤccNF7{W><| 6áF a c wQƟL9Aq>*a~cv 56 ӿk=PlشG|O%t5&A1\`~K~= ڵKy /|3><ԄC0y[ʁ E"|'VUUݻe޽J>)?:J(+dmm&iB|BӤBc/_.?я?O%.L| Df9A* K$ogAkxd6cv=+jKVWaޜ)hAƨĂCR/W|*ǍgÊu*{R|ء!et\ mU>< ~|/ꖯ?S46Cš -jN䱷^/We{ioᯩZ7>9NH8֯\YRy%jlʟAU'䳖tJbs~~/}G9U Ͳ顧YrCuc?.Y^xLLH1F~E !o$5;Tl-*2M4^<%'; ʓ\WQTl#PŇOaFYu-XePpCSjlEԧo-=/u`o°{XBcI%O,vFw|% .o5FȌ>n!-Zƹc%RՍ~bX1")&LȰ JkzAi~y/P!nbSY'F@#h7+ʘx*teBrA6T/i"T3V4 @T<)1/g |Ȱ`$pV/n @auѱg:Ji8pTPNiv_}62udKg˅Tc u d+IK(X&) BX/PA8#T ;'}ޮ~ C * # }(%2`o/`*1mOg&7K]⑗%ܸA T$9/ֆmHX7).cXAN'qb)CNgw _ <)E 7h$ 5o9 XxAE((tB8V]]-/ѧ|;#_* ]QQqBhB# wߕ7|SΞ=g:BX  ηP"\T~WI(ؽ{]ZʰnD`]kF@ԏ [oMIju'B-% L^rLkJM8x#3MrI~{^Vs_hWbr ӫ!se8#W/ǫ?{\'7$ ]!;< UN+ȗJ.L 8O}!Lr u50ޟ=\8_5y$P.Cn7k-=F2Fygb{=~P.8RVi\^BUT`0)c1 A ;o('~ 1"$r ؍1R~%M.&f(q;+M XPKU S|л?X+jX<=Pi8.O,ug ?R>*q6]䳷Aֶ?,KM1`W{u[}BiIƆ$D*)R~A*2gc\\;s # C)W3:bjT,qNOXURM*|dcأB?_\zPe*CRpo2꒓ mҴ)G Q/0#'4v=Iex Da6^/-mvi9z55yZk{>Mm x]:'G&slVaY1f)^zwW($5^xX)*CmG7$ўʇ ]0lIzUt68<|΋Ts vA6ѝAF@#X@btS'@**X "!\0χ$ eV4,U `$B x1v#̂[SYqI#4|]A (9y;?yQSVmyYS2ǘ5:W}ik5~00uAE.eض7_6)I$B1KʱNRL (C0r>Ç:HbE<0ۮBp{ b*J6tuDX)~8CH6b PwL(h^^^}e ݴj* ٱcl۶Myc>}Z M6#<%>zYsrM(xץOg > !tZG'F`! 555r!EՂڐo.k!}wK~5RxYN\dg8- i(HMr> o``D8% @|~0R*=X֘DztɧI[km_sØՏo}.O7#8yp{#sIdI *097! gӦ%AzH@\_./wxt*Pja)N+#rDv 2XjΏ.[ʬ'"%)'O!W?` kU$$C=qCVP@>2A9nIU".Ǔ ׻ڇFmhEC9Sj8) lHrİS|JTv<}&ԏ2X8)Z1iT`!‡S~h& 合pnu\UeH1<З\PKe<Ʌvi<AՃq&'N. bH$({X]<"ϚX"wS!tc~V\d1Pd=b>x#izmQ8ctoɕփkQ/,bT(iV9կo)>ĘE:5wX$ iuw%Ͽ)__(#̦u?3ߖ 'JK^I$Fk>}(Mb郇ϑM.(W!躜//`zϐױkDU Cqh4! zAL Zel`rh`#3- ҘD lUr'" #2F<i {r _VC( 6;L`w{@IDATiü'(Cu|XI^eci~N#MekJY1M1:<.{Q^&)s-K9ֵD%y[(>ms6`uCU*bIPsAy^c`1ڥdqB=LxئqubQG$! 4Ld0XQ2P^;/]˧OJ, ,.G<*Ą _Af*,@QN(FF/>$Wwks1b)  LU}P}¤W4迋Q8KM%-k^#?wqqyPFf뮻bC!\rE)k!y,OCE9|`?<䓲w^R@b%T/:uI,o`/(CYwww W^^.PYՅs$!xc/^p?*WLb 1loPC.t&vki,2|" S\;<isO੓싶U/{C S'f ʄ\m1@T/(<\֯V#tA ^i+x:H12PC-8X03A"%Y!691 /{-mqNW4-1qi5v>Yr\ s`C/'WF*>}8oqH`N0xɺ>!WwTg~^ ]åvBUVnxXӤBhcF@#[\n$^'!AE C LI~n/ _]V5`44 T5 $,#eEo !3q9|M=[7IbY8܃x!d! ,nq /$}&I]89s&RXmTvD#wvXc ijla`ȥᬐP,3XQgV`x8=r)$HR Gk<,"~ XaoxA8 jn*U+#RPc*:d$|lyt ֙lǖa{| Gќ~L/y@hشGVI'&dc R*U}iE򦦦R6C}Y;? bjҥ F\d>0>OUUDnq@4b11XdVcr ЋI9ލNu<0ڙo;۫z7f(e ԝNs #Is%CɍzWTVJm^[}h4i!EˠSSU.01R"7AM(XH]W)mA%O8! |%'Uᗶ?X=PIeLmx XqQ` K,@QI<}:̵w3&I˺1ѻyCv)l 2w8{zqLJ1a\M"K0?-3Qo7=%5 hk班}88ɮJ&9.$ }0FW='q (j;+,B[RkO5QKy`J+; i4F@#`#)G|X0rI/tѐI0P!^4E}JQ0 sJfVI$`{H*QD|y%U5H{/g" DB!AH Dh7<`=PwxpˠJ*hSa`U8  8H,#2wMwXF[GF Pph4.[0T ~Jx1PF + RY090hAmbE $l I4 ';)f$B:M-+`N(.sՀdg>t1 'QϾA^+1쀒{'2c*nYוKvvIa!DR(,V8#ETzAt !}ưxA`9ǬcN϶ yͶw@DB8%4MU ғ7 3/+Woo~;hF*Ig^ZVX!;vPéVp|;vLD `yA{)B'|"f؍ R^G}T9y\pR{w/=}okݻ C8NuM "0DSwz i )u2n޼wܪUJ T˘$h&zNEB/~ `0bI ;QP]`kDHs`hO$:oHgX,3#FwoYlH8ٺ\tC:l+u 9 t$Yx#ؼ?efH˵ mG,&=7iUWI}.c*^cUhV|q4.pIRO[F@#hm;C^xUP+`NM.z42AoPQ^ u@8Y0 *TqxXk_jP&d="`8qe (W/{9nj{H#ߨ^<V .@^'kx'zq!`mp=dcABG҆;I4Wvtx 0t"؝P'hR)ǫD;c gx a ] x"9 HDC̈́vୃj 80:=/N}%C=jMyTknKUZ U֙u 0ؼg^ U pM}diy(d #Ņ,4 }ad^_L`eY bIJ-9 ~g ` ~ E'+H*"<$\zU>s9}rZ=_+A;cQbC>#ITWmN93K/pBL$||ønG& `nc&,N T@v'%Z֭[]4We˅䫾'~Q2jj8Y0+Pg1:%C_~IÛE^e8ӯE_ Ϙ7ُH ?RllUYh4w XrN'F@#h/Z A $;`o4UyOv )QDr(4%iT 0gd}Ւ%1T,)ɁaI øRlbHD X^(!O(x 6nJݓp,fm㣽̔eU N: Ňwoy bd ֈg*qUB&Vvͷ 0$*A0x#C/* 0 9sǓ$^AJd]X̅+j%pD6 1&XЪ' < ˞+0kNQ%KErԁa=LLyW4P0q+U*'uE ,㋋DϨ`V# anxۉRX_]wEK9PPb[WFr*[oĂ'|Rpi_pP>})|.]lt&~{U/*AP9zb5B(x)Pu=r߽[ɺ"H6{ʾ's|0We0 Xlr :mMQ|'$7lXyU/DN]s*U*R &#jy &h4F@#h4(&ic Lr FA?i `\ҐD%7().l?Y8$aL\@I6@$O8s]$6AbMeH <\H7` n/]ٟˬu-[&Tnʣv|^,CB"ޞ.*RER&|"eրZ_PX@/t  xj}6dՂ6i_$q_"T,_Z`39 Q\@baxE2`_@!'=ApEjo,i; 䃍G9r HBЧ!,_^gdέ׌2>R/q7Pa_BK\@ž={$>wP5I&CD$,A֭[$l۶MXdvL۶lctZO i)l*cl%~2jj8YYĕyH~!&lA /|paǶ SX0C szN⿁pjQ!a r0S^ F4QU_'AՀu leg.1ZZn&‰<ُcH$ pˑ|e<b `c~=_ڢyC|d%+dΧe}OIv c1ocJ$yD*تhe@dIc d7B'_l޼Y֯_/V?P//^SN-I}lrE8)">I[dNHG  Jn,{rYIMB Y`ݙ``!1CySmbv }E:`G;"Ja3q ɥOLO}H:TmUVv&{Q?XO>Z0$O?bz%8 Pֆ |G+~&L;|M*\X#0`TFs_r.C)B!QGb"FVb^#h4F@#h4;UP/4B"d l fn|L 2T 2#ie[KK#^P'70`zOӰIYv1Q.H@n\~(K lω7LB 7 Yx$5hiSH;d fC|+)>)  @w!l\X(p{J؅j{ޘh(@3 35?+kJ4DB 1O$`;| HDǑ}bL!‡:CpfJ\ @o],[#l5[THeky,s_1RD#a|#8LH!-ĐcJKR۴ZyUSF@vaXfBv1Y呏㔓/D( (40TE Ne!q<,ˇ)dda,mVๅbsרv0L`@qBP^p.iz.1Uԏ1q7DBA:D{AlIG(pY,#6')hFr@$H@2Q`>(RA|]Fgă BH P`8) <6Q&½b\iu&.kjjW^yEnݪ pQv??~\lr߯ﷷŕNyxw?j\%6mڤ$ٳg2${oMP.;v2>u3=c3ڬ[,Ҙ'_ish v_TF@#h4F@#hPJ K6@h2($;AF&@^k{0'" ^@$Xy5PIuN4тI-F%m-tC1TX0Йeĺ%94  P68܉>{$= D!'B= ;>=A%HH4Ø n(x~80L668vGaT29 <22@ou.Kb2X')q>2wOcAٸy8tak2y/(Fd`\? RFV(2V $2ؑpOCQdjv=: 2J/E%  01DžwMPuDĪocAWwwZl ?E8JWf!4Z/caPPo͛ࡇR… r 1ϰ oIT$M)2O=RSSc/vMVMPKw');I Pgrm`YgI=Uh:#*Y0;_ &~-CGS h4F@#h4$<P "^٘ErC#d`dh1lkL ҏI\Z,R% P(Xn4xBE>۝ex3 fʰi(0\@ɶ[nX2m2 LO~UQF" ʾ[R$%ޏqpdh@#|BrhvvW@0 Љ&\^T8 L<a bB^|1#9["u^J{Q":NJT2i1n2i Rmd1mLJ ( rZ7*o8 .`tHB"?2A X!>(~˗HYl)|x je %+'H@mrSߺ~$5vCfɏqX nNBa&S0ܢC>/F_1O(Pд|-_ՄYCx'T8]v)}I'rh?y9rDy?РHTg;88(jIׯ_MN˅cX{LV^-I^<(:GsPE=θtI h4##pDʩ:W a)7CF@#h4F@#:0 24騩\@\q'^L 2NL|긕H ,Jb1H+#3 Nc׸NĸW0h@M@sDo;{$I||   c$"B!l粆$ 8N˽>jyA ?P6VpA‹GAGr Lvege.s<`$  !&5hU)΢f-KƀyHA m7GcDhg 4B@IE0PuI_[7`/r튴X PqL:.Ѿ"m#µhب¸h4e IzcԚ!DFdl0bI{ D7I@I=UH ”,2T_)6VcE)WK8 C(XּF{cjYg''r CB8KNDx); ~s$| D" 7n4U<ֳ,k7 﫾.PPhth˙';Oj0շ"T-XTJ7^7w4F@#h4F@#0^BhAFC 0~Z>!-hkҰt4,+77a^) 4cp"^Iz8. $OrAT,Ifb(X~RaË`L$@ݠ" āR/0c qEI(ϲw<F.5<03)SM?C0BA+ d \;!%q&%9|ƫC|(a(PY' T6!dB>!TL wsNɠHcogK\6 oP~XB"(/F~nCxU2eSZI*^ۿ[MUU_p[ʴ L#`D{dzTT2.`PJŏ[7Ck]5ܛܘ![씯Lwﰶ 'cCԚ ! Bc<2Ϳ#vv0l?0D}L52VQ<3q2q僴tǮ.ccQc"7VI#h4xʒedEb_2IwUI&(<;Ivsil}WY{,]Q" +Xu7U\*K a]AL y>Fu|lIfGu;9+-I412B'g5[z>r>$37E*+8א|X.(ZLXտ2qEH3Dj_e3FXc7F4F@#p@ !<Fچ*O*upU4kqjȪøإq20ʑh)O-ԓPk|)eiGext AH)^l H^v=`\:]G JHXHI  ÐOvDH6`g PDEBFP*>ܽJ>V`q&<&ji+Gr0(%..jL8/}d i!DO [D /<= `ú{Fu]邻rrA@0gQY-ٲdɶږnmyz5fZgV{Nzr^Kl-ےeʙ( r.|߹u"P@ln8;{ޛ$bg(&?<)}gzď\-D;qg3pXWgrKّ0Cy;`\kt||\^h\K@uu|_zHm&5399)j!0%A(կ/ y 7iBt뒓 (te5EE+ͻrᓷ+wcM"#AJ]EmE6t’!yĢVQh4F@#h4h鬮tOU˅p)<؄ !vHm-bCAݧP!``NrDŚZMY%(b(NÛgP_w!(h`cXCkXBnIi[DXP}$Vq,,pO HhJVq bSbрpHCYlWb< UTGpIgqK߅ $)"J%ώK[]+֌\ȯnb1 B)KV$DnDI( 2Qg$?/FPZV*6IIenx]c- HV)cP 0kmX?3.DE}8>xpްb-eXXOE  g iFf5Ч-`k_O@{(X^$w{GxWdhhHy 8{ #w!{KG7|S@ 3ov풯}kT@òt XW(c}+ShPHp7nVJVhY XP)mkRE#h4F@#h4 D.TKd#$V lVP:.YO(@dA:])@my0~Aq\Cl6):ˢ2o OHiEKmnJ`zذL!\`258&~ “$b FYD7\ɒRxe,:Cl.EGgj%+":\!xNN&0wX_Tb7+CN * `sVSp`>H_wdb H!tpyO4niW9;Q6?!0D'b1Oh0  lo 5 BP8۩xGe$DAY: G*wIiu+"Fz! ?"gҶ7cQ$#ю$_2(ЏBQYGC{AxIɒͶ=7__Hc}Sw/! o֦0*?Oy;S+*/^{LyG8qLkmկ  BGBO|/_7" Ej,ѰD.D"Jj1ڒQ}F@#h4F@#h4+r0Z-#eп "TUi,%մ.2/I7'(ndOKts|/] FLLRN~ZpM?~qH L£i`Z'm8 D(8~XZ8FelٲE):ygTXR'<˚5kpK7m.O?<ra2& p< 7ox!velP0O ։ -dHFCh4/θF6t)nG“鏙'G]lTR^Xy,`VeCjRzRbP)w<5zF@#h4tԃPP):QkAF-*O"ĩH1D9s~Ks J`x"P Vٰ6VVp/n>\羄2.aE=ũ X]o! OMHLYG?o@2 ` $B!`fà 7qvE@`HRDȳGb)/RP!q$cY x')+3ˌ %D19w/}=A( N26u42ã>2Cba]Ycנ\b$t5:`) o 7⇇KA8Tx'٣۫r#/V#l_`wa,4JeMUR^Ғ:Td8rpDy%5x\x[x L{GO$, @A#dvXZn@IDATD7(XW w(Y S֣kpA 8|ƣ 1Q|HPrF +Aq0B@hbR-IeD8_8RXFkxLA*`YlVr7D(}C E^nPo]|I裏_y8s<}OnFIȑ#(Bg0©S$sNyGp>$Q|"['=) nv 7WQ:]Z#Ȇ3,a,lF@#XEЊrRX*'?\ r4F@#X%KgJ'*%!ow .YPlǠ #0.}]X`o3,~/+>i4cT,9I2+mn6.uDEKmj!D~o\dgDDE!e}Jkd}p$ tPk 4UH4\r|S "FH؜> oQ bP"k*;l'$%_yH"HPc9gEje:83K>P_j &|G ]>R__/[n^{Mz)9{tww C#:tH>w]TA/~!oرcz1ťqmSQ<Лj ~xE&yCeJJ+j/'U'Δ M*A#A/: M(ޠh!.n6]]jh4!B9t zcYצ%u5"mDڂT]J@`pedLBQ1A+-" a|AELCH1A `m NB"&nܦJQK o(]mqVTUjκ\XuPDx>l-p{q }*W^,#a|I2=9%6 @ ݂g.YjSM  بH(._oJTm^Bbaw6In{Jbc$:}N(iҏe4!)aנb&I^K ڷtkA'A߸* LM6  "2n ?$m7I/#\Z0V*Fs %6n  B)XBb=DuYO B{"oub2aE;\Io- H1u֧%(P:wH(ب=БFșM6)R5\#駟9zo-= 3ȁ 2|PoȞ={T>$y vn,޴p0߰QY\0B&xer`T Lh( F謹>_L&X%ԉ^mPxD .x6plPjG19w/}=AXjQi޻]JP7*шnx#89( Xq \h] hQ"@ˎ!,ʡOG:`נ 0O)6T @, 䄄I(X~@B tBM4@=H M׊ <5pW$H&D XA*5_ E1>B`=sr_29ځPgdBo}myP^X޹TVVUW]%;v{WqerE,Q~_Lp]w)OvT{L(>!BZ4XWW6T~F`#(Z4FA_ŒG&LF@#8| OH2K6iăH0/"mCҢ89(}y0r¸8ʳ(*Bj;hsV .ABH gs NGqRd,a]xXh3O %' *a2/#=#`qޏB] %A_ Enʚa^T.>V6X+aiUڛ{H >ck0=<&ޞA +Apb ݂~,`)OJ *DtBq>A \TPT Q=t҅ nYҴw+AܱA?I<9'_A H6eb18 u{&|,6wO^kbw`Ē\vWub<2  &0B%&`rrLB?']H+qziPߦ$ MDodόe(Ƭ=kxJ(˦v`jK"!Y֭['7oD߯Bloo}kr-Ȗ-[mՎ ~㒷 iB9gvR=-d?k +ӧOKWWV5l߾5Z2N<)Ν1 SN2rUceDe1dw%{8/.}vH0{~5~QRc%"%u T쁅TLZnij9u9zF@#,O8۫D~`Kf×)ԏ/\Mh4OHHC #Ho!!HZ4W qpfNz -k MD\z,ZBH3LqaZ9꘰PYSqeR|]q U< c|>C#W@(c7$2zLbL:iov)!M]PXD`]VXL@ `tIec̹:1Q6+Q@aoTf0h wr@e04$X Oe rAÁ OC4dt ,烰, ~aif4(R\gXS2ݨW9b FfF:?Q`7s @2 CZՁЛHנ@` *H+=scm1c-X÷"G\e ;b,.Zj%zC|mПA{!gN`޽^}Qz(O7n X# U(y>)oR~!4`7ғZxh&#gU;CM*X2>CyUf! D+' ^CjD£!i,s'˥oR%X UVб^pj@xOi3I k|K9P*_=HW5*Ғa(X`=> MRY t[)x4a>1Ɠ?1s@C3E<3*K]ʵ=][\HPaq^.2u)AbsR9I"R "o  ejp LB>?$D tC|e@G$PqcãAUJJ-r؄$Dddq|4RnK\]j6 q(ӳ]z Jlތe7 B!k2[@lxAKn?DS x/`(B&irԵ\B|Q87@oP.y[I@9H2HH܇ |kr$#as BE!aIH˘$s!'A󣖥C@ RrYYڵKk1啠ixOW-+# {o A.+TvNNNJ"elYT >X2rNxطV5e-`"{ ߸x1ZQ(yژg5øQBY5HaAE쉘n|iUc[[SIXHv3U#\ İؘ뽃}byJhF`y 5[T*F=?2Y]M+ʻHb1K( ,# "dK8ܮ!ݦ?e@DtT\sS^$ A5N:>h ۍ$apS^(1kVit:,}&Aeߟ]_֭KYU;MLVĖI1mf5)ʀRujBRTOWL)uS2TxPЋ–r-OW0PP0tUz7ȂL@xH(xg}o",Nerۭ-4-v\qNruש(YUc°.KŇɷَmȆ_cNَ"=r 3p<Rx2I9h4F@#'T2|Q"7hݱJ~:z9D5*y,kIV$g!Hg`\ChPkՉ]f"a^:r}SKFR_iGb-+DQr]mhFu~mzafhlb '諤ӲĎ eb:=ĆVr}$c}õkFfQ}ĺS#E£{Zj{oF~ȐTNQ#N$8FZ47V%!0;-j~jL &?xO :# +D(~*IlSM(~#&yZI#|'b*d7,lwԧx*MQ* ZR\[D(Od{l(G 0W4VIn `hi~<-1x6A(˩ 4:|d]KӮMRRSq{)Lc7DFK rVWoA\XgC96Е'q﷤u=)[ƥ< eQYx8 b#`^Ixx$*EH ȇ }ua\#*<̜{ @`0쉉//-&,:䫢@z&.!WJ(eCgݜhGC.v܈o"Oo=x,ĘB⹙1K媫KRT )$ğ@b{2A XggZ;`S6?.u= I 8MŵŏBOJ:Ah"oN|:hqo*$rШpAXh@{[rz8B!^C+ ċyRo zǚ^5`RTs#pF.:$=%M5W3iDk 1\ y!V)<Xl6Gs = XB3PoU%Td*V`d@Qy#P`#mPm.ZẊPѱN\*ҊI7p0((ʶRR*aZ(!YApt[w˺ob&dEIh4F`U@z<SQI.TP>XoV kʒ>?BPӌےR!4ԃtcېDGJp ּ ][ ue)ǻHBڄĺ*M".96݇DC.qJ$1ޙ$>b}GXRByZ.# KHl,!!1Hw#!i,1!t Id $8pIt6Oܼm . .`وk0DZna ̝9OHHʈG|W" lYLY,H `ZFϮcKQNƕ%(U$읐*|25؇4!) j+ZU4궴KMRC\ @^bdEq8-,[B62j|3G x '(PPvny@Z;'7 fq> ǰ kP fϟ`k}8tsIl2nʷpXޙB RQNFF '\*tpKAK.R#KD#5)hp8N*-{P5t@fuW#XaPID)'IZCxꖨ_Nc|I#Q} `i̓0A7>㲟$H1 I FjŞ ?e \B Bo}Ӎ $M,Z GΎJDDb6CϷ.U0]_af.ߜx\1^pH(X2v\t}~V4K I.'5TR9*4XF+QXG4^ ׋jPqm_Dl:a\_9TunH0Ma|"2wQ}@P^x;~B?klwKYS؝N`Ⱥ$UxD>c{WnBڭ6q I'#<3Ay(('# 0n[:v]*6x80dRʌı !5i'Ek*,ɼiˈ3173 ^@Fxw-c>*ǎa.˶] ۶n>b# "A7BL͡B p8~HƲpBEa ܞGhw[RvZSɨpnQN PZ%h!ICwQC\ @%ɱGbG%: iRUH$Um|f,^dvE@iJv〸"F;ADBa^qL@%v&i! ~Dm5.l*:D&NK1M2[Vd|}-2:|^zKSB [,koPJP r;?%Aau1e-U׭uU` وs/(*rْ b /ky"<էgFYG a72jV'~寞)ӓ|AlTZQ+7#@W)~H*>:GF@#PTbxKo+ [ܞI*(y\0̊iP2KV%'F@*3uZVjҤU7t5 O7OA+V6"19#?!tiWtvFxQ?Z4RE ڹa}ʁgִGө'!4A+~ev=lii8DzS 9ʶ$k*wø7by3+*m䟜(z;e/r&>,5e"z@G,<}BR{^ QyH>ASZ4=NTz\=GnTJ>:+G0ĂkyesA<`( kd$`p寲Nn0SQi 9yuv <(#2 8$=dZ;jwZ(hqO:U U<>0a[2tY>P:ܸgppesm^h.0<$Cq1C;$ RܵRQ[ "A"]vKFq$a  1Xq4 =3d9ULw}?p\C# ?Wy"|ӟzq #Ү8 u@3h4"!(bErq.a=-M$d\RY?MR[" Qh1/R..Qtȏ~$l-,+_67Tsă1XcYrhnI_5P,H 8Q ? %/(*rg W pv#:6`Y!|4/,S"N]^t0o~Se\՚& AFHxַD/s!gsOΥX;vûp'mGn9vLE'٥ \wIPH6\{A a"?1&-[@вL\3VpU2,PeقaތK w+P1Ab `.P pQb܀6(0'H,X9!y2|^%I')Qeln@HR)VO"$:y8 b-Y(sο?ͅ HƠ0#-pwcQ `G(qx';xG>+T?Ўid%^Gy8pU]`I=JxWB\Է$k;ڊRDXn-O#>_)L\߼׮Rs_ITW.aqTCAB2SM|%$Pu]TR(  Ts8_60uL_@e?6~֨#U4z\ؼY[DxBwe1 q @ ٟPfR@RI>bG$;OG\>!င )֏yA6a9L`ėHHYO@!L=rvVDB<TY>RH(u*/9PFbօm;1cR AD5 brWO(-.3 "ӌ@5CI|.90-Џ%d%K@GvP~܎bacG 1XO6ZzZ+ljwͧ? iLlR#s +_$\),*%w$99^>WųG7]8H្5THZ{WrCuSK!d(ދ'.Ë44RfBS!&d/n+2!0>u蹇`G Z4H12?PB0 O" XO̐|NG#e$[c>G>ă0p6,DP24*gR&%2-BwRd20HC"R9`zZ8SJG0|@&XFx$$ q*?n03';Cx<ϵ;>^(Ω@|VJԍpi_E?gCӓbϴ>£G$2Ob>PǕrұX u(8BK1Xk@6hKi#ű̘d sb)X0XvxG'qp@AK<},p >ko&V7G臬P#˅cX#<8ٰ+Ҳ+RVۉa° 8@3SLcaՏ)$Leq8Sjsۖt-g}TB(ظm|kʖMC>t!0_BkXwUnp1QurGʥ TOy59U;1ɃXrQ^ 4-4P{?ï*s0z }[,vE<LJ}NSi?p4u ԟx ̈6w\OO2a!FYԳP*E{*uz1X0qpd X. %9}]i/tYV dx,b*X }?!;i* 5$w"bHBNh4KkCB CPNB)Nk pv|3F8=ns*oMpHD=CNr@B%-FhO>y9TX>#?t|*詀yl}D"?)o46 %>0ܹsFHy -@ IDopW;.sx:UQy\ ΅J$S @? ,K:A2؀|~sCRŕ}ù c@o$Vg^Ҙp:.\u/р^ amOG,Yݱ_"e-ge.,BWXy^ ;)'*z~_A ?/?- .b P(+__:;ցÜIJz-dDG/P@BB!?9?j)&R(b?B q5<rpNvCW!h~H Qmj) Ge|{SWrZnS)1a onm036{~բm_M@<$ת en#מTX#6i+ Ms(ѭOuNdzRߑxZ+]<9Ur-IRRZw! 2=K]5g- E?P&$MPH9uQ#qe+' o |=r`7/,-'c#=rZwIu-M\/{#RRAm=DZrClO=n+n|g/ʠRB5l!1`ǎf{|H%.ܞ'$1NHJ%|'i(X'j g 4@qqK!S*R@0g`{o""@L`!a#RI>ʇP`OH̠w\0cy|,Gx{q[?rFE.!UJDL #80;142ţ=Ҳ)E̡M^{x='YG^? 6bwC <$/2ka /P?ZΤZy! [o>֓eE90 {N)HER1#鑤Uv\}7E of]݈شn\udkN\{.8ZR^z, )aeI*BTUݔ"Hb2ذHau ^Bcz)ӫTXӽlbZSvPyaΐezQIwD>@KZ " xJҗ BA#eh})tT,! R)#{6h 7 rFXYE~%$zx1@\ywăY^3Rr9.}]?MOs {X x+v2SpY[cL*F-@Q,lG1D0?J$[)J›<+q` L9$9,-_Rҁ)k#0PmՎ95;j75h̼q8$6#ЏwfPBH4)|BbD $Sh3 6QVo"d9M+x\L1w*]bw\8B{.1DTÛa* @xWY} ɩC!D'N4&M!J,` q\3{mD)AxS5yRȧ^Ta-p&o_dhG< wHh$Nu͢x j;O V[.&#ބg ݲc&`1;kU|m*S7]#p%"@ )QK'2,u k ˁ'ENUZ!{-S,^:/$\e[eM;{Z, p׹GnoB pl)6gGEu1^WQzA/jѾ[ F|zA}AIHR{ġWU(TRV%|Fv[@>Qy-Łeurg!QuUm3p䔵[e5Hp: dxl^d)!6U1X[#XP/ΔIOA2TϖdEٳg z.Ӫ 38Nsad5?SHgYxK)@B덹2G^7C1<2OM I!Yp;4II*/$dTǐTJrE\71 %׾*g<0مG^}Uw HnsO?gfsC*Yj *QXZ@(h4 M 8?Qo<Ža-p{Aὀaj?+QemR}-gմ@UmքDžwx48Il:Cu1JG: < p\ uXHp>wIl O{SjţP0Ԡc|z#hV{@ق^7$EMy{rXwڍwIYU;P-EB@  h]̪D~V>GB$03W)n P+xd/^Y)srA @c*ERF%,fLئT@n1EDee^9j@IDAT/orYWJSlИ!~tw;Ga*UT3~$3"XH,tIf>\EùA(P %tljRc-&p]"]ZOL& TwBBS*! )]+ 㑞ijR"$MUJ_E.&{MdB)6q\@a x?lZFw, [\dLZ{XG$y%,ÃHd@\,hpm䨿ElU[Fp8jj[FP Ue`FA45ƀǰhٮƘE8 X%(*RRP|`/"߉I(ȵϣ8 qi P G;\~_ɺm_wYF\{ʻ  Xq%%zzh!cҊJ&1F ~un!E*ƼPLp @>1 {r[aPR P`ˎk_Oַ) ՛43N(I::։ۅNF`Xr7|Lsɖ,3[eᆅBcYgoSsWjK/25/ÓAG̐ ~Z"15lrC[A:I75_~kzvTiوbrV&?bpOe˖t/aXO s@&? 2b×WDFkl<1}'d)*t*fI9J3pQeJYeܸW59APiB%5͐ <OZ lo!39]ËqR{6@z'pXF%i@? @$p+'wZD,i, Ug=XXX/4one2=8VGG.o$5*FF@#J7wZå:H(/AP'YHH& $3ȣfc>YE`'s?e؂B2g>cOTԄx!PP8e#T<G|y #@1fxdfH<3e@E#0C.* sQu"ЅRW^F.t)(1Ġx5s3$n8BRb%R&dAzx5xwux8D%;k9bu91 "(~kXh= o(LIE㨻JMܧdv2U; U Xw=<ûr+Xz{ L݆GIP!I0eocT " s"d`Z,SvpF1<ϗ/R! /y$Z9}&왣F4<<)$ƺPuf.{FHuCM^XQa7H~!\$ Anx𷒤\Qy$+䣐v,k/9 N_p;Kp]F`/M$ň["ARW]lD^IX-F[#yqTOE|ɹo?UivK,xo!l*L!(~moW  K }a ??W#;E/.q`R6 {EƑ{Sk;Ɵ$7/g w ;k۶ɚ R Zze|Gֶ@Y 4>ygԋC+N`}9}?q2)!x$,1l氣q!6Cx0&-T3), +]'Ɂ}#>ǣޅ64l_u.;'Gߒ Q\|BɊO)Xvvh^w咶EF`!@AAfTNM(h "\B3<' xW^K(I<7T϶=FVd7T3ʢ}&1-S2)|H뛜?Ula( O%O)I39,?k/3<۱)Ig_Z4y!0ű*VQfLc|:EρPLpIy+=~<[JgDRJ6bN]gdO 6SVTj)1۔X5b򈲾k{ ~ܣ&^  T$Ē^W*QC[PN9aTUxvF]' ~ILu9QdyIr 3\͙j|^Xydgyrg X[,bAH-W,A KXP pxӀ">]E~1ے<a|YI=6e62s7f:+Ŭg1&ˏiE-c&y~),q^({| !e)Z49(B y`x(Є9 ,B;u@V,pV!7%k}2pL* (i锖]7kR J ?y{8^Mbs#HYY!ArlK2H"GzdKF,2G4ޟ|^FH^^_ e;((^~@/Z>osu~ s> / q/ 'W)!Vz)-ĩű=vl$b ]h66oD_~[t,ΊE1wI fa$9^}Bz((?' txЅؑ; a06$I.LqO:elW+j^qZ_#rӝ-ސwI qXA._ɩoA7ԟvj"]asd -:O#[9uw^T e!ΞMC0;u} Iu2cBHb/mEl) g,VF/bI}na?"1W>#ʅ'F! 8,6^]ij ; ƞ -xT^8½G}z*$& y40 ª8BA։7o)#rw,aFyJ,ʃRpJ Aa.#>k.G9{]x;07*2K)nQ 2.4T5Wvao!k;;$P qJbQR*%P~``) ZnO^s|P+Bª z LNyS>|3LPf•nP^@ 㿠RPwNAE˿r+c|;.x 4M Gx:}]2K!tAj+;98PQݎrVΓCp>ʈ` *6yoY W,^ʇXcM )̣J0ņNgQ?i9}@wc?|灴V EfTxr,XxΉq]^ jElG0\ʟOzFky,AGTΆyU:+wן< ØsqWĵ!8.XݷU^&٣qyIœ* YlrÅ-^1#~W.vƸJStF`!@I(JEXp *_$#TB%twkl,7Һ ZTT<)f;_7UyC. zX:VE#;OJ [HAI9ztR:[NMYa[|֢Ȇ@D%2p#<HMz\uz _- œ㫐S52Qr]י9'A R4E-8I뭧Yքfͼ<3xd˲dII19 9U}VWWWUwWwu {뜳Wdh1t92# ?3J *n]&NΠ)#9e7lFqk A1?QK@:9gMa DɨꞋ䛩  ]N̡50k%^/|pȐa6㭯(8CTYn/뷑{,\s5O*:%[,V} ֫|4_t|rv7l/X0S$ V@@0 tXN@1(Abng,tt8qseFesONM*PYҕoY |[ Gw‘ 3D]LѴV9_FF|]%+@28 n~۹A۸0B 410 4MA7Ah˔uքC|&c =:`0a7vW Ri_B %9G\'TTٞ?#3X@ɥ)%BA@(| ÷J$^bر~[ FcQ*Γo78-%u||M죋k}S7gؕZ{qBDdxofY+PNqi%u|v{q&k<P1Ixvi>AKZ9Z~nCGlAY <'-W+t/K>U !!Hrk1"ЇXz*ȨZկT%2ȰQgf'Of{]}N}=}:nWL"h6ylhn߉eZG&x!!$*3SU$@^={>|U[G&t%f~aZS[$b7uBA:mHrXl^nk LLz/:6_x$ضɬ<(n+ZP NF"qs.-aPKX/s8)  Eqii+gX!={ C q<<|o'-å'qRxݦb6ߴt˚QHQ W@g2LOhfk{pfK/uu[. &'@lLfVXRkiŘw)-HDgTMޝ͇l5foFK(pB[ޫ.'O (PVmM}5k`4 @g?d?ԖL~Fyi6"_рۅ -XnR+ehKW{ur@2J%=UX`xtw:ZԯׂptM S@k7چ][GɨA% Чl0[K` eYY+e ݓd1OX8l>ع~+V_Aa)PzgNtKԿQQhE,'gx@ŇޫZg۠$S-6pZXeU"FN"g'l< `cnv F }ϟ~-OP4,PeonN2['9ʘdff]֖X7G-O|ݶ>Q`^ } &h^,H܇:6P4&uS' [mn=Ue z8 ZzZeh,Gs;Bf;?FuwM1 2=#Ѿc˕7{7G~8c >! pM*Õ\r#/VȳOeN,OR6E!}E 5pV &^ t7[V &Yl^˝W~Uv6h'ݜ?ޮᑵQԱ+LdNo\-{g۱~GX9ۺ#;`EeQy"õB>x*SWNc?iw{_g|- Z"E/w)}ioI1.З @,J rpƉ꽝'9Y䅡 /˄Rb`/`1Xs_p%x!ڵC۶Ȍ{@g{alh+e#Ƒ33|ӢD]S`6l>>[bPO_|0OWd;Xfp4lvj* ~j?m4 M' rI.t2 `Pwmww7ӟ=c&o)0ay`"Kj.˸iSt+PCh} ڜMĮY&q>(![(\}8g>ڔ8 [X\媽@:xCӸ?i|鋯*!Au .* ={k P0eFnX`MdSIgm5?Oz|Z8Z@ J63N&zีiU]@`DpdTH|'C@_?2{/dׇo"!.^yp-P$zA-neеc84(xa'ۼYq5oEJG(q0ejBgGܕ*p[ަtD s-ǟ&9d.5vb;Ba6؁YZ}ԵzA%?<34go)\c=5.eA $OG14mP]`;gtR"9 O2s BM>UTgE:L.lcxO*3Ѧ6@_VWʔא@'hAmXSNbTBbH:k6 GLhS8L)HӻPw}Ig5"V==k.T DQbu$@a*:ڛ)uYjC)h|H!ЅK{vѯ4VZAFf f\L@73akm1-ccwΠٚ'oL3,(^:ƙ2:zYe7vdב2˜b( ^*W-O٨ A Y& C1+f8#SGEl૏ .9A"d#O:gN(MpDZCwS{+YHML7x( ]9/-kz׆#1Pmy(:i8WHO #(BsTev͍(f} Ư.浸*Yo 1Ǐ]?ALa R'.ZTMf)t I)F$u[ [&dpCusF ZeY[kpuUam[  {[̻0q|7rĤ1~.";#?32ʈA2_"eN "=q$sI}@;qs@TSP 1ȃ_8DMByd/2>VDv!\}CEYc}6 1[LٗpgdžV2HOZ`) &vswxGIciRA>`% 2CL,C 2J?k.b= P_)zz^F>TC|pK:ľ6B#Ćd02 B,T%_o_vڞڷjYVc4{l?zˮ_>Ǻ~MdPMSk7>h[ve?]t*W_V"xDl_a&X!Ia\^]Wn̏}+ hw,_ Y7EbOʔ3l'?fy\=kNGGACTlhqȻ@F䥐{ך.\*<Yyfus̟4 g7(RO"x צRmcTdna#m8w(Y5_'TkOEɵ`E}Ɖ .(ݖf߰Ѱ~c8yk춷|!lck`X YHh O618ZOh>km-ڥ\#lX4AS 7ӓ0Ex| ܞITۜˋ}.b؊_.]:)\׏k';DHK^ H%-ƾ/%V]:e#s.P@E`IBgjq`cXrpJ'=Idc^dO> a .(Dy?˒ 3nPU2 ] <' PI F >ڍݘΉ@r>~RV#5;`cPݒs#dҎ7}|48M|u`rٮ]IK.$93ط!2P !чB#>Q:Q{ H]̒ýoqecNjG@lGiq˒jhIGi%i&iEN:I.gRZz _W)C=:ۜSV!?oEy)?4[<Yss72dIH_z\fG_FΔLsFtjW&BG&Q{ea~\1SDpΈ?qp>2K(PPg݃;XP!"E;xAu sv,UzZm0e>aX[` VNaTW^9s s0B&oD^ag(g%0LZ+!0L ,d F 'bNLآɵ~Б*h@C l n a*Y/VLP7,*ׅ Hhg>9:䌿Z&Ř䘅hXL^0AlOF~qTa&cϑXK.pNP܍x9*jG;Gc؁v+-'+m]=a|+tt,qpuOO+ &/YLya~c=Y {-^ńR(*,gA _9 ۹_ .Ș/F Od/IAa e1<T0>t+/^ٱEvZ"62褭) vh͈Օ0kp[h?ZDgE]6g$ ` TLudӧtdhsS d^ sa;V35Ra#UC`D5b-;JH̙% CzUlm]l,Q6ұ*,`I6`X"۞=5>P]=l~9 +N`56KgY_o'bLM غOYg"$9{0L"CaHBz6R8ڑ&j& f.7ڦmwZύv_>&,ldh=d#Ovr7y`˞gMsbo{1߼R c8 1״9 *[׿_8f1ár3Dm-IFhʆ8,@[ hBw jES:0{V<.I`&h~ Nq米f^ck6(ha606A:Z/Y+H%AW^YekT vzڭ6jT I0^=l-MI6(SE q-RE`ҲrR*՗[Ѡ~~rԮD#ulIRךbb>}1hEM[Z{F89PAy=5=z.:2I.Cآk2~ 3ۿ5۽;3 f{jW2nPϰiqfOa$* -T.8P}>$ dW{3rQrUBlDzቬO s8R{d h&hh(Ⱌ6{Ry *gHٙR"1m+lu {dNMX5VZH (bꘊp|ΨB(2=`)[gfO48/2+'s1,n 95(DJ]zh0K0ܹ:2F~ˊ V O(9NѨ \9H!1PENt5O! `rbj*6(yë!qנ7:z[WOaj;ܱW峬v)wݖlklQ wVQBz?~w*<Q̳Jr93 ƒf5Ypodnzޮ^YٰǞ?u1ٸ wd.0u=&D2/UH/_U }1s2Q^ż 0("Uҙ;4~JTҷ/N!ޙ Rk8ej),x,hcA^p7lW{Ճ=V]Fk_ր~fͮgMs^F\z]#\ԳaÜ}!"ٵI:W~EL9?殯Ncf64~w13"q?P{ dyKBP}r"]J?k@a~zl~/lcBtm%ιʐaC,3$R_~%=cdj5}}"p}r2x8uGxn0Zee{m kTecQBJq pɧX_| 5k f(5ki@jQV*Me5SMxtkxd<+\Cb?!@[*!rغn۲ng,]j8}` @٣[觍K]vb?:Z4OmTT5 QڍIP([;J~B!&f!FO!)!ٟ MlpoXP0_Z?Gry[o?ٯlf^et~/s!=~\쩧s'3QcR/fhԑ@c pYzG60R jCLF%@ni V@{1 i kFgWzIMUouHyVLYv Sb"#dP3xf) cr iPq@0SWNQ F"߇v ON]$*v"Vq:LN`1 cuc<p!oƄt79UHOza:@t1w1 ^@1 yx΀,m!zZxmgTMn2Uw+筭ڛݸP)(mw~6bO+S Pd/#NKX'Qz3Wj^k;w-SC^*!Di"2̹HaT V3soviW 2<4n*ͯ5\gtt:ۮxUscW!T-g.Gk6pdQKj7%?/l}vʩd6Ӭ2*hHSAg[vփ N_~*QOz;Uw>Bu{K։s˗Jmsո={u_~ k' ),w5k5MJ'jR$xm'2%.T#H,-ܹ%O(~ / }7cHD LfrZ[ٮI?du<~lLҧ@jg$UҟG7G^1HTҩRKLFDu7 8d*#xpqmFp-`D`1` b>s&2/ < 06l$O 11&`3'M8@{Sp19R镳 ս!;

Äݍ [ohlj8l(l,9{ =mJq96Hm!޹W-[_+_e,g߶{ mQ{eF-3kERG~l͍l'$;fG3%;^+'tzB(;86Z_3J">f|{nXzV }_yLr qӮ\2y=W25]ī 9/q p[։' 9"]%=/ʛy{c[mϒrz[̇/Ħk6lqg?3mP+f3q{bj kZm]۰۔MM^{C۲.!TFOŃ۲f#ǚ$J;0Q;4OǺo 6@KlNZVLv1\(Ak Z6ċiUnJ+[9.ڽּV{wQ_)[ wSS_2mL .&QʸWy@05M,&NٿWfYy9!ڰ޷gB %2+|eVH/:jnf” V/Cwٖ-fb^Bmp!P;A?>KeWz:,gvYMM ,\":oLl bP^ҽ;8v39.}25` y")hbz4W l){.ң "4+t#e6 C>__CB>+h01 Fk痄9tiX$젏F)@L'\>UFyJ"OӼ(Z РnvkuVgn~eMoWs\(b15ec'Q@<ޡ0%PؘoXѢwn\#ړ@*E!($s~LP Y!;0uS; |-?m*/Ѹ,_D{ۖ~q+v,t Xߺÿf;,ļ Ʊ)_$$6=?n 0 !PpCE7yya\09AUR &kaY xtGicQ9|; +s\sy'5k׀_]:V{5b4 7چ ؓ݋c?abjJc!ÊitEFW1?qw* цHǛ* 2'@7]i|[)Ƞ;a.13(|S_;OØ~Llhe`d+ņ;6+jn̛l8LO|wκy%B M;?弩sz޲WރZFh:Ȏ78ŋ`ρ?\8}r~}Je l]eC C\^W `}U^ %5ힽZӵ.ވh)'cb4t%2(,ZY+/{mj:(xkջ>׵N U1Z!zx.tVo1\U[)(1W檻ڮ V15x !`Lm@bHP=P^ 6W&߯ot HԼ÷Zԓ{_ @AAd >4%c4Э:G#C()k& %L f 4m)r~K1ZbW*W#%V]:ehA $#&B0Ja9ֶjz;1C#_4p ]M@a@t fPa>0*qҵnhsւ$`u@ql #kq֞kr/}+ O RwuJ__| 5pS5e/| p^추ߢI.ny_Ѣh<= ʻ8:^g֝wEM+1ߌ'k7Nc1{ z#_I8 ޞ6Grq>28m/1\a **wH!E7l`sc%FNw~`M޵%`_;apG1COq+#uk0XǍ'[;WMzF֦dVm(MK3Qʥln|F\:wu񬠂wy+LAf^TƐx4W]..ޅ;X.<Y$"}tܸbc;}n{O^7S5}jn͙cT^mv2 ٕǭ5cyeu tv$F}dL cd~T&W7(BW/d}֌E*tG?)N"6n;Hv eHO  @/F>D и*)L/}..{gBqc{:[/ fqˬr46>ߚa(iea`"X\{dw.*ƽHzvHt頂q w,SY͠]Zߨ<N{`cJ>2&&l !0$KGrF{-0 ^*Cv.ycPt3::-F $s?%īɿG}3["lP>0 >coӗ `px>QmRU;/;}B ȐjxbD!{VIý/;t!PĽ2&c~Ӷöv0UL"c/; D/.Tq2̽PĆVUz*%Fl2(>RƯBʘT? P1 #$J ԭۑ bG۫/|`*fk7LC7bW@8%4+Ng;i^]e|uH[4<[RfX")I!=K×xlzg~D`*d){vfNLl}@%%'hͫ+o~{򞇹 Ns^YDh$ZhM8:2X Ɖ0>)jnyyVe{Uf ݿX,_ha/ pL4ene\S"D`#1cv.(lB"<_?#%9 AG&G GʬkcREtq4mLaAUel.vx⽋K[OuD5e jg28uA+ dӕ(}| ̥d!ܙR{_K=Ҷql]\V4*(X](km37lc qlB&ʢ!!㯃c Pl GSy(oK}p Qv ` SQ("e=ઔGYuE%`q)(H E"Y9+_ۺx;f^~+> bzc񿴰…5Pp>C;f[W^T/&M3x ($V F)\c0`]I ܝAL[)LH^*ہuZj*0hJi7Xys<9D4Hl%˘[DcC sJR&irʠ^̽ϝxZ~=_Q>Q}#)cօSIػJrt0"3.p=،YeWۖ kʵޓpFkWI1hѣv˾_*W׀_| @`+| 5k dD|ykÓ] SxwŁX :pͺ=SƢ2, F[Ե.I[W'Ku$lx/vı $,[@c~mw2;#r+CZ wC{M׉mA_ۄ8fYg#vn;|3ʫ (Hd&ںgm}} aSZ[ yϫk7:Od>ʣ}}W;rչjm*=\Y.=+G}}b;u;KM  h {p'a6 8Q2@D<jׯ߲kB^)iHYPU?8CU?2Wޭ;rX>z9;wU&U}P});r?mkؒ/f}p1!ʢF~jjM6l>PJ](pVY>xv`F꟦;S]13(DB(EDy(LwϺguٲd P==LcvZBhHo]mMɴv3+v"08q F 6'vNoNFE Z\TQ BNN '?}  6sh З6Mph,B,6"5vڮpU^ܭ@3瘕VW6o3 g:`5Y@O, /ku%착B"x=!)'sLxEa5X]uF4UJz;mN>^<+*ZYQEQ:S4vwb'znrYwX7.>8 Q'b.tۓ^vۍ67' BԳV1Z`95wMc0j4~!O`v=ߴu;BGUoBV{aYwhW[ۯ Id^.xx苯_| 4pAB}vzBZn2***R2?hou^{JѾ 2aAUn=pFzurD@yO7Q|D=`@?fFկߑR߈gyUZvj> R^Y,l_m汤400toxh/~/͚̄J۹~[GCWa 0 9<+GQkE A`Q/Tboزvbַqܠ@qquVZVQu/|lӾkW4270Ʋwcl{=-ɜ0L9*2 qO>Id |'Pk@UtS8 `>b_ƨ1bW.a3l |u(6g( KZ15ě>=1=A9lu [Mioiñ 1Vm^*r`lECCeÅ`LjsOπz^5&K`Y(tY"xކjr u3Dk\i>Ȋ2Ŕ-E{KۊVկ~.__Yj-5?YvLҭ%S:be9}Ƌv[ݎy06 U OkCyGyyfi@9q=[D. lKޮpģJKr;¡}+@xT؆tŊD\k[#/n 4fVđ*{lh`i xi_sQ,]7+lxA@g奥 GVU`uMDa 4@Ҁ! +*((kR^R F)GDhBRPoIvAO<ȓ\~(`X*e|.7!ö_ |1QϣQUE&;u wA}; bCg1$Z依ۤ?f{ݽ,n~k>OVDeJ5k׀Eh BXDT@!R)N.B-*3Uu6]a{2ؐk@ ^@x3^Զ굎`= S[!O0_qrxE !SKRjK=4&se^ 3  Q0b3nBl#ƣAs44QEǙ~<׸~d].m>ӫ^Z]Ҧ_>ktۧ^`L*>62aCc~WRχ9DԼJꜭ2˺F~Ms|E}C*)B?k@ӹ0b/hdprօm/;"V\ 0 nf4`p$j#%ROpPvϤ<W|~{4`Ms2.{&2r'>+o< oV&(\dBzC]ʔu:ɴz3O]ͼ2O. D{$|cL)Bt8z6P`^FB!W.aZbpJ[!d`XQr/*]4<0;k*j@K0݂_ֻ~Κ[N{ʉW!>gZfxdNӒrX@yN׷Y]!Ǜ8dZ?(4 ۚ50c~ | 5 ׀fʷ\H(td*nnon>_Esp q Bdku,Ha怎|]MJLiݗdwآqJb#zKUBhsᙡ#12:6T 5KYطsIY,n`iO%.’9޿beH;WZGy|e8a6lҀn]ae>u(=ˡ8Np,qy~! ѯGs! ygE?s|cbrd 64Xbk:7l]f@ו+s bxc[?,?\<@GeM+ 4`>44d]LYaI1XW +-Z\ uQQh@Hy;[+e2PX.{I^ y)&ۜe{moz,9u9@n P(2֓.V>cyhXw :.20 ѷ'T(`P [j3 2ɪ' zz+iR{7Z^{}~?rk ?RIc:po+]%FWL5󓗓~5K/ە+WXM6{Pa.~s=Oƍ}pm~s| 5I@>$m5k׀_| 50o TUّG: Qmi/>q[m.[_ sk } ,Ly2$G59oehC9拁ADc"ڝ!$vp(9mҗg#w_P@Nd';a:~kTW8q+n_81K,٤o@Cz]@ aB,Z&죱B"!@9 ^2 `2 +4Xzm&ѥe4!kv(i1j^"f. ,ZCi \^4|G? |A峲Xke&-֞ڹtF^X2 yh$t %J_o{Ww[{'vЏ~iGh G|v}DLccx\X 6 4@ERp7 hB *4a::&ڭZyB)$XbT+,ƶu[ɷj; Graӄ[On:WW"?] +@CG@coPހ`԰@?`f=wj'O7 Hʣ*Bp$MvSj=;5~4f;&jceZv5Jm4ҶdRټ (꾳*;sHdD]p22#Gd{⽒!̰?^رc\nF@#whRR F?HʭQDF@C#h6@zEn j(*CR&ccm uH0Т<"{nlQq(0_Nc&uת#@ _rM,_`29rX8gOiP,/}[#q,w @<,$"r 2r 12z`^] `3U$ގdm9'lQz aX._`P/Z{b~K%,XBDxn jٮZaa,&l ,fx,Tf~,l,gm]X|XcS,wp[qc}9,S/S'zAqAN.$m$"mjBv N;/.B4*\LIE+X>ᓹ^k Ap[ZAmlh1@4Ix*Yw(uy%.@:H3'%͊I6z[*51m5۫V\9TPWS@aˠٮꁶX1H l yyT4A= WG 9 b!yhXV50!$#2g_32Tf@IDATU(/BnaG qsgJ9aՇB<{A 3aA Odj|HX ȑ;sPTM,&_ᡠF@#r0 *-aLEX"KT wa!qDx֘LѕwK#(O N9P&.'i9I#⼇xL/澕A74vH3Uh.ϋc '`Ibm<ßsW`YqitcXx<1]s.]WǚXs? ׏b9,X6xϲyօy; ceن~,4<TY$ae ĭ 1f^Gw^Ě7yԋ1v Ęerޜ7\:‰cy ki,<9F1q{I#^0_" &  }6C}ICW|obP>HFE l`dɊNDJbI7hPAޑl4x !X\4R͠oL]#6{Uja>lze5wiepd3?.K ß"8@2a#34BrrxϮ<8R ?}8HjaGg.Fy|xF8F鯎B}LpU H`Caf즌ߺ"ӣhK}A98x?vf">W53H91$<) %.駟*R+"gϞO0cۮh4[GS:i4HwQߒ [Fah4-}Ik<h ~ن"Z(c0)_;&]^i;S*TP+pƚd.U6u*3UWi/16z,ӲR^i3GB>$:G *!o[XΥYp,, LiV0fHPbi5?y"xmf[Mo\LLkulKX!o*$#PɠQs+ .䀼7wOhDpCq^H$4ZľFaQ;ͣ/7`l )rޱ5h@[,%n>x7#j$ j V޴pcf` zevzͰx 㙔LZ6e3$`ȁlI#|B/ HS?TTD  *X^E%*@ \ƇJ,kVYwrk~yuPFpɂp'ysuT;A)c 1j &, dAa(#cq`> pqr+WT8*<Ih4xj+hT:)oI)AL>"`&&p였i>"]>F &o,a62# 8=Gu@Lcl590doT_NB`+nx2L„A}&q`ezW#NB& ӳ nlj)GU^C|\ZhMS}T2, .#4vhmRb^rɰdX|*'@[O0 =AN i0P4 Иhf|y D_4C2 C TWIP ((+Y++ؽP2r6^%E,\\&#YsٴdwrP6ȒPsb&Su4lGca:lkM^!| X/djlH|?kw'v'd-iH# uQ! H&`))% E&I*TPIe? e!% J*QE.yˋ/(O?|lއ'zF@#hR] F`߻)ԑG_rqx=xLdJnǐV x. aZeKǾ&OZ5ER2jme>> r˵涑5~8Rs_AO4˨m+=VZ߷TùC;;02nA'F@#5 _yEXX%3\ϵI N88blZ?p\tO&p"$I4hokcG uw4x0 @!qPX%bX,\1~٣}V2Da, !W `1Cy1EN9(Z@|J:EHڗ@Dȃxȃ( ̩&DKJ'R\H|aN),}6=;r/ߍ!bj$ pA;+cde^U*Ɲo{8 ˩H`nRE5A}T3!ƅN\.i+2uY-Ow eP1E.}?V/}K 2ƔNF@#y{޹s,Uӧvq Q'O6yȕݺuKT5Ve;Ԧ鿬|]swo >%d:0"ڇYp+?60_}^VYB ޔ黔 ^mSOʑG+D&%Q \!+/z}xuX9MyƖT;0Xk6~/X,V rM=nl\F@#EK OX.xe$,[ӎYTHfĠ\ t/R}D=ܩǥ[26vah6smV""#S+.$ߐ F|+&TB0vʄ)tm嵊EBI,&Ƈ2?Vޅmy]\"\#?y!ZYe>GyZ_zgw,Q2RJ#cp ~aDXw .mm N 0Z ]veHvğW/0ç_焇7_FLlF P;K.:` @?)PoԫB4@j/+DEZmMk8Ib+$.Us+ /h"ܠR>P]F+ſ Cg!5lyKyNOY 2iO@؄bקhpqqQ @\8'y;JoH+PlBx]hA&aK7\" \@~QIbsxh%@}}E;::ٳrQQ^WNZEqj)cqqH4~dxfF.\> ' tA= /A >F@#l =G*`bqV(ب"mG; Ar_pbR^w+_\uzt|(:T0pt|D޽<^Vik(u\.[K2?A9kI_DX&o>peqQإZ_5VkbW"L'F@#hɓPKH" j둣EtI5oY[p<$K =A!9,g%oVꝈs<)Ǯ<++`2Y2y w!9uACWփ6JG4H(Oh#.eI4 FâQPH|G([:H g*f7A1UilrC\JST7(9J@ِ1nn@G, ݟzFj$dNW"p2k$Źy)`^UPO*dGZHo!,Z2q^E XVǰ#$A((B²SǷe2x`Tu68qBo I&LU leWODM=|F\(ڵߙ U d||\~SO=%t3uh4v!wJB9dO^pڇnWT!.ccj6smVs 5G ygq^S9Y"K$MW~[÷>?sebr5|MΜx8Xh4F@#h4A yQ S0~DHr V)]Op<(![rq x//V(;MJ/{5w4XO&+,P4pp{(z ü@2C'DfA,JKvqJ 3Cze5 )?LE ^Ȓu$;HMFt_x0 6x,۠tv* zʳ8~fY@7Vf;<`t(e<8Jy7 D΃{q`W!r l Y,H$oINŘ@xrכnZd*DLgMqpCmJgk< At;ՏP#h/$^w%InFXbh#2(T`2[T ǣaaJh655~(0 P+0lvb^mDtc sʷ^]n,-I6v"1\3pX&ޓ+"R;Q/]F@#<\cO%2)c</i`,G}7yC.ccCc1k;xmP|piKO>#qB#[/ti$řt$-o$ą;5I&xS#h4F@#0T//8ewr!i%#,O$hAlsjlU wɧ3.I8%_=S.M ?,s ^Z+0hfa! I7s( JdcpF: re<4| +`yX4b'lQE0<`E{I"ʧN7HP7p&#ܽ=*hq=lLef]@(8TXY W>ѻPLT$6gaP惸1lY3 1]f(E+ ^7, #T(x ߑIK3Z5k-%_6sĐ[&*a88U UcT^؍e?uR]=kbB˰{NHMcK]y_ E:::|J5$;u-ń̌ˉ'TX*$;q_I#h4B@ >F@#(K4$[urF@#h4F@#I00&)-%E9嚕.kUF @?#b!\+GH"YkW=,}r B! {4 ea d2BBJ8c3,4r*eR0.Q0Ґ2Yŧgy>)8 lJCW#pFKO;nb" ω:ӽ CG@FE4G5&E4p l\=D8G{*Ci4v+4sAY<PX@C2LyV:D0rzG%i+g:S9NZ`!2va\s܀=D;a IOU)#l`C[=A( </[2٠õ^ƭVZ o^UDz!g(,{;^F&1k{v0)~nhQJ*ꊽi[\k U z{{ܹsr]x K6S7*\zU;&/<}L'F@#lM**xF` P@zmYXH䍏w ޾C7D#h4F!Шkv<S+ + ccJ`iznB *W#0`uIX<ORS'bKD%uz UV)jIBm.vpD'> tzT@tJ+>Wa2B j%J㌉cDˍFL*(A92HR(*/kC@{,>Y_6Y:M傶6*Tʵk$o=kjܷ;NYqG?RgyF|PiI#h4"I"hxHs2ryAiw++g+F@#h4.9#k4!hFY0M",9 L5ɝӭ2v4 &̦Qhv~CLu Zc/OFT(}XA2H1 K.$T NX]PDD*!bIbؖcz걲 .fclvXOpzpER(C{DLep^Tv!uWX?2lJt6}v Z\pYE.x뭷ҥK ^ϙ?&]$ ɏcy瞓`0xUHF@#xФus5(1:ѼtBnƏ,Mƻ2zWNH q4D  ^0^ʧYz唿G@J B-˥[CV} 5. 4ⱳG$ ®g >0<!<&9@4/*2`_7A40BXF44+ 0:8g/P2pĎ&}E2f_aݴVfl')%oNᢟ+0I,Zh3*?|7rzG#h4F`"J[F9 &;-3wnK29oyGG|XиbmA-ߌF#'$T bP60>PЈEt:<X`&oC\!N B'!tU@gmN\T-~``@ ~mzA$kojTj _'/LIh.#V1 W[H EIFLcoO|WO6ݸVu6F`sanc#%nJ 4S#{ZTlt4U`YH 2( D"fB;K< Չ\lB`iD>DD\߳w@  d l tsgٝEU_՗P/0 J+ƈR0: ݪ,Z*opqQ<8$3lSdJ''dr!j}Dl̓E'C{k]6+l\59aw%/lAQPšny$+4| T i #!T  t @4c|tۤ!BЍm*Bi)V(pXx-Qd*uBdca$X8fq@h(yXGe(a:* 닰/V_+BCeb@tuuI{{+ᆱ hܯLS_M29/errRXK!:i4Fj݆m|TI^<@g1Y~)LڄtFtoW7bfD$> KdNjfDT2~뜔JrY9ᓓ]hʶVa`|l݇c}/n>y;LϳUjׇjgFX==WAs2.pV\ɜzi!Km@@"!=z]f-8ټtW'CM~ixD=I(|["?pXpzD^ d쩓F@#3V)F풏c*-̻uU걳ıS6oNFuTANY{i셜~&Of+IŲʔCZU;+@ְ I2ȥڀ#جd& CŐ07 $cgb,1AT P2(Fk[/B,hlAQ ,G&<~|7r`>/<K.¨>zs4 E@'y{OKk0Ը>9-)Bi "\@+Zw'&I:_Ohb& k< {{s0tڬI'P3! K[L|Qci .kxJ Шϱbva"tէG0^=M0Ë xC L@"vYfށ; Ӧ{ W dxįfedvXfpjC#_i"`P"sk/~OxAp`-{ݯS", URDּa|Cbl6-XFEh@2B*,< C%玞vUSg  Z[[G{9x266))isg"|NF@#X 'U` _&KF+΃Tp{,왕{`.;YA2OF@#h67SO1!IqL`xm|Ұarؙ)HS4');w'wO4kՂ+suL&˪2̈+Ǐ/'Qb ]\JZNA VEVl ŘMKN8Z.;{o`_KzzH~ۼ L/`P åH2A:(-eys)KnxGFOzS+CK/s孋s=$e28.N(4x3s ;BB(oceC?p1GYHH o΃XPD\tV2P4e L>)io4$r$* R\@UƕVXw4A4pD^J+f1ޕcД2 6W`{ys\!srGU|Nǽ^B -Iؗ#x }Ricx I }UGI(1b}k*pHgA0h+UEZ p7O 0,IH ѠUUT@bE.`%6tvVU5ex:ZeXT`HxݡPH-$<3˛o)o1rl>F@#avT@"xfrY_pqe4z>_4_eSwR;1Ǣ|߯a]jExn?l26~}h4V+n˾aI(aү*|Z/fOe_;'.L4*ٴL,fG_엻'!ӞCKp!-TX51&a? $fejJMqA 4CvH.3 Lv|boXDSl#/y$< " *U(GR4*}R6=u($+))s}1倛_M(!bkj%%$P|P ! bRF&  )dΦ$]hK D7azu@XA1r, wKUk! $ ($Bת&;(D#U$lm,\䩧?\)\UavQx߇wQh4m@O;`et+Yjulc]סGT֣Gm I&' R)@yocm[o.c= V{XݻkaҤIT+fLsu=zY,M N4'OdBZgƄ|Mt rYjH-io!Xʀ ﷵ"݂ay"\j$5[I#hNCW6yPOsl˂Yx瓲h+!wtZ ,Vq1o5s-V]{5]-,%#tXt܊@p(y S7#A4h؍GՇ{im,w;awQ6[I2uN2AQ 9HKOB4`] |GbR0@A4 QFh sgvA*hoGTRN]~!]*/, 4$]43#FbI we˛Djmm|7a[( *ZH*RADH]XjhV5;[onIbu lv{|P(QcSjӲaL!B"Ԝm; A nqXa`F3x0(K+W$}TqȤ24IkC"0Dm Gd&o3HT;#m+nȱ{O*A =2@IDAT1~W! r9 ?6YM40Sh/l `p J wA݀dW{@ g9EIy,C@~8ސ@!efnx/KڄĶ պ>fedD&$ vUz7H$d!9,քPAOpԲV;p3 lK dsy;vWƦ%SGgμ,'AnF12.I)lcA L(0 bQu' %8ƐÐ L%+GmϚexx7 kna>\\\pu"|'{7nvZ>,_WʂV.XSfD4^Fx-uh45 2[+K^ʷ]?t\ p]F@#NK2(NH;m5d<>#`E慔މ*r@\R|s9M,<2q/}#sIl5&oxB[/ِdJG & ɭ)uޛZ%!悤~x\zƤc*!x!oq) CU)M2q/S!B+RF[:Nm #q.Z:YJf! JkD8݊HxLn`xT]".fJؙ-? w=CR˙؀a9s[fQ\u6)MGq1oH/75Biqb.~0sFPDmD\L$"B j޴PBxqHrhU,tyd/X8q:Ry]`xf;2.Ě1V?׊xƘ"EX2+ UXhm؇-iiKYqf`DypӪ$ `@I E;X6Vm>\^ B}Ɓjܝ% #d>& l^isr6W\J4 'ID ~E#ZycO5{ >uM&G%Mw6jֈCE%m>}[<Ӈcϋ J@ >&*|B.(9*o-T8G1P4clNVSvbA;MO{ކ(JREC Ȕ%qNy~$[_ѧ@4s̓@; U+I-*͎JR=ƒB%DX$FH_y|zSPIaI(x闤 /(a1\>s 1X2!@P |ƈ1N}Wg}OV( [P'nIco@< , yACcNA2 .ő9pOXq#!z] NjtƢ ~ @haaGV.K$/1Fx=fІhc, Wo'GѸbsZuˡs< yp$QiBi1Ř1:MoF!)MؘyD0~8(7kcAEau=xc\PPgjy$& cV6p<\x2TJMIz}|W+yFFusuܷ0isBY$ <= 0x$G4X+I Q,<yB$ \>h`=φh#k&'KS2}gH3W oDU1뮺$xV}A5!(TtYk]- -6xn%7$:!=͝a7iYֹ0R4oW  ^Pkb $'a[~j"6ǺUyz?(zh_bRoa8>E.ƫ*osIH* `||\駟W^y\ju  oo&? th4{ {ZW#S(I7 T&XF^_[/^U+F;H_3:K AR'k$='rݿV>z`kc'Crz$ l^$%t1BZ$ ;`#MgOTڦ,J]89 r/T2w6UZg4Rs&y-H ĚuxskC֤@yT eCdmۃ~ P'E1EVgV45N\P_1>=MD!) VOd7%m!O_8cqA>J؛F8zHr5qY^g.+cx~HgnK,6<0Ƀ|hnyh6?uf$/?c*nӒLIykֳ\:?H:)| !iPVj^4@[c1.oxbt'xԴqvlb{ ֧}whƺ06[F槺ٱP)8( {ɫu`;S#rcLowGcKU ԃסpL) 2r[úcax#dbۉ{0,M]ߟʰ2\}SX_(z@C9BʠOz6ĬyE. y6#=*d__+E|F`"I{t5^[JZn6UL# czFЫw8"ӨRʣff_vVhDd} qG} #2i|='Mα 4Vm[~)'VʱŌU 0vI(A#lP\QFXTcIⲔ  Z'>ou3j״I`X,z42Sk$(0)a%?+Z,MC \qa" ʀ `Ѡ2T#O!]Wxؙ87s BG7b[KA[B"V*|$8zwat<1`LK0甚A!/(A.EɬMc_@X/ 00* uKGyKB8P؝ ]N!FO*nwHt rAIh(FUk\H\^.],7\H$"9CD#OyTrODc}uR!%u2%T/H>@ %`%^$ckaA qAy_Ye0Ǝ}b-_~ĞN~!y饗ޓ7|Sfggw\s8V |sX[[҄3:i4&uhE`+B-mvOԙ4F` gT{הJ'/߈z=eB#2<Wy#2w?$_4'ߚBhe*zfs<*{ QEVIr孜hű % <3dŠ,+ Uz@Gu*&a\K1:onwx%mbWA: z&D.&s9 [+mnx`v /ak.j#cz⾍(@BqRZPbYR6f@TBx-9,ECȽu0Hrf}{ 22Gas7MAq]ka4#9| 4@qTm+XDgR !KP6H,4h@"~"T}J^xIaJ5(&C%X؄zWN9Dv|jUA4o>}Gnݺ!q(Ek`9Ɂ\*%36pUBǯK!j:^\oZ2O+Y|8+9x3(_!.:<#r7J3㏛[% /ѳfd/ OX9bU3;<,xLθR.<Թ)Syc YW$WklG$,ox? h&pF& u+ Kd!;b1~C1WxVclYej|a0C>Z65ZǙ3XDY)_E`D.]}uU1̱/CLk 6&} ֛53}TC,0RᙟL@ ł^) &Nۃ@1n܂0p+ 듦_-g3%X+=T>!T/pޖO{:R"4RrRE\Wa`SDa`=BR"DS( J#YY8Tq;?%Xׄ7~7V !$𼦱% y^#I’}рu-^|dvHfn4wʁ I{7=zVC V,ݤP%2qK B.xg3P6,HyNo2>#viX_o^BFnK"j`?=gd?bx}"u }-뷯;LJV4 )VZsoU;%^_Nq "n+ r*+?#(^c ;ީ\pMeX7!Νq]Hvcxb:|GY>hH'w]_n8`ʸ`ꫯ6T$.cc[c1Ace ?!a %YHO&K.)ﲐ]i vzَW'+~HMS9՘\R'냿Rٟ' )?ϕm_>߮Mh4]@m>*ժO?ڥՁ^WΉ7~C~0f8HC=)Q4 J(D[ܫc.1g@@Czog?,/Xf`  `DI(5൥$tϋcM# @u$89s0}?eD cg_w=$'G8H`@6V]7.Sݷ1qqrl/O$BRѯq/ h&ĺ=/}Ӭ3M.ĀfI.a}=To8ѬLd:=Xp[+[r N8V)@[\08OyyMVp .K2YڃA2肷n54ݩ8ΕW>^6q/@H"|N(Ă@uA&n0N^{"Pʟ͸YM2:W$O{FBr^eSufLø.Xʰ^t^ ] U N I4PY<2_6@؃s!bY(#^PHdž^11Q~5hy>!~LM|nT%&_|V;e qpQ!%x>ʄU@0S@ Nlf|^OA~t֤NY\p]p8K/$ϟǣDA`__:i4=\|HI*΃HQvGA^]Fjc81׃OJxgM㸮,[BaH }DRH[-Ken3=螎阈=1?fioݲdK]"%"% 9/+ DVK&*^v)]"O90f`'b_xߘ2ޏzVjH+=;%\iAu[. <de& K[l d66~a.5[Gj \zRx/ "UU 5lCm"i_R3bL8iOji޳^f!ˀf-A _f77>[Qh~7zjkvKпQ|?1 lAfa>>S}~}0-NxЎ% XvJb"RqmxY #m6C qڠ+Mx!@:s*cy1e50~3\{s*jZ2ј1b/")HUa-`7U ݯuze`!\rN | DJ,h66:?B&}Ru?N hl}cuy~{oz "64+1==bDBCxPS[;!fp`3(S4X $*p:2 ಄;4}dnV.ьtBQ&t:uilQ!#,P% dAY0Ҍy/gN%ae].uu/P(`\0ƊE!uHb S`q*=> sݱ-T:D=.u ievb<3V0]] 0Cױn[l 5pkgTqC/ f( =(߭ VGRӛ4hpӖ@iz2F{2(А tFK=ICO1mm 0 yL 4faqdWIXϵ 3miMe63A7_d'hE"YxOOBvd6ǟ<^S&qscSy'0Zv͚&`a'p 'q93y1?Νjn: [O7& 1|"_U&C2 􅤲uX2w[_DKD- ۾@+@AHAH/Ȱ Eˤ`5„ B)}2 QO0ņGaD:7 y%@hGt ^xt^PT9h<3#ZA#4p" oa<*ƌO4TzkwI#_ >48CAѠM`2XKAt# wO^!`zWYI]uʅQs> jJڵR E~M2t| Gw204d. AϿs \zU Ƞ/+&dz|k4&;wLLG]vl  D3P& ZzAff؍X4hr&A]GfUfzb֕TwIrkGpG$K``gLJ_*[)SoC׋+N9v-r(8DAR\:r͗,~ck>]ﴉ ^B-"?44X&I{WINp}_B[Z4è1Ȟ=t4Vv`LCC6 f!ps1z{]/z >0`z͵4Z#9XQ?ѨdO'4f[y8@977fa f(zZ98*Ty/k6\|E-L,XIʹv %3 lj!F(3İ)5B`kl ڠM<ÜA%yR@AaOܠT9Q™8bdc8/݃`(R寅4bOFr{\: ̎0߃CȄNIV27 uB} D%lX8\0̪sJPÉ0Dm+&:tHL6ϧ`%/RRx.g2J>ceWikր[T!i{frf)]Gfڵuju吢҅둿ڿ.#7̰/ 38A-m Xcoߝ,r `)rǞKRlb ӤTD^mk_> bӪ@c-&2zݣED|7N^˪e=RQÁArg(~kkm"Ķ_744yUѓ⫓4vSF|.i2ܛNA5!tɍ4j[EFH-U_?%ݺY ۢ i4 <<\rzے3xa;ZۓQuLLc8ΘM癕K'gR ljĝ cŹia=,l C&7d]8¹(q~g͈ynn[ou6IM!{Yvޯ6,l;ä4 Հgg;/Z1gQL5#AIu> y *-h|Di|EN:O\c|y ``[Z.KI~%}# ;{elDN'ch<3 9(XY^J +ooGp598pd+A /lNϯoCB̿: _4c2M$9Fz :ō䀷ћBW!"`@c4p( 4ThdcNK-Ryz6u:ݷr}R(ÔT(?#EQ2o8$ 1 _tUElOe[˶4pLU> \.5B.ɐGUF; E*31#Mn2bkր> bkրy5#[y7#܀U&8d5# c]3?Y:aί} nije4@ZVyalZE go7P7tTOlY}7Rn@@3rlAm=GzX1,*pcQ51~?4C/t6yE-n;(G>@ v4bӘhxC;^sȇg4o'(o,̏]4R{ғ:ۜlFa׮iQi45FOZ 'N} `paB`Q"=B, u:'&&fqfN[Ջ6Q Jy9Eq8[?^qZCny3#JϤL/\R"uА ~ăWXD>I~q ]YP UE  GO%ãU`dPG8 /1|"(rIQ7̫Er;e=R,]0 c>=yڳ#}zx/*d:yQC]C+xjg ɵO.c}wwXΙX#mhs$C4K}[Y䠾5_-"EKq-7[vm 4S[l 504OLPF:.ĉEMIنVFeV5} j 5s! rSt*D~˵r 7*.a9+˪zYAJ`XPB.4ƁK0'fد-` |A:{nKS7Ї}/ Z6ʥkr殩m.'6>1asO:;rg,~ϔ KW.圴u"f?(Fěr؁*N֬(%lƮti;A =& N' QσFQ;it4n8꫚1DV3Ah褑RhB/+RFiTk?Umay*7)jL+,4ғ*DqT3қ6ЏM0CXԹa>ߋ>'!zZ]JV 9h tP B"_<!Љ >/?:8'iB  S!Eb(f@ Tɶp? X  ?B&0UV9m}H13]+O`V | pn]g4K}@ChTAWO0W"=9@Zqo:oCInbX/ q)ܤJ /U Q<_ak@ FgрKE~x[Xf01EމcX]F;FX kl>*"s41+U0FP\ +;o&"o9 H|GE[g!~9ǡoWsDȩ00 l$m?sל3P7eߖh&`>_Տgs2t-p455akϟW>p[nm5`kl5`k|ԀC%rǥ zWg_+@aKy*_[y@*jV`mfs>vnGѷFGI}2E"zgG.`·eXz鄁/TZ.duBx"`jIj2}8pR 6@Ip`l۸T?}i NYb[~]6AE96k>=ΐãKWO`F&&d `AY^vߵOۮ"[|yk/S߳GPMZ V!ĸ+zxQF75i$,4cȲ cZL@ wvjCnH)P@/o:/|кyxЀB@Uy4c2A>XoS#Xq<(dBԧ48׍|}|{8 zq{:lJ0WO#:A"C=!KA*P;q$KIB Ci 8o~3Pk*c}q{(LU@.y#0yXX Hک l3%RH/2/JP?ujaUKKӥ^ =.!>*`H܉Xb5`?SC,ahtШ:7Whd^O4mRCl}BY_Ґ6TcqN72 I{NZۮ#]p[dReh <dЇ#qک/uxb '].\# ŏux#~7)R9CGɅ&,>ٴbԔB ltUu~/'Pqԁ#YEJqa`xd\G:! Mx pM*t}8}Z6 -zV.?y1k5-h!Sz``ѷFu,RN3@wNP=ondTWwe&kf #J]ؿ{.pW2խ< $իt%S:@/a=xXɶm" B ǙURs#/&kXMcn Pa]h-MsׁJVO4FMQ"`%P96̢ W/ƅN` .Sl422 JYIi#/T[ `6&Pڈ( qy@ ?%iA$uAEBYUA@g6=RaA'1|qY1]5LׁqH_ 1\ cc!, bh0@ "g7 @ AA޻eER]R?00RMd9^sgI+` PP}}mfX p@a~<@]A A͎UGpNn~UסtUߴ`X9Udgo k6Ar_xn}ϷVOlj0oy!G_s1$s QӼ1ջHQu>s:c6`w躛g * *1lb0{8'q4ƣ,p;}01ghP 'Psy-r18$a^y'۰pXW1 6oE\U1!.@rzPWD{aHB6;'Cox0y! |@ V2TE?x=%@q_cɪ;q !LS3 zG+O8! *45eѢEѲgv 5`?5 |׉>[n ؠ[cV5`k,h Mm@نlA2l(x0<\m_ #Zwo_+F)6M~*ж~Gn\0D@M%63z\,ϼSvF󈍔jy+€xp9υO2v j2 QԏJЦ? /(|KogZd@&>̝KV;e E RCZ;[ハ +E}}+|*[\"BZA1ۭ4q:AF1\*%7gi%z:!V^d&@ qTVeрN*fa4d7ha[ )IIOOɄ 1aECihf{b ܪEv 5s0cJpOԏռupsɪ#s? 9?uZ{~Kg'ˢ6Y\iS5 2a\{9ⅅRVS |ďS yߨMewdλz0k=,SkĺYER4@| P"zP7 v>0L^"A jFBFb- M sǃ@ u2<;ϫ1YPFz琴x܃0 be s%BPXR0 pYxDg]+iX*v:ä)>Dix8}eDbUÁ4.x K;9j!|f9O9>.o^9%>04.XH@ˀL ` q0 JUB<0u(6d2K?2/Nz)Q;D9eB nq JdLZSy6k ea#!l8'$#DҌ.l`0Vi$|i֯fZߜ/Et2ej)* Jc[Bjj#ŊSGT?#wɕk6`ZG.l5;lP470;&'56nw/j^/NH>#hId44qede <~#,4AY9H`zuO&Ccc>2t\`̯<7'?*1 zӪR %:+AɃWmJсd0K*`\>T6F}׎{PF $ҥsdd +Ʋ˧Bsf8pn|o;m@^ N,()c>yF 01:_+VY"}:#IpyO,y?r+|⁁8gt`)x^R̦=&q+;6P\4?FgDsH & 2BIC qFf 6A caF Ġ>*7ƛ:>cBDABz~x=0JOf[fMn! 5D`'sBeVHٗzNPS^ՔJʶvz[ųC(X/Z4B`jdz!= r nTS{EC u0S |+е<[fC|RS!IaWW`\fvBS/o}[p74]4*) oc'LFƒoel^'kȈozN::̓eUҖLx3X2ϙklٰ]j'\D ߓ=1AIGp t?IY:;]Ep_sNH+22HfO(Ę޻>~;1g2?,p`~8K;kM% }xu@q7G# $lĵH ᜡ:bèS/8 q~!J+c5ٍck׮UVBN#\ dP ؞]x&piߕ jJy,KeϞ=DhwN[fOx[W~AU*dTZyA+- ]Tfڭqs;=b ӻ.ub1!lXtO1xNtwscUiL&Ngō i ` _8pS0ªR:odD<{2q.G>8~ԁ. h@\H.NbX\@Dr7Il;GՋ!fYs GYiuh@.]yR_*uv? k|"0 yp`6Hqؔ 3zB ޓCmmgtTFds4`%N4 =Ų`.U; JV1VeT}G:a֭H4NX5iUxAwؼ㥰\8w<LǼlۤܧLNhhqp@T~x!q5!|BaY?:TMq*~2>B]ҸN84~$ռٮ4ҍ9udB9O/rW(E=H-{\Lc+v,YYE^}ux2s=A 4ؓ"cyC0 b4]fq^d3 $ pjHǶHւ'a_X,ŻR0N/>D 6q')+o!(JhFPTZLΆkQ|;;Wa"t$aˀ' l#X;1=Y&r-z$6ahTZu1 N4@->$p|^x2I9uc ?c`5ٵrMcݽjz蜮]CԶYPYpP+VPḩd:o,:)7 IpR v`Ŗ%J3@@A]]?m'͸XbTik ?,dX?e?x󰽜VQס/Y`+̺N=XOZ*Y.Xͣ۽}kV>T7ߨ&PTTE3KzA]WSj13LeבR51s+6ˎ? R]]mSF%f5.^<)xI݀1|TWx$;4HVɮ;fu=p{qyuc |HPDgĴmUepJ?(-W. <>詧Dzl?ȋ/Nj9vwK1 {lQ tq.nўlfP$xsĦs2 <MNvmnBkeqI4H7ƷFc[ ꚦ!ˁHsd hn] kue1ݳow9yYΝSLJ2xKmذAV]>i[8 /_}V9ttؓVjil)@K;A3rwP~!3.>!Zk <;#ޜḊV͋?MgdumF?s+O?ĶCS~N'COY=Ac0P?'ެ6T?'o𸿡 1/o_SmW}H?[N/7\2,C@@R]$7o|AcBZo^hs< cpdX.*'P"B$'O$0 (S}T0LNHl4Ϭ:ogz6$1!2 x o#3tBD˳z ̠01DOq{Hޛ"d|1p M0s` H$קQO({]m"1Uk7̹S|r0{W%[7#b @>| w$ zsg0;1 ,eƯgCDiΥݑ%_Nha</-Z McE>6,РOpF3 "apLq>OQw}W ` P;12@s:IUjܮncNo[("?A(DlPjFn~ayI‹مERVR~\"ao.7f`2P g9 @A?)ܘ(`P a3*LEM7yE=_QdbaZjR=0v%SAcuw_;=]_ `U>gYЅSE K,v^]T:ؤg.sπb!Ys8P>"|762MTUgO1OR0Vm]m,): n>҉͹"f] zAzzI^j*a|ҷEiOu L}ӓꊚ ` s!L%`sʼnx:ulsp~b دfR0m8.x\@e<;`'E !`QNZwxqb{PZA*?3qS?PSR7{\f=i1 [|JOŪ,'^;Q:3 r仺HU-]5E}A T`QQS4fr͍At(kH#oad^7ysL^Vn.Y`Xe,!N|Aj-9p^OX. Jk_^.OӜD&m>&~?msy}(c`'OG>(~ &ʲ?:{A$?=0Z{7>tr€bk2G2`+Յ R!$ 4v ֣& `n' {0 qiy}(t">|*'4z2,1I#}&y,2_yP/?%l&? υq"TK7cϐp% J{-ZяPt h/c|!%ZqUXDPP x DEc h [AX`'8sd3uqScQ=0y7."l PXW}`"Tk@9жXġ#3A8)1nӋ]yM|q -KH~|mc'K`U,\M(%×K6Ɏu N_#P:GqNj>ߖ%'gC&kA 6mݻwˑ#G_lXSy=`d=2  نn~;?Ln{[LО"M'폶l 50 cs='Bf.Q1 A*T]erﮇxf݂zYڰB-1|kyFa傡 F'>yٸ(%|&x2.=/r Al_{vSL ix^ (fܺSV-]hɺ"x@Ez.P-?/dp#MtaB1-UɦwȎ-; e{0.F: \Yz!ң2OEylt`L†Iy)IEE 6 rU<d-po 訶>ga>޶o[5fS*6 sd{oIՋ Ĥ<;v^L0,3C0:Z}hdώ#>术K/KW˙ן0&& 0Zm(uu&96@S:'qy44 yX Л[K>`eDΞ][+,򕯈44 ӱ/ X6>WVB~*#Uzc:;샕aeR>ta>y|;A}dmұ@Nl`$Q^MI:F*냠29dX mEk6.tz[ a 8, RmT–/d9<x4O(&mA>DR Q5\IQe8iq bDAS2POC(ˆ/v ;qDf$3&SFbЏ=R(׸6#a𞠋k=4!= ꞁWHϓY\u#:+mҋ&X&GLaz&C`ŚK@vNBo^@Ѩt.<@Go/D"0K1 BUǺ=H _>pi UWQg(ʸM 6C}#r " -rw=[>&uUz]5Xmb<  NaV!FPxpx d.QWZMO=:&lVk>Շ}phg |sbC6`65_W^͙{oN̦upK "Ci|ׯW/ŵSI' p0:(ltl?byL(-. a^gGq+Hz dd3 Q)rH* [U0Tʚi#%rOk4f7ՉioRh{ f<; O<?\ 6h^ hdDomeO-)fFdJ}+T7 }VC@Ǖa@d}z2*g>#䓚1zKHQKo>_,t:GkNX$UFqc*NiVs1c{ǹE Ro޽6V,zZr<? gNy`YȚ$82nn:"@e L׮^ܶMG*xvM9,W̷!5 ?oopt/ɃKvQ5v=Lд_3bP-)|a,x"D]N,pZЉ9^d/EFdz x %,s{)_'6l := =p^`( qB%'D1wF` j@#d6l JK*a}Ay.],hyk1B=|>K@5:KM"^Tp8s͗ӆ߶b*yE4^6lk @^lMb}Hݜ5{GbԔ LJ?5n[n} %x81V2d9kN>,BaOF^l ldCb|/)ㄢ aаh4]| ) r`CЩAz"@꬀)c*AĹׅ7oͮ‚Ϧq|/]>'o~Cx@^?Fa8Ls_ TzeڠM璉R@v-R ҆XP$)_̸BA\o)Q*^xȹ`IuʸɭWJ_<"zeaC]7qZ* ]x> 'Khh!^kE>7`و;EHoސ"YxVB7={DV(MglaLG? au`LOW4sRl0^vl٢]{j!RΜ: F&W[zyd~Hs"yDQ>B#żQ醡f#|.9 IN8Qn..iԊ"A<^/]3,LMT FL>$U`5~+yo((VJN|8_}0 '@0!0s' D%L^.z-p ;\ iTla[2>v4@[OuD8 Yr4js`A/WV=av~^Ʒ?~^Y77"ŜJh&: T܌N%,2ďWnaCkJ`a7hP}Gv6VUS\"^,׋M5XʻN+6GBhj4!Fxlr[u 0/=b;A AK,˗:.൏OJ<.Nz_.pxR"H@DžYr!@*мIbfRNUՕ e!dB2X@6R*JaHȬ;Q'L0(<C0lu0t0L{vId'yI cBku) ǙbX~jD`ނFR WtMW٘vlofHju QF{z;ӳ4J8IC?<%0!>'t踿*zyPgѣZsW0,IT@i+w23Ieğta*`t}hjR=|P|dpKQ#]x &R[+F!Ƭ`LQN}WT`>oaxdUۂOLy뮱Qdsv]{>(@IDAT Ku`Lvn3<1_^_d.5MnCxasX/dI!%X2>Bv./gPi$A *EUQs!rXy^ ݻR4rzl1chff'yFQ &$)Asڤ[o~9 C9:g-ȁkp,V.-0#ǎck氎 g.΃eU%dq 1`70Ĺ禅8x93L8G(`2oϳ P3hA#Af.~@_66MVw4@c=nҥX\Q2Ј>=[=`gCos6,s, eXpߟK TZI|y,y L?bkր\5`ZZրj`?pƔ1XoƇD[-m,!]y bɖW{?<c^VCO-WA9XCXlk6'8=׭Z}l d`8dh'+rwp Xշ_`$܈5lC[wK #BNVo$o|MJp¥#)A hG0zz:&\j6 ` CPTyAo! VM4L?}tKjDѪX*Z5ɥ+d]ݘFBfUaO2502K*b pc c\AlF&1 "F}~4$bC7W%Nh45THL5 Uz4g9 4l />,o_fmeo['fa]Vz~cxyMWdT3z^ =an /А;U0w4"x`)q(]Mg̞L=&?f *8p@WӼ!fo3x*Mdv༰:4:Zgh@fC<Zw7w'է6qBhD0&u rK` F`duR$T?, <qx|N4`XAg(EX?;(`1p폡OeվI_XubsăgsUS66(URI)<ٯ QD}FS8Bh!C`3qfV!Pω`<|t{>+^)@؄bR("IAЌX1L1Md k,2Qe+)*Ez:n}\l3tG.}ngRW9<=w1lW镰Loo>q3$ЋQs^!c:˘5 p㙟 &{k!֚^r{׊g ޻w:*{z.(A&2M0 a?WL@ ˲:ʣ*gZ7Zy<ڔT{M%IAc/P2?&ܹLN.'Qu fmѿJ;V|%eJgzz9xeTj7.,?HJe^-+O(럴<` {u?pX;̘ohej@]ĆKSSD]vjZ0垭:΁ڑyQY4 d\[aWS% GC}DFHW!e+e ]X􅢣r޸**,ٔc`yi|'_}OmP#A9s$;C ;dRj0Obc^hEzg*jd~D?84|,=Y0.ų42s㱲J3BLDF ;UBf7 #_G7 GFh 4}OvS$ErHq(j)͘iL2۱j̖?hkfkk36͌4;)(7Do @]@GfVfV}# UY't +"40d)q!?~n?o2fvqCQj]+3h,8n6' ڜs J&S4w7po= 9Ul?\ȌCc)6zy  A , |X# sax.u]82=hx̯i~G l}_>aL@, (eMGt Wv0@JNZ8?8#4,~5\BȁvbT4mˊ>1ڥqzY9Aqd#Vۛ=b{(~Ҽ-\`= t/| X`^9o{وV 1ߣBk/!梴U։ ԕ<}RvPѡsI7s\P/'=I,E`2*wu|gx+[G47$Yx3z#H&QVk0f>yȄ{"3w91F󏕎%S30،M5 } -NϮBNva 6Veg3rc(cvhkM|"^V-vR`nf0) L"4Lbı0N(Q{c}ݲѧdmS)3uTH"㚜8sL. aܡz𜁴FCX؝gp4<"?}GrY{C%/>Lo'A;4M]քa(oܸ;W#;p|z=(˄LdV߾OPQI߇01ɹ ;-Q_Y5Lξ]v5,Џ_/@%oX\oEMƇ䴁JnTS0#.'9pvX>`\v@,SS›o'#߃l,L>wAO[ȿf]$XF{c V 4*ua"6eX +gϊ<(X̙nӹQ>,L'SZbis)"e$ |PF^E]y\1yi3 96VJ9snٴ@:tH^l S1Yj|һD!THsg\ReRr6,'qu(9Qk~1xpuaW6V|muu.\x *-h@^\일U}B/exTDN$\6w|c a{ilHMݡ$WWl V}1˕8@vE9(6wm1l \PKe^x:Z0ܥ#`َ :D%(`,&Yh6`=W\#=1;]VJJJ2 f(K eb9A1#:Rke:&w=de3C1R`@33|ށJȊ0wt(4,rzofHCzM7C2YD+}-ou7C)+ZeӒ(A ۴f?LCTnOuT p-DmҪyZ<כ8aFаY(?>$a GS -$ i)7R%04&sͺ1cci' XRlA;p Ql Og-+.p'*͓s1}iKeg=+h1\QX󞭬ߺu+6],A1Պca``jK*ؐH@n> >]ؙ@ Ӗ*pW}ſuG(^{Hws`uf$d䮌ٯ;o @Xh%I;~Oȇ!_|+'4X+ؼa{T@9 Cey$2vO(/CIDҙxİ12:(,z@#X}٪o9g*ab69=.GysBnusAo|CEؼ&nPm/8e$Iݿ+ӌ|7dJT dT?TjJK7ivۮzJyD=I07J P.hkDZZ8/N,N2]?*Y_3mndx5N9c UtS@= T|޸!i<yP9:d?T@z1.oe" }x=@(9h򞮥x69^$UǸ1+cj`|/̎xQN,۔5;N3) eJ-SVs=)Ô|SXg.鶂Ƿv@E9X8SbmZw*- '"zTnTij Sv|>Glhn Ig6m'Mްar3ca毻\9CZW&]-\?B" c3vAׁwR|V,0QOgju[ϜJtOEHq# 8PXLYC_twO8Xۅ/pƑ\x`\>u6 ȑ h~$n-6kVuՆk~ 0OM`F}0^}.@YLSUM췐 qJLz[ ۚ$OzMv@P Ը0~W .ģMZ8@q "]8bXa <.Ccڇ+TT6})@|eK- !#cdF%V PȎZ NpאujY2 `\IkGSΔL:d\-K%ɼLAiߟiY/Ӛ:G~MWĊ FR|.,' =3!<o?#đ2F|:w@K q3XҋCq[ZZR j/t ȣ0VVX?#Mugta B|x= Urѧev9񙜿zFM'R;:: w| D/}  Pt3815T7Ƶ@¬&vC돟Lza/O v!ysO,X/r U\09@eQr*Jw*(^Qɣ5@O$']* > %*0섈.5Vz#4q3[> |:mI`Ǚx! dXU x7\@r0 3p{5veXQBCIS|%)m͂@r |-\WҀnZS8+?26qd3dYydExR*)b .j_|s~l~7* 0h@j(w0]爊'sn}Rw-ynbS:eZ0H ԛ$ct;ԩ`++Q( T%wԱg6p6@Swɋ,6b3 ͋{(6 t`=& 7 (ˍ[0A?1T3%ؗ)`єtN.%ݔUFsbU̴L%:H ZkzeE (.fD%N\\!Piu`  sӓ݆CǓ$>t‹`0Q8lRzF~6xS?T3pV+e<34M=628[Y^N81Y Ht`hO6 );2+M-N䳘A]L1[< k皍Q9 VajQD=hP^`+XȲq—lfB'7TZ2ǏM@N 4Y_QN3Vbue4'887VZe:0,OnUqwe.o>Zin\pql  ]7UQ]VqI K}YPevPT &Ibhk>8 - BUQٸ FX^[QY(-*a+4,J^3ա+"/AXa/>qAA崕3dd?.|uw+~}sg@wܝb1\]2;i70k#7T52]Ot1__/QX-Z<}|A}\儦|,/8vŰ@ 8OYseL\d/>ALpҩJXCP\@22u+/>$Pk$P p~}~n4>f+n6fqǰnWs<>oA.]XP$$}(/>r^_%(#yS_> \|d릇e@\C4+zg27 ^(|+ߐ;G7ãXd TŸ' /x hmPwc+`y/UauŮǀnif{\ ZX,g"Xn#lнQeZ Hu/}aYHkT1|hm̀ro}Xc9*W:_7e~u`ZţfR;1ü%0Ps{k=ܗ izOWCQWj;v߀noa*>.@g "$O]'h_ J}}k\ϸOB0~caJhs";PeXC2_ 7d U(S{ )x qc5 ZSaxyR6-͛ &QMCES$GV h%G2"rA p JB jzj9A"=EE<ܣTA^ dzrEnKT]I{n+F ;ʔb $v>/L6` |)%Z.2[˶N *V Kv%x>r񠬊ᾖkiY jQi *8x\v-VA/pűi |d+<>!J@B`TʂX#>pEIKvn/oųp-xfcni O<)X3Ne ڻ,P 0x<xz:MYRtQxνV9wꓘVVApϋၐ0jS>K3p7ѵZ.ذa<쳲~-/]~x;B^ |(^M) z_8@Z`v3HdȥyiWQjȃӨ6Zg&")6dVN5#H r%at9F2Vj⑶}Oc2N$Z&A~僕jʍj6%-/.L޼)Mηٰ(Nh+_7E&@eJЂOj,`~/܆|?V' ay|x8n}j XFLd kyW71 2sE~/Pkԣ4@~ "k9'+*`,q݁q !ɻL;,?~ch hez A$MִH@\mTzTEeCT w>ȂLp <h[W\&L/q$T@fOH`alIR#I7O@ z\R %T-R@G7M6V 5i6lo,?_]V@U!x `܌w.W+z!v)ZM ґA ^AÊ ʊ.!dF g6i]̿r$@ܩ T4^tI<('NDK_ :;;.hAwQ;w|+-2^+PE] 7f~F5/ǯu6H8'Ą!0M z? Ս :QqnkJX}ĩQUHV3tע>L/h ܢHK36xa*Ld_淃*"f8jPʪ7-<< {D1=hn-c0V_ru,Z\Pnv0/u:` )_%8gj(- G(B@l (`Ɔ"`6+=*iyU0Vgp"CJl(OHbqh@ntb-͹-(ɿtMP_Jl.] -w:lh٢ )G,CܼಏF4gm cMZYB?6ȩOdƭG.غnT5++OQ]2LMrw#kRL.ލ ݀߃ t/_RWAC q ##Czong|ċsp%Єcs3U*MYMǡd_q?Znn|ǚ\!UJ}WD\uuOiYAyB>>^/QE\'Na?]Z"]~A>:Y]j-2J˱I  oe <{[8h?|\>;Eb 89JÄ;~"\2@YØGbp=A `>P6NJP+`QTF9i}n("SLU.:L%<IK(V Tfi.}B.&q  .P-dBY{Fjɀ[ׁQ ɝ6tl5ݸIK)0'dJ & NN"UJ"gAV"Z*oj1JA@fȋÍ#scU̵%PRR"u;v'|"GQTߋnᛇJP 蒛j* /:耩&;`4;*eZ^BÐ5q|@]hF p¾tӤȴH(ċ4"Qb[9U$ڳCߟG3$#!Ս]cn5 W3xMsKzNh^E; …g翘a}-#vi tg@. Ew[_45F5iRtApI세*{wMazO-0޹u<yrDKeu:ustB7*oϐµ~qN·ɋ:W[/A!h#Bv{0٭mgxIZ>Ϙؙ43^ߴTe8=Pjh.,O K!G-'l{Ț)H$,GN*~8Ezxl퇠 0ug|x16Fi2Al7;з >ijX-UyPL~H`!U|ů-g'Տ[:5i,of'O/Po*Ʊlpkfl.rN͓O7Ip4!O.|Qx9eH]Wxo*˥ 8N!4n&(́/)_`X6( B&᳾FNh0ڗnYQ!׬|; ɮסC".rFvsʮj9*X[ zO\LC>Z̫`]JF`YӍr&m"]w.EѩL ,е@g0~;oc5%<=F)pb]),TǜHMJzcb ;|у 4o/n/3ƍAo~x`Eм]ēlxK7l6}GQeNyU0״wڈG0ȉ'8WR2@5h@IHڶevO`q9O?M*R"&E$diX(%9bA˨Iԛ9+/jVeeqUO|Lj%S7XMF1ڤ_kSVv=@j}F~/^l`ғY! 04Y mȡlN @E %pI ֭[':QQȈǷ .{D__><~ \%vہJtʖ*[@2\zQ~۟}MS ^gv)pE-$7S=w鴜rf&2zsʊٽ}P)o*xI:B[_zTW9] a؁}CϠy k4#8}J:od h$rxmS epݮ;?>>ҷEzyWd"N W]ԝ&5u՗3ضq<1L=woTnvݐ=RY^Q,18/]R^y5y|ϓY0K!oZ 5X e5mNȺm ˁhO\^tiSg(r^P_;T㙿rK\ٱOT{;$];d7 A!'o,s\\ uMVĠn,m _.P-7ZIItN .iOk  j*J䉝arшT뇧b b~XrCF'>4)by/VF=f^h%_\-g$/l>; n,L7MWƲC5~He_LjL#V(N,"pU]:["ȱc4y%u:$x:Tm½uPdݵa]Ǚ 2R[uG rt{@KX`+\.0%ӂ;:6g"*+J/͞Cw|'uICP7V(0i!fu2 ܽB,WvHRI:|nR+o'w\pV )@|=PɅWCƢV 2HFFn7h K&`PNdlK|z^?,$-hр@faE%k+%w"=L(355jdAp͹*M} {I8k,Uj4Q^}#=Eʗmhy*{'  twxfSub>1_)K-,Bs:yz׋gۓR,SЍnfdUTPgpUԣ }E&#C̘C 4̎ut^䁰4XLhΘxFAIV#lz4&ʾWҐED`q}>`+̓7xq4cx۵e5G2Bu mG.ȸPeh.vTr8<3b(J(? pQ ;N]DukeYy C9!]͔h:<<$#%;ruJlJROI$ Bk<^ʒ YE9zPİȐAϠW>;1)] *[;+ʛJG󮼜h UȪ㙴ɱ0V *qxu5w3c{}XFcF0$H$4-\rT>Sx[J*nJH ZtcpNR!uɋqX[Rb.r̈c!%g0<_j WCѳwu0NRX6KӚɵ؅;(o >bhY!nNjWk>.}#+ m#@b CR9{e@ʫց]"YϽϓM{8U]K~)×>XvHBq숉aK&)Cᰌl \]nwz-iawDA:֮V9uŏ`l"0@041J ?Jϴi/u۱ ^_GW?-]}AXn;.᳝'O4q,ΌO?f$0^Iϒ&E\H hV |+\9L'@8sOw\>:&o1Y?:-tWZbBr,/iA>+[3}!OG+Wc9N`j+eM]ıE ポgL~EI{h7WNϠXW.ܖG{~Ƃ.FN? WwhW1:|G|r[j`Ȭ(?.i*30Α;{LSQ>Zsxϕ[ Mo2y]]Q( xRy RJ v{D;'rL8޿a+gWKi?5GZe.yDrj\w =,)umDNA9.qs2.j%7D~[LAw$vLF^, hg/WwTH$ n<"I kSq ]hEn"i!jk[$~'ÝPYywV z2H. @ dwՄ8Qsop7P9԰y$g?Z"h?p Ҫn_~YN8!}~ힴ\o. 8|R_+ @1״H`Gs]09{:r{l`|gtxlu/pc8T)#Gy4-2:%Yp4NXs]-??B3 &78]ϴg <4 #lv0ܩAL1-Xo}Ô@,׮DZd3;Xk-o2Poy4 OyUƁQ^9Y(ؼy+>!B[0e(˂۲(RE %PJ漃4ɻ0.B-=(!;!Vf>DŽ x9.>bO%B"!ذW~1vpVt! #hӭtYY2\ Gyg=r+W:K##M7V Hu;;&%gS"~S4JP)?yJdP`?CsKr{"MGXr"m`Gr.F39A y#cNX$4NzV~#:$[ cǧrR~ J?}7t'rϐOj3N9u-3|,٢ w-  .8ʆ+mClGP3x(d)oBY6݁`0+J%^ |>c1 ?pc#+j.AЋ=niCo9+~eдvJc~qrZ,qL`r$jN.;u׹ni-;gx)Gg7EOUoeA~Y ٧MaP(jG"19vŦ'J pdw +Uһ:('`}-`vDgTrao-mr{K 0tѾ xsD%3r({]\Y e}%vvCaK0l2s mTvB,#>u-̵vGT 5t_הJ*({,! ɺ#p:7GH@))]gm zHH;g;'E~6wS ;1@O/z;aMJڤ3C a>"$+C|{QWy ; w`VȴoUe=j0)s:+a:c}9U><)\6 dŸzl%O*J( WK̹ [&vUϖ꒤BepDkPZ.(׋0NW6p:bgVH=$ 2@ `ѳ߼ \ bHHYe 7-0/)J``st!iWs`OFPBDm[5cNۄ15ii݀BwM.C"%2 L n|S>-*eֆn`?8zm) *՞1k=bX" *k ey܀2{2tȃ]6Q%{Z,F)s&bsjaHL(`5W(27@ƍgYt@Wяe/Jykryw/qEưϕ+wٟ ??U )"¥*4sOX)<|+̱Ň[~~-?n~x!sʦ]ҴC37oBgbialwa*;>=95%*vǰ`]pbH |y9MҒvvn`݋.ʃIm(gr̢=l.ŸR\2sENNs˖].J#mN&Fb>X"?|D C:sG%8S% 9_ˬp2f*dyd-s< Ox6=Қ2NLO^X6Uv8pg~w#+-LP9%Ѯ @A7m0uMC` ߶71,˟NCuJaaZ %T|PbT8zb\p@!(Ox*#mKL˪@%Pa* XTfB7 (1(am2L Ť7*Pui(DŸTg~o4?L~#;t=*% (;i`OVܰ.Fɸ`ºCQA9BUh6tL(qrkS.*PIX/$ 'Ix^H|F  |hSD$&R8Xa2ЀX/NX+57\TTXE@%U&"Ȁ 7K5Ǡ+V!UkVj]ܥ >4n!0 Z iqyb"K-nW86@buC.6fD1ߥչnoය$0U[`Xc%J$ x|໣/zvGx؇ZsmlXRj[;,Jyw)հr 17hH\[2 K`Yو?؟, $oC^;~9H-U&)URl.dۊՇ˼tKt@#GÔtNJ~wFk7>S3ڥg_ҼDyٞJZ%WC) exM iyU9m1V"?uO۾:| 9Ou dxd<3쩆#;ӛk=ѹuEN 5)RQ tS |ɤZiZ0CWM]4-r>ʗ52ؖ]SB~Eɢ.Q1X 3<$<<+{m}y˃+9Y( D\?C224u ᆰܙү@ӧ IL? 4nˊ/7۔Bʫȣpi. /S.E=V X+0#wzĎ,.t u_VxJe۪Ǧɤs݉L) Gi˼-Yfm XȬů,v5Hr3'mRщ_/w9C)@.Fpy(ek mkmer=Gh%w JS#u ~ ^>/ g&-OQh :=xZzt/y$)k _~b5R+VN-Xb@Yϑ }.ΝN*LXDž&ԏm[X0 ܀N0z I ܂18agyѿpY+쇬?6@@ v-bB%]nt(_}{t s8v+ʸP neXhiyୄExW&U1(MFcpYR4) (hB/F=V ' xW\P G@)dOyZ `~ၕO%/2֤k`Нv]uJ5<XY]twCL0-lX'X/jrNZr08TтiZ%O+X';pi{h~7,l"i \x-E44Hs `PPU E (ӪukSSܹS>.`ښ?rkT|[%W*q㞾m1P؃cM6TɎ PMW d5{\SC(GV~Z* !v`B^˞OG&*힯yI]KPZ>0uZXS(YvMf&=#UZ`Md&CKZ0@#i#>?P"2 xU30ZhĎ77 M3"EY}f4v:v WUAYg^1*[aHŀʠҢ$0Y1H63ne*z-Ng2RH^`zƸ 4W9Y(#y"KwOd?\ 4tJ=,!,,eG-nq ?D™̭Q( zU>y5E 噑 W$J̥p /Ʌ)̃ɚJA1]$J[A%W{w7eQ%U5\nQW\+$]٠߅)ݭ=hNf(Qq$tA{֬ʐĮhC$2*}06h oX@%,;gp1ufVyX2c3)WO9!0)a+e6.Z)fA % fQfgXJ @I >ߗJ>o:/<***2b+yaCG\ Y^\z/<.9C;GWIy$-E.$4ԃ*waHM[v@)T-Wä[&zO3EyǴ"ܤk< y]<\JdIvyLI*_ȎP M i bȖ@OŸ!Q[呋[7<ùK[ʭlWO,-TY龯@6I'jC K.*/l›V ]F] 5Kxp#k6N\ۅښ&lõ,xYTಌd P\%Rky.[CM-u~ᱨu=7L؀jX@\&~ ƅߵ\: 9/K`IxZ3֎u!<`- o6?*ȵ'D#a4 kK$pd ʐqSu|\ >Qh)uAuǤ!up-T 4ށ4Z="HSدt[8x!+=XwqP#̓|ֻR9Pר[AɓrPZ~n\*`%T*5Z {@߹uM{|N}Xq,:IBݏw Y|EZE %P@QE %K/J@ll;$cԮg%m-KLw- ˄ t`}R劋;| Vɡ}ʙO8P r莡N߁+\nOPJYJP2B!RЮ |^n\ORQ%*#2ߺd@ZLT 8: u2KmpZ!'U(F&h֍`d[@&f"edXLYC wd68{3#lnl|FֵdCF۫(KInVbAI 86DЭ1, $d `k<* X5eX\kw [|<h0(aUX]TeI,>5*Jo4uʓPt0)^;Q#2ԔǤ4|qwjs)xqgdʌ=?mxY@&o vFMCjD˨  *{P& "d?}vy'a+,7 y9qep$o}G‚ ?- P!b-(J(I ГW5S>'_ڿ]jС?̞6XrIM]-C&;;?t\ntޑcoʳn1{&9+]גU`A$H R\E)J"TH׺E6p}i~h1y? ƦdC  (gV|񈬬B-Y r*#9'="|~ՀՀՀՀՀk3$~?~M`&G`\x/(H W4m0y-5Z !I= 4- q߰OK_摻vwg[wr1\h[^^ 8!^ J;'CCOa2bXJ]*V_dzzLҩ@ :1o~4 w M/7GNqsR< rWC??ErWhu0-L`qO몈924d:yմw? <xj~/ u"5ytպc*{|FĂd U5E$)d *AWA r4"~x$ \AWXh XKOab=1. 䁎lU1'0 (tGت2w7֕K;Ish CCޤ|[j###/\M| ~!4[:#9s"O?C183 Agy|p 0@&_f[FBjV<4Vf0:VpDT@Z@wb|65-&xv;xC 8O iK:U%GJ8k))}dp ЭV_!䢘h96T9cen2)ҭc.7ZԼ. 7G@ce'Gf<7T7(`bh텅n,dlQ:1#jP_J@UnA[tY6VVVj\ UؾzlJ>$KpW@;>MZێiI\V/hI9wR_幓[K -qS! ~M;(x7&-hY T`o[oRV>9RuTQtVj %`0O\0d68W2s>% h:cZK}"H 'IC@q.JS' 5.'.9qbkQ&<6@Gj}X"=&͋ ژkw:I:3r [6>5&ggn8vK4@Lk /퀑4񭽠 R" snWdXNH3x (43,Q.zǀP2utEwեbU}L\=/,[8Ԯ+rɿd!>{ЃL\Bk&E oUj +cXY,e!E)dP@)B* "3hPࠈ [d$iܞ7+/ t<Q GG 2!>(\(LJG9M(6+wK,?4#OÕ,*(MĆ浧^ywxSi7<Ʌ'ϕdk Li暣OG\@;P@@0jyM# sPZWz(?{sy(pc&jjj`5,>$`0Pv8W⇬>q}.@!w&Y OuHȱg,.'}*Mh&e&Zw}˙^1LJ r"CK.><e$㿗珽1Rjj]ՓQ\KOI:pG[Ww]Q]~(I RQL|ބW~ò0$ X:RbpI" 0'/sȾ N<baITIlɗ4s҈oP%(!u /ԢQΨG@"!BMCF AC6FϚLY[M4Xs5ZY^5V[@ rAZ3M՚|uu4 E 8iFBA|cʙxT9ΐ+h xrSEFZSdy[F>}H[YSۥOκ)KNҒs iWyMI8r c~c${K}70Vgc)h l멣+{g(M@y4|^+A`8!|0.ttGnc>e-/;3:< af!FvguW^kMbA ATIuuj,n`0+s7lmPR{i-,pC'D,pP8.n< BA0μГ{-ԋ3S}N35:`։~yw›׀|jZT,Mꪄ7o\<򡗑HemJ9c)%^b "DAe~l|9 )I,X*]# HpȴQXXe!zK*V\\Q%_aUsAyx6 ЀQ:,bت ia&SrƬ7ϏS{ LT+҃,5@IDAT1w ́!<Wp Y*y;`?0Jun/CզH[j% L&[r4b?gθ%[, !xc}׭)lH'yf;h75@OI> _ddGb tC#?M6(:@S%zp=X@As:i*;[V@X(?}UޙU!ŇMVVVVVVVwI4F*0B_}Z˜H j}/&̸D10V t~ XEZ =6\D^#8Rm<&_s n@1u Õ+0Z`ugvr҉m?…jxx/gce0ffa[VK߰OKav◦YNEx9E`w+GRj@:.IWޜiփPzLj1݌Qgz p꩑NKCLcRAp JAR 27s x5^ hat1 q/ pA$?1! &ax4W AP`ޮ^r2jC̡n+AC>uJgFY5 0:nQWEǯc_?{~TW;R}G* ܹޥ c5Ц,ҘoorIg k6ڕyע4Ӽ?NGv13TpLKĐL]W"G8OôQzcγVi YZ])Me5z5"j'+|r}Sc#(фwB `|ȜW]pnP&G0,U9MVgq)@Oi6/#hh9ؔr Gi$y$?> *Հ@KSl*>RX`Bjjjjjjj`5@4WϦ{O>HBY +h ^)dzT XYq{`ޢX_u'1 #\whDQ:G, 8N Č7+V.ff)*=m1%eh `] gMľ$ LM 23N]ҫA,VSG:4ë  j롳zzYIz+cIW@=bk_ +TBàD^60@sM62>Έ~Xj2I%(jr5f01[b`|!2y04B"<5 lJ.]) ٬vᆪ7[p]@rI Gt$X&Fˠc05G$+hUPzݱ9 3k䫯˗/?7mw:U ! 9.K@Lmz5#=;|o|Ug% zoyRs@H:G<|ևON|!ZrTxllL.]7o'|ry6yS2Ο?/W^/oˋ{+cZV=ZU-f>h:uz;|~ʝR(/6}+ 0hoHӋ0 O@y*A_܇Q x1LTx= O xTBVnޜm&2@*8{G8^t.87 &/=kQDU;V'ߊs8%ng][[mʴŬ@ ̙3_wo/^I(=>y>1OUܾUW` $ ʐ" 2ŴydV.շxgQ?Z-r 55ݑ %)QF>ip\YgB4֔3$z׽X<{(@Ȱ֐"NK/ N,'" s3I)}IԻқ}f{wՠqQw|@ԭV 24]?0I߽?ֳ3ٴLI y|y[?_,3>V=q' #cD2 vŨtN#Fe#Ԁ|n0Il%\]2ݿdW=9ݱ~)>mdjZ6 s0@bƵ6Y|6@aр`B' )A 㬞։|AS%hF}Oto԰~(#Lcr72u07aXRJ9c)%^\;6aNp X) @sGhI2Z 3*LMsAL3rU:9/Fۑo<~ (8q`c֪dNv+JxM-`ȏ)19}VBLHXDygid5'رcu0KJj?!C/ @׮ 0'=G}&4ZGOЂcKY:xe9X};HH` W@1`z @ <-E.+5qަ., q é5H61屳^"kr)]QʙWZH~ԶZ^dH3ǡqؐ qaP$n;ṊTi 1o* BmH;!ug;"bbm};8hYVWT`(H-s/q馀D$ L\ ėϗ2B(is9x?eLbۋxQ ڵ i  ,cl"S^NIo+YfλYF+ZE+׃2Usz"86Y X X X X v mU\Rf3x67wV<$~xOCLt+eӲ\*jJ;;뉆߻,/ /^PnW^x+)TS/Hz'w~nK<P% Rh:/@(tlO0` b6 ir^N욯EOV2Ƿ^KFs#㗿c_">xMjx +a :tD- ƚ680#;'iMb'&'2qЃ&oVӁ=I3Jw<,܂z'2܋S*m,%Z8yFd:24Tx\Yຖ`ɬ@&O?Tz- w@fj,ufE?t“`Gw  658EneA!A i {7:Fo, '`䥼=rFtO5`j1:1ÄS FZ^X DH fdA!徲toxk!4(R e tax(&^ p9\6)4:j^@GFF^':플}i;詀[+9Z]5'R=h:||v}yl%c5?L\<œ\1]ėE ꓗ=,}] ?5Pr!􎔥K쐰7"!x&`EѰ9.G3 K2gr6T!血.=y@dO p誳kSbQ5TW/oe F:[4: d/B'p |A1 dB8mS‘ x5HH` (u k{8c$ZBԒcf nY Y>U̢uýqXFZ,fןʍ1.O ':l.gzlHo@cpW \0 *|''s'e> @ iόɏCKG#oܿ5x&ds0!Sh{glƒ DC"0 ˂F= [ ue0@)VV[.POPԷnݺo^AK^kx8.^`1lmSk<րoՁ Tx/p" r纴 {5cB?C:4{hVn*1/\y xN/4hoG;V2;4]7(/_ &JN@{XzzzXJ>:uQLey%%wO^~By7Tjjjjjjjjjjj`whIZĊ_L.ͤ6`C}d'{㈣ :IYאƉ_8ArװAqd԰Wze FYƥ8($(ûHF B&誧 ?+) v{:?'s  <t j)I{gqy0kHs ? Ļ:7z4c[O\f43S Y?xcNŹcx{D8C. &އ1A/5WKphZM)Gjn=<1dă0iWI*rpeB2|?'<لl!s6 xO,t|`xctg)}%1V!x  =0ݚ< z0x+ԑ~:Ӄl-C(Y"w%8>3,bDzx`!D"nf"u!j|e.Xs*iY)e0Q c?m]% 0yQ|Kb,y !^'2 .ƝʨTc#!Inu˸|V/?qX*="CC0Qxp^.ge~dҷ%OBVnЀ+izr"YxȘH# G(\p@h C Fb^ >8 zrBvҸe2>AmQuy\:+2Kzne7VãR}W*PCR7.m!c!Po`ǿxpM&nFLxSqK 5>')?|f^x("r7p'CϠ@3V>95'A1i0C&Coш]WX_ #&d5XXXǏɓ'!x 53]#˛]@>( Ri0hր HC6M0言/l]='u1 c,]Sl?>_ }aL"x1IK#|Rr#gFW|&Y0@CЛ%mڣ@ ~)78mחrU8g8M!)Cc2<w}*g6>:.ǿHRP-v6ej5`5`5`5оO%ke/ax/ U90 Za˘9^-] L%{AX#Q]2"͎{9qTpwfke(5`2<P̙t+|Ua&\]A=]#"%.W bגȀ@,r [рT08ѣwח! h$x%&a b1Rі~ iss{/W]&[+4!~aҠJN-zp=@W$HҷyBJK5Fpݝߑހ ʾ$#CFXʱkVw{ɷ+0Da1{ZI C8x8+PxhX?Hr=xx#YCF!͛_lg] @7ч*ñԽlV .N87]%ོ 3 4eV{`zи!t?#(r-Iڥ̀@,|#;Ja>sxfG<[.̛LW& x~4n.ECtU䤕ќnVW*3\/حL-6_z=R\J! Sb F:$7$d 8Nb l9^3v,O:^uYئa*[ Bݭ0c2Е?VP iu# J0@փk4Yה!z03tBSBիABaL+ ~_4ZLrh\#CYߴ(&22vK>>&v7}VA4ޘ>0'7Jp }IerrdY_m TOHkuh 'X(HcDzCe opڂưu1Ua]p<ja+6Jl/_ 7h?ue>?JZC8tCf!) ٯ '6^/~)?ٛכ\~UYSM{jjjj_(Hcrk=J- c_+cD).'uMVVVVVVj˹4ˆbQ?!m#u xOcpoubQndŪ`r~O/ &= ےV䡑ۏ?NRꡀӡwj0xf=X'wQVfЕy}Hv0Vcv!,*BݣA1xavق+` A ] 4~$ ]^R0_:y ^lw;8ʼn]iN+==O:_"d "}%t tjxTʱJ%zԣA+{GD䱡0BlExDKU 2vyR]&q @cՁ=I3G$\sp)Z/ !^xB=Ý0\P7W: \R24l=!l2օ-H5Կ~nR-`kʕ+r9v\~݂ VTU>|/+]q'}tp83a`АnLԆJ|# H@[:O4s~S.*z JM,Sʱ.GY< 5A"Æk aSk6_T89G)IG2i1~ԢQxRDN.̐dzW2HzS%h{AqC܋`]U[PjVVVG|W` (=ܶjj]M3r}`C!ܟjjjj`5PMTiPaa93YU(`P."ɦr%\Bh ƒA,7+h%P<Sɯ4uYC)`܀ @oe'Ggp *RØp s ta:}g}~KU\s5  ADcF cq V?>ѠpG{7G<bͨ  |&ׯH6v+*eT#OH9Ty` W#x CI>xDd.hspER)c/tHy v}O*~ qƜ4`PJO!rtWȅғE{mS}pRAsm >sodffk^?M]*g{?I8uA4^,6p D1ɟ=:*잱[_% /gXk4EIlq>m%r":?y 2p;]x&<=]s.+9 LA{1atran- qO(rf Ȝhe![+ `\JvACRB eܯj>!qoA-Vh@%)>㝥1.qp?#XuI/&X\BXbG ,% + cL;WWޖ+[W?4P(.{^R8'sR٪,*0 }~y* *]C˷$^̕1Q ~U| dzzc N6O$瓧4P IڵP<[W6!W<.O58y a}59gI=`Ė~։Ew)x)Yl I [cG:8zrKc%v1i) P;y.siA&=?lPK/I N,_XRsl (XO;렂*zɓ'Q aťX`5emE?0\]ڭnѼvBWצ}ʝh" ýT\\>U?_$|H8<%v~(bWpǷJ*}V?8˒Eyf—QO̯WsGJ]@6jjjj^=%1vIg'KwJca# 0~y9bzSkS~J8qbg0a%Q 0F$$ J`>Z`)EvNM?ܭw޾G[~ #}OH8 6/ 23KXł3%78#-P#F"r&3,,_|)9{yROKa!b^z|t h`XT$$s!f13cwEFvdT-봣V!Bf=V6z T ~ߩg.`v +ssz9j-C:+ ~9Y]E@@>XM#eSۑn q]s4sj c/*h\zWwAA*|(qtd}4s` "b 9cK2Y4#yX +<0<ٓF r!y>!~mB L԰AKJ4 _}< ~i6 ]ng`e6lίKm5`5`55gpHpY*UGe'pP6XI~d5`5`5`5ph~d5`5`5`5`5`5 V헱ں Ch/ U90p{|*q<ض"Waer(]v7Oʞ`UBf~Iv Nk48(\ ^U)|0=5&- Rv8Ɏ\&@4n` *qx37XK83!'JnqAbw?F<-AZMVYEkLUx1dTB H|U,°?7>Z.@\A,n aٓ C}Ay۰直õt_.&d>SM?C#rmYM$VOp'9wܸqA>i38M.fBf H5:Oa,mj1jG>j>4T*4U(O^VyS.ukqy)c-<؆b4SF Z>WH `Jz uK?X КsRDCjH'?,ʀGEPyhƀvțXWZ)UiY\366ج'-Zh @BV%8A)0pry ~vH>>_~@x(ɿ렂P($e -o"+)HlUj6SD;iя>tP}x=e9_/˲VVVVVVVVVVVV-jY) FG涘I I"an-2f` "P'8AO,#3&װp[繁(Tc*f9$֑˜:(S桏_JpϢ pj55 ;:%1xA0mtC$as(YTjP;?vtgX`/J' (8j=l~{A_yz}z&w6Kb@IDATpxuee4턮M;юEzZCW?ȖNTeyjjjjjjjjjjjjy gvRƸΡOy;\v c1^78VD⇑YFc:1Ns@32p]]) 7_`Mv"[ 2"eTege~dd~F-T!kbw}O)G_83\ e\[wPZ>A&N(Qu[ŪfxCzA p| <~UgY;8<="qUI! !> p {쁧\oܟ9O>SPeﷻuUM|oSq' ˏ|[$3mvyZp49_dҸFx5EoyUB=\90Ӹ'c$ sLSe)m)Ry`2FpC <4ky*0z8LC`A(00v yPcGuc׺Y6"OykAJjOHC9TQBu#6pZWD pjגЋG=)WRXP?G{ng<X@OwT@l`jYjmݬ<Z)s;wONNۻ6Y X X X X X X X X X X X ʹT`Tkkp{z% ~.}8w<+2.K#ޖ3R( oWT{C#'J2 աj_֩^nW S˩Kn:MюMJ_ (И`5\ǿF˩t:w6oܾ}[4^m_ CI E1L5r^@K Lx]zsj~i:p? 㦄1ô{Z$>(@ nF*2Npm G CqاwrT7PR Q(C?1Nhx3NE@`(` X`ǎ+Fz7Qz`=8OPˑ[q 4V Ce3'qܹ5x\`i'!5Ǽ:xlM{ȟ`f=Pߪ旊^?)`!Mm>3)+/9d&- J kKsܪj`$"lm1`ϛ7] Г$V,MKRzObS gQ779@T@Nz1Qۃ: rMÅuUg S߂XI#`c Pྟuw]Z-hBޔ_hCW &S#^?M7?]@o! {͐"66릏>?)~xDso~K?{SzliITВ,ՀՀՀNk@_8͛N1 T ܘ&hcZ>I ~r CuMrA5ŬtF 2@e3M_V`URA0Fˆ++1U͉Fw eLXNa#5XF'qϿkݱW{j1/T\ŒLOI:usK R&2}3" T+CG@7 "UT,GO}#.!BTKI-'oXY PD8NN,Rs h b6,ICcnQP{hdaɴ~;xUMT@Yԕ5lR84@@<+y`/L`{15L}k:8~} 1i6wc&n t:7IBВzSΛ{I(=1ySQFpz_` ~1 0uyCiڤ a+}$%a~1 O3-AO d;2 ^b z<6tD_G2tC k|28\/4){ `4F^_첐 Py/n P0<< !7۔(x?-HƬ+ UϖO-Sed5`5`5`5UW{yDBg>.aX X @6_cg/_*|kd`=;(;U6F5tB H f2%kl!4ߙCfrZc[Ayo0k2CF8W"!xJ 2nep^,p2\ַ-ʏHBX $@CϠUysh.JJ|?rYXF,Ӝb.Ӌi\֠*AK:(`U:poCRWt1>5+]סUAVV\OHUhf5i`Fu] =+kKSֶ@uvժcVjܺu cFa/|w>x(cÌ HjXW+/$ \j]dW#uLFX攺ħyg^@ 4gyx4SXB/eE@6QU" JCxXnji*FZPHܥlm T|֬|zj +T"~@|}|C*s_^w/{^w[GLx8{Y>~ LCC=Tp/o5`5`5`5`5`524Fskj,E)kފӁ ObaY2[N b! `ݣ hQw\{]qN3āḳT5pxNOV$䅊Sy (O1&qx܇Ve>uc{X~M*|v/Vspw^W/PhPΧ9/̧0@Ȅ )"( x#_}T= J7(eC0 *ߍ&kҫ"SX}^B< «G:>AW߻ex(r[Όp-]PuCG% dIm 5RXVw gEa< o}_ݯ rѻ|c/C9 ^;hw+޷hoSZ8r䈼[=2 @A ?&.LxDCi^epzjAaW2vg/or[^<]9YGpz q@Xt8O*qO @4``̱_ZYz`X5j}̛N ruQ*XN8:#7m3\:1 0sRaT5@Vه:ĚSOc}8mȋ` x (B? ބLZ^Y;}Crl~{~ XP:ʱ| x0 $[Ll 7eܑjx30au'mǮݒ&$&Y3P((NLvȾ=}Èځ&SToٱ X> y$\Q󜠜O7WnʭyY[s)čjX$"{zdtm۶kI:ь1_4}}.W0q87z[>v㶼w \?$?~&eՀՀՀՀՀ `ex((gM@hWt K(C_v>ceTyzd \( 'qRgݤs\A҈_ʩU"&iTT1aSBF`~?eKW{wK 3y;.IX*eK4iV:t5cOSW7U迯tEAJ0W*~fAA`p` = #IX0Z~.:sn&x>f5 ux`-ؙrU['>V yN9htj8Ő4315`؃qQJ_5:з yni&@wʑ+.yl cJY[ssL2aǚ|@Һ. ?(2 ި֔yK.[Op arQwPvrhH7:2t?ԪLj5#_PȨ:<4OK IժSy/R}$t-Ԭ {j ]lX#zCf7G{$f*j-g>oCl8z(ez(X~#qUVjjj`'5UW20`dey᧛]Su4U: -dSMBL-慮拘h0äGa Z{;n9e/4r+`/ܒG5jj&Gi r( ]0MD#a]Wy,3ݫ6&錼y糯ds>,#f7˶jjjjj~@p<@t#V<9 ~ aE>m2(ꐆ-S >YdaYns&fߑ8 y|Lera(WfKs~ O)07-|h~ʫ!@"=tͽK SUD?*wŔeAVzXW^x3O$nϤLQn˒\?MF&U Bt^ﯥoŲU[8B(`A;A VGpfiepq0<-tx6?{oǑ ZX pDBZRmw7͏yϛ>g9>3o;>햻m.ڥ"E$@^T(P(,7޸{esI&py֧Wce4>6?lCC9qO"| i5H {  #O׶B#қ8$dg%߻OmN1Yzk0'>b PuMuZ768:Vo Q<pZB,R>>tSr+Ybȃ|OۿxZIFST@DիWځّ۷:їjhll[nI0Tk`!*ʕ+G *%y$MW/H^F^ ~?W%UHd2,s|L-}gcjgiq rе߱EIvJSC!pnhgM.JS'"&ba W\Oh@cxgπuK{]Ѓ𨜃~ZI,ŠE$`?xS~i0C䞜M+V TVZj<Ww}&4IW/bK#Y:ul7xF 0-U\MpӀӀӀӀ@ "Cw0̅DJꂊ+.`&-T:7aɦ J&~11p-r#&܏,'WC08τ0/ea@YM}Xe ݠpLpPy!Q+uImcꚧ=!0`')k1}c9h/fx{E@\bbhs'ߛ˷Vx!HzRʎb.$+s#S +VΐTÑd竗z^ X&:=< {z܇g̽<"tk)?s|UR,iQ4E|4S#/zFzP0Y~Sl435Aui94HӬ:ݸqtp cF>yEFrA.QTzU>(@ƛyӻÑi/>D5FJwDԥ-cZFJ+ *xv8]'Rm4]_JG@q||ٻ2u[r\)z.???(&=}_.*YoA)ǃĴ]ӝ/tmb5Iu{wI\nIgdu4 #g.aKLP8%Ey+q(_G8ʋ}~[ItrL1Ѱ{mOޕfL0 @bP-%y7#X70"t.mo.RdZ+%Ѽ}/o~tNKiǏ eF/p 3|oش$[H=\pk\ӀӀӀӀjiA Pfʚ[T!qw@j3ӺZX|° zfh^n˜4'*U0v2T"'aQr465wqGv`Dey e.'A8˧Hȃati5U- (kMr.9^ rat*E8/7ɯ?-ƸG-gd7)Bn==!(I}ZSk  RUV,afGZs`o_/a, 4֪\ Nl^-x\Ѣ?D CSǽps=ٞNl4444440 C<L Nx?bKqUda\ʱ*U]Z8 /*ŷb8EAx Ȑw"r-,C`A >SdgclA55'ly9 (m|MZkƑǻќߗcX0yc욥׻u!^ J` P@R>pU܆A<O8X/+KԈ3&nmaHd=R nt R BeY޿V?V/' tN|P^WU&1srCTĴ\VZvk 0.<O( URX>|>c^SkӀӀӀӀ@q#X`V+8uD+l^0>g#-ySA; e _y ^ 1f)q5t5XS()ȓ{Qx1*3z *m~ ˍ)3R۩Vfd\o䠼꽀u6@ - uPJ#_Ȏӆ' #-<2=gddx6G5~i[r4=jRJ Pau**,,ԿnHR@#5:]'Rm\]/HA^.g؄%jgPaKͦƉcN7 * ^ 22a(6}e.@9̼l:7tѰO`Lh2-*Q){aO,ʈH&WZc 5`RRPK$Ye\%m `{q\ǾQLsUJmfɮ<55?rV\z[paiZlQ]"ѽ5R4˴D`$f` ~Za >ܙ@1=5%aj{#t.Xj8  x ioo_`_m{͍M6=/p79!vQ =)X ̾G@c0I(I2hRG`b 4#G 9yT `  tS'J)`< ` pdi&+G TwS؀'J1xrljl?43q?0gPAkf.My_ͬoFc`ݺLsɏmLxwQs|O;/(%6e%`88 8 {I~n}i\ gO>˷>ȋHaAWoRr| X%3 Cd]ia4`~}e qv\kjQ)ˑr>0 nDֆnڝviEpl)/*Ң<( 9Ld8T'3a,- *q7'd"!ʗH7 ns{x#^SYQ>Vﺫ-wC0 䆲p[mWJrI>|EVEzeyق -+Qvkwq fHAA"|P?\уC{꽑e,Hriiiiii`Ch J+X+d|pX8 O4Υ x;`P'}HCz3Е,Ε8Ѐ!|`9`~xF|0rڶ.c3-!ukD5xR;>ƦRdW#,Of^cY.;G%1==*1rPk)/(^-y95pi,k>x‹ڄ>C*C DӞa<#8c\@N"(ϱX f4LXjd|_ictKM{~)ƻilD7E_`lx (DP}r{q+Tǘ0G%|zᤤP'q>J&<iWcD],Nroܣ4xkIHCN}SbkƫNWɳZROZc稩#0ȏyfyL R &GJH:&M{Z3,'CILT{*>@jyK8&6j qhnI/nmʆ'v>z[Z/(wh`8 8  da=׽Y R %2F.ي.(/֗hIoɫ~!M2Hx|-X-<{d<ǢD&ԥGFFٙp_ CiN([pG8)?\)'\Vb֌Qԝ3,!yy9 w3 qj%-һ`n\ .p4wJ+ALTd,VSDCcWO(<>)@شyO vJʝXB cjŏޗ8[c &(c䨑8yhwFqkb6fo<($=ʪ8KI<}5{ !)h [Wh.4rGluu>2<2Ǥ X%] m$ "&'Q=!gB/xdDh?e95z 4=GoD! B0 jHˣn N*q$Qfk55.b-X@`Ym²' _'"bOcںb >|MnFk J޷<̧U>ww-Xiii  9_sBVl+ Yinv+oJF4+<>h93w:uExTv5$sRNgʗ;(Ck8ьFpw`4={ dK;wr`fZsG|LX9˘&pYߟ0&Q6x9& 5C\3MO :2'Khwkin4 3ht5O|#)eg9T^p7W"oG9@"%@׎".r?B|LmdbϖPn+,M$ 6U>*ίTV4444q4gg}pڰ Vc{ݦ 9i +`:]Ƨ*H+~Qhdc2(?'GWD3Di6䌟QB|g体%p ή>trD/?+s7Bt/^+~ojK!x*\YbmKrޣ*# VK.6+.޼#wVx21+?xy0~b[/* %<<pJ#M+Q'=\Bȃ1~axL(+S=_16PG_ȅ͸ٕzWv\>;wMK#(D͛J!O\蕮ALJO*O>?{ ,Y~z ӀӀӀӀӀ&j9u c1x j^5 )T 4jޕPKv<|<S@NHр/3T=@NBv_*k+mxr ߞMfz'hйrs?1M+p$h[Cfwq\oQx@1h }w*$:Fl\y`k#-wB$?ϋx4o$7>۔~f(~Lze pTCe`w{1CWJlP ( Pnl |w fi6Oxh1t>w1ʣFzV)@O#5ȃ&pzyHnj42s>@IDAT33Hh6uǷKh 24޳.Nq&=5`^#xyz0R 9x4:R+iL>P/zs1OQLb`fAQ9++Cn )o1Z&3y&yy €I1 TȐZ (8yzRC$mDkPL֎`\ 'ӀӀ2=05C4 pR6 p^cv6Tɣw C*`j(5VcE D<,V/GbۮauДBX<&\_5 Y3zz;C0cR& 4N Iw3 KEYa~lFg# ːL)y3c 2=cwlǪ|uK@Aǿ,'/\Q? J)$~ kMi@\mۢG_>.=+Ĉ\iwC!O^f\kʊ cd;o @'Մ}śsbgKuY7}ūv;NNNNNNN4 ̑r)hՀ1 4(f\q~9W0S^ Z}@=. M+81')4P43._-Wl Ɏ  ur! ^x 0!OO/0*ޖ5 N,&}s<YzK|6CPP%4M[HsFtS9ψ4жJy|w HD7$P0Ha3ICjo-w~xGQaܰ@x.u [gR|N'yϪac\*gW~k7ސ_]_xl[@AdeY13i#3vxy0jsϔ}ѻpBt` s/k93tPm8oyFDxvy6Śm]j O<@@4<h}*Q-1*?~qi0jG}W=9xSX4n9yD -U;,O ,J0s~G-.ʜ0%M޻U^f_q}Ty'/fՔFo'a MVlzð!*zP-{#9+rHL+Q8r)@AN<>A\DW8쬯=P+?~td$>~T,ߦ z*c\P&= eG>]m"wppppppHD1\Lp*>F.a2 Qϫ OY@NHaآAx2ȗN0Xqn 8'a''ɁVg#D7>S`&PF[՞'(%~`3c` *E{:H`t{)' #oV+ƒJL MƷ oC_D̙"8"0zApTM_N:Z4W`iW^y%Pzf9 `$97λq B,}*W" V/}ːQVAGDYZcV`V&yg4lB>y7yI^Ug)l w"?;9? (8XY5 CvL1AࡁFg KzVS.`އW 6r ORQq]U)UlЕ:%!D"Xmz{.ޫ5Jr4t?} IH3> ?`"w㸗>4< 7[U?TK*0w444444Naf 04j_XңQ8{ I ڴQ.|5wʣ'\q BB& A#Հ! @c7_ђVA'\kPPp[+4uԢKwC=Bp|7a$. J] [mƅh1|,Aǭ~ަ*33Wx"CxU0?2 @#OM D% OiV5Ӏ@&3jdI}P7& ZC, o%o 0ˊ?ܔƜ}mF>-HsH@KZ'A:IZ@ ,`yM䭪ǖj>Q^JndflQT (/0uZzQFI2V)#jH 'QYzVz0 Zgԭz#чĀ3obu@ }J@i y0g.>;.]K |㥗 T)q$wAwT0__jӳ ).hpF#)**}{r&#umR&}h) ? #^`pW21ztkO%䑛o45oceIe ʿWwJk{.q˕⻷JvߛH+z:Du0"&z0B#p;kN8UHD? @b7`uk32y3h&O>W94sɒ-}x*Lq8+وt ?<rpWK3|R6O7ȳjEg6"W5 P`s g ,ʄߦn5D^ {q*H(c>w*(E0i<}xzYGeY  ScE 8*cϴ \{R@}i7lpJK {r+XrO#{m6llH6|plP x 0Ŋ$Xz4=ذpgeÊcj jJ H-0y<~iN`_Sn444445`:L hhI( sEcc̻ԤS0&]ꦼ`}( 1Ax3cM-%|u81yOy&1v#>5=%$;6eI>]: fЅOrRe(<e^&#m]ey|" Jрa7;o(c1pߜLғ+ך\~Ҳ(CpLcˬ45ruE8҇K䑆yrӤՠmbݶZ  ,5|с%/]Px y z6fU`S ##(tO` =@BOJ)U4cUglM1d,٠% +r%Bbp*@C lHG,/(ռ$Ӏ4@@cI=&0(n^|i?-4k&n|4R|oJ;Ec3X ߥ4h3{q{d550Fm|܄$@z*@9'-e8!dzSQX'%buYX#즽4Tf5^j.PZ'J7[* [.h%==3`6 P("e&x>3џѓyIp DW9h 280*j ,S7C|@cP/(U.잰6s: R1\P4ԼQxu6'kշJ])W:9~|ܬ.h襷 &d gҿ_㠲B[:X+s}۟l#pw VK-3~f(ț+ )'\HQ*!\q qK<38LC-IB0Dv8<;0t/Y2Xlσjx5+lמ)Wn{'/ȟ ͛ fZĵϛmL= k}^p]UՁO7 *U mpSJIGot9K!g[sKE'@6-6}6k-C0M3uaiiiiiii`-h h᧗Mn-Ѐ '@m%l}, )÷D +pt)n ld|@m7Mgʜ,Q_d`MPq-S38: _'@;}1aղM18w]>N>2&, z#`qXu]|u凭^R(}#4ܻ!S#:Psl_~b1c'4l(>lSګը@PBPPp>LYmQf,% xx'X~5)PjFG[i(gqH@')cxy WۥxuZB|5ԡwV su`!PC1Ճ gr`#J}X%^ȧ& iA5B<\ #j̆tb ;j9g,ǐPpFlzqi]hஃ $x\jxE ;GvvH8 쮆aYNF;U=q?˗/KLEF}LoF'^7!L<>]_wJWG`ni~|U^ y`w{pWq\ z51g|U>9{.oGL?p-,5qقp?Grp,R[^"cnRY$-O@@CM<\"0 yپӐ8W %zMw)\ӯ:f=Ւ}`d ^ ~)i%qal׈D_ꖞ:qlb˷ TR 1.$(4444444n4 f@mc\&Ì`yx D|_,X,-tO[dUdm%`Y|ĹqxLbtCa@WN-y!0N}QD&qЀD 4%2^;ǚ1ol&(O#=H,:xYs<֖2`tڼ<0$^9՛mrm#ېK ܼϦJxp A.쇹E˚ZS@X { 1< `ǚ!@%ttob ֆN8!om> rst~N_]zOrי:}pЩݘ=Z!R.)`D4[օMs)'@aeA92F,sdx4= Cʂ򐅵VF^g=p(K?5«qIPeHrZ97sEm 4<3FoP@PZ %;DQno"3a{Ѐc >suBo2Ʉ{,*̐PPr!# NJ$gy j87PjƀuŬ#T)Éw<6a?0Kץ7b|yt[<\Jקe` jKd8=Wөe_w4 /PpzQ1\\~|Bc 5vղ>7+^y$a##(M=LicgC lq|5uV_|~(/yY ޽8 *1)pNO兩NUrCS@x`g X%2ԔFw05>gT -ddevK7p6$. m!qyԋ oNK <> O{n9z$ْQ.-3݉vZHu2؋/,P05*XJr9 8 8 ,F(н}tRJX0 W.ްC ɾ {dMrV?uIN74Dsut{AVO}+RVvs)SS[_?zS#\W`30VQR D}?eopjNLF:|(_C}3ݺS-Β͛J"Ý$d%VbƊ#B+8%&*/^牲] hJ~cʜ-pb%K"Xr'랇}E J}eTݢ 8 8 8 8 8 8 ܳ@*L@Nb¿m+` \@ԉ^`@'9YXyUÓ@z4"ǀ}Xwg*?C eCO: K.Xe A~,ʩH|}t\&~2x0^xX!b䡖\ !(x@,k VE3CxW C3 jF!a4󗉿QҚLԤY;9Aùe5{4&;Ay$|&9 1F\C[R(x(xjFVo-1d-]'4li`ׄSY@6(~O w ױ*3HU9kiQ ="֯ ՙ.LI}@ Wům %]@?/ I=00mb~fӳrՁzs6Y< X;\rpppXF ꫯ. P`s"%qM|(f-H9XR7NG||2 ~Xx] ů`eWށ!-goKFUɷ~XnP.8J1Z+/#b~&o|tVCbMу*y+?D$Fh6qLLO PCNLZPyVK.'J&61r@: 04:aa1GK%S=ụ+H aﯤD.iiiiiii`i h`t Y -Ѐq'x.>I44iG @O%EyR kiƆ+\#G^oކvC +b0;gh|I,ݨ! +t$ݣN\|K7(Cxn#OW+] 3PV6Ȣ?]7o&5.Z q0L|gfP0qO ȃ OF sί,T@#3̭͍q&=@Bw|{z~y҃i(6*M&a-giqT1BYpdU^9rlՔM x8Ėd1+AM^©!qdg-Rh2vl_PT&k,/d @jIT1㺘2$} `i``#Aqpޏ|u* G8fJda͵a0-.C2 0J% >xMlCWennP0nF]oW445xRoKt!H WS/Q04`% yvWM:?(/=$epi`ntʩK7P@f%B?xVn{{L%I1iN><-C 0ȳG+!pN)C8Ai.w0@\>4AGOJ7H`Y^ )H;WZ-S!!"7M%+Q'" ~+$I">12:&9ӀӀӀӀӀӀӀ@zP%tn:^jiez1j /j\ d u?3|`GP # 5Nƿ<d< ϱ7uo6Up?!Y,ݵ 1SU׃h 7(:1K/ dvgBqҴb-=ft=wy0%x[oXzn. #B`۳4ǵd5큲TƮZ@zj.ɤkx~iŝܚR&}HϚz.0K8o]nzwwӕƮЧB=%[l)+/0O3>3jTɶ,ace34>Ȩ`7DZBj 0Uư"_9#x@Zy25gFxg,f(XNopoRaW4Gm(of\%d`&<`B#G )eB*|s5a|LX񦠧PMko DZ3w(Hd:_tY@AW^&=zoi`jGS[Bl *Hh=WW`{/tR Vk]Ш~uOqzW'4aYva~ ~I0^V`|)-2Z/ *H B$&5t'H8[5w+=n4wʞ TwØsSnNHk5$b՝@`C:i%@.$McWM!?x"nPB밉,#~qiiiiiiii`4` 4;Sen9{Ĥ>T=õ8S5Ƣ~xC˕LC>VMH>\ ǜ`g[ڎS V iJp~GP0]{`C=Мs!]'2U7^n,$:[PC<~_<0dc,VH>\~9!葞^ !eim&xvU`ii>]q=x}=/1Z^_Wziߗ_}!}%SO3_ޔ4DOOj|0`a]OS5?͓52u*0[`OI qxjM>OSY%e5sϳ V ].Ѱ>Щ!3, R,C5t"O^SPn- L*HBxx]0dԑ)NS')H5^D =<dcj-{z/`Q*3`-`!W ed`3P&k?YgOSM?VNTru5; ,"0̝9sfuA 'u]rw[¥']o$v{}Lbs[\݅j@NtG}s O˿thC{qW9 w\ (,/ܐ%UqP\ތ /hH& 2Sd]jZ 2<lA@JK{oufeehHNk =D`69a^9y"KwдpW֠Dd†44444444h@Q@.J7 4hFz;}49'M?`Dڱj߀`, -$ya5!fVs&Kw T/Xz4F!7_<9(GW1s<9q Yd=&5t-R@]%:OucƭF.lX@>$bF@Pd ēBq.^wI|SN@6Dk#2 cccp=,##C2xȸѡ!xs[k,uZg !R:քx O~"mmm:P paٴwdj8Pz3h`7IWcO51vq@Q5b6-HcƖ+q* 4s_\-?0SH)EpwzFw2F (<}1Lby?Z Hՙ|= XNqN@wYyiOʣ ?~F=U]y -i  t{ jy:AizW^eg> }:|7բؾ -PٱcGy Sַ;8$th5rT'7鮳o`HnyU7i7G$wyNNNNNNNj XqkX6hPLG`O`ݑG=#ZjB'6~c,z%H5.]z4͑5dd@1QvcHH%KOU2w<#c\+k vki3(ouxc=vtO'ɦ<Մ>ƕ 1~zQkVb"= zL % 9.ʕ+W>?hdȎ qM 1jٻ(xW4-U,Pax(+xG٭qߢ2a'$xz_- CVdd#XzBEH}@Y O9[ArryL!XcjA" 9/u_SրH-Qpc^9#ʙ@@V-Loj>54 ̬WYpyj0H܏vEyK5Sje 5h 02a;ic8T! Xw\؟P"Ch~󊢦I3HMMzZZgG;@ls9TիWzfٷo߲^(n݂$(B%˙8`XXkQWv;Y؎F0V Mîo\>!dbpQ4U֧0ɑw`RL`ͬ39aFЭՕ3IW嘆jFmO7[O= O_!wn SH@z=0vQ"Kt|BUIIXUqhlBUo8+ &th~qƶ͛dKuh@<&Mk^(fqBG24 HLcC$I .\k#j+_|\r!O>W~qshtLnTc=ϧ/?qBDTsG4( HL[/uZ>?B絏Fi9v2:ؔ3m\ӀӀӀӀӀ@"ЀcEtK+t7auΤXZ#50p__zȩƥhLҘÐy> |գ7A.ArCȀda$_cqϰːla<ՓFz&qe9{ʵ^x@E,0!*KAPԔGMPkEV03{@IDATN);٘ E>/z@4 ۦ_ca n7r\NFNrgbP , 0}y>?rb C>R*|+*Ys j` +dTxQ5OdIS}ie/񀡣iDC*;tLAHCV 0* $T Yet8'BO<+P(ve%9Yt:vu|n}_zQ*7͚;5@9u?WcSO=젂qIy$++K}exv5]eÙ`s`>5-;h*[j;'-62pw0_nLJ;30tm<ƽqo`M}_&{/w@]S1i?w콓҆#&Pd.uKΝ04k- z!{ ;L}U@( 6A QE)--g]ΪbrIJNOVvW#i2u~߁r:)xT`.2~Tޓ^>tE☦(ݸKjrҏɲoy(ߖPsҊ+ܺ ~oKZ0WI wZbG ͍A8vN--&2zAHAa~|.i؄NK/8ukGwLoR&(џjl V3^=.! <<^2G`xTz>[_{i- xn)zqf KQVXi]7?:4đeX$xSWO_-ߓx@ezP2$r-p1z^[?C;c_ -D5/ ٌt@ۗzઢ[h_1id#N`< AboEN+ 﫩D (4m+H9J>w u={ej`ғz)q~GpiɄu}l)ǾM]f#p|rQe.oݺ:HT2:::Tj 4塺ܰG!OBе*wÇϟq)L`q]|_-T"Dž#GtjQYzd6y&ֹL_bcimC^Nͬ<'yZa>.ه+}y&͇KP ܭ* c5u+uǤl*ac[|uu|{ RE%Y7V^/4ЍJgXmzǥK&+}a]ʽGSӖS NТs'tjLvwo{Lnۤ $jiܩqS׺^G+~N^<NR)|= L (*K% oϠNu1ycjyڳUeyd*id|0„]KW984ҥlaV*1m`]N=@@%Z !Q4pGgj /~12Smi#󸆘Z!`!P `ԜTA`H PW\ s& h⑬a2fwSrdk]3:2!CyZZA7$} ƪCǻ^3x]p|h抳IwԢo~c]v,}Uӭ (id8pUq,!W_andtTBk߾]}`oG_r݇3L;v:n\`b:WbF/-WrlK ׾ 1wo`˰AV,/@BdBBBly:kgn%P!MŲA1 39'Iıl l=$1r'E6 "AξsgבDE5e ~bxVKnKIMJ9=m3z697 `ŠgyQ5J[2w]$ɪK]*ǏCa)/Y&b?$)d!>wڥ<~@sk &c~,7n(O=K'!޽{e߾}3]~QÏi<79+*m֚&X0Y"Ü!zYT|M@8SI=[7SϿ"?*`NIe& 8v(]ʎ,_8cŠiqJ( t["D&ʉ7. +oiZD3׊a ҂I6<3pv!}^eY \B s>%Wonk;7Beڃ~ׂurpg\٤pfxﱃ_`jiJu.߭jjUw&u/?^ΜT~&пw甌~^I$׹ ˛n*mM fp\ 8{J{&c@ ."vvW7َ},%w.#ndF6XE)ĠqM`klDZ`D7z&|v\l=@Kךf1,!`!;bK_mk}" ;& D7̶BP{ 9\i8<.[ٷu,0 LY}•>GBנtg5M.42;8;Q~XMB. C21n@5d*&TV ƣ 1o(@=K D᠃Ve1,2A&0**xLEAB"Q9VY[tb;rQ\xr/1W݃ Y:e ʏ~#tҴy؝MM>7uTP㔎+$ <,sR S90ai&uI ` vb=H6p T3 s v9i%Z GYN'8n:'N9ԕc]8GKzpR:Le}Z_oש+Ϋ`h,ϑIkDEVy# (F>bs\$8LU+Zdmg]L PJd 8D٨5?Dk9эc\.>d* ʣ? ַܟ mV!+TkP__Z^Aeop@3̾oX͎3TD755?,gΜH'я~T]L/}pf;|7ק7 >L C1OesC*dZ\@el+$O> i]YeCl'Ň. ? VӗJK*o$P(y\.q5>۠ ^fO LbKVl^2rP/5ɶM_C/s-`M#rx6U,~?,[\NԗI `,4d\'52nkR& mD=u> ƥkrj AO qm6LoY/_'pGθf@B&XhOhQOd0KM*q`]#LӦw&%Ĝd4 {wg_!w{uտwΞ> lǵ6-9I`/݇UEX.S O\_ւoөeٱ!`!` A Ƽ+JG_Z7H9G)Tx2Pb5ŤLL;\:5Pzl˶²Ho7ޡznR˙!|D͘iuoU=>V h dDDHM#AR5}+ny'(gK\yT%0vfd|9[s np!P=r%' a(Hlq (Ša(pY<ˡ[*UN:*˹ǀqiSgY$N9qs X?l 4qԽA (#89f9 s+$AGAv ]|2]Ibg nm UrXBs`|w:(b߉,x %fp4 /~9]IL\BhMcIrY8V =uC:9t3Z[UZok_/c ;d:< bMӄ+mbE*m ByHA3Y&ILqwŝe[Z֫D@<9Hlqھ׬j^ɾJ[[@ZOKQҐ¤ Ld+@NX5`JҪA'5iy!X M l(Pێn]aC&q "_(JAĉ/^P^z.P5B/^<-g޼( &z(=~C8]674• wY`&oBsi@H}6=xRBw$àkW 'eQPꌒy0r\v9g X#xUČthwhq(YQ&BFŏpgYT; htNXUT3s*sF^-DKp*O3 LFENM;3u/t,h*n9;jr^e!K;wytg"Fm@[ r4^Pk~GWB1/ReN9GK^ *8SʼnH2C5aXvNEOd6Џzx]6  Y`N$ lGjQC T{gJиj"N9z{}1ʮՖ $"m9-!`!`Еd:rͰBUe8L |#)KHj4qN`>o0%`R|nF7Puȿ@IHN1 c$YzFrvtC\ŶZD+u34alL=:iO m*߷!P 7XۊHLwtlyEY tEl9 ۝`'Ŗ 1 `* Q2ɰd,M䅓koQŚ?۷[v 0ݥ? ZX!y Bbn': ٨)x.fZ Plx阉(]!%9/*`&C0UT#ޱq ɎiX,4oPds;sh`"xH%fbZA:7L b:DNeEM]ԅs*ǍHnGu7X/k婰wb>'0>g:)2Oe1! /%8xZۡz,w|'g^bs0#"`?Nങu|[zV6H гn"ܼ\ kelZp† ^yG~y@Ɔxm<,h&(J}W5?LF%;ho ĝwwH#o9+d!f>ZRAU֬VpPn<8,u(T0hMמCJY|lpU?5-8xU()OL0 BqUs.9iwSkw:h,:jZ40٨1l>?:ez .S ̯b4g!9MsJ aM\ZCuF*IxG&q'_giӗ.$0Ɣ|lYQѭD>UIӆ29yx'˃ xز h5VߜPBAwekҌP0;6kjm4 C`I  :$d~+W(` L'?#YLlGú^Ҋ0C0 C0 C0 e-+mmur4R4Z3 W:?*J&IÂyUL⫔ˠT,$d&] fhǶ rdgt_˕tDIw\N`;+ťaphT+p+٩&ΰ) $; :8㧦b;ܧ=!h6=M,UT>;} 5ץcJ4dkVX"OP9$(IwsjekQfT3^gw(}e'C7ft:xϔ:HS1rJf[=,e?ʫ w:‘(4 zQpG.-ב#QTk9L=2iNyd9g,X B}}. h%H%.Ïʣ?(ַalvq]`,Eira57 C`9#@?⭭GQ:VSE=tXER:du3 C0 C0 CX2ai*ȡlmIKKL|L'u2us@!%`S>}g287'FyyuA9?K߫:a,#H b;vr$5 ^ѡ A0;RU Ep(pDov!Ԫ Z3I2DTP0)Å\8)(oSjq\?_י׏!Dum8j MZ/ȌS a\7r2fK,7얙tN~«rE 2tGIGsLb!(w=HK$ lݺU.9vlۼQ݌(ʯox]rTc*wA #@JggΛΞ95GA(8pHwT#V?XWєǑ *yJAy]'4)yuOMNiz@N41h_LrR֋tœ*zydjH-W |2JgwP]{コH F(xk 쥷@ o1 q~{c!`~R!` E`c}DnHʾ1i I}$/19G,C$(QIeMW Yǣ{ LӴ~8keVNsA(S9uR}Wڰ ūU%Pz6ӟSot5 !$a tK8b 4v^ ^; x5PR<"ՕZp2E {M^;s^F*twf}{7K[cZc1);V1k-w55 ;}{G}=>SW3cq o3>c8S]/v[ʝJ`*q:ӻ K$C40~2@f~ϊ[jfcQG>#)S3F+gŔ0_!3 gyAN<۝vQC3kũ2>w:;WxQkفyPS>kN4wUsc>T45m7O3!#UC&xExqؒDEE63J:h$)yx hL/S=9 pvi5CiZWۊdP9Es;JPDdD^L}Xs%q(-;i4ډrcVMzVJf/II`"Ui9Z`dz$'g!I/Wj ӯ\ta0o^/儩20xl{ll!`\gX嶷= *0SKǤ y}瘋`!`dH4uiOe ^~GG0NJؐFBXhT>7l@rLSQb E$\!DUYFXUX5;X:Kg e 3(o{GGµtg@4E@0 . :?0R$\lSu)bG\&$">@4$2f빚uU)k^Z~V PG_(&0x ī०k c,-rl| H6U Sޱ^.Gv6#i8$79oܬ@Ǻ5K]:I$FIci)U*Ve\>uYqle6 XQ9s8nUV/oLۯsM/T-S{1/S{^ Щq^A"Բ\Z{s .\ˋ/Jt`^e^N*W/0qdamuÊNW;5 jރ!nKиr~ 9|h_tK?K͉'*#I~0RA9YC0? I +燗~VْgG<\lk!`,%$ hʡ-u5&MNz,tT `c4_a6{}P!|soe/>lmMY /H3We x\EAT PJOG` .uuu LOHt&C# T:{J<({qn Qo dgNqUm-}cY}AB ZRiQwh-#c$  bU R)u[~ipfP$$[Sx+^xU>#ن٥FRB뙇,@P}F((4K9 YC0 Ux./^= 5t42Jn2 C`!$_ +Ӣ4 +5V_C0 C`m"e{\ƨ4=mbZr: (P =;׫{h4Z48{OtUX6ùjY5fsXOpVULF R;>!nP_B43 $ RŒ$%z<` $|{7K[cкJZ<<*Cp=KXw4MrC6 i F7I0Q/!/$!?/ KI_|ߖ.f(2B`@ ɨ?$pHGs#Rmur3L{l4-c*X2PZ.\.7Ȃ_\eMk.u0,Z%a~> @A]%Qi@WF%bK؟/>` Q(*oZ#---fWp)ܾƶF!˂|XΜ࢜SF+$$caC[>e|[&ƒ0%ƘƒBS/ )+ o7B}DrBGhaڂ!`#ߵVu'E;,NJEF49V&gXVG)if!OG,NhfrTY;a鰔H0)Ko? skgZ?ي,ޱ^.GvmdTv}>ޛ!0B*סyםT6/bd߻ +ê|Oi|WJgԗ~5|0X~*]ض-ŲdܻIn;LdXf<2_1D.{PF1w_ء!`!`,#ZSa9)!m>"Mxۊ*B7ED!W(f Q!X-:+id,?I\;_$t@ؒɡu7J .B?.WQuP $BƸV j04&=}P ʆ@I`K8ƺԧ (5~8 tG Օӯs P‰3ڹwd."8PIxv9Q'2W#acɤ1qt"cf1]zEوڍ!`K@Qj~%b*N3\뾺@BP@^#lM4 C0 C0 C0 C ؞CIDXbY(a"fX!&*4.@Px]( 9(VӐm!g2V"VQI5%eۆ9UcPVz=qX5PC'uB}@ ^%hƂZ4H%bUBL p>7]>N=ًrRVH(Ň߾K4(uژqʺB>RvwMB,E[o<̫/KZ ~I+ &o0 .\{+i0 C0 C0 C0 CXhU nHJ+,V>Y5Rnv JE&jgK%&H.bPW򲿣EFd V ҒGXHv Kw\*Opk X["7m( W>d" @86(o:Ao߀dي,jo{wm{6JX' #Zj抲]WrD]KK0޸[Ho1!`,$7@W3i .6k y*d0%-I%*Mѯvcr$4p0K}i9wm\x\2QjğQu&@K\(%ɢ>ipeJ5^4G65K,JKw<6ߦ@IDAT"B]W1b܂9KaA#ڵ <}PP T̩`*"vl!`!`!`!` .,7mNʁ PX%`d~(a^@|uW@HV!Ӳ8L$w47}KBq! cIoLeVW1\a/y9 q9Y f.. +}gH@XU:ՁÝEGw5u,IJ%.=p!T|~VP,f`ڕc{冀!`!`kY,E?O}1y#ƆﵳhCC`Y[>hN:%ވ۶mÇWK%ĉomrӧO2P5MbX|dydp?;sL fD,GW_ؽyqmײlׂ8cBg!#ɘ: Ijb)ϟ]pfB-!`k_n;!&HPiX  I ?VaUx`0 `[_. `QtPV y~2-MaU~GWQ}nzٿAI9X5BUӹ=]roD޺:$KC4߱N>xklIBo ?8.X3# w1Vc勰ZX[nZ-%Hwf$a%L%'b1Y~MdRa5TP~U__? 3ѳۓooZ@1BG`ő hR_\w_IK%瞓'xBN*d2b2 °*'ê|| /vKS"(_xpۼF`pJ\~Y0xsTϛ ʕ5}T|D5v-o7g"W^؉OgSywx^Qb2{Sko)T&Kʖ1 C0V $Yjatߐ +M̍K!ReU$)> 4x.pPȎIaQ×7H"@}~;9Pʵ0_N KMIBAMrwL^+{F_\E;nA:ZQ'&`% w=JGR2bC0V $2u&9riK*  av9ovKy]˶!`4PҮ#6LFyVTªXJC0 C`N] y4\{ ։Dg!`bXGXnژan&]a|߼ +qWDŽ6-p:T~=zTEB47WݰT2n;R@(Uê<ʰZ^X_Ki!`8\|Ca<+^la`S{TO4MϞOJ=5'0OP rw*\37ڟVz F@AHbj쇋F `5/]RMo܊p?`8$!6Tiܭa͂5\v[cCS`]UV LuUؘDf%< K!XTp0s3a5 Й|n)Uw=d] K#rvz8&taAZʕy| E-$hC,J^NP*T lݸq܂!`(`2fxb|Y3j%/HrZ #ee7v=LFy*V|{0 8L:1N>Q~Xe.,W;o!`\oyS}Pnˍ~imH`t\aLq|y_ ]=ͶW "!o!-29MEd(&WA?qm,񸋱3Y:{b;l0V&O><`e^UU ZZZիʨW^yHg C_On>`d13AW^JS3 (?rW$ĒuھMm0=F((ZJC0 @<엽rzd/0L<ٌ;_$.L@wnDJVTaƽ0Xo ԯ$ "z'*RA>3"R0+?l,9Vj8umJs\Ѹ CX? /d3 +ⲮJM*# >}f]/){oE-!`C9Cu ,Xt S_[-&5Bsh>(Raߊ'$L4mdZ,T+v"otqQI)DL&#aUXe#Sc"p Yq;ɹ\J*ۘr]TϧCaZ/=sy\KmKdк8ts=B[\f菎l-$4[{XGUԹD7L+`[ &7ICvX|??<ּ*0 -P09ВY* >k.fǢ@ٳ8$l߾bT/ʛo);v쨸h!0&m vdbb̔]mDgY&&k,{ɟ4]& 0y_$ᔨs݉abF$?UO("VV͓4H6)@ G@/a@cfz fFg_W; Jd$} Rm8))Gw6IGsLhzHBQ 4mFba*i` a{+ĊJ+/AspOGAk1A0_acI-'= HΉ!`H(x`\VŲITuViiip^zIo>ʰL!`!`!`KS'Ίqٴi4`C]}OP*yը,-*zBދmE2Xn66|rhKK+'񳆹YWV%Q"v*3'F&V;_MIdL28;4 C`!p2x j_˃r>e ~T5J&@F!`!` /on}<-v5B<8! RA("꽽կ~U] xcib|_Vkk AFWWBܬn$f0iXVtR{]7?ˎ[oTf5,VOBw%gV՛&xI1慨*B1a5/D\c?(G%p[Astg,WΝ;? !P9X'QV.Ҁҹ"¼=/_]S L~^I1|E2IȅXl6l ;^"!|h6MhG?]/@6Cd[Uw[-P&>A^l>ӻ k&ð~eGqacIm ?}}ał!` FXTe˿yVH\M } '* +HW%3i Uq$ IiۡXiGb_3` EA؈$foz aH$t*|9~LP2Y2C0 C0Gk$U38p@ԑ[dsSLb!7l5* fJYYhو 1:VȰ xؑ!`C -T[nNill\tS [[`*+zvE}=U95 ރAGm)8&9yU2NiV޴6rϪm,)($Y?zJ-k3d]pb*ʰj>pˑ#G4]%{ڡ2'Uv[[>|X6n,]j C`"rYmAH0 ~2VׯTCX. "G 돸YLc>ddFDH dwɁ4'ðJV!`,9+"cx``DV6<8pH[ъ>oO=pͣH검Ϗ*7A玆rx4I V ,!0B &͜@}˿KbW;wNC6.w}wUWHwb5IvX $Ժ&aXVK"ٳE޼%c3'ߗA*zZNGnPR`E۵MFܰ2;τP9n`n:!j~+T;B1|Mַ%<^~Q=V:ZB ylZ t[Ĵ] kgr{pi Y(H T긊/Jȡ*[8IE$ $ J8e%TS;0 C%|<f H($?^WkrdU),(rVUrx4,޺G'/X:#?r6x|5a.7Be^ % 9-%5h/V`0 C`#<t.)Y^Z(.,/@+3S;f&c*̲װZXPP,}TЄ>)9qℼ[U'駟ӧO~y衇jb*B CX`@,w-?{5/=i~7XrflTοv^yxT>  !`!`,t:-/|oJV1/)JC'$CCX pB|uP]]0\Ry .Gω& 4&}j͏"=0IE *A0J7B~c9 Ax0 e?Rhѭ] 8!7إ x3F´B ]1[Vt aDYYlJ ?4x^Bd3n&l&h}v ܆!P6F((*KTL*`hr| _iLU1/_zJ>˽+4j0 Մ@s}B>ȫo_N ޹%]in,T~Gt]xKI2Owv`jb!`K-ŝ:uJh෿~1!{{ C:? %+ij:3)@gU{P}^3SI.L RBS (dPnޒ[DӽJŌ8_C0VH%Ϗ(7z/>Y|ay} 9Ir 0iC> x| Â!`+#ī}VJAطo|s$Wɜ={Vd׃>RF.rY&CX`a*$3:"HsX郏?`2,,&GYp|bU2 C0 Z!o6ZdZc\\x"]<Bk CL(i Tx fCH<0[z&YF(]q9^'1,F$?=IFQ!`,[fys}~x{m,yNO&v̒"s͕1Kѥm0 刀 U:-Z 䮻OFqrkppPk_<j)뿆!*p.o_g߾YMmz66]лn$Y0 C0 C`nFGG_rI űc͛7cKg[CX8\Zp%&\f]78,́vV.=$1? agyd \Yٲ @Nu*FP0 C`Y#a Vp: ~\G VqpyjV+8e$C[j]ApoX1n^c+# hR1b~P٬mՉQJ.7!w;}c&`E ʡ6˗y ^^}[%LV{s?K{ \'Ȅ/p!`!`^-?7P6۪mGO}S{n l)6oTB. Fɂ N'NU`eh!=*y{ D\Bw+̎LYQלfA9 C071d5?9.xc.x@(wG`lķtMeȶ!`,sH(}VPkn[ TT@ɤ='hQ _-?Od޽BrDȶ" C% nmJ =]`84A a|FBJ&(^elC0 CX4p9.oBKenSBтEﵔjekrY?h<59%/jۮ!`2Eσۂ2<%ټ9, d,SxZ!`"G_PP /F*`{R|Cx<.-cc4W1~_~;iii;Snٶm[ł`H:uJ^{5/Pu˛)#e8qBW qmrVK$175Jј{RB'IRʘϚxuD^z%yk.ʲs###2CgX͏°kX͏b5asIWW4~߾}5*[}EˠK^xAt:-u}TKrF(5VZF=}xriX` iɌ9[ir&}4 ~$kAqs4_ft)d85!`_4<h} XaP )B0DZ"(yRZE%WXUCF  g3bk0 匀C(x"Y`ʁ ^inn|+:1VeӊW5]"pR+eH6XH_|QqU BI'xBOILdy 2B2ʰj>gǪxG>RuRАɘL:cXMcjNx&4&1j?lj :;;R$o_BW^Urw]^D"~a\c@A>N@q% A _0"(Q|jbHCJQh?j %Q}dDC0 j#O4LI qIi$ @ H5 JENa| p@e?Ny*3 d!B !`,cP/UmU'X,/+~Z5'*9pqr+4GED(WpuO-˥  LVsSzְ*Ec}jn|JVh̽O+=ᰒpN|֙p3É +Ê_:ZBd[o6/]@V Jƍ塇{G uf0j@0e%L ΎowB%KH a_A(ue\7632#TFQPA]oa3$!a  ϩ~;o׽}vKSuN=uNy}c3|gv(0-4-LЭx¢IhCchҁ -#YFIUXBSTCD`ˉ&Qw 0(.cٳS]pv9 qf:VZ+V  "Dnt )Pr@2*Vq"XϕF"!B͘1ѨwQ &$OE G6pCjwR񽕁뮻.yU !}:ፀo74m]FȡE1 㮚{%0w͈O\Mwe5?S|6#)^ M5 mt=e$бfu!p8C /EX`b(ձu! p96 '.2x6h)x)TP lfuUFERxF~|})γ"vn<*ӎuQw6Fƨ0EkʨXQM ،Ű³#K#SUSE ŪwFc${ <>hdܴe^~wp 7h#{`'pBZ4GXpuY`;Ӈ#T"[>>" FTnHiǘA\LJ?_f̔Tdh&Q2@G 5WW>au\(anww_wy)u ۫uo짭!xBmX%Wm͖[VЇ}aҤIibm@PL(AT ~go}Z_`eL>Yf6a./6U^2 fR+@9}BV-'*>fbU'rXO#wdzLt6R˗'-bw}=Si<M=sO;SmNuVE``I. N 5%HaPQ@45 ƹVڈho)Nh E"10LhQFj: 2D@D@D@L`DQS`9sf{=lWm aбw饗&o;C{agN.*XD@D@D@D@De `ٲev]wx#"L E`׾֎= onN0wEvm'ۘך&!xe{~EM梃bieaHQC"0IYF`P " " MM`/Nnްs̆[Zۄ)mFtKBTn )O:9~˜i[6 ꅇvO-6- : KҒ@ ;6y8#wv'מ}dt;hiӦٜ9s nB:TID@D@D@D@D@* m}g-J! ?%Ay#N|XB;P iYBAE@Fi2ڳiY]i(*L " " MK t# p!́OB#d4 2u\D@D@DOET5dV[mbr~veم^<DfOے%KK 뮻i]v3g:ۚᮩ" " " " "0BDp'x {7x+h4qD7o^ u0w$& @@&F"+a0$D XHCQF@H~u>'6ɘ)RKMgBwڼMj˅qQMkLJu,) 挂az[bGu]yv'}iy(NJ}dO :# Dp O/bIDL! ԩSm}>:y& leMYᆞ`5pmL1 4bS<%uOۺK"1C'oM5NP*D@D@.-~F[y[Cr.C;" ΋) }3}d3{A)dK{[fI^^Mxy -( @S B\pi_zkK.1F4ksp͚5ix2@dp5פ0qٳmM:6E^" " " " "  ^.ě]D7}t;HM7k֬$ h.MJ y˝#B;<ࡠ1$qz!AnjED@D) $ZfOBuc<C{ScCrz=i@JTqKnC_:"_3ƍ6, vmB)4N" " " " "0Z nj*[h=I@zGLR9su8 0aBEu" " " " " " " " CC)Eqtl1tA{pF %K"[_ycSON., &Ol3fH j@Ȅ-TuƜ<P#/F&Q?M+ V :=wuWz}vF;ʼ]W+`hx?=OPxo/w|W!&;wm)T"%!>qw-ڱE[ExE0NOOڈa=m kyUIZcCQF*T451o#N'w̆[Xڥ6C[3ew9^|.XM=~^1lװ2֭z*y!B}6 n|@ZT1vd`p SO;.r馛BDf3)bŊ$ZdiӦC f(YfZtiu0FDqNQ/HD.+=0Ps8JeT_G*T_l*U%C͊n~ƌ#&޻[9!T{CI^ fo6uUG1 4c H:` s}B:ßqQ ^WT/" "\V"d;Sh\qqAAg\vQ -#B/xFuWVD@D@Dl|^g2o<[re pW0 :j(:-$!4xKB:ƏEM:=Z DPI<#m֕ ggun8V躨΅-"}/|6{lK*ӺThN3n_zdk!~ŭ{Qu X0ڈGomvɛL{ӡCBE *;#Vb꿁z4:+4iRzmdR+]J心V 4\BD|ٲe]֮]rHϭ3 BNprJГT'p$Y`~3jOQ}/z\ g?k^z:^xG{.'y5iQ|tf4>bÏ3K !Hx)OS Z2 f&3ORjOl! vzc[xVxyooM"Njo^uk ŽWZ Ysڬ񪟇p Y׎XNsyߨWm+cO81З2'b2P2~W@@hyQAΐt"0OGr^XbN3q nk[mU7o՘.^k$ʨFvɤFvɤFvɤFvɤږĊ~Pɚo{キ͛7va#ꈇ%$$uMiޝnY0{玗sWy&nFG?ōB9\W;u/z~?^&xX0eB_𐤮3wꣀ1WwșmyLMٳu D`hiVWW-74y=g$2KMcj߳s<_XoaB xؾ ڴ 8gR,P]%~p'ONS8瞳+ nk5?n$.W]tib+:&j)lL ʝHD@D@D@D`]6| SO&^X~ga Fx 'N;d쳏瞆xU9--"yљ 2k_*T{87ꤽɠB$dx 뎗Vsux|qCQk#jf^jB aJ ]VL1lS$"l?xJ~chGRӯ2],ʩҵ," " "PQ'*B'$=SlvڢER|pTX NtFQgx DT|'7)(" " " "Ђ\2  *{ !jZrxGk.ۮE4Լ_+IE 02c-#1_u7ӝa'mD|r)] ߚ}`0yǎqO uu(#Z~dow!%1W"!$nJuݎIV _asHm榰ޯ{C}.hauӶCL{1Bxswx("41k@0l:+ r/o?{g{' d#'n!t*/^8 t1:|cbuJznlٲ9<?ݐw:@D9snDOAZzȂuC+.p89Cτnd=4Ў5/:Lg  $`"e.釢P.[ܸ"&EXpE8B#TKZxE<"x{.3N:ScvV;ZE@D%j9 ֗/ t7A;b(oQ|;ʅok E=4R?M 8 FJ"0$*qc"ڡD|Edpw JA;#kzy!4`Vcyڴi)Rybj)@;⁘K` ;.YDx!`  ȣWD@nuwf9qUf O((4mjBӾ K;omvJ{UKԴEO 3mHG1Y{CJ1A.dtFh8UV%Q9p~D DuR7BDvXࡇJ!.%Rwt>({5!8kA,3O ~$:t7p~{:2eIVVnS+j[}D B &x؄idm;UFPŪHYjdz #B9֮]D!,+OwUq!" ٳz3;B"y*@ul_^-\Ng.mKɕ4 q9.pau?:T/kj!CQF+Ԟ!_jWp|dǁ9wg,Qm_)>XU\D`@&LMF :BR`酀<x(q<eoo0 ۥ6o&@2Ӑ2z62mu/#P]%hZHhPG| XDp@c}ݗ <-YŮrJ;⍛]OL>\L7\T2՞Ū'j[Īۇ5\c{Ѝܕ'l<k*-"VeTʷU9ŠM!@,rL4+M:5 ]0͜93 ]C@XGh뮤xljF&cMG?KM7onDi[j C)cE{ 'm 0CPFQ.kV^w4{{̎=5law9~sz!v\ԛ 'ց" 'o~"ԑڛNQ"7')‚mIb1<~?}Ɲg+cjo[}!vJ" " " ' QvC&AO YLϸqAL?p :z%P+[F!ܛI&^#L(CkxAHWeVD.j.V F'*>bU'rUsB;y$@8;cYb*vJ!@}`G70P<ëJ" "0Db(Awlx.>SH"7Ey=ܜ~;?z3}f\x(m1wcv1l~ /8捪yGLj@Ȍ*Iio<#NegkhQ2*ʬT-D}֗t.2x ٸ?CS0 |\fA/uoռt@G`裏v =su]݀ B,3xڴi}D9tBiTFDJoxa3]gNDP*>XbU'rб})52G(L^&Q1R ~ȻĬYOl!<wH%jpmc[ǽ?R鎴F}gJ ltSrƒAF;CECMҥfc{V?4;@󏟞yd]"~Gf2w_8=#б= p8C /EX_-c-![椶$G7!#,B#Hh7KA:Q0oS pIhV*p0!4 ەx6/ D#.@lXF|6&t>GBH#ƈ2'*Vb5<_~Q#S(WSE X^r@`X6mJOw;fx 3gpZᅌyD`4XpuY`;G%5rm/s3gWZmfzM Mj;Hs7cL .&x~(4ftk wnx)՟sFa?9$-2{׻n) GNtn3f[ٌgHI:=}f{/f?SmaV Jx{F\|Mvc\k'U^x!:mEVF_Au]Dz+Bw==Kl}@'NkZf*׽ykB6SwDo[Z/Yۜy(կw<'1L,}|?-}/ԏu("@x; 3ah-ܰa=GDx2XxqQtj%,u>t/r؇`C A2sFMT3o)= |eǫ2*ĪK֑ŠPSvsBeGQD.*g9\~g&޿bB(> mxP a3gLs㘔D@Di `Ff.$>R 6~ IH~ki(*L MIzo!%aƸ]qT6󎱲E~C+쾟.1x?K}l7w eq>+C@(v3{͎<0R7\Xĕy._܅Uaw70oBQq +xCHB s{)l 'a*~[o/!ޗe^W6rhog?uȄ7R} WCQFeZǽo,ߗyb[ӀR<b̟ϟzK\6p|Rk^dh!B |\>IH=7)0mN_ ,-[]p]{ 3w}w C[~r<Il Gvء~XrH6HVMv5r?0Q6~y~G{]w3gUG@&PQ@7-:k0c`vwd 20N|KfCal\Eo}XL~;s1p6ٙ_yHZc/~aaF؇7JQF4R2xgLpoƌ:fOfwY+wKWJw6|W_mvE7QRJykW*~7އD ,ڿmyqbEG  ƹVڈhoVS4ڢNAۘqthR푼Imf0|y2exڴ2@Vd"7}_Nv'>Da\m~(O;c>x\|QVk]|??ɻu~7^}{=2сuʉm\|ŮW2$Aes\#]|7g~wS epMGq=I#~wb\ˣl=A/}p>{n4ǁg_)e/Ry|{,c7$.ms cPf;SK#q'lKuzkV F\h@c7SO=p#4~0X睉w'|^)`?۔|# @<0cƌR}3A̜|s_j'"  lchol&xe{~EM梃bieIq(!yRC#`42%mjUTߕܺajtz}+;{͚2P6v 箻-?f>$$<$sO?)!fX!2uD >(_F{xя̾e}Gqo= aN$[oJwÙ Z|/$`}gX` wATA~z##o>g0.z#^? 6܇2C.ޛDfjJA}Lnii?AZ7 -GD-S4 nD^{l4Vxoa/yh395洱zi s6WB4 ĸC[C&" C@_7ZPcs@dh^ ݠsiP԰2>3&%ݏsa0tU ?ټy]wPe!@1J+3➄QxPK}qP9wWqͧ}r=UYagN5W @ZT(aSuTIYazE mNpjS[~ÝmzI)v-%t@Rv@ /g<}%*&,X1ߨ<F淽mc?}).t ԫ/!s1uKڹʶ1 Bxa-x3{bzYylcĶ#pBo `7Fa;^ҰC{\#GEjՀZ">4 .@hRǰ=b!3RBʄ -:ĄQ"rB6W}x# ?/|X ( qIX9eF{ż@.17FAW&"PBL#!yGRJa_~•/b碓=h;Qlڿo*})6i<лg]p 7݂X}_IK G $"Pq;| fJ脱CG%~%l[Aޚ9CȅC=vidk#\tEI Pi{|vK{iZAP?}a Z(׾f-v?>Y![97`{#Ü9sJCB^{g[@3o.S "5Šl9' 1Lv-TV.M+S؀ Ayh,swq<Иhx`Ԛ H B8}љ_ R.4`9_9¶8okVE@Du c&㈉!`eG#u3}9m< 9l!eRV~hpĵ =nso5:o4+!4$kY-a+uPkVLNw\)&wFWJO^7dvr},2RFկ-VwwJo&4,q&%apOHM(称[k76~ѐ8ow\]+"Ӊ;MP 'h't7ӓ} ;@P|Л3aHؑ~&a̙370I ꤿ<𜈽 yp%"> w{$oo|cmD&.y|F`DB ~S'! !$)_{(S1AOstɨ$P&6xF1r0GpPPGEӠg=GcD<Xgy؎(/=!4l _6Hr>% `Es,3g@,qy2ϳ>p=m1LJ"@W#˛{g=whwx_A& pi;Ez0cp#v޹,tAG}=CGNTfzE'~:GpU<&[ 7lJPnf"BD47!z[̪y7c|yIq=-x}{wLGpfmd|cwQ;}w?J2d炵lH$m!'Hd?{<i#~򓟴~nCԟKσ0ӟth3pe i{{u[so14oy><c7o3+Eؐ0_Ub #q>ؾF={l^agCϽ2{R@__Ow)$ol.x B%V\$*w@WWc]hh0eM*F0,O_ P%%:sc/?!F@xb|2*Dî$" %3!|϶0m7sޖA6Q  0.>XVhr&wcp6f{e_3 FiF%H=ʼnh>rO12߿%w7 G;(oyKQa aM;x&{PslPqQEܳj Z*ܥSQV*OG?Z#2@>sÕ?4~4"7%08W΄mE =ly"UMUJ0~GlwO}˕ɷ11 #?=\4Fue⻜Ё! Nhǧ DM Zm>)WM$A?UW]eozӛV `؞>c3(K8;C {'$CŀC[΄jϺܰ!ų1\IMv"!Sv0Xf'" " "00<6{ F#㒱;1l`A1Z(;:Nj~jbR_}8h>!OR3aXǸ(ŋͭ=Κ^zQ=F< ̜Y!ãIzR>ʍJᇛxc!ýu6o^ ˕(sTౢRy0y@$" #{EP͎¥|{!=#ʣ?(pv΃k}OĠ?sn.O9'Fz})P)CW_<2^ xV>R6xFH`(+=5I\3rHT0reS\ Gh 4<Ș0Pbᡊ  +BU!ƶ0\po˗Ϗp|&yZe-&^`emrlc;|;?!Dž1@Q/9w;R+\ V+z2s^1'_LXzFCm+oWa03%S|{\{=耈9˸9Җf0s*WD@D@CڻU&g̓r\_C\;?Sl] a@}giL.)jZ8v|c'Bo 딥x=KV9: 2(KxdD. .JqP~ge^{ GQ_D@D gބqiz'/.γ?3&-qta9?׭ޚ+E~6 Dn;S?wq{zQ]=شrkNP$wǡ3ҝ#A% QUӨĕF05C|y#5b@R0V)Pf.Zc9 N%b^<_ yr+Lk#sMVɧ89QNl9b'zeEG~֙87sRl\Qʾ0R8Oq9M<_YWy> q?I>>("6f.&Oy9/'X:yO9<:3 <1휃zUk׾#OG}#uֻ-N1J"xiID6F>Y%xNβ('a&]yKPx7'$OqFcdDY-Ͻ4 SyMn߻`AwAAYDߺUp/:zOo8s}m{Q Z‡jvh8vm]oY퍻'yW-킰OB_ň{}HA;z~L3/s? aAI~6w}#2&=_͜93/$>ᚨ~5}%F :0cƌn.= iJ"jEQ-<˷zmae /<{cl?zL2oq|2]E<@*C^_7y|9gH[K8&/cv+#X w)?._f;6O%GC?}51+պYޮ o .omH|߶K.Ď;8;ӀFs\{;ꮌƨVDÅ.L42a` sќCID@D@D@CKF !`v:B08t+?RS#" " " }"+\r_pEN|`/{N ߚaGT(AxQm} Зc! 1Ax D0u.,8/xcqq,IG_q7nqp /kJ" " 冾afF?E-;KE{ҧQ)n ?l1=W_SM1Xo}k hѢ. Ý.D\s98;裓l ~Em4ϽPMqW Bl2J)Dq#cw>cS" " " "Н$5#y嗛}3ލzby65Q" " " "rptfV\p_-aA-ࡇ>? ZMHsф>ti|ty"dzb]l[A^| xe{MZ@ [*W[f2eJQ}0} ]I~~)z_Ω'V[>kG+z8ٵ~ LW]uLarDIr4,ue<#B,W#FU|Bt,3S򜲖E@D@D~|Nu 06mZ8uëa̜?㝀9۔D@D@D@D`@Xft!7p_-!*[`'P'D ]` 80`(Soy0{k^)ླK9PN3j ;/_"l e^ "c?C@g[HsFدk;zxt{"{oo=vvD#zl L-2 uTcx<,9D#uX@j%]pѦWF=`(DEh"TKBsadafwmHT;`l%3ȷ}LCID@D@lcBrXηu |e"+D@D@D@D@FyvيyCr_1[.x u~Ee6sfƁmY@€^B8Dw{x0 APCz};fX,)rH!̈r5&Oqi/5f&gF~ӕp <傳2Q!ɔFn讬Gql;찃p v饗څ^GqV5\c{{c9&4$*^W#Io"JyEZ 4Au&Qx%@' 8e# -GJa@tvc[هȥ+he|ö΍xzZ7uk|K>u#hIЧ*[܃64 x _#$B}9uuN!8,!(G{իw.A9rM7f?Ӡ͇  l}1qPQĘ|f^[=>ٟmMR'p^~_0N?hL=m`t*q݌DVjʕ=DlB5[Fe`c 'eg/̙cgq-#lTgGqDe%*?]!n^ ZO A!b|c!6 B mYg? D !baAļ%p~ƜtV~mP]lLݴ􊋳22S BXZI1+ ^u1R u@<=y<7 e&#ub1Ufȶx1* K pƮT2W&ńЃmI=P~4!#>!wga"[k- e6cs6EY"G3 7ڰ?K֗7أ{aD1նwڸ&~tAi?HÃ>Kb]M@}u"P J{ 2_,3|;#q"<0˱lmSBg8wy!!ȷ/yC$@GHL7:Fv~+T'@{S|wͅeonB۝ 㢭?h2= .sQ%KlѢES;$RKpi=L?|079};:bXFtr>?z>gD@t0E' t Ĝ|~ 71e!Gʟ}eۙq}xtmunm7ў>;b|S7ǹsƶVWvVZG|}A}_4[֟?l] y߰52sf:^-n*ϱ/f{[ aB-Gq@{߶|ǎmu.elyosΡ$H`z|s/w1S3.o+.}yg}ٶA|opϻo_{؟#]%H /!]4Y{sD.ųބMU_?(k|($*@D`DKc[&0e1)_g|ݼ Cuj)51}!=l!vzϷG(z&TKtf;T= j2%}3f_Z1*K0~2 ~e0 `B,GڹPp/ܨ s` S/vءP=wQdB9k/|Ƚ P1m yL7 N#FGmwq7và޴7>lہG}[2}lOxZ#86H>l[`7Yf[<ҙgi>`xI߄|D}.ňvZupZBD"7IT0VDàRpF|;r7x|9?U B*眏('֙Y|Ƕȁcg[+׹ĤV@IDAT8w;QVh6G7b<dž܀u|}ۇzNȀ޼wa ký? ZP\J(Di 0ʘ6i4蟈kvy mcN[\r4z{yB\s5)< +3S.{ޟWD:uj]>ń~_A): );7Ϝ92|{KB8G+A؇<.QL3.CIlx 6ӟXpLs_gq߹<F sJ" ;(%/!1/ͤ" " " " " uj%"`a$?ynt;=ȍQyon=XyƜviI^6pS#C=mvvy%e{oE{2p<v8DFO-׆z߁sAe. Nq]̹Nf /3<qBxxȲu[ 1I'd3gLܚP}<¹@0Jމo_b8D)Q,@`[o],W;97ӊ 8 'z:o,6$Oo.bk,1Ї x2L03霮7+{FQ|7FV n ae0Ve&ۘm/CXM5Yl2H0fc8Ƹߗ{_v҈ < \?m"V`bo1dcGP}sۅUf?C{yo @7HCc@_)/#8“S+݇Oʠԁ:rCG:93؋Zʰ 7s$(;/epX>3=\ q}1V8j& QȾ:DuW\XBN X>5Үc:ecf95Lx)@Hipk5SS1Q9 ԽZ^RR Ǒ̾BPy*B\B^B[4}恔 Fim-p=EnBLy W9o:u`-&y'zapgq>ia@),C"o_q#}C%~nyG{9s%@^{;CFR޹I`Ri\\6N,0 4aQ2=C?8?[o-Ķ|N BPNoHD@D@D@D@D@D@D@D@D@D@D@D@D@D@D``H]44'"'<ߊ+g}֮]kǏ'[om3gt}ctNeh  Qg5kإ^j?^4q*l͝;>Oۄ Q BONJ 3ͳ7߼@U/ KN5 WUV?olM2&Ol[%q`0nܸV~]X|m{U3f̰vءZmH+b/BT׺;38Ö.] bڪmַ>8YE@D@D@D@D@D@D@D@D@D@DÆ^/F+{+W&5 L:bd3 70wuWc~ Ù0}$c*bq G1}t3g͝;7gy睶x$`T8V>z>G4qWXWoO "1&O6{o;lv2\7l_~yDp1J:0=iE]d{0|'C|oR/9wv~L\z5ρC^J=dɒį;h!sXΛ"&<& Z … m} 1߸x{UDAJGv AZY<?#E@D@D@D@D@D@D@D@D@D`9B.TѺ E2j`0z c2nk}N`e8/Ϫ;ϦNjͳSN9% k6h_i[D@D@D@D@D@D@D@D@D@D@F FQWi+dLd?xGCG#uYge]w]s`A`I'do{۪6 U3t@0 W]uU"IQɵuHA*~ ;Ŀ*5*)iԈ 5`QDh;mDbE44(6 %dJ>~>s9ƹݬ\^k~sũ*UU O-d >ta5 ٪@~wLCNnK@TP38rt\.O^JfĈ믿)QY*5]~*MdR2G*}b캍G} DZxЃ'7[Ql4zc2HT%sm"s1閷eznl c;d/c`IO&7I-=,o}+}mEȊޡxVûx@R*UUUUUUUUUUUUUUUUUUUU3 TRAm kկ:Ǜ%?'zꩣKك3l\Jj"|\kR@Gݖ"Nn67M'K7S2EHw{tIuKͬm\uU N_aMdR20_f+?S bwϥGwtj}v@@@@@@@@@@@@@@@@@@Zi*753x^s5o}kbLBAV>v:s,#-o@U r=SĂu^$C9$]g&bk^t׮sVkު+ ~W^eaov?8./r:*󲑴D10ȄiycO5P#ܨk5omy+?o-_q@yOpy_%]j@6h1OB@uᗧy9O~6z-bu]xBgNt޶}^Bg1y' K-"!_ҁx;.D__J{ү6 xۿ[sVU~c9})׭WjnV-}NMj`T?x] }t/p P|D __4D =u^ziHJ  / <`AԔ~yU@sק˿l}{W}L=|+_)>=oK.wK:vIOچ ڝ}{grto6H/RUU~ 'q~=ξ??D:s/N{H3u{uD3v#R5P5P5P5P5P5P5P5P5P5P5P5P5P5P5P50Ml,զ2M_@%lW\կN?㛽R/Jߥ 0?|??7ޮ cs]ݞ偼yQzT}3~3+lyNݾ$җT/%-^3IzAR@, MHB_ve?aw&bq{C=X5P5[qP>G7~#>IGuT'?OKȄF))js޳wIK KԧRR7A'_SRzRx`:;%XX/~M4_ 9-LwalfUU p?`0ps'gzЃ}۽\Tkp/IG?эWxK@pSOm~[s^?&B$)g= o((Qve@4xܐl^魣61?eMoLYBrw7 .m4)h r|@D >.recà UW T T T T T T T T T ,$W*B"Z f¹%~zJb׻ \s5}{_B&`/[_?C78ɜtIvUU 2}JFspղorURv}$X!?<=OlKcwH O{^# |to_Mo}+?nmSN9~=oV>ְmYnZA;}6oM?OGe.aazA@>!BPcZ87o!0v) zu@$|-[qd#BځA&`,z?&R/"kfLHHc_6*tHocXgUu_=OMQnr[VW~Wvoӗcxڒ?SS~gm¤0iK emU?eguh~ךqΖ@+J""m+tj芶Gw':?g!xge};v4IwڕlAH {wQg>NwӼٲ@1Q֩}!!пOaˬn~_|qӞ<.O3&mD|LsNC Gcعatdgzmx_ He鎑LE :jj* 5M;Ⱥc 1d^&}y aJy|/T T T T T l[ 9/4)`ҥ`ψ1]w݌`ۘD'>ƞbƜ(gٯ؞RT T T ȴn5/}iԻ8c\xtt#$ 3&o[nu:޷Yjl-OIVׅ `;xN?M2 O޵ ==@ ⹮'؃ ((Dׇ?f l!/}7 ~zWM_~EFz(+km:r爘 ppzH 4o_?oG Xk{^H[n'Rps hj6~s=/`7Y]Uu~1@A&bӪ ڝ; ωG*Їi-O>%}NKt9y5'x|ZGOCBH}UO;-EWCeǵ-1wꬒ w@@@@ 2A̫bœ73U` vMiԫ3 pDv\N:)zMS!"6VX7 TR~ ܌sΥ^μHi0$`0vxGK"|v@Sg0>.:Y*DTRIug4S]xWż~mI'_ D?M'@C4ȪD3ECI9xnCrs>5ӟ&=|@$ %51c4ҿvw߽?Ѻv mCԍD]ջ Q@1Qk)b\D2o ˟%Sz^V?Dd0^A>;їϡc ku9$  )rHx@@@6@$#OJW\²xA,El`k*UU7j "Am X$E)j7^?*ҜzM@~kyٹ#b룍lejݍ߿Vg5w[#wG>ɍ픓+ QuZRIG\R2Z3~JƄ*x]tQ1 *aۢx@u/<:KAD,@6y$$m:V&}څ}^6_N9Q𼛆?%}C˓~d/E:E bweO" v+ȡߕabL,]xM~xO! (220Zć1QBMh_[YO|!Z.`Y56m/`X~[%+-)䉾{HDn׌CӇH>HSQA닷$U"S?K6F(h\xVh S OM!R#'m~R*d[5P5k-m3` FHlm=F"hkؿ5& <<];RUUK`=~ih23 C}_D<sH/<J$trA *={s&c! :} 076᪀]m^Mm6pz\zO|oi#]j@}?HF}BwH$|;:Kt d5iXF[h{-Jc2O"r꺶iam=9UހƓe 7^1Bڋ6?mcs{c: oD)87]_"wQ:&z)\ 2yRmS5``CUV7Zc>/~!2*ܶ XزuEب}}K*_m ({Mjj`k5`a6gx2KW#,S54Loy[60b0Dk 1DxI BBHC9d[v[ƥQvg%/i@NPGEH<~:F7YHc:'C=Yx G?*&!|sW|:w{Bpl0Ռ/9o9}D[>-`c"{OIVQ?Oҥ^Z$[{zM<.C[Dȳv_`D_U}A~:c,oSwb/dd ۺh-M[܇la<e -"ʅ(s/~q0 u(3%tk#ci󶌪R5P5P5P5P54 zb՟;1-D$qO#hLSGS:4b4ѪЀ]/{GJ*n5t ʈ1L8dLI@z^o Dsԧ>UK7"u<9I;bSp# %\D[Yoڥrt_E!yHȯ^"(ԧ>w_?#u,o}[{O5p~vխnըزBKз_p@z3uؕܗw5@>5|͸`mG>O$'`q&mܢ[deI^{` B*@DAp">)'5T T T T T l+ D\" eSR{S칸$>AKceҽbR=H@@@@@@4صZcWRAm"UUQTkmɳ*dL7@Uy}ǻy0`6O~A.Ys.`@{l@)Y@6R[WC6D,Mx>AKQ&&?w/< YIek+ $K KZ{Ցrcg^xa~^^6BE]=)7b0]<%δGLqu3.a{ؤj>XΏ*B40˪kwݽ9/P1//]r?<]p=V{ sζC\@{eǕG_nאQhη|GPZtr/ AxS˒yU.VJZT]ĵS>XfKy[qu{ĶK1qJ1q=4Qb} ̻=.Y?l%?iI,o00OssOOR1ߎYgeQ<MCWV~oQ(v@1w-Zƪuk=\yf|WMQ 0MaKOOY]ʶ|D4`[)S@P(h`opq=U5L L%]L%-NS9]~I8 =$@ <Կ%/in~ĵwꫯ9c >R|D~~#} _ct]W4p]۸=ﹴH< =Ʀԑr ??_JhDlV`|2ӗ!N:LoIu>ΓF&!!]E_ J"LOӛ6.=a뉱.2bLkg}΃+>P{FWw_>rad~bhc; վ/߿E6U"й-:D n}PN=W5P5P5P5gT<2Aؖ*)&)k"/B#͢ 䇙)NEW4y9Lr϶ʷWaJ_JJ_Z ,os0a}Ss@Fžf[5/hD{W0&̳^Y;]/ _{ XYѺmr\ǯo[<6;ѭ)u]=!=:.nZ<( [T_99>a# 2m!i;A6B"JvaU/GԍHZANE;v(NsYW6qvmg7#RkNg7'N{jF;&ާ79l:? /Zp_Zljm/oE.ht.4fGUƖy\cbSgv`oM}V9tY_5j_t '}i3zꖰz8]~/0K_:} @c"}^&SʡsOHg#fz՟TRA Exi/!`9/|\csT CaMz{Λo;w6lȸ,0"/{QGG<ϦB!NW~u@.#Ŝ|ɽmfY0TMXZc)} Qmrh"7$&&yֵ>9A[cg_[&MWM*m ~.,};y~~#oA|SEd7Q$drI&P^-vAJ@@@@I|)&3eS0Sg``YcR4#"]Lg@,sf`<axlҊmt0b0 !pJ;-83ń"~{7%[E<1)&}.jʏXP k韦y8#{nJ~ɦClf a3c|#$E48~+f䊩ޝXzϞFbWg ^W0J7zoΪ:xF-:@IDATs!fDD׾vKD=\l[&9]vvgSXLML}~60s3̉'@$tu(M_Ϭ۰do|55=gu|gokr[\_Fc$J3ogx.KJPe7#.kZRy oxn[>M$);o骫??ݲ<w20RՋ>Nԇza]뽃(6FǼW6/tZ%yN.Jw]Ӊ1V睛W۳u)y^_h;yAg7Mr5٣m<ȘQnmiPXyd%U|cs5~#i)QaKMUnVږki:&gAPuE!cm[{]ګ3\]'{_5ItiJiX{9') mQxcULxH ]n2]gו 菵 >:$&{qߡq׾^&TGʀm:$%1]xF,Tcǎ'?PPvQn΢>۾c-ж- Ku)BCkǵX;/=M:vgWVןwPGDٵkF@KRylxRIvE;EE/lTc,ZӋT:6fidyu·7&$%gvAt[ >!T/N<Ŗl̫r,ZeJm^&k/fJm[[_$]{8OODeC'1pM]ζJnwL{~0Y<6HWh<ܵ)-=_YկUvv/QxL-_7 .N,H{7dR|7)VVzqKr c"}, g,~ދ k41#7)=֥zpzFF>IhKx0oP^0]ʫ t@1) -Ce۳KC^-b-~q5k-%A:bG6R&d/@7*ҵcꦹ!WIǮ0ƽկnYs蟈yQJ=l,t^%:惍3DmR0{ vIJqN|.0U&` Cww"sKK=Afi )?8_9lkR=wx$<'Ťz{uTg&E tN`ʨ܀e9^M/zLgڕ5hO "y h3%)AA7|sk8&BWeXЗν?"c+pZ4t99moKDy`joͭFdj#d@ PPj{yke-o.n!]6WiH8^{ Hݾo;ożs3yEDK" Mx-:Ƈ"{Jj34b4Ȫw(ڭ!}d 8=<$snDizwgCuiٙE2HѾ.nؼƟ y^_#LTnyю | K ' t S2:_c7) k;z6H(^.(tܱc\ɬK/w/J/e{dQ6/L Ē軼H` T@'ɞØ(iXLMԋ:؈{l=8=-1"7uW[y1K/ͪkGHygcI X3lSz֔s&XI}l3/<PJ޻Cb,;슶d+-Br>_5P5P5P5P5%@EM'öxRzSSXζ%yĜk. Wb~ڄ=vH)&ggvFL|"T Ǎb1>:^qzJn-l^LyGaOe/Ka`,UmT$?Ny+zge4ҳ%h@Y~s/;lO6Wh]]??UXkm%뙍kgcǽDɎmZM$K;wldoԶتPvyA#llY>4Ā6^fTͤ沾"oH4ʣ؝lv=F[ij)@;%j@pnRW<}s{; !.bWGNweo텭֕~+.@^h Iv5ﭨ_8m_oho]VG$Zې#gp\Ds>ֿ8z1vR!y#Q}Sh7u7NYؠ }_gi'# ]@bU{ˇe욾1`Z Su,2 ֹx3h"S&D}Iju!|< }op)c|pD=bM Jw6}!OƤ`*s&&ׇia6*Ewڽ_;{U ݬVo\ >> dOc};C(0B EI? \#Ccy8ihE,mHB{[ڒw2$t񪁪-gT S4^uDzS (cwp2c> mHbljq@T} N M$ʰ+RLXy,||sb>Lj}7 0@3=I&9E˙я麔3<<05̰5B( dW |x27QDiD$9,كؚ.@<"f Ց~!:XxJȚE$0ՠ+o"8=Ëiw{cxj/~}hc3@ ޚd?|QGG>ZҺ=;ea_ x#l+VA(z55K_="7{NѶQq^TS#*B[^4PݵS&p`iۮ)/x Ȭ@aDvQ$z~5wˢ*oqȎ[`gWSvJB{b.Ɩc a+A,h˹>}A> ц9 gvϥADiGcn,m_u%]?,C@ `kc~Ϗ8vZy[UvL#Z} 7΋Zm[sH5Gw Xly%BYgՐr;@}qxc9&mغ]BMߘ-ò/e?W5ҀWs:vM+ٹJ xNP'i{*~V/͋kӠE=b4I_{і^Nݴ;нSGIko Lwgt)7SLԡɅT@nʸ1Ntw'*Sۊ,luh7e/|lp {,׷7]ͬk + >Qf{! f-8hϛ!1Y({"5[:zAdN?5,K:PGRy[b!j?y^T8/ȝ<>:+W `'pj3}yHr x[G#iF(r)Ŝ",c" DXG0#'B'K/cL $<Ͽ l嗖6lք琂T~+cFTj`k4LyKp|J9 ca&T.$* o*)]t'$rQR*+Oߢ;#(_ CF6)ďU|w'x:i҄9j@Anc};B=lZ4MѡIJ]S6~bX{}I w3X @·'t1bQeolD{,ʢk0X0ph;X.2$mR6ēJT T T T T lk D$@y?;*P^1gSęM'skA4 ؆wR:-@x_S 7$0*B 1WN{ ."-s;O}u @f'PፙtoFA~v2'DDxG=jqR%-tF9; P=D*@0 `h焞_Y9{uB),d޶vt'/BxEBnlu[Q"MeJ& EP۹sFMʑ7"oWΈ?:=#B6>ڴu"rG,Uo dbk64v.6n 5d׶l:@Tf[5#c";tdЗu8[Mt!\VM$=y]hgcl.DЎ[h%=.N"it>IGi;(ʃڦaJZ}#Jq}-h<9MV[~o^c9P.2#=tN&J@IDmmTH{ 3ͽD;[ ivS8-ޓzWI)w\JO pU1/K>p9nA4 >^@+&`3=ba='FH}<{3s o?–fK+D+ mRAxw6 q ]} eSC7$gRD"ߘ-cկc;jiv,TyUӗ!]уBo M[5u` n*L=vIq 5~Vo +]I>׵!H3.,2DQf&¥~,m E/L9g D(']""zF 'S]1fVzj"d[dM嬨F֠_qP@60Hץti)6M[[7{. 0$c-hjBȧFZp2FϺ Y.:Ͼao.`}r"&vE7d{@.E iÀ1:_2z^!H`[6߽X959Juy+ݎ ꠮l ]%ckm$o DmZM#vUh(X9c7c!;_[:x}S@m?kϺlӵpfD BAKPwW{vZ~+'l1Py@^"9\{3"k n=!t7:ƥ.vGvLS 9-oRkiwDT l% ry6oI2H\\X/^s BVKBx v@UB؍dwsxL@@Sˆv6FJ07__z'>;m4&PBDS& ƄA&*ytϙԚ>0&BUnc{RԶ/tr?B.t}z}B[.q: [D8'v'ytۧh<$h>(ۖK1uN[^C9BQGk~ $W+_9'…oț/esk >a7QDrØ\==)3o>"'s-“i}yE-l2D҈r$vNRxcئBքrcӚG&r]p<`my؈@YņEOmwhʶJakSC)ve"& qɢc/ۭH)pr}C[[g΍jvߏ9<[w v . w U B)YDuӪW T T T T b8`%@ymķo~lE ) GX˻' J 2."`N8ޟl ?9b@W2~4EhY4(79xbPc*D~:/]ۍTjV>9 _OOƔ>AEXD"u]dq{Zlؒm=|3{}D!f1!oݺc;V-JUg uu2nc+Y5]-ݘ}}6$U :mh;g[b^-?[{P6&pTOq(u>뜹}[3 x/TQdy2Xy+|l} X}E%|d_J1}mG{luwF zY 5me2y2>Nwc9ǥmLL=M,LH!*b6+Eo;ocCL!h>USǴ>Ԅ41GFWmk>@-@,\pe`[?xݔ1l,vڢ-4$u+v깪mya{>о@ 6ZWŜknN]#a^g1gTa 4d8|EYbj{,@*PT`< $'2!LʿzV/}"@!?d !qݿbش<~)6?.|6'jIh1PX5}ਛT"+5y-]u-LgȞ!m+59#aaΈ(O}q tu[u~9o}e_&ߗ~>.ͮ;_}}Hd__؏g;݈ڽ_[8u?^I^CxxLKc裏^Z(^_ga@kTv`>WbI@0$`[`RvZΝ;{_F{ sa>>s u F cP a{AT9w^1KɶLJJ^Hcv\e4`lB#{̃D @dɚ@${{xo}ӻ虱<у,-, M^yX?x36E(1 /lHCy79׿-J^m4ͺ_"QbmޣT T T T T  ĸާn0Tu,|cȧmlsLz"9= )\51 a%};,˗oe#iG8fˀ pn,Z~!D-Jm7Q%]!f0Ev~HyuOkvzzka]pȎ2Dl][:aJ?"[qf\YXCҭnu sʠ]}׎H'ω⣭zv^;=kY9t64Ql߮{ ]]EWOkJs3׳ۖSJsua> 1R%?aN9 ulia) Co}˿E8=A*\s衇6W\:⳧4)LKvH~F{^%R= c7$@L ڈh2} #r(novuwV0U鳨?YXv| ]Y@t!m _^yyϷ#RB>))ۘ)^=ݏZ@dH*24O4ݕUT l@~O(ݢvm됇ƞg~!(m 0 d&-.6=^әݾ{lV`dvm[]tXf,g[~|wǾ\C }$}Eem_r-Ǡ)p]v5~p#\P($eab vOJ_>կNvZ.{eQ$Jzq)aLt@yX:$@>c&7?PΣ_$yN~zsX N'tq$|$?=E|_=jYuZLgeYөXJ"Lԥ/ K>[_Wƶv'%=zG&iwezlRNjp>e+ xj#-[ 菫 R74w$$-JXtJkwػUj?= +U[ /)%)dz%e  cK11~DޭK1 3>c XUx~f#X THtvDtc.s%jdC|RQۛ貌kUK5uVZ yX}3 є_6 q{FV3pa#F.mFt뜷O9<7D'G h"|pbsܹsP[ilw_d';T'ѕ>U6m|NՀEH- OD7t1L6/ƻx iPz9&ǀ4ǼK/Գi"f IcE{qwc3}ոکih]sXw9D6?H1SY}[1X`M 5!Lc?+r'SHYͰPnQ/vQPasJ2ciUUUUU⽘N8!O #+Bg9ΡyZS,gxX&&)))< 81/o[w\}(SLu cx zl_ؔnsџH%?¸1N1}ǺvZ{LWaDhɷ~CT 9,IncaqW׻uC7?;;{mN²[=my[2_zCiþ+E + u˟nma!(6}%"I[|K8g1= $ǡIt#?,%EWր>+~m>zii"C-!ļon"<򑏜DMG$mW{i>Gz:tr+=c9n<ԋ$oWQ7y1icw2DY$e- 3>!.Z8!ato5Ċ[- H`{i ;>)&)EkG̗ ~3RS {J;f^sFvJwz b>0@a ,$mc.hVơa;ObF5k'G1EH{'o;̆N K˛pƪk-kc!@ljD_̶=lmY<~Sxȳm^yMdֶU]ۘ{V-뜷y]m-;Lp\٣RlǶTo_m_ߡ^ؒXA-WŒ}Y]5Z :m\C"W qI@1iR#r0z׻6{oy$O.7(7s`Eɓ[]x9V6`kےg;LEza5S'f@c>@$ |((, 1#r>yr!C4sX%d pɟmwe[ EZ0<~~[ۗҁzJ`ahB[oھ>i2 y ҙ:zxݼz۬ߢHaF?(jrnWLKmR[Gpx4!x?˪kqmxqlP.D!}1~-86Cդ MeÔó[GRrY!lOThi a :Jbǡ'sMXk BAD 4349J$ױ4?⃤DPbDb@F]RzSSR,{~й=kc>(ʌdQ7A --&Ψ>F񉍓Yg8Ad[Tuw#뜷ڍ n\ľX<]̶le3vmĒyLWR"N)g5/ >>=1續uS~ ҝLIËa{X`^)Z J91ٶ=O~En{S.vE4UtwDBiL9K]{9e`q~1XhDh=.rz ~a̫DzGm^1ct/(?fso}{6|+Η = 4I$oM;q{,z@dSˮk)QE{YTr릁5mm֘yKݦ{gŻR=(?o;׌ }^Ģ1>Ϙgҽ#آCYp[̣zm@@@@=bA4,nH1ys]= ||D(syGKl? LXHfY`PDBWbِo{[,"A_}<& $ym]Qҡ_H6X)&9æ1smHt0tǗ"l.kKC!inG^sMؚrak[<=g݅. ؾBٛ6N]*:筤+~,"sv.*ݿN^e-J*ۮ+l۶S='7\y9sw+:n؆SnY5^f7C h)%1X I<9餓N W~=a:蠾ӽLf9{0{c!?.d-ԦLL=lI1&N^{m%gGWS$׏Ix4G@uW7m4 gԍ>`0ahঈɡtWQ%kOyAw]EME"j+&_}C.Ġ Jc Ƅ>E{Av[TrѴVq]!*ͼxVXo DdǤg`2yr$–AF@"ckc~;1Pr#c2ixll[.`ݝ /=΋ lcߐgf+ "\b/+ Dw nyn=.+X4yysWjmzj`ikwU$Ehkz6XgZ:!Kv9]"߿Vu(Sm:O}Nws_:Qo=Ա](3:Θc׵#*UUUUU,6N,<% >G5N6쨢$!9H]bRT;ENk+q-u>Y9o] 8MoK5SpfD ץ4lq%H8W-Ƶꗾ8{v1XJgE[Kj_P Wr x/zыOel} c]_mgJEnlrey)됇\.|XEƜF<ح˽뜷$P$?GĬDDyXE83zI̓39u{Nc__`&iW5Ngi"t\-s^}om5 o3M&KZHj`, ၛQqgLFPW?$eY~; G@^d_&yZۯ$H6t2elO=-tE )y]ۺmww&%d_vhFDrQeqQV'=IMK2:NkB_+ [WcO<1²D_Qv L)kƞSƝ)  CX7_Uf0T&CBj:YLkF6?C:6‹c-[#&1Եڿw'S5xG+l/nD,}Ν;풆WǐQ&*2QlM{ԆtNg5M}}ceS/Xm`o1A̹:@vD.7o5BL).8c,-&H泟aCȘ!/d$Wl?fu v b]fDb3HtϱE?>YօK$=bC<Bp:⋾qٳvk+zC"uL76!|D VokO%RWEt%L3Jӕ~/ʅrWm,4M$ :iZQA([{#JkZ! 03Fs=75xǜud46Φ(o):!۷5{[fz9묳&rƘ=A?O2뜷e]çn=lk )9ΰ]|Md\8J_,#a)ȑPtR}) 9?lڸ#;;fV}o Waåg6ݶ3lo;u`qe׮]MUUk1;b~GK Q s} =1a8Bݘ!AVا4u\,I@ØkIkjw }C룢v 8~W2A_ڙz!bb5ϼǶGXvy2/}3?=guc12|E0ug,Jg!o "1k+@!҅@Y#ꚾɅ#Ma" =|;DU$o[|Sfrr饳͐ܮ-mƵ~gAP*U@0v V@26]PǶyOgD.6 @|>Cr^UD#6vyO-PmK=OuR_8GeXM-Ôٵs#C; r#:C.;D^'P_ꗮ9ڵk21-6ҵAr Sgs1ݻ{_m׾ӌ͋NAhT!x:3<&Y5A  ~^^ (yf  >M0@Q<d >ged4 E}O/`gѕɢ|&A. ͒xzXd`ZNTΓHǷ[ Zw"'mAǴquʡtLBigʲ,#i#Q$`dnS^z䣝[t[&mIԁ74e6g,қ"t{rx Ba8_kZ &Ed_9 H> 6e‡2ԾZlXcA/OZ9"/&PMK7&ʫʯbLT#~ս>_63O\WYmE"\hK˪kmX(,"c}0*XB4F_,VHhw.]S mNB'J:cekO4 )n}kD_p/XG0/ = WlTgsUUUUU[롘k6Cyj8>Oҋ_<=tfgx OcFc.ļi7lj}cԯ0bq﫬Ɯ䫥1y,=)&z(kp3Uvc7X;5[-n=Bp諓ei=ĂO/Jy$ֻ:'zm 5,6ggoY4:q݇ =2%' Ta!Y@B6,lL8[S۾x!,훍g:y``WM#$N`+E9o{U7v&Qsz'؇]ێʎͨn?>ol Mgv4vWgkJkLU~{Eh! hЛ ;"딓, s諢Ag1GG5yp͔J*LmgmH:L<,}txË`'ї ۭNs|sަ5lE'pB&^N{#B(v>YϟDKݷ5|g܇=Iwu]p>LNk_%RY7l֯m75W[~DZpv+}#%K]2X+8)lu{P^ PHQeOuUEiIW^>˔Ua(^\ ].7#ԇufq'H{$kgi賬vz*9 K ) 2UI 1=U))8{M)Q?<҉'`Lon070/Hp٤󏗽, `Jo,_>D9;a*w^zE➣ <@D|Nϭ[z~"xS?N^gty2' y|A&$#} ׀+6(m):0;wlҝ*ye3:pgpkgo"ڞG h~ŁCd_szozcC,x86~&}ϡ#8 OeiK}oiOk+پzS;b.X` ֫릌""w^Bg_xUlۚsg+m~jjj`\L=V5P5P5P5h#g´'.0 6a9" T8{Jfu#{0ݳsCN׿>JbS\6`205j"H9;M.s۟NN Y^\&ZG3SJ DQQ.|1,B9R킉YUWt3<G5$Lixr#S:nIУm&!YEگ>!τgVx#ʞuTDRMm^󖂵;^23}Ro)HvDZ|ft<bP+{>l<(iqG~؆uȃ|o; ;D] Qg{ av =@rL&) $e{"uYy[f+"dٜ׶ >ϤK.ٻ(9+W9Fe3 H(AD6l0ǻ>߳wmkl`XAȈ9,{{zfgg{T w_w0пiY_%&]jU그[]OUAsUW- k Vd%#\JucKtNx XO,ZU8߁ZZ/n{1~ iXSp%0m1S}RHh @rOOf@&j*??P%ڟ@(|r~ FOl.זj?6L0_h Cci&6sT@ۿ[%/] foEfs2l*/f|.)_!Ι *\(_ug*o6ouSzU\2e}Vz +Y.(jV|5nҺJxи GҸ.bj6oiA0 ^P>KerRKl%\b ba؍7=XΫ Ye *Mh;t5VsW%N3i/(sKHЌ(NeuۺL0v:EPFUm`u,Z^@ <2/(tOWJ<} m=dQ=5V^\q'dD#ߝg}[ߛZr%ՄY L+8]W C${ Կ_:2]Ȇ1hY ܽ&]Au +URI2TzB # k_;TF񔼠fH7hM9cK='zhJP9J :tO*{@  .+(c;ᛮIOaj&D*:}ΰ{US?s.]_]}Ou=4_}k"@]C>{#c[t5{M**?TNRARl@8^g}e%PlesR @EXr+_v,Ɍ9AQk?8i-L٪cw2U>4Yظd 崂RZSJѣGv"%Gb TP1{;k%lU^zVPFA=WPul|a0X =YA|쏥1|sl߅CQ 50uӸԏT__]C}YA\lαK qtJ(slgY-{&{E^4nYgO砇?w݃4 TgrȄ/v@]_͙dYC;ѹ*ɿJrT%vu ] 5]޻\u,}?=*T 149j #x>sֳ{/ۮoDc2(C{I:w=YJI ",YJ֔24Y|䪀*OT`3l6p@W^٩ʐ_fuDeL)(mIdM C J&O},Tq}N,}L@ʵ17Z d?}t7mFaF7@@@@@D$$}H*@@*)"%P–*ѤIݻg?n﷓'OJ2PfkiUQzM+J";v 2vmZܹsРAwP Y#[Ys)   @Ȑ \ri%(WQJFRjʽ}^^FѺuW"{N@CQ#+X{lf={'WAҬ]d=^! 8M ġ`SuJ/( ,@(UGbݸ?%@ y̾}ܖ2` L z5 @@ J@E?ggΜqH7o:ڐ(eE@@@@r/P0(௕s  @14ERJb KFH*+E@@@@@@ 8-ڞl@*g@@@@@@@rY\z@@@@@@@ TA\       @. TW#       A 2ˡ@@@@@@@e r1v@@@@@@@2(@RAq94       ,@RA._=Ǝ       @H* .F@@@@@@@ H*@@@@@@@Ƞ@ C#   dSl۶mvZ۱cڵ @N},QC3H+W iUK^SBO>m[lyn+W|`FCZݺu6 E h^l   '>I&ƍ]@޽otkWk=5k<WA"C@Ikz)/qjӽ{w7n-X%_O=K4k׮. |hp Is)    mlĉ6c Z[ou:4@I@os#FؠAܒ=&LŋIPMK@@dY    Q .ҥKm|rkܸu]lfj . z(@qJ>}.l2O! PnH*(7A@@@,pyW_w\venFgϞ=2tE6@rK@n|AܹsQ{]vr- %@RAn]/F    (`̙kَ;lذavwZ6mX N7@rIrnI뮻ZhaO?[ԩS#XH,ȥ X@rRrNA#   8-y0w\{7m׮]6rHSYh4@ʃCѣ=ֵkW[j e˖pz Y-@RAV_   $XlM0U4sܸqni! P,ױcGW@K,1-{t  Y'.0 @@@@ 5m۶ɓl!Cwa͛7 tj|l UTC=dZYfٌ3̙39x6 @ 7D@@@N8aӧOٳg[mԨQֺuk @< ([nvwZz@7o6- DC@ T~S   d\`nvfn={Kd@D@K!\s5q9{WرcY2: @ |]O@@@*;lΜ9A+4@*@5lر֮];[`͟?߾꫊D" P*$ 3    x-[-ZJ?2ĕ~N9  %p%%\oS .E rB1L@@@@@NtR;y%TT @*ꪫuֶqFW̙3҂F@ S$dJ"   XjX:wl_~U^=pH@rGv6br|K˝3R@_F@@@pGuG.@0QF_ږ-[  @H*H$A@@@2-yf۰al:ufdfO @.W\aZaܹv\6cD@ '($    yٗ}v!@Dĉ6i$/\ZjـkzEؾ}͞=۶mfkv_omڴ*U @ bӦM۸qN::;! @I   dSl׮]v1kժ5k,+ɠF@AYf1:d={$6O?}]=zvmְaôÁ(@ݭnݺ.֠Awo8l $D{    {uI7͛[ժNV^2Թs\o=D .yF m'O4_gΜS3u/%,_܎9f8oyyy15jXΝmϞ=fW=C@@dd~   f\DmJYf.`׮]Md@HIi0    @&^k׶Zje+dTV-nL 6իǽ[AVrew…# & a@@@@L h]h=X eoaK/ub?޽{[=H lOvsnѢǺtb5j(\R@}\Җ.]݁ܮ 46msjR=dF۲eqI}XӦMJ*I[~tmuOˋgӦM6|ܷoulo;w5k  @E "\e@@@@ c ZwsH+Ud= 4ȪWU~„ `;v)m>p #F1cƸ~k<>K*Xh\0ĉ]0ﶱcǦ(! ]ٳgIVMd?'!CpDW}ݻd7}t;t4&5:_o\ۨQ;%(W}Z^РĎnJok6lpڵ=֮]~O#VUO4V3:cG o8pqT2*x߰Q?N>ۀZ-"OC@Il@@@@|/l~)(p2eկkf&M2-7_*Я~_HgOfS]|\‡|KuLng_R9 !sUk.X@ r*1F@@@@ؿ )m۶uk+}A[z:@0o};߱f͚e:WU(/ +k׮iӦA36n82\6,2eկMmUP>\Yܹ Z޽6md/m۶E4~K/OӤ>h}XBA˖-Y~}*goP\l2IsQn=D]~)7%T/qשCH@@" TPd2v@@@@@ f+ p<ƌիW7ծ>uT\ٳM4)r{lVy܌q?|{lper1b5?HI|y;yթSjժVZ5بwΜ96cƌ-uݺusgt d}5eʔ}O.]j\r:Y&*yC7hy:Ԕؠկx'L$TCˬ{J^ѹꪫo ?gm.\3-9l6lp͛7ZA  PnH*(C@@@(+TMϮzU6Tp\ Z'qC@F\`ؿ\Vjfo߾=jH j:Psz衇\oIJй|G֫W/K6s^3]s5qI  7j>}nݺCkkوÇ{22ѣMpY|ܹ3Z  @y \I@@@@  pO X%U@K*q~<_ر#ne]fv^Jرcf˫eѯ LDTy!,$jÿaìqƑd -ִH8rHǚiġmYk׎:N-5qWZZ_XJ ٳgOdC@@ H* @@@@O@3U^eS *p3(_lt~yka}-Qf͔NU3cU`E֭3{ܹȐtw3#oBJB 6-!輵]+$h~iY<\@߾}Mx.6-k w>=gkJؠ! ]~9?@@@@RPgϞKh߾}i@L֔(q٨RU`ic~RJy3,ݼys\  |}Nv:Bnݺq&sǗͯAN*\6^U~!$Ž  @ TWq#   dݩƃ't=tP\=_VIf_ض͛7 _nڴ).Eq $c jժѳoR ‚J8Qߵk"WH}>Mesq$`! ]o,g!    ǟ@IDAT׏ ^*X;?N!6i$.@eO:e ֶlO%,ݵkW'O%KDFCI}dL6>aI)b:uT ؔBRAP  P^H*(WB@@@I[>~ H_x1.'A%̞=ۆj&ѣGmƌֲeBgv vgΜx|UvرcR Jf )Ary  R@@@@,ЬvD2uf"PRƍMw5U੧{epIW?l-#FX04U?tW_m]v5}^VvmˬEq.Կ*^vxF@@ (@RAP    ={6j*k тo̙rHBSPJ&H,8pq֬YDŽ_֪U+nI ]v6rH-iS@:.&–dHt G@YqM8s@@@@L ,A%vBaK*(|…֭[m„ k׮ȱ5sBѾ}{뮻l.0V rWrA[-K%]׏K*8qD\Ebw  Zr}y99@@@@\طo_ܬ}KZ"?i `;Y_Z9)IG?ii?.\h۶msIиqckٲ{tg5*rP,8}ÇRD>L UBVZᗛ@@ @@@@(+_bi{5l0cIEla'{]ԥ֯_o*[߾}:oцJzꡊz. m۶Vv %^:d[)?ZZ|p6D@: 0 @@@@\ؾ}-^֭(Ыb׺oҤըQ#㤺a^^^\`^n_?lycoذc>}/q <}E% 7i/칬KN:ni?5kƍ]t>]q<֡C7.~aϺG,X`Eօ@@[rv!    {J xWO?'O|*wl}=,???vZ^7k,.YAcWBfҴ|O?mk׮-u_mٲe`w&[Yۭ[7Sŀ`;wM>|;{e\r&|E޺u=.JiL@Z 00@@@@\PY{SI,3gKB]@-ZrPBƍ*̚5WhwO /JkժիW-e7oW_|-!t˪_wsiֿ*9s&vZ_BW\g̃KP$X uhG@(,P>#g    ?8`zk\PY3HW6 :T>RJ6h We`ݑ._|Zny?Bc֬uăKIZ9tK+X[ƏIжzԬY3UZhٲ)Qcǎ)Us(~ek@~5pIFK:Hfx)uشi 6̚6mp!C`% K/rqU+-Zd?V$x@@#@RAŹ֜)    @8q^}U?~ֿ[f+oؖ-[fkvy~}QOQɓ'.\p:ٳMUƌc{wa&Mrj%K}EX0n8;|͜9 W&*PAy^WRŽċÇȑ#]m۶v7GF%%^y:EZ"ٰ-52Νk{qVfѰaCraMݻw7]` %8<38kԨQd7{WU@@ H*ȩ`@@@@rA@%UXW _n6 l)@^y1QzuYsY[reMFӧO$Rhp2M!="W;w6>#d馛\~}d?}Ą^x%;:"M{U[;@@ʿIs    ,~6eW̙3. ^k> ;<U)H4#7u~Хׯo?ܘ'Nh66@6ml׮]qƦM3뮻2kv v롄 UHP{߾}nƿxK/5VVq(i{)H[o% b w>Xru94"TF??n:4|O뮳{UOP"D%@@ H*k    dX>}\e˖% _r%:u@|ǩ6oժ.CK j{v3u_ֳH/\2>I?zhGc*/̙3ݲ~+V޽{un,ʪ_߿U[`BĬYlڵ.qoY׶K.6p@۷["!ՊJ>r/! .@RA_AƏ     ۶m/ҕ?uKP][l mQZwK t֍5jW^y+aW@x%-Bnܳ P/K ƐԩSmժU?_oz(÷E>?8p-Z +حݻwwc+~}g%4nƌ) {챣G 2uTE+J>QԦcZmd(t_/kdrZk|:D׺ݾwq_?ߞx ;|gu뜋v\C@rA̅b    dzoڵk |+HfQܦ}TpW3qscP9Q2Z@)BU5kJY(*aCy6o(!BI=VWbгD(M*ET=r饗MeŽ_ؘP2OxIv{  @. TW#   䔀*@ >k*V-KX (@۷,+~SpW|9~KWƤc@@&P&"    @Z<PvJLz_@U1@@\ ׮E@@@@ 4ha;z+|?˖-k׺ J&hѢEԚeկ  Ix3    9,`z+8y?6o\ĂM6 /`[nҥGWVF @@196^    9.PjUkӦ^N89۾}kZli6?ޔLg}f .D6Uzּy{zQVF @@1? 0\@@@@=oVXa_~edJظq:kYÆ ~QF ;~ݻ::8w={6^T^^ݻ{aeo8@@\ WD@@@@ R?lO=_>Ri@+8uܹ3LŋzkJB7a_[^!l7l,  @.T W1"    P*Wlz>#;x`Y$7C~˳Aٝwim۶ Pw+~}<#  k$c    Y%kv &MӒ TRŚ7on3Ɩ/_n .t vaO=߯cǎ.1+0o^R}oKx  @T+Wq"   dn7U p׭[7+ǛmR@՝ٵ^kW_}?Ξ=kǎ'O-D:u*ԬYTujժQ*~:NG@@H*(+@    9-vz,??T@TfIg`2S=|]߮$eoI̾  IM_    V@t-TN{"ZM}Dcg@@r\r#       @H*,E@@@@@@@ H*+@@@@@@@ȐI       I~?        C@@@@@@@\ ׯ G@@@@@@@ C$d"       $d       dH rX@@@@@@@r]\@@@@@@@ T!X       @ TW#       ! 2a@@@@@@@u r 2~@@@@@@@2$@RA`9,       .@RA_AƏ       @H*,E@@@@@@@  0~@@@@8}͞=ۦMfի[mȑ֮]<)FN6a[d:ujժe #FX+' OwM9#@@@@ '8` ,^xgm۶% ' D l۶%Hmذ{RJv1۷/I"  P.H*(@@@@rO… vyӳoYe)SNu3Ϗ9buԱlyyye94B;w\w i  @y <\E@@@@&Gٛoi{<_hK(0`U? ! d@!D@@@@JQ`ݺuvСH% U8ymݺTŀ  @E "]m@@@@ 8}t}p3g΄܆  7t+    K,ɓ'ݻ]/ڵÇ[3+E4h`ժU* Zգ*ؼyn={ѣI&D@B TP!/;'   ]@ԩS::իq$=X`ذa/# zm=zZje8~M2VXU+ s@ +.@@@@rCܹsny?ڳgF\P_W"lj'nݺ֬Y3bH@ p…@@ H*k@@@@@TbsRJQC+@@F @@@@Ir4Ƃ  @2aS@@@@@@@_F@@@@@@@2`2aS@@@@ N>mwÇ9s7pmtҸF^^5oԩyoŋʕ+mϞ=vYwMZ^gϞV~Tm֭W_YÆ {~ۊ+>͛7۩Sv]t!CXVJ*~s|[nرVZ5Wk׮uŋnݺYݺuL0®?M6mƍޔm~~lo;w5k͋eɓשS'{AǏkk׮V^=IJ}>׵];Nƍ|aFӏ۶m}v;xUVt϶k.2رըQ#Æn ߏsWfZ1w^/wT[9bUVuCtDNJGZJL[cHw~OZʖ/_nvru]i-O>&Te]#GM߅'NđMaMSK4@] r1r@@@@ 3gδW^yG`S iʔ)6}ۑ ~ߴo"bΝ{ٌ3\SIZxcҤI.(# 7y 8>6o<,=gg? ;]`=.w}ύ5ma}]8q c}6vb'R$볬Ǯjk+ [o.kB+CDao[۰aC$X>wClՑL~O>q+zsw7]Mz.կ&;%l\z6zh6lXZU&A3'(x㏻RuMgj6{"sU%h߾ k͚5zm 4Hiƽaþi(8Φg0s>O>K3fLI-r?~L}VwD‹SC߽_|є(>\F~>SwD$]f JJ2裏ܾJd͛d$'UfIpyd}QPI6n>C@`    &E6~ͶUP(QPY ~iW>YBSABk\ueHl_mo~:uKH,lau@6m"W5%xoveU?]M1ks k֬cǎ? }We?OM/@}}T \>pIeTuBxHP<Ʃu=~… &7MHT!6@$w5W_}]C-%,;R ^hD2J(i"*/HVUXôf+PG+/:`S,TD&ˢȯu/(@(0훪_n=ֲ3u_h&fk :Fr2toiӦYƍÿ֭~%=T MJ Z*ۯ_?4h[@mY\x^hs8h$D~[u=ڶmk5r{]/]`2~7(@IJZ!j$:?U0%?թS]]/=UЬ~5MZ^@I7?:au %(E}eGΝJP]oU3u~YcOG߾}]UM%8^`Ϟ=Ӿl   Pz$5=!   9% ࡂr f)H蛒.{[Q J A+:رcO>.>S)v-Ν^+bM4O*?W^ il j-TXѶQI0EI&W]uZt_h]^x!: }fN믿ޖ,Y>(f@V\igvϴw:v*.G6yd{SLqIcWϝwi-Z\;m~=lٲJђJPDd&߄ M(Z`_վ. |?~q$1 Tk)PTjݏC{2PSPW^.YX``8`PXCIfxd}0h֑Wp_dU1AUUUP`SرK$(AAf槳=c{6`_~TWww^DM 4>ش٭6]p;Ͳl|Q2, DMIGJXe˖.p0[j JbҒZ@JPPX"EDonO=6O(}VTcl~Su;*C먊ʁ%{-~k[OO@Gex     .X㷾-@g 2$.HΚ  F 7δ`ƣnv (Dڇa6n˗/w%5j-KlZMkkP@+{U]KQ+hLS%^;ca=ݓJDQ` =qDۼysJΝ;MK ݻ7ҥΡYD%|?LۥD%OW~J\Pu’|3T M U6w~u-X.#8t>{&x=öOOK&h?K&Pqʎ;\UBJa|+ rUc@@ TPo@@@@P0V3(jSO 'HZ;^뺧:7ձ]ֲEY]NŖ?qDT`x#*|/n53^cߔ<0uT~Jk<<-Y{~nժU\3fزeˊ}߆ xhݺ+"vD?7%N:r)V)H4Y{饗LKBǤ :4%Uq(i&6*DlMXT6  P1H*יD@@@rB@  uVkل`LnѢ+y^XD~t?׀OcJ6QL7W Jt?~=3nypJ(R%)<^v5'~;nقc${oӦMvɨMEqe]T DKYh x%6(xROK^6eʔX|B7N78t)!xS];3ٸ @ʇ@q    @EPp, f'@VvEsljZ4 d7|[@pT>v ݺusKĖY n;p{踪/*)tMv׺ AA]v[煽VyQh aU$vv~a'ҥKxP}[yEK,)V{mo~.c@*Is    @Ьq99N N}b쨾_h$&(}wH{2zh[vi ?&De-+ۛoioUOH% d#)) c?UgOI>WRAXbBptޱcD;>%h)T+hL_Ty!XӦM3-]Q{\;zKHB@!@RA    d@5lܸq6l#L%mi4я~d\rQU@3gδ=zD+"G JЯSNhRBXfU(QRAiWNlܸ1j){?vOv3lN! @v Ta1*@@@@@|4iĭa/l(|c=f} MhPZAŋ?nO +h8yX?*! BXuL,Pgׯ*Aּys_5u ;:)"]ѢEOy @0#@@@@RQ] ]\{y g͚eNxU0a]zֶmۄeT{]vN<ٽ&B((uVגmڴRX_=zΞ=*D~U} 6!1jJܹ[^ݻwG>t落⋦ :u MCX2Dvlȑ%ma/1@ʏkə    d@lmhdqǝLq){m۶mQ#QyămŊ6{l;rHR{|5\=cGf͚EA &K.O>$.Q"2ǎs-v1cdg}:6lia?~KZF bJ%'?# A F@@@@ aX= C ^qcۿd3cǎ5U*ۭe˖B3'Md{z~Pre]fwuW\ł۷mmZA`Se[q۲e"j֭l";]'Nw%OK|߶GYKJpBSNe1LEJOpL  \>|    WDcg +VY"Y)˕:^Z_x||i\{i֏fkك[F\*ݻ xwŮj_~p>i ]}dR|_n ޽{}PVRAX[ ?ݖ,YbNBAn|@Y[ohj5sr * p*[Ϟ=%߹s皖A(jSMvW^y5l0v Cc)IM#ևKn8m۶+b;vպuk>HÛzWV 0 @ʯIrf    @PVkO  hjM JPS|IS؋yҺݻߏN=:w,F lԨQִiӨ1(~ , 6W0E5Y%UJ}̘1ֻwHv=4i+g~ Y ryey͛74sa(}m 6ͦE)o4>MKjR \|>{guWG$",M/|<#78-N:tM{IF?fu3O<+ĤMI~, dg-ffH"@;%a"INz_n8m*.dY \I;-'@ @\ 38〻hKAM1k' [oU7V.XtXd;w0yFN-  &;Lе t Z]nz5Mm{m*?emÆ mIM jgdFI=}<&p@so ?UPُ=귛O{Zw饗Vv~:⥗^׭-3$<&i&4 ף9'fi׆rɬ ۷o?$xDIrJ3 ^N]}/~cǎjv֥ĂԹbŊ"3~IȵcD$Z roܑs4Ig~NT1I8ԛ%.bԓfehW @Tпce @ @M_IDATHqݺue˖-exxx} *%Vu)_HPi<IЭ|NE%=f{9N_׫b*% k%XwvŹs?A٧zjiӦrWw޵^1N8Jd3H~iK~[Rvrr|7}۫sfHкzs'{]g뵣~?OUwNvw~~|կ~Z ׫ֲs]ʞ6$Y}|x'/-u1㙙rHH)-I: 0_|]_ɸ[qksIp~;IJ$d, њz[ss @f_ @ @_˔8j %H`ڷn+w~k_]V|~[ߪS5Cιo?%Q ' ޹ =-3ǎ~z[: ;-ef]HK^O=7<&Н xn$˓o~wyc^Fsl.zouoPf^gv.7ܴ#:i^r:ALþta,E~J&ȝiWϞay}OE;9&Ʋ{?6d: g}vK]~s;rz,P5\S.sg/9ڝu ' e8>^>wx 7O~U<. v\Zb!a{JbGt<͛_9sF~Dϗc<\sύ$td\r^*3nϯڗY2Y ~f$/"׫GՌ6$dY  lt @ @@'~;Sr-](S'wfaK)I -Y 6[*%s6eLdK[|ͪLe`Ve .;ergmܒ8LI /TӾic֬YS=&x8{&>K$ }dd[G˶wrL#I9N f*Sg4ѣ:o.+mMsN\nWXJ:{$E7:S tcN;ԙT䷗z$9.jc>gBzn,$$>>O|i׶cr ׮kݕkdr]M2'Rﱭy{oN-luci>X:e;9&iw0؜DېP5=`SVnicڜ=O}SڞϧbKrFMu$@$ `FS_ @ @H($NXM4W[}O9'z>6v)HHʨ^[y睲f͚2gΜ)h*  @ H*1# @ @`,XP,YR͛W^}jƂ. @I 7xJ*3g LӁ @E_ O  @ @*0cƌrI'U;K/ԯM. 0%>hٷo_9s$)iJ  @ H*A% @ @`0V\YVXQ%dLl#@JA [6lPϟ萀A* @ mEUV><_v'@x衇ݻKf͚Ոvk$h&6 @ @~zYvm_ZmVφLk[媫* .:O:- Ӣ#@ @tQSN)guV`e˖2<<M_fYxq'>Q͛B I ,M%@ @̜9s9eݺue֭G)C={ʯ뮻{A  @@7$tSW @ @.|՝-*YC'B-$@/iӦ}rg .,?dZH TAd @ @`z dWJ,0O '@x_[nd; yY/R @ @8c_^vUPL9]  @ ܹu]o[ٸqcYzu9ꨣ]@QL8D @ 3ʊ+ʕW^Y͛K=}Aw.wqGyg{.첲`鍢 @ h @ @@7 ?rVs=嗿Ăn+T w˖-[ʪUe-[6mR9t  @ 0g._|qٷo___,0CsvY [n-K.-zkX$; =IݳU2 @ @'sέAHA ~__|s-=iJ @@{rwrin]uQ]U @@$8 @ @ 0o޼5.\X~?W_-]w] J> @ ٳ(?ry[nz MM%@ H*hi7 @ @`9sʆ ŋ }|ϔ!oȴX@R @ @O @Z~e͚5eӦM(O=TY~}ꫫM= =&@@}OTyoV$#HY`& 6 @@ H* @ @@>ʵ^[>r=?O+=P G?r '8i @`J2+Ν;?\-߳k׮wrǗ7K/z.!nJI 0$Lu @ @`pY53kUW]U6o\%ٳH @/N,gϞkW^y ~_*ov[m%\R>{ӑR8X@R&!@ @ ̙3N;\s֭ <+3s̙gYnq`0tL/wuWL=I8E]TV^].\(`FK @@IzxE @H$ ̙3,^\qݟYzǎe_.n:wLEtB I,8If$XlY93ʒ%Kʼy7F􏀤 -!@ @t] 3dVY+guݢݣ 1 L'Y@IDATxwUE Q $d$`8Aљq;檭~Ufęq JE$`~|z{svy}='<>ۃ)$NďxfB0D`uX0C׀%:+" " "P_~[~}άSB裏vݺu+dW#"!ߺ?}Cus;QGZ@سgۻwoP%:#@{vA)ҦM'|}!8v va3k?<qG@D@D@D@& aFȴT/r-՛ALD@D~_W ?" ݂ {H8݄ ܙg; ;"0{l7uʴr+" MHwy[n]J0\4k̋XY7o [{ZhC¶1D@D@D@0#;#m!" " " " " " "Pt1c۲eK*o48S J" " " "|׎Dt 'xkݺ;]۶m]vg PtbHk_z& aF=׮&" " " " "P8V^]y1SNj̟TD zX? SHgȝ6G}m۶5C>Rg:uZjZl#5X@& aF|]D@D@D@D@DYSP_mڴq7p8qb;k۷ϱCQ4  uN:$*J" " " Hԯ_D@D@D@D@DbZ<T[|̢$!Bjo?19 $" " " "PZ`v˼yرck׮QZ:@ 0*IYO2~_UMVՖT5=zqƹ-ZO?ԏ$655IUW]ٳݻ7!sѷ" " %X'poC9-S}ʇhs(@S& aFS}]D@DI8ݯ~&YvZD@I`[n))>GG3NID@D~ T~Id" MJz?5QŊI2h~ٽ{۵kCp~;a4ef4WE@D@D@D@D@D CHHxkFM2;C9?#]fKXı9^1!Tผ?S \<uQ:%KY!JgŚ7o9-Z;Ο/Z&=Ŵ]ѩl6lO[TE@D@D2{4Jzj\D@D@D@D@D@D+DPԌj!mG>2HBpЪU+sLZa)ӎ?T{=ꫯ娧3kq Qk׮ ;vtoԉYƋ:=XO=mنrl߾oyfСݻڵ?ܔD@D@D@D `/ .,ޫgN*@@ ]" " " " " " s`n_~ =gn:/@(&OF:̪UE/ 0zhN;-vo2k,3Ϥ~GLr 7 &~`ٳgg}6W\E4Ͻ~z /KS-[߫W/wN8!kXcx^FGX1]20 u &LٱqF7c xb|G901EK>HE;eN2yt>t;[oϟ~icǎ讍'B\g袋\֭m/ĉd# aF6B]D@D@D@D@D@D@Ν;c=͛@мysGE Lu´&$?pb餓N;8?ŠbS#!@{Ob=#|(7AFwq3gӮ vh]g޽~EͨT D@D@D@D@D@DgѱE@D@D@D@D@D@D@-0E@bnРASNi<8#?,_ܭX½۩rСҥC1wu֠L=2|p/ 6 A@wTȁ\ݻwGyėE~ʊSO=aNu"l߾1 y${].2%'8SN0# aF:2^D@D@D@D@D@D@SmK /a%\&LN<>݋/}Qe;"mۺkֱ B?(ML%Kspr$Q+}su={t͚5Ke1•ӧ5kָ/2۶mܺu\&*I\D@D@D@D@D@D@D@USʈTG(Aes'IWp죎:ʍ1"VaF1zh1i:>cڵkDH7n0eʂ ǵD}H ~/o=\v܅^?F6СCH$_{5QH$J" " " " " " " MMUjhD)(F! 3%CT ')#ZjlrJhlذܹOc'+ybѢE~SNq#G3r檫rǏw͛7O{Z#C l˴&O>~^kCЦSTTD@D@D@D@D@D 7|3f ` !XrIZr,aڷo#Eiǎ~[EÇT?cK0رc32,GyѣGʯx {C@ŒS*d$i&w-;3vv (=XǒK"jFt[f >(&q_|GoXѳgO?'\AS))~rMm۶u;vtGqD]vXȴiӦ ҈$NMϾ(" " " " " " "+ a \S| 6%a|xVrLaԮ't5jիWr}@N::uZn@pXJ^e!0aǒSL$" " " " " " UI@3Z)HqX„"DE?mKt':B[oe0݇%:t2ds-%D2>Z*{X+@&fdD@D@D@D@D@D@DoP":G #S;v4-[K}駟 ˗O]#Gvڥ۵jo޼kѢEQN6MKO={d" aF&:MD@D@D@D@D@D@"6mڸ:*U-[Kw}7pA={v۶mK4&Z{cݺu^硫p)L>0 K ?Itp}_foݪd" " " " " ">u-q8c}w%/-^=n>E`4[nu3gtSvu޽Q>;~ ^{-BiLnnLMx /8S|`WvLacǎutL?OBh"a;)LN=Tևzc;!R֭[իΝ;S "= `С޽; .eر#u͛4 AR?{ϱ Q3޽{tϞ=M:p nS0DD@D@D@D@D@D qxWϖ(" " " " " " "$LUN!a;1IJW_e˖E5UVnȑgϞIc߿W_u4rM۶msoaBtRM“0o," " " " " " "PzQz:Zׯ֭[MW̏3X]T&7o#<{~ qbpkGy}4 HCuC qD쨦+Wf͚DQ'7(#3L.]t0o" " " " " " " K@Œ[LD@D@D@D@D@" cʔ)yk 'ڵk7ovz!/Bի;S@âe1C@XlbA^zɟ!?;-SNaKX,YuW38óg}.sń ު6IG}(- 3JWG!pi&5AQ3jr( {.>p/BZ `!C˖-'?tu#] Hp}56`G}>O>)p` :x%3<30A B'k֬q?۰aC"O>^#" " " " " " "ФHѤ[U~{w OZ& ʸ[t&wqGeP9*@߾}Alܸ?SW_9=! sCdѦMGT0}~z01m B^x!:g={#L@(7|Y^رOrYgy1 _w&Bwnذah%JGh/Lĵ$" &1%bA y޿΋/" " " "?z2ϏoŊ駟vof0رcݘ1c䈍ҿc9\.hڻ n2dzٳѴgҥKڵkݸqW_툢 De4ij' Iɓ0- 裏v:t‹;wz<;c|D.LcM0ر?~ѣGg_'&L?bꤘ*@9sL%ZI\U%W6D@D@D@D0_ " " " "P~m7}t/ΰnݺ}>D~֭kE@D #Fؾ/b :#ӓ\uU~;k޿)LĠR&pAlBd$7oN 0(H`.sGLmk޽{ 35RZ @ٳg7xN?4[D@G#%!:餓=,E%Ιds=VS)g<@v|{@ fTu>UŒ-D@D@D@D@Dev;4 g (x7?/^wD]VM"]9Ѻ6lp֭s,y bE]Kd ͚5K <gu-c&!;8>feFp~8qcT2uTŅH.J" "PJxMx3ݡ!}$@=æFF{zs)r]sَTʙ~ȝE'f8C6}"I{hK :\&%(ve7#9޲eBD@D* 0s=/_e.rw'|̔q 6m{홲:PdРA^D;DS߾}>t tP!8S] >-b LKpB?u3K8NmۺC:RLT~G~tøyrt[uPn&; "DtM%MYؽ{[fM7sG y$(tHMd:P&s_~[nMC=ڵkݲeR+p27z 1b@ İi=8Q|f X"V`)uBdS "N"eYhTW\q;}9p; E(Y4CFt޺4Bُ-D!F?S1;k׮UwMཆQ #T7B؟=;{qЅ3AǦR*D,@0kXR?K$i@P_nGF HɃ}PD'#ūIAT .+I;v1:tlM2%c EN1ٵkW*c'zGV0]bTK)e @e%h=VJegۤI$Hч|ɉ؇mjF-ٴir 3$0c 7gΜRnܸG#\S'-JD@[7ʙ@Jb! ڤ TuE1c|"ǐ[(%H|DPԌ$VX'ʄQx (ԑ-<幔) [kH@nIMǪ jq7)h4D@֭[ +,%v8S"*Ԟ" " ' u(S)1F(u y뭷L [Z#s0}^XASg=,q֭[;(@S @jӦMS(("PǻVID@D@D@DHQ{u@I_DKV&r``iVLgȐ!~m k֬q+VpDY9,R #u0a;6;OD@D@D YD3|S/Y¶o׮E@DHQ,@1p"h֬#9Nj-S#1o=Ӛ0uҡ{?܇O7̙3ڵkS!ڵ;R;{:"g(jF׶'" " #Qݻ 2Ƥr3@$ȗ(F DoʵL0?$:#^sMtRq)_^S%;v6nܘ‚swڴinݺuns>sg&/_.]?rQF={_5gy=n믯Vp fl7|}:g;l [cnc!ar,l0lLbl!,]Ps " "P$̨ZSE@D@D ٺj*uV_ZtEήYfE,ώݾÓ;>m|1":qzx≮SN~TgN>ak׮]^BK'ý3r۷mذ-[̇tzˍ1Ÿ|)?BZ{Nh\۷23DR][ɵ[F\D@D@D ;ݎ;2o~mp0`@lt6)Nlut\ 4wDu; sqL[d{SyƉ^xM>=| q3j_'9P=pʔPY?묳 Y&0e޽˾A^ :]s5ӍC??^tw, uE('"9S_Xp}u]E8qSuL~~Opm/\09'̔!N#>׫R@p#[ayÒ-γqƹ1clwȗ+؎s;Od2;.fsڶ)NG}G _$sA(x'q6q;<}`fѢEpt,"GM\ qek;wԯ#z\ʓO*w7DgKq˵D}RsN.i ȸ8QnAftJɅx?RI>GFBٞqǫ; &^eʇ$AK1 [(xTo$FtEpMkGTEI^0bV|ncI'D砍4gΜFBLf?W_} 0*# \kZԒ}{Dž'yҕ:;&HwX^u% _&W׿}{Y² 3jƔ_h"a!-Du;g!j:qDN5Ή g.Nӊf#ZC>zt##J96{DӟԀ9#G3?!RwDCM7T>X#@6Qcs)tBd=7 u+FYKÎ+]u5BGa-Qt2zk:؞|J#{:5|}&<#!έ#$A{:WMgGHq$!y\2s>oK‹"\ʪ$"PyLm@4PڀL#on E/Lya{L83D+sҦe+fLljW0#?3v<=v% Iƃ#?'kX 'b v":vn؆ov6D+"6/ʃI";BpZDx44ԛ%mMKM(uiuej-Mmz%Φ,;pNL`ɹ9'e} S$[sUZځ!ed1,V>~[.$h> aH{"_ny %aFHCE@D@D@!KF*Ga "~\7QM4pYbDL%1;$F3zltZn !K: ƙh= p7M40nN2a•ytY yݻY 35t#5Lo&o#;8IpƑ6mĝ:ud:ϖ0p}it~؝~$X|xu}%ؽ؇6!+cbcYž_rwMϖ-[R "دrcW39ɄM*/ڱqdA^ΝBgn#M{D40<3sSunf8YkkGq $zwװat;,\Soܺ)1DD:D$}F6uA(\gܛj]Q) a|!LL'~wu8ԥK׶m[.>,#-ʄ`:4zgz^1AgI?$%397OО =B{e`=CClSRg#6Q.d\ʕ!m~ϧ<3v)e^^!S39AuwAQ$L'#XH4j0~58Or# ct>dAhqF!z~pi0W. \c;&8&axcA#-8#JVz7 ͎vNѸNh<0цvZEsĭa104p40] uc[:OYH8i"8pm8 B¡)o?8 lp]%c\t^X2dH`D =#+^x7ix*6R#G]κZg6J"+ĞkI:s=wSG4%lFd6m}vB:vjl^{[|:9vK\ӟHs9&"!@-Dm;Gvcǎ?1b:ؠ T?tF%D& u $ -3sLߩ`b:k^y'1E<(+Lm{>1=BI9DX// PB);fܺ)1DdD%5",Ҿ F =MULNw>¨qolbt39w!fLN3,|eLso lǻm w8&{r<}?O!s?7fA0g[R}L0<_\yx^r%^G~z;}G9z`&ӢM3\ߴĵ<؏ͯimmuH3W˻4l[[v[:bػؙ%//x'9s1,w+-_ZZŒb j$@5<:T9S|ey8qۡEc#eɓ'{CcK Ƕ5S:(7xod259PM@+_&ʠA;oZlo۔Piи9Og[%S%rWq" h@fH]q:kcT8!qEcttmhSDt1.[G #`qx9v&\ڠX9qT%(q6%ʉ誫7=~Nfܹ6y@ hFG?Jټa: _Mi.Χ I'9ӶY@?Y逌~g%&7ag'I:7N=>tl❌~ā{0N௱DԪr~M8xn>cgc=m|A3?7!e )"v^{M _sUZ!-W!h"b&kQsP6" " " uG#C,t*eN82 |e0~%qy5+˰1z!81Ӊ2c0sP؂!M# o8-/g0:"?`4iZ8g#OD-Ր*QRu ĮO}11DC3]#3#;EAR@(;-NeL`k4шsF 1rȲB8uK9M^>O$:)H>^tZD:h!?32 f,a iIzݔ0Q_n_E@'/#Qy6#Ovx Kk޻"u]~޷IxOk |âuFmI$KtTOK,?2J=oَ3 ~etr(ᙍMDQo2Ag鸔\Vhԩu%?(Lavﭰ\+!eh-ܒvJr֔=!"q>`jzԹE@D@D@Dh+ u"$l#sZCH|yцPc9F,aOX"D Lpy ),"4d Fۍ9(`B&Q&n$lfk53*QdӸ 8F% J%u8xߕۙWܪ)hvXS\t(6ιvb8WX LeI$Dމv_D{3!&ʰɃX5'Y&aFSc؇N BVZL30# Cq78?;9Dωg4.R,C!+DGާ:kٞK ('"PXb o\"ZufÂhP[%? #Рh X⽌2|# *\J;e/>DM>s$Dڮ#1i 3uJ] ʬN{B} J'I>W_}CRR?6 eHfwSK< t!!L68y5 YmpaQKf/" " "P0!qi a#J4 7D2naCK3ӗp\=棑hoJ/edӈ$;!-ѨIXR]kDaY%hD;iXIs:հ>QcG;ٳgڎ1#_H}DMi.]b;fR!Q:+QG:4et3%G e0tFϚ5+q饗谙<'Zg"G,LˆȔ*(>%! ;새[1˱2mCXiD tSF }&"ĕgʕ+6\я~? ~޽{{AQ:W%UE9[lϕ)tF* pH=ܦK˗/d(*D _"6̈bCDw0r#ݹJc%`d5.4 s@C[6zu;1s0TUۤL㚆, glGQF>hc']K}2U5ACˮ'< /:]Ackȗ^tx8lhSHߊ4]ط,A6 p6a2Ssgۿi`3N:̆ ,ak.uٰ)m4"huLGm]ٛ`)0 -NH-9e@r?|gj(ޜ9s7蔿 kرc /a QDVY`7n\N 1 Œ1!_1Sin̙^-y$P1&j' {g=""ֈ{ޗ\V6vD=cbk|5RϹb5Ϻ 6䟨\rIu5eP)(#䅏g? * Wg 3 }D@D@D@F*4X0pNZUmXE hP-<sXl CXZ(h#BPϔzu' -Br-ps=~ t02‘pvc='Y&Dvi:eʔ$c@ !3誻B7aC"T[ؕg٦6| pz&=-Xj;@t0fUTL+YeNw:[=@8W^yŏ&L;ٞN੘k_x=H28:& |9"㙱qԠ~Lw0}0NbYtF)۷{[ 2 whxgR1b7~moפfP7 "|QHXҥr*~ר]cT\UK}|ΝCgg(q%^vm#-.\֏F6$H#" " "PpbeNI+Dz 2sE\`4B×41cFbEaQn̉FT u }N6#px0%vB0Zr Z#QS5yMe\2 44D e_a`Ӊ¼=[ޑ&6iҥ%D{ z{1|O:>|ÍQD Oh3g[D=_"P jߧGAv ;i3X#:&[I َ {x[yg!eZ/FG}^ VDю"fd'r5L5Ϧ\+JD VM5$7.Yk5x`? 9}ψ3no;mJYŒRqE@D@D@jDvD痬BI^KhLNR TCs t>!L?1:8(-,D!cDuFBQ$rfnc3Qcj:cli*!Q2o(N{פTCQrW{g'DN JY'!JMF刢W];I] :4&e)wLq7(Gq>Eg]-`?"UD#,!,bb"zD(? -Z= 1IGH<~@!z;Kpr+Q)$Ce|Ub4pFш}Csx`ReT[s{~NV?BAc)p2ށ[:'Sj9~Rmp,}~ʵ;vmlZ8gB̲e˼sʖK~(gXۇ儖.v/'Fn2Y2we>SZ 4hD= ޠ=p7:控VID De˖>e> r7ȶmܬY 0 %9 ft)$ڵk3rC Y=4(g9dؠt1l5жy}478+d]sUZE\!>kGP__݋/9o[4S(R%0|Yyfr~{OmRDpcb-aF)" " " UM"#?T^iX`}2S)S?Ya-e̡Aw8Oq*6jc:58l0řAݻNÌy+妛n!֥#ө_~a^" " "PyA}Q?tQo߾V!h=>S?@ 7SmXǴA 9rED PyF?c}}^K#bkQ0|ZtzَܳGs AI'©ڌ8aiLx>!(VԘK8?G'5O?B R /1m, ]\r!+DEprNO5aj` @ & ٗb'΢q32!j,aFԄ!" " "P64ΝCI1l1r:7a8h0 O PR@᪹sDx@\>h ?b TYŋȨoPD@D@Dp FB[ꫯ#KJph(>jcF^ L:9gΜ9 ۨt^1!j$"P<_={~a?8KtTc&ƷgMٳuVyX1z^d.w'ϨO?ѶIߎ8/cX#cGװ0L}.׹Zt\¶oBg [fELLKyr1we$z]MyeRMKQMJ~0fw̘1ϔ8sFZbF׵P4GC?rFk"8B$" " J]&"e\|MBa=vF=PZٱcGޝhHQ{ןr\\\s5~ O3QzTTaԩb#/S;J8D7&2t$ 4xFϰ|ga?aÄd8aς_KȞgj>QG'?` 3hڵ7i5w]P˩꘼.yOfFp7_5n͇̜2eJO@MÌvS*쁨M햫@goOF6D{;,fԈgM\{ kR3K/S2~ZD0zTt%{G F3GN|A4D ΃phqSm_w(ڵkvn)$QA)¢T(_<ˆCaX=! w8Sa#d+>pǽ;m|:W5^gyիĴ^\Lg'QE/#M!ɖ>~b#?f [$̨R>E@D@D1@C?lZ67.DsM|QuzVvM^He rz4|=q=H񃡟jfTkuL~ &3YI.p_}.Bx?v%" OHkw^V| њeF  l8[uֱttT :u5U͌jiD;FH︎j毼@5@ 7c4ޓLV訍j Ie U|aD+D* Z >3D O$t% `!AeKӧOwWYAxjoKrZU83 S[oՠ{aQoǯ4CH0gegXKZʬ*" " "t |*i "5qJq7$D`K^Q={X#W}-3q 0Qzҥu: B>s K8G$HGMߋT׭[X+v<| 33;Do ZFW tasBw-` D.8vO6#|I/^ȶ}.6t00pB/ՖhA-$!kX:&R%#˙:ƐD{ zχ@q 7dȐF|C3fp~atС(B$̠,wVZ[<ọg :2E5=iҤ=Z48C$]W!Hs p6ٶ0#!." " "P5dj}H!0f5&WO?۱cGl'2E( Hؑ#G6A=sFg&cy(~a7sXƍ`Taɒ%>d`ज8RV5kw7uTb /k4rm0ޯ," &`6MtyHZ5_np>_\lz(g5Q,aB=vc`4Da?F#<m{G?^3B qcP;_ [-7D<zہ{0{MVJ1)S}Y/@s3.цz3gos]$"PD\=u#D),Syr_i-%maP|!: c}uLd_$*Dc}lM]M=`[ϟ 2t5y,zŮ\x g)W!*A<,aY r'r-۳ΎW굄&㋀$F !"141zn& KM7 ̿H;#aqhlaadŠ#NFAËi< wZZӘb>Q Gs&,4V1|qdS~nd\Wf6lwp~pʇNZ6z1 RNHSu D`)5D$ ~Ŝt(#٨$"-:zdSD@JO l6KhYŖ#:g$N yoFr}@Ad=l9ny,/&.$s`̴;R[ 1mjBg#+\SZ6W kҴOXWѧ #0`@}aZD y< /bC3ܷDw` `5:}yoJY1!8DHO(Bhye9'n$_?2qLyePAvth\fKe;wlr*E10·,c%r#D ^ -qH P̶OK$MKD@D@D0DiM0 p]v))p@ZcАL7U 0b8#t箥ϝ:ur?я|hKz֘r$cTb82~ Ł<n:I9xJ\8sBuLg >Ca"tpjP'^xen;}<Gt6s"P':з" $H,~lp獶D# :]LWޓHYLt;lbEMTD)k(fNWF3n ntu*=uJ{0e))kRyc"YTIhS">|o#W(K.$5PA 9}t7yd?؂FѦP.}lFxV.b_aH2^2 1ð@tY!hb6ỉ@K^G٢ly|PD5XqI+u\{1>i$ E:W%Udfx?ڀl}.rF;>T rOwMl^0\_"l1p…>B4*$̨uSD@D@D`t2bؒ%K|#* l3:NALO~7c o3J'ݱ؞LmHPO3نF5.jL3y82bfByLc7]JECV77p]&si߾}l*2YX'q\Q: !B}“95²/Htp?}N]$" "NCFIV*?F!~gkam9bdNp>c}oSg]l]8~C{{&zִLB{>Xc!G=;Ia(;{LbD` _A9yCHBgxqKox3I~F,BP{*wS=>"T #`c1{6Bh=BNDO<񄏊D',uUkD(W͢5C mIcGlS"τ")~g*ͨ/Ƿ|N!}?i$>_s#E5ikO4z!S+l?w\/2k6|J0tul `T~4Dh(ac00!H9 38WaT( F 4x Tq:SQM`WCb|;~F50RF0,<0VY v fx0 4hdA#(!\sye#\W ~81Kud,o; uE{^:ɫ@ 7 ,[ L/"fo ?W\S Ǽyeٶp>" _ #x^H\6> %VN[2ߒ^\Gdz Oɼ7`+߶>6u(D^i B<"P1Qk<\ԵJlKO\Z{sOz'Q ۇU,Csk$ hlz# `wSbr X\AxoҞg{_,,aFĴ@U ^-#lHϖ؞081 Qr, :~AJ#X;)i; L +MyO)zL(o4`w5x4/b^AYhÂ:'%'6 uF.<] \6k+02,>n%/Xh#򡳃k|`f:q:!}j Ȼ ;HQ3*W#v/hP/qιKαYq"8,|7l/jL8 D>noyc[ 5PXɨ[blmq8a 5瓒ds=W<':Jdf-J\3c7n_hp-% mkpe_" %<}d&P *LB.Pj93͏Ļw ;:M -)y"<r8S1(GQm:GyE,A=Xg3>21g:a3uW{*"#:!r db[ťr*]I Xd6&A).0 3|6O]GP~㾋+0B $F<׿ `}̽~mk6z$SYt (y,&8Ӎ;> |N&ejO4Pb"%ɂ $ƅ\ '21Y긥c+ %`c,J#=SAh {:hG2K>sa] 5oJIS?_qv\,إc?,J" ALD9sPX}>m4qe$Dy/ % b@d>?ޗ֡i$9r6x&M.yE,KO<Nhgtx^w!.^k~fZ_~{kvN9>1m-Q?[*׹}"ꪫLEÔ/qŸ=`)0L7 e`i.œ r/} " , " '>t%|2% glZeD2lKt0-,_ψmgX-Z9:'e<1I޽}\5yur_05 h p R'\pDcNͽM0Pud eH("' ,zgy?ϲeh,\0LĔGk^q*IQ*:z 1]Q$aF)" " " " " "PCp1fP)" " " " " 5At ݏNr: Mt=)rZcܹO?m۶-JpCYBO:D'6`ʑyr\ƌ{ʎn];o9ZQe'ij 1KRbq:@"nswuW"AD@D 02ЬD@D@D@D@/_KX.1bĈBl{E@D J@3D4adQjLP"%*qVаeXMֹ̳1 2b(4.I7 |6O0yZ M9!}zg-1 h53((;\^~# %ީS'ΙnI AB8[*޽{bH:s xwb  СC"'bfsj_/Ch: 0˥" # aFXL" " " " " " 5JNgy/VF[,u;{lu>gϞnĉnȐ!.X$V\鏃#8Wv>}|;G>kow<.Xw2¢ǚ??׀4tzhRav!6l{9+6yҥn̙!`P7z|qѹx(sS\\?#;{|I7o>#A}yUy@9 ,D D" 3 Zv];[l[3Lmg<cl06AY"%@ ay}>Rϩۺk DRIMe< JJ%2N1d֭[x.lRاO~D(XABI~lf,"PB#Qgk[$  H@MxBDPMx(={fdi- H@$  H ۿ۲40?yG Xܯ_0v0~1ƍ~Ν9k$Yk΋;rY~}XlY1cF:thSm @H0Rcƌ 3gΌAh dlٲ%}Ww4QI$dȑ#LJJ WYZ2X"nܸqqL3q đ|`ADdӒx~|dp8T>Hj! cN^z{ g,A$  H@Ea*Ж5[[k#\zD0MZC@aFky$  H@$ .DC[o=%5]zu43 8ɝ1jGJ;{}.M.& bs=Q\@Gq70—a1q;jԨpuD ᢋ.j kX'ȽHKWIcdm& "KՄ!ZMxDA$ H@@i<>ѳĉyѳϏ<晓m,ŞMKQ TG1ryf&laLU@..D8—`L|O2%̙3'z? 3Px_n$bmM)WL^xa^73aJmՏxGԏRM?ROB0QF6t}-^Q& H@@ๅi!y !1y ɋLl^]dUi^/ H Œ|"- H@$  H@(t#GPLLzB}0!l>0i)& 5ϡC‹kBV`ǻFJ[_3]N'| 7Rzw;Dm{=0Lm-FO: kJ2ҵ3!Kd8p ^{%mՏ|Q3}~J[.#_oOǿ%  H,3P VN # e!Yc) eg)iYm=q}(E3xRxL@fT %  H@$  H 0\rII"B90if0L>ϯ( |/5ƍRDpߕW,لnjRŒ۷l2dH`4^6(~)i"]eሠ!Jy?i~DP#eFqرpoJ_F[)tu|1,7JFl":> I+cjk H@c ܽ{Ê+gh$  &s$  H@$ z'F]My0g:餓5I{ֿFҰdSGIL OM$ F'up=e˖5{it6_$=f8$  H@ FX_yk͕$|͎/%6/J5xy0'z *ZN<0xhƨo*@n i~tD?2$#TW^.fT8w k! tmxj&zb}-Sz&bp,Y򆖎eK$fE$  bu]uTc* H@@k 0 :ujXpaU}լ{F^ }ѣG}U O>h{ a}ԓ$ H@$  H@(F@aF12$  H@$  t!ؘ1cF8qb׌-[>;6;Sn5 ={,~ ?0r< 7N;ȤPybŊO6 c(cʔ)En4R?>( ! !mo:+\r%O,U9vUWSeQ/ϖ$zU| H@:Œl$ Z"@ᄏd]$  tI뮻dlT@pxbÆ ѻB|Cq{̙3LJ$*\Cбqưf͚(,"s9 5d xy̙&ѣQuEQLM\?? 0 ..Ȇ(CȦM #uHT}]TRd%%  H@$ #0:J@$  H@@c ƕW^]5!͛7?z۷o6lX xWؽ{wtkA4j ɨQO? T {KZgB1f̘p嗇ӧ~(8Scx:a)֏_*I$  H@$  %0(H@$  H@Dxe֭[xcX DYc<ۈ0XZJ 1bĈp'tznOY3 H@$0,ܒ$  H@$  $њcx0 l`_%X>UIJeק|8oܹO<^~cǎP-%AxU\pA gRbm7Qu;3 /^V^CIdQAؓJÇu; xEEN~lݖ$  H@h[;; n}-ڶ$  @tkrZ H@$  H@@%ЧO0u԰uր'ƍ+*i2L2%'LJ#GVMD1EO (M&L֭ /b mk׮x@T%ޓ! cǎ 3f̈ 0) C()l%dҤIgϞ /0{.,_  jBU>Yˆ&ں' H@$Иz(TA8^PRHDs-L{zڪ$  H@$  H@$  H@$Dwމav|AXF<6IQ xoV1H@$  H@$  H@$  H@@ (E !<6,n7 .d];m$  H@$  H@$  H@ɓڵkÇMX#Gn46Aq ?~vєƍ×MkbCaFMt$  H@$  H@$  H@*%ž={ѣG{~$?zyoT].T.XT$  H@$  H@$  H@'|r>|xGe+]'NU$  H@$  H@$  H@$  H@Zft5$  H@$  H@$  H@$  CPgX H@$  H@$  H@(L>'tR8 ^ H@uH࣏>  sݻw={  "htXڷo߸4'/~Iݺuy}Сpر=z>}4m߿ػwo8zh<4hPd_#GsSȓ:ʩ>|8۷/֝Ï2[ci9uAYNR)O, ~޽-RëzԵ}Ā"?>~#7gC_(aիW/$-yo VScyw^;ʤ/KSɱ~*s%  H@$  H@$  H@mD 7[c6l$  t ۷o[l V ֭B1=:uYaҤIqFڵ+<3a۶m1<Ι3'vieR9ׯ/R30Ο?? >On(42dHS.|ߏt;OQ//^ |_}/t?S40"(ovz=֮]oFo:ƹ<1_uRA؀gaÆ:'x" h/s9=]ShgډxG ;wB [K.$/HxUԫf{{'$(Gxq{ |' !O(,/~_{, ^>6mT 1~V1%Y<_s!"âEZFANKK4RmRQ-9$  H@$  H@$ v'|1s +Wl&۽  H _ /e2Df/8D ]w]\0V@_^}j*Ty?#x; "tPW,>Q__Ë/^Wh >,X B5 S_<@?+g_RιSukr [h_9ulٞc1BkWtn)KaŊ1H|0~{1cFBR>-!Z~vݿw/m-U~ SO=UC //Gqp^qf$%  H@$  H@$  H/_b;S|Xs B@V`OƱt-q/1L"8x` В!3?Z+-2R*1Ȧk*YO1sk<>'ߨ/OGH-%ƴ{^رcGn@<n)'l.`C8S{Z뿶lk{#GF E€p a~wuBLG~ oO~%TѷN$|hm-҅~_B܄h :}S2 (6 *)!Hp]wPY霖{'T%  H@$  H@$  H@%a޽hEo\W2_"kI@P L_gbQFEo@1r!׃;.1'F6zݦ|(!i$BA`(>|xN8h5k9={ƏXzoٲ%lܸ1G$A_a@/%9-R`x`͸`L7a]i$f8&OphOǺ-c.k"PokBd?C=lBѷofz_,n СCĉX^McY6!>^پ}{zjS><S9k ~61pYgEvcg>=+ʀaXƍWYK!ڳgOSܫS&Mt e4 H@$  H@$  H@xLzo|D H@0.?2!31_b4bOP|͗\])-^8. ˜=7 D3&L_~y\N uð!Jy͠? _nݺ2~ڴi1l였}P~@1V3ȧ#R_[¥^XRBկ~5s _k/:cey7@ؑ%K:ޱя~ŶH02 O߾f;jRJ[="%\/D!RBAZhQ%7=\krxQ ׽=, H@$  H@$  H@,mvY$|ŊPZNɓ'7Qx 41t%S>Ɛ^"p)Skued̙oF|a ٳg~o~p Y// Ɋm Hedی2Fb<r|f/_GZ뿶lsGw{eqg|+Q?/qOFo?>6lH ;)לz*GGK׳|s~ws E=^,zxȆh~QI;y$  H@$  H@$  H@h_mq-~q3t __Cj2DfmxXDʧwuW2BQ(d 5$D'(d=ekx͟??ǃ`^zcxܮkvvXh:W^8`&~5߮B|Ν%+p 99ȏ>Vk䗃 PRl^W\qE mfTRJaF$  H@$  H@$  H@hC￟+Q.g?^3[kb{3茶`SdՇl0yPx7QQFljm]KVm" mY f͊0Ӓ@ qpFsg}kQR] 3zf!"Rރv.]s%oxa\D&nJۅڥ0I@$  H@$  H@$  H n/)}y܄8s\ o,7Z</)-ZJ(B1&QD8_mVM6536ٹIa'8S_[#B[ֻ=J,-2(1L|A&DrA/"(%xWÁrPP^9'J{WNlŒBT' H@$  H@$  H@$ 6 rx;7THu|O>OOy:ؖ3bĈfIRL;̸o.Un EST-ۑctqX0+/|S0`@AaF{k~wLҬ>p| B_:1 H@$  H@$  H@$  Hz<p՗\W!Ro-3G6^d&_×y v%ƍ+ڔGk]Z!{}5c!{}WFPXڻwo{kڵDQK_M^:Mo{.ȸ_$  H@$  H@$  H@$PH1hР61Xj^ކ y "J0/.\Z sCޯJF6;Yn?jBתKݻw3AE5 / y?F|ұB14~f$%  H@$  H@$  H@$ 3* YU2v @{hDxطo_cҥaaܹ1,IG!G`(JX!C"h'tR8qb\Jy\7fo H@$  H@$  H@$   x={>8>裦h\^zEB8y)1Dؒ8J"g?&Os& 42t3+^hZ+~6mZ tP%IaF%fcǎN)$  H@A۷o_O H/)9ðan$  H@$  63^TUj=\`?rDPMHjS_СCM$Z|K_o-[֮]ڵkW'D>CsA&La 2 ftTUsرa۶m9o3kf+h+# H@$ v"$ɦMrrgP(A$  H@$ "0dȐf L,QxPؿUSN9{?^fd1o&~9sfd\n]XjU;wn3sh= 3ڞ9J@$NxQw6nU\3gN۩Dxa}d5F /miv;*"5 '1ѯ_(icc#a!YΝ;=c! ]O>= qb<mgG'M$  H@Mw|grmŒ$Q x目aXjIW+W_}ua̓ .RØ4$  H@]o_=~Y~w\w޽6vI@] w> [ie¯~|N*$Acզ}?Ԑ0SYOy??ŰЙRvfŊq1e&/ Y0O|a@8ZT^ˎ±GxK3{l}-M?{嗣Ϗ3(;v,zo 3Z6O$  H X˽Suq׀L!1$^Z~{K@$  H/_6l 5qOp[@袋/~@4#?Oq#!F"B?mRA>kW^yeXzu">χɓ'G^2G-5uO4_x \Ӹ<ŋGJ#"HKb0foFK2v{0a_N;(L~?Lq2{<^~ᩧo}[á#FL 48A ~ _}x_es 3ڻ_$  H@m@1YH@$  \M̄8_DouՅVV;( ,IJUBOš7gܵR>m?iSS$  H@@!uZfK$ F"pYg}k'?I"~׮]q +nKy&)J |aÆ;zDEW_}ub8? \N8':ʭ{:&\_B`Xۘz'ǏkK4W̓U8+VB͒Ν{ vZ_jnK^Ufc!!ϟzx:/.4Ϊj˯c|JקQI>OEs. ϑ$  H@@_~9?/P'yo <S*aHAqnb^4y 7|0yb ,&"6O?&_^"{aٲeaڵM/Æ 3fJԁvRx>yIJ &ԏ妖X0AK&n>qG?<SqƅO?=L2%NT*P.ږx-6'38#X_I|AAj33iҤfhiGGqL Hf^o %X3I@$  H@ӧco^z8WB/ba޼yq%y0DXiL0X3" ̏0o>`.yK/4γ: `nd˖-q>yK#u`3όsO`lH%kmg[ևYfyFiӦ/Wؿ!>Jk6 }c]Jx`>7js*]%bewYXg|8~~_pF-93 j^~oHwjq>}o`%p_C1=qxƍa2-+M sܗ1˼e7շ>AK@$P_pu-4w_sj Z^Py86a >,Dx|)0u\H_w0,^8|n:aꯢ؂>čH^.[ll'讻*Y ^NxP$  H@@݇wbL ` dxڿ8ׄ؂BsNgЈ99B5/ y1.0'C||F: ԥ:p]>5ɳ5צy\Q %w{&9!?ݢ#֔_6)8UjK^-թfc%^Gd"j7O\Kjۓ͇9\~ߪI k~'[6*s0 2ҽKxh$  H@5CNIn{I(I?_$ H@$  H@[@RIbD[$DY*I̷umUTvkl͵Hޒs[|yS]TʹkXiݺuQ(a 5֬ےWKYXhA׹gMSi.~mO57;*$wT# H@Z Kȑ#ot&B Ty)2CO!8=8y21r ϗ)ahF؁jo t^Kk"PVگ勰^{osʃC)K>!;bkD^~(`yʂuI NIBЯɳX*W+b^9R}_}%+ K^&Q3SoT& H@$  H@$ЕByDRj|͏|1dT*ߵ$оf/_s$  HJ BxxYMNj!' |rʦ8/+Ǐ131'h|OիW7|3G0* @!I|I ~w>qL̙3{̘11L po@$ 0ɟI\8̋ ;3l }^P1\=ܢn1c8&ŋA؏TjH&(Bdƫb+" K0\E୷ފƍWN:'e&cfS>AA9M5*'?љK:02؞;wn׾L`4|0k֬p؟K, 6l5I@$  H@$ DsJx͊=茥RyzLh{ 3ڞ9J@$P0 ,ۥP*gPGic͚5!a?*I /~1 D#,mQgmʖ5\'z뭑Q7c4?0I՟L*Dfϟn蹣0`@l`w$0y(@|ә3vwV#@ML: o% m$dL~3I@A@aFm􃵐$  H +tMnJǨV TŐ$00ƀOQbK+I^|8Ƣ̖P".kU@?x$"ȏ0#ڼ[{]g/>f,0)'w§?pYgSO=55[;Ҽ^$  H@$ z$W^_qsƪUG;vxe>y6O8b B}v̙L@PQ}c$  H@+0"HǨ> %_^SP#_y嶱Pª'xb|!֕USg1O`RI$=q}/z!ϬY&"$ H@$  H@s9qIMd  2h$ ![?}eM%  H@hG޽;lݺ5pB 8x`IXvɚ9  C=ժm.*|-8Gr(y}j[zudذa΋㣐ǯ<)$  H@$  4"J9?r# ft^ $  TMa† š5k?E:tB\ =b C ib̻ZcOwygx==#AW%Kٳg O?=0I@$  Hx#ψ H@$ z#0zJ@$f`KG?Qx7z#d 'eR;>?A5 ^K3Qڻw0gΜ0|Nx;>;vlf̘~߄~;駟^k.O}* ;|7I@$  4.CЋW6/qn$  H@@PQ/=e=%  H@hSL-]4|ߎ9&0t0`Я_!Zˣtm=1|ͪ$ 7FX%;0#S>{_ΝVX?QxؿH>:6m &N$  H@h@ofw^G>ՂDi%  H@:ŒNo$  t]?A(C#y.JG+m۶w޺f m)#瞛vS} \rI @cٲea˖-aM?' rw3I@$  4&D$;V\Nؘ@l$  H@@PQw]f%  H@h-'<n Oܸ[âE.Fѳg@H0ٳ' r6ɓ'GOO$  H@h,~+ٳ… pPy& H@$  =fKOYO H@b؍| qtgۭ[5npya& 3%+7.[MMƛ͛ -4BBs>s 6mawTx^o3ߵ$  H@@C oR$  H@@h> x l$  H@u@'L/M B%9rd5Fb~s '4իc]DPĮ>x`ݷN2%;+H@$  4B1*hn$  HKP%FH@>xݻwNcfܹ3g_?ȇ|-%&W zNNlںukXdId0˒M]%Kgq*˸6'KV(pf/}$  H@$  H@@]PQd%%  H@ لW^*ї<s1\B΁?;V\~򓟄UV?9f̘9[sgo߾[sU3jϞ=ˣ>Ls̉YFP8Q+;ӄsƍNڴ H@$  H@$  H@F@aF$  40={K.$zb@x]4<3M ه ㎀ %^zi4%2Ɠ7^}0mڴ0t>  #G [l KYzȐ!QB[c0a~)q_k .O=TNڇ@f:t(,7tSI=cp`0p/;f86iҤ|&10[G H@$  H@$  H@ ((ŝ$  "2YhQx0 [O|MxvO䃡n!)6or<ſ1#袋ٳczfаcdž|+/R׌l:rH'R 3f̘.un-O0(@ SQWЖRu>FBOs=i$  H@$  H@$PfE7YI H@ / t.]х`+WC|_,/K|B[\ve1|e$wڟ]sNnb}ݻwϞaQA{u(RHK 1 Gӧ(x(ڒE!k?RJ!LJ5#Dʃu[n0" chҥcWfJ)SD6x2al$  H@$  H@$  3{]$Ѐ0>Nm^7A%/xaqRlS^~W]wUxP$  H@@gདw:>d}9ޗׯ_VZxᣎbL$ F%PQ n H@j^ 6bȑq)7/x$G9"Jms16,.[ȟ"k5c!ٖmJyn|ۻSWK$  H@$  Hc g!5B?. !m#࣌";wF-[¦MR}$ H@ht 3}~ H@$  H@$ .C`֭hedC$  tq'O{nV1"$`1`^#y@ ={>&aI$f4rv H@$  H@$ .EG sOj$Е go}[1Hg:uj 7| H@@gTgҷl H@$  H@$  H@$E 4(\za]6K$P=fɳ$  H@$  H@$  H@ @蒡CO}SaܹW^e\)$ K@aF[[& H@$  H@$  H@:@Ϟ=ÀرcUW]̙za[$  HV (̨՞^$  H@$  H@$  H 2dH>|x5jT7n\4iR\Qg$  H (p( H@$  H@$  H@ 'N9_~aqAAȒ#Fąc'xb}4ZJ@:ŒmQ$  H@$  H@$  H @(]tI[n{q}'mi1Eҷo߀0aƠA(޴ H@g 3$  H@$  H@$  H@(@ફ gώ" #@IѣG(kDI& H@'0zv^) H@$  H@$ #0}0shtHWn裏 رcqs.e6yck20bd,7}!.7^ːU,[tҰk׮HM& H@jŒk! H@$  H@$ !pYg&<XH~Aa_Y/fTSFŒݻwӎ#Gf$LJRaeo =nݺNf;g$  H6(̨~$  H@$  H@'0zV!b oʨ&uDRKJ}d,۲*'{TVYkUZzT$ F"0z۶J@$  H@$  HHa?:rB|%1?QF RԝD[vYF>˪8#'RZdUI-$  H@]ŒЋA$  H@$  H@mL#ÇBG-XBG1I 2>#KH$OR ]R82~/jgZoYɪA:$  H@|*pH@$  H@$  H@ȑ#a޽͎ew ߿Qe8p$gϞ_~ 2ҵ2kY=*,۵ʪ-$  H@J|ynzK@$  H@$  H@$  H@$ N"njNo$  H@$  H@5)ȱc_1P"G^a?eRI؏(px`My|Af}ݻv&Sn Y9-)zD$ D@aFWM" H@$  H@$ VzСo߾ b4hPEaG6 0)X*d,#hy-3dH*ZngH@$( h$  H@$  H@$ &~XzuزeK8~x1|0f̘D7څ޽{o=#/ ĻJTL+&rx?!֭[7n\2eJӊ3 H@$Ѐf4`d H@$  H@$  $x8zh0]F={XWe#@A;(kaɒ%7ވaLN:hDäIa0&EKllSVDפF,_}B8*—uxj~OqUhtUXn{%  H@h 3m$  H@$  H@@ 5 ݻwׯ_WqUHͿ%  H@h  3m$  H@$  H@ ەg$*'pBA$pJ %Bd1T fdSN  Æ CmE2'׌92M~6n냇v >h_RK:\Vu۶ma˖-a߾}Qs O+#F}c)AI{׮]I 0 m/ss8qL} ƌSThF{ A|KxҖrY:fH㊲G Y1S޽cQ; 1Wx[Fv0&DYC ǏcR (-+DùI[I-݃3+OjRe$  4Ꞑ$  H@$  H@@"C>lm+I`P=ytT"QR:bڂq2>& ۷G}2s 6bg̘9(VNbeăC3<6l@\wua̙M}1}g=1,,D Y䌩̔bjfM˩?d^&|MF$D dD x}Oγ}wxpZ}iӦbΝ}~G ?&o6l(bXe˖ &ڵkS_}'! mDDh,nq"|l\mݺ;0R$='#!Pfe/>(\K@$[fVZ H@$  H@$ ''1!

'}QD9f!!?Q#rx'8dԟr2ˉmOw_ZCY07F =SY&ocx$<:Q՟ѨȳlƊQF^/ H@@Pѻ}o%  H@$  H@zo!BCvxNx[e܍[1vvvہ<)A?ip@NhGl+VHN|ȗ:cc> ^Sԭ'* l <`e? 0yc@P·!u)o F`[+!CGԩSSxaOm>- h}ІԨ .U .{os%Q'x} cheFlU7ʈ$  H@ 3$  H@$  H@!1B0cm07etQwU !H}I'3gL'iBR~iի D~x2j yḼ>۶mKSfU.8"o!}$>PhפInF{K1JO.'pBb`}A7%p(㢋.J2 .G>Q_7ʨ+$  Xzo{͗$0x#$ H@0҆176!(ڲne?_c-Fc|N؀;#9‗_~9.A:6m K,)>$ *RPg^XsìY ϿOFie^r%cG;؏ߘ'݁U)Cx8AKgqF6mZqwՃ##xsܹ)l 8Ap,wuW I EQC,CgA"O?]\pF/?b؆6~*F0BP*E_u%Fu֥$% V U`???>ryڑQnJuXAȇ< `?_'oV?gF/ (+ -*^yd/Q*QBy,/o*NcIAv,\@B91#U7_FU!Rb?"\wߝ? QBЇuג(YDyv26. H@%0c-$  H@$  H@@[0tbH(!0]67:&VQ^%z>lMᄈ%RA7 MFk#/*x@T"0|`/"=My"TAp2YR.e!Juj,0GY' yBA*|Nvvx}^UW*x.UazBDQ>&oGU8b0NG|bGa֭QFQǪv <@f!)xmUcbP#͗(#/u H@zŒg[) H@$  H@$ JxJ bRn@Av (co&CxU(r|x޽;| :^W;0C;Jk߾}oUev=?4;}QՎ(#$owm H'h6`PGy)Cn?ȓum?``j;,ׄ={#,&O`n>QFjeDY.%  H@M 3zm$  H@$  H@@@駟0 zWf< A;[e0ѣ pXfMqw;vgäIS&ãD29oO0L];XQCBk&ce4 1\:MIΑ2ȷ'\̚5+ "֭[X$P$2MԟvPvQ׎Vʡދ/N &$¹9|b„ 2v:}Z22. H@@P{}n%  H@$  H@zZ %;I*CjvPNvg3gLor! _kWnot0tw?PueЎX6{}>kG8c__\tE;Iy+c9\)gzA 1q/Nbcի(O!Kl;QFu? H@?㿏m$  H@$  H@`L*Eh qa%aF[ `f uak+lmA];2XgUG+-ZJ;'pU'cz!*\cg+W1{W<3KȩrmG7.%  H@@{% H@$  H@$  i ft0ВwyO7hרM};C'E2etʊ~@0#wUl'B9u Dٛ!9}bڴi|Ysւ Aԭ˨/gqNj rQ7$ ꫯnϧW=%Oz]s9|}EkC=9im`э2 " H@@PC@$  H@$  H@L&0N0C-:pA1v0c-'<<UDA$F^~iW9sf oFqgtS];]Q&?uv˪rZn!AP'*.zD;8J2eUgyGQ\~W^hqNZ8뮻. 41vtj$  H@5Y: H@$  H@$   @#( Ɂle 0̒1<99am&!OR^碍zX~}Z􍌶0#L^FD?䛷jr;q)wN!O☵kO*qQ/B~id1%cNpPnG1 8bl}|_=۱}u!//yG bÆ O|YJֻG?ciVL: `nݺ13D;аsW_-6nܘNG7|8.%  H@M 3zm$  H@$  H@@pJ amFgBW`xmn7 ^MV}=|sy@IDATϥ#s)&LPuQIhwނ%ٓ C6|0L#h@s^nv’|oĊ@m۶x6DWZ3gL"zُDXE%!eI8Ç#ʀ N7o^Y!L&9~ 0q ^5~qTCϫ02MS/|2IxK/TL88G202v H@zŒg[) H@$  H@$ >8 !:hq_~Yk%nq衇:I1b= &9H ~7H^3`qlG \pA) V xҠD1wvP^Uf,XPڵxS:1^$0!/ʤ oxXti+ 3De+(ʷQ}ݮ <Œ͛7' ;P+(뭴?a:9ף _r4iRqa$>cr{lq)w^ q '$oy.:aƃuS7ߖ׵<-WWNoϦ&xG z/O?a<(`pma^U oN8SUT?! ˪eD]uT3VncJ+@? h .ۨkk68.Oy; -kn6+DK|AbŊ$J C aM`ȿo^Վ1(\K@$л?.[. H@$0 rvÆ ʕ+Sjc˖-KVI$  jY1ֶBQF Uypg& 1h6 KOg`T1z`hFs)oof™u;gΜJ쏈L `7o]=1W:]z)Fe=oK r<_ ,زeK&AܴgϞyĸzG]FY^$ O@ac[( H@$&.qmq~g7ͤ#sM$  H@CO#'FT>$U * r},zP/< c ii4^:.6nJ@$0V ՖXo H@$ qEGuT?ooT$  H@$  H@$  H@3FsX7 H@$pKLsׯO{„ )4o5$  H@@!o1*p๭/(kx`IyUa,hb_e2d帪?[O'hOB^2XՄp%Dod~|7t+W}Q_)}\k B<0 ⱎ/>rHlWZ$  H`P1 J@$  H@$  H` c$ Mak2!@Q" < +=ЂGQyG>lGa$  H1$  H@$  H@$  H@bB‹Q  2?>aqWL08qb1iҤ$h+Qu8 H@X#0c$  H@$  H@$  H@]"@(F!4Jsr&aI'TL:9sf1}L$   H@$  H@$  H@$ (ٳXn](O/;$ 4I$f$  H@$  H@$  H`o߾y׋M6˗//.\Xz衃%  H@cŒ^$  H@$  H@$  *=YfM'&/:QUG+# H@&w0˒$  H@$  H@$  H@ [n-~E7Zl+%  H@Qŭ$  H@$  H@$  H` YgUbKXӟ}7|Sꫯ:-x?/64mذ!yΘ8qbYx$  H`P1H@$  H@$  Hu+Ν) H@XrettPtW}*a\ 2_|>O?M/W|}7; H@x'0c퓀$  H@$  H@@FaƯl$01ŒGNډx1>bIG~;;wL5G2UyM$0(k=f}%  H@$  H@$  H@@xQG>SL+p'{-S{k׮\$  H?$  H@$  H@$  H@$И\ Ը h~+ H@m 3zm$  H@$  H@$  H@$  H@H@a05k H@$  H@$  H@$  H@zŒ[/ H@$  H@$  H@$  H@0P1pZ$  H@$  H@$  H@$ &0K@$  H@$  H@$  H@$0f #\$  H@$  H@$  H@$  H ($  H@$  H@$  H@$  #׬%  H@$  H@$  H@$  H@m 3zm$  H@$  H@$  H@$  H@H@a05k H@$  H@$  H@$  H@zOz^$  H@$  H@$0 |Ŷmۊwyꫯ:6mZ1uԴ>ko$  H@f8$  H@$  H@$  H@믿^Xx뭷0,f̘QmZ:g$  H@@0A  H@$0 fom߾`}„ ŬY;lTI@$  H@*k֬)֭[W|}Y~ł ɓ'|pvW$  H5_u+}Q!z9sf u$l m:ZKKm$  H@E +W&aƟN(.\X,[8cl3$  H@$0Z`4ЕO]aFNu H@xwݻ{nOOZ&m5eYA m4$3I 3zm$  H@?ovqׯ_nG?QwH@$  nu/H=9szo-[l)vڕ8qStPQn2S H@[$BFᱵQo͛7x.B48RSL)8F0Qe/q!:cŒ1QVS$  ~n۶ظqc#ػwoK/˗/7I?:# H@7 %k׮-޾3>}nx{衇R1oN;xW I&"1MVg?6f* H`i$ "{)ip>SO!xDiq$6$_fݾ$  H`\`ҝ7p[Nl I&N;/ H@$0N Ν;Z3+h5ጫHucj>cOb̙ٳi;AgI]D»_z]*.% Ol9 %  H@F BuQi <>uWSq]$  &~V1uW^ye_f R o2l[b"#c0Q" TPQEm$  H@#N ;wnz ӄ ŋ}]$  H@$0 `;#q m$K]"Np}4iRq3sVUJ궏X? H;ftH@$  I'tRqmG}tcǎcQ_\tEm$  H@$  H@#McqwoV τaƿۿ9_ϟ_lݺ5}LRL6Moĥ$0ŒH  H@$0Zg?+͛WL8صkWW_'pB1cƌC-մ$  H@$  H@-믋-[}Qqz!o(^⋓nj{/ՙ9ٳg3 15;ΪI` (x H@$ xk|L$  H@EOS'.-G_1HUXrap@})>|P|goR&pponxq;;~+~')=#:.?_P/;۸rޗP~X'<#H^=acMÞ={R0h?.aV~ijyc @0ۃ C>-?ds~!?c:SyyK@cNx附k2ׯC=tPv) tN@aFC\sMq'c;5k/rrc6a}W_] XaӦMM;`[[.C<"~⪫*NZ!. fLgvuO7;>^1+DR_O뮻.$  H@X&0c,u$  H@$  HkxCC)o:o8.y{$ =L*ݻgIb:Cq]ʟxě'Dx^}ݗD)=}KܰaCq'!i5/w\#bҤIEYSίmĈ'7x8|< 6s9}׃W7v uZD {(un%/861cF[XZ}$  H@@ 8km' H@$  H@$cǎ~1,!m>!3m!C8  EYDA(: x,O7KS8#>^#x|\0:㥁&r1 E+Z+Vq0/DZG? FrALX[ni˓C(Cc=!B!Oo_"b9:3Ha60qF"˼y wp o^xa2\ 0x6D^

Rc*ZbGu'!v9䓓G X3Cx!nmj\nk=_B=!qʹ1SO=5B(1uPof0{$&DSGgr:ӓ!YQqyBr]wzWWI7%bFwsgpZ|V$9ʆc3n~cn y0qƇ%K IAD_;wnB>qkM4|v]P.1$  H@$  Hg ߨDնd%0HĄ=0cٲei>ke+DLcd1?C(0gΜ5kl駓 Fv^0a qgK.M:SJD!:Dg ^ve ӼMQ>F(Dx A bD6AQ%2J,XK QobH5Yfvl #b01yV-{1*o۶- d ќGa c/c1sysNqt>9w)$H\aZĵF\GZĘ=}n]y:Ƚ7ײX*O#D3r"p⒪czbӦM08D1Ľnގ Dy_^ziKBNǽx'D.䅐Px` @`1(<+qZpasMyx>~=Ɋ+'"1^$0Rfy˕$  H@$  H@FEq7p a kؽ{w  FK:n+XbњP'ޚEĀ@!FX|yz;:!%'0$b`!B<AoVah 7ܐ\ &HD%HÈTwJsX'0&4:G/AsѾK 8Af`αh{0o,5E,1# Ve1Һmke`hVuE>x4 DܯVcw2/&M I뮻.7*Y3Ddzv6m0ъO:d̍N30aFq>0;Z$)x#pݸ/!b7o^5Բ#?CP / _39!e|鹑 3D%^7t9,=D1AG>U<^K\7=:(/[ __[ep_FXccO?Mcq)N>CUc8#Xz.B$DX\D^K,i+1I` (j' H@$  H@,&LcaƭZ,[lp7>dNoZbp-$.яa-7Z/N^* R~M2%5n,H裏x oFWcx #c:7sO~L{1b:xQ!inI``@^8Y~?`B3,GȓZ1vxh>?E~u x'yL_Hw@.b 23 $<.'1HO3ϣu2A ;gBmMՍN1;9c$  H@$  H@0d10L2 0'vJ}H$ @P6`7K)C^*ɿn_1C;v;E'8R (W偱lp N*n>2oٲ%֯_?`{"e: Au(IC<5 6xȽ 7Fz>s\KǸ3oXQ2WNdV6V:q]}M7ݔBJ-y&LB;" =b\5BZq<'xC`q7'O .+//7’Dڽ{w^U*qٜL~bŊUQm<[cRεhѢSO=ݻ7yh1cƐ^3xEO,Z"F~<.{qi !'0c虚$  H@$  H@$0 `Gcd޺d$4mǎ cɈHxz7Hb<`+A][A}L3Ҵݠ1~H1ofV6 BK3N=d3gNzΨ<ƻK.7cp:#_gMiӦ!4y~ͷqM_tiz 1+ȉ}KaHFjUx{܄!.K}h$93W+VBC VsM:!0j# H@$  H@$  Ä>əg[20:! `c9ܦm۶|*P(,|r1x@!/w/90!o/68d$ֈ6Guo뮻n@Fs a=qGCQYܸ߱qcSqxh7hz \GejGX;#q{t2~_&(\Wi}V'ȆFc-?uF5k֤C9O9}2uQ>#aP1"-T$  H@$  H@i 0 >t-/bn'ob<}pd}ޣ1b(3k׮M§0Q{Cވ+$~p‚7ߌ}Z43[JHί\0 aKFwQO~y/B <=|ɻd~{ \q'Ĕq,q8gK [K@$  H@ ?ID1_}^\No??L0I\ir՛@|ξL|!]9S̝;w|KP_d Ni+mQe#bF1P‚de剉W_}531oŞ}ٕ؏ *Eº*1 /&FA5l >! aƌ3RI#MqۗkK$-Yf%s iÆ O<;\K)й7z+vr^4Vum}bĉ陈7oޜ Bw]w}yP( ֹ&&P%ޖG9!x^c<{^hs37ƾew.K?;߱D~ x뤿[[lNH/[5s4^C\X›~ }/Dnx_F '$  H@$   Lc, .HF~ >l/IA孥%K˖-+L'60| z<0* d@H;ȣ$Őa<9CZ`Az _%#C}gY9aƄ#o2)H1X!/ub$[.`-Z۶39~}ԁ *_y饗7}#Nh{+}H{9Tó:kXa2!h0[?5={nT6 +Wq.xK.Ic2*QQ˯BCxWaX }xC?@#G} 3#O/8OUm䞕_c ưγvfKmߢUs\9>[&GsOay{?GĽ$o7Z¢( o8 (N- H@$  H@]'VndL 2AHOLy 3 kӄ`L5J'Y{(q=Nk ޽xFZY%)SBǗyNB,3p$DE[={AYl^je8kC`xΘgK%  H@$  H` @,Wgy&' LFb@ĀGm cɠ+N>?/nQwߝI0&oV7Kh&Ƅ&xs&'^E.uG}tO^6bRX:3[Qф|Unx0щr(70FI;AGLF.ú60O10'Lcm"Vr1io[g_̯K+(#}k`/؞_wc^\3F3 }{4lD>sI@5\n <+w9n~?–.(;yCtK(":s{oدْ}6syՕo\^(wmRfy˕$  H@$ $?FyitI'3g̘ L0GL`nUQϝ;7ir!*?7P`4Ā!!(00"! AH{'L8o!iVWJbcxښ'hSjCSg&L. ; =Oar7?xxǒ|Cǟs9}LPW@Pm0HUۚ߭҇ԟ3j# M9w,B8.5!`RqG_F3{/s)0Fk,UQzp(D_ϩ/,'p1x˵7s:'X;F3<,]471IHp_޺I`4N<ڌ๕{rHbk& dfAvvۀqev׫:BB"l/x.l>yUUbߦ<1SB0ocU&Ff4w$  H@$0.0LH cdu`5,< " l޼9yh'<WExEnL>}X\ /!L^; r@>hc~#q\&ZNi_.%ȃq xA˵>vjǟʧh Sgp9䓓>^u=[q;cj4k}\u_ṋۺI`YBOۭ?ݪ{!V77孷ޚ"6oK.dLj ЕbX-f GqC;^IS-U>'\W[%xP齢$:Ρc]'h5g!cbhP/s7lySubR96ΩꫡPǽ)vؑD7 l?~^d{F Y H@$  H@p[n%-ˢ( D&T haAku M6%aH>K,i(dV 7SW7u+:s&qW 7|ؾ}{]a'6&#〉QbiF W᪫J8l¹PӬ: KeA׵\m FeƆ B.g #x unj<(`F[ٓBwh,$(Z;cj4qS'K~֭[1yn BǍ7󻉐yBd]^%#VYDS>nx!d脶<{Db۱߻Hh8l H@$  H@@xkqu] uoEּaG ybrI<-5le|x>u  pDB@@ٳg)%o] Lfu;1-`g052q׮] ׊-:!N^n9N z yjK 'X*'B%F0chڹsglQqa(_OPFG^.Cw*uZG܇#G}4= ( as gmixaaY .Hs-c={!a*&4vl%0c5$  H@$ !" IL`t*n>9o F,>u #.|[+Lx&tߪPsؗ-)>u6擑GxÕHLbv:&#}{t+c[un1O?==bIy8glu'7ş$/y=hf&$7H J˗FeaE)K%a^/ƽ9"o= H܇/41x@={#c:pxS{'cg& V 3FkX/ H@$ DY C[V& H@IIfO> /& r>U&x"LśRQV1 0D#o}'x~p€hd9tf*O(U<_[x 0я!yֹICE`)oۮ\r@|(8aY pUWpOkT#D>0 aMF>k6'zRqT+_ 3HKa2)y\x!(O\εOQuYXoroٲ% >#yếdɒ ^ԃ<.4=z1/DQos/~D1J|&=}s=l}ċũQp,FhG hݪS|@+fB}$  H@N~wK? 7?J[ǻ$0 0n82&M*m۶76mڔCf#L 3' o cbmVA?1v$LGU6 T@ pu% ov3/0Ja ʆb vc<€Uާ\o8Fr>?J/|0`(  W>!`P`Q m~ !a4hkcj(sr^[hޏxbŊ$\E&~1wuWpݍϴ|`=&C}0qO?wbK~ުD& f_!Gs+$  H@#0oHkƏ_|1rd$ H`<(O|Exc CIL8n7g+be2qQ$_c XE;]&7@h瑯#3ùG[-?Wi1[W7:u6!Ęْo|I5V\yzP?SCٮ`?ypqԟ6& H`d ̍(G}@0f_/Z|yq9><3AgDs# o(ډ < kD=Y.]4=c[O>uQ*(#^°dL$0 (ͽc$  H@@ oY)Ab%0N 0QZeT }b׹o>#[=d1G m#0V6PlcT?ͅ^D{&'o|Eje9H}(yDC04k֬C1]J@$ @gs>:1pWW˖-KFD€(uE0o xL҄ T%1⹡JIQ5} i_3Cpb-Y$)B_p$a>ǻ=( uR6@VZ$㵅ߒyqFŒo$  H@=H?Dp+ouw]@3'xdR)(z(XKL^:ELrv…ɰO|4L^ 3Xo'NwR׺cTsWѨܴuq$  H@0r-}x'va@Q -f lY"^xff><rl y IhLC*sM"W>>8̱B[ O1gvzUB̂xaƌ  ݆ aߢQ!Ox-lig9Q/Xnaŋ;:eŒ&l$  H@7?Qï_8ɏI~#$3{Hv  " *8VY]Q}Q77@TGtu ]]jiXNଈ" Zj9Ǔ_dxy2bk|r}oJGĹ}T%s֭eҥKc(ޡ kG6)xl'N=շeuAq$  H@t5ĥS jL # QIi@>s.̠@^Nf̘e -Wmqo0Ǧ!N;}7cڧPkdq} `C0}8 ھԊ:YpJ7Lv 3,$  H@pL0$Ce˖ S2V[pV n&8q 3y™nDŬ1<|ܽ{wt`7RQ-sGSuP|H=ؗ+k?yVQ$  H2@tOu"7G$†]6i85cxm]=lXpR+ꘗz(F?' Fm@(hi$  H@hF"!+B, e6o  HCxֱm߾} vEjΆ8Yo[ =!nq;cuLAP'n^]gQ(&fq\sc;YwV UVORBdEAPjj{ H@$  H`//,Bͱw;|~~u$  H@_ gV+bxBEL.o>a/_䂡bF\f1)OD/* !O"b9߷{yl$B1 *&B?G=u"r獴!u@QL6n+$  H@$-VYO$fŒnl$ H@$  H@Ú ,g'fmܸ1>g lvvqcl]vg}6H"I'Ώۺukx'|\2[.~_oh9N֝ A<16\wY%  H@$  T"$g/ZcKT*K$  H@$  H?Ӣ Ϟe8XWG˃zOC!F ȳ<؊0}IqVUC,zv@IKf͚ʏ<~{~~a[n jkdS*[ov0Pmت]yY&$ H@$  H?| s{sNȹW$pP'37o H@$  H@ p ᬳΊݻwK/H,űxU˞Y;v숃[l n\jժUqo?5 N4)PFhs؟7Gyd<GFK#2!œO>E%X qF>쳁^{ޙg᧟~{,.ȀO'.d)Jj\BRɓ'ӆ3e&,pJ)ٳ'}Kʹik?X+Y&N⛎ I5QM f@ѾTHeO$  K LN9g'@H@$  H@ @D@H.:uj8㖢/>  Bp<|5/pv!HX‚A}9sf,'Œ . }qԒ)8%Bx=3:xCAD /ALB,F9pכ̙3'+<6m 0"B"䝖;O?=M;e IM<mr' Th˗GD!QSN9% |H~sL3fLl?H#C%ml,6$  H]ؗ@ '}k*H@A{ I>U@@aF/e$  H@$ aI_|q@M X'2رc #@t8^!=#j t, ̳$R x'p/]4. / <,˂Ё0 2H.87o< HzI'+W>1`H8\>_/,҈0ug OTjC" ~) "i!ψڏωGRj?-I $  H@}C ET!6l} 3Î7.newѯfJD\Ã@1|]9=LR$  H@#*hP8xƉr"(pzf''OI.(!Br @Qi ,)M8>,+.G@h$ A(aɒ%Q1eʔ t9#)Hc=mfK/BY&%_*%b$ H@@@HAOkJ'!  6>>+Q^kҿ!/s6畾$0${G9@ @rߛ$KfRkYV H@$  H@I xMa8wI8|qMp5x]O8Y͒,?sCQ+pښ5kmP8nGuTŲ$  H^t) H@$  H@h@ w!1Biy2Mv&)D'JDrLRoBl#`tJ@@ |,$  H@$  H@$  H@@`rp$ ff$  H@$  H@$  H@$  H)A$ % kw+`%  ]vᄏGJk1%  H@$  H@$ "0cz^ H@@3fLޥXƍfOsZ H@$  H@$  4MLF$  H@$  H@$  H@$  H:$  H@$  H@$  H@$  H@hŒy$  H@$  H@$  H@$  H@N@aFu>~+ H@$  H@$  H@$  H@&0it( H@$  H@$  H@$  H@8~+ H@$  H@$  H@,>|aԨQav5]$>e˖';6̞=;ztG!- / H@$  H@$  H@Z [.l۶-}aaĉtmY0 H@@{<{ {1cƄۿ - #GlO!EuPQ'(w$  H@$  H@$ xM7nD8ѣ_s$  Hk^ _~@x0|D|-ftKKX H@$  T @fJ裏qa̙aҤIc H@$  _= ^ؾ}{ꫯfGZ H@@Sg}_ 0S$  H@ _֬Y(Pk!U$  H@E`ڴicՆV$  w 3`%  H@|x@xƔv\|y2eJW H@$  sh,erG38CaFߵ$  H@M@aF$  H@i&ts1}qE2/ H@$Oƍ.]9pA#FS$  H@0 0c4U$  /2<쳁|M8CԩSìY֣X&_ ~a7f̘0}0cƌ<8 9r/<0jԨ> H@$  #>q?:I@$  צH$З(o~#|1t9s??e[op!XdI/@9?>̞=;|_bMm#GuTgq7 H@$  H@$  H@n$  4LO>$ Daƞ={«: 3 &+"L4)4qĸ6 $  H@$  H@$  t/6L$  H@!qI'O?4ѣcI@$  H@$  H@@wPc$  H@$ 5jTD" H@ iĈ͝dQ:O@ H@$  H@zŒj. + H@$  H@Nkx7û]gԩ 4_~eعsgرcG\ ꣏> y8øqQGf̘JUt~?$  H@$  H /L$  H@$  7x#~W^ |Mdɒpe>zO?|oGa-c̘1ǂ E]O^ʦU)=J@$  kM{ー{'/"nD<á&Nē&MTԷ2_®]x#HFL&O?+;ڕL>, 2^|0aBKx""p ^iG;Cb!L~#ov3LySwyg&trGqDٸ.?LE 3z,$  H` D8o w}b1谗%:vX`,d~ѐa1رcÑG{}|bN3v9S _.wdž)SIJ{!蔝ĒpŨd6I0, G ;S#r$,(W*ͲƘ/KԩOP_3\ͦvq^6zz)FÇ{ HEAԌp .Œm۶5kքGy$2xCzp ' 3Zuo~4CU; cHk0 Ncp n!83Q`rsK;ʈQqUWE#=P4F0)e˖;,ޏ2`%g!KSs64WزeKx+ڜr ijBf0Ώ󡘨c^z]Wlr͛7+ 3(/3/^hjWBPµuuM;59HpO'$8~7>?ϕG}4򜩕x>WL:O/ H@$0| >""I>lx9ϟ'8/ׯ'RN y :Gjv?֭Z7 g}vX|yR@?|Os h9ߵ^N;L#TN0LyXYz뭷"?| O:餲]L]K@aF6$  o j$0]0#z@HPLit1F} }8f408q17s̺ 0śF8Cnhء eC$AC% a1fQ5P $YgNX BA f _=:Pc{1Bپ4%t]|.SNit޼Lܨ0m 3fӖ8-f*L"Ж`xo$P/[߲(#R7g9Cp6SS &bhW^aOeoHIVtWISű8^qC; 3ڑG )o|rK.'v!rBBVzŒ^i)) H@fc &qJs>ǨόF@B́1 SN9fq)O08n!^YزYe6B/C."D;FAPpL  W+Z ^Dc`ʕ+1]<..~̋JulrbeժG'ژk㦛nبRyТ 3*s HHpJ_|qTfZ oy2^$  H`x *>#$ƍ}]>*q g1I%ok 8!P"Xv ʉd}d&AD!̋Hy7xc\$BATN)A/KA2O}~ՌDQt.Q1 .m6$>-#g'7e""qm=-`5yۧz*n$0@$ !SRiH>gƲY)N0fcb  ccS fkmr XGʔ =cL:Q7 !6%e!K=)3 >1t"*D"4! ׮];l-mù1qұu,/s%j L EgG,PΛ^})K'ژv?}} )mo%ʈ('REuf$P/{9 `pL`aP# g?Fmڟefr:p~$ pt1%4{fV]WiKY:8k%%hË.(Z=7vQÂho$ z {~1DaMl~OM:O~oذ!κNw뮋^e$  H@CI{|-L;wn7o^E1i1eecwo?5j 3W&)3A|e 2/>&!2(T+/D@X KV `[ouA?wVd^3?b!~ʄЀIB\yPN1$o!k?A\V$I;{eIe{ t+2K$0 `.Z(naRf`(a4ccaaDaMQFY E9Oțo9i?D.\XQU-b H jX" " f+0÷U  8Mq;`Cbf Í$D `Ͳ$ހ*0 q pM/30x(tc^5Ff&GQuw})Cژ 37R)|oSDWE'#!?p4I@F<;sԪ4go;OA8i$  H@؟b c"$pHG3|R"r䕢1W_>4g S^&2qB?]y1IFڰlٲ蹩 _“\)7Q5%BV5%`D;yW7mG=sQ \G%z`[9spnŒnn& H@@xp嗇9sDQ Ae<`hQ_ 'QǢ gMˤx `2zQCa4,8 |`K08v &q*y=); @daҥxr!# a9$ӮN/uT#aY_Q%a>䍿N=N7C|8aFYڕ>(&ؐ_D$/%hD%:LS\i3|c.)[;$C|=0.m.(`|p H&Zy^H<6R72I@$ `A"@IDAT믿>%ȳFAMB1+,СUyt!r0OK\_+q?YBSlZ:Eݩ$  H@ba1󐒍SN HtJr zˍ䉙 ,iH\ꪫbdjkn:)d@LwK=;ƴeј<&Sz8s39 fk$<$P+^:0Ip"@toΤ(`;&s+S""oYj'ߧ="ݵh[: `ԁk:$[Ǭp [o6I@?.E|2&e6/;S@fj_HɎ9,ShaSO=5P^Dun;3ܹs6ڑG'Ey/Z4c$ft{ Y> H@Y20s5=}# zL dɀ z78N X0hÀ?C0NY,Cwsw})W'ۘ8S㬕m+MXct  W 2e˖8țsm3Š: 86{솞 >caݺu8-Z?0(/\3溦 P?J=h? LCsZp&>8u20úgGUO)5sU|lR?ښϟ_u ^8kOq} Y0P\3fIŌw^l'of?X+ H `OgCt ̼҇ϕQs/)yoW^os}P|\ԋgQGk> H|,Mf"Qчg4CbWK4%jZX H@$P ) +Wbü&C#˥;hf&O ]6*yϱ !-FV@4wdiK.$sOpf=9,]ɠixY+~::d9R+D 9IieEHtNs͛7/ VCODVY AK/4̠BF#I'.װB0rON \~}`ԬV.A[1wڐu  }w߭v8Ke8b By@ݸv0JYJbIf!袋8'e3ˮt.ʅMsFTm}fwqGbQ|%?o f2OߋԆ bM=B@~?.°lٲK* 6yY߻f~c{}5l3Rt25:sP +/Le}Vj"뮻.}׼O'bllѫ7/PieK Fv^H@aF/e$  H0RFlfb3H@a1LbK + !LɓFf &^9ژKA~8&~ 1@@"i< f04eOS9HbrDg`kŸZ~a(?wuW|6l="<ʀw#8An-:iOJX|ߡ|)"*!EAr/mGt~/ aY4wS3[ߊۊo%IA Cgf>Gy$:eys#YsUWEqQȱ J#馛kxn$^4(ki?@HJ{ac>5k  t#_<2bkNl~+/ V9WCTQ&$f??P?Z1||O7"-\pyPI\\%6~3ܺM$ } m{íc<{bg(F$iCP֡-mL|ǹ0d4)PaPGr ~ɒ%13&s03 UƹA8eIuR D h4!`f;!6QǹI#5lNX"˫ԓ8{[oJYE=`@p0#sg1[Enym3"JP~82? \nCP'wF5߈xm( ʫR~w=0'"vds!df)C`0$-Gh fcA]vYo-+PIԙ| ʗ3 t#*I$ !'Hk֬g]9P2ygbD01Z4Ra ^0^[`A0c6Nj|'4?<` *2M}fbʹ^[ J N16"07(qgS#"0}cC̮m4q.ʂAue NyҒ\\\ۼqq=#IX2=0N3A(eʔ)Q㊼c0 qrr5ʣ2xby) Ð|pMP$` ?xvY=L~#%Ö#|0/yO?= Y w.jV~i_!ڕW%R]+6(+x76rl< BmotOe &8X2!EJ,]NG΃ӈSYfb΅ ?W^EGʵkzTK8(g|pX||v( v; AcJvQQ #D;x衇bT ~Dĉ+Vźsم^g݊.D=}Swe/+Je!o黲a1>}}rogNJlef =[64? Œv6 H@D뮻bT0 RfBc#_Hb s^M ^`Q a tk,akKg sK t3p\yq8)9&!  x1O$ D<M8LUW]}6GgJDA A>0}q`}u'X.Q AT|1>^$a?X80a #D x&,b;Pi{,'pLVp Q/?%΍G?Q|nr%aV&b n,4psM#BPC/n\Fp! H@@w`iO3r˖-;BC#律Yrov?uHbfc?#ЙISϲwhlf9d4%KfRkYV H@ZBf& 0ó8C%vIcI }Ms̼f07Ykd3 *kR΋w}>w#t3HYu5gFT. 6*"#i„ Ih4pd#rE *?gSvOr(u%j$  H~ڇOQ+{Ŧ(;Oپ3E{eEׯ_OWy|vE>ň|ɖz =}bs~kUj4N2D͘')6%R孷' vQ~^K(<$  X**idv0b`ZC!™ќ;`B:n8?u*@}Akm̠6йs{U(bfg Dl$-Wӭ [d'GK_ca@-w:WlybQF-}*ǩ8 䙌xU*ߪϙV 2{rj}Ffc;LQ R9qVo!_k`'x4?l4%  H@9r[J ЮWhs,Zͮ(C?ż׬YOg s=WZv六͐_[w}C.nfOqtQگyt!y3}Gbz|@2nh_6ÌR$jQ0LkSI QTEe=hKx0xJ^N8]\΄A\Jez*2 ^Olc"a| j ؕE!u㊍kpZx-#lE1~eC-Oek/;D1 -p lڴ)tMnObH{-4veOvΆ&둷I&pP `$  H^D7`'I:}B> £Lg"%΃AL2(hooHWydž>;l lذ!^:,]t?FگÉU_\NAHw6%3xFxzt\v+D Lr G pTSNu9Kxbۼ5]3|NԟN&E+6N\𮝬WY8;Jyw(x˿RS{:+ݵk0:x2$<3cw~Ju)py[S1k/})Z.p~i{ /B_> a e RYڕעE£>dI\(7pr(H!:nY#W|n5cK7//'FV|/?">2_"lik _΋(9F9yBÔ "ctM~\yE(9#=XIv(")/_%MftSkX H@BHuցxg`3όt1F . 38aiΜᡇܽŋp |e&:0}lhf(pݧ)1Ј ٳgGczbT3Š+^Hhc8ܗ "#Fx#B Ξu+^s/w%T52y$  H@hs9')>."0ر#D? a/`>}F}'YD#ѯD܀5eʔ(̠ [}J!Ԯ`uF6؊ipW 6aʴL%ӷĞ1laJ\]('"6p 秾Z8q?/b9E=ۑGʫ Sz@S}FBX/{#`xriSŒno!' H@S 8_QNYU9H}`, CY \Ws΍j}hhD ҬnjN1 f6ȁ =->ܯ88cBr?8qtųZrH~EӉp8 }~kM<[9')Ow+} 8`,8SNp€e ߨ2霭xcJԕ" wrS?7vjWG$  T#@.K:oʅ)B@vq`Z;HD|,I|a{l$7PDμ\wuq ɧC9G'[ҏN D*cyb^s$y'!&a1$I8p }vky/߻ [>?1~:wT]z1/M{ t C$ 0ϟ]0ЛNzu1"10R8;"!qO" *zA@'F{Jԛ08FN`3qRBOۘk$% 8“]ffAA=_~y[2pLq1BQusLge[G8#suz?~ l4Df<q{St,wl8qk\Ro`=,łECƍ#/Dž9Kocchv߼$  H@@%-\rI_Y΋: B3ߍDW H@@,`Ӭ_>N) R=`3`361RbC안h3INEU8Cd)luZ]y/fg~zS?/@uSY&M.6o}gD?I>c cΜ9]-~ȣXv2, 믿>]Q@ ۈgH"W ŒvP6 H@ZF.(KeWtT``l9a_eGػsQx:DIXti4nb9!a 0"0xMdU;W+K~h3:Hebv#0xԳRj%2|V3 c3!Ձ3281N[qv1lJsȺuçqM])3?bZ=~ Of=:JB`: (e8+9D_ K-{w "NDꙖ0A29;G%=){.]} ~Cv#0 KrsM?i  H@@`:% ` 4E М }FD B#F .A1T&l@聿(s2Wm?̲|ەl2 ۑ(t0^?1K~8RG>K+Ջ~<K>NxOsQwWCDR/vQ_;Arx `~`Rv6,8¾LB E?Sӎ],K`( Te[$  k 鬳atՁ8ks&#RV \,O€(~:* aHNH;Ha,A' !1>mZ52`,-|W+y^uUq C)ڍԛhW_ڙВ\O\0`<'U>@u󶫍Cf# _ .~v="5¬<%Lfu3Ln3<&FÔsg ~!7QX$-`mw7`G Yym;~/~ox]8=& H'9GI@g} d~{D_ DOL—Æ?jܹ1g,yeljW^F:-CXdQG= ȇ(_{mGea1o-'`w|}Omi] {CAsJ@$0t'#;v H:zZ3s?a a;|ƌM-S̗rc2PMy19xv udɘMFN⃁T29G9h8Ow0it]qmU2[YPm ' g6fp-q&:rۜD/kQ7-O\K?Ecٗ4z۷o^堙{S4T.[Sb<lڴiI|:A\3>bC‹{Gɩky% @8-Ĉ~+S(f~gWjs$Pϰdǩ7a;*%[Uvvn쓡QڑGYa7>fPC3e  (( H@z@2b[p:f: ,b`\TfXVhdljZlehc@>AL(H8d 'lKdf"z )˅%rhl$!DCD4#~M~Y^0Gr!v:%( b>iVRLQħCwf yޞK {ᇣhhB)~;:Scs$  H@7M[ H@$  H@8sH-&DN AC%"B8 71(}=ā+Ӵ#10Ɩ'ڒunݚ\{aE)!BC~Ns`)qMpp/ i>xDl_" U鼕xQb1J@KN~MCwIYr H@$ n%e߭\$  H@$зD?~|=z>udJ0`sƁJtDayB z$Qիc !Lz%!NA(Hb`1%̖5Y+O"߸qc\%|(WD;壑JkJXZ$ 0:餓bS9s4X# H@$ ^ 08ϽPK( H@$  H@]K^^8ꨣY]Pml?\ C!D~ӟ28^xa̗e?Q78W_}vEK,%C!:C-A ,f#(I,_u駟9眊,:~0+$ЛmpgZoRK@$   3KK[O H@$  H@]L`„ ϏI`xfO=T~ XزeK`!y%|]\Ƭ38#N%Z[o ?x4iR.`> 8 H" E#ӎs۷o"ʖ|8O'N VAy3یUV "eLp!DD@AvD AԎԅ{~˕z|aѢEZH K۰, y!@`&<9ڀrA02" tr" "Pgjp^ATqWO4\tEᬳؗϋ9ˁ I Q*=F ɗcDe̿+GAT{nexJ/xɃe{^^ ,+?$  H@$ vPN% H@$  H@ $ ?w f2⌲Č|>`&>vg #Na`At|K/+Xv&pqDž뮻.F8@$ =X!$D`I DT G?1 u%*%ڍPe?*F@r$/Ch ADDyfYj%'</"-%״iR?x* H@$  H@M,$  H@$ .' /?™gaÆ_tiܟ(L}3YZbqGD Y8S﹈&TXGb EyLE leٗXOE`5ԑ'h?D N=ԸcARBgώKs̙3';ϙ?Rysɒ%/ Ш 2/_ϟ?@4zX8+֭[1VN;x?$'5QիWM6#H _ig판3fA3f`pl 3r) LI&Վ+kfe;( MnDAPw@p',@6 4,(gB;8,g@~Ќء>#gנDS01G}t,ԫNrp0O$>}zdoy={ĺ#]XʃkAuF+FʚIs׮]3s]/\'rq6w=K}DZmA{qAqpq缸3q.,f1$ H }Q~k~}>6#e9a`$  H@Ռ[p}ٴVJA 3Zh $fc  `&j[Og`A^kv|9e(ugP=՟s1x6*E6U`@9MSԃ:TG=Ei<:#*eڄzÀ+elOyrm\$̗QO97pZOJ9ҽ+u0I@$P{^FM %BZl,\$dorZ>Ͽۿ"$  H@ÕVpmy- H@$  H B`@l}⧁JWV KgT*'B}ʾcӉ2 ( ZuD)`V%lڙ`v22/ H@@gxע8<qh#l7ID"jI$0 (έo%  H`X Dĉe KJ@@ 3+ H@$  H@$  H@$ rǏSL 'tR8SԩSÑG8p$ aH@a0lt, H@$  H@$  H@!0bĈ0f̘0a„0y0iҤp1;vlXdȑ& H@%0c_' H@$  H@$  H@VAY 8C¨Q롇-;찀ЂG/r,0I@$PŒڌC$  H@$  H@$  _>b )Ejnʆh#m,E{`T[X H@R 3a,$  H@$  H@$  H@h̙3=$  H@h1-$  H@N@_ۋi$  <-[zX H@?V l& H@Z& H@$0k@$0r޼yìVW$  H@H>hmW |@IDATH@CH`̘1CxvO- H@@/K$  H@$  H@$  H@$  H(ֲ$  H@$  H@$  H@$  =\V$  H@$  H@$  H@$ ^"0Z˲J@$  H@$  H@$  H@$SfTsYX H@$  H@$  H@$  H@zŒ^j-* H@$  H@$  H@$  H@@O8Jka%  H@$  Hk |aaᬳ |זՂI@$  H@$  H]fH@$  H@C_uxs=vٳ'_|Ea<_ w}Q q1DŽ ;/t&g}dK$  H@$  H %ֵn$  H@Io v O=T[o|IǎN?hѢ0k֬0qpᇇ#F 2w$  H@$  H@@Pmh $  H@$  _iӦ~/> TW_}b3fgqF8cc̘1hyy<$ F  ѣ=K@$  H@M@aƠz H@$  H@Aśo27nvc|g.1`0spG11I@&;v?ps=aҤIᤓN g'|r0aBnj0" H@$  H (Ƶj$  H@j`9?0<3aÆ a֭QAt 1!r!SNK0|Qq衇:K@~;>xƱz(8b$SO=5s1᠃tuX$  H@@z$  H@|q0/Rؽ{w裏_|w':!D;vl1bD)L@W3gNsMy睰e˖SOqƅ)SYfhӦMsɓjI # H@$  H (6$  H@"7Ic}x\9wgbW^y%Fsg#K `"M>=viqC']J$  H@@WPc$  H@$  4G૯ [nae0gϞ8;32`9iҤc'N vX`$ ~"pQFϾSeKV\>o_|1.{"lm߾= ֭[k,yrꩧc9ǡO$  H@$  CJ@$  H@h7.03τ~:gJXgϞ.\ Ot ,M gvnZxq裏bČ^z)FȗzꩰiӦ8/2D@Ac=6L0!;60$ H`8 C6<9&;w jAO>$>[%O֮]3 8w{bIBBBBhA 8x!N\NU*)k2N\)cc-HIh_O'}}3sg̽wޙ3g~No}IB"j 0P'*. H@$  HŒR$  H@$>Jn+VLƧ~_\$U >ʛ H@N Xdʔ)qQ֮]v<!H)O?'D@a\^x E`[v7 L` S}'>Ⱥug,Dj8wF_Z}$  H (hǻj$  H@mN1n-NGsqsϭI*Ng}6:7nWTӏ}H1#9P9rdu~!yp駟ueau~1۠AQ&$>dȐ~j|ᇱw_qaԩ5sBnݺc_~9N=AA V=)G -[{OXw&>8~=wߍQ-ZxqfϞE0cɒ%dΝ⸧p#U YRmߧ) )Y1vB DWLxz(N{|[j'=G? ".(zՌ]?+'{ӷҘWԗ:ر#֏1uHc#} cl9>]\{qK .tPաب|vз=I`k &H8loDi>B?XlY(/z=\7o^L0b>('C$  X}Q3) H@`'8qGTO8ppYku6ùYi;(:Ol.`8ikV,*n&MDAz裏:G0EAaQ?2*{͚5bɓ'3f%h>}x>W8ba$7pauѿΝ08}oLR';8]B˗D`fGB3A L4^H@@!<[1CR$k~ymC:thTf2'mh۷ov!6U~_]ےo,sOV(Wy{v1"r2Ց>J4H`~>f|_4! !^B¶I&$̠??ޟގu뭷F c& l6g=S{ᇣK|B?O_ EAuڌs7-}s½K6LpRǁ cƌ FA>I@@)y'D #BOdOaW0=q2d<ߖmןv vM%a󰵱Y|mAd .Akz!@؂(cd_ŋK/4%LgS9{N}Q(E?Juy`3s_EƵ3* QmQzks2` Z)ܻjk H@$jf$  D'P8򨙐qzwIu858ꪫ ӋS7-beַ$GoWt @m~k>=maH B<3Rkf#8D8*Wxرc*r~cw>gL1)C$h#`"։#%  H،*RDD2ǦK5b$;/ 8@DCAҷ5b2> o rBUldML#*FP9 ?7czNԖn+e/w(Z-g CZL7.,Y&4s,ǃ>Bf,Mj$  H@Œx$  H@M@GQ[_8 o-l$lt5}/-ӗMp% !Y IA: gWM&B:NW*}xQtip,8$T`$86 ی#Ti;Kf„{#akV{Rpy+G0ADvKc$ҥKc_㻀Tn#d9rvZbbރ,º;GGWL% ^l~݊]HԪ`lWJbnS ۪0=^1bĈuvac+緛TLs_&I=6Ƕ$*v'vcR؇"A4 (ƿogV:b3>B4&`mEXDE<ikmg;c>w{D{v>aj)BovD@8P!J9R]$  t:l$  H dG?Q4HҦgҏm^$M@%QpR'$y?vY8Aqx[lY̻0C(@>^}&G燛o9:Tyτ9_ԏvS7cũf-ԕл8㳅vzQt_чp6s`V#HKs[UMZKc&|(44nܸ0tĈݳH@(L3˃wRL6-2R ~oo^l0&ܕ>tb+z*%c/DZEO>6<2`)tza̘1# -IA=/0\L3M3gNcsǍ7~-"X2VRT6ƫ~|衇f$  H@@fȷ%  H@hNL6HA/8B^XOAJw" E83gΜ.8ɊR!8Bqs=Jʅ F1+)8q2 @q̘1/VdV4fUԩSCBYC<{zs1)q# #?gLx8&p_Mi>RV"\F!jܹsbQ}OIwyʭJ! H@Fyy `b1޺;)-FjO~Qo;0i=RM7 H5r2treH)lmAC@ȇm]x-GCUVue[؀a2@@_E4Ҭ"m\'+d |N"0xCSi>R˂(2ym$  H@K@a=A$  N)wuWtgOpVE8@J&iMp#詰 'l6`$ -q9N GcԅɃf YLa&5 8ˉ2F#ބf2b~5{ɷשNȅL0X'駟ELѓ+{ H@h&&) 6Rٔ'L*#偀'>æ|v 6&uu>jMG_b" r]*آD@y5y/_-RBk&L2% )< "! & Ad c"#lnfJ6`anXE$  H: 3^$  HRLP(Eî7\B+sci6\2Ua8R.dG`#[SPu-D 8T&ZH:Xݕ/垶B)~2I8(EmaE/^, 3jž w?OĬ}_Z=&|y`tIaQ)SRZ ぽ]&O[Wk$mlAD}UWE[tX#_zq<0wZB~HnDI0LlD΃ꫯ(<0q>\ͱ!]i򨦴B)`#cl }{Θ8$  H@@@aF3  H@@K`" '  2ҹpr '|Vv튡sIX\a?& -8o/¸ڟUg QNDE5+iS+r cNQ3YʾfX~Ͷ7DP8ߙ4uO%i?ߗ$ %R d"~ҤIqfڄT IΥ?-.ZhQd1DfCQFA'+zLwy=ڵ=Xjs3fH}lA&լ6ɦM۰wJ~?‚{xA+]$  H@@;(oV H@$`8K/bӦMJ Va3&:[_y啮_VjӦMY}#Nbҥ#Ȟٝylbl{Hvt%3+t MTHkVɑΦ '2JT|F-$ F4hP|g؊6#%aU*+W0зy:=b%(N=x_ {=6vrCp#BZB&%VnmϏ ƘA_V#ڎ5I_CA2!X$  H@*he$  H` aɒ%%ApETV.˿K$`g⾚( Syر#lٲ%Q:bĈ0jԨEhvQ?&#Q :4N` a"; x{qA0n~CHI1cԩ }ꩧqW#KJmN}DD䓍H#Upy}O$  t"xm$  H@uyC4űvvQ]KM^DMJ8 gw;Yiq8>ˆ:Nn8 ڷsp뭷!0idi>S{pJQB |0{X|զH峏3?E[؉&8%NT9^& H@hLBco]矏&bL&I}K~:)BO{T"I ]/YO6A("dvO`f 'Б)l06gϞw۽~ }ac" j$w>C1!=~# H@ZŒVSS$ !^xdc}QX껬0o-Lj"bT 08SADpWr$BI*O A8>R jB|Y±-3laUyT0$^Ǝ.h1gV#xoܑGFi`[Mpʭa<@|a<;5q 3G].B?-tZA\D?f LBsmHO}  )W/Rס?\_sY$  H@ ((V H@$P@tuߦkpwǁf$ɞ/8i8I/q7%b>s kʤH5mJ"Ex0cwe˖E:G?ip/Bnݺ8YEH=#HUB:#8"~i=K@@+`RI?>L<9CL!4<4mIVœրhn<'0H|{_Ef<]fpϊ#Iuls2袋0c%UvmQtG۶ވpGJ Ȏw@h>GAԌ0ԇ|Y$  H@n 3$  HFLf#/q62m:/qn$_X*|al9pwZ큉'xOV‹gqF2eJ ]c /ZМ+}c)MnƮi… y*S&+W $+0BL>1)Esd) H@e `Ec;"2"\7nsڵaw,xs\kpxbw-_tn)D>ط )#dJωx6s(DB\-DL#h7n\܏4> }$j]AH6c.H@$  t'0;H@$ pzW|IUɪ|jIyg+ԱZy |WF.L$d +<FD:uj"Bɶ,*M6uFg&f&LP}x,]48Ӥ0I&o$ $;<Lr)Qo=R&xl۶-N.^+ D8ꨣVx [۳/ x }Ցh_}U|ݚ5}  |nc&GD6dl@_6mZQ }7\|/q15i+`3w7<=F$ !0s-$  HApz9[pESsDEGP{u;5v#l fH?}/u]Q|w`FଳΪ)I+jÇ)_n5@cfܹk_ZoNoǰjժ7!= aY%Ioe hɋ~1^H-i x 8bd1&c /Y&hIqMRtr8|+_;$M_ڟ̃41s?i%T+h>[F.DH-<@7wZ$  H$  H@( 6/zH395y훽KÙJ!CD!?L3|=ώϿf#:W~h(RUZ%$L>d&0_%;zH_EiV6K$Pv$6l~IyBZ3~[,l"[uZi;6_ bKayB ؐޑu; D@F.⃢2J+TZcY ttP 2E$  HOf$  H@@U裏J%s_;8_Eu9ļD}+ѣcG}4._pYqq(PzBɷ#V͒[tlD`QIk Eɚ5kbvcAe}IsK@@;`<R`W+׈30nݺw;?ق*) uD*[$ `<ЗuLujqgٲe1 _c=ڒزeKÔ)S*ڎGz{GD! YN:hs' H@ڑŒvI$ >%#3?"틋'E~% D:&-r28IQAnd'O3H]#_j՟]vYtKGu91LanBr8g;4c#Fc=6?Xw$ H@hFDMyo4z*(C0덼g~ CV2d_7~x." -."b+B8ew%EmO5[֭D 6mZ#XIq *[ H@$ %0Þ  H@j$~Hr73)WgCĦ뤺?qf8qZDÄ1&.Q0f̘- ;wӎ G,900rn!ݗf#a#ag͚ә[Hz&j)ADC5\Ov垓IP}rQO* H@@-]300ȾN@S]I1R7o}-)>[/[d@  :*S*}M$5oob>^9r믿ΰaÆBL+H#3gΌGG1e$$  H@("0$  H@@Eə_MU45(b0/`bV0Y رc5\~v6vb6`#xHxqA 6wt&yZkpQM*q׶mۢ`(^+V 'xbI<&-jg$  s/m$  H@DH!| gp_E9pR"_#WB+}#cSN 7 Q~' .GrMK81"uY%3)nݺx$  H@B[/[d'\Dּ(kQG¸_3AyZ%|׿hS !RŒإH#,y,Xo$  H@ؽO$  H@`G &Z DرcGLxQ->#8Z#{СCKk ]Z4_2eJ8K΂Urτ+JB$ &x/_}*B|>Lԋ@>!5R"va᪫!"8EHuzk̘11 FRAhѢqƮH/K@$Љft]$  EG\ZVP!C;;lذ93֯_mFp6 oߞW,6{V^n }s) jL… {yf$  H@K[/?AOmE=DV{&ъɞ?0qn ٛ1cli>o=,wyFos5# $  t,{m$  H@%ӉpLHg ׮]Ԑf۲eK-F:cSG"ly:ꨀH9GJC=gL|aLfBɷפ8<'>l K$  tvm$  H@u@qAhٓ۴iSaj~>gr{ŁũТGNJ:o|ѡ8KA֟Bp-8qOh-G9DZmݺ5f@[$  H@@; v>dr63 U\`smnzNH7A+W}d41۵ BV#M0.O {iK@$ N gm$  H<! {bb{Gz5kDiD#:!\ƤIJ_V+3S.[a|Oui>ڈ8 M0tYg$  H@hL.1(i(Jvc%KtGa :"͋ 5k|aKEI-/fl-Gu93<30ʖ˗ȍH@$ N%0S햀$  H.7n\7'(Ћ- |qQZiǏÑO(ֱ?'B>_7Ϣҍ^zn`V9~+FrLTYf=\ok#$  H@@ ZaM$իMz̙3'l޼-xPr)aRʄr c˜ /Ⱦߨ c͘ 9.]Hk#cϜ93 ?Yf qE$  t"xm$  H@uc=9Z-LB_>w of2}=8Xo\#im7{o o=jAܹs9U–0ق(sڇl~7Ҧ^^|.*G]6V*/NZr!X*9/$  H@oرc٣Lr-ю'B<~-ر>lϿ 駟M͹jqy=|zko~/B啄DPc8H GE"B}Rjr/,$  H (Ļn%  H@hwyݜ8bqM7]v|-Dy3pᄽKʦ^[8)-0۰aCEωj1 XjUѮ pR0[?,WW -o;.\-[VK]ib+F5#| H@$.XgRE m=_m@#ۣݍ= b؋/8s1#L܀-=xDΠ]Ɍn0o޼=gܲiӦnm ʕ  ^{mSj6,Z1UH9nN'O("2\( U$  H&k H@$Os4[p!V D^W坵sV=ᮻ 7n&UGA%]M99r(/~p#u͚5\={vR),<裱Hs(oVOIGi괼{@rlٲ%]@cRT:!AXC]Dh0"bʹ[ }nwq11TLͤD:$  H Ľ袋x" ڵk~p9DJ6K"mK,㉼{x{WUH-\~^>lF:,;N&B_]L֭mO(ut=b>ӳ}v%=w؊1CrwKO?c =q{6 -3V#6>GmۺNo]oD$  tpm$  H@}G*' 8'SaR(p,#'pBc ꫯF&":8ED#FBH38#:0Yuu@E +VaÆŕMu:ԏy%ω$΅4*]p f=ؒ;]'I'T2Av \ nz1'4f_R>'8~/x7ZԆZ߇%}! " H@ڕv0+6&l =&%s`{2&e;!bblkl᧞z*$F=-'N ^zi^/'cc#θJ&ywҥqrGzb3y`'3`\k30 ){G0q OD6XbWw}TR2%BpD( a!/6쇘{SN6[lZd܈ƍ O$  H@t 3:~ H@"8|7b\vق"+p%E|C6/|LHe[%c9[W\qE.+Ҋ6#g8Ij3'hzv(,e"]ECVH!8$-\\>ݹsgt>d#p?X 5uY1TZԆZߧ8-]wu%Z$  H + &SA [[H IH5Ѽ !|WOgyL+rzD{$ b$  H@tYNK@$ 8F8HwL NQvSKJ,)W^ye\vwHsh,'}'( CRẤ:ujŕZp`\Φ"A6:գdEՖV#ն!zh'},$  H iqaf1v%٠Qx%Bssw !-6r~܂(qH6Gs3gΌ尿),yqv\ʤIM^3N~@IDAT#zF4&`_R!"V#YxR0O5ڞC$  4vG$  H@-Iȑ#÷]k֬)\Kp#4.,D8Ĥ 2!ւ0(3f̈0#EOlcǎ _WcJҏ: '} s\)I~^B Oߒs;vv8Lj$n$  H@MB gώ͛.\Xd4Ǐ6(BpՊ!.&THǸuGj-Q#E cz cq驤w.G~z(,X F1)g=2"S+|y@bHCԕ|a<[Q\$  H@L@aF3& H@@C$NbF&f" `%E~g"п&pltI"uGшy+ƆN51}bŊ^P+W`Ʊ'|r|p Qш\٬^YQaݓ7*:Vk*لE,]-o7"6^&g3QҬ}NTh;zCQsSO=5|[ wygX~},mD= H@$R%Ǎ,O2dH8êU⸀|2A*4΅ǸqHjCT& XX*q1Z :D;wnC{?cM>Ͻ~Tx w@;H*ci#[cf#) z&`}-~HCě~t)*?ߓ$  H@@3Oۡ!A$ {WWDS䦛nOtÆ 1. 3p*E/Db qr+eƱ8 hCmzr1Eڈ_z#\63&  Ja5SIc`{_)'ȧ |i׌T*X$  H@hoFhk$  H@MG&N즈8D8x @Sԇ tfs`8yP'P?}:Սԯ/QV‹Sԅ:>zIpD9\8tͳo>B{ap7/"N7QϽ$  H@D{<"ؘw5A7l\&ⱇ{3ksc 'bBJ06Icԗ8wpC Fcz% {õC#|x=zt8Sj$  H@o=\0.w\4iR8Cg>I@$  H@MN@aF ' H@@x뭷¼yœ9s+|Kc=sv/g8T$  H@Bg wygXvm>86 Z>(XdIؼysa!dl$  H@ZŒֽw\$ 6"cǎpM7{,ڵ+|'ZG47|3 3r$  H@ZްiӦ{uk )M\0%7H@$  H(h{b$  H@08UWX~"'glO$  H@$:Hiuօ?NJ3`l`$  H@ŒGP$ 6'@ڒwetAaʔ)ᨣ  zذaC Tl$  H@:>lR"g?ƏN:p!y۶m V _җbZ΢dk%  H@$Кf}$  ?~KZ4xp5ׄSO=52%M ~;;a}-$  H@@ؾ}{x'cl+.p_cw}70Av$  H@$f4=$  1z+<3%s0jԨ0}(HgEDz血cO> 7n\V H@$  7o6lc7~G_bg}816|5*_g^Soܧ[ ocǽ3fL8ÌRm$  H`(^R$ $7MXn]\8ԮnA#]'=9/sիWG'!y!C~Z!KX]i$  H@h>i*r{Dc :B,_<̙3'D~`"hFdɒh~1FO_u@&Euar!\a\D AX D`<⪫JIGLø0 NEڊ䦛n >hp+E$  H9(h`-$  H@]uD(*8n(Ic>W_}u\eU9ơY8+Sx'o۸I%9B׬Y8E`Q<ϟcyd |/U9G0>EȞ$  H@OlېHD [5, g Qg]SڿmS%Q`e 6j~СѮymE?A(qD8V?SVbˊ28'ԯ|+/"u8)]#Gq6zNe˖E1ytuHDhY8Zq$R8᚜s \rI8û9ir!1 Bo9XD;wnkTS.KN;-_xy@~d$  H@h>ؔ<(LcU*Sp…/8s1eσ]HqO?t`s̉/o2Pu@ܐZS:ZYcǎi .zh0. o ,"0RiSƑ@?XP7ljJ@$  43J@$ ~#USǝ;0"Qz ![8 t'rPn ŋ(8ĉBqWF1 rgq㢈pƩ%֭[Ӧ|M n8|o@+U_wYMhnِml' H@WLcKi E tŰ/@T/h3A.ًU>0~@plYaBx7Cuj+VD!|-bN0(#{M A3|wlZ iR*f͊ 5i\a" s,$  H@A@aFsk! H@j&CA?^F0~0qĊtAxLⓞ0BH^x!nOϛ.D XfϞ\+9I3 8q\ `U̙3 WrV}W ?ObXH+vJ@IIf&I7[wse i-M#UcHov^f]13!ե -ZӧG6 )3b wRd#(ԊÆ ůG$  H (^Q$  4ɬ;aIrglj.q;@ u(W=ۘPX~} ZH*Qf KH !/ªD7F;Іh=P5 O T \l$  H^|A ~yZqĈAE$  H5h}$  Hoذ$ZE\k!QGs?S];!2ίk:tUAOsHE+]q +]zNdW\qE!l IBđ~8Fپ}{7qICHh"dCx~%$  t" `Ν;7 3R3n 0v)!B U='ѣ͛Kx0PUj 0ϖˆS3& 5=jO|_$  4u?$  H@NoVe{_җ~#'_VA_=>fCՍrG(y2dHgnXi܈?N-BD7sL8'Ƶ>K9nj,YϟgI37nHg}v, V/ H@@{{ #).HՆmCd~kI(0prF@6Ezؙ͛W"JQ%yA@3ԡv}E1AoZ0ch]x]Qرc{%ɶ$  H@O@aF3$  H! -L޳:q {f4Cu5mseʕ+k>=us .3#B~o80tИH-JyΤuD̯k:و0Ep-mu`vr9)_8aIMbJdˆNHjV2c &w}ڵ4X}j_^U 񀐑L,J-Ațڷ 0&>d zļGE ꐭOÔ1v{ HZzuDDgcL5fds H@$ "0$  H@@Eq@=~M8+zxPV厴!m۷on9&1,O{RDÆ gqF;wn\H&Lf DڰH@:ɋ/DBD2 65*vaq%piv"ۭԋ@:3(v0_Pmjُ1A^D Ѩ˜4&瑀$  H@ 3W$  H@u! ''NXJ2m:厣M8a$d Td22:묳¸qt:zF*+mz[8MXqA4Ci"vΝCdÇ-#$  H@hs͓$  H@N(pۺheeX~u* I um[CR&'N+M c5GҢ%!>d͚51ʕ+cv"gl۶-,]4yQAv$  H]aŊawAƇ~~dE#| ܝ/[ lZk؃{?Gkd u֧QiWQF$q= |9cبx H@$ 09$  H@5`g^ wo1 g|~y69g8 'uU&MHsª`=P3?x6mZL" H@hU^,^8dҤIYl$  2"#߷UV;wD~ N<9 ]lj|fCzD9 Y1bDmL agb̘1'Z/bؾ}{hl޼9,\0N^1jGD.Yre\M}V s=:r)qR=q"ǁDx9}TMk/ks=nO~^"-ĉc|]׼=9ITCm׬Yva<B yN3![Ϣi;Ik?>̘1#Bw 0~ 3~Bڵ:j8a+#m?E~^M7"yaI:?cf# 2B̄HhQtI@$  H 3$  HE sv#+.]Z0LFfP24CRqZI4!{8`iSQďtl?|IHh&eYYxpgwy@Q>LHvi1RL`ҷH#sM7{,FO]ٛoC^0㩧 ~{q~e,oX:緘( <~&#!@ V>]d ’/Z(FPQt>u7[~R$> 'L'u=?1&DE &B FL+9䐸 O sL\z~֭[e˖:R?ө<#{Sf9!+dꉔՊX  %Iqpլ-q\hڇXZ"X.IuO:5FH/ H@$=} H@$Ф@01yp뭷vՒ&E~_lt+|pwɊWs)4u&#T1=vت&ThgqF0UV _~y^s&ALp,GC=4 wdOkhW^ /(jdP&|;1&a7lqZVw|eҙhL63cloo?xᄂL\ڟ\>|hOB&HI5, 6ULMG#/*O?t;^;DҘ;eUW]#1p[ksvjV81 .lJc 8? Eꐭ!yaӧo?KD 3_p 1ЙgdnD.}„ p|. H@ڜŒ66O$ &@8aLd;ù$#9 [p$ xVwyaʕJ3!Տ87oޜ6E)c;eW_Jp&&N'op⦂Ch$9駟$^v%|DgD@d6u@O᷒ Vc+aG͛Ϗ 'sbٲe1ĴiOCSIS&{:9s t̙7-1EDeJ%S4CRvyHen^e;Da;9|cHeӦMQE:[n|9d lG$  H@@k{l %  H@hc8 -|e8Uq$"8cqR2 Gm3%224) uHƙSUi,/۶m _}x"$`sDKRd Rd08aմ 4&8s4Y=&ҵzO~M; T5jT\ (#&I}&cHgb &KX1Z'~OnEw$^0Bl։? v1i5/DZtiFPD'dBvo b{'2|&Xo v|P7_tEM%@DC"0S7ߩA%\C1(lz(FBx}ˌ|_3q=fCj3O$ fg\N6wЅ^;HwDDȍmy0oHݙ H@$ '05$  H@ 0QՑ7tSI ٚ&Xp62H/d/C,Jc)MĐTp&3igd,ַ"W&SaD|:Uџb7 <OL6f yѳgsG]("W) QN8!0wL2 ?V#JCDqYf\DwrUedB d$$!2) J+.tvvM~jkBAI!d A@in{s[usoתTݪs9sNU9rO!(NTa\Tn:rӹ l |\MvἃfO(΃8c__, wa|/|ao-˗_~y6lXa=aփ&"%}᷇߀|ޔs`^~ȅx=~xPc~+(X7 \lQ@P@h 3Zc? ( ( !I ]Ũ.hQGBC"cs( ޹ցVv#N!0=G:_n];hFWgTLf{p/I*tctYTG=Y^ܠ#BgJiXr{љwnyN'_ëA Һ|_ җ1yɯk#sAQ.t{:^C{hѢv?$f8p`zO6@BV7߶B:]cP'P@P@̯5[ (=TN:>HL4 Rh,;3t2V{g4 r')ܑK`C==a0s')PW:qRIL4p°~v 槑O^ ꠁsj8NJ}C+'(:Q0#|/+ oJ`(/~Z|-FlfZ%[k,;ցua{ o C=*~*Lo.=(ruIF.qa쨤B+{:N:P@P@z=sV ( ("ַK造3Fz;Ȑ!N.cॱ޷z+>h#>Rx4 aC0@WKOXI/N0ŹSthqA xwVh$T4Xi CnviDǖ Y›j^0QHg~e{Gw`㏻wg}6MNF!ݾNa:=|- Th @8TYI&un| ,L Ӵ\I6=:OBq"֋ . UM3K1!̙3'ݝ7=,XDZ!\?bƌ1Nh3s4fY>伒Vsk@O%psnA2DQ?ߜ `,q.εd k8Ɖky9fy[9?aqNX5WWոԛ~3yߢ ( (v5V@P@z#Giip4ǃCRӐG ,VC,,'Hg 4>c@϶:a-j;l@أs=ұ̶XU3??؏syApœWLÃs\]z:pŃ  x`@QLϹ:lr~cK0Hr ِuw't帨u^P@PqZ5n֤ ( (Gf5S7Eިׁ FnY-'e`_;0 8]t½1? ]hp-j;]ZKzj]^V;nDa8u֕2H:2# ~3&aÆ 1G{Lzv 0lCC]wz%3zmk)%`C^w0 'CǺ 'Ȱ-֞|ox=nd!߯f~k}Ѭu^P@P@R3J=KP@P@k4s3I=q0OJŋ;7mӑ]6;6vqfO)e˖؉J@ s.EG 7J{Wx9X7f70hf|y(/j-0| ީԘ\aG!/|g UGA<騎>㻷bŊO !C.ȰDI;;[7?W@P@P@OܜKP@P@hwuQ1ɓ's˗`:'? ޝi 'e7Y&v 2^w oGk ֛1cF7n\ p#QP@P@P@#``Fs\UP@P@tBy)S~8IMG<NIbC=(%|8 Y ȐQ (W7>tWtTRGK:n: L>塚mGB@ CD/v桇X;i)l( w_<dwT8;/vVJκaNPIG bFd:$4mdH3gN X"[ڕeй :;Pw3|zkL9~(]"cFGd.~ AW{±ΣR_ #0\7XGL:5qx( ( ( .3zrmP@P@P\ׯ_.K: Р:L_ti@C=RtVvm1(W_C<_!:D>⊘ tLvp#E=ydfXhQ[c5WSmLd<`o=sfT[& l/JKba5(:FQ\v5S7w=j  C .\pArt^l9N|g°D UN^ ` ȃA"ȈAkyxf@G ],R@C0?xa]=ztmx}֭1 lTwyŊ[ny hp|~l#8%h'e`޻vZq11HzȰJ-NӧOhЌd~Ð1EeĐ9tcw Tt{c]|eyMZk8@֌Fuva%|+. ֱ1GO+l id0t(>'ͅb JOGP@P@Ps3:7r P@)S(=Q ucX `OwZG;Rk0LӈB #n1&n :=&q=Či}+\{T>?V ~PPA؉6t̳.I`WՒgXCَ|d["}0K,C a'!8wF : *xGzzMPA8dȃ29c :pN9A … i8>#t07񍸮,?v"89sf5kVz}O0L A& CJ 6I`[X#F8 z& =!p)7o^gy9zu=Q,hF8.W7Y3_ ~x )r7 Glp=g:9ֻ\Y4# (@s8?( (q (@ p1_P@-@3d ?=L'>tn޼9qgcz<:*t_> R!x`„ 1#3 @3o@ƾ-l۶$Y[! 5Q pS]HE|>#b^W^HE ㊀ ֝LGd :ZP@P@P@m3) ( (@ dQ8bϧ; xdР}Xn] KD^H?uԲt7nܸ@ւCu$x@^uc׮]ULtx80Cv <>gy >n>m%K\DQ5AisV*'( 3_)f͢eP?*!\`FZ+~+pðR>Ï 3f̈ARdJZ ( ( (@O00!OP@P@C r!aaܹ1[o3g 1L gqFgJ!6nX-;ҏ?8C*Oi~b:1Sa fHv3Cغukۢ x.y%`%vX-cya<{k 8^NzN<8OK8.3GP d#w+>dG+-bCtoCTk-F~N3^P@P@P@00u[ ( (-(@$ 'bʔ)pC@;vz*vr7w߾}I1Cށp@ܮwHN:xwyZ AV\FG^Xogv̙Ѳ+ #/) $ d: #e)3 5Уe6j ]p,U&?q Bd&Q )C0"+ߡ|$C@W()`Txo3!(=N\ d8qb%h>P@P@P@)``F/ ( ( (P:@O?8i>qrW˂Az~8BG~10!OVbCW_d;vl4Kt$3w~I&> "/,E#EFT'GO ` (:ݕ$[e92^L X nL뒿b0Qnؓ]@2,;#=~g'L2 3ugl* ( ( 4GZ ( ( (#|'KC )|DNb :;(Mi$->T:׋Y'Møqs=^yhѹ%C`8d Æ QM܋ )C˯޽]}X Ф':/vXe"{b8/i] cYk w<±B>/|=7aڴiaС1SQ=fu+ ( ( [qzTP@BwIP@z@W: ?2pɁ`2gX"5]} (.C9va! X^N٪+ L9b<^xᅶla۶maҥq2h3S;TXm<R-/24 AA~C kv~ZVox{#/|GynFA?*7tp2=lV #GƬ8hĈ1 >__Ϛ5+4{9֯ (- ( ( cPPh4C͢ (@5t*Ic0Cmсb'&2jUn:T$g'\x1 Gr!x"' sΘdsύE4wDLƈ~ŎFz(@0 8@b0L+1M ffQ.0@r`Ǭ'dR7Sn4]W4s%` '~xXP@P@P@z=g_& ( ( (^ @J+@'vy].AqJӹ[. uW3l"F;|(<#x&/|Z ʢ ( ( (@Gftg ( ( (1Ftyz1ԥDzj r}# Ç;vWAt `1ArJ9ei{^7}u'֩3%p VO!K 1؛PԳ-͜rdٵkW8ꨣxK/RIWQ.X>+a+ ( ( t-P@P@P@^&% 5P,ftAŷ{tyG?>(k׆'|2ŏ[P@P@P@n00ۨ] ( ( ({pqǕlCI04Kޯep5kJ&'>Qsd^#F.ق7x#} $M@s^OdQ,d`?vG NM6EZ~:KfR2U0CyOt)sN@(.].eAHcv|,Z(ZP@P@P@00;] ( ( (K@PA^T@z(K,i7隆rJUuƉ<2`&聠 k(T V=o %B&b<<{챁G^daȋg}6C|'C i ȘC`D~J wix~KȔq=bBPѣc&;D`0/ԽaÆXw=J^AT _} EP@P@P@jڷT;) ( ( 'pBQFGjߎ?xA 'xb`؂P .Z8 I>gȃ5Rq!^4ٸqcxꩧ5^3B_~9i !Me˖p8I~GYg=:B 8R*1p'Ƿ;?p]#YWGAA,s„ U /8qb>/zk ȿ[Z/։@뮻.wx衇¶mO ( ( ( 00 o) ( ( 1KpyÒ! ʽ{7p (R~2"}eH]vn)|; ˖- d~OKAq92TBs^nym^|N_F; f&0lBPɝQk NƆ_Wqxr{p-9sdk1cƄQFd')WGGԩScFno9^gㆌ;wqG x (`Æ ӦMk7O9NL9^bbH᷿mUAE_O~?̝;7fؽ{wuN ( ( (j ( ( (@C9$v̮^$T֠ qƅ:w}7偎G}4p}^`֬Ya=5"֬Y[郠:vM Ŋ+]wh CRI& |^:`޾}{8묳ȑ#cD>IO? @;vY|}Oz衕&3*[n tFS'P!U ӦMC(WzM&.(f#{cv.t48B3:=-[(vi)]yM1>O2cB`Hl^S ɗ˰rJ̔0m^/|2o޼2%ˠA?2g|>`oS +MߓՇ@*j7.P@P@P@]3}) ( (Ms &M Y{8AgAt6,vٷoߚ' )k_OG.e^nJ.*2OC6,[,hRIt;6Xm 2tаrʰxљ;`;=0|QMG8Og{9![Â67P'M` ۂҥKc`@Gl'C̐I֑z:=nmQ>2p 8FY\ž hg͚5!c: {K!~Ol{D_>^:>K%>ug^mĈ10cQ֯LV@P@P@w>B/) ( ( 4IKʷz+vľ꫱sOt)L8". F-A ꓍bӦMa׮]c :P ȧ+zSXׁj 4^{@ عsg`0ah`y#^MWr`h$w:}b` HG}W֭Mڈ ``q߽fP!oF<&Y>c4ք}A`|'Yx]Nt]dR!puc'u`]8^q<љ+ ( ( T00+ ( ( ,0tHЙI9oT&H-g uszS_~[wGr`i[xN9-Qˠ3)iweĊu(ȞFF7uljUliGnkW#.sfm* ( ( 3zϾrMP@P@P@P@P@P@P@P 4~^* ( ( ( ( ( ( (00Y֫ ( ( ( ( ( ( (-/``F( ( ( ( ( ( ( (@ h* ( ( ( ( ( ( (@  ( ( ( ( ( ( ( (,3%k ( ( ( ( ( ( ( (f!  ( ( ( ( ( ( ( 4KfZ ( ( ( ( ( ( ( - ( ( ( ( ( ( (00Y֫ ( ( ( ( ( ( (-/``F( ( ( ( ( ( ( (@ h* ( ( ( ( ( ( (@  ( ( ( ( ( ( ( (,3%k ( ( ( ( ( ( ( (f!  ( ( ( ( ( ( ( 4KfZ ( ( ( ( ( ( ( - ( ( ( ( ( ( (00Y֫ ( ( ( ( ( ( (-/``F( ( ( ( ( ( ( (@ h* ( ( ( ( ( ( (@  ( ( ( ( ( ( ( (,3%k ( ( ( ( ( ( ( (f!  ( ( ( ( ( ( ( 4KfZ ( ( ( ( ( ( ( - ( ( ( ( ( ( (00Y֫ ( ( ( ( ( ( (-/``F( ( ( ( ( ( ( (@ h* ( ( ( ( ( ( (@  ( ( ( ( ( ( ( (,3%k ( ( ( ( ( ( ( (f!  ( ( ( ( ( ( ( 4KfZ ( ( ( ( ( ( ( - ( ( ( ( ( ( (00Y֫ ( ( ( ( ( ( (-/``F( ( ( ( ( ( ( (@ h* ( ( ( ( ( ( (@  ( ( ( ( ( ( ( (,3%k ( ( ( ( ( ( ( (f!  ( ( ( ( ( ( ( 4KfZ ( ( ( ( ( ( ( - ( ( ( ( ( ( (00Y֫ ( ( ( ( ( ( (-/``F( ( ( ( ( ( ( (@ h* ( ( ( ( ( ( (@  ( ( ( ( ( ( ( (,3%k ( ( ( ( ( ( ( (f!  ( ( ( ( ( ( ( 4KfZ ( ( ( ( ( ( ( - ( ( ( ( ( ( (00Y֫ ( ( ( ( ( ( (-/``F( ( ( ( ( ( ( (@ h* ( ( ( ( ( ( (@  ( ( ( ( ( ( ( (,3%k ( ( ( ( ( ( ( (f!  ( ( ( ( ( ( ( 4KfZ ( ( ( ( ( ( ( - ( ( ( ( ( ( (00Y֫ ( ( ( ( ( ( (-/``F( ( ( ( ( ( ( (@ h* ( ( ( ( ( ( (@  ( ( ( ( ( ( ( (,3%k ( ( ( ( ( ( ( (f!  ( ( ( ( ( ( ( 4KfZ ( ( ( ( ( ( ( - ( ( ( ( ( ( (00Y֫ ( ( ( ( ( ( (-/``F( ( ( ( ( ( ( (@ h* ( ( ( ( ( ( (@  ( ( ( ( ( ( ( (,3%k ( ( ( ( ( ( ( (f!  ( ( ( ( ( ( ( 4KfZ ( ( ( ( ( ( ( - ( ( ( ( ( ( (00Y֫ ( ( ( ( ( ( (-/``F( ( ( ( ( ( ( (@ h* ( ( ( ( ( ( (@  ( ( ( ( ( ( ( (,3%k ( ( ( ( ( ( ( (f!  ( ( ( ( ( ( ( 4KfZ ( ( ( ( ( ( ( [^@P@P@P@P@P`^}հs믇/x裏p@oon ( (@00WIP@P@P@P@G=PX~}x饗;{>c #FSN  GY( ( (@=DP@P@P@P@P ڵ+a޼yn ȨgG?0rp饗ÇQ P@P@肀]sVP@P@P@P@P`o ̟??z ov^<49}-r)gP@P@92v ( T ]wmo &M 'O<( ( (@Xn]† چ+ueɮA~: 6,|VAå,[,1lf͊ ( ( -dP@hMƕ޲eK;cJcS_T3ZpP@P@z+nqvAGqD:uj:th۷okv,Yd [ox ^:.@XjU5j ( (@K ђݍV@P@V 8wi|բ ( (K@ /yꩧ/2spE~)! `߿ :K/ k֬ sOXvmӟ3_dYyǒQMPGgu ( (^uP@P@P@P@P` +?l bc0F!0?[P@P@Pf4P@P@P@P@P@ Kq8|A} GuTkkMy]V@P@P@ |qUY ( ( ( ( (@w6IDATJuGaJJP@P@NuP@P@x믿6o^xᅰcǎk׮xG'>xСC '<Ȳ(/c~M[oű} ㎻>cY ³>S+޽;۱8 ~aȐ!sL:yufZG>=0hРYlɒ%aժUq:۷o8餓رc?o`ݷocxsW"xf;X.`x|mׯ_;-'NS˩=vX_OΣ ( (Xwy'<3a͚5aΝ`?O| d5jTsnÃ7|3G]Qܧ\f=/XqXnuko&3P@P@.|`OCp}-]X* ( T aΜ9A{hCXhФo̘1qi%e˖}->Q5/t|6nܸb+dcG}4.cz9MםYƲ={v rHy뮻.K% _WÀ+W /b'cGֳ>;\}r=?gƃ>lSG/8wymӟb@Lۄ`k66Hw2+ ( tx.`x1瑜r~<籜ON5狜QM¯~v?~|ij9w}G: yv \aoÇ8o]6\M0dǹ=Æ ^zi9rdl$~ *Ok 3f̈ܯJbPFgesҿK`smreIlOQ)(#UN __ŀޭޫꪫuAP@P@ ?KnrQP@PxpM7m۶FOI wFJ& R!w1]G.QD@]ciZ2JKNcÅ^?{:X. :y,\0?.6MǼ6+^doS2y7!ԕͦM:tϧF|pA9}P@P@ p9w&,T\>87'?IK 25puT'' uX62׿)8#72,[,f;c: pHaXTR 1~EOQ^`-,;/3\C:d ,\¾z+eƍaҤI1_Wq^P@P@fw]P@P[';hBc% f<0HKpAMM˥g4h `h;  iÊ+!YGӤzy/4G (7Mgw奒i>M51oDo5rP@P@8/#3Atr~̹'$/9f}5ab`=z?W;$!uT༶+\[ԓK믿>PϹ-$N;?̗Qukޣ>Y(WS~'|2_nJ\W7@}P@P@fw]P@P[۷o/i c)S#FǝY4򑉁3 j٬ u]tE ɜ5@R.UpMð F ?mōl5T>cƀ9^wG.^Š:XF1Pt^ȃ䙎a(4hP LǓ)l5 i;cT'g}v lNhe4^F{'zYwƌNid1i=3c 48CF^6kӛ|Si|:_+ ( 4Wy  ,stNO31oϸ>As\2m \uA^f iV|Jg͚Nvºۿ[rva .uf kO>=tI18ƍ%e¸뮻J ܸ{5E)K֒tCrJ9sfFz@+WƠbV-ZTN_oڄLgqF !=ٰ\pA*ʃTϋb!|0; ( 4^ƛZ ( (PٜgsN8!6B҈]S4H hL? NWkA4Θ1#uYN69XJq 4hsW̵D㎹B`ȗ1$N;idSa`~a>! _Bl,nl7_6/M.\>(WhMi{ߺu£>ފdc,?P@P@ aqʄ UW]O>OsNy+ ꫯ9g%dTAҜ #'KyQ<^3O>iyڂn^!pQ \ve S!k)2Pk ]Wp>O@ŹuX'1uM \WPG~,@C\딮c֌ 62AǏ]|myQMo.||,uH9iP@P@jb^[=N ( (P d dԱ1b_:| IAgG9眰z;h7iSIid<#e(6Z8Ii>$ GfbPF^QF,TB:FpһZh`gpR\~Fj|>+ ( 4^@ tD${4A΅S`L]LSp3Ȥy.[dIZ|d FO7^ DiF7ۂۘ!8'Pz uϞ=;#x|\` C"5k$C ^)>~P?zpq ri{>+ ( 4^/P@P@2 ƾpHX|xHPH߾}cn-G ؓՂyR"dtĉ%AGO/D/pn}/˿K?~I} \N 8hРLܕB })rQ\p}N욍+8D<"dd6@T}^P@P@:00s#P@P@*@V!Bƍz+8񴳆csN=nܸ@F^L'XBp?CP)S{SO~v|gj^S5Ekfp-@7C7ݱb־J\W\_t6lڴ$]W ( ( ( ( (@ qB \Ɣ~饗4i+ ({Q|H|x p r>eѨeC3gΌ;kZvZP@P@>\N ( ( 2f0,ibJ O>΂cĈ6Sy)w iy/D#0e2[m#ӦM zjXfMʕ+ 2A>v\pzj!Ht-n:̣,\W8;]=+ \_S0b 6 2$uYqS( ( .``FfΡ ( 4T?2Ö-[JRёLˉ "3ɓ{d# Dw۶mqX_=}MbcCal.\ :jPgxx|pʝy䑰vڶ9F1RnǶ} ( (-dd [F1r-jšA{4 0aB ^|y ޸qc\; tQK HSt[<1a9Gf ^ z('Sr=\_aD!襣@rkERoXbExb֕T' aE1HgP@P@ xSkT@P@jsÍ7+9$ xg]OSN gyfׯ_>^y^4BzҘJ):e압P>:th<Uބp])tbȑz :x2qIPxwy.]ϟ1&(%w%J7C2qpq1%? l=پ}{ugY8e( (^uP@PW С)43gNx/\-8adN\ h‹{7gp3k֬. YP@P63jrjP@P4qgUW]8㌘vI {4^?y|P8y5mzh<$CcT|%i}瞋wղfL㹣k-hOFFK.$.:^P@Py08qb88͹àA.-c 8?Νۖ݁:9'`Ft'?p+ ( 4WZ ( (P f4ѹMg}vae˖FT)AdxGW\qEU\1h`T@2%dz1m/oۅK.:PT2^ֆN#A6d\l(mQ@P@YN;%YX>}ɓ'O?=f$&]S&\'tM=&0`7׳=) ?(+ضSO=5\zz08S'mS '`N`NVՏP@P@00v (I8{y~a̘1q ؗCA$C4pWc=F 8UP`I\!ý1Jц fJ#5C$.oL&L]o;w;7Nb3bĈj\gP@P@#9Z  rf54bO8O|]Uf́fիWŋ ೟l8p`0p ozV[7l_:&PA&>SY{Ez J!1qͯc؏ 5N ( (@c h) (Mƴcƴ3|뭷݅Ek׮ .]Z23 e؍z6Jq'[FK@iӦ:l߾=f#ygVf_pLHSdm裏Y5P% ˅^ :bQ@P@;w ( %@C u3gΌw\{m"AyGc3yB.{SPol 1nܸxFFSWyƉRG>i<ۆK>8aQ@P@w\f/9kp%  Һt]k'~HutLP2SN-(m(nY9Fax=|b=^zk YuYƵw6o\:0$߹P@P@00] ( (%80zf̝Qn!BHK_0HPGLO- j7/1#F*m˥vNvF/5b;f8r+M ( ( #( Ț𦺚,).'e)<8\WQMP똞5E&|{5Õp~5F*\Wnr<#aŊ4-eRoGgP@P@ |k; ( (! yc(4UjeZZ#r#GC_>J4XkЀoݻúu뚹X74V s-C(+]uP@P@-OSg<:UhHA]YszNJ<Ք<:k ""8SX6\_4K1h_,Yy^,^L ( ("`ƌnav! ( (@e$iޝ;w::蠒FbOlpt4o4>PKThAO3ɘa2p㭷a#FıbqSt„ SO1: ( (@ 0R0_tiLwW<@{,A#Gy9?C킥i' G\W0 Y":+d㺂|()ƍ,Ζ݌ flR2`"@j =^tV+ ( 4_P@P 4Ι3'7|YgΊ3 ]xy!)j;*3FqU @jaC9-͛ V1u1 7%do?0`O jRNSa[|` L3403 Ř 4{?m+X~Hf֫iNu ( (8:ujxK3y8+sΉծ%A dZغukb!j}׮]幖 @Վ %-[n渌@B#j1paqwSpI+f9w8_sG 46mZ >HsM9t d͛7@4=?|;c=8o Ai.Ҹ?-ZGľK"=<!3όY 7u /ЖR<׎cb!0}Y`Oq>V@P@N}Kqu9 ( (@hL$1 4XHC*wJ0A-#[qi#F uqG4QHƤ7>a N?x7Wz y'ccL/ã>N>uFARJ0 4|8z.5pWO@crºo5k:/KL|oRRP^muСFUCT[) ( T/:9oܗsc2Ipy;d]㼐kλi&s28篩yR}\̚5!LΝV {ի6l#2^PI.^8^ < ;JalW1$.a۾)z)kCd,Ѷmڜ*fm8W P@P@.w"gU@P@]FDR;4qg dD!MCºXi8=`r^ `hNCxZNk66̦z$%o3x _;찴P@P@Ysr:8'Ж@TRsz's# d+e0O=TXbE|Cp% ]Dq8"Y)¸w\҅.Dp %Jp@#zQD(F#S NMSМtr:z󞷲Sy SZ*.\ ;MK[-[b o:~3I =w+R$ɖkl&`i$d2 )u FHދR@P@U*$g0lkkؚjG^Rw)9-c}K.+WĆ\"`?744K1cƌxƠ6lצrIٖ$lI~wΝ;éSR)I,o-[kpn,sX1$?;v$WD蒞'ʄ{ Mww(% ( (P%` ( (0y+*ezǐABO@u9c 7…t$cCsqn,^ti2'% \Rƙ4r}s2f86T`lKŋh~|Ou;xnǏ9y$CJ %yۍkk:-AX#m ( (c+ 7oׯ>T<(zY۱^sss3f.g?m۶jxT YƘKII$36{ݝS!#10 ߸ _ q>[..;ئpيLy^sd=sl:GR%ɓIẓxF---qܞű$^ܼy *Pq~\9 ,}2|yP)s\cP@P@?M_-ݹ ( T  葨@Yޔ+ xcKpJ(fנ.2 ! b$B*Ds|m\#`'S>$0gKy1|ʼntGl&cµkȜd 'FX 9o .X ߿?6bj=@I,ibXa\N  ( (@Y{{{㸕*k$g03vcY \؎2bw\D>˗/~ٌg͚5HH`[uS)HР/뒘̖Y$390'Qqܩ̡ؗc/|ik{I$p@w^NU>3!A?ý3go8 +i6sy ( (V( (c"@0/*A0 R!YAB7yh5rtb?e>n\ IpH}H aƾCOه@$]b^W,!4M2 HJIXKOR# Melbi ( (L)C>r8]a<̖P8>i.x=f t#'X~%+zd`…$F:;ʙ"pVP@P`tn? ( (#*[mNoqr\ǯ_9d*iP@P@P`| 0 ; %Vd>+_dlYP@PL̨cP@P@OT$ɯ̬%riֳsN\;<lsfi ( ( O?zzzr{F|iNb f͊K{S@P@3*YP@P@Q`9s䔕0s΅{CBǍ7™3gr:X빱rEK ( ('0{0u$}%={6tuuŋpĉ6ۘWp  ( (@u (wuޯV@P@P 46mZLm޽{N::;;êUŋ %P>˗ÇU3RcYq+ ( ( (0&L£G 4 n݊'\2X"VCT#ի͛իWjjj¦MwnP@P@K_TW ( (##@bŋÑ#Gr*]A)b1}vڤIbÇל ?gϞHҰ) ( (xq8tPxIM!H`0y?~ *1ȆIرcGhii/ ( (U#`bF<*; ( (0< tӧ ^"FI?EXm# ( (/ѣ|I4+Ҝc 5*mݺ5lذ!&sMP@P:L̨fP@P@FHO>wQ0:RK֬YfK CP@P@PHK, 0iT[|y+nQ( (#`bF< { ( (0QI 1J0nFYꤾ>.[’%MMMaʔ)1p:P@P@P`~RT~LYxM\PH9sf[,^8,Y$~gYTeqNP@PzL̨geOP@P@FAǏ۷oqY:===c9`#)ĉ>(tK( ( (@ |8`ˇĤnO[!S@P@J01D0wW@P@y %Iæ ( ( #ii˜yE1z ( T ( ( ( ( ( ( ( TUñk ( ( ( ( ( ( ( (P&fT ( ( ( ( ( ( ( (P&fTñk ( ( ( ( ( ( ( (P&fT ( ( ( ( ( ( ( (P2PVIENDB`stargz-snapshotter-0.12.0/docs/ipfs.md000066400000000000000000000162551426301527400177040ustar00rootroot00000000000000# Running containers on IPFS (experimental) You can run OCI-compatible container images on IPFS with lazy pulling. To enable this feature, add the following configuration to `config.toml` of Stargz Snapsohtter (typically located at `/etc/containerd-stargz-grpc/config.toml`). ```toml ipfs = true ``` ## IPFS-enabled OCI Image For obtaining IPFS-enabled OCI Image, each descriptor in an OCI image must contain the following [IPFS URL](https://docs.ipfs.io/how-to/address-ipfs-on-web/#native-urls) in `urls` field. ``` ipfs:// ``` `` is the Base32 case-insensitive CIDv1 of the blob that the descriptor points to. An image is represented as a CID pointing to the OCI descriptor of the top-level blob of the image (i.e. image index). The following is an example OCI descriptor pointing to the image index of an IPFS-enabled image: ```console # ipfs cat bafkreie7754qk7fl56ebauawdgfuqqa3kdd7sotvuhsm6wbz3qin6ssw3a | jq { "mediaType": "application/vnd.oci.image.index.v1+json", "digest": "sha256:80d6aec48c0a74635a5f3dc106328c1673afaa21ed6e1270a9a44de66e8ffa55", "size": 314, "urls": [ "ipfs://bafkreiea22xmjdakorrvuxz5yeddfdawoox2uipnnyjhbknejxtg5d72ku" ] } ``` ## Lazy pulling with Stargz Snapshotter If layer descriptors of an image contain the URLs described above and these blobs are formatted as eStargz, Stargz Snapshotter mounts them from IPFS to the container's rootfs using FUSE with lazy pulling support. Thus container can startup without waiting for the entire image contents being locally available. Necessary chunks of contents (e.g. each file in the rootfs) are fetched from IPFS on-demand. If the container image isn't eStargz or the snapshotter isn't Stargz Snapshotter (e.g. overlayfs snapshotter), containerd fetches the entire image contents from IPFS and unpacks it to the local directory before starting the container. Thus possibly you'll see slow container cold-start. ## Examples This section describes some examples of storing images to IPFS and running them as containers. Make sure IPFS daemon runs on your node. For example, you can run an IPFS daemon using the following command. ``` ipfs daemon ``` :information_source: If you don't want IPFS to communicate with nodes on the internet, you can run IPFS daemon in offline mode using `--offline` flag or you can create a private IPFS network as described in Appendix 1. ### Running a container with lazy pulling `ctr-remote image ipfs-push` command converts an image to IPFS-enabled eStargz and stores it to IPFS. ```console # ctr-remote i pull ghcr.io/stargz-containers/python:3.9-org # ctr-remote i ipfs-push ghcr.io/stargz-containers/python:3.9-org bafkreie7754qk7fl56ebauawdgfuqqa3kdd7sotvuhsm6wbz3qin6ssw3a ``` The printed IPFS CID (`bafkreie7754qk7fl56ebauawdgfuqqa3kdd7sotvuhsm6wbz3qin6ssw3a`) points to an OCI descriptor which points to the image index of the added image. ```console # ipfs cat bafkreie7754qk7fl56ebauawdgfuqqa3kdd7sotvuhsm6wbz3qin6ssw3a | jq { "mediaType": "application/vnd.oci.image.index.v1+json", "digest": "sha256:80d6aec48c0a74635a5f3dc106328c1673afaa21ed6e1270a9a44de66e8ffa55", "size": 314, "urls": [ "ipfs://bafkreiea22xmjdakorrvuxz5yeddfdawoox2uipnnyjhbknejxtg5d72ku" ] } ``` You can run this image from IPFS using that CID as an image reference for `ctr-remote image rpull`. `--ipfs` option is needed for enabling this. Note that `ctr-remote` accepts an IPFS CID as the image reference but doesn't support `/ipfs`-prefixed path as of now. We're working on eliminating this limitation. ```console # time ( ctr-remote i rpull --ipfs bafkreie7754qk7fl56ebauawdgfuqqa3kdd7sotvuhsm6wbz3qin6ssw3a && \ ctr-remote run --snapshotter=stargz --rm -t bafkreie7754qk7fl56ebauawdgfuqqa3kdd7sotvuhsm6wbz3qin6ssw3a foo python -c 'print("Hello, World!")' ) fetching sha256:80d6aec4... application/vnd.oci.image.index.v1+json fetching sha256:16d36f86... application/vnd.oci.image.manifest.v1+json fetching sha256:236b4bd7... application/vnd.oci.image.config.v1+json Hello, World! real 0m1.099s user 0m0.047s sys 0m0.037s ``` ### Running a container without lazy pulling Though eStargz-based lazy pulling is highly recommended for speeding up the container startup time, you can store and run non-eStargz images with IPFS as well. In this case, containerd fetches the entire image contents from IPFS and unpacks it to the local directory before starting the container. You can add a non-eStargz image to IPFS using `--estargz=false` option. ```console # ctr-remote i pull ghcr.io/stargz-containers/python:3.9-org # ctr-remote i ipfs-push --estargz=false ghcr.io/stargz-containers/python:3.9-org bafkreienbir4knaofs3o5f57kqw2the2v7zdhdlzpkq346mipuopwvqhty ``` You don't need FUSE nor stargz snapshotter for running this image but will see slow container cold-start. This example uses overlayfs snapshotter of containerd. ```console # time ( ctr-remote i rpull --snapshotter=overlayfs --ipfs bafkreienbir4knaofs3o5f57kqw2the2v7zdhdlzpkq346mipuopwvqhty && \ ctr-remote run --snapshotter=overlayfs --rm -t bafkreienbir4knaofs3o5f57kqw2the2v7zdhdlzpkq346mipuopwvqhty foo python -c 'print("Hello, World!")' ) fetching sha256:7240ac9f... application/vnd.oci.image.index.v1+json fetching sha256:17dc54f4... application/vnd.oci.image.manifest.v1+json fetching sha256:6f1289b1... application/vnd.oci.image.config.v1+json fetching sha256:9476e460... application/vnd.oci.image.layer.v1.tar+gzip fetching sha256:64c0f10e... application/vnd.oci.image.layer.v1.tar+gzip fetching sha256:4c25b309... application/vnd.oci.image.layer.v1.tar+gzip fetching sha256:942374d5... application/vnd.oci.image.layer.v1.tar+gzip fetching sha256:3fff52a3... application/vnd.oci.image.layer.v1.tar+gzip fetching sha256:5cf06daf... application/vnd.oci.image.layer.v1.tar+gzip fetching sha256:419e258e... application/vnd.oci.image.layer.v1.tar+gzip fetching sha256:1acf5650... application/vnd.oci.image.layer.v1.tar+gzip fetching sha256:b95c0dd0... application/vnd.oci.image.layer.v1.tar+gzip Hello, World! real 0m11.320s user 0m0.556s sys 0m0.280s ``` ## Appendix 1: Creating IPFS private network You can create a private IPFS network as described in the official docs. - https://github.com/ipfs/go-ipfs/blob/v0.10.0/docs/experimental-features.md#private-networks The following is the summary. First, generate a key and save it to `~/.ipfs/swarm.key` (or under `$IPFS_PATH` if configured) of nodes you want to have in the network. IPFS only connects to peers having this key. ``` go install github.com/Kubuxu/go-ipfs-swarm-key-gen/ipfs-swarm-key-gen@latest ~/go/bin/ipfs-swarm-key-gen > ~/.ipfs/swarm.key ``` Select nodes as a bootstrap nodes. IPFS daemons learn about the peers on the private network from them. Configure all non-bootstrap nodes to recognize only our bootstrap nodes instead of public ones like the following example. ``` ipfs bootstrap rm --all ipfs bootstrap add /ip4//tcp/4001/ipfs/ ``` :information_source: You can get Peer ID of a node by `ipfs config show | grep "PeerID"`. Finally, start all nodes in the private network. ``` export LIBP2P_FORCE_PNET=1 ipfs daemon ``` `LIBP2P_FORCE_PNET=1` makes sure that the daemon uses the private network and fails if the private network isn't configured. stargz-snapshotter-0.12.0/docs/overview.md000066400000000000000000000263441426301527400206110ustar00rootroot00000000000000# Containerd Stargz Snapshotter Plugin Overview __Before get through this overview document, we recommend you to read [README](../README.md).__ Pulling image is one of the time-consuming steps in the container startup process. 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) - [#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) The solution for the fast image distribution is called *Remote Snapshotter* plugin. 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 startup containers before the entire image contents to be locally available. We call these remotely mounted layers as *remote snapshots*. *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. 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). 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. This document gives you a high-level overview of stargz snapshotter. ![overview](/docs/images/overview01.png) ## 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. 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. 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. We assume that you are using containerd (> v1.4.2). ```toml version = 2 # Plug stargz snapshotter into containerd # Containerd recognizes stargz snapshotter through specified socket address. # The specified address below is the default which stargz snapshotter listen to. [proxy_plugins] [proxy_plugins.stargz] type = "snapshot" address = "/run/containerd-stargz-grpc/containerd-stargz-grpc.sock" # Use stargz snapshotter through CRI [plugins."io.containerd.grpc.v1.cri".containerd] snapshotter = "stargz" disable_snapshot_annotations = false ``` This repo contains [a Dockerfile as a KinD node image](/Dockerfile) which includes the above configuration. ## State directory Stargz snapshotter mounts eStargz layers from registries to the node using FUSE. 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 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 /`. Instead, you can directly access the directory by specifying the path (`/.stargz-snapshotter`). State directory contains JSON-formatted metadata files for each layer. In the following example, metadata JSON files for overlayed 7 layers are visible. 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. - `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 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. ```console # ctr-remote run --rm -t --snapshotter=stargz docker.io/stargz/golang:1.12.9-esgz test /bin/bash root@1d43741b8d29:/go# ls -a / . bin dev go lib media opt root sbin sys usr .. boot etc home lib64 mnt proc run srv tmp var root@1d43741b8d29:/go# ls /.stargz-snapshotter/* /.stargz-snapshotter/sha256:2b1fc65cafe05b65acc9e9f186df4dd81ae74c58ef73d89ecfc15e7286b3e960.json /.stargz-snapshotter/sha256:42d56485c1f672e394a02855048774621731c8fd44a54dc816a421a3a52b8482.json /.stargz-snapshotter/sha256:6a5826d877de5c93fb4a9e1d0369cfdef6d43df2610562501ebf42e4bcb2ef73.json /.stargz-snapshotter/sha256:a4d35801573274df19d9c2ae2aed80eba96d5aa69a38c464e1f01f9abf81e34e.json /.stargz-snapshotter/sha256:ab13100112faac6e04d2da2281db3df942efc8cef2532ab2cac688c6232944d8.json /.stargz-snapshotter/sha256:e8cc31024eb09fe216ad906392aec139038330c6d29dfd3fe5c81c4b2dd21430.json /.stargz-snapshotter/sha256:f077511be7d385c17ba88980379c5cd0aab7068844dffa7a1cefbf68cc3daea3.json root@1d43741b8d29:/go# cat /.stargz-snapshotter/* {"digest":"sha256:2b1fc65cafe05b65acc9e9f186df4dd81ae74c58ef73d89ecfc15e7286b3e960","size":131339690,"fetchedSize":7939690,"fetchedPercent":6.045156646859757} {"digest":"sha256:42d56485c1f672e394a02855048774621731c8fd44a54dc816a421a3a52b8482","size":10047608,"fetchedSize":2047608,"fetchedPercent":20.379059374131632} {"digest":"sha256:6a5826d877de5c93fb4a9e1d0369cfdef6d43df2610562501ebf42e4bcb2ef73","size":54352828,"fetchedSize":2302828,"fetchedPercent":4.236813584014432} {"digest":"sha256:a4d35801573274df19d9c2ae2aed80eba96d5aa69a38c464e1f01f9abf81e34e","size":70359295,"fetchedSize":2259295,"fetchedPercent":3.211082487395588} {"digest":"sha256:ab13100112faac6e04d2da2281db3df942efc8cef2532ab2cac688c6232944d8","size":7890588,"fetchedSize":2140588,"fetchedPercent":27.12837116828302} {"digest":"sha256:e8cc31024eb09fe216ad906392aec139038330c6d29dfd3fe5c81c4b2dd21430","size":52934435,"fetchedSize":2634435,"fetchedPercent":4.976788738748227} {"digest":"sha256:f077511be7d385c17ba88980379c5cd0aab7068844dffa7a1cefbf68cc3daea3","size":580,"fetchedSize":580,"fetchedPercent":100} ``` ## Registry-related configuration You can configure stargz snapshotter for accessing registries with custom configurations. The config file must be formatted with TOML and can be passed to stargz snapshotter with `--config` option. ### Authentication Stargz snapshotter doesn't share private registries creds with containerd. Instead, this supports authentication in the following methods, - Using `$DOCKER_CONFIG` or `~/.docker/config.json` - Proxying and scanning CRI Image Service API - Using Kubernetes secrets (type = `kubernetes.io/dockerconfigjson`) #### dockerconfig-based authentication By default, This snapshotter tries to get creds from `$DOCKER_CONFIG` or `~/.docker/config.json`. Following example enables stargz snapshotter to access to private registries using `docker login` command. [`nerdctl login`](https://github.com/containerd/nerdctl) can also be used for this. Stargz snapshotter doesn't share credentials with containerd so credentials specified by `ctr-remote`'s `--user` option in the example is just for containerd. ```console # docker login (Enter username and password) # ctr-remote image rpull --user : docker.io//ubuntu:18.04 ``` #### CRI-based authentication Following configuration enables stargz snapshotter to pull private images on Kubernetes. The snapshotter works as a proxy of CRI Image Service and exposes CRI Image Service API on the snapshotter's unix socket (i.e. `/run/containerd-stargz-grpc/containerd-stargz-grpc.sock`). 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. ```toml # Stargz Snapshotter proxies CRI Image Service into containerd socket. [cri_keychain] enable_keychain = true image_service_path = "/run/containerd/containerd.sock" ``` #### kubeconfig-based authentication This is another way to enable lazy pulling of private images on Kubernetes. Following configuration enables stargz snapshotter to access to private registries using kubernetes secrets (type = `kubernetes.io/dockerconfigjson`) in the cluster using kubeconfig files. You can specify the path of kubeconfig file using `kubeconfig_path` option. It's no problem that the specified file doesn't exist when this snapshotter starts. In this case, snapsohtter polls the file until actually provided. This is useful for some environments (e.g. single node cluster with containerized apiserver) where stargz snapshotter needs to start before everything, including booting containerd/kubelet/apiserver and configuring users/roles. If no `kubeconfig_path` is specified, snapshotter searches kubeconfig files from `$KUBECONFIG` or `~/.kube/config`. ```toml # Use Kubernetes secrets accessible by the kubeconfig `/etc/kubernetes/snapshotter/config.conf`. [kubeconfig_keychain] enable_keychain = true kubeconfig_path = "/etc/kubernetes/snapshotter/config.conf" ``` Please note that kubeconfig-based authentication requires additional privilege (i.e. kubeconfig to list/watch secrets) to the node. And this doesn't work if kubelet retrieve creds from somewhere not API server (e.g. [credential provider](https://kubernetes.io/docs/tasks/kubelet-credential-provider/kubelet-credential-provider/)). ### Registry mirrors and insecure connection You can also configure mirrored registries and insecure connection. The hostname used as a mirror host can be specified using `host` option. If an optional field `insecure` is `true`, snapshotter tries to connect to the registry using plain HTTP instead of HTTPS. ```toml # Use `mirrorhost.io` as a mirrored host of `exampleregistry.io` and # use plain HTTP for connecting to the mirror host. [[resolver.host."exampleregistry.io".mirrors]] host = "mirrorhost.io" insecure = true # Use plain HTTP for connecting to `exampleregistry.io`. [[resolver.host."exampleregistry.io".mirrors]] host = "exampleregistry.io" insecure = true ``` The config file can be passed to stargz snapshotter using `containerd-stargz-grpc`'s `--config` option. ## Make your remote snapshotter It isn't difficult for you to implement your remote snapshotter using [our general snapshotter package](/snapshot) without considering the protocol between that and containerd. You can configure the remote snapshotter with your `FileSystem` structure which you want to use as a backend filesystem. [Our snapshotter command](/cmd/containerd-stargz-grpc/main.go) is a good example for the integration. stargz-snapshotter-0.12.0/docs/pre-converted-images.md000066400000000000000000000143361426301527400227610ustar00rootroot00000000000000# Trying pre-converted images We have several pre-converted stargz images on Github Container Registry (`ghcr.io/stargz-containers`), mainly for benchmarking purpose. This document lists them. :information_source: You can build your eStargz images optimized for your workload, using [`ctr-remote` command](/docs/ctr-remote.md). :information_source: You can convert arbitrary images into eStargz on the registry-side, using [`estargz.kontain.me`](https://estargz.kontain.me). ## Pre-converted images In the following table, image names listed in `Image Name` contain the following suffixes based on the type of the image. - `org`: Legacy image copied from `docker.io/library` without optimization. Layers are normal tarballs. - `esgz`: eStargz-formatted version of the `org` images. `ctr-remote images optimize` command is used for the optimization. `Optimized Workload` column describes workloads used for building `esgz` images. We optimized these images for benchmarking which is based on [HelloBench](https://github.com/Tintri/hello-bench) so we specified "hello-world"-like workloads for the command. See [benchmarking script](/script/benchmark/hello-bench/src/hello.py) for the exact command option specified for `ctr-remote images optimize`. |Image Name|Optimized Workload| ---|--- |`ghcr.io/stargz-containers/alpine:3.15.3-org`|Executing `echo hello` on the shell| |`ghcr.io/stargz-containers/alpine:3.15.3-esgz`|Executing `echo hello` on the shell| |`ghcr.io/stargz-containers/drupal:9.3.9-org`|Code execution until up and ready message (`apache2 -D FOREGROUND`) is printed| |`ghcr.io/stargz-containers/drupal:9.3.9-esgz`|Code execution until up and ready message (`apache2 -D FOREGROUND`) is printed| |`ghcr.io/stargz-containers/fedora:35-org`|Executing `echo hello` on the shell| |`ghcr.io/stargz-containers/fedora:35-esgz`|Executing `echo hello` on the shell| |`ghcr.io/stargz-containers/gcc:11.2.0-org`|Compiling and executing a program which prints `hello`| |`ghcr.io/stargz-containers/gcc:11.2.0-esgz`|Compiling and executing a program which prints `hello`| |`ghcr.io/stargz-containers/golang:1.18-org`|Compiling and executing a program which prints `hello`| |`ghcr.io/stargz-containers/golang:1.18-esgz`|Compiling and executing a program which prints `hello`| |`ghcr.io/stargz-containers/jenkins:2.60.3-org`|Code execution until up and ready message (`Jenkins is fully up and running`) is printed| |`ghcr.io/stargz-containers/jenkins:2.60.3-esgz`|Code execution until up and ready message (`Jenkins is fully up and running`) is printed| |`ghcr.io/stargz-containers/jruby:9.3.4-org`|Printing `hello`| |`ghcr.io/stargz-containers/jruby:9.3.4-esgz`|Printing `hello`| |`ghcr.io/stargz-containers/node:17.8.0-org`|Printing `hello`| |`ghcr.io/stargz-containers/node:17.8.0-esgz`|Printing `hello`| |`ghcr.io/stargz-containers/perl:5.34.1-org`|Printing `hello`| |`ghcr.io/stargz-containers/perl:5.34.1-esgz`|Printing `hello`| |`ghcr.io/stargz-containers/php:8.1.4-org`|Printing `hello`| |`ghcr.io/stargz-containers/php:8.1.4-esgz`|Printing `hello`| |`ghcr.io/stargz-containers/pypy:3.9-org`|Printing `hello`| |`ghcr.io/stargz-containers/pypy:3.9-esgz`|Printing `hello`| |`ghcr.io/stargz-containers/python:3.10-org`|Printing `hello`| |`ghcr.io/stargz-containers/python:3.10-esgz`|Printing `hello`| |`ghcr.io/stargz-containers/r-base:4.1.3-org`|Printing `hello`| |`ghcr.io/stargz-containers/r-base:4.1.3-esgz`|Printing `hello`| |`ghcr.io/stargz-containers/redis:6.2.6-org`|Code execution until up and ready message (`Ready to accept connections`) is printed| |`ghcr.io/stargz-containers/redis:6.2.6-esgz`|Code execution until up and ready message (`Ready to accept connections`) is printed| |`ghcr.io/stargz-containers/rethinkdb:2.4.1-org`|Code execution until up and ready message (`Server ready`) is printed| |`ghcr.io/stargz-containers/rethinkdb:2.4.1-esgz`|Code execution until up and ready message (`Server ready`) is printed| |`ghcr.io/stargz-containers/tomcat:10.1.0-jdk17-openjdk-bullseye-org`|Code execution until up and ready message (`Server startup`) is printed| |`ghcr.io/stargz-containers/tomcat:10.1.0-jdk17-openjdk-bullseye-esgz`|Code execution until up and ready message (`Server startup`) is printed| |`ghcr.io/stargz-containers/postgres:14.2-org`|Code execution until up and ready message (`database system is ready to accept connections`) is printed| |`ghcr.io/stargz-containers/postgres:14.2-esgz`|Code execution until up and ready message (`database system is ready to accept connections`) is printed| |`ghcr.io/stargz-containers/wordpress:5.9.2-org`|Code execution until up and ready message (`apache2 -D FOREGROUND`) is printed| |`ghcr.io/stargz-containers/wordpress:5.9.2-esgz`|Code execution until up and ready message (`apache2 -D FOREGROUND`) is printed| |`ghcr.io/stargz-containers/mariadb:10.7.3-org`|Code execution until up and ready message (`mysqld: ready for connections`) is printed| |`ghcr.io/stargz-containers/mariadb:10.7.3-esgz`|Code execution until up and ready message (`mysqld: ready for connections`) is printed| |`ghcr.io/stargz-containers/php:8.1.4-apache-bullseye-org`|Code execution until up and ready message (`apache2 -D FOREGROUND`) is printed| |`ghcr.io/stargz-containers/php:8.1.4-apache-bullseye-esgz`|Code execution until up and ready message (`apache2 -D FOREGROUND`) is printed| |`ghcr.io/stargz-containers/rabbitmq:3.9.14-org`|Code execution until up and ready message (`Server startup complete`) is printed| |`ghcr.io/stargz-containers/rabbitmq:3.9.14-esgz`|Code execution until up and ready message (`Server startup complete`) is printed| |`ghcr.io/stargz-containers/elasticsearch:8.1.1-org`|Code execution until up and ready message (`started`) is printed| |`ghcr.io/stargz-containers/elasticsearch:8.1.1-esgz`|Code execution until up and ready message (`started`) is printed| |`ghcr.io/stargz-containers/nixos/nix:2.3.12-org`|Executing `echo hello` on the shell| |`ghcr.io/stargz-containers/nixos/nix:2.3.12-esgz`|Executing `echo hello` on the shell| ## lazy-pulling-enabled KinD node image You can enable lazy pulling of eStargz on [KinD](https://github.com/kubernetes-sigs/kind) using our prebuilt node image [`ghcr.io/stargz-containers/estargz-kind-node`](https://github.com/orgs/stargz-containers/packages/container/package/estargz-kind-node). Please refer to README for more details. stargz-snapshotter-0.12.0/docs/stargz-estargz.md000066400000000000000000000002171426301527400217210ustar00rootroot00000000000000# eStargz: Standard-Compatible Extensions to Tar.gz Layers for Lazy Pulling Container Images Moved to [`/docs/estargz.md`](/docs/estargz.md). stargz-snapshotter-0.12.0/docs/verification.md000066400000000000000000000001241426301527400214110ustar00rootroot00000000000000# Content Verification in eStargz Moved to [`/docs/estargz.md`](/docs/estargz.md). stargz-snapshotter-0.12.0/estargz/000077500000000000000000000000001426301527400171375ustar00rootroot00000000000000stargz-snapshotter-0.12.0/estargz/build.go000066400000000000000000000401001426301527400205600ustar00rootroot00000000000000/* 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. */ /* Copyright 2019 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. */ package estargz import ( "archive/tar" "bytes" "compress/gzip" "context" "errors" "fmt" "io" "os" "path" "runtime" "strings" "sync" "github.com/containerd/stargz-snapshotter/estargz/errorutil" "github.com/klauspost/compress/zstd" digest "github.com/opencontainers/go-digest" "golang.org/x/sync/errgroup" ) type options struct { chunkSize int compressionLevel int prioritizedFiles []string missedPrioritizedFiles *[]string compression Compression ctx context.Context } type Option func(o *options) error // WithChunkSize option specifies the chunk size of eStargz blob to build. func WithChunkSize(chunkSize int) Option { return func(o *options) error { o.chunkSize = chunkSize return nil } } // WithCompressionLevel option specifies the gzip compression level. // The default is gzip.BestCompression. // See also: https://godoc.org/compress/gzip#pkg-constants func WithCompressionLevel(level int) Option { return func(o *options) error { o.compressionLevel = level return nil } } // WithPrioritizedFiles option specifies the list of prioritized files. // These files must be complete paths that are absolute or relative to "/" // For example, all of "foo/bar", "/foo/bar", "./foo/bar" and "../foo/bar" // are treated as "/foo/bar". func WithPrioritizedFiles(files []string) Option { return func(o *options) error { o.prioritizedFiles = files return nil } } // WithAllowPrioritizeNotFound makes Build continue the execution even if some // of prioritized files specified by WithPrioritizedFiles option aren't found // in the input tar. Instead, this records all missed file names to the passed // slice. func WithAllowPrioritizeNotFound(missedFiles *[]string) Option { return func(o *options) error { if missedFiles == nil { return fmt.Errorf("WithAllowPrioritizeNotFound: slice must be passed") } o.missedPrioritizedFiles = missedFiles return nil } } // WithCompression specifies compression algorithm to be used. // Default is gzip. func WithCompression(compression Compression) Option { return func(o *options) error { o.compression = compression return nil } } // WithContext specifies a context that can be used for clean canceleration. func WithContext(ctx context.Context) Option { return func(o *options) error { o.ctx = ctx return nil } } // Blob is an eStargz blob. type Blob struct { io.ReadCloser diffID digest.Digester tocDigest digest.Digest } // DiffID returns the digest of uncompressed blob. // It is only valid to call DiffID after Close. func (b *Blob) DiffID() digest.Digest { return b.diffID.Digest() } // TOCDigest returns the digest of uncompressed TOC JSON. func (b *Blob) TOCDigest() digest.Digest { return b.tocDigest } // Build builds an eStargz blob which is an extended version of stargz, from a blob (gzip, zstd // or plain tar) passed through the argument. If there are some prioritized files are listed in // the option, these files are grouped as "prioritized" and can be used for runtime optimization // (e.g. prefetch). This function builds a blob in parallel, with dividing that blob into several // (at least the number of runtime.GOMAXPROCS(0)) sub-blobs. func Build(tarBlob *io.SectionReader, opt ...Option) (_ *Blob, rErr error) { var opts options opts.compressionLevel = gzip.BestCompression // BestCompression by default for _, o := range opt { if err := o(&opts); err != nil { return nil, err } } if opts.compression == nil { opts.compression = newGzipCompressionWithLevel(opts.compressionLevel) } layerFiles := newTempFiles() ctx := opts.ctx if ctx == nil { ctx = context.Background() } done := make(chan struct{}) defer close(done) go func() { select { case <-done: // nop case <-ctx.Done(): layerFiles.CleanupAll() } }() defer func() { if rErr != nil { if err := layerFiles.CleanupAll(); err != nil { rErr = fmt.Errorf("failed to cleanup tmp files: %v: %w", err, rErr) } } if cErr := ctx.Err(); cErr != nil { rErr = fmt.Errorf("error from context %q: %w", cErr, rErr) } }() tarBlob, err := decompressBlob(tarBlob, layerFiles) if err != nil { return nil, err } entries, err := sortEntries(tarBlob, opts.prioritizedFiles, opts.missedPrioritizedFiles) if err != nil { return nil, err } tarParts := divideEntries(entries, runtime.GOMAXPROCS(0)) writers := make([]*Writer, len(tarParts)) payloads := make([]*os.File, len(tarParts)) var mu sync.Mutex var eg errgroup.Group for i, parts := range tarParts { i, parts := i, parts // builds verifiable stargz sub-blobs eg.Go(func() error { esgzFile, err := layerFiles.TempFile("", "esgzdata") if err != nil { return err } sw := NewWriterWithCompressor(esgzFile, opts.compression) sw.ChunkSize = opts.chunkSize if err := sw.AppendTar(readerFromEntries(parts...)); err != nil { return err } mu.Lock() writers[i] = sw payloads[i] = esgzFile mu.Unlock() return nil }) } if err := eg.Wait(); err != nil { rErr = err return nil, err } tocAndFooter, tocDgst, err := closeWithCombine(opts.compressionLevel, writers...) if err != nil { rErr = err return nil, err } var rs []io.Reader for _, p := range payloads { fs, err := fileSectionReader(p) if err != nil { return nil, err } rs = append(rs, fs) } diffID := digest.Canonical.Digester() pr, pw := io.Pipe() go func() { r, err := opts.compression.Reader(io.TeeReader(io.MultiReader(append(rs, tocAndFooter)...), pw)) if err != nil { pw.CloseWithError(err) return } defer r.Close() if _, err := io.Copy(diffID.Hash(), r); err != nil { pw.CloseWithError(err) return } pw.Close() }() return &Blob{ ReadCloser: readCloser{ Reader: pr, closeFunc: layerFiles.CleanupAll, }, tocDigest: tocDgst, diffID: diffID, }, nil } // closeWithCombine takes unclosed Writers and close them. This also returns the // toc that combined all Writers into. // Writers doesn't write TOC and footer to the underlying writers so they can be // combined into a single eStargz and tocAndFooter returned by this function can // be appended at the tail of that combined blob. func closeWithCombine(compressionLevel int, ws ...*Writer) (tocAndFooterR io.Reader, tocDgst digest.Digest, err error) { if len(ws) == 0 { return nil, "", fmt.Errorf("at least one writer must be passed") } for _, w := range ws { if w.closed { return nil, "", fmt.Errorf("writer must be unclosed") } defer func(w *Writer) { w.closed = true }(w) if err := w.closeGz(); err != nil { return nil, "", err } if err := w.bw.Flush(); err != nil { return nil, "", err } } var ( mtoc = new(JTOC) currentOffset int64 ) mtoc.Version = ws[0].toc.Version for _, w := range ws { for _, e := range w.toc.Entries { // Recalculate Offset of non-empty files/chunks if (e.Type == "reg" && e.Size > 0) || e.Type == "chunk" { e.Offset += currentOffset } mtoc.Entries = append(mtoc.Entries, e) } if w.toc.Version > mtoc.Version { mtoc.Version = w.toc.Version } currentOffset += w.cw.n } return tocAndFooter(ws[0].compressor, mtoc, currentOffset) } func tocAndFooter(compressor Compressor, toc *JTOC, offset int64) (io.Reader, digest.Digest, error) { buf := new(bytes.Buffer) tocDigest, err := compressor.WriteTOCAndFooter(buf, offset, toc, nil) if err != nil { return nil, "", err } return buf, tocDigest, nil } // divideEntries divides passed entries to the parts at least the number specified by the // argument. func divideEntries(entries []*entry, minPartsNum int) (set [][]*entry) { var estimatedSize int64 for _, e := range entries { estimatedSize += e.header.Size } unitSize := estimatedSize / int64(minPartsNum) var ( nextEnd = unitSize offset int64 ) set = append(set, []*entry{}) for _, e := range entries { set[len(set)-1] = append(set[len(set)-1], e) offset += e.header.Size if offset > nextEnd { set = append(set, []*entry{}) nextEnd += unitSize } } return } var errNotFound = errors.New("not found") // sortEntries reads the specified tar blob and returns a list of tar entries. // If some of prioritized files are specified, the list starts from these // files with keeping the order specified by the argument. func sortEntries(in io.ReaderAt, prioritized []string, missedPrioritized *[]string) ([]*entry, error) { // Import tar file. intar, err := importTar(in) if err != nil { return nil, fmt.Errorf("failed to sort: %w", err) } // Sort the tar file respecting to the prioritized files list. sorted := &tarFile{} for _, l := range prioritized { if err := moveRec(l, intar, sorted); err != nil { if errors.Is(err, errNotFound) && missedPrioritized != nil { *missedPrioritized = append(*missedPrioritized, l) continue // allow not found } return nil, fmt.Errorf("failed to sort tar entries: %w", err) } } if len(prioritized) == 0 { sorted.add(&entry{ header: &tar.Header{ Name: NoPrefetchLandmark, Typeflag: tar.TypeReg, Size: int64(len([]byte{landmarkContents})), }, payload: bytes.NewReader([]byte{landmarkContents}), }) } else { sorted.add(&entry{ header: &tar.Header{ Name: PrefetchLandmark, Typeflag: tar.TypeReg, Size: int64(len([]byte{landmarkContents})), }, payload: bytes.NewReader([]byte{landmarkContents}), }) } // Dump all entry and concatinate them. return append(sorted.dump(), intar.dump()...), nil } // readerFromEntries returns a reader of tar archive that contains entries passed // through the arguments. func readerFromEntries(entries ...*entry) io.Reader { pr, pw := io.Pipe() go func() { tw := tar.NewWriter(pw) defer tw.Close() for _, entry := range entries { if err := tw.WriteHeader(entry.header); err != nil { pw.CloseWithError(fmt.Errorf("Failed to write tar header: %v", err)) return } if _, err := io.Copy(tw, entry.payload); err != nil { pw.CloseWithError(fmt.Errorf("Failed to write tar payload: %v", err)) return } } pw.Close() }() return pr } func importTar(in io.ReaderAt) (*tarFile, error) { tf := &tarFile{} pw, err := newCountReader(in) if err != nil { return nil, fmt.Errorf("failed to make position watcher: %w", err) } tr := tar.NewReader(pw) // Walk through all nodes. for { // Fetch and parse next header. h, err := tr.Next() if err != nil { if err == io.EOF { break } else { return nil, fmt.Errorf("failed to parse tar file, %w", err) } } switch cleanEntryName(h.Name) { case PrefetchLandmark, NoPrefetchLandmark: // Ignore existing landmark continue } // Add entry. If it already exists, replace it. if _, ok := tf.get(h.Name); ok { tf.remove(h.Name) } tf.add(&entry{ header: h, payload: io.NewSectionReader(in, pw.currentPos(), h.Size), }) } return tf, nil } func moveRec(name string, in *tarFile, out *tarFile) error { name = cleanEntryName(name) if name == "" { // root directory. stop recursion. if e, ok := in.get(name); ok { // entry of the root directory exists. we should move it as well. // this case will occur if tar entries are prefixed with "./", "/", etc. out.add(e) in.remove(name) } return nil } _, okIn := in.get(name) _, okOut := out.get(name) if !okIn && !okOut { return fmt.Errorf("file: %q: %w", name, errNotFound) } parent, _ := path.Split(strings.TrimSuffix(name, "/")) if err := moveRec(parent, in, out); err != nil { return err } if e, ok := in.get(name); ok && e.header.Typeflag == tar.TypeLink { if err := moveRec(e.header.Linkname, in, out); err != nil { return err } } if e, ok := in.get(name); ok { out.add(e) in.remove(name) } return nil } type entry struct { header *tar.Header payload io.ReadSeeker } type tarFile struct { index map[string]*entry stream []*entry } func (f *tarFile) add(e *entry) { if f.index == nil { f.index = make(map[string]*entry) } f.index[cleanEntryName(e.header.Name)] = e f.stream = append(f.stream, e) } func (f *tarFile) remove(name string) { name = cleanEntryName(name) if f.index != nil { delete(f.index, name) } var filtered []*entry for _, e := range f.stream { if cleanEntryName(e.header.Name) == name { continue } filtered = append(filtered, e) } f.stream = filtered } func (f *tarFile) get(name string) (e *entry, ok bool) { if f.index == nil { return nil, false } e, ok = f.index[cleanEntryName(name)] return } func (f *tarFile) dump() []*entry { return f.stream } type readCloser struct { io.Reader closeFunc func() error } func (rc readCloser) Close() error { return rc.closeFunc() } func fileSectionReader(file *os.File) (*io.SectionReader, error) { info, err := file.Stat() if err != nil { return nil, err } return io.NewSectionReader(file, 0, info.Size()), nil } func newTempFiles() *tempFiles { return &tempFiles{} } type tempFiles struct { files []*os.File filesMu sync.Mutex cleanupOnce sync.Once } func (tf *tempFiles) TempFile(dir, pattern string) (*os.File, error) { f, err := os.CreateTemp(dir, pattern) if err != nil { return nil, err } tf.filesMu.Lock() tf.files = append(tf.files, f) tf.filesMu.Unlock() return f, nil } func (tf *tempFiles) CleanupAll() (err error) { tf.cleanupOnce.Do(func() { err = tf.cleanupAll() }) return } func (tf *tempFiles) cleanupAll() error { tf.filesMu.Lock() defer tf.filesMu.Unlock() var allErr []error for _, f := range tf.files { if err := f.Close(); err != nil { allErr = append(allErr, err) } if err := os.Remove(f.Name()); err != nil { allErr = append(allErr, err) } } tf.files = nil return errorutil.Aggregate(allErr) } func newCountReader(r io.ReaderAt) (*countReader, error) { pos := int64(0) return &countReader{r: r, cPos: &pos}, nil } type countReader struct { r io.ReaderAt cPos *int64 mu sync.Mutex } func (cr *countReader) Read(p []byte) (int, error) { cr.mu.Lock() defer cr.mu.Unlock() n, err := cr.r.ReadAt(p, *cr.cPos) if err == nil { *cr.cPos += int64(n) } return n, err } func (cr *countReader) Seek(offset int64, whence int) (int64, error) { cr.mu.Lock() defer cr.mu.Unlock() switch whence { default: return 0, fmt.Errorf("Unknown whence: %v", whence) case io.SeekStart: case io.SeekCurrent: offset += *cr.cPos case io.SeekEnd: return 0, fmt.Errorf("Unsupported whence: %v", whence) } if offset < 0 { return 0, fmt.Errorf("invalid offset") } *cr.cPos = offset return offset, nil } func (cr *countReader) currentPos() int64 { cr.mu.Lock() defer cr.mu.Unlock() return *cr.cPos } func decompressBlob(org *io.SectionReader, tmp *tempFiles) (*io.SectionReader, error) { if org.Size() < 4 { return org, nil } src := make([]byte, 4) if _, err := org.Read(src); err != nil && err != io.EOF { return nil, err } var dR io.Reader if bytes.Equal([]byte{0x1F, 0x8B, 0x08}, src[:3]) { // gzip dgR, err := gzip.NewReader(io.NewSectionReader(org, 0, org.Size())) if err != nil { return nil, err } defer dgR.Close() dR = io.Reader(dgR) } else if bytes.Equal([]byte{0x28, 0xb5, 0x2f, 0xfd}, src[:4]) { // zstd dzR, err := zstd.NewReader(io.NewSectionReader(org, 0, org.Size())) if err != nil { return nil, err } defer dzR.Close() dR = io.Reader(dzR) } else { // uncompressed return io.NewSectionReader(org, 0, org.Size()), nil } b, err := tmp.TempFile("", "uncompresseddata") if err != nil { return nil, err } if _, err := io.Copy(b, dR); err != nil { return nil, err } return fileSectionReader(b) } stargz-snapshotter-0.12.0/estargz/build_test.go000066400000000000000000000362271426301527400216360ustar00rootroot00000000000000/* 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. */ /* Copyright 2019 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. */ package estargz import ( "archive/tar" "bytes" "compress/gzip" "fmt" "io" "reflect" "testing" ) func TestSort(t *testing.T) { longname1 := longstring(120) longname2 := longstring(150) tests := []struct { name string in []tarEntry log []string // MUST NOT include "./" prefix here want []tarEntry wantFail bool allowMissedFiles []string }{ { name: "nolog", in: tarOf( file("foo.txt", "foo"), dir("bar/"), file("bar/baz.txt", "baz"), file("bar/bar.txt", "bar"), ), want: tarOf( noPrefetchLandmark(), file("foo.txt", "foo"), dir("bar/"), file("bar/baz.txt", "baz"), file("bar/bar.txt", "bar"), ), }, { name: "identical", in: tarOf( file("foo.txt", "foo"), dir("bar/"), file("bar/baz.txt", "baz"), file("bar/bar.txt", "bar"), file("bar/baa.txt", "baa"), ), log: []string{"foo.txt", "bar/baz.txt"}, want: tarOf( file("foo.txt", "foo"), dir("bar/"), file("bar/baz.txt", "baz"), prefetchLandmark(), file("bar/bar.txt", "bar"), file("bar/baa.txt", "baa"), ), }, { name: "shuffle_reg", in: tarOf( file("foo.txt", "foo"), file("baz.txt", "baz"), file("bar.txt", "bar"), file("baa.txt", "baa"), ), log: []string{"baa.txt", "bar.txt", "baz.txt"}, want: tarOf( file("baa.txt", "baa"), file("bar.txt", "bar"), file("baz.txt", "baz"), prefetchLandmark(), file("foo.txt", "foo"), ), }, { name: "shuffle_directory", in: tarOf( file("foo.txt", "foo"), dir("bar/"), file("bar/bar.txt", "bar"), file("bar/baa.txt", "baa"), dir("baz/"), file("baz/baz1.txt", "baz"), file("baz/baz2.txt", "baz"), dir("baz/bazbaz/"), file("baz/bazbaz/bazbaz_b.txt", "baz"), file("baz/bazbaz/bazbaz_a.txt", "baz"), ), log: []string{"baz/bazbaz/bazbaz_a.txt", "baz/baz2.txt", "foo.txt"}, want: tarOf( dir("baz/"), dir("baz/bazbaz/"), file("baz/bazbaz/bazbaz_a.txt", "baz"), file("baz/baz2.txt", "baz"), file("foo.txt", "foo"), prefetchLandmark(), dir("bar/"), file("bar/bar.txt", "bar"), file("bar/baa.txt", "baa"), file("baz/baz1.txt", "baz"), file("baz/bazbaz/bazbaz_b.txt", "baz"), ), }, { name: "shuffle_link", in: tarOf( file("foo.txt", "foo"), file("baz.txt", "baz"), link("bar.txt", "baz.txt"), file("baa.txt", "baa"), ), log: []string{"baz.txt"}, want: tarOf( file("baz.txt", "baz"), prefetchLandmark(), file("foo.txt", "foo"), link("bar.txt", "baz.txt"), file("baa.txt", "baa"), ), }, { name: "longname", in: tarOf( file("foo.txt", "foo"), file(longname1, "test"), dir("bar/"), file("bar/bar.txt", "bar"), file("bar/baa.txt", "baa"), file(fmt.Sprintf("bar/%s", longname2), "test2"), ), log: []string{fmt.Sprintf("bar/%s", longname2), longname1}, want: tarOf( dir("bar/"), file(fmt.Sprintf("bar/%s", longname2), "test2"), file(longname1, "test"), prefetchLandmark(), file("foo.txt", "foo"), file("bar/bar.txt", "bar"), file("bar/baa.txt", "baa"), ), }, { name: "various_types", in: tarOf( file("foo.txt", "foo"), symlink("foo2", "foo.txt"), chardev("foochar", 10, 50), blockdev("fooblock", 15, 20), fifo("fifoo"), ), log: []string{"fifoo", "foo2", "foo.txt", "fooblock"}, want: tarOf( fifo("fifoo"), symlink("foo2", "foo.txt"), file("foo.txt", "foo"), blockdev("fooblock", 15, 20), prefetchLandmark(), chardev("foochar", 10, 50), ), }, { name: "existing_landmark", in: tarOf( file("baa.txt", "baa"), file("bar.txt", "bar"), file("baz.txt", "baz"), prefetchLandmark(), file("foo.txt", "foo"), ), log: []string{"foo.txt", "bar.txt"}, want: tarOf( file("foo.txt", "foo"), file("bar.txt", "bar"), prefetchLandmark(), file("baa.txt", "baa"), file("baz.txt", "baz"), ), }, { name: "existing_landmark_nolog", in: tarOf( file("baa.txt", "baa"), file("bar.txt", "bar"), file("baz.txt", "baz"), prefetchLandmark(), file("foo.txt", "foo"), ), want: tarOf( noPrefetchLandmark(), file("baa.txt", "baa"), file("bar.txt", "bar"), file("baz.txt", "baz"), file("foo.txt", "foo"), ), }, { name: "existing_noprefetch_landmark", in: tarOf( noPrefetchLandmark(), file("baa.txt", "baa"), file("bar.txt", "bar"), file("baz.txt", "baz"), file("foo.txt", "foo"), ), log: []string{"foo.txt", "bar.txt"}, want: tarOf( file("foo.txt", "foo"), file("bar.txt", "bar"), prefetchLandmark(), file("baa.txt", "baa"), file("baz.txt", "baz"), ), }, { name: "existing_noprefetch_landmark_nolog", in: tarOf( noPrefetchLandmark(), file("baa.txt", "baa"), file("bar.txt", "bar"), file("baz.txt", "baz"), file("foo.txt", "foo"), ), want: tarOf( noPrefetchLandmark(), file("baa.txt", "baa"), file("bar.txt", "bar"), file("baz.txt", "baz"), file("foo.txt", "foo"), ), }, { name: "not_existing_file", in: tarOf( file("foo.txt", "foo"), file("baz.txt", "baz"), file("bar.txt", "bar"), file("baa.txt", "baa"), ), log: []string{"baa.txt", "bar.txt", "dummy"}, want: tarOf(), wantFail: true, }, { name: "not_existing_file_allow_fail", in: tarOf( file("foo.txt", "foo"), file("baz.txt", "baz"), file("bar.txt", "bar"), file("baa.txt", "baa"), ), log: []string{"baa.txt", "dummy1", "bar.txt", "dummy2"}, want: tarOf( file("baa.txt", "baa"), file("bar.txt", "bar"), prefetchLandmark(), file("foo.txt", "foo"), file("baz.txt", "baz"), ), allowMissedFiles: []string{"dummy1", "dummy2"}, }, { name: "duplicated_entry", in: tarOf( file("foo.txt", "foo"), dir("bar/"), file("bar/baz.txt", "baz"), dir("bar/"), file("bar/baz.txt", "baz2"), ), log: []string{"bar/baz.txt"}, want: tarOf( dir("bar/"), file("bar/baz.txt", "baz2"), prefetchLandmark(), file("foo.txt", "foo"), ), }, { name: "hardlink", in: tarOf( file("baz.txt", "aaaaa"), link("bazlink", "baz.txt"), ), log: []string{"bazlink"}, want: tarOf( file("baz.txt", "aaaaa"), link("bazlink", "baz.txt"), prefetchLandmark(), ), }, { name: "root_relative_file", in: tarOf( dir("./"), file("./foo.txt", "foo"), file("./baz.txt", "baz"), file("./baa.txt", "baa"), link("./bazlink", "./baz.txt"), ), log: []string{"baa.txt", "bazlink"}, want: tarOf( dir("./"), file("./baa.txt", "baa"), file("./baz.txt", "baz"), link("./bazlink", "./baz.txt"), prefetchLandmark(), file("./foo.txt", "foo"), ), }, { // GNU tar accepts tar containing absolute path name: "root_absolute_file", in: tarOf( dir("/"), file("/foo.txt", "foo"), file("/baz.txt", "baz"), file("/baa.txt", "baa"), link("/bazlink", "/baz.txt"), ), log: []string{"baa.txt", "bazlink"}, want: tarOf( dir("/"), file("/baa.txt", "baa"), file("/baz.txt", "baz"), link("/bazlink", "/baz.txt"), prefetchLandmark(), file("/foo.txt", "foo"), ), }, } for _, tt := range tests { for _, srcCompression := range srcCompressions { srcCompression := srcCompression for _, logprefix := range allowedPrefix { logprefix := logprefix for _, tarprefix := range allowedPrefix { tarprefix := tarprefix t.Run(fmt.Sprintf("%s-logprefix=%q-tarprefix=%q-src=%d", tt.name, logprefix, tarprefix, srcCompression), func(t *testing.T) { // Sort tar file var pfiles []string for _, f := range tt.log { pfiles = append(pfiles, logprefix+f) } var opts []Option var missedFiles []string if tt.allowMissedFiles != nil { opts = append(opts, WithAllowPrioritizeNotFound(&missedFiles)) } rc, err := Build(compressBlob(t, buildTar(t, tt.in, tarprefix), srcCompression), append(opts, WithPrioritizedFiles(pfiles))...) if tt.wantFail { if err != nil { return } t.Errorf("This test must fail but succeeded") return } if err != nil { t.Errorf("failed to build stargz: %v", err) } defer rc.Close() zr, err := gzip.NewReader(rc) if err != nil { t.Fatalf("failed to create gzip reader: %v", err) } if tt.allowMissedFiles != nil { want := map[string]struct{}{} for _, f := range tt.allowMissedFiles { want[logprefix+f] = struct{}{} } got := map[string]struct{}{} for _, f := range missedFiles { got[f] = struct{}{} } if !reflect.DeepEqual(got, want) { t.Errorf("unexpected missed files: want %v, got: %v", want, got) return } } gotTar := tar.NewReader(zr) // Compare all wantTar := tar.NewReader(buildTar(t, tt.want, tarprefix)) for { // Fetch and parse next header. gotH, wantH, err := next(t, gotTar, wantTar) if err != nil { if err == io.EOF { break } else { t.Fatalf("Failed to parse tar file: %v", err) } } if !reflect.DeepEqual(gotH, wantH) { t.Errorf("different header (got = name:%q,type:%d,size:%d; want = name:%q,type:%d,size:%d)", gotH.Name, gotH.Typeflag, gotH.Size, wantH.Name, wantH.Typeflag, wantH.Size) return } got, err := io.ReadAll(gotTar) if err != nil { t.Fatal("failed to read got tar payload") } want, err := io.ReadAll(wantTar) if err != nil { t.Fatal("failed to read want tar payload") } if !bytes.Equal(got, want) { t.Errorf("different payload (got = %q; want = %q)", string(got), string(want)) return } } }) } } } } } func next(t *testing.T, a *tar.Reader, b *tar.Reader) (ah *tar.Header, bh *tar.Header, err error) { eofA, eofB := false, false ah, err = nextWithSkipTOC(a) if err != nil { if err == io.EOF { eofA = true } else { t.Fatalf("Failed to parse tar file: %q", err) } } bh, err = nextWithSkipTOC(b) if err != nil { if err == io.EOF { eofB = true } else { t.Fatalf("Failed to parse tar file: %q", err) } } if eofA != eofB { if !eofA { t.Logf("a = %q", ah.Name) } if !eofB { t.Logf("b = %q", bh.Name) } t.Fatalf("got eof %t != %t", eofB, eofA) } if eofA { err = io.EOF } return } func nextWithSkipTOC(a *tar.Reader) (h *tar.Header, err error) { if h, err = a.Next(); err != nil { return } if h.Name == TOCTarName { return nextWithSkipTOC(a) } return } func longstring(size int) (str string) { unit := "long" for i := 0; i < size/len(unit)+1; i++ { str = fmt.Sprintf("%s%s", str, unit) } return str[:size] } func TestCountReader(t *testing.T) { tests := []struct { name string ops func(*countReader) error wantPos int64 }{ { name: "nop", ops: func(pw *countReader) error { return nil }, wantPos: 0, }, { name: "read", ops: func(pw *countReader) error { size := 5 if _, err := pw.Read(make([]byte, size)); err != nil { return err } return nil }, wantPos: 5, }, { name: "readtwice", ops: func(pw *countReader) error { size1, size2 := 5, 3 if _, err := pw.Read(make([]byte, size1)); err != nil { if err != io.EOF { return err } } if _, err := pw.Read(make([]byte, size2)); err != nil { if err != io.EOF { return err } } return nil }, wantPos: 8, }, { name: "seek_start", ops: func(pw *countReader) error { size := int64(5) if _, err := pw.Seek(size, io.SeekStart); err != nil { if err != io.EOF { return err } } return nil }, wantPos: 5, }, { name: "seek_start_twice", ops: func(pw *countReader) error { size1, size2 := int64(5), int64(3) if _, err := pw.Seek(size1, io.SeekStart); err != nil { if err != io.EOF { return err } } if _, err := pw.Seek(size2, io.SeekStart); err != nil { if err != io.EOF { return err } } return nil }, wantPos: 3, }, { name: "seek_current", ops: func(pw *countReader) error { size := int64(5) if _, err := pw.Seek(size, io.SeekCurrent); err != nil { if err != io.EOF { return err } } return nil }, wantPos: 5, }, { name: "seek_current_twice", ops: func(pw *countReader) error { size1, size2 := int64(5), int64(3) if _, err := pw.Seek(size1, io.SeekCurrent); err != nil { if err != io.EOF { return err } } if _, err := pw.Seek(size2, io.SeekCurrent); err != nil { if err != io.EOF { return err } } return nil }, wantPos: 8, }, { name: "seek_current_twice_negative", ops: func(pw *countReader) error { size1, size2 := int64(5), int64(-3) if _, err := pw.Seek(size1, io.SeekCurrent); err != nil { if err != io.EOF { return err } } if _, err := pw.Seek(size2, io.SeekCurrent); err != nil { if err != io.EOF { return err } } return nil }, wantPos: 2, }, { name: "mixed", ops: func(pw *countReader) error { size1, size2, size3, size4, size5 := int64(5), int64(-3), int64(4), int64(-1), int64(6) if _, err := pw.Read(make([]byte, size1)); err != nil { if err != io.EOF { return err } } if _, err := pw.Seek(size2, io.SeekCurrent); err != nil { if err != io.EOF { return err } } if _, err := pw.Seek(size3, io.SeekStart); err != nil { if err != io.EOF { return err } } if _, err := pw.Seek(size4, io.SeekCurrent); err != nil { if err != io.EOF { return err } } if _, err := pw.Read(make([]byte, size5)); err != nil { if err != io.EOF { return err } } return nil }, wantPos: 9, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { pw, err := newCountReader(bytes.NewReader(make([]byte, 100))) if err != nil { t.Fatalf("failed to make position watcher: %q", err) } if err := tt.ops(pw); err != nil { t.Fatalf("failed to operate position watcher: %q", err) } gotPos := pw.currentPos() if tt.wantPos != gotPos { t.Errorf("current position: %d; want %d", gotPos, tt.wantPos) } }) } } stargz-snapshotter-0.12.0/estargz/errorutil/000077500000000000000000000000001426301527400211665ustar00rootroot00000000000000stargz-snapshotter-0.12.0/estargz/errorutil/errors.go000066400000000000000000000020521426301527400230300ustar00rootroot00000000000000/* 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 errorutil import ( "errors" "fmt" "strings" ) // Aggregate combines a list of errors into a single new error. func Aggregate(errs []error) error { switch len(errs) { case 0: return nil case 1: return errs[0] default: points := make([]string, len(errs)+1) points[0] = fmt.Sprintf("%d error(s) occurred:", len(errs)) for i, err := range errs { points[i+1] = fmt.Sprintf("* %s", err) } return errors.New(strings.Join(points, "\n\t")) } } stargz-snapshotter-0.12.0/estargz/errorutil/errors_test.go000066400000000000000000000026301426301527400240710ustar00rootroot00000000000000/* 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 errorutil import ( "errors" "testing" ) func TestNoError(t *testing.T) { if err := Aggregate(nil); err != nil { t.Errorf("Aggregate(nil) = %v, wanted nil", err) } if err := Aggregate([]error{}); err != nil { t.Errorf("Aggregate(nil) = %v, wanted nil", err) } } func TestOneError(t *testing.T) { want := errors.New("this is A random Error string") if got := Aggregate([]error{want}); got != want { t.Errorf("Aggregate() = %v, wanted %v", got, want) } } func TestMultipleErrors(t *testing.T) { want := `3 error(s) occurred: * foo * bar * baz` err := Aggregate([]error{ errors.New("foo"), errors.New("bar"), errors.New("baz"), }) if err == nil { t.Errorf("Aggregate() = nil, wanted %s", want) } else if got := err.Error(); got != want { t.Errorf("Aggregate() = %s, wanted %s", got, want) } } stargz-snapshotter-0.12.0/estargz/estargz.go000066400000000000000000000676271426301527400211670ustar00rootroot00000000000000/* 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. */ /* Copyright 2019 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. */ package estargz import ( "bufio" "bytes" "compress/gzip" "crypto/sha256" "errors" "fmt" "hash" "io" "os" "path" "sort" "strings" "sync" "time" "github.com/containerd/stargz-snapshotter/estargz/errorutil" digest "github.com/opencontainers/go-digest" "github.com/vbatts/tar-split/archive/tar" ) // A Reader permits random access reads from a stargz file. type Reader struct { sr *io.SectionReader toc *JTOC tocDigest digest.Digest // m stores all non-chunk entries, keyed by name. m map[string]*TOCEntry // chunks stores all TOCEntry values for regular files that // are split up. For a file with a single chunk, it's only // stored in m. chunks map[string][]*TOCEntry decompressor Decompressor } type openOpts struct { tocOffset int64 decompressors []Decompressor telemetry *Telemetry } // OpenOption is an option used during opening the layer type OpenOption func(o *openOpts) error // WithTOCOffset option specifies the offset of TOC func WithTOCOffset(tocOffset int64) OpenOption { return func(o *openOpts) error { o.tocOffset = tocOffset return nil } } // WithDecompressors option specifies decompressors to use. // Default is gzip-based decompressor. func WithDecompressors(decompressors ...Decompressor) OpenOption { return func(o *openOpts) error { o.decompressors = decompressors return nil } } // WithTelemetry option specifies the telemetry hooks func WithTelemetry(telemetry *Telemetry) OpenOption { return func(o *openOpts) error { o.telemetry = telemetry return nil } } // MeasureLatencyHook is a func which takes start time and records the diff type MeasureLatencyHook func(time.Time) // Telemetry is a struct which defines telemetry hooks. By implementing these hooks you should be able to record // the latency metrics of the respective steps of estargz open operation. To be used with estargz.OpenWithTelemetry(...) type Telemetry struct { GetFooterLatency MeasureLatencyHook // measure time to get stargz footer (in milliseconds) GetTocLatency MeasureLatencyHook // measure time to GET TOC JSON (in milliseconds) DeserializeTocLatency MeasureLatencyHook // measure time to deserialize TOC JSON (in milliseconds) } // Open opens a stargz file for reading. // The behavior is configurable using options. // // Note that each entry name is normalized as the path that is relative to root. func Open(sr *io.SectionReader, opt ...OpenOption) (*Reader, error) { var opts openOpts for _, o := range opt { if err := o(&opts); err != nil { return nil, err } } gzipCompressors := []Decompressor{new(GzipDecompressor), new(LegacyGzipDecompressor)} decompressors := append(gzipCompressors, opts.decompressors...) // Determine the size to fetch. Try to fetch as many bytes as possible. fetchSize := maxFooterSize(sr.Size(), decompressors...) if maybeTocOffset := opts.tocOffset; maybeTocOffset > fetchSize { if maybeTocOffset > sr.Size() { return nil, fmt.Errorf("blob size %d is smaller than the toc offset", sr.Size()) } fetchSize = sr.Size() - maybeTocOffset } start := time.Now() // before getting layer footer footer := make([]byte, fetchSize) if _, err := sr.ReadAt(footer, sr.Size()-fetchSize); err != nil { return nil, fmt.Errorf("error reading footer: %v", err) } if opts.telemetry != nil && opts.telemetry.GetFooterLatency != nil { opts.telemetry.GetFooterLatency(start) } var allErr []error var found bool var r *Reader for _, d := range decompressors { fSize := d.FooterSize() fOffset := positive(int64(len(footer)) - fSize) maybeTocBytes := footer[:fOffset] _, tocOffset, tocSize, err := d.ParseFooter(footer[fOffset:]) if err != nil { allErr = append(allErr, err) continue } if tocSize <= 0 { tocSize = sr.Size() - tocOffset - fSize } if tocSize < int64(len(maybeTocBytes)) { maybeTocBytes = maybeTocBytes[:tocSize] } r, err = parseTOC(d, sr, tocOffset, tocSize, maybeTocBytes, opts) if err == nil { found = true break } allErr = append(allErr, err) } if !found { return nil, errorutil.Aggregate(allErr) } if err := r.initFields(); err != nil { return nil, fmt.Errorf("failed to initialize fields of entries: %v", err) } return r, nil } // OpenFooter extracts and parses footer from the given blob. // only supports gzip-based eStargz. func OpenFooter(sr *io.SectionReader) (tocOffset int64, footerSize int64, rErr error) { if sr.Size() < FooterSize && sr.Size() < legacyFooterSize { return 0, 0, fmt.Errorf("blob size %d is smaller than the footer size", sr.Size()) } var footer [FooterSize]byte if _, err := sr.ReadAt(footer[:], sr.Size()-FooterSize); err != nil { return 0, 0, fmt.Errorf("error reading footer: %v", err) } var allErr []error for _, d := range []Decompressor{new(GzipDecompressor), new(LegacyGzipDecompressor)} { fSize := d.FooterSize() fOffset := positive(int64(len(footer)) - fSize) _, tocOffset, _, err := d.ParseFooter(footer[fOffset:]) if err == nil { return tocOffset, fSize, err } allErr = append(allErr, err) } return 0, 0, errorutil.Aggregate(allErr) } // initFields populates the Reader from r.toc after decoding it from // JSON. // // Unexported fields are populated and TOCEntry fields that were // implicit in the JSON are populated. func (r *Reader) initFields() error { r.m = make(map[string]*TOCEntry, len(r.toc.Entries)) r.chunks = make(map[string][]*TOCEntry) var lastPath string uname := map[int]string{} gname := map[int]string{} var lastRegEnt *TOCEntry for _, ent := range r.toc.Entries { ent.Name = cleanEntryName(ent.Name) if ent.Type == "reg" { lastRegEnt = ent } if ent.Type == "chunk" { ent.Name = lastPath r.chunks[ent.Name] = append(r.chunks[ent.Name], ent) if ent.ChunkSize == 0 && lastRegEnt != nil { ent.ChunkSize = lastRegEnt.Size - ent.ChunkOffset } } else { lastPath = ent.Name if ent.Uname != "" { uname[ent.UID] = ent.Uname } else { ent.Uname = uname[ent.UID] } if ent.Gname != "" { gname[ent.GID] = ent.Gname } else { ent.Gname = uname[ent.GID] } ent.modTime, _ = time.Parse(time.RFC3339, ent.ModTime3339) if ent.Type == "dir" { ent.NumLink++ // Parent dir links to this directory } r.m[ent.Name] = ent } if ent.Type == "reg" && ent.ChunkSize > 0 && ent.ChunkSize < ent.Size { r.chunks[ent.Name] = make([]*TOCEntry, 0, ent.Size/ent.ChunkSize+1) r.chunks[ent.Name] = append(r.chunks[ent.Name], ent) } if ent.ChunkSize == 0 && ent.Size != 0 { ent.ChunkSize = ent.Size } } // Populate children, add implicit directories: for _, ent := range r.toc.Entries { if ent.Type == "chunk" { continue } // add "foo/": // add "foo" child to "" (creating "" if necessary) // // add "foo/bar/": // add "bar" child to "foo" (creating "foo" if necessary) // // add "foo/bar.txt": // add "bar.txt" child to "foo" (creating "foo" if necessary) // // add "a/b/c/d/e/f.txt": // create "a/b/c/d/e" node // add "f.txt" child to "e" name := ent.Name pdirName := parentDir(name) if name == pdirName { // This entry and its parent are the same. // Ignore this for avoiding infinite loop of the reference. // The example case where this can occur is when tar contains the root // directory itself (e.g. "./", "/"). continue } pdir := r.getOrCreateDir(pdirName) ent.NumLink++ // at least one name(ent.Name) references this entry. if ent.Type == "hardlink" { org, err := r.getSource(ent) if err != nil { return err } org.NumLink++ // original entry is referenced by this ent.Name. ent = org } pdir.addChild(path.Base(name), ent) } lastOffset := r.sr.Size() for i := len(r.toc.Entries) - 1; i >= 0; i-- { e := r.toc.Entries[i] if e.isDataType() { e.nextOffset = lastOffset } if e.Offset != 0 { lastOffset = e.Offset } } return nil } func (r *Reader) getSource(ent *TOCEntry) (_ *TOCEntry, err error) { if ent.Type == "hardlink" { org, ok := r.m[cleanEntryName(ent.LinkName)] if !ok { return nil, fmt.Errorf("%q is a hardlink but the linkname %q isn't found", ent.Name, ent.LinkName) } ent, err = r.getSource(org) if err != nil { return nil, err } } return ent, nil } func parentDir(p string) string { dir, _ := path.Split(p) return strings.TrimSuffix(dir, "/") } func (r *Reader) getOrCreateDir(d string) *TOCEntry { e, ok := r.m[d] if !ok { e = &TOCEntry{ Name: d, Type: "dir", Mode: 0755, NumLink: 2, // The directory itself(.) and the parent link to this directory. } r.m[d] = e if d != "" { pdir := r.getOrCreateDir(parentDir(d)) pdir.addChild(path.Base(d), e) } } return e } func (r *Reader) TOCDigest() digest.Digest { return r.tocDigest } // VerifyTOC checks that the TOC JSON in the passed blob matches the // passed digests and that the TOC JSON contains digests for all chunks // contained in the blob. If the verification succceeds, this function // returns TOCEntryVerifier which holds all chunk digests in the stargz blob. func (r *Reader) VerifyTOC(tocDigest digest.Digest) (TOCEntryVerifier, error) { // Verify the digest of TOC JSON if r.tocDigest != tocDigest { return nil, fmt.Errorf("invalid TOC JSON %q; want %q", r.tocDigest, tocDigest) } return r.Verifiers() } // Verifiers returns TOCEntryVerifier of this chunk. Use VerifyTOC instead in most cases // because this doesn't verify TOC. func (r *Reader) Verifiers() (TOCEntryVerifier, error) { chunkDigestMap := make(map[int64]digest.Digest) // map from chunk offset to the chunk digest regDigestMap := make(map[int64]digest.Digest) // map from chunk offset to the reg file digest var chunkDigestMapIncomplete bool var regDigestMapIncomplete bool var containsChunk bool for _, e := range r.toc.Entries { if e.Type != "reg" && e.Type != "chunk" { continue } // offset must be unique in stargz blob _, dOK := chunkDigestMap[e.Offset] _, rOK := regDigestMap[e.Offset] if dOK || rOK { return nil, fmt.Errorf("offset %d found twice", e.Offset) } if e.Type == "reg" { if e.Size == 0 { continue // ignores empty file } // record the digest of regular file payload if e.Digest != "" { d, err := digest.Parse(e.Digest) if err != nil { return nil, fmt.Errorf("failed to parse regular file digest %q: %w", e.Digest, err) } regDigestMap[e.Offset] = d } else { regDigestMapIncomplete = true } } else { containsChunk = true // this layer contains "chunk" entries. } // "reg" also can contain ChunkDigest (e.g. when "reg" is the first entry of // chunked file) if e.ChunkDigest != "" { d, err := digest.Parse(e.ChunkDigest) if err != nil { return nil, fmt.Errorf("failed to parse chunk digest %q: %w", e.ChunkDigest, err) } chunkDigestMap[e.Offset] = d } else { chunkDigestMapIncomplete = true } } if chunkDigestMapIncomplete { // Though some chunk digests are not found, if this layer doesn't contain // "chunk"s and all digest of "reg" files are recorded, we can use them instead. if !containsChunk && !regDigestMapIncomplete { return &verifier{digestMap: regDigestMap}, nil } return nil, fmt.Errorf("some ChunkDigest not found in TOC JSON") } return &verifier{digestMap: chunkDigestMap}, nil } // verifier is an implementation of TOCEntryVerifier which holds verifiers keyed by // offset of the chunk. type verifier struct { digestMap map[int64]digest.Digest digestMapMu sync.Mutex } // Verifier returns a content verifier specified by TOCEntry. func (v *verifier) Verifier(ce *TOCEntry) (digest.Verifier, error) { v.digestMapMu.Lock() defer v.digestMapMu.Unlock() d, ok := v.digestMap[ce.Offset] if !ok { return nil, fmt.Errorf("verifier for offset=%d,size=%d hasn't been registered", ce.Offset, ce.ChunkSize) } return d.Verifier(), nil } // ChunkEntryForOffset returns the TOCEntry containing the byte of the // named file at the given offset within the file. // Name must be absolute path or one that is relative to root. func (r *Reader) ChunkEntryForOffset(name string, offset int64) (e *TOCEntry, ok bool) { name = cleanEntryName(name) e, ok = r.Lookup(name) if !ok || !e.isDataType() { return nil, false } ents := r.chunks[name] if len(ents) < 2 { if offset >= e.ChunkSize { return nil, false } return e, true } i := sort.Search(len(ents), func(i int) bool { e := ents[i] return e.ChunkOffset >= offset || (offset > e.ChunkOffset && offset < e.ChunkOffset+e.ChunkSize) }) if i == len(ents) { return nil, false } return ents[i], true } // Lookup returns the Table of Contents entry for the given path. // // To get the root directory, use the empty string. // Path must be absolute path or one that is relative to root. func (r *Reader) Lookup(path string) (e *TOCEntry, ok bool) { path = cleanEntryName(path) if r == nil { return } e, ok = r.m[path] if ok && e.Type == "hardlink" { var err error e, err = r.getSource(e) if err != nil { return nil, false } } return } // OpenFile returns the reader of the specified file payload. // // Name must be absolute path or one that is relative to root. func (r *Reader) OpenFile(name string) (*io.SectionReader, error) { name = cleanEntryName(name) ent, ok := r.Lookup(name) if !ok { // TODO: come up with some error plan. This is lazy: return nil, &os.PathError{ Path: name, Op: "OpenFile", Err: os.ErrNotExist, } } if ent.Type != "reg" { return nil, &os.PathError{ Path: name, Op: "OpenFile", Err: errors.New("not a regular file"), } } fr := &fileReader{ r: r, size: ent.Size, ents: r.getChunks(ent), } return io.NewSectionReader(fr, 0, fr.size), nil } func (r *Reader) getChunks(ent *TOCEntry) []*TOCEntry { if ents, ok := r.chunks[ent.Name]; ok { return ents } return []*TOCEntry{ent} } type fileReader struct { r *Reader size int64 ents []*TOCEntry // 1 or more reg/chunk entries } func (fr *fileReader) ReadAt(p []byte, off int64) (n int, err error) { if off >= fr.size { return 0, io.EOF } if off < 0 { return 0, errors.New("invalid offset") } var i int if len(fr.ents) > 1 { i = sort.Search(len(fr.ents), func(i int) bool { return fr.ents[i].ChunkOffset >= off }) if i == len(fr.ents) { i = len(fr.ents) - 1 } } ent := fr.ents[i] if ent.ChunkOffset > off { if i == 0 { return 0, errors.New("internal error; first chunk offset is non-zero") } ent = fr.ents[i-1] } // If ent is a chunk of a large file, adjust the ReadAt // offset by the chunk's offset. off -= ent.ChunkOffset finalEnt := fr.ents[len(fr.ents)-1] compressedOff := ent.Offset // compressedBytesRemain is the number of compressed bytes in this // file remaining, over 1+ chunks. compressedBytesRemain := finalEnt.NextOffset() - compressedOff sr := io.NewSectionReader(fr.r.sr, compressedOff, compressedBytesRemain) const maxRead = 2 << 20 var bufSize = maxRead if compressedBytesRemain < maxRead { bufSize = int(compressedBytesRemain) } br := bufio.NewReaderSize(sr, bufSize) if _, err := br.Peek(bufSize); err != nil { return 0, fmt.Errorf("fileReader.ReadAt.peek: %v", err) } dr, err := fr.r.decompressor.Reader(br) if err != nil { return 0, fmt.Errorf("fileReader.ReadAt.decompressor.Reader: %v", err) } defer dr.Close() if n, err := io.CopyN(io.Discard, dr, off); n != off || err != nil { return 0, fmt.Errorf("discard of %d bytes = %v, %v", off, n, err) } return io.ReadFull(dr, p) } // A Writer writes stargz files. // // Use NewWriter to create a new Writer. type Writer struct { bw *bufio.Writer cw *countWriter toc *JTOC diffHash hash.Hash // SHA-256 of uncompressed tar closed bool gz io.WriteCloser lastUsername map[int]string lastGroupname map[int]string compressor Compressor // ChunkSize optionally controls the maximum number of bytes // of data of a regular file that can be written in one gzip // stream before a new gzip stream is started. // Zero means to use a default, currently 4 MiB. ChunkSize int } // currentCompressionWriter writes to the current w.gz field, which can // change throughout writing a tar entry. // // Additionally, it updates w's SHA-256 of the uncompressed bytes // of the tar file. type currentCompressionWriter struct{ w *Writer } func (ccw currentCompressionWriter) Write(p []byte) (int, error) { ccw.w.diffHash.Write(p) if ccw.w.gz == nil { if err := ccw.w.condOpenGz(); err != nil { return 0, err } } return ccw.w.gz.Write(p) } func (w *Writer) chunkSize() int { if w.ChunkSize <= 0 { return 4 << 20 } return w.ChunkSize } // Unpack decompresses the given estargz blob and returns a ReadCloser of the tar blob. // TOC JSON and footer are removed. func Unpack(sr *io.SectionReader, c Decompressor) (io.ReadCloser, error) { footerSize := c.FooterSize() if sr.Size() < footerSize { return nil, fmt.Errorf("blob is too small; %d < %d", sr.Size(), footerSize) } footerOffset := sr.Size() - footerSize footer := make([]byte, footerSize) if _, err := sr.ReadAt(footer, footerOffset); err != nil { return nil, err } blobPayloadSize, _, _, err := c.ParseFooter(footer) if err != nil { return nil, fmt.Errorf("failed to parse footer: %w", err) } return c.Reader(io.LimitReader(sr, blobPayloadSize)) } // NewWriter returns a new stargz writer (gzip-based) writing to w. // // The writer must be closed to write its trailing table of contents. func NewWriter(w io.Writer) *Writer { return NewWriterLevel(w, gzip.BestCompression) } // NewWriterLevel returns a new stargz writer (gzip-based) writing to w. // The compression level is configurable. // // The writer must be closed to write its trailing table of contents. func NewWriterLevel(w io.Writer, compressionLevel int) *Writer { return NewWriterWithCompressor(w, NewGzipCompressorWithLevel(compressionLevel)) } // NewWriterWithCompressor returns a new stargz writer writing to w. // The compression method is configurable. // // The writer must be closed to write its trailing table of contents. func NewWriterWithCompressor(w io.Writer, c Compressor) *Writer { bw := bufio.NewWriter(w) cw := &countWriter{w: bw} return &Writer{ bw: bw, cw: cw, toc: &JTOC{Version: 1}, diffHash: sha256.New(), compressor: c, } } // Close writes the stargz's table of contents and flushes all the // buffers, returning any error. func (w *Writer) Close() (digest.Digest, error) { if w.closed { return "", nil } defer func() { w.closed = true }() if err := w.closeGz(); err != nil { return "", err } // Write the TOC index and footer. tocDigest, err := w.compressor.WriteTOCAndFooter(w.cw, w.cw.n, w.toc, w.diffHash) if err != nil { return "", err } if err := w.bw.Flush(); err != nil { return "", err } return tocDigest, nil } func (w *Writer) closeGz() error { if w.closed { return errors.New("write on closed Writer") } if w.gz != nil { if err := w.gz.Close(); err != nil { return err } w.gz = nil } return nil } // nameIfChanged returns name, unless it was the already the value of (*mp)[id], // in which case it returns the empty string. func (w *Writer) nameIfChanged(mp *map[int]string, id int, name string) string { if name == "" { return "" } if *mp == nil { *mp = make(map[int]string) } if (*mp)[id] == name { return "" } (*mp)[id] = name return name } func (w *Writer) condOpenGz() (err error) { if w.gz == nil { w.gz, err = w.compressor.Writer(w.cw) } return } // AppendTar reads the tar or tar.gz file from r and appends // each of its contents to w. // // The input r can optionally be gzip compressed but the output will // always be compressed by the specified compressor. func (w *Writer) AppendTar(r io.Reader) error { return w.appendTar(r, false) } // AppendTarLossLess reads the tar or tar.gz file from r and appends // each of its contents to w. // // The input r can optionally be gzip compressed but the output will // always be compressed by the specified compressor. // // The difference of this func with AppendTar is that this writes // the input tar stream into w without any modification (e.g. to header bytes). // // Note that if the input tar stream already contains TOC JSON, this returns // error because w cannot overwrite the TOC JSON to the one generated by w without // lossy modification. To avoid this error, if the input stream is known to be stargz/estargz, // you shoud decompress it and remove TOC JSON in advance. func (w *Writer) AppendTarLossLess(r io.Reader) error { return w.appendTar(r, true) } func (w *Writer) appendTar(r io.Reader, lossless bool) error { var src io.Reader br := bufio.NewReader(r) if isGzip(br) { zr, _ := gzip.NewReader(br) src = zr } else { src = io.Reader(br) } dst := currentCompressionWriter{w} var tw *tar.Writer if !lossless { tw = tar.NewWriter(dst) // use tar writer only when this isn't lossless mode. } tr := tar.NewReader(src) if lossless { tr.RawAccounting = true } for { h, err := tr.Next() if err == io.EOF { if lossless { if remain := tr.RawBytes(); len(remain) > 0 { // Collect the remaining null bytes. // https://github.com/vbatts/tar-split/blob/80a436fd6164c557b131f7c59ed69bd81af69761/concept/main.go#L49-L53 if _, err := dst.Write(remain); err != nil { return err } } } break } if err != nil { return fmt.Errorf("error reading from source tar: tar.Reader.Next: %v", err) } if cleanEntryName(h.Name) == TOCTarName { // It is possible for a layer to be "stargzified" twice during the // distribution lifecycle. So we reserve "TOCTarName" here to avoid // duplicated entries in the resulting layer. if lossless { // We cannot handle this in lossless way. return fmt.Errorf("existing TOC JSON is not allowed; decompress layer before append") } continue } xattrs := make(map[string][]byte) const xattrPAXRecordsPrefix = "SCHILY.xattr." if h.PAXRecords != nil { for k, v := range h.PAXRecords { if strings.HasPrefix(k, xattrPAXRecordsPrefix) { xattrs[k[len(xattrPAXRecordsPrefix):]] = []byte(v) } } } ent := &TOCEntry{ Name: h.Name, Mode: h.Mode, UID: h.Uid, GID: h.Gid, Uname: w.nameIfChanged(&w.lastUsername, h.Uid, h.Uname), Gname: w.nameIfChanged(&w.lastGroupname, h.Gid, h.Gname), ModTime3339: formatModtime(h.ModTime), Xattrs: xattrs, } if err := w.condOpenGz(); err != nil { return err } if tw != nil { if err := tw.WriteHeader(h); err != nil { return err } } else { if _, err := dst.Write(tr.RawBytes()); err != nil { return err } } switch h.Typeflag { case tar.TypeLink: ent.Type = "hardlink" ent.LinkName = h.Linkname case tar.TypeSymlink: ent.Type = "symlink" ent.LinkName = h.Linkname case tar.TypeDir: ent.Type = "dir" case tar.TypeReg: ent.Type = "reg" ent.Size = h.Size case tar.TypeChar: ent.Type = "char" ent.DevMajor = int(h.Devmajor) ent.DevMinor = int(h.Devminor) case tar.TypeBlock: ent.Type = "block" ent.DevMajor = int(h.Devmajor) ent.DevMinor = int(h.Devminor) case tar.TypeFifo: ent.Type = "fifo" default: return fmt.Errorf("unsupported input tar entry %q", h.Typeflag) } // We need to keep a reference to the TOC entry for regular files, so that we // can fill the digest later. var regFileEntry *TOCEntry var payloadDigest digest.Digester if h.Typeflag == tar.TypeReg { regFileEntry = ent payloadDigest = digest.Canonical.Digester() } if h.Typeflag == tar.TypeReg && ent.Size > 0 { var written int64 totalSize := ent.Size // save it before we destroy ent tee := io.TeeReader(tr, payloadDigest.Hash()) for written < totalSize { if err := w.closeGz(); err != nil { return err } chunkSize := int64(w.chunkSize()) remain := totalSize - written if remain < chunkSize { chunkSize = remain } else { ent.ChunkSize = chunkSize } ent.Offset = w.cw.n ent.ChunkOffset = written chunkDigest := digest.Canonical.Digester() if err := w.condOpenGz(); err != nil { return err } teeChunk := io.TeeReader(tee, chunkDigest.Hash()) var out io.Writer if tw != nil { out = tw } else { out = dst } if _, err := io.CopyN(out, teeChunk, chunkSize); err != nil { return fmt.Errorf("error copying %q: %v", h.Name, err) } ent.ChunkDigest = chunkDigest.Digest().String() w.toc.Entries = append(w.toc.Entries, ent) written += chunkSize ent = &TOCEntry{ Name: h.Name, Type: "chunk", } } } else { w.toc.Entries = append(w.toc.Entries, ent) } if payloadDigest != nil { regFileEntry.Digest = payloadDigest.Digest().String() } if tw != nil { if err := tw.Flush(); err != nil { return err } } } remainDest := io.Discard if lossless { remainDest = dst // Preserve the remaining bytes in lossless mode } _, err := io.Copy(remainDest, src) return err } // DiffID returns the SHA-256 of the uncompressed tar bytes. // It is only valid to call DiffID after Close. func (w *Writer) DiffID() string { return fmt.Sprintf("sha256:%x", w.diffHash.Sum(nil)) } func maxFooterSize(blobSize int64, decompressors ...Decompressor) (res int64) { for _, d := range decompressors { if s := d.FooterSize(); res < s && s <= blobSize { res = s } } return } func parseTOC(d Decompressor, sr *io.SectionReader, tocOff, tocSize int64, tocBytes []byte, opts openOpts) (*Reader, error) { if len(tocBytes) > 0 { start := time.Now() toc, tocDgst, err := d.ParseTOC(bytes.NewReader(tocBytes)) if err == nil { if opts.telemetry != nil && opts.telemetry.DeserializeTocLatency != nil { opts.telemetry.DeserializeTocLatency(start) } return &Reader{ sr: sr, toc: toc, tocDigest: tocDgst, decompressor: d, }, nil } } start := time.Now() tocBytes = make([]byte, tocSize) if _, err := sr.ReadAt(tocBytes, tocOff); err != nil { return nil, fmt.Errorf("error reading %d byte TOC targz: %v", len(tocBytes), err) } if opts.telemetry != nil && opts.telemetry.GetTocLatency != nil { opts.telemetry.GetTocLatency(start) } start = time.Now() toc, tocDgst, err := d.ParseTOC(bytes.NewReader(tocBytes)) if err != nil { return nil, err } if opts.telemetry != nil && opts.telemetry.DeserializeTocLatency != nil { opts.telemetry.DeserializeTocLatency(start) } return &Reader{ sr: sr, toc: toc, tocDigest: tocDgst, decompressor: d, }, nil } func formatModtime(t time.Time) string { if t.IsZero() || t.Unix() == 0 { return "" } return t.UTC().Round(time.Second).Format(time.RFC3339) } func cleanEntryName(name string) string { // Use path.Clean to consistently deal with path separators across platforms. return strings.TrimPrefix(path.Clean("/"+name), "/") } // countWriter counts how many bytes have been written to its wrapped // io.Writer. type countWriter struct { w io.Writer n int64 } func (cw *countWriter) Write(p []byte) (n int, err error) { n, err = cw.w.Write(p) cw.n += int64(n) return } // isGzip reports whether br is positioned right before an upcoming gzip stream. // It does not consume any bytes from br. func isGzip(br *bufio.Reader) bool { const ( gzipID1 = 0x1f gzipID2 = 0x8b gzipDeflate = 8 ) peek, _ := br.Peek(3) return len(peek) >= 3 && peek[0] == gzipID1 && peek[1] == gzipID2 && peek[2] == gzipDeflate } func positive(n int64) int64 { if n < 0 { return 0 } return n } stargz-snapshotter-0.12.0/estargz/estargz_test.go000066400000000000000000000063721426301527400222140ustar00rootroot00000000000000/* 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. */ /* Copyright 2019 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. */ package estargz import "testing" // Tests *Reader.ChunkEntryForOffset about offset and size calculation. func TestChunkEntryForOffset(t *testing.T) { const chunkSize = 4 tests := []struct { name string fileSize int64 reqOffset int64 wantOk bool wantChunkOffset int64 wantChunkSize int64 }{ { name: "1st_chunk_in_1_chunk_reg", fileSize: chunkSize * 1, reqOffset: chunkSize * 0, wantChunkOffset: chunkSize * 0, wantChunkSize: chunkSize, wantOk: true, }, { name: "2nd_chunk_in_1_chunk_reg", fileSize: chunkSize * 1, reqOffset: chunkSize * 1, wantOk: false, }, { name: "1st_chunk_in_2_chunks_reg", fileSize: chunkSize * 2, reqOffset: chunkSize * 0, wantChunkOffset: chunkSize * 0, wantChunkSize: chunkSize, wantOk: true, }, { name: "2nd_chunk_in_2_chunks_reg", fileSize: chunkSize * 2, reqOffset: chunkSize * 1, wantChunkOffset: chunkSize * 1, wantChunkSize: chunkSize, wantOk: true, }, { name: "3rd_chunk_in_2_chunks_reg", fileSize: chunkSize * 2, reqOffset: chunkSize * 2, wantOk: false, }, } for _, te := range tests { t.Run(te.name, func(t *testing.T) { name := "test" _, r := regularFileReader(name, te.fileSize, chunkSize) ce, ok := r.ChunkEntryForOffset(name, te.reqOffset) if ok != te.wantOk { t.Errorf("ok = %v; want (%v)", ok, te.wantOk) } else if ok { if !(ce.ChunkOffset == te.wantChunkOffset && ce.ChunkSize == te.wantChunkSize) { t.Errorf("chunkOffset = %d, ChunkSize = %d; want (chunkOffset = %d, chunkSize = %d)", ce.ChunkOffset, ce.ChunkSize, te.wantChunkOffset, te.wantChunkSize) } } }) } } // regularFileReader makes a minimal Reader of "reg" and "chunk" without tar-related information. func regularFileReader(name string, size int64, chunkSize int64) (*TOCEntry, *Reader) { ent := &TOCEntry{ Name: name, Type: "reg", } m := ent chunks := make([]*TOCEntry, 0, size/chunkSize+1) var written int64 for written < size { remain := size - written cs := chunkSize if remain < cs { cs = remain } ent.ChunkSize = cs ent.ChunkOffset = written chunks = append(chunks, ent) written += cs ent = &TOCEntry{ Name: name, Type: "chunk", } } if len(chunks) == 1 { chunks = nil } return m, &Reader{ m: map[string]*TOCEntry{name: m}, chunks: map[string][]*TOCEntry{name: chunks}, } } stargz-snapshotter-0.12.0/estargz/go.mod000066400000000000000000000003741426301527400202510ustar00rootroot00000000000000module github.com/containerd/stargz-snapshotter/estargz go 1.16 require ( github.com/klauspost/compress v1.15.7 github.com/opencontainers/go-digest v1.0.0 github.com/vbatts/tar-split v0.11.2 golang.org/x/sync v0.0.0-20201207232520-09787c993a3a ) stargz-snapshotter-0.12.0/estargz/go.sum000066400000000000000000000035601426301527400202760ustar00rootroot00000000000000github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 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.15.7 h1:7cgTQxJCU/vy+oP/E3B9RGbQTgbiVzIJWIKOLoAsPok= github.com/klauspost/compress v1.15.7/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= 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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 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.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/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= stargz-snapshotter-0.12.0/estargz/gzip.go000066400000000000000000000154511426301527400204450ustar00rootroot00000000000000/* 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. */ /* Copyright 2019 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. */ package estargz import ( "archive/tar" "bytes" "compress/gzip" "encoding/binary" "encoding/json" "fmt" "hash" "io" "strconv" digest "github.com/opencontainers/go-digest" ) type gzipCompression struct { *GzipCompressor *GzipDecompressor } func newGzipCompressionWithLevel(level int) Compression { return &gzipCompression{ &GzipCompressor{level}, &GzipDecompressor{}, } } func NewGzipCompressor() *GzipCompressor { return &GzipCompressor{gzip.BestCompression} } func NewGzipCompressorWithLevel(level int) *GzipCompressor { return &GzipCompressor{level} } type GzipCompressor struct { compressionLevel int } func (gc *GzipCompressor) Writer(w io.Writer) (io.WriteCloser, error) { return gzip.NewWriterLevel(w, gc.compressionLevel) } func (gc *GzipCompressor) WriteTOCAndFooter(w io.Writer, off int64, toc *JTOC, diffHash hash.Hash) (digest.Digest, error) { tocJSON, err := json.MarshalIndent(toc, "", "\t") if err != nil { return "", err } gz, _ := gzip.NewWriterLevel(w, gc.compressionLevel) gw := io.Writer(gz) if diffHash != nil { gw = io.MultiWriter(gz, diffHash) } tw := tar.NewWriter(gw) if err := tw.WriteHeader(&tar.Header{ Typeflag: tar.TypeReg, Name: TOCTarName, Size: int64(len(tocJSON)), }); err != nil { return "", err } if _, err := tw.Write(tocJSON); err != nil { return "", err } if err := tw.Close(); err != nil { return "", err } if err := gz.Close(); err != nil { return "", err } if _, err := w.Write(gzipFooterBytes(off)); err != nil { return "", err } return digest.FromBytes(tocJSON), nil } // gzipFooterBytes returns the 51 bytes footer. func gzipFooterBytes(tocOff int64) []byte { buf := bytes.NewBuffer(make([]byte, 0, FooterSize)) gz, _ := gzip.NewWriterLevel(buf, gzip.NoCompression) // MUST be NoCompression to keep 51 bytes // Extra header indicating the offset of TOCJSON // https://tools.ietf.org/html/rfc1952#section-2.3.1.1 header := make([]byte, 4) header[0], header[1] = 'S', 'G' subfield := fmt.Sprintf("%016xSTARGZ", tocOff) binary.LittleEndian.PutUint16(header[2:4], uint16(len(subfield))) // little-endian per RFC1952 gz.Header.Extra = append(header, []byte(subfield)...) gz.Close() if buf.Len() != FooterSize { panic(fmt.Sprintf("footer buffer = %d, not %d", buf.Len(), FooterSize)) } return buf.Bytes() } type GzipDecompressor struct{} func (gz *GzipDecompressor) Reader(r io.Reader) (io.ReadCloser, error) { return gzip.NewReader(r) } func (gz *GzipDecompressor) ParseTOC(r io.Reader) (toc *JTOC, tocDgst digest.Digest, err error) { return parseTOCEStargz(r) } func (gz *GzipDecompressor) ParseFooter(p []byte) (blobPayloadSize, tocOffset, tocSize int64, err error) { if len(p) != FooterSize { return 0, 0, 0, fmt.Errorf("invalid length %d cannot be parsed", len(p)) } zr, err := gzip.NewReader(bytes.NewReader(p)) if err != nil { return 0, 0, 0, err } defer zr.Close() extra := zr.Header.Extra si1, si2, subfieldlen, subfield := extra[0], extra[1], extra[2:4], extra[4:] if si1 != 'S' || si2 != 'G' { return 0, 0, 0, fmt.Errorf("invalid subfield IDs: %q, %q; want E, S", si1, si2) } if slen := binary.LittleEndian.Uint16(subfieldlen); slen != uint16(16+len("STARGZ")) { return 0, 0, 0, fmt.Errorf("invalid length of subfield %d; want %d", slen, 16+len("STARGZ")) } if string(subfield[16:]) != "STARGZ" { return 0, 0, 0, fmt.Errorf("STARGZ magic string must be included in the footer subfield") } tocOffset, err = strconv.ParseInt(string(subfield[:16]), 16, 64) if err != nil { return 0, 0, 0, fmt.Errorf("legacy: failed to parse toc offset: %w", err) } return tocOffset, tocOffset, 0, nil } func (gz *GzipDecompressor) FooterSize() int64 { return FooterSize } func (gz *GzipDecompressor) DecompressTOC(r io.Reader) (tocJSON io.ReadCloser, err error) { return decompressTOCEStargz(r) } type LegacyGzipDecompressor struct{} func (gz *LegacyGzipDecompressor) Reader(r io.Reader) (io.ReadCloser, error) { return gzip.NewReader(r) } func (gz *LegacyGzipDecompressor) ParseTOC(r io.Reader) (toc *JTOC, tocDgst digest.Digest, err error) { return parseTOCEStargz(r) } func (gz *LegacyGzipDecompressor) ParseFooter(p []byte) (blobPayloadSize, tocOffset, tocSize int64, err error) { if len(p) != legacyFooterSize { return 0, 0, 0, fmt.Errorf("legacy: invalid length %d cannot be parsed", len(p)) } zr, err := gzip.NewReader(bytes.NewReader(p)) if err != nil { return 0, 0, 0, fmt.Errorf("legacy: failed to get footer gzip reader: %w", err) } defer zr.Close() extra := zr.Header.Extra if len(extra) != 16+len("STARGZ") { return 0, 0, 0, fmt.Errorf("legacy: invalid stargz's extra field size") } if string(extra[16:]) != "STARGZ" { return 0, 0, 0, fmt.Errorf("legacy: magic string STARGZ not found") } tocOffset, err = strconv.ParseInt(string(extra[:16]), 16, 64) if err != nil { return 0, 0, 0, fmt.Errorf("legacy: failed to parse toc offset: %w", err) } return tocOffset, tocOffset, 0, nil } func (gz *LegacyGzipDecompressor) FooterSize() int64 { return legacyFooterSize } func (gz *LegacyGzipDecompressor) DecompressTOC(r io.Reader) (tocJSON io.ReadCloser, err error) { return decompressTOCEStargz(r) } func parseTOCEStargz(r io.Reader) (toc *JTOC, tocDgst digest.Digest, err error) { tr, err := decompressTOCEStargz(r) if err != nil { return nil, "", err } dgstr := digest.Canonical.Digester() toc = new(JTOC) if err := json.NewDecoder(io.TeeReader(tr, dgstr.Hash())).Decode(&toc); err != nil { return nil, "", fmt.Errorf("error decoding TOC JSON: %v", err) } if err := tr.Close(); err != nil { return nil, "", err } return toc, dgstr.Digest(), nil } func decompressTOCEStargz(r io.Reader) (tocJSON io.ReadCloser, err error) { zr, err := gzip.NewReader(r) if err != nil { return nil, fmt.Errorf("malformed TOC gzip header: %v", err) } zr.Multistream(false) tr := tar.NewReader(zr) h, err := tr.Next() if err != nil { return nil, fmt.Errorf("failed to find tar header in TOC gzip stream: %v", err) } if h.Name != TOCTarName { return nil, fmt.Errorf("TOC tar entry had name %q; expected %q", h.Name, TOCTarName) } return readCloser{tr, zr.Close}, nil } stargz-snapshotter-0.12.0/estargz/gzip_test.go000066400000000000000000000102441426301527400214770ustar00rootroot00000000000000/* 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. */ /* Copyright 2019 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. */ package estargz import ( "bytes" "compress/gzip" "crypto/sha256" "fmt" "io" "testing" ) // TestGzipEStargz tests gzip-based eStargz func TestGzipEStargz(t *testing.T) { CompressionTestSuite(t, gzipControllerWithLevel(gzip.NoCompression), gzipControllerWithLevel(gzip.BestSpeed), gzipControllerWithLevel(gzip.BestCompression), gzipControllerWithLevel(gzip.DefaultCompression), gzipControllerWithLevel(gzip.HuffmanOnly), ) } func gzipControllerWithLevel(compressionLevel int) TestingController { return &gzipController{&GzipCompressor{compressionLevel}, &GzipDecompressor{}} } type gzipController struct { *GzipCompressor *GzipDecompressor } func (gc *gzipController) String() string { return fmt.Sprintf("gzip_compression_level=%v", gc.GzipCompressor.compressionLevel) } func (gc *gzipController) CountStreams(t *testing.T, b []byte) (numStreams int) { len0 := len(b) br := bytes.NewReader(b) zr := new(gzip.Reader) t.Logf("got gzip streams:") for { zoff := len0 - br.Len() if err := zr.Reset(br); err != nil { if err == io.EOF { return } t.Fatalf("countStreams(gzip), Reset: %v", err) } zr.Multistream(false) n, err := io.Copy(io.Discard, zr) if err != nil { t.Fatalf("countStreams(gzip), Copy: %v", err) } var extra string if len(zr.Header.Extra) > 0 { extra = fmt.Sprintf("; extra=%q", zr.Header.Extra) } t.Logf(" [%d] at %d in stargz, uncompressed length %d%s", numStreams, zoff, n, extra) numStreams++ } } func (gc *gzipController) DiffIDOf(t *testing.T, b []byte) string { h := sha256.New() zr, err := gzip.NewReader(bytes.NewReader(b)) if err != nil { t.Fatalf("diffIDOf(gzip): %v", err) } defer zr.Close() if _, err := io.Copy(h, zr); err != nil { t.Fatalf("diffIDOf(gzip).Copy: %v", err) } return fmt.Sprintf("sha256:%x", h.Sum(nil)) } // Tests footer encoding, size, and parsing of gzip-based eStargz. func TestGzipFooter(t *testing.T) { for off := int64(0); off <= 200000; off += 1023 { checkFooter(t, off) checkLegacyFooter(t, off) } } // TODO: check fallback func checkFooter(t *testing.T, off int64) { footer := gzipFooterBytes(off) if len(footer) != FooterSize { t.Fatalf("for offset %v, footer length was %d, not expected %d. got bytes: %q", off, len(footer), FooterSize, footer) } _, got, _, err := (&GzipDecompressor{}).ParseFooter(footer) if err != nil { t.Fatalf("failed to parse footer for offset %d, footer: %x: err: %v", off, footer, err) } if got != off { t.Fatalf("ParseFooter(footerBytes(offset %d)) = %d; want %d", off, got, off) } } func checkLegacyFooter(t *testing.T, off int64) { footer := legacyFooterBytes(off) if len(footer) != legacyFooterSize { t.Fatalf("for offset %v, footer length was %d, not expected %d. got bytes: %q", off, len(footer), legacyFooterSize, footer) } _, got, _, err := (&LegacyGzipDecompressor{}).ParseFooter(footer) if err != nil { t.Fatalf("failed to parse legacy footer for offset %d, footer: %x: err: %v", off, footer, err) } if got != off { t.Fatalf("ParseFooter(legacyFooterBytes(offset %d)) = %d; want %d", off, got, off) } } func legacyFooterBytes(tocOff int64) []byte { buf := bytes.NewBuffer(make([]byte, 0, legacyFooterSize)) gz, _ := gzip.NewWriterLevel(buf, gzip.NoCompression) gz.Header.Extra = []byte(fmt.Sprintf("%016xSTARGZ", tocOff)) gz.Close() if buf.Len() != legacyFooterSize { panic(fmt.Sprintf("footer buffer = %d, not %d", buf.Len(), legacyFooterSize)) } return buf.Bytes() } stargz-snapshotter-0.12.0/estargz/testutil.go000066400000000000000000001646551426301527400213640ustar00rootroot00000000000000/* 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. */ /* Copyright 2019 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. */ package estargz import ( "archive/tar" "bytes" "compress/gzip" "crypto/sha256" "encoding/json" "errors" "fmt" "io" "os" "path/filepath" "reflect" "sort" "strings" "testing" "time" "github.com/containerd/stargz-snapshotter/estargz/errorutil" "github.com/klauspost/compress/zstd" digest "github.com/opencontainers/go-digest" ) // TestingController is Compression with some helper methods necessary for testing. type TestingController interface { Compression CountStreams(*testing.T, []byte) int DiffIDOf(*testing.T, []byte) string String() string } // CompressionTestSuite tests this pkg with controllers can build valid eStargz blobs and parse them. func CompressionTestSuite(t *testing.T, controllers ...TestingController) { t.Run("testBuild", func(t *testing.T) { t.Parallel(); testBuild(t, controllers...) }) t.Run("testDigestAndVerify", func(t *testing.T) { t.Parallel(); testDigestAndVerify(t, controllers...) }) t.Run("testWriteAndOpen", func(t *testing.T) { t.Parallel(); testWriteAndOpen(t, controllers...) }) } const ( uncompressedType int = iota gzipType zstdType ) var srcCompressions = []int{ uncompressedType, gzipType, zstdType, } var allowedPrefix = [4]string{"", "./", "/", "../"} // testBuild tests the resulting stargz blob built by this pkg has the same // contents as the normal stargz blob. func testBuild(t *testing.T, controllers ...TestingController) { tests := []struct { name string chunkSize int in []tarEntry }{ { name: "regfiles and directories", chunkSize: 4, in: tarOf( file("foo", "test1"), dir("foo2/"), file("foo2/bar", "test2", xAttr(map[string]string{"test": "sample"})), ), }, { name: "empty files", chunkSize: 4, in: tarOf( file("foo", "tttttt"), file("foo_empty", ""), file("foo2", "tttttt"), file("foo_empty2", ""), file("foo3", "tttttt"), file("foo_empty3", ""), file("foo4", "tttttt"), file("foo_empty4", ""), file("foo5", "tttttt"), file("foo_empty5", ""), file("foo6", "tttttt"), ), }, { name: "various files", chunkSize: 4, in: tarOf( file("baz.txt", "bazbazbazbazbazbazbaz"), file("foo.txt", "a"), symlink("barlink", "test/bar.txt"), dir("test/"), dir("dev/"), blockdev("dev/testblock", 3, 4), fifo("dev/testfifo"), chardev("dev/testchar1", 5, 6), file("test/bar.txt", "testbartestbar", xAttr(map[string]string{"test2": "sample2"})), dir("test2/"), link("test2/bazlink", "baz.txt"), chardev("dev/testchar2", 1, 2), ), }, { name: "no contents", chunkSize: 4, in: tarOf( file("baz.txt", ""), symlink("barlink", "test/bar.txt"), dir("test/"), dir("dev/"), blockdev("dev/testblock", 3, 4), fifo("dev/testfifo"), chardev("dev/testchar1", 5, 6), file("test/bar.txt", "", xAttr(map[string]string{"test2": "sample2"})), dir("test2/"), link("test2/bazlink", "baz.txt"), chardev("dev/testchar2", 1, 2), ), }, } for _, tt := range tests { for _, srcCompression := range srcCompressions { srcCompression := srcCompression for _, cl := range controllers { cl := cl for _, srcTarFormat := range []tar.Format{tar.FormatUSTAR, tar.FormatPAX, tar.FormatGNU} { srcTarFormat := srcTarFormat for _, prefix := range allowedPrefix { prefix := prefix t.Run(tt.name+"-"+fmt.Sprintf("compression=%v,prefix=%q,src=%d,format=%s", cl, prefix, srcCompression, srcTarFormat), func(t *testing.T) { tarBlob := buildTar(t, tt.in, prefix, srcTarFormat) // Test divideEntries() entries, err := sortEntries(tarBlob, nil, nil) // identical order if err != nil { t.Fatalf("failed to parse tar: %v", err) } var merged []*entry for _, part := range divideEntries(entries, 4) { merged = append(merged, part...) } if !reflect.DeepEqual(entries, merged) { for _, e := range entries { t.Logf("Original: %v", e.header) } for _, e := range merged { t.Logf("Merged: %v", e.header) } t.Errorf("divided entries couldn't be merged") return } // Prepare sample data wantBuf := new(bytes.Buffer) sw := NewWriterWithCompressor(wantBuf, cl) sw.ChunkSize = tt.chunkSize if err := sw.AppendTar(tarBlob); err != nil { t.Fatalf("failed to append tar to want stargz: %v", err) } if _, err := sw.Close(); err != nil { t.Fatalf("failed to prepare want stargz: %v", err) } wantData := wantBuf.Bytes() want, err := Open(io.NewSectionReader( bytes.NewReader(wantData), 0, int64(len(wantData))), WithDecompressors(cl), ) if err != nil { t.Fatalf("failed to parse the want stargz: %v", err) } // Prepare testing data rc, err := Build(compressBlob(t, tarBlob, srcCompression), WithChunkSize(tt.chunkSize), WithCompression(cl)) if err != nil { t.Fatalf("failed to build stargz: %v", err) } defer rc.Close() gotBuf := new(bytes.Buffer) if _, err := io.Copy(gotBuf, rc); err != nil { t.Fatalf("failed to copy built stargz blob: %v", err) } gotData := gotBuf.Bytes() got, err := Open(io.NewSectionReader( bytes.NewReader(gotBuf.Bytes()), 0, int64(len(gotData))), WithDecompressors(cl), ) if err != nil { t.Fatalf("failed to parse the got stargz: %v", err) } // Check DiffID is properly calculated rc.Close() diffID := rc.DiffID() wantDiffID := cl.DiffIDOf(t, gotData) if diffID.String() != wantDiffID { t.Errorf("DiffID = %q; want %q", diffID, wantDiffID) } // Compare as stargz if !isSameVersion(t, cl, wantData, gotData) { t.Errorf("built stargz hasn't same json") return } if !isSameEntries(t, want, got) { t.Errorf("built stargz isn't same as the original") return } // Compare as tar.gz if !isSameTarGz(t, cl, wantData, gotData) { t.Errorf("built stargz isn't same tar.gz") return } }) } } } } } } func isSameTarGz(t *testing.T, controller TestingController, a, b []byte) bool { aGz, err := controller.Reader(bytes.NewReader(a)) if err != nil { t.Fatalf("failed to read A") } defer aGz.Close() bGz, err := controller.Reader(bytes.NewReader(b)) if err != nil { t.Fatalf("failed to read B") } defer bGz.Close() // Same as tar's Next() method but ignores landmarks and TOCJSON file next := func(r *tar.Reader) (h *tar.Header, err error) { for { if h, err = r.Next(); err != nil { return } if h.Name != PrefetchLandmark && h.Name != NoPrefetchLandmark && h.Name != TOCTarName { return } } } aTar := tar.NewReader(aGz) bTar := tar.NewReader(bGz) for { // Fetch and parse next header. aH, aErr := next(aTar) bH, bErr := next(bTar) if aErr != nil || bErr != nil { if aErr == io.EOF && bErr == io.EOF { break } t.Fatalf("Failed to parse tar file: A: %v, B: %v", aErr, bErr) } if !reflect.DeepEqual(aH, bH) { t.Logf("different header (A = %v; B = %v)", aH, bH) return false } aFile, err := io.ReadAll(aTar) if err != nil { t.Fatal("failed to read tar payload of A") } bFile, err := io.ReadAll(bTar) if err != nil { t.Fatal("failed to read tar payload of B") } if !bytes.Equal(aFile, bFile) { t.Logf("different tar payload (A = %q; B = %q)", string(a), string(b)) return false } } return true } func isSameVersion(t *testing.T, controller TestingController, a, b []byte) bool { aJTOC, _, err := parseStargz(io.NewSectionReader(bytes.NewReader(a), 0, int64(len(a))), controller) if err != nil { t.Fatalf("failed to parse A: %v", err) } bJTOC, _, err := parseStargz(io.NewSectionReader(bytes.NewReader(b), 0, int64(len(b))), controller) if err != nil { t.Fatalf("failed to parse B: %v", err) } t.Logf("A: TOCJSON: %v", dumpTOCJSON(t, aJTOC)) t.Logf("B: TOCJSON: %v", dumpTOCJSON(t, bJTOC)) return aJTOC.Version == bJTOC.Version } func isSameEntries(t *testing.T, a, b *Reader) bool { aroot, ok := a.Lookup("") if !ok { t.Fatalf("failed to get root of A") } broot, ok := b.Lookup("") if !ok { t.Fatalf("failed to get root of B") } aEntry := stargzEntry{aroot, a} bEntry := stargzEntry{broot, b} return contains(t, aEntry, bEntry) && contains(t, bEntry, aEntry) } func compressBlob(t *testing.T, src *io.SectionReader, srcCompression int) *io.SectionReader { buf := new(bytes.Buffer) var w io.WriteCloser var err error if srcCompression == gzipType { w = gzip.NewWriter(buf) } else if srcCompression == zstdType { w, err = zstd.NewWriter(buf) if err != nil { t.Fatalf("failed to init zstd writer: %v", err) } } else { return src } src.Seek(0, io.SeekStart) if _, err := io.Copy(w, src); err != nil { t.Fatalf("failed to compress source") } if err := w.Close(); err != nil { t.Fatalf("failed to finalize compress source") } data := buf.Bytes() return io.NewSectionReader(bytes.NewReader(data), 0, int64(len(data))) } type stargzEntry struct { e *TOCEntry r *Reader } // 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". func contains(t *testing.T, a, b stargzEntry) bool { ae, ar := a.e, a.r be, br := b.e, b.r t.Logf("Comparing: %q vs %q", ae.Name, be.Name) if !equalEntry(ae, be) { t.Logf("%q != %q: entry: a: %v, b: %v", ae.Name, be.Name, ae, be) return false } if ae.Type == "dir" { t.Logf("Directory: %q vs %q: %v vs %v", ae.Name, be.Name, allChildrenName(ae), allChildrenName(be)) iscontain := true ae.ForeachChild(func(aBaseName string, aChild *TOCEntry) bool { // Walk through all files on this stargz file. if aChild.Name == PrefetchLandmark || aChild.Name == NoPrefetchLandmark { return true // Ignore landmarks } // Ignore a TOCEntry of "./" (formated as "" by stargz lib) on root directory // because this points to the root directory itself. if aChild.Name == "" && ae.Name == "" { return true } bChild, ok := be.LookupChild(aBaseName) if !ok { t.Logf("%q (base: %q): not found in b: %v", ae.Name, aBaseName, allChildrenName(be)) iscontain = false return false } childcontain := contains(t, stargzEntry{aChild, a.r}, stargzEntry{bChild, b.r}) if !childcontain { t.Logf("%q != %q: non-equal dir", ae.Name, be.Name) iscontain = false return false } return true }) return iscontain } else if ae.Type == "reg" { af, err := ar.OpenFile(ae.Name) if err != nil { t.Fatalf("failed to open file %q on A: %v", ae.Name, err) } bf, err := br.OpenFile(be.Name) if err != nil { t.Fatalf("failed to open file %q on B: %v", be.Name, err) } var nr int64 for nr < ae.Size { abytes, anext, aok := readOffset(t, af, nr, a) bbytes, bnext, bok := readOffset(t, bf, nr, b) if !aok && !bok { break } else if !(aok && bok) || anext != bnext { 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) return false } nr = anext if !bytes.Equal(abytes, bbytes) { t.Logf("%q != %q: different contents %v vs %v", ae.Name, be.Name, string(abytes), string(bbytes)) return false } } return true } return true } func allChildrenName(e *TOCEntry) (children []string) { e.ForeachChild(func(baseName string, _ *TOCEntry) bool { children = append(children, baseName) return true }) return } func equalEntry(a, b *TOCEntry) bool { // Here, we selectively compare fileds that we are interested in. return a.Name == b.Name && a.Type == b.Type && a.Size == b.Size && a.ModTime3339 == b.ModTime3339 && a.Stat().ModTime().Equal(b.Stat().ModTime()) && // modTime time.Time a.LinkName == b.LinkName && a.Mode == b.Mode && a.UID == b.UID && a.GID == b.GID && a.Uname == b.Uname && a.Gname == b.Gname && (a.Offset > 0) == (b.Offset > 0) && (a.NextOffset() > 0) == (b.NextOffset() > 0) && a.DevMajor == b.DevMajor && a.DevMinor == b.DevMinor && a.NumLink == b.NumLink && reflect.DeepEqual(a.Xattrs, b.Xattrs) && // chunk-related infomations aren't compared in this function. // ChunkOffset int64 `json:"chunkOffset,omitempty"` // ChunkSize int64 `json:"chunkSize,omitempty"` // children map[string]*TOCEntry a.Digest == b.Digest } func readOffset(t *testing.T, r *io.SectionReader, offset int64, e stargzEntry) ([]byte, int64, bool) { ce, ok := e.r.ChunkEntryForOffset(e.e.Name, offset) if !ok { return nil, 0, false } data := make([]byte, ce.ChunkSize) t.Logf("Offset: %v, NextOffset: %v", ce.Offset, ce.NextOffset()) n, err := r.ReadAt(data, ce.ChunkOffset) if err != nil { t.Fatalf("failed to read file payload of %q (offset:%d,size:%d): %v", e.e.Name, ce.ChunkOffset, ce.ChunkSize, err) } if int64(n) != ce.ChunkSize { t.Fatalf("unexpected copied data size %d; want %d", n, ce.ChunkSize) } return data[:n], offset + ce.ChunkSize, true } func dumpTOCJSON(t *testing.T, tocJSON *JTOC) string { jtocData, err := json.Marshal(*tocJSON) if err != nil { t.Fatalf("failed to marshal TOC JSON: %v", err) } buf := new(bytes.Buffer) if _, err := io.Copy(buf, bytes.NewReader(jtocData)); err != nil { t.Fatalf("failed to read toc json blob: %v", err) } return buf.String() } const chunkSize = 3 // 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) // testDigestAndVerify runs specified checks against sample stargz blobs. func testDigestAndVerify(t *testing.T, controllers ...TestingController) { tests := []struct { name string tarInit func(t *testing.T, dgstMap map[string]digest.Digest) (blob []tarEntry) checks []check }{ { name: "no-regfile", tarInit: func(t *testing.T, dgstMap map[string]digest.Digest) (blob []tarEntry) { return tarOf( dir("test/"), ) }, checks: []check{ checkStargzTOC, checkVerifyTOC, checkVerifyInvalidStargzFail(buildTar(t, tarOf( dir("test2/"), // modified ), allowedPrefix[0])), }, }, { name: "small-files", tarInit: func(t *testing.T, dgstMap map[string]digest.Digest) (blob []tarEntry) { return tarOf( regDigest(t, "baz.txt", "", dgstMap), regDigest(t, "foo.txt", "a", dgstMap), dir("test/"), regDigest(t, "test/bar.txt", "bbb", dgstMap), ) }, checks: []check{ checkStargzTOC, checkVerifyTOC, checkVerifyInvalidStargzFail(buildTar(t, tarOf( file("baz.txt", ""), file("foo.txt", "M"), // modified dir("test/"), file("test/bar.txt", "bbb"), ), allowedPrefix[0])), // checkVerifyInvalidTOCEntryFail("foo.txt"), // TODO checkVerifyBrokenContentFail("foo.txt"), }, }, { name: "big-files", tarInit: func(t *testing.T, dgstMap map[string]digest.Digest) (blob []tarEntry) { return tarOf( regDigest(t, "baz.txt", "bazbazbazbazbazbazbaz", dgstMap), regDigest(t, "foo.txt", "a", dgstMap), dir("test/"), regDigest(t, "test/bar.txt", "testbartestbar", dgstMap), ) }, checks: []check{ checkStargzTOC, checkVerifyTOC, checkVerifyInvalidStargzFail(buildTar(t, tarOf( file("baz.txt", "bazbazbazMMMbazbazbaz"), // modified file("foo.txt", "a"), dir("test/"), file("test/bar.txt", "testbartestbar"), ), allowedPrefix[0])), checkVerifyInvalidTOCEntryFail("test/bar.txt"), checkVerifyBrokenContentFail("test/bar.txt"), }, }, { name: "with-non-regfiles", tarInit: func(t *testing.T, dgstMap map[string]digest.Digest) (blob []tarEntry) { return tarOf( regDigest(t, "baz.txt", "bazbazbazbazbazbazbaz", dgstMap), regDigest(t, "foo.txt", "a", dgstMap), symlink("barlink", "test/bar.txt"), dir("test/"), regDigest(t, "test/bar.txt", "testbartestbar", dgstMap), dir("test2/"), link("test2/bazlink", "baz.txt"), ) }, checks: []check{ checkStargzTOC, checkVerifyTOC, checkVerifyInvalidStargzFail(buildTar(t, tarOf( file("baz.txt", "bazbazbazbazbazbazbaz"), file("foo.txt", "a"), symlink("barlink", "test/bar.txt"), dir("test/"), file("test/bar.txt", "testbartestbar"), dir("test2/"), link("test2/bazlink", "foo.txt"), // modified ), allowedPrefix[0])), checkVerifyInvalidTOCEntryFail("test/bar.txt"), checkVerifyBrokenContentFail("test/bar.txt"), }, }, } for _, tt := range tests { for _, srcCompression := range srcCompressions { srcCompression := srcCompression for _, cl := range controllers { cl := cl for _, prefix := range allowedPrefix { prefix := prefix for _, srcTarFormat := range []tar.Format{tar.FormatUSTAR, tar.FormatPAX, tar.FormatGNU} { srcTarFormat := srcTarFormat t.Run(tt.name+"-"+fmt.Sprintf("compression=%v,prefix=%q,format=%s", cl, prefix, srcTarFormat), func(t *testing.T) { // Get original tar file and chunk digests dgstMap := make(map[string]digest.Digest) tarBlob := buildTar(t, tt.tarInit(t, dgstMap), prefix, srcTarFormat) rc, err := Build(compressBlob(t, tarBlob, srcCompression), WithChunkSize(chunkSize), WithCompression(cl)) if err != nil { t.Fatalf("failed to convert stargz: %v", err) } tocDigest := rc.TOCDigest() defer rc.Close() buf := new(bytes.Buffer) if _, err := io.Copy(buf, rc); err != nil { t.Fatalf("failed to copy built stargz blob: %v", err) } newStargz := buf.Bytes() // NoPrefetchLandmark is added during `Bulid`, which is expected behaviour. dgstMap[chunkID(NoPrefetchLandmark, 0, int64(len([]byte{landmarkContents})))] = digest.FromBytes([]byte{landmarkContents}) for _, check := range tt.checks { check(t, newStargz, tocDigest, dgstMap, cl) } }) } } } } } } // 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 // checks all chunk digests stored to the TOC JSON match the actual contents. func checkStargzTOC(t *testing.T, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController) { sgz, err := Open( io.NewSectionReader(bytes.NewReader(sgzData), 0, int64(len(sgzData))), WithDecompressors(controller), ) if err != nil { t.Errorf("failed to parse converted stargz: %v", err) return } digestMapTOC, err := listDigests(io.NewSectionReader( bytes.NewReader(sgzData), 0, int64(len(sgzData))), controller, ) if err != nil { t.Fatalf("failed to list digest: %v", err) } found := make(map[string]bool) for id := range dgstMap { found[id] = false } zr, err := controller.Reader(bytes.NewReader(sgzData)) if err != nil { t.Fatalf("failed to decompress converted stargz: %v", err) } defer zr.Close() tr := tar.NewReader(zr) for { h, err := tr.Next() if err != nil { if err != io.EOF { t.Errorf("failed to read tar entry: %v", err) return } break } if h.Name == TOCTarName { // Check the digest of TOC JSON based on the actual contents // It's sure that TOC JSON exists in this archive because // Open succeeded. dgstr := digest.Canonical.Digester() if _, err := io.Copy(dgstr.Hash(), tr); err != nil { t.Fatalf("failed to calculate digest of TOC JSON: %v", err) } if dgstr.Digest() != tocDigest { t.Errorf("invalid TOC JSON %q; want %q", tocDigest, dgstr.Digest()) } continue } if _, ok := sgz.Lookup(h.Name); !ok { t.Errorf("lost stargz entry %q in the converted TOC", h.Name) return } var n int64 for n < h.Size { ce, ok := sgz.ChunkEntryForOffset(h.Name, n) if !ok { t.Errorf("lost chunk %q(offset=%d) in the converted TOC", h.Name, n) return } // Get the original digest to make sure the file contents are kept unchanged // from the original tar, during the whole conversion steps. id := chunkID(h.Name, n, ce.ChunkSize) want, ok := dgstMap[id] if !ok { t.Errorf("Unexpected chunk %q(offset=%d,size=%d): %v", h.Name, n, ce.ChunkSize, dgstMap) return } found[id] = true // Check the file contents dgstr := digest.Canonical.Digester() if _, err := io.CopyN(dgstr.Hash(), tr, ce.ChunkSize); err != nil { t.Fatalf("failed to calculate digest of %q (offset=%d,size=%d)", h.Name, n, ce.ChunkSize) } if want != dgstr.Digest() { t.Errorf("Invalid contents in converted stargz %q: %q; want %q", h.Name, dgstr.Digest(), want) return } // Check the digest stored in TOC JSON dgstTOC, ok := digestMapTOC[ce.Offset] if !ok { t.Errorf("digest of %q(offset=%d,size=%d,chunkOffset=%d) isn't registered", h.Name, ce.Offset, ce.ChunkSize, ce.ChunkOffset) } if want != dgstTOC { t.Errorf("Invalid digest in TOCEntry %q: %q; want %q", h.Name, dgstTOC, want) return } n += ce.ChunkSize } } for id, ok := range found { if !ok { t.Errorf("required chunk %q not found in the converted stargz: %v", id, found) } } } // 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 // all chunks work. func checkVerifyTOC(t *testing.T, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController) { sgz, err := Open( io.NewSectionReader(bytes.NewReader(sgzData), 0, int64(len(sgzData))), WithDecompressors(controller), ) if err != nil { t.Errorf("failed to parse converted stargz: %v", err) return } ev, err := sgz.VerifyTOC(tocDigest) if err != nil { t.Errorf("failed to verify stargz: %v", err) return } found := make(map[string]bool) for id := range dgstMap { found[id] = false } zr, err := controller.Reader(bytes.NewReader(sgzData)) if err != nil { t.Fatalf("failed to decompress converted stargz: %v", err) } defer zr.Close() tr := tar.NewReader(zr) for { h, err := tr.Next() if err != nil { if err != io.EOF { t.Errorf("failed to read tar entry: %v", err) return } break } if h.Name == TOCTarName { continue } if _, ok := sgz.Lookup(h.Name); !ok { t.Errorf("lost stargz entry %q in the converted TOC", h.Name) return } var n int64 for n < h.Size { ce, ok := sgz.ChunkEntryForOffset(h.Name, n) if !ok { t.Errorf("lost chunk %q(offset=%d) in the converted TOC", h.Name, n) return } v, err := ev.Verifier(ce) if err != nil { t.Errorf("failed to get verifier for %q(offset=%d)", h.Name, n) } found[chunkID(h.Name, n, ce.ChunkSize)] = true // Check the file contents if _, err := io.CopyN(v, tr, ce.ChunkSize); err != nil { t.Fatalf("failed to get chunk of %q (offset=%d,size=%d)", h.Name, n, ce.ChunkSize) } if !v.Verified() { t.Errorf("Invalid contents in converted stargz %q (should be succeeded)", h.Name) return } n += ce.ChunkSize } } for id, ok := range found { if !ok { t.Errorf("required chunk %q not found in the converted stargz: %v", id, found) } } } // checkVerifyInvalidTOCEntryFail checks if misconfigured TOC JSON can be // detected during the verification and the verification returns an error. func checkVerifyInvalidTOCEntryFail(filename string) check { return func(t *testing.T, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController) { funcs := map[string]rewriteFunc{ "lost digest in a entry": func(t *testing.T, toc *JTOC, sgz *io.SectionReader) { var found bool for _, e := range toc.Entries { if cleanEntryName(e.Name) == filename { if e.Type != "reg" && e.Type != "chunk" { t.Fatalf("entry %q to break must be regfile or chunk", filename) } if e.ChunkDigest == "" { t.Fatalf("entry %q is already invalid", filename) } e.ChunkDigest = "" found = true } } if !found { t.Fatalf("rewrite target not found") } }, "duplicated entry offset": func(t *testing.T, toc *JTOC, sgz *io.SectionReader) { var ( sampleEntry *TOCEntry targetEntry *TOCEntry ) for _, e := range toc.Entries { if e.Type == "reg" || e.Type == "chunk" { if cleanEntryName(e.Name) == filename { targetEntry = e } else { sampleEntry = e } } } if sampleEntry == nil { t.Fatalf("TOC must contain at least one regfile or chunk entry other than the rewrite target") } if targetEntry == nil { t.Fatalf("rewrite target not found") } targetEntry.Offset = sampleEntry.Offset }, } for name, rFunc := range funcs { t.Run(name, func(t *testing.T) { newSgz, newTocDigest := rewriteTOCJSON(t, io.NewSectionReader(bytes.NewReader(sgzData), 0, int64(len(sgzData))), rFunc, controller) buf := new(bytes.Buffer) if _, err := io.Copy(buf, newSgz); err != nil { t.Fatalf("failed to get converted stargz") } isgz := buf.Bytes() sgz, err := Open( io.NewSectionReader(bytes.NewReader(isgz), 0, int64(len(isgz))), WithDecompressors(controller), ) if err != nil { t.Fatalf("failed to parse converted stargz: %v", err) return } _, err = sgz.VerifyTOC(newTocDigest) if err == nil { t.Errorf("must fail for invalid TOC") return } }) } } } // checkVerifyInvalidStargzFail checks if the verification detects that the // given stargz file doesn't match to the expected digest and returns error. func checkVerifyInvalidStargzFail(invalid *io.SectionReader) check { return func(t *testing.T, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController) { rc, err := Build(invalid, WithChunkSize(chunkSize), WithCompression(controller)) if err != nil { t.Fatalf("failed to convert stargz: %v", err) } defer rc.Close() buf := new(bytes.Buffer) if _, err := io.Copy(buf, rc); err != nil { t.Fatalf("failed to copy built stargz blob: %v", err) } mStargz := buf.Bytes() sgz, err := Open( io.NewSectionReader(bytes.NewReader(mStargz), 0, int64(len(mStargz))), WithDecompressors(controller), ) if err != nil { t.Fatalf("failed to parse converted stargz: %v", err) return } _, err = sgz.VerifyTOC(tocDigest) if err == nil { t.Errorf("must fail for invalid TOC") return } } } // checkVerifyBrokenContentFail checks if the verifier detects broken contents // that doesn't match to the expected digest and returns error. func checkVerifyBrokenContentFail(filename string) check { return func(t *testing.T, sgzData []byte, tocDigest digest.Digest, dgstMap map[string]digest.Digest, controller TestingController) { // Parse stargz file sgz, err := Open( io.NewSectionReader(bytes.NewReader(sgzData), 0, int64(len(sgzData))), WithDecompressors(controller), ) if err != nil { t.Fatalf("failed to parse converted stargz: %v", err) return } ev, err := sgz.VerifyTOC(tocDigest) if err != nil { t.Fatalf("failed to verify stargz: %v", err) return } // Open the target file sr, err := sgz.OpenFile(filename) if err != nil { t.Fatalf("failed to open file %q", filename) } ce, ok := sgz.ChunkEntryForOffset(filename, 0) if !ok { t.Fatalf("lost chunk %q(offset=%d) in the converted TOC", filename, 0) return } if ce.ChunkSize == 0 { t.Fatalf("file mustn't be empty") return } data := make([]byte, ce.ChunkSize) if _, err := sr.ReadAt(data, ce.ChunkOffset); err != nil { t.Errorf("failed to get data of a chunk of %q(offset=%q)", filename, ce.ChunkOffset) } // Check the broken chunk (must fail) v, err := ev.Verifier(ce) if err != nil { t.Fatalf("failed to get verifier for %q", filename) } broken := append([]byte{^data[0]}, data[1:]...) if _, err := io.CopyN(v, bytes.NewReader(broken), ce.ChunkSize); err != nil { t.Fatalf("failed to get chunk of %q (offset=%d,size=%d)", filename, ce.ChunkOffset, ce.ChunkSize) } if v.Verified() { t.Errorf("verification must fail for broken file chunk %q(org:%q,broken:%q)", filename, data, broken) } } } func chunkID(name string, offset, size int64) string { return fmt.Sprintf("%s-%d-%d", cleanEntryName(name), offset, size) } type rewriteFunc func(t *testing.T, toc *JTOC, sgz *io.SectionReader) func rewriteTOCJSON(t *testing.T, sgz *io.SectionReader, rewrite rewriteFunc, controller TestingController) (newSgz io.Reader, tocDigest digest.Digest) { decodedJTOC, jtocOffset, err := parseStargz(sgz, controller) if err != nil { t.Fatalf("failed to extract TOC JSON: %v", err) } rewrite(t, decodedJTOC, sgz) tocFooter, tocDigest, err := tocAndFooter(controller, decodedJTOC, jtocOffset) if err != nil { t.Fatalf("failed to create toc and footer: %v", err) } // Reconstruct stargz file with the modified TOC JSON if _, err := sgz.Seek(0, io.SeekStart); err != nil { t.Fatalf("failed to reset the seek position of stargz: %v", err) } return io.MultiReader( io.LimitReader(sgz, jtocOffset), // Original stargz (before TOC JSON) tocFooter, // Rewritten TOC and footer ), tocDigest } func listDigests(sgz *io.SectionReader, controller TestingController) (map[int64]digest.Digest, error) { decodedJTOC, _, err := parseStargz(sgz, controller) if err != nil { return nil, err } digestMap := make(map[int64]digest.Digest) for _, e := range decodedJTOC.Entries { if e.Type == "reg" || e.Type == "chunk" { if e.Type == "reg" && e.Size == 0 { continue // ignores empty file } if e.ChunkDigest == "" { return nil, fmt.Errorf("ChunkDigest of %q(off=%d) not found in TOC JSON", e.Name, e.Offset) } d, err := digest.Parse(e.ChunkDigest) if err != nil { return nil, err } digestMap[e.Offset] = d } } return digestMap, nil } func parseStargz(sgz *io.SectionReader, controller TestingController) (decodedJTOC *JTOC, jtocOffset int64, err error) { fSize := controller.FooterSize() footer := make([]byte, fSize) if _, err := sgz.ReadAt(footer, sgz.Size()-fSize); err != nil { return nil, 0, fmt.Errorf("error reading footer: %w", err) } _, tocOffset, _, err := controller.ParseFooter(footer[positive(int64(len(footer))-fSize):]) if err != nil { return nil, 0, fmt.Errorf("failed to parse footer: %w", err) } // Decode the TOC JSON tocReader := io.NewSectionReader(sgz, tocOffset, sgz.Size()-tocOffset-fSize) decodedJTOC, _, err = controller.ParseTOC(tocReader) if err != nil { return nil, 0, fmt.Errorf("failed to parse TOC: %w", err) } return decodedJTOC, tocOffset, nil } func testWriteAndOpen(t *testing.T, controllers ...TestingController) { const content = "Some contents" invalidUtf8 := "\xff\xfe\xfd" xAttrFile := xAttr{"foo": "bar", "invalid-utf8": invalidUtf8} sampleOwner := owner{uid: 50, gid: 100} tests := []struct { name string chunkSize int in []tarEntry want []stargzCheck wantNumGz int // expected number of streams wantNumGzLossLess int // expected number of streams (> 0) in lossless mode if it's different from wantNumGz wantFailOnLossLess bool }{ { name: "empty", in: tarOf(), wantNumGz: 2, // empty tar + TOC + footer wantNumGzLossLess: 3, // empty tar + TOC + footer want: checks( numTOCEntries(0), ), }, { name: "1dir_1empty_file", in: tarOf( dir("foo/"), file("foo/bar.txt", ""), ), wantNumGz: 3, // dir, TOC, footer want: checks( numTOCEntries(2), hasDir("foo/"), hasFileLen("foo/bar.txt", 0), entryHasChildren("foo", "bar.txt"), hasFileDigest("foo/bar.txt", digestFor("")), ), }, { name: "1dir_1file", in: tarOf( dir("foo/"), file("foo/bar.txt", content, xAttrFile), ), wantNumGz: 4, // var dir, foo.txt alone, TOC, footer want: checks( numTOCEntries(2), hasDir("foo/"), hasFileLen("foo/bar.txt", len(content)), hasFileDigest("foo/bar.txt", digestFor(content)), hasFileContentsRange("foo/bar.txt", 0, content), hasFileContentsRange("foo/bar.txt", 1, content[1:]), entryHasChildren("", "foo"), entryHasChildren("foo", "bar.txt"), hasFileXattrs("foo/bar.txt", "foo", "bar"), hasFileXattrs("foo/bar.txt", "invalid-utf8", invalidUtf8), ), }, { name: "2meta_2file", in: tarOf( dir("bar/", sampleOwner), dir("foo/", sampleOwner), file("foo/bar.txt", content, sampleOwner), ), wantNumGz: 4, // both dirs, foo.txt alone, TOC, footer want: checks( numTOCEntries(3), hasDir("bar/"), hasDir("foo/"), hasFileLen("foo/bar.txt", len(content)), entryHasChildren("", "bar", "foo"), entryHasChildren("foo", "bar.txt"), hasChunkEntries("foo/bar.txt", 1), hasEntryOwner("bar/", sampleOwner), hasEntryOwner("foo/", sampleOwner), hasEntryOwner("foo/bar.txt", sampleOwner), ), }, { name: "3dir", in: tarOf( dir("bar/"), dir("foo/"), dir("foo/bar/"), ), wantNumGz: 3, // 3 dirs, TOC, footer want: checks( hasDirLinkCount("bar/", 2), hasDirLinkCount("foo/", 3), hasDirLinkCount("foo/bar/", 2), ), }, { name: "symlink", in: tarOf( dir("foo/"), symlink("foo/bar", "../../x"), ), wantNumGz: 3, // metas + TOC + footer want: checks( numTOCEntries(2), hasSymlink("foo/bar", "../../x"), entryHasChildren("", "foo"), entryHasChildren("foo", "bar"), ), }, { name: "chunked_file", chunkSize: 4, in: tarOf( dir("foo/"), file("foo/big.txt", "This "+"is s"+"uch "+"a bi"+"g fi"+"le"), ), wantNumGz: 9, want: checks( numTOCEntries(7), // 1 for foo dir, 6 for the foo/big.txt file hasDir("foo/"), hasFileLen("foo/big.txt", len("This is such a big file")), hasFileDigest("foo/big.txt", digestFor("This is such a big file")), hasFileContentsRange("foo/big.txt", 0, "This is such a big file"), hasFileContentsRange("foo/big.txt", 1, "his is such a big file"), hasFileContentsRange("foo/big.txt", 2, "is is such a big file"), hasFileContentsRange("foo/big.txt", 3, "s is such a big file"), hasFileContentsRange("foo/big.txt", 4, " is such a big file"), hasFileContentsRange("foo/big.txt", 5, "is such a big file"), hasFileContentsRange("foo/big.txt", 6, "s such a big file"), hasFileContentsRange("foo/big.txt", 7, " such a big file"), hasFileContentsRange("foo/big.txt", 8, "such a big file"), hasFileContentsRange("foo/big.txt", 9, "uch a big file"), hasFileContentsRange("foo/big.txt", 10, "ch a big file"), hasFileContentsRange("foo/big.txt", 11, "h a big file"), hasFileContentsRange("foo/big.txt", 12, " a big file"), hasFileContentsRange("foo/big.txt", len("This is such a big file")-1, ""), hasChunkEntries("foo/big.txt", 6), ), }, { name: "recursive", in: tarOf( dir("/", sampleOwner), dir("bar/", sampleOwner), dir("foo/", sampleOwner), file("foo/bar.txt", content, sampleOwner), ), wantNumGz: 4, // dirs, bar.txt alone, TOC, footer want: checks( maxDepth(2), // 0: root directory, 1: "foo/", 2: "bar.txt" ), }, { name: "block_char_fifo", in: tarOf( tarEntryFunc(func(w *tar.Writer, prefix string, format tar.Format) error { return w.WriteHeader(&tar.Header{ Name: prefix + "b", Typeflag: tar.TypeBlock, Devmajor: 123, Devminor: 456, Format: format, }) }), tarEntryFunc(func(w *tar.Writer, prefix string, format tar.Format) error { return w.WriteHeader(&tar.Header{ Name: prefix + "c", Typeflag: tar.TypeChar, Devmajor: 111, Devminor: 222, Format: format, }) }), tarEntryFunc(func(w *tar.Writer, prefix string, format tar.Format) error { return w.WriteHeader(&tar.Header{ Name: prefix + "f", Typeflag: tar.TypeFifo, Format: format, }) }), ), wantNumGz: 3, want: checks( lookupMatch("b", &TOCEntry{Name: "b", Type: "block", DevMajor: 123, DevMinor: 456, NumLink: 1}), lookupMatch("c", &TOCEntry{Name: "c", Type: "char", DevMajor: 111, DevMinor: 222, NumLink: 1}), lookupMatch("f", &TOCEntry{Name: "f", Type: "fifo", NumLink: 1}), ), }, { name: "modes", in: tarOf( dir("foo1/", 0755|os.ModeDir|os.ModeSetgid), file("foo1/bar1", content, 0700|os.ModeSetuid), file("foo1/bar2", content, 0755|os.ModeSetgid), dir("foo2/", 0755|os.ModeDir|os.ModeSticky), file("foo2/bar3", content, 0755|os.ModeSticky), dir("foo3/", 0755|os.ModeDir), file("foo3/bar4", content, os.FileMode(0700)), file("foo3/bar5", content, os.FileMode(0755)), ), wantNumGz: 8, // dir, bar1 alone, bar2 alone + dir, bar3 alone + dir, bar4 alone, bar5 alone, TOC, footer want: checks( hasMode("foo1/", 0755|os.ModeDir|os.ModeSetgid), hasMode("foo1/bar1", 0700|os.ModeSetuid), hasMode("foo1/bar2", 0755|os.ModeSetgid), hasMode("foo2/", 0755|os.ModeDir|os.ModeSticky), hasMode("foo2/bar3", 0755|os.ModeSticky), hasMode("foo3/", 0755|os.ModeDir), hasMode("foo3/bar4", os.FileMode(0700)), hasMode("foo3/bar5", os.FileMode(0755)), ), }, { name: "lossy", in: tarOf( dir("bar/", sampleOwner), dir("foo/", sampleOwner), file("foo/bar.txt", content, sampleOwner), file(TOCTarName, "dummy"), // ignored by the writer. (lossless write returns error) ), wantNumGz: 4, // both dirs, foo.txt alone, TOC, footer want: checks( numTOCEntries(3), hasDir("bar/"), hasDir("foo/"), hasFileLen("foo/bar.txt", len(content)), entryHasChildren("", "bar", "foo"), entryHasChildren("foo", "bar.txt"), hasChunkEntries("foo/bar.txt", 1), hasEntryOwner("bar/", sampleOwner), hasEntryOwner("foo/", sampleOwner), hasEntryOwner("foo/bar.txt", sampleOwner), ), wantFailOnLossLess: true, }, { name: "hardlink should be replaced to the destination entry", in: tarOf( dir("foo/"), file("foo/foo1", "test"), link("foolink", "foo/foo1"), ), wantNumGz: 4, // dir, foo1 + link, TOC, footer want: checks( mustSameEntry("foo/foo1", "foolink"), ), }, } for _, tt := range tests { for _, cl := range controllers { cl := cl for _, prefix := range allowedPrefix { prefix := prefix for _, srcTarFormat := range []tar.Format{tar.FormatUSTAR, tar.FormatPAX, tar.FormatGNU} { srcTarFormat := srcTarFormat for _, lossless := range []bool{true, false} { t.Run(tt.name+"-"+fmt.Sprintf("compression=%v,prefix=%q,lossless=%v,format=%s", cl, prefix, lossless, srcTarFormat), func(t *testing.T) { var tr io.Reader = buildTar(t, tt.in, prefix, srcTarFormat) origTarDgstr := digest.Canonical.Digester() tr = io.TeeReader(tr, origTarDgstr.Hash()) var stargzBuf bytes.Buffer w := NewWriterWithCompressor(&stargzBuf, cl) w.ChunkSize = tt.chunkSize if lossless { err := w.AppendTarLossLess(tr) if tt.wantFailOnLossLess { if err != nil { return // expected to fail } t.Fatalf("Append wanted to fail on lossless") } if err != nil { t.Fatalf("Append(lossless): %v", err) } } else { if err := w.AppendTar(tr); err != nil { t.Fatalf("Append: %v", err) } } if _, err := w.Close(); err != nil { t.Fatalf("Writer.Close: %v", err) } b := stargzBuf.Bytes() if lossless { // Check if the result blob reserves original tar metadata rc, err := Unpack(io.NewSectionReader(bytes.NewReader(b), 0, int64(len(b))), cl) if err != nil { t.Errorf("failed to decompress blob: %v", err) return } defer rc.Close() resultDgstr := digest.Canonical.Digester() if _, err := io.Copy(resultDgstr.Hash(), rc); err != nil { t.Errorf("failed to read result decompressed blob: %v", err) return } if resultDgstr.Digest() != origTarDgstr.Digest() { t.Errorf("lossy compression occurred: digest=%v; want %v", resultDgstr.Digest(), origTarDgstr.Digest()) return } } diffID := w.DiffID() wantDiffID := cl.DiffIDOf(t, b) if diffID != wantDiffID { t.Errorf("DiffID = %q; want %q", diffID, wantDiffID) } got := cl.CountStreams(t, b) wantNumGz := tt.wantNumGz if lossless && tt.wantNumGzLossLess > 0 { wantNumGz = tt.wantNumGzLossLess } if got != wantNumGz { t.Errorf("number of streams = %d; want %d", got, wantNumGz) } telemetry, checkCalled := newCalledTelemetry() r, err := Open( io.NewSectionReader(bytes.NewReader(b), 0, int64(len(b))), WithDecompressors(cl), WithTelemetry(telemetry), ) if err != nil { t.Fatalf("stargz.Open: %v", err) } if err := checkCalled(); err != nil { t.Errorf("telemetry failure: %v", err) } for _, want := range tt.want { want.check(t, r) } }) } } } } } } func newCalledTelemetry() (telemetry *Telemetry, check func() error) { var getFooterLatencyCalled bool var getTocLatencyCalled bool var deserializeTocLatencyCalled bool return &Telemetry{ func(time.Time) { getFooterLatencyCalled = true }, func(time.Time) { getTocLatencyCalled = true }, func(time.Time) { deserializeTocLatencyCalled = true }, }, func() error { var allErr []error if !getFooterLatencyCalled { allErr = append(allErr, fmt.Errorf("metrics GetFooterLatency isn't called")) } if !getTocLatencyCalled { allErr = append(allErr, fmt.Errorf("metrics GetTocLatency isn't called")) } if !deserializeTocLatencyCalled { allErr = append(allErr, fmt.Errorf("metrics DeserializeTocLatency isn't called")) } return errorutil.Aggregate(allErr) } } func digestFor(content string) string { sum := sha256.Sum256([]byte(content)) return fmt.Sprintf("sha256:%x", sum) } type numTOCEntries int func (n numTOCEntries) check(t *testing.T, r *Reader) { if r.toc == nil { t.Fatal("nil TOC") } if got, want := len(r.toc.Entries), int(n); got != want { t.Errorf("got %d TOC entries; want %d", got, want) } t.Logf("got TOC entries:") for i, ent := range r.toc.Entries { entj, _ := json.Marshal(ent) t.Logf(" [%d]: %s\n", i, entj) } if t.Failed() { t.FailNow() } } func checks(s ...stargzCheck) []stargzCheck { return s } type stargzCheck interface { check(t *testing.T, r *Reader) } type stargzCheckFn func(*testing.T, *Reader) func (f stargzCheckFn) check(t *testing.T, r *Reader) { f(t, r) } func maxDepth(max int) stargzCheck { return stargzCheckFn(func(t *testing.T, r *Reader) { e, ok := r.Lookup("") if !ok { t.Fatal("root directory not found") } d, err := getMaxDepth(t, e, 0, 10*max) if err != nil { t.Errorf("failed to get max depth (wanted %d): %v", max, err) return } if d != max { t.Errorf("invalid depth %d; want %d", d, max) return } }) } func getMaxDepth(t *testing.T, e *TOCEntry, current, limit int) (max int, rErr error) { if current > limit { return -1, fmt.Errorf("walkMaxDepth: exceeds limit: current:%d > limit:%d", current, limit) } max = current e.ForeachChild(func(baseName string, ent *TOCEntry) bool { t.Logf("%q(basename:%q) is child of %q\n", ent.Name, baseName, e.Name) d, err := getMaxDepth(t, ent, current+1, limit) if err != nil { rErr = err return false } if d > max { max = d } return true }) return } func hasFileLen(file string, wantLen int) stargzCheck { return stargzCheckFn(func(t *testing.T, r *Reader) { for _, ent := range r.toc.Entries { if ent.Name == file { if ent.Type != "reg" { t.Errorf("file type of %q is %q; want \"reg\"", file, ent.Type) } else if ent.Size != int64(wantLen) { t.Errorf("file size of %q = %d; want %d", file, ent.Size, wantLen) } return } } t.Errorf("file %q not found", file) }) } func hasFileXattrs(file, name, value string) stargzCheck { return stargzCheckFn(func(t *testing.T, r *Reader) { for _, ent := range r.toc.Entries { if ent.Name == file { if ent.Type != "reg" { t.Errorf("file type of %q is %q; want \"reg\"", file, ent.Type) } if ent.Xattrs == nil { t.Errorf("file %q has no xattrs", file) return } valueFound, found := ent.Xattrs[name] if !found { t.Errorf("file %q has no xattr %q", file, name) return } if string(valueFound) != value { t.Errorf("file %q has xattr %q with value %q instead of %q", file, name, valueFound, value) } return } } t.Errorf("file %q not found", file) }) } func hasFileDigest(file string, digest string) stargzCheck { return stargzCheckFn(func(t *testing.T, r *Reader) { ent, ok := r.Lookup(file) if !ok { t.Fatalf("didn't find TOCEntry for file %q", file) } if ent.Digest != digest { t.Fatalf("Digest(%q) = %q, want %q", file, ent.Digest, digest) } }) } func hasFileContentsRange(file string, offset int, want string) stargzCheck { return stargzCheckFn(func(t *testing.T, r *Reader) { f, err := r.OpenFile(file) if err != nil { t.Fatal(err) } got := make([]byte, len(want)) n, err := f.ReadAt(got, int64(offset)) if err != nil { t.Fatalf("ReadAt(len %d, offset %d) = %v, %v", len(got), offset, n, err) } if string(got) != want { t.Fatalf("ReadAt(len %d, offset %d) = %q, want %q", len(got), offset, got, want) } }) } func hasChunkEntries(file string, wantChunks int) stargzCheck { return stargzCheckFn(func(t *testing.T, r *Reader) { ent, ok := r.Lookup(file) if !ok { t.Fatalf("no file for %q", file) } if ent.Type != "reg" { t.Fatalf("file %q has unexpected type %q; want reg", file, ent.Type) } chunks := r.getChunks(ent) if len(chunks) != wantChunks { t.Errorf("len(r.getChunks(%q)) = %d; want %d", file, len(chunks), wantChunks) return } f := chunks[0] var gotChunks []*TOCEntry var last *TOCEntry for off := int64(0); off < f.Size; off++ { e, ok := r.ChunkEntryForOffset(file, off) if !ok { t.Errorf("no ChunkEntryForOffset at %d", off) return } if last != e { gotChunks = append(gotChunks, e) last = e } } if !reflect.DeepEqual(chunks, gotChunks) { t.Errorf("gotChunks=%d, want=%d; contents mismatch", len(gotChunks), wantChunks) } // And verify the NextOffset for i := 0; i < len(gotChunks)-1; i++ { ci := gotChunks[i] cnext := gotChunks[i+1] if ci.NextOffset() != cnext.Offset { t.Errorf("chunk %d NextOffset %d != next chunk's Offset of %d", i, ci.NextOffset(), cnext.Offset) } } }) } func entryHasChildren(dir string, want ...string) stargzCheck { return stargzCheckFn(func(t *testing.T, r *Reader) { want := append([]string(nil), want...) var got []string ent, ok := r.Lookup(dir) if !ok { t.Fatalf("didn't find TOCEntry for dir node %q", dir) } for baseName := range ent.children { got = append(got, baseName) } sort.Strings(got) sort.Strings(want) if !reflect.DeepEqual(got, want) { t.Errorf("children of %q = %q; want %q", dir, got, want) } }) } func hasDir(file string) stargzCheck { return stargzCheckFn(func(t *testing.T, r *Reader) { for _, ent := range r.toc.Entries { if ent.Name == cleanEntryName(file) { if ent.Type != "dir" { t.Errorf("file type of %q is %q; want \"dir\"", file, ent.Type) } return } } t.Errorf("directory %q not found", file) }) } func hasDirLinkCount(file string, count int) stargzCheck { return stargzCheckFn(func(t *testing.T, r *Reader) { for _, ent := range r.toc.Entries { if ent.Name == cleanEntryName(file) { if ent.Type != "dir" { t.Errorf("file type of %q is %q; want \"dir\"", file, ent.Type) return } if ent.NumLink != count { t.Errorf("link count of %q = %d; want %d", file, ent.NumLink, count) } return } } t.Errorf("directory %q not found", file) }) } func hasMode(file string, mode os.FileMode) stargzCheck { return stargzCheckFn(func(t *testing.T, r *Reader) { for _, ent := range r.toc.Entries { if ent.Name == cleanEntryName(file) { if ent.Stat().Mode() != mode { t.Errorf("invalid mode: got %v; want %v", ent.Stat().Mode(), mode) return } return } } t.Errorf("file %q not found", file) }) } func hasSymlink(file, target string) stargzCheck { return stargzCheckFn(func(t *testing.T, r *Reader) { for _, ent := range r.toc.Entries { if ent.Name == file { if ent.Type != "symlink" { t.Errorf("file type of %q is %q; want \"symlink\"", file, ent.Type) } else if ent.LinkName != target { t.Errorf("link target of symlink %q is %q; want %q", file, ent.LinkName, target) } return } } t.Errorf("symlink %q not found", file) }) } func lookupMatch(name string, want *TOCEntry) stargzCheck { return stargzCheckFn(func(t *testing.T, r *Reader) { e, ok := r.Lookup(name) if !ok { t.Fatalf("failed to Lookup entry %q", name) } if !reflect.DeepEqual(e, want) { t.Errorf("entry %q mismatch.\n got: %+v\nwant: %+v\n", name, e, want) } }) } func hasEntryOwner(entry string, owner owner) stargzCheck { return stargzCheckFn(func(t *testing.T, r *Reader) { ent, ok := r.Lookup(strings.TrimSuffix(entry, "/")) if !ok { t.Errorf("entry %q not found", entry) return } if ent.UID != owner.uid || ent.GID != owner.gid { t.Errorf("entry %q has invalid owner (uid:%d, gid:%d) instead of (uid:%d, gid:%d)", entry, ent.UID, ent.GID, owner.uid, owner.gid) return } }) } func mustSameEntry(files ...string) stargzCheck { return stargzCheckFn(func(t *testing.T, r *Reader) { var first *TOCEntry for _, f := range files { if first == nil { var ok bool first, ok = r.Lookup(f) if !ok { t.Errorf("unknown first file on Lookup: %q", f) return } } // Test Lookup e, ok := r.Lookup(f) if !ok { t.Errorf("unknown file on Lookup: %q", f) return } if e != first { t.Errorf("Lookup: %+v(%p) != %+v(%p)", e, e, first, first) return } // Test LookupChild pe, ok := r.Lookup(filepath.Dir(filepath.Clean(f))) if !ok { t.Errorf("failed to get parent of %q", f) return } e, ok = pe.LookupChild(filepath.Base(filepath.Clean(f))) if !ok { t.Errorf("failed to get %q as the child of %+v", f, pe) return } if e != first { t.Errorf("LookupChild: %+v(%p) != %+v(%p)", e, e, first, first) return } // Test ForeachChild pe.ForeachChild(func(baseName string, e *TOCEntry) bool { if baseName == filepath.Base(filepath.Clean(f)) { if e != first { t.Errorf("ForeachChild: %+v(%p) != %+v(%p)", e, e, first, first) return false } } return true }) } }) } func tarOf(s ...tarEntry) []tarEntry { return s } type tarEntry interface { appendTar(tw *tar.Writer, prefix string, format tar.Format) error } type tarEntryFunc func(*tar.Writer, string, tar.Format) error func (f tarEntryFunc) appendTar(tw *tar.Writer, prefix string, format tar.Format) error { return f(tw, prefix, format) } func buildTar(t *testing.T, ents []tarEntry, prefix string, opts ...interface{}) *io.SectionReader { format := tar.FormatUnknown for _, opt := range opts { switch v := opt.(type) { case tar.Format: format = v default: panic(fmt.Errorf("unsupported opt for buildTar: %v", opt)) } } buf := new(bytes.Buffer) tw := tar.NewWriter(buf) for _, ent := range ents { if err := ent.appendTar(tw, prefix, format); err != nil { t.Fatalf("building input tar: %v", err) } } if err := tw.Close(); err != nil { t.Errorf("closing write of input tar: %v", err) } data := append(buf.Bytes(), make([]byte, 100)...) // append empty bytes at the tail to see lossless works return io.NewSectionReader(bytes.NewReader(data), 0, int64(len(data))) } func dir(name string, opts ...interface{}) tarEntry { return tarEntryFunc(func(tw *tar.Writer, prefix string, format tar.Format) error { var o owner mode := os.FileMode(0755) for _, opt := range opts { switch v := opt.(type) { case owner: o = v case os.FileMode: mode = v default: return errors.New("unsupported opt") } } if !strings.HasSuffix(name, "/") { panic(fmt.Sprintf("missing trailing slash in dir %q ", name)) } tm, err := fileModeToTarMode(mode) if err != nil { return err } return tw.WriteHeader(&tar.Header{ Typeflag: tar.TypeDir, Name: prefix + name, Mode: tm, Uid: o.uid, Gid: o.gid, Format: format, }) }) } // xAttr are extended attributes to set on test files created with the file func. type xAttr map[string]string // owner is owner ot set on test files and directories with the file and dir functions. type owner struct { uid int gid int } func file(name, contents string, opts ...interface{}) tarEntry { return tarEntryFunc(func(tw *tar.Writer, prefix string, format tar.Format) error { var xattrs xAttr var o owner mode := os.FileMode(0644) for _, opt := range opts { switch v := opt.(type) { case xAttr: xattrs = v case owner: o = v case os.FileMode: mode = v default: return errors.New("unsupported opt") } } if strings.HasSuffix(name, "/") { return fmt.Errorf("bogus trailing slash in file %q", name) } tm, err := fileModeToTarMode(mode) if err != nil { return err } if len(xattrs) > 0 { format = tar.FormatPAX // only PAX supports xattrs } if err := tw.WriteHeader(&tar.Header{ Typeflag: tar.TypeReg, Name: prefix + name, Mode: tm, Xattrs: xattrs, Size: int64(len(contents)), Uid: o.uid, Gid: o.gid, Format: format, }); err != nil { return err } _, err = io.WriteString(tw, contents) return err }) } func symlink(name, target string) tarEntry { return tarEntryFunc(func(tw *tar.Writer, prefix string, format tar.Format) error { return tw.WriteHeader(&tar.Header{ Typeflag: tar.TypeSymlink, Name: prefix + name, Linkname: target, Mode: 0644, Format: format, }) }) } func link(name string, linkname string) tarEntry { now := time.Now() return tarEntryFunc(func(w *tar.Writer, prefix string, format tar.Format) error { return w.WriteHeader(&tar.Header{ Typeflag: tar.TypeLink, Name: prefix + name, Linkname: linkname, ModTime: now, Format: format, }) }) } func chardev(name string, major, minor int64) tarEntry { now := time.Now() return tarEntryFunc(func(w *tar.Writer, prefix string, format tar.Format) error { return w.WriteHeader(&tar.Header{ Typeflag: tar.TypeChar, Name: prefix + name, Devmajor: major, Devminor: minor, ModTime: now, Format: format, }) }) } func blockdev(name string, major, minor int64) tarEntry { now := time.Now() return tarEntryFunc(func(w *tar.Writer, prefix string, format tar.Format) error { return w.WriteHeader(&tar.Header{ Typeflag: tar.TypeBlock, Name: prefix + name, Devmajor: major, Devminor: minor, ModTime: now, Format: format, }) }) } func fifo(name string) tarEntry { now := time.Now() return tarEntryFunc(func(w *tar.Writer, prefix string, format tar.Format) error { return w.WriteHeader(&tar.Header{ Typeflag: tar.TypeFifo, Name: prefix + name, ModTime: now, Format: format, }) }) } func prefetchLandmark() tarEntry { return tarEntryFunc(func(w *tar.Writer, prefix string, format tar.Format) error { if err := w.WriteHeader(&tar.Header{ Name: PrefetchLandmark, Typeflag: tar.TypeReg, Size: int64(len([]byte{landmarkContents})), Format: format, }); err != nil { return err } contents := []byte{landmarkContents} if _, err := io.CopyN(w, bytes.NewReader(contents), int64(len(contents))); err != nil { return err } return nil }) } func noPrefetchLandmark() tarEntry { return tarEntryFunc(func(w *tar.Writer, prefix string, format tar.Format) error { if err := w.WriteHeader(&tar.Header{ Name: NoPrefetchLandmark, Typeflag: tar.TypeReg, Size: int64(len([]byte{landmarkContents})), Format: format, }); err != nil { return err } contents := []byte{landmarkContents} if _, err := io.CopyN(w, bytes.NewReader(contents), int64(len(contents))); err != nil { return err } return nil }) } func regDigest(t *testing.T, name string, contentStr string, digestMap map[string]digest.Digest) tarEntry { if digestMap == nil { t.Fatalf("digest map mustn't be nil") } content := []byte(contentStr) var n int64 for n < int64(len(content)) { size := int64(chunkSize) remain := int64(len(content)) - n if remain < size { size = remain } dgstr := digest.Canonical.Digester() if _, err := io.CopyN(dgstr.Hash(), bytes.NewReader(content[n:n+size]), size); err != nil { t.Fatalf("failed to calculate digest of %q (name=%q,offset=%d,size=%d)", string(content[n:n+size]), name, n, size) } digestMap[chunkID(name, n, size)] = dgstr.Digest() n += size } return tarEntryFunc(func(w *tar.Writer, prefix string, format tar.Format) error { if err := w.WriteHeader(&tar.Header{ Typeflag: tar.TypeReg, Name: prefix + name, Size: int64(len(content)), Format: format, }); err != nil { return err } if _, err := io.CopyN(w, bytes.NewReader(content), int64(len(content))); err != nil { return err } return nil }) } func fileModeToTarMode(mode os.FileMode) (int64, error) { h, err := tar.FileInfoHeader(fileInfoOnlyMode(mode), "") if err != nil { return 0, err } return h.Mode, nil } // fileInfoOnlyMode is os.FileMode that populates only file mode. type fileInfoOnlyMode os.FileMode func (f fileInfoOnlyMode) Name() string { return "" } func (f fileInfoOnlyMode) Size() int64 { return 0 } func (f fileInfoOnlyMode) Mode() os.FileMode { return os.FileMode(f) } func (f fileInfoOnlyMode) ModTime() time.Time { return time.Now() } func (f fileInfoOnlyMode) IsDir() bool { return os.FileMode(f).IsDir() } func (f fileInfoOnlyMode) Sys() interface{} { return nil } stargz-snapshotter-0.12.0/estargz/types.go000066400000000000000000000266251426301527400206450ustar00rootroot00000000000000/* 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. */ /* Copyright 2019 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. */ package estargz import ( "archive/tar" "hash" "io" "os" "path" "time" digest "github.com/opencontainers/go-digest" ) const ( // TOCTarName is the name of the JSON file in the tar archive in the // table of contents gzip stream. TOCTarName = "stargz.index.json" // FooterSize is the number of bytes in the footer // // The footer is an empty gzip stream with no compression and an Extra // header of the form "%016xSTARGZ", where the 64 bit hex-encoded // number is the offset to the gzip stream of JSON TOC. // // 51 comes from: // // 10 bytes gzip header // 2 bytes XLEN (length of Extra field) = 26 (4 bytes header + 16 hex digits + len("STARGZ")) // 2 bytes Extra: SI1 = 'S', SI2 = 'G' // 2 bytes Extra: LEN = 22 (16 hex digits + len("STARGZ")) // 22 bytes Extra: subfield = fmt.Sprintf("%016xSTARGZ", offsetOfTOC) // 5 bytes flate header // 8 bytes gzip footer // (End of the eStargz blob) // // NOTE: For Extra fields, subfield IDs SI1='S' SI2='G' is used for eStargz. FooterSize = 51 // legacyFooterSize is the number of bytes in the legacy stargz footer. // // 47 comes from: // // 10 byte gzip header + // 2 byte (LE16) length of extra, encoding 22 (16 hex digits + len("STARGZ")) == "\x16\x00" + // 22 bytes of extra (fmt.Sprintf("%016xSTARGZ", tocGzipOffset)) // 5 byte flate header // 8 byte gzip footer (two little endian uint32s: digest, size) legacyFooterSize = 47 // TOCJSONDigestAnnotation is an annotation for an image layer. This stores the // digest of the TOC JSON. // This annotation is valid only when it is specified in `.[]layers.annotations` // of an image manifest. TOCJSONDigestAnnotation = "containerd.io/snapshot/stargz/toc.digest" // StoreUncompressedSizeAnnotation is an additional annotation key for eStargz to enable lazy // pulling on containers/storage. Stargz Store is required to expose the layer's uncompressed size // to the runtime but current OCI image doesn't ship this information by default. So we store this // to the special annotation. StoreUncompressedSizeAnnotation = "io.containers.estargz.uncompressed-size" // PrefetchLandmark is a file entry which indicates the end position of // prefetch in the stargz file. PrefetchLandmark = ".prefetch.landmark" // NoPrefetchLandmark is a file entry which indicates that no prefetch should // occur in the stargz file. NoPrefetchLandmark = ".no.prefetch.landmark" landmarkContents = 0xf ) // JTOC is the JSON-serialized table of contents index of the files in the stargz file. type JTOC struct { Version int `json:"version"` Entries []*TOCEntry `json:"entries"` } // TOCEntry is an entry in the stargz file's TOC (Table of Contents). type TOCEntry struct { // Name is the tar entry's name. It is the complete path // stored in the tar file, not just the base name. Name string `json:"name"` // Type is one of "dir", "reg", "symlink", "hardlink", "char", // "block", "fifo", or "chunk". // The "chunk" type is used for regular file data chunks past the first // TOCEntry; the 2nd chunk and on have only Type ("chunk"), Offset, // ChunkOffset, and ChunkSize populated. Type string `json:"type"` // Size, for regular files, is the logical size of the file. Size int64 `json:"size,omitempty"` // ModTime3339 is the modification time of the tar entry. Empty // means zero or unknown. Otherwise it's in UTC RFC3339 // format. Use the ModTime method to access the time.Time value. ModTime3339 string `json:"modtime,omitempty"` modTime time.Time // LinkName, for symlinks and hardlinks, is the link target. LinkName string `json:"linkName,omitempty"` // Mode is the permission and mode bits. Mode int64 `json:"mode,omitempty"` // UID is the user ID of the owner. UID int `json:"uid,omitempty"` // GID is the group ID of the owner. GID int `json:"gid,omitempty"` // Uname is the username of the owner. // // In the serialized JSON, this field may only be present for // the first entry with the same UID. Uname string `json:"userName,omitempty"` // Gname is the group name of the owner. // // In the serialized JSON, this field may only be present for // the first entry with the same GID. Gname string `json:"groupName,omitempty"` // Offset, for regular files, provides the offset in the // stargz file to the file's data bytes. See ChunkOffset and // ChunkSize. Offset int64 `json:"offset,omitempty"` nextOffset int64 // the Offset of the next entry with a non-zero Offset // DevMajor is the major device number for "char" and "block" types. DevMajor int `json:"devMajor,omitempty"` // DevMinor is the major device number for "char" and "block" types. DevMinor int `json:"devMinor,omitempty"` // NumLink is the number of entry names pointing to this entry. // Zero means one name references this entry. // This field is calculated during runtime and not recorded in TOC JSON. NumLink int `json:"-"` // Xattrs are the extended attribute for the entry. Xattrs map[string][]byte `json:"xattrs,omitempty"` // Digest stores the OCI checksum for regular files payload. // It has the form "sha256:abcdef01234....". Digest string `json:"digest,omitempty"` // ChunkOffset is non-zero if this is a chunk of a large, // regular file. If so, the Offset is where the gzip header of // ChunkSize bytes at ChunkOffset in Name begin. // // In serialized form, a "chunkSize" JSON field of zero means // that the chunk goes to the end of the file. After reading // from the stargz TOC, though, the ChunkSize is initialized // to a non-zero file for when Type is either "reg" or // "chunk". ChunkOffset int64 `json:"chunkOffset,omitempty"` ChunkSize int64 `json:"chunkSize,omitempty"` // ChunkDigest stores an OCI digest of the chunk. This must be formed // as "sha256:0123abcd...". ChunkDigest string `json:"chunkDigest,omitempty"` children map[string]*TOCEntry } // ModTime returns the entry's modification time. func (e *TOCEntry) ModTime() time.Time { return e.modTime } // NextOffset returns the position (relative to the start of the // stargz file) of the next gzip boundary after e.Offset. func (e *TOCEntry) NextOffset() int64 { return e.nextOffset } func (e *TOCEntry) addChild(baseName string, child *TOCEntry) { if e.children == nil { e.children = make(map[string]*TOCEntry) } if child.Type == "dir" { e.NumLink++ // Entry ".." in the subdirectory links to this directory } e.children[baseName] = child } // isDataType reports whether TOCEntry is a regular file or chunk (something that // contains regular file data). func (e *TOCEntry) isDataType() bool { return e.Type == "reg" || e.Type == "chunk" } // Stat returns a FileInfo value representing e. func (e *TOCEntry) Stat() os.FileInfo { return fileInfo{e} } // ForeachChild calls f for each child item. If f returns false, iteration ends. // If e is not a directory, f is not called. func (e *TOCEntry) ForeachChild(f func(baseName string, ent *TOCEntry) bool) { for name, ent := range e.children { if !f(name, ent) { return } } } // LookupChild returns the directory e's child by its base name. func (e *TOCEntry) LookupChild(baseName string) (child *TOCEntry, ok bool) { child, ok = e.children[baseName] return } // fileInfo implements os.FileInfo using the wrapped *TOCEntry. type fileInfo struct{ e *TOCEntry } var _ os.FileInfo = fileInfo{} func (fi fileInfo) Name() string { return path.Base(fi.e.Name) } func (fi fileInfo) IsDir() bool { return fi.e.Type == "dir" } func (fi fileInfo) Size() int64 { return fi.e.Size } func (fi fileInfo) ModTime() time.Time { return fi.e.ModTime() } func (fi fileInfo) Sys() interface{} { return fi.e } func (fi fileInfo) Mode() (m os.FileMode) { // TOCEntry.Mode is tar.Header.Mode so we can understand the these bits using `tar` pkg. m = (&tar.Header{Mode: fi.e.Mode}).FileInfo().Mode() & (os.ModePerm | os.ModeSetuid | os.ModeSetgid | os.ModeSticky) switch fi.e.Type { case "dir": m |= os.ModeDir case "symlink": m |= os.ModeSymlink case "char": m |= os.ModeDevice | os.ModeCharDevice case "block": m |= os.ModeDevice case "fifo": m |= os.ModeNamedPipe } return m } // TOCEntryVerifier holds verifiers that are usable for verifying chunks contained // in a eStargz blob. type TOCEntryVerifier interface { // Verifier provides a content verifier that can be used for verifying the // contents of the specified TOCEntry. Verifier(ce *TOCEntry) (digest.Verifier, error) } // Compression provides the compression helper to be used creating and parsing eStargz. // This package provides gzip-based Compression by default, but any compression // algorithm (e.g. zstd) can be used as long as it implements Compression. type Compression interface { Compressor Decompressor } // Compressor represents the helper mothods to be used for creating eStargz. type Compressor interface { // Writer returns WriteCloser to be used for writing a chunk to eStargz. // Everytime a chunk is written, the WriteCloser is closed and Writer is // called again for writing the next chunk. Writer(w io.Writer) (io.WriteCloser, error) // WriteTOCAndFooter is called to write JTOC to the passed Writer. // diffHash calculates the DiffID (uncompressed sha256 hash) of the blob // WriteTOCAndFooter can optionally write anything that affects DiffID calculation // (e.g. uncompressed TOC JSON). // // This function returns tocDgst that represents the digest of TOC that will be used // to verify this blob when it's parsed. WriteTOCAndFooter(w io.Writer, off int64, toc *JTOC, diffHash hash.Hash) (tocDgst digest.Digest, err error) } // Decompressor represents the helper mothods to be used for parsing eStargz. type Decompressor interface { // Reader returns ReadCloser to be used for decompressing file payload. Reader(r io.Reader) (io.ReadCloser, error) // FooterSize returns the size of the footer of this blob. FooterSize() int64 // ParseFooter parses the footer and returns the offset and (compressed) size of TOC. // payloadBlobSize is the (compressed) size of the blob payload (i.e. the size between // the top until the TOC JSON). // // Here, tocSize is optional. If tocSize <= 0, it's by default the size of the range // from tocOffset until the beginning of the footer (blob size - tocOff - FooterSize). ParseFooter(p []byte) (blobPayloadSize, tocOffset, tocSize int64, err error) // ParseTOC parses TOC from the passed reader. The reader provides the partial contents // of the underlying blob that has the range specified by ParseFooter method. // // This function returns tocDgst that represents the digest of TOC that will be used // to verify this blob. This must match to the value returned from // Compressor.WriteTOCAndFooter that is used when creating this blob. ParseTOC(r io.Reader) (toc *JTOC, tocDgst digest.Digest, err error) } stargz-snapshotter-0.12.0/estargz/zstdchunked/000077500000000000000000000000001426301527400214655ustar00rootroot00000000000000stargz-snapshotter-0.12.0/estargz/zstdchunked/zstdchunked.go000066400000000000000000000130601426301527400243420ustar00rootroot00000000000000/* 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 zstdchunked import ( "bufio" "bytes" "encoding/binary" "encoding/json" "fmt" "hash" "io" "sync" "github.com/containerd/stargz-snapshotter/estargz" "github.com/klauspost/compress/zstd" digest "github.com/opencontainers/go-digest" ) const ( // ManifestChecksumAnnotation is an annotation that contains the compressed TOC Digset ManifestChecksumAnnotation = "io.containers.zstd-chunked.manifest-checksum" // ManifestPositionAnnotation is an annotation that contains the offset to the TOC. ManifestPositionAnnotation = "io.containers.zstd-chunked.manifest-position" // FooterSize is the size of the footer FooterSize = 40 manifestTypeCRFS = 1 ) var ( skippableFrameMagic = []byte{0x50, 0x2a, 0x4d, 0x18} zstdFrameMagic = []byte{0x28, 0xb5, 0x2f, 0xfd} zstdChunkedFrameMagic = []byte{0x47, 0x6e, 0x55, 0x6c, 0x49, 0x6e, 0x55, 0x78} ) type Decompressor struct{} func (zz *Decompressor) Reader(r io.Reader) (io.ReadCloser, error) { decoder, err := zstd.NewReader(r) if err != nil { return nil, err } return &zstdReadCloser{decoder}, nil } func (zz *Decompressor) ParseTOC(r io.Reader) (toc *estargz.JTOC, tocDgst digest.Digest, err error) { zr, err := zstd.NewReader(r) if err != nil { return nil, "", err } defer zr.Close() dgstr := digest.Canonical.Digester() toc = new(estargz.JTOC) if err := json.NewDecoder(io.TeeReader(zr, dgstr.Hash())).Decode(&toc); err != nil { return nil, "", fmt.Errorf("error decoding TOC JSON: %w", err) } return toc, dgstr.Digest(), nil } func (zz *Decompressor) ParseFooter(p []byte) (blobPayloadSize, tocOffset, tocSize int64, err error) { offset := binary.LittleEndian.Uint64(p[0:8]) compressedLength := binary.LittleEndian.Uint64(p[8:16]) if !bytes.Equal(zstdChunkedFrameMagic, p[32:40]) { return 0, 0, 0, fmt.Errorf("invalid magic number") } // 8 is the size of the zstd skippable frame header + the frame size (see WriteTOCAndFooter) return int64(offset - 8), int64(offset), int64(compressedLength), nil } func (zz *Decompressor) FooterSize() int64 { return FooterSize } func (zz *Decompressor) DecompressTOC(r io.Reader) (tocJSON io.ReadCloser, err error) { decoder, err := zstd.NewReader(r) if err != nil { return nil, err } br := bufio.NewReader(decoder) if _, err := br.Peek(1); err != nil { return nil, err } return &reader{br, decoder.Close}, nil } type reader struct { io.Reader closeFunc func() } func (r *reader) Close() error { r.closeFunc(); return nil } type zstdReadCloser struct{ *zstd.Decoder } func (z *zstdReadCloser) Close() error { z.Decoder.Close() return nil } type Compressor struct { CompressionLevel zstd.EncoderLevel Metadata map[string]string pool sync.Pool } func (zc *Compressor) Writer(w io.Writer) (io.WriteCloser, error) { if wc := zc.pool.Get(); wc != nil { ec := wc.(*zstd.Encoder) ec.Reset(w) return &poolEncoder{ec, zc}, nil } ec, err := zstd.NewWriter(w, zstd.WithEncoderLevel(zc.CompressionLevel), zstd.WithLowerEncoderMem(true)) if err != nil { return nil, err } return &poolEncoder{ec, zc}, nil } type poolEncoder struct { *zstd.Encoder zc *Compressor } func (w *poolEncoder) Close() error { if err := w.Encoder.Close(); err != nil { return err } w.zc.pool.Put(w.Encoder) return nil } func (zc *Compressor) WriteTOCAndFooter(w io.Writer, off int64, toc *estargz.JTOC, diffHash hash.Hash) (digest.Digest, error) { tocJSON, err := json.MarshalIndent(toc, "", "\t") if err != nil { return "", err } buf := new(bytes.Buffer) encoder, err := zstd.NewWriter(buf, zstd.WithEncoderLevel(zc.CompressionLevel)) if err != nil { return "", err } if _, err := encoder.Write(tocJSON); err != nil { return "", err } if err := encoder.Close(); err != nil { return "", err } compressedTOC := buf.Bytes() _, err = io.Copy(w, bytes.NewReader(appendSkippableFrameMagic(compressedTOC))) // 8 is the size of the zstd skippable frame header + the frame size tocOff := uint64(off) + 8 if _, err := w.Write(appendSkippableFrameMagic( zstdFooterBytes(tocOff, uint64(len(tocJSON)), uint64(len(compressedTOC)))), ); err != nil { return "", err } if zc.Metadata != nil { zc.Metadata[ManifestChecksumAnnotation] = digest.FromBytes(compressedTOC).String() zc.Metadata[ManifestPositionAnnotation] = fmt.Sprintf("%d:%d:%d:%d", tocOff, len(compressedTOC), len(tocJSON), manifestTypeCRFS) } return digest.FromBytes(tocJSON), err } // zstdFooterBytes returns the 40 bytes footer. func zstdFooterBytes(tocOff, tocRawSize, tocCompressedSize uint64) []byte { footer := make([]byte, FooterSize) binary.LittleEndian.PutUint64(footer, tocOff) binary.LittleEndian.PutUint64(footer[8:], tocCompressedSize) binary.LittleEndian.PutUint64(footer[16:], tocRawSize) binary.LittleEndian.PutUint64(footer[24:], manifestTypeCRFS) copy(footer[32:40], zstdChunkedFrameMagic) return footer } func appendSkippableFrameMagic(b []byte) []byte { size := make([]byte, 4) binary.LittleEndian.PutUint32(size, uint32(len(b))) return append(append(skippableFrameMagic, size...), b...) } stargz-snapshotter-0.12.0/estargz/zstdchunked/zstdchunked_test.go000066400000000000000000000145621426301527400254110ustar00rootroot00000000000000/* 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 zstdchunked import ( "bytes" "crypto/sha256" "encoding/binary" "fmt" "io" "testing" "github.com/containerd/stargz-snapshotter/estargz" "github.com/klauspost/compress/zstd" ) // TestZstdChunked tests zstd:chunked func TestZstdChunked(t *testing.T) { estargz.CompressionTestSuite(t, zstdControllerWithLevel(zstd.SpeedFastest), zstdControllerWithLevel(zstd.SpeedDefault), zstdControllerWithLevel(zstd.SpeedBetterCompression), // zstdControllerWithLevel(zstd.SpeedBestCompression), // consumes too much memory to pass on CI ) } func zstdControllerWithLevel(compressionLevel zstd.EncoderLevel) estargz.TestingController { return &zstdController{&Compressor{CompressionLevel: compressionLevel}, &Decompressor{}} } type zstdController struct { *Compressor *Decompressor } func (zc *zstdController) String() string { return fmt.Sprintf("zstd_compression_level=%v", zc.Compressor.CompressionLevel) } func (zc *zstdController) CountStreams(t *testing.T, b []byte) (numStreams int) { t.Logf("got zstd streams (compressed size: %d):", len(b)) zh := new(zstd.Header) magicLen := 4 // length of magic bytes and skippable frame magic bytes zoff := 0 for { if len(b) <= zoff { break } else if len(b)-zoff <= magicLen { t.Fatalf("invalid frame size %d is too small", len(b)-zoff) } remainingFrames := b[zoff:] // Check if zoff points to the beginning of a frame if !bytes.Equal(remainingFrames[:magicLen], zstdFrameMagic) { if !bytes.Equal(remainingFrames[:magicLen], skippableFrameMagic) { t.Fatalf("frame must start from magic bytes; but %x", remainingFrames[:magicLen]) } // This is a skippable frame size := binary.LittleEndian.Uint32(remainingFrames[magicLen : magicLen+4]) t.Logf(" [%d] at %d in stargz, SKIPPABLE FRAME (nextFrame: %d/%d)", numStreams, zoff, zoff+(magicLen+4+int(size)), len(b)) zoff += (magicLen + 4 + int(size)) numStreams++ continue } // Parse header and get uncompressed size of this frame if err := zh.Decode(remainingFrames); err != nil { t.Fatalf("countStreams(zstd), *Header.Decode: %v", err) } uncompressedFrameSize := zh.FrameContentSize if uncompressedFrameSize == 0 { // FrameContentSize is optional so it's possible we cannot get size info from // this field. If this frame contains only one block, we can get the decompressed // size from that block header. if zh.FirstBlock.OK && zh.FirstBlock.Last && !zh.FirstBlock.Compressed { uncompressedFrameSize = uint64(zh.FirstBlock.DecompressedSize) } else { t.Fatalf("countStreams(zstd), failed to get uncompressed frame size") } } // Identify the offset of the next frame nextFrame := magicLen // ignore the magic bytes of this frame for { // search for the beginning magic bytes of the next frame searchBase := nextFrame nextMagicIdx := nextIndex(remainingFrames[searchBase:], zstdFrameMagic) nextSkippableIdx := nextIndex(remainingFrames[searchBase:], skippableFrameMagic) nextFrame = len(remainingFrames) for _, i := range []int{nextMagicIdx, nextSkippableIdx} { if 0 < i && searchBase+i < nextFrame { nextFrame = searchBase + i } } // "nextFrame" seems the offset of the next frame. Verify it by checking if // the decompressed size of this frame is the same value as set in the header. zr, err := zstd.NewReader(bytes.NewReader(remainingFrames[:nextFrame])) if err != nil { t.Logf(" [%d] invalid frame candidate: %v", numStreams, err) continue } defer zr.Close() res, err := io.ReadAll(zr) if err != nil && err != io.ErrUnexpectedEOF { t.Fatalf("countStreams(zstd), ReadAll: %v", err) } if uint64(len(res)) == uncompressedFrameSize { break } // Try the next magic byte candidate until end if uint64(len(res)) > uncompressedFrameSize || nextFrame > len(remainingFrames) { t.Fatalf("countStreams(zstd), cannot identify frame (off:%d)", zoff) } } t.Logf(" [%d] at %d in stargz, uncompressed length %d (nextFrame: %d/%d)", numStreams, zoff, uncompressedFrameSize, zoff+nextFrame, len(b)) zoff += nextFrame numStreams++ } return numStreams } func nextIndex(s1, sub []byte) int { for i := 0; i < len(s1); i++ { if len(s1)-i < len(sub) { return -1 } else if bytes.Equal(s1[i:i+len(sub)], sub) { return i } } return -1 } func (zc *zstdController) DiffIDOf(t *testing.T, b []byte) string { h := sha256.New() zr, err := zstd.NewReader(bytes.NewReader(b)) if err != nil { t.Fatalf("diffIDOf(zstd): %v", err) } defer zr.Close() if _, err := io.Copy(h, zr); err != nil { t.Fatalf("diffIDOf(zstd).Copy: %v", err) } return fmt.Sprintf("sha256:%x", h.Sum(nil)) } // Tests footer encoding, size, and parsing of zstd:chunked. func TestZstdChunkedFooter(t *testing.T) { max := int64(200000) for off := int64(0); off <= max; off += 1023 { size := max - off checkZstdChunkedFooter(t, off, size, size/2) } } func checkZstdChunkedFooter(t *testing.T, off, size, cSize int64) { footer := zstdFooterBytes(uint64(off), uint64(size), uint64(cSize)) if len(footer) != FooterSize { t.Fatalf("for offset %v, footer length was %d, not expected %d. got bytes: %q", off, len(footer), FooterSize, footer) } gotBlobPayloadSize, gotOff, gotSize, err := (&Decompressor{}).ParseFooter(footer) if err != nil { t.Fatalf("failed to parse footer for offset %d, footer: %x: err: %v", off, footer, err) } if gotBlobPayloadSize != off-8 { // 8 is the size of the zstd skippable frame header + the frame size (see WriteTOCAndFooter) t.Fatalf("ParseFooter(footerBytes(offset %d)) = blobPayloadSize %d; want %d", off, gotBlobPayloadSize, off-8) } if gotOff != off { t.Fatalf("ParseFooter(footerBytes(offset %d)) = off %d; want %d", off, gotOff, off) } if gotSize != cSize { t.Fatalf("ParseFooter(footerBytes(offset %d)) = size %d; want %d", off, gotSize, cSize) } } stargz-snapshotter-0.12.0/fs/000077500000000000000000000000001426301527400160705ustar00rootroot00000000000000stargz-snapshotter-0.12.0/fs/config/000077500000000000000000000000001426301527400173355ustar00rootroot00000000000000stargz-snapshotter-0.12.0/fs/config/config.go000066400000000000000000000072571426301527400211440ustar00rootroot00000000000000/* 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. */ /* Copyright 2019 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the NOTICE.md file. */ package config const ( // TargetSkipVerifyLabel is a snapshot label key that indicates to skip content // verification for the layer. TargetSkipVerifyLabel = "containerd.io/snapshot/remote/stargz.skipverify" // TargetPrefetchSizeLabel is a snapshot label key that indicates size to prefetch // the layer. If the layer is eStargz and contains prefetch landmarks, these config // will be respeced. TargetPrefetchSizeLabel = "containerd.io/snapshot/remote/stargz.prefetch" ) type Config struct { HTTPCacheType string `toml:"http_cache_type"` FSCacheType string `toml:"filesystem_cache_type"` // ResolveResultEntryTTLSec is TTL (in sec) to cache resolved layers for // future use. (default 120s) ResolveResultEntryTTLSec int `toml:"resolve_result_entry_ttl_sec"` ResolveResultEntry int `toml:"resolve_result_entry"` // deprecated PrefetchSize int64 `toml:"prefetch_size"` PrefetchTimeoutSec int64 `toml:"prefetch_timeout_sec"` NoPrefetch bool `toml:"noprefetch"` NoBackgroundFetch bool `toml:"no_background_fetch"` Debug bool `toml:"debug"` AllowNoVerification bool `toml:"allow_no_verification"` DisableVerification bool `toml:"disable_verification"` MaxConcurrency int64 `toml:"max_concurrency"` NoPrometheus bool `toml:"no_prometheus"` // BlobConfig is config for layer blob management. BlobConfig `toml:"blob"` // DirectoryCacheConfig is config for directory-based cache. DirectoryCacheConfig `toml:"directory_cache"` FuseConfig `toml:"fuse"` } type BlobConfig struct { ValidInterval int64 `toml:"valid_interval"` CheckAlways bool `toml:"check_always"` // ChunkSize is the granularity at which background fetch and on-demand reads // are fetched from the remote registry. ChunkSize int64 `toml:"chunk_size"` FetchTimeoutSec int64 `toml:"fetching_timeout_sec"` ForceSingleRangeMode bool `toml:"force_single_range_mode"` // PrefetchChunkSize is the maximum bytes transferred per http GET from remote registry // during prefetch. It is recommended to have PrefetchChunkSize > ChunkSize. // If PrefetchChunkSize < ChunkSize prefetch bytes will be fetched as a single http GET, // else total GET requests for prefetch = ceil(PrefetchSize / PrefetchChunkSize). PrefetchChunkSize int64 `toml:"prefetch_chunk_size"` MaxRetries int `toml:"max_retries"` MinWaitMSec int `toml:"min_wait_msec"` MaxWaitMSec int `toml:"max_wait_msec"` } type DirectoryCacheConfig struct { MaxLRUCacheEntry int `toml:"max_lru_cache_entry"` MaxCacheFds int `toml:"max_cache_fds"` SyncAdd bool `toml:"sync_add"` Direct bool `toml:"direct" default:"true"` } type FuseConfig struct { // AttrTimeout defines overall timeout attribute for a file system in seconds. AttrTimeout int64 `toml:"attr_timeout"` // EntryTimeout defines TTL for directory, name lookup in seconds. EntryTimeout int64 `toml:"entry_timeout"` } stargz-snapshotter-0.12.0/fs/fs.go000066400000000000000000000362101426301527400170310ustar00rootroot00000000000000/* 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. */ /* Copyright 2019 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the NOTICE.md file. */ // // Example implementation of FileSystem. // // This implementation uses stargz by CRFS(https://github.com/google/crfs) as // image format, which has following feature: // - We can use docker registry as a backend store (means w/o additional layer // stores). // - The stargz-formatted image is still docker-compatible (means normal // runtimes can still use the formatted image). // // Currently, we reimplemented CRFS-like filesystem for ease of integration. // But in the near future, we intend to integrate it with CRFS. // package fs import ( "context" "fmt" "os/exec" "strconv" "sync" "syscall" "time" "github.com/containerd/containerd/log" "github.com/containerd/containerd/reference" "github.com/containerd/containerd/remotes/docker" "github.com/containerd/stargz-snapshotter/estargz" "github.com/containerd/stargz-snapshotter/fs/config" "github.com/containerd/stargz-snapshotter/fs/layer" commonmetrics "github.com/containerd/stargz-snapshotter/fs/metrics/common" layermetrics "github.com/containerd/stargz-snapshotter/fs/metrics/layer" "github.com/containerd/stargz-snapshotter/fs/remote" "github.com/containerd/stargz-snapshotter/fs/source" "github.com/containerd/stargz-snapshotter/metadata" memorymetadata "github.com/containerd/stargz-snapshotter/metadata/memory" "github.com/containerd/stargz-snapshotter/snapshot" "github.com/containerd/stargz-snapshotter/task" metrics "github.com/docker/go-metrics" fusefs "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fuse" digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/sirupsen/logrus" ) const ( defaultFuseTimeout = time.Second defaultMaxConcurrency = 2 fusermountBin = "fusermount" ) type Option func(*options) type options struct { getSources source.GetSources resolveHandlers map[string]remote.Handler metadataStore metadata.Store metricsLogLevel *logrus.Level overlayOpaqueType layer.OverlayOpaqueType } func WithGetSources(s source.GetSources) Option { return func(opts *options) { opts.getSources = s } } func WithResolveHandler(name string, handler remote.Handler) Option { return func(opts *options) { if opts.resolveHandlers == nil { opts.resolveHandlers = make(map[string]remote.Handler) } opts.resolveHandlers[name] = handler } } func WithMetadataStore(metadataStore metadata.Store) Option { return func(opts *options) { opts.metadataStore = metadataStore } } func WithMetricsLogLevel(logLevel logrus.Level) Option { return func(opts *options) { opts.metricsLogLevel = &logLevel } } func WithOverlayOpaqueType(overlayOpaqueType layer.OverlayOpaqueType) Option { return func(opts *options) { opts.overlayOpaqueType = overlayOpaqueType } } func NewFilesystem(root string, cfg config.Config, opts ...Option) (_ snapshot.FileSystem, err error) { var fsOpts options for _, o := range opts { o(&fsOpts) } maxConcurrency := cfg.MaxConcurrency if maxConcurrency == 0 { maxConcurrency = defaultMaxConcurrency } attrTimeout := time.Duration(cfg.FuseConfig.AttrTimeout) * time.Second if attrTimeout == 0 { attrTimeout = defaultFuseTimeout } entryTimeout := time.Duration(cfg.FuseConfig.EntryTimeout) * time.Second if entryTimeout == 0 { entryTimeout = defaultFuseTimeout } metadataStore := fsOpts.metadataStore if metadataStore == nil { metadataStore = memorymetadata.NewReader } getSources := fsOpts.getSources if getSources == nil { getSources = source.FromDefaultLabels(func(refspec reference.Spec) (hosts []docker.RegistryHost, _ error) { return docker.ConfigureDefaultRegistries(docker.WithPlainHTTP(docker.MatchLocalhost))(refspec.Hostname()) }) } tm := task.NewBackgroundTaskManager(maxConcurrency, 5*time.Second) r, err := layer.NewResolver(root, tm, cfg, fsOpts.resolveHandlers, metadataStore, fsOpts.overlayOpaqueType) if err != nil { return nil, fmt.Errorf("failed to setup resolver: %w", err) } var ns *metrics.Namespace if !cfg.NoPrometheus { ns = metrics.NewNamespace("stargz", "fs", nil) logLevel := logrus.DebugLevel if fsOpts.metricsLogLevel != nil { logLevel = *fsOpts.metricsLogLevel } commonmetrics.Register(logLevel) // Register common metrics. This will happen only once. } c := layermetrics.NewLayerMetrics(ns) if ns != nil { metrics.Register(ns) // Register layer metrics. } return &filesystem{ resolver: r, getSources: getSources, prefetchSize: cfg.PrefetchSize, noprefetch: cfg.NoPrefetch, noBackgroundFetch: cfg.NoBackgroundFetch, debug: cfg.Debug, layer: make(map[string]layer.Layer), backgroundTaskManager: tm, allowNoVerification: cfg.AllowNoVerification, disableVerification: cfg.DisableVerification, metricsController: c, attrTimeout: attrTimeout, entryTimeout: entryTimeout, }, nil } type filesystem struct { resolver *layer.Resolver prefetchSize int64 noprefetch bool noBackgroundFetch bool debug bool layer map[string]layer.Layer layerMu sync.Mutex backgroundTaskManager *task.BackgroundTaskManager allowNoVerification bool disableVerification bool getSources source.GetSources metricsController *layermetrics.Controller attrTimeout time.Duration entryTimeout time.Duration } func (fs *filesystem) Mount(ctx context.Context, mountpoint string, labels map[string]string) (retErr error) { // Setting the start time to measure the Mount operation duration. start := time.Now() // This is a prioritized task and all background tasks will be stopped // execution so this can avoid being disturbed for NW traffic by background // tasks. fs.backgroundTaskManager.DoPrioritizedTask() defer fs.backgroundTaskManager.DonePrioritizedTask() ctx = log.WithLogger(ctx, log.G(ctx).WithField("mountpoint", mountpoint)) // Get source information of this layer. src, err := fs.getSources(labels) if err != nil { return err } else if len(src) == 0 { return fmt.Errorf("source must be passed") } defaultPrefetchSize := fs.prefetchSize if psStr, ok := labels[config.TargetPrefetchSizeLabel]; ok { if ps, err := strconv.ParseInt(psStr, 10, 64); err == nil { defaultPrefetchSize = ps } } // Resolve the target layer var ( resultChan = make(chan layer.Layer) errChan = make(chan error) ) go func() { rErr := fmt.Errorf("failed to resolve target") for _, s := range src { l, err := fs.resolver.Resolve(ctx, s.Hosts, s.Name, s.Target) if err == nil { resultChan <- l fs.prefetch(ctx, l, defaultPrefetchSize, start) return } rErr = fmt.Errorf("failed to resolve layer %q from %q: %v: %w", s.Target.Digest, s.Name, err, rErr) } errChan <- rErr }() // Also resolve and cache other layers in parallel preResolve := src[0] // TODO: should we pre-resolve blobs in other sources as well? for _, desc := range neighboringLayers(preResolve.Manifest, preResolve.Target) { desc := desc go func() { // Avoids to get canceled by client. ctx := log.WithLogger(context.Background(), log.G(ctx).WithField("mountpoint", mountpoint)) l, err := fs.resolver.Resolve(ctx, preResolve.Hosts, preResolve.Name, desc) if err != nil { log.G(ctx).WithError(err).Debug("failed to pre-resolve") return } fs.prefetch(ctx, l, defaultPrefetchSize, start) // Release this layer because this isn't target and we don't use it anymore here. // However, this will remain on the resolver cache until eviction. l.Done() }() } // Wait for resolving completion var l layer.Layer select { case l = <-resultChan: case err := <-errChan: log.G(ctx).WithError(err).Debug("failed to resolve layer") return fmt.Errorf("failed to resolve layer: %w", err) case <-time.After(30 * time.Second): log.G(ctx).Debug("failed to resolve layer (timeout)") return fmt.Errorf("failed to resolve layer (timeout)") } defer func() { if retErr != nil { l.Done() // don't use this layer. } }() // Verify layer's content if fs.disableVerification { // Skip if verification is disabled completely l.SkipVerify() log.G(ctx).Infof("Verification forcefully skipped") } else if tocDigest, ok := labels[estargz.TOCJSONDigestAnnotation]; ok { // Verify this layer using the TOC JSON digest passed through label. dgst, err := digest.Parse(tocDigest) if err != nil { log.G(ctx).WithError(err).Debugf("failed to parse passed TOC digest %q", dgst) return fmt.Errorf("invalid TOC digest: %v: %w", tocDigest, err) } if err := l.Verify(dgst); err != nil { log.G(ctx).WithError(err).Debugf("invalid layer") return fmt.Errorf("invalid stargz layer: %w", err) } log.G(ctx).Debugf("verified") } else if _, ok := labels[config.TargetSkipVerifyLabel]; ok && fs.allowNoVerification { // If unverified layer is allowed, use it with warning. // This mode is for legacy stargz archives which don't contain digests // necessary for layer verification. l.SkipVerify() log.G(ctx).Warningf("No verification is held for layer") } else { // Verification must be done. Don't mount this layer. return fmt.Errorf("digest of TOC JSON must be passed") } node, err := l.RootNode(0) if err != nil { log.G(ctx).WithError(err).Warnf("Failed to get root node") return fmt.Errorf("failed to get root node: %w", err) } // Measuring duration of Mount operation for resolved layer. digest := l.Info().Digest // get layer sha defer commonmetrics.MeasureLatencyInMilliseconds(commonmetrics.Mount, digest, start) // Register the mountpoint layer fs.layerMu.Lock() fs.layer[mountpoint] = l fs.layerMu.Unlock() fs.metricsController.Add(mountpoint, l) // mount the node to the specified mountpoint // TODO: bind mount the state directory as a read-only fs on snapshotter's side rawFS := fusefs.NewNodeFS(node, &fusefs.Options{ AttrTimeout: &fs.attrTimeout, EntryTimeout: &fs.entryTimeout, NullPermissions: true, }) mountOpts := &fuse.MountOptions{ AllowOther: true, // allow users other than root&mounter to access fs FsName: "stargz", // name this filesystem as "stargz" Debug: fs.debug, } if _, err := exec.LookPath(fusermountBin); err == nil { mountOpts.Options = []string{"suid"} // option for fusermount; allow setuid inside container } else { log.G(ctx).WithError(err).Infof("%s not installed; trying direct mount", fusermountBin) mountOpts.DirectMount = true } server, err := fuse.NewServer(rawFS, mountpoint, mountOpts) if err != nil { log.G(ctx).WithError(err).Debug("failed to make filesystem server") return err } go server.Serve() return server.WaitMount() } func (fs *filesystem) Check(ctx context.Context, mountpoint string, labels map[string]string) error { // This is a prioritized task and all background tasks will be stopped // execution so this can avoid being disturbed for NW traffic by background // tasks. fs.backgroundTaskManager.DoPrioritizedTask() defer fs.backgroundTaskManager.DonePrioritizedTask() defer commonmetrics.MeasureLatencyInMilliseconds(commonmetrics.PrefetchesCompleted, digest.FromString(""), time.Now()) // measuring the time the container launch is blocked on prefetch to complete ctx = log.WithLogger(ctx, log.G(ctx).WithField("mountpoint", mountpoint)) fs.layerMu.Lock() l := fs.layer[mountpoint] fs.layerMu.Unlock() if l == nil { log.G(ctx).Debug("layer not registered") return fmt.Errorf("layer not registered") } // Check the blob connectivity and try to refresh the connection on failure if err := fs.check(ctx, l, labels); err != nil { log.G(ctx).WithError(err).Warn("check failed") return err } // Wait for prefetch compeletion if !fs.noprefetch { if err := l.WaitForPrefetchCompletion(); err != nil { log.G(ctx).WithError(err).Warn("failed to sync with prefetch completion") } } return nil } func (fs *filesystem) check(ctx context.Context, l layer.Layer, labels map[string]string) error { err := l.Check() if err == nil { return nil } log.G(ctx).WithError(err).Warn("failed to connect to blob") // Check failed. Try to refresh the connection with fresh source information src, err := fs.getSources(labels) if err != nil { return err } var ( retrynum = 1 rErr = fmt.Errorf("failed to refresh connection") ) for retry := 0; retry < retrynum; retry++ { log.G(ctx).Warnf("refreshing(%d)...", retry) for _, s := range src { err := l.Refresh(ctx, s.Hosts, s.Name, s.Target) if err == nil { log.G(ctx).Debug("Successfully refreshed connection") return nil } log.G(ctx).WithError(err).Warnf("failed to refresh the layer %q from %q", s.Target.Digest, s.Name) rErr = fmt.Errorf("failed(layer:%q, ref:%q): %v: %w", s.Target.Digest, s.Name, err, rErr) } } return rErr } func (fs *filesystem) Unmount(ctx context.Context, mountpoint string) error { fs.layerMu.Lock() l, ok := fs.layer[mountpoint] if !ok { fs.layerMu.Unlock() return fmt.Errorf("specified path %q isn't a mountpoint", mountpoint) } delete(fs.layer, mountpoint) // unregisters the corresponding layer l.Done() fs.layerMu.Unlock() fs.metricsController.Remove(mountpoint) // The goroutine which serving the mountpoint possibly becomes not responding. // In case of such situations, we use MNT_FORCE here and abort the connection. // In the future, we might be able to consider to kill that specific hanging // goroutine using channel, etc. // See also: https://www.kernel.org/doc/html/latest/filesystems/fuse.html#aborting-a-filesystem-connection return syscall.Unmount(mountpoint, syscall.MNT_FORCE) } func (fs *filesystem) prefetch(ctx context.Context, l layer.Layer, defaultPrefetchSize int64, start time.Time) { // Prefetch a layer. The first Check() for this layer waits for the prefetch completion. if !fs.noprefetch { go l.Prefetch(defaultPrefetchSize) } // Fetch whole layer aggressively in background. if !fs.noBackgroundFetch { go func() { if err := l.BackgroundFetch(); err == nil { // write log record for the latency between mount start and last on demand fetch commonmetrics.LogLatencyForLastOnDemandFetch(ctx, l.Info().Digest, start, l.Info().ReadTime) } }() } } // neighboringLayers returns layer descriptors except the `target` layer in the specified manifest. func neighboringLayers(manifest ocispec.Manifest, target ocispec.Descriptor) (descs []ocispec.Descriptor) { for _, desc := range manifest.Layers { if desc.Digest.String() != target.Digest.String() { descs = append(descs, desc) } } return } stargz-snapshotter-0.12.0/fs/fs_test.go000066400000000000000000000061571426301527400200770ustar00rootroot00000000000000/* 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. */ /* Copyright 2019 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the NOTICE.md file. */ package fs import ( "context" "fmt" "testing" "time" "github.com/containerd/containerd/reference" "github.com/containerd/containerd/remotes/docker" "github.com/containerd/stargz-snapshotter/fs/layer" "github.com/containerd/stargz-snapshotter/fs/remote" "github.com/containerd/stargz-snapshotter/fs/source" "github.com/containerd/stargz-snapshotter/task" fusefs "github.com/hanwen/go-fuse/v2/fs" digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) func TestCheck(t *testing.T) { bl := &breakableLayer{} fs := &filesystem{ layer: map[string]layer.Layer{ "test": bl, }, backgroundTaskManager: task.NewBackgroundTaskManager(1, time.Millisecond), getSources: source.FromDefaultLabels(func(refspec reference.Spec) (hosts []docker.RegistryHost, _ error) { return docker.ConfigureDefaultRegistries(docker.WithPlainHTTP(docker.MatchLocalhost))(refspec.Hostname()) }), } bl.success = true if err := fs.Check(context.TODO(), "test", nil); err != nil { t.Errorf("connection failed; wanted to succeed: %v", err) } bl.success = false if err := fs.Check(context.TODO(), "test", nil); err == nil { t.Errorf("connection succeeded; wanted to fail") } } type breakableLayer struct { success bool } func (l *breakableLayer) Info() layer.Info { return layer.Info{} } func (l *breakableLayer) RootNode(uint32) (fusefs.InodeEmbedder, error) { return nil, nil } 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) ReadAt([]byte, int64, ...remote.Option) (int, error) { return 0, nil } func (l *breakableLayer) WaitForPrefetchCompletion() error { return fmt.Errorf("fail") } func (l *breakableLayer) BackgroundFetch() error { return fmt.Errorf("fail") } func (l *breakableLayer) Check() error { if !l.success { return fmt.Errorf("failed") } return nil } func (l *breakableLayer) Refresh(ctx context.Context, hosts source.RegistryHosts, refspec reference.Spec, desc ocispec.Descriptor) error { if !l.success { return fmt.Errorf("failed") } return nil } func (l *breakableLayer) Done() {} stargz-snapshotter-0.12.0/fs/layer/000077500000000000000000000000001426301527400172045ustar00rootroot00000000000000stargz-snapshotter-0.12.0/fs/layer/layer.go000066400000000000000000000505701426301527400206560ustar00rootroot00000000000000/* 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. */ /* Copyright 2019 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the NOTICE.md file. */ package layer import ( "bytes" "context" "fmt" "io" "os" "path/filepath" "sync" "time" "github.com/containerd/containerd/log" "github.com/containerd/containerd/reference" "github.com/containerd/stargz-snapshotter/cache" "github.com/containerd/stargz-snapshotter/estargz" "github.com/containerd/stargz-snapshotter/estargz/zstdchunked" "github.com/containerd/stargz-snapshotter/fs/config" commonmetrics "github.com/containerd/stargz-snapshotter/fs/metrics/common" "github.com/containerd/stargz-snapshotter/fs/reader" "github.com/containerd/stargz-snapshotter/fs/remote" "github.com/containerd/stargz-snapshotter/fs/source" "github.com/containerd/stargz-snapshotter/metadata" "github.com/containerd/stargz-snapshotter/task" "github.com/containerd/stargz-snapshotter/util/cacheutil" "github.com/containerd/stargz-snapshotter/util/namedmutex" fusefs "github.com/hanwen/go-fuse/v2/fs" digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/sirupsen/logrus" ) const ( defaultResolveResultEntryTTLSec = 120 defaultMaxLRUCacheEntry = 10 defaultMaxCacheFds = 10 defaultPrefetchTimeoutSec = 10 memoryCacheType = "memory" ) // Layer represents a layer. type Layer interface { // Info returns the information of this layer. Info() Info // RootNode returns the root node of this layer. RootNode(baseInode uint32) (fusefs.InodeEmbedder, error) // Check checks if the layer is still connectable. Check() error // Refresh refreshes the layer connection. Refresh(ctx context.Context, hosts source.RegistryHosts, refspec reference.Spec, desc ocispec.Descriptor) error // Verify verifies this layer using the passed TOC Digest. // Nop if Verify() or SkipVerify() was already called. Verify(tocDigest digest.Digest) (err error) // SkipVerify skips verification for this layer. // Nop if Verify() or SkipVerify() was already called. SkipVerify() // Prefetch prefetches the specified size. If the layer is eStargz and contains landmark files, // the range indicated by these files is respected. Prefetch(prefetchSize int64) error // ReadAt reads this layer. ReadAt([]byte, int64, ...remote.Option) (int, error) // WaitForPrefetchCompletion waits untils Prefetch completes. WaitForPrefetchCompletion() error // BackgroundFetch fetches the entire layer contents to the cache. // Fetching contents is done as a background task. BackgroundFetch() error // 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. Done() } // Info is the current status of a layer. type Info struct { Digest digest.Digest Size int64 // layer size in bytes FetchedSize int64 // layer fetched size in bytes PrefetchSize int64 // layer prefetch size in bytes ReadTime time.Time // last time the layer was read } // Resolver resolves the layer location and provieds the handler of that layer. type Resolver struct { rootDir string resolver *remote.Resolver prefetchTimeout time.Duration layerCache *cacheutil.TTLCache layerCacheMu sync.Mutex blobCache *cacheutil.TTLCache blobCacheMu sync.Mutex backgroundTaskManager *task.BackgroundTaskManager resolveLock *namedmutex.NamedMutex config config.Config metadataStore metadata.Store overlayOpaqueType OverlayOpaqueType } // NewResolver returns a new layer resolver. func NewResolver(root string, backgroundTaskManager *task.BackgroundTaskManager, cfg config.Config, resolveHandlers map[string]remote.Handler, metadataStore metadata.Store, overlayOpaqueType OverlayOpaqueType) (*Resolver, error) { resolveResultEntryTTL := time.Duration(cfg.ResolveResultEntryTTLSec) * time.Second if resolveResultEntryTTL == 0 { resolveResultEntryTTL = defaultResolveResultEntryTTLSec * time.Second } prefetchTimeout := time.Duration(cfg.PrefetchTimeoutSec) * time.Second if prefetchTimeout == 0 { prefetchTimeout = defaultPrefetchTimeoutSec * time.Second } // layerCache caches resolved layers for future use. This is useful in a use-case where // the filesystem resolves and caches all layers in an image (not only queried one) in parallel, // before they are actually queried. layerCache := cacheutil.NewTTLCache(resolveResultEntryTTL) layerCache.OnEvicted = func(key string, value interface{}) { if err := value.(*layer).close(); err != nil { logrus.WithField("key", key).WithError(err).Warnf("failed to clean up layer") return } logrus.WithField("key", key).Debugf("cleaned up layer") } // blobCache caches resolved blobs for futural use. This is especially useful when a layer // isn't eStargz/stargz (the *layer object won't be created/cached in this case). blobCache := cacheutil.NewTTLCache(resolveResultEntryTTL) blobCache.OnEvicted = func(key string, value interface{}) { if err := value.(remote.Blob).Close(); err != nil { logrus.WithField("key", key).WithError(err).Warnf("failed to clean up blob") return } logrus.WithField("key", key).Debugf("cleaned up blob") } if err := os.MkdirAll(root, 0700); err != nil { return nil, err } return &Resolver{ rootDir: root, resolver: remote.NewResolver(cfg.BlobConfig, resolveHandlers), layerCache: layerCache, blobCache: blobCache, prefetchTimeout: prefetchTimeout, backgroundTaskManager: backgroundTaskManager, config: cfg, resolveLock: new(namedmutex.NamedMutex), metadataStore: metadataStore, overlayOpaqueType: overlayOpaqueType, }, nil } func newCache(root string, cacheType string, cfg config.Config) (cache.BlobCache, error) { if cacheType == memoryCacheType { return cache.NewMemoryCache(), nil } dcc := cfg.DirectoryCacheConfig maxDataEntry := dcc.MaxLRUCacheEntry if maxDataEntry == 0 { maxDataEntry = defaultMaxLRUCacheEntry } maxFdEntry := dcc.MaxCacheFds if maxFdEntry == 0 { maxFdEntry = defaultMaxCacheFds } bufPool := &sync.Pool{ New: func() interface{} { return new(bytes.Buffer) }, } dCache, fCache := cacheutil.NewLRUCache(maxDataEntry), cacheutil.NewLRUCache(maxFdEntry) dCache.OnEvicted = func(key string, value interface{}) { value.(*bytes.Buffer).Reset() bufPool.Put(value) } fCache.OnEvicted = func(key string, value interface{}) { value.(*os.File).Close() } // create a cache on an unique directory if err := os.MkdirAll(root, 0700); err != nil { return nil, err } cachePath, err := os.MkdirTemp(root, "") if err != nil { return nil, fmt.Errorf("failed to initialize directory cache: %w", err) } return cache.NewDirectoryCache( cachePath, cache.DirectoryCacheConfig{ SyncAdd: dcc.SyncAdd, DataCache: dCache, FdCache: fCache, BufPool: bufPool, Direct: dcc.Direct, }, ) } // Resolve resolves a layer based on the passed layer blob information. func (r *Resolver) Resolve(ctx context.Context, hosts source.RegistryHosts, refspec reference.Spec, desc ocispec.Descriptor, esgzOpts ...metadata.Option) (_ Layer, retErr error) { name := refspec.String() + "/" + desc.Digest.String() // Wait if resolving this layer is already running. The result // can hopefully get from the cache. r.resolveLock.Lock(name) defer r.resolveLock.Unlock(name) ctx = log.WithLogger(ctx, log.G(ctx).WithField("src", name)) // First, try to retrieve this layer from the underlying cache. r.layerCacheMu.Lock() c, done, ok := r.layerCache.Get(name) r.layerCacheMu.Unlock() if ok { if l := c.(*layer); l.Check() == nil { log.G(ctx).Debugf("hit layer cache %q", name) return &layerRef{l, done}, nil } // Cached layer is invalid done() r.layerCacheMu.Lock() r.layerCache.Remove(name) r.layerCacheMu.Unlock() } log.G(ctx).Debugf("resolving") // Resolve the blob. blobR, err := r.resolveBlob(ctx, hosts, refspec, desc) if err != nil { return nil, fmt.Errorf("failed to resolve the blob: %w", err) } defer func() { if retErr != nil { blobR.done() } }() fsCache, err := newCache(filepath.Join(r.rootDir, "fscache"), r.config.FSCacheType, r.config) if err != nil { return nil, fmt.Errorf("failed to create fs cache: %w", err) } defer func() { if retErr != nil { fsCache.Close() } }() // Get a reader for stargz archive. // Each file's read operation is a prioritized task and all background tasks // will be stopped during the execution so this can avoid being disturbed for // NW traffic by background tasks. sr := io.NewSectionReader(readerAtFunc(func(p []byte, offset int64) (n int, err error) { r.backgroundTaskManager.DoPrioritizedTask() defer r.backgroundTaskManager.DonePrioritizedTask() return blobR.ReadAt(p, offset) }), 0, blobR.Size()) // define telemetry hooks to measure latency metrics inside estargz package telemetry := metadata.Telemetry{ GetFooterLatency: func(start time.Time) { commonmetrics.MeasureLatencyInMilliseconds(commonmetrics.StargzFooterGet, desc.Digest, start) }, GetTocLatency: func(start time.Time) { commonmetrics.MeasureLatencyInMilliseconds(commonmetrics.StargzTocGet, desc.Digest, start) }, DeserializeTocLatency: func(start time.Time) { commonmetrics.MeasureLatencyInMilliseconds(commonmetrics.DeserializeTocJSON, desc.Digest, start) }, } meta, err := r.metadataStore(sr, append(esgzOpts, metadata.WithTelemetry(&telemetry), metadata.WithDecompressors(new(zstdchunked.Decompressor)))...) if err != nil { return nil, err } vr, err := reader.NewReader(meta, fsCache, desc.Digest) if err != nil { return nil, fmt.Errorf("failed to read layer: %w", err) } // Combine layer information together and cache it. l := newLayer(r, desc, blobR, vr) r.layerCacheMu.Lock() cachedL, done2, added := r.layerCache.Add(name, l) r.layerCacheMu.Unlock() if !added { l.close() // layer already exists in the cache. discrad this. } log.G(ctx).Debugf("resolved") return &layerRef{cachedL.(*layer), done2}, nil } // resolveBlob resolves a blob based on the passed layer blob information. func (r *Resolver) resolveBlob(ctx context.Context, hosts source.RegistryHosts, refspec reference.Spec, desc ocispec.Descriptor) (_ *blobRef, retErr error) { name := refspec.String() + "/" + desc.Digest.String() // Try to retrieve the blob from the underlying cache. r.blobCacheMu.Lock() c, done, ok := r.blobCache.Get(name) r.blobCacheMu.Unlock() if ok { if blob := c.(remote.Blob); blob.Check() == nil { return &blobRef{blob, done}, nil } // invalid blob. discard this. done() r.blobCacheMu.Lock() r.blobCache.Remove(name) r.blobCacheMu.Unlock() } httpCache, err := newCache(filepath.Join(r.rootDir, "httpcache"), r.config.HTTPCacheType, r.config) if err != nil { return nil, fmt.Errorf("failed to create http cache: %w", err) } defer func() { if retErr != nil { httpCache.Close() } }() // Resolve the blob and cache the result. b, err := r.resolver.Resolve(ctx, hosts, refspec, desc, httpCache) if err != nil { return nil, fmt.Errorf("failed to resolve the source: %w", err) } r.blobCacheMu.Lock() cachedB, done, added := r.blobCache.Add(name, b) r.blobCacheMu.Unlock() if !added { b.Close() // blob already exists in the cache. discard this. } return &blobRef{cachedB.(remote.Blob), done}, nil } func newLayer( resolver *Resolver, desc ocispec.Descriptor, blob *blobRef, vr *reader.VerifiableReader, ) *layer { return &layer{ resolver: resolver, desc: desc, blob: blob, verifiableReader: vr, prefetchWaiter: newWaiter(), } } type layer struct { resolver *Resolver desc ocispec.Descriptor blob *blobRef verifiableReader *reader.VerifiableReader prefetchWaiter *waiter prefetchSize int64 prefetchSizeMu sync.Mutex r reader.Reader closed bool closedMu sync.Mutex prefetchOnce sync.Once backgroundFetchOnce sync.Once } func (l *layer) Info() Info { var readTime time.Time if l.r != nil { readTime = l.r.LastOnDemandReadTime() } return Info{ Digest: l.desc.Digest, Size: l.blob.Size(), FetchedSize: l.blob.FetchedSize(), PrefetchSize: l.prefetchedSize(), ReadTime: readTime, } } func (l *layer) prefetchedSize() int64 { l.prefetchSizeMu.Lock() sz := l.prefetchSize l.prefetchSizeMu.Unlock() return sz } func (l *layer) Check() error { if l.isClosed() { return fmt.Errorf("layer is already closed") } return l.blob.Check() } func (l *layer) Refresh(ctx context.Context, hosts source.RegistryHosts, refspec reference.Spec, desc ocispec.Descriptor) error { if l.isClosed() { return fmt.Errorf("layer is already closed") } return l.blob.Refresh(ctx, hosts, refspec, desc) } func (l *layer) Verify(tocDigest digest.Digest) (err error) { if l.isClosed() { return fmt.Errorf("layer is already closed") } if l.r != nil { return nil } l.r, err = l.verifiableReader.VerifyTOC(tocDigest) return } func (l *layer) SkipVerify() { if l.r != nil { return } l.r = l.verifiableReader.SkipVerify() } func (l *layer) Prefetch(prefetchSize int64) (err error) { l.prefetchOnce.Do(func() { ctx := context.Background() l.resolver.backgroundTaskManager.DoPrioritizedTask() defer l.resolver.backgroundTaskManager.DonePrioritizedTask() err = l.prefetch(ctx, prefetchSize) if err != nil { log.G(ctx).WithError(err).Warnf("failed to prefetch layer=%v", l.desc.Digest) return } log.G(ctx).Debug("completed to prefetch") }) return } func (l *layer) prefetch(ctx context.Context, prefetchSize int64) error { defer l.prefetchWaiter.done() // Notify the completion // Measuring the total time to complete prefetch (use defer func() because l.Info().PrefetchSize is set later) start := time.Now() defer func() { commonmetrics.WriteLatencyWithBytesLogValue(ctx, l.desc.Digest, commonmetrics.PrefetchTotal, start, commonmetrics.PrefetchSize, l.prefetchedSize()) }() if l.isClosed() { return fmt.Errorf("layer is already closed") } rootID := l.verifiableReader.Metadata().RootID() if _, _, err := l.verifiableReader.Metadata().GetChild(rootID, estargz.NoPrefetchLandmark); err == nil { // do not prefetch this layer return nil } else if id, _, err := l.verifiableReader.Metadata().GetChild(rootID, estargz.PrefetchLandmark); err == nil { offset, err := l.verifiableReader.Metadata().GetOffset(id) if err != nil { return fmt.Errorf("failed to get offset of prefetch landmark: %w", err) } // override the prefetch size with optimized value prefetchSize = offset } else if prefetchSize > l.blob.Size() { // adjust prefetch size not to exceed the whole layer size prefetchSize = l.blob.Size() } // Fetch the target range downloadStart := time.Now() err := l.blob.Cache(0, prefetchSize) commonmetrics.WriteLatencyLogValue(ctx, l.desc.Digest, commonmetrics.PrefetchDownload, downloadStart) // time to download prefetch data if err != nil { return fmt.Errorf("failed to prefetch layer: %w", err) } // Set prefetch size for metrics after prefetch completed l.prefetchSizeMu.Lock() l.prefetchSize = prefetchSize l.prefetchSizeMu.Unlock() // Cache uncompressed contents of the prefetched range decompressStart := time.Now() err = l.verifiableReader.Cache(reader.WithFilter(func(offset int64) bool { return offset < prefetchSize // Cache only prefetch target })) commonmetrics.WriteLatencyLogValue(ctx, l.desc.Digest, commonmetrics.PrefetchDecompress, decompressStart) // time to decompress prefetch data if err != nil { return fmt.Errorf("failed to cache prefetched layer: %w", err) } return nil } func (l *layer) WaitForPrefetchCompletion() error { if l.isClosed() { return fmt.Errorf("layer is already closed") } return l.prefetchWaiter.wait(l.resolver.prefetchTimeout) } func (l *layer) BackgroundFetch() (err error) { l.backgroundFetchOnce.Do(func() { ctx := context.Background() err = l.backgroundFetch(ctx) if err != nil { log.G(ctx).WithError(err).Warnf("failed to fetch whole layer=%v", l.desc.Digest) return } log.G(ctx).Debug("completed to fetch all layer data in background") }) return } func (l *layer) backgroundFetch(ctx context.Context) error { defer commonmetrics.WriteLatencyLogValue(ctx, l.desc.Digest, commonmetrics.BackgroundFetchTotal, time.Now()) if l.isClosed() { return fmt.Errorf("layer is already closed") } br := io.NewSectionReader(readerAtFunc(func(p []byte, offset int64) (retN int, retErr error) { l.resolver.backgroundTaskManager.InvokeBackgroundTask(func(ctx context.Context) { // Measuring the time to download background fetch data (in milliseconds) defer commonmetrics.MeasureLatencyInMilliseconds(commonmetrics.BackgroundFetchDownload, l.Info().Digest, time.Now()) // time to download background fetch data retN, retErr = l.blob.ReadAt( p, offset, remote.WithContext(ctx), // Make cancellable remote.WithCacheOpts(cache.Direct()), // Do not pollute mem cache ) }, 120*time.Second) return }), 0, l.blob.Size()) defer commonmetrics.WriteLatencyLogValue(ctx, l.desc.Digest, commonmetrics.BackgroundFetchDecompress, time.Now()) // time to decompress background fetch data (in milliseconds) return l.verifiableReader.Cache( reader.WithReader(br), // Read contents in background reader.WithCacheOpts(cache.Direct()), // Do not pollute mem cache ) } func (l *layerRef) Done() { l.done() } func (l *layer) RootNode(baseInode uint32) (fusefs.InodeEmbedder, error) { if l.isClosed() { return nil, fmt.Errorf("layer is already closed") } if l.r == nil { return nil, fmt.Errorf("layer hasn't been verified yet") } 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) { return l.blob.ReadAt(p, offset, opts...) } func (l *layer) close() error { l.closedMu.Lock() defer l.closedMu.Unlock() if l.closed { return nil } l.closed = true defer l.blob.done() // Close reader first, then close the blob l.verifiableReader.Close() if l.r != nil { return l.r.Close() } return nil } func (l *layer) isClosed() bool { l.closedMu.Lock() closed := l.closed l.closedMu.Unlock() return closed } // blobRef is a reference to the blob in the cache. Calling `done` decreases the reference counter // of this blob in the underlying cache. When nobody refers to the blob in the cache, resources bound // to this blob will be discarded. type blobRef struct { remote.Blob done func() } // layerRef is a reference to the layer in the cache. Calling `Done` or `done` decreases the // reference counter of this blob in the underlying cache. When nobody refers to the layer in the // cache, resources bound to this layer will be discarded. type layerRef struct { *layer done func() } func newWaiter() *waiter { return &waiter{ completionCond: sync.NewCond(&sync.Mutex{}), } } type waiter struct { isDone bool isDoneMu sync.Mutex completionCond *sync.Cond } func (w *waiter) done() { w.isDoneMu.Lock() w.isDone = true w.isDoneMu.Unlock() w.completionCond.Broadcast() } func (w *waiter) wait(timeout time.Duration) error { wait := func() <-chan struct{} { ch := make(chan struct{}) go func() { w.isDoneMu.Lock() isDone := w.isDone w.isDoneMu.Unlock() w.completionCond.L.Lock() if !isDone { w.completionCond.Wait() } w.completionCond.L.Unlock() ch <- struct{}{} }() return ch } select { case <-time.After(timeout): w.isDoneMu.Lock() w.isDone = true w.isDoneMu.Unlock() w.completionCond.Broadcast() return fmt.Errorf("timeout(%v)", timeout) case <-wait(): return nil } } type readerAtFunc func([]byte, int64) (int, error) func (f readerAtFunc) ReadAt(p []byte, offset int64) (int, error) { return f(p, offset) } stargz-snapshotter-0.12.0/fs/layer/layer_test.go000066400000000000000000000027101426301527400217060ustar00rootroot00000000000000/* 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. */ /* Copyright 2019 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the NOTICE.md file. */ package layer import ( "testing" "time" memorymetadata "github.com/containerd/stargz-snapshotter/metadata/memory" ) func TestLayer(t *testing.T) { TestSuiteLayer(t, memorymetadata.NewReader) } func TestWaiter(t *testing.T) { var ( w = newWaiter() waitTime = time.Second startTime = time.Now() doneTime time.Time done = make(chan struct{}) ) go func() { defer close(done) if err := w.wait(10 * time.Second); err != nil { t.Errorf("failed to wait: %v", err) return } doneTime = time.Now() }() time.Sleep(waitTime) w.done() <-done if doneTime.Sub(startTime) < waitTime { t.Errorf("wait time is too short: %v; want %v", doneTime.Sub(startTime), waitTime) } } stargz-snapshotter-0.12.0/fs/layer/node.go000066400000000000000000000512671426301527400204730ustar00rootroot00000000000000/* 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. */ /* Copyright 2019 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the NOTICE.md file. */ package layer import ( "bytes" "context" "encoding/json" "errors" "fmt" "io" "os" "sort" "strings" "sync" "syscall" "time" "github.com/containerd/containerd/log" "github.com/containerd/stargz-snapshotter/estargz" commonmetrics "github.com/containerd/stargz-snapshotter/fs/metrics/common" "github.com/containerd/stargz-snapshotter/fs/reader" "github.com/containerd/stargz-snapshotter/fs/remote" "github.com/containerd/stargz-snapshotter/metadata" fusefs "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fuse" digest "github.com/opencontainers/go-digest" "github.com/sirupsen/logrus" "golang.org/x/sys/unix" ) const ( blockSize = 4096 whiteoutPrefix = ".wh." whiteoutOpaqueDir = whiteoutPrefix + whiteoutPrefix + ".opq" opaqueXattrValue = "y" stateDirName = ".stargz-snapshotter" statFileMode = syscall.S_IFREG | 0400 // -r-------- stateDirMode = syscall.S_IFDIR | 0500 // dr-x------ ) type OverlayOpaqueType int const ( OverlayOpaqueAll OverlayOpaqueType = iota OverlayOpaqueTrusted OverlayOpaqueUser ) var opaqueXattrs = map[OverlayOpaqueType][]string{ OverlayOpaqueAll: {"trusted.overlay.opaque", "user.overlay.opaque"}, OverlayOpaqueTrusted: {"trusted.overlay.opaque"}, OverlayOpaqueUser: {"user.overlay.opaque"}, } func newNode(layerDgst digest.Digest, r reader.Reader, blob remote.Blob, baseInode uint32, opaque OverlayOpaqueType) (fusefs.InodeEmbedder, error) { rootID := r.Metadata().RootID() rootAttr, err := r.Metadata().GetAttr(rootID) if err != nil { return nil, err } opq, ok := opaqueXattrs[opaque] if !ok { return nil, fmt.Errorf("Unknown overlay opaque type") } ffs := &fs{ r: r, layerDigest: layerDgst, baseInode: baseInode, rootID: rootID, opaqueXattrs: opq, } ffs.s = ffs.newState(layerDgst, blob) return &node{ id: rootID, attr: rootAttr, fs: ffs, }, nil } // fs contains global metadata used by nodes type fs struct { r reader.Reader s *state layerDigest digest.Digest baseInode uint32 rootID uint32 opaqueXattrs []string } func (fs *fs) inodeOfState() uint64 { return (uint64(fs.baseInode) << 32) | 1 // reserved } func (fs *fs) inodeOfStatFile() uint64 { return (uint64(fs.baseInode) << 32) | 2 // reserved } func (fs *fs) inodeOfID(id uint32) (uint64, error) { // 0 is reserved by go-fuse 1 and 2 are reserved by the state dir if id > ^uint32(0)-3 { return 0, fmt.Errorf("too many inodes") } return (uint64(fs.baseInode) << 32) | uint64(3+id), nil } // node is a filesystem inode abstraction. type node struct { fusefs.Inode fs *fs id uint32 attr metadata.Attr ents []fuse.DirEntry entsCached bool } func (n *node) isRootNode() bool { return n.id == n.fs.rootID } func (n *node) isOpaque() bool { if _, _, err := n.fs.r.Metadata().GetChild(n.id, whiteoutOpaqueDir); err == nil { return true } return false } var _ = (fusefs.InodeEmbedder)((*node)(nil)) var _ = (fusefs.NodeReaddirer)((*node)(nil)) func (n *node) Readdir(ctx context.Context) (fusefs.DirStream, syscall.Errno) { ents, errno := n.readdir() if errno != 0 { return nil, errno } return fusefs.NewListDirStream(ents), 0 } func (n *node) readdir() ([]fuse.DirEntry, syscall.Errno) { // Measure how long node_readdir operation takes (in microseconds). start := time.Now() // set start time defer commonmetrics.MeasureLatencyInMicroseconds(commonmetrics.NodeReaddir, n.fs.layerDigest, start) if n.entsCached { return n.ents, 0 } isRoot := n.isRootNode() var ents []fuse.DirEntry whiteouts := map[string]uint32{} normalEnts := map[string]bool{} var lastErr error if err := n.fs.r.Metadata().ForeachChild(n.id, func(name string, id uint32, mode os.FileMode) bool { // We don't want to show prefetch landmarks in "/". if isRoot && (name == estargz.PrefetchLandmark || name == estargz.NoPrefetchLandmark) { return true } // We don't want to show whiteouts. if strings.HasPrefix(name, whiteoutPrefix) { if name == whiteoutOpaqueDir { return true } // Add the overlayfs-compiant whiteout later. whiteouts[name] = id return true } // This is a normal entry. normalEnts[name] = true ino, err := n.fs.inodeOfID(id) if err != nil { lastErr = err return false } ents = append(ents, fuse.DirEntry{ Mode: fileModeToSystemMode(mode), Name: name, Ino: ino, }) return true }); err != nil || lastErr != nil { n.fs.s.report(fmt.Errorf("node.Readdir: err = %v; lastErr = %v", err, lastErr)) return nil, syscall.EIO } // Append whiteouts if no entry replaces the target entry in the lower layer. for w, id := range whiteouts { if !normalEnts[w[len(whiteoutPrefix):]] { ino, err := n.fs.inodeOfID(id) if err != nil { n.fs.s.report(fmt.Errorf("node.Readdir: err = %v; lastErr = %v", err, lastErr)) return nil, syscall.EIO } ents = append(ents, fuse.DirEntry{ Mode: syscall.S_IFCHR, Name: w[len(whiteoutPrefix):], Ino: ino, }) } } // Avoid undeterministic order of entries on each call sort.Slice(ents, func(i, j int) bool { return ents[i].Name < ents[j].Name }) n.ents, n.entsCached = ents, true // cache it return ents, 0 } var _ = (fusefs.NodeLookuper)((*node)(nil)) func (n *node) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fusefs.Inode, syscall.Errno) { isRoot := n.isRootNode() // We don't want to show prefetch landmarks in "/". if isRoot && (name == estargz.PrefetchLandmark || name == estargz.NoPrefetchLandmark) { return nil, syscall.ENOENT } // We don't want to show whiteouts. if strings.HasPrefix(name, whiteoutPrefix) { return nil, syscall.ENOENT } // state directory if isRoot && name == stateDirName { return n.NewInode(ctx, n.fs.s, n.fs.stateToAttr(&out.Attr)), 0 } // lookup on memory nodes if cn := n.GetChild(name); cn != nil { switch tn := cn.Operations().(type) { case *node: ino, err := n.fs.inodeOfID(tn.id) if err != nil { n.fs.s.report(fmt.Errorf("node.Lookup: %v", err)) return nil, syscall.EIO } entryToAttr(ino, tn.attr, &out.Attr) case *whiteout: ino, err := n.fs.inodeOfID(tn.id) if err != nil { n.fs.s.report(fmt.Errorf("node.Lookup: %v", err)) return nil, syscall.EIO } entryToAttr(ino, tn.attr, &out.Attr) default: n.fs.s.report(fmt.Errorf("node.Lookup: uknown node type detected")) return nil, syscall.EIO } return cn, 0 } // early return if this entry doesn't exist if n.entsCached { var found bool for _, e := range n.ents { if e.Name == name { found = true } } if !found { return nil, syscall.ENOENT } } id, ce, err := n.fs.r.Metadata().GetChild(n.id, name) if err != nil { // If the entry exists as a whiteout, show an overlayfs-styled whiteout node. if whID, wh, err := n.fs.r.Metadata().GetChild(n.id, fmt.Sprintf("%s%s", whiteoutPrefix, name)); err == nil { ino, err := n.fs.inodeOfID(whID) if err != nil { n.fs.s.report(fmt.Errorf("node.Lookup: %v", err)) return nil, syscall.EIO } return n.NewInode(ctx, &whiteout{ id: whID, fs: n.fs, attr: wh, }, entryToWhAttr(ino, wh, &out.Attr)), 0 } n.readdir() // This code path is very expensive. Cache child entries here so that the next call don't reach here. return nil, syscall.ENOENT } ino, err := n.fs.inodeOfID(id) if err != nil { n.fs.s.report(fmt.Errorf("node.Lookup: %v", err)) return nil, syscall.EIO } return n.NewInode(ctx, &node{ id: id, fs: n.fs, attr: ce, }, entryToAttr(ino, ce, &out.Attr)), 0 } var _ = (fusefs.NodeOpener)((*node)(nil)) func (n *node) Open(ctx context.Context, flags uint32) (fh fusefs.FileHandle, fuseFlags uint32, errno syscall.Errno) { ra, err := n.fs.r.OpenFile(n.id) if err != nil { n.fs.s.report(fmt.Errorf("node.Open: %v", err)) return nil, 0, syscall.EIO } return &file{ n: n, ra: ra, }, fuse.FOPEN_KEEP_CACHE, 0 } var _ = (fusefs.NodeGetattrer)((*node)(nil)) func (n *node) Getattr(ctx context.Context, f fusefs.FileHandle, out *fuse.AttrOut) syscall.Errno { ino, err := n.fs.inodeOfID(n.id) if err != nil { n.fs.s.report(fmt.Errorf("node.Getattr: %v", err)) return syscall.EIO } entryToAttr(ino, n.attr, &out.Attr) return 0 } var _ = (fusefs.NodeGetxattrer)((*node)(nil)) func (n *node) Getxattr(ctx context.Context, attr string, dest []byte) (uint32, syscall.Errno) { ent := n.attr opq := n.isOpaque() for _, opaqueXattr := range n.fs.opaqueXattrs { if attr == opaqueXattr && opq { // This node is an opaque directory so give overlayfs-compliant indicator. if len(dest) < len(opaqueXattrValue) { return uint32(len(opaqueXattrValue)), syscall.ERANGE } return uint32(copy(dest, opaqueXattrValue)), 0 } } if v, ok := ent.Xattrs[attr]; ok { if len(dest) < len(v) { return uint32(len(v)), syscall.ERANGE } return uint32(copy(dest, v)), 0 } return 0, syscall.ENODATA } var _ = (fusefs.NodeListxattrer)((*node)(nil)) func (n *node) Listxattr(ctx context.Context, dest []byte) (uint32, syscall.Errno) { ent := n.attr opq := n.isOpaque() var attrs []byte if opq { // This node is an opaque directory so add overlayfs-compliant indicator. for _, opaqueXattr := range n.fs.opaqueXattrs { attrs = append(attrs, []byte(opaqueXattr+"\x00")...) } } for k := range ent.Xattrs { attrs = append(attrs, []byte(k+"\x00")...) } if len(dest) < len(attrs) { return uint32(len(attrs)), syscall.ERANGE } return uint32(copy(dest, attrs)), 0 } var _ = (fusefs.NodeReadlinker)((*node)(nil)) func (n *node) Readlink(ctx context.Context) ([]byte, syscall.Errno) { ent := n.attr return []byte(ent.LinkName), 0 } var _ = (fusefs.NodeStatfser)((*node)(nil)) func (n *node) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno { defaultStatfs(out) return 0 } // file is a file abstraction which implements file handle in go-fuse. type file struct { n *node ra io.ReaderAt } var _ = (fusefs.FileReader)((*file)(nil)) func (f *file) Read(ctx context.Context, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) { defer commonmetrics.MeasureLatencyInMicroseconds(commonmetrics.ReadOnDemand, f.n.fs.layerDigest, time.Now()) // measure time for on-demand file reads (in microseconds) defer commonmetrics.IncOperationCount(commonmetrics.OnDemandReadAccessCount, f.n.fs.layerDigest) // increment the counter for on-demand file accesses n, err := f.ra.ReadAt(dest, off) if err != nil && err != io.EOF { f.n.fs.s.report(fmt.Errorf("file.Read: %v", err)) return nil, syscall.EIO } return fuse.ReadResultData(dest[:n]), 0 } var _ = (fusefs.FileGetattrer)((*file)(nil)) func (f *file) Getattr(ctx context.Context, out *fuse.AttrOut) syscall.Errno { ino, err := f.n.fs.inodeOfID(f.n.id) if err != nil { f.n.fs.s.report(fmt.Errorf("file.Getattr: %v", err)) return syscall.EIO } entryToAttr(ino, f.n.attr, &out.Attr) return 0 } // whiteout is a whiteout abstraction compliant to overlayfs. type whiteout struct { fusefs.Inode id uint32 fs *fs attr metadata.Attr } var _ = (fusefs.NodeGetattrer)((*whiteout)(nil)) func (w *whiteout) Getattr(ctx context.Context, f fusefs.FileHandle, out *fuse.AttrOut) syscall.Errno { ino, err := w.fs.inodeOfID(w.id) if err != nil { w.fs.s.report(fmt.Errorf("whiteout.Getattr: %v", err)) return syscall.EIO } entryToWhAttr(ino, w.attr, &out.Attr) return 0 } var _ = (fusefs.NodeStatfser)((*whiteout)(nil)) func (w *whiteout) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno { defaultStatfs(out) return 0 } // newState provides new state directory node. // It creates statFile at the same time to give it stable inode number. func (fs *fs) newState(layerDigest digest.Digest, blob remote.Blob) *state { return &state{ statFile: &statFile{ name: layerDigest.String() + ".json", statJSON: statJSON{ Digest: layerDigest.String(), Size: blob.Size(), }, blob: blob, fs: fs, }, fs: fs, } } // state is a directory which contain a "state file" of this layer aiming to // observability. This filesystem uses it to report something(e.g. error) to // the clients(e.g. Kubernetes's livenessProbe). // This directory has mode "dr-x------ root root". type state struct { fusefs.Inode statFile *statFile fs *fs } var _ = (fusefs.NodeReaddirer)((*state)(nil)) func (s *state) Readdir(ctx context.Context) (fusefs.DirStream, syscall.Errno) { return fusefs.NewListDirStream([]fuse.DirEntry{ { Mode: statFileMode, Name: s.statFile.name, Ino: s.fs.inodeOfStatFile(), }, }), 0 } var _ = (fusefs.NodeLookuper)((*state)(nil)) func (s *state) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fusefs.Inode, syscall.Errno) { if name != s.statFile.name { return nil, syscall.ENOENT } attr, errno := s.statFile.attr(&out.Attr) if errno != 0 { return nil, errno } return s.NewInode(ctx, s.statFile, attr), 0 } var _ = (fusefs.NodeGetattrer)((*state)(nil)) func (s *state) Getattr(ctx context.Context, f fusefs.FileHandle, out *fuse.AttrOut) syscall.Errno { s.fs.stateToAttr(&out.Attr) return 0 } var _ = (fusefs.NodeStatfser)((*state)(nil)) func (s *state) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno { defaultStatfs(out) return 0 } func (s *state) report(err error) { s.statFile.report(err) } type statJSON struct { Error string `json:"error,omitempty"` Digest string `json:"digest"` // URL is excluded for potential security reason Size int64 `json:"size"` FetchedSize int64 `json:"fetchedSize"` FetchedPercent float64 `json:"fetchedPercent"` // Fetched / Size * 100.0 } // statFile is a file which contain something to be reported from this layer. // This filesystem uses statFile.report() to report something(e.g. error) to // the clients(e.g. Kubernetes's livenessProbe). // This file has mode "-r-------- root root". type statFile struct { fusefs.Inode name string blob remote.Blob statJSON statJSON mu sync.Mutex fs *fs } var _ = (fusefs.NodeOpener)((*statFile)(nil)) func (sf *statFile) Open(ctx context.Context, flags uint32) (fh fusefs.FileHandle, fuseFlags uint32, errno syscall.Errno) { return nil, 0, 0 } var _ = (fusefs.NodeReader)((*statFile)(nil)) func (sf *statFile) Read(ctx context.Context, f fusefs.FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) { sf.mu.Lock() defer sf.mu.Unlock() st, err := sf.updateStatUnlocked() if err != nil { return nil, syscall.EIO } n, err := bytes.NewReader(st).ReadAt(dest, off) if err != nil && err != io.EOF { return nil, syscall.EIO } return fuse.ReadResultData(dest[:n]), 0 } var _ = (fusefs.NodeGetattrer)((*statFile)(nil)) func (sf *statFile) Getattr(ctx context.Context, f fusefs.FileHandle, out *fuse.AttrOut) syscall.Errno { _, errno := sf.attr(&out.Attr) return errno } var _ = (fusefs.NodeStatfser)((*statFile)(nil)) func (sf *statFile) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno { defaultStatfs(out) return 0 } // logContents puts the contents of statFile in the log // to keep that information accessible for troubleshooting. // The entries naming is kept to be consistend with the field naming in statJSON. func (sf *statFile) logContents() { ctx := context.Background() log.G(ctx).WithFields(logrus.Fields{ "digest": sf.statJSON.Digest, "size": sf.statJSON.Size, "fetchedSize": sf.statJSON.FetchedSize, "fetchedPercent": sf.statJSON.FetchedPercent, }).WithError(errors.New(sf.statJSON.Error)).Error("statFile error") } func (sf *statFile) report(err error) { sf.mu.Lock() defer sf.mu.Unlock() sf.statJSON.Error = err.Error() sf.logContents() } func (sf *statFile) attr(out *fuse.Attr) (fusefs.StableAttr, syscall.Errno) { sf.mu.Lock() defer sf.mu.Unlock() st, err := sf.updateStatUnlocked() if err != nil { return fusefs.StableAttr{}, syscall.EIO } return sf.fs.statFileToAttr(uint64(len(st)), out), 0 } func (sf *statFile) updateStatUnlocked() ([]byte, error) { sf.statJSON.FetchedSize = sf.blob.FetchedSize() sf.statJSON.FetchedPercent = float64(sf.statJSON.FetchedSize) / float64(sf.statJSON.Size) * 100.0 j, err := json.Marshal(&sf.statJSON) if err != nil { return nil, err } j = append(j, []byte("\n")...) return j, nil } // entryToAttr converts metadata.Attr to go-fuse's Attr. func entryToAttr(ino uint64, e metadata.Attr, out *fuse.Attr) fusefs.StableAttr { out.Ino = ino out.Size = uint64(e.Size) if e.Mode&os.ModeSymlink != 0 { out.Size = uint64(len(e.LinkName)) } out.Blksize = blockSize out.Blocks = out.Size / uint64(out.Blksize) if out.Size%uint64(out.Blksize) > 0 { out.Blocks++ } mtime := e.ModTime out.SetTimes(nil, &mtime, nil) out.Mode = fileModeToSystemMode(e.Mode) out.Owner = fuse.Owner{Uid: uint32(e.UID), Gid: uint32(e.GID)} out.Rdev = uint32(unix.Mkdev(uint32(e.DevMajor), uint32(e.DevMinor))) out.Nlink = uint32(e.NumLink) if out.Nlink == 0 { out.Nlink = 1 // zero "NumLink" means one. } out.Padding = 0 // TODO return fusefs.StableAttr{ Mode: out.Mode, Ino: out.Ino, // NOTE: The inode number is unique throughout the lifetime of // this filesystem so we don't consider about generation at this // moment. } } // entryToWhAttr converts metadata.Attr to go-fuse's Attr of whiteouts. func entryToWhAttr(ino uint64, e metadata.Attr, out *fuse.Attr) fusefs.StableAttr { out.Ino = ino out.Size = 0 out.Blksize = blockSize out.Blocks = 0 mtime := e.ModTime out.SetTimes(nil, &mtime, nil) out.Mode = syscall.S_IFCHR out.Owner = fuse.Owner{Uid: 0, Gid: 0} out.Rdev = uint32(unix.Mkdev(0, 0)) out.Nlink = 1 out.Padding = 0 // TODO return fusefs.StableAttr{ Mode: out.Mode, Ino: out.Ino, // NOTE: The inode number is unique throughout the lifetime of // this filesystem so we don't consider about generation at this // moment. } } // stateToAttr converts state directory to go-fuse's Attr. func (fs *fs) stateToAttr(out *fuse.Attr) fusefs.StableAttr { out.Ino = fs.inodeOfState() out.Size = 0 out.Blksize = blockSize out.Blocks = 0 out.Nlink = 1 // root can read and open it (dr-x------ root root). out.Mode = stateDirMode out.Owner = fuse.Owner{Uid: 0, Gid: 0} // dummy out.Mtime = 0 out.Mtimensec = 0 out.Rdev = 0 out.Padding = 0 return fusefs.StableAttr{ Mode: out.Mode, Ino: out.Ino, // NOTE: The inode number is unique throughout the lifetime of // this filesystem so we don't consider about generation at this // moment. } } // statFileToAttr converts stat file to go-fuse's Attr. // func statFileToAttr(id uint64, sf *statFile, size uint64, out *fuse.Attr) fusefs.StableAttr { func (fs *fs) statFileToAttr(size uint64, out *fuse.Attr) fusefs.StableAttr { out.Ino = fs.inodeOfStatFile() out.Size = size out.Blksize = blockSize out.Blocks = out.Size / uint64(out.Blksize) out.Nlink = 1 // Root can read it ("-r-------- root root"). out.Mode = statFileMode out.Owner = fuse.Owner{Uid: 0, Gid: 0} // dummy out.Mtime = 0 out.Mtimensec = 0 out.Rdev = 0 out.Padding = 0 return fusefs.StableAttr{ Mode: out.Mode, Ino: out.Ino, // NOTE: The inode number is unique throughout the lifetime of // this filesystem so we don't consider about generation at this // moment. } } func fileModeToSystemMode(m os.FileMode) uint32 { // Permission bits res := uint32(m & os.ModePerm) // File type bits switch m & os.ModeType { case os.ModeDevice: res |= syscall.S_IFBLK case os.ModeDevice | os.ModeCharDevice: res |= syscall.S_IFCHR case os.ModeDir: res |= syscall.S_IFDIR case os.ModeNamedPipe: res |= syscall.S_IFIFO case os.ModeSymlink: res |= syscall.S_IFLNK case os.ModeSocket: res |= syscall.S_IFSOCK default: // regular file. res |= syscall.S_IFREG } // suid, sgid, sticky bits if m&os.ModeSetuid != 0 { res |= syscall.S_ISUID } if m&os.ModeSetgid != 0 { res |= syscall.S_ISGID } if m&os.ModeSticky != 0 { res |= syscall.S_ISVTX } return res } func defaultStatfs(stat *fuse.StatfsOut) { // http://man7.org/linux/man-pages/man2/statfs.2.html stat.Blocks = 0 // dummy stat.Bfree = 0 stat.Bavail = 0 stat.Files = 0 // dummy stat.Ffree = 0 stat.Bsize = blockSize stat.NameLen = 1<<32 - 1 stat.Frsize = blockSize stat.Padding = 0 stat.Spare = [6]uint32{} } stargz-snapshotter-0.12.0/fs/layer/testutil.go000066400000000000000000000631421426301527400214160ustar00rootroot00000000000000/* 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. */ /* Copyright 2019 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the NOTICE.md file. */ package layer import ( "bytes" "context" "crypto/sha256" "encoding/json" "fmt" "io" "math/rand" "net/http" "os" "path" "path/filepath" "strings" "syscall" "testing" "time" "github.com/containerd/containerd/reference" "github.com/containerd/stargz-snapshotter/cache" "github.com/containerd/stargz-snapshotter/estargz" "github.com/containerd/stargz-snapshotter/fs/reader" "github.com/containerd/stargz-snapshotter/fs/remote" "github.com/containerd/stargz-snapshotter/fs/source" "github.com/containerd/stargz-snapshotter/metadata" "github.com/containerd/stargz-snapshotter/task" "github.com/containerd/stargz-snapshotter/util/testutil" fusefs "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fuse" digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "golang.org/x/sys/unix" ) const ( sampleChunkSize = 3 sampleData1 = "0123456789" sampleData2 = "abcdefghij" ) func TestSuiteLayer(t *testing.T, store metadata.Store) { testPrefetch(t, store) testNodeRead(t, store) testExistence(t, store) } var testStateLayerDigest = digest.FromString("dummy") func testPrefetch(t *testing.T, factory metadata.Store) { defaultPrefetchSize := int64(10000) landmarkPosition := func(t *testing.T, l *layer) int64 { if l.r == nil { t.Fatalf("layer hasn't been verified yet") } if id, _, err := l.r.Metadata().GetChild(l.r.Metadata().RootID(), estargz.PrefetchLandmark); err == nil { offset, err := l.r.Metadata().GetOffset(id) if err != nil { t.Fatalf("failed to get offset of prefetch landmark") } return offset } return defaultPrefetchSize } tests := []struct { name string in []testutil.TarEntry wantNum int // number of chunks wanted in the cache wants []string // filenames to compare prefetchSize func(*testing.T, *layer) int64 prioritizedFiles []string }{ { name: "no_prefetch", in: []testutil.TarEntry{ testutil.File("foo.txt", sampleData1), }, wantNum: 0, prioritizedFiles: nil, }, { name: "prefetch", in: []testutil.TarEntry{ testutil.File("foo.txt", sampleData1), testutil.File("bar.txt", sampleData2), }, wantNum: chunkNum(sampleData1), wants: []string{"foo.txt"}, prefetchSize: landmarkPosition, prioritizedFiles: []string{"foo.txt"}, }, { name: "with_dir", in: []testutil.TarEntry{ testutil.Dir("foo/"), testutil.File("foo/bar.txt", sampleData1), testutil.Dir("buz/"), testutil.File("buz/buzbuz.txt", sampleData2), }, wantNum: chunkNum(sampleData1), wants: []string{"foo/bar.txt"}, prefetchSize: landmarkPosition, prioritizedFiles: []string{"foo/", "foo/bar.txt"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { sr, dgst, err := testutil.BuildEStargz(tt.in, testutil.WithEStargzOptions( estargz.WithChunkSize(sampleChunkSize), estargz.WithPrioritizedFiles(tt.prioritizedFiles), )) if err != nil { t.Fatalf("failed to build eStargz: %v", err) } blob := newBlob(sr) mcache := cache.NewMemoryCache() mr, err := factory(sr) if err != nil { t.Fatalf("failed to create metadata reader: %v", err) } defer mr.Close() vr, err := reader.NewReader(mr, mcache, digest.FromString("")) if err != nil { t.Fatalf("failed to create reader: %v", err) } l := newLayer( &Resolver{ prefetchTimeout: time.Second, backgroundTaskManager: task.NewBackgroundTaskManager(10, 5*time.Second), }, ocispec.Descriptor{Digest: testStateLayerDigest}, &blobRef{blob, func() {}}, vr, ) if err := l.Verify(dgst); err != nil { t.Errorf("failed to verify reader: %v", err) return } prefetchSize := int64(0) if tt.prefetchSize != nil { prefetchSize = tt.prefetchSize(t, l) } if err := l.Prefetch(defaultPrefetchSize); err != nil { t.Errorf("failed to prefetch: %v", err) return } if blob.calledPrefetchOffset != 0 { t.Errorf("invalid prefetch offset %d; want %d", blob.calledPrefetchOffset, 0) } if blob.calledPrefetchSize != prefetchSize { t.Errorf("invalid prefetch size %d; want %d", blob.calledPrefetchSize, prefetchSize) } if cLen := len(mcache.(*cache.MemoryCache).Membuf); tt.wantNum != cLen { t.Errorf("number of chunks in the cache %d; want %d: %v", cLen, tt.wantNum, err) return } lr := l.r if lr == nil { t.Fatalf("failed to get reader from layer: %v", err) } for _, file := range tt.wants { id, err := lookup(lr.Metadata(), file) if err != nil { t.Fatalf("failed to lookup %q: %v", file, err) } e, err := lr.Metadata().GetAttr(id) if err != nil { t.Fatalf("failed to get attr of %q: %v", file, err) } wantFile, err := lr.OpenFile(id) if err != nil { t.Fatalf("failed to open file %q", file) } blob.readCalled = false if _, err := io.Copy(io.Discard, io.NewSectionReader(wantFile, 0, e.Size)); err != nil { t.Fatalf("failed to read file %q", file) } if blob.readCalled { t.Errorf("chunks of file %q aren't cached", file) return } } }) } } func lookup(r metadata.Reader, name string) (uint32, error) { name = strings.TrimPrefix(path.Clean("/"+name), "/") if name == "" { return r.RootID(), nil } dir, base := filepath.Split(name) pid, err := lookup(r, dir) if err != nil { return 0, err } id, _, err := r.GetChild(pid, base) return id, err } func chunkNum(data string) int { return (len(data)-1)/sampleChunkSize + 1 } func newBlob(sr *io.SectionReader) *sampleBlob { return &sampleBlob{ r: sr, } } type sampleBlob struct { r *io.SectionReader readCalled bool calledPrefetchOffset int64 calledPrefetchSize int64 } func (sb *sampleBlob) Authn(tr http.RoundTripper) (http.RoundTripper, error) { return nil, nil } func (sb *sampleBlob) Check() error { return nil } func (sb *sampleBlob) Size() int64 { return sb.r.Size() } func (sb *sampleBlob) FetchedSize() int64 { return 0 } func (sb *sampleBlob) ReadAt(p []byte, offset int64, opts ...remote.Option) (int, error) { sb.readCalled = true return sb.r.ReadAt(p, offset) } func (sb *sampleBlob) Cache(offset int64, size int64, option ...remote.Option) error { sb.calledPrefetchOffset = offset sb.calledPrefetchSize = size return nil } func (sb *sampleBlob) Refresh(ctx context.Context, hosts source.RegistryHosts, refspec reference.Spec, desc ocispec.Descriptor) error { return nil } func (sb *sampleBlob) Close() error { return nil } const ( sampleMiddleOffset = sampleChunkSize / 2 lastChunkOffset1 = sampleChunkSize * (int64(len(sampleData1)) / sampleChunkSize) ) func testNodeRead(t *testing.T, factory metadata.Store) { sizeCond := map[string]int64{ "single_chunk": sampleChunkSize - sampleMiddleOffset, "multi_chunks": sampleChunkSize + sampleMiddleOffset, } innerOffsetCond := map[string]int64{ "at_top": 0, "at_middle": sampleMiddleOffset, } baseOffsetCond := map[string]int64{ "of_1st_chunk": sampleChunkSize * 0, "of_2nd_chunk": sampleChunkSize * 1, "of_last_chunk": lastChunkOffset1, } fileSizeCond := map[string]int64{ "in_1_chunk_file": sampleChunkSize * 1, "in_2_chunks_file": sampleChunkSize * 2, "in_max_size_file": int64(len(sampleData1)), } for sn, size := range sizeCond { for in, innero := range innerOffsetCond { for bo, baseo := range baseOffsetCond { for fn, filesize := range fileSizeCond { t.Run(fmt.Sprintf("reading_%s_%s_%s_%s", sn, in, bo, fn), func(t *testing.T) { if filesize > int64(len(sampleData1)) { t.Fatal("sample file size is larger than sample data") } wantN := size offset := baseo + innero if remain := filesize - offset; remain < wantN { if wantN = remain; wantN < 0 { wantN = 0 } } // use constant string value as a data source. want := strings.NewReader(sampleData1) // data we want to get. wantData := make([]byte, wantN) _, err := want.ReadAt(wantData, offset) if err != nil && err != io.EOF { t.Fatalf("want.ReadAt (offset=%d,size=%d): %v", offset, wantN, err) } // data we get from the file node. f, closeFn := makeNodeReader(t, []byte(sampleData1)[:filesize], sampleChunkSize, factory) defer closeFn() tmpbuf := make([]byte, size) // fuse library can request bigger than remain rr, errno := f.Read(context.Background(), tmpbuf, offset) if errno != 0 { t.Errorf("failed to read off=%d, size=%d, filesize=%d: %v", offset, size, filesize, err) return } if rsize := rr.Size(); int64(rsize) != wantN { t.Errorf("read size: %d; want: %d; passed %d", rsize, wantN, size) return } tmpbuf = make([]byte, len(tmpbuf)) respData, fs := rr.Bytes(tmpbuf) if fs != fuse.OK { t.Errorf("failed to read result data for off=%d, size=%d, filesize=%d: %v", offset, size, filesize, err) } if !bytes.Equal(wantData, respData) { t.Errorf("off=%d, filesize=%d; read data{size=%d,data=%q}; want (size=%d,data=%q)", offset, filesize, len(respData), string(respData), wantN, string(wantData)) return } }) } } } } } func makeNodeReader(t *testing.T, contents []byte, chunkSize int, factory metadata.Store) (_ *file, closeFn func() error) { testName := "test" sr, _, err := testutil.BuildEStargz( []testutil.TarEntry{testutil.File(testName, string(contents))}, testutil.WithEStargzOptions(estargz.WithChunkSize(chunkSize)), ) if err != nil { t.Fatalf("failed to build sample eStargz: %v", err) } r, err := factory(sr) if err != nil { t.Fatalf("failed to create reader: %v", err) } rootNode := getRootNode(t, r, OverlayOpaqueAll) var eo fuse.EntryOut inode, errno := rootNode.Lookup(context.Background(), testName, &eo) if errno != 0 { r.Close() t.Fatalf("failed to lookup test node; errno: %v", errno) } f, _, errno := inode.Operations().(fusefs.NodeOpener).Open(context.Background(), 0) if errno != 0 { r.Close() t.Fatalf("failed to open test file; errno: %v", errno) } return f.(*file), r.Close } func testExistence(t *testing.T, factory metadata.Store) { for _, o := range []OverlayOpaqueType{OverlayOpaqueAll, OverlayOpaqueTrusted, OverlayOpaqueUser} { testExistenceWithOpaque(t, factory, o) } } func testExistenceWithOpaque(t *testing.T, factory metadata.Store, opaque OverlayOpaqueType) { hasOpaque := func(entry string) check { return func(t *testing.T, root *node) { for _, k := range opaqueXattrs[opaque] { hasNodeXattrs(entry, k, opaqueXattrValue)(t, root) } } } tests := []struct { name string in []testutil.TarEntry want []check }{ { name: "1_whiteout_with_sibling", in: []testutil.TarEntry{ testutil.Dir("foo/"), testutil.File("foo/bar.txt", ""), testutil.File("foo/.wh.foo.txt", ""), }, want: []check{ hasValidWhiteout("foo/foo.txt"), fileNotExist("foo/.wh.foo.txt"), }, }, { name: "1_whiteout_with_duplicated_name", in: []testutil.TarEntry{ testutil.Dir("foo/"), testutil.File("foo/bar.txt", "test"), testutil.File("foo/.wh.bar.txt", ""), }, want: []check{ hasFileDigest("foo/bar.txt", digestFor("test")), fileNotExist("foo/.wh.bar.txt"), }, }, { name: "1_opaque", in: []testutil.TarEntry{ testutil.Dir("foo/"), testutil.File("foo/.wh..wh..opq", ""), }, want: []check{ hasOpaque("foo/"), fileNotExist("foo/.wh..wh..opq"), }, }, { name: "1_opaque_with_sibling", in: []testutil.TarEntry{ testutil.Dir("foo/"), testutil.File("foo/.wh..wh..opq", ""), testutil.File("foo/bar.txt", "test"), }, want: []check{ hasOpaque("foo/"), hasFileDigest("foo/bar.txt", digestFor("test")), fileNotExist("foo/.wh..wh..opq"), }, }, { name: "1_opaque_with_xattr", in: []testutil.TarEntry{ testutil.Dir("foo/", testutil.WithDirXattrs(map[string]string{"foo": "bar"})), testutil.File("foo/.wh..wh..opq", ""), }, want: []check{ hasOpaque("foo/"), hasNodeXattrs("foo/", "foo", "bar"), fileNotExist("foo/.wh..wh..opq"), }, }, { name: "prefetch_landmark", in: []testutil.TarEntry{ testutil.File(estargz.PrefetchLandmark, "test"), testutil.Dir("foo/"), testutil.File(fmt.Sprintf("foo/%s", estargz.PrefetchLandmark), "test"), }, want: []check{ fileNotExist(estargz.PrefetchLandmark), hasFileDigest(fmt.Sprintf("foo/%s", estargz.PrefetchLandmark), digestFor("test")), }, }, { name: "no_prefetch_landmark", in: []testutil.TarEntry{ testutil.File(estargz.NoPrefetchLandmark, "test"), testutil.Dir("foo/"), testutil.File(fmt.Sprintf("foo/%s", estargz.NoPrefetchLandmark), "test"), }, want: []check{ fileNotExist(estargz.NoPrefetchLandmark), hasFileDigest(fmt.Sprintf("foo/%s", estargz.NoPrefetchLandmark), digestFor("test")), }, }, { name: "state_file", in: []testutil.TarEntry{ testutil.File("test", "test"), }, want: []check{ hasFileDigest("test", digestFor("test")), hasStateFile(t, testStateLayerDigest.String()+".json"), }, }, { name: "file_suid", in: []testutil.TarEntry{ testutil.File("test", "test", testutil.WithFileMode(0644|os.ModeSetuid)), }, want: []check{ hasExtraMode("test", os.ModeSetuid), }, }, { name: "dir_sgid", in: []testutil.TarEntry{ testutil.Dir("test/", testutil.WithDirMode(0755|os.ModeSetgid)), }, want: []check{ hasExtraMode("test/", os.ModeSetgid), }, }, { name: "file_sticky", in: []testutil.TarEntry{ testutil.File("test", "test", testutil.WithFileMode(0644|os.ModeSticky)), }, want: []check{ hasExtraMode("test", os.ModeSticky), }, }, { name: "symlink_size", in: []testutil.TarEntry{ testutil.Symlink("test", "target"), }, want: []check{ hasSize("test", len("target")), }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { sgz, _, err := testutil.BuildEStargz(tt.in) if err != nil { t.Fatalf("failed to build sample eStargz: %v", err) } r, err := factory(sgz) if err != nil { t.Fatalf("failed to create reader: %v", err) } defer r.Close() rootNode := getRootNode(t, r, opaque) for _, want := range tt.want { want(t, rootNode) } }) } } func getRootNode(t *testing.T, r metadata.Reader, opaque OverlayOpaqueType) *node { rootNode, err := newNode(testStateLayerDigest, &testReader{r}, &testBlobState{10, 5}, 100, opaque) if err != nil { t.Fatalf("failed to get root node: %v", err) } fusefs.NewNodeFS(rootNode, &fusefs.Options{}) // initializes root node return rootNode.(*node) } type testReader struct { r metadata.Reader } func (tr *testReader) OpenFile(id uint32) (io.ReaderAt, error) { return tr.r.OpenFile(id) } func (tr *testReader) Metadata() metadata.Reader { return tr.r } func (tr *testReader) Cache(opts ...reader.CacheOption) error { return nil } func (tr *testReader) Close() error { return nil } func (tr *testReader) LastOnDemandReadTime() time.Time { return time.Now() } type testBlobState struct { size int64 fetchedSize int64 } func (tb *testBlobState) Check() error { return nil } func (tb *testBlobState) Size() int64 { return tb.size } func (tb *testBlobState) FetchedSize() int64 { return tb.fetchedSize } func (tb *testBlobState) ReadAt(p []byte, offset int64, opts ...remote.Option) (int, error) { return 0, nil } func (tb *testBlobState) Cache(offset int64, size int64, opts ...remote.Option) error { return nil } func (tb *testBlobState) Refresh(ctx context.Context, host source.RegistryHosts, refspec reference.Spec, desc ocispec.Descriptor) error { return nil } func (tb *testBlobState) Close() error { return nil } type check func(*testing.T, *node) func fileNotExist(file string) check { return func(t *testing.T, root *node) { if _, _, err := getDirentAndNode(t, root, file); err == nil { t.Errorf("Node %q exists", file) } } } func hasFileDigest(filename string, digest string) check { return func(t *testing.T, root *node) { _, n, err := getDirentAndNode(t, root, filename) if err != nil { t.Fatalf("failed to get node %q: %v", filename, err) } ni := n.Operations().(*node) attr, err := ni.fs.r.Metadata().GetAttr(ni.id) if err != nil { t.Fatalf("failed to get attr %q(%d): %v", filename, ni.id, err) } fh, _, errno := ni.Open(context.Background(), 0) if errno != 0 { t.Fatalf("failed to open node %q: %v", filename, errno) } rr, errno := fh.(*file).Read(context.Background(), make([]byte, attr.Size), 0) if errno != 0 { t.Fatalf("failed to read node %q: %v", filename, errno) } res, status := rr.Bytes(make([]byte, attr.Size)) if status != fuse.OK { t.Fatalf("failed to get read result of node %q: %v", filename, status) } if ndgst := digestFor(string(res)); ndgst != digest { t.Fatalf("Digest(%q) = %q, want %q", filename, ndgst, digest) } } } func hasSize(name string, size int) check { return func(t *testing.T, root *node) { _, n, err := getDirentAndNode(t, root, name) if err != nil { t.Fatalf("failed to get node %q: %v", name, err) } var ao fuse.AttrOut 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) } if ao.Attr.Size != uint64(size) { t.Fatalf("got size = %d, want %d", ao.Attr.Size, size) } } } func hasExtraMode(name string, mode os.FileMode) check { return func(t *testing.T, root *node) { _, n, err := getDirentAndNode(t, root, name) if err != nil { t.Fatalf("failed to get node %q: %v", name, err) } var ao fuse.AttrOut 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) } a := ao.Attr gotMode := a.Mode & (syscall.S_ISUID | syscall.S_ISGID | syscall.S_ISVTX) wantMode := extraModeToTarMode(mode) if gotMode != uint32(wantMode) { t.Fatalf("got mode = %b, want %b", gotMode, wantMode) } } } func hasValidWhiteout(name string) check { return func(t *testing.T, root *node) { ent, n, err := getDirentAndNode(t, root, name) if err != nil { t.Fatalf("failed to get node %q: %v", name, err) } var ao fuse.AttrOut if errno := n.Operations().(fusefs.NodeGetattrer).Getattr(context.Background(), nil, &ao); errno != 0 { t.Fatalf("failed to get attributes of file %q: %v", name, errno) } a := ao.Attr if a.Ino != ent.Ino { t.Errorf("inconsistent inodes %d(Node) != %d(Dirent)", a.Ino, ent.Ino) return } // validate the direntry if ent.Mode != syscall.S_IFCHR { t.Errorf("whiteout entry %q isn't a char device", name) return } // validate the node if a.Mode != syscall.S_IFCHR { t.Errorf("whiteout %q has an invalid mode %o; want %o", name, a.Mode, syscall.S_IFCHR) return } if a.Rdev != uint32(unix.Mkdev(0, 0)) { t.Errorf("whiteout %q has invalid device numbers (%d, %d); want (0, 0)", name, unix.Major(uint64(a.Rdev)), unix.Minor(uint64(a.Rdev))) return } } } func hasNodeXattrs(entry, name, value string) check { return func(t *testing.T, root *node) { _, n, err := getDirentAndNode(t, root, entry) if err != nil { t.Fatalf("failed to get node %q: %v", entry, err) } // check xattr exists in the xattrs list. buf := make([]byte, 1000) nb, errno := n.Operations().(fusefs.NodeListxattrer).Listxattr(context.Background(), buf) if errno != 0 { t.Fatalf("failed to get xattrs list of node %q: %v", entry, err) } attrs := strings.Split(string(buf[:nb]), "\x00") var found bool for _, x := range attrs { if x == name { found = true } } if !found { t.Errorf("node %q doesn't have an opaque xattr %q", entry, value) return } // check the xattr has valid value. v := make([]byte, len(value)) nv, errno := n.Operations().(fusefs.NodeGetxattrer).Getxattr(context.Background(), name, v) if errno != 0 { t.Fatalf("failed to get xattr %q of node %q: %v", name, entry, err) } if int(nv) != len(value) { t.Fatalf("invalid xattr size for file %q, value %q got %d; want %d", name, value, nv, len(value)) } if string(v) != value { t.Errorf("node %q has an invalid xattr %q; want %q", entry, v, value) return } } } func hasEntry(t *testing.T, name string, ents fusefs.DirStream) (fuse.DirEntry, bool) { for ents.HasNext() { de, errno := ents.Next() if errno != 0 { t.Fatalf("faield to read entries for %q", name) } if de.Name == name { return de, true } } return fuse.DirEntry{}, false } func hasStateFile(t *testing.T, id string) check { return func(t *testing.T, root *node) { // Check the state dir is hidden on OpenDir for "/" ents, errno := root.Readdir(context.Background()) if errno != 0 { t.Errorf("failed to open root directory: %v", errno) return } if _, ok := hasEntry(t, stateDirName, ents); ok { t.Errorf("state direntry %q should not be listed", stateDirName) return } // Check existence of state dir var eo fuse.EntryOut sti, errno := root.Lookup(context.Background(), stateDirName, &eo) if errno != 0 { t.Errorf("failed to lookup directory %q: %v", stateDirName, errno) return } st, ok := sti.Operations().(*state) if !ok { t.Errorf("directory %q isn't a state node", stateDirName) return } // Check existence of state file ents, errno = st.Readdir(context.Background()) if errno != 0 { t.Errorf("failed to open directory %q: %v", stateDirName, errno) return } if _, ok := hasEntry(t, id, ents); !ok { t.Errorf("direntry %q not found in %q", id, stateDirName) return } inode, errno := st.Lookup(context.Background(), id, &eo) if errno != 0 { t.Errorf("failed to lookup node %q in %q: %v", id, stateDirName, errno) return } n, ok := inode.Operations().(*statFile) if !ok { t.Errorf("entry %q isn't a normal node", id) return } // wanted data rand.Seed(time.Now().UnixNano()) wantErr := fmt.Errorf("test-%d", rand.Int63()) // report the data root.fs.s.report(wantErr) // obtain file size (check later) var ao fuse.AttrOut errno = n.Operations().(fusefs.NodeGetattrer).Getattr(context.Background(), nil, &ao) if errno != 0 { t.Errorf("failed to get attr of state file: %v", errno) return } attr := ao.Attr // get data via state file tmp := make([]byte, 4096) res, errno := n.Read(context.Background(), nil, tmp, 0) if errno != 0 { t.Errorf("failed to read state file: %v", errno) return } gotState, status := res.Bytes(nil) if status != fuse.OK { t.Errorf("failed to get result bytes of state file: %v", errno) return } if attr.Size != uint64(len(string(gotState))) { t.Errorf("size %d; want %d", attr.Size, len(string(gotState))) return } var j statJSON if err := json.Unmarshal(gotState, &j); err != nil { t.Errorf("failed to unmarshal %q: %v", string(gotState), err) return } if wantErr.Error() != j.Error { t.Errorf("expected error %q, got %q", wantErr.Error(), j.Error) return } } } // getDirentAndNode gets dirent and node at the specified path at once and makes // sure that the both of them exist. func getDirentAndNode(t *testing.T, root *node, path string) (ent fuse.DirEntry, n *fusefs.Inode, err error) { dir, base := filepath.Split(filepath.Clean(path)) // get the target's parent directory. var eo fuse.EntryOut d := root for _, name := range strings.Split(dir, "/") { if len(name) == 0 { continue } di, errno := d.Lookup(context.Background(), name, &eo) if errno != 0 { err = fmt.Errorf("failed to lookup directory %q: %v", name, errno) return } var ok bool if d, ok = di.Operations().(*node); !ok { err = fmt.Errorf("directory %q isn't a normal node", name) return } } // get the target's direntry. ents, errno := d.Readdir(context.Background()) if errno != 0 { err = fmt.Errorf("failed to open directory %q: %v", path, errno) } ent, ok := hasEntry(t, base, ents) if !ok { err = fmt.Errorf("direntry %q not found in the parent directory of %q", base, path) } // get the target's node. n, errno = d.Lookup(context.Background(), base, &eo) if errno != 0 { err = fmt.Errorf("failed to lookup node %q: %v", path, errno) } return } func digestFor(content string) string { sum := sha256.Sum256([]byte(content)) return fmt.Sprintf("sha256:%x", sum) } // suid, guid, sticky bits for archive/tar // https://github.com/golang/go/blob/release-branch.go1.13/src/archive/tar/common.go#L607-L609 const ( cISUID = 04000 // Set uid cISGID = 02000 // Set gid cISVTX = 01000 // Save text (sticky bit) ) func extraModeToTarMode(fm os.FileMode) (tm int64) { if fm&os.ModeSetuid != 0 { tm |= cISUID } if fm&os.ModeSetgid != 0 { tm |= cISGID } if fm&os.ModeSticky != 0 { tm |= cISVTX } return } stargz-snapshotter-0.12.0/fs/metrics/000077500000000000000000000000001426301527400175365ustar00rootroot00000000000000stargz-snapshotter-0.12.0/fs/metrics/common/000077500000000000000000000000001426301527400210265ustar00rootroot00000000000000stargz-snapshotter-0.12.0/fs/metrics/common/metrics.go000066400000000000000000000236411426301527400230310ustar00rootroot00000000000000/* 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 commonmetrics import ( "context" "sync" "time" "github.com/containerd/containerd/log" digest "github.com/opencontainers/go-digest" "github.com/prometheus/client_golang/prometheus" "github.com/sirupsen/logrus" ) const ( // OperationLatencyKeyMilliseconds is the key for stargz operation latency metrics in milliseconds. OperationLatencyKeyMilliseconds = "operation_duration_milliseconds" // OperationLatencyKeyMicroseconds is the key for stargz operation latency metrics in microseconds. OperationLatencyKeyMicroseconds = "operation_duration_microseconds" // OperationCountKey is the key for stargz operation count metrics. OperationCountKey = "operation_count" // BytesServedKey is the key for any metric related to counting bytes served as the part of specific operation. BytesServedKey = "bytes_served" // Keep namespace as stargz and subsystem as fs. namespace = "stargz" subsystem = "fs" ) // Lists all metric labels. const ( // prometheus metrics Mount = "mount" RemoteRegistryGet = "remote_registry_get" NodeReaddir = "node_readdir" StargzHeaderGet = "stargz_header_get" StargzFooterGet = "stargz_footer_get" StargzTocGet = "stargz_toc_get" DeserializeTocJSON = "stargz_toc_json_deserialize" PrefetchesCompleted = "all_prefetches_completed" ReadOnDemand = "read_on_demand" MountLayerToLastOnDemandFetch = "mount_layer_to_last_on_demand_fetch" OnDemandReadAccessCount = "on_demand_read_access_count" OnDemandRemoteRegistryFetchCount = "on_demand_remote_registry_fetch_count" OnDemandBytesServed = "on_demand_bytes_served" OnDemandBytesFetched = "on_demand_bytes_fetched" // logs metrics PrefetchTotal = "prefetch_total" PrefetchDownload = "prefetch_download" PrefetchDecompress = "prefetch_decompress" BackgroundFetchTotal = "background_fetch_total" BackgroundFetchDownload = "background_fetch_download" BackgroundFetchDecompress = "background_fetch_decompress" PrefetchSize = "prefetch_size" ) var ( // Buckets for OperationLatency metrics. latencyBucketsMilliseconds = []float64{1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384} // in milliseconds latencyBucketsMicroseconds = []float64{1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024} // in microseconds // operationLatencyMilliseconds collects operation latency numbers in milliseconds grouped by // operation, type and layer digest. operationLatencyMilliseconds = prometheus.NewHistogramVec( prometheus.HistogramOpts{ Namespace: namespace, Subsystem: subsystem, Name: OperationLatencyKeyMilliseconds, Help: "Latency in milliseconds of stargz snapshotter operations. Broken down by operation type and layer sha.", Buckets: latencyBucketsMilliseconds, }, []string{"operation_type", "layer"}, ) // operationLatencyMicroseconds collects operation latency numbers in microseconds grouped by // operation, type and layer digest. operationLatencyMicroseconds = prometheus.NewHistogramVec( prometheus.HistogramOpts{ Namespace: namespace, Subsystem: subsystem, Name: OperationLatencyKeyMicroseconds, Help: "Latency in microseconds of stargz snapshotter operations. Broken down by operation type and layer sha.", Buckets: latencyBucketsMicroseconds, }, []string{"operation_type", "layer"}, ) // operationCount collects operation count numbers by operation // type and layer sha. operationCount = prometheus.NewCounterVec( prometheus.CounterOpts{ Namespace: namespace, Subsystem: subsystem, Name: OperationCountKey, Help: "The count of stargz snapshotter operations. Broken down by operation type and layer sha.", }, []string{"operation_type", "layer"}, ) // bytesCount reflects the number of bytes served as the part of specitic operation type per layer sha. bytesCount = prometheus.NewGaugeVec( prometheus.GaugeOpts{ Namespace: namespace, Subsystem: subsystem, Name: BytesServedKey, Help: "The number of bytes served per stargz snapshotter operations. Broken down by operation type and layer sha.", }, []string{"operation_type", "layer"}, ) ) var register sync.Once var logLevel logrus.Level = logrus.DebugLevel // 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 // .Milliseconds() returns an integer value and you can lost a precision for sub-millisecond values. func sinceInMilliseconds(start time.Time) float64 { return float64(time.Since(start).Nanoseconds()) / 1e6 } // sinceInMicroseconds gets the time since the specified start in microseconds. // The division by 1e3 is made to have the microseconds value as floating point number, since the native method // .Microseconds() returns an integer value and you can lost a precision for sub-microsecond values. func sinceInMicroseconds(start time.Time) float64 { return float64(time.Since(start).Nanoseconds()) / 1e3 } // Register registers metrics. This is always called only once. func Register(l logrus.Level) { register.Do(func() { logLevel = l prometheus.MustRegister(operationLatencyMilliseconds) prometheus.MustRegister(operationLatencyMicroseconds) prometheus.MustRegister(operationCount) prometheus.MustRegister(bytesCount) }) } // MeasureLatencyInMilliseconds wraps the labels attachment as well as calling Observe into a single method. // Right now we attach the operation and layer digest, so it's possible to see the breakdown for latency // by operation and individual layers. // If you want this to be layer agnostic, just pass the digest from empty string, e.g. // layerDigest := digest.FromString("") func MeasureLatencyInMilliseconds(operation string, layer digest.Digest, start time.Time) { operationLatencyMilliseconds.WithLabelValues(operation, layer.String()).Observe(sinceInMilliseconds(start)) } // MeasureLatencyInMicroseconds wraps the labels attachment as well as calling Observe into a single method. // Right now we attach the operation and layer digest, so it's possible to see the breakdown for latency // by operation and individual layers. // If you want this to be layer agnostic, just pass the digest from empty string, e.g. // layerDigest := digest.FromString("") func MeasureLatencyInMicroseconds(operation string, layer digest.Digest, start time.Time) { operationLatencyMicroseconds.WithLabelValues(operation, layer.String()).Observe(sinceInMicroseconds(start)) } // IncOperationCount wraps the labels attachment as well as calling Inc into a single method. func IncOperationCount(operation string, layer digest.Digest) { operationCount.WithLabelValues(operation, layer.String()).Inc() } // AddBytesCount wraps the labels attachment as well as calling Add into a single method. func AddBytesCount(operation string, layer digest.Digest, bytes int64) { bytesCount.WithLabelValues(operation, layer.String()).Add(float64(bytes)) } // WriteLatencyLogValue wraps writing the log info record for latency in milliseconds. The log record breaks down by operation and layer digest. func WriteLatencyLogValue(ctx context.Context, layer digest.Digest, operation string, start time.Time) { ctx = log.WithLogger(ctx, log.G(ctx).WithField("metrics", "latency").WithField("operation", operation).WithField("layer_sha", layer.String())) log.G(ctx).Logf(logLevel, "value=%v milliseconds", sinceInMilliseconds(start)) } // WriteLatencyWithBytesLogValue wraps writing the log info record for latency in milliseconds with adding the size in bytes. // The log record breaks down by operation, layer digest and byte value. func WriteLatencyWithBytesLogValue(ctx context.Context, layer digest.Digest, latencyOperation string, start time.Time, bytesMetricName string, bytesMetricValue int64) { ctx = log.WithLogger(ctx, log.G(ctx).WithField("metrics", "latency").WithField("operation", latencyOperation).WithField("layer_sha", layer.String())) log.G(ctx).Logf(logLevel, "value=%v milliseconds; %v=%v bytes", sinceInMilliseconds(start), bytesMetricName, bytesMetricValue) } // LogLatencyForLastOnDemandFetch implements a special case for measuring the latency of last on demand fetch, which must be invoked at the end of // background fetch operation only. Since this is expected to happen only once per container launch, it writes a log line, // instead of directly emitting a metric. // We do that in the following way: // 1. We record the mount start time // 2. We constantly record the timestamps when we do on demand fetch for each layer sha // 3. On background fetch completed we measure the difference between the last on demand fetch and mount start time // and record it as a metric func LogLatencyForLastOnDemandFetch(ctx context.Context, layer digest.Digest, start time.Time, end time.Time) { diffInMilliseconds := float64(end.Sub(start).Milliseconds()) // value can be negative if we pass the default value for time.Time as `end` // this can happen if there were no on-demand fetch for the particular layer if diffInMilliseconds > 0 { ctx = log.WithLogger(ctx, log.G(ctx).WithField("metrics", "latency").WithField("operation", MountLayerToLastOnDemandFetch).WithField("layer_sha", layer.String())) log.G(ctx).Logf(logLevel, "value=%v milliseconds", diffInMilliseconds) } } stargz-snapshotter-0.12.0/fs/metrics/layer/000077500000000000000000000000001426301527400206525ustar00rootroot00000000000000stargz-snapshotter-0.12.0/fs/metrics/layer/layer.go000066400000000000000000000030371426301527400223200ustar00rootroot00000000000000/* 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 layermetrics import ( "github.com/containerd/stargz-snapshotter/fs/layer" metrics "github.com/docker/go-metrics" "github.com/prometheus/client_golang/prometheus" ) var layerMetrics = []*metric{ { name: "layer_fetched_size", help: "Total fetched size of the layer", unit: metrics.Bytes, vt: prometheus.CounterValue, getValues: func(l layer.Layer) []value { return []value{ { v: float64(l.Info().FetchedSize), }, } }, }, { name: "layer_prefetch_size", help: "Total prefetched size of the layer", unit: metrics.Bytes, vt: prometheus.CounterValue, getValues: func(l layer.Layer) []value { return []value{ { v: float64(l.Info().PrefetchSize), }, } }, }, { name: "layer_size", help: "Total size of the layer", unit: metrics.Bytes, vt: prometheus.CounterValue, getValues: func(l layer.Layer) []value { return []value{ { v: float64(l.Info().Size), }, } }, }, } stargz-snapshotter-0.12.0/fs/metrics/layer/metrics.go000066400000000000000000000050411426301527400226470ustar00rootroot00000000000000/* 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 layermetrics import ( "sync" "github.com/containerd/stargz-snapshotter/fs/layer" metrics "github.com/docker/go-metrics" "github.com/prometheus/client_golang/prometheus" ) func NewLayerMetrics(ns *metrics.Namespace) *Controller { if ns == nil { return &Controller{} } c := &Controller{ ns: ns, layer: make(map[string]layer.Layer), } c.metrics = append(c.metrics, layerMetrics...) ns.Add(c) return c } type Controller struct { ns *metrics.Namespace metrics []*metric layer map[string]layer.Layer layerMu sync.RWMutex } func (c *Controller) Describe(ch chan<- *prometheus.Desc) { for _, e := range c.metrics { ch <- e.desc(c.ns) } } func (c *Controller) Collect(ch chan<- prometheus.Metric) { c.layerMu.RLock() wg := &sync.WaitGroup{} for mp, l := range c.layer { mp, l := mp, l wg.Add(1) go func() { defer wg.Done() for _, e := range c.metrics { e.collect(mp, l, c.ns, ch) } }() } c.layerMu.RUnlock() wg.Wait() } func (c *Controller) Add(key string, l layer.Layer) { if c.ns == nil { return } c.layerMu.Lock() c.layer[key] = l c.layerMu.Unlock() } func (c *Controller) Remove(key string) { if c.ns == nil { return } c.layerMu.Lock() delete(c.layer, key) c.layerMu.Unlock() } type value struct { v float64 l []string } type metric struct { name string help string unit metrics.Unit vt prometheus.ValueType labels []string // getValues returns the value and labels for the data getValues func(l layer.Layer) []value } func (m *metric) desc(ns *metrics.Namespace) *prometheus.Desc { return ns.NewDesc(m.name, m.help, m.unit, append([]string{"digest", "mountpoint"}, m.labels...)...) } func (m *metric) collect(mountpoint string, l layer.Layer, ns *metrics.Namespace, ch chan<- prometheus.Metric) { values := m.getValues(l) for _, v := range values { ch <- prometheus.MustNewConstMetric(m.desc(ns), m.vt, v.v, append([]string{l.Info().Digest.String(), mountpoint}, v.l...)...) } } stargz-snapshotter-0.12.0/fs/reader/000077500000000000000000000000001426301527400173325ustar00rootroot00000000000000stargz-snapshotter-0.12.0/fs/reader/reader.go000066400000000000000000000342351426301527400211320ustar00rootroot00000000000000/* 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. */ /* Copyright 2019 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the NOTICE.md file. */ package reader import ( "bufio" "bytes" "context" "crypto/sha256" "fmt" "io" "os" "runtime" "sync" "time" "github.com/containerd/stargz-snapshotter/cache" "github.com/containerd/stargz-snapshotter/estargz" commonmetrics "github.com/containerd/stargz-snapshotter/fs/metrics/common" "github.com/containerd/stargz-snapshotter/metadata" "github.com/hashicorp/go-multierror" digest "github.com/opencontainers/go-digest" "golang.org/x/sync/errgroup" "golang.org/x/sync/semaphore" ) const maxWalkDepth = 10000 type Reader interface { OpenFile(id uint32) (io.ReaderAt, error) Metadata() metadata.Reader Close() error LastOnDemandReadTime() time.Time } // VerifiableReader produces a Reader with a given verifier. type VerifiableReader struct { r *reader lastVerifyErr error lastVerifyErrMu sync.Mutex prohibitVerifyFailure bool prohibitVerifyFailureMu sync.RWMutex closed bool closedMu sync.Mutex verifier func(uint32, string) (digest.Verifier, error) } func (vr *VerifiableReader) storeLastVerifyErr(err error) { vr.lastVerifyErrMu.Lock() vr.lastVerifyErr = err vr.lastVerifyErrMu.Unlock() } func (vr *VerifiableReader) loadLastVerifyErr() error { vr.lastVerifyErrMu.Lock() err := vr.lastVerifyErr vr.lastVerifyErrMu.Unlock() return err } func (vr *VerifiableReader) SkipVerify() Reader { return vr.r } func (vr *VerifiableReader) VerifyTOC(tocDigest digest.Digest) (Reader, error) { if vr.isClosed() { return nil, fmt.Errorf("reader is already closed") } vr.prohibitVerifyFailureMu.Lock() vr.prohibitVerifyFailure = true lastVerifyErr := vr.loadLastVerifyErr() vr.prohibitVerifyFailureMu.Unlock() if err := lastVerifyErr; err != nil { return nil, fmt.Errorf("content error occurs during caching contents: %w", err) } if actual := vr.r.r.TOCDigest(); actual != tocDigest { return nil, fmt.Errorf("invalid TOC JSON %q; want %q", actual, tocDigest) } vr.r.verify = true return vr.r, nil } func (vr *VerifiableReader) Metadata() metadata.Reader { // TODO: this shouldn't be called before verified return vr.r.r } func (vr *VerifiableReader) Cache(opts ...CacheOption) (err error) { if vr.isClosed() { return fmt.Errorf("reader is already closed") } var cacheOpts cacheOptions for _, o := range opts { o(&cacheOpts) } gr := vr.r r := gr.r if cacheOpts.reader != nil { r, err = r.Clone(cacheOpts.reader) if err != nil { return err } } rootID := r.RootID() filter := func(int64) bool { return true } if cacheOpts.filter != nil { filter = cacheOpts.filter } eg, egCtx := errgroup.WithContext(context.Background()) eg.Go(func() error { return vr.cacheWithReader(egCtx, 0, eg, semaphore.NewWeighted(int64(runtime.GOMAXPROCS(0))), rootID, r, filter, cacheOpts.cacheOpts...) }) return eg.Wait() } func (vr *VerifiableReader) cacheWithReader(ctx context.Context, currentDepth int, eg *errgroup.Group, sem *semaphore.Weighted, dirID uint32, r metadata.Reader, filter func(int64) bool, opts ...cache.Option) (rErr error) { if currentDepth > maxWalkDepth { return fmt.Errorf("tree is too deep (depth:%d)", currentDepth) } gr := vr.r rootID := r.RootID() r.ForeachChild(dirID, func(name string, id uint32, mode os.FileMode) bool { e, err := r.GetAttr(id) if err != nil { rErr = err return false } if mode.IsDir() { // Walk through all files on this stargz file. // Ignore the entry of "./" (formated as "" by stargz lib) on root directory // because this points to the root directory itself. if dirID == rootID && name == "" { return true } if err := vr.cacheWithReader(ctx, currentDepth+1, eg, sem, id, r, filter, opts...); err != nil { rErr = err return false } return true } else if !mode.IsRegular() { // Only cache regular files return true } else if dirID == rootID && name == estargz.TOCTarName { // We don't need to cache TOC json file return true } offset, err := r.GetOffset(id) if err != nil { rErr = err return false } if !filter(offset) { // This entry need to be filtered out return true } fr, err := r.OpenFile(id) if err != nil { rErr = err return false } var nr int64 for nr < e.Size { chunkOffset, chunkSize, chunkDigestStr, ok := fr.ChunkEntryForOffset(nr) if !ok { break } nr += chunkSize if err := sem.Acquire(ctx, 1); err != nil { rErr = err return false } eg.Go(func() (retErr error) { defer sem.Release(1) defer func() { if retErr != nil { vr.storeLastVerifyErr(retErr) } }() // Check if the target chunks exists in the cache cacheID := genID(id, chunkOffset, chunkSize) if r, err := gr.cache.Get(cacheID, opts...); err == nil { return r.Close() } // missed cache, needs to fetch and add it to the cache br := bufio.NewReaderSize(io.NewSectionReader(fr, chunkOffset, chunkSize), int(chunkSize)) if _, err := br.Peek(int(chunkSize)); err != nil { return fmt.Errorf("cacheWithReader.peek: %v", err) } w, err := gr.cache.Add(cacheID, opts...) if err != nil { return err } defer w.Close() v, err := vr.verifier(id, chunkDigestStr) if err != nil { vr.prohibitVerifyFailureMu.RLock() if vr.prohibitVerifyFailure { vr.prohibitVerifyFailureMu.RUnlock() return fmt.Errorf("verifier not found %q(off:%d,size:%d): %w", name, chunkOffset, chunkSize, err) } vr.storeLastVerifyErr(err) vr.prohibitVerifyFailureMu.RUnlock() } tee := io.Discard if v != nil { tee = io.Writer(v) // verification is required } if _, err := io.CopyN(w, io.TeeReader(br, tee), chunkSize); err != nil { w.Abort() return fmt.Errorf("failed to cache file payload of %q (offset:%d,size:%d): %w", name, chunkOffset, chunkSize, err) } if v != nil && !v.Verified() { err := fmt.Errorf("invalid chunk %q (offset:%d,size:%d)", name, chunkOffset, chunkSize) vr.prohibitVerifyFailureMu.RLock() if vr.prohibitVerifyFailure { vr.prohibitVerifyFailureMu.RUnlock() w.Abort() return err } vr.storeLastVerifyErr(err) vr.prohibitVerifyFailureMu.RUnlock() } return w.Commit() }) } return true }) return } func (vr *VerifiableReader) Close() error { vr.closedMu.Lock() defer vr.closedMu.Unlock() if vr.closed { return nil } vr.closed = true return vr.r.Close() } func (vr *VerifiableReader) isClosed() bool { vr.closedMu.Lock() closed := vr.closed vr.closedMu.Unlock() return closed } // NewReader creates a Reader based on the given stargz blob and cache implementation. // It returns VerifiableReader so the caller must provide a metadata.ChunkVerifier // to use for verifying file or chunk contained in this stargz blob. func NewReader(r metadata.Reader, cache cache.BlobCache, layerSha digest.Digest) (*VerifiableReader, error) { vr := &reader{ r: r, cache: cache, bufPool: sync.Pool{ New: func() interface{} { return new(bytes.Buffer) }, }, layerSha: layerSha, verifier: digestVerifier, } return &VerifiableReader{r: vr, verifier: digestVerifier}, nil } type reader struct { r metadata.Reader cache cache.BlobCache bufPool sync.Pool layerSha digest.Digest lastReadTime time.Time lastReadTimeMu sync.Mutex closed bool closedMu sync.Mutex verify bool verifier func(uint32, string) (digest.Verifier, error) } func (gr *reader) Metadata() metadata.Reader { return gr.r } func (gr *reader) setLastReadTime(lastReadTime time.Time) { gr.lastReadTimeMu.Lock() gr.lastReadTime = lastReadTime gr.lastReadTimeMu.Unlock() } func (gr *reader) LastOnDemandReadTime() time.Time { gr.lastReadTimeMu.Lock() t := gr.lastReadTime gr.lastReadTimeMu.Unlock() return t } func (gr *reader) OpenFile(id uint32) (io.ReaderAt, error) { if gr.isClosed() { return nil, fmt.Errorf("reader is already closed") } var fr metadata.File fr, err := gr.r.OpenFile(id) if err != nil { return nil, fmt.Errorf("failed to open file %d: %w", id, err) } return &file{ id: id, fr: fr, gr: gr, }, nil } func (gr *reader) Close() (retErr error) { gr.closedMu.Lock() defer gr.closedMu.Unlock() if gr.closed { return nil } gr.closed = true if err := gr.cache.Close(); err != nil { retErr = multierror.Append(retErr, err) } if err := gr.r.Close(); err != nil { retErr = multierror.Append(retErr, err) } return } func (gr *reader) isClosed() bool { gr.closedMu.Lock() closed := gr.closed gr.closedMu.Unlock() return closed } func (gr *reader) putBuffer(b *bytes.Buffer) { b.Reset() gr.bufPool.Put(b) } type file struct { id uint32 fr metadata.File gr *reader } // ReadAt reads chunks from the stargz file with trying to fetch as many chunks // as possible from the cache. func (sf *file) ReadAt(p []byte, offset int64) (int, error) { nr := 0 for nr < len(p) { chunkOffset, chunkSize, chunkDigestStr, ok := sf.fr.ChunkEntryForOffset(offset + int64(nr)) if !ok { break } var ( id = genID(sf.id, chunkOffset, chunkSize) lowerDiscard = positive(offset - chunkOffset) upperDiscard = positive(chunkOffset + chunkSize - (offset + int64(len(p)))) expectedSize = chunkSize - upperDiscard - lowerDiscard ) // Check if the content exists in the cache if r, err := sf.gr.cache.Get(id); err == nil { n, err := r.ReadAt(p[nr:int64(nr)+expectedSize], lowerDiscard) if (err == nil || err == io.EOF) && int64(n) == expectedSize { nr += n r.Close() continue } r.Close() } // We missed cache. Take it from underlying reader. // We read the whole chunk here and add it to the cache so that following // reads against neighboring chunks can take the data without decmpression. if lowerDiscard == 0 && upperDiscard == 0 { // We can directly store the result to the given buffer ip := p[nr : int64(nr)+chunkSize] n, err := sf.fr.ReadAt(ip, chunkOffset) if err != nil && err != io.EOF { return 0, fmt.Errorf("failed to read data: %w", err) } commonmetrics.IncOperationCount(commonmetrics.OnDemandRemoteRegistryFetchCount, sf.gr.layerSha) // increment the number of on demand file fetches from remote registry commonmetrics.AddBytesCount(commonmetrics.OnDemandBytesFetched, sf.gr.layerSha, int64(n)) // record total bytes fetched sf.gr.setLastReadTime(time.Now()) // Verify this chunk if err := sf.verify(sf.id, ip, chunkDigestStr); err != nil { return 0, fmt.Errorf("invalid chunk: %w", err) } // Cache this chunk if w, err := sf.gr.cache.Add(id); err == nil { if cn, err := w.Write(ip); err != nil || cn != len(ip) { w.Abort() } else { w.Commit() } w.Close() } nr += n continue } // Use temporally buffer for aligning this chunk b := sf.gr.bufPool.Get().(*bytes.Buffer) b.Reset() b.Grow(int(chunkSize)) ip := b.Bytes()[:chunkSize] if _, err := sf.fr.ReadAt(ip, chunkOffset); err != nil && err != io.EOF { sf.gr.putBuffer(b) return 0, fmt.Errorf("failed to read data: %w", err) } // We can end up doing on demand registry fetch when aligning the chunk commonmetrics.IncOperationCount(commonmetrics.OnDemandRemoteRegistryFetchCount, sf.gr.layerSha) // increment the number of on demand file fetches from remote registry commonmetrics.AddBytesCount(commonmetrics.OnDemandBytesFetched, sf.gr.layerSha, int64(len(ip))) // record total bytes fetched sf.gr.setLastReadTime(time.Now()) // Verify this chunk if err := sf.verify(sf.id, ip, chunkDigestStr); err != nil { sf.gr.putBuffer(b) return 0, fmt.Errorf("invalid chunk: %w", err) } // Cache this chunk if w, err := sf.gr.cache.Add(id); err == nil { if cn, err := w.Write(ip); err != nil || cn != len(ip) { w.Abort() } else { w.Commit() } w.Close() } n := copy(p[nr:], ip[lowerDiscard:chunkSize-upperDiscard]) sf.gr.putBuffer(b) if int64(n) != expectedSize { return 0, fmt.Errorf("unexpected final data size %d; want %d", n, expectedSize) } nr += n } commonmetrics.AddBytesCount(commonmetrics.OnDemandBytesServed, sf.gr.layerSha, int64(nr)) // measure the number of on demand bytes served return nr, nil } func (sf *file) verify(id uint32, p []byte, chunkDigestStr string) error { if !sf.gr.verify { return nil // verification is not required } v, err := sf.gr.verifier(id, chunkDigestStr) if err != nil { return fmt.Errorf("invalid chunk: %w", err) } if _, err := v.Write(p); err != nil { return fmt.Errorf("invalid chunk: failed to write to verifier: %w", err) } if !v.Verified() { return fmt.Errorf("invalid chunk: not verified") } return nil } func genID(id uint32, offset, size int64) string { sum := sha256.Sum256([]byte(fmt.Sprintf("%d-%d-%d", id, offset, size))) return fmt.Sprintf("%x", sum) } func positive(n int64) int64 { if n < 0 { return 0 } return n } type CacheOption func(*cacheOptions) type cacheOptions struct { cacheOpts []cache.Option filter func(int64) bool reader *io.SectionReader } func WithCacheOpts(cacheOpts ...cache.Option) CacheOption { return func(opts *cacheOptions) { opts.cacheOpts = cacheOpts } } func WithFilter(filter func(int64) bool) CacheOption { return func(opts *cacheOptions) { opts.filter = filter } } func WithReader(sr *io.SectionReader) CacheOption { return func(opts *cacheOptions) { opts.reader = sr } } func digestVerifier(id uint32, chunkDigestStr string) (digest.Verifier, error) { chunkDigest, err := digest.Parse(chunkDigestStr) if err != nil { return nil, fmt.Errorf("invalid chunk: no digset is recorded: %w", err) } return chunkDigest.Verifier(), nil } stargz-snapshotter-0.12.0/fs/reader/reader_test.go000066400000000000000000000016771426301527400221750ustar00rootroot00000000000000/* 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. */ /* Copyright 2019 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the NOTICE.md file. */ package reader import ( "testing" memorymetadata "github.com/containerd/stargz-snapshotter/metadata/memory" ) func TestReader(t *testing.T) { TestSuiteReader(t, memorymetadata.NewReader) } stargz-snapshotter-0.12.0/fs/reader/testutil.go000066400000000000000000000365251426301527400215510ustar00rootroot00000000000000/* 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. */ /* Copyright 2019 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the NOTICE.md file. */ package reader import ( "bytes" "fmt" "io" "os" "path" "strings" "sync" "testing" "time" "github.com/containerd/stargz-snapshotter/cache" "github.com/containerd/stargz-snapshotter/estargz" "github.com/containerd/stargz-snapshotter/metadata" "github.com/containerd/stargz-snapshotter/util/testutil" digest "github.com/opencontainers/go-digest" "golang.org/x/sync/errgroup" ) type region struct{ b, e int64 } const ( sampleChunkSize = 3 sampleMiddleOffset = sampleChunkSize / 2 sampleData1 = "0123456789" lastChunkOffset1 = sampleChunkSize * (int64(len(sampleData1)) / sampleChunkSize) ) func TestSuiteReader(t *testing.T, store metadata.Store) { testFileReadAt(t, store) testCacheVerify(t, store) testFailReader(t, store) } func testFileReadAt(t *testing.T, factory metadata.Store) { sizeCond := map[string]int64{ "single_chunk": sampleChunkSize - sampleMiddleOffset, "multi_chunks": sampleChunkSize + sampleMiddleOffset, } innerOffsetCond := map[string]int64{ "at_top": 0, "at_middle": sampleMiddleOffset, } baseOffsetCond := map[string]int64{ "of_1st_chunk": sampleChunkSize * 0, "of_2nd_chunk": sampleChunkSize * 1, "of_last_chunk": lastChunkOffset1, } fileSizeCond := map[string]int64{ "in_1_chunk_file": sampleChunkSize * 1, "in_2_chunks_file": sampleChunkSize * 2, "in_max_size_file": int64(len(sampleData1)), } cacheCond := map[string][]region{ "with_clean_cache": nil, "with_edge_filled_cache": { region{0, sampleChunkSize - 1}, region{lastChunkOffset1, int64(len(sampleData1)) - 1}, }, "with_sparse_cache": { region{0, sampleChunkSize - 1}, region{2 * sampleChunkSize, 3*sampleChunkSize - 1}, }, } for sn, size := range sizeCond { for in, innero := range innerOffsetCond { for bo, baseo := range baseOffsetCond { for fn, filesize := range fileSizeCond { for cc, cacheExcept := range cacheCond { t.Run(fmt.Sprintf("reading_%s_%s_%s_%s_%s", sn, in, bo, fn, cc), func(t *testing.T) { if filesize > int64(len(sampleData1)) { t.Fatal("sample file size is larger than sample data") } wantN := size offset := baseo + innero if remain := filesize - offset; remain < wantN { if wantN = remain; wantN < 0 { wantN = 0 } } // use constant string value as a data source. want := strings.NewReader(sampleData1) // data we want to get. wantData := make([]byte, wantN) _, err := want.ReadAt(wantData, offset) if err != nil && err != io.EOF { t.Fatalf("want.ReadAt (offset=%d,size=%d): %v", offset, wantN, err) } // data we get through a file. f, closeFn := makeFile(t, []byte(sampleData1)[:filesize], sampleChunkSize, factory) defer closeFn() f.fr = newExceptFile(t, f.fr, cacheExcept...) for _, reg := range cacheExcept { id := genID(f.id, reg.b, reg.e-reg.b+1) w, err := f.gr.cache.Add(id) if err != nil { w.Close() t.Fatalf("failed to add cache %v: %v", id, err) } if _, err := w.Write([]byte(sampleData1[reg.b : reg.e+1])); err != nil { w.Close() t.Fatalf("failed to write cache %v: %v", id, err) } if err := w.Commit(); err != nil { w.Close() t.Fatalf("failed to commit cache %v: %v", id, err) } w.Close() } respData := make([]byte, size) n, err := f.ReadAt(respData, offset) if err != nil { t.Errorf("failed to read off=%d, size=%d, filesize=%d: %v", offset, size, filesize, err) return } respData = respData[:n] if !bytes.Equal(wantData, respData) { t.Errorf("off=%d, filesize=%d; read data{size=%d,data=%q}; want (size=%d,data=%q)", offset, filesize, len(respData), string(respData), wantN, string(wantData)) return } // check cache has valid contents. cn := 0 nr := 0 for int64(nr) < wantN { chunkOffset, chunkSize, _, ok := f.fr.ChunkEntryForOffset(offset + int64(nr)) if !ok { break } data := make([]byte, chunkSize) id := genID(f.id, chunkOffset, chunkSize) r, err := f.gr.cache.Get(id) if err != nil { t.Errorf("missed cache of offset=%d, size=%d: %v(got size=%d)", chunkOffset, chunkSize, err, n) return } defer r.Close() if n, err := r.ReadAt(data, 0); (err != nil && err != io.EOF) || n != int(chunkSize) { t.Errorf("failed to read cache of offset=%d, size=%d: %v(got size=%d)", chunkOffset, chunkSize, err, n) return } nr += n cn++ } }) } } } } } } func newExceptFile(t *testing.T, fr metadata.File, except ...region) metadata.File { er := exceptFile{fr: fr, t: t} er.except = map[region]bool{} for _, reg := range except { er.except[reg] = true } return &er } type exceptFile struct { fr metadata.File except map[region]bool t *testing.T } func (er *exceptFile) ReadAt(p []byte, offset int64) (int, error) { if er.except[region{offset, offset + int64(len(p)) - 1}] { er.t.Fatalf("Requested prohibited region of chunk: (%d, %d)", offset, offset+int64(len(p))-1) } return er.fr.ReadAt(p, offset) } func (er *exceptFile) ChunkEntryForOffset(offset int64) (off int64, size int64, dgst string, ok bool) { return er.fr.ChunkEntryForOffset(offset) } func makeFile(t *testing.T, contents []byte, chunkSize int, factory metadata.Store) (*file, func() error) { testName := "test" sr, dgst, err := testutil.BuildEStargz([]testutil.TarEntry{ testutil.File(testName, string(contents)), }, testutil.WithEStargzOptions(estargz.WithChunkSize(chunkSize))) if err != nil { t.Fatalf("failed to build sample estargz") } mr, err := factory(sr) if err != nil { t.Fatalf("failed to create reader: %v", err) } vr, err := NewReader(mr, cache.NewMemoryCache(), digest.FromString("")) if err != nil { mr.Close() t.Fatalf("failed to make new reader: %v", err) } r, err := vr.VerifyTOC(dgst) if err != nil { vr.Close() t.Fatalf("failed to verify TOC: %v", err) } tid, _, err := r.Metadata().GetChild(r.Metadata().RootID(), testName) if err != nil { vr.Close() t.Fatalf("failed to get %q: %v", testName, err) } ra, err := r.OpenFile(tid) if err != nil { vr.Close() t.Fatalf("Failed to open testing file: %v", err) } f, ok := ra.(*file) if !ok { vr.Close() t.Fatalf("invalid type of file %q", tid) } return f, vr.Close } func testCacheVerify(t *testing.T, factory metadata.Store) { sr, tocDgst, err := testutil.BuildEStargz([]testutil.TarEntry{ testutil.File("a", sampleData1+"a"), testutil.File("b", sampleData1+"b"), }, testutil.WithEStargzOptions(estargz.WithChunkSize(sampleChunkSize))) if err != nil { t.Fatalf("failed to build sample estargz") } for _, skipVerify := range [2]bool{true, false} { for _, invalidChunkBeforeVerify := range [2]bool{true, false} { for _, invalidChunkAfterVerify := range [2]bool{true, false} { name := fmt.Sprintf("test_cache_verify_%v_%v_%v", skipVerify, invalidChunkBeforeVerify, invalidChunkAfterVerify) t.Run(name, func(t *testing.T) { // Determine the expected behaviour var wantVerifyFail, wantCacheFail, wantCacheFail2 bool if skipVerify { // always no error if verification is disabled wantVerifyFail, wantCacheFail, wantCacheFail2 = false, false, false } else if invalidChunkBeforeVerify { // errors occurred before verifying TOC must be reported via VerifyTOC() wantVerifyFail = true } else if invalidChunkAfterVerify { // errors occurred after verifying TOC must be reported via Cache() wantVerifyFail, wantCacheFail, wantCacheFail2 = false, true, true } else { // otherwise no verification error wantVerifyFail, wantCacheFail, wantCacheFail2 = false, false, false } // Prepare reader verifier := &failIDVerifier{} mr, err := factory(sr) if err != nil { t.Fatalf("failed to prepare reader %v", err) } defer mr.Close() vr, err := NewReader(mr, cache.NewMemoryCache(), digest.FromString("")) if err != nil { t.Fatalf("failed to make new reader: %v", err) } if verifier != nil { vr.verifier = verifier.verifier vr.r.verifier = verifier.verifier } off2id, id2path, err := prepareMap(vr.Metadata(), vr.Metadata().RootID(), "") if err != nil || off2id == nil || id2path == nil { t.Fatalf("failed to prepare offset map %v, off2id = %+v, id2path = %+v", err, off2id, id2path) } // Perform Cache() before verification // 1. Either of "a" or "b" is read and verified // 2. VerifyTOC/SkipVerify is called // 3. Another entry ("a" or "b") is called verifyDone := make(chan struct{}) var firstEntryCalled bool var eg errgroup.Group eg.Go(func() error { return vr.Cache(WithFilter(func(off int64) bool { id, ok := off2id[off] if !ok { t.Fatalf("no ID is assigned to offset %d", off) } name, ok := id2path[id] if !ok { t.Fatalf("no name is assigned to id %d", id) } if name == "a" || name == "b" { if !firstEntryCalled { firstEntryCalled = true if invalidChunkBeforeVerify { verifier.registerFails([]uint32{id}) } return true } <-verifyDone if invalidChunkAfterVerify { verifier.registerFails([]uint32{id}) } return true } return false })) }) time.Sleep(10 * time.Millisecond) // Perform verification if skipVerify { vr.SkipVerify() } else { _, err = vr.VerifyTOC(tocDgst) } if checkErr := checkError(wantVerifyFail, err); checkErr != nil { t.Errorf("verify: %v", checkErr) return } if err != nil { return } close(verifyDone) // Check the result of Cache() if checkErr := checkError(wantCacheFail, eg.Wait()); checkErr != nil { t.Errorf("cache: %v", checkErr) return } // Call Cache() again and check the result if checkErr := checkError(wantCacheFail2, vr.Cache()); checkErr != nil { t.Errorf("cache(2): %v", checkErr) return } }) } } } } type failIDVerifier struct { fails []uint32 failsMu sync.Mutex } func (f *failIDVerifier) registerFails(fails []uint32) { f.failsMu.Lock() defer f.failsMu.Unlock() f.fails = fails } func (f *failIDVerifier) verifier(id uint32, chunkDigest string) (digest.Verifier, error) { f.failsMu.Lock() defer f.failsMu.Unlock() success := true for _, n := range f.fails { if n == id { success = false break } } return &testVerifier{success}, nil } type testVerifier struct { success bool } func (bv *testVerifier) Write(p []byte) (n int, err error) { return len(p), nil } func (bv *testVerifier) Verified() bool { return bv.success } func checkError(wantFail bool, err error) error { if wantFail && err == nil { return fmt.Errorf("wanted to fail but succeeded") } else if !wantFail && err != nil { return fmt.Errorf("wanted to succeed verification but failed: %w", err) } return nil } func prepareMap(mr metadata.Reader, id uint32, p string) (off2id map[int64]uint32, id2path map[uint32]string, _ error) { attr, err := mr.GetAttr(id) if err != nil { return nil, nil, err } id2path = map[uint32]string{id: p} off2id = make(map[int64]uint32) if attr.Mode.IsRegular() { off, err := mr.GetOffset(id) if err != nil { return nil, nil, err } off2id[off] = id } var retErr error mr.ForeachChild(id, func(name string, id uint32, mode os.FileMode) bool { o2i, i2p, err := prepareMap(mr, id, path.Join(p, name)) if err != nil { retErr = err return false } for k, v := range o2i { off2id[k] = v } for k, v := range i2p { id2path[k] = v } return true }) if retErr != nil { return nil, nil, retErr } return off2id, id2path, nil } func testFailReader(t *testing.T, factory metadata.Store) { testFileName := "test" stargzFile, tocDigest, err := testutil.BuildEStargz([]testutil.TarEntry{ testutil.File(testFileName, sampleData1), }, testutil.WithEStargzOptions(estargz.WithChunkSize(sampleChunkSize))) if err != nil { t.Fatalf("failed to build sample estargz") } for _, rs := range []bool{true, false} { for _, vs := range []bool{true, false} { br := &breakReaderAt{ ReaderAt: stargzFile, success: true, } bev := &testChunkVerifier{true} mcache := cache.NewMemoryCache() mr, err := factory(io.NewSectionReader(br, 0, stargzFile.Size())) if err != nil { t.Fatalf("failed to prepare metadata reader") } defer mr.Close() vr, err := NewReader(mr, mcache, digest.FromString("")) if err != nil { t.Fatalf("failed to make new reader: %v", err) } defer vr.Close() vr.verifier = bev.verifier vr.r.verifier = bev.verifier gr, err := vr.VerifyTOC(tocDigest) if err != nil { t.Fatalf("failed to verify TOC: %v", err) } notexist := uint32(0) found := false for i := uint32(0); i < 1000000; i++ { if _, err := gr.Metadata().GetAttr(i); err != nil { notexist, found = i, true break } } if !found { t.Fatalf("free ID not found") } // tests for opening non-existing file _, err = gr.OpenFile(notexist) if err == nil { t.Errorf("succeeded to open file but wanted to fail") return } // tests failure behaviour of a file read tid, _, err := gr.Metadata().GetChild(gr.Metadata().RootID(), testFileName) if err != nil { t.Errorf("failed to get %q: %v", testFileName, err) return } fr, err := gr.OpenFile(tid) if err != nil { t.Errorf("failed to open file but wanted to succeed: %v", err) return } mcache.(*cache.MemoryCache).Membuf = map[string]*bytes.Buffer{} br.success = rs bev.success = vs // tests for reading file p := make([]byte, len(sampleData1)) n, err := fr.ReadAt(p, 0) if rs && vs { if err != nil || n != len(sampleData1) || !bytes.Equal([]byte(sampleData1), p) { t.Errorf("failed to read data but wanted to succeed: %v", err) return } } else { if err == nil { t.Errorf("succeeded to read data but wanted to fail (reader:%v,verify:%v)", rs, vs) return } } } } } type breakReaderAt struct { io.ReaderAt success bool } func (br *breakReaderAt) ReadAt(p []byte, off int64) (int, error) { if br.success { return br.ReaderAt.ReadAt(p, off) } return 0, fmt.Errorf("failed") } type testChunkVerifier struct { success bool } func (bev *testChunkVerifier) verifier(id uint32, chunkDigest string) (digest.Verifier, error) { return &testVerifier{bev.success}, nil } stargz-snapshotter-0.12.0/fs/remote/000077500000000000000000000000001426301527400173635ustar00rootroot00000000000000stargz-snapshotter-0.12.0/fs/remote/blob.go000066400000000000000000000320241426301527400206310ustar00rootroot00000000000000/* 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. */ /* Copyright 2019 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the NOTICE.md file. */ package remote import ( "context" "fmt" "io" "regexp" "sort" "strings" "sync" "time" "github.com/containerd/containerd/reference" "github.com/containerd/stargz-snapshotter/cache" "github.com/containerd/stargz-snapshotter/fs/source" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "golang.org/x/sync/errgroup" "golang.org/x/sync/singleflight" ) var contentRangeRegexp = regexp.MustCompile(`bytes ([0-9]+)-([0-9]+)/([0-9]+|\\*)`) type Blob interface { Check() error Size() int64 FetchedSize() int64 ReadAt(p []byte, offset int64, opts ...Option) (int, error) Cache(offset int64, size int64, opts ...Option) error Refresh(ctx context.Context, host source.RegistryHosts, refspec reference.Spec, desc ocispec.Descriptor) error Close() error } type blob struct { fetcher fetcher fetcherMu sync.Mutex size int64 chunkSize int64 prefetchChunkSize int64 cache cache.BlobCache lastCheck time.Time lastCheckMu sync.Mutex checkInterval time.Duration fetchTimeout time.Duration fetchedRegionSet regionSet fetchedRegionSetMu sync.Mutex fetchedRegionGroup singleflight.Group fetchedRegionCopyMu sync.Mutex resolver *Resolver closed bool closedMu sync.Mutex } func makeBlob(fetcher fetcher, size int64, chunkSize int64, prefetchChunkSize int64, blobCache cache.BlobCache, lastCheck time.Time, checkInterval time.Duration, r *Resolver, fetchTimeout time.Duration) *blob { return &blob{ fetcher: fetcher, size: size, chunkSize: chunkSize, prefetchChunkSize: prefetchChunkSize, cache: blobCache, lastCheck: lastCheck, checkInterval: checkInterval, resolver: r, fetchTimeout: fetchTimeout, } } func (b *blob) Close() error { b.closedMu.Lock() defer b.closedMu.Unlock() if b.closed { return nil } b.closed = true return b.cache.Close() } func (b *blob) isClosed() bool { b.closedMu.Lock() closed := b.closed b.closedMu.Unlock() return closed } func (b *blob) Refresh(ctx context.Context, hosts source.RegistryHosts, refspec reference.Spec, desc ocispec.Descriptor) error { if b.isClosed() { return fmt.Errorf("blob is already closed") } // refresh the fetcher f, newSize, err := b.resolver.resolveFetcher(ctx, hosts, refspec, desc) if err != nil { return err } if 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 b.fetcherMu.Lock() b.fetcher = f b.fetcherMu.Unlock() b.lastCheckMu.Lock() b.lastCheck = time.Now() b.lastCheckMu.Unlock() return nil } func (b *blob) Check() error { if b.isClosed() { return fmt.Errorf("blob is already closed") } now := time.Now() b.lastCheckMu.Lock() lastCheck := b.lastCheck b.lastCheckMu.Unlock() if now.Sub(lastCheck) < b.checkInterval { // do nothing if not expired return nil } b.fetcherMu.Lock() fr := b.fetcher b.fetcherMu.Unlock() err := fr.check() if err == nil { // update lastCheck only if check succeeded. // on failure, we should check this layer next time again. b.lastCheckMu.Lock() b.lastCheck = now b.lastCheckMu.Unlock() } return err } func (b *blob) Size() int64 { return b.size } func (b *blob) FetchedSize() int64 { b.fetchedRegionSetMu.Lock() sz := b.fetchedRegionSet.totalSize() b.fetchedRegionSetMu.Unlock() return sz } func makeSyncKey(allData map[region]io.Writer) string { keys := make([]string, len(allData)) keysIndex := 0 for key := range allData { keys[keysIndex] = fmt.Sprintf("[%d,%d]", key.b, key.e) keysIndex++ } sort.Strings(keys) return strings.Join(keys, ",") } func (b *blob) cacheAt(offset int64, size int64, fr fetcher, cacheOpts *options) error { fetchReg := region{floor(offset, b.chunkSize), ceil(offset+size-1, b.chunkSize) - 1} discard := make(map[region]io.Writer) err := b.walkChunks(fetchReg, func(reg region) error { if r, err := b.cache.Get(fr.genID(reg), cacheOpts.cacheOpts...); err == nil { return r.Close() // nop if the cache hits } discard[reg] = io.Discard return nil }) if err != nil { return err } return b.fetchRange(discard, cacheOpts) } func (b *blob) Cache(offset int64, size int64, opts ...Option) error { if b.isClosed() { return fmt.Errorf("blob is already closed") } var cacheOpts options for _, o := range opts { o(&cacheOpts) } b.fetcherMu.Lock() fr := b.fetcher b.fetcherMu.Unlock() if b.prefetchChunkSize <= b.chunkSize { return b.cacheAt(offset, size, fr, &cacheOpts) } eg, _ := errgroup.WithContext(context.Background()) fetchSize := b.chunkSize * (b.prefetchChunkSize / b.chunkSize) end := offset + size for i := offset; i < end; i += fetchSize { i, l := i, fetchSize if i+l > end { l = end - i } eg.Go(func() error { return b.cacheAt(i, l, fr, &cacheOpts) }) } return eg.Wait() } // ReadAt reads remote chunks from specified offset for the buffer size. // It tries to fetch as many chunks as possible from local cache. // We can configure this function with options. func (b *blob) ReadAt(p []byte, offset int64, opts ...Option) (int, error) { if b.isClosed() { return 0, fmt.Errorf("blob is already closed") } if len(p) == 0 || offset > b.size { return 0, nil } // Make the buffer chunk aligned allRegion := region{floor(offset, b.chunkSize), ceil(offset+int64(len(p))-1, b.chunkSize) - 1} allData := make(map[region]io.Writer) var readAtOpts options for _, o := range opts { o(&readAtOpts) } // 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() b.walkChunks(allRegion, func(chunk region) error { var ( base = positive(chunk.b - offset) lowerUnread = positive(offset - chunk.b) upperUnread = positive(chunk.e + 1 - (offset + int64(len(p)))) expectedSize = chunk.size() - upperUnread - lowerUnread ) // Check if the content exists in the cache r, err := b.cache.Get(fr.genID(chunk), readAtOpts.cacheOpts...) 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 get the whole chunk here and add it to the cache so that following // reads against neighboring chunks can take the data without making HTTP requests. allData[chunk] = newBytesWriter(p[base:base+expectedSize], lowerUnread) return nil }) // Read required data if err := b.fetchRange(allData, &readAtOpts); err != nil { return 0, err } // Adjust the buffer size according to the blob size if remain := b.size - offset; int64(len(p)) >= remain { if remain < 0 { remain = 0 } p = p[:remain] } return len(p), nil } // fetchRegions fetches all specified chunks from remote blob and puts it in the local cache. // It must be called from within fetchRange and need to ensure that it is inside the singleflight `Do` operation. func (b *blob) fetchRegions(allData map[region]io.Writer, fetched map[region]bool, opts *options) error { if len(allData) == 0 { return nil } // 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 var req []region for reg := range allData { req = append(req, reg) fetched[reg] = false } fetchCtx, cancel := context.WithTimeout(context.Background(), b.fetchTimeout) defer cancel() if opts.ctx != nil { fetchCtx = opts.ctx } mr, err := fr.fetch(fetchCtx, req, true) if err != nil { return err } defer mr.Close() // Update the check timer because we succeeded to access the blob b.lastCheckMu.Lock() b.lastCheck = time.Now() b.lastCheckMu.Unlock() // chunk and cache responsed data. Regions must be aligned by chunk size. // TODO: Reorganize remoteData to make it be aligned by chunk size for { reg, p, err := mr.Next() if err == io.EOF { break } else if err != nil { return fmt.Errorf("failed to read multipart resp: %w", err) } if err := b.walkChunks(reg, func(chunk region) (retErr error) { id := fr.genID(chunk) cw, err := b.cache.Add(id, opts.cacheOpts...) if err != nil { 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 }); err != nil { return fmt.Errorf("failed to get chunks: %w", err) } } // Check all chunks are fetched var unfetched []region for c, b := range fetched { if !b { unfetched = append(unfetched, c) } } if unfetched != nil { return fmt.Errorf("failed to fetch region %v", unfetched) } return nil } // fetchRange fetches all specified chunks from local cache and remote blob. func (b *blob) fetchRange(allData map[region]io.Writer, opts *options) error { if len(allData) == 0 { 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) fetched := make(map[region]bool) _, err, shared := b.fetchedRegionGroup.Do(key, func() (interface{}, error) { return nil, b.fetchRegions(allData, fetched, opts) }) // 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 err == nil && shared { for reg := range allData { 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 } type walkFunc func(reg region) error // walkChunks walks chunks from begin to end in order in the specified region. // specified region must be aligned by chunk size. func (b *blob) walkChunks(allRegion region, walkFn walkFunc) error { if allRegion.b%b.chunkSize != 0 { return fmt.Errorf("region (%d, %d) must be aligned by chunk size", allRegion.b, allRegion.e) } for i := allRegion.b; i <= allRegion.e && i < b.size; i += b.chunkSize { reg := region{i, i + b.chunkSize - 1} if reg.e >= b.size { reg.e = b.size - 1 } if err := walkFn(reg); err != nil { return err } } return nil } func newBytesWriter(dest []byte, destOff int64) io.Writer { return &bytesWriter{ dest: dest, destOff: destOff, current: 0, } } type bytesWriter struct { dest []byte destOff int64 current int64 } func (bw *bytesWriter) Write(p []byte) (int, error) { defer func() { bw.current = bw.current + int64(len(p)) }() var ( destBase = positive(bw.current - bw.destOff) pBegin = positive(bw.destOff - bw.current) pEnd = positive(bw.destOff + int64(len(bw.dest)) - bw.current) ) if destBase > int64(len(bw.dest)) { return len(p), nil } if pBegin >= int64(len(p)) { return len(p), nil } if pEnd > int64(len(p)) { pEnd = int64(len(p)) } copy(bw.dest[destBase:], p[pBegin:pEnd]) return len(p), nil } func floor(n int64, unit int64) int64 { return (n / unit) * unit } func ceil(n int64, unit int64) int64 { return (n/unit + 1) * unit } func positive(n int64) int64 { if n < 0 { return 0 } return n } stargz-snapshotter-0.12.0/fs/remote/blob_test.go000066400000000000000000000546531426301527400217040ustar00rootroot00000000000000/* 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. */ /* Copyright 2019 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the NOTICE.md file. */ package remote import ( "bytes" "fmt" "io" "mime" "mime/multipart" "net/http" "net/textproto" "sort" "strconv" "strings" "sync" "sync/atomic" "testing" "time" "github.com/containerd/stargz-snapshotter/cache" ) const ( testURL = "http://testdummy.com/v2/library/test/blobs/sha256:deadbeaf" rangeHeaderPrefix = "bytes=" sampleChunkSize = 3 sampleMiddleOffset = sampleChunkSize / 2 sampleData1 = "0123456789" lastChunkOffset1 = sampleChunkSize * (int64(len(sampleData1)) / sampleChunkSize) defaultPrefetchChunkSize = 0 ) // Tests ReadAt and Cache method of each file. func TestReadAt(t *testing.T) { sizeCond := map[string]int64{ "single_chunk": sampleChunkSize - sampleMiddleOffset, "multi_chunks": 2*sampleChunkSize + sampleMiddleOffset, } innerOffsetCond := map[string]int64{ "at_top": 0, "at_middle": sampleMiddleOffset, } baseOffsetCond := map[string]int64{ "of_1st_chunk": sampleChunkSize * 0, "of_2nd_chunk": sampleChunkSize * 1, "of_last_chunk": sampleChunkSize * (int64(len(sampleData1)) / sampleChunkSize), } blobSizeCond := map[string]int64{ "in_1_chunk_blob": sampleChunkSize * 1, "in_3_chunks_blob": sampleChunkSize * 3, "in_max_size_blob": int64(len(sampleData1)), } prefetchChunkSizeCond := map[string]int64{ "single_get_prefetch": 0, "multiple_get_prefetch": sampleChunkSize * 2, } type cacheCond struct { reg region mustHit bool } transportCond := map[string]struct { allowMultiRange bool cacheCond []cacheCond }{ "with_multi_reg_with_clean_cache": { allowMultiRange: true, cacheCond: nil, }, "with_single_reg_with_clean_cache": { allowMultiRange: false, cacheCond: nil, }, "with_multi_reg_with_edge_filled_cache": { allowMultiRange: true, cacheCond: []cacheCond{ {region{0, sampleChunkSize - 1}, true}, {region{lastChunkOffset1, int64(len(sampleData1)) - 1}, true}, }, }, "with_single_reg_with_edge_filled_cache": { allowMultiRange: false, cacheCond: []cacheCond{ {region{0, sampleChunkSize - 1}, true}, {region{lastChunkOffset1, int64(len(sampleData1)) - 1}, true}, }, }, "with_multi_reg_with_sparse_cache": { allowMultiRange: true, cacheCond: []cacheCond{ {region{0, sampleChunkSize - 1}, true}, {region{2 * sampleChunkSize, 3*sampleChunkSize - 1}, true}, }, }, "with_single_reg_with_sparse_cache": { allowMultiRange: false, cacheCond: []cacheCond{ {region{0, sampleChunkSize - 1}, true}, {region{2 * sampleChunkSize, 3*sampleChunkSize - 1}, false}, }, }, } for sn, size := range sizeCond { for in, innero := range innerOffsetCond { for bo, baseo := range baseOffsetCond { for bs, blobsize := range blobSizeCond { for pc, prefetchchunksize := range prefetchChunkSizeCond { for tc, trCond := range transportCond { t.Run(fmt.Sprintf("reading_%s_%s_%s_%s_%s_%s", sn, in, bo, bs, pc, tc), func(t *testing.T) { if blobsize > int64(len(sampleData1)) { t.Fatal("sample file size is larger than sample data") } wantN := size offset := baseo + innero if remain := blobsize - offset; remain < wantN { if wantN = remain; wantN < 0 { wantN = 0 } } // use constant string value as a data source. want := strings.NewReader(sampleData1) // data we want to get. wantData := make([]byte, wantN) _, err := want.ReadAt(wantData, offset) if err != nil && err != io.EOF { t.Fatalf("want.ReadAt (offset=%d,size=%d): %v", offset, wantN, err) } // data we get through a remote blob. blob := []byte(sampleData1)[:blobsize] // Check with allowing multi range requests var cacheChunks []region var except []region for _, cond := range trCond.cacheCond { cacheChunks = append(cacheChunks, cond.reg) if cond.mustHit { except = append(except, cond.reg) } } tr := multiRoundTripper(t, blob, allowMultiRange(trCond.allowMultiRange), exceptChunks(except)) // Check ReadAt method bb1 := makeTestBlob(t, blobsize, sampleChunkSize, prefetchchunksize, tr) cacheAll(t, bb1, cacheChunks) checkRead(t, wantData, bb1, offset, size) // Check Cache method bb2 := makeTestBlob(t, blobsize, sampleChunkSize, prefetchchunksize, tr) cacheAll(t, bb2, cacheChunks) checkCache(t, bb2, offset, size) }) } } } } } } } func cacheAll(t *testing.T, b *blob, chunks []region) { for _, reg := range chunks { id := b.fetcher.genID(reg) w, err := b.cache.Add(id) if err != nil { w.Close() t.Fatalf("failed to add cache %v: %v", id, err) } if _, err := w.Write([]byte(sampleData1[reg.b : reg.e+1])); err != nil { w.Close() t.Fatalf("failed to write cache %v: %v", id, err) } if err := w.Commit(); err != nil { w.Close() t.Fatalf("failed to commit cache %v: %v", id, err) } w.Close() } } func checkRead(t *testing.T, wantData []byte, r *blob, offset int64, wantSize int64) { respData := make([]byte, wantSize) t.Logf("reading offset:%d, size:%d", offset, wantSize) n, err := r.ReadAt(respData, offset) if err != nil { t.Errorf("failed to read off=%d, size=%d, blobsize=%d: %v", offset, wantSize, r.Size(), err) return } respData = respData[:n] if !bytes.Equal(wantData, respData) { t.Errorf("off=%d, blobsize=%d; read data{size=%d,data=%q}; want (size=%d,data=%q)", offset, r.Size(), len(respData), string(respData), len(wantData), string(wantData)) return } // check cache has valid contents. checkAllCached(t, r, offset, wantSize) } func checkCache(t *testing.T, r *blob, offset int64, size int64) { if err := r.Cache(offset, size); err != nil { t.Errorf("failed to cache off=%d, size=%d, blobsize=%d: %v", offset, size, r.Size(), err) return } // check cache has valid contents. checkAllCached(t, r, offset, size) } func checkAllCached(t *testing.T, r *blob, offset, size int64) { cn := 0 whole := region{floor(offset, r.chunkSize), ceil(offset+size-1, r.chunkSize) - 1} if err := r.walkChunks(whole, func(reg region) error { data := make([]byte, reg.size()) id := r.fetcher.genID(reg) r, err := r.cache.Get(id) if err != nil { return fmt.Errorf("missed cache of region={%d,%d}(size=%d): %v", reg.b, reg.e, reg.size(), err) } defer r.Close() if n, err := r.ReadAt(data, 0); (err != nil && err != io.EOF) || int64(n) != reg.size() { return fmt.Errorf("failed to read cache of region={%d,%d}(size=%d): %v", reg.b, reg.e, reg.size(), err) } cn++ return nil }); err != nil { t.Errorf("%v", err) return } } // Tests ReadAt method for failure cases. func TestFailReadAt(t *testing.T) { // test failed http respose. r := makeTestBlob(t, int64(len(sampleData1)), sampleChunkSize, defaultPrefetchChunkSize, failRoundTripper()) respData := make([]byte, len(sampleData1)) _, err := r.ReadAt(respData, 0) if err == nil || err == io.EOF { t.Errorf("must be fail for http failure but err=%v", err) return } // test broken body with allowing multi range checkBrokenBody(t, true) // with allowing multi range checkBrokenBody(t, false) // with prohibiting multi range // test broken header checkBrokenHeader(t, true) // with allowing multi range checkBrokenHeader(t, false) // with prohibiting multi range } func checkBrokenBody(t *testing.T, allowMultiRange bool) { respData := make([]byte, len(sampleData1)) r := makeTestBlob(t, int64(len(sampleData1)), sampleChunkSize, defaultPrefetchChunkSize, brokenBodyRoundTripper(t, []byte(sampleData1), allowMultiRange)) if _, err := r.ReadAt(respData, 0); err == nil || err == io.EOF { t.Errorf("must be fail for broken full body but err=%v (allowMultiRange=%v)", err, allowMultiRange) return } r = makeTestBlob(t, int64(len(sampleData1)), sampleChunkSize, defaultPrefetchChunkSize, brokenBodyRoundTripper(t, []byte(sampleData1), allowMultiRange)) if _, err := r.ReadAt(respData[0:len(sampleData1)/2], 0); err == nil || err == io.EOF { t.Errorf("must be fail for broken multipart body but err=%v (allowMultiRange=%v)", err, allowMultiRange) return } } func checkBrokenHeader(t *testing.T, allowMultiRange bool) { r := makeTestBlob(t, int64(len(sampleData1)), sampleChunkSize, defaultPrefetchChunkSize, brokenHeaderRoundTripper(t, []byte(sampleData1), allowMultiRange)) respData := make([]byte, len(sampleData1)) if _, err := r.ReadAt(respData[0:len(sampleData1)/2], 0); err == nil || err == io.EOF { t.Errorf("must be fail for broken multipart header but err=%v (allowMultiRange=%v)", err, allowMultiRange) return } } func TestParallelDownloadingBehavior(t *testing.T) { type regionsBoundaries struct { regions []region start int64 end int64 } type testData struct { name string regions [3]regionsBoundaries roundtripCount int64 chunkSize int64 content string } tests := []testData{ { name: "no_data", regions: [3]regionsBoundaries{}, roundtripCount: 0, chunkSize: 4, }, { name: "same_regions", regions: [3]regionsBoundaries{ { regions: []region{ { b: 0, e: 3, }, }, start: 0, end: 3, }, { regions: []region{ { b: 0, e: 3, }, }, start: 0, end: 3, }, { regions: []region{ { b: 0, e: 3, }, }, start: 0, end: 3, }, }, roundtripCount: 1, chunkSize: 4, content: "test", }, { name: "same_regions_multiple_values", regions: [3]regionsBoundaries{ { regions: []region{ { b: 0, e: 3, }, { b: 4, e: 7, }, }, start: 0, end: 7, }, { regions: []region{ { b: 0, e: 3, }, { b: 4, e: 7, }, }, start: 0, end: 7, }, { regions: []region{ { b: 0, e: 3, }, { b: 4, e: 7, }, }, start: 0, end: 7, }, }, roundtripCount: 1, chunkSize: 4, content: "test1234", }, { name: "different_regions", regions: [3]regionsBoundaries{ { regions: []region{ { b: 0, e: 3, }, }, start: 0, end: 3, }, { regions: []region{ { b: 4, e: 7, }, }, start: 4, end: 7, }, { regions: []region{ { b: 8, e: 11, }, }, start: 8, end: 11, }, }, roundtripCount: 3, chunkSize: 4, content: "test12345678", }, { name: "some_overlap", regions: [3]regionsBoundaries{ { regions: []region{ { b: 0, e: 3, }, }, start: 0, end: 3, }, { regions: []region{ { b: 0, e: 3, }, }, start: 0, end: 3, }, { regions: []region{ { b: 4, e: 7, }, }, start: 4, end: 7, }, }, roundtripCount: 2, chunkSize: 4, content: "test1234", }, } var wg sync.WaitGroup // we always run 3 routines routines := 3 for _, tst := range tests { var ( tr = &callsCountRoundTripper{ content: tst.content, } b = &blob{ fetcher: &httpFetcher{ url: "test", tr: tr, }, chunkSize: tst.chunkSize, size: int64(len(tst.content)), cache: cache.NewMemoryCache(), } ) start := make(chan struct{}) wg.Add(routines) var contentBytes [3][]byte for i := 0; i < routines; i++ { p := make([]byte, len(tst.content)) contentBytes[i] = p allData := make(map[region]io.Writer) if i < len(tst.regions) { offset := int64(0) for j := range tst.regions[i].regions { r := tst.regions[i].regions[j] var ( base = positive(r.b - offset) lowerUnread = positive(offset - r.b) upperUnread = positive(r.e + 1 - (offset + int64(len(p)))) expectedSize = r.size() - upperUnread - lowerUnread ) allData[tst.regions[i].regions[j]] = newBytesWriter(p[base:base+expectedSize], lowerUnread) } } go func() { <-start // by blocking on channel start we can ensure that the goroutines will run at approximately the same time defer wg.Done() b.fetchRange(allData, &options{}) }() } close(start) // starting wg.Wait() // We expect the number of round trip calls to be 1, since we are making 5 calls to fetchRange with // overlapping intervals. if tr.count != tst.roundtripCount { t.Errorf("%v test failed: the round trip count should be %v, but was %v", tst.name, tst.roundtripCount, tr.count) } // Check for contents for j := range contentBytes { start := tst.regions[j].start end := tst.regions[j].end for i := start; i < end; i++ { if contentBytes[j][i] != []byte(tst.content)[i] { t.Errorf("%v test failed: the output sequence is wrong, wanted %v, got %v", tst.name, []byte(tst.content)[start:end], contentBytes[j][start:end]) break } } } } } func makeTestBlob(t *testing.T, size int64, chunkSize int64, prefetchChunkSize int64, fn RoundTripFunc) *blob { var ( lastCheck time.Time checkInterval time.Duration ) return makeBlob( &httpFetcher{ url: testURL, tr: fn, }, size, chunkSize, prefetchChunkSize, cache.NewMemoryCache(), lastCheck, checkInterval, &Resolver{}, time.Duration(defaultFetchTimeoutSec)*time.Second) } func TestCheckInterval(t *testing.T) { var ( tr = &calledRoundTripper{} firstTime = time.Now() b = &blob{ fetcher: &httpFetcher{ url: "test", tr: tr, }, lastCheck: firstTime, } ) check := func(name string, checkInterval time.Duration) (time.Time, bool) { beforeUpdate := time.Now() time.Sleep(time.Millisecond) tr.called = false b.checkInterval = checkInterval if err := b.Check(); err != nil { t.Fatalf("%q: check mustn't be failed", name) } time.Sleep(time.Millisecond) afterUpdate := time.Now() if !tr.called { return b.lastCheck, false } 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) } return b.lastCheck, true } // second time(not expired yet) secondTime, called := check("second time", time.Hour) if called { t.Error("mustn't be checked if not expired") } if !secondTime.Equal(firstTime) { t.Errorf("lastCheck time must be same as first time(%q) but %q", firstTime, secondTime) } // third time(expired, must be checked) if _, called := check("third time", 0); !called { t.Error("must be called for the third time") } } type callsCountRoundTripper struct { count int64 content string } func (c *callsCountRoundTripper) RoundTrip(req *http.Request) (res *http.Response, err error) { atomic.AddInt64(&c.count, 1) time.Sleep(50 * time.Millisecond) // sleep for 50 milliseconds to emulate the http call and to make sure that we can run tests on parallel goroutines convertBody := func(r io.ReadCloser) io.ReadCloser { return r } header := make(http.Header) header.Add("Content-Length", fmt.Sprintf("%d", len(c.content))) return &http.Response{ StatusCode: http.StatusOK, Header: header, Body: convertBody(io.NopCloser(bytes.NewReader([]byte(c.content)))), }, nil } type calledRoundTripper struct { called bool } func (c *calledRoundTripper) RoundTrip(req *http.Request) (res *http.Response, err error) { c.called = true res = &http.Response{ StatusCode: http.StatusOK, Header: make(http.Header), Body: io.NopCloser(bytes.NewReader([]byte("test"))), } return } type RoundTripFunc func(req *http.Request) *http.Response func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { return f(req), nil } type bodyConverter func(r io.ReadCloser) io.ReadCloser type exceptChunks []region type allowMultiRange bool func multiRoundTripper(t *testing.T, contents []byte, opts ...interface{}) RoundTripFunc { multiRangeEnable := true doNotFetch := []region{} convertBody := func(r io.ReadCloser) io.ReadCloser { return r } for _, opt := range opts { if v, ok := opt.(allowMultiRange); ok { multiRangeEnable = bool(v) } else if v, ok := opt.(exceptChunks); ok { doNotFetch = []region(v) } else if v, ok := opt.(bodyConverter); ok { convertBody = (func(r io.ReadCloser) io.ReadCloser)(v) } } emptyResponse := func(statusCode int) *http.Response { return &http.Response{ StatusCode: statusCode, Header: make(http.Header), Body: io.NopCloser(bytes.NewReader([]byte{})), } } return func(req *http.Request) *http.Response { // Validate request if req.Method != "GET" || req.URL.String() != testURL { return emptyResponse(http.StatusBadRequest) } ranges := req.Header.Get("Range") if ranges == "" { return emptyResponse(http.StatusBadRequest) } if !strings.HasPrefix(ranges, rangeHeaderPrefix) { return emptyResponse(http.StatusBadRequest) } rlist := strings.Split(ranges[len(rangeHeaderPrefix):], ",") if len(rlist) == 0 { return emptyResponse(http.StatusBadRequest) } // check this request can be served as one whole blob. var sorted []region for _, part := range rlist { begin, end := parseRangeString(t, part) sorted = append(sorted, region{begin, end}) } sort.Slice(sorted, func(i, j int) bool { return sorted[i].b < sorted[j].b }) var sparse bool if sorted[0].b == 0 { var max int64 for _, reg := range sorted { if reg.e > max { if max < reg.b-1 { sparse = true break } max = reg.e } } if max >= int64(len(contents)-1) && !sparse { t.Logf("serving whole range %q = %d", ranges, len(contents)) header := make(http.Header) header.Add("Content-Length", fmt.Sprintf("%d", len(contents))) return &http.Response{ StatusCode: http.StatusOK, Header: header, Body: convertBody(io.NopCloser(bytes.NewReader(contents))), } } } if !multiRangeEnable { if len(rlist) > 1 { return emptyResponse(http.StatusBadRequest) // prohibiting multi range } // serve as single part response begin, end := parseRangeString(t, rlist[0]) target := region{begin, end} for _, reg := range doNotFetch { if target.b <= reg.b && reg.e <= target.e { t.Fatalf("Requested prohibited region of chunk(singlepart): (%d, %d) contained in fetching region (%d, %d)", reg.b, reg.e, target.b, target.e) } } header := make(http.Header) header.Add("Content-Length", fmt.Sprintf("%d", target.size())) header.Add("Content-Range", fmt.Sprintf("bytes %d-%d/%d", target.b, target.e, len(contents))) header.Add("Content-Type", "application/octet-stream") part := contents[target.b : target.e+1] return &http.Response{ StatusCode: http.StatusPartialContent, Header: header, Body: convertBody(io.NopCloser(bytes.NewReader(part))), } } // Write multipart response. var buf bytes.Buffer mw := multipart.NewWriter(&buf) for _, part := range rlist { mh := make(textproto.MIMEHeader) mh.Set("Content-Range", fmt.Sprintf("bytes %s/%d", part, len(contents))) w, err := mw.CreatePart(mh) if err != nil { t.Fatalf("failed to create part: %v", err) } begin, end := parseRangeString(t, part) if begin >= int64(len(contents)) { // skip if out of range. continue } if end > int64(len(contents)-1) { end = int64(len(contents) - 1) } for _, reg := range doNotFetch { if begin <= reg.b && reg.e <= end { t.Fatalf("Requested prohibited region of chunk (multipart): (%d, %d) contained in fetching region (%d, %d)", reg.b, reg.e, begin, end) } } if n, err := w.Write(contents[begin : end+1]); err != nil || int64(n) != end+1-begin { t.Fatalf("failed to write to part(%d-%d): %v", begin, end, err) } } mw.Close() param := map[string]string{ "boundary": mw.Boundary(), } header := make(http.Header) header.Add("Content-Type", mime.FormatMediaType("multipart/text", param)) return &http.Response{ StatusCode: http.StatusPartialContent, Header: header, Body: convertBody(io.NopCloser(&buf)), } } } func failRoundTripper() RoundTripFunc { return func(req *http.Request) *http.Response { return &http.Response{ StatusCode: http.StatusInternalServerError, Header: make(http.Header), Body: io.NopCloser(bytes.NewReader([]byte{})), } } } func brokenBodyRoundTripper(t *testing.T, contents []byte, multiRange bool) RoundTripFunc { breakReadCloser := func(r io.ReadCloser) io.ReadCloser { defer r.Close() data, err := io.ReadAll(r) if err != nil { t.Fatalf("failed to break read closer faild to read original: %v", err) } return io.NopCloser(bytes.NewReader(data[:len(data)/2])) } tr := multiRoundTripper(t, contents, allowMultiRange(multiRange), bodyConverter(breakReadCloser)) return func(req *http.Request) *http.Response { return tr(req) } } func brokenHeaderRoundTripper(t *testing.T, contents []byte, multiRange bool) RoundTripFunc { tr := multiRoundTripper(t, contents, allowMultiRange(multiRange)) return func(req *http.Request) *http.Response { res := tr(req) res.Header = make(http.Header) return res } } func parseRangeString(t *testing.T, rangeString string) (int64, int64) { rng := strings.Split(rangeString, "-") if len(rng) != 2 { t.Fatalf("falied to parse range %q", rng) } begin, err := strconv.ParseInt(rng[0], 10, 64) if err != nil { t.Fatalf("failed to parse beginning offset: %v", err) } end, err := strconv.ParseInt(rng[1], 10, 64) if err != nil { t.Fatalf("failed to parse ending offset: %v", err) } return begin, end } stargz-snapshotter-0.12.0/fs/remote/resolver.go000066400000000000000000000472101426301527400215570ustar00rootroot00000000000000/* 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. */ /* Copyright 2019 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the NOTICE.md file. */ package remote import ( "context" "crypto/sha256" "fmt" "io" "math/rand" "mime" "mime/multipart" "net/http" "path" "strconv" "strings" "sync" "time" "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/log" "github.com/containerd/containerd/reference" "github.com/containerd/containerd/remotes/docker" "github.com/containerd/stargz-snapshotter/cache" "github.com/containerd/stargz-snapshotter/fs/config" commonmetrics "github.com/containerd/stargz-snapshotter/fs/metrics/common" "github.com/containerd/stargz-snapshotter/fs/source" "github.com/hashicorp/go-multierror" rhttp "github.com/hashicorp/go-retryablehttp" digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) const ( defaultChunkSize = 50000 defaultValidIntervalSec = 60 defaultFetchTimeoutSec = 300 defaultMaxRetries = 5 defaultMinWaitMSec = 30 defaultMaxWaitMSec = 300000 ) func NewResolver(cfg config.BlobConfig, handlers map[string]Handler) *Resolver { if cfg.ChunkSize == 0 { // zero means "use default chunk size" cfg.ChunkSize = defaultChunkSize } if cfg.ValidInterval == 0 { // zero means "use default interval" cfg.ValidInterval = defaultValidIntervalSec } if cfg.CheckAlways { cfg.ValidInterval = 0 } if cfg.FetchTimeoutSec == 0 { cfg.FetchTimeoutSec = defaultFetchTimeoutSec } if cfg.MaxRetries == 0 { cfg.MaxRetries = defaultMaxRetries } if cfg.MinWaitMSec == 0 { cfg.MinWaitMSec = defaultMinWaitMSec } if cfg.MaxWaitMSec == 0 { cfg.MaxWaitMSec = defaultMaxWaitMSec } return &Resolver{ blobConfig: cfg, handlers: handlers, } } type Resolver struct { blobConfig config.BlobConfig handlers map[string]Handler } type fetcher interface { fetch(ctx context.Context, rs []region, retry bool) (multipartReadCloser, error) check() error genID(reg region) string } func (r *Resolver) Resolve(ctx context.Context, hosts source.RegistryHosts, refspec reference.Spec, desc ocispec.Descriptor, blobCache cache.BlobCache) (Blob, error) { f, size, err := r.resolveFetcher(ctx, hosts, refspec, desc) if err != nil { return nil, err } blobConfig := &r.blobConfig return makeBlob(f, size, blobConfig.ChunkSize, blobConfig.PrefetchChunkSize, blobCache, time.Now(), time.Duration(blobConfig.ValidInterval)*time.Second, r, time.Duration(blobConfig.FetchTimeoutSec)*time.Second), nil } 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 fc := &fetcherConfig{ hosts: hosts, refspec: refspec, desc: desc, maxRetries: blobConfig.MaxRetries, minWaitMSec: time.Duration(blobConfig.MinWaitMSec) * time.Millisecond, maxWaitMSec: time.Duration(blobConfig.MaxWaitMSec) * time.Millisecond, } var handlersErr error for name, p := range r.handlers { // TODO: allow to configure the selection of readers based on the hostname in refspec r, size, err := p.Handle(ctx, desc) if err != nil { handlersErr = multierror.Append(handlersErr, err) continue } log.G(ctx).WithField("handler name", name).WithField("ref", refspec.String()).WithField("digest", desc.Digest). Debugf("contents is provided by a handler") return &remoteFetcher{r}, size, nil } log.G(ctx).WithError(handlersErr).WithField("ref", refspec.String()).WithField("digest", desc.Digest).Debugf("using default handler") hf, size, err := newHTTPFetcher(ctx, fc) if err != nil { return nil, 0, err } if blobConfig.ForceSingleRangeMode { hf.singleRangeMode() } return hf, size, err } type fetcherConfig struct { hosts source.RegistryHosts refspec reference.Spec desc ocispec.Descriptor maxRetries int minWaitMSec time.Duration maxWaitMSec time.Duration } func jitter(duration time.Duration) time.Duration { if duration <= 0 { return duration } return time.Duration(rand.Int63n(int64(duration)) + int64(duration)) } // backoffStrategy extends retryablehttp's DefaultBackoff to add a random jitter to avoid overwhelming the repository // when it comes back online // DefaultBackoff either tries to parse the 'Retry-After' header of the response; or, it uses an exponential backoff // 2 ^ numAttempts, limited by max func backoffStrategy(min, max time.Duration, attemptNum int, resp *http.Response) time.Duration { delayTime := rhttp.DefaultBackoff(min, max, attemptNum, resp) return jitter(delayTime) } // retryStrategy extends retryablehttp's DefaultRetryPolicy to debug log the error when retrying // DefaultRetryPolicy retries whenever err is non-nil (except for some url errors) or if returned // status code is 429 or 5xx (except 501) func retryStrategy(ctx context.Context, resp *http.Response, err error) (bool, error) { retry, err2 := rhttp.DefaultRetryPolicy(ctx, resp, err) if retry { log.G(ctx).WithError(err).Debugf("Retrying request") } return retry, err2 } func newHTTPFetcher(ctx context.Context, fc *fetcherConfig) (*httpFetcher, int64, error) { reghosts, err := fc.hosts(fc.refspec) if err != nil { return nil, 0, err } desc := fc.desc if desc.Digest.String() == "" { return nil, 0, fmt.Errorf("Digest is mandatory in layer descriptor") } digest := desc.Digest pullScope, err := docker.RepositoryScope(fc.refspec, false) if err != nil { return nil, 0, err } // Try to create fetcher until succeeded rErr := fmt.Errorf("failed to resolve") for _, host := range reghosts { if host.Host == "" || strings.Contains(host.Host, "/") { rErr = fmt.Errorf("invalid destination (host %q, ref:%q, digest:%q): %w", host.Host, fc.refspec, digest, rErr) continue // Try another } // Prepare transport with authorization functionality tr := host.Client.Transport if rt, ok := tr.(*rhttp.RoundTripper); ok { rt.Client.RetryMax = fc.maxRetries rt.Client.RetryWaitMin = fc.minWaitMSec rt.Client.RetryWaitMax = fc.maxWaitMSec rt.Client.Backoff = backoffStrategy rt.Client.CheckRetry = retryStrategy } timeout := host.Client.Timeout if host.Authorizer != nil { tr = &transport{ inner: tr, auth: host.Authorizer, scope: pullScope, } } // Resolve redirection and get blob URL blobURL := fmt.Sprintf("%s://%s/%s/blobs/%s", host.Scheme, path.Join(host.Host, host.Path), strings.TrimPrefix(fc.refspec.Locator, fc.refspec.Hostname()+"/"), digest) url, err := redirect(ctx, blobURL, tr, timeout) if err != nil { rErr = fmt.Errorf("failed to redirect (host %q, ref:%q, digest:%q): %v: %w", host.Host, fc.refspec, digest, err, rErr) continue // Try another } // Get size information // TODO: we should try to use the Size field in the descriptor here. start := time.Now() // start time before getting layer header size, err := getSize(ctx, url, tr, timeout) commonmetrics.MeasureLatencyInMilliseconds(commonmetrics.StargzHeaderGet, digest, start) // time to get layer header if err != nil { rErr = fmt.Errorf("failed to get size (host %q, ref:%q, digest:%q): %v: %w", host.Host, fc.refspec, digest, err, rErr) continue // Try another } // Hit one destination return &httpFetcher{ url: url, tr: tr, blobURL: blobURL, digest: digest, timeout: timeout, }, size, nil } return nil, 0, fmt.Errorf("cannot resolve layer: %w", rErr) } type transport struct { inner http.RoundTripper auth docker.Authorizer scope string } func (tr *transport) RoundTrip(req *http.Request) (*http.Response, error) { ctx := docker.WithScope(req.Context(), tr.scope) roundTrip := func(req *http.Request) (*http.Response, error) { // authorize the request using docker.Authorizer if err := tr.auth.Authorize(ctx, req); err != nil { return nil, err } // send the request return tr.inner.RoundTrip(req) } resp, err := roundTrip(req) if err != nil { return nil, err } // TODO: support more status codes and retries if resp.StatusCode == http.StatusUnauthorized { log.G(ctx).Infof("Received status code: %v. Refreshing creds...", resp.Status) // prepare authorization for the target host using docker.Authorizer if err := tr.auth.AddResponses(ctx, []*http.Response{resp}); err != nil { if errdefs.IsNotImplemented(err) { return resp, nil } return nil, err } // re-authorize and send the request return roundTrip(req.Clone(ctx)) } return resp, nil } func redirect(ctx context.Context, blobURL string, tr http.RoundTripper, timeout time.Duration) (url string, err error) { if timeout > 0 { var cancel context.CancelFunc ctx, cancel = context.WithTimeout(ctx, timeout) defer cancel() } // We use GET request for redirect. // gcr.io returns 200 on HEAD without Location header (2020). // ghcr.io returns 200 on HEAD without Location header (2020). req, err := http.NewRequestWithContext(ctx, "GET", blobURL, nil) if err != nil { return "", fmt.Errorf("failed to make request to the registry: %w", err) } req.Close = false req.Header.Set("Range", "bytes=0-1") res, err := tr.RoundTrip(req) if err != nil { return "", fmt.Errorf("failed to request: %w", err) } defer func() { io.Copy(io.Discard, res.Body) res.Body.Close() }() if res.StatusCode/100 == 2 { url = blobURL } else if redir := res.Header.Get("Location"); redir != "" && res.StatusCode/100 == 3 { // TODO: Support nested redirection url = redir } else { return "", fmt.Errorf("failed to access to the registry with code %v", res.StatusCode) } return } func getSize(ctx context.Context, url string, tr http.RoundTripper, timeout time.Duration) (int64, error) { if timeout > 0 { var cancel context.CancelFunc ctx, cancel = context.WithTimeout(ctx, timeout) defer cancel() } req, err := http.NewRequestWithContext(ctx, "HEAD", url, nil) if err != nil { return 0, err } req.Close = false res, err := tr.RoundTrip(req) if err != nil { return 0, err } defer res.Body.Close() if res.StatusCode == http.StatusOK { return strconv.ParseInt(res.Header.Get("Content-Length"), 10, 64) } headStatusCode := res.StatusCode // Failed to do HEAD request. Fall back to GET. // ghcr.io (https://github-production-container-registry.s3.amazonaws.com) doesn't allow // HEAD request (2020). req, err = http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return 0, fmt.Errorf("failed to make request to the registry: %w", err) } req.Close = false req.Header.Set("Range", "bytes=0-1") res, err = tr.RoundTrip(req) if err != nil { return 0, fmt.Errorf("failed to request: %w", err) } defer func() { io.Copy(io.Discard, res.Body) res.Body.Close() }() if res.StatusCode == http.StatusOK { return strconv.ParseInt(res.Header.Get("Content-Length"), 10, 64) } else if res.StatusCode == http.StatusPartialContent { _, size, err := parseRange(res.Header.Get("Content-Range")) return size, err } return 0, fmt.Errorf("failed to get size with code (HEAD=%v, GET=%v)", headStatusCode, res.StatusCode) } type httpFetcher struct { url string urlMu sync.Mutex tr http.RoundTripper blobURL string digest digest.Digest singleRange bool singleRangeMu sync.Mutex timeout time.Duration } type multipartReadCloser interface { Next() (region, io.Reader, error) Close() error } func (f *httpFetcher) fetch(ctx context.Context, rs []region, retry bool) (multipartReadCloser, error) { if len(rs) == 0 { return nil, fmt.Errorf("no request queried") } var ( tr = f.tr singleRangeMode = f.isSingleRangeMode() ) // squash requesting chunks for reducing the total size of request header // (servers generally have limits for the size of headers) // TODO: when our request has too many ranges, we need to divide it into // multiple requests to avoid huge header. var s regionSet for _, reg := range rs { s.add(reg) } requests := s.rs if singleRangeMode { // Squash requests if the layer doesn't support multi range. requests = []region{superRegion(requests)} } // Request to the registry f.urlMu.Lock() url := f.url f.urlMu.Unlock() req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return nil, err } var ranges string for _, reg := range requests { ranges += fmt.Sprintf("%d-%d,", reg.b, reg.e) } req.Header.Add("Range", fmt.Sprintf("bytes=%s", ranges[:len(ranges)-1])) req.Header.Add("Accept-Encoding", "identity") req.Close = false // Recording the roundtrip latency for remote registry GET operation. start := time.Now() res, err := tr.RoundTrip(req) // NOT DefaultClient; don't want redirects commonmetrics.MeasureLatencyInMilliseconds(commonmetrics.RemoteRegistryGet, f.digest, start) if err != nil { return nil, err } if res.StatusCode == http.StatusOK { // We are getting the whole blob in one part (= status 200) size, err := strconv.ParseInt(res.Header.Get("Content-Length"), 10, 64) if err != nil { return nil, fmt.Errorf("failed to parse Content-Length: %w", err) } return newSinglePartReader(region{0, size - 1}, res.Body), nil } else if res.StatusCode == http.StatusPartialContent { mediaType, params, err := mime.ParseMediaType(res.Header.Get("Content-Type")) if err != nil { return nil, fmt.Errorf("invalid media type %q: %w", mediaType, err) } if strings.HasPrefix(mediaType, "multipart/") { // We are getting a set of chunks as a multipart body. return newMultiPartReader(res.Body, params["boundary"]), nil } // We are getting single range reg, _, err := parseRange(res.Header.Get("Content-Range")) if err != nil { return nil, fmt.Errorf("failed to parse Content-Range: %w", err) } return newSinglePartReader(reg, res.Body), nil } else if retry && res.StatusCode == http.StatusForbidden { log.G(ctx).Infof("Received status code: %v. Refreshing URL and retrying...", res.Status) // re-redirect and retry this once. if err := f.refreshURL(ctx); err != nil { return nil, fmt.Errorf("failed to refresh URL on %v: %w", res.Status, err) } return f.fetch(ctx, rs, false) } else if retry && res.StatusCode == http.StatusBadRequest && !singleRangeMode { log.G(ctx).Infof("Received status code: %v. Setting single range mode and retrying...", res.Status) // gcr.io (https://storage.googleapis.com) returns 400 on multi-range request (2020 #81) f.singleRangeMode() // fallbacks to singe range request mode return f.fetch(ctx, rs, false) // retries with the single range mode } return nil, fmt.Errorf("unexpected status code: %v", res.Status) } func (f *httpFetcher) check() error { ctx := context.Background() if f.timeout > 0 { var cancel context.CancelFunc ctx, cancel = context.WithTimeout(ctx, f.timeout) defer cancel() } f.urlMu.Lock() url := f.url f.urlMu.Unlock() req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return fmt.Errorf("check failed: failed to make request: %w", err) } req.Close = false req.Header.Set("Range", "bytes=0-1") res, err := f.tr.RoundTrip(req) if err != nil { return fmt.Errorf("check failed: failed to request to registry: %w", err) } defer func() { io.Copy(io.Discard, res.Body) res.Body.Close() }() if res.StatusCode == http.StatusOK || res.StatusCode == http.StatusPartialContent { return nil } else if res.StatusCode == http.StatusForbidden { // Try to re-redirect this blob rCtx := context.Background() if f.timeout > 0 { var rCancel context.CancelFunc rCtx, rCancel = context.WithTimeout(rCtx, f.timeout) defer rCancel() } if err := f.refreshURL(rCtx); err == nil { return nil } return fmt.Errorf("failed to refresh URL on status %v", res.Status) } return fmt.Errorf("unexpected status code %v", res.StatusCode) } func (f *httpFetcher) refreshURL(ctx context.Context) error { newURL, err := redirect(ctx, f.blobURL, f.tr, f.timeout) if err != nil { return err } f.urlMu.Lock() f.url = newURL f.urlMu.Unlock() return nil } func (f *httpFetcher) genID(reg region) string { sum := sha256.Sum256([]byte(fmt.Sprintf("%s-%d-%d", f.blobURL, reg.b, reg.e))) return fmt.Sprintf("%x", sum) } func (f *httpFetcher) singleRangeMode() { f.singleRangeMu.Lock() f.singleRange = true f.singleRangeMu.Unlock() } func (f *httpFetcher) isSingleRangeMode() bool { f.singleRangeMu.Lock() r := f.singleRange f.singleRangeMu.Unlock() return r } func newSinglePartReader(reg region, rc io.ReadCloser) multipartReadCloser { return &singlepartReader{ r: rc, Closer: rc, reg: reg, } } type singlepartReader struct { io.Closer r io.Reader reg region called bool } func (sr *singlepartReader) Next() (region, io.Reader, error) { if !sr.called { sr.called = true return sr.reg, sr.r, nil } return region{}, nil, io.EOF } func newMultiPartReader(rc io.ReadCloser, boundary string) multipartReadCloser { return &multipartReader{ m: multipart.NewReader(rc, boundary), Closer: rc, } } type multipartReader struct { io.Closer m *multipart.Reader } func (sr *multipartReader) Next() (region, io.Reader, error) { p, err := sr.m.NextPart() if err != nil { return region{}, nil, err } reg, _, err := parseRange(p.Header.Get("Content-Range")) if err != nil { return region{}, nil, fmt.Errorf("failed to parse Content-Range: %w", err) } return reg, p, nil } func parseRange(header string) (region, int64, error) { submatches := contentRangeRegexp.FindStringSubmatch(header) if len(submatches) < 4 { return region{}, 0, fmt.Errorf("Content-Range %q doesn't have enough information", header) } begin, err := strconv.ParseInt(submatches[1], 10, 64) if err != nil { return region{}, 0, fmt.Errorf("failed to parse beginning offset %q: %w", submatches[1], err) } end, err := strconv.ParseInt(submatches[2], 10, 64) if err != nil { return region{}, 0, fmt.Errorf("failed to parse end offset %q: %w", submatches[2], err) } blobSize, err := strconv.ParseInt(submatches[3], 10, 64) if err != nil { return region{}, 0, fmt.Errorf("failed to parse blob size %q: %w", submatches[3], err) } return region{begin, end}, blobSize, nil } type Option func(*options) type options struct { ctx context.Context cacheOpts []cache.Option } func WithContext(ctx context.Context) Option { return func(opts *options) { opts.ctx = ctx } } func WithCacheOpts(cacheOpts ...cache.Option) Option { return func(opts *options) { opts.cacheOpts = cacheOpts } } type remoteFetcher struct { r Fetcher } func (r *remoteFetcher) fetch(ctx context.Context, rs []region, retry bool) (multipartReadCloser, error) { var s regionSet for _, reg := range rs { s.add(reg) } reg := superRegion(s.rs) rc, err := r.r.Fetch(ctx, reg.b, reg.size()) if err != nil { return nil, err } return newSinglePartReader(reg, rc), nil } func (r *remoteFetcher) check() error { return r.r.Check() } func (r *remoteFetcher) genID(reg region) string { return r.r.GenID(reg.b, reg.size()) } type Handler interface { Handle(ctx context.Context, desc ocispec.Descriptor) (fetcher Fetcher, size int64, err error) } type Fetcher interface { Fetch(ctx context.Context, off int64, size int64) (io.ReadCloser, error) Check() error GenID(off int64, size int64) string } stargz-snapshotter-0.12.0/fs/remote/resolver_test.go000066400000000000000000000206511426301527400226160ustar00rootroot00000000000000/* 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. */ /* Copyright 2019 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the NOTICE.md file. */ package remote import ( "bytes" "context" "fmt" "io" "net/http" "net/url" "regexp" "strings" "testing" "github.com/containerd/containerd/reference" "github.com/containerd/containerd/remotes/docker" rhttp "github.com/hashicorp/go-retryablehttp" digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) func TestMirror(t *testing.T) { ref := "dummyexample.com/library/test" refspec, err := reference.Parse(ref) if err != nil { t.Fatalf("failed to prepare dummy reference: %v", err) } var ( blobDigest = digest.FromString("dummy") blobPath = fmt.Sprintf("/v2/%s/blobs/%s", strings.TrimPrefix(refspec.Locator, refspec.Hostname()+"/"), blobDigest.String()) refHost = refspec.Hostname() ) tests := []struct { name string tr http.RoundTripper mirrors []string wantHost string error bool }{ { name: "no-mirror", tr: &sampleRoundTripper{okURLs: []string{refHost}}, mirrors: nil, wantHost: refHost, }, { name: "valid-mirror", tr: &sampleRoundTripper{okURLs: []string{"mirrorexample.com"}}, mirrors: []string{"mirrorexample.com"}, wantHost: "mirrorexample.com", }, { name: "invalid-mirror", tr: &sampleRoundTripper{ withCode: map[string]int{ "mirrorexample1.com": http.StatusInternalServerError, "mirrorexample2.com": http.StatusUnauthorized, "mirrorexample3.com": http.StatusNotFound, }, okURLs: []string{"mirrorexample4.com", refHost}, }, mirrors: []string{ "mirrorexample1.com", "mirrorexample2.com", "mirrorexample3.com", "mirrorexample4.com", }, wantHost: "mirrorexample4.com", }, { name: "invalid-all-mirror", tr: &sampleRoundTripper{ withCode: map[string]int{ "mirrorexample1.com": http.StatusInternalServerError, "mirrorexample2.com": http.StatusUnauthorized, "mirrorexample3.com": http.StatusNotFound, }, okURLs: []string{refHost}, }, mirrors: []string{ "mirrorexample1.com", "mirrorexample2.com", "mirrorexample3.com", }, wantHost: refHost, }, { name: "invalid-hostname-of-mirror", tr: &sampleRoundTripper{ okURLs: []string{`.*`}, }, mirrors: []string{"mirrorexample.com/somepath/"}, wantHost: refHost, }, { name: "redirected-mirror", tr: &sampleRoundTripper{ redirectURL: map[string]string{ regexp.QuoteMeta(fmt.Sprintf("mirrorexample.com%s", blobPath)): "https://backendexample.com/blobs/" + blobDigest.String(), }, okURLs: []string{`.*`}, }, mirrors: []string{"mirrorexample.com"}, wantHost: "backendexample.com", }, { name: "invalid-redirected-mirror", tr: &sampleRoundTripper{ withCode: map[string]int{ "backendexample.com": http.StatusInternalServerError, }, redirectURL: map[string]string{ regexp.QuoteMeta(fmt.Sprintf("mirrorexample.com%s", blobPath)): "https://backendexample.com/blobs/" + blobDigest.String(), }, okURLs: []string{`.*`}, }, mirrors: []string{"mirrorexample.com"}, wantHost: refHost, }, { name: "fail-all", tr: &sampleRoundTripper{}, mirrors: []string{"mirrorexample.com"}, wantHost: "", error: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { hosts := func(refspec reference.Spec) (reghosts []docker.RegistryHost, _ error) { host := refspec.Hostname() for _, m := range append(tt.mirrors, host) { reghosts = append(reghosts, docker.RegistryHost{ Client: &http.Client{Transport: tt.tr}, Host: m, Scheme: "https", Path: "/v2", Capabilities: docker.HostCapabilityPull, }) } return } fetcher, _, err := newHTTPFetcher(context.Background(), &fetcherConfig{ hosts: hosts, refspec: refspec, desc: ocispec.Descriptor{Digest: blobDigest}, }) if err != nil { if tt.error { return } t.Fatalf("failed to resolve reference: %v", err) } nurl, err := url.Parse(fetcher.url) if err != nil { t.Fatalf("failed to parse url %q: %v", fetcher.url, err) } if nurl.Hostname() != tt.wantHost { t.Errorf("invalid hostname %q(%q); want %q", nurl.Hostname(), nurl.String(), tt.wantHost) } }) } } type sampleRoundTripper struct { withCode map[string]int redirectURL map[string]string okURLs []string } func (tr *sampleRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { for host, code := range tr.withCode { if ok, _ := regexp.Match(host, []byte(req.URL.String())); ok { return &http.Response{ StatusCode: code, Header: make(http.Header), Body: io.NopCloser(bytes.NewReader([]byte{})), Request: req, }, nil } } for host, rurl := range tr.redirectURL { if ok, _ := regexp.Match(host, []byte(req.URL.String())); ok { header := make(http.Header) header.Add("Location", rurl) return &http.Response{ StatusCode: http.StatusMovedPermanently, Header: header, Body: io.NopCloser(bytes.NewReader([]byte{})), Request: req, }, nil } } for _, host := range tr.okURLs { if ok, _ := regexp.Match(host, []byte(req.URL.String())); ok { header := make(http.Header) header.Add("Content-Length", "1") return &http.Response{ StatusCode: http.StatusOK, Header: header, Body: io.NopCloser(bytes.NewReader([]byte{0})), Request: req, }, nil } } return &http.Response{ StatusCode: http.StatusNotFound, Header: make(http.Header), Body: io.NopCloser(bytes.NewReader([]byte{})), Request: req, }, nil } func TestCheck(t *testing.T) { tr := &breakRoundTripper{} f := &httpFetcher{ url: "test", tr: tr, } tr.success = true if err := f.check(); err != nil { t.Errorf("connection failed; wanted to succeed") } tr.success = false if err := f.check(); err == nil { t.Errorf("connection succeeded; wanted to fail") } } type breakRoundTripper struct { success bool } func (b *breakRoundTripper) RoundTrip(req *http.Request) (res *http.Response, err error) { if b.success { res = &http.Response{ StatusCode: http.StatusPartialContent, Header: make(http.Header), Body: io.NopCloser(bytes.NewReader([]byte("test"))), } } else { res = &http.Response{ StatusCode: http.StatusInternalServerError, Header: make(http.Header), Body: io.NopCloser(bytes.NewReader([]byte{})), } } return } func TestRetry(t *testing.T) { tr := &retryRoundTripper{} rclient := rhttp.NewClient() rclient.HTTPClient.Transport = tr rclient.Backoff = backoffStrategy f := &httpFetcher{ url: "test", tr: &rhttp.RoundTripper{Client: rclient}, } regions := []region{{b: 0, e: 1}} _, err := f.fetch(context.Background(), regions, true) if err != nil { t.Fatalf("unexpected error = %v", err) } if tr.retryCount != 4 { t.Fatalf("unxpected retryCount; expected=4 got=%d", tr.retryCount) } } type retryRoundTripper struct { retryCount int } func (r *retryRoundTripper) RoundTrip(req *http.Request) (res *http.Response, err error) { defer func() { r.retryCount++ }() switch r.retryCount { case 0: err = fmt.Errorf("dummy error") case 1: res = &http.Response{ StatusCode: http.StatusTooManyRequests, Header: make(http.Header), Body: io.NopCloser(bytes.NewReader([]byte{})), } case 2: res = &http.Response{ StatusCode: http.StatusServiceUnavailable, Header: make(http.Header), Body: io.NopCloser(bytes.NewReader([]byte{})), } default: header := make(http.Header) header.Add("Content-Length", "4") res = &http.Response{ StatusCode: http.StatusOK, Header: header, Body: io.NopCloser(bytes.NewReader([]byte("test"))), } } return } stargz-snapshotter-0.12.0/fs/remote/util.go000066400000000000000000000060151426301527400206710ustar00rootroot00000000000000/* 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. */ /* Copyright 2019 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the NOTICE.md file. */ package remote // region is HTTP-range-request-compliant range. // "b" is beginning byte of the range and "e" is the end. // "e" is must be inclusive along with HTTP's range expression. type region struct{ b, e int64 } func (c region) size() int64 { return c.e - c.b + 1 } func superRegion(regs []region) region { s := regs[0] for _, reg := range regs { if reg.b < s.b { s.b = reg.b } if reg.e > s.e { s.e = reg.e } } return s } // regionSet is a set of regions type regionSet struct { rs []region // must be kept sorted } // add attempts to merge r to rs.rs with squashing the regions as // small as possible. This operation takes O(n). // TODO: more efficient way to do it. func (rs *regionSet) add(r region) { // Iterate over the sorted region slice from the tail. // a) When an overwrap occurs, adjust `r` to fully contain the looking region // `l` and remove `l` from region slice. // b) Once l.e become less than r.b, no overwrap will occur again. So immediately // insert `r` which fully contains all overwrapped regions, to the region slice. // Here, `r` is inserted to the region slice with keeping it sorted, without // overwrapping to any regions. // *) If any `l` contains `r`, we don't need to do anything so return immediately. for i := len(rs.rs) - 1; i >= 0; i-- { l := &rs.rs[i] // *) l contains r if l.b <= r.b && r.e <= l.e { return } // a) r overwraps to l so adjust r to fully contain l and reomve l // from region slice. if l.b <= r.b && r.b <= l.e+1 && l.e <= r.e { r.b = l.b rs.rs = append(rs.rs[:i], rs.rs[i+1:]...) continue } if r.b <= l.b && l.b <= r.e+1 && r.e <= l.e { r.e = l.e rs.rs = append(rs.rs[:i], rs.rs[i+1:]...) continue } if r.b <= l.b && l.e <= r.e { rs.rs = append(rs.rs[:i], rs.rs[i+1:]...) continue } // b) No overwrap will occur after this iteration. Instert r to the // region slice immediately. if l.e < r.b { rs.rs = append(rs.rs[:i+1], append([]region{r}, rs.rs[i+1:]...)...) return } // No overwrap occurs yet. See the next region. } // r is the topmost region among regions in the slice. rs.rs = append([]region{r}, rs.rs...) } func (rs *regionSet) totalSize() int64 { var sz int64 for _, f := range rs.rs { sz += f.size() } return sz } stargz-snapshotter-0.12.0/fs/remote/util_test.go000066400000000000000000000043271426301527400217340ustar00rootroot00000000000000/* 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. */ /* Copyright 2019 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the NOTICE.md file. */ package remote import ( "reflect" "testing" ) func TestRegionSet(t *testing.T) { tests := []struct { input []region expected []region }{ { input: []region{{1, 3}, {2, 4}}, expected: []region{{1, 4}}, }, { input: []region{{1, 5}, {2, 4}}, expected: []region{{1, 5}}, }, { input: []region{{2, 4}, {1, 5}}, expected: []region{{1, 5}}, }, { input: []region{{2, 4}, {6, 8}, {1, 5}}, expected: []region{{1, 8}}, }, { input: []region{{1, 2}, {1, 2}}, expected: []region{{1, 2}}, }, { input: []region{{1, 3}, {1, 2}}, expected: []region{{1, 3}}, }, { input: []region{{1, 3}, {2, 3}}, expected: []region{{1, 3}}, }, { input: []region{{1, 3}, {3, 6}}, expected: []region{{1, 6}}, }, { input: []region{{1, 3}, {4, 6}}, // region.e is inclusive expected: []region{{1, 6}}, }, { input: []region{{4, 6}, {1, 3}}, // region.e is inclusive expected: []region{{1, 6}}, }, { input: []region{{4, 6}, {1, 3}, {7, 9}, {2, 8}}, expected: []region{{1, 9}}, }, { input: []region{{4, 6}, {1, 5}, {7, 9}, {4, 8}}, expected: []region{{1, 9}}, }, { input: []region{{7, 8}, {1, 2}, {5, 6}}, expected: []region{{1, 2}, {5, 8}}, }, } for i, tt := range tests { var rs regionSet for _, f := range tt.input { rs.add(f) } if !reflect.DeepEqual(tt.expected, rs.rs) { t.Errorf("#%d: expected %v, got %v", i, tt.expected, rs.rs) } } } stargz-snapshotter-0.12.0/fs/source/000077500000000000000000000000001426301527400173705ustar00rootroot00000000000000stargz-snapshotter-0.12.0/fs/source/source.go000066400000000000000000000154371426301527400212310ustar00rootroot00000000000000/* 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 source import ( "context" "fmt" "strings" "github.com/containerd/containerd/images" "github.com/containerd/containerd/labels" "github.com/containerd/containerd/reference" "github.com/containerd/containerd/remotes/docker" "github.com/containerd/stargz-snapshotter/fs/config" digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) // GetSources is a function for converting snapshot labels into typed blob sources // information. This package defines a default converter which provides source // information based on some labels but implementations aren't required to use labels. // Implementations are allowed to return several sources (registry config + image refs) // about the blob. type GetSources func(labels map[string]string) (source []Source, err error) // RegistryHosts returns a list of registries that provides the specified image. type RegistryHosts func(reference.Spec) ([]docker.RegistryHost, error) // Source is a typed blob source information. This contains information about // a blob stored in registries and some contexts of the blob. type Source struct { // Hosts is a registry configuration where this blob is stored. Hosts RegistryHosts // Name is an image reference which contains this blob. Name reference.Spec // Target is a descriptor of this blob. Target ocispec.Descriptor // Manifest is an image manifest which contains the blob. This will // be used by the filesystem to pre-resolve some layers contained in // the manifest. // Currently, only layer digests (Manifest.Layers.Digest) will be used. Manifest ocispec.Manifest } const ( // targetRefLabel is a label which contains image reference. targetRefLabel = "containerd.io/snapshot/remote/stargz.reference" // targetDigestLabel is a label which contains layer digest. targetDigestLabel = "containerd.io/snapshot/remote/stargz.digest" // targetImageLayersLabel is a label which contains layer digests contained in // the target image. targetImageLayersLabel = "containerd.io/snapshot/remote/stargz.layers" // targetImageURLsLabelPrefix is a label prefix which constructs a map from the layer index to // urls of the layer descriptor. targetImageURLsLabelPrefix = "containerd.io/snapshot/remote/urls." // targetURsLLabel is a label which contains layer URL. This is only used to pass URL from containerd // to snapshotter. targetURLsLabel = "containerd.io/snapshot/remote/urls" ) // FromDefaultLabels returns a function for converting snapshot labels to // source information based on labels. func FromDefaultLabels(hosts RegistryHosts) GetSources { return func(labels map[string]string) ([]Source, error) { refStr, ok := labels[targetRefLabel] if !ok { return nil, fmt.Errorf("reference hasn't been passed") } refspec, err := reference.Parse(refStr) if err != nil { return nil, err } digestStr, ok := labels[targetDigestLabel] if !ok { return nil, fmt.Errorf("digest hasn't been passed") } target, err := digest.Parse(digestStr) if err != nil { return nil, err } var neighboringLayers []ocispec.Descriptor if l, ok := labels[targetImageLayersLabel]; ok { layersStr := strings.Split(l, ",") for i, l := range layersStr { d, err := digest.Parse(l) if err != nil { return nil, err } if d.String() != target.String() { desc := ocispec.Descriptor{Digest: d} if urls, ok := labels[targetImageURLsLabelPrefix+fmt.Sprintf("%d", i)]; ok { desc.URLs = strings.Split(urls, ",") } neighboringLayers = append(neighboringLayers, desc) } } } targetDesc := ocispec.Descriptor{ Digest: target, Annotations: labels, } if targetURLs, ok := labels[targetURLsLabel]; ok { targetDesc.URLs = append(targetDesc.URLs, strings.Split(targetURLs, ",")...) } return []Source{ { Hosts: hosts, Name: refspec, Target: targetDesc, Manifest: ocispec.Manifest{Layers: append([]ocispec.Descriptor{targetDesc}, neighboringLayers...)}, }, }, nil } } // AppendDefaultLabelsHandlerWrapper makes a handler which appends image's basic // information to each layer descriptor as annotations during unpack. These // annotations will be passed to this remote snapshotter as labels and used to // construct source information. func AppendDefaultLabelsHandlerWrapper(ref string, prefetchSize int64) func(f images.Handler) images.Handler { return func(f images.Handler) images.Handler { return images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { children, err := f.Handle(ctx, desc) if err != nil { return nil, err } switch desc.MediaType { case ocispec.MediaTypeImageManifest, images.MediaTypeDockerSchema2Manifest: for i := range children { c := &children[i] if images.IsLayerType(c.MediaType) { if c.Annotations == nil { c.Annotations = make(map[string]string) } c.Annotations[targetRefLabel] = ref c.Annotations[targetDigestLabel] = c.Digest.String() var layers string for i, l := range children[i:] { if images.IsLayerType(l.MediaType) { ls := fmt.Sprintf("%s,", l.Digest.String()) // This avoids the label hits the size limitation. // Skipping layers is allowed here and only affects performance. if err := labels.Validate(targetImageLayersLabel, layers+ls); err != nil { break } layers += ls // Store URLs of the neighbouring layer as well. urlsKey := targetImageURLsLabelPrefix + fmt.Sprintf("%d", i) c.Annotations[urlsKey] = appendWithValidation(urlsKey, l.URLs) } } c.Annotations[targetImageLayersLabel] = strings.TrimSuffix(layers, ",") c.Annotations[config.TargetPrefetchSizeLabel] = fmt.Sprintf("%d", prefetchSize) // store URL in annotation to let containerd to pass it to the snapshotter c.Annotations[targetURLsLabel] = appendWithValidation(targetURLsLabel, c.URLs) } } } return children, nil }) } } func appendWithValidation(key string, values []string) string { var v string for _, u := range values { s := fmt.Sprintf("%s,", u) if err := labels.Validate(key, v+s); err != nil { break } v += s } return strings.TrimSuffix(v, ",") } stargz-snapshotter-0.12.0/go.mod000066400000000000000000000040501426301527400165650ustar00rootroot00000000000000module github.com/containerd/stargz-snapshotter go 1.16 require ( github.com/containerd/console v1.0.3 github.com/containerd/containerd v1.6.6 github.com/containerd/continuity v0.3.0 github.com/containerd/stargz-snapshotter/estargz v0.12.0 github.com/docker/cli v20.10.17+incompatible github.com/docker/docker v20.10.7+incompatible // indirect github.com/docker/docker-credential-helpers v0.6.4 // indirect github.com/docker/go-metrics v0.0.1 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da github.com/hanwen/go-fuse/v2 v2.1.1-0.20220112183258-f57e95bda82d github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-retryablehttp v0.7.1 github.com/klauspost/compress v1.15.7 github.com/moby/sys/mountinfo v0.6.2 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 github.com/pelletier/go-toml v1.9.4 // indirect github.com/prometheus/client_golang v1.12.2 github.com/rs/xid v1.4.0 github.com/sirupsen/logrus v1.8.1 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a google.golang.org/grpc v1.47.0 k8s.io/api v0.24.2 k8s.io/apimachinery v0.24.2 k8s.io/client-go v0.24.2 k8s.io/cri-api v0.25.0-alpha.2 ) replace ( // Import local package for estargz. github.com/containerd/stargz-snapshotter/estargz => ./estargz // Temporary fork for avoiding importing patent-protected code: https://github.com/hashicorp/golang-lru/issues/73 github.com/hashicorp/golang-lru => github.com/ktock/golang-lru v0.5.5-0.20211029085301-ec551be6f75c // NOTE1: github.com/containerd/containerd v1.4.0 depends on github.com/urfave/cli v1.22.1 // because of https://github.com/urfave/cli/issues/1092 // NOTE2: Automatic upgrade of this is disabled in denendabot.yml. When we remove this replace // directive, we must remove the corresponding "ignore" configuration from dependabot.yml github.com/urfave/cli => github.com/urfave/cli v1.22.1 ) stargz-snapshotter-0.12.0/go.sum000066400000000000000000004555571426301527400166400ustar00rootroot00000000000000bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 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 v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= 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/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= 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-20210715213245-6c3934b029d8/go.mod h1:CzsSbkDixRphAF5hS6wbMKq0eI6ccJRb7/A0M6JBnwg= github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= github.com/Microsoft/hcsshim v0.8.20/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= github.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= github.com/Microsoft/hcsshim v0.8.23/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01nnU2M8jKDg= github.com/Microsoft/hcsshim v0.9.2/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= github.com/Microsoft/hcsshim v0.9.3 h1:k371PzBuRrz2b+ebGuI2nVgVhgsVX60jMfSw80NECxo= github.com/Microsoft/hcsshim v0.9.3/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= 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-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= github.com/alexflint/go-filemutex v1.1.0/go.mod h1:7P4iRhttt/nUvUOrYIhcpMzv2G6CY9UnI16Z+UJqRyk= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/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.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= 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.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= 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/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= github.com/containerd/aufs v1.0.0/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E= github.com/containerd/btrfs v0.0.0-20210316141732-918d888fb676/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= github.com/containerd/btrfs v1.0.0/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI= github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= github.com/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBdAP4= github.com/containerd/cgroups v1.0.3/go.mod h1:/ofk34relqNjSGyqPrmEULrO4Sc8LJhvJmWbUCUKqj8= github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.9/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c= github.com/containerd/containerd v1.5.8/go.mod h1:YdFSv5bTFLpG2HIYmfqDpSYYTDX+mc5qtSuYx1YUb/s= github.com/containerd/containerd v1.6.1/go.mod h1:1nJz5xCZPusx6jJU8Frfct988y0NpumIq9ODB0kLtoE= github.com/containerd/containerd v1.6.6 h1:xJNPhbrmz8xAMDNoVjHy9YHtWwEQNS+CDkcIRh7t8Y0= github.com/containerd/containerd v1.6.6/go.mod h1:ZoP1geJldzCVY3Tonoz7b1IXk8rIX0Nltt5QE4OMNk0= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= github.com/containerd/continuity v0.2.2/go.mod h1:pWygW9u7LtS1o4N/Tn0FoCFDIXZ7rxcMX7HX1Dmibvk= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= github.com/containerd/fifo v1.0.0 h1:6PirWBr9/L7GDamKr+XM0IeUFXu5mf3M/BPpH9gaLBU= github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU= github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk= github.com/containerd/go-cni v1.1.0/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA= github.com/containerd/go-cni v1.1.3/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA= github.com/containerd/go-cni v1.1.6/go.mod h1:BWtoWl5ghVymxu6MBjg79W9NZrCRyHIdUtk4cauMe34= github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= github.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0= github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA= github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow= github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms= github.com/containerd/imgcrypt v1.1.3/go.mod h1:/TPA1GIDXMzbj01yd8pIbQiLdQxed5ue1wb8bP7PQu4= github.com/containerd/imgcrypt v1.1.4/go.mod h1:LorQnPtzL/T0IyCeftcsMEO7AqxUDbdO8j/tSUpgxvo= github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= github.com/containerd/ttrpc v1.1.0 h1:GbtyLRxb0gOLR0TYQWt3O6B0NvT8tMdorEHqIQo/lWI= github.com/containerd/ttrpc v1.1.0/go.mod h1:XX4ZTnoOId4HklF4edwc4DcqskFZuvXB1Evzy5KFQpQ= github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= github.com/containerd/typeurl v1.0.2 h1:Chlt8zIieDbzQFzXzAeBEF92KhExuE4p9p92/QmY7aY= github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s= github.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2/go.mod h1:8IgZOBdv8fAgXddBT4dBXJPtxyRsejFIpXoklgxgEjw= github.com/containerd/zfs v0.0.0-20210301145711-11e8f1707f62/go.mod h1:A9zfAbMlQwE+/is6hi0Xw8ktpL+6glmqZYtevJgaB8Y= github.com/containerd/zfs v0.0.0-20210315114300-dde8f0fda960/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= github.com/containerd/zfs v0.0.0-20210324211415-d5c4544f0433/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/cni v1.0.1/go.mod h1:AKuhXbN5EzmD4yTNtfSsX3tPcmtrBI6QcRV0NiNt15Y= github.com/containernetworking/cni v1.1.1/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw= github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= github.com/containernetworking/plugins v1.0.1/go.mod h1:QHCfGpaTwYTbbH+nZXKVTxNBDZcxSOplJT5ico8/FLE= github.com/containernetworking/plugins v1.1.1/go.mod h1:Sr5TH/eBsGLXK/h71HeLfX19sZPp3ry5uHSkI4LPxV8= github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= github.com/containers/ocicrypt v1.1.2/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= github.com/containers/ocicrypt v1.1.3/go.mod h1:xpdkbVAuaH3WzbEabUd5yDsl9SwJA5pABH85425Es2g= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg= 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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v20.10.17+incompatible h1:eO2KS7ZFeov5UJeaDmIs1NFEDRf32PaqRpvoEkKBy5M= github.com/docker/cli v20.10.17+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v20.10.7+incompatible h1:Z6O9Nhsjv+ayUEeI1IojKbYcsGdgYSNqxe1s2MYzUhQ= github.com/docker/docker v20.10.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= github.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56WZ2Pwu/fKWtKuZB0o= github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= 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.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= 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-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= 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.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= 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-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.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= 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-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 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/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= github.com/gogo/googleapis v1.4.0 h1:zgVt4UpGxcqVOw97aRGxT4svlcmdK35fynLNctY32zI= github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 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/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 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-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 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.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/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= 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.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.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.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.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.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= 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/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/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= 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.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.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.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/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.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 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/martian/v3 v3.1.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-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 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.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/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.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hanwen/go-fuse/v2 v2.1.1-0.20220112183258-f57e95bda82d h1:ibbzF2InxMOS+lLCphY9PHNKPURDUBNKaG6ErSq8gJQ= github.com/hanwen/go-fuse/v2 v2.1.1-0.20220112183258-f57e95bda82d/go.mod h1:B1nGE/6RBFyBRC1RRnf23UpwCdyJ31eukw34oAKukAc= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 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.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ= github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 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.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9FCCAVRQ= github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= github.com/j-keck/arping v1.0.2/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= 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/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.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/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/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 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.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 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/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.15.7 h1:7cgTQxJCU/vy+oP/E3B9RGbQTgbiVzIJWIKOLoAsPok= github.com/klauspost/compress v1.15.7/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= 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.2/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/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 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= 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/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= 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/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/ktock/golang-lru v0.5.5-0.20211029085301-ec551be6f75c/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= 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/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= github.com/moby/sys/signal v0.6.0 h1:aDpY94H8VlhTGa9sNYUFCFsMZIUh5wm0B6XkIoJj/iY= github.com/moby/sys/signal v0.6.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg= github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= github.com/moby/sys/symlink v0.2.0/go.mod h1:7uZVF2dqJjG/NsClqul95CqKOBRQyYSNnJ6BMgR/gFs= github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= 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/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/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/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 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/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= github.com/networkplumbing/go-nft v0.2.0/go.mod h1:HnnM+tYvlGAsMU7yoYwXEVLLiDW9gdMmb5HoGcwpuQs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= 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/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= 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/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec= github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= github.com/opencontainers/runc v1.1.0/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= github.com/opencontainers/runc v1.1.2 h1:2VSZwLx5k/BfsBxMMipG/LYUnmqOD/BPkIVgQUcTlLw= github.com/opencontainers/runc v1.1.2/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 h1:3snG66yBm59tKhhSPQrQ/0bCrv1LQbKt40LnUPiUxdc= github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/opencontainers/selinux v1.10.1 h1:09LIPVRP3uuZGQvgR+SgMSNBd1Eb3vlRbGqQpoHsF8w= github.com/opencontainers/selinux v1.10.1/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/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/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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/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 v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 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.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34= github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/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-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/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.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/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.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/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.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 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 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 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/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/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.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/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.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 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY= github.com/urfave/cli v1.22.1/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= github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 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.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= 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.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0/go.mod h1:vEhqr0m4eTc+DWxfsXoXue2GBgV2uUwVznkGIHW/e5w= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0/go.mod h1:hO1KLR7jcKaDDKDkvI9dP/FIhpmna5lkqPUQdEjFAM8= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0/go.mod h1:keUU7UfnwWTWpJ+FWnyqmogPa82nuU5VUANFq49hlMY= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.3.0/go.mod h1:QNX1aly8ehqqX1LEa6YniTU7VY9I6R3X/oPxhGdTceE= go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs= go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.11.0/go.mod h1:QpEjXPrNQzrFDZgoTo49dgHR9RYRSrg3NAKnUGl9YpQ= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/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-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-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/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-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 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-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-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/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/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.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/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-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/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-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-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-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 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-20190619014844-b5b0513f8c1b/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-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/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-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-20201006153459-a7d1128ccaa0/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-20201031054903-ff519b6c9102/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-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 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-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 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-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-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-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/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-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/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-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-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-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/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-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/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-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/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-20191022100944-742c48ecaeb7/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-20191210023423-ac6580df4449/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-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/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-20200217220822-9197077df867/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-20200519105757-fe76b779f299/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-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/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-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210903071746-97244b99971b/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-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220405210540-1e041c57c461/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 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.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.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/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-20180221164845-07fd8470d635/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-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/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-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-20190328211700-ab21143f2384/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-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/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-20190624222133-a101b041ded4/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-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= 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-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/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-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-20200505023115-26f46d2f7ef8/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-20200616133436-c1934b75d054/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-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-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20200916195026-c9a70fc28ce3/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= 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/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= 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.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/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= 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-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= 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-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-20200117163144-32f20d992d24/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-20200423170343-7949de9c1215/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-20200513103714-09dca8ec2884/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-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 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-20200904004341-0bd0a958aa1d/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-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6 h1:FglFEfyj61zP3c6LgjmVHxYxZWXYul9oiS1EZqD5gLc= google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= 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.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 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.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= 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.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.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8= google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= 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-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 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.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.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= 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 v1.0.0-20141024133853-64131543e789/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/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 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.3/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-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= 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.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= k8s.io/api v0.22.5/go.mod h1:mEhXyLaSD1qTOf40rRiKXkc+2iCem09rWLlFwhCEiAs= k8s.io/api v0.24.2 h1:g518dPU/L7VRLxWfcadQn2OnsiGWVOadTLpdnqgY2OI= k8s.io/api v0.24.2/go.mod h1:AHqbSkTm6YrQ0ObxjO3Pmp/ubFF/KuM7jU+3khoBsOg= k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= k8s.io/apimachinery v0.22.5/go.mod h1:xziclGKwuuJ2RM5/rSFQSYAj0zdbci3DH8kj+WvyN0U= k8s.io/apimachinery v0.24.2 h1:5QlH9SL2C8KMcrNJPor+LbXVTaZRReml7svPEh4OKDM= k8s.io/apimachinery v0.24.2/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= k8s.io/apiserver v0.22.5/go.mod h1:s2WbtgZAkTKt679sYtSudEQrTGWUSQAPe6MupLnlmaQ= k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= k8s.io/client-go v0.22.5/go.mod h1:cs6yf/61q2T1SdQL5Rdcjg9J1ElXSwbjSrW2vFImM4Y= k8s.io/client-go v0.24.2 h1:CoXFSf8if+bLEbinDqN9ePIDGzcLtqhfd6jpfnwGOFA= k8s.io/client-go v0.24.2/go.mod h1:zg4Xaoo+umDsfCWr4fCnmLEtQXyCNXCvJuSsglNcV30= k8s.io/code-generator v0.19.7/go.mod h1:lwEq3YnLYb/7uVXLorOJfxg+cUu2oihFhHZ0n9NIla0= k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= k8s.io/component-base v0.22.5/go.mod h1:VK3I+TjuF9eaa+Ln67dKxhGar5ynVbwnGrUiNF4MqCI= k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= k8s.io/cri-api v0.23.1/go.mod h1:REJE3PSU0h/LOV1APBrupxrEJqnoxZC8KWzkBUHwrK4= k8s.io/cri-api v0.25.0-alpha.2 h1:KSB1Untl+/iXXPuoqWtiW0YZbjqnnYGzhz4BbaLd3pg= k8s.io/cri-api v0.25.0-alpha.2/go.mod h1:bKbUiy31Ex/ogNMxLEikgk+5kPv1vevtbiLN+xWEXr8= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20201113003025-83324d819ded/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/klog/v2 v2.60.1 h1:VW25q3bZx9uE3vvdL6M8ezOX79vA2Aq1nEWLqNQclHc= k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 h1:Gii5eqf+GmIEwGNKQYQClCayuJCe2/4fZUvF7VG99sU= k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 h1:HNSDgDCrr/6Ly3WEGKZftiE7IY19Vz2GdbOCyI4qqhc= k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 h1:kDi4JBNAsJWfz1aEXhO8Jg87JJaPNLh5tIzYHgStQ9Y= sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY= sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y= sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= stargz-snapshotter-0.12.0/ipfs/000077500000000000000000000000001426301527400164215ustar00rootroot00000000000000stargz-snapshotter-0.12.0/ipfs/converter.go000066400000000000000000000061601426301527400207620ustar00rootroot00000000000000/* 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 ipfs import ( "context" "encoding/json" "fmt" "strings" "github.com/containerd/containerd" "github.com/containerd/containerd/content" "github.com/containerd/containerd/images/converter" "github.com/containerd/containerd/platforms" "github.com/ipfs/go-cid" files "github.com/ipfs/go-ipfs-files" iface "github.com/ipfs/interface-go-ipfs-core" "github.com/ipfs/interface-go-ipfs-core/options" ipath "github.com/ipfs/interface-go-ipfs-core/path" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) // Push pushes the provided image ref to IPFS with converting it to IPFS-enabled format. func Push(ctx context.Context, client *containerd.Client, api iface.CoreAPI, ref string, layerConvert converter.ConvertFunc, platformMC platforms.MatchComparer) (ipath.Resolved, error) { ctx, done, err := client.WithLease(ctx) if err != nil { return nil, err } defer done(ctx) img, err := client.ImageService().Get(ctx, ref) if err != nil { return nil, err } desc, err := converter.IndexConvertFuncWithHook(layerConvert, true, platformMC, converter.ConvertHooks{ PostConvertHook: pushBlobHook(api), })(ctx, client.ContentStore(), img.Target) if err != nil { return nil, err } root, err := json.Marshal(desc) if err != nil { return nil, err } return api.Unixfs().Add(ctx, files.NewBytesFile(root), options.Unixfs.Pin(true), options.Unixfs.CidVersion(1)) } func pushBlobHook(api iface.CoreAPI) converter.ConvertHookFunc { return func(ctx context.Context, cs content.Store, desc ocispec.Descriptor, newDesc *ocispec.Descriptor) (*ocispec.Descriptor, error) { resultDesc := newDesc if resultDesc == nil { descCopy := desc resultDesc = &descCopy } ra, err := cs.ReaderAt(ctx, *resultDesc) if err != nil { return nil, err } p, err := api.Unixfs().Add(ctx, files.NewReaderFile(content.NewReader(ra)), options.Unixfs.Pin(true), options.Unixfs.CidVersion(1)) if err != nil { return nil, err } // record IPFS URL using CIDv1 : https://docs.ipfs.io/how-to/address-ipfs-on-web/#native-urls if p.Cid().Version() == 0 { return nil, fmt.Errorf("CID verions 0 isn't supported") } resultDesc.URLs = []string{"ipfs://" + p.Cid().String()} return resultDesc, nil } } func GetPath(desc ocispec.Descriptor) (ipath.Path, error) { for _, u := range desc.URLs { if strings.HasPrefix(u, "ipfs://") { // support only content addressable URL (ipfs://) c, err := cid.Decode(u[7:]) if err != nil { return nil, err } return ipath.IpfsPath(c), nil } } return nil, fmt.Errorf("no CID is recorded") } stargz-snapshotter-0.12.0/ipfs/go.mod000066400000000000000000000011741426301527400175320ustar00rootroot00000000000000module github.com/containerd/stargz-snapshotter/ipfs go 1.16 require ( github.com/containerd/containerd v1.6.6 github.com/ipfs/go-cid v0.1.0 github.com/ipfs/go-ipfs-files v0.1.1 github.com/ipfs/interface-go-ipfs-core v0.7.0 github.com/ipld/go-codec-dagpb v1.3.2 // indirect github.com/libp2p/go-libp2p-record v0.1.1 // indirect github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 ) // Temporary fork for avoiding importing patent-protected code: https://github.com/hashicorp/golang-lru/issues/73 replace github.com/hashicorp/golang-lru => github.com/ktock/golang-lru v0.5.5-0.20211029085301-ec551be6f75c stargz-snapshotter-0.12.0/ipfs/go.sum000066400000000000000000006620121426301527400175630ustar00rootroot00000000000000bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo= 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 v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= 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/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= 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/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8/go.mod h1:CzsSbkDixRphAF5hS6wbMKq0eI6ccJRb7/A0M6JBnwg= github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY= github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= github.com/Microsoft/hcsshim v0.8.20/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= github.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= github.com/Microsoft/hcsshim v0.8.23/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01nnU2M8jKDg= github.com/Microsoft/hcsshim v0.9.2/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= github.com/Microsoft/hcsshim v0.9.3 h1:k371PzBuRrz2b+ebGuI2nVgVhgsVX60jMfSw80NECxo= github.com/Microsoft/hcsshim v0.9.3/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/Stebalien/go-bitfield v0.0.1/go.mod h1:GNjFpasyUVkHMsfEOk8EFLJ9syQ6SI+XWrX9Wf2XH0s= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= 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-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= github.com/alexflint/go-filemutex v1.1.0/go.mod h1:7P4iRhttt/nUvUOrYIhcpMzv2G6CY9UnI16Z+UJqRyk= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/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.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= github.com/btcsuite/btcd v0.0.0-20190523000118-16327141da8c/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.0.0-20190605094302-a0d1e3e36d50/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.21.0-beta h1:At9hIZdJW0s9E/fAz28nrz6AmcNlSVucCH796ZteX1M= github.com/btcsuite/btcd v0.21.0-beta/go.mod h1:ZSWyehm27aAuS9bvkATT+Xte3hjHZ+MRgMY/8NJ7K94= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= 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.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= 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-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= github.com/containerd/aufs v1.0.0/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E= github.com/containerd/btrfs v0.0.0-20210316141732-918d888fb676/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= github.com/containerd/btrfs v1.0.0/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI= github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= github.com/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBdAP4= github.com/containerd/cgroups v1.0.3/go.mod h1:/ofk34relqNjSGyqPrmEULrO4Sc8LJhvJmWbUCUKqj8= github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.9/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c= github.com/containerd/containerd v1.5.8/go.mod h1:YdFSv5bTFLpG2HIYmfqDpSYYTDX+mc5qtSuYx1YUb/s= github.com/containerd/containerd v1.6.1/go.mod h1:1nJz5xCZPusx6jJU8Frfct988y0NpumIq9ODB0kLtoE= github.com/containerd/containerd v1.6.6 h1:xJNPhbrmz8xAMDNoVjHy9YHtWwEQNS+CDkcIRh7t8Y0= github.com/containerd/containerd v1.6.6/go.mod h1:ZoP1geJldzCVY3Tonoz7b1IXk8rIX0Nltt5QE4OMNk0= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= github.com/containerd/continuity v0.2.2 h1:QSqfxcn8c+12slxwu00AtzXrsami0MJb/MQs9lOLHLA= github.com/containerd/continuity v0.2.2/go.mod h1:pWygW9u7LtS1o4N/Tn0FoCFDIXZ7rxcMX7HX1Dmibvk= github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= github.com/containerd/fifo v1.0.0 h1:6PirWBr9/L7GDamKr+XM0IeUFXu5mf3M/BPpH9gaLBU= github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU= github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk= github.com/containerd/go-cni v1.1.0/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA= github.com/containerd/go-cni v1.1.3/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA= github.com/containerd/go-cni v1.1.6/go.mod h1:BWtoWl5ghVymxu6MBjg79W9NZrCRyHIdUtk4cauMe34= github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= github.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0= github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA= github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow= github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms= github.com/containerd/imgcrypt v1.1.3/go.mod h1:/TPA1GIDXMzbj01yd8pIbQiLdQxed5ue1wb8bP7PQu4= github.com/containerd/imgcrypt v1.1.4/go.mod h1:LorQnPtzL/T0IyCeftcsMEO7AqxUDbdO8j/tSUpgxvo= github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= github.com/containerd/stargz-snapshotter/estargz v0.4.1/go.mod h1:x7Q9dg9QYb4+ELgxmo4gBUeJB0tl5dqH1Sdz0nJU1QM= github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= github.com/containerd/ttrpc v1.1.0 h1:GbtyLRxb0gOLR0TYQWt3O6B0NvT8tMdorEHqIQo/lWI= github.com/containerd/ttrpc v1.1.0/go.mod h1:XX4ZTnoOId4HklF4edwc4DcqskFZuvXB1Evzy5KFQpQ= github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= github.com/containerd/typeurl v1.0.2 h1:Chlt8zIieDbzQFzXzAeBEF92KhExuE4p9p92/QmY7aY= github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s= github.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2/go.mod h1:8IgZOBdv8fAgXddBT4dBXJPtxyRsejFIpXoklgxgEjw= github.com/containerd/zfs v0.0.0-20210301145711-11e8f1707f62/go.mod h1:A9zfAbMlQwE+/is6hi0Xw8ktpL+6glmqZYtevJgaB8Y= github.com/containerd/zfs v0.0.0-20210315114300-dde8f0fda960/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= github.com/containerd/zfs v0.0.0-20210324211415-d5c4544f0433/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containernetworking/cni v1.0.1/go.mod h1:AKuhXbN5EzmD4yTNtfSsX3tPcmtrBI6QcRV0NiNt15Y= github.com/containernetworking/cni v1.1.1/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw= github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= github.com/containernetworking/plugins v1.0.1/go.mod h1:QHCfGpaTwYTbbH+nZXKVTxNBDZcxSOplJT5ico8/FLE= github.com/containernetworking/plugins v1.1.1/go.mod h1:Sr5TH/eBsGLXK/h71HeLfX19sZPp3ry5uHSkI4LPxV8= github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= github.com/containers/ocicrypt v1.1.2/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= github.com/containers/ocicrypt v1.1.3/go.mod h1:xpdkbVAuaH3WzbEabUd5yDsl9SwJA5pABH85425Es2g= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3 h1:HVTnpeuvF6Owjd5mniCL8DEXo7uYXdQEmOP4FJbV5tg= github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3/go.mod h1:p1d6YEZWvFzEh4KLyvBcVSnrfNDDvK2zfK/4x2v/4pE= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cskr/pubsub v1.0.2 h1:vlOzMhl6PFn60gRlTQQsIfVwaPB/B/8MziK8FhEPt/0= github.com/cskr/pubsub v1.0.2/go.mod h1:/8MzYXk/NJAz782G8RPkFzXTZVu63VotefPnR9TIRis= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4= github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= github.com/dgraph-io/badger v1.5.5-0.20190226225317-8115aed38f8f/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ= github.com/dgraph-io/badger v1.6.0-rc1/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgraph-io/badger v1.6.1/go.mod h1:FRmFw3uxvcpa8zG3Rxs0th+hCLIuaQg8HlNV5bjgnuU= github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-farm v0.0.0-20190104051053-3adb47b1fb0f/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= 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.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= 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.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/flynn/noise v0.0.0-20180327030543-2492fe189ae6/go.mod h1:1i71OnUq3iUe1ma7Lr6yG6/rjvM3emb6yoL7xLFzcVQ= github.com/flynn/noise v1.0.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= 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/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= 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.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 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/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= github.com/gogo/googleapis v1.4.0 h1:zgVt4UpGxcqVOw97aRGxT4svlcmdK35fynLNctY32zI= github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 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/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/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-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= 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/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= 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.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.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.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.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.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= 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/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 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/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= 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.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.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.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/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.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= 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/martian/v3 v3.1.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-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 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.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/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.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4= github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/gxed/hashland/keccakpg v0.0.1/go.mod h1:kRzw3HkwxFU1mpmPP8v1WyQzwdGfmKFJ6tItnhQ67kU= github.com/gxed/hashland/murmur3 v0.0.1/go.mod h1:KjXop02n4/ckmZSnY2+HKcLud/tcmvhST0bie/0lS48= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/huin/goupnp v1.0.0 h1:wg75sLpL6DZqwHQN6E1Cfk6mtfzS45z8OV+ic+DtHRo= github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc= github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o= 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.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9FCCAVRQ= github.com/ipfs/bbloom v0.0.1/go.mod h1:oqo8CVWsJFMOZqTglBG4wydCE4IQA/G2/SEofB0rjUI= github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= github.com/ipfs/go-bitswap v0.1.0/go.mod h1:FFJEf18E9izuCqUtHxbWEvq+reg7o4CW5wSAE1wsxj0= github.com/ipfs/go-bitswap v0.1.2/go.mod h1:qxSWS4NXGs7jQ6zQvoPY3+NmOfHHG47mhkiLzBpJQIs= github.com/ipfs/go-bitswap v0.1.8/go.mod h1:TOWoxllhccevbWFUR2N7B1MTSVVge1s6XSMiCSA4MzM= github.com/ipfs/go-bitswap v0.3.4/go.mod h1:4T7fvNv/LmOys+21tnLzGKncMeeXUYUd1nUiJ2teMvI= github.com/ipfs/go-bitswap v0.6.0 h1:f2rc6GZtoSFhEIzQmddgGiel9xntj02Dg0ZNf2hSC+w= github.com/ipfs/go-bitswap v0.6.0/go.mod h1:Hj3ZXdOC5wBJvENtdqsixmzzRukqd8EHLxZLZc3mzRA= github.com/ipfs/go-block-format v0.0.1/go.mod h1:DK/YYcsSUIVAFNwo/KZCdIIbpN0ROH/baNLgayt4pFc= github.com/ipfs/go-block-format v0.0.2/go.mod h1:AWR46JfpcObNfg3ok2JHDUfdiHRgWhJgCQF+KIgOPJY= github.com/ipfs/go-block-format v0.0.3 h1:r8t66QstRp/pd/or4dpnbVfXT5Gt7lOqRvC+/dDTpMc= github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk= github.com/ipfs/go-blockservice v0.1.0/go.mod h1:hzmMScl1kXHg3M2BjTymbVPjv627N7sYcvYaKbop39M= github.com/ipfs/go-blockservice v0.1.4/go.mod h1:OTZhFpkgY48kNzbgyvcexW9cHrpjBYIjSR0KoDOFOLU= github.com/ipfs/go-blockservice v0.3.0 h1:cDgcZ+0P0Ih3sl8+qjFr2sVaMdysg/YZpLj5WJ8kiiw= github.com/ipfs/go-blockservice v0.3.0/go.mod h1:P5ppi8IHDC7O+pA0AlGTF09jruB2h+oP3wVVaZl8sfk= github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.4/go.mod h1:4LLaPOQwmk5z9LBgQnpkivrx8BJjUyGwTXCd5Xfj6+M= github.com/ipfs/go-cid v0.0.5/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67FexhXog= github.com/ipfs/go-cid v0.0.6/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= github.com/ipfs/go-cid v0.0.7/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= github.com/ipfs/go-cid v0.1.0 h1:YN33LQulcRHjfom/i25yoOZR4Telp1Hr/2RU3d0PnC0= github.com/ipfs/go-cid v0.1.0/go.mod h1:rH5/Xv83Rfy8Rw6xG+id3DYAMUVmem1MowoKwdXmN2o= github.com/ipfs/go-datastore v0.0.1/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= github.com/ipfs/go-datastore v0.0.5/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= github.com/ipfs/go-datastore v0.1.0/go.mod h1:d4KVXhMt913cLBEI/PXAy6ko+W7e9AhyAKBGh803qeE= github.com/ipfs/go-datastore v0.1.1/go.mod h1:w38XXW9kVFNp57Zj5knbKWM2T+KOZCGDRVNdgPHtbHw= github.com/ipfs/go-datastore v0.3.1/go.mod h1:w38XXW9kVFNp57Zj5knbKWM2T+KOZCGDRVNdgPHtbHw= github.com/ipfs/go-datastore v0.4.0/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= github.com/ipfs/go-datastore v0.4.1/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= github.com/ipfs/go-datastore v0.4.4/go.mod h1:SX/xMIKoCszPqp+z9JhPYCmoOoXTvaa13XEbGtsFUhA= github.com/ipfs/go-datastore v0.4.5/go.mod h1:eXTcaaiN6uOlVCLS9GjJUJtlvJfM3xk23w3fyfrmmJs= github.com/ipfs/go-datastore v0.5.0 h1:rQicVCEacWyk4JZ6G5bD9TKR7lZEG1MWcG7UdWYrFAU= github.com/ipfs/go-datastore v0.5.0/go.mod h1:9zhEApYMTl17C8YDp7JmU7sQZi2/wqiYh73hakZ90Bk= github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk= github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps= github.com/ipfs/go-ds-badger v0.0.2/go.mod h1:Y3QpeSFWQf6MopLTiZD+VT6IC1yZqaGmjvRcKeSGij8= github.com/ipfs/go-ds-badger v0.0.5/go.mod h1:g5AuuCGmr7efyzQhLL8MzwqcauPojGPUaHzfGTzuE3s= github.com/ipfs/go-ds-badger v0.2.1/go.mod h1:Tx7l3aTph3FMFrRS838dcSJh+jjA7cX9DrGVwx/NOwE= github.com/ipfs/go-ds-badger v0.2.3/go.mod h1:pEYw0rgg3FIrywKKnL+Snr+w/LjJZVMTBRn4FS6UHUk= github.com/ipfs/go-ds-leveldb v0.0.1/go.mod h1:feO8V3kubwsEF22n0YRQCffeb79OOYIykR4L04tMOYc= github.com/ipfs/go-ds-leveldb v0.4.1/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s= github.com/ipfs/go-ds-leveldb v0.4.2/go.mod h1:jpbku/YqBSsBc1qgME8BkWS4AxzF2cEu1Ii2r79Hh9s= github.com/ipfs/go-fetcher v1.5.0/go.mod h1:5pDZ0393oRF/fHiLmtFZtpMNBQfHOYNPtryWedVuSWE= github.com/ipfs/go-ipfs-blockstore v0.0.1/go.mod h1:d3WClOmRQKFnJ0Jz/jj/zmksX0ma1gROTlovZKBmN08= github.com/ipfs/go-ipfs-blockstore v0.1.0/go.mod h1:5aD0AvHPi7mZc6Ci1WCAhiBQu2IsfTduLl+422H6Rqw= github.com/ipfs/go-ipfs-blockstore v0.1.4/go.mod h1:Jxm3XMVjh6R17WvxFEiyKBLUGr86HgIYJW/D/MwqeYQ= github.com/ipfs/go-ipfs-blockstore v1.2.0 h1:n3WTeJ4LdICWs/0VSfjHrlqpPpl6MZ+ySd3j8qz0ykw= github.com/ipfs/go-ipfs-blockstore v1.2.0/go.mod h1:eh8eTFLiINYNSNawfZOC7HOxNTxpB1PFuA5E1m/7exE= github.com/ipfs/go-ipfs-blocksutil v0.0.1 h1:Eh/H4pc1hsvhzsQoMEP3Bke/aW5P5rVM1IWFJMcGIPQ= github.com/ipfs/go-ipfs-blocksutil v0.0.1/go.mod h1:Yq4M86uIOmxmGPUHv/uI7uKqZNtLb449gwKqXjIsnRk= github.com/ipfs/go-ipfs-chunker v0.0.1/go.mod h1:tWewYK0we3+rMbOh7pPFGDyypCtvGcBFymgY4rSDLAw= github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= github.com/ipfs/go-ipfs-delay v0.0.1 h1:r/UXYyRcddO6thwOnhiznIAiSvxMECGgtv35Xs1IeRQ= github.com/ipfs/go-ipfs-delay v0.0.1/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw= github.com/ipfs/go-ipfs-ds-help v0.0.1/go.mod h1:gtP9xRaZXqIQRh1HRpp595KbBEdgqWFxefeVKOV8sxo= github.com/ipfs/go-ipfs-ds-help v0.1.1/go.mod h1:SbBafGJuGsPI/QL3j9Fc5YPLeAu+SzOkI0gFwAg+mOs= github.com/ipfs/go-ipfs-ds-help v1.1.0 h1:yLE2w9RAsl31LtfMt91tRZcrx+e61O5mDxFRR994w4Q= github.com/ipfs/go-ipfs-ds-help v1.1.0/go.mod h1:YR5+6EaebOhfcqVCyqemItCLthrpVNot+rsOU/5IatU= github.com/ipfs/go-ipfs-exchange-interface v0.0.1/go.mod h1:c8MwfHjtQjPoDyiy9cFquVtVHkO9b9Ob3FG91qJnWCM= github.com/ipfs/go-ipfs-exchange-interface v0.1.0 h1:TiMekCrOGQuWYtZO3mf4YJXDIdNgnKWZ9IE3fGlnWfo= github.com/ipfs/go-ipfs-exchange-interface v0.1.0/go.mod h1:ych7WPlyHqFvCi/uQI48zLZuAWVP5iTQPXEfVaw5WEI= github.com/ipfs/go-ipfs-exchange-offline v0.0.1/go.mod h1:WhHSFCVYX36H/anEKQboAzpUws3x7UeEGkzQc3iNkM0= github.com/ipfs/go-ipfs-exchange-offline v0.2.0 h1:2PF4o4A7W656rC0RxuhUace997FTcDTcIQ6NoEtyjAI= github.com/ipfs/go-ipfs-exchange-offline v0.2.0/go.mod h1:HjwBeW0dvZvfOMwDP0TSKXIHf2s+ksdP4E3MLDRtLKY= github.com/ipfs/go-ipfs-files v0.0.3/go.mod h1:INEFm0LL2LWXBhNJ2PMIIb2w45hpXgPjNoE7yA8Y1d4= github.com/ipfs/go-ipfs-files v0.0.8/go.mod h1:wiN/jSG8FKyk7N0WyctKSvq3ljIa2NNTiZB55kpTdOs= github.com/ipfs/go-ipfs-files v0.1.1 h1:/MbEowmpLo9PJTEQk16m9rKzUHjeP4KRU9nWJyJO324= github.com/ipfs/go-ipfs-files v0.1.1/go.mod h1:8xkIrMWH+Y5P7HvJ4Yc5XWwIW2e52dyXUiC0tZyjDbM= github.com/ipfs/go-ipfs-posinfo v0.0.1/go.mod h1:SwyeVP+jCwiDu0C313l/8jg6ZxM0qqtlt2a0vILTc1A= github.com/ipfs/go-ipfs-pq v0.0.1/go.mod h1:LWIqQpqfRG3fNc5XsnIhz/wQ2XXGyugQwls7BgUmUfY= github.com/ipfs/go-ipfs-pq v0.0.2 h1:e1vOOW6MuOwG2lqxcLA+wEn93i/9laCY8sXAw76jFOY= github.com/ipfs/go-ipfs-pq v0.0.2/go.mod h1:LWIqQpqfRG3fNc5XsnIhz/wQ2XXGyugQwls7BgUmUfY= github.com/ipfs/go-ipfs-routing v0.1.0/go.mod h1:hYoUkJLyAUKhF58tysKpids8RNDPO42BVMgK5dNsoqY= github.com/ipfs/go-ipfs-routing v0.2.1 h1:E+whHWhJkdN9YeoHZNj5itzc+OR292AJ2uE9FFiW0BY= github.com/ipfs/go-ipfs-routing v0.2.1/go.mod h1:xiNNiwgjmLqPS1cimvAw6EyB9rkVDbiocA4yY+wRNLM= github.com/ipfs/go-ipfs-util v0.0.1/go.mod h1:spsl5z8KUnrve+73pOhSVZND1SIxPW5RyBCNzQxlJBc= github.com/ipfs/go-ipfs-util v0.0.2 h1:59Sswnk1MFaiq+VcaknX7aYEyGyGDAA73ilhEK2POp8= github.com/ipfs/go-ipfs-util v0.0.2/go.mod h1:CbPtkWJzjLdEcezDns2XYaehFVNXG9zrdrtMecczcsQ= github.com/ipfs/go-ipld-cbor v0.0.2/go.mod h1:wTBtrQZA3SoFKMVkp6cn6HMRteIB1VsmHA0AQFOn7Nc= github.com/ipfs/go-ipld-cbor v0.0.3/go.mod h1:wTBtrQZA3SoFKMVkp6cn6HMRteIB1VsmHA0AQFOn7Nc= github.com/ipfs/go-ipld-cbor v0.0.5 h1:ovz4CHKogtG2KB/h1zUp5U0c/IzZrL435rCh5+K/5G8= github.com/ipfs/go-ipld-cbor v0.0.5/go.mod h1:BkCduEx3XBCO6t2Sfo5BaHzuok7hbhdMm9Oh8B2Ftq4= github.com/ipfs/go-ipld-format v0.0.1/go.mod h1:kyJtbkDALmFHv3QR6et67i35QzO3S0dCDnkOJhcZkms= github.com/ipfs/go-ipld-format v0.0.2/go.mod h1:4B6+FM2u9OJ9zCV+kSbgFAZlOrv1Hqbf0INGQgiKf9k= github.com/ipfs/go-ipld-format v0.2.0/go.mod h1:3l3C1uKoadTPbeNfrDi+xMInYKlx2Cvg1BuydPSdzQs= github.com/ipfs/go-ipld-format v0.3.0 h1:Mwm2oRLzIuUwEPewWAWyMuuBQUsn3awfFEYVb8akMOQ= github.com/ipfs/go-ipld-format v0.3.0/go.mod h1:co/SdBE8h99968X0hViiw1MNlh6fvxxnHpvVLnH7jSM= github.com/ipfs/go-ipld-legacy v0.1.0 h1:wxkkc4k8cnvIGIjPO0waJCe7SHEyFgl+yQdafdjGrpA= github.com/ipfs/go-ipld-legacy v0.1.0/go.mod h1:86f5P/srAmh9GcIcWQR9lfFLZPrIyyXQeVlOWeeWEuI= github.com/ipfs/go-log v0.0.1/go.mod h1:kL1d2/hzSpI0thNYjiKfjanbVNU+IIGA/WnNESY9leM= github.com/ipfs/go-log v1.0.2/go.mod h1:1MNjMxe0u6xvJZgeqbJ8vdo2TKaGwZ1a0Bpza+sr2Sk= github.com/ipfs/go-log v1.0.3/go.mod h1:OsLySYkwIbiSUR/yBTdv1qPtcE4FW3WPWk/ewz9Ru+A= github.com/ipfs/go-log v1.0.4/go.mod h1:oDCg2FkjogeFOhqqb+N39l2RpTNPL6F/StPkB3kPgcs= github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8= github.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo= github.com/ipfs/go-log/v2 v2.0.2/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= github.com/ipfs/go-log/v2 v2.0.3/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBWFaa9+0= github.com/ipfs/go-log/v2 v2.0.5/go.mod h1:eZs4Xt4ZUJQFM3DlanGhy7TkwwawCZcSByscwkWG+dw= github.com/ipfs/go-log/v2 v2.1.1/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHntrv9KM= github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g= github.com/ipfs/go-log/v2 v2.3.0 h1:31Re/cPqFHpsRHgyVwjWADPoF0otB1WrjTy8ZFYwEZU= github.com/ipfs/go-log/v2 v2.3.0/go.mod h1:QqGoj30OTpnKaG/LKTGTxoP2mmQtjVMEnK72gynbe/g= github.com/ipfs/go-merkledag v0.2.3/go.mod h1:SQiXrtSts3KGNmgOzMICy5c0POOpUNQLvB3ClKnBAlk= github.com/ipfs/go-merkledag v0.3.2/go.mod h1:fvkZNNZixVW6cKSZ/JfLlON5OlgTXNdRLz0p6QG/I2M= github.com/ipfs/go-merkledag v0.6.0 h1:oV5WT2321tS4YQVOPgIrWHvJ0lJobRTerU+i9nmUCuA= github.com/ipfs/go-merkledag v0.6.0/go.mod h1:9HSEwRd5sV+lbykiYP+2NC/3o6MZbKNaa4hfNcH5iH0= github.com/ipfs/go-metrics-interface v0.0.1 h1:j+cpbjYvu4R8zbleSs36gvB7jR+wsL2fGD6n0jO4kdg= github.com/ipfs/go-metrics-interface v0.0.1/go.mod h1:6s6euYU4zowdslK0GKHmqaIZ3j/b/tL7HTWtJ4VPgWY= github.com/ipfs/go-path v0.1.1 h1:0rfiI0IoNTYUyQN0ifz2zQBR6mZhOKv7qW5Jjx/4fG8= github.com/ipfs/go-path v0.1.1/go.mod h1:vC8q4AKOtrjJz2NnllIrmr2ZbGlF5fW2OKKyhV9ggb0= github.com/ipfs/go-peertaskqueue v0.1.0/go.mod h1:Jmk3IyCcfl1W3jTW3YpghSwSEC6IJ3Vzz/jUmWw8Z0U= github.com/ipfs/go-peertaskqueue v0.1.1/go.mod h1:Jmk3IyCcfl1W3jTW3YpghSwSEC6IJ3Vzz/jUmWw8Z0U= github.com/ipfs/go-peertaskqueue v0.2.0/go.mod h1:5/eNrBEbtSKWCG+kQK8K8fGNixoYUnr+P7jivavs9lY= github.com/ipfs/go-peertaskqueue v0.7.0 h1:VyO6G4sbzX80K58N60cCaHsSsypbUNs1GjO5seGNsQ0= github.com/ipfs/go-peertaskqueue v0.7.0/go.mod h1:M/akTIE/z1jGNXMU7kFB4TeSEFvj68ow0Rrb04donIU= github.com/ipfs/go-unixfs v0.2.4/go.mod h1:SUdisfUjNoSDzzhGVxvCL9QO/nKdwXdr+gbMUdqcbYw= github.com/ipfs/go-unixfsnode v1.1.2/go.mod h1:5dcE2x03pyjHk4JjamXmunTMzz+VUtqvPwZjIEkfV6s= github.com/ipfs/go-verifcid v0.0.1 h1:m2HI7zIuR5TFyQ1b79Da5N9dnnCP1vcu2QqawmWlK2E= github.com/ipfs/go-verifcid v0.0.1/go.mod h1:5Hrva5KBeIog4A+UpqlaIU+DEstipcJYQQZc0g37pY0= github.com/ipfs/interface-go-ipfs-core v0.7.0 h1:7tb+2upz8oCcjIyjo1atdMk+P+u7wPmI+GksBlLE8js= github.com/ipfs/interface-go-ipfs-core v0.7.0/go.mod h1:lF27E/nnSPbylPqKVXGZghal2hzifs3MmjyiEjnc9FY= github.com/ipld/go-codec-dagpb v1.3.0/go.mod h1:ga4JTU3abYApDC3pZ00BC2RSvC3qfBb9MSJkMLSwnhA= github.com/ipld/go-codec-dagpb v1.3.2 h1:MZQUIjanHXXfDuYmtWYT8nFbqfFsZuyHClj6VDmSXr4= github.com/ipld/go-codec-dagpb v1.3.2/go.mod h1:ga4JTU3abYApDC3pZ00BC2RSvC3qfBb9MSJkMLSwnhA= github.com/ipld/go-ipld-prime v0.9.1-0.20210324083106-dc342a9917db/go.mod h1:KvBLMr4PX1gWptgkzRjVZCrLmSGcZCb/jioOQwCqZN8= github.com/ipld/go-ipld-prime v0.11.0 h1:jD/b/22R7CSL+F9xNffcexs+wO0Ji/TfwXO/TWck+70= github.com/ipld/go-ipld-prime v0.11.0/go.mod h1:+WIAkokurHmZ/KwzDOMUuoeJgaRQktHtEaLglS3ZeV8= github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= github.com/j-keck/arping v1.0.2/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw= github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= github.com/jackpal/go-nat-pmp v1.0.1/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jbenet/go-cienv v0.0.0-20150120210510-1bb1476777ec/go.mod h1:rGaEvXB4uRSZMmzKNLoXvTu1sfx+1kv/DojUlPrSZGs= github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA= github.com/jbenet/go-temp-err-catcher v0.0.0-20150120210811-aac704a3f4f2/go.mod h1:8GXXJV31xl8whumTzdZsTt3RnUIiPqzkyf7mxToRCMs= github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk= github.com/jbenet/goprocess v0.0.0-20160826012719-b497e2f366b8/go.mod h1:Ly/wlsjFq/qrU3Rar62tu1gASgGw6chQbSh/XgIIXCY= github.com/jbenet/goprocess v0.1.3/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= github.com/jbenet/goprocess v0.1.4 h1:DRGOFReOMqqDNXwW70QkacFW0YN9QnwLV0Vqk+3oU0o= github.com/jbenet/goprocess v0.1.4/go.mod h1:5yspPrukOVuOLORacaBi858NqyClJPQxYZlqdZVfqY4= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= 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/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= 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.8/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/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/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 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/kami-zh/go-capturer v0.0.0-20171211120116-e492ea43421d/go.mod h1:P2viExyCEfeWGU259JnaQ34Inuec4R38JCyBx2edgD0= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 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/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.13 h1:eSvu8Tmq6j2psUJqJrLcWH6K3w5Dwc+qipbaA6eVEN4= github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.6 h1:dQ5ueTiftKxp0gyjKSx5+8BtPWkyQbd95m8Gys/RarI= github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 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.2/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/koron/go-ssdp v0.0.0-20180514024734-4a0ed625a78b/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d h1:68u9r4wEvL3gYg2jvAOgROwZ3H+Y3hIDk4tbbmIjcYQ= github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= 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 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= 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/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= 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/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/ktock/golang-lru v0.5.5-0.20211029085301-ec551be6f75c h1:xNyENjfLL0OOOIOLCPnDTLN4whgVMcak4Ep9CUEwbtI= github.com/ktock/golang-lru v0.5.5-0.20211029085301-ec551be6f75c/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/libp2p/go-addr-util v0.0.1/go.mod h1:4ac6O7n9rIAKB1dnd+s8IbbMXkt+oBpzX4/+RACcnlQ= github.com/libp2p/go-addr-util v0.0.2 h1:7cWK5cdA5x72jX0g8iLrQWm5TRJZ6CzGdPEhWj7plWU= github.com/libp2p/go-addr-util v0.0.2/go.mod h1:Ecd6Fb3yIuLzq4bD7VcywcVSBtefcAwnUISBM3WG15E= github.com/libp2p/go-buffer-pool v0.0.1/go.mod h1:xtyIz9PMobb13WaxR6Zo1Pd1zXJKYg0a8KiIvDp3TzQ= github.com/libp2p/go-buffer-pool v0.0.2 h1:QNK2iAFa8gjAe1SPz6mHSMuCcjs+X1wlHzeOSqcmlfs= github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= github.com/libp2p/go-conn-security-multistream v0.1.0/go.mod h1:aw6eD7LOsHEX7+2hJkDxw1MteijaVcI+/eP2/x3J1xc= github.com/libp2p/go-conn-security-multistream v0.2.0/go.mod h1:hZN4MjlNetKD3Rq5Jb/P5ohUnFLNzEAR4DLSzpn2QLU= github.com/libp2p/go-conn-security-multistream v0.2.1/go.mod h1:cR1d8gA0Hr59Fj6NhaTpFhJZrjSYuNmhpT2r25zYR70= github.com/libp2p/go-eventbus v0.1.0/go.mod h1:vROgu5cs5T7cv7POWlWxBaVLxfSegC5UGQf8A2eEmx4= github.com/libp2p/go-eventbus v0.2.1 h1:VanAdErQnpTioN2TowqNcOijf6YwhuODe4pPKSDpxGc= github.com/libp2p/go-eventbus v0.2.1/go.mod h1:jc2S4SoEVPP48H9Wpzm5aiGwUCBMfGhVhhBjyhhCJs8= github.com/libp2p/go-flow-metrics v0.0.1/go.mod h1:Iv1GH0sG8DtYN3SVJ2eG221wMiNpZxBdp967ls1g+k8= github.com/libp2p/go-flow-metrics v0.0.3/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs= github.com/libp2p/go-libp2p v0.1.0/go.mod h1:6D/2OBauqLUoqcADOJpn9WbKqvaM07tDw68qHM0BxUM= github.com/libp2p/go-libp2p v0.1.1/go.mod h1:I00BRo1UuUSdpuc8Q2mN7yDF/oTUTRAX6JWpTiK9Rp8= github.com/libp2p/go-libp2p v0.6.1/go.mod h1:CTFnWXogryAHjXAKEbOf1OWY+VeAP3lDMZkfEI5sT54= github.com/libp2p/go-libp2p v0.7.0/go.mod h1:hZJf8txWeCduQRDC/WSqBGMxaTHCOYHt2xSU1ivxn0k= github.com/libp2p/go-libp2p v0.7.4/go.mod h1:oXsBlTLF1q7pxr+9w6lqzS1ILpyHsaBPniVO7zIHGMw= github.com/libp2p/go-libp2p v0.8.1/go.mod h1:QRNH9pwdbEBpx5DTJYg+qxcVaDMAz3Ee/qDKwXujH5o= github.com/libp2p/go-libp2p v0.13.0/go.mod h1:pM0beYdACRfHO1WcJlp65WXyG2A6NqYM+t2DTVAJxMo= github.com/libp2p/go-libp2p v0.14.3 h1:NST/bkwGSyaOt+stT7GBHY1+OqaANZ+QUOjpsmjXVC4= github.com/libp2p/go-libp2p v0.14.3/go.mod h1:d12V4PdKbpL0T1/gsUNN8DfgMuRPDX8bS2QxCZlwRH0= github.com/libp2p/go-libp2p-autonat v0.1.0/go.mod h1:1tLf2yXxiE/oKGtDwPYWTSYG3PtvYlJmg7NeVtPRqH8= github.com/libp2p/go-libp2p-autonat v0.1.1/go.mod h1:OXqkeGOY2xJVWKAGV2inNF5aKN/djNA3fdpCWloIudE= github.com/libp2p/go-libp2p-autonat v0.2.0/go.mod h1:DX+9teU4pEEoZUqR1PiMlqliONQdNbfzE1C718tcViI= github.com/libp2p/go-libp2p-autonat v0.2.1/go.mod h1:MWtAhV5Ko1l6QBsHQNSuM6b1sRkXrpk0/LqCr+vCVxI= github.com/libp2p/go-libp2p-autonat v0.2.2/go.mod h1:HsM62HkqZmHR2k1xgX34WuWDzk/nBwNHoeyyT4IWV6A= github.com/libp2p/go-libp2p-autonat v0.4.0/go.mod h1:YxaJlpr81FhdOv3W3BTconZPfhaYivRdf53g+S2wobk= github.com/libp2p/go-libp2p-autonat v0.4.2 h1:YMp7StMi2dof+baaxkbxaizXjY1RPvU71CXfxExzcUU= github.com/libp2p/go-libp2p-autonat v0.4.2/go.mod h1:YxaJlpr81FhdOv3W3BTconZPfhaYivRdf53g+S2wobk= github.com/libp2p/go-libp2p-blankhost v0.1.1/go.mod h1:pf2fvdLJPsC1FsVrNP3DUUvMzUts2dsLLBEpo1vW1ro= github.com/libp2p/go-libp2p-blankhost v0.1.4/go.mod h1:oJF0saYsAXQCSfDq254GMNmLNz6ZTHTOvtF4ZydUvwU= github.com/libp2p/go-libp2p-blankhost v0.2.0/go.mod h1:eduNKXGTioTuQAUcZ5epXi9vMl+t4d8ugUBRQ4SqaNQ= github.com/libp2p/go-libp2p-circuit v0.1.0/go.mod h1:Ahq4cY3V9VJcHcn1SBXjr78AbFkZeIRmfunbA7pmFh8= github.com/libp2p/go-libp2p-circuit v0.1.4/go.mod h1:CY67BrEjKNDhdTk8UgBX1Y/H5c3xkAcs3gnksxY7osU= github.com/libp2p/go-libp2p-circuit v0.2.1/go.mod h1:BXPwYDN5A8z4OEY9sOfr2DUQMLQvKt/6oku45YUmjIo= github.com/libp2p/go-libp2p-circuit v0.4.0/go.mod h1:t/ktoFIUzM6uLQ+o1G6NuBl2ANhBKN9Bc8jRIk31MoA= github.com/libp2p/go-libp2p-core v0.0.1/go.mod h1:g/VxnTZ/1ygHxH3dKok7Vno1VfpvGcGip57wjTU4fco= github.com/libp2p/go-libp2p-core v0.0.2/go.mod h1:9dAcntw/n46XycV4RnlBq3BpgrmyUi9LuoTNdPrbUco= github.com/libp2p/go-libp2p-core v0.0.3/go.mod h1:j+YQMNz9WNSkNezXOsahp9kwZBKBvxLpKD316QWSJXE= github.com/libp2p/go-libp2p-core v0.0.4/go.mod h1:jyuCQP356gzfCFtRKyvAbNkyeuxb7OlyhWZ3nls5d2I= github.com/libp2p/go-libp2p-core v0.2.0/go.mod h1:X0eyB0Gy93v0DZtSYbEM7RnMChm9Uv3j7yRXjO77xSI= github.com/libp2p/go-libp2p-core v0.2.2/go.mod h1:8fcwTbsG2B+lTgRJ1ICZtiM5GWCWZVoVrLaDRvIRng0= github.com/libp2p/go-libp2p-core v0.2.4/go.mod h1:STh4fdfa5vDYr0/SzYYeqnt+E6KfEV5VxfIrm0bcI0g= github.com/libp2p/go-libp2p-core v0.3.0/go.mod h1:ACp3DmS3/N64c2jDzcV429ukDpicbL6+TrrxANBjPGw= github.com/libp2p/go-libp2p-core v0.3.1/go.mod h1:thvWy0hvaSBhnVBaW37BvzgVV68OUhgJJLAa6almrII= github.com/libp2p/go-libp2p-core v0.4.0/go.mod h1:49XGI+kc38oGVwqSBhDEwytaAxgZasHhFfQKibzTls0= github.com/libp2p/go-libp2p-core v0.5.0/go.mod h1:49XGI+kc38oGVwqSBhDEwytaAxgZasHhFfQKibzTls0= github.com/libp2p/go-libp2p-core v0.5.1/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= github.com/libp2p/go-libp2p-core v0.5.4/go.mod h1:uN7L2D4EvPCvzSH5SrhR72UWbnSGpt5/a35Sm4upn4Y= github.com/libp2p/go-libp2p-core v0.5.5/go.mod h1:vj3awlOr9+GMZJFH9s4mpt9RHHgGqeHCopzbYKZdRjM= github.com/libp2p/go-libp2p-core v0.5.6/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= github.com/libp2p/go-libp2p-core v0.5.7/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= github.com/libp2p/go-libp2p-core v0.6.0/go.mod h1:txwbVEhHEXikXn9gfC7/UDDw7rkxuX0bJvM49Ykaswo= github.com/libp2p/go-libp2p-core v0.7.0/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= github.com/libp2p/go-libp2p-core v0.8.0/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= github.com/libp2p/go-libp2p-core v0.8.1/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= github.com/libp2p/go-libp2p-core v0.8.2/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= github.com/libp2p/go-libp2p-core v0.8.5 h1:aEgbIcPGsKy6zYcC+5AJivYFedhYa4sW7mIpWpUaLKw= github.com/libp2p/go-libp2p-core v0.8.5/go.mod h1:FfewUH/YpvWbEB+ZY9AQRQ4TAD8sJBt/G1rVvhz5XT8= github.com/libp2p/go-libp2p-crypto v0.1.0/go.mod h1:sPUokVISZiy+nNuTTH/TY+leRSxnFj/2GLjtOTW90hI= github.com/libp2p/go-libp2p-discovery v0.1.0/go.mod h1:4F/x+aldVHjHDHuX85x1zWoFTGElt8HnoDzwkFZm29g= github.com/libp2p/go-libp2p-discovery v0.2.0/go.mod h1:s4VGaxYMbw4+4+tsoQTqh7wfxg97AEdo4GYBt6BadWg= github.com/libp2p/go-libp2p-discovery v0.3.0/go.mod h1:o03drFnz9BVAZdzC/QUQ+NeQOu38Fu7LJGEOK2gQltw= github.com/libp2p/go-libp2p-discovery v0.5.0/go.mod h1:+srtPIU9gDaBNu//UHvcdliKBIcr4SfDcm0/PfPJLug= github.com/libp2p/go-libp2p-loggables v0.1.0 h1:h3w8QFfCt2UJl/0/NW4K829HX/0S4KD31PQ7m8UXXO8= github.com/libp2p/go-libp2p-loggables v0.1.0/go.mod h1:EyumB2Y6PrYjr55Q3/tiJ/o3xoDasoRYM7nOzEpoa90= github.com/libp2p/go-libp2p-mplex v0.2.0/go.mod h1:Ejl9IyjvXJ0T9iqUTE1jpYATQ9NM3g+OtR+EMMODbKo= github.com/libp2p/go-libp2p-mplex v0.2.1/go.mod h1:SC99Rxs8Vuzrf/6WhmH41kNn13TiYdAWNYHrwImKLnE= github.com/libp2p/go-libp2p-mplex v0.2.2/go.mod h1:74S9eum0tVQdAfFiKxAyKzNdSuLqw5oadDq7+L/FELo= github.com/libp2p/go-libp2p-mplex v0.2.3/go.mod h1:CK3p2+9qH9x+7ER/gWWDYJ3QW5ZxWDkm+dVvjfuG3ek= github.com/libp2p/go-libp2p-mplex v0.4.0/go.mod h1:yCyWJE2sc6TBTnFpjvLuEJgTSw/u+MamvzILKdX7asw= github.com/libp2p/go-libp2p-mplex v0.4.1/go.mod h1:cmy+3GfqfM1PceHTLL7zQzAAYaryDu6iPSC+CIb094g= github.com/libp2p/go-libp2p-nat v0.0.4/go.mod h1:N9Js/zVtAXqaeT99cXgTV9e75KpnWCvVOiGzlcHmBbY= github.com/libp2p/go-libp2p-nat v0.0.5/go.mod h1:1qubaE5bTZMJE+E/uu2URroMbzdubFz1ChgiN79yKPE= github.com/libp2p/go-libp2p-nat v0.0.6 h1:wMWis3kYynCbHoyKLPBEMu4YRLltbm8Mk08HGSfvTkU= github.com/libp2p/go-libp2p-nat v0.0.6/go.mod h1:iV59LVhB3IkFvS6S6sauVTSOrNEANnINbI/fkaLimiw= github.com/libp2p/go-libp2p-netutil v0.1.0 h1:zscYDNVEcGxyUpMd0JReUZTrpMfia8PmLKcKF72EAMQ= github.com/libp2p/go-libp2p-netutil v0.1.0/go.mod h1:3Qv/aDqtMLTUyQeundkKsA+YCThNdbQD54k3TqjpbFU= github.com/libp2p/go-libp2p-noise v0.1.1/go.mod h1:QDFLdKX7nluB7DEnlVPbz7xlLHdwHFA9HiohJRr3vwM= github.com/libp2p/go-libp2p-noise v0.2.0/go.mod h1:IEbYhBBzGyvdLBoxxULL/SGbJARhUeqlO8lVSREYu2Q= github.com/libp2p/go-libp2p-peer v0.2.0/go.mod h1:RCffaCvUyW2CJmG2gAWVqwePwW7JMgxjsHm7+J5kjWY= github.com/libp2p/go-libp2p-peerstore v0.1.0/go.mod h1:2CeHkQsr8svp4fZ+Oi9ykN1HBb6u0MOvdJ7YIsmcwtY= github.com/libp2p/go-libp2p-peerstore v0.1.3/go.mod h1:BJ9sHlm59/80oSkpWgr1MyY1ciXAXV397W6h1GH/uKI= github.com/libp2p/go-libp2p-peerstore v0.2.0/go.mod h1:N2l3eVIeAitSg3Pi2ipSrJYnqhVnMNQZo9nkSCuAbnQ= github.com/libp2p/go-libp2p-peerstore v0.2.1/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRjwRLBr4TYKfNgrUkOPA= github.com/libp2p/go-libp2p-peerstore v0.2.2/go.mod h1:NQxhNjWxf1d4w6PihR8btWIRjwRLBr4TYKfNgrUkOPA= github.com/libp2p/go-libp2p-peerstore v0.2.6/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= github.com/libp2p/go-libp2p-peerstore v0.2.7 h1:83JoLxyR9OYTnNfB5vvFqvMUv/xDNa6NoPHnENhBsGw= github.com/libp2p/go-libp2p-peerstore v0.2.7/go.mod h1:ss/TWTgHZTMpsU/oKVVPQCGuDHItOpf2W8RxAi50P2s= github.com/libp2p/go-libp2p-pnet v0.2.0/go.mod h1:Qqvq6JH/oMZGwqs3N1Fqhv8NVhrdYcO0BW4wssv21LA= github.com/libp2p/go-libp2p-quic-transport v0.10.0/go.mod h1:RfJbZ8IqXIhxBRm5hqUEJqjiiY8xmEuq3HUDS993MkA= github.com/libp2p/go-libp2p-record v0.1.0/go.mod h1:ujNc8iuE5dlKWVy6wuL6dd58t0n7xI4hAIl8pE6wu5Q= github.com/libp2p/go-libp2p-record v0.1.1 h1:ZJK2bHXYUBqObHX+rHLSNrM3M8fmJUlUHrodDPPATmY= github.com/libp2p/go-libp2p-record v0.1.1/go.mod h1:VRgKajOyMVgP/F0L5g3kH7SVskp17vFi2xheb5uMJtg= github.com/libp2p/go-libp2p-secio v0.1.0/go.mod h1:tMJo2w7h3+wN4pgU2LSYeiKPrfqBgkOsdiKK77hE7c8= github.com/libp2p/go-libp2p-secio v0.2.0/go.mod h1:2JdZepB8J5V9mBp79BmwsaPQhRPNN2NrnB2lKQcdy6g= github.com/libp2p/go-libp2p-secio v0.2.1/go.mod h1:cWtZpILJqkqrSkiYcDBh5lA3wbT2Q+hz3rJQq3iftD8= github.com/libp2p/go-libp2p-secio v0.2.2/go.mod h1:wP3bS+m5AUnFA+OFO7Er03uO1mncHG0uVwGrwvjYlNY= github.com/libp2p/go-libp2p-swarm v0.1.0/go.mod h1:wQVsCdjsuZoc730CgOvh5ox6K8evllckjebkdiY5ta4= github.com/libp2p/go-libp2p-swarm v0.2.2/go.mod h1:fvmtQ0T1nErXym1/aa1uJEyN7JzaTNyBcHImCxRpPKU= github.com/libp2p/go-libp2p-swarm v0.2.3/go.mod h1:P2VO/EpxRyDxtChXz/VPVXyTnszHvokHKRhfkEgFKNM= github.com/libp2p/go-libp2p-swarm v0.2.8/go.mod h1:JQKMGSth4SMqonruY0a8yjlPVIkb0mdNSwckW7OYziM= github.com/libp2p/go-libp2p-swarm v0.3.0/go.mod h1:hdv95GWCTmzkgeJpP+GK/9D9puJegb7H57B5hWQR5Kk= github.com/libp2p/go-libp2p-swarm v0.4.0/go.mod h1:XVFcO52VoLoo0eitSxNQWYq4D6sydGOweTOAjJNraCw= github.com/libp2p/go-libp2p-swarm v0.5.0/go.mod h1:sU9i6BoHE0Ve5SKz3y9WfKrh8dUat6JknzUehFx8xW4= github.com/libp2p/go-libp2p-testing v0.0.2/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= github.com/libp2p/go-libp2p-testing v0.0.3/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= github.com/libp2p/go-libp2p-testing v0.0.4/go.mod h1:gvchhf3FQOtBdr+eFUABet5a4MBLK8jM3V4Zghvmi+E= github.com/libp2p/go-libp2p-testing v0.1.0/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0= github.com/libp2p/go-libp2p-testing v0.1.1/go.mod h1:xaZWMJrPUM5GlDBxCeGUi7kI4eqnjVyavGroI2nxEM0= github.com/libp2p/go-libp2p-testing v0.1.2-0.20200422005655-8775583591d8/go.mod h1:Qy8sAncLKpwXtS2dSnDOP8ktexIAHKu+J+pnZOFZLTc= github.com/libp2p/go-libp2p-testing v0.3.0/go.mod h1:efZkql4UZ7OVsEfaxNHZPzIehtsBXMrXnCfJIgDti5g= github.com/libp2p/go-libp2p-testing v0.4.0 h1:PrwHRi0IGqOwVQWR3xzgigSlhlLfxgfXgkHxr77EghQ= github.com/libp2p/go-libp2p-testing v0.4.0/go.mod h1:Q+PFXYoiYFN5CAEG2w3gLPEzotlKsNSbKQ/lImlOWF0= github.com/libp2p/go-libp2p-tls v0.1.3/go.mod h1:wZfuewxOndz5RTnCAxFliGjvYSDA40sKitV4c50uI1M= github.com/libp2p/go-libp2p-transport-upgrader v0.1.1/go.mod h1:IEtA6or8JUbsV07qPW4r01GnTenLW4oi3lOPbUMGJJA= github.com/libp2p/go-libp2p-transport-upgrader v0.2.0/go.mod h1:mQcrHj4asu6ArfSoMuyojOdjx73Q47cYD7s5+gZOlns= github.com/libp2p/go-libp2p-transport-upgrader v0.3.0/go.mod h1:i+SKzbRnvXdVbU3D1dwydnTmKRPXiAR/fyvi1dXuL4o= github.com/libp2p/go-libp2p-transport-upgrader v0.4.0/go.mod h1:J4ko0ObtZSmgn5BX5AmegP+dK3CSnU2lMCKsSq/EY0s= github.com/libp2p/go-libp2p-transport-upgrader v0.4.2/go.mod h1:NR8ne1VwfreD5VIWIU62Agt/J18ekORFU/j1i2y8zvk= github.com/libp2p/go-libp2p-yamux v0.2.0/go.mod h1:Db2gU+XfLpm6E4rG5uGCFX6uXA8MEXOxFcRoXUODaK8= github.com/libp2p/go-libp2p-yamux v0.2.1/go.mod h1:1FBXiHDk1VyRM1C0aez2bCfHQ4vMZKkAQzZbkSQt5fI= github.com/libp2p/go-libp2p-yamux v0.2.2/go.mod h1:lIohaR0pT6mOt0AZ0L2dFze9hds9Req3OfS+B+dv4qw= github.com/libp2p/go-libp2p-yamux v0.2.5/go.mod h1:Zpgj6arbyQrmZ3wxSZxfBmbdnWtbZ48OpsfmQVTErwA= github.com/libp2p/go-libp2p-yamux v0.2.7/go.mod h1:X28ENrBMU/nm4I3Nx4sZ4dgjZ6VhLEn0XhIoZ5viCwU= github.com/libp2p/go-libp2p-yamux v0.2.8/go.mod h1:/t6tDqeuZf0INZMTgd0WxIRbtK2EzI2h7HbFm9eAKI4= github.com/libp2p/go-libp2p-yamux v0.4.0/go.mod h1:+DWDjtFMzoAwYLVkNZftoucn7PelNoy5nm3tZ3/Zw30= github.com/libp2p/go-libp2p-yamux v0.5.0/go.mod h1:AyR8k5EzyM2QN9Bbdg6X1SkVVuqLwTGf0L4DFq9g6po= github.com/libp2p/go-libp2p-yamux v0.5.1/go.mod h1:dowuvDu8CRWmr0iqySMiSxK+W0iL5cMVO9S94Y6gkv4= github.com/libp2p/go-libp2p-yamux v0.5.4/go.mod h1:tfrXbyaTqqSU654GTvK3ocnSZL3BuHoeTSqhcel1wsE= github.com/libp2p/go-maddr-filter v0.0.4/go.mod h1:6eT12kSQMA9x2pvFQa+xesMKUBlj9VImZbj3B9FBH/Q= github.com/libp2p/go-maddr-filter v0.0.5/go.mod h1:Jk+36PMfIqCJhAnaASRH83bdAvfDRp/w6ENFaC9bG+M= github.com/libp2p/go-maddr-filter v0.1.0/go.mod h1:VzZhTXkMucEGGEOSKddrwGiOv0tUhgnKqNEmIAz/bPU= github.com/libp2p/go-mplex v0.0.3/go.mod h1:pK5yMLmOoBR1pNCqDlA2GQrdAVTMkqFalaTWe7l4Yd0= github.com/libp2p/go-mplex v0.1.0/go.mod h1:SXgmdki2kwCUlCCbfGLEgHjC4pFqhTp0ZoV6aiKgxDU= github.com/libp2p/go-mplex v0.1.1/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3SFXI1lk= github.com/libp2p/go-mplex v0.1.2/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3SFXI1lk= github.com/libp2p/go-mplex v0.2.0/go.mod h1:0Oy/A9PQlwBytDRp4wSkFnzHYDKcpLot35JQ6msjvYQ= github.com/libp2p/go-mplex v0.3.0/go.mod h1:0Oy/A9PQlwBytDRp4wSkFnzHYDKcpLot35JQ6msjvYQ= github.com/libp2p/go-msgio v0.0.2/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= github.com/libp2p/go-msgio v0.0.3/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= github.com/libp2p/go-msgio v0.0.4/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ= github.com/libp2p/go-msgio v0.0.6 h1:lQ7Uc0kS1wb1EfRxO2Eir/RJoHkHn7t6o+EiwsYIKJA= github.com/libp2p/go-msgio v0.0.6/go.mod h1:4ecVB6d9f4BDSL5fqvPiC4A3KivjWn+Venn/1ALLMWA= github.com/libp2p/go-nat v0.0.3/go.mod h1:88nUEt0k0JD45Bk93NIwDqjlhiOwOoV36GchpcVc1yI= github.com/libp2p/go-nat v0.0.4/go.mod h1:Nmw50VAvKuk38jUBcmNh6p9lUJLoODbJRvYAa/+KSDo= github.com/libp2p/go-nat v0.0.5 h1:qxnwkco8RLKqVh1NmjQ+tJ8p8khNLFxuElYG/TwqW4Q= github.com/libp2p/go-nat v0.0.5/go.mod h1:B7NxsVNPZmRLvMOwiEO1scOSyjA56zxYAGv1yQgRkEU= github.com/libp2p/go-netroute v0.1.2/go.mod h1:jZLDV+1PE8y5XxBySEBgbuVAXbhtuHSdmLPL2n9MKbk= github.com/libp2p/go-netroute v0.1.3/go.mod h1:jZLDV+1PE8y5XxBySEBgbuVAXbhtuHSdmLPL2n9MKbk= github.com/libp2p/go-netroute v0.1.5/go.mod h1:V1SR3AaECRkEQCoFFzYwVYWvYIEtlxx89+O3qcpCl4A= github.com/libp2p/go-netroute v0.1.6 h1:ruPJStbYyXVYGQ81uzEDzuvbYRLKRrLvTYd33yomC38= github.com/libp2p/go-netroute v0.1.6/go.mod h1:AqhkMh0VuWmfgtxKPp3Oc1LdU5QSWS7wl0QLhSZqXxQ= github.com/libp2p/go-openssl v0.0.2/go.mod h1:v8Zw2ijCSWBQi8Pq5GAixw6DbFfa9u6VIYDXnvOXkc0= github.com/libp2p/go-openssl v0.0.3/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= github.com/libp2p/go-openssl v0.0.4/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= github.com/libp2p/go-openssl v0.0.5/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= github.com/libp2p/go-openssl v0.0.7 h1:eCAzdLejcNVBzP/iZM9vqHnQm+XyCEbSSIheIPRGNsw= github.com/libp2p/go-openssl v0.0.7/go.mod h1:unDrJpgy3oFr+rqXsarWifmJuNnJR4chtO1HmaZjggc= github.com/libp2p/go-reuseport v0.0.1/go.mod h1:jn6RmB1ufnQwl0Q1f+YxAj8isJgDCQzaaxIFYDhcYEA= github.com/libp2p/go-reuseport v0.0.2/go.mod h1:SPD+5RwGC7rcnzngoYC86GjPzjSywuQyMVAheVBD9nQ= github.com/libp2p/go-reuseport-transport v0.0.2/go.mod h1:YkbSDrvjUVDL6b8XqriyA20obEtsW9BLkuOUyQAOCbs= github.com/libp2p/go-reuseport-transport v0.0.3/go.mod h1:Spv+MPft1exxARzP2Sruj2Wb5JSyHNncjf1Oi2dEbzM= github.com/libp2p/go-reuseport-transport v0.0.4/go.mod h1:trPa7r/7TJK/d+0hdBLOCGvpQQVOU74OXbNCIMkufGw= github.com/libp2p/go-sockaddr v0.0.2/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= github.com/libp2p/go-sockaddr v0.1.0/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= github.com/libp2p/go-sockaddr v0.1.1 h1:yD80l2ZOdGksnOyHrhxDdTDFrf7Oy+v3FMVArIRgZxQ= github.com/libp2p/go-sockaddr v0.1.1/go.mod h1:syPvOmNs24S3dFVGJA1/mrqdeijPxLV2Le3BRLKd68k= github.com/libp2p/go-stream-muxer v0.0.1/go.mod h1:bAo8x7YkSpadMTbtTaxGVHWUQsR/l5MEaHbKaliuT14= github.com/libp2p/go-stream-muxer-multistream v0.2.0/go.mod h1:j9eyPol/LLRqT+GPLSxvimPhNph4sfYfMoDPd7HkzIc= github.com/libp2p/go-stream-muxer-multistream v0.3.0/go.mod h1:yDh8abSIzmZtqtOt64gFJUXEryejzNb0lisTt+fAMJA= github.com/libp2p/go-tcp-transport v0.1.0/go.mod h1:oJ8I5VXryj493DEJ7OsBieu8fcg2nHGctwtInJVpipc= github.com/libp2p/go-tcp-transport v0.1.1/go.mod h1:3HzGvLbx6etZjnFlERyakbaYPdfjg2pWP97dFZworkY= github.com/libp2p/go-tcp-transport v0.2.0/go.mod h1:vX2U0CnWimU4h0SGSEsg++AzvBcroCGYw28kh94oLe0= github.com/libp2p/go-tcp-transport v0.2.1/go.mod h1:zskiJ70MEfWz2MKxvFB/Pv+tPIB1PpPUrHIWQ8aFw7M= github.com/libp2p/go-tcp-transport v0.2.3/go.mod h1:9dvr03yqrPyYGIEN6Dy5UvdJZjyPFvl1S/igQ5QD1SU= github.com/libp2p/go-testutil v0.1.0/go.mod h1:81b2n5HypcVyrCg/MJx4Wgfp/VHojytjVe/gLzZ2Ehc= github.com/libp2p/go-ws-transport v0.1.0/go.mod h1:rjw1MG1LU9YDC6gzmwObkPd/Sqwhw7yT74kj3raBFuo= github.com/libp2p/go-ws-transport v0.2.0/go.mod h1:9BHJz/4Q5A9ludYWKoGCFC5gUElzlHoKzu0yY9p/klM= github.com/libp2p/go-ws-transport v0.3.0/go.mod h1:bpgTJmRZAvVHrgHybCVyqoBmyLQ1fiZuEaBYusP5zsk= github.com/libp2p/go-ws-transport v0.4.0/go.mod h1:EcIEKqf/7GDjth6ksuS/6p7R49V4CBY6/E7R/iyhYUA= github.com/libp2p/go-yamux v1.2.2/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/libp2p/go-yamux v1.2.3/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/libp2p/go-yamux v1.3.0/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/libp2p/go-yamux v1.3.3/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/libp2p/go-yamux v1.3.5/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow= github.com/libp2p/go-yamux v1.3.7/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= github.com/libp2p/go-yamux v1.4.0/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= github.com/libp2p/go-yamux v1.4.1/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE= github.com/libp2p/go-yamux/v2 v2.0.0/go.mod h1:NVWira5+sVUIU6tu1JWvaRn1dRnG+cawOJiflsAM+7U= github.com/libp2p/go-yamux/v2 v2.2.0/go.mod h1:3So6P6TV6r75R9jiBpiIKgU/66lOarCZjqROGxzPpPQ= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo= github.com/lucas-clemente/quic-go v0.19.3/go.mod h1:ADXpNbTQjq1hIzCpB+y/k5iz4n4z4IwqoLb94Kh5Hu8= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= github.com/marten-seemann/qtls v0.10.0/go.mod h1:UvMd1oaYDACI99/oZUYLzMCkBXQVT0aGm99sJhbT8hs= github.com/marten-seemann/qtls-go1-15 v0.1.1/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.12/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.28/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c/go.mod h1:0SQS9kMwD2VsyFEB++InYyBJroV/FRmBgcydeSUcJms= github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b/go.mod h1:lxPUiZwKoFL8DUUmalo2yJJUCxbPKtm8OKfqr2/FTNU= github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc/go.mod h1:cGKTAVKx4SxOuR/czcZ/E2RSJ3sfHs8FpHhQ5CWMf9s= 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/sha256-simd v0.0.0-20190131020904-2d45a736cd16/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= github.com/minio/sha256-simd v0.0.0-20190328051042-05b4dd3047e5/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= github.com/minio/sha256-simd v0.1.0/go.mod h1:2FMWW+8GMoPweT6+pI63m9YE3Lmw4J71hV56Chs1E/U= github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= 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/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= github.com/moby/sys/mountinfo v0.5.0 h1:2Ks8/r6lopsxWi9m58nlwjaeSzUX9iiL1vj5qB/9ObI= github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= github.com/moby/sys/signal v0.6.0 h1:aDpY94H8VlhTGa9sNYUFCFsMZIUh5wm0B6XkIoJj/iY= github.com/moby/sys/signal v0.6.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg= github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= github.com/moby/sys/symlink v0.2.0/go.mod h1:7uZVF2dqJjG/NsClqul95CqKOBRQyYSNnJ6BMgR/gFs= github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= github.com/mr-tron/base58 v1.1.1/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8= github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= 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/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-base36 v0.1.0 h1:JR6TyF7JjGd3m6FbLU2cOxhC0Li8z8dLNGQ89tUg4F4= github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM= github.com/multiformats/go-multiaddr v0.0.1/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.0.2/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.0.4/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.1.0/go.mod h1:xKVEak1K9cS1VdmPZW3LSIb6lgmoS58qz/pzqmAxV44= github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo= github.com/multiformats/go-multiaddr v0.2.0/go.mod h1:0nO36NvPpyV4QzvTLi/lafl2y95ncPj0vFwVF6k6wJ4= github.com/multiformats/go-multiaddr v0.2.1/go.mod h1:s/Apk6IyxfvMjDafnhJgJ3/46z7tZ04iMk5wP4QMGGE= github.com/multiformats/go-multiaddr v0.2.2/go.mod h1:NtfXiOtHvghW9KojvtySjH5y0u0xW5UouOmQQrn6a3Y= github.com/multiformats/go-multiaddr v0.3.0/go.mod h1:dF9kph9wfJ+3VLAaeBqo9Of8x4fJxp6ggJGteB8HQTI= github.com/multiformats/go-multiaddr v0.3.1/go.mod h1:uPbspcUPd5AfaP6ql3ujFY+QWzmBD8uLLL4bXW0XfGc= github.com/multiformats/go-multiaddr v0.3.3 h1:vo2OTSAqnENB2rLk79pLtr+uhj+VAzSe3uef5q0lRSs= github.com/multiformats/go-multiaddr v0.3.3/go.mod h1:lCKNGP1EQ1eZ35Za2wlqnabm9xQkib3fyB+nZXHLag0= github.com/multiformats/go-multiaddr-dns v0.0.1/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= github.com/multiformats/go-multiaddr-dns v0.0.2/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q= github.com/multiformats/go-multiaddr-dns v0.2.0/go.mod h1:TJ5pr5bBO7Y1B18djPuRsVkduhQH2YqYSbxWJzYGdK0= github.com/multiformats/go-multiaddr-dns v0.3.1 h1:QgQgR+LQVt3NPTjbrLLpsaT2ufAA2y0Mkk+QRVJbW3A= github.com/multiformats/go-multiaddr-dns v0.3.1/go.mod h1:G/245BRQ6FJGmryJCrOuTdB37AMA5AMOVuO6NY3JwTk= github.com/multiformats/go-multiaddr-fmt v0.0.1/go.mod h1:aBYjqL4T/7j4Qx+R73XSv/8JsgnRFlf0w2KGLCmXl3Q= github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E= github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo= github.com/multiformats/go-multiaddr-net v0.0.1/go.mod h1:nw6HSxNmCIQH27XPGBuX+d1tnvM7ihcFwHMSstNAVUU= github.com/multiformats/go-multiaddr-net v0.1.0/go.mod h1:5JNbcfBOP4dnhoZOv10JJVkJO0pCCEf8mTnipAo2UZQ= github.com/multiformats/go-multiaddr-net v0.1.1/go.mod h1:5JNbcfBOP4dnhoZOv10JJVkJO0pCCEf8mTnipAo2UZQ= github.com/multiformats/go-multiaddr-net v0.1.2/go.mod h1:QsWt3XK/3hwvNxZJp92iMQKME1qHfpYmyIjFVsSOY6Y= github.com/multiformats/go-multiaddr-net v0.1.3/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= github.com/multiformats/go-multiaddr-net v0.1.4/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= github.com/multiformats/go-multiaddr-net v0.1.5/go.mod h1:ilNnaM9HbmVFqsb/qcNysjCu4PVONlrBZpHIrw/qQuA= github.com/multiformats/go-multiaddr-net v0.2.0 h1:MSXRGN0mFymt6B1yo/6BPnIRpLPEnKgQNvVfCX5VDJk= github.com/multiformats/go-multiaddr-net v0.2.0/go.mod h1:gGdH3UXny6U3cKKYCvpXI5rnK7YaOIEOPVDI9tsJbEA= github.com/multiformats/go-multibase v0.0.1/go.mod h1:bja2MqRZ3ggyXtZSEDKpl0uO/gviWFaSteVbWT51qgs= github.com/multiformats/go-multibase v0.0.3 h1:l/B6bJDQjvQ5G52jw4QGSYeOTZoAwIO77RblWplfIqk= github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc= github.com/multiformats/go-multicodec v0.3.0 h1:tstDwfIjiHbnIjeM5Lp+pMrSeN+LCMsEwOrkPmWm03A= github.com/multiformats/go-multicodec v0.3.0/go.mod h1:qGGaQmioCDh+TeFOnxrbU0DaIPw8yFgAZgFG0V7p1qQ= github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U= github.com/multiformats/go-multihash v0.0.5/go.mod h1:lt/HCbqlQwlPBz7lv0sQCdtfcMtlJvakRUn/0Ual8po= github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= github.com/multiformats/go-multihash v0.0.10/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= github.com/multiformats/go-multihash v0.0.13/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= github.com/multiformats/go-multihash v0.0.14/go.mod h1:VdAWLKTwram9oKAatUcLxBNUjdtcVwxObEQBtRfuyjc= github.com/multiformats/go-multihash v0.0.15 h1:hWOPdrNqDjwHDx82vsYGSDZNyktOJJ2dzZJzFkOV1jM= github.com/multiformats/go-multihash v0.0.15/go.mod h1:D6aZrWNLFTV/ynMpKsNtB40mJzmCl4jb1alC0OvHiHg= github.com/multiformats/go-multistream v0.1.0/go.mod h1:fJTiDfXJVmItycydCnNx4+wSzZ5NwG2FEVAI30fiovg= github.com/multiformats/go-multistream v0.1.1/go.mod h1:KmHZ40hzVxiaiwlj3MEbYgK9JFk2/9UktWZAF54Du38= github.com/multiformats/go-multistream v0.2.0/go.mod h1:5GZPQZbkWOLOn3J2y4Y99vVW7vOfsAflxARk3x14o6k= github.com/multiformats/go-multistream v0.2.1/go.mod h1:5GZPQZbkWOLOn3J2y4Y99vVW7vOfsAflxARk3x14o6k= github.com/multiformats/go-multistream v0.2.2 h1:TCYu1BHTDr1F/Qm75qwYISQdzGcRdC21nFgQW7l7GBo= github.com/multiformats/go-multistream v0.2.2/go.mod h1:UIcnm7Zuo8HKG+HkWgfQsGL+/MIEhyTqbODbIUwSXKs= github.com/multiformats/go-varint v0.0.1/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/multiformats/go-varint v0.0.2/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.6 h1:gk85QWKxh3TazbLxED/NlDVv8+q+ReFJk7Y2W/KhfNY= github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/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-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/networkplumbing/go-nft v0.2.0/go.mod h1:HnnM+tYvlGAsMU7yoYwXEVLLiDW9gdMmb5HoGcwpuQs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= 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/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= 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/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec= github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= github.com/opencontainers/runc v1.1.0/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= github.com/opencontainers/runc v1.1.2 h1:2VSZwLx5k/BfsBxMMipG/LYUnmqOD/BPkIVgQUcTlLw= github.com/opencontainers/runc v1.1.2/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 h1:3snG66yBm59tKhhSPQrQ/0bCrv1LQbKt40LnUPiUxdc= github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/opencontainers/selinux v1.10.1 h1:09LIPVRP3uuZGQvgR+SgMSNBd1Eb3vlRbGqQpoHsF8w= github.com/opencontainers/selinux v1.10.1/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/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/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= 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/polydawn/refmt v0.0.0-20190221155625-df39d6c2d992/go.mod h1:uIp+gprXxxrWSjjklXD+mN4wed/tMfjMMmN/9+JsA9o= github.com/polydawn/refmt v0.0.0-20190408063855-01bf1e26dd14/go.mod h1:uIp+gprXxxrWSjjklXD+mN4wed/tMfjMMmN/9+JsA9o= github.com/polydawn/refmt v0.0.0-20190807091052-3d65705ee9f1/go.mod h1:uIp+gprXxxrWSjjklXD+mN4wed/tMfjMMmN/9+JsA9o= github.com/polydawn/refmt v0.0.0-20201211092308-30ac6d18308e h1:ZOcivgkkFRnjfoTcGsDq3UQYiBmekwLA+qg0OjyB/ls= github.com/polydawn/refmt v0.0.0-20201211092308-30ac6d18308e/go.mod h1:uIp+gprXxxrWSjjklXD+mN4wed/tMfjMMmN/9+JsA9o= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.8.0/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 v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 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.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/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-20190115171406-56726106282f/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-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.0/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.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/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.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/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.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw= github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI= github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg= github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw= github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y= github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q= github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ= github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I= github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0= github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk= github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8= github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= github.com/smartystreets/goconvey v0.0.0-20190222223459-a17d461953aa/go.mod h1:2RVY1rIf+2J2o/IM9+vPq9RzmHDSseB7FoXiSNIUsoU= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smola/gocompat v0.2.0/go.mod h1:1B0MlxbmoZNo3h8guHp8HztB3BSYR5itql9qtVc0ypY= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/spacemonkeygo/openssl v0.0.0-20181017203307-c2dcc5cca94a/go.mod h1:7AyxJNCJ7SBZ1MfVQCWD6Uqo2oubI2Eq2y2eqf+A5r0= github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 h1:RC6RW7j+1+HkWaX/Yh71Ee5ZHaHYt7ZP4sQgUrm6cDU= github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572/go.mod h1:w0SWMsp6j9O/dk4/ZpIhL+3CkG8ofA2vuv7k+ltqUMc= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/src-d/envconfig v1.0.0/go.mod h1:Q9YQZ7BKITldTBnoxsE5gOeB5y66RyPXeue/R4aaNBc= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/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.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/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.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 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/warpfork/go-wish v0.0.0-20180510122957-5ad1f5abf436/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= github.com/warpfork/go-wish v0.0.0-20190328234359-8b3e70f8e830/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= github.com/warpfork/go-wish v0.0.0-20200122115046-b9ea61034e4a h1:G++j5e0OC488te356JvdhaM8YS6nMsjLAYF7JxCv07w= github.com/warpfork/go-wish v0.0.0-20200122115046-b9ea61034e4a/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= github.com/whyrusleeping/cbor-gen v0.0.0-20200123233031-1cdf64d27158 h1:WXhVOwj2USAXB5oMDwRl3piOux2XMV9TANaYxXHdkoE= github.com/whyrusleeping/cbor-gen v0.0.0-20200123233031-1cdf64d27158/go.mod h1:Xj/M2wWU+QdTdRbu/L/1dIZY8/Wb2K9pAhtroQuxJJI= github.com/whyrusleeping/chunker v0.0.0-20181014151217-fe64bd25879f/go.mod h1:p9UJB6dDgdPgMJZs7UjUOdulKyRr9fqkS+6JKAInPy8= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= github.com/whyrusleeping/go-logging v0.0.0-20170515211332-0457bb6b88fc/go.mod h1:bopw91TMyo8J3tvftk8xmU2kPmlrt4nScJQZU2hE5EM= github.com/whyrusleeping/go-logging v0.0.1/go.mod h1:lDPYj54zutzG1XYfHAhcc7oNXEburHQBn+Iqd4yS4vE= github.com/whyrusleeping/go-notifier v0.0.0-20170827234753-097c5d47330f/go.mod h1:cZNvX9cFybI01GriPRMXDtczuvUhgbcYr9iCGaNlRv8= github.com/whyrusleeping/mafmt v1.2.8/go.mod h1:faQJFPbLSxzD9xpA02ttW/tS9vZykNvXwGvqIpk20FA= github.com/whyrusleeping/mdns v0.0.0-20180901202407-ef14215e6b30/go.mod h1:j4l84WPFclQPj320J9gp0XwNKBb3U0zt5CBqjPp22G4= github.com/whyrusleeping/mdns v0.0.0-20190826153040-b9b60ed33aa9/go.mod h1:j4l84WPFclQPj320J9gp0XwNKBb3U0zt5CBqjPp22G4= github.com/whyrusleeping/multiaddr-filter v0.0.0-20160516205228-e903e4adabd7/go.mod h1:X2c0RVCI1eSUFI8eLcY3c0423ykwiUdxLJtkDvruhjI= github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 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.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= 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.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= 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.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0/go.mod h1:vEhqr0m4eTc+DWxfsXoXue2GBgV2uUwVznkGIHW/e5w= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0/go.mod h1:hO1KLR7jcKaDDKDkvI9dP/FIhpmna5lkqPUQdEjFAM8= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0/go.mod h1:keUU7UfnwWTWpJ+FWnyqmogPa82nuU5VUANFq49hlMY= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.3.0/go.mod h1:QNX1aly8ehqqX1LEa6YniTU7VY9I6R3X/oPxhGdTceE= go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs= go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.11.0/go.mod h1:QpEjXPrNQzrFDZgoTo49dgHR9RYRSrg3NAKnUGl9YpQ= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec= go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190225124518-7f87c0fbb88b/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-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 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-20180702182130-06c8688daad7/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-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-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/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/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.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181106065722-10aee1819953/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-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/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-20190125091013-d26f9f9a57f3/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-20190227160552-c95aed5357e7/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-20190313220215-9f648a60d977/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-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/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-20190619014844-b5b0513f8c1b/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-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/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-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-20201006153459-a7d1128ccaa0/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-20201031054903-ff519b6c9102/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-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f h1:hEYJvxw1lSnWIl8X9ofsYMklzaDs90JI2az5YMd4fPM= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 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-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= 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-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-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-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/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-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/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-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/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-20190219092855-153ac476189d/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190302025703-b6889370fb10/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-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/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-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190524122548-abf6ff778158/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190526052359-791d8a0f4d09/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/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-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/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-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/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-20191022100944-742c48ecaeb7/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-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/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-20200217220822-9197077df867/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-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/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-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210903071746-97244b99971b/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-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 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.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.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/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-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181130052023-1c3d964395ce/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-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-20190328211700-ab21143f2384/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-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/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-20190624222133-a101b041ded4/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-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= 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-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/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-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-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 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-20200103221440-774c71fcf114/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-20200505023115-26f46d2f7ef8/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-20200616133436-c1934b75d054/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-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-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20200916195026-c9a70fc28ce3/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= 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/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.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/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 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-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= 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-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-20200117163144-32f20d992d24/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-20200423170343-7949de9c1215/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-20200513103714-09dca8ec2884/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-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 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-20200904004341-0bd0a958aa1d/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-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa h1:I0YcKz0I7OAhddo7ya8kMnvprhcWM045PmkBdMO9zN0= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= 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.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.28.1/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.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM= google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= 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-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 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.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.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= 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 v1.0.0-20141024133853-64131543e789/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/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/src-d/go-cli.v0 v0.0.0-20181105080154-d492247bbc0d/go.mod h1:z+K8VcOYVYcSwSjGebuDL6176A1XskgbtNl64NSg+n8= gopkg.in/src-d/go-log.v1 v1.0.1/go.mod h1:GN34hKP0g305ysm2/hctJ0Y8nWP3zxXXJ8GFabTyABE= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 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.3/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-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/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.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= k8s.io/api v0.22.5/go.mod h1:mEhXyLaSD1qTOf40rRiKXkc+2iCem09rWLlFwhCEiAs= k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= k8s.io/apimachinery v0.22.5/go.mod h1:xziclGKwuuJ2RM5/rSFQSYAj0zdbci3DH8kj+WvyN0U= k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= k8s.io/apiserver v0.22.5/go.mod h1:s2WbtgZAkTKt679sYtSudEQrTGWUSQAPe6MupLnlmaQ= k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= k8s.io/client-go v0.22.5/go.mod h1:cs6yf/61q2T1SdQL5Rdcjg9J1ElXSwbjSrW2vFImM4Y= k8s.io/code-generator v0.19.7/go.mod h1:lwEq3YnLYb/7uVXLorOJfxg+cUu2oihFhHZ0n9NIla0= k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= k8s.io/component-base v0.22.5/go.mod h1:VK3I+TjuF9eaa+Ln67dKxhGar5ynVbwnGrUiNF4MqCI= k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= k8s.io/cri-api v0.23.1/go.mod h1:REJE3PSU0h/LOV1APBrupxrEJqnoxZC8KWzkBUHwrK4= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20201113003025-83324d819ded/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= stargz-snapshotter-0.12.0/ipfs/resolver.go000066400000000000000000000055021426301527400206130ustar00rootroot00000000000000/* 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 ipfs import ( "context" "encoding/json" "fmt" "io" "path" "github.com/containerd/containerd/remotes" "github.com/ipfs/go-cid" files "github.com/ipfs/go-ipfs-files" iface "github.com/ipfs/interface-go-ipfs-core" ipath "github.com/ipfs/interface-go-ipfs-core/path" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) type resolver struct { api iface.CoreAPI scheme string } type ResolverOptions struct { // Scheme is the scheme to fetch the specified IPFS content. "ipfs" or "ipns". Scheme string } func NewResolver(client iface.CoreAPI, options ResolverOptions) (remotes.Resolver, error) { s := options.Scheme if s != "ipfs" && s != "ipns" { return nil, fmt.Errorf("unsupported scheme %q", s) } return &resolver{client, s}, nil } // Resolve resolves the provided ref for IPFS. ref must be a CID. // TODO: Allow specifying IPFS path or URL. This requires to modify `reference` pkg because // it's incompatbile to the current reference specification. func (r *resolver) Resolve(ctx context.Context, ref string) (name string, desc ocispec.Descriptor, err error) { c, err := cid.Decode(ref) if err != nil { return "", ocispec.Descriptor{}, err } p := ipath.New(path.Join("/", r.scheme, c.String())) if err := p.IsValid(); err != nil { return "", ocispec.Descriptor{}, err } n, err := r.api.Unixfs().Get(ctx, p) if err != nil { return "", ocispec.Descriptor{}, err } rc := files.ToFile(n) defer rc.Close() if err := json.NewDecoder(rc).Decode(&desc); err != nil { return "", ocispec.Descriptor{}, err } if _, err := GetPath(desc); err != nil { return "", ocispec.Descriptor{}, err } return ref, desc, nil } func (r *resolver) Fetcher(ctx context.Context, ref string) (remotes.Fetcher, error) { return &fetcher{r}, nil } func (r *resolver) Pusher(ctx context.Context, ref string) (remotes.Pusher, error) { return nil, fmt.Errorf("immutable remote") } type fetcher struct { r *resolver } func (f *fetcher) Fetch(ctx context.Context, desc ocispec.Descriptor) (io.ReadCloser, error) { p, err := GetPath(desc) if err != nil { return nil, err } n, err := f.r.api.Unixfs().Get(ctx, p) if err != nil { return nil, fmt.Errorf("failed to get file %q: %w", p.String(), err) } return files.ToFile(n), nil } stargz-snapshotter-0.12.0/metadata/000077500000000000000000000000001426301527400172405ustar00rootroot00000000000000stargz-snapshotter-0.12.0/metadata/memory/000077500000000000000000000000001426301527400205505ustar00rootroot00000000000000stargz-snapshotter-0.12.0/metadata/memory/reader.go000066400000000000000000000163211426301527400223440ustar00rootroot00000000000000/* 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 memory import ( "fmt" "io" "math" "os" "time" "github.com/containerd/stargz-snapshotter/estargz" "github.com/containerd/stargz-snapshotter/metadata" digest "github.com/opencontainers/go-digest" ) type reader struct { r *estargz.Reader rootID uint32 idMap map[uint32]*estargz.TOCEntry // NOTE: Once "reader.idOfEntry" is initialized by "reader.asssignIDs()", it must keyed by the value of "reader.idMap" // but not by "*estargz.TOCEntry" returned by "estargz.Reader" calls (e.g. "estargz.Reader.Lookup()"). This is because once // "reader" is replicated by "reader.Clone()", the replicated one has the different instance of "estargz.Reader" than the original // "*reader". Thus a "*estargz.TOCEntry" obtained by that (cloned) "estargz.Reader" is the different instance than the original and // can the key of "reader.idOfEntry". idOfEntry map[*estargz.TOCEntry]uint32 estargzOpts []estargz.OpenOption } func newReader(er *estargz.Reader, rootID uint32, idMap map[uint32]*estargz.TOCEntry, idOfEntry map[*estargz.TOCEntry]uint32, estargzOpts []estargz.OpenOption) *reader { return &reader{r: er, rootID: rootID, idMap: idMap, idOfEntry: idOfEntry, estargzOpts: estargzOpts} } func NewReader(sr *io.SectionReader, opts ...metadata.Option) (metadata.Reader, error) { var rOpts metadata.Options for _, o := range opts { if err := o(&rOpts); err != nil { return nil, fmt.Errorf("failed to apply option: %w", err) } } telemetry := &estargz.Telemetry{} if rOpts.Telemetry != nil { telemetry.GetFooterLatency = estargz.MeasureLatencyHook(rOpts.Telemetry.GetFooterLatency) telemetry.GetTocLatency = estargz.MeasureLatencyHook(rOpts.Telemetry.GetTocLatency) telemetry.DeserializeTocLatency = estargz.MeasureLatencyHook(rOpts.Telemetry.DeserializeTocLatency) } var decompressors []estargz.Decompressor for _, d := range rOpts.Decompressors { decompressors = append(decompressors, d) } erOpts := []estargz.OpenOption{ estargz.WithTOCOffset(rOpts.TOCOffset), estargz.WithTelemetry(telemetry), estargz.WithDecompressors(decompressors...), } er, err := estargz.Open(sr, erOpts...) if err != nil { return nil, err } root, ok := er.Lookup("") if !ok { return nil, fmt.Errorf("failed to get root node") } rootID, idMap, idOfEntry, err := assignIDs(er, root) if err != nil { return nil, err } r := newReader(er, rootID, idMap, idOfEntry, erOpts) return r, nil } // assignIDs assigns an to each TOC item and returns a mapping from ID to entry and vice-versa. func assignIDs(er *estargz.Reader, e *estargz.TOCEntry) (rootID uint32, idMap map[uint32]*estargz.TOCEntry, idOfEntry map[*estargz.TOCEntry]uint32, err error) { idMap = make(map[uint32]*estargz.TOCEntry) idOfEntry = make(map[*estargz.TOCEntry]uint32) curID := uint32(0) nextID := func() (uint32, error) { if curID == math.MaxUint32 { return 0, fmt.Errorf("sequence id too large") } curID++ return curID, nil } var mapChildren func(e *estargz.TOCEntry) (uint32, error) mapChildren = func(e *estargz.TOCEntry) (uint32, error) { if e.Type == "hardlink" { return 0, fmt.Errorf("unexpected type \"hardlink\": this should be replaced to the destination entry") } var ok bool id, ok := idOfEntry[e] if !ok { id, err = nextID() if err != nil { return 0, err } idMap[id] = e idOfEntry[e] = id } e.ForeachChild(func(_ string, ent *estargz.TOCEntry) bool { _, err = mapChildren(ent) return err == nil }) if err != nil { return 0, err } return id, nil } rootID, err = mapChildren(e) if err != nil { return 0, nil, nil, err } return rootID, idMap, idOfEntry, nil } func (r *reader) RootID() uint32 { return r.rootID } func (r *reader) TOCDigest() digest.Digest { return r.r.TOCDigest() } func (r *reader) GetOffset(id uint32) (offset int64, err error) { e, ok := r.idMap[id] if !ok { return 0, fmt.Errorf("entry %d not found", id) } return e.Offset, nil } func (r *reader) GetAttr(id uint32) (attr metadata.Attr, err error) { e, ok := r.idMap[id] if !ok { err = fmt.Errorf("entry %d not found", id) return } // TODO: zero copy attrFromTOCEntry(e, &attr) return } func (r *reader) GetChild(pid uint32, base string) (id uint32, attr metadata.Attr, err error) { e, ok := r.idMap[pid] if !ok { err = fmt.Errorf("parent entry %d not found", pid) return } child, ok := e.LookupChild(base) if !ok { err = fmt.Errorf("child %q of entry %d not found", base, pid) return } cid, ok := r.idOfEntry[child] if !ok { err = fmt.Errorf("id of entry %q not found", base) return } // TODO: zero copy attrFromTOCEntry(child, &attr) return cid, attr, nil } func (r *reader) ForeachChild(id uint32, f func(name string, id uint32, mode os.FileMode) bool) error { e, ok := r.idMap[id] if !ok { return fmt.Errorf("parent entry %d not found", id) } var err error e.ForeachChild(func(baseName string, ent *estargz.TOCEntry) bool { id, ok := r.idOfEntry[ent] if !ok { err = fmt.Errorf("id of child entry %q not found", baseName) return false } return f(baseName, id, ent.Stat().Mode()) }) return err } func (r *reader) OpenFile(id uint32) (metadata.File, error) { e, ok := r.idMap[id] if !ok { return nil, fmt.Errorf("entry %d not found", id) } sr, err := r.r.OpenFile(e.Name) if err != nil { return nil, err } return &file{r, e, sr}, nil } func (r *reader) Clone(sr *io.SectionReader) (metadata.Reader, error) { er, err := estargz.Open(sr, r.estargzOpts...) if err != nil { return nil, err } return newReader(er, r.rootID, r.idMap, r.idOfEntry, r.estargzOpts), nil } func (r *reader) Close() error { return nil } type file struct { r *reader e *estargz.TOCEntry sr *io.SectionReader } func (r *file) ChunkEntryForOffset(offset int64) (off int64, size int64, dgst string, ok bool) { e, ok := r.r.r.ChunkEntryForOffset(r.e.Name, offset) if !ok { return 0, 0, "", false } dgst = e.Digest if e.ChunkDigest != "" { // NOTE* "reg" also can contain ChunkDigest (e.g. when "reg" is the first entry of // chunked file) dgst = e.ChunkDigest } return e.ChunkOffset, e.ChunkSize, dgst, true } func (r *file) ReadAt(p []byte, off int64) (n int, err error) { return r.sr.ReadAt(p, off) } func (r *reader) NumOfNodes() (i int, _ error) { return len(r.idMap), nil } // TODO: share it with db pkg func attrFromTOCEntry(src *estargz.TOCEntry, dst *metadata.Attr) *metadata.Attr { dst.Size = src.Size dst.ModTime, _ = time.Parse(time.RFC3339, src.ModTime3339) dst.LinkName = src.LinkName dst.Mode = src.Stat().Mode() dst.UID = src.UID dst.GID = src.GID dst.DevMajor = src.DevMajor dst.DevMinor = src.DevMinor dst.Xattrs = src.Xattrs dst.NumLink = src.NumLink return dst } stargz-snapshotter-0.12.0/metadata/memory/reader_test.go000066400000000000000000000017111426301527400234000ustar00rootroot00000000000000/* 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 memory import ( "io" "testing" "github.com/containerd/stargz-snapshotter/metadata" ) func TestReader(t *testing.T) { metadata.TestReader(t, readerFactory) } func readerFactory(sr *io.SectionReader, opts ...metadata.Option) (metadata.TestableReader, error) { r, err := NewReader(sr, opts...) if err != nil { return nil, err } return r.(*reader), nil } stargz-snapshotter-0.12.0/metadata/metadata.go000066400000000000000000000073471426301527400213620ustar00rootroot00000000000000/* 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 metadata import ( "io" "os" "time" "github.com/containerd/stargz-snapshotter/estargz" digest "github.com/opencontainers/go-digest" ) // Attr reprensents the attributes of a node. type Attr struct { // Size, for regular files, is the logical size of the file. Size int64 // ModTime is the modification time of the node. ModTime time.Time // LinkName, for symlinks, is the link target. LinkName string // Mode is the permission and mode bits. Mode os.FileMode // UID is the user ID of the owner. UID int // GID is the group ID of the owner. GID int // DevMajor is the major device number for device. DevMajor int // DevMinor is the major device number for device. DevMinor int // Xattrs are the extended attribute for the node. Xattrs map[string][]byte // NumLink is the number of names pointing to this node. NumLink int } // Store reads the provided eStargz blob and creates a metadata reader. type Store func(sr *io.SectionReader, opts ...Option) (Reader, error) // Reader provides access to file metadata of a blob. type Reader interface { RootID() uint32 TOCDigest() digest.Digest GetOffset(id uint32) (offset int64, err error) GetAttr(id uint32) (attr Attr, err error) GetChild(pid uint32, base string) (id uint32, attr Attr, err error) ForeachChild(id uint32, f func(name string, id uint32, mode os.FileMode) bool) error OpenFile(id uint32) (File, error) Clone(sr *io.SectionReader) (Reader, error) Close() error } type File interface { ChunkEntryForOffset(offset int64) (off int64, size int64, dgst string, ok bool) ReadAt(p []byte, off int64) (n int, err error) } type Decompressor interface { estargz.Decompressor // DecompressTOC decompresses the passed blob and returns a reader of TOC JSON. DecompressTOC(io.Reader) (tocJSON io.ReadCloser, err error) } type Options struct { TOCOffset int64 Telemetry *Telemetry Decompressors []Decompressor } // Option is an option to configure the behaviour of reader. type Option func(o *Options) error // WithTOCOffset option specifies the offset of TOC func WithTOCOffset(tocOffset int64) Option { return func(o *Options) error { o.TOCOffset = tocOffset return nil } } // WithTelemetry option specifies the telemetry hooks func WithTelemetry(telemetry *Telemetry) Option { return func(o *Options) error { o.Telemetry = telemetry return nil } } // WithDecompressors option specifies decompressors to use. // Default is gzip-based decompressor. func WithDecompressors(decompressors ...Decompressor) Option { return func(o *Options) error { o.Decompressors = decompressors return nil } } // A func which takes start time and records the diff type MeasureLatencyHook func(time.Time) // A struct which defines telemetry hooks. By implementing these hooks you should be able to record // the latency metrics of the respective steps of estargz open operation. type Telemetry struct { GetFooterLatency MeasureLatencyHook // measure time to get stargz footer (in milliseconds) GetTocLatency MeasureLatencyHook // measure time to GET TOC JSON (in milliseconds) DeserializeTocLatency MeasureLatencyHook // measure time to deserialize TOC JSON (in milliseconds) } stargz-snapshotter-0.12.0/metadata/testutil.go000066400000000000000000000527311426301527400214540ustar00rootroot00000000000000/* 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 metadata import ( "compress/gzip" "fmt" "io" "os" "path" "path/filepath" "reflect" "strings" "testing" "time" "github.com/containerd/stargz-snapshotter/estargz" "github.com/containerd/stargz-snapshotter/estargz/zstdchunked" "github.com/containerd/stargz-snapshotter/util/testutil" "github.com/hashicorp/go-multierror" "github.com/klauspost/compress/zstd" ) var allowedPrefix = [4]string{"", "./", "/", "../"} type compression interface { estargz.Compressor Decompressor } var srcCompressions = map[string]compression{ "zstd-fastest": zstdCompressionWithLevel(zstd.SpeedFastest), "zstd-default": zstdCompressionWithLevel(zstd.SpeedDefault), "zstd-bettercompression": zstdCompressionWithLevel(zstd.SpeedBetterCompression), "gzip-nocompression": gzipCompressionWithLevel(gzip.NoCompression), "gzip-bestspeed": gzipCompressionWithLevel(gzip.BestSpeed), "gzip-bestcompression": gzipCompressionWithLevel(gzip.BestCompression), "gzip-defaultcompression": gzipCompressionWithLevel(gzip.DefaultCompression), "gzip-huffmanonly": gzipCompressionWithLevel(gzip.HuffmanOnly), } type zstdCompression struct { *zstdchunked.Compressor *zstdchunked.Decompressor } func zstdCompressionWithLevel(compressionLevel zstd.EncoderLevel) compression { return &zstdCompression{&zstdchunked.Compressor{CompressionLevel: compressionLevel}, &zstdchunked.Decompressor{}} } type gzipCompression struct { *estargz.GzipCompressor *estargz.GzipDecompressor } func gzipCompressionWithLevel(compressionLevel int) compression { return gzipCompression{estargz.NewGzipCompressorWithLevel(compressionLevel), &estargz.GzipDecompressor{}} } type ReaderFactory func(sr *io.SectionReader, opts ...Option) (r TestableReader, err error) type TestableReader interface { Reader NumOfNodes() (i int, _ error) } // TestReader tests Reader returns correct file metadata. func TestReader(t *testing.T, factory ReaderFactory) { sampleTime := time.Now().Truncate(time.Second) sampleText := "qwer" + "tyui" + "opas" + "dfgh" + "jk" tests := []struct { name string chunkSize int in []testutil.TarEntry want []check }{ { name: "empty", in: []testutil.TarEntry{}, want: []check{ numOfNodes(2), // root dir + prefetch landmark }, }, { name: "files", in: []testutil.TarEntry{ testutil.File("foo", "foofoo", testutil.WithFileMode(0644|os.ModeSetuid)), testutil.Dir("bar/"), testutil.File("bar/baz.txt", "bazbazbaz", testutil.WithFileOwner(1000, 1000)), testutil.File("xxx.txt", "xxxxx", testutil.WithFileModTime(sampleTime)), testutil.File("y.txt", "", testutil.WithFileXattrs(map[string]string{"testkey": "testval"})), }, want: []check{ numOfNodes(7), // root dir + prefetch landmark + 1 dir + 4 files hasFile("foo", "foofoo", 6), hasMode("foo", 0644|os.ModeSetuid), hasFile("bar/baz.txt", "bazbazbaz", 9), hasOwner("bar/baz.txt", 1000, 1000), hasFile("xxx.txt", "xxxxx", 5), hasModTime("xxx.txt", sampleTime), hasFile("y.txt", "", 0), hasXattrs("y.txt", map[string]string{"testkey": "testval"}), }, }, { name: "dirs", in: []testutil.TarEntry{ testutil.Dir("foo/", testutil.WithDirMode(os.ModeDir|0600|os.ModeSticky)), testutil.Dir("foo/bar/", testutil.WithDirOwner(1000, 1000)), testutil.File("foo/bar/baz.txt", "testtest"), testutil.File("foo/bar/xxxx", "x"), testutil.File("foo/bar/yyy", "yyy"), testutil.Dir("foo/a/", testutil.WithDirModTime(sampleTime)), testutil.Dir("foo/a/1/", testutil.WithDirXattrs(map[string]string{"testkey": "testval"})), testutil.File("foo/a/1/2", "1111111111"), }, want: []check{ numOfNodes(10), // root dir + prefetch landmark + 4 dirs + 4 files hasDirChildren("foo", "bar", "a"), hasDirChildren("foo/bar", "baz.txt", "xxxx", "yyy"), hasDirChildren("foo/a", "1"), hasDirChildren("foo/a/1", "2"), hasMode("foo", os.ModeDir|0600|os.ModeSticky), hasOwner("foo/bar", 1000, 1000), hasModTime("foo/a", sampleTime), hasXattrs("foo/a/1", map[string]string{"testkey": "testval"}), hasFile("foo/bar/baz.txt", "testtest", 8), hasFile("foo/bar/xxxx", "x", 1), hasFile("foo/bar/yyy", "yyy", 3), hasFile("foo/a/1/2", "1111111111", 10), }, }, { name: "hardlinks", in: []testutil.TarEntry{ testutil.File("foo", "foofoo", testutil.WithFileOwner(1000, 1000)), testutil.Dir("bar/"), testutil.Link("bar/foolink", "foo"), testutil.Link("bar/foolink2", "bar/foolink"), testutil.Dir("bar/1/"), testutil.File("bar/1/baz.txt", "testtest"), testutil.Link("barlink", "bar/1/baz.txt"), testutil.Symlink("foosym", "bar/foolink2"), }, want: []check{ numOfNodes(7), // root dir + prefetch landmark + 2 dirs + 1 flie(linked) + 1 file(linked) + 1 symlink hasFile("foo", "foofoo", 6), hasOwner("foo", 1000, 1000), hasFile("bar/foolink", "foofoo", 6), hasOwner("bar/foolink", 1000, 1000), hasFile("bar/foolink2", "foofoo", 6), hasOwner("bar/foolink2", 1000, 1000), hasFile("bar/1/baz.txt", "testtest", 8), hasFile("barlink", "testtest", 8), hasDirChildren("bar", "foolink", "foolink2", "1"), hasDirChildren("bar/1", "baz.txt"), sameNodes("foo", "bar/foolink", "bar/foolink2"), sameNodes("bar/1/baz.txt", "barlink"), linkName("foosym", "bar/foolink2"), hasNumLink("foo", 3), // parent dir + 2 links hasNumLink("barlink", 2), // parent dir + 1 link hasNumLink("bar", 3), // parent + "." + child's ".." }, }, { name: "various files", in: []testutil.TarEntry{ testutil.Dir("bar/"), testutil.File("bar/../bar///////////////////foo", ""), testutil.Chardev("bar/cdev", 10, 11), testutil.Blockdev("bar/bdev", 100, 101), testutil.Fifo("bar/fifo"), }, want: []check{ numOfNodes(7), // root dir + prefetch landmark + 1 file + 1 dir + 1 cdev + 1 bdev + 1 fifo hasFile("bar/foo", "", 0), hasChardev("bar/cdev", 10, 11), hasBlockdev("bar/bdev", 100, 101), hasFifo("bar/fifo"), }, }, { name: "chunks", chunkSize: 4, in: []testutil.TarEntry{ testutil.Dir("foo/"), testutil.File("foo/small", sampleText[:2]), testutil.File("foo/large", sampleText), }, want: []check{ numOfNodes(5), // root dir + prefetch landmark + 1 dir + 2 files numOfChunks("foo/large", 1+(len(sampleText)/4)), hasFileContentsOffset("foo/small", 0, sampleText[:2]), hasFileContentsOffset("foo/large", 0, sampleText[0:]), hasFileContentsOffset("foo/large", 1, sampleText[1:]), hasFileContentsOffset("foo/large", 2, sampleText[2:]), hasFileContentsOffset("foo/large", 3, sampleText[3:]), hasFileContentsOffset("foo/large", 4, sampleText[4:]), hasFileContentsOffset("foo/large", 5, sampleText[5:]), hasFileContentsOffset("foo/large", 6, sampleText[6:]), hasFileContentsOffset("foo/large", 7, sampleText[7:]), hasFileContentsOffset("foo/large", 8, sampleText[8:]), hasFileContentsOffset("foo/large", 9, sampleText[9:]), hasFileContentsOffset("foo/large", 10, sampleText[10:]), hasFileContentsOffset("foo/large", 11, sampleText[11:]), hasFileContentsOffset("foo/large", 12, sampleText[12:]), hasFileContentsOffset("foo/large", int64(len(sampleText)-1), ""), }, }, } for _, tt := range tests { for _, prefix := range allowedPrefix { prefix := prefix for srcCompresionName, srcCompression := range srcCompressions { srcCompression := srcCompression t.Run(tt.name+"-"+srcCompresionName, func(t *testing.T) { opts := []testutil.BuildEStargzOption{ testutil.WithBuildTarOptions(testutil.WithPrefix(prefix)), testutil.WithEStargzOptions(estargz.WithCompression(srcCompression)), } if tt.chunkSize > 0 { opts = append(opts, testutil.WithEStargzOptions(estargz.WithChunkSize(tt.chunkSize))) } esgz, _, err := testutil.BuildEStargz(tt.in, opts...) if err != nil { t.Fatalf("failed to build sample eStargz: %v", err) } telemetry, checkCalled := newCalledTelemetry() r, err := factory(esgz, WithDecompressors(new(zstdchunked.Decompressor)), WithTelemetry(telemetry)) if err != nil { t.Fatalf("failed to create new reader: %v", err) } defer r.Close() t.Logf("vvvvv Node tree vvvvv") t.Logf("[%d] ROOT", r.RootID()) dumpNodes(t, r, r.RootID(), 1) t.Logf("^^^^^^^^^^^^^^^^^^^^^") for _, want := range tt.want { want(t, r) } if err := checkCalled(); err != nil { t.Errorf("telemetry failure: %v", err) } // Test the cloned reader works correctly as well esgz2, _, err := testutil.BuildEStargz(tt.in, opts...) if err != nil { t.Fatalf("failed to build sample eStargz: %v", err) } clonedR, err := r.Clone(esgz2) if err != nil { t.Fatalf("failed to clone reader: %v", err) } defer clonedR.Close() t.Logf("vvvvv Node tree (cloned) vvvvv") t.Logf("[%d] ROOT", clonedR.RootID()) dumpNodes(t, clonedR.(TestableReader), clonedR.RootID(), 1) t.Logf("^^^^^^^^^^^^^^^^^^^^^") for _, want := range tt.want { want(t, clonedR.(TestableReader)) } }) } } } t.Run("clone-id-stability", func(t *testing.T) { 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) { if m == nil { m = make(map[string]uint32) } return m, r.ForeachChild(id, func(name string, id uint32, mode os.FileMode) bool { m[name] = id if _, err := mapEntries(r, id, m); err != nil { t.Fatalf("could not map files: %s", err) return false } return true }) } in := []testutil.TarEntry{ testutil.File("foo", "foofoo"), testutil.Dir("bar/"), testutil.File("bar/zzz.txt", "bazbazbaz"), testutil.File("bar/aaa.txt", "bazbazbaz"), testutil.File("bar/fff.txt", "bazbazbaz"), testutil.File("xxx.txt", "xxxxx"), testutil.File("y.txt", ""), } esgz, _, err := testutil.BuildEStargz(in) if err != nil { t.Fatalf("failed to build sample eStargz: %v", err) } r, err := factory(esgz) if err != nil { t.Fatalf("failed to create new reader: %v", err) } fileMap, err := mapEntries(r, r.RootID(), nil) if err != nil { t.Fatalf("could not map files: %s", err) } cr, err := r.Clone(esgz) if err != nil { t.Fatalf("could not clone reader: %s", err) } cloneFileMap, err := mapEntries(cr.(TestableReader), cr.RootID(), nil) if err != nil { t.Fatalf("could not map files in cloned reader: %s", err) } if !reflect.DeepEqual(fileMap, cloneFileMap) { for f, id := range fileMap { t.Logf("original mapping %s -> %d", f, id) } for f, id := range cloneFileMap { t.Logf("clone mapping %s -> %d", f, id) } t.Fatal("file -> ID mappings did not match between original and cloned reader") } }) } func newCalledTelemetry() (telemetry *Telemetry, check func() error) { var getFooterLatencyCalled bool var getTocLatencyCalled bool var deserializeTocLatencyCalled bool return &Telemetry{ func(time.Time) { getFooterLatencyCalled = true }, func(time.Time) { getTocLatencyCalled = true }, func(time.Time) { deserializeTocLatencyCalled = true }, }, func() error { var allErr error if !getFooterLatencyCalled { allErr = multierror.Append(allErr, fmt.Errorf("metrics GetFooterLatency isn't called")) } if !getTocLatencyCalled { allErr = multierror.Append(allErr, fmt.Errorf("metrics GetTocLatency isn't called")) } if !deserializeTocLatencyCalled { allErr = multierror.Append(allErr, fmt.Errorf("metrics DeserializeTocLatency isn't called")) } return allErr } } 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 { ind := "" for i := 0; i < level; i++ { ind += " " } t.Logf("%v+- [%d] %q : %v", ind, id, name, mode) dumpNodes(t, r, id, level+1) return true }); err != nil { t.Errorf("failed to dump nodes %v", err) } } type check func(*testing.T, TestableReader) func numOfNodes(want int) check { return func(t *testing.T, r TestableReader) { i, err := r.NumOfNodes() if err != nil { t.Errorf("num of nodes: %v", err) } if want != i { t.Errorf("unexpected num of nodes %d; want %d", i, want) } } } func numOfChunks(name string, num int) check { return func(t *testing.T, r TestableReader) { nr, ok := r.(interface { NumOfChunks(id uint32) (i int, _ error) }) if !ok { return // skip } id, err := lookup(r, name) if err != nil { t.Errorf("failed to lookup %q: %v", name, err) return } i, err := nr.NumOfChunks(id) if err != nil { t.Errorf("failed to get num of chunks of %q: %v", name, err) return } if i != num { t.Errorf("unexpected num of chunk of %q : %d want %d", name, i, num) } } } func sameNodes(n string, nodes ...string) check { return func(t *testing.T, r TestableReader) { id, err := lookup(r, n) if err != nil { t.Errorf("failed to lookup %q: %v", n, err) return } for _, en := range nodes { eid, err := lookup(r, en) if err != nil { t.Errorf("failed to lookup %q: %v", en, err) return } if eid != id { t.Errorf("unexpected ID of %q: %d want %d", en, eid, id) } } } } func linkName(name string, linkName string) check { return func(t *testing.T, r TestableReader) { id, err := lookup(r, name) if err != nil { t.Errorf("failed to lookup %q: %v", name, err) return } attr, err := r.GetAttr(id) if err != nil { t.Errorf("failed to get attr of %q: %v", name, err) return } if attr.Mode&os.ModeSymlink == 0 { t.Errorf("%q is not a symlink: %v", name, attr.Mode) return } if attr.LinkName != linkName { t.Errorf("unexpected link name of %q : %q want %q", name, attr.LinkName, linkName) return } } } func hasNumLink(name string, numLink int) check { return func(t *testing.T, r TestableReader) { id, err := lookup(r, name) if err != nil { t.Errorf("failed to lookup %q: %v", name, err) return } attr, err := r.GetAttr(id) if err != nil { t.Errorf("failed to get attr of %q: %v", name, err) return } if attr.NumLink != numLink { t.Errorf("unexpected numLink of %q: %d want %d", name, attr.NumLink, numLink) return } } } func hasDirChildren(name string, children ...string) check { return func(t *testing.T, r TestableReader) { id, err := lookup(r, name) if err != nil { t.Errorf("failed to lookup %q: %v", name, err) return } attr, err := r.GetAttr(id) if err != nil { t.Errorf("failed to get attr of %q: %v", name, err) return } if !attr.Mode.IsDir() { t.Errorf("%q is not directory: %v", name, attr.Mode) return } found := map[string]struct{}{} if err := r.ForeachChild(id, func(name string, id uint32, mode os.FileMode) bool { found[name] = struct{}{} return true }); err != nil { t.Errorf("failed to see children %v", err) return } if len(found) != len(children) { t.Errorf("unexpected number of children of %q : %d want %d", name, len(found), len(children)) } for _, want := range children { if _, ok := found[want]; !ok { t.Errorf("expected child %q not found in %q", want, name) } } } } func hasChardev(name string, maj, min int) check { return func(t *testing.T, r TestableReader) { id, err := lookup(r, name) if err != nil { t.Errorf("cannot find chardev %q: %v", name, err) return } attr, err := r.GetAttr(id) if err != nil { t.Errorf("cannot get attr of chardev %q: %v", name, err) return } if attr.Mode&os.ModeDevice == 0 || attr.Mode&os.ModeCharDevice == 0 { t.Errorf("file %q is not a chardev: %v", name, attr.Mode) return } if attr.DevMajor != maj || attr.DevMinor != min { t.Errorf("unexpected major/minor of chardev %q: %d/%d want %d/%d", name, attr.DevMajor, attr.DevMinor, maj, min) return } } } func hasBlockdev(name string, maj, min int) check { return func(t *testing.T, r TestableReader) { id, err := lookup(r, name) if err != nil { t.Errorf("cannot find blockdev %q: %v", name, err) return } attr, err := r.GetAttr(id) if err != nil { t.Errorf("cannot get attr of blockdev %q: %v", name, err) return } if attr.Mode&os.ModeDevice == 0 || attr.Mode&os.ModeCharDevice != 0 { t.Errorf("file %q is not a blockdev: %v", name, attr.Mode) return } if attr.DevMajor != maj || attr.DevMinor != min { t.Errorf("unexpected major/minor of blockdev %q: %d/%d want %d/%d", name, attr.DevMajor, attr.DevMinor, maj, min) return } } } func hasFifo(name string) check { return func(t *testing.T, r TestableReader) { id, err := lookup(r, name) if err != nil { t.Errorf("cannot find blockdev %q: %v", name, err) return } attr, err := r.GetAttr(id) if err != nil { t.Errorf("cannot get attr of blockdev %q: %v", name, err) return } if attr.Mode&os.ModeNamedPipe == 0 { t.Errorf("file %q is not a fifo: %v", name, attr.Mode) return } } } func hasFile(name, content string, size int64) check { return func(t *testing.T, r TestableReader) { id, err := lookup(r, name) if err != nil { t.Errorf("cannot find file %q: %v", name, err) return } attr, err := r.GetAttr(id) if err != nil { t.Errorf("cannot get attr of file %q: %v", name, err) return } if !attr.Mode.IsRegular() { t.Errorf("file %q is not a regular file: %v", name, attr.Mode) return } sr, err := r.OpenFile(id) if err != nil { t.Errorf("cannot open file %q: %v", name, err) return } data, err := io.ReadAll(io.NewSectionReader(sr, 0, attr.Size)) if err != nil { t.Errorf("cannot read file %q: %v", name, err) return } if attr.Size != size { t.Errorf("unexpected size of file %q : %d (%q) want %d (%q)", name, attr.Size, string(data), size, content) return } if string(data) != content { t.Errorf("unexpected content of %q: %q want %q", name, string(data), content) return } } } func hasFileContentsOffset(name string, off int64, contents string) check { return func(t *testing.T, r TestableReader) { id, err := lookup(r, name) if err != nil { t.Errorf("failed to lookup %q: %v", name, err) return } fr, err := r.OpenFile(id) if err != nil { t.Errorf("failed to open file %q: %v", name, err) return } buf := make([]byte, len(contents)) n, err := fr.ReadAt(buf, off) if err != nil && err != io.EOF { t.Errorf("failed to read file %q (off:%d, want:%q): %v", name, off, contents, err) return } if n != len(contents) { t.Errorf("failed to read contents %q (off:%d, want:%q) got %q", name, off, contents, string(buf)) return } } } func hasMode(name string, mode os.FileMode) check { return func(t *testing.T, r TestableReader) { id, err := lookup(r, name) if err != nil { t.Errorf("cannot find file %q: %v", name, err) return } attr, err := r.GetAttr(id) if err != nil { t.Errorf("cannot get attr of file %q: %v", name, err) return } if attr.Mode != mode { t.Errorf("unexpected mode of %q: %v want %v", name, attr.Mode, mode) return } } } func hasOwner(name string, uid, gid int) check { return func(t *testing.T, r TestableReader) { id, err := lookup(r, name) if err != nil { t.Errorf("cannot find file %q: %v", name, err) return } attr, err := r.GetAttr(id) if err != nil { t.Errorf("cannot get attr of file %q: %v", name, err) return } if attr.UID != uid || attr.GID != gid { t.Errorf("unexpected owner of %q: (%d:%d) want (%d:%d)", name, attr.UID, attr.GID, uid, gid) return } } } func hasModTime(name string, modTime time.Time) check { return func(t *testing.T, r TestableReader) { id, err := lookup(r, name) if err != nil { t.Errorf("cannot find file %q: %v", name, err) return } attr, err := r.GetAttr(id) if err != nil { t.Errorf("cannot get attr of file %q: %v", name, err) return } attrModTime := attr.ModTime if attrModTime.Before(modTime) || attrModTime.After(modTime) { t.Errorf("unexpected time of %q: %v; want %v", name, attrModTime, modTime) return } } } func hasXattrs(name string, xattrs map[string]string) check { return func(t *testing.T, r TestableReader) { id, err := lookup(r, name) if err != nil { t.Errorf("cannot find file %q: %v", name, err) return } attr, err := r.GetAttr(id) if err != nil { t.Errorf("cannot get attr of file %q: %v", name, err) return } if len(attr.Xattrs) != len(xattrs) { t.Errorf("unexpected size of xattr of %q: %d want %d", name, len(attr.Xattrs), len(xattrs)) return } for k, v := range attr.Xattrs { if xattrs[k] != string(v) { t.Errorf("unexpected xattr of %q: %q=%q want %q=%q", name, k, string(v), k, xattrs[k]) } } } } func lookup(r TestableReader, name string) (uint32, error) { name = strings.TrimPrefix(path.Clean("/"+name), "/") if name == "" { return r.RootID(), nil } dir, base := filepath.Split(name) pid, err := lookup(r, dir) if err != nil { return 0, err } id, _, err := r.GetChild(pid, base) return id, err } stargz-snapshotter-0.12.0/nativeconverter/000077500000000000000000000000001426301527400206765ustar00rootroot00000000000000stargz-snapshotter-0.12.0/nativeconverter/estargz/000077500000000000000000000000001426301527400223555ustar00rootroot00000000000000stargz-snapshotter-0.12.0/nativeconverter/estargz/estargz.go000066400000000000000000000117001426301527400243620ustar00rootroot00000000000000/* 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 estargz import ( "context" "fmt" "io" "github.com/containerd/containerd/archive/compression" "github.com/containerd/containerd/content" "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/images" "github.com/containerd/containerd/images/converter" "github.com/containerd/containerd/images/converter/uncompress" "github.com/containerd/containerd/labels" "github.com/containerd/stargz-snapshotter/estargz" "github.com/containerd/stargz-snapshotter/util/ioutils" "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) // LayerConvertWithLayerAndCommonOptsFunc converts legacy tar.gz layers into eStargz tar.gz // layers. Media type is unchanged. Should be used in conjunction with WithDockerToOCI(). See // LayerConvertFunc for more details. The difference between this function and // LayerConvertFunc is that this allows to specify additional eStargz options per layer. func LayerConvertWithLayerAndCommonOptsFunc(opts map[digest.Digest][]estargz.Option, commonOpts ...estargz.Option) converter.ConvertFunc { if opts == nil { return LayerConvertFunc(commonOpts...) } return func(ctx context.Context, cs content.Store, desc ocispec.Descriptor) (*ocispec.Descriptor, error) { // TODO: enable to speciy option per layer "index" because it's possible that there are // two layers having same digest in an image (but this should be rare case) return LayerConvertFunc(append(commonOpts, opts[desc.Digest]...)...)(ctx, cs, desc) } } // LayerConvertFunc converts legacy tar.gz layers into eStargz tar.gz layers. // Media type is unchanged. // // Should be used in conjunction with WithDockerToOCI(). // // Otherwise "containerd.io/snapshot/stargz/toc.digest" annotation will be lost, // because the Docker media type does not support layer annotations. func LayerConvertFunc(opts ...estargz.Option) converter.ConvertFunc { return func(ctx context.Context, cs content.Store, desc ocispec.Descriptor) (*ocispec.Descriptor, error) { if !images.IsLayerType(desc.MediaType) { // No conversion. No need to return an error here. return nil, nil } info, err := cs.Info(ctx, desc.Digest) if err != nil { return nil, err } labelz := info.Labels if labelz == nil { labelz = make(map[string]string) } ra, err := cs.ReaderAt(ctx, desc) if err != nil { return nil, err } defer ra.Close() sr := io.NewSectionReader(ra, 0, desc.Size) blob, err := estargz.Build(sr, append(opts, estargz.WithContext(ctx))...) if err != nil { return nil, err } defer blob.Close() ref := fmt.Sprintf("convert-estargz-from-%s", desc.Digest) w, err := content.OpenWriter(ctx, cs, content.WithRef(ref)) if err != nil { return nil, err } defer w.Close() // Reset the writing position // Old writer possibly remains without aborted // (e.g. conversion interrupted by a signal) if err := w.Truncate(0); err != nil { return nil, err } // Copy and count the contents pr, pw := io.Pipe() c := new(ioutils.CountWriter) doneCount := make(chan struct{}) go func() { defer close(doneCount) defer pr.Close() decompressR, err := compression.DecompressStream(pr) if err != nil { pr.CloseWithError(err) return } defer decompressR.Close() if _, err := io.Copy(c, decompressR); err != nil { pr.CloseWithError(err) return } }() n, err := io.Copy(w, io.TeeReader(blob, pw)) if err != nil { return nil, err } if err := blob.Close(); err != nil { return nil, err } if err := pw.Close(); err != nil { return nil, err } <-doneCount // update diffID label labelz[labels.LabelUncompressed] = blob.DiffID().String() if err = w.Commit(ctx, n, "", content.WithLabels(labelz)); err != nil && !errdefs.IsAlreadyExists(err) { return nil, err } if err := w.Close(); err != nil { return nil, err } newDesc := desc if uncompress.IsUncompressedType(newDesc.MediaType) { if images.IsDockerType(newDesc.MediaType) { newDesc.MediaType += ".gzip" } else { newDesc.MediaType += "+gzip" } } newDesc.Digest = w.Digest() newDesc.Size = n if newDesc.Annotations == nil { newDesc.Annotations = make(map[string]string, 1) } newDesc.Annotations[estargz.TOCJSONDigestAnnotation] = blob.TOCDigest().String() newDesc.Annotations[estargz.StoreUncompressedSizeAnnotation] = fmt.Sprintf("%d", c.Size()) return &newDesc, nil } } stargz-snapshotter-0.12.0/nativeconverter/estargz/estargz_test.go000066400000000000000000000040461426301527400254260ustar00rootroot00000000000000/* 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 estargz import ( "context" "testing" "github.com/containerd/containerd/images" "github.com/containerd/containerd/images/converter" "github.com/containerd/containerd/platforms" "github.com/containerd/stargz-snapshotter/estargz" "github.com/containerd/stargz-snapshotter/util/testutil" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) // TestLayerConvertFunc tests eStargz conversion. // TestLayerConvertFunc is a pure unit test that does not need the daemon to be running. func TestLayerConvertFunc(t *testing.T) { ctx := context.Background() desc, cs, err := testutil.EnsureHello(ctx) if err != nil { t.Fatal(err) } lcf := LayerConvertFunc(estargz.WithPrioritizedFiles([]string{"hello"})) docker2oci := true platformMC := platforms.DefaultStrict() cf := converter.DefaultIndexConvertFunc(lcf, docker2oci, platformMC) newDesc, err := cf(ctx, cs, *desc) if err != nil { t.Fatal(err) } var tocDigests []string handler := func(hCtx context.Context, hDesc ocispec.Descriptor) ([]ocispec.Descriptor, error) { if hDesc.Annotations != nil { if x, ok := hDesc.Annotations[estargz.TOCJSONDigestAnnotation]; ok && len(x) > 0 { tocDigests = append(tocDigests, x) } } return nil, nil } handlers := images.Handlers( images.ChildrenHandler(cs), images.HandlerFunc(handler), ) if err := images.Walk(ctx, handlers, *newDesc); err != nil { t.Fatal(err) } if len(tocDigests) == 0 { t.Fatal("no eStargz layer was created") } } stargz-snapshotter-0.12.0/nativeconverter/nativeconverter.go000066400000000000000000000012661426301527400244500ustar00rootroot00000000000000/* 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 nativeconverter requires this empty file to pass golangci-lint package nativeconverter stargz-snapshotter-0.12.0/nativeconverter/zstdchunked/000077500000000000000000000000001426301527400232245ustar00rootroot00000000000000stargz-snapshotter-0.12.0/nativeconverter/zstdchunked/zstdchunked.go000066400000000000000000000157001426301527400261040ustar00rootroot00000000000000/* 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 zstdchunked import ( "context" "fmt" "io" "github.com/containerd/containerd/archive/compression" "github.com/containerd/containerd/content" "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/images" "github.com/containerd/containerd/images/converter" "github.com/containerd/containerd/images/converter/uncompress" "github.com/containerd/containerd/labels" "github.com/containerd/stargz-snapshotter/estargz" "github.com/containerd/stargz-snapshotter/estargz/zstdchunked" "github.com/containerd/stargz-snapshotter/util/ioutils" "github.com/klauspost/compress/zstd" "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/sirupsen/logrus" ) type zstdCompression struct { *zstdchunked.Decompressor *zstdchunked.Compressor } // LayerConvertWithLayerOptsFunc converts legacy tar.gz layers into zstd:chunked layers. // // This changes Docker MediaType to OCI MediaType so this should be used in // conjunction with WithDockerToOCI(). // See LayerConvertFunc for more details. The difference between this function and // LayerConvertFunc is that this allows to specify additional eStargz options per layer. func LayerConvertWithLayerOptsFunc(opts map[digest.Digest][]estargz.Option) converter.ConvertFunc { if opts == nil { return LayerConvertFunc() } return func(ctx context.Context, cs content.Store, desc ocispec.Descriptor) (*ocispec.Descriptor, error) { // TODO: enable to speciy option per layer "index" because it's possible that there are // two layers having same digest in an image (but this should be rare case) return LayerConvertFunc(opts[desc.Digest]...)(ctx, cs, desc) } } // LayerConvertFunc converts legacy tar.gz layers into zstd:chunked layers. // // This changes Docker MediaType to OCI MediaType so this should be used in // conjunction with WithDockerToOCI(). // // Otherwise "io.containers.zstd-chunked.manifest-checksum" annotation will be lost, // because the Docker media type does not support layer annotations. func LayerConvertFunc(opts ...estargz.Option) converter.ConvertFunc { return func(ctx context.Context, cs content.Store, desc ocispec.Descriptor) (*ocispec.Descriptor, error) { if !images.IsLayerType(desc.MediaType) { // No conversion. No need to return an error here. return nil, nil } uncompressedDesc := &desc // We need to uncompress the archive first if !uncompress.IsUncompressedType(desc.MediaType) { var err error uncompressedDesc, err = uncompress.LayerConvertFunc(ctx, cs, desc) if err != nil { return nil, err } if uncompressedDesc == nil { return nil, fmt.Errorf("unexpectedly got the same blob after compression (%s, %q)", desc.Digest, desc.MediaType) } 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) if err != nil { return nil, err } labelz := info.Labels if labelz == nil { labelz = make(map[string]string) } uncompressedReaderAt, err := cs.ReaderAt(ctx, *uncompressedDesc) if err != nil { return nil, err } defer uncompressedReaderAt.Close() uncompressedSR := io.NewSectionReader(uncompressedReaderAt, 0, uncompressedDesc.Size) metadata := make(map[string]string) opts = append(opts, estargz.WithCompression(&zstdCompression{ new(zstdchunked.Decompressor), &zstdchunked.Compressor{ CompressionLevel: zstd.SpeedDefault, Metadata: metadata, }, })) blob, err := estargz.Build(uncompressedSR, append(opts, estargz.WithContext(ctx))...) if err != nil { return nil, err } defer blob.Close() ref := fmt.Sprintf("convert-zstdchunked-from-%s", desc.Digest) w, err := cs.Writer(ctx, content.WithRef(ref)) if err != nil { return nil, err } defer w.Close() // Reset the writing position // Old writer possibly remains without aborted // (e.g. conversion interrupted by a signal) if err := w.Truncate(0); err != nil { return nil, err } // Copy and count the contents pr, pw := io.Pipe() c := new(ioutils.CountWriter) doneCount := make(chan struct{}) go func() { defer close(doneCount) defer pr.Close() decompressR, err := compression.DecompressStream(pr) if err != nil { pr.CloseWithError(err) return } defer decompressR.Close() if _, err := io.Copy(c, decompressR); err != nil { pr.CloseWithError(err) return } }() n, err := io.Copy(w, io.TeeReader(blob, pw)) if err != nil { return nil, err } if err := blob.Close(); err != nil { return nil, err } // update diffID label labelz[labels.LabelUncompressed] = blob.DiffID().String() if err = w.Commit(ctx, n, "", content.WithLabels(labelz)); err != nil && !errdefs.IsAlreadyExists(err) { return nil, err } if err := w.Close(); err != nil { return nil, err } newDesc := desc newDesc.MediaType, err = convertMediaTypeToZstd(newDesc.MediaType) if err != nil { return nil, err } newDesc.Digest = w.Digest() newDesc.Size = n if newDesc.Annotations == nil { newDesc.Annotations = make(map[string]string, 1) } tocDgst := blob.TOCDigest().String() newDesc.Annotations[estargz.TOCJSONDigestAnnotation] = tocDgst newDesc.Annotations[estargz.StoreUncompressedSizeAnnotation] = fmt.Sprintf("%d", c.Size()) if p, ok := metadata[zstdchunked.ManifestChecksumAnnotation]; ok { newDesc.Annotations[zstdchunked.ManifestChecksumAnnotation] = p } if p, ok := metadata[zstdchunked.ManifestPositionAnnotation]; ok { newDesc.Annotations[zstdchunked.ManifestPositionAnnotation] = p } return &newDesc, nil } } // NOTE: this converts docker mediatype to OCI mediatype func convertMediaTypeToZstd(mt string) (string, error) { ociMediaType := converter.ConvertDockerMediaTypeToOCI(mt) switch ociMediaType { case ocispec.MediaTypeImageLayer, ocispec.MediaTypeImageLayerGzip, ocispec.MediaTypeImageLayerZstd: return ocispec.MediaTypeImageLayerZstd, nil case ocispec.MediaTypeImageLayerNonDistributable, ocispec.MediaTypeImageLayerNonDistributableGzip, ocispec.MediaTypeImageLayerNonDistributableZstd: return ocispec.MediaTypeImageLayerNonDistributableZstd, nil default: return "", fmt.Errorf("unknown mediatype %q", mt) } } stargz-snapshotter-0.12.0/nativeconverter/zstdchunked/zstdchunked_test.go000066400000000000000000000053661426301527400271520ustar00rootroot00000000000000/* 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 zstdchunked import ( "context" "testing" "runtime/debug" "github.com/containerd/containerd/images" "github.com/containerd/containerd/images/converter" "github.com/containerd/containerd/platforms" "github.com/containerd/stargz-snapshotter/estargz" "github.com/containerd/stargz-snapshotter/estargz/zstdchunked" "github.com/containerd/stargz-snapshotter/util/testutil" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) // TestLayerConvertFunc tests zstd:chunked conversion. // TestLayerConvertFunc is a pure unit test that does not need the daemon to be running. func TestLayerConvertFunc(t *testing.T) { ctx := context.Background() desc, cs, err := testutil.EnsureHello(ctx) if err != nil { t.Fatal(err) } lcf := LayerConvertFunc(estargz.WithPrioritizedFiles([]string{"hello"})) docker2oci := true platformMC := platforms.DefaultStrict() cf := converter.DefaultIndexConvertFunc(lcf, docker2oci, platformMC) newDesc, err := cf(ctx, cs, *desc) if err != nil { t.Log(string(debug.Stack())) t.Fatal(err) } metadata := make(map[string]string) mt := make(map[string]struct{}) handler := func(hCtx context.Context, hDesc ocispec.Descriptor) ([]ocispec.Descriptor, error) { mt[hDesc.MediaType] = struct{}{} for k, v := range hDesc.Annotations { if k == estargz.TOCJSONDigestAnnotation || k == zstdchunked.ManifestChecksumAnnotation || k == zstdchunked.ManifestPositionAnnotation { metadata[k] = v } } return nil, nil } handlers := images.Handlers( images.ChildrenHandler(cs), images.HandlerFunc(handler), ) if err := images.Walk(ctx, handlers, *newDesc); err != nil { t.Fatal(err) } if _, ok := mt[ocispec.MediaTypeImageLayerZstd]; !ok { t.Errorf("mediatype %q is not created", ocispec.MediaTypeImageLayerZstd) } if _, ok := metadata[estargz.TOCJSONDigestAnnotation]; !ok { t.Errorf("%q is not set", estargz.TOCJSONDigestAnnotation) } if _, ok := metadata[zstdchunked.ManifestChecksumAnnotation]; !ok { t.Errorf("%q is not set", zstdchunked.ManifestChecksumAnnotation) } if _, ok := metadata[zstdchunked.ManifestPositionAnnotation]; !ok { t.Errorf("%q is not set", zstdchunked.ManifestPositionAnnotation) } } stargz-snapshotter-0.12.0/recorder/000077500000000000000000000000001426301527400172655ustar00rootroot00000000000000stargz-snapshotter-0.12.0/recorder/recorder.go000066400000000000000000000022001426301527400214130ustar00rootroot00000000000000/* 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 recorder provides log recorder package recorder import ( "encoding/json" "io" "sync" ) type Entry struct { Path string `json:"path"` ManifestDigest string `json:"manifestDigest,omitempty"` LayerIndex *int `json:"layerIndex,omitempty"` } func New(w io.Writer) *Recorder { return &Recorder{ enc: json.NewEncoder(w), } } // Recorder is thread-safe. type Recorder struct { enc *json.Encoder mu sync.Mutex } func (ll *Recorder) Record(e *Entry) error { ll.mu.Lock() defer ll.mu.Unlock() return ll.enc.Encode(e) } stargz-snapshotter-0.12.0/script/000077500000000000000000000000001426301527400167645ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/benchmark/000077500000000000000000000000001426301527400207165ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/benchmark/config-containerd/000077500000000000000000000000001426301527400243075ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/benchmark/config-containerd/etc/000077500000000000000000000000001426301527400250625ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/benchmark/config-containerd/etc/containerd-stargz-grpc/000077500000000000000000000000001426301527400314515ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/benchmark/config-containerd/etc/containerd-stargz-grpc/config.toml000066400000000000000000000002121426301527400336060ustar00rootroot00000000000000# the value of noprefetch will be replaced during benchmarking noprefetch = true debug_address = "/run/containerd-stargz-grpc/debug.sock" stargz-snapshotter-0.12.0/script/benchmark/config-containerd/etc/containerd/000077500000000000000000000000001426301527400272105ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/benchmark/config-containerd/etc/containerd/config.toml000066400000000000000000000002241426301527400313500ustar00rootroot00000000000000version = 2 [proxy_plugins] [proxy_plugins.stargz] type = "snapshot" address = "/run/containerd-stargz-grpc/containerd-stargz-grpc.sock" stargz-snapshotter-0.12.0/script/benchmark/config-podman/000077500000000000000000000000001426301527400234375ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/benchmark/config-podman/etc/000077500000000000000000000000001426301527400242125ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/benchmark/config-podman/etc/containers/000077500000000000000000000000001426301527400263575ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/benchmark/config-podman/etc/containers/policy.json000066400000000000000000000001341426301527400305470ustar00rootroot00000000000000{ "default": [ { "type": "insecureAcceptAnything" } ] } stargz-snapshotter-0.12.0/script/benchmark/config-podman/etc/stargz-store/000077500000000000000000000000001426301527400266565ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/benchmark/config-podman/etc/stargz-store/config.toml000066400000000000000000000001211426301527400310120ustar00rootroot00000000000000# the value of noprefetch will be replaced during benchmarking noprefetch = true stargz-snapshotter-0.12.0/script/benchmark/hello-bench/000077500000000000000000000000001426301527400230765ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/benchmark/hello-bench/prepare.sh000077500000000000000000000023521426301527400250750ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail CONTEXT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )/" REPO="${CONTEXT}../../../" MEASURING_SCRIPT="${REPO}/script/benchmark/hello-bench/src/hello.py" REBOOT_CONTAINERD_SCRIPT="${REPO}/script/benchmark/hello-bench/reboot_containerd.sh" if [ $# -lt 1 ] ; then echo "Specify benchmark target." echo "Ex) ${0} --all" echo "Ex) ${0} alpine busybox" exit 1 fi TARGET_REPOSITORY="${1}" TARGET_IMAGES=${@:2} DISABLE_ESTARGZ="true" "${REBOOT_CONTAINERD_SCRIPT}" "${MEASURING_SCRIPT}" --repository=${TARGET_REPOSITORY} --op=prepare ${TARGET_IMAGES} stargz-snapshotter-0.12.0/script/benchmark/hello-bench/reboot_containerd.sh000077500000000000000000000061661426301527400271460ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail CONTAINERD_ROOT=/var/lib/containerd/ CONTAINERD_CONFIG_DIR=/etc/containerd/ REMOTE_SNAPSHOTTER_SOCKET=/run/containerd-stargz-grpc/containerd-stargz-grpc.sock REMOTE_SNAPSHOTTER_ROOT=/var/lib/containerd-stargz-grpc/ REMOTE_SNAPSHOTTER_CONFIG_DIR=/etc/containerd-stargz-grpc/ RETRYNUM=30 RETRYINTERVAL=1 TIMEOUTSEC=180 function retry { local SUCCESS=false for i in $(seq ${RETRYNUM}) ; do if eval "timeout ${TIMEOUTSEC} ${@}" ; then SUCCESS=true break fi echo "Fail(${i}). Retrying..." sleep ${RETRYINTERVAL} done if [ "${SUCCESS}" == "true" ] ; then return 0 else return 1 fi } function kill_all { if [ "${1}" != "" ] ; then ps aux | grep "${1}" \ | grep -v "benchmark" \ | grep -v grep \ | grep -v "hello.py" \ | grep -v $(basename ${0}) \ | sed -E 's/ +/ /g' | cut -f 2 -d ' ' | xargs -I{} kill -9 {} || true fi } function cleanup { rm -rf "${CONTAINERD_ROOT}"* if [ -f "${REMOTE_SNAPSHOTTER_SOCKET}" ] ; then rm "${REMOTE_SNAPSHOTTER_SOCKET}" fi if [ -d "${REMOTE_SNAPSHOTTER_ROOT}snapshotter/snapshots/" ] ; then find "${REMOTE_SNAPSHOTTER_ROOT}snapshotter/snapshots/" \ -maxdepth 1 -mindepth 1 -type d -exec umount "{}/fs" \; fi rm -rf "${REMOTE_SNAPSHOTTER_ROOT}"* } echo "cleaning up the environment..." kill_all "containerd" kill_all "containerd-stargz-grpc" cleanup if [ "${DISABLE_PREFETCH:-}" == "true" ] ; then sed -i 's/noprefetch = .*/noprefetch = true/g' "${REMOTE_SNAPSHOTTER_CONFIG_DIR}config.toml" else sed -i 's/noprefetch = .*/noprefetch = false/g' "${REMOTE_SNAPSHOTTER_CONFIG_DIR}config.toml" fi if [ "${DISABLE_ESTARGZ:-}" == "true" ] ; then echo "DO NOT RUN remote snapshotter" else echo "running remote snaphsotter..." if [ "${LOG_FILE:-}" == "" ] ; then LOG_FILE=/dev/null fi containerd-stargz-grpc --log-level=debug \ --address="${REMOTE_SNAPSHOTTER_SOCKET}" \ --config="${REMOTE_SNAPSHOTTER_CONFIG_DIR}config.toml" \ 2>&1 | tee -a "${LOG_FILE}" & # Dump all log retry ls "${REMOTE_SNAPSHOTTER_SOCKET}" fi echo "running containerd..." containerd --config="${CONTAINERD_CONFIG_DIR}config.toml" & CTRCMD=ctr-remote if ! which "${CTRCMD}" ; then if ! which ctr ; then echo "ctr nor ctr-remote not found" exit 1 fi CTRCMD=ctr fi retry "${CTRCMD}" version stargz-snapshotter-0.12.0/script/benchmark/hello-bench/reboot_store.sh000077500000000000000000000065461426301527400261560ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail PODMAN_CONFIG_DIR=/etc/containers/ PODMAN_STORAGE_CONFIG_FILE="${PODMAN_CONFIG_DIR}storage.conf" REG_STORAGE_CONFIG_FILE="/etc/stargz-store/config.toml" REG_STORAGE_ROOT=/var/lib/stargz-store/ REG_STORAGE_DIR="${REG_STORAGE_ROOT}store/" REG_STORAGE_POOL_LINK="${REG_STORAGE_ROOT}store/pool" REG_STORAGE_MOUNTPOINT="${REG_STORAGE_DIR}" RETRYNUM=30 RETRYINTERVAL=1 TIMEOUTSEC=180 function retry { local SUCCESS=false for i in $(seq ${RETRYNUM}) ; do if eval "timeout ${TIMEOUTSEC} ${@}" ; then SUCCESS=true break fi echo "Fail(${i}). Retrying..." sleep ${RETRYINTERVAL} done if [ "${SUCCESS}" == "true" ] ; then return 0 else return 1 fi } function kill_all { if [ "${1}" != "" ] ; then ps aux | grep "${1}" \ | grep -v grep \ | grep -v "hello.py" \ | grep -v $(basename ${0}) \ | sed -E 's/ +/ /g' | cut -f 2 -d ' ' | xargs -I{} kill -9 {} || true fi } function cleanup { umount "${REG_STORAGE_MOUNTPOINT}" || true rm -rf "${REG_STORAGE_DIR}" || true if [ -d "${REG_STORAGE_ROOT}pool/" ] ; then for POOL in $(ls "${REG_STORAGE_ROOT}pool/") ; do umount "${REG_STORAGE_ROOT}pool/${POOL}" || true for MP in $(ls "${REG_STORAGE_ROOT}pool/${POOL}") ; do umount "${REG_STORAGE_ROOT}pool/${POOL}/${MP}" || true done done fi rm -rf "${REG_STORAGE_ROOT}"* rm "${PODMAN_STORAGE_CONFIG_FILE}" || true podman system reset -f } echo "cleaning up the environment..." kill_all "stargz-store" cleanup if [ "${DISABLE_PREFETCH:-}" == "true" ] ; then sed -i 's/noprefetch = .*/noprefetch = true/g' "${REG_STORAGE_CONFIG_FILE}" else sed -i 's/noprefetch = .*/noprefetch = false/g' "${REG_STORAGE_CONFIG_FILE}" fi mkdir -p "${PODMAN_CONFIG_DIR}" if [ "${DISABLE_ESTARGZ:-}" == "true" ] ; then echo "DO NOT RUN additional storage" cat < "${PODMAN_STORAGE_CONFIG_FILE}" [storage] driver = "overlay" graphroot = "/var/lib/containers/storage" runroot = "/run/containers/storage" EOF else echo "running remote snaphsotter..." if [ "${LOG_FILE:-}" == "" ] ; then LOG_FILE=/dev/null fi cat < "${PODMAN_STORAGE_CONFIG_FILE}" [storage] driver = "overlay" graphroot = "/var/lib/containers/storage" runroot = "/run/containers/storage" [storage.options] additionallayerstores = ["${REG_STORAGE_MOUNTPOINT}:ref"] EOF mkdir -p "${REG_STORAGE_MOUNTPOINT}" stargz-store --log-level=debug \ --config="${REG_STORAGE_CONFIG_FILE}" \ "${REG_STORAGE_MOUNTPOINT}" \ 2>&1 | tee -a "${LOG_FILE}" & # Dump all log retry ls "${REG_STORAGE_POOL_LINK}" > /dev/null fi stargz-snapshotter-0.12.0/script/benchmark/hello-bench/run.sh000077500000000000000000000114021426301527400242370ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail LEGACY_MODE="legacy" ESTARGZ_NOOPT_MODE="estargz-noopt" ESTARGZ_MODE="estargz" ZSTDCHUNKED_MODE="zstdchunked" CONTEXT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )/" REPO="${CONTEXT}../../../" # NOTE: The entire contents of containerd/stargz-snapshotter are located in # the testing container so utils.sh is visible from this script during runtime. # TODO: Refactor the code dependencies and pack them in the container without # expecting and relying on volumes. source "${REPO}/script/util/utils.sh" MEASURING_SCRIPT="${REPO}/script/benchmark/hello-bench/src/hello.py" BENCHMARKOUT_MARK_OUTPUT="BENCHMARK_OUTPUT: " if [ $# -lt 1 ] ; then echo "Specify benchmark target." echo "Ex) ${0} --all" echo "Ex) ${0} alpine busybox" exit 1 fi TARGET_REPOSITORY="${1}" TARGET_IMAGES=${@:2} NUM_OF_SAMPLES="${BENCHMARK_SAMPLES_NUM:-1}" REBOOT_SCRIPT= if [ "${BENCHMARK_RUNTIME_MODE}" == "containerd" ] ; then REBOOT_SCRIPT="${REPO}/script/benchmark/hello-bench/reboot_containerd.sh" elif [ "${BENCHMARK_RUNTIME_MODE}" == "podman" ] ; then REBOOT_SCRIPT="${REPO}/script/benchmark/hello-bench/reboot_store.sh" else echo "Unknown runtime: ${BENCHMARK_RUNTIME_MODE}" exit 1 fi TMP_LOG_FILE=$(mktemp) WORKLOADS_LIST=$(mktemp) function cleanup { local ORG_EXIT_CODE="${1}" rm "${TMP_LOG_FILE}" || true rm "${WORKLOADS_LIST}" exit "${ORG_EXIT_CODE}" } trap 'cleanup "$?"' EXIT SIGHUP SIGINT SIGQUIT SIGTERM function output { echo "${BENCHMARKOUT_MARK_OUTPUT}${1}" } function measure { local OPTION="${1}" local REPOSITORY="${2}" "${MEASURING_SCRIPT}" ${OPTION} --repository=${REPOSITORY} --op=run --runtime="${BENCHMARK_RUNTIME_MODE}" ${@:3} } if [[ -z "${BENCHMARK_PROFILE:-}" ]]; then echo "Run single experiment" additional_flags='--experiments=1' else echo "Run the experiment for ${BENCHMARK_PROFILE}s" additional_flags="--profile=$BENCHMARK_PROFILE" fi mkdir -p /tmp/hello-bench-output echo "=========" echo "SPEC LIST" echo "=========" uname -r cat /etc/os-release cat /proc/cpuinfo cat /proc/meminfo mount df echo "=========" echo "BENCHMARK" echo "=========" output "[" for SAMPLE_NO in $(seq ${NUM_OF_SAMPLES}) ; do echo -n "" > "${WORKLOADS_LIST}" # Randomize workloads for IMAGE in ${TARGET_IMAGES} ; do for MODE in ${LEGACY_MODE} ${ESTARGZ_NOOPT_MODE} ${ESTARGZ_MODE} ${ZSTDCHUNKED_MODE} ; do echo "${IMAGE},${MODE}" >> "${WORKLOADS_LIST}" done done sort -R -o "${WORKLOADS_LIST}" "${WORKLOADS_LIST}" echo "Workloads of iteration [${SAMPLE_NO}]" cat "${WORKLOADS_LIST}" # Run the workloads for THEWL in $(cat "${WORKLOADS_LIST}") ; do echo "The workload is ${THEWL}" IMAGE=$(echo "${THEWL}" | cut -d ',' -f 1) MODE=$(echo "${THEWL}" | cut -d ',' -f 2) echo "===== Measuring [${SAMPLE_NO}] ${IMAGE} (${MODE}) =====" if [ "${MODE}" == "${LEGACY_MODE}" ] ; then # disable lazy pulling DISABLE_ESTARGZ="true" "${REBOOT_SCRIPT}" measure "--mode=legacy" ${TARGET_REPOSITORY} ${IMAGE} "$additional_flags" fi if [ "${MODE}" == "${ESTARGZ_NOOPT_MODE}" ] ; then echo -n "" > "${TMP_LOG_FILE}" # disable prefetch DISABLE_PREFETCH="true" LOG_FILE="${TMP_LOG_FILE}" "${REBOOT_SCRIPT}" measure "--mode=estargz-noopt" ${TARGET_REPOSITORY} ${IMAGE} "$additional_flags" check_remote_snapshots "${TMP_LOG_FILE}" fi if [ "${MODE}" == "${ESTARGZ_MODE}" ] ; then echo -n "" > "${TMP_LOG_FILE}" LOG_FILE="${TMP_LOG_FILE}" "${REBOOT_SCRIPT}" measure "--mode=estargz" ${TARGET_REPOSITORY} ${IMAGE} "$additional_flags" check_remote_snapshots "${TMP_LOG_FILE}" fi if [ "${MODE}" == "${ZSTDCHUNKED_MODE}" ] ; then echo -n "" > "${TMP_LOG_FILE}" LOG_FILE="${TMP_LOG_FILE}" "${REBOOT_SCRIPT}" measure "--mode=zstdchunked" ${TARGET_REPOSITORY} ${IMAGE} "$additional_flags" check_remote_snapshots "${TMP_LOG_FILE}" fi done done output "]" stargz-snapshotter-0.12.0/script/benchmark/hello-bench/src/000077500000000000000000000000001426301527400236655ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/benchmark/hello-bench/src/elasticsearch/000077500000000000000000000000001426301527400264775ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/benchmark/hello-bench/src/elasticsearch/elasticsearch.yml000066400000000000000000000000341426301527400320310ustar00rootroot00000000000000discovery.type: single-node stargz-snapshotter-0.12.0/script/benchmark/hello-bench/src/gcc/000077500000000000000000000000001426301527400244215ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/benchmark/hello-bench/src/gcc/main.c000066400000000000000000000001161426301527400255070ustar00rootroot00000000000000#include int main(int argc, char *argv[]) { printf("hello\n"); } stargz-snapshotter-0.12.0/script/benchmark/hello-bench/src/go/000077500000000000000000000000001426301527400242725ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/benchmark/hello-bench/src/go/main.go000066400000000000000000000012261426301527400255460ustar00rootroot00000000000000/* 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" func main() { fmt.Println("hello") } stargz-snapshotter-0.12.0/script/benchmark/hello-bench/src/hello.py000077500000000000000000000607361426301527400253610ustar00rootroot00000000000000#!/usr/bin/env python # 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. # The MIT License (MIT) # # Copyright (c) 2015 Tintri # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import os, sys, subprocess, select, random, urllib2, time, json, tempfile, shutil, itertools TMP_DIR = tempfile.mkdtemp() LEGACY_MODE = "legacy" ESTARGZ_NOOPT_MODE = "estargz-noopt" ESTARGZ_MODE = "estargz" ZSTDCHUNKED_MODE = "zstdchunked" DEFAULT_OPTIMIZER = "ctr-remote image optimize --oci" DEFAULT_PULLER = "nerdctl image pull" DEFAULT_PUSHER = "nerdctl image push" DEFAULT_TAGGER = "nerdctl image tag" BENCHMARKOUT_MARK = "BENCHMARK_OUTPUT: " CTR="ctr" PODMAN="podman" def exit(status): # cleanup shutil.rmtree(TMP_DIR) sys.exit(status) def tmp_path(): tmp_path.nxt += 1 return os.path.join(TMP_DIR, str(tmp_path.nxt)) tmp_path.nxt = 0 def tmp_copy(src): dst = tmp_path() if os.path.isdir(src): shutil.copytree(src, dst) else: shutil.copyfile(src, dst) return dst def genargs_for_optimization(arg): if arg == None or arg == "": return "" else: return '-args \'["%s"]\'' % arg.replace('"', '\\\"').replace('\'', '\'"\'"\'') def format_repo(mode, repository, name): if mode == ESTARGZ_MODE: return "%s/%s-esgz" % (repository, name) elif mode == ESTARGZ_NOOPT_MODE: return "%s/%s-esgz-noopt" % (repository, name) elif mode == ZSTDCHUNKED_MODE: return "%s/%s-zstdchunked" % (repository, name) else: return "%s/%s-org" % (repository, name) class RunArgs: def __init__(self, env={}, arg='', stdin='', stdin_sh='sh', waitline='', mount=[]): self.env = env self.arg = arg self.stdin = stdin self.stdin_sh = stdin_sh self.waitline = waitline self.mount = mount class Bench: def __init__(self, name, category='other'): self.name = name self.category = category def __str__(self): return json.dumps(self.__dict__) class BenchRunner: ECHO_HELLO = set(['alpine:3.15.3', 'nixos/nix:2.3.12', 'fedora:35',]) CMD_ARG_WAIT = {'rethinkdb:2.4.1': RunArgs(waitline='Server ready'), 'glassfish:4.1-jdk8': RunArgs(waitline='Running GlassFish'), 'drupal:9.3.9': RunArgs(waitline='apache2 -D FOREGROUND'), 'jenkins:2.60.3': RunArgs(waitline='Jenkins is fully up and running'), 'redis:6.2.6': RunArgs(waitline='Ready to accept connections'), 'tomcat:10.1.0-jdk17-openjdk-bullseye': RunArgs(waitline='Server startup'), 'postgres:14.2': RunArgs(waitline='database system is ready to accept connections', env={'POSTGRES_PASSWORD': 'abc'}), 'mariadb:10.7.3': RunArgs(waitline='mysqld: ready for connections', env={'MYSQL_ROOT_PASSWORD': 'abc'}), 'wordpress:5.9.2': RunArgs(waitline='apache2 -D FOREGROUND'), 'php:8.1.4-apache-bullseye': RunArgs(waitline='apache2 -D FOREGROUND'), 'rabbitmq:3.9.14': RunArgs(waitline='Server startup complete'), 'elasticsearch:8.1.1': RunArgs(waitline='"started"', mount=[('elasticsearch/elasticsearch.yml', '/usr/share/elasticsearch/config/elasticsearch.yml')]), } CMD_STDIN = {'php:8.1.4': RunArgs(stdin='php -r "echo \\\"hello\\n\\\";"; exit\n'), 'gcc:11.2.0': RunArgs(stdin='cd /src; gcc main.c; ./a.out; exit\n', mount=[('gcc', '/src')]), 'golang:1.18': RunArgs(stdin='cd /go/src; go run main.go; exit\n', mount=[('go', '/go/src')]), 'jruby:9.3.4': RunArgs(stdin='jruby -e "puts \\\"hello\\\""; exit\n'), 'r-base:4.1.3': RunArgs(stdin='sprintf("hello")\nq()\n', stdin_sh='R --no-save'), } CMD_ARG = {'perl:5.34.1': RunArgs(arg='perl -e \'print("hello\\n")\''), 'python:3.10': RunArgs(arg='python -c \'print("hello")\''), 'pypy:3.9': RunArgs(arg='pypy3 -c \'print("hello")\''), 'node:17.8.0': RunArgs(arg='node -e \'console.log("hello")\''), } # complete listing ALL = dict([(b.name, b) for b in [Bench('alpine:3.15.3', 'distro'), Bench('fedora:35', 'distro'), Bench('rethinkdb:2.4.1', 'database'), Bench('postgres:14.2', 'database'), Bench('redis:6.2.6', 'database'), Bench('mariadb:10.7.3', 'database'), Bench('python:3.10', 'language'), Bench('golang:1.18', 'language'), Bench('gcc:11.2.0', 'language'), Bench('jruby:9.3.4', 'language'), Bench('perl:5.34.1', 'language'), Bench('php:8.1.4', 'language'), Bench('pypy:3.9', 'language'), Bench('r-base:4.1.3', 'language'), Bench('drupal:9.3.9'), Bench('jenkins:2.60.3'), Bench('node:17.8.0'), Bench('tomcat:10.1.0-jdk17-openjdk-bullseye', 'web-server'), Bench('wordpress:5.9.2', 'web-server'), Bench('php:8.1.4-apache-bullseye', 'web-server'), Bench('rabbitmq:3.9.14'), Bench('elasticsearch:8.1.1'), # needs "--srcrepository=docker.io" Bench('nixos/nix:2.3.12'), ]]) def __init__(self, repository='docker.io/library', srcrepository='docker.io/library', mode=LEGACY_MODE, optimizer=DEFAULT_OPTIMIZER, puller=DEFAULT_PULLER, pusher=DEFAULT_PUSHER, runtime="containerd", profile=0): if runtime == "containerd": self.controller = ContainerdController(mode == ESTARGZ_NOOPT_MODE or mode == ESTARGZ_MODE or mode == ZSTDCHUNKED_MODE) elif runtime == "podman": self.controller = PodmanController() else: print 'Unknown runtime mode: '+runtime exit(1) self.repository = repository self.srcrepository = srcrepository self.mode = mode self.optimizer = optimizer self.puller = puller self.pusher = pusher profile = int(profile) if profile > 0: self.controller.start_profile(profile) def cleanup(self, cid, bench): self.controller.cleanup(cid, self.fully_qualify(bench.name)) def fully_qualify(self, repo): return format_repo(self.mode, self.repository, repo) def run_task(self, cid): cmd = self.controller.task_start_cmd(cid) startrun = time.time() rc = os.system(cmd) runtime = time.time() - startrun assert(rc == 0) return runtime def run_echo_hello(self, image, cid): cmd = self.controller.create_echo_hello_cmd(image, cid) startcreate = time.time() rc = os.system(cmd) createtime = time.time() - startcreate assert(rc == 0) return createtime, self.run_task(cid) def run_cmd_arg(self, image, cid, runargs): assert(len(runargs.mount) == 0) cmd = self.controller.create_cmd_arg_cmd(image, cid, runargs) startcreate = time.time() rc = os.system(cmd) createtime = time.time() - startcreate assert(rc == 0) return createtime, self.run_task(cid) def run_cmd_arg_wait(self, image, cid, runargs): cmd = self.controller.create_cmd_arg_wait_cmd(image, cid, runargs) startcreate = time.time() rc = os.system(cmd) createtime = time.time() - startcreate assert(rc == 0) cmd = self.controller.task_start_cmd(cid) runtime = 0 startrun = time.time() # line buffer output p = subprocess.Popen(cmd, shell=True, bufsize=1, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) while True: l = p.stdout.readline() if l == '': continue print 'out: ' + l.strip() # are we done? if l.find(runargs.waitline) >= 0: runtime = time.time() - startrun # cleanup print 'DONE' cmd = self.controller.task_kill_cmd(cid) rc = os.system(cmd) assert(rc == 0) break p.wait() return createtime, runtime def run_cmd_stdin(self, image, cid, runargs): cmd = self.controller.create_cmd_stdin_cmd(image, cid, runargs) startcreate = time.time() rc = os.system(cmd) createtime = time.time() - startcreate assert(rc == 0) cmd = self.controller.task_start_cmd(cid) startrun = time.time() p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) print runargs.stdin out, _ = p.communicate(runargs.stdin) runtime = time.time() - startrun print out assert(p.returncode == 0) return createtime, runtime def run(self, bench, cid): name = bench.name image = self.fully_qualify(bench.name) print "Pulling the image..." pullcmd = self.controller.pull_cmd(image) startpull = time.time() rc = os.system(pullcmd) assert(rc == 0) pulltime = time.time() - startpull runtime = 0 createtime = 0 if name in BenchRunner.ECHO_HELLO: createtime, runtime = self.run_echo_hello(image=image, cid=cid) elif name in BenchRunner.CMD_ARG: createtime, runtime = self.run_cmd_arg(image=image, cid=cid, runargs=BenchRunner.CMD_ARG[name]) elif name in BenchRunner.CMD_ARG_WAIT: createtime, runtime = self.run_cmd_arg_wait(image=image, cid=cid, runargs=BenchRunner.CMD_ARG_WAIT[name]) elif name in BenchRunner.CMD_STDIN: createtime, runtime = self.run_cmd_stdin(image=image, cid=cid, runargs=BenchRunner.CMD_STDIN[name]) else: print 'Unknown bench: '+name exit(1) return pulltime, createtime, runtime def convert_echo_hello(self, src, dest, option): period=10 cmd = ('%s %s -cni -period %s -entrypoint \'["/bin/sh", "-c"]\' -args \'["echo hello"]\' %s %s' % (self.optimizer, option, period, src, dest)) print cmd rc = os.system(cmd) assert(rc == 0) def convert_cmd_arg(self, src, dest, runargs, option): period = 30 assert(len(runargs.mount) == 0) entry = "" if runargs.arg != "": # FIXME: this is naive... entry = '-entrypoint \'["/bin/sh", "-c"]\'' cmd = ('%s %s -cni -period %s %s %s %s %s' % (self.optimizer, option, period, entry, genargs_for_optimization(runargs.arg), src, dest)) print cmd rc = os.system(cmd) assert(rc == 0) def convert_cmd_arg_wait(self, src, dest, runargs, option): mounts = '' for a,b in runargs.mount: a = os.path.join(os.path.dirname(os.path.abspath(__file__)), a) a = tmp_copy(a) mounts += '--mount type=bind,src=%s,dst=%s,options=rbind ' % (a,b) period = 90 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' % (self.optimizer, option, period, mounts, env, genargs_for_optimization(runargs.arg), src, dest)) print cmd rc = os.system(cmd) assert(rc == 0) def convert_cmd_stdin(self, src, dest, runargs, option): mounts = '' for a,b in runargs.mount: a = os.path.join(os.path.dirname(os.path.abspath(__file__)), a) a = tmp_copy(a) mounts += '--mount type=bind,src=%s,dst=%s,options=rbind ' % (a,b) period = 60 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)) print cmd p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE) print runargs.stdin out,_ = p.communicate(runargs.stdin) print out p.wait() assert(p.returncode == 0) def push_img(self, dest): cmd = '%s %s' % (self.pusher, dest) print cmd rc = os.system(cmd) assert(rc == 0) def pull_img(self, src): cmd = '%s %s' % (self.puller, src) print cmd rc = os.system(cmd) assert(rc == 0) def copy_img(self, src, dest): cmd = 'crane copy %s %s' % (src, dest) print cmd rc = os.system(cmd) assert(rc == 0) def convert_and_push_img(self, src, dest): self.pull_img(src) cmd = '%s --no-optimize %s %s' % (self.optimizer, src, dest) print cmd rc = os.system(cmd) assert(rc == 0) self.push_img(dest) def optimize_img(self, name, src, dest, option): self.pull_img(src) if name in BenchRunner.ECHO_HELLO: self.convert_echo_hello(src=src, dest=dest, option=option) elif name in BenchRunner.CMD_ARG: self.convert_cmd_arg(src=src, dest=dest, runargs=BenchRunner.CMD_ARG[name], option=option) elif name in BenchRunner.CMD_ARG_WAIT: self.convert_cmd_arg_wait(src=src, dest=dest, runargs=BenchRunner.CMD_ARG_WAIT[name], option=option) elif name in BenchRunner.CMD_STDIN: self.convert_cmd_stdin(src=src, dest=dest, runargs=BenchRunner.CMD_STDIN[name], option=option) else: print 'Unknown bench: '+name exit(1) self.push_img(dest) def prepare(self, bench): name = bench.name src = '%s/%s' % (self.srcrepository, name) self.copy_img(src=src, dest=format_repo(LEGACY_MODE, self.repository, name)) self.convert_and_push_img(src=src, dest=format_repo(ESTARGZ_NOOPT_MODE, self.repository, name)) self.optimize_img(name=name, src=src, dest=format_repo(ESTARGZ_MODE, self.repository, name), option="") self.optimize_img(name=name, src=src, dest=format_repo(ZSTDCHUNKED_MODE, self.repository, name), option="--zstdchunked") def operation(self, op, bench, cid): if op == 'run': return self.run(bench, cid) elif op == 'prepare': self.prepare(bench) return 0, 0, 0 else: print 'Unknown operation: '+op exit(1) class ContainerdController: def __init__(self, is_lazypull=False): self.is_lazypull = is_lazypull def pull_cmd(self, image): base_cmd = "%s i pull" % CTR if self.is_lazypull: base_cmd = "ctr-remote i rpull" cmd = '%s %s' % (base_cmd, image) print cmd return cmd def create_echo_hello_cmd(self, image, cid): snapshotter_opt = "" if self.is_lazypull: snapshotter_opt = "--snapshotter=stargz" cmd = '%s c create --net-host %s -- %s %s echo hello' % (CTR, snapshotter_opt, image, cid) print cmd return cmd def create_cmd_arg_cmd(self, image, cid, runargs): snapshotter_opt = "" if self.is_lazypull: snapshotter_opt = "--snapshotter=stargz" cmd = '%s c create --net-host %s ' % (CTR, snapshotter_opt) cmd += '-- %s %s ' % (image, cid) cmd += runargs.arg print cmd return cmd def create_cmd_arg_wait_cmd(self, image, cid, runargs): snapshotter_opt = "" if self.is_lazypull: snapshotter_opt = "--snapshotter=stargz" 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) for a,b in runargs.mount: a = os.path.join(os.path.dirname(os.path.abspath(__file__)), a) a = tmp_copy(a) cmd += '--mount type=bind,src=%s,dst=%s,options=rbind ' % (a,b) cmd += '-- %s %s %s' % (image, cid, runargs.arg) print cmd return cmd def create_cmd_stdin_cmd(self, image, cid, runargs): snapshotter_opt = "" if self.is_lazypull: snapshotter_opt = "--snapshotter=stargz" cmd = '%s c create --net-host %s ' % (CTR, snapshotter_opt) for a,b in runargs.mount: a = os.path.join(os.path.dirname(os.path.abspath(__file__)), a) a = tmp_copy(a) cmd += '--mount type=bind,src=%s,dst=%s,options=rbind ' % (a,b) cmd += '-- %s %s ' % (image, cid) if runargs.stdin_sh: cmd += runargs.stdin_sh # e.g., sh -c print cmd return cmd def task_start_cmd(self, cid): cmd = '%s t start %s' % (CTR, cid) print cmd return cmd def task_kill_cmd(self, cid): cmd = '%s t kill -s 9 %s' % (CTR, cid) print cmd return cmd def cleanup(self, name, image): print "Cleaning up environment..." cmd = '%s t kill -s 9 %s' % (CTR, name) print cmd rc = os.system(cmd) # sometimes containers already exit. we ignore the failure. cmd = '%s c rm %s' % (CTR, name) print cmd rc = os.system(cmd) assert(rc == 0) cmd = '%s image rm %s' % (CTR, image) print cmd rc = os.system(cmd) assert(rc == 0) def start_profile(self, seconds): out = open('/tmp/hello-bench-output/profile-%d.out' % (random.randint(1,1000000)), 'w') p = subprocess.Popen( [CTR, 'pprof', '-d', '/run/containerd-stargz-grpc/debug.sock', 'profile', '-s', '%ds' % (seconds)], stdout=out ) class PodmanController: def pull_cmd(self, image): cmd = '%s pull docker://%s' % (PODMAN, image) print cmd return cmd def create_echo_hello_cmd(self, image, cid): cmd = '%s create --name %s docker://%s echo hello' % (PODMAN, cid, image) print cmd return cmd def create_cmd_arg_cmd(self, image, cid, runargs): cmd = '%s create --name %s docker://%s ' % (PODMAN, cid, image) cmd += runargs.arg print cmd return cmd def create_cmd_arg_wait_cmd(self, image, cid, runargs): env = ' '.join(['--env %s=%s' % (k,v) for k,v in runargs.env.iteritems()]) cmd = '%s create %s --name %s '% (PODMAN, env, cid) for a,b in runargs.mount: a = os.path.join(os.path.dirname(os.path.abspath(__file__)), a) a = tmp_copy(a) cmd += '--mount type=bind,src=%s,dst=%s ' % (a,b) cmd += ' docker://%s %s ' % (image, runargs.arg) print cmd return cmd def create_cmd_stdin_cmd(self, image, cid, runargs): cmd = '%s create -i ' % PODMAN for a,b in runargs.mount: a = os.path.join(os.path.dirname(os.path.abspath(__file__)), a) a = tmp_copy(a) cmd += '--mount type=bind,src=%s,dst=%s ' % (a,b) cmd += '--name %s docker://%s ' % (cid, image) if runargs.stdin_sh: cmd += runargs.stdin_sh # e.g., sh -c print cmd return cmd def task_start_cmd(self, cid): cmd = '%s start -a %s' % (PODMAN, cid) print cmd return cmd def task_kill_cmd(self, cid): cmd = '%s kill -s 9 %s' % (PODMAN, cid) print cmd return cmd def cleanup(self, name, image): print "Cleaning up environment..." cmd = '%s kill -s 9 %s' % (PODMAN, name) print cmd rc = os.system(cmd) # sometimes containers already exit. we ignore the failure. cmd = '%s rm %s' % (PODMAN, name) print cmd rc = os.system(cmd) assert(rc == 0) cmd = '%s image rm %s' % (PODMAN, image) print cmd rc = os.system(cmd) assert(rc == 0) cmd = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../reboot_store.sh') print cmd rc = os.system(cmd) assert(rc == 0) def main(): if len(sys.argv) == 1: print 'Usage: bench.py [OPTIONS] [BENCHMARKS]' print 'OPTIONS:' print '--repository=' print '--srcrepository=' print '--all' print '--list' print '--list-json' print '--experiments' print '--profile=' print '--runtime' print '--op=(prepare|run)' print '--mode=(%s|%s|%s)' % (LEGACY_MODE, ESTARGZ_NOOPT_MODE, ESTARGZ_MODE, ZSTDCHUNKED_MODE) exit(1) benches = [] kvargs = {} # parse args for arg in sys.argv[1:]: if arg.startswith('--'): parts = arg[2:].split('=') if len(parts) == 2: kvargs[parts[0]] = parts[1] elif parts[0] == 'all': benches.extend(BenchRunner.ALL.values()) elif parts[0] == 'list': template = '%-16s\t%-20s' print template % ('CATEGORY', 'NAME') for b in sorted(BenchRunner.ALL.values(), key=lambda b:(b.category, b.name)): print template % (b.category, b.name) elif parts[0] == 'list-json': print json.dumps([b.__dict__ for b in BenchRunner.ALL.values()]) else: benches.append(BenchRunner.ALL[arg]) op = kvargs.pop('op', 'run') trytimes = int(kvargs.pop('experiments', '1')) if not op == "run": trytimes = 1 experiments = range(trytimes) profile = int(kvargs.get('profile', 0)) if profile > 0: experiments = itertools.count(start=0) # run benchmarks runner = BenchRunner(**kvargs) for bench in benches: cid = '%s_bench_%d' % (bench.name.replace(':', '-').replace('/', '-'), random.randint(1,1000000)) elapsed_times = [] pull_times = [] create_times = [] run_times = [] for_start = time.time() for i in experiments: start = time.time() pulltime, createtime, runtime = runner.operation(op, bench, cid) elapsed = time.time() - start if op == "run": runner.cleanup(cid, bench) elapsed_times.append(elapsed) pull_times.append(pulltime) create_times.append(createtime) run_times.append(runtime) print 'ITERATION %s:' % i print 'elapsed %s' % elapsed print 'pull %s' % pulltime print 'create %s' % createtime print 'run %s' % runtime if profile > 0 and time.time() - for_start > profile: 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)} js = json.dumps(row) print '%s%s,' % (BENCHMARKOUT_MARK, js) sys.stdout.flush() if __name__ == '__main__': main() exit(0) stargz-snapshotter-0.12.0/script/benchmark/test.sh000077500000000000000000000135471426301527400222460ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail CONTEXT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )/" REPO="${CONTEXT}../../" # This project's Dockerfile doesn't work without BuildKit. export DOCKER_BUILDKIT=1 BENCHMARK_TARGETS="${BENCHMARK_TARGETS:-}" if [[ -z "$BENCHMARK_TARGETS" ]]; then echo "BENCHMARK_TARGETS must be specified." exit 1 fi BENCHMARKING_BASE_IMAGE_NAME="benchmark-image-base" BENCHMARKING_NODE_IMAGE_NAME="benchmark-image-test" BENCHMARKING_NODE=hello-bench BENCHMARKING_CONTAINER=hello-bench-container BENCHMARK_USER=${BENCHMARK_USER:-stargz-containers} export BENCHMARK_RUNTIME_MODE=${BENCHMARK_RUNTIME_MODE:-containerd} BENCHMARKING_TARGET_BASE_IMAGE= BENCHMARKING_TARGET_CONFIG_DIR= if [ "${BENCHMARK_RUNTIME_MODE}" == "containerd" ] ; then BENCHMARKING_TARGET_BASE_IMAGE=snapshotter-base BENCHMARKING_TARGET_CONFIG_DIR="${CONTEXT}/config-containerd" elif [ "${BENCHMARK_RUNTIME_MODE}" == "podman" ] ; then BENCHMARKING_TARGET_BASE_IMAGE=podman-base BENCHMARKING_TARGET_CONFIG_DIR="${CONTEXT}/config-podman" else echo "Unknown runtime: ${BENCHMARK_RUNTIME_MODE}" exit 1 fi if [ "${BENCHMARKING_NO_RECREATE:-}" != "true" ] ; then echo "Preparing node image..." docker build ${DOCKER_BUILD_ARGS:-} -t "${BENCHMARKING_BASE_IMAGE_NAME}" \ --target "${BENCHMARKING_TARGET_BASE_IMAGE}" "${REPO}" fi DOCKER_COMPOSE_YAML=$(mktemp) TMP_CONTEXT=$(mktemp -d) function cleanup { local ORG_EXIT_CODE="${1}" rm "${DOCKER_COMPOSE_YAML}" || true rm -rf "${TMP_CONTEXT}" || true exit "${ORG_EXIT_CODE}" } trap 'cleanup "$?"' EXIT SIGHUP SIGINT SIGQUIT SIGTERM cp -R "${BENCHMARKING_TARGET_CONFIG_DIR}" "${TMP_CONTEXT}/config" cat < "${TMP_CONTEXT}/Dockerfile" FROM ${BENCHMARKING_BASE_IMAGE_NAME} RUN apt-get update -y && \ apt-get --no-install-recommends install -y python jq && \ go install github.com/google/go-containerregistry/cmd/crane@v0.8.0 COPY ./config / ENV CONTAINERD_SNAPSHOTTER="" ENTRYPOINT [ "sleep", "infinity" ] EOF docker build -t "${BENCHMARKING_NODE_IMAGE_NAME}" ${DOCKER_BUILD_ARGS:-} "${TMP_CONTEXT}" echo "Preparing docker-compose.yml..." cat < "${DOCKER_COMPOSE_YAML}" version: "3.7" services: ${BENCHMARKING_NODE}: image: ${BENCHMARKING_NODE_IMAGE_NAME} container_name: ${BENCHMARKING_CONTAINER} privileged: true init: true working_dir: /go/src/github.com/containerd/stargz-snapshotter environment: - NO_PROXY=127.0.0.1,localhost - HTTP_PROXY=${HTTP_PROXY:-} - HTTPS_PROXY=${HTTPS_PROXY:-} - http_proxy=${http_proxy:-} - https_proxy=${https_proxy:-} tmpfs: - /tmp:exec,mode=777 volumes: - "${REPO}:/go/src/github.com/containerd/stargz-snapshotter:ro" - "/dev/fuse:/dev/fuse" - "containerd-data:/var/lib/containerd:delegated" - "containerd-stargz-grpc-data:/var/lib/containerd-stargz-grpc:delegated" - "containers-data:/var/lib/containers:delegated" - "additional-store-data:/var/lib/stargz-store:delegated" volumes: containerd-data: containerd-stargz-grpc-data: containers-data: additional-store-data: EOF echo "Preparing for benchmark..." OUTPUTDIR="${BENCHMARK_RESULT_DIR:-}" if [ "${OUTPUTDIR}" == "" ] ; then OUTPUTDIR=$(mktemp -d) fi echo "See output for >>> ${OUTPUTDIR}" LOG_DIR="${BENCHMARK_LOG_DIR:-}" if [ "${LOG_DIR}" == "" ] ; then LOG_DIR=$(mktemp -d) fi LOG_FILE="${LOG_DIR}/containerd-stargz-grpc-benchmark-$(date '+%Y%m%d%H%M%S')" touch "${LOG_FILE}" echo "Logging to >>> ${LOG_FILE} (will finally be stored under ${OUTPUTDIR})" echo "Benchmarking..." FAIL= if ! ( cd "${CONTEXT}" && \ docker-compose -f "${DOCKER_COMPOSE_YAML}" build ${DOCKER_BUILD_ARGS:-} \ "${BENCHMARKING_NODE}" && \ docker-compose -f "${DOCKER_COMPOSE_YAML}" up -d --force-recreate && \ docker exec \ -e BENCHMARK_RUNTIME_MODE -e BENCHMARK_SAMPLES_NUM -e BENCHMARK_PROFILE \ -i "${BENCHMARKING_CONTAINER}" \ script/benchmark/hello-bench/run.sh \ "${BENCHMARK_REGISTRY:-ghcr.io}/${BENCHMARK_USER}" \ ${BENCHMARK_TARGETS} &> "${LOG_FILE}" ) ; then echo "Failed to run benchmark." FAIL=true fi echo "Collecting data inside ${BENCHMARKING_CONTAINER}..." docker exec -i "${BENCHMARKING_CONTAINER}" \ tar zcf - -C /tmp/hello-bench-output . | tar zxvf - -C "$OUTPUTDIR" echo "Harvesting log ${LOG_FILE} -> ${OUTPUTDIR} ..." tar zcvf "${OUTPUTDIR}/result.log.tar.gz" "${LOG_FILE}" if [ "${FAIL}" != "true" ] ; then echo "Formatting output..." if ! ( tar zOxf "${OUTPUTDIR}/result.log.tar.gz" | "${CONTEXT}/tools/format.sh" > "${OUTPUTDIR}/result.json" && \ cat "${OUTPUTDIR}/result.json" | "${CONTEXT}/tools/plot.sh" "${OUTPUTDIR}" && \ cat "${OUTPUTDIR}/result.json" | "${CONTEXT}/tools/percentiles.sh" "${OUTPUTDIR}" && \ cat "${OUTPUTDIR}/result.json" | "${CONTEXT}/tools/table.sh" > "${OUTPUTDIR}/result.md" && \ cat "${OUTPUTDIR}/result.json" | "${CONTEXT}/tools/csv.sh" > "${OUTPUTDIR}/result.csv" ) ; then echo "Failed to formatting output (but you can try it manually from ${OUTPUTDIR})" FAIL=true fi fi echo "Cleaning up environment..." docker-compose -f "${DOCKER_COMPOSE_YAML}" down -v if [ "${FAIL}" == "true" ] ; then exit 1 fi exit 0 stargz-snapshotter-0.12.0/script/benchmark/tools/000077500000000000000000000000001426301527400220565ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/benchmark/tools/csv.sh000077500000000000000000000044261426301527400232160ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail JSON="$(mktemp)" cat > "${JSON}" CONTEXT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )/" source "${CONTEXT}/util.sh" MODES=( ${TARGET_MODES:-} ) if [ ${#MODES[@]} -eq 0 ] ; then MODES=("legacy" "estargz-noopt" "estargz" "zstdchunked") fi IMAGES=( ${TARGET_IMAGES:-} ) if [ ${#IMAGES[@]} -eq 0 ] ; then IMAGES=( $(cat "${JSON}" | jq -r '[ .[] | select(.mode=="'${MODES[0]}'").repo ] | unique[]') ) fi # Ensure we use the exact same number of samples among benchmarks MINSAMPLES= for IMGNAME in "${IMAGES[@]}" ; do for MODE in "${MODES[@]}"; do THEMIN=$(min_samples "${JSON}" "${IMGNAME}" "${MODE}") if [ "${MINSAMPLES}" == "" ] ; then MINSAMPLES="${THEMIN}" fi MINSAMPLES=$(echo "${MINSAMPLES} ${THEMIN}" | tr ' ' '\n' | sort -n | head -1) done done INDEX="image,operation" for MODE in "${MODES[@]}"; do INDEX="${INDEX},${MODE}" done echo "${INDEX}" for IMGNAME in "${IMAGES[@]}" ; do PULLLINE="${IMGNAME},pull" for MODE in "${MODES[@]}"; do PULLTIME=$(percentile "${JSON}" "${MINSAMPLES}" "${IMGNAME}" "${MODE}" "elapsed_pull") PULLLINE="${PULLLINE},${PULLTIME}" done echo "${PULLLINE}" CREATELINE="${IMGNAME},create" for MODE in "${MODES[@]}"; do CREATETIME=$(percentile "${JSON}" "${MINSAMPLES}" "${IMGNAME}" "${MODE}" "elapsed_create") CREATELINE="${CREATELINE},${CREATETIME}" done echo "${CREATELINE}" RUNLINE="${IMGNAME},run" for MODE in "${MODES[@]}"; do RUNTIME=$(percentile "${JSON}" "${MINSAMPLES}" "${IMGNAME}" "${MODE}" "elapsed_run") RUNLINE="${RUNLINE},${RUNTIME}" done echo "${RUNLINE}" done stargz-snapshotter-0.12.0/script/benchmark/tools/format.sh000077500000000000000000000014201426301527400237020ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail OUTPUT_MARK="BENCHMARK_OUTPUT: " grep "${OUTPUT_MARK}" | sed -e 's/^'"${OUTPUT_MARK}"'//g' | sed -e ':begin;$!N;s/,\n\(\(}\|]\),*\)/\n\1/g;tbegin;P;D' stargz-snapshotter-0.12.0/script/benchmark/tools/percentiles.sh000077500000000000000000000065641426301527400247450ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail JSON="$(mktemp)" cat > "${JSON}" CONTEXT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )/" source "${CONTEXT}/util.sh" if [ "${1}" == "" ] ; then echo "Specify directory for output" exit 1 fi DATADIR="${1}/" echo "output into: ${DATADIR}" MODES=( ${TARGET_MODES:-} ) if [ ${#MODES[@]} -eq 0 ] ; then MODES=("legacy" "estargz-noopt" "estargz" "zstdchunked") fi IMAGES=( ${TARGET_IMAGES:-} ) if [ ${#IMAGES[@]} -eq 0 ] ; then IMAGES=( $(cat "${JSON}" | jq -r '[ .[] | select(.mode=="'${MODES[0]}'").repo ] | unique[]') ) fi GRANULARITY="${BENCHMARK_PERCENTILES_GRANULARITY:-}" if [ "${GRANULARITY}" == "" ] ; then GRANULARITY="10" fi # Ensure we use the exact same number of samples among benchmarks MINSAMPLES= for IMGNAME in "${IMAGES[@]}" ; do for MODE in "${MODES[@]}"; do THEMIN=$(min_samples "${JSON}" "${IMGNAME}" "${MODE}") if [ "${MINSAMPLES}" == "" ] ; then MINSAMPLES="${THEMIN}" fi MINSAMPLES=$(echo "${MINSAMPLES} ${THEMIN}" | tr ' ' '\n' | sort -n | head -1) done done function template { local GRAPHFILE="${1}" local IMGNAME="${2}" local MODE="${3}" local OPERATION="${4}" local SAMPLES="${5}" cat < "${CSVFILE}" for MODE in "${MODES[@]}"; do for OPERATION in "pull" "create" "run" ; do DATAFILE="${RAWDATADIR}${IMAGE}-${MODE}-${OPERATION}.dat" PLTFILE="${PLTDATADIR}result-${IMAGE}-${MODE}-${OPERATION}.plt" template "${IMGDATADIR}result-${IMAGE}-${MODE}-${OPERATION}.png" \ "${IMGNAME}" "${MODE}" "${OPERATION}" "${MINSAMPLES}" > "${PLTFILE}" for PCTL in $(seq 0 "${GRANULARITY}" 100) ; do TIME=$(PERCENTILE=${PCTL} percentile "${JSON}" "${MINSAMPLES}" "${IMGNAME}" "${MODE}" "elapsed_${OPERATION}") echo "${PCTL} ${TIME}" >> "${DATAFILE}" echo "${IMGNAME},${MODE},${OPERATION},${PCTL},${TIME}" >> "${CSVFILE}" done echo 'plot "'"${DATAFILE}"'" using 0:2:xtic(1) with boxes lw 1 lc rgb "black" notitle' >> "${PLTFILE}" gnuplot "${PLTFILE}" done done done stargz-snapshotter-0.12.0/script/benchmark/tools/plot.sh000077500000000000000000000060641426301527400234010ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail JSON="$(mktemp)" cat > "${JSON}" CONTEXT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )/" source "${CONTEXT}/util.sh" if [ "${1}" == "" ] ; then echo "Specify directory for output" exit 1 fi DATADIR="${1}/" echo "output into: ${DATADIR}" MODES=( ${TARGET_MODES:-} ) if [ ${#MODES[@]} -eq 0 ] ; then MODES=("legacy" "estargz-noopt" "estargz" "zstdchunked") fi IMAGES=( ${TARGET_IMAGES:-} ) if [ ${#IMAGES[@]} -eq 0 ] ; then IMAGES=( $(cat "${JSON}" | jq -r '[ .[] | select(.mode=="'${MODES[0]}'").repo ] | unique[]') ) fi # Ensure we use the exact same number of samples among benchmarks MINSAMPLES= for IMGNAME in "${IMAGES[@]}" ; do for MODE in "${MODES[@]}"; do THEMIN=$(min_samples "${JSON}" "${IMGNAME}" "${MODE}") if [ "${MINSAMPLES}" == "" ] ; then MINSAMPLES="${THEMIN}" fi MINSAMPLES=$(echo "${MINSAMPLES} ${THEMIN}" | tr ' ' '\n' | sort -n | head -1) done done PLTFILE_ALL="${DATADIR}result.plt" GRAPHFILE_ALL="${DATADIR}result.png" cat < "${PLTFILE_ALL}" set output '${GRAPHFILE_ALL}' set title "Time to take for starting up containers(${PERCENTILE} pctl., ${MINSAMPLES} samples)" set terminal png size 1000, 750 set style data histogram set style histogram rowstack gap 1 set style fill solid 1.0 border -1 set key autotitle columnheader set xtics rotate by -45 set ylabel 'time[sec]' set lmargin 10 set rmargin 5 set tmargin 5 set bmargin 7 plot \\ EOF NOTITLE= INDEX=1 for IMGNAME in "${IMAGES[@]}" ; do echo "[${INDEX}]Processing: ${IMGNAME}" IMAGE=$(echo "${IMGNAME}" | sed 's|[/:]|-|g') DATAFILE="${DATADIR}${IMAGE}.dat" SUFFIX=', \' if [ ${INDEX} -eq ${#IMAGES[@]} ] ; then SUFFIX='' fi echo "mode pull create run" > "${DATAFILE}" for MODE in "${MODES[@]}"; do PULLTIME=$(percentile "${JSON}" "${MINSAMPLES}" "${IMGNAME}" "${MODE}" "elapsed_pull") CREATETIME=$(percentile "${JSON}" "${MINSAMPLES}" "${IMGNAME}" "${MODE}" "elapsed_create") RUNTIME=$(percentile "${JSON}" "${MINSAMPLES}" "${IMGNAME}" "${MODE}" "elapsed_run") echo "${MODE} ${PULLTIME} ${CREATETIME} ${RUNTIME}" >> "${DATAFILE}" done echo 'newhistogram "'"${IMAGE}"'", "'"${DATAFILE}"'" u 2:xtic(1) fs pattern 1 lt -1 '"${NOTITLE}"', "" u 3 fs pattern 2 lt -1 '"${NOTITLE}"', "" u 4 fs pattern 3 lt -1 '"${NOTITLE}""${SUFFIX}" \ >> "${PLTFILE_ALL}" NOTITLE=notitle ((INDEX+=1)) done gnuplot "${PLTFILE_ALL}" stargz-snapshotter-0.12.0/script/benchmark/tools/table.sh000077500000000000000000000041151426301527400235050ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail JSON="$(mktemp)" cat > "${JSON}" CONTEXT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )/" source "${CONTEXT}/util.sh" MODES=( ${TARGET_MODES:-} ) if [ ${#MODES[@]} -eq 0 ] ; then MODES=("legacy" "estargz-noopt" "estargz" "zstdchunked") fi IMAGES=( ${TARGET_IMAGES:-} ) if [ ${#IMAGES[@]} -eq 0 ] ; then IMAGES=( $(cat "${JSON}" | jq -r '[ .[] | select(.mode=="'${MODES[0]}'").repo ] | unique[]') ) fi # Ensure we use the exact same number of samples among benchmarks MINSAMPLES= for IMGNAME in "${IMAGES[@]}" ; do for MODE in "${MODES[@]}"; do THEMIN=$(min_samples "${JSON}" "${IMGNAME}" "${MODE}") if [ "${MINSAMPLES}" == "" ] ; then MINSAMPLES="${THEMIN}" fi MINSAMPLES=$(echo "${MINSAMPLES} ${THEMIN}" | tr ' ' '\n' | sort -n | head -1) done done cat < "${CALCTEMP}" local PYTHON_BIN= if which python &> /dev/null ; then PYTHON_BIN=python elif which python3 &> /dev/null ; then # Try also with python3 PYTHON_BIN=python3 else echo "Python not found" exit 1 fi cat < /lib/systemd/system/demo.target [Unit] Description=testing CRI-O Wants=stargz-store.service crio.service EOF exec /sbin/init --unit=demo.target stargz-snapshotter-0.12.0/script/config/000077500000000000000000000000001426301527400202315ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/config/etc/000077500000000000000000000000001426301527400210045ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/config/etc/containerd-stargz-grpc/000077500000000000000000000000001426301527400253735ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/config/etc/containerd-stargz-grpc/config.toml000066400000000000000000000007761426301527400275470ustar00rootroot00000000000000# Append configurations for Stargz Snapshotter in TOML format. # Enables CRI-based keychain # Stargz Snapshotter works as a proxy of CRI. # kubelet MUST listen stargz snapshotter's socket (unix:///run/containerd-stargz-grpc/containerd-stargz-grpc.sock) # instead of containerd for image service. # i.e. add `--image-service-endpoint=unix:///run/containerd-stargz-grpc/containerd-stargz-grpc.sock` option to kubelet. [cri_keychain] enable_keychain = true image_service_path = "/run/containerd/containerd.sock" stargz-snapshotter-0.12.0/script/config/etc/containerd/000077500000000000000000000000001426301527400231325ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/config/etc/containerd/config.toml000066400000000000000000000016241426301527400252770ustar00rootroot00000000000000# See also: https://github.com/kubernetes-sigs/kind/blob/fd64a56b0c3d5654eb6d22bce812e2a87eac5853/images/base/files/etc/containerd/config.toml # explicitly use v2 config format version = 2 # - Set default runtime handler to v2, which has a per-pod shim # - Enable to use stargz snapshotter [plugins."io.containerd.grpc.v1.cri".containerd] default_runtime_name = "runc" snapshotter = "stargz" disable_snapshot_annotations = false [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc] runtime_type = "io.containerd.runc.v2" # Setup a runtime with the magic name ("test-handler") used for Kubernetes # runtime class tests ... [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.test-handler] runtime_type = "io.containerd.runc.v2" # Use stargz snapshotter [proxy_plugins] [proxy_plugins.stargz] type = "snapshot" address = "/run/containerd-stargz-grpc/containerd-stargz-grpc.sock" stargz-snapshotter-0.12.0/script/config/etc/default/000077500000000000000000000000001426301527400224305ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/config/etc/default/kubelet000066400000000000000000000001771426301527400240130ustar00rootroot00000000000000KUBELET_EXTRA_ARGS=--fail-swap-on=false --image-service-endpoint=unix:///run/containerd-stargz-grpc/containerd-stargz-grpc.sockstargz-snapshotter-0.12.0/script/config/etc/systemd/000077500000000000000000000000001426301527400224745ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/config/etc/systemd/system/000077500000000000000000000000001426301527400240205ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/config/etc/systemd/system/stargz-snapshotter.service000066400000000000000000000004701426301527400312650ustar00rootroot00000000000000[Unit] Description=stargz snapshotter After=network.target Before=containerd.service [Service] Type=notify Environment=HOME=/root ExecStart=/usr/local/bin/containerd-stargz-grpc --log-level=debug --config=/etc/containerd-stargz-grpc/config.toml Restart=always RestartSec=1 [Install] WantedBy=multi-user.target stargz-snapshotter-0.12.0/script/cri-containerd/000077500000000000000000000000001426301527400216655ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/cri-containerd/const.sh000066400000000000000000000014141426301527400233470ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail NODE_BASE_IMAGE_NAME="cri-containerd-image-base" NODE_TEST_IMAGE_NAME="cri-containerd-image-test" PREPARE_NODE_IMAGE="cri-containerd-prepare-image" stargz-snapshotter-0.12.0/script/cri-containerd/mirror.sh000066400000000000000000000033271426301527400235400ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail if [ "${TOOLS_DIR}" == "" ] ; then echo "tools dir must be provided" exit 1 fi LIST_FILE="${TOOLS_DIR}/list" HOST_FILE="${TOOLS_DIR}/host" SS_REPO="/go/src/github.com/containerd/stargz-snapshotter" RETRYNUM=30 RETRYINTERVAL=1 TIMEOUTSEC=180 function retry { local SUCCESS=false for i in $(seq ${RETRYNUM}) ; do if eval "timeout ${TIMEOUTSEC} ${@}" ; then SUCCESS=true break fi echo "Fail(${i}). Retrying..." sleep ${RETRYINTERVAL} done if [ "${SUCCESS}" == "true" ] ; then return 0 else return 1 fi } cd "${SS_REPO}" PREFIX=/out/ make ctr-remote mv /out/ctr-remote /bin/ctr-remote containerd & retry ctr-remote version HOST=$(cat "${HOST_FILE}") cat "${LIST_FILE}" | sort | uniq | while read IMAGE ; do MIRROR_URL="${HOST}"$(echo "${IMAGE}" | sed -E 's/^[^/]*//g' | sed -E 's/@.*//g') echo "Mirroring: ${IMAGE} to ${MIRROR_URL}" ctr-remote images pull "${IMAGE}" ctr-remote images optimize --oci --period=1 "${IMAGE}" "${MIRROR_URL}" ctr-remote images push --plain-http "${MIRROR_URL}" done stargz-snapshotter-0.12.0/script/cri-containerd/test-legacy.sh000077500000000000000000000050331426301527400244460ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail CONTEXT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )/" CONTAINERD_SOCK=unix:///run/containerd/containerd.sock SNAPSHOTTER_SOCK=unix:///run/containerd-stargz-grpc/containerd-stargz-grpc.sock source "${CONTEXT}/const.sh" IMAGE_LIST="${1}" LOG_TMP=$(mktemp) LIST_TMP=$(mktemp) function cleanup { ORG_EXIT_CODE="${1}" rm "${LOG_TMP}" || true rm "${LIST_TMP}" || true exit "${ORG_EXIT_CODE}" } TEST_NODE_ID=$(docker run --rm -d --privileged \ -v /dev/fuse:/dev/fuse \ --tmpfs=/var/lib/containerd:suid \ --tmpfs=/var/lib/containerd-stargz-grpc:suid \ "${NODE_TEST_IMAGE_NAME}") echo "Running node on: ${TEST_NODE_ID}" FAIL= for i in $(seq 100) ; do if docker exec -i "${TEST_NODE_ID}" ctr version ; then break fi echo "Fail(${i}). Retrying..." if [ $i == 100 ] ; then FAIL=true fi sleep 1 done # If container started successfully, varidate the runtime through CRI if [ "${FAIL}" == "" ] ; then if ! ( echo "===== VERSION INFORMATION =====" && \ docker exec "${TEST_NODE_ID}" runc --version && \ docker exec "${TEST_NODE_ID}" containerd --version && \ echo "===============================" && \ docker exec -i "${TEST_NODE_ID}" /go/bin/critest --runtime-endpoint=${CONTAINERD_SOCK} --image-endpoint=${SNAPSHOTTER_SOCK} ) ; then FAIL=true fi fi # Dump all names of images used in the test docker exec -i "${TEST_NODE_ID}" journalctl -xu containerd > "${LOG_TMP}" cat "${LOG_TMP}" | grep PullImage | sed -E 's/.*PullImage \\"([^\\]*)\\".*/\1/g' > "${LIST_TMP}" cat "${LOG_TMP}" | grep SandboxImage | sed -E 's/.*SandboxImage:([^ ]*).*/\1/g' >> "${LIST_TMP}" cat "${LIST_TMP}" | sort | uniq > "${IMAGE_LIST}" docker kill "${TEST_NODE_ID}" if [ "${FAIL}" != "" ] ; then exit 1 fi exit 0 stargz-snapshotter-0.12.0/script/cri-containerd/test-stargz.sh000077500000000000000000000162541426301527400245230ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail CONTEXT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )/" REPO="${CONTEXT}../../" REGISTRY_HOST="cri-registry" TEST_NODE_NAME="cri-testenv-container" CONTAINERD_SOCK=unix:///run/containerd/containerd.sock SNAPSHOTTER_SOCK=unix:///run/containerd-stargz-grpc/containerd-stargz-grpc.sock PREPARE_NODE_NAME="cri-prepare-node" source "${CONTEXT}/const.sh" source "${REPO}/script/util/utils.sh" IMAGE_LIST="${1}" TMP_CONTEXT=$(mktemp -d) DOCKER_COMPOSE_YAML=$(mktemp) CONTAINERD_CONFIG=$(mktemp) SNAPSHOTTER_CONFIG=$(mktemp) TMPFILE=$(mktemp) LOG_FILE=$(mktemp) MIRROR_TMP=$(mktemp -d) function cleanup { ORG_EXIT_CODE="${1}" docker-compose -f "${DOCKER_COMPOSE_YAML}" down -v || true rm -rf "${TMP_CONTEXT}" || true rm "${DOCKER_COMPOSE_YAML}" || true rm "${CONTAINERD_CONFIG}" || true rm "${SNAPSHOTTER_CONFIG}" || true rm "${TMPFILE}" || true rm "${LOG_FILE}" || true rm -rf "${MIRROR_TMP}" || true exit "${ORG_EXIT_CODE}" } trap 'cleanup "$?"' EXIT SIGHUP SIGINT SIGQUIT SIGTERM # Prepare the testing node and registry cat < "${DOCKER_COMPOSE_YAML}" version: "3.3" services: cri-testenv-service: image: ${NODE_TEST_IMAGE_NAME} container_name: ${TEST_NODE_NAME} privileged: true tmpfs: - /tmp:exec,mode=777 volumes: - /dev/fuse:/dev/fuse - "critest-containerd-data:/var/lib/containerd" - "critest-containerd-stargz-grpc-data:/var/lib/containerd-stargz-grpc" image-prepare: image: "${PREPARE_NODE_IMAGE}" container_name: "${PREPARE_NODE_NAME}" privileged: true entrypoint: - sleep - infinity tmpfs: - /tmp:exec,mode=777 environment: - TOOLS_DIR=/tools/ volumes: - "critest-prepare-containerd-data:/var/lib/containerd" - "critest-prepare-containerd-stargz-grpc-data:/var/lib/containerd-stargz-grpc" - "${REPO}:/go/src/github.com/containerd/stargz-snapshotter:ro" - "${MIRROR_TMP}:/tools/" registry: image: registry:2 container_name: ${REGISTRY_HOST} volumes: critest-containerd-data: critest-containerd-stargz-grpc-data: critest-prepare-containerd-data: critest-prepare-containerd-stargz-grpc-data: EOF docker-compose -f "${DOCKER_COMPOSE_YAML}" up -d --force-recreate CONNECTED= for i in $(seq 100) ; do if docker exec "${TEST_NODE_NAME}" curl -k --head "http://${REGISTRY_HOST}:5000/v2/" ; then CONNECTED=true break fi echo "Fail(${i}). Retrying..." sleep 1 done if [ "${CONNECTED}" != "true" ] ; then echo "Failed to connect to containerd" exit 1 fi # Mirror and optimize all images used in tests echo "${REGISTRY_HOST}:5000" > "${MIRROR_TMP}/host" cp "${IMAGE_LIST}" "${MIRROR_TMP}/list" cp "${REPO}/script/cri-containerd/mirror.sh" "${MIRROR_TMP}/mirror.sh" docker exec "${PREPARE_NODE_NAME}" /bin/bash /tools/mirror.sh # Configure mirror registries for containerd and snapshotter docker exec "${TEST_NODE_NAME}" cat /etc/containerd/config.toml > "${CONTAINERD_CONFIG}" docker exec "${TEST_NODE_NAME}" cat /etc/containerd-stargz-grpc/config.toml > "${SNAPSHOTTER_CONFIG}" cat "${IMAGE_LIST}" | sed -E 's/^([^/]*).*/\1/g' | sort | uniq | while read DOMAIN ; do echo "Adding mirror config: ${DOMAIN}" cat <> "${CONTAINERD_CONFIG}" [plugins."io.containerd.grpc.v1.cri".registry.mirrors."${DOMAIN}"] endpoint = ["http://${REGISTRY_HOST}:5000"] EOF if [ "${BUILTIN_SNAPSHOTTER:-}" == "true" ] ; then cat <> "${CONTAINERD_CONFIG}" [plugins."io.containerd.snapshotter.v1.stargz".registry.mirrors."${DOMAIN}"] endpoint = ["http://${REGISTRY_HOST}:5000"] EOF else cat <> "${SNAPSHOTTER_CONFIG}" [[resolver.host."${DOMAIN}".mirrors]] host = "${REGISTRY_HOST}:5000" insecure = true EOF fi done echo "==== Containerd config ====" cat "${CONTAINERD_CONFIG}" echo "==== Snapshotter config ====" cat "${SNAPSHOTTER_CONFIG}" docker cp "${CONTAINERD_CONFIG}" "${TEST_NODE_NAME}":/etc/containerd/config.toml docker cp "${SNAPSHOTTER_CONFIG}" "${TEST_NODE_NAME}":/etc/containerd-stargz-grpc/config.toml # Replace digests specified in testing tool to stargz-formatted one docker exec "${PREPARE_NODE_NAME}" ctr-remote i ls cat "${IMAGE_LIST}" | grep "@sha256:" | while read IMAGE ; do URL_PATH=$(echo "${IMAGE}" | sed -E 's/^[^/]*//g' | sed -E 's/@.*//g') MIRROR_TAG="${REGISTRY_HOST}:5000${URL_PATH}" OLD_DIGEST=$(echo "${IMAGE}" | sed -E 's/.*(sha256:[a-z0-9]*).*/\1/g') echo "Getting the digest of : ${MIRROR_TAG}" NEW_DIGEST=$(docker exec "${PREPARE_NODE_NAME}" ctr-remote i ls name=="${MIRROR_TAG}" \ | grep "sha256" | sed -E 's/.*(sha256:[a-z0-9]*).*/\1/g') echo "Converting: ${OLD_DIGEST} => ${NEW_DIGEST}" docker exec "${TEST_NODE_NAME}" \ find /go/src/github.com/kubernetes-sigs/cri-tools/pkg -type f -exec \ sed -i -e "s|${OLD_DIGEST}|${NEW_DIGEST}|g" {} \; done # Rebuild cri testing tool docker exec "${TEST_NODE_NAME}" /bin/bash -c \ "cd /go/src/github.com/kubernetes-sigs/cri-tools && make && make install -e BINDIR=/go/bin" # Varidate the runtime through CRI if [ "${BUILTIN_SNAPSHOTTER:-}" != "true" ] ; then docker exec "${TEST_NODE_NAME}" systemctl restart stargz-snapshotter fi docker exec "${TEST_NODE_NAME}" systemctl restart containerd CONNECTED= for i in $(seq 100) ; do if docker exec "${TEST_NODE_NAME}" ctr version ; then CONNECTED=true break fi echo "Fail(${i}). Retrying..." sleep 1 done if [ "${CONNECTED}" != "true" ] ; then echo "Failed to connect to containerd" exit 1 fi echo "===== VERSION INFORMATION =====" docker exec "${TEST_NODE_NAME}" runc --version docker exec "${TEST_NODE_NAME}" containerd --version echo "===============================" docker exec "${TEST_NODE_NAME}" /go/bin/critest --runtime-endpoint=${CONTAINERD_SOCK} --image-endpoint=${SNAPSHOTTER_SOCK} echo "Check if stargz snapshotter is working" docker exec "${TEST_NODE_NAME}" \ ctr-remote --namespace=k8s.io snapshot --snapshotter=stargz ls \ | sed -E '1d' > "${TMPFILE}" if ! [ -s "${TMPFILE}" ] ; then echo "No snapshots created; stargz snapshotter might be connected to containerd" exit 1 fi echo "Check all remote snapshots are created successfully" if [ "${BUILTIN_SNAPSHOTTER:-}" == "true" ] ; then docker exec "${TEST_NODE_NAME}" journalctl -u containerd \ | grep "${LOG_REMOTE_SNAPSHOT}" \ | sed -E 's/^[^\{]*(\{.*)$/\1/g' > "${LOG_FILE}" else docker exec "${TEST_NODE_NAME}" journalctl -u stargz-snapshotter \ | grep "${LOG_REMOTE_SNAPSHOT}" \ | sed -E 's/^[^\{]*(\{.*)$/\1/g' > "${LOG_FILE}" fi check_remote_snapshots "${LOG_FILE}" exit 0 stargz-snapshotter-0.12.0/script/cri-containerd/test.sh000077500000000000000000000125211426301527400232040ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail CONTEXT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )/" REPO="${CONTEXT}../../" SNAPSHOTTER_SOCK_PATH=/run/containerd-stargz-grpc/containerd-stargz-grpc.sock source "${CONTEXT}/const.sh" source "${REPO}/script/util/utils.sh" CNI_PLUGINS_VERSION=$(get_version_from_arg "${REPO}/Dockerfile" "CNI_PLUGINS_VERSION") CRI_TOOLS_VERSION=$(get_version_from_arg "${REPO}/Dockerfile" "CRI_TOOLS_VERSION") GINKGO_VERSION=v1.16.5 if [ "${CRI_NO_RECREATE:-}" != "true" ] ; then echo "Preparing node image..." TARGET_STAGE= if [ "${BUILTIN_SNAPSHOTTER:-}" == "true" ] ; then TARGET_STAGE="--target kind-builtin-snapshotter" fi docker build ${DOCKER_BUILD_ARGS:-} -t "${NODE_BASE_IMAGE_NAME}" ${TARGET_STAGE} "${REPO}" docker build ${DOCKER_BUILD_ARGS:-} -t "${PREPARE_NODE_IMAGE}" --target containerd-base "${REPO}" fi TMP_CONTEXT=$(mktemp -d) IMAGE_LIST=$(mktemp) function cleanup { local ORG_EXIT_CODE="${1}" rm -rf "${TMP_CONTEXT}" || true rm "${IMAGE_LIST}" || true exit "${ORG_EXIT_CODE}" } trap 'cleanup "$?"' EXIT SIGHUP SIGINT SIGQUIT SIGTERM BUILTIN_HACK_INST= if [ "${BUILTIN_SNAPSHOTTER:-}" == "true" ] ; then # Special configuration for CRI containerd + builtin stargz snapshotter cat < "${TMP_CONTEXT}/containerd.hack.toml" version = 2 [debug] format = "json" level = "debug" [plugins."io.containerd.grpc.v1.cri".containerd] default_runtime_name = "runc" snapshotter = "stargz" disable_snapshot_annotations = false [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc] runtime_type = "io.containerd.runc.v2" [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.test-handler] runtime_type = "io.containerd.runc.v2" [plugins."io.containerd.snapshotter.v1.stargz"] cri_keychain_image_service_path = "${SNAPSHOTTER_SOCK_PATH}" metadata_store = "memory" [plugins."io.containerd.snapshotter.v1.stargz".cri_keychain] enable_keychain = true EOF BUILTIN_HACK_INST="COPY containerd.hack.toml /etc/containerd/config.toml" fi cat < "${TMP_CONTEXT}/test.conflist" { "cniVersion": "0.4.0", "name": "containerd-net", "plugins": [ { "type": "bridge", "bridge": "cni0", "isGateway": true, "ipMasq": true, "promiscMode": true, "ipam": { "type": "host-local", "ranges": [ [{ "subnet": "10.88.0.0/16" }] ], "routes": [ { "dst": "0.0.0.0/0" } ] } }, { "type": "portmap", "capabilities": {"portMappings": true} } ] } EOF USE_METADATA_STORE="memory" if [ "${METADATA_STORE:-}" != "" ] ; then USE_METADATA_STORE="${METADATA_STORE}" fi SNAPSHOTTER_CONFIG_FILE=/etc/containerd-stargz-grpc/config.toml if [ "${BUILTIN_SNAPSHOTTER:-}" == "true" ] ; then SNAPSHOTTER_CONFIG_FILE=/etc/containerd/config.toml fi # Prepare the testing node cat < "${TMP_CONTEXT}/Dockerfile" # 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". FROM ${NODE_BASE_IMAGE_NAME} ARG TARGETARCH # see https://medium.com/nttlabs/ubuntu-21-10-and-fedora-35-do-not-work-on-docker-20-10-9-1cd439d9921 SHELL ["/clone3-workaround", "/bin/sh", "-c"] ENV PATH=$PATH:/usr/local/go/bin ENV GOPATH=/go # Do not install git and its dependencies here which will cause failure of building the image RUN apt-get update && apt-get install -y --no-install-recommends make && \ curl -Ls https://dl.google.com/go/go1.18.linux-\${TARGETARCH:-amd64}.tar.gz | tar -C /usr/local -xz && \ go install github.com/onsi/ginkgo/ginkgo@${GINKGO_VERSION} && \ mkdir -p \${GOPATH}/src/github.com/kubernetes-sigs/cri-tools /tmp/cri-tools && \ curl -sL https://github.com/kubernetes-sigs/cri-tools/archive/refs/tags/v${CRI_TOOLS_VERSION}.tar.gz | tar -C /tmp/cri-tools -xz && \ mv /tmp/cri-tools/cri-tools-${CRI_TOOLS_VERSION}/* \${GOPATH}/src/github.com/kubernetes-sigs/cri-tools/ && \ cd \${GOPATH}/src/github.com/kubernetes-sigs/cri-tools && \ make && make install -e BINDIR=\${GOPATH}/bin && \ curl -Ls https://github.com/containernetworking/plugins/releases/download/v${CNI_PLUGINS_VERSION}/cni-plugins-linux-\${TARGETARCH:-amd64}-v${CNI_PLUGINS_VERSION}.tgz | tar xzv -C /opt/cni/bin && \ systemctl disable kubelet COPY ./test.conflist /etc/cni/net.d/test.conflist ${BUILTIN_HACK_INST} RUN sed -i 's/^metadata_store.*/metadata_store = "${USE_METADATA_STORE}"/g' "${SNAPSHOTTER_CONFIG_FILE}" ENTRYPOINT [ "/usr/local/bin/entrypoint", "/sbin/init" ] EOF docker build -t "${NODE_TEST_IMAGE_NAME}" ${DOCKER_BUILD_ARGS:-} "${TMP_CONTEXT}" echo "Testing..." "${CONTEXT}/test-legacy.sh" "${IMAGE_LIST}" "${CONTEXT}/test-stargz.sh" "${IMAGE_LIST}" stargz-snapshotter-0.12.0/script/cri-o/000077500000000000000000000000001426301527400177755ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/cri-o/const.sh000066400000000000000000000013611426301527400214600ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail NODE_BASE_IMAGE_NAME="cri-o-image-base" NODE_TEST_IMAGE_NAME="cri-o-image-test" PREPARE_NODE_IMAGE="cri-o-prepare-image" stargz-snapshotter-0.12.0/script/cri-o/mirror.sh000066400000000000000000000033271426301527400216500ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail if [ "${TOOLS_DIR}" == "" ] ; then echo "tools dir must be provided" exit 1 fi LIST_FILE="${TOOLS_DIR}/list" HOST_FILE="${TOOLS_DIR}/host" SS_REPO="/go/src/github.com/containerd/stargz-snapshotter" RETRYNUM=30 RETRYINTERVAL=1 TIMEOUTSEC=180 function retry { local SUCCESS=false for i in $(seq ${RETRYNUM}) ; do if eval "timeout ${TIMEOUTSEC} ${@}" ; then SUCCESS=true break fi echo "Fail(${i}). Retrying..." sleep ${RETRYINTERVAL} done if [ "${SUCCESS}" == "true" ] ; then return 0 else return 1 fi } cd "${SS_REPO}" PREFIX=/out/ make ctr-remote mv /out/ctr-remote /bin/ctr-remote containerd & retry ctr-remote version HOST=$(cat "${HOST_FILE}") cat "${LIST_FILE}" | sort | uniq | while read IMAGE ; do MIRROR_URL="${HOST}"$(echo "${IMAGE}" | sed -E 's/^[^/]*//g' | sed -E 's/@.*//g') echo "Mirroring: ${IMAGE} to ${MIRROR_URL}" ctr-remote images pull "${IMAGE}" ctr-remote images optimize --oci --period=1 "${IMAGE}" "${MIRROR_URL}" ctr-remote images push --plain-http "${MIRROR_URL}" done stargz-snapshotter-0.12.0/script/cri-o/test-legacy.sh000077500000000000000000000051571426301527400225650ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail CONTEXT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )/" CRIO_SOCK=unix:///run/crio/crio.sock PAUSE_IMAGE_NAME_PATH=/pause_name source "${CONTEXT}/const.sh" IMAGE_LIST="${1}" LOG_TMP=$(mktemp) LIST_TMP=$(mktemp) function cleanup { ORG_EXIT_CODE="${1}" rm "${LOG_TMP}" || true rm "${LIST_TMP}" || true exit "${ORG_EXIT_CODE}" } RETRYNUM=100 RETRYINTERVAL=1 TIMEOUTSEC=180 function retry { local SUCCESS=false for i in $(seq ${RETRYNUM}) ; do if eval "timeout ${TIMEOUTSEC} ${@}" ; then SUCCESS=true break fi echo "Fail(${i}). Retrying..." sleep ${RETRYINTERVAL} done if [ "${SUCCESS}" == "true" ] ; then return 0 else return 1 fi } TEST_NODE_ID=$(docker run --rm -d --privileged \ -v /dev/fuse:/dev/fuse \ --tmpfs=/var/lib/containers:suid \ --tmpfs=/var/lib/stargz-store:suid \ "${NODE_TEST_IMAGE_NAME}") echo "Running node on: ${TEST_NODE_ID}" retry docker exec "${TEST_NODE_ID}" /go/bin/crictl stats # If container started successfully, varidate the runtime through CRI FAIL= if ! ( echo "===== VERSION INFORMATION =====" && \ docker exec "${TEST_NODE_ID}" runc --version && \ docker exec "${TEST_NODE_ID}" crio --version && \ echo "===============================" && \ docker exec -i "${TEST_NODE_ID}" /go/bin/critest --runtime-endpoint=${CRIO_SOCK} ) ; then FAIL=true fi echo "Dump all names of images used in the test: ${IMAGE_LIST}" docker exec -i "${TEST_NODE_ID}" journalctl -xu crio > "${LOG_TMP}" cat "${LOG_TMP}" | grep "Pulling image: " | sed -E 's/.*Pulling image: ([^"]*)".*/\1/g' > "${LIST_TMP}" docker exec -i "${TEST_NODE_ID}" cat "${PAUSE_IMAGE_NAME_PATH}" >> "${LIST_TMP}" cat "${LIST_TMP}" | sort | uniq > "${IMAGE_LIST}" echo "Cleaning up..." docker kill "${TEST_NODE_ID}" if [ "${FAIL}" != "" ] ; then exit 1 fi exit 0 stargz-snapshotter-0.12.0/script/cri-o/test-stargz.sh000077500000000000000000000143341426301527400226300ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail CONTEXT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )/" REPO="${CONTEXT}../../" REGISTRY_HOST="cri-registry" TEST_NODE_NAME="cri-testenv-container" CRIO_SOCK=unix:///run/crio/crio.sock PREPARE_NODE_NAME="cri-prepare-node" source "${CONTEXT}/const.sh" source "${REPO}/script/util/utils.sh" IMAGE_LIST="${1}" TMP_CONTEXT=$(mktemp -d) DOCKER_COMPOSE_YAML=$(mktemp) CRIO_CONFIG=$(mktemp) STORE_CONFIG=$(mktemp) TMPFILE=$(mktemp) LOG_FILE=$(mktemp) MIRROR_TMP=$(mktemp -d) function cleanup { ORG_EXIT_CODE="${1}" docker-compose -f "${DOCKER_COMPOSE_YAML}" down -v || true rm -rf "${TMP_CONTEXT}" || true rm "${DOCKER_COMPOSE_YAML}" || true rm "${CRIO_CONFIG}" || true rm "${STORE_CONFIG}" || true rm "${TMPFILE}" || true rm "${LOG_FILE}" || true rm -rf "${MIRROR_TMP}" || true exit "${ORG_EXIT_CODE}" } trap 'cleanup "$?"' EXIT SIGHUP SIGINT SIGQUIT SIGTERM RETRYNUM=100 RETRYINTERVAL=1 TIMEOUTSEC=180 function retry { local SUCCESS=false for i in $(seq ${RETRYNUM}) ; do if eval "timeout ${TIMEOUTSEC} ${@}" ; then SUCCESS=true break fi echo "Fail(${i}). Retrying..." sleep ${RETRYINTERVAL} done if [ "${SUCCESS}" == "true" ] ; then return 0 else return 1 fi } # Prepare the testing node and registry cat < "${DOCKER_COMPOSE_YAML}" version: "3.3" services: cri-testenv-service: image: ${NODE_TEST_IMAGE_NAME} container_name: ${TEST_NODE_NAME} privileged: true tmpfs: - /tmp:exec,mode=777 volumes: - /dev/fuse:/dev/fuse - "critest-crio-data:/var/lib/containers" - "critest-crio-stargz-store-data:/var/lib/stargz-store" image-prepare: image: "${PREPARE_NODE_IMAGE}" container_name: "${PREPARE_NODE_NAME}" privileged: true entrypoint: - sleep - infinity tmpfs: - /tmp:exec,mode=777 environment: - TOOLS_DIR=/tools/ volumes: - "critest-prepare-containerd-data:/var/lib/containerd" - "critest-prepare-containerd-stargz-grpc-data:/var/lib/containerd-stargz-grpc" - "${REPO}:/go/src/github.com/containerd/stargz-snapshotter:ro" - "${MIRROR_TMP}:/tools/" registry: image: registry:2 container_name: ${REGISTRY_HOST} volumes: critest-crio-data: critest-crio-stargz-store-data: critest-prepare-containerd-data: critest-prepare-containerd-stargz-grpc-data: EOF docker-compose -f "${DOCKER_COMPOSE_YAML}" up -d --force-recreate retry docker exec "${PREPARE_NODE_NAME}" curl -k --head "http://${REGISTRY_HOST}:5000/v2/" # Mirror and optimize all images used in tests echo "${REGISTRY_HOST}:5000" > "${MIRROR_TMP}/host" cp "${IMAGE_LIST}" "${MIRROR_TMP}/list" cp "${REPO}/script/cri-o/mirror.sh" "${MIRROR_TMP}/mirror.sh" docker exec "${PREPARE_NODE_NAME}" /bin/bash /tools/mirror.sh # Configure mirror registries for CRI-O and stargz store docker exec "${TEST_NODE_NAME}" cat /etc/containers/registries.conf > "${CRIO_CONFIG}" docker exec "${TEST_NODE_NAME}" cat /etc/stargz-store/config.toml > "${STORE_CONFIG}" cat "${IMAGE_LIST}" | sed -E 's/^([^/]*).*/\1/g' | sort | uniq | while read DOMAIN ; do echo "Adding mirror config: ${DOMAIN}" cat <> "${CRIO_CONFIG}" [[registry]] prefix = "${DOMAIN}" insecure = true blocked = false location = "${REGISTRY_HOST}:5000" EOF cat <> "${STORE_CONFIG}" [[resolver.host."${DOMAIN}".mirrors]] host = "${REGISTRY_HOST}:5000" insecure = true EOF done echo "==== CRI-O (containers/image) config ====" cat "${CRIO_CONFIG}" echo "==== Store config ====" cat "${STORE_CONFIG}" docker cp "${CRIO_CONFIG}" "${TEST_NODE_NAME}":/etc/containers/registries.conf docker cp "${STORE_CONFIG}" "${TEST_NODE_NAME}":/etc/stargz-store/config.toml # Replace digests specified in testing tool to stargz-formatted one docker exec "${PREPARE_NODE_NAME}" ctr-remote i ls cat "${IMAGE_LIST}" | grep "@sha256:" | while read IMAGE ; do URL_PATH=$(echo "${IMAGE}" | sed -E 's/^[^/]*//g' | sed -E 's/@.*//g') MIRROR_TAG="${REGISTRY_HOST}:5000${URL_PATH}" OLD_DIGEST=$(echo "${IMAGE}" | sed -E 's/.*(sha256:[a-z0-9]*).*/\1/g') echo "Getting the digest of : ${MIRROR_TAG}" NEW_DIGEST=$(docker exec "${PREPARE_NODE_NAME}" ctr-remote i ls name=="${MIRROR_TAG}" \ | grep "sha256" | sed -E 's/.*(sha256:[a-z0-9]*).*/\1/g') echo "Converting: ${OLD_DIGEST} => ${NEW_DIGEST}" docker exec "${TEST_NODE_NAME}" \ find /go/src/github.com/kubernetes-sigs/cri-tools/pkg -type f -exec \ sed -i -e "s|${OLD_DIGEST}|${NEW_DIGEST}|g" {} \; done # Rebuild cri testing tool docker exec "${TEST_NODE_NAME}" /bin/bash -c \ "cd /go/src/github.com/kubernetes-sigs/cri-tools && make && make install -e BINDIR=/go/bin" # Varidate the runtime through CRI docker exec "${TEST_NODE_NAME}" systemctl restart stargz-store docker exec "${TEST_NODE_NAME}" systemctl restart crio CONNECTED= for i in $(seq 100) ; do if docker exec "${TEST_NODE_NAME}" /go/bin/crictl stats ; then CONNECTED=true break fi echo "Fail(${i}). Retrying..." sleep 1 done if [ "${CONNECTED}" != "true" ] ; then echo "Failed to connect to CRI-O" exit 1 fi echo "===== VERSION INFORMATION =====" docker exec "${TEST_NODE_NAME}" runc --version docker exec "${TEST_NODE_NAME}" crio --version echo "===============================" docker exec "${TEST_NODE_NAME}" /go/bin/critest --runtime-endpoint=${CRIO_SOCK} echo "Check all remote snapshots are created successfully" docker exec "${TEST_NODE_NAME}" journalctl -u stargz-store \ | grep "${LOG_REMOTE_SNAPSHOT}" \ | sed -E 's/^[^\{]*(\{.*)$/\1/g' > "${LOG_FILE}" check_remote_snapshots "${LOG_FILE}" exit 0 stargz-snapshotter-0.12.0/script/cri-o/test.sh000077500000000000000000000057211426301527400213200ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail CONTEXT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )/" REPO="${CONTEXT}../../" source "${CONTEXT}/const.sh" source "${REPO}/script/util/utils.sh" CRI_TOOLS_VERSION=$(get_version_from_arg "${REPO}/Dockerfile" "CRI_TOOLS_VERSION") GINKGO_VERSION=v1.16.5 if [ "${CRI_NO_RECREATE:-}" != "true" ] ; then echo "Preparing node image..." docker build ${DOCKER_BUILD_ARGS:-} -t "${NODE_BASE_IMAGE_NAME}" --target crio-stargz-store "${REPO}" docker build ${DOCKER_BUILD_ARGS:-} -t "${PREPARE_NODE_IMAGE}" --target containerd-base "${REPO}" fi USE_METADATA_STORE="memory" if [ "${METADATA_STORE:-}" != "" ] ; then USE_METADATA_STORE="${METADATA_STORE}" fi TMP_CONTEXT=$(mktemp -d) IMAGE_LIST=$(mktemp) function cleanup { local ORG_EXIT_CODE="${1}" rm -rf "${TMP_CONTEXT}" || true rm "${IMAGE_LIST}" || true exit "${ORG_EXIT_CODE}" } trap 'cleanup "$?"' EXIT SIGHUP SIGINT SIGQUIT SIGTERM # Prepare the testing node cat < "${TMP_CONTEXT}/Dockerfile" # 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". FROM ${NODE_BASE_IMAGE_NAME} ARG TARGETARCH ENV PATH=$PATH:/usr/local/go/bin ENV GOPATH=/go # Do not install git and its dependencies here which will cause failure of building the image RUN apt-get update && apt-get install -y --no-install-recommends make && \ curl -Ls https://dl.google.com/go/go1.18.linux-\${TARGETARCH:-amd64}.tar.gz | tar -C /usr/local -xz && \ go install github.com/onsi/ginkgo/ginkgo@${GINKGO_VERSION} && \ mkdir -p \${GOPATH}/src/github.com/kubernetes-sigs/cri-tools /tmp/cri-tools && \ curl -sL https://github.com/kubernetes-sigs/cri-tools/archive/refs/tags/v${CRI_TOOLS_VERSION}.tar.gz | tar -C /tmp/cri-tools -xz && \ mv /tmp/cri-tools/cri-tools-${CRI_TOOLS_VERSION}/* \${GOPATH}/src/github.com/kubernetes-sigs/cri-tools/ && \ cd \${GOPATH}/src/github.com/kubernetes-sigs/cri-tools && \ make && make install -e BINDIR=\${GOPATH}/bin RUN echo "metadata_store = \"${USE_METADATA_STORE}\"" >> /etc/stargz-store/config.toml ENTRYPOINT [ "/usr/local/bin/entrypoint" ] EOF docker build -t "${NODE_TEST_IMAGE_NAME}" ${DOCKER_BUILD_ARGS:-} "${TMP_CONTEXT}" echo "Testing..." "${CONTEXT}/test-legacy.sh" "${IMAGE_LIST}" "${CONTEXT}/test-stargz.sh" "${IMAGE_LIST}" stargz-snapshotter-0.12.0/script/criauth/000077500000000000000000000000001426301527400204235ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/criauth/create-pod.sh000077500000000000000000000071511426301527400230110ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail REMOTE_SNAPSHOT_LABEL="containerd.io/snapshot/remote" TEST_POD_NAME=testpod-$(head /dev/urandom | tr -dc a-z0-9 | head -c 10) TEST_POD_NS=ns1 TEST_CONTAINER_NAME=testcontainer-$(head /dev/urandom | tr -dc a-z0-9 | head -c 10) KIND_NODENAME="${1}" KIND_KUBECONFIG="${2}" TESTIMAGE="${3}" echo "Creating testing pod...." cat < 100" exit 1 fi ((LAYERSNUM+=1)) LABEL=$(docker exec -i "${KIND_NODENAME}" ctr-remote --namespace="k8s.io" \ snapshots --snapshotter=stargz info "${LAYER}" \ | jq -r ".Labels.\"${REMOTE_SNAPSHOT_LABEL}\"") echo "Checking layer ${LAYER} : ${LABEL}" if [ "${LABEL}" == "null" ] ; then echo "layer ${LAYER} isn't remote snapshot" exit 1 fi done if [ ${LAYERSNUM} -eq 0 ] ; then echo "cannot get layers" exit 1 fi exit 0 stargz-snapshotter-0.12.0/script/criauth/mirror.sh000066400000000000000000000025441426301527400222760ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail SRC="${1}" DST="${2}" SS_REPO="/go/src/github.com/containerd/stargz-snapshotter" RETRYNUM=30 RETRYINTERVAL=1 TIMEOUTSEC=180 function retry { local SUCCESS=false for i in $(seq ${RETRYNUM}) ; do if eval "timeout ${TIMEOUTSEC} ${@}" ; then SUCCESS=true break fi echo "Fail(${i}). Retrying..." sleep ${RETRYINTERVAL} done if [ "${SUCCESS}" == "true" ] ; then return 0 else return 1 fi } update-ca-certificates cd "${SS_REPO}" PREFIX=/out/ make ctr-remote containerd & retry /out/ctr-remote version /out/ctr-remote images pull "${SRC}" /out/ctr-remote images optimize --oci "${SRC}" "${DST}" /out/ctr-remote images push -u "${REGISTRY_CREDS}" "${DST}" stargz-snapshotter-0.12.0/script/criauth/run-kind.sh000077500000000000000000000105051426301527400225120ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail NODE_IMAGE_NAME="cri-stargz-snapshotter-node:1" NODE_BASE_IMAGE_NAME="cri-stargz-snapshotter-node-base:1" NODE_TEST_CERT_FILE="/usr/local/share/ca-certificates/registry.crt" REGISTRY_HOST=kind-cri-private-registry # Arguments KIND_CLUSTER_NAME="${1}" KIND_USER_KUBECONFIG="${2}" KIND_REGISTRY_CA="${3}" REPO="${4}" REGISTRY_NETWORK="${5}" DOCKERCONFIGJSON_DATA="${6}" TMP_BUILTIN_CONF=$(mktemp) TMP_CONTEXT=$(mktemp -d) SN_KUBECONFIG=$(mktemp) function cleanup { local ORG_EXIT_CODE="${1}" rm "${SN_KUBECONFIG}" rm -rf "${TMP_CONTEXT}" rm -rf "${TMP_BUILTIN_CONF}" exit "${ORG_EXIT_CODE}" } trap 'cleanup "$?"' EXIT SIGHUP SIGINT SIGQUIT SIGTERM if [ "${KIND_NO_RECREATE:-}" != "true" ] ; then echo "Preparing node image..." TARGET_STAGE= if [ "${BUILTIN_SNAPSHOTTER:-}" == "true" ] ; then TARGET_STAGE="--target kind-builtin-snapshotter" fi docker build ${DOCKER_BUILD_ARGS:-} -t "${NODE_BASE_IMAGE_NAME}" ${TARGET_STAGE} "${REPO}" fi # Prepare the testing node with enabling k8s keychain cat < "${TMP_CONTEXT}/config.containerd.append.toml" [plugins."io.containerd.grpc.v1.cri".registry.configs."${REGISTRY_HOST}:5000".tls] ca_file = "${NODE_TEST_CERT_FILE}" EOF BUILTIN_HACK_INST= if [ "${BUILTIN_SNAPSHOTTER:-}" == "true" ] ; then # Special configuration for CRI containerd + builtin stargz snapshotter cat < "${TMP_CONTEXT}/containerd.hack.toml" version = 2 [debug] format = "json" level = "debug" [plugins."io.containerd.grpc.v1.cri".containerd] default_runtime_name = "runc" snapshotter = "stargz" disable_snapshot_annotations = false [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc] runtime_type = "io.containerd.runc.v2" [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.test-handler] runtime_type = "io.containerd.runc.v2" [plugins."io.containerd.grpc.v1.cri".registry.configs."${REGISTRY_HOST}:5000".tls] ca_file = "${NODE_TEST_CERT_FILE}" [plugins."io.containerd.snapshotter.v1.stargz"] cri_keychain_image_service_path = "/run/containerd-stargz-grpc/containerd-stargz-grpc.sock" [plugins."io.containerd.snapshotter.v1.stargz".cri_keychain] enable_keychain = true [plugins."io.containerd.snapshotter.v1.stargz".registry.configs."${REGISTRY_HOST}:5000".tls] ca_file = "${NODE_TEST_CERT_FILE}" EOF BUILTIN_HACK_INST="COPY containerd.hack.toml /etc/containerd/config.toml" fi cp "${KIND_REGISTRY_CA}" "${TMP_CONTEXT}/registry.crt" cat < "${TMP_CONTEXT}/Dockerfile" FROM ${NODE_BASE_IMAGE_NAME} COPY registry.crt "${NODE_TEST_CERT_FILE}" COPY ./config.containerd.append.toml /tmp/ RUN cat /tmp/config.containerd.append.toml >> /etc/containerd/config.toml && \ update-ca-certificates ${BUILTIN_HACK_INST} EOF docker build -t "${NODE_IMAGE_NAME}" ${DOCKER_BUILD_ARGS:-} "${TMP_CONTEXT}" # cluster must be single node echo "Cleating kind cluster and connecting to the registry network..." kind create cluster --name "${KIND_CLUSTER_NAME}" \ --kubeconfig "${KIND_USER_KUBECONFIG}" \ --image "${NODE_IMAGE_NAME}" KIND_NODENAME=$(kind get nodes --name "${KIND_CLUSTER_NAME}" | sed -n 1p) # must be single node docker network connect "${REGISTRY_NETWORK}" "${KIND_NODENAME}" echo "===== VERSION INFORMATION =====" docker exec "${KIND_NODENAME}" containerd --version docker exec "${KIND_NODENAME}" runc --version echo "===============================" echo "Configuring kubernetes cluster..." CONFIGJSON_BASE64="$(cat ${DOCKERCONFIGJSON_DATA} | base64 -i -w 0)" cat </dev/null 2>&1 && pwd )/" REPO="${CONTEXT}../../" REGISTRY_HOST=kind-cri-private-registry REGISTRY_NETWORK=kind_cri_registry_network DUMMYUSER=dummyuser DUMMYPASS=dummypass TESTIMAGE_ORIGIN="ghcr.io/stargz-containers/ubuntu:20.04" TESTIMAGE="${REGISTRY_HOST}:5000/library/ubuntu:20.04" KIND_CLUSTER_NAME=kind-cri-stargz-snapshotter PREPARE_NODE_NAME="kind-cri-prepare-node" PREPARE_NODE_IMAGE="kind-cri-prepare-image" source "${REPO}/script/util/utils.sh" if [ "${KIND_NO_RECREATE:-}" != "true" ] ; then echo "Preparing preparation node image..." docker build ${DOCKER_BUILD_ARGS:-} -t "${PREPARE_NODE_IMAGE}" --target containerd-base "${REPO}" fi AUTH_DIR=$(mktemp -d) DOCKERCONFIG=$(mktemp) DOCKER_COMPOSE_YAML=$(mktemp) KIND_KUBECONFIG=$(mktemp) MIRROR_TMP=$(mktemp -d) function cleanup { local ORG_EXIT_CODE="${1}" rm -rf "${AUTH_DIR}" || true rm "${DOCKER_COMPOSE_YAML}" || true rm "${DOCKERCONFIG}" || true rm "${KIND_KUBECONFIG}" || true rm -rf "${MIRROR_TMP}" || true exit "${ORG_EXIT_CODE}" } trap 'cleanup "$?"' EXIT SIGHUP SIGINT SIGQUIT SIGTERM echo "Preparing creds..." prepare_creds "${AUTH_DIR}" "${REGISTRY_HOST}" "${DUMMYUSER}" "${DUMMYPASS}" echo -n '{"auths":{"'"${REGISTRY_HOST}"':5000":{"auth":"'$(echo -n "${DUMMYUSER}:${DUMMYPASS}" | base64 -i -w 0)'"}}}' > "${DOCKERCONFIG}" echo "Preparing private registry..." cat < "${DOCKER_COMPOSE_YAML}" version: "3.5" services: testenv_registry: image: registry:2 container_name: ${REGISTRY_HOST} environment: - HTTP_PROXY=${HTTP_PROXY:-} - HTTPS_PROXY=${HTTPS_PROXY:-} - http_proxy=${http_proxy:-} - https_proxy=${https_proxy:-} - REGISTRY_AUTH=htpasswd - REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" - REGISTRY_AUTH_HTPASSWD_PATH=/auth/auth/htpasswd - REGISTRY_HTTP_TLS_CERTIFICATE=/auth/certs/domain.crt - REGISTRY_HTTP_TLS_KEY=/auth/certs/domain.key volumes: - ${AUTH_DIR}:/auth image-prepare: image: "${PREPARE_NODE_IMAGE}" container_name: "${PREPARE_NODE_NAME}" privileged: true entrypoint: - sleep - infinity tmpfs: - /tmp:exec,mode=777 environment: - REGISTRY_CREDS=${DUMMYUSER}:${DUMMYPASS} volumes: - "criauth-prepare-containerd-data:/var/lib/containerd" - "criauth-prepare-containerd-stargz-grpc-data:/var/lib/containerd-stargz-grpc" - "${AUTH_DIR}/certs/domain.crt:/usr/local/share/ca-certificates/rgst.crt:ro" - "${REPO}:/go/src/github.com/containerd/stargz-snapshotter:ro" - "${MIRROR_TMP}:/tools/" volumes: criauth-prepare-containerd-data: criauth-prepare-containerd-stargz-grpc-data: networks: default: external: name: ${REGISTRY_NETWORK} EOF cp "${REPO}/script/criauth/mirror.sh" "${MIRROR_TMP}/mirror.sh" if ! ( cd "${CONTEXT}" && \ docker network create "${REGISTRY_NETWORK}" && \ docker-compose -f "${DOCKER_COMPOSE_YAML}" up -d --force-recreate && \ docker exec "${PREPARE_NODE_NAME}" /bin/bash /tools/mirror.sh \ "${TESTIMAGE_ORIGIN}" "${TESTIMAGE}" ) ; then echo "Failed to prepare private registry" docker-compose -f "${DOCKER_COMPOSE_YAML}" down -v docker network rm "${REGISTRY_NETWORK}" exit 1 fi echo "Testing in kind cluster (kubeconfig: ${KIND_KUBECONFIG})..." FAIL= if ! ( "${CONTEXT}"/run-kind.sh "${KIND_CLUSTER_NAME}" \ "${KIND_KUBECONFIG}" \ "${AUTH_DIR}/certs/domain.crt" \ "${REPO}" \ "${REGISTRY_NETWORK}" \ "${DOCKERCONFIG}" && \ sleep 20 && \ echo "Trying to pull private image with secret..." && \ "${CONTEXT}"/create-pod.sh "$(kind get nodes --name "${KIND_CLUSTER_NAME}" | sed -n 1p)" \ "${KIND_KUBECONFIG}" "${TESTIMAGE}" ) ; then FAIL=true fi docker-compose -f "${DOCKER_COMPOSE_YAML}" down -v kind delete cluster --name "${KIND_CLUSTER_NAME}" docker network rm "${REGISTRY_NETWORK}" if [ "${FAIL}" == "true" ] ; then exit 1 fi exit 0 stargz-snapshotter-0.12.0/script/demo-store/000077500000000000000000000000001426301527400210425ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/demo-store/docker-compose.yml000066400000000000000000000023321426301527400244770ustar00rootroot00000000000000version: "3.4" services: store_demo: build: context: ../../ target: podman-base args: NO_PROXY: "${NO_PROXY}" HTTP_PROXY: "${HTTP_PROXY}" HTTPS_PROXY: "${HTTPS_PROXY}" http_proxy: "${http_proxy}" https_proxy: "${https_proxy}" container_name: store_demo privileged: true stdin_open: true tty: true working_dir: /go/src/github.com/containerd/stargz-snapshotter entrypoint: /bin/bash environment: - NO_PROXY=127.0.0.1,localhost,registry2-store:5000 - HTTP_PROXY=${HTTP_PROXY} - HTTPS_PROXY=${HTTPS_PROXY} - http_proxy=${http_proxy} - https_proxy=${https_proxy} - GOPATH=/go tmpfs: - /tmp:exec,mode=777 volumes: - /dev/fuse:/dev/fuse - "${GOPATH}/src/github.com/containerd/stargz-snapshotter:/go/src/github.com/containerd/stargz-snapshotter:ro" - "containers-data:/var/lib/containers" - "additional-store-data:/var/lib/stargz-store" registry2: image: registry:2 container_name: registry2-store environment: - HTTP_PROXY=${HTTP_PROXY} - HTTPS_PROXY=${HTTPS_PROXY} - http_proxy=${http_proxy} - https_proxy=${https_proxy} volumes: containers-data: additional-store-data: stargz-snapshotter-0.12.0/script/demo-store/etc/000077500000000000000000000000001426301527400216155ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/demo-store/etc/containers/000077500000000000000000000000001426301527400237625ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/demo-store/etc/containers/policy.json000066400000000000000000000001341426301527400261520ustar00rootroot00000000000000{ "default": [ { "type": "insecureAcceptAnything" } ] } stargz-snapshotter-0.12.0/script/demo-store/etc/containers/registries.conf000066400000000000000000000000731426301527400270110ustar00rootroot00000000000000[registries.insecure] registries = ['registry2-store:5000']stargz-snapshotter-0.12.0/script/demo-store/etc/containers/storage.conf000066400000000000000000000002721426301527400262760ustar00rootroot00000000000000[storage] driver = "overlay" graphroot = "/var/lib/containers/storage" runroot = "/run/containers/storage" [storage.options] additionallayerstores = ["/var/lib/stargz-store/store:ref"] stargz-snapshotter-0.12.0/script/demo-store/etc/stargz-store/000077500000000000000000000000001426301527400242615ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/demo-store/etc/stargz-store/config.toml000066400000000000000000000002361426301527400264240ustar00rootroot00000000000000metrics_address = "127.0.0.1:8234" disable_verification = true [[resolver.host."registry2-store:5000".mirrors]] host = "registry2-store:5000" insecure = true stargz-snapshotter-0.12.0/script/demo-store/run.sh000077500000000000000000000042341426301527400222100ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail REPO=$GOPATH/src/github.com/containerd/stargz-snapshotter REG_STORAGE_CONFIG_FILE="/etc/stargz-store/config.toml" REG_STORAGE_ROOT=/var/lib/stargz-store/ REG_STORAGE_MOUNTPOINT="${REG_STORAGE_ROOT}store/" RETRYNUM=30 RETRYINTERVAL=1 TIMEOUTSEC=180 function retry { local SUCCESS=false for i in $(seq ${RETRYNUM}) ; do if eval "timeout ${TIMEOUTSEC} ${@}" ; then SUCCESS=true break fi echo "Fail(${i}). Retrying..." sleep ${RETRYINTERVAL} done if [ "${SUCCESS}" == "true" ] ; then return 0 else return 1 fi } function kill_all { if [ "${1}" != "" ] ; then ps aux | grep "${1}" \ | grep -v grep \ | grep -v "hello.py" \ | grep -v $(basename ${0}) \ | sed -E 's/ +/ /g' | cut -f 2 -d ' ' | xargs -I{} kill -9 {} || true fi } function cleanup { podman system reset -f || true umount "${REG_STORAGE_MOUNTPOINT}" || true rm -rf "${REG_STORAGE_ROOT}"* } echo "cleaning up the environment..." cleanup kill_all "stargz-store" echo "copying config from repo..." cp -R "${REPO}/script/demo-store/etc/"* "/etc/" echo "preparing commands..." ( cd "${REPO}" && PREFIX=/tmp/out/ make clean && \ PREFIX=/tmp/out/ make -j2 && \ PREFIX=/tmp/out/ make install ) echo "running stargz store..." mkdir -p "${REG_STORAGE_MOUNTPOINT}" stargz-store --log-level=debug \ --config="${REG_STORAGE_CONFIG_FILE}" \ "${REG_STORAGE_MOUNTPOINT}" & retry ls "${REG_STORAGE_ROOT}store/pool" stargz-snapshotter-0.12.0/script/demo/000077500000000000000000000000001426301527400177105ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/demo/.env000066400000000000000000000001021426301527400204720ustar00rootroot00000000000000GOPATH=/go HTTP_PROXY= HTTPS_PROXY= http_proxy= https_proxy= HOME=stargz-snapshotter-0.12.0/script/demo/config.cni.conflist000066400000000000000000000005141426301527400234700ustar00rootroot00000000000000{ "cniVersion": "0.4.0", "name": "demo", "plugins" : [{ "type": "bridge", "bridge": "optimizer0", "isDefaultGateway": true, "forceAddress": false, "ipMasq": true, "hairpinMode": true, "ipam": { "type": "host-local", "subnet": "10.10.0.0/16" } }, { "type": "loopback" }] } stargz-snapshotter-0.12.0/script/demo/config.containerd.toml000066400000000000000000000002241426301527400241750ustar00rootroot00000000000000version = 2 [proxy_plugins] [proxy_plugins.stargz] type = "snapshot" address = "/run/containerd-stargz-grpc/containerd-stargz-grpc.sock" stargz-snapshotter-0.12.0/script/demo/config.stargz.toml000066400000000000000000000002751426301527400233670ustar00rootroot00000000000000metrics_address = "127.0.0.1:8234" disable_verification = true ipfs = true [[resolver.host."registry2:5000".mirrors]] host = "registry2:5000" insecure = true [directory_cache] direct = truestargz-snapshotter-0.12.0/script/demo/docker-compose.yml000066400000000000000000000023731426301527400233520ustar00rootroot00000000000000version: "3.4" services: containerd_demo: build: context: ../../ target: demo args: NO_PROXY: "${NO_PROXY}" HTTP_PROXY: "${HTTP_PROXY}" HTTPS_PROXY: "${HTTPS_PROXY}" http_proxy: "${http_proxy}" https_proxy: "${https_proxy}" container_name: containerd_demo privileged: true stdin_open: true tty: true working_dir: /go/src/github.com/containerd/stargz-snapshotter entrypoint: /bin/bash environment: - NO_PROXY=127.0.0.1,localhost,registry2:5000 - HTTP_PROXY=${HTTP_PROXY} - HTTPS_PROXY=${HTTPS_PROXY} - http_proxy=${http_proxy} - https_proxy=${https_proxy} - GOPATH=/go tmpfs: - /tmp:exec,mode=777 volumes: - /dev/fuse:/dev/fuse - "${GOPATH}/src/github.com/containerd/stargz-snapshotter:/go/src/github.com/containerd/stargz-snapshotter:ro" - "demo-containerd-data:/var/lib/containerd" - "demo-containerd-stargz-grpc-data:/var/lib/containerd-stargz-grpc" registry2: image: registry:2 container_name: registry2 environment: - HTTP_PROXY=${HTTP_PROXY} - HTTPS_PROXY=${HTTPS_PROXY} - http_proxy=${http_proxy} - https_proxy=${https_proxy} volumes: demo-containerd-data: demo-containerd-stargz-grpc-data: stargz-snapshotter-0.12.0/script/demo/run.sh000077500000000000000000000057061426301527400210630ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail REPO=$GOPATH/src/github.com/containerd/stargz-snapshotter CONTAINERD_CONFIG_DIR=/etc/containerd/ CONTAINERD_ROOT=/var/lib/containerd/ REMOTE_SNAPSHOTTER_CONFIG_DIR=/etc/containerd-stargz-grpc/ REMOTE_SNAPSHOTTER_ROOT=/var/lib/containerd-stargz-grpc/ REMOTE_SNAPSHOTTER_SOCKET=/run/containerd-stargz-grpc/containerd-stargz-grpc.sock CNI_CONFIG_DIR=/etc/cni/net.d/ RETRYNUM=30 RETRYINTERVAL=1 TIMEOUTSEC=180 function retry { local SUCCESS=false for i in $(seq ${RETRYNUM}) ; do if eval "timeout ${TIMEOUTSEC} ${@}" ; then SUCCESS=true break fi echo "Fail(${i}). Retrying..." sleep ${RETRYINTERVAL} done if [ "${SUCCESS}" == "true" ] ; then return 0 else return 1 fi } function kill_all { if [ "${1}" != "" ] ; then ps aux | grep "${1}" | grep -v grep | sed -E 's/ +/ /g' | cut -f 2 -d ' ' | xargs -I{} kill -9 {} || true fi } function cleanup { rm -rf "${CONTAINERD_ROOT}"* if [ -f "${REMOTE_SNAPSHOTTER_SOCKET}" ] ; then rm "${REMOTE_SNAPSHOTTER_SOCKET}" fi if [ -d "${REMOTE_SNAPSHOTTER_ROOT}snapshotter/snapshots/" ] ; then find "${REMOTE_SNAPSHOTTER_ROOT}snapshotter/snapshots/" \ -maxdepth 1 -mindepth 1 -type d -exec umount "{}/fs" \; fi rm -rf "${REMOTE_SNAPSHOTTER_ROOT}"* } echo "copying config from repo..." mkdir -p "${CONTAINERD_CONFIG_DIR}" "${REMOTE_SNAPSHOTTER_CONFIG_DIR}" "${CNI_CONFIG_DIR}" && \ cp "${REPO}/script/demo/config.containerd.toml" "${CONTAINERD_CONFIG_DIR}config.toml" && \ cp "${REPO}/script/demo/config.stargz.toml" "${REMOTE_SNAPSHOTTER_CONFIG_DIR}config.toml" && \ cp "${REPO}/script/demo/config.cni.conflist" "${CNI_CONFIG_DIR}optimizer.conflist" echo "cleaning up the environment..." kill_all "containerd" kill_all "containerd-stargz-grpc" cleanup echo "preparing commands..." ( cd "${REPO}" && PREFIX=/tmp/out/ make clean && \ PREFIX=/tmp/out/ make -j2 && \ PREFIX=/tmp/out/ make install ) echo "running remote snaphsotter..." containerd-stargz-grpc --log-level=debug \ --address="${REMOTE_SNAPSHOTTER_SOCKET}" \ --config="${REMOTE_SNAPSHOTTER_CONFIG_DIR}config.toml" & retry ls "${REMOTE_SNAPSHOTTER_SOCKET}" echo "running containerd..." containerd --config="${CONTAINERD_CONFIG_DIR}config.toml" $@ & stargz-snapshotter-0.12.0/script/generated-files/000077500000000000000000000000001426301527400220225ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/generated-files/generate.sh000077500000000000000000000061231426301527400241550ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail CONTEXT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )/" REPO="${CONTEXT}../../" source "${REPO}/script/util/utils.sh" GOBASE_VERSION=$(go_base_version "${REPO}/Dockerfile") # TODO: get the following versions from go.mod once we add them. PROTOC_VERSION=3.17.3 GOGO_VERSION=v1.3.2 COMMAND="${1}" if [ "${COMMAND}" != "update" ] && [ "${COMMAND}" != "validate" ] ; then echo "either of \"update\" or \"validate\" must be specified" exit 1 fi TMP_CONTEXT=$(mktemp -d) GENERATED_FILES=$(mktemp -d) function cleanup { local ORG_EXIT_CODE="${1}" rm -rf "${GENERATED_FILES}" || true rm -rf "${TMP_CONTEXT}" || true exit "${ORG_EXIT_CODE}" } trap 'cleanup "$?"' EXIT SIGHUP SIGINT SIGQUIT SIGTERM cat < "${TMP_CONTEXT}/Dockerfile" FROM golang:${GOBASE_VERSION} AS golang-base ARG PROTOC_VERSION=${PROTOC_VERSION} ARG GOGO_VERSION=${GOGO_VERSION} ARG TARGETOS TARGETARCH RUN apt-get update && apt-get --no-install-recommends install -y unzip && \ PARCH=\$(echo \${TARGETARCH} | sed -e 's/amd64/x86_64/' -e 's/arm64/aarch_64/') && \ wget -O /tmp/protoc.zip \ https://github.com/google/protobuf/releases/download/v\${PROTOC_VERSION}/protoc-\${PROTOC_VERSION}-\${TARGETOS}-\${PARCH}.zip && \ unzip /tmp/protoc.zip -d /usr/local && \ go install github.com/gogo/protobuf/protoc-gen-gogo@\$GOGO_VERSION WORKDIR /go/src/github.com/containerd/stargz-snapshotter FROM golang-base AS generate COPY . . RUN git add -A && \ go generate -v ./... && \ mkdir /generated-files && \ git ls-files -m --others -- **/*.pb.go | tar -cf - --files-from - | tar -C /generated-files -xf - FROM scratch AS update COPY --from=generate /generated-files / FROM golang-base AS validate COPY . . RUN git add -A && \ go generate -v ./... && \ DIFFS=\$(git status --porcelain -- **/*.pb.go 2>/dev/null) ; \ if [ "\${DIFFS}" ]; then \ echo "Unexpected generated files; Please run \"make generate\"" && \ git diff && \ echo \${DIFFS} && \ exit 1 ; \ else \ echo OK ; \ fi EOF case ${COMMAND} in update) DOCKER_BUILDKIT=1 docker build --progress=plain --target update -f "${TMP_CONTEXT}/Dockerfile" -o - "${REPO}" | tar -C "${GENERATED_FILES}/" -x if ! [ -z "$(ls -A ${GENERATED_FILES}/)" ]; then cp -R "${GENERATED_FILES}/"* "${REPO}" ; fi ;; validate) DOCKER_BUILDKIT=1 docker build --progress=plain --target validate -f "${TMP_CONTEXT}/Dockerfile" "${REPO}" ;; esac stargz-snapshotter-0.12.0/script/integration/000077500000000000000000000000001426301527400213075ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/integration/containerd/000077500000000000000000000000001426301527400234355ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/integration/containerd/config.containerd.toml000066400000000000000000000005701426301527400277260ustar00rootroot00000000000000version = 2 [plugins."io.containerd.snapshotter.v1.stargz"] root_path = "/var/lib/containerd-stargz-grpc/" disable_verification = false metadata_store = "memory" [plugins."io.containerd.snapshotter.v1.stargz".blob] check_always = true [plugins."io.containerd.snapshotter.v1.stargz".registry.mirrors."registry-integration.test"] endpoint = ["http://registry-alt.test:5000"] stargz-snapshotter-0.12.0/script/integration/containerd/config.stargz.toml000066400000000000000000000002511426301527400271060ustar00rootroot00000000000000metadata_store = "memory" ipfs = true [blob] check_always = true [[resolver.host."registry-integration.test".mirrors]] host = "registry-alt.test:5000" insecure = true stargz-snapshotter-0.12.0/script/integration/containerd/entrypoint.sh000077500000000000000000000343521426301527400262160ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail # NOTE: The entire contents of containerd/stargz-snapshotter are located in # the testing container so utils.sh is visible from this script during runtime. # TODO: Refactor the code dependencies and pack them in the container without # expecting and relying on volumes. source "/utils.sh" PLUGIN=stargz REGISTRY_HOST=registry-integration.test REGISTRY_ALT_HOST=registry-alt.test DUMMYUSER=dummyuser DUMMYPASS=dummypass USR_ORG=$(mktemp -d) USR_MIRROR=$(mktemp -d) USR_REFRESH=$(mktemp -d) USR_NORMALSN_UNSTARGZ=$(mktemp -d) USR_STARGZSN_UNSTARGZ=$(mktemp -d) USR_NORMALSN_STARGZ=$(mktemp -d) USR_STARGZSN_STARGZ=$(mktemp -d) USR_NORMALSN_ZSTD=$(mktemp -d) USR_STARGZSN_ZSTD=$(mktemp -d) USR_NORMALSN_PLAIN_STARGZ=$(mktemp -d) USR_STARGZSN_PLAIN_STARGZ=$(mktemp -d) USR_NORMALSN_IPFS=$(mktemp -d) USR_STARGZSN_IPFS=$(mktemp -d) LOG_FILE=$(mktemp) function cleanup { ORG_EXIT_CODE="${1}" rm -rf "${USR_ORG}" || true rm -rf "${USR_MIRROR}" || true rm -rf "${USR_REFRESH}" || true rm -rf "${USR_NORMALSN_UNSTARGZ}" || true rm -rf "${USR_STARGZSN_UNSTARGZ}" || true rm -rf "${USR_NORMALSN_STARGZ}" || true rm -rf "${USR_STARGZSN_STARGZ}" || true rm -rf "${USR_NORMALSN_PLAIN_STARGZ}" || true rm -rf "${USR_STARGZSN_PLAIN_STARGZ}" || true rm -rf "${USR_NORMALSN_ZSTD}" || true rm -rf "${USR_STARGZSN_ZSTD}" || true rm -rf "${USR_NORMALSN_IPFS}" || true rm -rf "${USR_STARGZSN_IPFS}" || true rm "${LOG_FILE}" exit "${ORG_EXIT_CODE}" } trap 'cleanup "$?"' EXIT SIGHUP SIGINT SIGQUIT SIGTERM RETRYNUM=100 RETRYINTERVAL=1 TIMEOUTSEC=180 function retry { local SUCCESS=false for i in $(seq ${RETRYNUM}) ; do if eval "timeout ${TIMEOUTSEC} ${@}" ; then SUCCESS=true break fi echo "Fail(${i}). Retrying..." sleep ${RETRYINTERVAL} done if [ "${SUCCESS}" == "true" ] ; then return 0 else return 1 fi } function kill_all { if [ "${1}" != "" ] ; then ps aux | grep "${1}" | grep -v grep | grep -v $(basename ${0}) | sed -E 's/ +/ /g' | cut -f 2 -d ' ' | xargs -I{} kill -9 {} || true fi } CONTAINERD_ROOT=/var/lib/containerd/ CONTAINERD_STATUS=/run/containerd/ REMOTE_SNAPSHOTTER_SOCKET=/run/containerd-stargz-grpc/containerd-stargz-grpc.sock REMOTE_SNAPSHOTTER_ROOT=/var/lib/containerd-stargz-grpc/ function reboot_containerd { kill_all "containerd" kill_all "containerd-stargz-grpc" rm -rf "${CONTAINERD_STATUS}"* rm -rf "${CONTAINERD_ROOT}"* if [ -f "${REMOTE_SNAPSHOTTER_SOCKET}" ] ; then rm "${REMOTE_SNAPSHOTTER_SOCKET}" fi if [ -d "${REMOTE_SNAPSHOTTER_ROOT}snapshotter/snapshots/" ] ; then find "${REMOTE_SNAPSHOTTER_ROOT}snapshotter/snapshots/" \ -maxdepth 1 -mindepth 1 -type d -exec umount "{}/fs" \; fi rm -rf "${REMOTE_SNAPSHOTTER_ROOT}"* if [ "${BUILTIN_SNAPSHOTTER}" == "true" ] ; then if [ "${CONTAINERD_CONFIG:-}" != "" ] ; then containerd --log-level debug --config="${CONTAINERD_CONFIG:-}" 2>&1 | tee -a "${LOG_FILE}" & else containerd --log-level debug --config=/etc/containerd/config.toml 2>&1 | tee -a "${LOG_FILE}" & fi else if [ "${SNAPSHOTTER_CONFIG:-}" == "" ] ; then containerd-stargz-grpc --log-level=debug \ --address="${REMOTE_SNAPSHOTTER_SOCKET}" \ 2>&1 | tee -a "${LOG_FILE}" & # Dump all log else containerd-stargz-grpc --log-level=debug \ --address="${REMOTE_SNAPSHOTTER_SOCKET}" \ --config="${SNAPSHOTTER_CONFIG}" \ 2>&1 | tee -a "${LOG_FILE}" & fi retry ls "${REMOTE_SNAPSHOTTER_SOCKET}" containerd --log-level debug --config=/etc/containerd/config.toml & fi # Makes sure containerd and containerd-stargz-grpc are up-and-running. UNIQUE_SUFFIX=$(date +%s%N | shasum | base64 | fold -w 10 | head -1) retry ctr snapshots --snapshotter="${PLUGIN}" prepare "connectiontest-dummy-${UNIQUE_SUFFIX}" "" } function optimize { local SRC="${1}" local DST="${2}" local PUSHOPTS=${@:3} ctr-remote image pull -u "${DUMMYUSER}:${DUMMYPASS}" "${SRC}" ctr-remote image optimize --oci "${SRC}" "${DST}" ctr-remote image push ${PUSHOPTS} -u "${DUMMYUSER}:${DUMMYPASS}" "${DST}" } function convert { local SRC="${1}" local DST="${2}" local PUSHOPTS=${@:3} ctr-remote image pull -u "${DUMMYUSER}:${DUMMYPASS}" "${SRC}" ctr-remote image optimize --no-optimize "${SRC}" "${DST}" ctr-remote image push ${PUSHOPTS} -u "${DUMMYUSER}:${DUMMYPASS}" "${DST}" } function copy { local SRC="${1}" local DST="${2}" ctr-remote i pull --all-platforms "${SRC}" ctr-remote i tag "${SRC}" "${DST}" ctr-remote i push -u "${DUMMYUSER}:${DUMMYPASS}" "${DST}" } function copy_out_dir { local IMAGE="${1}" local TARGETDIR="${2}" local DEST="${3}" local SNAPSHOTTER="${4}" TMPFILE=$(mktemp) UNIQUE=$(date +%s%N | shasum | base64 | fold -w 10 | head -1) ctr-remote run --snapshotter="${SNAPSHOTTER}" "${IMAGE}" "${UNIQUE}" tar -c "${TARGETDIR}" > "${TMPFILE}" tar -C "${DEST}" -xf "${TMPFILE}" ctr-remote c rm "${UNIQUE}" || true rm "${TMPFILE}" } function dump_dir { local IMAGE="${1}" local TARGETDIR="${2}" local SNAPSHOTTER="${3}" local REMOTE="${4}" local DEST="${5}" reboot_containerd if [ "${REMOTE}" == "true" ] ; then run_and_check_remote_snapshots ctr-remote images rpull --user "${DUMMYUSER}:${DUMMYPASS}" "${IMAGE}" else ctr-remote images pull --snapshotter="${SNAPSHOTTER}" --user "${DUMMYUSER}:${DUMMYPASS}" "${IMAGE}" fi copy_out_dir "${IMAGE}" "${TARGETDIR}" "${DEST}" "${SNAPSHOTTER}" } function run_and_check_remote_snapshots { echo -n "" > "${LOG_FILE}" ${@:1} check_remote_snapshots "${LOG_FILE}" } echo "===== VERSION INFORMATION =====" containerd --version runc --version echo "===============================" cat <> /etc/containerd/config.toml [debug] format = "json" level = "debug" EOF if [ "${BUILTIN_SNAPSHOTTER}" != "true" ] ; then cat <> /etc/containerd/config.toml [proxy_plugins] [proxy_plugins.stargz] type = "snapshot" address = "/run/containerd-stargz-grpc/containerd-stargz-grpc.sock" EOF fi echo "Logging into the registry..." cp /auth/certs/domain.crt /usr/local/share/ca-certificates update-ca-certificates retry nerdctl login -u "${DUMMYUSER}" -p "${DUMMYPASS}" "${REGISTRY_HOST}" reboot_containerd OK=$(ctr-remote plugins ls \ | grep io.containerd.snapshotter \ | sed -E 's/ +/ /g' \ | cut -d ' ' -f 2,4 \ | grep "${PLUGIN}" \ | cut -d ' ' -f 2) if [ "${OK}" != "ok" ] ; then echo "Plugin ${PLUGIN} not found" 1>&2 exit 1 fi echo "Preparing images..." copy ghcr.io/stargz-containers/ubuntu:20.04-org "${REGISTRY_HOST}/ubuntu:18.04" copy ghcr.io/stargz-containers/alpine:3.15.3-org "${REGISTRY_HOST}/alpine:3.15.3" stargzify "${REGISTRY_HOST}/ubuntu:18.04" "${REGISTRY_HOST}/ubuntu:sgz" optimize "${REGISTRY_HOST}/ubuntu:18.04" "${REGISTRY_HOST}/ubuntu:esgz" optimize "${REGISTRY_HOST}/ubuntu:18.04" "${REGISTRY_HOST}/ubuntu:zstdchunked" optimize "${REGISTRY_HOST}/alpine:3.15.3" "${REGISTRY_HOST}/alpine:esgz" optimize "${REGISTRY_HOST}/alpine:3.15.3" "${REGISTRY_ALT_HOST}:5000/alpine:esgz" --plain-http if [ "${BUILTIN_SNAPSHOTTER}" != "true" ] ; then ############ # Tests with IPFS if standalone snapshotter echo "Testing with IPFS..." ipfs init ipfs daemon --offline & retry curl -X POST localhost:5001/api/v0/version >/dev/null 2>&1 # wait for up # stargz snapshotter ctr-remote i pull --user "${DUMMYUSER}:${DUMMYPASS}" "${REGISTRY_HOST}/ubuntu:18.04" CID=$(ctr-remote i ipfs-push "${REGISTRY_HOST}/ubuntu:18.04") reboot_containerd run_and_check_remote_snapshots ctr-remote i rpull --ipfs "${CID}" copy_out_dir "${CID}" "/usr" "${USR_STARGZSN_IPFS}" "stargz" # overlayfs snapshotter ctr-remote i pull --user "${DUMMYUSER}:${DUMMYPASS}" "${REGISTRY_HOST}/ubuntu:18.04" CID=$(ctr-remote i ipfs-push --estargz=false "${REGISTRY_HOST}/ubuntu:18.04") reboot_containerd ctr-remote i rpull --snapshotter=overlayfs --ipfs "${CID}" copy_out_dir "${CID}" "/usr" "${USR_NORMALSN_IPFS}" "overlayfs" echo "Diffing between two root filesystems(normal vs stargz snapshotter, IPFS rootfs)" diff --no-dereference -qr "${USR_NORMALSN_IPFS}/" "${USR_STARGZSN_IPFS}/" kill_all ipfs fi ############ # Tests for refreshing and mirror echo "Testing refreshing and mirror..." reboot_containerd echo "Getting image with normal snapshotter..." ctr-remote images pull --user "${DUMMYUSER}:${DUMMYPASS}" "${REGISTRY_HOST}/alpine:esgz" copy_out_dir "${REGISTRY_HOST}/alpine:esgz" "/usr" "${USR_ORG}" "overlayfs" echo "Getting image with stargz snapshotter..." run_and_check_remote_snapshots ctr-remote images rpull --user "${DUMMYUSER}:${DUMMYPASS}" "${REGISTRY_HOST}/alpine:esgz" REGISTRY_HOST_IP=$(getent hosts "${REGISTRY_HOST}" | awk '{ print $1 }') REGISTRY_ALT_HOST_IP=$(getent hosts "${REGISTRY_ALT_HOST}" | awk '{ print $1 }') echo "Disabling source registry and check if mirroring is working for stargz snapshotter..." iptables -A OUTPUT -d "${REGISTRY_HOST_IP}" -j DROP iptables -L copy_out_dir "${REGISTRY_HOST}/alpine:esgz" "/usr" "${USR_MIRROR}" "stargz" iptables -D OUTPUT -d "${REGISTRY_HOST_IP}" -j DROP echo "Disabling mirror registry and check if refreshing works for stargz snapshotter..." iptables -A OUTPUT -d "${REGISTRY_ALT_HOST_IP}" -j DROP iptables -L copy_out_dir "${REGISTRY_HOST}/alpine:esgz" "/usr" "${USR_REFRESH}" "stargz" iptables -D OUTPUT -d "${REGISTRY_ALT_HOST_IP}" -j DROP echo "Disabling all registries and running container should fail" iptables -A OUTPUT -d "${REGISTRY_HOST_IP}","${REGISTRY_ALT_HOST_IP}" -j DROP iptables -L if ctr-remote run --rm --snapshotter=stargz "${REGISTRY_HOST}/alpine:esgz" test tar -c /usr > /usr_dummy_fail.tar ; then echo "All registries are disabled so this must be failed" exit 1 else echo "Failed to run the container as expected" fi iptables -D OUTPUT -d "${REGISTRY_HOST_IP}","${REGISTRY_ALT_HOST_IP}" -j DROP echo "Diffing root filesystems for mirroring" diff --no-dereference -qr "${USR_ORG}/" "${USR_MIRROR}/" echo "Diffing root filesystems for refreshing" diff --no-dereference -qr "${USR_ORG}/" "${USR_REFRESH}/" ############ # Tests for stargz filesystem echo "Testing stargz filesystem..." # Test with a normal image echo "Getting normal image with normal snapshotter..." dump_dir "${REGISTRY_HOST}/ubuntu:18.04" "/usr" "overlayfs" "false" "${USR_NORMALSN_UNSTARGZ}" echo "Getting normal image with stargz snapshotter..." dump_dir "${REGISTRY_HOST}/ubuntu:18.04" "/usr" "stargz" "false" "${USR_STARGZSN_UNSTARGZ}" echo "Diffing between two root filesystems(normal vs stargz snapshotter, normal rootfs)" diff --no-dereference -qr "${USR_NORMALSN_UNSTARGZ}/" "${USR_STARGZSN_UNSTARGZ}/" # Test with an eStargz image echo "Getting eStargz image with normal snapshotter..." dump_dir "${REGISTRY_HOST}/ubuntu:esgz" "/usr" "overlayfs" "false" "${USR_NORMALSN_STARGZ}" echo "Getting eStargz image with stargz snapshotter..." dump_dir "${REGISTRY_HOST}/ubuntu:esgz" "/usr" "stargz" "true" "${USR_STARGZSN_STARGZ}" echo "Diffing between two root filesystems(normal vs stargz snapshotter, eStargz rootfs)" diff --no-dereference -qr "${USR_NORMALSN_STARGZ}/" "${USR_STARGZSN_STARGZ}/" # Test with a zstd:chunked image echo "Getting zstd:chunked image with normal snapshotter..." dump_dir "${REGISTRY_HOST}/ubuntu:zstdchunked" "/usr" "overlayfs" "false" "${USR_NORMALSN_ZSTD}" echo "Getting zstd:chunked image with stargz snapshotter..." dump_dir "${REGISTRY_HOST}/ubuntu:zstdchunked" "/usr" "stargz" "true" "${USR_STARGZSN_ZSTD}" echo "Diffing between two root filesystems(normal vs stargz snapshotter, zstd:cunked rootfs)" diff --no-dereference -qr "${USR_NORMALSN_ZSTD}/" "${USR_STARGZSN_ZSTD}/" ############ # Checking compatibility with plain stargz reboot_containerd echo "Getting (legacy) stargz image with normal snapshotter..." dump_dir "${REGISTRY_HOST}/ubuntu:sgz" "/usr" "overlayfs" "false" "${USR_NORMALSN_PLAIN_STARGZ}" echo "Getting (legacy) stargz image with stargz snapshotter..." TEST_CONTAINERD_CONFIG= TEST_SNAPSHOTTER_CONFIG= if [ "${BUILTIN_SNAPSHOTTER}" == "true" ] ; then cp /etc/containerd/config.toml /tmp/config.containerd.noverify.toml sed -i 's/disable_verification = false/disable_verification = true/g' /tmp/config.containerd.noverify.toml TEST_CONTAINERD_CONFIG="/tmp/config.containerd.noverify.toml" else echo "disable_verification = true" > /tmp/config.stargz.noverify.toml cat /etc/containerd-stargz-grpc/config.toml >> /tmp/config.stargz.noverify.toml TEST_SNAPSHOTTER_CONFIG="/tmp/config.stargz.noverify.toml" fi CONTAINERD_CONFIG="${TEST_CONTAINERD_CONFIG}" SNAPSHOTTER_CONFIG="${TEST_SNAPSHOTTER_CONFIG}" \ dump_dir "${REGISTRY_HOST}/ubuntu:sgz" "/usr" "stargz" "true" "${USR_STARGZSN_PLAIN_STARGZ}" echo "Diffing between two root filesystems(normal vs stargz snapshotter, plain stargz rootfs)" diff --no-dereference -qr "${USR_NORMALSN_PLAIN_STARGZ}/" "${USR_STARGZSN_PLAIN_STARGZ}/" ############ # Try to pull this image from different namespace. ctr-remote --namespace=dummy images rpull --user "${DUMMYUSER}:${DUMMYPASS}" \ "${REGISTRY_HOST}/ubuntu:esgz" ############ # Test for starting when no configuration file. mv /etc/containerd-stargz-grpc/config.toml /etc/containerd-stargz-grpc/config.toml_rm reboot_containerd mv /etc/containerd-stargz-grpc/config.toml_rm /etc/containerd-stargz-grpc/config.toml exit 0 stargz-snapshotter-0.12.0/script/integration/test.sh000077500000000000000000000125111426301527400226250ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail CONTEXT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )/" REPO="${CONTEXT}../../" INTEGRATION_BASE_IMAGE_NAME="integration-image-base" INTEGRATION_TEST_IMAGE_NAME="integration-image-test" REGISTRY_HOST=registry-integration.test REGISTRY_ALT_HOST=registry-alt.test CONTAINERD_NODE=testenv_integration DUMMYUSER=dummyuser DUMMYPASS=dummypass IPFS_VERSION=v0.10.0 source "${REPO}/script/util/utils.sh" if [ "${INTEGRATION_NO_RECREATE:-}" != "true" ] ; then echo "Preparing node image..." TARGET_STAGE=snapshotter-base if [ "${BUILTIN_SNAPSHOTTER:-}" == "true" ] ; then TARGET_STAGE=containerd-snapshotter-base fi # Enable to check race # Temporary disable race check until docker/cli fixing race issue # https://github.com/docker/cli/pull/3264 # BUILD_FLAGS="-race" BUILD_FLAGS="" docker build ${DOCKER_BUILD_ARGS:-} -t "${INTEGRATION_BASE_IMAGE_NAME}" \ --target "${TARGET_STAGE}" \ --build-arg=SNAPSHOTTER_BUILD_FLAGS="${BUILD_FLAGS}" \ "${REPO}" fi USE_METADATA_STORE="memory" if [ "${METADATA_STORE:-}" != "" ] ; then USE_METADATA_STORE="${METADATA_STORE}" fi SNAPSHOTTER_CONFIG_FILE=/etc/containerd-stargz-grpc/config.toml if [ "${BUILTIN_SNAPSHOTTER:-}" == "true" ] ; then SNAPSHOTTER_CONFIG_FILE=/etc/containerd/config.toml fi DOCKER_COMPOSE_YAML=$(mktemp) AUTH_DIR=$(mktemp -d) SS_ROOT_DIR=$(mktemp -d) TMP_CONTEXT=$(mktemp -d) function cleanup { local ORG_EXIT_CODE="${1}" rm "${DOCKER_COMPOSE_YAML}" || true rm -rf "${AUTH_DIR}" || true rm -rf "${SS_ROOT_DIR}" || true rm -rf "${TMP_CONTEXT}" || true exit "${ORG_EXIT_CODE}" } trap 'cleanup "$?"' EXIT SIGHUP SIGINT SIGQUIT SIGTERM cp -R "${CONTEXT}/containerd" \ "${REPO}/script/util/utils.sh" \ "${TMP_CONTEXT}" cat < "${TMP_CONTEXT}/Dockerfile" FROM ${INTEGRATION_BASE_IMAGE_NAME} RUN apt-get update -y && \ apt-get --no-install-recommends install -y iptables jq && \ go install github.com/google/crfs/stargz/stargzify@71d77da419c90be7b05d12e59945ac7a8c94a543 && \ wget https://dist.ipfs.io/go-ipfs/${IPFS_VERSION}/go-ipfs_${IPFS_VERSION}_linux-amd64.tar.gz && \ tar -xvzf go-ipfs_${IPFS_VERSION}_linux-amd64.tar.gz && \ cd go-ipfs && \ bash install.sh COPY ./containerd/config.containerd.toml /etc/containerd/config.toml COPY ./containerd/config.stargz.toml /etc/containerd-stargz-grpc/config.toml COPY ./containerd/entrypoint.sh ./utils.sh / RUN sed -i 's/^metadata_store.*/metadata_store = "${USE_METADATA_STORE}"/g' "${SNAPSHOTTER_CONFIG_FILE}" ENV CONTAINERD_SNAPSHOTTER="" ENTRYPOINT [ "/entrypoint.sh" ] EOF docker build ${DOCKER_BUILD_ARGS:-} -t "${INTEGRATION_TEST_IMAGE_NAME}" ${DOCKER_BUILD_ARGS:-} "${TMP_CONTEXT}" echo "Preparing creds..." prepare_creds "${AUTH_DIR}" "${REGISTRY_HOST}" "${DUMMYUSER}" "${DUMMYPASS}" echo "Preparing docker-compose.yml..." cat < "${DOCKER_COMPOSE_YAML}" version: "3.3" services: ${CONTAINERD_NODE}: image: ${INTEGRATION_TEST_IMAGE_NAME} container_name: testenv_integration privileged: true environment: - NO_PROXY=127.0.0.1,localhost,${REGISTRY_HOST}:443,${REGISTRY_ALT_HOST}:5000 - HTTP_PROXY=${HTTP_PROXY:-} - HTTPS_PROXY=${HTTPS_PROXY:-} - http_proxy=${http_proxy:-} - https_proxy=${https_proxy:-} - BUILTIN_SNAPSHOTTER=${BUILTIN_SNAPSHOTTER:-} tmpfs: - /tmp:exec,mode=777 volumes: - "${REPO}:/go/src/github.com/containerd/stargz-snapshotter:ro" - ${AUTH_DIR}:/auth - /dev/fuse:/dev/fuse - "integration-containerd-data:/var/lib/containerd" - "integration-containerd-stargz-grpc-data:/var/lib/containerd-stargz-grpc" registry: image: registry:2 container_name: ${REGISTRY_HOST} environment: - HTTP_PROXY=${HTTP_PROXY:-} - HTTPS_PROXY=${HTTPS_PROXY:-} - http_proxy=${http_proxy:-} - https_proxy=${https_proxy:-} - REGISTRY_AUTH=htpasswd - REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" - REGISTRY_AUTH_HTPASSWD_PATH=/auth/auth/htpasswd - REGISTRY_HTTP_TLS_CERTIFICATE=/auth/certs/domain.crt - REGISTRY_HTTP_TLS_KEY=/auth/certs/domain.key - REGISTRY_HTTP_ADDR=${REGISTRY_HOST}:443 volumes: - ${AUTH_DIR}:/auth registry-alt: image: registry:2 container_name: "${REGISTRY_ALT_HOST}" volumes: integration-containerd-data: integration-containerd-stargz-grpc-data: EOF echo "Testing..." FAIL= if ! ( cd "${CONTEXT}" && \ docker-compose -f "${DOCKER_COMPOSE_YAML}" build ${DOCKER_BUILD_ARGS:-} \ "${CONTAINERD_NODE}" && \ docker-compose -f "${DOCKER_COMPOSE_YAML}" up --abort-on-container-exit ) ; then FAIL=true fi docker-compose -f "${DOCKER_COMPOSE_YAML}" down -v if [ "${FAIL}" == "true" ] ; then exit 1 fi exit 0 stargz-snapshotter-0.12.0/script/k3s-argo-workflow/000077500000000000000000000000001426301527400222625ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/k3s-argo-workflow/go.yaml.template000066400000000000000000000063041426301527400253700ustar00rootroot00000000000000# Example golang CI based on https://github.com/argoproj/argo-workflows/blob/7dc6515ce1ef76475ac7bd2a7a3c3cdbe795a13c/examples/ci.yaml apiVersion: argoproj.io/v1alpha1 kind: Workflow metadata: generateName: ci-example- spec: # entrypoint is the name of the template used as the starting point of the workflow entrypoint: ci-example arguments: parameters: - name: revision value: 09c3a5e # a temporary volume, named workdir, will be used as a working directory # for this workflow. This volume is passed around from step to step. volumeClaimTemplates: - metadata: name: workdir spec: accessModes: [ "ReadWriteOnce" ] resources: requests: storage: 1Gi templates: - name: ci-example inputs: parameters: - name: revision steps: - - name: golangci-lint template: golangci-lint-example arguments: parameters: - name: revision value: "{{inputs.parameters.revision}}" - - name: gosec template: gosec-example - - name: build template: build-golang-example # the test step expands into three parallel steps running # different operating system images. each mounts the workdir # and runs the compiled binary from the build step. - - name: test template: run-hello arguments: parameters: - name: os-image value: "{{item.image}}:{{item.tag}}" withItems: - { image: 'ghcr.io/stargz-containers/alpine', tag: '3.10.2-${IMAGE_TYPE}' } - { image: 'ghcr.io/stargz-containers/ubuntu', tag: '20.04-${IMAGE_TYPE}' } - { image: 'ghcr.io/stargz-containers/fedora', tag: '30-${IMAGE_TYPE}' } - name: golangci-lint-example inputs: parameters: - name: revision artifacts: - name: code path: /go/src/github.com/golang/example git: repo: https://github.com/golang/example.git revision: "{{inputs.parameters.revision}}" container: image: ghcr.io/stargz-containers/golangci-lint:v1.40.1-${IMAGE_TYPE} command: [sh, -c] args: [" cd /go/src/github.com/golang/example/hello && golangci-lint run ./... "] volumeMounts: - name: workdir mountPath: /go - name: gosec-example container: image: ghcr.io/stargz-containers/gosec:v2.8.0-${IMAGE_TYPE} command: [sh, -c] args: [" cd /go/src/github.com/golang/example/hello && gosec ./... "] volumeMounts: - name: workdir mountPath: /go - name: build-golang-example container: image: ghcr.io/stargz-containers/golang:1.15.3-buster-${IMAGE_TYPE} command: [sh, -c] args: [" cd /go/src/github.com/golang/example/hello && go build -v . "] volumeMounts: - name: workdir mountPath: /go - name: run-hello inputs: parameters: - name: os-image container: image: "{{inputs.parameters.os-image}}" command: [sh, -c] args: [" uname -a ; cat /etc/os-release ; /go/src/github.com/golang/example/hello/hello "] volumeMounts: - name: workdir mountPath: /go stargz-snapshotter-0.12.0/script/k3s-argo-workflow/run.sh000077500000000000000000000141721426301527400234320ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail CONTEXT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )/" REPO="${CONTEXT}../../" K3S_VERSION=master K3S_REPO=https://github.com/k3s-io/k3s K3S_NODE_REPO=ghcr.io/stargz-containers K3S_NODE_IMAGE_NAME=k3s K3S_NODE_TAG=1 K3S_NODE_IMAGE="${K3S_NODE_REPO}/${K3S_NODE_IMAGE_NAME}:${K3S_NODE_TAG}" K3S_CLUSTER_NAME="k3s-demo-cluster-$(date +%s%N | shasum | base64 | fold -w 10 | head -1)" ORG_ARGOYAML=$(mktemp) TMP_K3S_REPO=$(mktemp -d) TMP_GOLANGCI=$(mktemp) function cleanup { ORG_EXIT_CODE="${1}" rm "${ORG_ARGOYAML}" || true rm -rf "${TMP_K3S_REPO}" || true exit "${ORG_EXIT_CODE}" } trap 'cleanup "$?"' EXIT SIGHUP SIGINT SIGQUIT SIGTERM function argo_yaml() { local IMAGE_TYPE="${1}" local TMP_CUSTOM_ARGOYAML=$(mktemp) cp "${ORG_ARGOYAML}" "${TMP_CUSTOM_ARGOYAML}" sed -i 's|containerRuntimeExecutor: docker|containerRuntimeExecutor: pns|g' "${TMP_CUSTOM_ARGOYAML}" local ARGOEXEC_IMAGE=argoproj/argoexec:v3.0.3 local ARGOEXEC_IMAGE_USE=ghcr.io/stargz-containers/argoproj-argoexec:v3.0.3-"${IMAGE_TYPE}" replace_image "${TMP_CUSTOM_ARGOYAML}" "${ARGOEXEC_IMAGE}" "${ARGOEXEC_IMAGE_USE}" local MINIO_IMAGE=minio/minio:RELEASE.2019-12-17T23-16-33Z local MINIO_IMAGE_USE=ghcr.io/stargz-containers/minio:RELEASE.2019-12-17T23-16-33Z-"${IMAGE_TYPE}" replace_image "${TMP_CUSTOM_ARGOYAML}" "${MINIO_IMAGE}" "${MINIO_IMAGE_USE}" local ARGOWORKFLOW_IMAGE=argoproj/workflow-controller:v3.0.3 local ARGOWORKFLOW_IMAGE_USE=ghcr.io/stargz-containers/argoproj-workflow-controller:v3.0.3-"${IMAGE_TYPE}" replace_image "${TMP_CUSTOM_ARGOYAML}" "${ARGOWORKFLOW_IMAGE}" "${ARGOWORKFLOW_IMAGE_USE}" local ARGOCLI_IMAGE=argoproj/argocli:v3.0.3 local ARGOCLI_IMAGE_USE=ghcr.io/stargz-containers/argoproj-argocli:v3.0.3-"${IMAGE_TYPE}" replace_image "${TMP_CUSTOM_ARGOYAML}" "${ARGOCLI_IMAGE}" "${ARGOCLI_IMAGE_USE}" cat "${TMP_CUSTOM_ARGOYAML}" rm "${TMP_CUSTOM_ARGOYAML}" } function replace_image() { local TARGET_FILE="${1}" local IMAGE_ORG="${2}" local IMAGE_NEW="${3}" if ! cat "${TARGET_FILE}" | grep "${IMAGE_ORG}" > /dev/null 2>&1 ; then echo "error: image ${IMAGE_ORG} not specified" 1>&2 cat "${TARGET_FILE}" exit 1 fi sed -i "s|${IMAGE_ORG}|${IMAGE_NEW}|g" "${TARGET_FILE}" } function go_ci_yaml() { IMAGE_TYPE="${1}" envsubst < "${CONTEXT}/go.yaml.template" } function run { local IMAGE_TYPE="${1}" local SNAPSHOTTER="${2}" local ELAPSED_RESULT_FILE="${3}" # Prepare cluster configuration local CUSTOM_ARGOYAML=$(mktemp) argo_yaml "${IMAGE_TYPE}" > "${CUSTOM_ARGOYAML}" local TMP_GOCI_YAML=$(mktemp) go_ci_yaml "${IMAGE_TYPE}" > "${TMP_GOCI_YAML}" # Create argo cluster k3d cluster create "${K3S_CLUSTER_NAME}" --image="${K3S_NODE_IMAGE}" \ --k3s-arg='--snapshotter='"${SNAPSHOTTER}"'@server:*;agent:*' kubectl create ns argo kubectl apply -n argo -f "${CUSTOM_ARGOYAML}" # Wait for the cluster is ready local RETRYNUM=30 local RETRYINTERVAL=1 local TIMEOUTSEC=180 for i in $(seq ${RETRYNUM}) ; do if [ $(kubectl get -n argo pods -o json | jq -r '.items[]' | wc -l) -ne 0 ] ; then if [ $(kubectl get -n argo pods -o json | jq '.items[] | select(.status.phase != "Running" and .status.phase != "Succeeded")' | wc -l) -eq 0 ] then echo "argo is ready" break fi fi echo "Waiting for argo is ready..." sleep ${RETRYINTERVAL} done # Run the workflow and get the elapsed time argo submit -n argo --watch "${TMP_GOCI_YAML}" local START=$(argo list -n argo --completed -o json | jq -r '.[0].status.startedAt') local FINISH=$(argo list -n argo --completed -o json | jq -r '.[0].status.finishedAt') local ELAPSED=$(expr $(date --date "${FINISH}" +%s) - $(date --date "${START}" +%s)) echo '{"type" : "'"${IMAGE_TYPE}"'", "snapshotter" : "'"${SNAPSHOTTER}"'", "elapsed" : "'"${ELAPSED}"'"}' | tee -a "${ELAPSED_RESULT_FILE}" # Finalize k3d cluster delete "${K3S_CLUSTER_NAME}" rm "${CUSTOM_ARGOYAML}" rm "${TMP_GOCI_YAML}" } RESULT_FILE="${RESULT:-}" if [ "${RESULT_FILE}" == "" ] ; then RESULT_FILE=$(mktemp) fi echo "result to ${RESULT_FILE}" wget -O "${ORG_ARGOYAML}" https://raw.githubusercontent.com/argoproj/argo-workflows/stable/manifests/quick-start-minimal.yaml git clone -b ${K3S_VERSION} --depth 1 "${K3S_REPO}" "${TMP_K3S_REPO}" cat <> "${TMP_K3S_REPO}/go.mod" replace github.com/containerd/stargz-snapshotter => "$(realpath ${REPO})" replace github.com/containerd/stargz-snapshotter/estargz => "$(realpath ${REPO}/estargz)" EOF sed -i -E 's|(ENV DAPPER_RUN_ARGS .*)|\1 -v '"$(realpath ${REPO})":"$(realpath ${REPO})"':ro|g' "${TMP_K3S_REPO}/Dockerfile.dapper" sed -i -E 's|(ENV DAPPER_ENV .*)|\1 DOCKER_BUILDKIT|g' "${TMP_K3S_REPO}/Dockerfile.dapper" ( cd "${TMP_K3S_REPO}" && \ git config user.email "dummy@example.com" && \ git config user.name "dummy" && \ cat ./.golangci.json | jq '.run.deadline|="10m"' > "${TMP_GOLANGCI}" && \ cp "${TMP_GOLANGCI}" ./.golangci.json && \ make deps && \ git add . && \ git commit -m tmp && \ REPO="${K3S_NODE_REPO}" IMAGE_NAME="${K3S_NODE_IMAGE_NAME}" TAG="${K3S_NODE_TAG}" make ) #1 run "org" "overlayfs" "${RESULT_FILE}" run "esgz" "stargz" "${RESULT_FILE}" #2 run "org" "overlayfs" "${RESULT_FILE}" run "esgz" "stargz" "${RESULT_FILE}" #3 run "org" "overlayfs" "${RESULT_FILE}" run "esgz" "stargz" "${RESULT_FILE}" stargz-snapshotter-0.12.0/script/k3s/000077500000000000000000000000001426301527400174645ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/k3s/create-pod.sh000077500000000000000000000070741426301527400220560ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail REMOTE_SNAPSHOT_LABEL="containerd.io/snapshot/remote" TEST_POD_NAME=testpod-$(head /dev/urandom | tr -dc a-z0-9 | head -c 10) TEST_POD_NS=ns1 TEST_CONTAINER_NAME=testcontainer-$(head /dev/urandom | tr -dc a-z0-9 | head -c 10) K3S_NODENAME="${1}" K3S_KUBECONFIG="${2}" TESTIMAGE="${3}" echo "Creating testing pod...." cat < 100" exit 1 fi ((LAYERSNUM+=1)) LABEL=$(docker exec -i "${K3S_NODENAME}" ctr --namespace="k8s.io" \ snapshots --snapshotter=stargz info "${LAYER}" \ | jq -r ".Labels.\"${REMOTE_SNAPSHOT_LABEL}\"") echo "Checking layer ${LAYER} : ${LABEL}" if [ "${LABEL}" == "null" ] ; then echo "layer ${LAYER} isn't remote snapshot" exit 1 fi done if [ ${LAYERSNUM} -eq 0 ] ; then echo "cannot get layers" exit 1 fi exit 0 stargz-snapshotter-0.12.0/script/k3s/mirror.sh000066400000000000000000000025441426301527400213370ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail SRC="${1}" DST="${2}" SS_REPO="/go/src/github.com/containerd/stargz-snapshotter" RETRYNUM=30 RETRYINTERVAL=1 TIMEOUTSEC=180 function retry { local SUCCESS=false for i in $(seq ${RETRYNUM}) ; do if eval "timeout ${TIMEOUTSEC} ${@}" ; then SUCCESS=true break fi echo "Fail(${i}). Retrying..." sleep ${RETRYINTERVAL} done if [ "${SUCCESS}" == "true" ] ; then return 0 else return 1 fi } update-ca-certificates cd "${SS_REPO}" PREFIX=/out/ make ctr-remote containerd & retry /out/ctr-remote version /out/ctr-remote images pull "${SRC}" /out/ctr-remote images optimize --oci "${SRC}" "${DST}" /out/ctr-remote images push -u "${REGISTRY_CREDS}" "${DST}" stargz-snapshotter-0.12.0/script/k3s/run-k3s.sh000077500000000000000000000064641426301527400213370ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail K3S_VERSION=master K3S_REPO=https://github.com/k3s-io/k3s REGISTRY_HOST=k3s-private-registry K3S_NODE_REPO=ghcr.io/stargz-containers K3S_NODE_IMAGE_NAME=k3s K3S_NODE_TAG=1 K3S_NODE_IMAGE="${K3S_NODE_REPO}/${K3S_NODE_IMAGE_NAME}:${K3S_NODE_TAG}" # Arguments K3S_CLUSTER_NAME="${1}" K3S_USER_KUBECONFIG="${2}" K3S_REGISTRY_CA="${3}" REPO="${4}" REGISTRY_NETWORK="${5}" DOCKERCONFIGJSON_DATA="${6}" TMP_BUILTIN_CONF=$(mktemp) TMP_CONTEXT=$(mktemp -d) SN_KUBECONFIG=$(mktemp) TMP_K3S_REPO=$(mktemp -d) TMP_GOLANGCI=$(mktemp) function cleanup { local ORG_EXIT_CODE="${1}" rm "${SN_KUBECONFIG}" rm -rf "${TMP_CONTEXT}" rm -rf "${TMP_BUILTIN_CONF}" rm -rf "${TMP_K3S_REPO}" rm "${TMP_GOLANGCI}" exit "${ORG_EXIT_CODE}" } trap 'cleanup "$?"' EXIT SIGHUP SIGINT SIGQUIT SIGTERM echo "Preparing node image..." git clone -b ${K3S_VERSION} --depth 1 "${K3S_REPO}" "${TMP_K3S_REPO}" cat <> "${TMP_K3S_REPO}/go.mod" replace github.com/containerd/stargz-snapshotter => "$(realpath ${REPO})" replace github.com/containerd/stargz-snapshotter/estargz => "$(realpath ${REPO}/estargz)" EOF sed -i -E 's|(ENV DAPPER_RUN_ARGS .*)|\1 -v '"$(realpath ${REPO})":"$(realpath ${REPO})"':ro|g' "${TMP_K3S_REPO}/Dockerfile.dapper" sed -i -E 's|(ENV DAPPER_ENV .*)|\1 DOCKER_BUILDKIT|g' "${TMP_K3S_REPO}/Dockerfile.dapper" ( cd "${TMP_K3S_REPO}" && \ git config user.email "dummy@example.com" && \ git config user.name "dummy" && \ cat ./.golangci.json | jq '.run.deadline|="10m"' > "${TMP_GOLANGCI}" && \ cp "${TMP_GOLANGCI}" ./.golangci.json && \ make deps && \ git add . && \ git commit -m tmp && \ REPO="${K3S_NODE_REPO}" IMAGE_NAME="${K3S_NODE_IMAGE_NAME}" TAG="${K3S_NODE_TAG}" make ) cat < "${TMP_BUILTIN_CONF}" configs: ${REGISTRY_HOST}:5000: tls: ca_file: /registry.crt EOF echo "Createing k3s cluster" k3d cluster create "${K3S_CLUSTER_NAME}" --image="${K3S_NODE_IMAGE}" \ --registry-config="${TMP_BUILTIN_CONF}" -v "${K3S_REGISTRY_CA}":/registry.crt:ro \ --k3s-arg='--snapshotter=stargz@server:*;agent:*' k3d kubeconfig get "${K3S_CLUSTER_NAME}" > "${K3S_USER_KUBECONFIG}" K3S_NODENAME="$(k3d node list | grep ${K3S_CLUSTER_NAME}-server-0 | cut -d " " -f 1 | tr -d '\n')" docker network connect "${REGISTRY_NETWORK}" "${K3S_NODENAME}" echo "Configuring kubernetes cluster..." CONFIGJSON_BASE64="$(cat ${DOCKERCONFIGJSON_DATA} | base64 -i -w 0)" cat </dev/null 2>&1 && pwd )/" REPO="${CONTEXT}../../" REGISTRY_HOST=k3s-private-registry REGISTRY_NETWORK=k3s_registry_network DUMMYUSER=dummyuser DUMMYPASS=dummypass TESTIMAGE_ORIGIN="ghcr.io/stargz-containers/ubuntu:20.04" TESTIMAGE="${REGISTRY_HOST}:5000/library/ubuntu:20.04" K3S_CLUSTER_NAME=k3s-stargz-snapshotter PREPARE_NODE_NAME="cri-prepare-node" PREPARE_NODE_IMAGE="cri-prepare-image" source "${REPO}/script/util/utils.sh" if [ "${K3S_NO_RECREATE:-}" != "true" ] ; then echo "Preparing preparation node image..." docker build ${DOCKER_BUILD_ARGS:-} -t "${PREPARE_NODE_IMAGE}" --target containerd-base "${REPO}" fi AUTH_DIR=$(mktemp -d) DOCKERCONFIG=$(mktemp) DOCKER_COMPOSE_YAML=$(mktemp) K3S_KUBECONFIG=$(mktemp) MIRROR_TMP=$(mktemp -d) function cleanup { local ORG_EXIT_CODE="${1}" rm -rf "${AUTH_DIR}" || true rm "${DOCKER_COMPOSE_YAML}" || true rm "${DOCKERCONFIG}" || true rm "${K3S_KUBECONFIG}" || true rm -rf "${MIRROR_TMP}" || true exit "${ORG_EXIT_CODE}" } trap 'cleanup "$?"' EXIT SIGHUP SIGINT SIGQUIT SIGTERM echo "Preparing creds..." prepare_creds "${AUTH_DIR}" "${REGISTRY_HOST}" "${DUMMYUSER}" "${DUMMYPASS}" echo -n '{"auths":{"'"${REGISTRY_HOST}"':5000":{"auth":"'$(echo -n "${DUMMYUSER}:${DUMMYPASS}" | base64 -i -w 0)'"}}}' > "${DOCKERCONFIG}" echo "Preparing private registry..." cat < "${DOCKER_COMPOSE_YAML}" version: "3.5" services: testenv_registry: image: registry:2 container_name: ${REGISTRY_HOST} environment: - HTTP_PROXY=${HTTP_PROXY:-} - HTTPS_PROXY=${HTTPS_PROXY:-} - http_proxy=${http_proxy:-} - https_proxy=${https_proxy:-} - REGISTRY_AUTH=htpasswd - REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" - REGISTRY_AUTH_HTPASSWD_PATH=/auth/auth/htpasswd - REGISTRY_HTTP_TLS_CERTIFICATE=/auth/certs/domain.crt - REGISTRY_HTTP_TLS_KEY=/auth/certs/domain.key volumes: - ${AUTH_DIR}:/auth image-prepare: image: "${PREPARE_NODE_IMAGE}" container_name: "${PREPARE_NODE_NAME}" privileged: true entrypoint: - sleep - infinity tmpfs: - /tmp:exec,mode=777 environment: - REGISTRY_CREDS=${DUMMYUSER}:${DUMMYPASS} volumes: - "k3s-prepare-containerd-data:/var/lib/containerd" - "k3s-prepare-containerd-stargz-grpc-data:/var/lib/containerd-stargz-grpc" - "${AUTH_DIR}/certs/domain.crt:/usr/local/share/ca-certificates/rgst.crt:ro" - "${REPO}:/go/src/github.com/containerd/stargz-snapshotter:ro" - "${MIRROR_TMP}:/tools/" volumes: k3s-prepare-containerd-data: k3s-prepare-containerd-stargz-grpc-data: networks: default: external: name: ${REGISTRY_NETWORK} EOF cp "${REPO}/script/k3s/mirror.sh" "${MIRROR_TMP}/mirror.sh" if ! ( cd "${CONTEXT}" && \ docker network create "${REGISTRY_NETWORK}" && \ docker-compose -f "${DOCKER_COMPOSE_YAML}" up -d --force-recreate && \ docker exec "${PREPARE_NODE_NAME}" /bin/bash /tools/mirror.sh \ "${TESTIMAGE_ORIGIN}" "${TESTIMAGE}" ) ; then echo "Failed to prepare private registry" docker-compose -f "${DOCKER_COMPOSE_YAML}" down -v docker network rm "${REGISTRY_NETWORK}" exit 1 fi echo "Testing in k3s cluster (kubeconfig: ${K3S_KUBECONFIG})..." FAIL= if ! ( "${CONTEXT}"/run-k3s.sh "${K3S_CLUSTER_NAME}" \ "${K3S_KUBECONFIG}" \ "${AUTH_DIR}/certs/domain.crt" \ "${REPO}" \ "${REGISTRY_NETWORK}" \ "${DOCKERCONFIG}" && \ echo "Waiting until secrets fullly synced..." && \ sleep 30 && \ echo "Trying to pull private image with secret..." && \ "${CONTEXT}"/create-pod.sh "$(k3d node list | grep ${K3S_CLUSTER_NAME}-server-0 | cut -d " " -f 1 | tr -d '\n')" \ "${K3S_KUBECONFIG}" "${TESTIMAGE}" ) ; then FAIL=true fi docker-compose -f "${DOCKER_COMPOSE_YAML}" down -v k3d cluster delete "${K3S_CLUSTER_NAME}" docker network rm "${REGISTRY_NETWORK}" if [ "${FAIL}" == "true" ] ; then exit 1 fi exit 0 stargz-snapshotter-0.12.0/script/kind/000077500000000000000000000000001426301527400177115ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/kind/create-pod.sh000077500000000000000000000071511426301527400222770ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail REMOTE_SNAPSHOT_LABEL="containerd.io/snapshot/remote" TEST_POD_NAME=testpod-$(head /dev/urandom | tr -dc a-z0-9 | head -c 10) TEST_POD_NS=ns1 TEST_CONTAINER_NAME=testcontainer-$(head /dev/urandom | tr -dc a-z0-9 | head -c 10) KIND_NODENAME="${1}" KIND_KUBECONFIG="${2}" TESTIMAGE="${3}" echo "Creating testing pod...." cat < 100" exit 1 fi ((LAYERSNUM+=1)) LABEL=$(docker exec -i "${KIND_NODENAME}" ctr-remote --namespace="k8s.io" \ snapshots --snapshotter=stargz info "${LAYER}" \ | jq -r ".Labels.\"${REMOTE_SNAPSHOT_LABEL}\"") echo "Checking layer ${LAYER} : ${LABEL}" if [ "${LABEL}" == "null" ] ; then echo "layer ${LAYER} isn't remote snapshot" exit 1 fi done if [ ${LAYERSNUM} -eq 0 ] ; then echo "cannot get layers" exit 1 fi exit 0 stargz-snapshotter-0.12.0/script/kind/mirror.sh000066400000000000000000000025441426301527400215640ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail SRC="${1}" DST="${2}" SS_REPO="/go/src/github.com/containerd/stargz-snapshotter" RETRYNUM=30 RETRYINTERVAL=1 TIMEOUTSEC=180 function retry { local SUCCESS=false for i in $(seq ${RETRYNUM}) ; do if eval "timeout ${TIMEOUTSEC} ${@}" ; then SUCCESS=true break fi echo "Fail(${i}). Retrying..." sleep ${RETRYINTERVAL} done if [ "${SUCCESS}" == "true" ] ; then return 0 else return 1 fi } update-ca-certificates cd "${SS_REPO}" PREFIX=/out/ make ctr-remote containerd & retry /out/ctr-remote version /out/ctr-remote images pull "${SRC}" /out/ctr-remote images optimize --oci "${SRC}" "${DST}" /out/ctr-remote images push -u "${REGISTRY_CREDS}" "${DST}" stargz-snapshotter-0.12.0/script/kind/run-kind.sh000077500000000000000000000147321426301527400220060ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail NODE_IMAGE_NAME="stargz-snapshotter-node:1" NODE_BASE_IMAGE_NAME="stargz-snapshotter-node-base:1" NODE_TEST_CERT_FILE="/usr/local/share/ca-certificates/registry.crt" SNAPSHOTTER_KUBECONFIG_PATH=/etc/kubernetes/snapshotter/config.conf REGISTRY_HOST=kind-private-registry # Arguments KIND_CLUSTER_NAME="${1}" KIND_USER_KUBECONFIG="${2}" KIND_REGISTRY_CA="${3}" REPO="${4}" REGISTRY_NETWORK="${5}" DOCKERCONFIGJSON_DATA="${6}" TMP_BUILTIN_CONF=$(mktemp) TMP_CONTEXT=$(mktemp -d) SN_KUBECONFIG=$(mktemp) function cleanup { local ORG_EXIT_CODE="${1}" rm "${SN_KUBECONFIG}" rm -rf "${TMP_CONTEXT}" rm -rf "${TMP_BUILTIN_CONF}" exit "${ORG_EXIT_CODE}" } trap 'cleanup "$?"' EXIT SIGHUP SIGINT SIGQUIT SIGTERM if [ "${KIND_NO_RECREATE:-}" != "true" ] ; then echo "Preparing node image..." TARGET_STAGE= if [ "${BUILTIN_SNAPSHOTTER:-}" == "true" ] ; then TARGET_STAGE="--target kind-builtin-snapshotter" fi docker build ${DOCKER_BUILD_ARGS:-} -t "${NODE_BASE_IMAGE_NAME}" ${TARGET_STAGE} "${REPO}" fi # Prepare the testing node with enabling k8s keychain cat <<'EOF' > "${TMP_CONTEXT}/config.stargz.overwrite.toml" [kubeconfig_keychain] enable_keychain = true kubeconfig_path = "/etc/kubernetes/snapshotter/config.conf" EOF cat < "${TMP_CONTEXT}/config.containerd.append.toml" [plugins."io.containerd.grpc.v1.cri".registry.configs."${REGISTRY_HOST}:5000".tls] ca_file = "${NODE_TEST_CERT_FILE}" EOF echo "KUBELET_EXTRA_ARGS=--fail-swap-on=false" > "${TMP_CONTEXT}/kubelet" BUILTIN_HACK_INST= if [ "${BUILTIN_SNAPSHOTTER:-}" == "true" ] ; then # Special configuration for CRI containerd + builtin stargz snapshotter cat < "${TMP_CONTEXT}/containerd.hack.toml" version = 2 [debug] format = "json" level = "debug" [plugins."io.containerd.grpc.v1.cri".containerd] default_runtime_name = "runc" snapshotter = "stargz" disable_snapshot_annotations = false [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc] runtime_type = "io.containerd.runc.v2" [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.test-handler] runtime_type = "io.containerd.runc.v2" [plugins."io.containerd.grpc.v1.cri".registry.configs."${REGISTRY_HOST}:5000".tls] ca_file = "${NODE_TEST_CERT_FILE}" [plugins."io.containerd.snapshotter.v1.stargz".kubeconfig_keychain] enable_keychain = true kubeconfig_path = "/etc/kubernetes/snapshotter/config.conf" [plugins."io.containerd.snapshotter.v1.stargz".registry.configs."${REGISTRY_HOST}:5000".tls] ca_file = "${NODE_TEST_CERT_FILE}" EOF BUILTIN_HACK_INST="COPY containerd.hack.toml /etc/containerd/config.toml" fi cp "${KIND_REGISTRY_CA}" "${TMP_CONTEXT}/registry.crt" cat < "${TMP_CONTEXT}/Dockerfile" FROM ${NODE_BASE_IMAGE_NAME} COPY registry.crt "${NODE_TEST_CERT_FILE}" COPY ./config.stargz.overwrite.toml ./config.containerd.append.toml /tmp/ COPY kubelet /etc/default/kubelet RUN cat /tmp/config.stargz.overwrite.toml > /etc/containerd-stargz-grpc/config.toml && \ cat /tmp/config.containerd.append.toml >> /etc/containerd/config.toml && \ update-ca-certificates ${BUILTIN_HACK_INST} EOF docker build -t "${NODE_IMAGE_NAME}" ${DOCKER_BUILD_ARGS:-} "${TMP_CONTEXT}" # cluster must be single node echo "Cleating kind cluster and connecting to the registry network..." kind create cluster --name "${KIND_CLUSTER_NAME}" \ --kubeconfig "${KIND_USER_KUBECONFIG}" \ --image "${NODE_IMAGE_NAME}" KIND_NODENAME=$(kind get nodes --name "${KIND_CLUSTER_NAME}" | sed -n 1p) # must be single node docker network connect "${REGISTRY_NETWORK}" "${KIND_NODENAME}" echo "===== VERSION INFORMATION =====" docker exec "${KIND_NODENAME}" containerd --version docker exec "${KIND_NODENAME}" runc --version echo "===============================" echo "Configuring kubernetes cluster..." CONFIGJSON_BASE64="$(cat ${DOCKERCONFIGJSON_DATA} | base64 -i -w 0)" cat < "${SN_KUBECONFIG}" apiVersion: v1 kind: Config clusters: - name: default-cluster cluster: certificate-authority-data: ${CA} server: https://${KIND_NODENAME}:${APISERVER_PORT} contexts: - name: default-context context: cluster: default-cluster namespace: default user: default-user current-context: default-context users: - name: default-user user: token: ${TOKEN} EOF docker exec -i "${KIND_NODENAME}" mkdir -p $(dirname "${SNAPSHOTTER_KUBECONFIG_PATH}") docker cp "${SN_KUBECONFIG}" "${KIND_NODENAME}:${SNAPSHOTTER_KUBECONFIG_PATH}" stargz-snapshotter-0.12.0/script/kind/test.sh000077500000000000000000000112161426301527400212300ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail CONTEXT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )/" REPO="${CONTEXT}../../" REGISTRY_HOST=kind-private-registry REGISTRY_NETWORK=kind_registry_network DUMMYUSER=dummyuser DUMMYPASS=dummypass TESTIMAGE_ORIGIN="ghcr.io/stargz-containers/ubuntu:20.04" TESTIMAGE="${REGISTRY_HOST}:5000/library/ubuntu:20.04" KIND_CLUSTER_NAME=kind-stargz-snapshotter PREPARE_NODE_NAME="cri-prepare-node" PREPARE_NODE_IMAGE="cri-prepare-image" source "${REPO}/script/util/utils.sh" if [ "${KIND_NO_RECREATE:-}" != "true" ] ; then echo "Preparing preparation node image..." docker build ${DOCKER_BUILD_ARGS:-} -t "${PREPARE_NODE_IMAGE}" --target containerd-base "${REPO}" fi AUTH_DIR=$(mktemp -d) DOCKERCONFIG=$(mktemp) DOCKER_COMPOSE_YAML=$(mktemp) KIND_KUBECONFIG=$(mktemp) MIRROR_TMP=$(mktemp -d) function cleanup { local ORG_EXIT_CODE="${1}" rm -rf "${AUTH_DIR}" || true rm "${DOCKER_COMPOSE_YAML}" || true rm "${DOCKERCONFIG}" || true rm "${KIND_KUBECONFIG}" || true rm -rf "${MIRROR_TMP}" || true exit "${ORG_EXIT_CODE}" } trap 'cleanup "$?"' EXIT SIGHUP SIGINT SIGQUIT SIGTERM echo "Preparing creds..." prepare_creds "${AUTH_DIR}" "${REGISTRY_HOST}" "${DUMMYUSER}" "${DUMMYPASS}" echo -n '{"auths":{"'"${REGISTRY_HOST}"':5000":{"auth":"'$(echo -n "${DUMMYUSER}:${DUMMYPASS}" | base64 -i -w 0)'"}}}' > "${DOCKERCONFIG}" echo "Preparing private registry..." cat < "${DOCKER_COMPOSE_YAML}" version: "3.5" services: testenv_registry: image: registry:2 container_name: ${REGISTRY_HOST} environment: - HTTP_PROXY=${HTTP_PROXY:-} - HTTPS_PROXY=${HTTPS_PROXY:-} - http_proxy=${http_proxy:-} - https_proxy=${https_proxy:-} - REGISTRY_AUTH=htpasswd - REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" - REGISTRY_AUTH_HTPASSWD_PATH=/auth/auth/htpasswd - REGISTRY_HTTP_TLS_CERTIFICATE=/auth/certs/domain.crt - REGISTRY_HTTP_TLS_KEY=/auth/certs/domain.key volumes: - ${AUTH_DIR}:/auth image-prepare: image: "${PREPARE_NODE_IMAGE}" container_name: "${PREPARE_NODE_NAME}" privileged: true entrypoint: - sleep - infinity tmpfs: - /tmp:exec,mode=777 environment: - REGISTRY_CREDS=${DUMMYUSER}:${DUMMYPASS} volumes: - "kind-prepare-containerd-data:/var/lib/containerd" - "kind-prepare-containerd-stargz-grpc-data:/var/lib/containerd-stargz-grpc" - "${AUTH_DIR}/certs/domain.crt:/usr/local/share/ca-certificates/rgst.crt:ro" - "${REPO}:/go/src/github.com/containerd/stargz-snapshotter:ro" - "${MIRROR_TMP}:/tools/" volumes: kind-prepare-containerd-data: kind-prepare-containerd-stargz-grpc-data: networks: default: external: name: ${REGISTRY_NETWORK} EOF cp "${REPO}/script/kind/mirror.sh" "${MIRROR_TMP}/mirror.sh" if ! ( cd "${CONTEXT}" && \ docker network create "${REGISTRY_NETWORK}" && \ docker-compose -f "${DOCKER_COMPOSE_YAML}" up -d --force-recreate && \ docker exec "${PREPARE_NODE_NAME}" /bin/bash /tools/mirror.sh \ "${TESTIMAGE_ORIGIN}" "${TESTIMAGE}" ) ; then echo "Failed to prepare private registry" docker-compose -f "${DOCKER_COMPOSE_YAML}" down -v docker network rm "${REGISTRY_NETWORK}" exit 1 fi echo "Testing in kind cluster (kubeconfig: ${KIND_KUBECONFIG})..." FAIL= if ! ( "${CONTEXT}"/run-kind.sh "${KIND_CLUSTER_NAME}" \ "${KIND_KUBECONFIG}" \ "${AUTH_DIR}/certs/domain.crt" \ "${REPO}" \ "${REGISTRY_NETWORK}" \ "${DOCKERCONFIG}" && \ echo "Waiting until secrets fullly synced..." && \ sleep 30 && \ echo "Trying to pull private image with secret..." && \ "${CONTEXT}"/create-pod.sh "$(kind get nodes --name "${KIND_CLUSTER_NAME}" | sed -n 1p)" \ "${KIND_KUBECONFIG}" "${TESTIMAGE}" ) ; then FAIL=true fi docker-compose -f "${DOCKER_COMPOSE_YAML}" down -v kind delete cluster --name "${KIND_CLUSTER_NAME}" docker network rm "${REGISTRY_NETWORK}" if [ "${FAIL}" == "true" ] ; then exit 1 fi exit 0 stargz-snapshotter-0.12.0/script/optimize/000077500000000000000000000000001426301527400206245ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/optimize/optimize/000077500000000000000000000000001426301527400224645ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/optimize/optimize/entrypoint.sh000077500000000000000000000251231426301527400252410ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail REGISTRY_HOST=registry-optimize.test DUMMYUSER=dummyuser DUMMYPASS=dummypass ORG_IMAGE_TAG="${REGISTRY_HOST}/test/test:org$(date '+%M%S')" OPT_IMAGE_TAG="${REGISTRY_HOST}/test/test:opt$(date '+%M%S')" NOOPT_IMAGE_TAG="${REGISTRY_HOST}/test/test:noopt$(date '+%M%S')" TOC_JSON_DIGEST_ANNOTATION="containerd.io/snapshot/stargz/toc.digest" UNCOMPRESSED_SIZE_ANNOTATION="io.containers.estargz.uncompressed-size" REMOTE_SNAPSHOTTER_SOCKET=/run/containerd-stargz-grpc/containerd-stargz-grpc.sock ## Image for doing network-related tests # # FROM ubuntu:20.04 # RUN apt-get update && apt-get install -y curl iproute2 # NETWORK_MOUNT_TEST_ORG_IMAGE_TAG="ghcr.io/stargz-containers/ubuntu:20.04-curl-ip" ######################################## RETRYNUM=100 RETRYINTERVAL=1 TIMEOUTSEC=180 function retry { local SUCCESS=false for i in $(seq ${RETRYNUM}) ; do if eval "timeout ${TIMEOUTSEC} ${@}" ; then SUCCESS=true break fi echo "Fail(${i}). Retrying..." sleep ${RETRYINTERVAL} done if [ "${SUCCESS}" == "true" ] ; then return 0 else return 1 fi } function prepare_context { local CONTEXT_DIR="${1}" cat < "${CONTEXT_DIR}/Dockerfile" FROM scratch COPY ./a.txt ./b.txt accessor / COPY ./c.txt ./d.txt / COPY ./e.txt / ENTRYPOINT ["/accessor"] EOF for SAMPLE in "a" "b" "c" "d" "e" ; do echo "${SAMPLE}" > "${CONTEXT_DIR}/${SAMPLE}.txt" done mkdir -p "${GOPATH}/src/test/test" && \ cat <<'EOF' > "${GOPATH}/src/test/test/main.go" package main import ( "os" ) func main() { targets := []string{"/a.txt", "/c.txt"} for _, t := range targets { f, err := os.Open(t) if err != nil { panic("failed to open file") } f.Close() } } EOF GO111MODULE=off go build -ldflags '-extldflags "-static"' -o "${CONTEXT_DIR}/accessor" "${GOPATH}/src/test/test" } function validate_toc_json { local MANIFEST=${1} local LAYER_NUM=${2} TOCJSON_ANNOTATION="$(cat ${MANIFEST} | jq -r '.layers['"${LAYER_NUM}"'].annotations."'${TOC_JSON_DIGEST_ANNOTATION}'"')" LAYER_DIGEST="$(cat ${MANIFEST} | jq -r '.layers['"${LAYER_NUM}"'].digest')" TOCJSON_DIGEST="$(/tmp/out/ctr-remote ${GETTOCDIGEST_COMMAND} ${LAYER_DIGEST})" if [ "${TOCJSON_ANNOTATION}" != "${TOCJSON_DIGEST}" ] ; then echo "Invalid TOC JSON (layer:${LAYER_NUM}): want ${TOCJSON_ANNOTATION}; got: ${TOCJSON_DIGEST}" return 1 fi echo "Valid TOC JSON (layer:${LAYER_NUM}) ${TOCJSON_ANNOTATION} == ${TOCJSON_DIGEST}" return 0 } function check_uncompressed_size { local MANIFEST=${1} local LAYER_NUM=${2} local LAYER_TAR=${3} SIZE_ANNOTATION="$(cat ${MANIFEST} | jq -r '.layers['"${LAYER_NUM}"'].annotations."'${UNCOMPRESSED_SIZE_ANNOTATION}'"')" SIZE=$(cat "${LAYER_TAR}" | ${DECOMPRESS_COMMAND} | wc -c) if [ "${SIZE_ANNOTATION}" != "${SIZE}" ] ; then echo "Invalid uncompressed size (layer:${LAYER_NUM}): want ${SIZE_ANNOTATION}; got: ${SIZE}" return 1 fi echo "Valid uncompressed size (layer:${LAYER_NUM}) ${SIZE_ANNOTATION} == ${SIZE}" return 0 } function check_optimization { local TARGET=${1} LOCAL_WORKING_DIR="${WORKING_DIR}/$(date '+%H%M%S')" mkdir "${LOCAL_WORKING_DIR}" nerdctl pull "${TARGET}" && nerdctl save "${TARGET}" | tar xv -C "${LOCAL_WORKING_DIR}" LAYERS="$(cat "${LOCAL_WORKING_DIR}/manifest.json" | jq -r '.[0].Layers[]')" echo "Checking layers..." GOTNUM=0 for L in ${LAYERS}; do tar --list -f "${LOCAL_WORKING_DIR}/${L}" | tee "${LOCAL_WORKING_DIR}/${GOTNUM}" ((GOTNUM+=1)) done WANTNUM=0 for W in "${@:2}"; do cp "${W}" "${LOCAL_WORKING_DIR}/${WANTNUM}-want" ((WANTNUM+=1)) done if [ "${GOTNUM}" != "${WANTNUM}" ] ; then echo "invalid numbers of layers ${GOTNUM}; want ${WANTNUM}" return 1 fi for ((I=0; I < WANTNUM; I++)) ; do echo "Validating tarball contents of layer ${I}..." diff "${LOCAL_WORKING_DIR}/${I}" "${LOCAL_WORKING_DIR}/${I}-want" done crane manifest "${TARGET}" | tee "${LOCAL_WORKING_DIR}/dist-manifest.json" && echo "" INDEX=0 for L in ${LAYERS}; do echo "Validating TOC JSON digest of layer ${INDEX}..." validate_toc_json "${LOCAL_WORKING_DIR}/dist-manifest.json" "${INDEX}" check_uncompressed_size "${LOCAL_WORKING_DIR}/dist-manifest.json" \ "${INDEX}" \ "${LOCAL_WORKING_DIR}/${L}" ((INDEX+=1)) done return 0 } function append_toc { local TARGET="${1}" if [ "${INVISIBLE_TOC}" != "true" ] ; then echo "stargz.index.json" >> "${TARGET}" fi } echo "===== VERSION INFORMATION =====" containerd --version runc --version echo "===============================" echo "Logging into the registry..." cp /auth/certs/domain.crt /usr/local/share/ca-certificates update-ca-certificates retry nerdctl login -u "${DUMMYUSER}" -p "${DUMMYPASS}" "https://${REGISTRY_HOST}" echo "Running containerd and BuildKit..." buildkitd --oci-cni-binary-dir=/opt/tmp/cni/bin & containerd --log-level debug & retry buildctl du retry nerdctl version echo "Building sample image for testing..." CONTEXT_DIR=$(mktemp -d) prepare_context "${CONTEXT_DIR}" echo "Preparing sample image..." nerdctl build -t "${ORG_IMAGE_TAG}" "${CONTEXT_DIR}" nerdctl push "${ORG_IMAGE_TAG}" echo "Loading original image" nerdctl pull "${NETWORK_MOUNT_TEST_ORG_IMAGE_TAG}" nerdctl pull "${ORG_IMAGE_TAG}" echo "Checking optimized image..." WORKING_DIR=$(mktemp -d) PREFIX=/tmp/out/ make clean PREFIX=/tmp/out/ GO_BUILD_FLAGS="-race" make ctr-remote # Check data race /tmp/out/ctr-remote ${OPTIMIZE_COMMAND} -entrypoint='[ "/accessor" ]' "${ORG_IMAGE_TAG}" "${OPT_IMAGE_TAG}" nerdctl push "${OPT_IMAGE_TAG}" || true cat < "${WORKING_DIR}/0-want" accessor a.txt .prefetch.landmark b.txt EOF append_toc "${WORKING_DIR}/0-want" cat < "${WORKING_DIR}/1-want" c.txt .prefetch.landmark d.txt EOF append_toc "${WORKING_DIR}/1-want" cat < "${WORKING_DIR}/2-want" .no.prefetch.landmark e.txt EOF append_toc "${WORKING_DIR}/2-want" check_optimization "${OPT_IMAGE_TAG}" \ "${WORKING_DIR}/0-want" \ "${WORKING_DIR}/1-want" \ "${WORKING_DIR}/2-want" echo "Checking non-optimized image..." /tmp/out/ctr-remote ${NO_OPTIMIZE_COMMAND} "${ORG_IMAGE_TAG}" "${NOOPT_IMAGE_TAG}" nerdctl push "${NOOPT_IMAGE_TAG}" || true cat < "${WORKING_DIR}/0-want" .no.prefetch.landmark a.txt accessor b.txt EOF append_toc "${WORKING_DIR}/0-want" cat < "${WORKING_DIR}/1-want" .no.prefetch.landmark c.txt d.txt EOF append_toc "${WORKING_DIR}/1-want" cat < "${WORKING_DIR}/2-want" .no.prefetch.landmark e.txt EOF append_toc "${WORKING_DIR}/2-want" check_optimization "${NOOPT_IMAGE_TAG}" \ "${WORKING_DIR}/0-want" \ "${WORKING_DIR}/1-want" \ "${WORKING_DIR}/2-want" # Test networking & mounting work # Make bridge plugin manipulate iptables instead of nftables as this test runs # in a Docker container that network is configured with iptables. # c.f. https://github.com/moby/moby/issues/26824 update-alternatives --set iptables /usr/sbin/iptables-legacy # Try to connect to the internet from the container # CNI-related files are installed to irregular paths (see Dockerfile for more details). # Check if these files are recognized through flags. TESTDIR=$(mktemp -d) /tmp/out/ctr-remote ${OPTIMIZE_COMMAND} \ --period=20 \ --cni \ --cni-plugin-conf-dir='/etc/tmp/cni/net.d' \ --cni-plugin-dir='/opt/tmp/cni/bin' \ --add-hosts='testhost:1.2.3.4,test2:5.6.7.8' \ --dns-nameservers='8.8.8.8' \ --mount="type=bind,src=${TESTDIR},dst=/mnt,options=bind" \ --entrypoint='[ "/bin/bash", "-c" ]' \ --args='[ "curl example.com > /mnt/result_page && ip a show dev eth0 ; echo -n $? > /mnt/if_exists && ip a > /mnt/if_info && cat /etc/hosts > /mnt/hosts" ]' \ "${NETWORK_MOUNT_TEST_ORG_IMAGE_TAG}" "${REGISTRY_HOST}/test:1" # Check if all contents are successfuly passed if ! [ -f "${TESTDIR}/if_exists" ] || \ ! [ -f "${TESTDIR}/result_page" ] || \ ! [ -f "${TESTDIR}/if_info" ] || \ ! [ -f "${TESTDIR}/hosts" ]; then echo "the result files not found; bind-mount might not work" exit 1 fi # Check if /etc/hosts contains expected contents if [ "$(cat ${TESTDIR}/hosts | grep testhost | sed -E 's/([0-9.]*).*/\1/')" != "1.2.3.4" ] || \ [ "$(cat ${TESTDIR}/hosts | grep test2 | sed -E 's/([0-9.]*).*/\1/')" != "5.6.7.8" ]; then echo "invalid contents in /etc/hosts" cat "${TESTDIR}/hosts" exit 1 fi echo "hosts configured:" cat "${TESTDIR}/hosts" # Check if the interface is created by the bridge plugin if [ "$(cat ${TESTDIR}/if_exists)" != "0" ] ; then echo "interface didn't configured:" cat "${TESTDIR}/if_exists" echo "interface info:" cat "${TESTDIR}/if_info" exit 1 fi echo "Interface created:" cat "${TESTDIR}/if_info" # Check if the contents are downloaded from the internet SAMPLE_PAGE=$(mktemp) curl example.com > "${SAMPLE_PAGE}" if ! [ -s "${SAMPLE_PAGE}" ] ; then echo "sample page file is empty; failed to get the contents of example.com; check the internet connection" exit 1 fi echo "sample contents of example.com" cat "${SAMPLE_PAGE}" SAMPLE_PAGE_SHA256=$(cat "${SAMPLE_PAGE}" | sha256sum | sed -E 's/([^ ]*).*/sha256:\1/g') RESULT_PAGE_SHA256=$(cat "${TESTDIR}/result_page" | sha256sum | sed -E 's/([^ ]*).*/sha256:\1/g') if [ "${SAMPLE_PAGE_SHA256}" != "${RESULT_PAGE_SHA256}" ] ; then echo "failed to get expected contents from the internet, inside the container: ${SAMPLE_PAGE_SHA256} != ${RESULT_PAGE_SHA256}" echo "got contetns:" cat "${TESTDIR}/result_page" exit 1 fi echo "expected contents successfly downloaded from the internet, in the container. contents:" cat "${TESTDIR}/result_page" exit 0 stargz-snapshotter-0.12.0/script/optimize/test.sh000077500000000000000000000133471426301527400221520ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail CONTEXT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )/" REPO="${CONTEXT}../../" REGISTRY_HOST=registry-optimize.test REPO_PATH=/go/src/github.com/containerd/stargz-snapshotter DUMMYUSER=dummyuser DUMMYPASS=dummypass OPTIMIZE_BASE_IMAGE_NAME="optimize-image-base" OPTIMIZE_TEST_IMAGE_NAME="optimize-image-test" source "${REPO}/script/util/utils.sh" CNI_PLUGINS_VERSION=$(get_version_from_arg "${REPO}/Dockerfile" "CNI_PLUGINS_VERSION") NERDCTL_VERSION=$(get_version_from_arg "${REPO}/Dockerfile" "NERDCTL_VERSION") if [ "${OPTIMIZE_NO_RECREATE:-}" != "true" ] ; then echo "Preparing node image..." # Enable to check race docker build ${DOCKER_BUILD_ARGS:-} -t "${OPTIMIZE_BASE_IMAGE_NAME}" \ --target snapshotter-base \ --build-arg=SNAPSHOTTER_BUILD_FLAGS="-race" \ "${REPO}" fi DOCKER_COMPOSE_YAML=$(mktemp) AUTH_DIR=$(mktemp -d) TMP_CONTEXT=$(mktemp -d) function cleanup { local ORG_EXIT_CODE="${1}" rm "${DOCKER_COMPOSE_YAML}" || true rm -rf "${AUTH_DIR}" || true rm -rf "${TMP_CONTEXT}" || true exit "${ORG_EXIT_CODE}" } trap 'cleanup "$?"' EXIT SIGHUP SIGINT SIGQUIT SIGTERM cat <<'EOF' > "${TMP_CONTEXT}/test.conflist" { "cniVersion": "0.4.0", "name": "test", "plugins" : [{ "type": "bridge", "bridge": "test0", "isDefaultGateway": true, "forceAddress": false, "ipMasq": true, "hairpinMode": true, "ipam": { "type": "host-local", "subnet": "10.10.0.0/16" } }, { "type": "loopback" }] } EOF cat < "${TMP_CONTEXT}/Dockerfile" # 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". FROM ${OPTIMIZE_BASE_IMAGE_NAME} ARG TARGETARCH RUN apt-get update -y && \ apt-get --no-install-recommends install -y jq iptables zstd && \ go install github.com/google/go-containerregistry/cmd/crane@v0.8.0 && \ mkdir -p /opt/tmp/cni/bin /etc/tmp/cni/net.d && \ curl -Ls https://github.com/containernetworking/plugins/releases/download/v${CNI_PLUGINS_VERSION}/cni-plugins-linux-\${TARGETARCH:-amd64}-v${CNI_PLUGINS_VERSION}.tgz | tar xzv -C /opt/tmp/cni/bin && \ curl -sSL https://github.com/containerd/nerdctl/releases/download/v${NERDCTL_VERSION}/nerdctl-full-${NERDCTL_VERSION}-linux-\${TARGETARCH:-amd64}.tar.gz | tar -C /usr/local -zx bin/buildkitd bin/buildctl # Installs CNI-related files to irregular paths (/opt/tmp/cni/bin and /etc/tmp/cni/net.d) for test. # see entrypoint.sh for more details. COPY ./test.conflist /etc/tmp/cni/net.d/test.conflist EOF docker build -t "${OPTIMIZE_TEST_IMAGE_NAME}" ${DOCKER_BUILD_ARGS:-} "${TMP_CONTEXT}" echo "Preparing creds..." prepare_creds "${AUTH_DIR}" "${REGISTRY_HOST}" "${DUMMYUSER}" "${DUMMYPASS}" echo "Testing..." function test_optimize { local OPTIMIZE_COMMAND="${1}" local NO_OPTIMIZE_COMMAND="${2}" local GETTOCDIGEST_COMMAND="${3}" local DECOMPRESS_COMMAND="${4}" local INVISIBLE_TOC="${5}" cat < "${DOCKER_COMPOSE_YAML}" version: "3.3" services: testenv_opt: image: ${OPTIMIZE_TEST_IMAGE_NAME} container_name: testenv_opt privileged: true working_dir: ${REPO_PATH} entrypoint: ./script/optimize/optimize/entrypoint.sh environment: - NO_PROXY=127.0.0.1,localhost,${REGISTRY_HOST}:443 - OPTIMIZE_COMMAND=${OPTIMIZE_COMMAND} - NO_OPTIMIZE_COMMAND=${NO_OPTIMIZE_COMMAND} - GETTOCDIGEST_COMMAND=${GETTOCDIGEST_COMMAND} - DECOMPRESS_COMMAND=${DECOMPRESS_COMMAND} - INVISIBLE_TOC=${INVISIBLE_TOC} tmpfs: - /tmp:exec,mode=777 volumes: - "${REPO}:${REPO_PATH}:ro" - ${AUTH_DIR}:/auth:ro - "optimize-containerd-data:/var/lib/containerd" - "optimize-containerd-stargz-grpc-data:/var/lib/containerd-stargz-grpc" - "optimize-buildkit-data:/var/lib/buildkit" registry: image: registry:2 container_name: ${REGISTRY_HOST} environment: - REGISTRY_AUTH=htpasswd - REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" - REGISTRY_AUTH_HTPASSWD_PATH=/auth/auth/htpasswd - REGISTRY_HTTP_TLS_CERTIFICATE=/auth/certs/domain.crt - REGISTRY_HTTP_TLS_KEY=/auth/certs/domain.key - REGISTRY_HTTP_ADDR=${REGISTRY_HOST}:443 volumes: - ${AUTH_DIR}:/auth:ro volumes: optimize-containerd-data: optimize-containerd-stargz-grpc-data: optimize-buildkit-data: EOF local FAIL= if ! ( cd "${CONTEXT}" && \ docker-compose -f "${DOCKER_COMPOSE_YAML}" build ${DOCKER_BUILD_ARGS:-} testenv_opt && \ docker-compose -f "${DOCKER_COMPOSE_YAML}" up --abort-on-container-exit ) ; then FAIL=true fi docker-compose -f "${DOCKER_COMPOSE_YAML}" down -v if [ "${FAIL}" == "true" ] ; then exit 1 fi } test_optimize "image optimize --oci --zstdchunked" \ "image optimize --no-optimize --oci --zstdchunked" \ "image get-toc-digest --zstdchunked" \ "zstd -d" \ "true" test_optimize "image optimize --oci" \ "image optimize --no-optimize --oci" \ "image get-toc-digest" \ "gunzip" \ "false" exit 0 stargz-snapshotter-0.12.0/script/util/000077500000000000000000000000001426301527400177415ustar00rootroot00000000000000stargz-snapshotter-0.12.0/script/util/make.sh000077500000000000000000000030121426301527400212110ustar00rootroot00000000000000#!/bin/bash # 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. set -euo pipefail CONTEXT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )/" REPO="${CONTEXT}../../" IMAGE_NAME="minienv" source "${REPO}/script/util/utils.sh" GOBASE_VERSION=$(go_base_version "${REPO}/Dockerfile") TMP_CONTEXT=$(mktemp -d) function cleanup { local ORG_EXIT_CODE="${1}" rm -rf "${TMP_CONTEXT}" || true exit "${ORG_EXIT_CODE}" } trap 'cleanup "$?"' EXIT SIGHUP SIGINT SIGQUIT SIGTERM cat < "${TMP_CONTEXT}/Dockerfile" FROM golang:${GOBASE_VERSION} RUN apt-get update -y && apt-get --no-install-recommends install -y fuse EOF docker build -t "${IMAGE_NAME}" ${DOCKER_BUILD_ARGS:-} "${TMP_CONTEXT}" docker run --rm --privileged \ --device /dev/fuse \ --tmpfs /tmp:exec,mode=777 \ -w /go/src/github.com/containerd/stargz-snapshotter \ -v "${REPO}:/go/src/github.com/containerd/stargz-snapshotter:ro" \ "${IMAGE_NAME}" make ${@} PREFIX=/tmp/out/ stargz-snapshotter-0.12.0/script/util/utils.sh000066400000000000000000000054251426301527400214430ustar00rootroot00000000000000#!/bin/bash # 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. # Preparing creds for provided user and password for private registry(only for testing purpose) # See also: https://docs.docker.com/registry/deploying/ function prepare_creds { local OUTPUT="${1}" local REGISTRY_HOST="${2}" local USER="${3}" local PASS="${4}" mkdir "${OUTPUT}/auth" "${OUTPUT}/certs" openssl req -subj "/C=JP/ST=Remote/L=Snapshotter/O=TestEnv/OU=Integration/CN=${REGISTRY_HOST}" \ -addext "subjectAltName = DNS:${REGISTRY_HOST}" \ -newkey rsa:2048 -nodes -keyout "${OUTPUT}/certs/domain.key" \ -x509 -days 365 -out "${OUTPUT}/certs/domain.crt" htpasswd -Bbn "${USER}" "${PASS}" > "${OUTPUT}/auth/htpasswd" } # Check if all snapshots logged in the specified file are prepared as remote snapshots. # Whether a snapshot is prepared as a remote snapshot must be logged with the key # "remote-snapshot-prepared" in JSON-formatted log. # See also /snapshot/snapshot.go in this repo. LOG_REMOTE_SNAPSHOT="remote-snapshot-prepared" function check_remote_snapshots { local LOG_FILE="${1}" local REMOTE=0 local LOCAL=0 REMOTE=$(jq -r 'select(."'"${LOG_REMOTE_SNAPSHOT}"'" == "true")' "${LOG_FILE}" | wc -l) LOCAL=$(jq -r 'select(."'"${LOG_REMOTE_SNAPSHOT}"'" == "false")' "${LOG_FILE}" | wc -l) if [[ ${LOCAL} -gt 0 ]] ; then echo "some local snapshots creation have been reported (local:${LOCAL},remote:${REMOTE})" return 1 elif [[ ${REMOTE} -gt 0 ]] ; then echo "all layers have been reported as remote snapshots (local:${LOCAL},remote:${REMOTE})" return 0 else echo "no log for checking remote snapshot was provided; Is the log-level = debug?" return 1 fi } # Get version from ARG directive in the specified Dockerfile. function get_version_from_arg { local DOCKERFILE="${1}" local ARGNAME="${2}" cat "${DOCKERFILE}" | grep "${ARGNAME}=" | head -1 | sed -E 's/ARG +'"${ARGNAME}"'=v?([^ ]+).*/\1/g' | tr -d '\n' } # Get version of golang base image from the specified Dockerfile. function go_base_version { local DOCKERFILE="${1}" cat "${DOCKERFILE}" | grep -E 'FROM\s+golang:' | head -1 | sed -E 's/FROM +[^:]*:([^ ]+).*/\1/g' | tr -d '\n' } stargz-snapshotter-0.12.0/service/000077500000000000000000000000001426301527400171205ustar00rootroot00000000000000stargz-snapshotter-0.12.0/service/config.go000066400000000000000000000035651426301527400207250ustar00rootroot00000000000000/* 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 service import ( "github.com/containerd/stargz-snapshotter/fs/config" "github.com/containerd/stargz-snapshotter/service/resolver" ) type Config struct { config.Config // KubeconfigKeychainConfig is config for kubeconfig-based keychain. KubeconfigKeychainConfig `toml:"kubeconfig_keychain"` // CRIKeychainConfig is config for CRI-based keychain. CRIKeychainConfig `toml:"cri_keychain"` // ResolverConfig is config for resolving registries. ResolverConfig `toml:"resolver"` } // KubeconfigKeychainConfig is config for kubeconfig-based keychain. type KubeconfigKeychainConfig struct { // EnableKeychain enables kubeconfig-based keychain EnableKeychain bool `toml:"enable_keychain"` // KubeconfigPath is the path to kubeconfig which can be used to sync // secrets on the cluster into this snapshotter. KubeconfigPath string `toml:"kubeconfig_path"` } // CRIKeychainConfig is config for CRI-based keychain. type CRIKeychainConfig struct { // EnableKeychain enables CRI-based keychain EnableKeychain bool `toml:"enable_keychain"` // ImageServicePath is the path to the unix socket of backing CRI Image Service (e.g. containerd CRI plugin) ImageServicePath string `toml:"image_service_path"` } // ResolverConfig is config for resolving registries. type ResolverConfig resolver.Config stargz-snapshotter-0.12.0/service/cri.go000066400000000000000000000050211426301527400202220ustar00rootroot00000000000000/* 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 service import ( "fmt" "strings" "github.com/containerd/containerd/reference" "github.com/containerd/stargz-snapshotter/fs/source" digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) const ( // targetRefLabel is a label which contains image reference passed from CRI plugin. targetRefLabel = "containerd.io/snapshot/cri.image-ref" // targetDigestLabel is a label which contains layer digest passed from CRI plugin. targetDigestLabel = "containerd.io/snapshot/cri.layer-digest" // targetImageLayersLabel is a label which contains layer digests contained in // the target image and is passed from CRI plugin. targetImageLayersLabel = "containerd.io/snapshot/cri.image-layers" ) func sourceFromCRILabels(hosts source.RegistryHosts) source.GetSources { return func(labels map[string]string) ([]source.Source, error) { refStr, ok := labels[targetRefLabel] if !ok { return nil, fmt.Errorf("reference hasn't been passed") } refspec, err := reference.Parse(refStr) if err != nil { return nil, err } digestStr, ok := labels[targetDigestLabel] if !ok { return nil, fmt.Errorf("digest hasn't been passed") } target, err := digest.Parse(digestStr) if err != nil { return nil, err } var layersDgst []digest.Digest if l, ok := labels[targetImageLayersLabel]; ok { layersStr := strings.Split(l, ",") for _, l := range layersStr { d, err := digest.Parse(l) if err != nil { return nil, err } if d.String() != target.String() { layersDgst = append(layersDgst, d) } } } var layers []ocispec.Descriptor for _, dgst := range append([]digest.Digest{target}, layersDgst...) { layers = append(layers, ocispec.Descriptor{Digest: dgst}) } return []source.Source{ { Hosts: hosts, Name: refspec, Target: ocispec.Descriptor{Digest: target}, Manifest: ocispec.Manifest{Layers: layers}, }, }, nil } } stargz-snapshotter-0.12.0/service/keychain/000077500000000000000000000000001426301527400207135ustar00rootroot00000000000000stargz-snapshotter-0.12.0/service/keychain/cri/000077500000000000000000000000001426301527400214705ustar00rootroot00000000000000stargz-snapshotter-0.12.0/service/keychain/cri/cri.go000066400000000000000000000110371426301527400225760ustar00rootroot00000000000000/* 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 cri import ( "context" "errors" "fmt" "sync" "time" "github.com/containerd/containerd/log" "github.com/containerd/containerd/reference" distribution "github.com/containerd/containerd/reference/docker" "github.com/containerd/stargz-snapshotter/service/resolver" runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2" ) // NewCRIKeychain provides creds passed through CRI PullImage API. // This also returns a CRI image service server that works as a proxy backed by the specified CRI service. // This server reads all PullImageRequest and uses PullImageRequest.AuthConfig for authenticating snapshots. func NewCRIKeychain(ctx context.Context, connectCRI func() (runtime.ImageServiceClient, error)) (resolver.Credential, runtime.ImageServiceServer) { server := &instrumentedService{config: make(map[string]*runtime.AuthConfig)} go func() { log.G(ctx).Debugf("Waiting for CRI service is started...") for i := 0; i < 100; i++ { client, err := connectCRI() if err == nil { server.criMu.Lock() server.cri = client server.criMu.Unlock() log.G(ctx).Info("connected to backend CRI service") return } log.G(ctx).WithError(err).Warnf("failed to connect to CRI") time.Sleep(10 * time.Second) } log.G(ctx).Warnf("no connection is available to CRI") }() return server.credentials, server } type instrumentedService struct { cri runtime.ImageServiceClient criMu sync.Mutex config map[string]*runtime.AuthConfig configMu sync.Mutex } func (in *instrumentedService) credentials(host string, refspec reference.Spec) (string, string, error) { if host == "docker.io" || host == "registry-1.docker.io" { // Creds of "docker.io" is stored keyed by "https://index.docker.io/v1/". host = "index.docker.io" } in.configMu.Lock() defer in.configMu.Unlock() if cfg, ok := in.config[refspec.String()]; ok { return resolver.ParseAuth(cfg, host) } return "", "", nil } func (in *instrumentedService) getCRI() (c runtime.ImageServiceClient) { in.criMu.Lock() c = in.cri in.criMu.Unlock() return } func (in *instrumentedService) ListImages(ctx context.Context, r *runtime.ListImagesRequest) (res *runtime.ListImagesResponse, err error) { cri := in.getCRI() if cri == nil { return nil, errors.New("server is not initialized yet") } return cri.ListImages(ctx, r) } func (in *instrumentedService) ImageStatus(ctx context.Context, r *runtime.ImageStatusRequest) (res *runtime.ImageStatusResponse, err error) { cri := in.getCRI() if cri == nil { return nil, errors.New("server is not initialized yet") } return cri.ImageStatus(ctx, r) } func (in *instrumentedService) PullImage(ctx context.Context, r *runtime.PullImageRequest) (res *runtime.PullImageResponse, err error) { cri := in.getCRI() if cri == nil { return nil, errors.New("server is not initialized yet") } refspec, err := parseReference(r.GetImage().GetImage()) if err != nil { return nil, err } in.configMu.Lock() in.config[refspec.String()] = r.GetAuth() in.configMu.Unlock() return cri.PullImage(ctx, r) } func (in *instrumentedService) RemoveImage(ctx context.Context, r *runtime.RemoveImageRequest) (_ *runtime.RemoveImageResponse, err error) { cri := in.getCRI() if cri == nil { return nil, errors.New("server is not initialized yet") } refspec, err := parseReference(r.GetImage().GetImage()) if err != nil { return nil, err } in.configMu.Lock() delete(in.config, refspec.String()) in.configMu.Unlock() return cri.RemoveImage(ctx, r) } func (in *instrumentedService) ImageFsInfo(ctx context.Context, r *runtime.ImageFsInfoRequest) (res *runtime.ImageFsInfoResponse, err error) { cri := in.getCRI() if cri == nil { return nil, errors.New("server is not initialized yet") } return cri.ImageFsInfo(ctx, r) } func parseReference(ref string) (reference.Spec, error) { namedRef, err := distribution.ParseDockerRef(ref) if err != nil { return reference.Spec{}, fmt.Errorf("failed to parse image reference %q: %w", ref, err) } return reference.Parse(namedRef.String()) } stargz-snapshotter-0.12.0/service/keychain/dockerconfig/000077500000000000000000000000001426301527400233505ustar00rootroot00000000000000stargz-snapshotter-0.12.0/service/keychain/dockerconfig/dockerconfig.go000066400000000000000000000027251426301527400263420ustar00rootroot00000000000000/* 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 dockerconfig import ( "context" "github.com/containerd/containerd/log" "github.com/containerd/containerd/reference" "github.com/containerd/stargz-snapshotter/service/resolver" "github.com/docker/cli/cli/config" ) func NewDockerconfigKeychain(ctx context.Context) resolver.Credential { return func(host string, refspec reference.Spec) (string, string, error) { cf, err := config.Load("") if err != nil { log.G(ctx).WithError(err).Warnf("failed to load docker config file") return "", "", nil } if host == "docker.io" || host == "registry-1.docker.io" { // Creds of docker.io is stored keyed by "https://index.docker.io/v1/". host = "https://index.docker.io/v1/" } ac, err := cf.GetAuthConfig(host) if err != nil { return "", "", err } if ac.IdentityToken != "" { return "", ac.IdentityToken, nil } return ac.Username, ac.Password, nil } } stargz-snapshotter-0.12.0/service/keychain/kubeconfig/000077500000000000000000000000001426301527400230275ustar00rootroot00000000000000stargz-snapshotter-0.12.0/service/keychain/kubeconfig/kubeconfig.go000066400000000000000000000175031426301527400255000ustar00rootroot00000000000000/* 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 kubeconfig import ( "bytes" "context" "fmt" "os" "sync" "time" "github.com/containerd/containerd/log" "github.com/containerd/containerd/reference" "github.com/containerd/stargz-snapshotter/service/resolver" dcfile "github.com/docker/cli/cli/config/configfile" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/workqueue" ) const dockerconfigSelector = "type=" + string(corev1.SecretTypeDockerConfigJson) type options struct { kubeconfigPath string } type Option func(*options) func WithKubeconfigPath(path string) Option { return func(opts *options) { opts.kubeconfigPath = path } } // NewKubeconfigKeychain provides a keychain which can sync its contents with // kubernetes API server by fetching all `kubernetes.io/dockerconfigjson` // secrets in the cluster with provided kubeconfig. It's OK that config provides // kubeconfig path but the file doesn't exist at that moment. In this case, this // keychain keeps on trying to read the specified path periodically and when the // file is actually provided, this keychain tries to access API server using the // file. This is useful for some environments (e.g. single node cluster with // containerized apiserver) where stargz snapshotter needs to start before // everything, including booting containerd/kubelet/apiserver and configuring // users/roles. // TODO: support update of kubeconfig file func NewKubeconfigKeychain(ctx context.Context, opts ...Option) resolver.Credential { var kcOpts options for _, o := range opts { o(&kcOpts) } kc := newKeychain(ctx, kcOpts.kubeconfigPath) return kc.credentials } func newKeychain(ctx context.Context, kubeconfigPath string) *keychain { kc := &keychain{ config: make(map[string]*dcfile.ConfigFile), } ctx = log.WithLogger(ctx, log.G(ctx).WithField("kubeconfig", kubeconfigPath)) go func() { if kubeconfigPath != "" { log.G(ctx).Debugf("Waiting for kubeconfig being installed...") for { if _, err := os.Stat(kubeconfigPath); err == nil { break } else if !os.IsNotExist(err) { log.G(ctx).WithError(err). Warnf("failed to read; Disabling syncing") return } time.Sleep(10 * time.Second) } } // default loader for KUBECONFIG or `~/.kube/config` // if no explicit path provided, KUBECONFIG will be used. // if KUBECONFIG doesn't contain paths, `~/.kube/config` will be used. loadingRule := clientcmd.NewDefaultClientConfigLoadingRules() // explicitly provide path for kubeconfig. // if path isn't "", this path will be respected. loadingRule.ExplicitPath = kubeconfigPath // load and merge config files clientcfg, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( loadingRule, // loader for config files &clientcmd.ConfigOverrides{}, // no overrides for config ).ClientConfig() if err != nil { log.G(ctx).WithError(err).Warnf("failed to load config; Disabling syncing") return } client, err := kubernetes.NewForConfig(clientcfg) if err != nil { log.G(ctx).WithError(err).Warnf("failed to prepare client; Disabling syncing") return } if err := kc.startSyncSecrets(ctx, client); err != nil { log.G(ctx).WithError(err).Warnf("failed to sync secrets") } }() return kc } type keychain struct { config map[string]*dcfile.ConfigFile configMu sync.Mutex // the following entries are used for syncing secrets with API server. // these fields are lazily filled after kubeconfig file is provided. queue *workqueue.Type informer cache.SharedIndexInformer } func (kc *keychain) credentials(host string, refspec reference.Spec) (string, string, error) { if host == "docker.io" || host == "registry-1.docker.io" { // Creds of "docker.io" is stored keyed by "https://index.docker.io/v1/". host = "https://index.docker.io/v1/" } kc.configMu.Lock() defer kc.configMu.Unlock() for _, cfg := range kc.config { if acfg, err := cfg.GetAuthConfig(host); err == nil { if acfg.IdentityToken != "" { return "", acfg.IdentityToken, nil } else if !(acfg.Username == "" && acfg.Password == "") { return acfg.Username, acfg.Password, nil } } } return "", "", nil } func (kc *keychain) startSyncSecrets(ctx context.Context, client kubernetes.Interface) error { // don't let panics crash the process defer utilruntime.HandleCrash() // get informed on `kubernetes.io/dockerconfigjson` secrets in all namespaces informer := cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { // TODO: support legacy image secret `kubernetes.io/dockercfg` options.FieldSelector = dockerconfigSelector return client.CoreV1().Secrets(metav1.NamespaceAll).List(ctx, options) }, WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { // TODO: support legacy image secret `kubernetes.io/dockercfg` options.FieldSelector = dockerconfigSelector return client.CoreV1().Secrets(metav1.NamespaceAll).Watch(ctx, options) }, }, &corev1.Secret{}, 0, cache.Indexers{}, ) // use workqueue because each task possibly takes long for parsing config, // wating for lock, etc... queue := workqueue.New() defer queue.ShutDown() informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: func(obj interface{}) { key, err := cache.MetaNamespaceKeyFunc(obj) if err == nil { queue.Add(key) } }, UpdateFunc: func(old, new interface{}) { key, err := cache.MetaNamespaceKeyFunc(new) if err == nil { queue.Add(key) } }, DeleteFunc: func(obj interface{}) { key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj) if err == nil { queue.Add(key) } }, }) go informer.Run(ctx.Done()) if !cache.WaitForCacheSync(ctx.Done(), informer.HasSynced) { return fmt.Errorf("Timed out for syncing cache") } // get informer and queue kc.informer = informer kc.queue = queue // keep on syncing secrets wait.Until(kc.runWorker, time.Second, ctx.Done()) return nil } func (kc *keychain) runWorker() { for kc.processNextItem() { // continue looping } } // TODO: consider retrying? func (kc *keychain) processNextItem() bool { key, quit := kc.queue.Get() if quit { return false } defer kc.queue.Done(key) obj, exists, err := kc.informer.GetIndexer().GetByKey(key.(string)) if err != nil { utilruntime.HandleError(fmt.Errorf("failed to get object; don't sync %q: %v", key, err)) return true } if !exists { kc.configMu.Lock() delete(kc.config, key.(string)) kc.configMu.Unlock() return true } // TODO: support legacy image secret `kubernetes.io/dockercfg` data, ok := obj.(*corev1.Secret).Data[corev1.DockerConfigJsonKey] if !ok { utilruntime.HandleError(fmt.Errorf("no secret is provided; don't sync %q", key)) return true } configFile := dcfile.New("") if err := configFile.LoadFromReader(bytes.NewReader(data)); err != nil { utilruntime.HandleError(fmt.Errorf("broken data; don't sync %q: %v", key, err)) return true } kc.configMu.Lock() kc.config[key.(string)] = configFile kc.configMu.Unlock() return true } stargz-snapshotter-0.12.0/service/plugin/000077500000000000000000000000001426301527400204165ustar00rootroot00000000000000stargz-snapshotter-0.12.0/service/plugin/plugin.go000066400000000000000000000123431426301527400222460ustar00rootroot00000000000000/* 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 plugin import ( "errors" "fmt" "net" "os" "path/filepath" "time" "github.com/containerd/containerd/defaults" "github.com/containerd/containerd/log" "github.com/containerd/containerd/pkg/dialer" "github.com/containerd/containerd/platforms" ctdplugin "github.com/containerd/containerd/plugin" "github.com/containerd/stargz-snapshotter/service" "github.com/containerd/stargz-snapshotter/service/keychain/cri" "github.com/containerd/stargz-snapshotter/service/keychain/dockerconfig" "github.com/containerd/stargz-snapshotter/service/keychain/kubeconfig" "github.com/containerd/stargz-snapshotter/service/resolver" grpc "google.golang.org/grpc" "google.golang.org/grpc/backoff" "google.golang.org/grpc/credentials/insecure" runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2" ) // Config represents configuration for the stargz snapshotter plugin. type Config struct { service.Config // RootPath is the directory for the plugin RootPath string `toml:"root_path"` // CRIKeychainImageServicePath is the path to expose CRI service wrapped by CRI keychain CRIKeychainImageServicePath string `toml:"cri_keychain_image_service_path"` // Registry is CRI-plugin-compatible registry configuration Registry resolver.Registry `toml:"registry"` } func init() { ctdplugin.Register(&ctdplugin.Registration{ Type: ctdplugin.SnapshotPlugin, ID: "stargz", Config: &Config{}, InitFn: func(ic *ctdplugin.InitContext) (interface{}, error) { ic.Meta.Platforms = append(ic.Meta.Platforms, platforms.DefaultSpec()) ctx := ic.Context config, ok := ic.Config.(*Config) if !ok { return nil, errors.New("invalid stargz snapshotter configuration") } root := ic.Root if config.RootPath != "" { root = config.RootPath } ic.Meta.Exports["root"] = root // Configure keychain credsFuncs := []resolver.Credential{dockerconfig.NewDockerconfigKeychain(ctx)} if config.Config.KubeconfigKeychainConfig.EnableKeychain { var opts []kubeconfig.Option if kcp := config.Config.KubeconfigKeychainConfig.KubeconfigPath; kcp != "" { opts = append(opts, kubeconfig.WithKubeconfigPath(kcp)) } credsFuncs = append(credsFuncs, kubeconfig.NewKubeconfigKeychain(ctx, opts...)) } if addr := config.CRIKeychainImageServicePath; config.Config.CRIKeychainConfig.EnableKeychain && addr != "" { // connects to the backend CRI service (defaults to containerd socket) criAddr := ic.Address if cp := config.Config.CRIKeychainConfig.ImageServicePath; cp != "" { criAddr = cp } if criAddr == "" { return nil, errors.New("backend CRI service address is not specified") } connectCRI := func() (runtime.ImageServiceClient, 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)), } conn, err := grpc.Dial(dialer.DialAddress(criAddr), gopts...) if err != nil { return nil, err } return runtime.NewImageServiceClient(conn), nil } criCreds, criServer := cri.NewCRIKeychain(ctx, connectCRI) // Create a gRPC server rpc := grpc.NewServer() runtime.RegisterImageServiceServer(rpc, criServer) // 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 := rpc.Serve(l); err != nil { log.G(ctx).WithError(err).Warnf("error on serving via socket %q", addr) } }() credsFuncs = append(credsFuncs, criCreds) } // TODO(ktock): print warn if old configuration is specified. // TODO(ktock): should we respect old configuration? return service.NewStargzSnapshotterService(ctx, root, &config.Config, service.WithCustomRegistryHosts(resolver.RegistryHostsFromCRIConfig(ctx, config.Registry, credsFuncs...))) }, }) } stargz-snapshotter-0.12.0/service/resolver/000077500000000000000000000000001426301527400207615ustar00rootroot00000000000000stargz-snapshotter-0.12.0/service/resolver/cri.go000066400000000000000000000302531426301527400220700ustar00rootroot00000000000000/* 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 resolver // ===== // This is CRI-plugin-compatible registry hosts configuration. // Some functions are ported from https://github.com/containerd/containerd/blob/v1.5.2/pkg/cri as noted on each one. // TODO: import them from CRI package once we drop support to continerd v1.5.x (cri v1alpha2) // ===== import ( "context" "crypto/tls" "crypto/x509" "encoding/base64" "errors" "fmt" "net" "net/http" "net/url" "os" "path/filepath" "strings" "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/reference" "github.com/containerd/containerd/remotes/docker" dconfig "github.com/containerd/containerd/remotes/docker/config" "github.com/containerd/stargz-snapshotter/fs/source" rhttp "github.com/hashicorp/go-retryablehttp" runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2" ) // Registry is registry settings configured type Registry struct { // ConfigPath is a path to the root directory containing registry-specific // configurations. // If ConfigPath is set, the rest of the registry specific options are ignored. ConfigPath string `toml:"config_path" json:"configPath"` // Mirrors are namespace to mirror mapping for all namespaces. // This option will not be used when ConfigPath is provided. // DEPRECATED: Use ConfigPath instead. Remove in containerd 1.7. Mirrors map[string]Mirror `toml:"mirrors" json:"mirrors"` // Configs are configs for each registry. // The key is the domain name or IP of the registry. // This option will be fully deprecated for ConfigPath in the future. Configs map[string]RegistryConfig `toml:"configs" json:"configs"` } // Mirror contains the config related to the registry mirror type Mirror struct { // Endpoints are endpoints for a namespace. CRI plugin will try the endpoints // one by one until a working one is found. The endpoint must be a valid url // with host specified. // The scheme, host and path from the endpoint URL will be used. Endpoints []string `toml:"endpoint" json:"endpoint"` } // RegistryConfig contains configuration used to communicate with the registry. type RegistryConfig struct { // Auth contains information to authenticate to the registry. Auth *AuthConfig `toml:"auth" json:"auth"` // TLS is a pair of CA/Cert/Key which then are used when creating the transport // that communicates with the registry. // This field will not be used when ConfigPath is provided. // DEPRECATED: Use ConfigPath instead. Remove in containerd 1.7. TLS *TLSConfig `toml:"tls" json:"tls"` } // AuthConfig contains the config related to authentication to a specific registry type AuthConfig struct { // Username is the username to login the registry. Username string `toml:"username" json:"username"` // Password is the password to login the registry. Password string `toml:"password" json:"password"` // Auth is a base64 encoded string from the concatenation of the username, // a colon, and the password. Auth string `toml:"auth" json:"auth"` // IdentityToken is used to authenticate the user and get // an access token for the registry. IdentityToken string `toml:"identitytoken" json:"identitytoken"` } // TLSConfig contains the CA/Cert/Key used for a registry type TLSConfig struct { InsecureSkipVerify bool `toml:"insecure_skip_verify" json:"insecure_skip_verify"` CAFile string `toml:"ca_file" json:"caFile"` CertFile string `toml:"cert_file" json:"certFile"` KeyFile string `toml:"key_file" json:"keyFile"` } // RegistryHostsFromCRIConfig creates RegistryHosts (a set of registry configuration) from CRI-plugin-compatible config. // NOTE: ported from https://github.com/containerd/containerd/blob/v1.5.2/pkg/cri/server/image_pull.go#L332-L405 func RegistryHostsFromCRIConfig(ctx context.Context, config Registry, credsFuncs ...Credential) source.RegistryHosts { paths := filepath.SplitList(config.ConfigPath) if len(paths) > 0 { return func(ref reference.Spec) ([]docker.RegistryHost, error) { hostOptions := dconfig.HostOptions{} hostOptions.Credentials = multiCredsFuncs(ref, append(credsFuncs, func(host string, ref reference.Spec) (string, string, error) { config := config.Configs[host] if config.Auth != nil { return ParseAuth(toRuntimeAuthConfig(*config.Auth), host) } return "", "", nil })...) hostOptions.HostDir = hostDirFromRoots(paths) return dconfig.ConfigureHosts(ctx, hostOptions)(ref.Hostname()) } } return func(ref reference.Spec) ([]docker.RegistryHost, error) { host := ref.Hostname() var registries []docker.RegistryHost endpoints, err := registryEndpoints(config, host) if err != nil { return nil, fmt.Errorf("get registry endpoints: %w", err) } for _, e := range endpoints { u, err := url.Parse(e) if err != nil { return nil, fmt.Errorf("parse registry endpoint %q from mirrors: %w", e, err) } var ( rclient = rhttp.NewClient() config = config.Configs[u.Host] ) rclient.Logger = nil // disable logging every request if config.TLS != nil { if tr, ok := rclient.HTTPClient.Transport.(*http.Transport); ok { tr.TLSClientConfig, err = getTLSConfig(*config.TLS) if err != nil { return nil, fmt.Errorf("get TLSConfig for registry %q: %w", e, err) } } else { return nil, errors.New("TLS config cannot be applied; Client.Transport is not *http.Transport") } } client := rclient.StandardClient() authorizer := docker.NewDockerAuthorizer( docker.WithAuthClient(client), docker.WithAuthCreds(multiCredsFuncs(ref, credsFuncs...))) if u.Path == "" { u.Path = "/v2" } registries = append(registries, docker.RegistryHost{ Client: client, Authorizer: authorizer, Host: u.Host, Scheme: u.Scheme, Path: u.Path, Capabilities: docker.HostCapabilityResolve | docker.HostCapabilityPull, }) } return registries, nil } } // NOTE: Ported from https://github.com/containerd/containerd/blob/v1.5.2/pkg/cri/server/image_pull.go#L316-L330 func hostDirFromRoots(roots []string) func(string) (string, error) { rootfn := make([]func(string) (string, error), len(roots)) for i := range roots { rootfn[i] = dconfig.HostDirFromRoot(roots[i]) } return func(host string) (dir string, err error) { for _, fn := range rootfn { dir, err = fn(host) if (err != nil && !errdefs.IsNotFound(err)) || (dir != "") { break } } return } } // toRuntimeAuthConfig converts cri plugin auth config to runtime auth config. // NOTE: Ported from https://github.com/containerd/containerd/blob/v1.5.2/pkg/cri/server/helpers.go#L295-L303 func toRuntimeAuthConfig(a AuthConfig) *runtime.AuthConfig { return &runtime.AuthConfig{ Username: a.Username, Password: a.Password, Auth: a.Auth, IdentityToken: a.IdentityToken, } } // getTLSConfig returns a TLSConfig configured with a CA/Cert/Key specified by registryTLSConfig // NOTE: Ported from https://github.com/containerd/containerd/blob/v1.5.2/pkg/cri/server/image_pull.go#L316-L330 func getTLSConfig(registryTLSConfig TLSConfig) (*tls.Config, error) { var ( tlsConfig = &tls.Config{} cert tls.Certificate err error ) if registryTLSConfig.CertFile != "" && registryTLSConfig.KeyFile == "" { return nil, fmt.Errorf("cert file %q was specified, but no corresponding key file was specified", registryTLSConfig.CertFile) } if registryTLSConfig.CertFile == "" && registryTLSConfig.KeyFile != "" { return nil, fmt.Errorf("key file %q was specified, but no corresponding cert file was specified", registryTLSConfig.KeyFile) } if registryTLSConfig.CertFile != "" && registryTLSConfig.KeyFile != "" { cert, err = tls.LoadX509KeyPair(registryTLSConfig.CertFile, registryTLSConfig.KeyFile) if err != nil { return nil, fmt.Errorf("failed to load cert file: %w", err) } if len(cert.Certificate) != 0 { tlsConfig.Certificates = []tls.Certificate{cert} } tlsConfig.BuildNameToCertificate() // nolint:staticcheck } if registryTLSConfig.CAFile != "" { caCertPool, err := x509.SystemCertPool() if err != nil { return nil, fmt.Errorf("failed to get system cert pool: %w", err) } caCert, err := os.ReadFile(registryTLSConfig.CAFile) if err != nil { return nil, fmt.Errorf("failed to load CA file: %w", err) } caCertPool.AppendCertsFromPEM(caCert) tlsConfig.RootCAs = caCertPool } tlsConfig.InsecureSkipVerify = registryTLSConfig.InsecureSkipVerify return tlsConfig, nil } // defaultScheme returns the default scheme for a registry host. // NOTE: Ported from https://github.com/containerd/containerd/blob/v1.5.2/pkg/cri/server/image_pull.go#L316-L330 func defaultScheme(host string) string { if h, _, err := net.SplitHostPort(host); err == nil { host = h } if host == "localhost" || host == "127.0.0.1" || host == "::1" { return "http" } return "https" } // addDefaultScheme returns the endpoint with default scheme // NOTE: Ported from https://github.com/containerd/containerd/blob/v1.5.2/pkg/cri/server/image_pull.go#L316-L330 func addDefaultScheme(endpoint string) (string, error) { if strings.Contains(endpoint, "://") { return endpoint, nil } ue := "dummy://" + endpoint u, err := url.Parse(ue) if err != nil { return "", err } return fmt.Sprintf("%s://%s", defaultScheme(u.Host), endpoint), nil } // registryEndpoints returns endpoints for a given host. // It adds default registry endpoint if it does not exist in the passed-in endpoint list. // It also supports wildcard host matching with `*`. // NOTE: Ported from https://github.com/containerd/containerd/blob/v1.5.2/pkg/cri/server/image_pull.go#L431-L464 func registryEndpoints(config Registry, host string) ([]string, error) { var endpoints []string _, ok := config.Mirrors[host] if ok { endpoints = config.Mirrors[host].Endpoints } else { endpoints = config.Mirrors["*"].Endpoints } defaultHost, err := docker.DefaultHost(host) if err != nil { return nil, fmt.Errorf("get default host: %w", err) } for i := range endpoints { en, err := addDefaultScheme(endpoints[i]) if err != nil { return nil, fmt.Errorf("parse endpoint url: %w", err) } endpoints[i] = en } for _, e := range endpoints { u, err := url.Parse(e) if err != nil { return nil, fmt.Errorf("parse endpoint url: %w", err) } if u.Host == host { // Do not add default if the endpoint already exists. return endpoints, nil } } return append(endpoints, defaultScheme(defaultHost)+"://"+defaultHost), nil } // ParseAuth parses AuthConfig and returns username and password/secret required by containerd. // NOTE: Ported from https://github.com/containerd/containerd/blob/v1.5.2/pkg/cri/server/image_pull.go#L176-L214 func ParseAuth(auth *runtime.AuthConfig, host string) (string, string, error) { if auth == nil { return "", "", nil } if auth.ServerAddress != "" { // Do not return the auth info when server address doesn't match. u, err := url.Parse(auth.ServerAddress) if err != nil { return "", "", fmt.Errorf("parse server address: %w", err) } if host != u.Host { return "", "", nil } } if auth.Username != "" { return auth.Username, auth.Password, nil } if auth.IdentityToken != "" { return "", auth.IdentityToken, nil } if auth.Auth != "" { decLen := base64.StdEncoding.DecodedLen(len(auth.Auth)) decoded := make([]byte, decLen) _, err := base64.StdEncoding.Decode(decoded, []byte(auth.Auth)) if err != nil { return "", "", err } fields := strings.SplitN(string(decoded), ":", 2) if len(fields) != 2 { return "", "", fmt.Errorf("invalid decoded auth: %q", decoded) } user, passwd := fields[0], fields[1] return user, strings.Trim(passwd, "\x00"), nil } // TODO(random-liu): Support RegistryToken. // An empty auth config is valid for anonymous registry return "", "", nil } stargz-snapshotter-0.12.0/service/resolver/registry.go000066400000000000000000000063671426301527400231740ustar00rootroot00000000000000/* 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 resolver import ( "time" "github.com/containerd/containerd/reference" "github.com/containerd/containerd/remotes/docker" "github.com/containerd/stargz-snapshotter/fs/source" rhttp "github.com/hashicorp/go-retryablehttp" ) const defaultRequestTimeoutSec = 30 // Config is config for resolving registries. type Config struct { Host map[string]HostConfig `toml:"host"` } type HostConfig struct { Mirrors []MirrorConfig `toml:"mirrors"` } type MirrorConfig struct { // Host is the hostname of the host. Host string `toml:"host"` // Insecure is true means use http scheme instead of https. Insecure bool `toml:"insecure"` // RequestTimeoutSec is timeout seconds of each request to the registry. // RequestTimeoutSec == 0 indicates the default timeout (defaultRequestTimeoutSec). // RequestTimeoutSec < 0 indicates no timeout. RequestTimeoutSec int `toml:"request_timeout_sec"` } type Credential func(string, reference.Spec) (string, string, error) // RegistryHostsFromConfig creates RegistryHosts (a set of registry configuration) from Config. func RegistryHostsFromConfig(cfg Config, credsFuncs ...Credential) source.RegistryHosts { return func(ref reference.Spec) (hosts []docker.RegistryHost, _ error) { host := ref.Hostname() for _, h := range append(cfg.Host[host].Mirrors, MirrorConfig{ Host: host, }) { client := rhttp.NewClient() client.Logger = nil // disable logging every request tr := client.StandardClient() if h.RequestTimeoutSec >= 0 { if h.RequestTimeoutSec == 0 { tr.Timeout = defaultRequestTimeoutSec * time.Second } else { tr.Timeout = time.Duration(h.RequestTimeoutSec) * time.Second } } // h.RequestTimeoutSec < 0 means "no timeout" config := docker.RegistryHost{ Client: tr, Host: h.Host, Scheme: "https", Path: "/v2", Capabilities: docker.HostCapabilityPull | docker.HostCapabilityResolve, Authorizer: docker.NewDockerAuthorizer( docker.WithAuthClient(tr), docker.WithAuthCreds(multiCredsFuncs(ref, credsFuncs...))), } if localhost, _ := docker.MatchLocalhost(config.Host); localhost || h.Insecure { config.Scheme = "http" } if config.Host == "docker.io" { config.Host = "registry-1.docker.io" } hosts = append(hosts, config) } return } } func multiCredsFuncs(ref reference.Spec, credsFuncs ...Credential) func(string) (string, string, error) { return func(host string) (string, string, error) { for _, f := range credsFuncs { if username, secret, err := f(host, ref); err != nil { return "", "", err } else if !(username == "" && secret == "") { return username, secret, nil } } return "", "", nil } } stargz-snapshotter-0.12.0/service/service.go000066400000000000000000000102411426301527400211050ustar00rootroot00000000000000/* 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 service import ( "context" "path/filepath" "github.com/containerd/containerd/log" "github.com/containerd/containerd/snapshots" "github.com/containerd/containerd/snapshots/overlay/overlayutils" stargzfs "github.com/containerd/stargz-snapshotter/fs" "github.com/containerd/stargz-snapshotter/fs/layer" "github.com/containerd/stargz-snapshotter/fs/source" "github.com/containerd/stargz-snapshotter/service/resolver" snbase "github.com/containerd/stargz-snapshotter/snapshot" "github.com/hashicorp/go-multierror" ) type Option func(*options) type options struct { credsFuncs []resolver.Credential registryHosts source.RegistryHosts fsOpts []stargzfs.Option } // WithCredsFuncs specifies credsFuncs to be used for connecting to the registries. func WithCredsFuncs(creds ...resolver.Credential) Option { return func(o *options) { o.credsFuncs = append(o.credsFuncs, creds...) } } // WithCustomRegistryHosts is registry hosts to use instead. func WithCustomRegistryHosts(hosts source.RegistryHosts) Option { return func(o *options) { o.registryHosts = hosts } } // WithFilesystemOptions allowes to pass filesystem-related configuration. func WithFilesystemOptions(opts ...stargzfs.Option) Option { return func(o *options) { o.fsOpts = opts } } // NewStargzSnapshotterService returns stargz snapshotter. func NewStargzSnapshotterService(ctx context.Context, root string, config *Config, opts ...Option) (snapshots.Snapshotter, error) { var sOpts options for _, o := range opts { o(&sOpts) } hosts := sOpts.registryHosts if hosts == nil { // Use RegistryHosts based on ResolverConfig and keychain hosts = resolver.RegistryHostsFromConfig(resolver.Config(config.ResolverConfig), sOpts.credsFuncs...) } userxattr, err := overlayutils.NeedsUserXAttr(snapshotterRoot(root)) if err != nil { log.G(ctx).WithError(err).Warnf("cannot detect whether \"userxattr\" option needs to be used, assuming to be %v", userxattr) } opq := layer.OverlayOpaqueTrusted if userxattr { opq = layer.OverlayOpaqueUser } // Configure filesystem and snapshotter fsOpts := append(sOpts.fsOpts, stargzfs.WithGetSources(sources( sourceFromCRILabels(hosts), // provides source info based on CRI labels source.FromDefaultLabels(hosts), // provides source info based on default labels )), stargzfs.WithOverlayOpaqueType(opq)) fs, err := stargzfs.NewFilesystem(fsRoot(root), config.Config, fsOpts...) if err != nil { log.G(ctx).WithError(err).Fatalf("failed to configure filesystem") } var snapshotter snapshots.Snapshotter snapshotter, err = snbase.NewSnapshotter(ctx, snapshotterRoot(root), fs, snbase.AsynchronousRemove) if err != nil { log.G(ctx).WithError(err).Fatalf("failed to create new snapshotter") } return snapshotter, err } func snapshotterRoot(root string) string { return filepath.Join(root, "snapshotter") } func fsRoot(root string) string { return filepath.Join(root, "stargz") } func sources(ps ...source.GetSources) source.GetSources { return func(labels map[string]string) (source []source.Source, allErr error) { for _, p := range ps { src, err := p(labels) if err == nil { return src, nil } allErr = multierror.Append(allErr, err) } return } } // Supported returns nil when the remote snapshotter is functional on the system with the root directory. // Supported is not called during plugin initialization, but exposed for downstream projects which uses // this snapshotter as a library. func Supported(root string) error { // Remote snapshotter is implemented based on overlayfs snapshotter. return overlayutils.Supported(snapshotterRoot(root)) } stargz-snapshotter-0.12.0/snapshot/000077500000000000000000000000001426301527400173175ustar00rootroot00000000000000stargz-snapshotter-0.12.0/snapshot/snapshot.go000066400000000000000000000532031426301527400215100ustar00rootroot00000000000000/* 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 snapshot import ( "context" "fmt" "os" "path/filepath" "strings" "syscall" "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/log" "github.com/containerd/containerd/mount" "github.com/containerd/containerd/snapshots" "github.com/containerd/containerd/snapshots/overlay/overlayutils" "github.com/containerd/containerd/snapshots/storage" "github.com/containerd/continuity/fs" "github.com/moby/sys/mountinfo" "github.com/sirupsen/logrus" "golang.org/x/sync/errgroup" ) const ( targetSnapshotLabel = "containerd.io/snapshot.ref" remoteLabel = "containerd.io/snapshot/remote" remoteLabelVal = "remote snapshot" // remoteSnapshotLogKey is a key for log line, which indicates whether // `Prepare` method successfully prepared targeting remote snapshot or not, as // defined in the following: // - "true" : indicates the snapshot has been successfully prepared as a // remote snapshot // - "false" : indicates the snapshot failed to be prepared as a remote // snapshot // - null : undetermined remoteSnapshotLogKey = "remote-snapshot-prepared" prepareSucceeded = "true" prepareFailed = "false" ) // FileSystem is a backing filesystem abstraction. // // Mount() tries to mount a remote snapshot to the specified mount point // directory. If succeed, the mountpoint directory will be treated as a layer // snapshot. If Mount() fails, the mountpoint directory MUST be cleaned up. // Check() is called to check the connectibity of the existing layer snapshot // every time the layer is used by containerd. // Unmount() is called to unmount a remote snapshot from the specified mount point // directory. type FileSystem interface { Mount(ctx context.Context, mountpoint string, labels map[string]string) error Check(ctx context.Context, mountpoint string, labels map[string]string) error Unmount(ctx context.Context, mountpoint string) error } // SnapshotterConfig is used to configure the remote snapshotter instance type SnapshotterConfig struct { asyncRemove bool noRestore bool } // Opt is an option to configure the remote snapshotter type Opt func(config *SnapshotterConfig) error // AsynchronousRemove defers removal of filesystem content until // the Cleanup method is called. Removals will make the snapshot // referred to by the key unavailable and make the key immediately // available for re-use. func AsynchronousRemove(config *SnapshotterConfig) error { config.asyncRemove = true return nil } func NoRestore(config *SnapshotterConfig) error { config.noRestore = true return nil } type snapshotter struct { root string ms *storage.MetaStore asyncRemove bool // fs is a filesystem that this snapshotter recognizes. fs FileSystem userxattr bool // whether to enable "userxattr" mount option noRestore bool } // NewSnapshotter returns a Snapshotter which can use unpacked remote layers // as snapshots. This is implemented based on the overlayfs snapshotter, so // diffs are stored under the provided root and a metadata file is stored under // the root as same as overlayfs snapshotter. func NewSnapshotter(ctx context.Context, root string, targetFs FileSystem, opts ...Opt) (snapshots.Snapshotter, error) { if targetFs == nil { return nil, fmt.Errorf("Specify filesystem to use") } var config SnapshotterConfig for _, opt := range opts { if err := opt(&config); err != nil { return nil, err } } if err := os.MkdirAll(root, 0700); err != nil { return nil, err } supportsDType, err := fs.SupportsDType(root) if err != nil { return nil, err } if !supportsDType { return nil, fmt.Errorf("%s does not support d_type. If the backing filesystem is xfs, please reformat with ftype=1 to enable d_type support", root) } ms, err := storage.NewMetaStore(filepath.Join(root, "metadata.db")) if err != nil { return nil, err } if err := os.Mkdir(filepath.Join(root, "snapshots"), 0700); err != nil && !os.IsExist(err) { return nil, err } userxattr, err := overlayutils.NeedsUserXAttr(root) if err != nil { logrus.WithError(err).Warnf("cannot detect whether \"userxattr\" option needs to be used, assuming to be %v", userxattr) } o := &snapshotter{ root: root, ms: ms, asyncRemove: config.asyncRemove, fs: targetFs, userxattr: userxattr, noRestore: config.noRestore, } if err := o.restoreRemoteSnapshot(ctx); err != nil { return nil, fmt.Errorf("failed to restore remote snapshot: %w", err) } return o, nil } // Stat returns the info for an active or committed snapshot by name or // key. // // Should be used for parent resolution, existence checks and to discern // the kind of snapshot. func (o *snapshotter) Stat(ctx context.Context, key string) (snapshots.Info, error) { ctx, t, err := o.ms.TransactionContext(ctx, false) if err != nil { return snapshots.Info{}, err } defer t.Rollback() _, info, _, err := storage.GetInfo(ctx, key) if err != nil { return snapshots.Info{}, err } return info, nil } func (o *snapshotter) Update(ctx context.Context, info snapshots.Info, fieldpaths ...string) (snapshots.Info, error) { ctx, t, err := o.ms.TransactionContext(ctx, true) if err != nil { return snapshots.Info{}, err } info, err = storage.UpdateInfo(ctx, info, fieldpaths...) if err != nil { t.Rollback() return snapshots.Info{}, err } if err := t.Commit(); err != nil { return snapshots.Info{}, err } return info, nil } // Usage returns the resources taken by the snapshot identified by key. // // For active snapshots, this will scan the usage of the overlay "diff" (aka // "upper") directory and may take some time. // for remote snapshots, no scan will be held and recognise the number of inodes // and these sizes as "zero". // // For committed snapshots, the value is returned from the metadata database. func (o *snapshotter) Usage(ctx context.Context, key string) (snapshots.Usage, error) { ctx, t, err := o.ms.TransactionContext(ctx, false) if err != nil { return snapshots.Usage{}, err } id, info, usage, err := storage.GetInfo(ctx, key) t.Rollback() // transaction no longer needed at this point. if err != nil { return snapshots.Usage{}, err } upperPath := o.upperPath(id) if info.Kind == snapshots.KindActive { du, err := fs.DiskUsage(ctx, upperPath) if err != nil { // TODO(stevvooe): Consider not reporting an error in this case. return snapshots.Usage{}, err } usage = snapshots.Usage(du) } return usage, nil } func (o *snapshotter) Prepare(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) { s, err := o.createSnapshot(ctx, snapshots.KindActive, key, parent, opts) if err != nil { return nil, err } // Try to prepare the remote snapshot. If succeeded, we commit the snapshot now // and return ErrAlreadyExists. var base snapshots.Info for _, opt := range opts { if err := opt(&base); err != nil { return nil, err } } if target, ok := base.Labels[targetSnapshotLabel]; ok { // NOTE: If passed labels include a target of the remote snapshot, `Prepare` // must log whether this method succeeded to prepare that remote snapshot // or not, using the key `remoteSnapshotLogKey` defined in the above. This // log is used by tests in this project. lCtx := log.WithLogger(ctx, log.G(ctx).WithField("key", key).WithField("parent", parent)) if err := o.prepareRemoteSnapshot(lCtx, key, base.Labels); err != nil { log.G(lCtx).WithField(remoteSnapshotLogKey, prepareFailed). WithError(err).Warn("failed to prepare remote snapshot") } else { base.Labels[remoteLabel] = remoteLabelVal // Mark this snapshot as remote err := o.commit(ctx, true, target, key, append(opts, snapshots.WithLabels(base.Labels))...) if err == nil || errdefs.IsAlreadyExists(err) { // count also AlreadyExists as "success" log.G(lCtx).WithField(remoteSnapshotLogKey, prepareSucceeded).Debug("prepared remote snapshot") return nil, fmt.Errorf("target snapshot %q: %w", target, errdefs.ErrAlreadyExists) } log.G(lCtx).WithField(remoteSnapshotLogKey, prepareFailed). WithError(err).Warn("failed to internally commit remote snapshot") // Don't fallback here (= prohibit to use this key again) because the FileSystem // possible has done some work on this "upper" directory. return nil, err } } return o.mounts(ctx, s, parent) } func (o *snapshotter) View(ctx context.Context, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) { s, err := o.createSnapshot(ctx, snapshots.KindView, key, parent, opts) if err != nil { return nil, err } return o.mounts(ctx, s, parent) } // Mounts returns the mounts for the transaction identified by key. Can be // called on an read-write or readonly transaction. // // This can be used to recover mounts after calling View or Prepare. func (o *snapshotter) Mounts(ctx context.Context, key string) ([]mount.Mount, error) { ctx, t, err := o.ms.TransactionContext(ctx, false) if err != nil { return nil, err } s, err := storage.GetSnapshot(ctx, key) t.Rollback() if err != nil { return nil, fmt.Errorf("failed to get active mount: %w", err) } return o.mounts(ctx, s, key) } func (o *snapshotter) Commit(ctx context.Context, name, key string, opts ...snapshots.Opt) error { return o.commit(ctx, false, name, key, opts...) } func (o *snapshotter) commit(ctx context.Context, isRemote bool, name, key string, opts ...snapshots.Opt) error { ctx, t, err := o.ms.TransactionContext(ctx, true) if err != nil { return err } defer func() { if err != nil { if rerr := t.Rollback(); rerr != nil { log.G(ctx).WithError(rerr).Warn("failed to rollback transaction") } } }() // grab the existing id id, _, usage, err := storage.GetInfo(ctx, key) if err != nil { return err } if !isRemote { // skip diskusage for remote snapshots for allowing lazy preparation of nodes du, err := fs.DiskUsage(ctx, o.upperPath(id)) if err != nil { return err } usage = snapshots.Usage(du) } if _, err = storage.CommitActive(ctx, key, name, usage, opts...); err != nil { return fmt.Errorf("failed to commit snapshot: %w", err) } return t.Commit() } // Remove abandons the snapshot identified by key. The snapshot will // immediately become unavailable and unrecoverable. Disk space will // be freed up on the next call to `Cleanup`. func (o *snapshotter) Remove(ctx context.Context, key string) (err error) { ctx, t, err := o.ms.TransactionContext(ctx, true) if err != nil { return err } defer func() { if err != nil { if rerr := t.Rollback(); rerr != nil { log.G(ctx).WithError(rerr).Warn("failed to rollback transaction") } } }() _, _, err = storage.Remove(ctx, key) if err != nil { return fmt.Errorf("failed to remove: %w", err) } if !o.asyncRemove { var removals []string const cleanupCommitted = false removals, err = o.getCleanupDirectories(ctx, t, cleanupCommitted) if err != nil { return fmt.Errorf("unable to get directories for removal: %w", err) } // Remove directories after the transaction is closed, failures must not // return error since the transaction is committed with the removal // key no longer available. defer func() { if err == nil { for _, dir := range removals { if err := o.cleanupSnapshotDirectory(ctx, dir); err != nil { log.G(ctx).WithError(err).WithField("path", dir).Warn("failed to remove directory") } } } }() } return t.Commit() } // Walk the snapshots. func (o *snapshotter) Walk(ctx context.Context, fn snapshots.WalkFunc, fs ...string) error { ctx, t, err := o.ms.TransactionContext(ctx, false) if err != nil { return err } defer t.Rollback() return storage.WalkInfo(ctx, fn, fs...) } // Cleanup cleans up disk resources from removed or abandoned snapshots func (o *snapshotter) Cleanup(ctx context.Context) error { const cleanupCommitted = false return o.cleanup(ctx, cleanupCommitted) } func (o *snapshotter) cleanup(ctx context.Context, cleanupCommitted bool) error { cleanup, err := o.cleanupDirectories(ctx, cleanupCommitted) if err != nil { return err } log.G(ctx).Debugf("cleanup: dirs=%v", cleanup) for _, dir := range cleanup { if err := o.cleanupSnapshotDirectory(ctx, dir); err != nil { log.G(ctx).WithError(err).WithField("path", dir).Warn("failed to remove directory") } } return nil } func (o *snapshotter) cleanupDirectories(ctx context.Context, cleanupCommitted bool) ([]string, error) { // Get a write transaction to ensure no other write transaction can be entered // while the cleanup is scanning. ctx, t, err := o.ms.TransactionContext(ctx, true) if err != nil { return nil, err } defer t.Rollback() return o.getCleanupDirectories(ctx, t, cleanupCommitted) } func (o *snapshotter) getCleanupDirectories(ctx context.Context, t storage.Transactor, cleanupCommitted bool) ([]string, error) { ids, err := storage.IDMap(ctx) if err != nil { return nil, err } snapshotDir := filepath.Join(o.root, "snapshots") fd, err := os.Open(snapshotDir) if err != nil { return nil, err } defer fd.Close() dirs, err := fd.Readdirnames(0) if err != nil { return nil, err } cleanup := []string{} for _, d := range dirs { if !cleanupCommitted { if _, ok := ids[d]; ok { continue } } cleanup = append(cleanup, filepath.Join(snapshotDir, d)) } return cleanup, nil } func (o *snapshotter) cleanupSnapshotDirectory(ctx context.Context, dir string) error { // On a remote snapshot, the layer is mounted on the "fs" directory. // We use Filesystem's Unmount API so that it can do necessary finalization // before/after the unmount. mp := filepath.Join(dir, "fs") if err := o.fs.Unmount(ctx, mp); err != nil { log.G(ctx).WithError(err).WithField("dir", mp).Debug("failed to unmount") } if err := os.RemoveAll(dir); err != nil { return fmt.Errorf("failed to remove directory %q: %w", dir, err) } return nil } func (o *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, key, parent string, opts []snapshots.Opt) (_ storage.Snapshot, err error) { ctx, t, err := o.ms.TransactionContext(ctx, true) if err != nil { return storage.Snapshot{}, err } var td, path string defer func() { if err != nil { if td != "" { if err1 := o.cleanupSnapshotDirectory(ctx, td); err1 != nil { log.G(ctx).WithError(err1).Warn("failed to cleanup temp snapshot directory") } } if path != "" { if err1 := o.cleanupSnapshotDirectory(ctx, path); err1 != nil { log.G(ctx).WithError(err1).WithField("path", path).Error("failed to reclaim snapshot directory, directory may need removal") err = fmt.Errorf("failed to remove path: %v: %w", err1, err) } } } }() snapshotDir := filepath.Join(o.root, "snapshots") td, err = o.prepareDirectory(ctx, snapshotDir, kind) if err != nil { if rerr := t.Rollback(); rerr != nil { log.G(ctx).WithError(rerr).Warn("failed to rollback transaction") } return storage.Snapshot{}, fmt.Errorf("failed to create prepare snapshot dir: %w", err) } rollback := true defer func() { if rollback { if rerr := t.Rollback(); rerr != nil { log.G(ctx).WithError(rerr).Warn("failed to rollback transaction") } } }() s, err := storage.CreateSnapshot(ctx, kind, key, parent, opts...) if err != nil { return storage.Snapshot{}, fmt.Errorf("failed to create snapshot: %w", err) } if len(s.ParentIDs) > 0 { st, err := os.Stat(o.upperPath(s.ParentIDs[0])) if err != nil { return storage.Snapshot{}, fmt.Errorf("failed to stat parent: %w", err) } stat := st.Sys().(*syscall.Stat_t) if err := os.Lchown(filepath.Join(td, "fs"), int(stat.Uid), int(stat.Gid)); err != nil { if rerr := t.Rollback(); rerr != nil { log.G(ctx).WithError(rerr).Warn("failed to rollback transaction") } return storage.Snapshot{}, fmt.Errorf("failed to chown: %w", err) } } path = filepath.Join(snapshotDir, s.ID) if err = os.Rename(td, path); err != nil { return storage.Snapshot{}, fmt.Errorf("failed to rename: %w", err) } td = "" rollback = false if err = t.Commit(); err != nil { return storage.Snapshot{}, fmt.Errorf("commit failed: %w", err) } return s, nil } func (o *snapshotter) prepareDirectory(ctx context.Context, snapshotDir string, kind snapshots.Kind) (string, error) { td, err := os.MkdirTemp(snapshotDir, "new-") if err != nil { return "", fmt.Errorf("failed to create temp dir: %w", err) } if err := os.Mkdir(filepath.Join(td, "fs"), 0755); err != nil { return td, err } if kind == snapshots.KindActive { if err := os.Mkdir(filepath.Join(td, "work"), 0711); err != nil { return td, err } } return td, nil } func (o *snapshotter) mounts(ctx context.Context, s storage.Snapshot, checkKey string) ([]mount.Mount, error) { // Make sure that all layers lower than the target layer are available if checkKey != "" && !o.checkAvailability(ctx, checkKey) { return nil, fmt.Errorf("layer %q unavailable: %w", s.ID, errdefs.ErrUnavailable) } if len(s.ParentIDs) == 0 { // if we only have one layer/no parents then just return a bind mount as overlay // will not work roFlag := "rw" if s.Kind == snapshots.KindView { roFlag = "ro" } return []mount.Mount{ { Source: o.upperPath(s.ID), Type: "bind", Options: []string{ roFlag, "rbind", }, }, }, nil } var options []string if s.Kind == snapshots.KindActive { options = append(options, fmt.Sprintf("workdir=%s", o.workPath(s.ID)), fmt.Sprintf("upperdir=%s", o.upperPath(s.ID)), ) } else if len(s.ParentIDs) == 1 { return []mount.Mount{ { Source: o.upperPath(s.ParentIDs[0]), Type: "bind", Options: []string{ "ro", "rbind", }, }, }, nil } parentPaths := make([]string, len(s.ParentIDs)) for i := range s.ParentIDs { parentPaths[i] = o.upperPath(s.ParentIDs[i]) } options = append(options, fmt.Sprintf("lowerdir=%s", strings.Join(parentPaths, ":"))) if o.userxattr { options = append(options, "userxattr") } return []mount.Mount{ { Type: "overlay", Source: "overlay", Options: options, }, }, nil } func (o *snapshotter) upperPath(id string) string { return filepath.Join(o.root, "snapshots", id, "fs") } func (o *snapshotter) workPath(id string) string { return filepath.Join(o.root, "snapshots", id, "work") } // Close closes the snapshotter func (o *snapshotter) Close() error { // unmount all mounts including Committed const cleanupCommitted = true ctx := context.Background() if err := o.cleanup(ctx, cleanupCommitted); err != nil { log.G(ctx).WithError(err).Warn("failed to cleanup") } return o.ms.Close() } // prepareRemoteSnapshot tries to prepare the snapshot as a remote snapshot // using filesystems registered in this snapshotter. func (o *snapshotter) prepareRemoteSnapshot(ctx context.Context, key string, labels map[string]string) error { ctx, t, err := o.ms.TransactionContext(ctx, false) if err != nil { return err } defer t.Rollback() id, _, _, err := storage.GetInfo(ctx, key) if err != nil { return err } mountpoint := o.upperPath(id) log.G(ctx).Infof("preparing filesystem mount at mountpoint=%v", mountpoint) return o.fs.Mount(ctx, mountpoint, labels) } // checkAvailability checks avaiability of the specified layer and all lower // layers using filesystem's checking functionality. func (o *snapshotter) checkAvailability(ctx context.Context, key string) bool { ctx = log.WithLogger(ctx, log.G(ctx).WithField("key", key)) log.G(ctx).Debug("checking layer availability") ctx, t, err := o.ms.TransactionContext(ctx, false) if err != nil { log.G(ctx).WithError(err).Warn("failed to get transaction") return false } defer t.Rollback() eg, egCtx := errgroup.WithContext(ctx) for cKey := key; cKey != ""; { id, info, _, err := storage.GetInfo(ctx, cKey) if err != nil { log.G(ctx).WithError(err).Warnf("failed to get info of %q", cKey) return false } mp := o.upperPath(id) lCtx := log.WithLogger(ctx, log.G(ctx).WithField("mount-point", mp)) if _, ok := info.Labels[remoteLabel]; ok { eg.Go(func() error { log.G(lCtx).Debug("checking mount point") if err := o.fs.Check(egCtx, mp, info.Labels); err != nil { log.G(lCtx).WithError(err).Warn("layer is unavailable") return err } return nil }) } else { log.G(lCtx).Debug("layer is normal snapshot(overlayfs)") } cKey = info.Parent } if err := eg.Wait(); err != nil { return false } return true } func (o *snapshotter) restoreRemoteSnapshot(ctx context.Context) error { mounts, err := mountinfo.GetMounts(nil) if err != nil { return err } for _, m := range mounts { if strings.HasPrefix(m.Mountpoint, filepath.Join(o.root, "snapshots")) { if err := syscall.Unmount(m.Mountpoint, syscall.MNT_FORCE); err != nil { return fmt.Errorf("failed to unmount %s: %w", m.Mountpoint, err) } } } if o.noRestore { return nil } var task []snapshots.Info if err := o.Walk(ctx, func(ctx context.Context, info snapshots.Info) error { if _, ok := info.Labels[remoteLabel]; ok { task = append(task, info) } return nil }); err != nil && !errdefs.IsNotFound(err) { return err } for _, info := range task { if err := o.prepareRemoteSnapshot(ctx, info.Name, info.Labels); err != nil { return fmt.Errorf("failed to prepare remote snapshot: %s: %w", info.Name, err) } } return nil } stargz-snapshotter-0.12.0/snapshot/snapshot_test.go000066400000000000000000000440331426301527400225500ustar00rootroot00000000000000/* 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 snapshot import ( "context" _ "crypto/sha256" "fmt" "os" "path/filepath" "syscall" "testing" "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/mount" "github.com/containerd/containerd/pkg/testutil" "github.com/containerd/containerd/snapshots" "github.com/containerd/containerd/snapshots/storage" "github.com/containerd/containerd/snapshots/testsuite" ) const ( remoteSampleFile = "foo" remoteSampleFileContents = "remote layer" brokenLabel = "containerd.io/snapshot/broken" ) func prepareWithTarget(t *testing.T, sn snapshots.Snapshotter, target, key, parent string, labels map[string]string) string { ctx := context.TODO() if labels == nil { labels = make(map[string]string) } labels[targetSnapshotLabel] = target if _, err := sn.Prepare(ctx, key, parent, snapshots.WithLabels(labels)); !errdefs.IsAlreadyExists(err) { t.Fatalf("failed to prepare remote snapshot: %v", err) } return target } func TestRemotePrepare(t *testing.T) { testutil.RequiresRoot(t) ctx := context.TODO() root, err := os.MkdirTemp("", "overlay") if err != nil { t.Fatal(err) } defer os.RemoveAll(root) sn, err := NewSnapshotter(context.TODO(), root, bindFileSystem(t)) if err != nil { t.Fatalf("failed to make new remote snapshotter: %q", err) } // Prepare a remote snapshot. target := prepareWithTarget(t, sn, "testTarget", "/tmp/prepareTarget", "", nil) defer sn.Remove(ctx, target) // Get internally committed remote snapshot. var tinfo *snapshots.Info if err := sn.Walk(ctx, func(ctx context.Context, i snapshots.Info) error { if tinfo == nil && i.Kind == snapshots.KindCommitted { if i.Labels[targetSnapshotLabel] != target { return nil } if i.Parent != "" { return nil } tinfo = &i } return nil }); err != nil { t.Fatalf("failed to get remote snapshot: %v", err) } if tinfo == nil { t.Fatalf("prepared remote snapshot %q not found", target) } // Stat and validate the remote snapshot. info, err := sn.Stat(ctx, tinfo.Name) if err != nil { t.Fatal("failed to stat remote snapshot") } if info.Kind != snapshots.KindCommitted { t.Errorf("snapshot Kind is %q; want %q", info.Kind, snapshots.KindCommitted) } if label, ok := info.Labels[targetSnapshotLabel]; !ok || label != target { t.Errorf("remote snapshot hasn't valid remote label: %q", label) } } func TestRemoteOverlay(t *testing.T) { testutil.RequiresRoot(t) ctx := context.TODO() root, err := os.MkdirTemp("", "remote") if err != nil { t.Fatal(err) } defer os.RemoveAll(root) sn, err := NewSnapshotter(context.TODO(), root, bindFileSystem(t)) if err != nil { t.Fatalf("failed to make new remote snapshotter: %q", err) } // Prepare a remote snapshot. target := prepareWithTarget(t, sn, "testTarget", "/tmp/prepareTarget", "", nil) defer sn.Remove(ctx, target) // Prepare a new layer based on the remote snapshot. pKey := "/tmp/test" mounts, err := sn.Prepare(ctx, pKey, target) if err != nil { t.Fatalf("faild to prepare using lower remote layer: %v", err) } if len(mounts) != 1 { t.Errorf("should only have 1 mount but received %d", len(mounts)) } m := mounts[0] if m.Type != "overlay" { t.Errorf("mount type should be overlay but received %q", m.Type) } if m.Source != "overlay" { t.Errorf("expected source %q but received %q", "overlay", m.Source) } var ( bp = getBasePath(ctx, sn, root, pKey) work = "workdir=" + filepath.Join(bp, "work") upper = "upperdir=" + filepath.Join(bp, "fs") lower = "lowerdir=" + getParents(ctx, sn, root, pKey)[0] ) for i, v := range []string{ work, upper, lower, } { if m.Options[i] != v { t.Errorf("expected %q but received %q", v, m.Options[i]) } } // Validate the contents of the snapshot data, err := os.ReadFile(filepath.Join(getParents(ctx, sn, root, pKey)[0], remoteSampleFile)) if err != nil { t.Fatalf("failed to read a file in the remote snapshot: %v", err) } if e := string(data); e != remoteSampleFileContents { t.Fatalf("expected file contents %q but got %q", remoteSampleFileContents, e) } } func TestRemoteCommit(t *testing.T) { testutil.RequiresRoot(t) ctx := context.TODO() root, err := os.MkdirTemp("", "remote") if err != nil { t.Fatal(err) } defer os.RemoveAll(root) sn, err := NewSnapshotter(context.TODO(), root, bindFileSystem(t)) if err != nil { t.Fatalf("failed to make new remote snapshotter: %q", err) } // Prepare a remote snapshot. target := prepareWithTarget(t, sn, "testTarget", "/tmp/prepareTarget", "", nil) defer sn.Remove(ctx, target) // Prepare a new snapshot based on the remote snapshot pKey := "/tmp/test" mounts, err := sn.Prepare(ctx, pKey, target) if err != nil { t.Fatal(err) } // Make a new active snapshot based on the remote snapshot. snapshot, err := os.MkdirTemp("", "snapshot") if err != nil { t.Fatal(err) } defer os.RemoveAll(snapshot) m := mounts[0] if err := m.Mount(snapshot); err != nil { t.Fatal(err) } defer mount.Unmount(snapshot, 0) if err := os.WriteFile(filepath.Join(snapshot, "bar"), []byte("hi"), 0660); err != nil { t.Fatal(err) } mount.Unmount(snapshot, 0) // Commit the active snapshot cKey := "/tmp/layer" if err := sn.Commit(ctx, cKey, pKey); err != nil { t.Fatal(err) } // Validate the committed snapshot check, err := os.MkdirTemp("", "check") if err != nil { t.Fatal(err) } defer os.RemoveAll(check) mounts, err = sn.Prepare(ctx, "/tmp/test2", cKey) if err != nil { t.Fatal(err) } m = mounts[0] if err := m.Mount(check); err != nil { t.Fatal(err) } defer mount.Unmount(check, 0) data, err := os.ReadFile(filepath.Join(check, "bar")) if err != nil { t.Fatal(err) } if e := string(data); e != "hi" { t.Fatalf("expected file contents %q but got %q", "hi", e) } } func TestFailureDetection(t *testing.T) { testutil.RequiresRoot(t) tests := []struct { name string broken []bool // top element is the lowest layer overlay bool // whether appending the topmost normal overlay snapshot or not wantOK bool }{ { name: "flat_ok", broken: []bool{false}, wantOK: true, }, { name: "deep_ok", broken: []bool{false, false, false}, wantOK: true, }, { name: "flat_overlay_ok", broken: []bool{false}, overlay: true, wantOK: true, }, { name: "deep_overlay_ok", broken: []bool{false, false, false}, overlay: true, wantOK: true, }, { name: "flat_ng", broken: []bool{true}, wantOK: false, }, { name: "deep_ng", broken: []bool{false, true, false}, wantOK: false, }, { name: "flat_overlay_ng", broken: []bool{true}, overlay: true, wantOK: false, }, { name: "deep_overlay_ng", broken: []bool{false, true, false}, overlay: true, wantOK: false, }, } check := func(t *testing.T, ok bool, err error) bool { if err == nil { if !ok { t.Error("check all passed but wanted to be failed") return false } } else if errdefs.IsUnavailable(err) { if ok { t.Error("got Unavailable but wanted to be non-error") return false } } else { t.Errorf("got unexpected error %q", err) return false } return true } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := context.TODO() root, err := os.MkdirTemp("", "remote") if err != nil { t.Fatal(err) } defer os.RemoveAll(root) fi := bindFileSystem(t) sn, err := NewSnapshotter(context.TODO(), root, fi) if err != nil { t.Fatalf("failed to make new Snapshotter: %q", err) } fs, ok := fi.(*bindFs) if !ok { t.Fatalf("Invalid filesystem type(not *filesystem)") } // Prepare snapshots fs.checkFailure = false pKey := "" for i, broken := range tt.broken { var ( targetName = fmt.Sprintf("/tmp/testTarget%d", i) key = fmt.Sprintf("/tmp/testKey%d", i) labels = make(map[string]string) ) if broken { labels[brokenLabel] = "true" } target := prepareWithTarget(t, sn, targetName, key, pKey, labels) defer sn.Remove(ctx, target) pKey = target } if tt.overlay { key := "/tmp/test" _, err := sn.Prepare(ctx, key, pKey) if err != nil { t.Fatal(err) } cKey := "/tmp/layer" if err := sn.Commit(ctx, cKey, key); err != nil { t.Fatal(err) } defer sn.Remove(ctx, cKey) pKey = cKey } // Tests if we can detect layer unavailablity key := "/tmp/snapshot.test" fs.checkFailure = true defer sn.Remove(ctx, key) if _, err := sn.Prepare(ctx, key, pKey); !check(t, tt.wantOK, err) { return } fs.checkFailure = false key2 := "/tmp/test2" if _, err = sn.Prepare(ctx, key2, pKey); err != nil { t.Fatal(err) } defer sn.Remove(ctx, key2) fs.checkFailure = true if _, err := sn.Mounts(ctx, key2); !check(t, tt.wantOK, err) { return } }) } } func bindFileSystem(t *testing.T) FileSystem { root, err := os.MkdirTemp("", "remote") if err != nil { t.Fatalf("failed to prepare working-space for bind filesystem: %q", err) } if err := os.WriteFile(filepath.Join(root, remoteSampleFile), []byte(remoteSampleFileContents), 0660); err != nil { t.Fatalf("failed to write sample file of bind filesystem: %q", err) } return &bindFs{ root: root, t: t, broken: make(map[string]bool), } } type bindFs struct { t *testing.T root string checkFailure bool broken map[string]bool } func (fs *bindFs) Mount(ctx context.Context, mountpoint string, labels map[string]string) error { if _, ok := labels[brokenLabel]; ok { fs.broken[mountpoint] = true } if err := syscall.Mount(fs.root, mountpoint, "none", syscall.MS_BIND, ""); err != nil { fs.t.Fatalf("failed to bind mount %q to %q: %v", fs.root, mountpoint, err) } return nil } func (fs *bindFs) Check(ctx context.Context, mountpoint string, labels map[string]string) error { if fs.checkFailure { if broken, ok := fs.broken[mountpoint]; ok && broken { return fmt.Errorf("broken") } } return nil } func (fs *bindFs) Unmount(ctx context.Context, mountpoint string) error { return syscall.Unmount(mountpoint, 0) } func dummyFileSystem() FileSystem { return &dummyFs{} } type dummyFs struct{} func (fs *dummyFs) Mount(ctx context.Context, mountpoint string, labels map[string]string) error { return fmt.Errorf("dummy") } func (fs *dummyFs) Check(ctx context.Context, mountpoint string, labels map[string]string) error { return fmt.Errorf("dummy") } func (fs *dummyFs) Unmount(ctx context.Context, mountpoint string) error { return fmt.Errorf("dummy") } // ============================================================================= // Tests backword-comaptibility of overlayfs snapshotter. func newSnapshotter(ctx context.Context, root string) (snapshots.Snapshotter, func() error, error) { snapshotter, err := NewSnapshotter(context.TODO(), root, dummyFileSystem()) if err != nil { return nil, nil, err } return snapshotter, func() error { return snapshotter.Close() }, nil } func TestOverlay(t *testing.T) { testutil.RequiresRoot(t) testsuite.SnapshotterSuite(t, "Overlay", newSnapshotter) } func TestOverlayMounts(t *testing.T) { ctx := context.TODO() root, err := os.MkdirTemp("", "overlay") if err != nil { t.Fatal(err) } defer os.RemoveAll(root) o, _, err := newSnapshotter(ctx, root) if err != nil { t.Fatal(err) } mounts, err := o.Prepare(ctx, "/tmp/test", "") if err != nil { t.Fatal(err) } if len(mounts) != 1 { t.Errorf("should only have 1 mount but received %d", len(mounts)) } m := mounts[0] if m.Type != "bind" { t.Errorf("mount type should be bind but received %q", m.Type) } expected := filepath.Join(root, "snapshots", "1", "fs") if m.Source != expected { t.Errorf("expected source %q but received %q", expected, m.Source) } if m.Options[0] != "rw" { t.Errorf("expected mount option rw but received %q", m.Options[0]) } if m.Options[1] != "rbind" { t.Errorf("expected mount option rbind but received %q", m.Options[1]) } } func TestOverlayCommit(t *testing.T) { ctx := context.TODO() root, err := os.MkdirTemp("", "overlay") if err != nil { t.Fatal(err) } defer os.RemoveAll(root) o, _, err := newSnapshotter(ctx, root) if err != nil { t.Fatal(err) } key := "/tmp/test" mounts, err := o.Prepare(ctx, key, "") if err != nil { t.Fatal(err) } m := mounts[0] if err := os.WriteFile(filepath.Join(m.Source, "foo"), []byte("hi"), 0660); err != nil { t.Fatal(err) } if err := o.Commit(ctx, "base", key); err != nil { t.Fatal(err) } } func TestOverlayOverlayMount(t *testing.T) { ctx := context.TODO() root, err := os.MkdirTemp("", "overlay") if err != nil { t.Fatal(err) } defer os.RemoveAll(root) o, _, err := newSnapshotter(ctx, root) if err != nil { t.Fatal(err) } key := "/tmp/test" if _, err = o.Prepare(ctx, key, ""); err != nil { t.Fatal(err) } if err := o.Commit(ctx, "base", key); err != nil { t.Fatal(err) } var mounts []mount.Mount if mounts, err = o.Prepare(ctx, "/tmp/layer2", "base"); err != nil { t.Fatal(err) } if len(mounts) != 1 { t.Errorf("should only have 1 mount but received %d", len(mounts)) } m := mounts[0] if m.Type != "overlay" { t.Errorf("mount type should be overlay but received %q", m.Type) } if m.Source != "overlay" { t.Errorf("expected source %q but received %q", "overlay", m.Source) } var ( bp = getBasePath(ctx, o, root, "/tmp/layer2") work = "workdir=" + filepath.Join(bp, "work") upper = "upperdir=" + filepath.Join(bp, "fs") lower = "lowerdir=" + getParents(ctx, o, root, "/tmp/layer2")[0] ) for i, v := range []string{ work, upper, lower, } { if m.Options[i] != v { t.Errorf("expected %q but received %q", v, m.Options[i]) } } } func getBasePath(ctx context.Context, sn snapshots.Snapshotter, root, key string) string { o := sn.(*snapshotter) ctx, t, err := o.ms.TransactionContext(ctx, false) if err != nil { panic(err) } defer t.Rollback() s, err := storage.GetSnapshot(ctx, key) if err != nil { panic(err) } return filepath.Join(root, "snapshots", s.ID) } func getParents(ctx context.Context, sn snapshots.Snapshotter, root, key string) []string { o := sn.(*snapshotter) ctx, t, err := o.ms.TransactionContext(ctx, false) if err != nil { panic(err) } defer t.Rollback() s, err := storage.GetSnapshot(ctx, key) if err != nil { panic(err) } parents := make([]string, len(s.ParentIDs)) for i := range s.ParentIDs { parents[i] = filepath.Join(root, "snapshots", s.ParentIDs[i], "fs") } return parents } func TestOverlayOverlayRead(t *testing.T) { testutil.RequiresRoot(t) ctx := context.TODO() root, err := os.MkdirTemp("", "overlay") if err != nil { t.Fatal(err) } defer os.RemoveAll(root) o, _, err := newSnapshotter(ctx, root) if err != nil { t.Fatal(err) } key := "/tmp/test" mounts, err := o.Prepare(ctx, key, "") if err != nil { t.Fatal(err) } m := mounts[0] if err := os.WriteFile(filepath.Join(m.Source, "foo"), []byte("hi"), 0660); err != nil { t.Fatal(err) } if err := o.Commit(ctx, "base", key); err != nil { t.Fatal(err) } if mounts, err = o.Prepare(ctx, "/tmp/layer2", "base"); err != nil { t.Fatal(err) } dest := filepath.Join(root, "dest") if err := os.Mkdir(dest, 0700); err != nil { t.Fatal(err) } if err := mount.All(mounts, dest); err != nil { t.Fatal(err) } defer syscall.Unmount(dest, 0) data, err := os.ReadFile(filepath.Join(dest, "foo")) if err != nil { t.Fatal(err) } if e := string(data); e != "hi" { t.Fatalf("expected file contents hi but got %q", e) } } func TestOverlayView(t *testing.T) { ctx := context.TODO() root, err := os.MkdirTemp("", "overlay") if err != nil { t.Fatal(err) } defer os.RemoveAll(root) o, _, err := newSnapshotter(ctx, root) if err != nil { t.Fatal(err) } key := "/tmp/base" mounts, err := o.Prepare(ctx, key, "") if err != nil { t.Fatal(err) } m := mounts[0] if err := os.WriteFile(filepath.Join(m.Source, "foo"), []byte("hi"), 0660); err != nil { t.Fatal(err) } if err := o.Commit(ctx, "base", key); err != nil { t.Fatal(err) } key = "/tmp/top" _, err = o.Prepare(ctx, key, "base") if err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(getParents(ctx, o, root, "/tmp/top")[0], "foo"), []byte("hi, again"), 0660); err != nil { t.Fatal(err) } if err := o.Commit(ctx, "top", key); err != nil { t.Fatal(err) } mounts, err = o.View(ctx, "/tmp/view1", "base") if err != nil { t.Fatal(err) } if len(mounts) != 1 { t.Fatalf("should only have 1 mount but received %d", len(mounts)) } m = mounts[0] if m.Type != "bind" { t.Errorf("mount type should be bind but received %q", m.Type) } expected := getParents(ctx, o, root, "/tmp/view1")[0] if m.Source != expected { t.Errorf("expected source %q but received %q", expected, m.Source) } if m.Options[0] != "ro" { t.Errorf("expected mount option ro but received %q", m.Options[0]) } if m.Options[1] != "rbind" { t.Errorf("expected mount option rbind but received %q", m.Options[1]) } mounts, err = o.View(ctx, "/tmp/view2", "top") if err != nil { t.Fatal(err) } if len(mounts) != 1 { t.Fatalf("should only have 1 mount but received %d", len(mounts)) } m = mounts[0] if m.Type != "overlay" { t.Errorf("mount type should be overlay but received %q", m.Type) } if m.Source != "overlay" { t.Errorf("mount source should be overlay but received %q", m.Source) } if len(m.Options) != 1 { t.Errorf("expected 1 mount option but got %d", len(m.Options)) } lowers := getParents(ctx, o, root, "/tmp/view2") expected = fmt.Sprintf("lowerdir=%s:%s", lowers[0], lowers[1]) if m.Options[0] != expected { t.Errorf("expected option %q but received %q", expected, m.Options[0]) } } stargz-snapshotter-0.12.0/store/000077500000000000000000000000001426301527400166145ustar00rootroot00000000000000stargz-snapshotter-0.12.0/store/fs.go000066400000000000000000000425641426301527400175660ustar00rootroot00000000000000/* 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 store import ( "bytes" "context" "encoding/base64" "encoding/json" "errors" "fmt" "io" "os/exec" "sync" "syscall" "time" "github.com/containerd/containerd/log" "github.com/containerd/containerd/reference" "github.com/containerd/stargz-snapshotter/cache" "github.com/containerd/stargz-snapshotter/fs/layer" "github.com/containerd/stargz-snapshotter/fs/remote" fusefs "github.com/hanwen/go-fuse/v2/fs" "github.com/hanwen/go-fuse/v2/fuse" digest "github.com/opencontainers/go-digest" "golang.org/x/sync/singleflight" ) const ( defaultLinkMode = syscall.S_IFLNK | 0400 // -r-------- defaultDirMode = syscall.S_IFDIR | 0500 // dr-x------ defaultFileMode = 0400 // -r-------- layerFileMode = 0400 // -r-------- blockSize = 4096 poolLink = "pool" layerLink = "diff" blobLink = "blob" layerInfoLink = "info" layerUseFile = "use" fusermountBin = "fusermount" ) func Mount(ctx context.Context, mountpoint string, layerManager *LayerManager, debug bool) error { timeSec := time.Second rawFS := fusefs.NewNodeFS(&rootnode{ fs: &fs{ layerManager: layerManager, nodeMap: new(idMap), layerMap: new(idMap), }, }, &fusefs.Options{ AttrTimeout: &timeSec, EntryTimeout: &timeSec, NullPermissions: true, }) mountOpts := &fuse.MountOptions{ AllowOther: true, // allow users other than root&mounter to access fs FsName: "stargzstore", Debug: debug, } if _, err := exec.LookPath(fusermountBin); err == nil { mountOpts.Options = []string{"suid"} // option for fusermount; allow setuid inside container } else { log.G(ctx).WithError(err).Debugf("%s not installed; trying direct mount", fusermountBin) mountOpts.DirectMount = true } server, err := fuse.NewServer(rawFS, mountpoint, mountOpts) if err != nil { return err } go server.Serve() return server.WaitMount() } type fs struct { layerManager *LayerManager // nodeMap manages inode numbers for nodes other than nodes in layers // (i.e. nodes other than ones inside `diff` directories). // - inode number = [ 0 ][ uint32 ID ] nodeMap *idMap // layerMap manages upper bits of inode numbers for nodes inside layers. // - inode number = [ uint32 layer ID ][ uint32 number (unique inside `diff` directory) ] // inodes numbers of noeds inside each `diff` directory are prefixed by an unique uint32 // so that they don't conflict with nodes outside `diff` directories. layerMap *idMap knownNode map[string]map[string]*layerReleasable knownNodeMu sync.Mutex } type layerReleasable struct { n fusefs.InodeEmbedder released bool mu sync.Mutex } func (lh *layerReleasable) releasable() bool { lh.mu.Lock() released := lh.released lh.mu.Unlock() return released && isForgotten(lh.n.EmbeddedInode()) } func (lh *layerReleasable) release() { lh.mu.Lock() lh.released = true lh.mu.Unlock() } func isForgotten(n *fusefs.Inode) bool { if !n.Forgotten() { return false } for _, cn := range n.Children() { if !isForgotten(cn) { return false } } return true } type inoReleasable struct { n fusefs.InodeEmbedder } func (r *inoReleasable) releasable() bool { return r.n.EmbeddedInode().Forgotten() } func (fs *fs) newInodeWithID(ctx context.Context, p func(uint32) fusefs.InodeEmbedder) (*fusefs.Inode, syscall.Errno) { var ino fusefs.InodeEmbedder if err := fs.nodeMap.add(func(id uint32) (releasable, error) { ino = p(id) return &inoReleasable{ino}, nil }); err != nil || ino == nil { log.G(ctx).WithError(err).Debug("cannot generate ID") return nil, syscall.EIO } return ino.EmbeddedInode(), 0 } // rootnode is the mountpoint node of stargz-store. type rootnode struct { fusefs.Inode fs *fs } var _ = (fusefs.InodeEmbedder)((*rootnode)(nil)) var _ = (fusefs.NodeLookuper)((*rootnode)(nil)) // Lookup loads manifest and config of specified name (image reference) // and returns refnode of the specified name func (n *rootnode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fusefs.Inode, syscall.Errno) { // lookup on memory nodes if cn := n.GetChild(name); cn != nil { switch tn := cn.Operations().(type) { case *fusefs.MemSymlink: copyAttr(&out.Attr, &tn.Attr) case *refnode: copyAttr(&out.Attr, &tn.attr) default: log.G(ctx).Warn("rootnode.Lookup: uknown node type detected") return nil, syscall.EIO } out.Attr.Ino = cn.StableAttr().Ino return cn, 0 } switch name { case poolLink: sAttr := defaultLinkAttr(&out.Attr) cn := &fusefs.MemSymlink{Data: []byte(n.fs.layerManager.refPool.root())} copyAttr(&cn.Attr, &out.Attr) return n.fs.newInodeWithID(ctx, func(ino uint32) fusefs.InodeEmbedder { out.Attr.Ino = uint64(ino) cn.Attr.Ino = uint64(ino) sAttr.Ino = uint64(ino) return n.NewInode(ctx, cn, sAttr) }) } refBytes, err := base64.StdEncoding.DecodeString(name) if err != nil { log.G(ctx).WithError(err).Debugf("failed to decode ref base64 %q", name) return nil, syscall.EINVAL } ref := string(refBytes) refspec, err := reference.Parse(ref) if err != nil { log.G(ctx).WithError(err).Warnf("invalid reference %q for %q", ref, name) return nil, syscall.EINVAL } sAttr := defaultDirAttr(&out.Attr) cn := &refnode{ fs: n.fs, ref: refspec, } copyAttr(&cn.attr, &out.Attr) return n.fs.newInodeWithID(ctx, func(ino uint32) fusefs.InodeEmbedder { out.Attr.Ino = uint64(ino) cn.attr.Ino = uint64(ino) sAttr.Ino = uint64(ino) return n.NewInode(ctx, cn, sAttr) }) } // refnode is the node at /. type refnode struct { fusefs.Inode fs *fs attr fuse.Attr ref reference.Spec } var _ = (fusefs.InodeEmbedder)((*refnode)(nil)) var _ = (fusefs.NodeLookuper)((*refnode)(nil)) // Lookup returns layernode of the specified name func (n *refnode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fusefs.Inode, syscall.Errno) { // lookup on memory nodes if cn := n.GetChild(name); cn != nil { switch tn := cn.Operations().(type) { case *layernode: copyAttr(&out.Attr, &tn.attr) default: log.G(ctx).Warn("rootnode.Lookup: uknown node type detected") return nil, syscall.EIO } out.Attr.Ino = cn.StableAttr().Ino return cn, 0 } targetDigest, err := digest.Parse(name) if err != nil { log.G(ctx).WithError(err).Warnf("invalid digest for %q", name) return nil, syscall.EINVAL } sAttr := defaultDirAttr(&out.Attr) cn := &layernode{ fs: n.fs, digest: targetDigest, refnode: n, } copyAttr(&cn.attr, &out.Attr) return n.fs.newInodeWithID(ctx, func(ino uint32) fusefs.InodeEmbedder { out.Attr.Ino = uint64(ino) cn.attr.Ino = uint64(ino) sAttr.Ino = uint64(ino) return n.NewInode(ctx, cn, sAttr) }) } var _ = (fusefs.NodeRmdirer)((*refnode)(nil)) // Rmdir marks this layer as "release". // We don't use layernode.Unlink because Unlink event doesn't reach here when "use" file isn't visible // to the filesystem client. func (n *refnode) Rmdir(ctx context.Context, name string) syscall.Errno { targetDigest, err := digest.Parse(name) if err != nil { log.G(ctx).WithError(err).Warnf("invalid digest for %q during release", name) return syscall.EINVAL } current, err := n.fs.layerManager.release(ctx, n.ref, targetDigest) if err != nil { log.G(ctx).WithError(err).Warnf("failed to release layer %v / %v", n.ref, targetDigest) return syscall.EIO } if current == 0 { n.fs.knownNodeMu.Lock() lh, ok := n.fs.knownNode[n.ref.String()][targetDigest.String()] if !ok { n.fs.knownNodeMu.Unlock() log.G(ctx).WithError(err).Warnf("node of layer %v/%v is not registered", n.ref, targetDigest) return syscall.EIO } lh.release() delete(n.fs.knownNode[n.ref.String()], targetDigest.String()) if len(n.fs.knownNode[n.ref.String()]) == 0 { delete(n.fs.knownNode, n.ref.String()) } n.fs.knownNodeMu.Unlock() } log.G(ctx).WithField("refcounter", current).Infof("layer %v/%v is marked as RELEASE", n.ref, targetDigest) return syscall.ENOENT } // layernode is the node at //. type layernode struct { fusefs.Inode attr fuse.Attr fs *fs refnode *refnode digest digest.Digest } var _ = (fusefs.InodeEmbedder)((*layernode)(nil)) var _ = (fusefs.NodeCreater)((*layernode)(nil)) // Create marks this layer as "using". // We don't use refnode.Mkdir because Mkdir event doesn't reach here if layernode already exists. func (n *layernode) Create(ctx context.Context, name string, flags uint32, mode uint32, out *fuse.EntryOut) (node *fusefs.Inode, fh fusefs.FileHandle, fuseFlags uint32, errno syscall.Errno) { if name == layerUseFile { current := n.fs.layerManager.use(n.refnode.ref, n.digest) log.G(ctx).WithField("refcounter", current).Infof("layer %v / %v is marked as USING", n.refnode.ref, n.digest) } return nil, nil, 0, syscall.ENOENT } var _ = (fusefs.NodeLookuper)((*layernode)(nil)) // Lookup routes to the target file stored in the pool, based on the specified file name. func (n *layernode) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (*fusefs.Inode, syscall.Errno) { switch name { case layerInfoLink: info, err := n.fs.layerManager.getLayerInfo(ctx, n.refnode.ref, n.digest) if err != nil { log.G(ctx).WithError(err).Warnf("failed to get layer info for %q: %q", name, n.digest) return nil, syscall.EIO } buf := new(bytes.Buffer) if err := json.NewEncoder(buf).Encode(&info); err != nil { log.G(ctx).WithError(err).Warnf("failed to encode layer info for %q: %q", name, n.digest) return nil, syscall.EIO } infoData := buf.Bytes() sAttr := defaultFileAttr(uint64(len(infoData)), &out.Attr) cn := &fusefs.MemRegularFile{Data: infoData} copyAttr(&cn.Attr, &out.Attr) return n.fs.newInodeWithID(ctx, func(ino uint32) fusefs.InodeEmbedder { out.Attr.Ino = uint64(ino) cn.Attr.Ino = uint64(ino) sAttr.Ino = uint64(ino) return n.NewInode(ctx, cn, sAttr) }) case layerLink, blobLink: // Check if layer is already known if name == layerLink { n.fs.knownNodeMu.Lock() if lh, ok := n.fs.knownNode[n.refnode.ref.String()][n.digest.String()]; ok { var ao fuse.AttrOut if errno := lh.n.(fusefs.NodeGetattrer).Getattr(ctx, nil, &ao); errno != 0 { return nil, errno } copyAttr(&out.Attr, &ao.Attr) n.fs.knownNodeMu.Unlock() return n.NewInode(ctx, lh.n, fusefs.StableAttr{ Mode: out.Attr.Mode, Ino: out.Attr.Ino, }), 0 } n.fs.knownNodeMu.Unlock() } // Resolve layer l, err := n.fs.layerManager.getLayer(ctx, n.refnode.ref, n.digest) if err != nil { cErr := ctx.Err() if errors.Is(cErr, context.Canceled) || errors.Is(err, context.Canceled) { // When filesystem client canceled to lookup this layer, // do not log this as "preparation failure" because it's // intensional. log.G(ctx).WithError(err).Debugf("error resolving layer (context error: %v)", cErr) return nil, syscall.EIO } log.G(ctx).WithField(remoteSnapshotLogKey, prepareFailed). WithField("layerdigest", n.digest). WithError(err). Debugf("error resolving layer (context error: %v)", cErr) log.G(ctx).WithError(err).Warnf("failed to mount layer %q: %q", name, n.digest) return nil, syscall.EIO } if name == blobLink { sAttr := layerToAttr(l, &out.Attr) cn := &blobnode{l: l} copyAttr(&cn.attr, &out.Attr) return n.fs.newInodeWithID(ctx, func(ino uint32) fusefs.InodeEmbedder { out.Attr.Ino = uint64(ino) cn.attr.Ino = uint64(ino) sAttr.Ino = uint64(ino) return n.NewInode(ctx, cn, sAttr) }) } var cn *fusefs.Inode var errno syscall.Errno err = n.fs.layerMap.add(func(id uint32) (releasable, error) { root, err := l.RootNode(id) if err != nil { return nil, err } var ao fuse.AttrOut errno = root.(fusefs.NodeGetattrer).Getattr(ctx, nil, &ao) if errno != 0 { return nil, fmt.Errorf("failed to get root node: %v", errno) } copyAttr(&out.Attr, &ao.Attr) cn = n.NewInode(ctx, root, fusefs.StableAttr{ Mode: out.Attr.Mode, Ino: out.Attr.Ino, }) rr := &layerReleasable{n: root} n.fs.knownNodeMu.Lock() if n.fs.knownNode == nil { n.fs.knownNode = make(map[string]map[string]*layerReleasable) } if n.fs.knownNode[n.refnode.ref.String()] == nil { n.fs.knownNode[n.refnode.ref.String()] = make(map[string]*layerReleasable) } n.fs.knownNode[n.refnode.ref.String()][n.digest.String()] = rr n.fs.knownNodeMu.Unlock() return rr, nil }) if err != nil || errno != 0 { log.G(ctx).WithField(remoteSnapshotLogKey, prepareFailed). WithField("layerdigest", n.digest). WithError(err). WithField("errno", errno). Debugf("failed to get root node") if errno == 0 { errno = syscall.EIO } return nil, errno } return cn, 0 case layerUseFile: log.G(ctx).Debugf("\"use\" file is referred but return ENOENT for reference management") return nil, syscall.ENOENT default: log.G(ctx).Warnf("unknown filename %q", name) return nil, syscall.ENOENT } } // blobnode is a regular file node that contains raw blob data type blobnode struct { fusefs.Inode l layer.Layer attr fuse.Attr } var _ = (fusefs.InodeEmbedder)((*blobnode)(nil)) var _ = (fusefs.NodeOpener)((*blobnode)(nil)) func (n *blobnode) Open(ctx context.Context, flags uint32) (fh fusefs.FileHandle, fuseFlags uint32, errno syscall.Errno) { return &blobfile{l: n.l}, 0, 0 } // blob file is the file handle of blob contents. type blobfile struct { l layer.Layer } var _ = (fusefs.FileReader)((*blobfile)(nil)) func (f *blobfile) Read(ctx context.Context, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) { s, err := f.l.ReadAt(dest, off, remote.WithContext(ctx), // Make cancellable remote.WithCacheOpts(cache.Direct()), // Do not pollute mem cache ) if err != nil && err != io.EOF { return nil, syscall.EIO } return fuse.ReadResultData(dest[:s]), 0 } var _ = (fusefs.FileGetattrer)((*blobfile)(nil)) func (f *blobfile) Getattr(ctx context.Context, out *fuse.AttrOut) syscall.Errno { layerToAttr(f.l, &out.Attr) return 0 } func copyAttr(dest, src *fuse.Attr) { dest.Ino = src.Ino dest.Size = src.Size dest.Blocks = src.Blocks dest.Atime = src.Atime dest.Mtime = src.Mtime dest.Ctime = src.Ctime dest.Atimensec = src.Atimensec dest.Mtimensec = src.Mtimensec dest.Ctimensec = src.Ctimensec dest.Mode = src.Mode dest.Nlink = src.Nlink dest.Owner = src.Owner dest.Rdev = src.Rdev dest.Blksize = src.Blksize dest.Padding = src.Padding } func layerToAttr(l layer.Layer, out *fuse.Attr) fusefs.StableAttr { // out.Ino out.Size = uint64(l.Info().Size) out.Blksize = blockSize out.Blocks = out.Size / uint64(out.Blksize) if out.Size%uint64(out.Blksize) > 0 { out.Blocks++ } out.Nlink = 1 out.Mode = layerFileMode out.Owner = fuse.Owner{Uid: 0, Gid: 0} // out.Mtime // out.Mtimensec // out.Rdev // out.Padding return fusefs.StableAttr{ Mode: out.Mode, } } func defaultFileAttr(size uint64, out *fuse.Attr) fusefs.StableAttr { // out.Ino out.Size = size out.Blksize = blockSize out.Blocks = out.Size / uint64(out.Blksize) if out.Size%uint64(out.Blksize) > 0 { out.Blocks++ } out.Nlink = 1 out.Mode = defaultFileMode out.Owner = fuse.Owner{Uid: 0, Gid: 0} // out.Mtime // out.Mtimensec // out.Rdev // out.Padding return fusefs.StableAttr{ Mode: out.Mode, } } func defaultDirAttr(out *fuse.Attr) fusefs.StableAttr { // out.Ino out.Size = 0 // out.Blksize // out.Blocks // out.Nlink out.Mode = defaultDirMode out.Owner = fuse.Owner{Uid: 0, Gid: 0} // out.Mtime // out.Mtimensec // out.Rdev // out.Padding return fusefs.StableAttr{ Mode: out.Mode, } } func defaultLinkAttr(out *fuse.Attr) fusefs.StableAttr { // out.Ino out.Size = 0 // out.Blksize // out.Blocks // out.Nlink out.Mode = defaultLinkMode out.Owner = fuse.Owner{Uid: 0, Gid: 0} // out.Mtime // out.Mtimensec // out.Rdev // out.Padding return fusefs.StableAttr{ Mode: out.Mode, } } // idMap manages uint32 IDs with automatic GC for releasable objects. type idMap struct { m map[uint32]releasable max uint32 mu sync.Mutex cleanupG singleflight.Group } type releasable interface { releasable() bool } // add reserves an unique uint32 object for the provided releasable object. // when that object become releasable, that ID will be reused for other objects. func (m *idMap) add(p func(uint32) (releasable, error)) error { m.cleanupG.Do("cleanup", func() (interface{}, error) { m.mu.Lock() defer m.mu.Unlock() max := uint32(0) for i := uint32(0); i <= m.max; i++ { if e, ok := m.m[i]; ok { if e.releasable() { delete(m.m, i) } else { max = i } } } m.max = max return nil, nil }) m.mu.Lock() defer m.mu.Unlock() if m.m == nil { m.m = make(map[uint32]releasable) } for i := uint32(0); i <= ^uint32(0); i++ { if i == 0 { continue } e, ok := m.m[i] if !ok || e.releasable() { r, err := p(i) if err != nil { return err } if m.max < i { m.max = i } m.m[i] = r return nil } } return fmt.Errorf("no ID is usable") } stargz-snapshotter-0.12.0/store/manager.go000066400000000000000000000333151426301527400205620ustar00rootroot00000000000000/* 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 store import ( "context" "fmt" "strconv" "strings" "sync" "time" "github.com/containerd/containerd/log" "github.com/containerd/containerd/reference" "github.com/containerd/stargz-snapshotter/estargz" "github.com/containerd/stargz-snapshotter/estargz/zstdchunked" "github.com/containerd/stargz-snapshotter/fs/config" "github.com/containerd/stargz-snapshotter/fs/layer" layermetrics "github.com/containerd/stargz-snapshotter/fs/metrics/layer" "github.com/containerd/stargz-snapshotter/fs/source" "github.com/containerd/stargz-snapshotter/metadata" "github.com/containerd/stargz-snapshotter/task" "github.com/containerd/stargz-snapshotter/util/namedmutex" "github.com/docker/go-metrics" digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) const ( remoteSnapshotLogKey = "remote-snapshot-prepared" prepareSucceeded = "true" prepareFailed = "false" defaultMaxConcurrency = 2 ) func NewLayerManager(ctx context.Context, root string, hosts source.RegistryHosts, metadataStore metadata.Store, cfg config.Config) (*LayerManager, error) { refPool, err := newRefPool(ctx, root, hosts) if err != nil { return nil, err } maxConcurrency := cfg.MaxConcurrency if maxConcurrency == 0 { maxConcurrency = defaultMaxConcurrency } tm := task.NewBackgroundTaskManager(maxConcurrency, 5*time.Second) r, err := layer.NewResolver(root, tm, cfg, nil, metadataStore, layer.OverlayOpaqueAll) // TODO: support IPFS if err != nil { return nil, fmt.Errorf("failed to setup resolver: %w", err) } var ns *metrics.Namespace if !cfg.NoPrometheus { ns = metrics.NewNamespace("stargz", "fs", nil) } c := layermetrics.NewLayerMetrics(ns) if ns != nil { metrics.Register(ns) } return &LayerManager{ refPool: refPool, hosts: hosts, resolver: r, prefetchSize: cfg.PrefetchSize, noprefetch: cfg.NoPrefetch, noBackgroundFetch: cfg.NoBackgroundFetch, backgroundTaskManager: tm, allowNoVerification: cfg.AllowNoVerification, disableVerification: cfg.DisableVerification, metricsController: c, resolveLock: new(namedmutex.NamedMutex), layer: make(map[string]map[string]layer.Layer), refcounter: make(map[string]map[string]int), }, nil } // LayerManager manages layers of images and their resource lifetime. type LayerManager struct { refPool *refPool hosts source.RegistryHosts resolver *layer.Resolver prefetchSize int64 noprefetch bool noBackgroundFetch bool backgroundTaskManager *task.BackgroundTaskManager allowNoVerification bool disableVerification bool metricsController *layermetrics.Controller resolveLock *namedmutex.NamedMutex layer map[string]map[string]layer.Layer refcounter map[string]map[string]int mu sync.Mutex } func (r *LayerManager) cacheLayer(refspec reference.Spec, dgst digest.Digest, l layer.Layer) (_ layer.Layer, added bool) { r.mu.Lock() defer r.mu.Unlock() if r.layer == nil { r.layer = make(map[string]map[string]layer.Layer) } if r.layer[refspec.String()] == nil { r.layer[refspec.String()] = make(map[string]layer.Layer) } if cl, ok := r.layer[refspec.String()][dgst.String()]; ok { return cl, false // already exists } r.layer[refspec.String()][dgst.String()] = l return l, true } func (r *LayerManager) getCachedLayer(refspec reference.Spec, dgst digest.Digest) layer.Layer { r.mu.Lock() defer r.mu.Unlock() if r.layer == nil || r.layer[refspec.String()] == nil { return nil } if l, ok := r.layer[refspec.String()][dgst.String()]; ok { return l } return nil } func (r *LayerManager) getLayerInfo(ctx context.Context, refspec reference.Spec, dgst digest.Digest) (Layer, error) { manifest, config, err := r.refPool.loadRef(ctx, refspec) if err != nil { return Layer{}, fmt.Errorf("failed to get manifest and config: %w", err) } return genLayerInfo(ctx, dgst, manifest, config) } func (r *LayerManager) getLayer(ctx context.Context, refspec reference.Spec, dgst digest.Digest) (layer.Layer, error) { gotL := r.getCachedLayer(refspec, dgst) if gotL != nil { return gotL, nil } // resolve the layer and all other layers in the specified reference. var ( result layer.Layer resultChan = make(chan layer.Layer) errChan = make(chan error) ) manifest, _, err := r.refPool.loadRef(ctx, refspec) if err != nil { return nil, fmt.Errorf("failed to get manifest and config: %w", err) } var target ocispec.Descriptor var preResolve []ocispec.Descriptor var found bool for _, l := range manifest.Layers { if l.Digest == dgst { l := l found = true target = l continue } preResolve = append(preResolve, l) } if !found { return nil, fmt.Errorf("unknown digest %v for ref %q", target, refspec.String()) } for _, l := range append([]ocispec.Descriptor{target}, preResolve...) { l := l // Check if layer is already resolved before creating goroutine. gotL := r.getCachedLayer(refspec, l.Digest) if gotL != nil { // Layer already resolved if l.Digest.String() != target.Digest.String() { continue // This is not the target layer; nop } result = gotL continue } // Resolve the layer go func() { // Avoids to get canceled by client. ctx := context.Background() gotL, err := r.resolveLayer(ctx, refspec, l) if l.Digest.String() != target.Digest.String() { return // This is not target layer } if err != nil { errChan <- fmt.Errorf("failed to resolve layer %q / %q: %w", refspec, l.Digest, err) return } // Log this as preparation success log.G(ctx).WithField(remoteSnapshotLogKey, prepareSucceeded).Debugf("successfully resolved layer") resultChan <- gotL }() } if result != nil { return result, nil } // Wait for resolving completion var l layer.Layer select { case l = <-resultChan: case err := <-errChan: log.G(ctx).WithError(err).Debug("failed to resolve layer") return nil, fmt.Errorf("failed to resolve layer: %w", err) case <-time.After(30 * time.Second): log.G(ctx).Debug("failed to resolve layer (timeout)") return nil, fmt.Errorf("failed to resolve layer (timeout)") } return l, nil } func (r *LayerManager) resolveLayer(ctx context.Context, refspec reference.Spec, target ocispec.Descriptor) (layer.Layer, error) { key := refspec.String() + "/" + target.Digest.String() // Wait if resolving this layer is already running. r.resolveLock.Lock(key) defer r.resolveLock.Unlock(key) gotL := r.getCachedLayer(refspec, target.Digest) if gotL != nil { // layer already resolved return gotL, nil } // Resolve this layer. var esgzOpts []metadata.Option if target.Annotations != nil { if tocOffsetStr, ok := target.Annotations[zstdchunked.ManifestPositionAnnotation]; ok { if parts := strings.Split(tocOffsetStr, ":"); len(parts) == 4 { tocOffset, err := strconv.ParseInt(parts[0], 10, 64) if err == nil { esgzOpts = append(esgzOpts, metadata.WithTOCOffset(tocOffset)) } } } } l, err := r.resolver.Resolve(ctx, r.hosts, refspec, target, esgzOpts...) if err != nil { return nil, err } // Verify layer's content labels := target.Annotations if labels == nil { labels = make(map[string]string) } if r.disableVerification { // Skip if verification is disabled completely l.SkipVerify() log.G(ctx).Debugf("Verification forcefully skipped") } else if tocDigest, ok := labels[estargz.TOCJSONDigestAnnotation]; ok { // Verify this layer using the TOC JSON digest passed through label. dgst, err := digest.Parse(tocDigest) if err != nil { log.G(ctx).WithError(err).Debugf("failed to parse passed TOC digest %q", dgst) return nil, fmt.Errorf("invalid TOC digest: %v: %w", tocDigest, err) } if err := l.Verify(dgst); err != nil { log.G(ctx).WithError(err).Debugf("invalid layer") return nil, fmt.Errorf("invalid stargz layer: %w", err) } log.G(ctx).Debugf("verified") } else { // Verification must be done. Don't mount this layer. return nil, fmt.Errorf("digest of TOC JSON must be passed") } // Prefetch this layer. We prefetch several layers in parallel. The first // Check() for this layer waits for the prefetch completion. if !r.noprefetch { go func() { r.backgroundTaskManager.DoPrioritizedTask() defer r.backgroundTaskManager.DonePrioritizedTask() if err := l.Prefetch(r.prefetchSize); err != nil { log.G(ctx).WithError(err).Debug("failed to prefetched layer") return } log.G(ctx).Debug("completed to prefetch") }() } // Fetch whole layer aggressively in background. We use background // reader for this so prioritized tasks(Mount, Check, etc...) can // interrupt the reading. This can avoid disturbing prioritized tasks // about NW traffic. if !r.noBackgroundFetch { go func() { if err := l.BackgroundFetch(); err != nil { log.G(ctx).WithError(err).Debug("failed to fetch whole layer") return } log.G(ctx).Debug("completed to fetch all layer data in background") }() } // Cache this layer. cachedL, added := r.cacheLayer(refspec, target.Digest, l) if added { r.metricsController.Add(key, cachedL) } else { l.Done() // layer is already cached. use the cached one instead. discard this layer. } return cachedL, nil } func (r *LayerManager) release(ctx context.Context, refspec reference.Spec, dgst digest.Digest) (int, error) { r.refPool.release(refspec) r.mu.Lock() defer r.mu.Unlock() if r.refcounter == nil || r.refcounter[refspec.String()] == nil { return 0, fmt.Errorf("ref %q not tracked", refspec.String()) } else if _, ok := r.refcounter[refspec.String()][dgst.String()]; !ok { return 0, fmt.Errorf("layer %q/%q not tracked", refspec.String(), dgst.String()) } r.refcounter[refspec.String()][dgst.String()]-- i := r.refcounter[refspec.String()][dgst.String()] if i <= 0 { // No reference to this layer. release it. delete(r.refcounter, dgst.String()) if len(r.refcounter[refspec.String()]) == 0 { delete(r.refcounter, refspec.String()) } if r.layer == nil || r.layer[refspec.String()] == nil { return 0, fmt.Errorf("layer of reference %q is not registered (ref=%d)", refspec, i) } l, ok := r.layer[refspec.String()][dgst.String()] if !ok { return 0, fmt.Errorf("layer of digest %q/%q is not registered (ref=%d)", refspec, dgst, i) } l.Done() delete(r.layer[refspec.String()], dgst.String()) if len(r.layer[refspec.String()]) == 0 { delete(r.layer, refspec.String()) } log.G(ctx).WithField("refcounter", i).Infof("layer %v/%v is released due to no reference", refspec, dgst) } return i, nil } func (r *LayerManager) use(refspec reference.Spec, dgst digest.Digest) int { r.refPool.use(refspec) r.mu.Lock() defer r.mu.Unlock() if r.refcounter == nil { r.refcounter = make(map[string]map[string]int) } if r.refcounter[refspec.String()] == nil { r.refcounter[refspec.String()] = make(map[string]int) } if _, ok := r.refcounter[refspec.String()][dgst.String()]; !ok { r.refcounter[refspec.String()][dgst.String()] = 1 return 1 } r.refcounter[refspec.String()][dgst.String()]++ return r.refcounter[refspec.String()][dgst.String()] } func colon2dash(s string) string { return strings.ReplaceAll(s, ":", "-") } // Layer represents the layer information. Format is compatible to the one required by // "additional layer store" of github.com/containers/storage. type Layer struct { CompressedDigest digest.Digest `json:"compressed-diff-digest,omitempty"` CompressedSize int64 `json:"compressed-size,omitempty"` UncompressedDigest digest.Digest `json:"diff-digest,omitempty"` UncompressedSize int64 `json:"diff-size,omitempty"` CompressionType int `json:"compression,omitempty"` ReadOnly bool `json:"-"` } // Defined in https://github.com/containers/storage/blob/b64e13a1afdb0bfed25601090ce4bbbb1bc183fc/pkg/archive/archive.go#L108-L119 const gzipTypeMagicNum = 2 func genLayerInfo(ctx context.Context, dgst digest.Digest, manifest ocispec.Manifest, config ocispec.Image) (Layer, error) { if len(manifest.Layers) != len(config.RootFS.DiffIDs) { return Layer{}, fmt.Errorf( "len(manifest.Layers) != len(config.Rootfs): %d != %d", len(manifest.Layers), len(config.RootFS.DiffIDs)) } var ( layerIndex = -1 ) for i, l := range manifest.Layers { if l.Digest == dgst { layerIndex = i } } if layerIndex == -1 { return Layer{}, fmt.Errorf("layer %q not found in the manifest", dgst.String()) } var uncompressedSize int64 var err error if uncompressedSizeStr, ok := manifest.Layers[layerIndex].Annotations[estargz.StoreUncompressedSizeAnnotation]; ok { uncompressedSize, err = strconv.ParseInt(uncompressedSizeStr, 10, 64) if err != nil { log.G(ctx).WithError(err).Warnf("layer %q has invalid uncompressed size; exposing incomplete layer info", dgst.String()) } } else { log.G(ctx).Warnf("layer %q doesn't have uncompressed size; exposing incomplete layer info", dgst.String()) } return Layer{ CompressedDigest: manifest.Layers[layerIndex].Digest, CompressedSize: manifest.Layers[layerIndex].Size, UncompressedDigest: config.RootFS.DiffIDs[layerIndex], UncompressedSize: uncompressedSize, CompressionType: gzipTypeMagicNum, ReadOnly: true, }, nil } stargz-snapshotter-0.12.0/store/refs.go000066400000000000000000000210541426301527400201040ustar00rootroot00000000000000/* 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 store import ( "context" "encoding/json" "fmt" "io" "os" "path/filepath" "sync" "time" "github.com/containerd/containerd/images" "github.com/containerd/containerd/log" "github.com/containerd/containerd/platforms" "github.com/containerd/containerd/reference" "github.com/containerd/containerd/remotes" "github.com/containerd/containerd/remotes/docker" "github.com/containerd/stargz-snapshotter/fs/source" "github.com/containerd/stargz-snapshotter/util/cacheutil" "github.com/containerd/stargz-snapshotter/util/containerdutil" digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) const ( refCacheEntry = 30 defaultManifestCacheTime = 120 * time.Second ) func newRefPool(ctx context.Context, root string, hosts source.RegistryHosts) (*refPool, error) { var poolroot = filepath.Join(root, "pool") if err := os.MkdirAll(poolroot, 0700); err != nil { return nil, err } p := &refPool{ path: poolroot, hosts: hosts, refcounter: make(map[string]*releaser), } p.cache = cacheutil.NewLRUCache(refCacheEntry) p.cache.OnEvicted = func(key string, value interface{}) { refspec := value.(reference.Spec) if err := os.RemoveAll(p.metadataDir(refspec)); err != nil { log.G(ctx).WithField("key", key).WithError(err).Warnf("failed to clean up ref") return } log.G(ctx).WithField("key", key).Debugf("cleaned up ref") } return p, nil } type refPool struct { path string hosts source.RegistryHosts refcounter map[string]*releaser cache *cacheutil.LRUCache mu sync.Mutex } type releaser struct { count int release func() } func (p *refPool) loadRef(ctx context.Context, refspec reference.Spec) (manifest ocispec.Manifest, config ocispec.Image, err error) { manifest, config, err = p.readManifestAndConfig(refspec) if err == nil { log.G(ctx).Debugf("reusing manifest and config of %q", refspec.String()) return } log.G(ctx).WithError(err).Debugf("fetching manifest and config of %q", refspec.String()) manifest, config, err = p.fetchManifestAndConfig(ctx, refspec) if err != nil { return ocispec.Manifest{}, ocispec.Image{}, err } if err := p.writeManifestAndConfig(refspec, manifest, config); err != nil { return ocispec.Manifest{}, ocispec.Image{}, err } // Cache it so that next immediate call can acquire ref information from that dir. p.mu.Lock() _, done, _ := p.cache.Add(refspec.String(), refspec) p.mu.Unlock() go func() { // Release it after a reasonable amount of time. // If use() funcs are called for this reference, eviction of this won't be done until // all corresponding release() funcs are called. time.Sleep(defaultManifestCacheTime) done() }() return manifest, config, nil } func (p *refPool) use(refspec reference.Spec) int { p.mu.Lock() defer p.mu.Unlock() r, ok := p.refcounter[refspec.String()] if !ok { _, done, _ := p.cache.Add(refspec.String(), refspec) p.refcounter[refspec.String()] = &releaser{ count: 1, release: done, } return 1 } r.count++ return r.count } func (p *refPool) release(refspec reference.Spec) (int, error) { p.mu.Lock() defer p.mu.Unlock() r, ok := p.refcounter[refspec.String()] if !ok { return 0, fmt.Errorf("ref %q not tracked", refspec.String()) } r.count-- if r.count <= 0 { delete(p.refcounter, refspec.String()) r.release() return 0, nil } return r.count, nil } func (p *refPool) readManifestAndConfig(refspec reference.Spec) (manifest ocispec.Manifest, config ocispec.Image, _ error) { mPath, cPath := p.manifestFile(refspec), p.configFile(refspec) mf, err := os.Open(mPath) if err != nil { return ocispec.Manifest{}, ocispec.Image{}, err } defer mf.Close() if err := json.NewDecoder(mf).Decode(&manifest); err != nil { return ocispec.Manifest{}, ocispec.Image{}, err } cf, err := os.Open(cPath) if err != nil { return ocispec.Manifest{}, ocispec.Image{}, err } defer cf.Close() if err := json.NewDecoder(cf).Decode(&config); err != nil { return ocispec.Manifest{}, ocispec.Image{}, err } return manifest, config, nil } func (p *refPool) writeManifestAndConfig(refspec reference.Spec, manifest ocispec.Manifest, config ocispec.Image) error { mPath, cPath := p.manifestFile(refspec), p.configFile(refspec) if err := os.MkdirAll(filepath.Dir(mPath), 0700); err != nil { return err } if err := os.MkdirAll(filepath.Dir(cPath), 0700); err != nil { return err } mf, err := os.Create(mPath) if err != nil { return err } defer mf.Close() if err := json.NewEncoder(mf).Encode(&manifest); err != nil { return err } cf, err := os.Create(cPath) if err != nil { return err } defer cf.Close() return json.NewEncoder(cf).Encode(&config) } func (p *refPool) fetchManifestAndConfig(ctx context.Context, refspec reference.Spec) (ocispec.Manifest, ocispec.Image, error) { // temporary resolver. should only be used for resolving `refpec`. resolver := docker.NewResolver(docker.ResolverOptions{ Hosts: func(host string) ([]docker.RegistryHost, error) { if host != refspec.Hostname() { return nil, fmt.Errorf("unexpected host %q for image ref %q", host, refspec.String()) } return p.hosts(refspec) }, }) _, img, err := resolver.Resolve(ctx, refspec.String()) if err != nil { return ocispec.Manifest{}, ocispec.Image{}, err } fetcher, err := resolver.Fetcher(ctx, refspec.String()) if err != nil { return ocispec.Manifest{}, ocispec.Image{}, err } plt := platforms.DefaultSpec() // TODO: should we make this configurable? manifest, err := fetchManifestPlatform(ctx, fetcher, img, plt) if err != nil { return ocispec.Manifest{}, ocispec.Image{}, err } r, err := fetcher.Fetch(ctx, manifest.Config) if err != nil { return ocispec.Manifest{}, ocispec.Image{}, err } defer r.Close() var config ocispec.Image if err := json.NewDecoder(r).Decode(&config); err != nil { return ocispec.Manifest{}, ocispec.Image{}, err } return manifest, config, nil } func (p *refPool) root() string { return p.path } func (p *refPool) metadataDir(refspec reference.Spec) string { return filepath.Join(p.path, "metadata--"+colon2dash(digest.FromString(refspec.String()).String())) } func (p *refPool) manifestFile(refspec reference.Spec) string { return filepath.Join(p.metadataDir(refspec), "manifest") } func (p *refPool) configFile(refspec reference.Spec) string { return filepath.Join(p.metadataDir(refspec), "config") } func fetchManifestPlatform(ctx context.Context, fetcher remotes.Fetcher, desc ocispec.Descriptor, platform ocispec.Platform) (ocispec.Manifest, error) { ctx, cancel := context.WithTimeout(ctx, time.Minute) defer cancel() r, err := fetcher.Fetch(ctx, desc) if err != nil { return ocispec.Manifest{}, err } defer r.Close() var manifest ocispec.Manifest switch desc.MediaType { case images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest: p, err := io.ReadAll(r) if err != nil { return ocispec.Manifest{}, err } if err := containerdutil.ValidateMediaType(p, desc.MediaType); err != nil { return ocispec.Manifest{}, err } if err := json.Unmarshal(p, &manifest); err != nil { return ocispec.Manifest{}, err } return manifest, nil case images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex: var index ocispec.Index p, err := io.ReadAll(r) if err != nil { return ocispec.Manifest{}, err } if err := containerdutil.ValidateMediaType(p, desc.MediaType); err != nil { return ocispec.Manifest{}, err } if err = json.Unmarshal(p, &index); err != nil { return ocispec.Manifest{}, err } var target ocispec.Descriptor found := false for _, m := range index.Manifests { p := platforms.DefaultSpec() if m.Platform != nil { p = *m.Platform } if !platforms.NewMatcher(platform).Match(p) { continue } target = m found = true break } if !found { return ocispec.Manifest{}, fmt.Errorf("no manifest found for platform") } return fetchManifestPlatform(ctx, fetcher, target, platform) } return ocispec.Manifest{}, fmt.Errorf("unknown mediatype %q", desc.MediaType) } stargz-snapshotter-0.12.0/task/000077500000000000000000000000001426301527400164225ustar00rootroot00000000000000stargz-snapshotter-0.12.0/task/task.go000066400000000000000000000132631426301527400177200ustar00rootroot00000000000000/* 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 task import ( "context" "sync" "sync/atomic" "time" "golang.org/x/sync/semaphore" ) // NewBackgroundTaskManager provides a task manager. You can specify the // concurrency of background tasks. When running a background task, this will be // forced to wait until no prioritized task is running for some period. You can // specify the period through the argument of this function, too. func NewBackgroundTaskManager(concurrency int64, period time.Duration) *BackgroundTaskManager { return &BackgroundTaskManager{ backgroundSem: semaphore.NewWeighted(concurrency), prioritizedTaskSilencePeriod: period, prioritizedTaskStartNotify: make(chan struct{}), prioritizedTaskDoneCond: sync.NewCond(&sync.Mutex{}), } } // BackgroundTaskManager is a task manager which manages prioritized tasks and // background tasks execution. Background tasks are less important than // prioritized tasks. You can let these background tasks not to use compute // resources (CPU, NW, etc...) during more important tasks(=prioritized tasks) // running. // // When you run a prioritised task and don't want background tasks to use // resources you can tell it this manager by calling DoPrioritizedTask method. // DonePrioritizedTask method must be called at the end of the prioritised task // execution. // // For running a background task, you can use InvokeBackgroundTask method. The // background task must be able to be cancelled via context.Context argument. // The task is forced to wait until no prioritized task is running for some // period. You can specify the period when making this manager instance. The // limited number of background tasks run simultaneously and you can specify the // concurrency when making this manager instance too. If a prioritized task // starts during the execution of background tasks, all background tasks running // will be cancelled via context. These cancelled tasks will be executed again // later, same as other background tasks (when no prioritized task is running // for some period). type BackgroundTaskManager struct { prioritizedTasks int64 backgroundSem *semaphore.Weighted prioritizedTaskSilencePeriod time.Duration prioritizedTaskStartNotify chan struct{} prioritizedTaskStartNotifyMu sync.Mutex prioritizedTaskDoneCond *sync.Cond } // DoPrioritizedTask tells the manager that we are running a prioritized task // and don't want background tasks to disturb resources(CPU, NW, etc...) func (ts *BackgroundTaskManager) DoPrioritizedTask() { // Notify the prioritized task execution to background tasks. ts.prioritizedTaskStartNotifyMu.Lock() atomic.AddInt64(&ts.prioritizedTasks, 1) close(ts.prioritizedTaskStartNotify) ts.prioritizedTaskStartNotify = make(chan struct{}) ts.prioritizedTaskStartNotifyMu.Unlock() } // DonePrioritizedTask tells the manager that we've done a prioritized task // and don't want background tasks to disturb resources(CPU, NW, etc...) func (ts *BackgroundTaskManager) DonePrioritizedTask() { go func() { // Notify the task completion after `ts.prioritizedTaskSilencePeriod` // so that background tasks aren't invoked immediately. time.Sleep(ts.prioritizedTaskSilencePeriod) atomic.AddInt64(&ts.prioritizedTasks, -1) ts.prioritizedTaskDoneCond.L.Lock() ts.prioritizedTaskDoneCond.Broadcast() ts.prioritizedTaskDoneCond.L.Unlock() }() } // InvokeBackgroundTask invokes a background task. The task is started only when // no prioritized tasks are running. Prioritized task's execution stops the // execution of all background tasks. Background task must be able to be // cancelled via context.Context argument and be able to be restarted again. func (ts *BackgroundTaskManager) InvokeBackgroundTask(do func(context.Context), timeout time.Duration) { for { // Wait until all prioritized tasks are done for { if atomic.LoadInt64(&ts.prioritizedTasks) <= 0 { break } // waits until a prioritized task is done ts.prioritizedTaskDoneCond.L.Lock() if atomic.LoadInt64(&ts.prioritizedTasks) > 0 { ts.prioritizedTaskDoneCond.Wait() } ts.prioritizedTaskDoneCond.L.Unlock() } // limited number of background tasks can run at once. // if prioritized tasks are running, cancel this task. if func() bool { ts.backgroundSem.Acquire(context.Background(), 1) defer ts.backgroundSem.Release(1) // Get notify the prioritized tasks execution. ts.prioritizedTaskStartNotifyMu.Lock() ch := ts.prioritizedTaskStartNotify tasks := atomic.LoadInt64(&ts.prioritizedTasks) ts.prioritizedTaskStartNotifyMu.Unlock() if tasks > 0 { return false } // Invoke the background task. if some prioritized tasks added during // execution, cancel it and try it later. var ( done = make(chan struct{}) ctx, cancel = context.WithTimeout(context.Background(), timeout) ) defer cancel() go func() { do(ctx) close(done) }() // Wait until the background task is done or canceled. select { case <-ch: // some prioritized tasks started; retry it later cancel() return false case <-done: // All tasks completed } return true }() { break } } } stargz-snapshotter-0.12.0/task/task_test.go000066400000000000000000000175151426301527400207630ustar00rootroot00000000000000/* 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 task import ( "context" "fmt" "sync" "testing" "time" ) // TestBackgroundTasks tests background task manager. func TestBackgroundTasks(t *testing.T) { doGo := func(f func()) { invoked := make(chan struct{}) go func() { invoked <- struct{}{} f() }() <-invoked time.Sleep(10 * time.Millisecond) } wait := func(t *testing.T, name string, done func() bool) { ch := make(chan struct{}) go func() { for !done() { time.Sleep(10 * time.Millisecond) } ch <- struct{}{} }() select { case <-ch: case <-time.After(5 * time.Second): t.Fatalf("timeout for %q", name) } } tests := []struct { name string concurrency int64 checkInterval time.Duration context func(t *testing.T, pm *BackgroundTaskManager, task1, task2, task3, task4 *sampleTask) assert func(task1, task2, task3, task4 *sampleTask) bool }{ { name: "privilege_running", concurrency: 1, checkInterval: 100 * time.Millisecond, context: func(t *testing.T, pm *BackgroundTaskManager, task1, task2, task3, task4 *sampleTask) { pm.DoPrioritizedTask() doGo(func() { pm.InvokeBackgroundTask(task1.do, 24*time.Hour) }) time.Sleep(300 * time.Millisecond) // wait for long time... }, assert: func(task1, task2, task3, task4 *sampleTask) bool { return (task1.assert(false, false, false)) }, }, { name: "concurrency", concurrency: 2, checkInterval: time.Duration(0), // We don't care prioritized tasks now context: func(t *testing.T, pm *BackgroundTaskManager, task1, task2, task3, task4 *sampleTask) { doGo(func() { pm.InvokeBackgroundTask(task1.do, 24*time.Hour) }) wait(t, "task1 started", task1.checkStarted()) doGo(func() { pm.InvokeBackgroundTask(task2.do, 24*time.Hour) }) wait(t, "task2 started", task2.checkStarted()) doGo(func() { pm.InvokeBackgroundTask(task3.do, 24*time.Hour) }) doGo(func() { pm.InvokeBackgroundTask(task4.do, 24*time.Hour) }) time.Sleep(300 * time.Millisecond) // wait for long time... }, assert: func(task1, task2, task3, task4 *sampleTask) bool { return (task1.assert(true, false, false) && task2.assert(true, false, false) && task3.assert(false, false, false) && task4.assert(false, false, false)) }, }, { name: "cancel", concurrency: 2, checkInterval: 100 * time.Millisecond, context: func(t *testing.T, pm *BackgroundTaskManager, task1, task2, task3, task4 *sampleTask) { doGo(func() { pm.InvokeBackgroundTask(task1.do, 24*time.Hour) }) wait(t, "task1 started", task1.checkStarted()) doGo(func() { pm.InvokeBackgroundTask(task2.do, 24*time.Hour) }) wait(t, "task2 started", task2.checkStarted()) pm.DoPrioritizedTask() wait(t, "task1 canceled", task1.checkCanceled()) wait(t, "task2 canceled", task2.checkCanceled()) }, assert: func(task1, task2, task3, task4 *sampleTask) bool { return (task1.assert(true, false, true) && task2.assert(true, false, true)) }, }, { name: "resume", concurrency: 2, checkInterval: 100 * time.Millisecond, context: func(t *testing.T, pm *BackgroundTaskManager, task1, task2, task3, task4 *sampleTask) { doGo(func() { pm.InvokeBackgroundTask(task1.do, 24*time.Hour) }) wait(t, "task1 started", task1.checkStarted()) doGo(func() { pm.InvokeBackgroundTask(task2.do, 24*time.Hour) }) wait(t, "task2 started", task2.checkStarted()) pm.DoPrioritizedTask() wait(t, "task1 canceled", task1.checkCanceled()) wait(t, "task2 canceled", task2.checkCanceled()) task1.reset() task2.reset() pm.DonePrioritizedTask() wait(t, "task1 resumed", task1.checkStarted()) wait(t, "task2 resumed", task2.checkStarted()) }, assert: func(task1, task2, task3, task4 *sampleTask) bool { return (task1.assert(true, false, false) && task2.assert(true, false, false)) }, }, { name: "finish_partial", concurrency: 1, checkInterval: time.Duration(0), // We don't care prioritized tasks now context: func(t *testing.T, pm *BackgroundTaskManager, task1, task2, task3, task4 *sampleTask) { doGo(func() { pm.InvokeBackgroundTask(task1.do, 24*time.Hour) }) wait(t, "task1 started", task1.checkStarted()) doGo(func() { pm.InvokeBackgroundTask(task2.do, 24*time.Hour) }) task1.finish() wait(t, "task1 done", task1.checkDone()) wait(t, "task2 started", task2.checkStarted()) }, assert: func(task1, task2, task3, task4 *sampleTask) bool { return (task1.assert(true, true, false) && task2.assert(true, false, false)) }, }, { name: "finish_all", concurrency: 1, checkInterval: time.Duration(0), // We don't care prioritized tasks now context: func(t *testing.T, pm *BackgroundTaskManager, task1, task2, task3, task4 *sampleTask) { doGo(func() { pm.InvokeBackgroundTask(task1.do, 24*time.Hour) }) wait(t, "task1 started", task1.checkStarted()) doGo(func() { pm.InvokeBackgroundTask(task2.do, 24*time.Hour) }) task1.finish() wait(t, "task1 done", task1.checkDone()) wait(t, "task2 started", task2.checkStarted()) task2.finish() wait(t, "task2 done", task2.checkDone()) }, assert: func(task1, task2, task3, task4 *sampleTask) bool { return (task1.assert(true, true, false) && task2.assert(true, true, false)) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var ( pm = NewBackgroundTaskManager(tt.concurrency, tt.checkInterval) task1 = newSampleTask() task2 = newSampleTask() task3 = newSampleTask() task4 = newSampleTask() ) tt.context(t, pm, task1, task2, task3, task4) if !tt.assert(task1, task2, task3, task4) { t.Errorf("assertion failed:status=(task1:%q,task2:%q,task3:%q,task4:%q)", task1.dumpStatus(), task2.dumpStatus(), task3.dumpStatus(), task4.dumpStatus()) } }) } } type sampleTask struct { started bool done bool canceled bool mu sync.Mutex finishCh chan struct{} } func newSampleTask() *sampleTask { return &sampleTask{ finishCh: make(chan struct{}), } } func (st *sampleTask) checkStarted() func() bool { return func() bool { st.mu.Lock() defer st.mu.Unlock() return st.started } } func (st *sampleTask) checkDone() func() bool { return func() bool { st.mu.Lock() defer st.mu.Unlock() return st.done } } func (st *sampleTask) checkCanceled() func() bool { return func() bool { st.mu.Lock() defer st.mu.Unlock() return st.canceled } } func (st *sampleTask) do(ctx context.Context) { st.mu.Lock() st.started = true st.mu.Unlock() select { case <-st.finishCh: st.mu.Lock() st.done = true st.mu.Unlock() case <-ctx.Done(): st.mu.Lock() st.canceled = true st.mu.Unlock() } } func (st *sampleTask) finish() { st.finishCh <- struct{}{} } func (st *sampleTask) reset() { st.mu.Lock() st.started, st.done, st.canceled = false, false, false st.mu.Unlock() st.finishCh = make(chan struct{}) } func (st *sampleTask) dumpStatus() string { st.mu.Lock() defer st.mu.Unlock() return fmt.Sprintf("started=%v,done=%v,canceled=%v", st.started, st.done, st.canceled) } func (st *sampleTask) assert(started, done, canceled bool) bool { st.mu.Lock() defer st.mu.Unlock() return (st.started == started) && (st.done == done) && (st.canceled == canceled) } stargz-snapshotter-0.12.0/util/000077500000000000000000000000001426301527400164355ustar00rootroot00000000000000stargz-snapshotter-0.12.0/util/cacheutil/000077500000000000000000000000001426301527400203765ustar00rootroot00000000000000stargz-snapshotter-0.12.0/util/cacheutil/lrucache.go000066400000000000000000000077301426301527400225220ustar00rootroot00000000000000/* 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 cacheutil import ( "sync" "github.com/golang/groupcache/lru" ) // LRUCache is "groupcache/lru"-like cache. The difference is that "groupcache/lru" immediately // finalizes theevicted contents using OnEvicted callback but our version strictly tracks the // reference counts of contents and calls OnEvicted when nobody refers to the evicted contents. type LRUCache struct { cache *lru.Cache mu sync.Mutex // OnEvicted optionally specifies a callback function to be // executed when an entry is purged from the cache. OnEvicted func(key string, value interface{}) } // NewLRUCache creates new lru cache. func NewLRUCache(maxEntries int) *LRUCache { inner := lru.New(maxEntries) inner.OnEvicted = func(key lru.Key, value interface{}) { // Decrease the ref count incremented in Add(). // When nobody refers to this value, this value will be finalized via refCounter. value.(*refCounter).finalize() } return &LRUCache{ cache: inner, } } // Get retrieves the specified object from the cache and increments the reference counter of the // target content. Client must call `done` callback to decrease the reference count when the value // will no longer be used. func (c *LRUCache) Get(key string) (value interface{}, done func(), ok bool) { c.mu.Lock() defer c.mu.Unlock() o, ok := c.cache.Get(key) if !ok { return nil, nil, false } rc := o.(*refCounter) rc.inc() return rc.v, c.decreaseOnceFunc(rc), true } // Add adds object to the cache and returns the cached contents with incrementing the reference count. // If the specified content already exists in the cache, this sets `added` to false and returns // "already cached" content (i.e. doesn't replace the content with the new one). Client must call // `done` callback to decrease the counter when the value will no longer be used. func (c *LRUCache) Add(key string, value interface{}) (cachedValue interface{}, done func(), added bool) { c.mu.Lock() defer c.mu.Unlock() if o, ok := c.cache.Get(key); ok { rc := o.(*refCounter) rc.inc() return rc.v, c.decreaseOnceFunc(rc), false } rc := &refCounter{ key: key, v: value, onEvicted: c.OnEvicted, } rc.initialize() // Keep this object having at least 1 ref count (will be decreased in OnEviction) rc.inc() // The client references this object (will be decreased on "done") c.cache.Add(key, rc) return rc.v, c.decreaseOnceFunc(rc), true } // Remove removes the specified contents from the cache. OnEvicted callback will be called when // nobody refers to the removed content. func (c *LRUCache) Remove(key string) { c.mu.Lock() defer c.mu.Unlock() c.cache.Remove(key) } func (c *LRUCache) decreaseOnceFunc(rc *refCounter) func() { var once sync.Once return func() { c.mu.Lock() defer c.mu.Unlock() once.Do(func() { rc.dec() }) } } type refCounter struct { onEvicted func(key string, value interface{}) key string v interface{} refCounts int64 mu sync.Mutex initializeOnce sync.Once finalizeOnce sync.Once } func (r *refCounter) inc() { r.mu.Lock() defer r.mu.Unlock() r.refCounts++ } func (r *refCounter) dec() { r.mu.Lock() defer r.mu.Unlock() r.refCounts-- if r.refCounts <= 0 && r.onEvicted != nil { // nobody will refer this object r.onEvicted(r.key, r.v) } } func (r *refCounter) initialize() { r.initializeOnce.Do(func() { r.inc() }) } func (r *refCounter) finalize() { r.finalizeOnce.Do(func() { r.dec() }) } stargz-snapshotter-0.12.0/util/cacheutil/lrucache_test.go000066400000000000000000000067641426301527400235670ustar00rootroot00000000000000/* 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 cacheutil import ( "fmt" "testing" ) // TestLRUAdd tests Add API func TestLRUAdd(t *testing.T) { c := NewLRUCache(10) key, value := "key1", "abcd" v, _, added := c.Add(key, value) if !added { t.Fatalf("failed to add %q", key) } else if v.(string) != value { t.Fatalf("returned different object for %q; want %q; got %q", key, value, v.(string)) } key, newvalue := "key1", "dummy" v, _, added = c.Add(key, newvalue) if added || v.(string) != value { t.Fatalf("%q must be originally stored one; want %q; got %q (added:%v)", key, value, v.(string), added) } } // TestLRUGet tests Get API func TestLRUGet(t *testing.T) { c := NewLRUCache(10) key, value := "key1", "abcd" v, _, added := c.Add(key, value) if !added { t.Fatalf("failed to add %q", key) } else if v.(string) != value { t.Fatalf("returned different object for %q; want %q; got %q", key, value, v.(string)) } v, _, ok := c.Get(key) if !ok { t.Fatalf("failed to get obj %q (%q)", key, value) } else if v.(string) != value { t.Fatalf("unexpected object for %q; want %q; got %q", key, value, v.(string)) } } // TestLRURemoe tests Remove API func TestLRURemove(t *testing.T) { var evicted []string c := NewLRUCache(2) c.OnEvicted = func(key string, value interface{}) { evicted = append(evicted, key) } key1, value1 := "key1", "abcd1" _, done1, _ := c.Add(key1, value1) _, done12, _ := c.Get(key1) c.Remove(key1) if len(evicted) != 0 { t.Fatalf("no content must be evicted after remove") } done1() if len(evicted) != 0 { t.Fatalf("no content must be evicted until all reference are discarded") } done12() if len(evicted) != 1 { t.Fatalf("content must be evicted") } if evicted[0] != key1 { t.Fatalf("1st content %q must be evicted but got %q", key1, evicted[0]) } } // TestLRUEviction tests that eviction occurs when the overflow happens. func TestLRUEviction(t *testing.T) { var evicted []string c := NewLRUCache(2) c.OnEvicted = func(key string, value interface{}) { evicted = append(evicted, key) } key1, value1 := "key1", "abcd1" key2, value2 := "key2", "abcd2" _, done1, _ := c.Add(key1, value1) _, done2, _ := c.Add(key2, value2) _, done22, _ := c.Get(key2) if len(evicted) != 0 { t.Fatalf("no content must be evicted after addition") } for i := 0; i < 2; i++ { c.Add(fmt.Sprintf("key-add-%d", i), fmt.Sprintf("abcd-add-%d", i)) } if len(evicted) != 0 { t.Fatalf("no content must be evicted after overflow") } done1() if len(evicted) != 1 { t.Fatalf("1 content must be evicted") } if evicted[0] != key1 { t.Fatalf("1st content %q must be evicted but got %q", key1, evicted[0]) } done2() // effective done2() // ignored done2() // ignored if len(evicted) != 1 { t.Fatalf("only 1 content must be evicted") } done22() if len(evicted) != 2 { t.Fatalf("2 contents must be evicted") } if evicted[1] != key2 { t.Fatalf("2nd content %q must be evicted but got %q", key2, evicted[1]) } } stargz-snapshotter-0.12.0/util/cacheutil/ttlcache.go000066400000000000000000000065071426301527400225240ustar00rootroot00000000000000/* 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 cacheutil import ( "sync" "time" ) // TTLCache is a ttl-based cache with reference counters. // Each elements is deleted as soon as expiering the configured ttl. type TTLCache struct { m map[string]*refCounterWithTimer mu sync.Mutex ttl time.Duration // OnEvicted optionally specifies a callback function to be // executed when an entry is purged from the cache. OnEvicted func(key string, value interface{}) } // NewTTLCache creates a new ttl-based cache. func NewTTLCache(ttl time.Duration) *TTLCache { return &TTLCache{ m: make(map[string]*refCounterWithTimer), ttl: ttl, } } // Get retrieves the specified object from the cache and increments the reference counter of the // target content. Client must call `done` callback to decrease the reference count when the value // will no longer be used. func (c *TTLCache) Get(key string) (value interface{}, done func(), ok bool) { c.mu.Lock() defer c.mu.Unlock() rc, ok := c.m[key] if !ok { return nil, nil, false } rc.inc() return rc.v, c.decreaseOnceFunc(rc), true } // Add adds object to the cache and returns the cached contents with incrementing the reference count. // If the specified content already exists in the cache, this sets `added` to false and returns // "already cached" content (i.e. doesn't replace the content with the new one). Client must call // `done` callback to decrease the counter when the value will no longer be used. func (c *TTLCache) Add(key string, value interface{}) (cachedValue interface{}, done func(), added bool) { c.mu.Lock() defer c.mu.Unlock() if rc, ok := c.m[key]; ok { rc.inc() return rc.v, c.decreaseOnceFunc(rc), false } rc := &refCounterWithTimer{ refCounter: &refCounter{ key: key, v: value, onEvicted: c.OnEvicted, }, } rc.initialize() // Keep this object having at least 1 ref count (will be decreased in OnEviction) rc.inc() // The client references this object (will be decreased on "done") rc.t = time.AfterFunc(c.ttl, func() { c.mu.Lock() defer c.mu.Unlock() c.evictLocked(key) }) c.m[key] = rc return rc.v, c.decreaseOnceFunc(rc), true } // Remove removes the specified contents from the cache. OnEvicted callback will be called when // nobody refers to the removed content. func (c *TTLCache) Remove(key string) { c.mu.Lock() defer c.mu.Unlock() c.evictLocked(key) } func (c *TTLCache) evictLocked(key string) { if rc, ok := c.m[key]; ok { delete(c.m, key) rc.t.Stop() // stop timer to prevent GC to this content anymore rc.finalize() } } func (c *TTLCache) decreaseOnceFunc(rc *refCounterWithTimer) func() { var once sync.Once return func() { c.mu.Lock() defer c.mu.Unlock() once.Do(func() { rc.dec() }) } } type refCounterWithTimer struct { *refCounter t *time.Timer } stargz-snapshotter-0.12.0/util/cacheutil/ttlcache_test.go000066400000000000000000000113031426301527400235510ustar00rootroot00000000000000/* 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 cacheutil import ( "sync" "testing" "time" ) // TestTTLAdd tests Add API func TestTTLAdd(t *testing.T) { c := NewTTLCache(time.Hour) key, value := "key1", "abcd" v, _, added := c.Add(key, value) if !added { t.Fatalf("failed to add %q", key) } else if v.(string) != value { t.Fatalf("returned different object for %q; want %q; got %q", key, value, v.(string)) } key, newvalue := "key1", "dummy" v, _, added = c.Add(key, newvalue) if added || v.(string) != value { t.Fatalf("%q must be originally stored one; want %q; got %q (added:%v)", key, value, v.(string), added) } } // TestTTLGet tests Get API func TestTTLGet(t *testing.T) { c := NewTTLCache(time.Hour) key, value := "key1", "abcd" v, _, added := c.Add(key, value) if !added { t.Fatalf("failed to add %q", key) } else if v.(string) != value { t.Fatalf("returned different object for %q; want %q; got %q", key, value, v.(string)) } v, _, ok := c.Get(key) if !ok { t.Fatalf("failed to get obj %q (%q)", key, value) } else if v.(string) != value { t.Fatalf("unexpected object for %q; want %q; got %q", key, value, v.(string)) } } // TestTTLRemove tests Remove API func TestTTLRemove(t *testing.T) { var evicted []string c := NewTTLCache(time.Hour) c.OnEvicted = func(key string, value interface{}) { evicted = append(evicted, key) } key1, value1 := "key1", "abcd1" _, done1, _ := c.Add(key1, value1) _, done12, _ := c.Get(key1) c.Remove(key1) if len(evicted) != 0 { t.Fatalf("no content must be evicted after remove") } done1() if len(evicted) != 0 { t.Fatalf("no content must be evicted until all reference are discarded") } done12() if len(evicted) != 1 { t.Fatalf("content must be evicted") } if evicted[0] != key1 { t.Fatalf("1st content %q must be evicted but got %q", key1, evicted[0]) } } // TestTTLRemoveOverwritten tests old gc doesn't affect overwritten content func TestTTLRemoveOverwritten(t *testing.T) { var evicted []string c := NewTTLCache(3 * time.Second) c.OnEvicted = func(key string, value interface{}) { evicted = append(evicted, key) } key1, value1 := "key1", "abcd1" _, done1, _ := c.Add(key1, value1) done1() c.Remove(key1) // remove key1 as soon as possible // add another content with a new key time.Sleep(2 * time.Second) value12 := value1 + "!" _, done12, _ := c.Add(key1, value12) time.Sleep(2 * time.Second) // spent 4 sec (larger than ttl) since the previous key1 was added. // but the *newly-added* key1 hasn't been expierd yet so key1 must remain. v1, done122, getOK := c.Get(key1) if !getOK { t.Fatalf("unexpected eviction") } if s1, ok := v1.(string); !ok || s1 != value12 { t.Fatalf("unexpected content %q(%v) != %q", s1, ok, value12) } time.Sleep(2 * time.Second) done122() done12() // spent 4 sec since the new key1 was added. This should be expierd. if _, _, ok := c.Get(key1); ok { t.Fatalf("%q must be expierd but remaining", key1) } } // TestTTLEviction tests contents are evicted after TTL witout remaining reference. func TestTTLEviction(t *testing.T) { var ( evicted []string evictedMu sync.Mutex ) c := NewTTLCache(time.Second) c.OnEvicted = func(key string, value interface{}) { evictedMu.Lock() evicted = append(evicted, key) evictedMu.Unlock() } key1, value1 := "key1", "abcd1" key2, value2 := "key2", "abcd2" _, done1, _ := c.Add(key1, value1) done1() // evict key1 on expiering ttl _, done2, _ := c.Add(key2, value2) _, done22, _ := c.Get(key2) // hold reference of key2 to prevent eviction time.Sleep(3 * time.Second) // wait until elements reach ttl evictedMu.Lock() if len(evicted) != 1 { t.Fatalf("1 content must be removed") } if evicted[0] != key1 { t.Fatalf("1st content %q must be evicted but got %q", key1, evicted[0]) } evictedMu.Unlock() done2() // effective done2() // ignored done2() // ignored evictedMu.Lock() if len(evicted) != 1 { t.Fatalf("only 1 content must be evicted") } evictedMu.Unlock() done22() evictedMu.Lock() if len(evicted) != 2 { t.Fatalf("2 contents must be evicted") } if evicted[1] != key2 { t.Fatalf("2nd content %q must be evicted but got %q", key2, evicted[1]) } evictedMu.Unlock() } stargz-snapshotter-0.12.0/util/containerdutil/000077500000000000000000000000001426301527400214615ustar00rootroot00000000000000stargz-snapshotter-0.12.0/util/containerdutil/manifest.go000066400000000000000000000121041426301527400236140ustar00rootroot00000000000000/* 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 containerdutil import ( "context" "encoding/json" "fmt" "sort" "github.com/containerd/containerd/content" "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/images" "github.com/containerd/containerd/platforms" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) func ManifestDesc(ctx context.Context, provider content.Provider, image ocispec.Descriptor, platform platforms.MatchComparer) (ocispec.Descriptor, error) { var ( limit = 1 m []ocispec.Descriptor wasIndex bool ) if err := images.Walk(ctx, images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { switch desc.MediaType { case images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest: p, err := content.ReadBlob(ctx, provider, desc) if err != nil { return nil, err } if err := ValidateMediaType(p, desc.MediaType); err != nil { return nil, err } var manifest ocispec.Manifest if err := json.Unmarshal(p, &manifest); err != nil { return nil, err } if desc.Digest != image.Digest { if desc.Platform != nil && !platform.Match(*desc.Platform) { return nil, nil } if desc.Platform == nil { p, err := content.ReadBlob(ctx, provider, manifest.Config) if err != nil { return nil, err } var image ocispec.Image if err := json.Unmarshal(p, &image); err != nil { return nil, err } if !platform.Match(platforms.Normalize(ocispec.Platform{OS: image.OS, Architecture: image.Architecture})) { return nil, nil } } } m = append(m, desc) return nil, nil case images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex: p, err := content.ReadBlob(ctx, provider, desc) if err != nil { return nil, err } if err := ValidateMediaType(p, desc.MediaType); err != nil { return nil, err } var idx ocispec.Index if err := json.Unmarshal(p, &idx); err != nil { return nil, err } var descs []ocispec.Descriptor for _, d := range idx.Manifests { if d.Platform == nil || platform.Match(*d.Platform) { descs = append(descs, d) } } sort.SliceStable(descs, func(i, j int) bool { if descs[i].Platform == nil { return false } if descs[j].Platform == nil { return true } return platform.Less(*descs[i].Platform, *descs[j].Platform) }) wasIndex = true if len(descs) > limit { return descs[:limit], nil } return descs, nil } return nil, fmt.Errorf("unexpected media type %v for %v: %w", desc.MediaType, desc.Digest, errdefs.ErrNotFound) }), image); err != nil { return ocispec.Descriptor{}, err } if len(m) == 0 { err := fmt.Errorf("manifest %v: %w", image.Digest, errdefs.ErrNotFound) if wasIndex { err = fmt.Errorf("no match for platform in manifest %v: %w", image.Digest, errdefs.ErrNotFound) } return ocispec.Descriptor{}, err } return m[0], nil } // Forked from github.com/containerd/containerd/images/image.go // commit: a776a27af54a803657d002e7574a4425b3949f56 // unknownDocument represents a manifest, manifest list, or index that has not // yet been validated. type unknownDocument struct { MediaType string `json:"mediaType,omitempty"` Config json.RawMessage `json:"config,omitempty"` Layers json.RawMessage `json:"layers,omitempty"` Manifests json.RawMessage `json:"manifests,omitempty"` FSLayers json.RawMessage `json:"fsLayers,omitempty"` // schema 1 } // ValidateMediaType returns an error if the byte slice is invalid JSON or if // the media type identifies the blob as one format but it contains elements of // another format. func ValidateMediaType(b []byte, mt string) error { var doc unknownDocument if err := json.Unmarshal(b, &doc); err != nil { return err } if len(doc.FSLayers) != 0 { return fmt.Errorf("media-type: schema 1 not supported") } switch mt { case images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest: if len(doc.Manifests) != 0 || doc.MediaType == images.MediaTypeDockerSchema2ManifestList || doc.MediaType == ocispec.MediaTypeImageIndex { return fmt.Errorf("media-type: expected manifest but found index (%s)", mt) } case images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex: if len(doc.Config) != 0 || len(doc.Layers) != 0 || doc.MediaType == images.MediaTypeDockerSchema2Manifest || doc.MediaType == ocispec.MediaTypeImageManifest { return fmt.Errorf("media-type: expected index but found manifest (%s)", mt) } } return nil } stargz-snapshotter-0.12.0/util/ioutils/000077500000000000000000000000001426301527400201255ustar00rootroot00000000000000stargz-snapshotter-0.12.0/util/ioutils/countwriter.go000066400000000000000000000016061426301527400230440ustar00rootroot00000000000000/* 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 ioutils import "sync" type CountWriter struct { n int64 mu sync.Mutex } func (c *CountWriter) Write(p []byte) (n int, err error) { c.mu.Lock() c.n += int64(len(p)) c.mu.Unlock() return len(p), nil } func (c *CountWriter) Size() (n int64) { c.mu.Lock() n = c.n c.mu.Unlock() return } stargz-snapshotter-0.12.0/util/namedmutex/000077500000000000000000000000001426301527400206045ustar00rootroot00000000000000stargz-snapshotter-0.12.0/util/namedmutex/namedmutex.go000066400000000000000000000027571426301527400233150ustar00rootroot00000000000000/* 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 namedmutex provides NamedMutex that wraps sync.Mutex // and provides namespaced mutex. package namedmutex import ( "sync" ) // NamedMutex wraps sync.Mutex and provides namespaced mutex. type NamedMutex struct { muMap map[string]*sync.Mutex refMap map[string]int mu sync.Mutex } // Lock locks the mutex of the given name func (nl *NamedMutex) Lock(name string) { nl.mu.Lock() if nl.muMap == nil { nl.muMap = make(map[string]*sync.Mutex) } if nl.refMap == nil { nl.refMap = make(map[string]int) } if _, ok := nl.muMap[name]; !ok { nl.muMap[name] = &sync.Mutex{} } mu := nl.muMap[name] nl.refMap[name]++ nl.mu.Unlock() mu.Lock() } // Unlock unlocks the mutex of the given name func (nl *NamedMutex) Unlock(name string) { nl.mu.Lock() mu := nl.muMap[name] nl.refMap[name]-- if nl.refMap[name] <= 0 { delete(nl.muMap, name) delete(nl.refMap, name) } nl.mu.Unlock() mu.Unlock() } stargz-snapshotter-0.12.0/util/testutil/000077500000000000000000000000001426301527400203125ustar00rootroot00000000000000stargz-snapshotter-0.12.0/util/testutil/ensurehello.go000066400000000000000000000050221426301527400231650ustar00rootroot00000000000000/* 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 testutil import ( "compress/gzip" "context" "fmt" "io" "net/http" "os" "github.com/containerd/containerd/content" "github.com/containerd/containerd/content/local" "github.com/containerd/containerd/images/archive" "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) const ( // HelloArchiveURL points to an OCI archive of `hello-world`. // Exported from `docker.io/library/hello-world@sha256:1a523af650137b8accdaed439c17d684df61ee4d74feac151b5b337bd29e7eec` . // See https://github.com/AkihiroSuda/test-oci-archives/releases/tag/v20210101 HelloArchiveURL = "https://github.com/AkihiroSuda/test-oci-archives/releases/download/v20210101/hello-world.tar.gz" // HelloArchiveDigest is the digest of the archive. HelloArchiveDigest = "sha256:5aa022621c4de0e941ab2a30d4569c403e156b4ba2de2ec32e382ae8679f40e1" ) // EnsureHello creates a temp content store and ensures `hello-world` image from HelloArchiveURL into the store. func EnsureHello(ctx context.Context) (*ocispec.Descriptor, content.Store, error) { // Pulling an image without the daemon is a mess, so we use OCI archive here. resp, err := http.Get(HelloArchiveURL) if err != nil { return nil, nil, err } defer resp.Body.Close() sha256Digester := digest.SHA256.Digester() sha256Hasher := sha256Digester.Hash() tr := io.TeeReader(resp.Body, sha256Hasher) gzReader, err := gzip.NewReader(tr) if err != nil { return nil, nil, err } tempDir, err := os.MkdirTemp("", "test-estargz") if err != nil { return nil, nil, err } cs, err := local.NewStore(tempDir) if err != nil { return nil, nil, err } desc, err := archive.ImportIndex(ctx, cs, gzReader) if err != nil { return nil, nil, err } resp.Body.Close() if d := sha256Digester.Digest().String(); d != HelloArchiveDigest { err = fmt.Errorf("expected digest of %q to be %q, got %q", HelloArchiveURL, HelloArchiveDigest, d) return nil, nil, err } return &desc, cs, nil } stargz-snapshotter-0.12.0/util/testutil/estargz.go000066400000000000000000000040541426301527400223230ustar00rootroot00000000000000/* 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 testutil import ( "bytes" "io" "github.com/containerd/stargz-snapshotter/estargz" digest "github.com/opencontainers/go-digest" ) type buildEStargzOptions struct { estargzOptions []estargz.Option buildTarOptions []BuildTarOption } type BuildEStargzOption func(o *buildEStargzOptions) error // WithEStargzOptions specifies options for estargz lib func WithEStargzOptions(eo ...estargz.Option) BuildEStargzOption { return func(o *buildEStargzOptions) error { o.estargzOptions = eo return nil } } // WithBuildTarOptions option specifies the options for tar creation func WithBuildTarOptions(to ...BuildTarOption) BuildEStargzOption { return func(o *buildEStargzOptions) error { o.buildTarOptions = to return nil } } func BuildEStargz(ents []TarEntry, opts ...BuildEStargzOption) (*io.SectionReader, digest.Digest, error) { var beOpts buildEStargzOptions for _, o := range opts { o(&beOpts) } tarBuf := new(bytes.Buffer) if _, err := io.Copy(tarBuf, BuildTar(ents, beOpts.buildTarOptions...)); err != nil { return nil, "", err } tarData := tarBuf.Bytes() rc, err := estargz.Build( io.NewSectionReader(bytes.NewReader(tarData), 0, int64(len(tarData))), beOpts.estargzOptions...) if err != nil { return nil, "", err } defer rc.Close() vsb := new(bytes.Buffer) if _, err := io.Copy(vsb, rc); err != nil { return nil, "", err } vsbb := vsb.Bytes() return io.NewSectionReader(bytes.NewReader(vsbb), 0, int64(len(vsbb))), rc.TOCDigest(), nil } stargz-snapshotter-0.12.0/util/testutil/tar.go000066400000000000000000000166651426301527400214450ustar00rootroot00000000000000/* 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 testutil // This utility helps test codes to generate sample tar blobs. import ( "archive/tar" "fmt" "io" "os" "strings" "time" ) // TarEntry is an entry of tar. type TarEntry interface { AppendTar(tw *tar.Writer, opts BuildTarOptions) error } // BuildTarOptions is a set of options used during building blob. type BuildTarOptions struct { // Prefix is the prefix string need to be added to each file name (e.g. "./", "/", etc.) Prefix string } // BuildTarOption is an option used during building blob. type BuildTarOption func(o *BuildTarOptions) // WithPrefix is an option to add a prefix string to each file name (e.g. "./", "/", etc.) func WithPrefix(prefix string) BuildTarOption { return func(o *BuildTarOptions) { o.Prefix = prefix } } // BuildTar builds a tar blob func BuildTar(ents []TarEntry, opts ...BuildTarOption) io.Reader { var bo BuildTarOptions for _, o := range opts { o(&bo) } pr, pw := io.Pipe() go func() { tw := tar.NewWriter(pw) for _, ent := range ents { if err := ent.AppendTar(tw, bo); err != nil { pw.CloseWithError(err) return } } if err := tw.Close(); err != nil { pw.CloseWithError(err) return } pw.Close() }() return pr } type tarEntryFunc func(*tar.Writer, BuildTarOptions) error func (f tarEntryFunc) AppendTar(tw *tar.Writer, opts BuildTarOptions) error { return f(tw, opts) } // DirectoryBuildTarOption is an option for a directory entry. type DirectoryBuildTarOption func(o *dirOpts) type dirOpts struct { uid int gid int xattrs map[string]string mode *os.FileMode modTime time.Time } // WithDirModTime specifies the modtime of the dir. func WithDirModTime(modTime time.Time) DirectoryBuildTarOption { return func(o *dirOpts) { o.modTime = modTime } } // WithDirOwner specifies the owner of the directory. func WithDirOwner(uid, gid int) DirectoryBuildTarOption { return func(o *dirOpts) { o.uid = uid o.gid = gid } } // WithDirXattrs specifies the extended attributes of the directory. func WithDirXattrs(xattrs map[string]string) DirectoryBuildTarOption { return func(o *dirOpts) { o.xattrs = xattrs } } // WithDirMode specifies the mode of the directory. func WithDirMode(mode os.FileMode) DirectoryBuildTarOption { return func(o *dirOpts) { o.mode = &mode } } // Dir is a directory entry func Dir(name string, opts ...DirectoryBuildTarOption) TarEntry { return tarEntryFunc(func(tw *tar.Writer, buildOpts BuildTarOptions) error { var dOpts dirOpts for _, o := range opts { o(&dOpts) } if !strings.HasSuffix(name, "/") { panic(fmt.Sprintf("missing trailing slash in dir %q ", name)) } var mode int64 = 0755 if dOpts.mode != nil { mode = permAndExtraMode2TarMode(*dOpts.mode) } return tw.WriteHeader(&tar.Header{ Typeflag: tar.TypeDir, Name: buildOpts.Prefix + name, Mode: mode, ModTime: dOpts.modTime, Xattrs: dOpts.xattrs, Uid: dOpts.uid, Gid: dOpts.gid, }) }) } // FileBuildTarOption is an option for a file entry. type FileBuildTarOption func(o *fileOpts) type fileOpts struct { uid int gid int xattrs map[string]string mode *os.FileMode modTime time.Time } // WithFileOwner specifies the owner of the file. func WithFileOwner(uid, gid int) FileBuildTarOption { return func(o *fileOpts) { o.uid = uid o.gid = gid } } // WithFileXattrs specifies the extended attributes of the file. func WithFileXattrs(xattrs map[string]string) FileBuildTarOption { return func(o *fileOpts) { o.xattrs = xattrs } } // WithFileModTime specifies the modtime of the file. func WithFileModTime(modTime time.Time) FileBuildTarOption { return func(o *fileOpts) { o.modTime = modTime } } // WithFileMode specifies the mode of the file. func WithFileMode(mode os.FileMode) FileBuildTarOption { return func(o *fileOpts) { o.mode = &mode } } // File is a regilar file entry func File(name, contents string, opts ...FileBuildTarOption) TarEntry { return tarEntryFunc(func(tw *tar.Writer, buildOpts BuildTarOptions) error { var fOpts fileOpts for _, o := range opts { o(&fOpts) } if strings.HasSuffix(name, "/") { return fmt.Errorf("bogus trailing slash in file %q", name) } var mode int64 = 0644 if fOpts.mode != nil { mode = permAndExtraMode2TarMode(*fOpts.mode) } if err := tw.WriteHeader(&tar.Header{ Typeflag: tar.TypeReg, Name: buildOpts.Prefix + name, Mode: mode, ModTime: fOpts.modTime, Xattrs: fOpts.xattrs, Size: int64(len(contents)), Uid: fOpts.uid, Gid: fOpts.gid, }); err != nil { return err } _, err := io.WriteString(tw, contents) return err }) } // Symlink is a symlink entry func Symlink(name, target string) TarEntry { return tarEntryFunc(func(tw *tar.Writer, buildOpts BuildTarOptions) error { return tw.WriteHeader(&tar.Header{ Typeflag: tar.TypeSymlink, Name: buildOpts.Prefix + name, Linkname: target, Mode: 0644, }) }) } // Link is a hard-link entry func Link(name, linkname string) TarEntry { now := time.Now() return tarEntryFunc(func(w *tar.Writer, buildOpts BuildTarOptions) error { return w.WriteHeader(&tar.Header{ Typeflag: tar.TypeLink, Name: buildOpts.Prefix + name, Linkname: linkname, ModTime: now, AccessTime: now, ChangeTime: now, }) }) } // Chardev is a character device entry func Chardev(name string, major, minor int64) TarEntry { now := time.Now() return tarEntryFunc(func(w *tar.Writer, buildOpts BuildTarOptions) error { return w.WriteHeader(&tar.Header{ Typeflag: tar.TypeChar, Name: buildOpts.Prefix + name, Devmajor: major, Devminor: minor, ModTime: now, AccessTime: now, ChangeTime: now, }) }) } // Blockdev is a block device entry func Blockdev(name string, major, minor int64) TarEntry { now := time.Now() return tarEntryFunc(func(w *tar.Writer, buildOpts BuildTarOptions) error { return w.WriteHeader(&tar.Header{ Typeflag: tar.TypeBlock, Name: buildOpts.Prefix + name, Devmajor: major, Devminor: minor, ModTime: now, AccessTime: now, ChangeTime: now, }) }) } // Fifo is a fifo entry func Fifo(name string) TarEntry { now := time.Now() return tarEntryFunc(func(w *tar.Writer, buildOpts BuildTarOptions) error { return w.WriteHeader(&tar.Header{ Typeflag: tar.TypeFifo, Name: buildOpts.Prefix + name, ModTime: now, AccessTime: now, ChangeTime: now, }) }) } // suid, guid, sticky bits for archive/tar // https://github.com/golang/go/blob/release-branch.go1.13/src/archive/tar/common.go#L607-L609 const ( cISUID = 04000 // Set uid cISGID = 02000 // Set gid cISVTX = 01000 // Save text (sticky bit) ) func permAndExtraMode2TarMode(fm os.FileMode) (tm int64) { tm = int64(fm & os.ModePerm) if fm&os.ModeSetuid != 0 { tm |= cISUID } if fm&os.ModeSetgid != 0 { tm |= cISGID } if fm&os.ModeSticky != 0 { tm |= cISVTX } return } stargz-snapshotter-0.12.0/version/000077500000000000000000000000001426301527400171455ustar00rootroot00000000000000stargz-snapshotter-0.12.0/version/version.go000066400000000000000000000015011426301527400211560ustar00rootroot00000000000000/* 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 version var ( // Version is the version number. Filled in at linking time (via Makefile). Version = "" // Revision is the VCS (e.g. git) revision. Filled in at linking time (via Makefile). Revision = "" )