pax_global_header00006660000000000000000000000064151471464100014515gustar00rootroot0000000000000052 comment=53e2087035eacc4e9d82b6e3853848f0a0704dc4 accelerated-container-image-1.4.2/000077500000000000000000000000001514714641000170155ustar00rootroot00000000000000accelerated-container-image-1.4.2/.github/000077500000000000000000000000001514714641000203555ustar00rootroot00000000000000accelerated-container-image-1.4.2/.github/ISSUE_TEMPLATE/000077500000000000000000000000001514714641000225405ustar00rootroot00000000000000accelerated-container-image-1.4.2/.github/ISSUE_TEMPLATE/bug-report.yaml000066400000000000000000000042601514714641000255140ustar00rootroot00000000000000# Copyright The Accelerated Container Image 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. name: Bug Report description: File a bug report labels: [bug] body: - type: markdown id: preface attributes: value: | Thank you for reporting bugs to Accelerated Container Image! - type: textarea id: environment validations: required: true attributes: label: What happened in your environment? description: "Please also attach logs here." - type: textarea id: expect attributes: label: "What did you expect to happen?" - type: textarea id: reproduce validations: required: true attributes: label: "How can we reproduce it?" description: "Please tell us your environment information as minimally and precisely as possible." - type: textarea id: version validations: required: true attributes: label: What is the version of your Accelerated Container Image? description: "You can find the released versions from https://github.com/containerd/accelerated-container-image/releases." - type: input id: os validations: required: true attributes: label: What is your OS environment? description: "e.g. Ubuntu 22.04" - type: checkboxes id: idea attributes: label: "Are you willing to submit PRs to fix it?" description: "This is absolutely not required, but we are happy to guide you in the contribution process especially when you already have a good proposal or understanding of how to implement it. Join us at https://slack.cncf.io/ and choose #overlaybd channel." options: - label: Yes, I am willing to fix it.accelerated-container-image-1.4.2/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000015471514714641000245370ustar00rootroot00000000000000# Copyright The Accelerated Container Image 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. blank_issues_enabled: true contact_links: - name: Ask a question or request support in the community url: https://github.com/containerd/accelerated-container-image/discussions/ about: Ask a question or request support for using Accelerated Container Imageaccelerated-container-image-1.4.2/.github/ISSUE_TEMPLATE/feature-request.yaml000066400000000000000000000035641514714641000265550ustar00rootroot00000000000000# Copyright The Accelerated Container Image 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. name: Feature Request description: File a feature request labels: [enhancement] body: - type: markdown id: preface attributes: value: "Thank you for submitting new features for Accelerated Container Image." - type: input id: version attributes: label: "What is the version of your Accelerated Container Image" description: "You can find the released versions from https://github.com/containerd/accelerated-container-image/releases." - type: textarea id: description attributes: label: "What would you like to be added?" validations: required: true - type: textarea id: solution attributes: label: "Why is this needed for Accelerated Container Image?" description: "Please describe your user story or scenario." validations: required: true - type: checkboxes id: idea attributes: label: "Are you willing to submit PRs to contribute to this feature?" description: "This is absolutely not required, but we are happy to guide you in the contribution process especially when you already have a good proposal or understanding of how to implement it. Join us at https://slack.cncf.io/ and choose #overlaybd channel." options: - label: Yes, I am willing to implement it.accelerated-container-image-1.4.2/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000014051514714641000241560ustar00rootroot00000000000000**What this PR does / why we need it**: **Which issue(s) this PR fixes** *(optional, in `fixes #(, fixes #, ...)` format, will close the issue(s) when PR gets merged)*: Fixes # **Please check the following list**: - [ ] Does the affected code have corresponding tests, e.g. unit test, E2E test? - [ ] Does this change require a documentation update? - [ ] Does this introduce breaking changes that would require an announcement or bumping the major version? - [ ] Do all new files have an appropriate license header? accelerated-container-image-1.4.2/.github/workflows/000077500000000000000000000000001514714641000224125ustar00rootroot00000000000000accelerated-container-image-1.4.2/.github/workflows/check.yml000066400000000000000000000046761514714641000242270ustar00rootroot00000000000000name: Check on: push: branches: - main pull_request: jobs: # # Linter checker # linters: name: Linters runs-on: ubuntu-22.04 timeout-minutes: 10 strategy: matrix: go-version: [1.26] steps: - uses: actions/setup-go@v6 with: go-version: ${{ matrix.go-version }} - uses: actions/checkout@v6 - uses: golangci/golangci-lint-action@v9 with: version: v2.9 skip-cache: true args: --timeout=5m # # Project checker # # based on https://github.com/containerd/project-checks/blob/main/action.yml project: name: Project Checks runs-on: ubuntu-22.04 timeout-minutes: 5 steps: - uses: actions/checkout@v6 with: path: src/github.com/containerd/accelerated-container-image fetch-depth: 100 - name: set env shell: bash run: | echo "GOPATH=${{ github.workspace }}" >> $GITHUB_ENV echo "${{ github.workspace }}/bin" >> $GITHUB_PATH - name: install dependencies shell: bash env: GO111MODULE: on run: | echo "::group:: install dependencies" go install -v github.com/vbatts/git-validation@latest go install -v github.com/containerd/ltag@latest echo "::endgroup::" - name: DCO checker shell: bash working-directory: src/github.com/containerd/accelerated-container-image env: GITHUB_COMMIT_URL: ${{ github.event.pull_request.commits_url }} DCO_VERBOSITY: "-v" DCO_RANGE: "" run: | echo "::group:: DCO checks" set -eu -o pipefail if [ -z "${GITHUB_COMMIT_URL}" ]; then DCO_RANGE=$(jq -r '.after + "..HEAD"' ${GITHUB_EVENT_PATH}) else DCO_RANGE=$(curl ${GITHUB_COMMIT_URL} | jq -r '.[0].parents[0].sha + "..HEAD"') fi range= [ ! -z "${DCO_RANGE}" ] && range="-range ${DCO_RANGE}" git-validation ${DCO_VERBOSITY} ${range} -run DCO,short-subject,dangling-whitespace echo "::endgroup::" - name: validate file headers shell: bash working-directory: src/github.com/containerd/accelerated-container-image run: | set -eu -o pipefail echo "::group:: file headers" ltag -t "script/validate/template" --excludes "vendor contrib" --check -v echo "::endgroup::" accelerated-container-image-1.4.2/.github/workflows/ci-basic.yml000066400000000000000000000074011514714641000246110ustar00rootroot00000000000000name: CI | Basic on: workflow_call: inputs: image-tag: required: true type: string github-repository: required: true type: string jobs: run-ci-basic: name: Run CI | Basic runs-on: ubuntu-22.04 timeout-minutes: 10 container: image: ghcr.io/${{ inputs.github-repository }}/overlaybd-ci-images:${{ inputs.image-tag }} credentials: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} volumes: - /dev:/dev - /lib/modules:/lib/modules - /sys/kernel/config:/sys/kernel/config - /mnt:/var/lib/containerd options: --privileged steps: - name: Start OverlayBD working-directory: /app shell: bash run: | set -x bash start_services.sh sleep 5s ps axf | grep overlaybd ps axf | grep containerd lsmod | grep target lsmod | grep overlay mount | grep cgroup cat /sys/fs/cgroup/cgroup.subtree_control uname -a containerd -v runc -v - name: CI - run overlaybd container shell: bash run: | /opt/overlaybd/snapshotter/ctr rpull docker.io/overlaybd/redis:6.2.1_obd ctr run -d --net-host --snapshotter=overlaybd docker.io/overlaybd/redis:6.2.1_obd test ctr t ls | grep test ctr t kill -s 9 test && sleep 5s && ctr t ls ctr c rm test && ctr c ls ctr i rm docker.io/overlaybd/redis:6.2.1_obd && ctr i ls - name: CI - run overlaybd(zdfs) container shell: bash run: | set -x ctr i pull registry-1.docker.io/overlaybd/redis:7.2.3_obd_zdfsRef if [[ ! -f /opt/overlaybd/baselayers/.commit ]]; then ln /opt/overlaybd/baselayers/ext4_64 /opt/overlaybd/baselayers/.commit fi ctr run -d --net-host --snapshotter=overlaybd --snapshotter-label='containerd.io/snapshot/cri.image-ref:registry-1.docker.io/overlaybd/redis:7.2.3_obd_zdfsRef' registry-1.docker.io/overlaybd/redis:7.2.3_obd_zdfsRef test ctr t ls | grep test ctr t kill -s 9 test && sleep 5s && ctr t ls ctr c rm test && ctr c ls ctr i rm registry-1.docker.io/overlaybd/redis:7.2.3_obd_zdfsRef && ctr i ls - name: CI - obdconv shell: bash run: | ctr i pull registry.hub.docker.com/library/redis:6.2.1 /opt/overlaybd/snapshotter/ctr obdconv registry.hub.docker.com/library/redis:6.2.1 registry.hub.docker.com/overlaybd/redis:6.2.1_obd_new ctr i ls | grep 6.2.1_obd_new - name: CI - record trace shell: bash run: | set -x /opt/overlaybd/snapshotter/ctr rpull registry.hub.docker.com/overlaybd/redis:6.2.1_obdconv echo "[ by record-trace ]" /opt/overlaybd/snapshotter/ctr record-trace --runtime "io.containerd.runc.v2" --disable-network-isolation --time 15 registry.hub.docker.com/overlaybd/redis:6.2.1_obdconv registry.hub.docker.com/overlaybd/redis:6.2.1_obdconv_trace ctr i ls | grep 6.2.1_obdconv_trace sleep 10s # wait for tcmu device to be removed echo "[ by label ]" touch /tmp/trace_file ctr run -d --snapshotter=overlaybd --snapshotter-label containerd.io/snapshot/overlaybd/record-trace=yes --snapshotter-label containerd.io/snapshot/overlaybd/record-trace-path=/tmp/trace_file registry.hub.docker.com/overlaybd/redis:6.2.1_obdconv demo sleep 1s ls -l /tmp/ | grep trace_file.lock sleep 15s ctr t ls | grep demo ctr t kill -s 9 demo && sleep 5s && ctr t ls ctr c rm demo && ctr c ls sleep 1s ls -l /tmp/ | grep trace_file.ok ctr i ls accelerated-container-image-1.4.2/.github/workflows/ci-build-image.yml000066400000000000000000000030731514714641000257100ustar00rootroot00000000000000name: CI | Build Image on: workflow_call: inputs: commit-hash: required: true type: string image-tag: required: true type: string github-repository: required: true type: string env: GO_VERSION: "1.26" OBD_VERSION: "1.0.12" jobs: build-image: name: Build Image runs-on: ubuntu-22.04 timeout-minutes: 10 permissions: contents: read packages: write steps: - name: Checkout uses: actions/checkout@v6 with: ref: ${{ inputs.commit-hash }} fetch-depth: 0 - name: Setup Buildx uses: docker/setup-buildx-action@v2 - name: Login ghcr.io uses: docker/login-action@v2 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Set Env shell: bash run: | git fetch --tags releasever=$(git tag --sort=v:refname -l v* | tail -1) releasever=${releasever#v} echo "RELEASE_VERSION=${releasever}" >> $GITHUB_ENV - name: Build and Push uses: docker/build-push-action@v4 with: tags: ghcr.io/${{ inputs.github-repository }}/overlaybd-ci-images:${{ inputs.image-tag }} push: true context: . platforms: linux/amd64 file: ci/build_image/Dockerfile build-args: | RELEASE_VERSION=${{ env.RELEASE_VERSION }} GO_VERSION=${{ env.GO_VERSION }} OBD_VERSION=${{ env.OBD_VERSION }} accelerated-container-image-1.4.2/.github/workflows/ci-e2e.yml000066400000000000000000000021671514714641000242070ustar00rootroot00000000000000name: CI | E2E on: workflow_call: inputs: commit-hash: required: true type: string image-tag: required: true type: string github-repository: required: true type: string concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: build-test-image-amd64: uses: ./.github/workflows/ci-build-image.yml with: commit-hash: ${{ inputs.commit-hash }} image-tag: ${{ inputs.image-tag }} github-repository: ${{ inputs.github-repository }} secrets: inherit run-ci-basic: needs: build-test-image-amd64 uses: ./.github/workflows/ci-basic.yml with: image-tag: ${{ inputs.image-tag }} github-repository: ${{ inputs.github-repository }} secrets: inherit run-ci-userspace-convertor: needs: build-test-image-amd64 uses: ./.github/workflows/ci-userspace-convertor.yml with: commit-hash: ${{ inputs.commit-hash }} image-tag: ${{ inputs.image-tag }} github-repository: ${{ inputs.github-repository }} secrets: inherit accelerated-container-image-1.4.2/.github/workflows/ci-unit-test.yml000066400000000000000000000014311514714641000254610ustar00rootroot00000000000000name: Unit Test on: push: branches: - main pull_request: branches: - 'main' jobs: unit-test: name: Unit Test runs-on: ubuntu-22.04 timeout-minutes: 10 steps: - uses: actions/checkout@v6 with: path: src/github.com/containerd/accelerated-container-image fetch-depth: 100 - name: install Go uses: actions/setup-go@v6 with: go-version: '1.26' - name: set env shell: bash run: | echo "GOPATH=${{ github.workspace }}" >> $GITHUB_ENV echo "${{ github.workspace }}/bin" >> $GITHUB_PATH - name: unit test working-directory: src/github.com/containerd/accelerated-container-image run: | sudo GO_TESTFLAGS=-v make testaccelerated-container-image-1.4.2/.github/workflows/ci-userspace-convertor.yml000066400000000000000000000104541514714641000275430ustar00rootroot00000000000000name: CI | Userspace Convertor on: workflow_call: inputs: commit-hash: required: true type: string image-tag: required: true type: string github-repository: required: true type: string jobs: run-ci-userspace-convertor: name: Run CI | Userspace Convertor runs-on: ubuntu-22.04 timeout-minutes: 10 container: image: ghcr.io/${{ inputs.github-repository }}/overlaybd-ci-images:${{ inputs.image-tag }} credentials: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} volumes: - /dev:/dev - /lib/modules:/lib/modules - /sys/kernel/config:/sys/kernel/config - /mnt:/var/lib/containerd options: --privileged env: DOCKER_HOST: "unix:///app/dockerd/docker.sock" steps: - name: Checkout uses: actions/checkout@v6 with: ref: ${{ inputs.commit-hash }} fetch-depth: 0 - name: Start OverlayBD working-directory: /app shell: bash run: | bash start_services.sh sleep 5s - name: Prepare Local Registry working-directory: ci/scripts shell: bash run: | bash new_registry.sh bash prepare_image.sh registry.hub.docker.com/overlaybd/centos:centos7.9.2009 localhost:5000/centos:centos7.9.2009 && \ bash prepare_image.sh registry.hub.docker.com/overlaybd/ubuntu:22.04 localhost:5000/ubuntu:22.04 && \ bash prepare_image.sh registry.hub.docker.com/overlaybd/redis:7.2.3 localhost:5000/redis:7.2.3 && \ bash prepare_image.sh registry.hub.docker.com/overlaybd/redis@sha256:309f99718ff2424f4ae5ebf0e46f7f0ce03058bf47d9061d1d66e4af53b70ffc localhost:5000/redis@sha256:309f99718ff2424f4ae5ebf0e46f7f0ce03058bf47d9061d1d66e4af53b70ffc && \ bash prepare_image.sh registry.hub.docker.com/overlaybd/wordpress:6.4.2 localhost:5000/wordpress:6.4.2 && \ bash prepare_image.sh registry.hub.docker.com/overlaybd/nginx:1.25.3 localhost:5000/nginx:1.25.3 - name: CI - uconv reproduce working-directory: ci/uconv_reproduce shell: bash run: | bash ci-uconv-reproduce.sh - name: CI - uconv E2E working-directory: ci/scripts shell: bash run: | /opt/overlaybd/snapshotter/convertor -r localhost:5000/redis -i 7.2.3 --overlaybd 7.2.3_overlaybd --turboOCI 7.2.3_turbo bash run_container.sh localhost:5000/redis:7.2.3_overlaybd bash run_container.sh localhost:5000/redis:7.2.3_turbo /opt/overlaybd/snapshotter/convertor -r localhost:5000/redis -i 7.2.3 --overlaybd 7.2.3_overlaybd_256 --turboOCI 7.2.3_turbo_256 --vsize 256 bash run_container.sh localhost:5000/redis:7.2.3_overlaybd_256 bash run_container.sh localhost:5000/redis:7.2.3_turbo_256 - name: CI - uconv E2E with digest input working-directory: ci/scripts shell: bash run: | /opt/overlaybd/snapshotter/convertor -r localhost:5000/redis -g sha256:309f99718ff2424f4ae5ebf0e46f7f0ce03058bf47d9061d1d66e4af53b70ffc --overlaybd 309f99718ff2424f4ae5ebf0e46f7f0ce03058bf47d9061d1d66e4af53b70ffc_overlaybd --turboOCI 309f99718ff2424f4ae5ebf0e46f7f0ce03058bf47d9061d1d66e4af53b70ffc_turbo bash run_container.sh localhost:5000/redis:309f99718ff2424f4ae5ebf0e46f7f0ce03058bf47d9061d1d66e4af53b70ffc_overlaybd bash run_container.sh localhost:5000/redis:309f99718ff2424f4ae5ebf0e46f7f0ce03058bf47d9061d1d66e4af53b70ffc_turbo - name: install Go uses: actions/setup-go@v6 with: go-version: '1.26' - name: set env shell: bash run: | echo "GOPATH=${{ github.workspace }}" >> $GITHUB_ENV echo "${{ github.workspace }}/bin" >> $GITHUB_PATH - name: CI - E2E Go Test working-directory: ci/e2e shell: bash run: | go test -v ./... accelerated-container-image-1.4.2/.github/workflows/ci.yml000066400000000000000000000023551514714641000235350ustar00rootroot00000000000000name: CI on: push: branches: - main pull_request_target: branches: - 'main' types: # Adding 'labeled' to the list of activity types that trigger this event # (default: opened, synchronize, reopened) so that we can run this # workflow when the 'ok-to-test' label is added. # Reference: https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target - opened - synchronize - reopened - labeled jobs: lowercase-repo: name: Lowercase Repo runs-on: ubuntu-22.04 timeout-minutes: 10 outputs: repository: ${{ steps.lowercase_repository.outputs.repository }} steps: - id: lowercase_repository run: echo "repository=${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT" e2e-test: needs: lowercase-repo name: E2E Test if: ${{ contains(github.event.pull_request.labels.*.name, 'ok-to-test') || github.event_name == 'push' }} uses: ./.github/workflows/ci-e2e.yml with: commit-hash: ${{ github.event.pull_request.head.sha || github.sha }} image-tag: ${{ github.event.pull_request.number || 'default' }} github-repository: ${{ needs.lowercase-repo.outputs.repository }} secrets: inherit accelerated-container-image-1.4.2/.github/workflows/codeql.yml000066400000000000000000000011211514714641000243770ustar00rootroot00000000000000name: "CodeQL Scan" on: push: branches: - main pull_request: branches: - main jobs: CodeQL-Build: strategy: fail-fast: false runs-on: ubuntu-22.04 timeout-minutes: 30 steps: - name: Checkout repository uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: go-version: '1.26' # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v3 - run: | make - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 accelerated-container-image-1.4.2/.github/workflows/release.yml000066400000000000000000000073341514714641000245640ustar00rootroot00000000000000name: Release on: push: branches: - main tags: - "v*" env: GO_VERSION: "1.26" jobs: build: name: Build Release runs-on: ubuntu-latest steps: - name: Set Release Version if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') shell: bash run: | releasever=${{ github.ref }} releasever="${releasever#refs/tags/}" echo "RELEASE_VERSION=${releasever}" >> $GITHUB_ENV - name: Checkout uses: actions/checkout@v6 with: fetch-depth: 100 - name: Setup buildx instance uses: docker/setup-buildx-action@v2 with: use: true - name: Build shell: bash run: | RELEASE_VERSION=${{ env.RELEASE_VERSION }} if [[ -z ${RELEASE_VERSION} ]]; then git fetch --tags RELEASE_VERSION=$(git tag --sort=v:refname -l v* | tail -1) #v1.1.3 version="${RELEASE_VERSION#v}" IFS='.' read -ra version_parts <<< "$version" major_version=${version_parts[0]} minor_version=${version_parts[1]} patch_version=${version_parts[2]} : $((patch_version++)) new_version="$major_version.$minor_version.$patch_version" RELEASE_VERSION="${new_version}rc" #1.1.4rc fi echo "RELEASE_VERSION=${RELEASE_VERSION}" RELEASE_NUM="$(date +%Y%m%d%H%M%S).$(git rev-parse --short HEAD)" docker run --rm -v "${PWD}":/src --workdir /src "golang:${{ env.GO_VERSION }}" sh -c "go mod vendor -o /tmp/vendor && tar -C /tmp -c vendor/" | tar -x docker buildx build --build-arg RELEASE_NUM=${RELEASE_NUM} --build-arg RELEASE_VERSION=${RELEASE_VERSION} --build-arg GO_VERSION=${{ env.GO_VERSION }} -f .github/workflows/release/Dockerfile --platform=linux/amd64,linux/arm64 -o releases/ . XZ_OPT="-T0 -9" tar --create --auto-compress --file "releases/accelerated-container-image-${GITHUB_REF_NAME#v}-vendor.tar.xz" --transform="s:^:accelerated-container-image-${GITHUB_REF_NAME#v}/:" --owner=root --group=root vendor/ ls -l releases/* - name: Upload uses: actions/upload-artifact@v5 with: name: releases path: | releases/*/overlaybd-snapshotter* releases/accelerated-container-image-*-vendor.tar.xz dev-release: name: Development Release if: github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-latest needs: [build] steps: - name: Download builds and release notes uses: actions/download-artifact@v6 with: name: releases path: releases - name: Display downloaded files shell: bash run: ls -lR releases/ - name: Create Release uses: "marvinpinto/action-automatic-releases@latest" with: repo_token: "${{ secrets.GITHUB_TOKEN }}" automatic_release_tag: "latest" prerelease: true title: "Development Build" files: | releases/*/overlaybd-snapshotter* release: name: Tagged Release if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-latest needs: [build] steps: - name: Download builds and release notes uses: actions/download-artifact@v6 with: name: releases path: releases - name: Display downloaded files shell: bash run: ls -lR releases/ - name: Create Release uses: "marvinpinto/action-automatic-releases@latest" with: repo_token: "${{ secrets.GITHUB_TOKEN }}" prerelease: false files: | releases/*/overlaybd-snapshotter* releases/accelerated-container-image-*-vendor.tar.xz accelerated-container-image-1.4.2/.github/workflows/release/000077500000000000000000000000001514714641000240325ustar00rootroot00000000000000accelerated-container-image-1.4.2/.github/workflows/release/Dockerfile000066400000000000000000000025461514714641000260330ustar00rootroot00000000000000# Copyright The Accelerated Container Image 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 GO_VERSION ARG GO_IMAGE=golang:${GO_VERSION} FROM --platform=${BUILDPLATFORM} ${GO_IMAGE} AS builder WORKDIR /src/github.com/containerd/accelerated-container-image COPY . . ENV DEBIAN_FRONTEND=noninteractive RUN echo 'deb [trusted=yes] https://repo.goreleaser.com/apt/ /' | tee /etc/apt/sources.list.d/goreleaser.list && \ apt update && \ apt install -y nfpm && \ go mod tidy ARG TARGETOS TARGETARCH ENV GOOS=${TARGETOS} ENV GOARCH=${TARGETARCH} ARG RELEASE_VERSION ENV SEMVER=${RELEASE_VERSION} ARG RELEASE_NUM ENV RELEASE=${RELEASE_NUM} ENV COMMIT_ID="${RELEASE_VERSION}_${RELEASE_NUM}" RUN make && \ nfpm pkg --packager deb --target /tmp/ && \ nfpm pkg --packager rpm --target /tmp/ FROM scratch AS release COPY --from=builder /tmp/* / accelerated-container-image-1.4.2/.gitignore000066400000000000000000000000431514714641000210020ustar00rootroot00000000000000bin/ .vscode vendor/ tmp_conv/ tmp/accelerated-container-image-1.4.2/.golangci.yml000066400000000000000000000012061514714641000214000ustar00rootroot00000000000000version: "2" linters: enable: - misspell - unconvert disable: - errcheck settings: staticcheck: # Enable all options, with some exceptions. # For defaults, see https://golangci-lint.run/usage/linters/#staticcheck checks: - all - -QF1008 # Omit embedded fields from selector expression; https://staticcheck.dev/docs/checks/#QF1008 - -ST1000 # TODO: Incorrect or missing package comment; https://staticcheck.dev/docs/checks/#ST1000 - -ST1003 # Poorly chosen identifier; https://staticcheck.dev/docs/checks/#ST1003 formatters: enable: - gofmt - goimports accelerated-container-image-1.4.2/AGENTS.md000066400000000000000000000106331514714641000203230ustar00rootroot00000000000000# AGENTS.md This file provides guidance to Qoder (qoder.com) when working with code in this repository. ## Project Overview Accelerated Container Image is an implementation of DADI (Data Accelerator for Disaggregated Infrastructure) for container acceleration. It provides overlaybd, a block-device-based remote image format that enables on-demand image fetching without downloading entire images. The project consists of: - **overlaybd-snapshotter**: A containerd snapshotter plugin for overlaybd images (compatible with OCI images) - **convertor**: Standalone userspace tool to convert OCI images to overlaybd format - **ctr**: Modified containerd CLI tool with overlaybd support - **dadi-snapshotter**: Legacy snapshotter implementation (separate subdirectory with own go.mod) ## Build Commands Build all binaries: ```bash make ``` Build specific binaries (generates to `bin/` directory): ```bash make binaries ``` Install binaries to system: ```bash make install # Installs to /opt/overlaybd/snapshotter # Config to /etc/overlaybd-snapshotter ``` Clean build artifacts: ```bash make clean ``` ## Testing Run tests (requires root): ```bash go test ${GO_TESTFLAGS} ${GO_PACKAGES} -test.root ``` Or via Makefile: ```bash make test ``` ## Linting Lint with golangci-lint (configuration in .golangci.yml): ```bash golangci-lint run ``` Enabled linters: misspell, unconvert, staticcheck (with custom checks), gofmt, goimports ## Architecture ### Storage Types The snapshotter handles multiple storage types (pkg/snapshot/overlay.go): - **storageTypeNormal**: Standard OCI tar.gz layers (overlayfs lowerdir) - **storageTypeLocalBlock**: Overlaybd format layers - **storageTypeRemoteBlock**: Remote layers with on-demand pulling - **storageTypeLocalLayer**: Tar file layers requiring overlaybd-turboOCI meta generation ### Key Components **pkg/snapshot/**: Core snapshotter implementation - overlay.go: Main snapshotter logic with storage type handling - storage.go: Snapshot storage management **pkg/convertor/**: Image conversion logic **pkg/label/**: Label handling for image metadata **pkg/types/**: Common type definitions **cmd/overlaybd-snapshotter/**: Snapshotter service entry point - Default config: /etc/overlaybd-snapshotter/config.json - Exposes gRPC API via unix socket **cmd/convertor/**: Standalone converter CLI - Uses cobra for CLI parsing - Supports MySQL database backend for layer deduplication - Multiple output formats: overlaybd, turboOCI, fastoci **cmd/ctr/**: Modified containerd ctr CLI with overlaybd support ### Filesystem Support Overlaybd outputs virtual block devices that can be formatted with multiple filesystems (ext4, xfs, etc.). The device is exposed through TCMU (TCM userspace). ### Image Flow 1. OCI layers (tar.gz) → Overlaybd blocks + index (Zfile format) 2. Virtual block device exposed via TCMU → Filesystem mount 3. On-demand read: index lookup → remote blob fetch ## Configuration ### Snapshotter Config Location: /etc/overlaybd-snapshotter/config.json - `root`: Overlaybd data directory (default: /var/lib/overlaybd/) - `address`: Unix socket path - `rwMode`: Read-write mode setting - `autoRemoveDev`: Auto-remove devices - `writableLayerType`: Type for writable layers - `asyncRemove`: Async snapshot removal ### Containerd Integration Add to /etc/containerd/config.toml: ```toml [proxy_plugins.overlaybd] type = "snapshot" address = "/run/overlaybd-snapshotter/overlaybd.sock" ``` ## Dependencies - Go >= 1.26.x - containerd >= 2.0.x - runc >= 1.0 - overlaybd backstore (https://github.com/containerd/overlaybd) ## Running Services Start snapshotter: ```bash sudo bin/overlaybd-snapshotter ``` Restart containerd after config changes: ```bash sudo systemctl restart containerd ``` ## Converting Images Using convertor tool: ```bash docker run overlaybd-convertor -r registry.hub.docker.com/library/redis -i 6.2.1 -o 6.2.1_obd_new ``` Or use the ctr rpull command for remote pull with conversion. ## Release Version Support Images have annotation `containerd.io/snapshot/overlaybd/version`: - `0.1.0`: All overlaybd versions - `0.1.0-turbo.ociv1`: overlaybd >= v0.6.10 ## Commit Message Style Follow the seven rules: 1. Separate subject from body with blank line 2. Limit subject line to 50 characters 3. Capitalize subject line 4. No period at end of subject 5. Use imperative mood in subject 6. Wrap body at 72 characters 7. Explain what and why vs. how Sign commits with: ```bash git commit -s ``` accelerated-container-image-1.4.2/Dockerfile000066400000000000000000000022461514714641000210130ustar00rootroot00000000000000 ARG GO_VERSION=latest ARG GOLANG_IMAGE=golang:${GO_VERSION} FROM ${GOLANG_IMAGE} AS golang FROM ubuntu:22.04 AS builder WORKDIR /go/src/github.com ARG TARGETARCH ENV GOTOOLCHAIN=local COPY --link --from=golang /usr/local/go/ /usr/local/go/ RUN apt update && apt install -y \ libcurl4-openssl-dev libssl-dev libaio-dev libnl-3-dev libnl-genl-3-dev libgflags-dev libzstd-dev libext2fs-dev libgtest-dev libtool zlib1g-dev e2fsprogs \ sudo pkg-config autoconf automake \ g++ cmake make wget git curl \ && apt clean COPY ./overlaybd ./overlaybd COPY ./accelerated-container-image ./accelerated-container-image RUN export PATH=$PATH:/usr/local/go/bin && \ cd overlaybd && rm -rf build && mkdir build && cd build && cmake ../ && make -j && make install && cd ../.. && \ cd accelerated-container-image && make -j && make install FROM ubuntu:22.04 COPY --from=builder /opt/overlaybd /opt/overlaybd COPY --from=builder /etc/overlaybd /etc/overlaybd COPY --from=builder /etc/overlaybd-snapshotter /etc/overlaybd-snapshotter RUN apt update && apt install -y \ libcurl4-openssl-dev libaio-dev \ && apt clean ENTRYPOINT ["/opt/overlaybd/snapshotter/convertor"] accelerated-container-image-1.4.2/INSTALL.sh000077500000000000000000000043751514714641000204730ustar00rootroot00000000000000#!/bin/bash H="$(cd "$(dirname "$0")" && pwd)" DEST="/opt/overlaybd/snapshotter" getInput() { msg=$* while true do echo -n "${msg} " read RET if [[ "${RET}" == "Y" ]]; then return 1 fi if [[ "${RET}" == "N" ]]; then return 0 fi continue done } echo "Compile overlaybd-snapshotter..." cd $H make -j4 || exit 1 sudo make install || exit 1 sudo mkdir -p /etc/overlaybd-snapshotter echo "copy config.json to /etc/overlaybd-snapshotter/" sudo cp script/config.json /etc/overlaybd-snapshotter echo "create service..." sudo cp $H/script/overlaybd-snapshotter.service /opt/overlaybd/snapshotter sudo systemctl enable /opt/overlaybd/snapshotter/overlaybd-snapshotter.service sudo systemctl start overlaybd-snapshotter getInput 'Would you like make containerd support overlaybd-snapshotter [Y/N]? It will add proxy_plugin config and restart containerd' OP=$? create=0 installed=0 if [[ $OP -eq 1 ]]; then echo "Change config.toml to make containerd support snapshotter..." m=$(grep proxy_plugins.overlaybd /etc/containerd/config.toml) installed=$? if [[ $installed -ne 0 ]]; then if [[ ! -f /etc/containerd/config.toml ]]; then mkdir -p /etc/containerd ## this directory may not exist in ubuntu touch /etc/containerd/config.toml create=1 fi sudo cat <<-EOF | sudo tee --append /etc/containerd/config.toml [proxy_plugins.overlaybd] type = "snapshot" address = "/run/overlaybd-snapshotter/overlaybd.sock" EOF sudo systemctl restart containerd fi fi getInput 'Would you like change the default snapshotter value of cri to overlaybd [Y/N]?' OP=$? if [[ $OP -eq 1 ]]; then if [[ $create -eq 1 ]]; then sudo cat <<-EOF | sudo tee --append /etc/containerd/config.toml [plugins.cri] [plugins.cri.containerd] snapshotter = "overlaybd" disable_snapshot_annotations = false EOF elif [[ $installed -ne 0 ]]; then sed -i 's/snapshotter = "overlayfs"/snapshotter = "overlaybd"/g' /etc/containerd/config.toml sed -i 's/disable_snapshot_annotations = true/disable_snapshot_annotations = false/g' /etc/containerd/config.toml fi sudo systemctl restart containerd fi echo "install done." accelerated-container-image-1.4.2/LICENSE000066400000000000000000000261341514714641000200300ustar00rootroot00000000000000 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.accelerated-container-image-1.4.2/MAINTAINERS000066400000000000000000000011301514714641000205050ustar00rootroot00000000000000# accelerated-container-image 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 "liulanzheng", "Lanzheng Liu", "lanzheng.liulz@alibaba-inc.com" "bigvan", "Yifan Yuan", "tuji.yyf@alibaba-inc.com" "yuchen0cc", "yuchen.cc", "yuchen.cc@alibaba-inc.com" # REVIEWERS # GitHub ID, Name, Email address "estebanreyl", "Esteban Rey", "esrey@microsoft.com" accelerated-container-image-1.4.2/Makefile000066400000000000000000000016541514714641000204630ustar00rootroot00000000000000# used to install binaries SN_DESTDIR=/opt/overlaybd/snapshotter SN_CFGDIR=/etc/overlaybd-snapshotter # command COMMANDS=overlaybd-snapshotter ctr convertor overlaybd-attacher BINARIES=$(addprefix bin/,$(COMMANDS)) # go packages GO_PACKAGES=$(shell go list ${GO_TAGS} ./... | grep -v /vendor/) CGO_ENABLED ?= 0 all: binaries binaries: $(BINARIES) ## build binaries into bin # force to rebuild all the binaries force: # build a binary from cmd bin/%: cmd/% force @echo "$@" @GOOS=linux CGO_ENABLED="$(CGO_ENABLED)" go build -ldflags "-X 'main.commitID=$$COMMIT_ID'" -o $@ ./$< install: ## install binaries from bin @mkdir -p $(SN_DESTDIR) @install $(BINARIES) $(SN_DESTDIR) @install -m 0644 script/overlaybd-snapshotter.service $(SN_DESTDIR) @mkdir -p ${SN_CFGDIR} @install -m 0644 script/config.json ${SN_CFGDIR} test: ## run tests that require root @go test ${GO_TESTFLAGS} ${GO_PACKAGES} -test.root clean: @rm -rf ./bin accelerated-container-image-1.4.2/README.md000066400000000000000000000231071514714641000202770ustar00rootroot00000000000000# Accelerated Container Image ![logo](https://github.com/containerd/accelerated-container-image/blob/main/docs/images/overlaybd_logo.svg) Accelerated Container Image is an open-source implementation of paper **["DADI: Block-Level Image Service for Agile and Elastic Application Deployment. USENIX ATC'20"](https://www.usenix.org/conference/atc20/presentation/li-huiba)**. DADI (Data Accelerator for Disaggregated Infrastructure) is a solution for container acceleration including remote image and other features which has been widely used in Alibaba and Alibaba Cloud. By now, it has been already integrated by **Alibaba Cloud Registry (ACR)**, and **Alibaba serverless services (FC [FaaSNet. USENIX ATC'21](https://www.usenix.org/system/files/atc21-wang-ao.pdf) / SAE / ECI, etc)** which enter **the Forrester leader quadrant**. At the heart of the acceleration is overlaybd, which is a new remote image format based on block device. Overlaybd backstore provides a merged view of a sequence of block-based layers in userspace and outputs as a virtual blocks device through [TCMU](https://www.kernel.org/doc/Documentation/target/tcmu-design.txt). It can be used for container acceleration by supporting fetching image data on-demand without downloading and unpacking the whole image before a container running. With overlaybd image format, we can cold start a container instantly. The key features are: * **High Performance** It's a block-device-based storage of OCI image, which has much lower complexity than filesystem-based implementations. For example, cross-layer hardlink and non-copy commands like chown are very complex for filesystem-based image without copying up, but is natively supported by overlaybd. Overlaybd outperforms filesystem-based solutions in performance. Evaluation data is stated in [DADI paper](https://www.usenix.org/conference/atc20/presentation/li-huiba). * **High Reliability** Overlaybd outputs virtual block devices through TCMU, which is widely used and supported in most operation systems. Overlaybd backstore can recover from failures or crashes, which is difficult for FUSE-based image formats. * **[Native Support for Writable](docs/WRITABLE.md)** Overlaybd can be used as writable/container layer. It can be used as container layer for runtime instead of overlayfs upper layer, or used to build overlaybd images. * **[Multiple File System Supported](docs/MULTI_FS_SUPPORT.md)** Overlaybd outputs virtual block devices, which is supported to be formatted by multiple file system. It's convenient for user to choose ideal file system. Accelerated Container Image is a __non-core__ sub-project of containerd. ## Components * [overlaybd - Native](https://github.com/containerd/overlaybd) Overlaybd provides a merged view of block-based layer sequence as an virtual block device in user space. * overlaybd-snapshotter It is a [containerd](https://containerd.io/) snapshotter plugin for overlaybd image. This snapshotter is compatible for OCI image, as well as overlayfs snapshotter. * embedded image-convertor We provide a modified CLI tool(ctr) to facilitate image pull, and custom conversion from traditional OCI tarball format to overlaybd format. The convertor supports layer deduplication, which prevents duplication of layer conversion for every image conversion. * standalone userspace image-convertor Standalone userspace image-convertor has similar functionality to embedded image-convertor but runs in the userspace. It does not require root privilege and dependence on tcmu, configfs, snapshotter, or even on containerd. which makes it much more convenient to run in a container. What's more, standalone userspace image-convertor is faster than embedded image-convertor when used with our [customized libext2fs](https://github.com/data-accelerator/e2fsprogs). See [USERSPACE_CONVERTOR](https://github.com/containerd/accelerated-container-image/blob/main/docs/USERSPACE_CONVERTOR.md) for more details. * [buildkit for overlaybd](https://github.com/data-accelerator/buildkit) (Experimental) It is a customized buildkit for overlaybd images. It fetches the data of base images on demand without pulling whole data and uses overlaybd writable layer to build new layers. * [overlaybd - turboOCIv1](docs/TURBO_OCI.md) It is an overlaybd-based remote image format which enables the original OCI image to be a remote one without conversion. It is similar to [SOCI](https://github.com/awslabs/soci-snapshotter), but provides block device interface, which has advantages than FUSE-based formats in performance and stability. ## Docker Image The `Dockerfile` is supplied to build the image of the overlaybd convertor. You can build the docker image by yourself as follows: ``` docker build -f Dockerfile -t overlaybd-convertor . ``` Then run the overlaybd convertor image (see [QUICKSTART](docs/QUICKSTART.md) for more details): ``` docker run overlaybd-convertor -r registry.hub.docker.com/library/redis -i 6.2.1 -o 6.2.1_obd_new ``` ## Getting Started * [QUICKSTART](docs/QUICKSTART.md) helps quickly run an overlaybd image including basic usage. * See how to setup overlaybd backstore at [README](https://github.com/containerd/overlaybd). * See how to build snaphshotter and ctr plugin components at [BUILDING](docs/BUILDING.md). * After build or install, see our [EXAMPLES](docs/EXAMPLES.md) about how to run an accelerated container. see [EXAMPLES_CRI](docs/EXAMPLES_CRI.md) if you run containers by k8s/cri. * See the [PERFORMANCE](docs/PERFORMANCE.md) test about the acceleration. * Enable 'record-trace' function can achieve higher performance for the entrypoint that needs to read amount of data at container startup. See [ENABLE_TRACE](docs/trace-prefetch.md). * See how to convert OCI image into overlaybd with specified file system at [MULTI_FS_SUPPORT](docs/MULTI_FS_SUPPORT.md). * See how to use layer deduplication for image conversion at [IMAGE_CONVERTOR](docs/IMAGE_CONVERTOR.md). * See how to use overlaybd writable layer at [WRITABLE](docs/WRITABLE.md). * See how to use Prometheus to monitor metrics like latency/error count of snapshotter GRPC APIs at [PROMETHEUS](docs/PROMETHEUS.md). * See how to use TurboOCIv1 at [TurboOCIv1](docs/TURBO_OCI.md). * Welcome to contribute! [CONTRIBUTING](docs/CONTRIBUTING.md) ## Release Version Support There will be an annotation `containerd.io/snapshot/overlaybd/version` in the manifest of the converted image to specify the format version, following is the overlaybd release version required by them. * `0.1.0`: for now, all release versions of overlaybd support this. * `0.1.0-turbo.ociv1`: overlaybd >= v0.6.10 ## Overview With OCI image spec, an image layer blob is saved as a tarball on the registry, describing the [changeset](https://github.com/opencontainers/image-spec/blob/v1.0.1/layer.md#change-types) based on it's previous layer. However, tarball is not designed to be seekable and random access is not supported. Complete downloading of all blobs is always necessary before bringing up a container. An overlaybd blob is a collection of modified data blocks under the filesystem and corresponding to the files added, modified or deleted by the layer. The overlaybd backstore is used to provide the merged view of layers and provides a virtual block device. Filesystem is mounted on top of the device and an overlaybd blob can be accessed randomly and supports on-demond reading natively. ![image data flow](docs/images/image-flow.jpg "image data flow") The raw data of block differences, together with an index to the raw data, constitute the overlaybd blob. When attaching and mounting an overlaybd device, only indexes of each layer are loaded from remote, and stored in memory. For data reading, overlaybd performs a range lookup in the index to find out where in the blob to read and then performs a remote fetching. That blob is in Zfile format. Zfile is a new compression file format to support seekable decompression, which can reduce storage and transmission costs. And also the checksum information to protect against data corruptions for on-demand reading is stored in Zfile. In order to be compatible with existing registries and container engines, Zfile is wrapped by a tar file, which has only one Zfile inside. ![io-path](docs/images/io-path.png "io-path") Overlaybd connects with applications through a filesystem mounted on an virtual block device. Overlaybd is agnostic to the choice of filesystem so users can select one that best fits their needs. I/O requests go from applications to a regular filesystem such as ext4. From there they go to the loopback device (through TCM_loopback) and then to the user space overlaybd backstore (through TCMU). Backend read operations are always on layer files. Some of the layer files may have already been downloaded, so these reads would hit local filesystem. Other reads will be directed to registry, or hit the registry cache. Write and trim operations are handled by overlaybd backstore which writes the data and index files of the writable layer to the local file system. For more details, see the [paper](https://www.usenix.org/conference/atc20/presentation/li-huiba). ## Communication For async communication and long running discussions please use issues and pull requests on the github repo. This will be the best place to discuss design and implementation. For sync communication catch us in the #overlaybd slack channels on Cloud Native Computing Foundation's (CNCF) slack - cloud-native.slack.com. Everyone is welcome to join and chat. [Get Invite to CNCF slack.](https://communityinviter.com/apps/cloud-native/cncf) ## Licenses Accelerated Container Image is released under the Apache License, Version 2.0. accelerated-container-image-1.4.2/ci/000077500000000000000000000000001514714641000174105ustar00rootroot00000000000000accelerated-container-image-1.4.2/ci/build_image/000077500000000000000000000000001514714641000216515ustar00rootroot00000000000000accelerated-container-image-1.4.2/ci/build_image/Dockerfile000066400000000000000000000060051514714641000236440ustar00rootroot00000000000000# Copyright The Accelerated Container Image 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. # build overlaybd-snapshotter ARG GO_VERSION ARG GO_IMAGE=golang:${GO_VERSION} FROM --platform=${BUILDPLATFORM} ${GO_IMAGE} AS builder WORKDIR /src COPY . . ENV DEBIAN_FRONTEND=noninteractive RUN echo 'deb [trusted=yes] https://repo.goreleaser.com/apt/ /' | tee /etc/apt/sources.list.d/goreleaser.list && \ apt update && \ apt install -y nfpm && \ go mod tidy ARG TARGETOS TARGETARCH ENV GOOS=${TARGETOS} ENV GOARCH=${TARGETARCH} ARG RELEASE_VERSION ENV SEMVER=${RELEASE_VERSION} ARG RELEASE_NUM ENV RELEASE=${RELEASE_NUM} ENV COMMIT_ID="${RELEASE_VERSION}_${RELEASE_NUM}" RUN make && \ nfpm pkg --packager deb --target /tmp/ && \ nfpm pkg --packager rpm --target /tmp/ # build image FROM ubuntu:22.04 AS release ARG OBD_VERSION ARG RELEASE_VERSION SHELL ["/bin/bash", "-c"] WORKDIR /app COPY --from=builder /tmp/overlaybd-snapshotter_${RELEASE_VERSION}_amd64.deb . COPY ./ci/build_image/start_services.sh . RUN apt-get update && apt-get install -y apt-transport-https ca-certificates curl gnupg lsb-release software-properties-common && \ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg && \ echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null && \ apt-get update && apt-get install -y docker-ce docker-ce-cli containerd.io && \ apt-get install -y libnl-3-200 libnl-genl-3-200 libcurl4-openssl-dev libaio-dev wget less kmod && \ apt-get clean && rm -rf /var/lib/apt/lists/* && \ wget https://github.com/containerd/overlaybd/releases/download/v${OBD_VERSION}/overlaybd-${OBD_VERSION}-20240717.b5b704b.ubuntu1.22.04.x86_64.deb && \ dpkg -i overlaybd-${OBD_VERSION}-20240717.b5b704b.ubuntu1.22.04.x86_64.deb && \ dpkg -i overlaybd-snapshotter_${RELEASE_VERSION}_amd64.deb && \ sed -i 's/"autoRemoveDev": false,/"autoRemoveDev": true,/g' /etc/overlaybd-snapshotter/config.json && \ cat /etc/overlaybd-snapshotter/config.json && \ mkdir -p /etc/containerd/ && \ echo -e '[proxy_plugins.overlaybd]\n\ttype = "snapshot"\n\taddress = "/run/overlaybd-snapshotter/overlaybd.sock"' | tee -a /etc/containerd/config.toml && \ cat /etc/containerd/config.toml && \ chmod +x /app/start_services.sh && \ cat /app/start_services.shaccelerated-container-image-1.4.2/ci/build_image/start_services.sh000066400000000000000000000015771514714641000252570ustar00rootroot00000000000000#!/bin/bash set -e # cgroup v2: enable nesting # https://github.com/moby/moby/blob/38805f20f9bcc5e87869d6c79d432b166e1c88b4/hack/dind#L28-L38 if [ -f /sys/fs/cgroup/cgroup.controllers ]; then # move the processes from the root group to the /init group, # otherwise writing subtree_control fails with EBUSY. # An error during moving non-existent process (i.e., "cat") is ignored. mkdir -p /sys/fs/cgroup/init xargs -rn1 < /sys/fs/cgroup/cgroup.procs > /sys/fs/cgroup/init/cgroup.procs || : # enable controllers sed -e 's/ / +/g' -e 's/^/+/' < /sys/fs/cgroup/cgroup.controllers \ > /sys/fs/cgroup/cgroup.subtree_control fi /sbin/modprobe target_core_user && /opt/overlaybd/bin/overlaybd-tcmu & /opt/overlaybd/snapshotter/overlaybd-snapshotter &>/var/log/overlaybd-snapshotter.log & /sbin/modprobe overlay && /usr/bin/containerd &>/var/log/containerd.log & accelerated-container-image-1.4.2/ci/e2e/000077500000000000000000000000001514714641000200635ustar00rootroot00000000000000accelerated-container-image-1.4.2/ci/e2e/convert/000077500000000000000000000000001514714641000215435ustar00rootroot00000000000000accelerated-container-image-1.4.2/ci/e2e/convert/referrer_test.go000066400000000000000000000121431514714641000247460ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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 convert_test import ( "context" "encoding/json" "fmt" "io" "os/exec" "path/filepath" "runtime" "testing" dockerspec "github.com/containerd/containerd/v2/core/images" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/stretchr/testify/require" "oras.land/oras-go/v2" "oras.land/oras-go/v2/content/oci" "oras.land/oras-go/v2/registry" "oras.land/oras-go/v2/registry/remote" ) const ( ArtifactTypeOverlaybd = "application/vnd.containerd.overlaybd.native.v1+json" ArtifactTypeTurboOCI = "application/vnd.containerd.overlaybd.turbo.v1+json" TestRepository = "localhost:5000/hello-world" ) func TestConvertReferrer(t *testing.T) { ctx := context.Background() require := require.New(t) if err := copyImageToRegistry(ctx); err != nil { t.Fatal(err) } repo, err := remote.NewRepository(TestRepository) require.Nil(err, err) repo.SetReferrersCapability(true) fetchJSON := func(src ocispec.Descriptor, v any) { rc, err := repo.Fetch(ctx, src) require.Nil(err, err) defer rc.Close() bytes, err := io.ReadAll(rc) require.Nil(err, err) err = json.Unmarshal(bytes, v) require.Nil(err, err) t.Logf("fetch %s, content:\n%s", src.Digest.String(), string(bytes)) } var checkReferrer func(src, dst ocispec.Descriptor, artifactType string) checkReferrer = func(src, dst ocispec.Descriptor, artifactType string) { t.Logf("checking src %v to dst %v, artifact type %s", src, dst, artifactType) require.Equal(isIndex(src.MediaType), isIndex(dst.MediaType)) switch dst.MediaType { case ocispec.MediaTypeImageManifest, dockerspec.MediaTypeDockerSchema2Manifest: var manifest ocispec.Manifest fetchJSON(dst, &manifest) require.Equal(artifactType, manifest.ArtifactType) require.Equal(src.Digest, manifest.Subject.Digest) require.Equal(src.Size, manifest.Subject.Size) require.Equal(src.MediaType, manifest.Subject.MediaType) case ocispec.MediaTypeImageIndex, dockerspec.MediaTypeDockerSchema2ManifestList: var index ocispec.Index fetchJSON(dst, &index) require.Equal(artifactType, index.ArtifactType) require.Equal(src.Digest, index.Subject.Digest) require.Equal(src.Size, index.Subject.Size) require.Equal(src.MediaType, index.Subject.MediaType) var srcIndex ocispec.Index fetchJSON(src, &srcIndex) require.Equal(len(srcIndex.Manifests), len(index.Manifests)) for idx := range index.Manifests { checkReferrer(srcIndex.Manifests[idx], index.Manifests[idx], artifactType) } } } testcases := []struct { name string reference string }{ { name: "simple", reference: TestRepository + ":amd64", }, { name: "index", reference: TestRepository + ":multi-arch", }, { name: "nested index", reference: TestRepository + ":nested-index", }, } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { err := convert(ctx, tc.reference) require.Nil(err, err) src, err := repo.Resolve(ctx, tc.reference) require.Nil(err, err) obd, err := repo.Resolve(ctx, tc.reference+"_overlaybd") require.Nil(err, err) turbo, err := repo.Resolve(ctx, tc.reference+"_turbo") require.Nil(err, err) checkReferrer(src, obd, ArtifactTypeOverlaybd) checkReferrer(src, turbo, ArtifactTypeTurboOCI) }) } } const convertBin = "/opt/overlaybd/snapshotter/convertor" func convert(ctx context.Context, refspec string) error { ref, err := registry.ParseReference(refspec) if err != nil { return err } cmd := exec.CommandContext(ctx, convertBin, "-r", fmt.Sprintf("%s/%s", ref.Registry, ref.Repository), "-i", ref.Reference, "--overlaybd", ref.Reference+"_overlaybd", "--turboOCI", ref.Reference+"_turbo", "--referrer", ) if out, err := cmd.CombinedOutput(); err != nil { return fmt.Errorf("failed to convert: %w, output: %s", err, out) } return nil } func copyImageToRegistry(ctx context.Context) error { _, filename, _, _ := runtime.Caller(0) local, err := oci.New(filepath.Join(filepath.Dir(filename), "..", "resources", "store", "hello-world")) if err != nil { return err } repo, err := remote.NewRepository(TestRepository) if err != nil { return err } images := []string{"amd64", "arm64", "multi-arch", "nested-index"} for _, img := range images { src := img dst := TestRepository + ":" + img if _, err := oras.Copy(ctx, local, src, repo, dst, oras.CopyOptions{}); err != nil { return err } } return nil } func isIndex(mediaType string) bool { return mediaType == ocispec.MediaTypeImageIndex || mediaType == dockerspec.MediaTypeDockerSchema2ManifestList } accelerated-container-image-1.4.2/ci/e2e/go.mod000066400000000000000000000015031514714641000211700ustar00rootroot00000000000000module github.com/containerd/accelerated-container-image/ci/e2e go 1.26.0 require ( github.com/containerd/containerd/v2 v2.0.7 github.com/opencontainers/image-spec v1.1.0 github.com/stretchr/testify v1.10.0 oras.land/oras-go/v2 v2.5.0 ) require ( github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v1.0.0-rc.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/klauspost/compress v1.17.11 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/sirupsen/logrus v1.9.3 // indirect golang.org/x/sync v0.16.0 // indirect golang.org/x/sys v0.34.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) accelerated-container-image-1.4.2/ci/e2e/go.sum000066400000000000000000000070051514714641000212200ustar00rootroot00000000000000github.com/containerd/containerd/v2 v2.0.7 h1:55JsNhqP/L7VZOijyfq6Qn0O8Oeff0UizfRuP+2pc90= github.com/containerd/containerd/v2 v2.0.7/go.mod h1:su8B0Z1NFQMEIztOIbHwy7xtznbCms/kFlfsxIcQrZ8= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/platforms v1.0.0-rc.2 h1:0SPgaNZPVWGEi4grZdV8VRYQn78y+nm6acgLGv/QzE4= github.com/containerd/platforms v1.0.0-rc.2/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= 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.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= oras.land/oras-go/v2 v2.5.0 h1:o8Me9kLY74Vp5uw07QXPiitjsw7qNXi8Twd+19Zf02c= oras.land/oras-go/v2 v2.5.0/go.mod h1:z4eisnLP530vwIOUOJeBIj0aGI0L1C3d53atvCBqZHg= accelerated-container-image-1.4.2/ci/e2e/resources/000077500000000000000000000000001514714641000220755ustar00rootroot00000000000000accelerated-container-image-1.4.2/ci/e2e/resources/store/000077500000000000000000000000001514714641000232315ustar00rootroot00000000000000accelerated-container-image-1.4.2/ci/e2e/resources/store/hello-world/000077500000000000000000000000001514714641000254615ustar00rootroot00000000000000accelerated-container-image-1.4.2/ci/e2e/resources/store/hello-world/blobs/000077500000000000000000000000001514714641000265625ustar00rootroot00000000000000accelerated-container-image-1.4.2/ci/e2e/resources/store/hello-world/blobs/sha256/000077500000000000000000000000001514714641000275725ustar00rootroot00000000000000004d23c66201b22fce069b7505756f17088de7889c83891e9bc69d749fa3690e000066400000000000000000000010151514714641000377200ustar00rootroot00000000000000accelerated-container-image-1.4.2/ci/e2e/resources/store/hello-world/blobs/sha256{ "schemaVersion": 2, "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "config": { "mediaType": "application/vnd.docker.container.image.v1+json", "size": 1468, "digest": "sha256:a8117b42328be6ba54c594f9f14e216db10203deb22cc28aaa2c134f9ae2e0fd" }, "layers": [ { "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", "size": 2716, "digest": "sha256:630da353b594c9ca52c67727920737dba3e5aaa4fbe20cdd0fc70c0591b7a639" } ] }06bca41ba617acf0b3644df05d0d9c2d2f82ccaab629c0e39792b24682970040000066400000000000000000000010151514714641000402610ustar00rootroot00000000000000accelerated-container-image-1.4.2/ci/e2e/resources/store/hello-world/blobs/sha256{ "schemaVersion": 2, "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "config": { "mediaType": "application/vnd.docker.container.image.v1+json", "size": 1473, "digest": "sha256:e4728a982b0acd74e810aeb5e8a5c1bd87f0de7ed93a678d58b3a79e6f7da2e9" }, "layers": [ { "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", "size": 4065, "digest": "sha256:4bf3d0e19af8069cca924c84fccd58558900bd382ffbde49778906172aa135fb" } ] }084c3bdd1271adc754e2c5f6ba7046f1a2c099597dbd9643592fa8eb99981402000066400000000000000000000010151514714641000401740ustar00rootroot00000000000000accelerated-container-image-1.4.2/ci/e2e/resources/store/hello-world/blobs/sha256{ "schemaVersion": 2, "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "config": { "mediaType": "application/vnd.docker.container.image.v1+json", "size": 1483, "digest": "sha256:de23d9589845285bb62b33da594c4a19802fda5b09bb6aef4d00e5e8c747a8df" }, "layers": [ { "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", "size": 3658, "digest": "sha256:337a0e31c52265fdbab8ffb4f8922b73f2ea3689cf9970150afee8a5a026db30" } ] }337a0e31c52265fdbab8ffb4f8922b73f2ea3689cf9970150afee8a5a026db30000066400000000000000000000071121514714641000404550ustar00rootroot00000000000000accelerated-container-image-1.4.2/ci/e2e/resources/store/hello-world/blobs/sha256zmlyo|֍H*:1=)Erhю?fb63EJ:+niɩy-"$!&n[ En! Η)ڄF o1{GEݝ3<2fR|Ļ8w=Ǻ=эǺc ? ƿa|țz{?B?Bx$5`цƗhB=QSSwPCV?'dQ)in]T7wJ)h}1K0u5ZuXWz3+f^jj r!X,Vv-RZ^%"e w>{ ־Kd_56d3=lFYfQ5g 5 2)[3pBЬqGQGyIWmpJ0^ٯ$M8Sf ASib{v+/cl ZO%[Z 揧d:ĀeWQ>a'̞I`;L N>>WEEa·iQRZ((iXFiX6Ұ4,ry~.E,cO:d3 ( FddN򷞷TgAgƯq?S:zO : U,7ʧ%hV*{ziwJ|ZiOWra>-ش Ʀbai]uM'o?OpӪF^9u4;'I^~0{b]W(z`sRbڴT=Wbdm9U/4yo:lļ*}XG|^mD (sJd8vM\IOѾ&|*\U/hs#@(Y=bUOIgeOF>+|J6YI<٥Ԋݔì({*IssigSFv1G/OjvM::(-·EȊ./ɻ|?Na:iM<6򂤴 o+Uc \lW3s1mK+(_rzMir@CF-r/@ScD-Đok5CC(&u`\2*<IJ$ul+[kC [;%JԸ`cעWU[nV4~C3S[#/ҍUF RŮ:sGǀpB_,vn%ۭHJj0;fɎ̬qymuJ4sm*ϋb!~6~ n6VQglс+~#b|yGr@osm>/g[?ݎA#wo/#JJߨ?5Υ-씣;:{ҹP=m Pup֏=͠nʚZ"ZB(̱^LX27TLf,\6lK963jG(4a猴K3=ZݍK3L*9M# 68flGRI#mLLƨn4ag!WTYv"7j?ɥFdqf!kKƏ=Cv|GrFB `i|Edm2L Ώ%GXn"h ȄN`61|L ,3D.gԫ,g$Fs!ֽ:G;ԝgnh;Uܵ޳f߼ G,;&fWr%ovQ<}@9Lm ͸i}& x(-,OЄaLJ}&kһdj]ȕYBE9, cy %~9Nqm!8넱Q(ȿh$;Fޚ2 \2;l9˂';-k!8nYycƚsJqsRWD@G$EBo->wLxL$FzŸ3>χƺg@ÉLmU"i ?HM6vqYsNƚql~ 5lz v߉#=ҨUkOi8@JGuo{ Ŕ2&Xu _)M^|<E{7xQG"myi&#N3{@QǞl*9q!G sJNl3Vq^PkalgPk`$9G #Uoui9~ 8 5N3/֑ փg 8ios@|9Vg{ (_M†=7N[sν6$fٲ\:#aup_+QGݍ: JKLiu@F%!':pz w:XXp ' uF*N8u*q ׽`uVɩ5|#@DI3&"Z\FjKkFE{QvtX(ǂӁc!RY,8{1L2¢ßEH`3UYBX* Y"jP#r8 0banF2.o ?IUugB08iGo%ܵK,q=/DygSoX8 ^sF{O ۚx [K뽁aDzN$BL"noĠw>m]qI@8;g-{T7֜"S48౷#%/̓S 8N/&DPs=xO33,ӌy (k9Gcq5Jdk USp퀂*)N~ˑ?x戈p [#w_n\%bmKiUQe P\ CkXRh ad9N"SRY`S"8DS"Ы #B7(RUUN{QgR$,naoC<>%tF`W`kgHWks=s̫;ŀy`v5"Ы1E(2ԟe ѰkP'\&{W`TMC_^gޛz" z|Ĕia0Cp{g Mix߂(rY3o{ۃ kƑ+@dڠ._S_і@=}ͼ?0ot!=89"zCH1 JCĒk}d: 9ns)咀?Al`uvTX%xYf*u:[g3[mJ,q@^fǾf,AWDN,] ZÝU@_?5NS%)mmԘ;R) ($< lѝg4-aAC0=+a7B $yn o_7dqxGJ)^[eۻʖ}7 *!Z4, "A,\'gy~4uV.4\9ˬ\4 V.4\7Y&:+Lb[Lʥ_2~Ko3nr4V.4{KL+xpYBH'o“>O/.<<6FqZbƋ>?N0/6&SMܟwu;~{~mo~vԭy fgYn`G`DtY r" | /les&d<~a VEx.hvALzʖ ׾Dתּ=x{z:5o11t{[xXo$k͞h;ݑ ~YϠ>:Bnȥ 7dtL/Nܡ"53IMhvh&GGf67N,#:=+ܗ7h*W()sHO5q4ujwf.w53:MFGs8}}=tg?=ћѬ>nTnLL]swZRˍϿ[Q{o=+jivklpbRJl]g{Wc*I.I<ɲz1i;dSZ]a=5a&GFu1_i)E 3?ڛ& :MM 9z7{讫S4 zrU5i6V<+"E+lꅱxrsY8B1pό :MdlnJJshr~edbܜh1ġC#TvxѬI'<đd1 WҵT&'M!6y[s~4IBwC3/2#VX\'zd~=[̚-Z.Ul%7t3Z4Sb==icm+Ux >T .ux?z?_=; >}o}W!&˷>}_}ÇЕhy>>*)39?cɑlz4iџϚ7&, 9-i&ѯg,]HOBܷFDћ7'YȎLzGOYoF]w&wߙZ%N>}}蛃7㠙'ڜߖxC7O?(}06z7P w7߇[+"]mͼ߳AnլO 669_/l55^s{>7kKZǚOߞemx|<>AG &4f1af0faa5862569c7c76d72b1c8e09ba01f2adeea0a44f8f3758d185e95e918000066400000000000000000000075221514714641000405050ustar00rootroot00000000000000accelerated-container-image-1.4.2/ci/e2e/resources/store/hello-world/blobs/sha256mp[UzW2xz6@H8ر%Yf Mb[Bҕm2SgfL8@~6ζδ촓y)R ;K[ N{degI=Gy99cx\:n;a;wv;︳cg;:v@%e2V)t}{+}{I0w>&A4a9zZڶV¶XF+mC\wEu?1j0\TGOUjF`IKwC(-;)ym"Z$7IK~-jcV[=}y\|NܖO<#3UPߢPo*Z.<'Uع|_7;: 6sr`ƈ62w\>s6S݌P.w_3B}~4(N3z0sŗ_mxg ֽܖ9 {cqoض=ȇL;H/c{?7b)Wg;f{Glv%z'cg;+~ UK\es0S qϧ:uB36D$@X! {cmxu|*l`Z땯Smcw*ٮ3O: K7Uר(k$zĖ׬µF ~R6=k@Nbn /)-Wm+,ܲ9Ű[1RZےB>P3zˢmM26h 6 b}]ȇpӆC.UXf0{|㢽 ~Va~GCK_# Kȇp=(iPJN䤿t|AZZ9 f>O#6rfYg{z~MXt|N(IwFI[~.ɮzņ g;{uw᝟Z$ a\e?ϸ6&VNʉfꮮ|]-k)=;,fY'c݋Y Mȅ:"fg|o:kiVGXgFpSۤ[6ǴڭO.f,٬z1kgOdgmܾQI/tv=Rt{ ~\G|>wh-˗yTCS$Zd#ef6?FuW4kzFiKڭڎ0JZ 1$Zڀ/S~ZI[͚V_w>O :X`^qo=;:_aqh vݙ}.4DChF{h͌6и=̧mV_f\+Mscv\3fNW;:W`G>`GVѹGC͈Ȗ/'4{ 漡†6KlCwoXdLzZOmIB|OZSwa?UcDg]-]`nU9^FOYClTo=e'}.>ƺ:a~V2\s2os5rA-_YHa\e=jh]tke[FYbWc ]Dn*x?/[ռ]~v k砎溪g#kqfe<+0/|8_h[Kڎ'E'C>F3=-gR쳟io~M{k+k|A>WM{=6ȧ4<{vE2rǴdqQ~,+?3FA#_g̘w'Oy`F0 [|a#_ 5Ă)E.Z> )W_3A7F5~AOB>Ԁ߽lEp\ b~m F0.kvmoΝ+^b=?؀`8t{%~VnsZ_e Fv; ? CjM1CN8q4&[^'=ץjZ\g _g6fH S \ _~?_^BA}oBAr%{$o%ɍ{]au[<3]uO݅I~WH.Dd\r@!";qwa]r~$ཤu~یt`xPg%CNl{81*3 {3Xv403"=31x_r/FVWfm8h q5+{Zp3[|#NHܿ|aOtO bF3}EߍiKXbe^sWt/0#:{-<3+[\H#/9筽va@9u୙l@weǒ3;g#5g\Csǘ_97=S]ñc͹!.kcF\5rV9AW.{"fޮqxiy=߇՘[O_q_*8fdHsZq;7?!.9W^Vl9.SGW=cTY{k&o.~'ں_0߽}XS0mm]ȇx-wcAa], ڵף޻e4 aEfVv[f4 -aEf,WQ-ZsѽWZEư2>b_"ji7OJ:=ġEUpb*%'jt|<b4T&E69jĨx \P߮FqLU]۷M[c퍍{)KSѢq%mڊ*tx:h&bZz[ZTet{c#*>95>~o£N 3?d^!3Bk#M                                                         #m@{z,qh?=Q+cHE'D*Q9PnSm ݚ\'_:a[߰V^\+_׭~Ww3[skuMߴi֯Ynn\+u`>>v{_Y`WcU}d_s߹vAkݡ) 5735de2b810bba182986adaf3f0d2e6567697caecbebb5d3f2934d8d37f32d2d000066400000000000000000000026771514714641000406420ustar00rootroot00000000000000accelerated-container-image-1.4.2/ci/e2e/resources/store/hello-world/blobs/sha256{"architecture":"ppc64le","config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/hello"],"Image":"sha256:d10f62162101fe360d2292f3163e7d4d07c9bd3a4bbba4a48a4bdccfa622cd5d","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"container":"bf5c33b539ecc0a88ba3448ed5d95fdc587a6fb295347fc31a296286a419aea6","container_config":{"Hostname":"bf5c33b539ec","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh","-c","#(nop) ","CMD [\"/hello\"]"],"Image":"sha256:d10f62162101fe360d2292f3163e7d4d07c9bd3a4bbba4a48a4bdccfa622cd5d","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":{}},"created":"2023-05-04T23:35:14.791724762Z","docker_version":"20.10.23","history":[{"created":"2023-05-04T23:35:14.47386416Z","created_by":"/bin/sh -c #(nop) COPY file:d983edb4c32b67abbd6c309cebc3bf9ace8e1c065505fe8ae03686b5a75a5552 in / "},{"created":"2023-05-04T23:35:14.791724762Z","created_by":"/bin/sh -c #(nop) CMD [\"/hello\"]","empty_layer":true}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:ac1bc0996cc2106a1e725a10fac1236fb365304b85257f0b82ab950db3bd8880"]}}574efe68740d3bee2ef780036aee2e2da5cf7097ac06513f9f01f41e03365399000066400000000000000000000010151514714641000402500ustar00rootroot00000000000000accelerated-container-image-1.4.2/ci/e2e/resources/store/hello-world/blobs/sha256{ "schemaVersion": 2, "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "config": { "mediaType": "application/vnd.docker.container.image.v1+json", "size": 1470, "digest": "sha256:d549b3d1a2550a5e42a3f3250ce9fa4aa882b25f8528fd2eea1b789ce136631b" }, "layers": [ { "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", "size": 3265, "digest": "sha256:d898e3d98788d70a8f874f818177c11569c5bb3c818ef45b877e7bba29a3bf7f" } ] }630da353b594c9ca52c67727920737dba3e5aaa4fbe20cdd0fc70c0591b7a639000066400000000000000000000052341514714641000403640ustar00rootroot00000000000000accelerated-container-image-1.4.2/ci/e2e/resources/store/hello-world/blobs/sha256[}l[}\¥~E6;Աi(maC 4G!-pcnpM HMœ5& !@hD:MJ٤Ңvݭ,HI3k1M1ݟt=w<9>c'TJ/P0xwAkC!|gK8tˆQ&xև87,/4D jp= x"Y^ֶX n^`m)TNb]uī@}EӁ8p8^Z~->_3& 77v9gS뭟dZ1bd(hڌ81'F"s9>yb!jlC}f׭L;)t9J*;&)45tzfm<_u~rK!Ȝ^C@>Cnl>ml:=`aUK{\ܑ&6yxi1vlq2Ψ^FlP>S)E}̗yD),܊=S.-;AVq=Heֲ#+oent'3bNe8oދPni\'DKaA:G/?īܦ/u9Mfx]8 #glJh}N f6Fo/G[cXG5v9" JGre-rP|ϻؒ#56i狟%o&qz[@)ek+qXmqdYj &@6U2}84j!vv>H}ܹݳliBXͻ\C֣֣6A[p|1G;OY UƄE)-O8.Pk=ϯ~<nLp>h;sc VwHGgmACӔvX->3&4qC&# _O/5n6qPg2qFwuZ[-leSk"OAaP3jluf;7`13Jc^F>fzv$sȶ\o̍ZcjGc- N;][v-Gl[̴-]oF`t{_(LǦX,(z#:w;lî?8phHNGt@gk5Lje5:y}k%q6/ ]>9A4k|Z~P?}F1 oҧ\V%wo9CB_*BqT)曏7O Yﰛaw]{$ 6)x})na*ՕN6Ⱦ2;YVP4Qţ輿sG,{/<*oɖ6L6K/^C~{|XvZ@KPvooJ5Mv7՝lUO^57<g)赘nj:wT#.s{Mbߘ/M Lp'=]??rG'G׷%((# W'B'93?2C틼A(::lܛן'߬+~(>sW9>txpQC=dLE^DȲχ<a%/n8z³RJdԫ}(,6HIUuRHB$h)(VJ$)/z$4 +u(&+#]F뤴DBnc"zTJ`U"=(@ iK%4A19MX\tܔr7R yEG>{ZOB KAg4H"S!4PVLKʌ5T$R4Kdە6I1=)LWl5үٹF"(R6#E5_OJIɌ"WV`cEʲ$""HJ_2- GH$`'+z$)kj0&YhHL-MD $Iba/{gEI2GTO436"Q#rVI)yeQ"] rJү.ojJh=@LkxLiןTV2.j#ژTjv\IdJDH&ƦJ=U \/oϙ\(Qz#risڦ=^߿_w8p(Y^x$ B5ϛ/>WP*Qd:I @Mt)D"IYQ!/ľSen oϩ|g>?|\Ud*}^:nw@UV_oDqT1wTJyOxߪ > *xz-TX x^0B}`)ż\Yu^'8p?P:70f5ac315c5af948332962ff4678084ebcc215809506e4b8cd9e509660417205000066400000000000000000000061661514714641000376310ustar00rootroot00000000000000accelerated-container-image-1.4.2/ci/e2e/resources/store/hello-world/blobs/sha256ZlW~619Ӌ(1!1᭡q {mTakv9%)ۦY.81ۤic5 Nrw䨲7Ej &8P?Nڏ~|w4V8\4ojjmMyoii{[ZZm 66}~KjJMo[œ }A'̮3Zð.06wтقXPdb|kֿ|aI uM^W;iݮO7Xnhޒa:洌5ɕkZ%wg}wh?dg4D3נg=a0j?84@Icy8z_=z#@U-/~``ly;\n$[u~O>|:o]V {bc $0lgOtpmqDzNQ;4]+%~fYv\w3Uz#+[1-LN3@CgF2s z9I30 |9_.gb<׌!18YpanjW'Yr*)ߎ;0 .ƕΈNZVu؛AmJ.[ֽb>ҹ`u:5qLq^G {q݅!7˒kW`bO;=+CϺ,=᜼(Cd^W7Xdu{ zɣK<3Z`o_q SʬkA9y8ϖZ֩/;x?:a@`{|[.s˪OL|$Kn\1[ :5)%3!Weľ\^iY͏dfZSh؛uB&'/2C}C3—:g\!^g AU9g~u溌e);JL0r9du(IYsr-T+GqE<ӪPekpRƸiU`n<9P N_Q10~1Ӳז2~vėd΂qq_ =1_?Ųs PeU_Yxh.U]OZ F |v7(][Nեs {YlwAJud꜌@'Ô$!@`E {H)#m#/#6ҟ/}khjq [7xs-/wN]~ k 6pS.}s'@m4. +K S,+べ/0ȸqgDUZ`olG&^=\a;oJ뜺Nv$1@ױ`9xL;@{f>_A I=9 Ɠ1>Y*[=:BhїTRu%*Sd#Mpa T1>X.+37 |Vsv;?ѿ8bOc83 @:-1jg f;^Fa}O̒*yO}!v,cǀ+C]n6 :YW*'npEʊ|AuU?~ov ).Odԇ%}@1K+^p׃Uu@k9D+m1(l _N-lTdoܥgE킞ҳ..uZO:fZm`çW`'* p^n$ޜ(qǞY+QGY+pc''YVOoYCbAFy@2j{͍H׳K>%7*~+ӳ:sy @zZ|º!i:uL]w@`U 3 [LĊU`isf1S{mvfUf\f!/4.<$tW"CC{ԄGGM&*MT +=KH,)ѨE1J"I8RxbO$≄Ң=@VcjBTFx|*F>7U* %jL Uv7zhIj UghQดhGZ<55)h2ޣjaqcz *=]-K6Ub]}1o^ E}ZOF.%xv$H%ҧ{biW4OźxT܄> kZou}]ySx:x"T+=Q5iVd}kwY^'Eoh)s|E|wQ}r3|wQy?u{?R;v~n;߷x}:m}:z*}~y(胷PB %|3*719385e32844401d57ecfd3eacab360bf551a1491c05b85806ed8f1b08d792f6000066400000000000000000000046311514714641000401670ustar00rootroot00000000000000accelerated-container-image-1.4.2/ci/e2e/resources/store/hello-world/blobs/sha256[[l?ؓo!P8^\!dɷD8F1+vw;.H5qaRԷTjEC!oKm6uHlX"߃~\l5Y}ttl޼u8E6G:PkM];&;lTs7?t7|(b^_0jQwh jDQ -D.Ua.W#7_Zh; l֏Z U ;~eGt}ĥanyj CIjїkuyU9[s&?BN?Bbe! BT3B'~`|=b?*D> 6) f+vds3~wGUQi`]  3[H ~H[;¼k]3&O]O|.{b=Y({X%}vA@;_hkh4>֯\k|b*E,%eָpX_c{TxM0^eA9و`vo_yloB\ 6fn8dǂpw1YflX%0n ` Ro%r˲\"1#4޺?sy%{Bv,ˢE~lC˕k7-+ɩ&ysf'>,,(peK" fq@u U#E랙O#Ł ~:'RQ lO(l(`ƅnz6f:bFBp(|&\=q1]MOwb8'|gT ) nq-<) A\-W3ƾtGbh'MS\!_yhsVBn;k}xn1&hGlĂ&wm=B:㚫?dpieY'?NMHm"$a'hA05?Vfw0vn$s bOۢ-v#HgwOI0uN3 ɘo"ʹ@y9 3=IJᶸzrBֿy3Ѽ_t!4:΂9LM| ATLN~ ur3'{ءYn,s!۴`w ՂgC9HdƸ4\p=B8--K`|<tAv9>b' lgD#;;vTh-˲$؄رlfy-4kY#͕'?Z"={t뼺j!K_ Z [p p/Zo7J@O5: ;_)梵ΟbWo $0ֳY~eèfSkp&:{ `(1mR.~NQ,2$G2yY~4I,v`ԉ4Ⱚlaݞl,2N*Iv NhDݧDQRۙ3nI +"DVsuVcXSr2Ie)`d]k+LJyPf"F3$-r3ކ Ct ЛfM3cI'Jm(˧,*n(iBmpZ 9BI%WTG)rYYceR㑌!5ꔒjdA#JT8@<٬{nVw8wi?B轋Plyrw=xNsUԵ_-jw58.ӯo~ԵUǯ" #y{|}!\u_Q˭sswW!Qu߭%[]һp;zkXȯ_?H)ߡ >)3/gA1|& _C hMe*qq]+* XOb@5 u.n(a^$NV5lTΆKg 4U{Dl oGn}剈xCoHcUţ#*Ĵʩ-'HߔxLOGcGp|6haP_E|'s&3M'?k HHob{"Pz8"RA |Űfc%=R#FꙫKnq߻>Lv@ yYק^@U]_-m̮]͝n6oN>Ί&tJTsZtӒ@FF}9x&S@5/sha3cϣo(#m{f@>dgs%3/N *U_+j/ѣ_ɼ|n `b>d5fc= o2LHODT(jk-o$ K0QVvo4լ1_H2yO"O8#i(>cXv1vaz m ^}xٍb穿$-̯a*_Em?z!џ8jd\; FExvAbuW(ȑWHDUaIw/f|05JFtY$1ad_D?実E!K" zS$!Pr /&\sMZ6:>OV4k*VV$cYq} CIDw JZI+uKAoHDx?^@ oGٻsF y#!d^)ZԼvyowIξЛ/0y%6p_d:u67}7Zw}vúcwntO!Chs$4U!T-W[=qqLh9_OHzZ0^xtgK)~ԮU` wmI2=)z~z`_많[^Ǐ}WG45Ofs?dBrR{xllwNI\XuY'E{/v|R(Ugx&>{&M,C}7sg]C{9{W԰]Mt-qlNsM4{;mk'42MFzK Cfxjah߮TBt4ϵWG+/)h>&9FmkaA| }8>spag#ulj}r*91OM%#;Hu ,G!~s*ON֋Q~B{>Q]] ;F6W=6h>U@_YN;|𭠿"z{ڡrů.֎?"M0^:bvͦ$}2nXb}6:D_" { 5o^$I7|sXydI:$cU_$)d\/+ٯdI+wI 1ٯHj_~vxFDO*)>uFI܊oT6SFX 2/Q՜?!^Ŕy:Լp7_/0DM;Ri"lrCv#z؁ ޼ 'e|/yyz"z F} ņj-W/Խl:d9iq`a))06#XvimI ~LXf^vP͜%~R(C jrlw \sb^pi"krvbvaάR tT2MlH=v6sem+Tj_WAGQ:׶m#U0,1r" ejbV;ǧ2B*be-}F[yr rVn9u,sLR)#M,Kj:_[vƦN(,`Z?C՛;?d~P,Ypz1uϮIw l?N7){]03&i`[0<ϴjfFЬ|Pۀ,m\GWsS/tH rv~ >IX_0~x< [_MtR^~>mu+9)}NJ7~r_bi<c{l5ȯc4018b8bf4381a79e05c15591e1a1adf4452a9d383c4dcb63b1b7d4617d73af8000066400000000000000000000056431514714641000403200ustar00rootroot00000000000000accelerated-container-image-1.4.2/ci/e2e/resources/store/hello-world/blobs/sha256X}ly޻㇎4?٦e:~%ى,Kdɵ',̓u"Ϻ)ލ o[ܯ|(螀F0A|@0P 5ʚjxAX8^?s4jbԚLu)aG]Y-*(s( xƃE+a%7Uoc_>%+n6B/DeHk20Ft3j[qM|ʡ ՗>7<gk5ne'|'2G/Ts1⾦1^hZK;J+ӥw ݶw4\F;.i6Pu&Փ,q#sP;gJDr 7NNM@AȖͻL]P}<5sVi.4@&r/V=_톛fᩰ#7'g'FnږFs%tapiJFXH,MB [Z_"cvieg JW |T*5;sƜ ~gvouA 4r&瘲):VU*U wXПZ%MԴnx5 )( A~8H ^`9tNnzu@^8 n^VD@S$ޅkEh3E)NQ noΞS{H}9gC'}FQ0ݛ;;YW '׋/ 2C]oKX.Bg. XKQ ;RT9!#K;d]&յ5]pȐXjYav(u#99SП?-ŘA}AFh?BBRԏ@~O"J;11~@KñKI7Z`taՍ3oM0~qpzD7l0-;Syw6~olodeIG6?$B=oRMgp [Z/VR٘)E{m>H4[OH Pv ?ocԏ *,z0.hyX^#̿.MF hB $ OHtwYƢ/`$@?̺fZ秩Y+pGt|dvwH".77H{0P t}RCNz=35{FR2KsVɫNoq=^*N=i5 N۫{9m jӶÚ3iHDpڶ>hTsE_,NsF Vl NۼW 1Vsⴭ5#ZiiiMQt֍37pZMl8mЫwsږSxӶ5#ZU#m4z>\M?^nTӦ:7NjZ-ۅ+Z]yZ]Ó: bN| !%CG̗w|*-cFuqQa! VE^cr6TFI^ExP#rq9jr,De<$4 X]\UY>UQdR13d:iEa OJBJq9qUHXJ/ ˩Bl2Y[g9Lci،Њ}Y$;1ƸOoO SH1)%yb`D"NgSfZYL eٸ`UņϦBJM'PQ?lEʫk,>r+.ēJ* a)'˵Sc8# hfXN TIf-| )`6f%bDRN‰u"8["AZ4c>ft*)kDٴɇy|*-|PIcQUL}bv0T6heMaVBiHI]CbT!a_Bxf_*L~P~ ={S>rYaz ȚsU:'~~Pdqm[vq*GP ڌTKѣT=PF̨iT¨ SRJPZN*!Aʘ!YP<@ȓ_ 񪚖?U`ˀrÚٿw\c[U!\1ߎ μZj@1nRt7vHXi9g ά뇿%+>fu8Oƻd%&>>2FY[7q|[EPCy(,u,d549b3d1a2550a5e42a3f3250ce9fa4aa882b25f8528fd2eea1b789ce136631b000066400000000000000000000026761514714641000404000ustar00rootroot00000000000000accelerated-container-image-1.4.2/ci/e2e/resources/store/hello-world/blobs/sha256{"architecture":"s390x","config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/hello"],"Image":"sha256:0a4ed120e12f159b468ae1fbdf6d7e2c053fd5547f6ad1e12019283e425a4f0c","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"container":"16de42fe001ec70d74120e5db0a765dc7cf960bc3ecdfdd23fe034dac2b12962","container_config":{"Hostname":"16de42fe001e","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh","-c","#(nop) ","CMD [\"/hello\"]"],"Image":"sha256:0a4ed120e12f159b468ae1fbdf6d7e2c053fd5547f6ad1e12019283e425a4f0c","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":{}},"created":"2023-05-05T06:04:49.524781092Z","docker_version":"20.10.23","history":[{"created":"2023-05-05T06:04:49.407407092Z","created_by":"/bin/sh -c #(nop) COPY file:2412b45fdbb28b2191d4b0d59a4c66e460863549bae786d0aaf49c123b876e0b in / "},{"created":"2023-05-05T06:04:49.524781092Z","created_by":"/bin/sh -c #(nop) CMD [\"/hello\"]","empty_layer":true}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:3aced21b93452b84cf84067c6ef10ea4a4841f8ca36a18300504f9b408f0082f"]}}d898e3d98788d70a8f874f818177c11569c5bb3c818ef45b877e7bba29a3bf7f000066400000000000000000000063011514714641000403360ustar00rootroot00000000000000accelerated-container-image-1.4.2/ci/e2e/resources/store/hello-world/blobs/sha256zlug+Hks9"\-)itk]+ƕSF:+[n/!R&0f |m Y4mB@.R׮$,c.C 1=DK"@[}>oޯy;Z^d,vI״{ͧU];5ܹ}W=_~.PlBNZk:He2vCEMh dTV@GR>ˁQTqܗ>6a{Q)/x9*nc]x@lfH˗;z8?%fCqyubSw0;R8P¶@A϶UQU(Na k*eQliOS)ùU>t̋eXKg tʡh ?9 t~2(E; /CAyDvHb9o }/H8"s3sk/ʹ?)_qRXQ"r9Ğ &;ԡm)jR[Rj?_yWއ~F|˪9qpo>_ų;h"G~mr'? &kҚv &0e]*lwS/;tn:w?%J/d:i}\وb !pyo_CW+w/N#P]ݻ *o؝c_砸dϴ0ZO\8UF"Yd6~^=F%{t+tT=^+&JEѺ=:Of㸍m:QUϽo,B "PL3Z!_k|3yV.Gg/I`ðӍfQ_>y4*p)/.wOA[6Ge}̽,#"=6_5C6iWQq?pO3s~%)˖a³g ҳ"gGE{($~)ku=N)zKE X $ռ PݸρIvu)^qa 計olRo1W^A̍{#ޏs{ zWө7g"YUR࿿Īui}!]O+sJb!{_suIi14pK%KJO Jiϰ%%ӎKޒp^ȯ)fZen}l"2s%x FD? lu'JOyoM.)AΑ1"ƋDD(v12jnQEVo%VF٢=P cT7eoǧ]QM "N.z٣@]im׶(2Ɨ@"=uQͩC3vq/L+¢e^[Dl~v$xQ6`HěD# x% xyn|n{`ezEQgnެ9qv9*!hp ϰB+_gfsύ{hf+18i;0жUOY"wL7ݸ8*5GE? QmRTX8*?_Wnpfm(w% je<8uFG~p M C/']LGɍ^q]K^GX:N0B1>1!Q2g%o, UrDXJI7 "=͠h:/mN/:8#y9X`JuKwsU^wnvξ8՛U耙XD\wP*#f>(g+n+q 'Z?/*{еSkoX^9s ͧvN7M36zOiᤝD:v"1;d[RsG~2b-ֿ o`y ]ŷ:󷰿_~US>1sx&x1Q|5+۠JU /etc/docker/daemon.json dockerd &>/var/log/dockerd.log & sleep 3 rm -rf /etc/registry/ mkdir -p /etc/registry/certs/ mkdir -p /etc/registry/config/ # generate server certifications cat << EOF > /etc/registry/openssl.cnf [req] distinguished_name = req_distinguished_name x509_extensions = v3_req prompt = no [req_distinguished_name] C = CN ST = Beijing L = Beijing City O = Alibaba CN = localhost [v3_req] basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment subjectAltName = @alt_names [alt_names] DNS.1 = localhost IP.1 = 127.0.0.1 EOF openssl req -new -x509 -newkey rsa:2048 -sha256 -nodes -config /etc/registry/openssl.cnf \ -days 365 -out /etc/registry/certs/server.crt -keyout /etc/registry/certs/server.key ls /etc/registry/certs/ cp /etc/registry/certs/server.crt /usr/local/share/ca-certificates/registry.crt update-ca-certificates # start registry cat << EOF > /etc/registry/config/config.yml version: 0.1 log: fields: service: registry storage: cache: blobdescriptor: inmemory filesystem: rootdirectory: /var/lib/registry http: addr: :5000 headers: X-Content-Type-Options: [nosniff] tls: certificate: /certs/server.crt key: /certs/server.key health: storagedriver: enabled: true interval: 10s threshold: 3 EOF docker run -d --restart=always --name registry -p 5000:5000 \ -v /etc/registry/certs:/certs \ -v /etc/registry/config:/etc/docker/registry/ \ registry:2 sleep 5s docker ps -a apt-get update && apt-get install -y lsof lsof -i :5000 curl http://localhost:5000/v2/_catalog lsof -i :5000 curl https://localhost:5000/v2/_catalog accelerated-container-image-1.4.2/ci/scripts/prepare_image.sh000077500000000000000000000002131514714641000242320ustar00rootroot00000000000000#!/bin/bash from=${1:?} to=${2:?} set -x ctr i pull "${from}" ctr i tag "${from}" "${to}" ctr i push "${to}" ctr i rm "${from}" "${to}" accelerated-container-image-1.4.2/ci/scripts/run_container.sh000077500000000000000000000033031514714641000243030ustar00rootroot00000000000000#!/bin/bash # # rpull and run on-demand set -x image=$1 container_name=${2:-test} exit_code=0 /opt/overlaybd/snapshotter/ctr rpull "${image}" if ! ctr run -d --net-host --snapshotter=overlaybd "${image}" "${container_name}"; then exit_code=1 fi lsblk if ! ctr t ls | grep "${container_name}"; then exit_code=1 fi snapshot_id="" config_path="" if [[ ${exit_code} -eq 0 ]]; then snapshot_id=$(ctr c info "${container_name}" | grep -oP '"SnapshotKey":\s*"\K[^"]+') if [[ -n "${snapshot_id}" ]]; then config_path="/var/lib/overlaybd/snapshots/${snapshot_id}/block/config.v1.json" if [[ -f "${config_path}" ]]; then echo "Found config file: ${config_path}" cp "${config_path}" /tmp/test_config.v1.json else echo "Warning: config file not found at ${config_path}" fi else echo "Warning: could not get snapshot ID" fi fi ctr t kill -s 9 "${container_name}" && sleep 5s && ctr t ls ctr c rm "${container_name}" && ctr c ls ctr i rm "${image}" if [[ -f /tmp/test_config.v1.json && -n "${snapshot_id}" ]]; then echo "Testing overlaybd-attacher attach..." if /opt/overlaybd/snapshotter/overlaybd-attacher attach --id "${snapshot_id}" --config /tmp/test_config.v1.json; then echo "overlaybd-attacher attach succeeded" echo "Testing overlaybd-attacher detach..." if /opt/overlaybd/snapshotter/overlaybd-attacher detach --id "${snapshot_id}"; then echo "overlaybd-attacher detach succeeded" else echo "overlaybd-attacher detach failed" exit_code=1 fi else echo "overlaybd-attacher attach failed" exit_code=1 fi rm -f /tmp/test_config.v1.json fi if [[ ${exit_code} -ne 0 ]]; then cat /var/log/overlaybd.log fi exit ${exit_code} accelerated-container-image-1.4.2/ci/uconv_reproduce/000077500000000000000000000000001514714641000226125ustar00rootroot00000000000000accelerated-container-image-1.4.2/ci/uconv_reproduce/centos/000077500000000000000000000000001514714641000241055ustar00rootroot00000000000000accelerated-container-image-1.4.2/ci/uconv_reproduce/centos/config-turbo-erofs.json000066400000000000000000000027041514714641000305150ustar00rootroot00000000000000{"created":"2021-09-15T18:20:23.99863383Z","architecture":"amd64","os":"linux","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/bash"],"Labels":{"org.label-schema.build-date":"20201113","org.label-schema.license":"GPLv2","org.label-schema.name":"CentOS Base Image","org.label-schema.schema-version":"1.0","org.label-schema.vendor":"CentOS","org.opencontainers.image.created":"2020-11-13 00:00:00+00:00","org.opencontainers.image.licenses":"GPL-2.0-only","org.opencontainers.image.title":"CentOS Base Image","org.opencontainers.image.vendor":"CentOS"}},"rootfs":{"type":"layers","diff_ids":["sha256:379d33b3da38f8546c995fe8977c6453c6d87e6671426f516b9a6357fa386ea8"]},"history":[{"created":"2021-09-15T18:20:23.417639551Z","created_by":"/bin/sh -c #(nop) ADD file:b3ebbe8bd304723d43b7b44a6d990cd657b63d93d6a2a9293983a30bfc1dfa53 in / "},{"created":"2021-09-15T18:20:23.819893035Z","created_by":"/bin/sh -c #(nop) LABEL org.label-schema.schema-version=1.0 org.label-schema.name=CentOS Base Image org.label-schema.vendor=CentOS org.label-schema.license=GPLv2 org.label-schema.build-date=20201113 org.opencontainers.image.title=CentOS Base Image org.opencontainers.image.vendor=CentOS org.opencontainers.image.licenses=GPL-2.0-only org.opencontainers.image.created=2020-11-13 00:00:00+00:00","empty_layer":true},{"created":"2021-09-15T18:20:23.99863383Z","created_by":"/bin/sh -c #(nop) CMD [\"/bin/bash\"]","empty_layer":true}]}accelerated-container-image-1.4.2/ci/uconv_reproduce/centos/config-turbo.json000066400000000000000000000027041514714641000274010ustar00rootroot00000000000000{"created":"2021-09-15T18:20:23.99863383Z","architecture":"amd64","os":"linux","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/bash"],"Labels":{"org.label-schema.build-date":"20201113","org.label-schema.license":"GPLv2","org.label-schema.name":"CentOS Base Image","org.label-schema.schema-version":"1.0","org.label-schema.vendor":"CentOS","org.opencontainers.image.created":"2020-11-13 00:00:00+00:00","org.opencontainers.image.licenses":"GPL-2.0-only","org.opencontainers.image.title":"CentOS Base Image","org.opencontainers.image.vendor":"CentOS"}},"rootfs":{"type":"layers","diff_ids":["sha256:a5cdf182bbffc6992b556633bbb275b559056b6e953133772a80fa261886c07b"]},"history":[{"created":"2021-09-15T18:20:23.417639551Z","created_by":"/bin/sh -c #(nop) ADD file:b3ebbe8bd304723d43b7b44a6d990cd657b63d93d6a2a9293983a30bfc1dfa53 in / "},{"created":"2021-09-15T18:20:23.819893035Z","created_by":"/bin/sh -c #(nop) LABEL org.label-schema.schema-version=1.0 org.label-schema.name=CentOS Base Image org.label-schema.vendor=CentOS org.label-schema.license=GPLv2 org.label-schema.build-date=20201113 org.opencontainers.image.title=CentOS Base Image org.opencontainers.image.vendor=CentOS org.opencontainers.image.licenses=GPL-2.0-only org.opencontainers.image.created=2020-11-13 00:00:00+00:00","empty_layer":true},{"created":"2021-09-15T18:20:23.99863383Z","created_by":"/bin/sh -c #(nop) CMD [\"/bin/bash\"]","empty_layer":true}]}accelerated-container-image-1.4.2/ci/uconv_reproduce/centos/config.json000066400000000000000000000027041514714641000262500ustar00rootroot00000000000000{"created":"2021-09-15T18:20:23.99863383Z","architecture":"amd64","os":"linux","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/bash"],"Labels":{"org.label-schema.build-date":"20201113","org.label-schema.license":"GPLv2","org.label-schema.name":"CentOS Base Image","org.label-schema.schema-version":"1.0","org.label-schema.vendor":"CentOS","org.opencontainers.image.created":"2020-11-13 00:00:00+00:00","org.opencontainers.image.licenses":"GPL-2.0-only","org.opencontainers.image.title":"CentOS Base Image","org.opencontainers.image.vendor":"CentOS"}},"rootfs":{"type":"layers","diff_ids":["sha256:53c0d30a5a262d18df478f5db01e3392fffb498b8538ada56e66df293b17ab5b"]},"history":[{"created":"2021-09-15T18:20:23.417639551Z","created_by":"/bin/sh -c #(nop) ADD file:b3ebbe8bd304723d43b7b44a6d990cd657b63d93d6a2a9293983a30bfc1dfa53 in / "},{"created":"2021-09-15T18:20:23.819893035Z","created_by":"/bin/sh -c #(nop) LABEL org.label-schema.schema-version=1.0 org.label-schema.name=CentOS Base Image org.label-schema.vendor=CentOS org.label-schema.license=GPLv2 org.label-schema.build-date=20201113 org.opencontainers.image.title=CentOS Base Image org.opencontainers.image.vendor=CentOS org.opencontainers.image.licenses=GPL-2.0-only org.opencontainers.image.created=2020-11-13 00:00:00+00:00","empty_layer":true},{"created":"2021-09-15T18:20:23.99863383Z","created_by":"/bin/sh -c #(nop) CMD [\"/bin/bash\"]","empty_layer":true}]}accelerated-container-image-1.4.2/ci/uconv_reproduce/centos/manifest-turbo-erofs.json000066400000000000000000000016431514714641000310570ustar00rootroot00000000000000{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:48b560cf624c64f4864f769a3607f139a79fb592f33e1d3594f3e3c6d9259e26","size":1476},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:f64a7fa8973910cd8b6dd6884be20c20716baece12fedf90d1def44303040f4a","size":2697067,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:f64a7fa8973910cd8b6dd6884be20c20716baece12fedf90d1def44303040f4a","containerd.io/snapshot/overlaybd/blob-size":"2697067","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:2d473b07cdd5f0912cd6f1a703352c82b512407db6b05b43f2553732b55df3bc","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}}]}accelerated-container-image-1.4.2/ci/uconv_reproduce/centos/manifest-turbo.json000066400000000000000000000016431514714641000277430ustar00rootroot00000000000000{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:bdeebc045cc88055079dd5ab899cf594614b2757d5b572ec7acc6f4a24031fb1","size":1476},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:e91a425b0b5001fa96ae2909db392034ee9eccfdf9347410a1343719db83054e","size":2717102,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:e91a425b0b5001fa96ae2909db392034ee9eccfdf9347410a1343719db83054e","containerd.io/snapshot/overlaybd/blob-size":"2717102","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:2d473b07cdd5f0912cd6f1a703352c82b512407db6b05b43f2553732b55df3bc","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}}]}accelerated-container-image-1.4.2/ci/uconv_reproduce/centos/manifest.json000066400000000000000000000012361514714641000266100ustar00rootroot00000000000000{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:31b9bf1971683a48863ab9e3a820d34f1267e7e2927f5805b7620632d2d22413","size":1476},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:53c0d30a5a262d18df478f5db01e3392fffb498b8538ada56e66df293b17ab5b","size":124133376,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:53c0d30a5a262d18df478f5db01e3392fffb498b8538ada56e66df293b17ab5b","containerd.io/snapshot/overlaybd/blob-size":"124133376","containerd.io/snapshot/overlaybd/version":"0.1.0"}}]}accelerated-container-image-1.4.2/ci/uconv_reproduce/ci-uconv-reproduce.sh000066400000000000000000000074421514714641000266660ustar00rootroot00000000000000#!/bin/bash apt update && apt install -y python3 jq convertor="/opt/overlaybd/snapshotter/convertor" images=("centos:centos7.9.2009" "ubuntu:22.04" "redis:7.2.3" "wordpress:6.4.2" "nginx:1.25.3") registry="localhost:5000" ci_base=$(pwd) result=0 for image in "${images[@]}" do img=${image%%":"*} tag=${image##*":"} echo "${img} ${tag}" workspace="${ci_base}/workspace_${image/:/_}" rm -rf "${workspace}" mkdir -p "${workspace}" tag_obd="${tag}_overlaybd" tag_turbo="${tag}_turbo" tag_turbo_erofs="${tag}_turbo_erofs" manifest_obd="${workspace}/manifest.json" manifest_turbo="${workspace}/manifest-turbo.json" manifest_turbo_erofs="${workspace}/manifest-turbo-erofs.json" config_obd="${workspace}/config.json" config_turbo="${workspace}/config-turbo.json" config_turbo_erofs="${workspace}/config-turbo-erofs.json" output_obd="${workspace}/convert.overlaybd.out" output_turbo="${workspace}/convert.turbo.out" output_turbo_erofs="${workspace}/convert.turbo.erofs.out" ${convertor} -r "${registry}/${img}" -i "${tag}" --overlaybd "${tag_obd}" -d "${workspace}/overlaybd_tmp_conv" &> "${output_obd}" curl -H "Accept: application/vnd.docker.distribution.manifest.v2+json,application/vnd.oci.image.manifest.v1+json" -o "${manifest_obd}" "https://${registry}/v2/${img}/manifests/${tag_obd}" &> /dev/null configDigest=$(jq '.config.digest' "${manifest_obd}") configDigest=${configDigest//\"/} curl -o "${config_obd}" "https://${registry}/v2/${img}/blobs/${configDigest}" &> /dev/null ${convertor} -r "${registry}/${img}" -i "${tag}" --turboOCI "${tag_turbo}" -d "${workspace}/turbo_tmp_conv" &> "${output_turbo}" curl -H "Accept: application/vnd.docker.distribution.manifest.v2+json,application/vnd.oci.image.manifest.v1+json" -o "${manifest_turbo}" "https://${registry}/v2/${img}/manifests/${tag_turbo}" &> /dev/null configDigest=$(jq '.config.digest' "${manifest_turbo}") configDigest=${configDigest//\"/} curl -o "${config_turbo}" "https://${registry}/v2/${img}/blobs/${configDigest}" &> /dev/null ${convertor} -r "${registry}/${img}" -i "${tag}" --turboOCI "${tag_turbo_erofs}" --fstype erofs -d "${workspace}/turbo_erofs_tmp_conv" &> "${output_turbo_erofs}" curl -s -H "Accept: application/vnd.docker.distribution.manifest.v2+json" "https://${registry}/v2/${img}/manifests/${tag_turbo_erofs}" curl -H "Accept: application/vnd.docker.distribution.manifest.v2+json,application/vnd.oci.image.manifest.v1+json" -o "${manifest_turbo_erofs}" "https://${registry}/v2/${img}/manifests/${tag_turbo_erofs}" &> /dev/null configDigest=$(jq '.config.digest' "${manifest_turbo_erofs}") configDigest=${configDigest//\"/} curl -o "${config_turbo_erofs}" "https://${registry}/v2/${img}/blobs/${configDigest}" &> /dev/null prefix=$(date +%Y%m%d%H%M%S) mode=("manifest" "config" "manifest" "config" "manifest" "config") actual=("${manifest_obd}" "${config_obd}" "${manifest_turbo}" "${config_turbo}" "${manifest_turbo_erofs}" "${config_turbo_erofs}") expected=("${ci_base}/${img}/manifest.json" "${ci_base}/${img}/config.json" "${ci_base}/${img}/manifest-turbo.json" "${ci_base}/${img}/config-turbo.json" "${ci_base}/${img}/manifest-turbo-erofs.json" "${ci_base}/${img}/config-turbo-erofs.json") conv_res=0 n=${#mode[@]} for ((i=0; i " % os.path.basename(sys.argv[0])) ftype = sys.argv[1] fa = sys.argv[2] fb = sys.argv[3] if not os.path.exists(fa): print("file %s not exist" % fa) sys.exit(-1) if not os.path.exists(fb): print("file %s not exist" % fb) sys.exit(-1) fa_conf = json.load(open(fa, 'r')) fb_conf = json.load(open(fb, 'r')) if ftype == "manifest": sys.exit(compare_manifest(fa_conf, fb_conf)) elif ftype == "config": sys.exit(compare_config(fa_conf, fb_conf)) else: print("unknown type %s" % ftype) sys.exit(-1) if __name__ == '__main__': main() accelerated-container-image-1.4.2/ci/uconv_reproduce/nginx/000077500000000000000000000000001514714641000237355ustar00rootroot00000000000000accelerated-container-image-1.4.2/ci/uconv_reproduce/nginx/config-turbo-erofs.json000066400000000000000000000155311514714641000303470ustar00rootroot00000000000000{"created":"2023-10-24T22:44:45Z","architecture":"amd64","os":"linux","config":{"ExposedPorts":{"80/tcp":{}},"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","NGINX_VERSION=1.25.3","NJS_VERSION=0.8.2","PKG_RELEASE=1~bookworm"],"Entrypoint":["/docker-entrypoint.sh"],"Cmd":["nginx","-g","daemon off;"],"Labels":{"maintainer":"NGINX Docker Maintainers \u003cdocker-maint@nginx.com\u003e"},"StopSignal":"SIGQUIT","ArgsEscaped":true},"rootfs":{"type":"layers","diff_ids":["sha256:abb8474cd5ee7c0199099cb2f09b7b5039cae70c74042f6809059bbaca34d94a","sha256:2b33f144d6b15dd87b62b370fa81ffb3eed54af9dd37ebe8f477d8852675957e","sha256:586f5f1ae7cc13a219018cc8f20070bc233eb8cb7d153b0b249167c26332a127","sha256:461b9e691ecf8a12b9578d480a5adb3e4c2f2d14fc39b684113f001e635a4bcf","sha256:1004fcd5fd6ff6abff91febdb5302caa3beef80c74c233060f50407a8a1ef8f3","sha256:10cbe3c0fa7fc18da12dc5a6d4a2499e19781bc3c46d62f6ceba1ac0eb3bd462","sha256:2bafba57f3e89d4e47b97141db54b20862e4c57e241732cd31dcdc3245c9998e"]},"history":[{"created":"2023-10-24T22:44:45Z","created_by":"/bin/sh -c #(nop) ADD file:ac3cd70031d35e46d86b876934946ffc8756de4de065fbc926dce642dac07ff3 in / "},{"created":"2023-10-24T22:44:45Z","created_by":"/bin/sh -c #(nop) CMD [\"bash\"]","empty_layer":true},{"created":"2023-10-24T22:44:45Z","created_by":"LABEL maintainer=NGINX Docker Maintainers \u003cdocker-maint@nginx.com\u003e","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-10-24T22:44:45Z","created_by":"ENV NGINX_VERSION=1.25.3","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-10-24T22:44:45Z","created_by":"ENV NJS_VERSION=0.8.2","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-10-24T22:44:45Z","created_by":"ENV PKG_RELEASE=1~bookworm","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-10-24T22:44:45Z","created_by":"RUN /bin/sh -c set -x \u0026\u0026 groupadd --system --gid 101 nginx \u0026\u0026 useradd --system --gid nginx --no-create-home --home /nonexistent --comment \"nginx user\" --shell /bin/false --uid 101 nginx \u0026\u0026 apt-get update \u0026\u0026 apt-get install --no-install-recommends --no-install-suggests -y gnupg1 ca-certificates \u0026\u0026 NGINX_GPGKEY=573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62; NGINX_GPGKEY_PATH=/usr/share/keyrings/nginx-archive-keyring.gpg; export GNUPGHOME=\"$(mktemp -d)\"; found=''; for server in hkp://keyserver.ubuntu.com:80 pgp.mit.edu ; do echo \"Fetching GPG key $NGINX_GPGKEY from $server\"; gpg1 --keyserver \"$server\" --keyserver-options timeout=10 --recv-keys \"$NGINX_GPGKEY\" \u0026\u0026 found=yes \u0026\u0026 break; done; test -z \"$found\" \u0026\u0026 echo \u003e\u00262 \"error: failed to fetch GPG key $NGINX_GPGKEY\" \u0026\u0026 exit 1; gpg1 --export \"$NGINX_GPGKEY\" \u003e \"$NGINX_GPGKEY_PATH\" ; rm -rf \"$GNUPGHOME\"; apt-get remove --purge --auto-remove -y gnupg1 \u0026\u0026 rm -rf /var/lib/apt/lists/* \u0026\u0026 dpkgArch=\"$(dpkg --print-architecture)\" \u0026\u0026 nginxPackages=\" nginx=${NGINX_VERSION}-${PKG_RELEASE} nginx-module-xslt=${NGINX_VERSION}-${PKG_RELEASE} nginx-module-geoip=${NGINX_VERSION}-${PKG_RELEASE} nginx-module-image-filter=${NGINX_VERSION}-${PKG_RELEASE} nginx-module-njs=${NGINX_VERSION}+${NJS_VERSION}-${PKG_RELEASE} \" \u0026\u0026 case \"$dpkgArch\" in amd64|arm64) echo \"deb [signed-by=$NGINX_GPGKEY_PATH] https://nginx.org/packages/mainline/debian/ bookworm nginx\" \u003e\u003e /etc/apt/sources.list.d/nginx.list \u0026\u0026 apt-get update ;; *) echo \"deb-src [signed-by=$NGINX_GPGKEY_PATH] https://nginx.org/packages/mainline/debian/ bookworm nginx\" \u003e\u003e /etc/apt/sources.list.d/nginx.list \u0026\u0026 tempDir=\"$(mktemp -d)\" \u0026\u0026 chmod 777 \"$tempDir\" \u0026\u0026 savedAptMark=\"$(apt-mark showmanual)\" \u0026\u0026 apt-get update \u0026\u0026 apt-get build-dep -y $nginxPackages \u0026\u0026 ( cd \"$tempDir\" \u0026\u0026 DEB_BUILD_OPTIONS=\"nocheck parallel=$(nproc)\" apt-get source --compile $nginxPackages ) \u0026\u0026 apt-mark showmanual | xargs apt-mark auto \u003e /dev/null \u0026\u0026 { [ -z \"$savedAptMark\" ] || apt-mark manual $savedAptMark; } \u0026\u0026 ls -lAFh \"$tempDir\" \u0026\u0026 ( cd \"$tempDir\" \u0026\u0026 dpkg-scanpackages . \u003e Packages ) \u0026\u0026 grep '^Package: ' \"$tempDir/Packages\" \u0026\u0026 echo \"deb [ trusted=yes ] file://$tempDir ./\" \u003e /etc/apt/sources.list.d/temp.list \u0026\u0026 apt-get -o Acquire::GzipIndexes=false update ;; esac \u0026\u0026 apt-get install --no-install-recommends --no-install-suggests -y $nginxPackages gettext-base curl \u0026\u0026 apt-get remove --purge --auto-remove -y \u0026\u0026 rm -rf /var/lib/apt/lists/* /etc/apt/sources.list.d/nginx.list \u0026\u0026 if [ -n \"$tempDir\" ]; then apt-get purge -y --auto-remove \u0026\u0026 rm -rf \"$tempDir\" /etc/apt/sources.list.d/temp.list; fi \u0026\u0026 ln -sf /dev/stdout /var/log/nginx/access.log \u0026\u0026 ln -sf /dev/stderr /var/log/nginx/error.log \u0026\u0026 mkdir /docker-entrypoint.d # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-10-24T22:44:45Z","created_by":"COPY docker-entrypoint.sh / # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-10-24T22:44:45Z","created_by":"COPY 10-listen-on-ipv6-by-default.sh /docker-entrypoint.d # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-10-24T22:44:45Z","created_by":"COPY 15-local-resolvers.envsh /docker-entrypoint.d # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-10-24T22:44:45Z","created_by":"COPY 20-envsubst-on-templates.sh /docker-entrypoint.d # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-10-24T22:44:45Z","created_by":"COPY 30-tune-worker-processes.sh /docker-entrypoint.d # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-10-24T22:44:45Z","created_by":"ENTRYPOINT [\"/docker-entrypoint.sh\"]","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-10-24T22:44:45Z","created_by":"EXPOSE map[80/tcp:{}]","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-10-24T22:44:45Z","created_by":"STOPSIGNAL SIGQUIT","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-10-24T22:44:45Z","created_by":"CMD [\"nginx\" \"-g\" \"daemon off;\"]","comment":"buildkit.dockerfile.v0","empty_layer":true}]}accelerated-container-image-1.4.2/ci/uconv_reproduce/nginx/config-turbo.json000066400000000000000000000155311514714641000272330ustar00rootroot00000000000000{"created":"2023-10-24T22:44:45Z","architecture":"amd64","os":"linux","config":{"ExposedPorts":{"80/tcp":{}},"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","NGINX_VERSION=1.25.3","NJS_VERSION=0.8.2","PKG_RELEASE=1~bookworm"],"Entrypoint":["/docker-entrypoint.sh"],"Cmd":["nginx","-g","daemon off;"],"Labels":{"maintainer":"NGINX Docker Maintainers \u003cdocker-maint@nginx.com\u003e"},"StopSignal":"SIGQUIT","ArgsEscaped":true},"rootfs":{"type":"layers","diff_ids":["sha256:e33f3ead13b687a680cf13d7f86a569702cf655a0589aafd55612a917d95a08a","sha256:91383a518f47aeceff58fb3775f03b6a0b76d13bbc26c7b39832a23f1c0b0cde","sha256:766f42e8eb2ac31f367924bd8420fc15f4b44847c3b0722fb1b321e36c3775c2","sha256:49727dd910d22ecb0c3daa57f82525990cf3de2fe6870146e0598f63055b5766","sha256:95e2b5fa5d0146c2ec249d84545cf5cc16260eee36d946039d0c61b71a8b2262","sha256:481660c954bec0aeb5b3710c6a1dacbbbe193f168492121fd5977d5e806bcdef","sha256:85887d672eff2cc622f243384ad72977535756eee73ae785c04a6a4a223b4c8f"]},"history":[{"created":"2023-10-24T22:44:45Z","created_by":"/bin/sh -c #(nop) ADD file:ac3cd70031d35e46d86b876934946ffc8756de4de065fbc926dce642dac07ff3 in / "},{"created":"2023-10-24T22:44:45Z","created_by":"/bin/sh -c #(nop) CMD [\"bash\"]","empty_layer":true},{"created":"2023-10-24T22:44:45Z","created_by":"LABEL maintainer=NGINX Docker Maintainers \u003cdocker-maint@nginx.com\u003e","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-10-24T22:44:45Z","created_by":"ENV NGINX_VERSION=1.25.3","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-10-24T22:44:45Z","created_by":"ENV NJS_VERSION=0.8.2","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-10-24T22:44:45Z","created_by":"ENV PKG_RELEASE=1~bookworm","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-10-24T22:44:45Z","created_by":"RUN /bin/sh -c set -x \u0026\u0026 groupadd --system --gid 101 nginx \u0026\u0026 useradd --system --gid nginx --no-create-home --home /nonexistent --comment \"nginx user\" --shell /bin/false --uid 101 nginx \u0026\u0026 apt-get update \u0026\u0026 apt-get install --no-install-recommends --no-install-suggests -y gnupg1 ca-certificates \u0026\u0026 NGINX_GPGKEY=573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62; NGINX_GPGKEY_PATH=/usr/share/keyrings/nginx-archive-keyring.gpg; export GNUPGHOME=\"$(mktemp -d)\"; found=''; for server in hkp://keyserver.ubuntu.com:80 pgp.mit.edu ; do echo \"Fetching GPG key $NGINX_GPGKEY from $server\"; gpg1 --keyserver \"$server\" --keyserver-options timeout=10 --recv-keys \"$NGINX_GPGKEY\" \u0026\u0026 found=yes \u0026\u0026 break; done; test -z \"$found\" \u0026\u0026 echo \u003e\u00262 \"error: failed to fetch GPG key $NGINX_GPGKEY\" \u0026\u0026 exit 1; gpg1 --export \"$NGINX_GPGKEY\" \u003e \"$NGINX_GPGKEY_PATH\" ; rm -rf \"$GNUPGHOME\"; apt-get remove --purge --auto-remove -y gnupg1 \u0026\u0026 rm -rf /var/lib/apt/lists/* \u0026\u0026 dpkgArch=\"$(dpkg --print-architecture)\" \u0026\u0026 nginxPackages=\" nginx=${NGINX_VERSION}-${PKG_RELEASE} nginx-module-xslt=${NGINX_VERSION}-${PKG_RELEASE} nginx-module-geoip=${NGINX_VERSION}-${PKG_RELEASE} nginx-module-image-filter=${NGINX_VERSION}-${PKG_RELEASE} nginx-module-njs=${NGINX_VERSION}+${NJS_VERSION}-${PKG_RELEASE} \" \u0026\u0026 case \"$dpkgArch\" in amd64|arm64) echo \"deb [signed-by=$NGINX_GPGKEY_PATH] https://nginx.org/packages/mainline/debian/ bookworm nginx\" \u003e\u003e /etc/apt/sources.list.d/nginx.list \u0026\u0026 apt-get update ;; *) echo \"deb-src [signed-by=$NGINX_GPGKEY_PATH] https://nginx.org/packages/mainline/debian/ bookworm nginx\" \u003e\u003e /etc/apt/sources.list.d/nginx.list \u0026\u0026 tempDir=\"$(mktemp -d)\" \u0026\u0026 chmod 777 \"$tempDir\" \u0026\u0026 savedAptMark=\"$(apt-mark showmanual)\" \u0026\u0026 apt-get update \u0026\u0026 apt-get build-dep -y $nginxPackages \u0026\u0026 ( cd \"$tempDir\" \u0026\u0026 DEB_BUILD_OPTIONS=\"nocheck parallel=$(nproc)\" apt-get source --compile $nginxPackages ) \u0026\u0026 apt-mark showmanual | xargs apt-mark auto \u003e /dev/null \u0026\u0026 { [ -z \"$savedAptMark\" ] || apt-mark manual $savedAptMark; } \u0026\u0026 ls -lAFh \"$tempDir\" \u0026\u0026 ( cd \"$tempDir\" \u0026\u0026 dpkg-scanpackages . \u003e Packages ) \u0026\u0026 grep '^Package: ' \"$tempDir/Packages\" \u0026\u0026 echo \"deb [ trusted=yes ] file://$tempDir ./\" \u003e /etc/apt/sources.list.d/temp.list \u0026\u0026 apt-get -o Acquire::GzipIndexes=false update ;; esac \u0026\u0026 apt-get install --no-install-recommends --no-install-suggests -y $nginxPackages gettext-base curl \u0026\u0026 apt-get remove --purge --auto-remove -y \u0026\u0026 rm -rf /var/lib/apt/lists/* /etc/apt/sources.list.d/nginx.list \u0026\u0026 if [ -n \"$tempDir\" ]; then apt-get purge -y --auto-remove \u0026\u0026 rm -rf \"$tempDir\" /etc/apt/sources.list.d/temp.list; fi \u0026\u0026 ln -sf /dev/stdout /var/log/nginx/access.log \u0026\u0026 ln -sf /dev/stderr /var/log/nginx/error.log \u0026\u0026 mkdir /docker-entrypoint.d # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-10-24T22:44:45Z","created_by":"COPY docker-entrypoint.sh / # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-10-24T22:44:45Z","created_by":"COPY 10-listen-on-ipv6-by-default.sh /docker-entrypoint.d # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-10-24T22:44:45Z","created_by":"COPY 15-local-resolvers.envsh /docker-entrypoint.d # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-10-24T22:44:45Z","created_by":"COPY 20-envsubst-on-templates.sh /docker-entrypoint.d # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-10-24T22:44:45Z","created_by":"COPY 30-tune-worker-processes.sh /docker-entrypoint.d # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-10-24T22:44:45Z","created_by":"ENTRYPOINT [\"/docker-entrypoint.sh\"]","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-10-24T22:44:45Z","created_by":"EXPOSE map[80/tcp:{}]","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-10-24T22:44:45Z","created_by":"STOPSIGNAL SIGQUIT","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-10-24T22:44:45Z","created_by":"CMD [\"nginx\" \"-g\" \"daemon off;\"]","comment":"buildkit.dockerfile.v0","empty_layer":true}]}accelerated-container-image-1.4.2/ci/uconv_reproduce/nginx/config.json000066400000000000000000000155061514714641000261040ustar00rootroot00000000000000{"created":"2023-10-24T22:44:45Z","architecture":"amd64","os":"linux","config":{"ExposedPorts":{"80/tcp":{}},"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","NGINX_VERSION=1.25.3","NJS_VERSION=0.8.2","PKG_RELEASE=1~bookworm"],"Entrypoint":["/docker-entrypoint.sh"],"Cmd":["nginx","-g","daemon off;"],"Labels":{"maintainer":"NGINX Docker Maintainers \u003cdocker-maint@nginx.com\u003e"},"StopSignal":"SIGQUIT"},"rootfs":{"type":"layers","diff_ids":["sha256:a640fc493a1522f8e40b74dc77386011d1d10783d81750b8135e127b703d5d93","sha256:bce7e4294cde0058a61ec6d989273b90d686014247a42927c4f9eb5aeae6c39a","sha256:845f1d7a59e4b4df2d498696bc549d409dcde2b98e34e63cc65ae75db2fe83a8","sha256:398b316a94114cc6532a8c37657c0b838c756d197464eadd5548718234426b38","sha256:c0a684333684633bfdc9aae952335ae0af51b73d00e29431809d3b3e347ec55f","sha256:0372ed0bfd846e6580eeb13f82b138a06ffa12b11072f2eae6dce75cdc79a21f","sha256:05a0133ac3c319ad8900c559b4d868e595d20b47e6ec0804d262b19380414b06"]},"history":[{"created":"2023-10-24T22:44:45Z","created_by":"/bin/sh -c #(nop) ADD file:ac3cd70031d35e46d86b876934946ffc8756de4de065fbc926dce642dac07ff3 in / "},{"created":"2023-10-24T22:44:45Z","created_by":"/bin/sh -c #(nop) CMD [\"bash\"]","empty_layer":true},{"created":"2023-10-24T22:44:45Z","created_by":"LABEL maintainer=NGINX Docker Maintainers \u003cdocker-maint@nginx.com\u003e","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-10-24T22:44:45Z","created_by":"ENV NGINX_VERSION=1.25.3","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-10-24T22:44:45Z","created_by":"ENV NJS_VERSION=0.8.2","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-10-24T22:44:45Z","created_by":"ENV PKG_RELEASE=1~bookworm","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-10-24T22:44:45Z","created_by":"RUN /bin/sh -c set -x \u0026\u0026 groupadd --system --gid 101 nginx \u0026\u0026 useradd --system --gid nginx --no-create-home --home /nonexistent --comment \"nginx user\" --shell /bin/false --uid 101 nginx \u0026\u0026 apt-get update \u0026\u0026 apt-get install --no-install-recommends --no-install-suggests -y gnupg1 ca-certificates \u0026\u0026 NGINX_GPGKEY=573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62; NGINX_GPGKEY_PATH=/usr/share/keyrings/nginx-archive-keyring.gpg; export GNUPGHOME=\"$(mktemp -d)\"; found=''; for server in hkp://keyserver.ubuntu.com:80 pgp.mit.edu ; do echo \"Fetching GPG key $NGINX_GPGKEY from $server\"; gpg1 --keyserver \"$server\" --keyserver-options timeout=10 --recv-keys \"$NGINX_GPGKEY\" \u0026\u0026 found=yes \u0026\u0026 break; done; test -z \"$found\" \u0026\u0026 echo \u003e\u00262 \"error: failed to fetch GPG key $NGINX_GPGKEY\" \u0026\u0026 exit 1; gpg1 --export \"$NGINX_GPGKEY\" \u003e \"$NGINX_GPGKEY_PATH\" ; rm -rf \"$GNUPGHOME\"; apt-get remove --purge --auto-remove -y gnupg1 \u0026\u0026 rm -rf /var/lib/apt/lists/* \u0026\u0026 dpkgArch=\"$(dpkg --print-architecture)\" \u0026\u0026 nginxPackages=\" nginx=${NGINX_VERSION}-${PKG_RELEASE} nginx-module-xslt=${NGINX_VERSION}-${PKG_RELEASE} nginx-module-geoip=${NGINX_VERSION}-${PKG_RELEASE} nginx-module-image-filter=${NGINX_VERSION}-${PKG_RELEASE} nginx-module-njs=${NGINX_VERSION}+${NJS_VERSION}-${PKG_RELEASE} \" \u0026\u0026 case \"$dpkgArch\" in amd64|arm64) echo \"deb [signed-by=$NGINX_GPGKEY_PATH] https://nginx.org/packages/mainline/debian/ bookworm nginx\" \u003e\u003e /etc/apt/sources.list.d/nginx.list \u0026\u0026 apt-get update ;; *) echo \"deb-src [signed-by=$NGINX_GPGKEY_PATH] https://nginx.org/packages/mainline/debian/ bookworm nginx\" \u003e\u003e /etc/apt/sources.list.d/nginx.list \u0026\u0026 tempDir=\"$(mktemp -d)\" \u0026\u0026 chmod 777 \"$tempDir\" \u0026\u0026 savedAptMark=\"$(apt-mark showmanual)\" \u0026\u0026 apt-get update \u0026\u0026 apt-get build-dep -y $nginxPackages \u0026\u0026 ( cd \"$tempDir\" \u0026\u0026 DEB_BUILD_OPTIONS=\"nocheck parallel=$(nproc)\" apt-get source --compile $nginxPackages ) \u0026\u0026 apt-mark showmanual | xargs apt-mark auto \u003e /dev/null \u0026\u0026 { [ -z \"$savedAptMark\" ] || apt-mark manual $savedAptMark; } \u0026\u0026 ls -lAFh \"$tempDir\" \u0026\u0026 ( cd \"$tempDir\" \u0026\u0026 dpkg-scanpackages . \u003e Packages ) \u0026\u0026 grep '^Package: ' \"$tempDir/Packages\" \u0026\u0026 echo \"deb [ trusted=yes ] file://$tempDir ./\" \u003e /etc/apt/sources.list.d/temp.list \u0026\u0026 apt-get -o Acquire::GzipIndexes=false update ;; esac \u0026\u0026 apt-get install --no-install-recommends --no-install-suggests -y $nginxPackages gettext-base curl \u0026\u0026 apt-get remove --purge --auto-remove -y \u0026\u0026 rm -rf /var/lib/apt/lists/* /etc/apt/sources.list.d/nginx.list \u0026\u0026 if [ -n \"$tempDir\" ]; then apt-get purge -y --auto-remove \u0026\u0026 rm -rf \"$tempDir\" /etc/apt/sources.list.d/temp.list; fi \u0026\u0026 ln -sf /dev/stdout /var/log/nginx/access.log \u0026\u0026 ln -sf /dev/stderr /var/log/nginx/error.log \u0026\u0026 mkdir /docker-entrypoint.d # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-10-24T22:44:45Z","created_by":"COPY docker-entrypoint.sh / # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-10-24T22:44:45Z","created_by":"COPY 10-listen-on-ipv6-by-default.sh /docker-entrypoint.d # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-10-24T22:44:45Z","created_by":"COPY 15-local-resolvers.envsh /docker-entrypoint.d # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-10-24T22:44:45Z","created_by":"COPY 20-envsubst-on-templates.sh /docker-entrypoint.d # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-10-24T22:44:45Z","created_by":"COPY 30-tune-worker-processes.sh /docker-entrypoint.d # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-10-24T22:44:45Z","created_by":"ENTRYPOINT [\"/docker-entrypoint.sh\"]","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-10-24T22:44:45Z","created_by":"EXPOSE map[80/tcp:{}]","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-10-24T22:44:45Z","created_by":"STOPSIGNAL SIGQUIT","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-10-24T22:44:45Z","created_by":"CMD [\"nginx\" \"-g\" \"daemon off;\"]","comment":"buildkit.dockerfile.v0","empty_layer":true}]}accelerated-container-image-1.4.2/ci/uconv_reproduce/nginx/manifest-turbo-erofs.json000066400000000000000000000114451514714641000307100ustar00rootroot00000000000000{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:67af1543970e694908655ff9bee5c6306c2bfebbd5b5e5f85e6df11a2a7c4bf9","size":7001},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:8669c0b06bac9a3b6437647361cc0fed70a500744e513bdbb4088c71db0c4c01","size":1104210,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:8669c0b06bac9a3b6437647361cc0fed70a500744e513bdbb4088c71db0c4c01","containerd.io/snapshot/overlaybd/blob-size":"1104210","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:af107e978371b6cd6339127a05502c5eacd1e6b0e9eb7b2f4aa7b6fc87e2dd81","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:41e8e4b81e936a1587c15ffadc01505fd8b53602d96d28973356753c905de8a9","size":1445588,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:41e8e4b81e936a1587c15ffadc01505fd8b53602d96d28973356753c905de8a9","containerd.io/snapshot/overlaybd/blob-size":"1445588","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:336ba1f05c3ede29f0a73d3f88b39a14f6abdc57fafedf3891fd793504440263","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:c254cf608f0631dca80975f3fc34de475578bc77191c719dbf26492920637915","size":972,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:c254cf608f0631dca80975f3fc34de475578bc77191c719dbf26492920637915","containerd.io/snapshot/overlaybd/blob-size":"972","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:8c37d2ff6efa0a08f83056109a47aa0caf2cc82136d926d1176cd451f7fbb245","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:e42484694a78760fc87cad61e5aebd8314c9d17cbdde7e6bfe68dfb72b03c438","size":1055,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:e42484694a78760fc87cad61e5aebd8314c9d17cbdde7e6bfe68dfb72b03c438","containerd.io/snapshot/overlaybd/blob-size":"1055","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:51d6357098de68f5fc2e50afdaa73fc4fcbdeed2161adc9f14d1d8dae9d94d36","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:f272d06d4ad1db1612834947ffa53c8afc10ffcbf2f26985ce971fe219fb94e8","size":1077,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:f272d06d4ad1db1612834947ffa53c8afc10ffcbf2f26985ce971fe219fb94e8","containerd.io/snapshot/overlaybd/blob-size":"1077","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:782f1ecce57d1fa61421872a16b979ad92057db19841b5811616a749705214f4","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:4d8463f6791599f5ecc6b75a9c0c663b397f2569c6c4e8090d741f8e6e5b8ec4","size":1117,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:4d8463f6791599f5ecc6b75a9c0c663b397f2569c6c4e8090d741f8e6e5b8ec4","containerd.io/snapshot/overlaybd/blob-size":"1117","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:5e99d351b073fec15b9817dc5234f32433ef0404849cc66857be2eca5192ccf8","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:1c0a24247098f7e92210357dabc96adad60fb88c79ee6bcdad8d50b4a2e632b7","size":1137,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:1c0a24247098f7e92210357dabc96adad60fb88c79ee6bcdad8d50b4a2e632b7","containerd.io/snapshot/overlaybd/blob-size":"1137","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:7b73345df136081ef2e60fd5cb875771c02c5ecb76015292babbc4711d195a31","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}}]}accelerated-container-image-1.4.2/ci/uconv_reproduce/nginx/manifest-turbo.json000066400000000000000000000114471514714641000275760ustar00rootroot00000000000000{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:50af392da450b64e5eba79a2bf7801f928eeb810bd12bcd348f624faff8b8b1d","size":7001},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:93d5e7762541cd51f8e8dceea4978516596aa3dd837978334b60f0f8c03ca7b0","size":1124551,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:93d5e7762541cd51f8e8dceea4978516596aa3dd837978334b60f0f8c03ca7b0","containerd.io/snapshot/overlaybd/blob-size":"1124551","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:af107e978371b6cd6339127a05502c5eacd1e6b0e9eb7b2f4aa7b6fc87e2dd81","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:1506f7bd93175aeef67b9b3b82d9be033e4190df6ae969f92bc259c521fc57e5","size":1447184,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:1506f7bd93175aeef67b9b3b82d9be033e4190df6ae969f92bc259c521fc57e5","containerd.io/snapshot/overlaybd/blob-size":"1447184","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:336ba1f05c3ede29f0a73d3f88b39a14f6abdc57fafedf3891fd793504440263","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:8e437e609e9ba192e0ee5e565bd2751e259b22865e7d326137d2445d7948c29b","size":5461,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:8e437e609e9ba192e0ee5e565bd2751e259b22865e7d326137d2445d7948c29b","containerd.io/snapshot/overlaybd/blob-size":"5461","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:8c37d2ff6efa0a08f83056109a47aa0caf2cc82136d926d1176cd451f7fbb245","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:4207acbe5f7f14981e6bc9084f027cc0929e7a04522554e502f0d1d86db810bb","size":5515,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:4207acbe5f7f14981e6bc9084f027cc0929e7a04522554e502f0d1d86db810bb","containerd.io/snapshot/overlaybd/blob-size":"5515","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:51d6357098de68f5fc2e50afdaa73fc4fcbdeed2161adc9f14d1d8dae9d94d36","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:bf30195dd726146b4226dd9801be61e0489fffee76c691c4d3548adbfab1e0c1","size":5551,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:bf30195dd726146b4226dd9801be61e0489fffee76c691c4d3548adbfab1e0c1","containerd.io/snapshot/overlaybd/blob-size":"5551","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:782f1ecce57d1fa61421872a16b979ad92057db19841b5811616a749705214f4","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:119182134a3bf46ce36129d941f445c1f25a40aba54ca45125b297ae0333ee0f","size":5606,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:119182134a3bf46ce36129d941f445c1f25a40aba54ca45125b297ae0333ee0f","containerd.io/snapshot/overlaybd/blob-size":"5606","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:5e99d351b073fec15b9817dc5234f32433ef0404849cc66857be2eca5192ccf8","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:686371e8b8e647b5aef01a3efe017beb9416d7351a0d822d6a89bdfc389293cd","size":5635,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:686371e8b8e647b5aef01a3efe017beb9416d7351a0d822d6a89bdfc389293cd","containerd.io/snapshot/overlaybd/blob-size":"5635","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:7b73345df136081ef2e60fd5cb875771c02c5ecb76015292babbc4711d195a31","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}}]}accelerated-container-image-1.4.2/ci/uconv_reproduce/nginx/manifest.json000066400000000000000000000060001514714641000264320ustar00rootroot00000000000000{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:0d18c272af106b9344fd93e4db274da32b8dd9a4e63c04195fe595157602f515","size":6982},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:a640fc493a1522f8e40b74dc77386011d1d10783d81750b8135e127b703d5d93","size":46912000,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:a640fc493a1522f8e40b74dc77386011d1d10783d81750b8135e127b703d5d93","containerd.io/snapshot/overlaybd/blob-size":"46912000","containerd.io/snapshot/overlaybd/version":"0.1.0"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:bce7e4294cde0058a61ec6d989273b90d686014247a42927c4f9eb5aeae6c39a","size":66607104,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:bce7e4294cde0058a61ec6d989273b90d686014247a42927c4f9eb5aeae6c39a","containerd.io/snapshot/overlaybd/blob-size":"66607104","containerd.io/snapshot/overlaybd/version":"0.1.0"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:845f1d7a59e4b4df2d498696bc549d409dcde2b98e34e63cc65ae75db2fe83a8","size":613376,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:845f1d7a59e4b4df2d498696bc549d409dcde2b98e34e63cc65ae75db2fe83a8","containerd.io/snapshot/overlaybd/blob-size":"613376","containerd.io/snapshot/overlaybd/version":"0.1.0"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:398b316a94114cc6532a8c37657c0b838c756d197464eadd5548718234426b38","size":624640,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:398b316a94114cc6532a8c37657c0b838c756d197464eadd5548718234426b38","containerd.io/snapshot/overlaybd/blob-size":"624640","containerd.io/snapshot/overlaybd/version":"0.1.0"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:c0a684333684633bfdc9aae952335ae0af51b73d00e29431809d3b3e347ec55f","size":624640,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:c0a684333684633bfdc9aae952335ae0af51b73d00e29431809d3b3e347ec55f","containerd.io/snapshot/overlaybd/blob-size":"624640","containerd.io/snapshot/overlaybd/version":"0.1.0"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:0372ed0bfd846e6580eeb13f82b138a06ffa12b11072f2eae6dce75cdc79a21f","size":625152,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:0372ed0bfd846e6580eeb13f82b138a06ffa12b11072f2eae6dce75cdc79a21f","containerd.io/snapshot/overlaybd/blob-size":"625152","containerd.io/snapshot/overlaybd/version":"0.1.0"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:05a0133ac3c319ad8900c559b4d868e595d20b47e6ec0804d262b19380414b06","size":647168,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:05a0133ac3c319ad8900c559b4d868e595d20b47e6ec0804d262b19380414b06","containerd.io/snapshot/overlaybd/blob-size":"647168","containerd.io/snapshot/overlaybd/version":"0.1.0"}}]}accelerated-container-image-1.4.2/ci/uconv_reproduce/redis/000077500000000000000000000000001514714641000237205ustar00rootroot00000000000000accelerated-container-image-1.4.2/ci/uconv_reproduce/redis/config-turbo-erofs.json000066400000000000000000000217371514714641000303370ustar00rootroot00000000000000{"created":"2023-12-04T22:45:52Z","architecture":"amd64","os":"linux","config":{"ExposedPorts":{"6379/tcp":{}},"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","GOSU_VERSION=1.17","REDIS_VERSION=7.2.3","REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-7.2.3.tar.gz","REDIS_DOWNLOAD_SHA=3e2b196d6eb4ddb9e743088bfc2915ccbb42d40f5a8a3edd8cb69c716ec34be7"],"Entrypoint":["docker-entrypoint.sh"],"Cmd":["redis-server"],"Volumes":{"/data":{}},"WorkingDir":"/data","ArgsEscaped":true},"rootfs":{"type":"layers","diff_ids":["sha256:abb8474cd5ee7c0199099cb2f09b7b5039cae70c74042f6809059bbaca34d94a","sha256:312e03952b4011fd7a5a97aa841c6745bbdcb552fda64f0a12302ab1286d421a","sha256:d35ea03c96d308e524a19dd2a6e40c9b1f0e3589b0735d7edc1bb39769806e18","sha256:32c7d239b1c1dc330bc9f5bc0787390e9dcfadb85095bbafe5beab01d64fd026","sha256:cfcf8ce0595d8def09c1baa5b3d94d24eb934c06434f533b76edfc12cdeeeb75","sha256:0881eedbc0de0ab76d98646982cb6e5cb3e60b42505071e90e34f779eb1041f5","sha256:abfc89db2ffa592c20defa3403367da630ce76e18c44bf6cf4ef47a83fea2fa2","sha256:3fec3b8181c1872b3b6a0ed8d1ed2eec0ec07e801ff942ccb3e99c2497c0e373"]},"history":[{"created":"2023-12-04T22:45:52Z","created_by":"/bin/sh -c #(nop) ADD file:ac3cd70031d35e46d86b876934946ffc8756de4de065fbc926dce642dac07ff3 in / "},{"created":"2023-12-04T22:45:52Z","created_by":"/bin/sh -c #(nop) CMD [\"bash\"]","empty_layer":true},{"created":"2023-12-04T22:45:52Z","created_by":"RUN /bin/sh -c set -eux; \tgroupadd -r -g 999 redis; \tuseradd -r -g redis -u 999 redis # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-04T22:45:52Z","created_by":"RUN /bin/sh -c set -eux; \tapt-get update; \tapt-get install -y --no-install-recommends \t\ttzdata \t; \trm -rf /var/lib/apt/lists/* # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-04T22:45:52Z","created_by":"ENV GOSU_VERSION=1.17","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-12-04T22:45:52Z","created_by":"RUN /bin/sh -c set -eux; \tsavedAptMark=\"$(apt-mark showmanual)\"; \tapt-get update; \tapt-get install -y --no-install-recommends ca-certificates gnupg wget; \trm -rf /var/lib/apt/lists/*; \tarch=\"$(dpkg --print-architecture | awk -F- '{ print $NF }')\"; \tcase \"$arch\" in \t\t'amd64') url='https://github.com/tianon/gosu/releases/download/1.17/gosu-amd64'; sha256='bbc4136d03ab138b1ad66fa4fc051bafc6cc7ffae632b069a53657279a450de3' ;; \t\t'arm64') url='https://github.com/tianon/gosu/releases/download/1.17/gosu-arm64'; sha256='c3805a85d17f4454c23d7059bcb97e1ec1af272b90126e79ed002342de08389b' ;; \t\t'armel') url='https://github.com/tianon/gosu/releases/download/1.17/gosu-armel'; sha256='f9969910fa141140438c998cfa02f603bf213b11afd466dcde8fa940e700945d' ;; \t\t'i386') url='https://github.com/tianon/gosu/releases/download/1.17/gosu-i386'; sha256='087dbb8fe479537e64f9c86fa49ff3b41dee1cbd28739a19aaef83dc8186b1ca' ;; \t\t'mips64el') url='https://github.com/tianon/gosu/releases/download/1.17/gosu-mips64el'; sha256='87140029d792595e660be0015341dfa1c02d1181459ae40df9f093e471d75b70' ;; \t\t'ppc64el') url='https://github.com/tianon/gosu/releases/download/1.17/gosu-ppc64el'; sha256='1891acdcfa70046818ab6ed3c52b9d42fa10fbb7b340eb429c8c7849691dbd76' ;; \t\t'riscv64') url='https://github.com/tianon/gosu/releases/download/1.17/gosu-riscv64'; sha256='38a6444b57adce135c42d5a3689f616fc7803ddc7a07ff6f946f2ebc67a26ba6' ;; \t\t's390x') url='https://github.com/tianon/gosu/releases/download/1.17/gosu-s390x'; sha256='69873bab588192f760547ca1f75b27cfcf106e9f7403fee6fd0600bc914979d0' ;; \t\t'armhf') url='https://github.com/tianon/gosu/releases/download/1.17/gosu-armhf'; sha256='e5866286277ff2a2159fb9196fea13e0a59d3f1091ea46ddb985160b94b6841b' ;; \t\t*) echo \u003e\u00262 \"error: unsupported gosu architecture: '$arch'\"; exit 1 ;; \tesac; \twget -O /usr/local/bin/gosu.asc \"$url.asc\"; \twget -O /usr/local/bin/gosu \"$url\"; \techo \"$sha256 */usr/local/bin/gosu\" | sha256sum -c -; \texport GNUPGHOME=\"$(mktemp -d)\"; \tgpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \tgpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \tgpgconf --kill all; \trm -rf \"$GNUPGHOME\" /usr/local/bin/gosu.asc; \tapt-mark auto '.*' \u003e /dev/null; \t[ -z \"$savedAptMark\" ] || apt-mark manual $savedAptMark \u003e /dev/null; \tapt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \tchmod +x /usr/local/bin/gosu; \tgosu --version; \tgosu nobody true # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-04T22:45:52Z","created_by":"ENV REDIS_VERSION=7.2.3","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-12-04T22:45:52Z","created_by":"ENV REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-7.2.3.tar.gz","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-12-04T22:45:52Z","created_by":"ENV REDIS_DOWNLOAD_SHA=3e2b196d6eb4ddb9e743088bfc2915ccbb42d40f5a8a3edd8cb69c716ec34be7","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-12-04T22:45:52Z","created_by":"RUN /bin/sh -c set -eux; \t\tsavedAptMark=\"$(apt-mark showmanual)\"; \tapt-get update; \tapt-get install -y --no-install-recommends \t\tca-certificates \t\twget \t\t\t\tdpkg-dev \t\tgcc \t\tlibc6-dev \t\tlibssl-dev \t\tmake \t; \trm -rf /var/lib/apt/lists/*; \t\twget -O redis.tar.gz \"$REDIS_DOWNLOAD_URL\"; \techo \"$REDIS_DOWNLOAD_SHA *redis.tar.gz\" | sha256sum -c -; \tmkdir -p /usr/src/redis; \ttar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1; \trm redis.tar.gz; \t\tgrep -E '^ *createBoolConfig[(]\"protected-mode\",.*, *1 *,.*[)],$' /usr/src/redis/src/config.c; \tsed -ri 's!^( *createBoolConfig[(]\"protected-mode\",.*, *)1( *,.*[)],)$!\\10\\2!' /usr/src/redis/src/config.c; \tgrep -E '^ *createBoolConfig[(]\"protected-mode\",.*, *0 *,.*[)],$' /usr/src/redis/src/config.c; \t\tgnuArch=\"$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)\"; \textraJemallocConfigureFlags=\"--build=$gnuArch\"; \tdpkgArch=\"$(dpkg --print-architecture)\"; \tcase \"${dpkgArch##*-}\" in \t\tamd64 | i386 | x32) extraJemallocConfigureFlags=\"$extraJemallocConfigureFlags --with-lg-page=12\" ;; \t\t*) extraJemallocConfigureFlags=\"$extraJemallocConfigureFlags --with-lg-page=16\" ;; \tesac; \textraJemallocConfigureFlags=\"$extraJemallocConfigureFlags --with-lg-hugepage=21\"; \tgrep -F 'cd jemalloc \u0026\u0026 ./configure ' /usr/src/redis/deps/Makefile; \tsed -ri 's!cd jemalloc \u0026\u0026 ./configure !\u0026'\"$extraJemallocConfigureFlags\"' !' /usr/src/redis/deps/Makefile; \tgrep -F \"cd jemalloc \u0026\u0026 ./configure $extraJemallocConfigureFlags \" /usr/src/redis/deps/Makefile; \t\texport BUILD_TLS=yes; \tmake -C /usr/src/redis -j \"$(nproc)\" all; \tmake -C /usr/src/redis install; \t\tserverMd5=\"$(md5sum /usr/local/bin/redis-server | cut -d' ' -f1)\"; export serverMd5; \tfind /usr/local/bin/redis* -maxdepth 0 \t\t-type f -not -name redis-server \t\t-exec sh -eux -c ' \t\t\tmd5=\"$(md5sum \"$1\" | cut -d\" \" -f1)\"; \t\t\ttest \"$md5\" = \"$serverMd5\"; \t\t' -- '{}' ';' \t\t-exec ln -svfT 'redis-server' '{}' ';' \t; \t\trm -r /usr/src/redis; \t\tapt-mark auto '.*' \u003e /dev/null; \t[ -z \"$savedAptMark\" ] || apt-mark manual $savedAptMark \u003e /dev/null; \tfind /usr/local -type f -executable -exec ldd '{}' ';' \t\t| awk '/=\u003e/ { so = $(NF-1); if (index(so, \"/usr/local/\") == 1) { next }; gsub(\"^/(usr/)?\", \"\", so); print so }' \t\t| sort -u \t\t| xargs -r dpkg-query --search \t\t| cut -d: -f1 \t\t| sort -u \t\t| xargs -r apt-mark manual \t; \tapt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \t\tredis-cli --version; \tredis-server --version; \t\techo '{\"spdxVersion\":\"SPDX-2.3\",\"SPDXID\":\"SPDXRef-DOCUMENT\",\"name\":\"redis-server-sbom\",\"packages\":[{\"name\":\"redis-server\",\"versionInfo\":\"7.2.3\",\"SPDXID\":\"SPDXRef-Package--redis-server\",\"externalRefs\":[{\"referenceCategory\":\"PACKAGE-MANAGER\",\"referenceType\":\"purl\",\"referenceLocator\":\"pkg:generic/redis-server@7.2.3?os_name=debian\u0026os_version=bookworm\"}],\"licenseDeclared\":\"BSD-3-Clause\"}]}' \u003e /usr/local/redis.spdx.json # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-04T22:45:52Z","created_by":"RUN /bin/sh -c mkdir /data \u0026\u0026 chown redis:redis /data # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-04T22:45:52Z","created_by":"VOLUME [/data]","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-12-04T22:45:52Z","created_by":"WORKDIR /data","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-04T22:45:52Z","created_by":"COPY docker-entrypoint.sh /usr/local/bin/ # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-04T22:45:52Z","created_by":"ENTRYPOINT [\"docker-entrypoint.sh\"]","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-12-04T22:45:52Z","created_by":"EXPOSE map[6379/tcp:{}]","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-12-04T22:45:52Z","created_by":"CMD [\"redis-server\"]","comment":"buildkit.dockerfile.v0","empty_layer":true}]}accelerated-container-image-1.4.2/ci/uconv_reproduce/redis/config-turbo.json000066400000000000000000000217371514714641000272230ustar00rootroot00000000000000{"created":"2023-12-04T22:45:52Z","architecture":"amd64","os":"linux","config":{"ExposedPorts":{"6379/tcp":{}},"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","GOSU_VERSION=1.17","REDIS_VERSION=7.2.3","REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-7.2.3.tar.gz","REDIS_DOWNLOAD_SHA=3e2b196d6eb4ddb9e743088bfc2915ccbb42d40f5a8a3edd8cb69c716ec34be7"],"Entrypoint":["docker-entrypoint.sh"],"Cmd":["redis-server"],"Volumes":{"/data":{}},"WorkingDir":"/data","ArgsEscaped":true},"rootfs":{"type":"layers","diff_ids":["sha256:e33f3ead13b687a680cf13d7f86a569702cf655a0589aafd55612a917d95a08a","sha256:13f488625abb91a45c88f21dc58484580fea79baae85e9592da689a43d506159","sha256:93d35df4e70eb8771c252e8d2a5bc87e53523dd824560907d765482df5eee86c","sha256:9ad9174f99570d22d6d8b397fb66bcc4741bac4fd15fd36cc7d7fa97b126725f","sha256:43d4f5ead817f143fea6f4d8e9d0577822fadc31849ec416b926d1c3cec3302c","sha256:c671e3b6205d2bb9b239f6a611759ba5f54355b14a7a8e1095206d618859304a","sha256:e3ff243dc4fb5bb31947e888af090e83293fa090a08d48f4b76832a97b81802a","sha256:befd5a810e293950b6492c898f3bb32510a453bbb962e6fc75a8f1708255849b"]},"history":[{"created":"2023-12-04T22:45:52Z","created_by":"/bin/sh -c #(nop) ADD file:ac3cd70031d35e46d86b876934946ffc8756de4de065fbc926dce642dac07ff3 in / "},{"created":"2023-12-04T22:45:52Z","created_by":"/bin/sh -c #(nop) CMD [\"bash\"]","empty_layer":true},{"created":"2023-12-04T22:45:52Z","created_by":"RUN /bin/sh -c set -eux; \tgroupadd -r -g 999 redis; \tuseradd -r -g redis -u 999 redis # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-04T22:45:52Z","created_by":"RUN /bin/sh -c set -eux; \tapt-get update; \tapt-get install -y --no-install-recommends \t\ttzdata \t; \trm -rf /var/lib/apt/lists/* # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-04T22:45:52Z","created_by":"ENV GOSU_VERSION=1.17","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-12-04T22:45:52Z","created_by":"RUN /bin/sh -c set -eux; \tsavedAptMark=\"$(apt-mark showmanual)\"; \tapt-get update; \tapt-get install -y --no-install-recommends ca-certificates gnupg wget; \trm -rf /var/lib/apt/lists/*; \tarch=\"$(dpkg --print-architecture | awk -F- '{ print $NF }')\"; \tcase \"$arch\" in \t\t'amd64') url='https://github.com/tianon/gosu/releases/download/1.17/gosu-amd64'; sha256='bbc4136d03ab138b1ad66fa4fc051bafc6cc7ffae632b069a53657279a450de3' ;; \t\t'arm64') url='https://github.com/tianon/gosu/releases/download/1.17/gosu-arm64'; sha256='c3805a85d17f4454c23d7059bcb97e1ec1af272b90126e79ed002342de08389b' ;; \t\t'armel') url='https://github.com/tianon/gosu/releases/download/1.17/gosu-armel'; sha256='f9969910fa141140438c998cfa02f603bf213b11afd466dcde8fa940e700945d' ;; \t\t'i386') url='https://github.com/tianon/gosu/releases/download/1.17/gosu-i386'; sha256='087dbb8fe479537e64f9c86fa49ff3b41dee1cbd28739a19aaef83dc8186b1ca' ;; \t\t'mips64el') url='https://github.com/tianon/gosu/releases/download/1.17/gosu-mips64el'; sha256='87140029d792595e660be0015341dfa1c02d1181459ae40df9f093e471d75b70' ;; \t\t'ppc64el') url='https://github.com/tianon/gosu/releases/download/1.17/gosu-ppc64el'; sha256='1891acdcfa70046818ab6ed3c52b9d42fa10fbb7b340eb429c8c7849691dbd76' ;; \t\t'riscv64') url='https://github.com/tianon/gosu/releases/download/1.17/gosu-riscv64'; sha256='38a6444b57adce135c42d5a3689f616fc7803ddc7a07ff6f946f2ebc67a26ba6' ;; \t\t's390x') url='https://github.com/tianon/gosu/releases/download/1.17/gosu-s390x'; sha256='69873bab588192f760547ca1f75b27cfcf106e9f7403fee6fd0600bc914979d0' ;; \t\t'armhf') url='https://github.com/tianon/gosu/releases/download/1.17/gosu-armhf'; sha256='e5866286277ff2a2159fb9196fea13e0a59d3f1091ea46ddb985160b94b6841b' ;; \t\t*) echo \u003e\u00262 \"error: unsupported gosu architecture: '$arch'\"; exit 1 ;; \tesac; \twget -O /usr/local/bin/gosu.asc \"$url.asc\"; \twget -O /usr/local/bin/gosu \"$url\"; \techo \"$sha256 */usr/local/bin/gosu\" | sha256sum -c -; \texport GNUPGHOME=\"$(mktemp -d)\"; \tgpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \tgpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \tgpgconf --kill all; \trm -rf \"$GNUPGHOME\" /usr/local/bin/gosu.asc; \tapt-mark auto '.*' \u003e /dev/null; \t[ -z \"$savedAptMark\" ] || apt-mark manual $savedAptMark \u003e /dev/null; \tapt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \tchmod +x /usr/local/bin/gosu; \tgosu --version; \tgosu nobody true # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-04T22:45:52Z","created_by":"ENV REDIS_VERSION=7.2.3","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-12-04T22:45:52Z","created_by":"ENV REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-7.2.3.tar.gz","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-12-04T22:45:52Z","created_by":"ENV REDIS_DOWNLOAD_SHA=3e2b196d6eb4ddb9e743088bfc2915ccbb42d40f5a8a3edd8cb69c716ec34be7","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-12-04T22:45:52Z","created_by":"RUN /bin/sh -c set -eux; \t\tsavedAptMark=\"$(apt-mark showmanual)\"; \tapt-get update; \tapt-get install -y --no-install-recommends \t\tca-certificates \t\twget \t\t\t\tdpkg-dev \t\tgcc \t\tlibc6-dev \t\tlibssl-dev \t\tmake \t; \trm -rf /var/lib/apt/lists/*; \t\twget -O redis.tar.gz \"$REDIS_DOWNLOAD_URL\"; \techo \"$REDIS_DOWNLOAD_SHA *redis.tar.gz\" | sha256sum -c -; \tmkdir -p /usr/src/redis; \ttar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1; \trm redis.tar.gz; \t\tgrep -E '^ *createBoolConfig[(]\"protected-mode\",.*, *1 *,.*[)],$' /usr/src/redis/src/config.c; \tsed -ri 's!^( *createBoolConfig[(]\"protected-mode\",.*, *)1( *,.*[)],)$!\\10\\2!' /usr/src/redis/src/config.c; \tgrep -E '^ *createBoolConfig[(]\"protected-mode\",.*, *0 *,.*[)],$' /usr/src/redis/src/config.c; \t\tgnuArch=\"$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)\"; \textraJemallocConfigureFlags=\"--build=$gnuArch\"; \tdpkgArch=\"$(dpkg --print-architecture)\"; \tcase \"${dpkgArch##*-}\" in \t\tamd64 | i386 | x32) extraJemallocConfigureFlags=\"$extraJemallocConfigureFlags --with-lg-page=12\" ;; \t\t*) extraJemallocConfigureFlags=\"$extraJemallocConfigureFlags --with-lg-page=16\" ;; \tesac; \textraJemallocConfigureFlags=\"$extraJemallocConfigureFlags --with-lg-hugepage=21\"; \tgrep -F 'cd jemalloc \u0026\u0026 ./configure ' /usr/src/redis/deps/Makefile; \tsed -ri 's!cd jemalloc \u0026\u0026 ./configure !\u0026'\"$extraJemallocConfigureFlags\"' !' /usr/src/redis/deps/Makefile; \tgrep -F \"cd jemalloc \u0026\u0026 ./configure $extraJemallocConfigureFlags \" /usr/src/redis/deps/Makefile; \t\texport BUILD_TLS=yes; \tmake -C /usr/src/redis -j \"$(nproc)\" all; \tmake -C /usr/src/redis install; \t\tserverMd5=\"$(md5sum /usr/local/bin/redis-server | cut -d' ' -f1)\"; export serverMd5; \tfind /usr/local/bin/redis* -maxdepth 0 \t\t-type f -not -name redis-server \t\t-exec sh -eux -c ' \t\t\tmd5=\"$(md5sum \"$1\" | cut -d\" \" -f1)\"; \t\t\ttest \"$md5\" = \"$serverMd5\"; \t\t' -- '{}' ';' \t\t-exec ln -svfT 'redis-server' '{}' ';' \t; \t\trm -r /usr/src/redis; \t\tapt-mark auto '.*' \u003e /dev/null; \t[ -z \"$savedAptMark\" ] || apt-mark manual $savedAptMark \u003e /dev/null; \tfind /usr/local -type f -executable -exec ldd '{}' ';' \t\t| awk '/=\u003e/ { so = $(NF-1); if (index(so, \"/usr/local/\") == 1) { next }; gsub(\"^/(usr/)?\", \"\", so); print so }' \t\t| sort -u \t\t| xargs -r dpkg-query --search \t\t| cut -d: -f1 \t\t| sort -u \t\t| xargs -r apt-mark manual \t; \tapt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \t\tredis-cli --version; \tredis-server --version; \t\techo '{\"spdxVersion\":\"SPDX-2.3\",\"SPDXID\":\"SPDXRef-DOCUMENT\",\"name\":\"redis-server-sbom\",\"packages\":[{\"name\":\"redis-server\",\"versionInfo\":\"7.2.3\",\"SPDXID\":\"SPDXRef-Package--redis-server\",\"externalRefs\":[{\"referenceCategory\":\"PACKAGE-MANAGER\",\"referenceType\":\"purl\",\"referenceLocator\":\"pkg:generic/redis-server@7.2.3?os_name=debian\u0026os_version=bookworm\"}],\"licenseDeclared\":\"BSD-3-Clause\"}]}' \u003e /usr/local/redis.spdx.json # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-04T22:45:52Z","created_by":"RUN /bin/sh -c mkdir /data \u0026\u0026 chown redis:redis /data # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-04T22:45:52Z","created_by":"VOLUME [/data]","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-12-04T22:45:52Z","created_by":"WORKDIR /data","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-04T22:45:52Z","created_by":"COPY docker-entrypoint.sh /usr/local/bin/ # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-04T22:45:52Z","created_by":"ENTRYPOINT [\"docker-entrypoint.sh\"]","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-12-04T22:45:52Z","created_by":"EXPOSE map[6379/tcp:{}]","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-12-04T22:45:52Z","created_by":"CMD [\"redis-server\"]","comment":"buildkit.dockerfile.v0","empty_layer":true}]}accelerated-container-image-1.4.2/ci/uconv_reproduce/redis/config.json000066400000000000000000000217141514714641000260650ustar00rootroot00000000000000{"created":"2023-12-04T22:45:52Z","architecture":"amd64","os":"linux","config":{"ExposedPorts":{"6379/tcp":{}},"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","GOSU_VERSION=1.17","REDIS_VERSION=7.2.3","REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-7.2.3.tar.gz","REDIS_DOWNLOAD_SHA=3e2b196d6eb4ddb9e743088bfc2915ccbb42d40f5a8a3edd8cb69c716ec34be7"],"Entrypoint":["docker-entrypoint.sh"],"Cmd":["redis-server"],"Volumes":{"/data":{}},"WorkingDir":"/data"},"rootfs":{"type":"layers","diff_ids":["sha256:a640fc493a1522f8e40b74dc77386011d1d10783d81750b8135e127b703d5d93","sha256:d5fdb13be1b316474cbf894ba53a4da8b93239216fb400f1cdef70e57928a7cf","sha256:ef2248d97da7998d4a3365a9fa6b04097320cbeaaaecac8863b68e9c490f0a73","sha256:050b8447b04f15dd4858fedc5220e38fbe16b82b6312a0d109057b874368704d","sha256:d344d9a63442f0140c8a52c8baf6d5dd3a5efafe17f892f9d64275252dda1170","sha256:1c64ce1a3bce82b75da173470b150f730856213039996d88ac1ed9ea3f25a713","sha256:9ed898bdae406cdc98afe2befd7d38996250add262d943c10deb758489de0fca","sha256:92e2b8b3a96c9b4d9960205dce53be761327c073cc67325590d176526136ad84"]},"history":[{"created":"2023-12-04T22:45:52Z","created_by":"/bin/sh -c #(nop) ADD file:ac3cd70031d35e46d86b876934946ffc8756de4de065fbc926dce642dac07ff3 in / "},{"created":"2023-12-04T22:45:52Z","created_by":"/bin/sh -c #(nop) CMD [\"bash\"]","empty_layer":true},{"created":"2023-12-04T22:45:52Z","created_by":"RUN /bin/sh -c set -eux; \tgroupadd -r -g 999 redis; \tuseradd -r -g redis -u 999 redis # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-04T22:45:52Z","created_by":"RUN /bin/sh -c set -eux; \tapt-get update; \tapt-get install -y --no-install-recommends \t\ttzdata \t; \trm -rf /var/lib/apt/lists/* # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-04T22:45:52Z","created_by":"ENV GOSU_VERSION=1.17","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-12-04T22:45:52Z","created_by":"RUN /bin/sh -c set -eux; \tsavedAptMark=\"$(apt-mark showmanual)\"; \tapt-get update; \tapt-get install -y --no-install-recommends ca-certificates gnupg wget; \trm -rf /var/lib/apt/lists/*; \tarch=\"$(dpkg --print-architecture | awk -F- '{ print $NF }')\"; \tcase \"$arch\" in \t\t'amd64') url='https://github.com/tianon/gosu/releases/download/1.17/gosu-amd64'; sha256='bbc4136d03ab138b1ad66fa4fc051bafc6cc7ffae632b069a53657279a450de3' ;; \t\t'arm64') url='https://github.com/tianon/gosu/releases/download/1.17/gosu-arm64'; sha256='c3805a85d17f4454c23d7059bcb97e1ec1af272b90126e79ed002342de08389b' ;; \t\t'armel') url='https://github.com/tianon/gosu/releases/download/1.17/gosu-armel'; sha256='f9969910fa141140438c998cfa02f603bf213b11afd466dcde8fa940e700945d' ;; \t\t'i386') url='https://github.com/tianon/gosu/releases/download/1.17/gosu-i386'; sha256='087dbb8fe479537e64f9c86fa49ff3b41dee1cbd28739a19aaef83dc8186b1ca' ;; \t\t'mips64el') url='https://github.com/tianon/gosu/releases/download/1.17/gosu-mips64el'; sha256='87140029d792595e660be0015341dfa1c02d1181459ae40df9f093e471d75b70' ;; \t\t'ppc64el') url='https://github.com/tianon/gosu/releases/download/1.17/gosu-ppc64el'; sha256='1891acdcfa70046818ab6ed3c52b9d42fa10fbb7b340eb429c8c7849691dbd76' ;; \t\t'riscv64') url='https://github.com/tianon/gosu/releases/download/1.17/gosu-riscv64'; sha256='38a6444b57adce135c42d5a3689f616fc7803ddc7a07ff6f946f2ebc67a26ba6' ;; \t\t's390x') url='https://github.com/tianon/gosu/releases/download/1.17/gosu-s390x'; sha256='69873bab588192f760547ca1f75b27cfcf106e9f7403fee6fd0600bc914979d0' ;; \t\t'armhf') url='https://github.com/tianon/gosu/releases/download/1.17/gosu-armhf'; sha256='e5866286277ff2a2159fb9196fea13e0a59d3f1091ea46ddb985160b94b6841b' ;; \t\t*) echo \u003e\u00262 \"error: unsupported gosu architecture: '$arch'\"; exit 1 ;; \tesac; \twget -O /usr/local/bin/gosu.asc \"$url.asc\"; \twget -O /usr/local/bin/gosu \"$url\"; \techo \"$sha256 */usr/local/bin/gosu\" | sha256sum -c -; \texport GNUPGHOME=\"$(mktemp -d)\"; \tgpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \tgpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \tgpgconf --kill all; \trm -rf \"$GNUPGHOME\" /usr/local/bin/gosu.asc; \tapt-mark auto '.*' \u003e /dev/null; \t[ -z \"$savedAptMark\" ] || apt-mark manual $savedAptMark \u003e /dev/null; \tapt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \tchmod +x /usr/local/bin/gosu; \tgosu --version; \tgosu nobody true # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-04T22:45:52Z","created_by":"ENV REDIS_VERSION=7.2.3","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-12-04T22:45:52Z","created_by":"ENV REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-7.2.3.tar.gz","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-12-04T22:45:52Z","created_by":"ENV REDIS_DOWNLOAD_SHA=3e2b196d6eb4ddb9e743088bfc2915ccbb42d40f5a8a3edd8cb69c716ec34be7","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-12-04T22:45:52Z","created_by":"RUN /bin/sh -c set -eux; \t\tsavedAptMark=\"$(apt-mark showmanual)\"; \tapt-get update; \tapt-get install -y --no-install-recommends \t\tca-certificates \t\twget \t\t\t\tdpkg-dev \t\tgcc \t\tlibc6-dev \t\tlibssl-dev \t\tmake \t; \trm -rf /var/lib/apt/lists/*; \t\twget -O redis.tar.gz \"$REDIS_DOWNLOAD_URL\"; \techo \"$REDIS_DOWNLOAD_SHA *redis.tar.gz\" | sha256sum -c -; \tmkdir -p /usr/src/redis; \ttar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1; \trm redis.tar.gz; \t\tgrep -E '^ *createBoolConfig[(]\"protected-mode\",.*, *1 *,.*[)],$' /usr/src/redis/src/config.c; \tsed -ri 's!^( *createBoolConfig[(]\"protected-mode\",.*, *)1( *,.*[)],)$!\\10\\2!' /usr/src/redis/src/config.c; \tgrep -E '^ *createBoolConfig[(]\"protected-mode\",.*, *0 *,.*[)],$' /usr/src/redis/src/config.c; \t\tgnuArch=\"$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)\"; \textraJemallocConfigureFlags=\"--build=$gnuArch\"; \tdpkgArch=\"$(dpkg --print-architecture)\"; \tcase \"${dpkgArch##*-}\" in \t\tamd64 | i386 | x32) extraJemallocConfigureFlags=\"$extraJemallocConfigureFlags --with-lg-page=12\" ;; \t\t*) extraJemallocConfigureFlags=\"$extraJemallocConfigureFlags --with-lg-page=16\" ;; \tesac; \textraJemallocConfigureFlags=\"$extraJemallocConfigureFlags --with-lg-hugepage=21\"; \tgrep -F 'cd jemalloc \u0026\u0026 ./configure ' /usr/src/redis/deps/Makefile; \tsed -ri 's!cd jemalloc \u0026\u0026 ./configure !\u0026'\"$extraJemallocConfigureFlags\"' !' /usr/src/redis/deps/Makefile; \tgrep -F \"cd jemalloc \u0026\u0026 ./configure $extraJemallocConfigureFlags \" /usr/src/redis/deps/Makefile; \t\texport BUILD_TLS=yes; \tmake -C /usr/src/redis -j \"$(nproc)\" all; \tmake -C /usr/src/redis install; \t\tserverMd5=\"$(md5sum /usr/local/bin/redis-server | cut -d' ' -f1)\"; export serverMd5; \tfind /usr/local/bin/redis* -maxdepth 0 \t\t-type f -not -name redis-server \t\t-exec sh -eux -c ' \t\t\tmd5=\"$(md5sum \"$1\" | cut -d\" \" -f1)\"; \t\t\ttest \"$md5\" = \"$serverMd5\"; \t\t' -- '{}' ';' \t\t-exec ln -svfT 'redis-server' '{}' ';' \t; \t\trm -r /usr/src/redis; \t\tapt-mark auto '.*' \u003e /dev/null; \t[ -z \"$savedAptMark\" ] || apt-mark manual $savedAptMark \u003e /dev/null; \tfind /usr/local -type f -executable -exec ldd '{}' ';' \t\t| awk '/=\u003e/ { so = $(NF-1); if (index(so, \"/usr/local/\") == 1) { next }; gsub(\"^/(usr/)?\", \"\", so); print so }' \t\t| sort -u \t\t| xargs -r dpkg-query --search \t\t| cut -d: -f1 \t\t| sort -u \t\t| xargs -r apt-mark manual \t; \tapt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \t\tredis-cli --version; \tredis-server --version; \t\techo '{\"spdxVersion\":\"SPDX-2.3\",\"SPDXID\":\"SPDXRef-DOCUMENT\",\"name\":\"redis-server-sbom\",\"packages\":[{\"name\":\"redis-server\",\"versionInfo\":\"7.2.3\",\"SPDXID\":\"SPDXRef-Package--redis-server\",\"externalRefs\":[{\"referenceCategory\":\"PACKAGE-MANAGER\",\"referenceType\":\"purl\",\"referenceLocator\":\"pkg:generic/redis-server@7.2.3?os_name=debian\u0026os_version=bookworm\"}],\"licenseDeclared\":\"BSD-3-Clause\"}]}' \u003e /usr/local/redis.spdx.json # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-04T22:45:52Z","created_by":"RUN /bin/sh -c mkdir /data \u0026\u0026 chown redis:redis /data # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-04T22:45:52Z","created_by":"VOLUME [/data]","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-12-04T22:45:52Z","created_by":"WORKDIR /data","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-04T22:45:52Z","created_by":"COPY docker-entrypoint.sh /usr/local/bin/ # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-04T22:45:52Z","created_by":"ENTRYPOINT [\"docker-entrypoint.sh\"]","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-12-04T22:45:52Z","created_by":"EXPOSE map[6379/tcp:{}]","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-12-04T22:45:52Z","created_by":"CMD [\"redis-server\"]","comment":"buildkit.dockerfile.v0","empty_layer":true}]}accelerated-container-image-1.4.2/ci/uconv_reproduce/redis/manifest-turbo-erofs.json000066400000000000000000000126701514714641000306740ustar00rootroot00000000000000{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:b49d47b3ce281c40b3aca59497abd2e128e4d71c61ad363c007fcd4255a75197","size":9183},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:8669c0b06bac9a3b6437647361cc0fed70a500744e513bdbb4088c71db0c4c01","size":1104210,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:8669c0b06bac9a3b6437647361cc0fed70a500744e513bdbb4088c71db0c4c01","containerd.io/snapshot/overlaybd/blob-size":"1104210","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:af107e978371b6cd6339127a05502c5eacd1e6b0e9eb7b2f4aa7b6fc87e2dd81","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:2ec983482b74a1dec8fa90f4db1e38bf531c670eec5a4c0ecdfabaee05b62de9","size":4244,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:2ec983482b74a1dec8fa90f4db1e38bf531c670eec5a4c0ecdfabaee05b62de9","containerd.io/snapshot/overlaybd/blob-size":"4244","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:b031def5f2c4a8515f6b4e609686a0a9846d8a4308bf6c1d9f41b0bb627275f1","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:ae62057af30df44d69aeafdf6acbc91233a30fec6e6b220381d668afd307324d","size":2690,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:ae62057af30df44d69aeafdf6acbc91233a30fec6e6b220381d668afd307324d","containerd.io/snapshot/overlaybd/blob-size":"2690","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:bf7f0c8796d3701fef70b4099c3302f3a9e017cc615adf5046f95460fd4b921b","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:e96c198a15d3645b8b8670f6de8ae15b22821c1ceeae97e54bc3661b0dff4e5a","size":46065,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:e96c198a15d3645b8b8670f6de8ae15b22821c1ceeae97e54bc3661b0dff4e5a","containerd.io/snapshot/overlaybd/blob-size":"46065","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:e3b2691a4104bbe54bf413a8ae7b8b442d8fc00af86067a4174261f050cab9a5","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:d9385f00ddc8d932b6016ec11ffb521e8253f6388c864298a5f09aa022cb9a90","size":798524,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:d9385f00ddc8d932b6016ec11ffb521e8253f6388c864298a5f09aa022cb9a90","containerd.io/snapshot/overlaybd/blob-size":"798524","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:190b4d7a237a66227f26efb851e9731674003d6337dc61e7f3a41aabd8eaf479","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:6d382dfc0fb109b9a78c1c7958541d5d4f66868557f91b4bc17bf2bdf8a9f6d5","size":962,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:6d382dfc0fb109b9a78c1c7958541d5d4f66868557f91b4bc17bf2bdf8a9f6d5","containerd.io/snapshot/overlaybd/blob-size":"962","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:797591c7970a3db095434a6fd0673ae80c40f814da1c1b39787faf63b8dee51d","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:773122214cac50c09ff5d1697d9626146def05170f9444aa2e52445b30762ff1","size":914,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:773122214cac50c09ff5d1697d9626146def05170f9444aa2e52445b30762ff1","containerd.io/snapshot/overlaybd/blob-size":"914","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:9512ea073a9b05034fbfd9b19cc2d4644de29f841420dd96fd74591c3315cf82","size":7927,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:9512ea073a9b05034fbfd9b19cc2d4644de29f841420dd96fd74591c3315cf82","containerd.io/snapshot/overlaybd/blob-size":"7927","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:45ce3854ac9a2a8cebfac64e81e22a7047de8acc8a255ac6979272bb91eb645a","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}}]}accelerated-container-image-1.4.2/ci/uconv_reproduce/redis/manifest-turbo.json000066400000000000000000000126741514714641000275640ustar00rootroot00000000000000{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:1d48af0d36137d2affe73331c9eaea339a1df634743d006461a71645d8408d6a","size":9183},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:93d5e7762541cd51f8e8dceea4978516596aa3dd837978334b60f0f8c03ca7b0","size":1124551,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:93d5e7762541cd51f8e8dceea4978516596aa3dd837978334b60f0f8c03ca7b0","containerd.io/snapshot/overlaybd/blob-size":"1124551","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:af107e978371b6cd6339127a05502c5eacd1e6b0e9eb7b2f4aa7b6fc87e2dd81","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:08a96e7b2e9fc7da05e3de321a2c867a83c4d369b8be0f54e3d4a77b16fa6d89","size":6894,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:08a96e7b2e9fc7da05e3de321a2c867a83c4d369b8be0f54e3d4a77b16fa6d89","containerd.io/snapshot/overlaybd/blob-size":"6894","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:b031def5f2c4a8515f6b4e609686a0a9846d8a4308bf6c1d9f41b0bb627275f1","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:19ede912d35b4fbd5acab481830acd652cf1cf24f7850ed620cea134bd6d40a7","size":6109,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:19ede912d35b4fbd5acab481830acd652cf1cf24f7850ed620cea134bd6d40a7","containerd.io/snapshot/overlaybd/blob-size":"6109","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:bf7f0c8796d3701fef70b4099c3302f3a9e017cc615adf5046f95460fd4b921b","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:b7b8b7b4fd94dbbd6a855b7708050f2d392c4cbdbdd329e9394a1e1ca2325425","size":42292,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:b7b8b7b4fd94dbbd6a855b7708050f2d392c4cbdbdd329e9394a1e1ca2325425","containerd.io/snapshot/overlaybd/blob-size":"42292","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:e3b2691a4104bbe54bf413a8ae7b8b442d8fc00af86067a4174261f050cab9a5","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:f0e5dc901d12cf334a845d63fc9b44cb39fd6b1df2f2e847842929b8f7e09419","size":791631,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:f0e5dc901d12cf334a845d63fc9b44cb39fd6b1df2f2e847842929b8f7e09419","containerd.io/snapshot/overlaybd/blob-size":"791631","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:190b4d7a237a66227f26efb851e9731674003d6337dc61e7f3a41aabd8eaf479","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:3786922236f43eeabee3800d6bf106373734b768a9d1e612b024b8a75ff23769","size":5646,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:3786922236f43eeabee3800d6bf106373734b768a9d1e612b024b8a75ff23769","containerd.io/snapshot/overlaybd/blob-size":"5646","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:797591c7970a3db095434a6fd0673ae80c40f814da1c1b39787faf63b8dee51d","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:f6ce1c26c083a0719998a1b7dc1082191c16af6888394e011bbe205eba13af33","size":3613,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:f6ce1c26c083a0719998a1b7dc1082191c16af6888394e011bbe205eba13af33","containerd.io/snapshot/overlaybd/blob-size":"3613","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:af8f4fac91e770e200f53474175863f0b7fd62a1b5ca85e0cc2a815b2cf7cb41","size":5778,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:af8f4fac91e770e200f53474175863f0b7fd62a1b5ca85e0cc2a815b2cf7cb41","containerd.io/snapshot/overlaybd/blob-size":"5778","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:45ce3854ac9a2a8cebfac64e81e22a7047de8acc8a255ac6979272bb91eb645a","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}}]}accelerated-container-image-1.4.2/ci/uconv_reproduce/redis/manifest.json000066400000000000000000000066201514714641000264250ustar00rootroot00000000000000{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:033843c467845ea16b10160341f001495b1f8c7ce8587c6cf02fac96a14c8ba3","size":9164},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:a640fc493a1522f8e40b74dc77386011d1d10783d81750b8135e127b703d5d93","size":46912000,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:a640fc493a1522f8e40b74dc77386011d1d10783d81750b8135e127b703d5d93","containerd.io/snapshot/overlaybd/blob-size":"46912000","containerd.io/snapshot/overlaybd/version":"0.1.0"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:d5fdb13be1b316474cbf894ba53a4da8b93239216fb400f1cdef70e57928a7cf","size":623616,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:d5fdb13be1b316474cbf894ba53a4da8b93239216fb400f1cdef70e57928a7cf","containerd.io/snapshot/overlaybd/blob-size":"623616","containerd.io/snapshot/overlaybd/version":"0.1.0"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:ef2248d97da7998d4a3365a9fa6b04097320cbeaaaecac8863b68e9c490f0a73","size":721408,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:ef2248d97da7998d4a3365a9fa6b04097320cbeaaaecac8863b68e9c490f0a73","containerd.io/snapshot/overlaybd/blob-size":"721408","containerd.io/snapshot/overlaybd/version":"0.1.0"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:050b8447b04f15dd4858fedc5220e38fbe16b82b6312a0d109057b874368704d","size":3398144,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:050b8447b04f15dd4858fedc5220e38fbe16b82b6312a0d109057b874368704d","containerd.io/snapshot/overlaybd/blob-size":"3398144","containerd.io/snapshot/overlaybd/version":"0.1.0"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:d344d9a63442f0140c8a52c8baf6d5dd3a5efafe17f892f9d64275252dda1170","size":36371968,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:d344d9a63442f0140c8a52c8baf6d5dd3a5efafe17f892f9d64275252dda1170","containerd.io/snapshot/overlaybd/blob-size":"36371968","containerd.io/snapshot/overlaybd/version":"0.1.0"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:1c64ce1a3bce82b75da173470b150f730856213039996d88ac1ed9ea3f25a713","size":611840,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:1c64ce1a3bce82b75da173470b150f730856213039996d88ac1ed9ea3f25a713","containerd.io/snapshot/overlaybd/blob-size":"611840","containerd.io/snapshot/overlaybd/version":"0.1.0"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:9ed898bdae406cdc98afe2befd7d38996250add262d943c10deb758489de0fca","size":11264,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:9ed898bdae406cdc98afe2befd7d38996250add262d943c10deb758489de0fca","containerd.io/snapshot/overlaybd/blob-size":"11264","containerd.io/snapshot/overlaybd/version":"0.1.0"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:92e2b8b3a96c9b4d9960205dce53be761327c073cc67325590d176526136ad84","size":649728,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:92e2b8b3a96c9b4d9960205dce53be761327c073cc67325590d176526136ad84","containerd.io/snapshot/overlaybd/blob-size":"649728","containerd.io/snapshot/overlaybd/version":"0.1.0"}}]}accelerated-container-image-1.4.2/ci/uconv_reproduce/ubuntu/000077500000000000000000000000001514714641000241345ustar00rootroot00000000000000accelerated-container-image-1.4.2/ci/uconv_reproduce/ubuntu/config-turbo-erofs.json000066400000000000000000000022711514714641000305430ustar00rootroot00000000000000{"created":"2023-12-12T11:38:59.637410824Z","architecture":"amd64","os":"linux","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/bash"],"Labels":{"org.opencontainers.image.ref.name":"ubuntu","org.opencontainers.image.version":"22.04"}},"rootfs":{"type":"layers","diff_ids":["sha256:e3003c99ec7721c169b49f3eca6553241b29248c75ee9a109ef076bd5098881c"]},"history":[{"created":"2023-12-12T11:38:57.155589925Z","created_by":"/bin/sh -c #(nop) ARG RELEASE","empty_layer":true},{"created":"2023-12-12T11:38:57.224861025Z","created_by":"/bin/sh -c #(nop) ARG LAUNCHPAD_BUILD_ARCH","empty_layer":true},{"created":"2023-12-12T11:38:57.286311609Z","created_by":"/bin/sh -c #(nop) LABEL org.opencontainers.image.ref.name=ubuntu","empty_layer":true},{"created":"2023-12-12T11:38:57.352164165Z","created_by":"/bin/sh -c #(nop) LABEL org.opencontainers.image.version=22.04","empty_layer":true},{"created":"2023-12-12T11:38:59.296940147Z","created_by":"/bin/sh -c #(nop) ADD file:2b3b5254f38a790d40e31cb26155609f7fc99ef7bc99eae1e0d67fa9ae605f77 in / "},{"created":"2023-12-12T11:38:59.637410824Z","created_by":"/bin/sh -c #(nop) CMD [\"/bin/bash\"]","empty_layer":true}]}accelerated-container-image-1.4.2/ci/uconv_reproduce/ubuntu/config-turbo.json000066400000000000000000000022711514714641000274270ustar00rootroot00000000000000{"created":"2023-12-12T11:38:59.637410824Z","architecture":"amd64","os":"linux","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/bash"],"Labels":{"org.opencontainers.image.ref.name":"ubuntu","org.opencontainers.image.version":"22.04"}},"rootfs":{"type":"layers","diff_ids":["sha256:a180e8516e898cd26937f5505319704dba6fa7358116a0e2ae23996fd99dbe60"]},"history":[{"created":"2023-12-12T11:38:57.155589925Z","created_by":"/bin/sh -c #(nop) ARG RELEASE","empty_layer":true},{"created":"2023-12-12T11:38:57.224861025Z","created_by":"/bin/sh -c #(nop) ARG LAUNCHPAD_BUILD_ARCH","empty_layer":true},{"created":"2023-12-12T11:38:57.286311609Z","created_by":"/bin/sh -c #(nop) LABEL org.opencontainers.image.ref.name=ubuntu","empty_layer":true},{"created":"2023-12-12T11:38:57.352164165Z","created_by":"/bin/sh -c #(nop) LABEL org.opencontainers.image.version=22.04","empty_layer":true},{"created":"2023-12-12T11:38:59.296940147Z","created_by":"/bin/sh -c #(nop) ADD file:2b3b5254f38a790d40e31cb26155609f7fc99ef7bc99eae1e0d67fa9ae605f77 in / "},{"created":"2023-12-12T11:38:59.637410824Z","created_by":"/bin/sh -c #(nop) CMD [\"/bin/bash\"]","empty_layer":true}]}accelerated-container-image-1.4.2/ci/uconv_reproduce/ubuntu/config.json000066400000000000000000000022711514714641000262760ustar00rootroot00000000000000{"created":"2023-12-12T11:38:59.637410824Z","architecture":"amd64","os":"linux","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/bash"],"Labels":{"org.opencontainers.image.ref.name":"ubuntu","org.opencontainers.image.version":"22.04"}},"rootfs":{"type":"layers","diff_ids":["sha256:0db3bd62690a7520aa03c2ab1bfae2675dccf8bff172ced5d9bc9ddfba853218"]},"history":[{"created":"2023-12-12T11:38:57.155589925Z","created_by":"/bin/sh -c #(nop) ARG RELEASE","empty_layer":true},{"created":"2023-12-12T11:38:57.224861025Z","created_by":"/bin/sh -c #(nop) ARG LAUNCHPAD_BUILD_ARCH","empty_layer":true},{"created":"2023-12-12T11:38:57.286311609Z","created_by":"/bin/sh -c #(nop) LABEL org.opencontainers.image.ref.name=ubuntu","empty_layer":true},{"created":"2023-12-12T11:38:57.352164165Z","created_by":"/bin/sh -c #(nop) LABEL org.opencontainers.image.version=22.04","empty_layer":true},{"created":"2023-12-12T11:38:59.296940147Z","created_by":"/bin/sh -c #(nop) ADD file:2b3b5254f38a790d40e31cb26155609f7fc99ef7bc99eae1e0d67fa9ae605f77 in / "},{"created":"2023-12-12T11:38:59.637410824Z","created_by":"/bin/sh -c #(nop) CMD [\"/bin/bash\"]","empty_layer":true}]}accelerated-container-image-1.4.2/ci/uconv_reproduce/ubuntu/manifest-turbo-erofs.json000066400000000000000000000016431514714641000311060ustar00rootroot00000000000000{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:e0e2028c2ab1f93e3075c5cae78048e57b075c0164fc8739ea466309b3e2f364","size":1209},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:9b411d80d0ffc26639d34eacee6851fd194f009ecc2ed15b27346a8ef0271e17","size":1129359,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:9b411d80d0ffc26639d34eacee6851fd194f009ecc2ed15b27346a8ef0271e17","containerd.io/snapshot/overlaybd/blob-size":"1129359","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:a486411936734b0d1d201c8a0ed8e9d449a64d5033fdc33411ec95bc26460efb","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}}]}accelerated-container-image-1.4.2/ci/uconv_reproduce/ubuntu/manifest-turbo.json000066400000000000000000000016431514714641000277720ustar00rootroot00000000000000{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:518b8bf0ebc03e60ac5df4527f55621eef2617fd4b858dccaf4e178ba03c0f11","size":1209},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:604c4d33c8640204c5e6d28a7cd3211e1f34e756153a7dd939e00fc505b406c1","size":1152556,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:604c4d33c8640204c5e6d28a7cd3211e1f34e756153a7dd939e00fc505b406c1","containerd.io/snapshot/overlaybd/blob-size":"1152556","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:a486411936734b0d1d201c8a0ed8e9d449a64d5033fdc33411ec95bc26460efb","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}}]}accelerated-container-image-1.4.2/ci/uconv_reproduce/ubuntu/manifest.json000066400000000000000000000012341514714641000266350ustar00rootroot00000000000000{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:4271488200a968d57a8198b11576506bb294e6a52cb0d98a2841b7ea276537c7","size":1209},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:0db3bd62690a7520aa03c2ab1bfae2675dccf8bff172ced5d9bc9ddfba853218","size":48217600,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:0db3bd62690a7520aa03c2ab1bfae2675dccf8bff172ced5d9bc9ddfba853218","containerd.io/snapshot/overlaybd/blob-size":"48217600","containerd.io/snapshot/overlaybd/version":"0.1.0"}}]}accelerated-container-image-1.4.2/ci/uconv_reproduce/wordpress/000077500000000000000000000000001514714641000246425ustar00rootroot00000000000000accelerated-container-image-1.4.2/ci/uconv_reproduce/wordpress/config-turbo-erofs.json000066400000000000000000000403341514714641000312530ustar00rootroot00000000000000{"created":"2023-12-06T20:31:30Z","architecture":"amd64","os":"linux","config":{"ExposedPorts":{"80/tcp":{}},"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","PHPIZE_DEPS=autoconf \t\tdpkg-dev \t\tfile \t\tg++ \t\tgcc \t\tlibc-dev \t\tmake \t\tpkg-config \t\tre2c","PHP_INI_DIR=/usr/local/etc/php","APACHE_CONFDIR=/etc/apache2","APACHE_ENVVARS=/etc/apache2/envvars","PHP_CFLAGS=-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64","PHP_CPPFLAGS=-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64","PHP_LDFLAGS=-Wl,-O1 -pie","GPG_KEYS=39B641343D8C104B2B146DC3F9C39DC0B9698544 E60913E4DF209907D8E30D96659A97C9CF2A795A 1198C0117593497A5EC5C199286AF1F9897469DC","PHP_VERSION=8.2.13","PHP_URL=https://www.php.net/distributions/php-8.2.13.tar.xz","PHP_ASC_URL=https://www.php.net/distributions/php-8.2.13.tar.xz.asc","PHP_SHA256=2629bba10117bf78912068a230c68a8fd09b7740267bd8ebd3cfce91515d454b"],"Entrypoint":["docker-entrypoint.sh"],"Cmd":["apache2-foreground"],"Volumes":{"/var/www/html":{}},"WorkingDir":"/var/www/html","StopSignal":"SIGWINCH","ArgsEscaped":true},"rootfs":{"type":"layers","diff_ids":["sha256:abb8474cd5ee7c0199099cb2f09b7b5039cae70c74042f6809059bbaca34d94a","sha256:70df7065c9dee3cd8711452e1e86dc69d41a3a83d9f4ea95042921b054123c57","sha256:374299137f35668107aa96b7941d176575feac8dd547c9c4281bed44d6909408","sha256:b44048a19e734dd121bf31db94009f9a8eae942a466fe962149f44028d9770ce","sha256:2e43229fdb623c57a02229cf5bd8696585c09ef3b274c6b10ddc902fa81e7e61","sha256:ae163160a6f7da124c4fac43b3f1c850d538bd51a66458b735074f391f302a8b","sha256:18c738f34cd17e5400f434d7743ef53fdef4313fc5ef827b221d916fad18b259","sha256:1c5d96232a33dd33f0b8c2135038de364ba7f5da399ce63cf6d54635f712d630","sha256:2b22cf28e0cf7a2a7de6d1e47160c48e98e81601beb63ac18e085e6b3dac3387","sha256:71309229adad75f937d506057917ea911d89233cd172ffac2dc095ebf81211ad","sha256:ba03a3d963c4d22eaa7847a91e1011ffb3ee7f89dbcd60d502407a1ac5799ebd","sha256:131ef3fd7a86eda45204ee1deafe5638cd4e9a674c76bceb8a2b033d4c4c42d5","sha256:ee68ba1e34df8e7b926367bca3d83fd5e197626eac91d5aa78af7f53a35e2b3b","sha256:697b5d02e69ddd8335ea0aad07da8b552bb94e59f5917fddb906bcdb7b41a516","sha256:eafb5227ecf9d3a46bdb9bfbe8b8e69cff15b619790015b30601bc67fec0d5a7","sha256:86113c85556d6e2b6090f82c9ef8a0ecbbb694267e2c5c710631a7eebfc09f1e","sha256:add27ad021ec5e07c03e2dba6bf1a60467f587767d98164b3eff8951308a725e","sha256:69e9b130412f092e71b7dead12756aa2054214ea44fd3864add89da6ad642b31","sha256:782f645b19db473fe247e44c1e6a587f3d3dce0438fbaef431332780646cac6b","sha256:abce0e3caa425be28ba369ef855c457de9a05afb3081741f72c78fd8213cba99","sha256:e4c1a32a7f367045d4dc4168c60680045619cb21d0e99107d49fe00db266abdf"]},"history":[{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ADD file:ac3cd70031d35e46d86b876934946ffc8756de4de065fbc926dce642dac07ff3 in / "},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) CMD [\"bash\"]","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c set -eux; \t{ \t\techo 'Package: php*'; \t\techo 'Pin: release *'; \t\techo 'Pin-Priority: -1'; \t} \u003e /etc/apt/preferences.d/no-debian-php"},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENV PHPIZE_DEPS=autoconf \t\tdpkg-dev \t\tfile \t\tg++ \t\tgcc \t\tlibc-dev \t\tmake \t\tpkg-config \t\tre2c","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c set -eux; \tapt-get update; \tapt-get install -y --no-install-recommends \t\t$PHPIZE_DEPS \t\tca-certificates \t\tcurl \t\txz-utils \t; \trm -rf /var/lib/apt/lists/*"},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENV PHP_INI_DIR=/usr/local/etc/php","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c set -eux; \tmkdir -p \"$PHP_INI_DIR/conf.d\"; \t[ ! -d /var/www/html ]; \tmkdir -p /var/www/html; \tchown www-data:www-data /var/www/html; \tchmod 1777 /var/www/html"},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENV APACHE_CONFDIR=/etc/apache2","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENV APACHE_ENVVARS=/etc/apache2/envvars","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c set -eux; \tapt-get update; \tapt-get install -y --no-install-recommends apache2; \trm -rf /var/lib/apt/lists/*; \t\tsed -ri 's/^export ([^=]+)=(.*)$/: ${\\1:=\\2}\\nexport \\1/' \"$APACHE_ENVVARS\"; \t\t. \"$APACHE_ENVVARS\"; \tfor dir in \t\t\"$APACHE_LOCK_DIR\" \t\t\"$APACHE_RUN_DIR\" \t\t\"$APACHE_LOG_DIR\" \t\t\"$APACHE_RUN_DIR/socks\" \t; do \t\trm -rvf \"$dir\"; \t\tmkdir -p \"$dir\"; \t\tchown \"$APACHE_RUN_USER:$APACHE_RUN_GROUP\" \"$dir\"; \t\tchmod 1777 \"$dir\"; \tdone; \t\trm -rvf /var/www/html/*; \t\tln -sfT /dev/stderr \"$APACHE_LOG_DIR/error.log\"; \tln -sfT /dev/stdout \"$APACHE_LOG_DIR/access.log\"; \tln -sfT /dev/stdout \"$APACHE_LOG_DIR/other_vhosts_access.log\"; \tchown -R --no-dereference \"$APACHE_RUN_USER:$APACHE_RUN_GROUP\" \"$APACHE_LOG_DIR\""},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c a2dismod mpm_event \u0026\u0026 a2enmod mpm_prefork"},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c { \t\techo '\u003cFilesMatch \\.php$\u003e'; \t\techo '\\tSetHandler application/x-httpd-php'; \t\techo '\u003c/FilesMatch\u003e'; \t\techo; \t\techo 'DirectoryIndex disabled'; \t\techo 'DirectoryIndex index.php index.html'; \t\techo; \t\techo '\u003cDirectory /var/www/\u003e'; \t\techo '\\tOptions -Indexes'; \t\techo '\\tAllowOverride All'; \t\techo '\u003c/Directory\u003e'; \t} | tee \"$APACHE_CONFDIR/conf-available/docker-php.conf\" \t\u0026\u0026 a2enconf docker-php"},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENV PHP_CFLAGS=-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENV PHP_CPPFLAGS=-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENV PHP_LDFLAGS=-Wl,-O1 -pie","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENV GPG_KEYS=39B641343D8C104B2B146DC3F9C39DC0B9698544 E60913E4DF209907D8E30D96659A97C9CF2A795A 1198C0117593497A5EC5C199286AF1F9897469DC","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENV PHP_VERSION=8.2.13","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENV PHP_URL=https://www.php.net/distributions/php-8.2.13.tar.xz PHP_ASC_URL=https://www.php.net/distributions/php-8.2.13.tar.xz.asc","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENV PHP_SHA256=2629bba10117bf78912068a230c68a8fd09b7740267bd8ebd3cfce91515d454b","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c set -eux; \t\tsavedAptMark=\"$(apt-mark showmanual)\"; \tapt-get update; \tapt-get install -y --no-install-recommends gnupg; \trm -rf /var/lib/apt/lists/*; \t\tmkdir -p /usr/src; \tcd /usr/src; \t\tcurl -fsSL -o php.tar.xz \"$PHP_URL\"; \t\tif [ -n \"$PHP_SHA256\" ]; then \t\techo \"$PHP_SHA256 *php.tar.xz\" | sha256sum -c -; \tfi; \t\tif [ -n \"$PHP_ASC_URL\" ]; then \t\tcurl -fsSL -o php.tar.xz.asc \"$PHP_ASC_URL\"; \t\texport GNUPGHOME=\"$(mktemp -d)\"; \t\tfor key in $GPG_KEYS; do \t\t\tgpg --batch --keyserver keyserver.ubuntu.com --recv-keys \"$key\"; \t\tdone; \t\tgpg --batch --verify php.tar.xz.asc php.tar.xz; \t\tgpgconf --kill all; \t\trm -rf \"$GNUPGHOME\"; \tfi; \t\tapt-mark auto '.*' \u003e /dev/null; \tapt-mark manual $savedAptMark \u003e /dev/null; \tapt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false"},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) COPY file:ce57c04b70896f77cc11eb2766417d8a1240fcffe5bba92179ec78c458844110 in /usr/local/bin/ "},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c set -eux; \t\tsavedAptMark=\"$(apt-mark showmanual)\"; \tapt-get update; \tapt-get install -y --no-install-recommends \t\tapache2-dev \t\tlibargon2-dev \t\tlibcurl4-openssl-dev \t\tlibonig-dev \t\tlibreadline-dev \t\tlibsodium-dev \t\tlibsqlite3-dev \t\tlibssl-dev \t\tlibxml2-dev \t\tzlib1g-dev \t; \t\texport \t\tCFLAGS=\"$PHP_CFLAGS\" \t\tCPPFLAGS=\"$PHP_CPPFLAGS\" \t\tLDFLAGS=\"$PHP_LDFLAGS\" \t\tPHP_BUILD_PROVIDER='https://github.com/docker-library/php' \t\tPHP_UNAME='Linux - Docker' \t; \tdocker-php-source extract; \tcd /usr/src/php; \tgnuArch=\"$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)\"; \tdebMultiarch=\"$(dpkg-architecture --query DEB_BUILD_MULTIARCH)\"; \tif [ ! -d /usr/include/curl ]; then \t\tln -sT \"/usr/include/$debMultiarch/curl\" /usr/local/include/curl; \tfi; \t./configure \t\t--build=\"$gnuArch\" \t\t--with-config-file-path=\"$PHP_INI_DIR\" \t\t--with-config-file-scan-dir=\"$PHP_INI_DIR/conf.d\" \t\t\t\t--enable-option-checking=fatal \t\t\t\t--with-mhash \t\t\t\t--with-pic \t\t\t\t--enable-ftp \t\t--enable-mbstring \t\t--enable-mysqlnd \t\t--with-password-argon2 \t\t--with-sodium=shared \t\t--with-pdo-sqlite=/usr \t\t--with-sqlite3=/usr \t\t\t\t--with-curl \t\t--with-iconv \t\t--with-openssl \t\t--with-readline \t\t--with-zlib \t\t\t\t--disable-phpdbg \t\t\t\t--with-pear \t\t\t\t$(test \"$gnuArch\" = 's390x-linux-gnu' \u0026\u0026 echo '--without-pcre-jit') \t\t--with-libdir=\"lib/$debMultiarch\" \t\t\t\t--disable-cgi \t\t\t\t--with-apxs2 \t; \tmake -j \"$(nproc)\"; \tfind -type f -name '*.a' -delete; \tmake install; \tfind \t\t/usr/local \t\t-type f \t\t-perm '/0111' \t\t-exec sh -euxc ' \t\t\tstrip --strip-all \"$@\" || : \t\t' -- '{}' + \t; \tmake clean; \t\tcp -v php.ini-* \"$PHP_INI_DIR/\"; \t\tcd /; \tdocker-php-source delete; \t\tapt-mark auto '.*' \u003e /dev/null; \t[ -z \"$savedAptMark\" ] || apt-mark manual $savedAptMark; \tfind /usr/local -type f -executable -exec ldd '{}' ';' \t\t| awk '/=\u003e/ { so = $(NF-1); if (index(so, \"/usr/local/\") == 1) { next }; gsub(\"^/(usr/)?\", \"\", so); print so }' \t\t| sort -u \t\t| xargs -r dpkg-query --search \t\t| cut -d: -f1 \t\t| sort -u \t\t| xargs -r apt-mark manual \t; \tapt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \trm -rf /var/lib/apt/lists/*; \t\tpecl update-channels; \trm -rf /tmp/pear ~/.pearrc; \t\tphp --version"},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) COPY multi:e11221d43af7136e4dbad5a74e659bcfa753214a9e615c3daf357f1633d9d3d1 in /usr/local/bin/ "},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c docker-php-ext-enable sodium"},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENTRYPOINT [\"docker-php-entrypoint\"]","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) STOPSIGNAL SIGWINCH","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) COPY file:e3123fcb6566efa979f945bfac1c94c854a559d7b82723e42118882a8ac4de66 in /usr/local/bin/ "},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) WORKDIR /var/www/html","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) EXPOSE 80","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) CMD [\"apache2-foreground\"]","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"RUN /bin/sh -c set -eux; \tapt-get update; \tapt-get install -y --no-install-recommends \t\tghostscript \t; \trm -rf /var/lib/apt/lists/* # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-06T20:31:30Z","created_by":"RUN /bin/sh -c set -ex; \t\tsavedAptMark=\"$(apt-mark showmanual)\"; \t\tapt-get update; \tapt-get install -y --no-install-recommends \t\tlibfreetype6-dev \t\tlibicu-dev \t\tlibjpeg-dev \t\tlibmagickwand-dev \t\tlibpng-dev \t\tlibwebp-dev \t\tlibzip-dev \t; \t\tdocker-php-ext-configure gd \t\t--with-freetype \t\t--with-jpeg \t\t--with-webp \t; \tdocker-php-ext-install -j \"$(nproc)\" \t\tbcmath \t\texif \t\tgd \t\tintl \t\tmysqli \t\tzip \t; \tpecl install imagick-3.6.0; \tdocker-php-ext-enable imagick; \trm -r /tmp/pear; \t\tout=\"$(php -r 'exit(0);')\"; \t[ -z \"$out\" ]; \terr=\"$(php -r 'exit(0);' 3\u003e\u00261 1\u003e\u00262 2\u003e\u00263)\"; \t[ -z \"$err\" ]; \t\textDir=\"$(php -r 'echo ini_get(\"extension_dir\");')\"; \t[ -d \"$extDir\" ]; \tapt-mark auto '.*' \u003e /dev/null; \tapt-mark manual $savedAptMark; \tldd \"$extDir\"/*.so \t\t| awk '/=\u003e/ { so = $(NF-1); if (index(so, \"/usr/local/\") == 1) { next }; gsub(\"^/(usr/)?\", \"\", so); print so }' \t\t| sort -u \t\t| xargs -r dpkg-query --search \t\t| cut -d: -f1 \t\t| sort -u \t\t| xargs -rt apt-mark manual; \t\tapt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \trm -rf /var/lib/apt/lists/*; \t\t! { ldd \"$extDir\"/*.so | grep 'not found'; }; \terr=\"$(php --version 3\u003e\u00261 1\u003e\u00262 2\u003e\u00263)\"; \t[ -z \"$err\" ] # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-06T20:31:30Z","created_by":"RUN /bin/sh -c set -eux; \tdocker-php-ext-enable opcache; \t{ \t\techo 'opcache.memory_consumption=128'; \t\techo 'opcache.interned_strings_buffer=8'; \t\techo 'opcache.max_accelerated_files=4000'; \t\techo 'opcache.revalidate_freq=2'; \t} \u003e /usr/local/etc/php/conf.d/opcache-recommended.ini # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-06T20:31:30Z","created_by":"RUN /bin/sh -c { \t\techo 'error_reporting = E_ERROR | E_WARNING | E_PARSE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING | E_RECOVERABLE_ERROR'; \t\techo 'display_errors = Off'; \t\techo 'display_startup_errors = Off'; \t\techo 'log_errors = On'; \t\techo 'error_log = /dev/stderr'; \t\techo 'log_errors_max_len = 1024'; \t\techo 'ignore_repeated_errors = On'; \t\techo 'ignore_repeated_source = Off'; \t\techo 'html_errors = Off'; \t} \u003e /usr/local/etc/php/conf.d/error-logging.ini # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-06T20:31:30Z","created_by":"RUN /bin/sh -c set -eux; \ta2enmod rewrite expires; \t\ta2enmod remoteip; \t{ \t\techo 'RemoteIPHeader X-Forwarded-For'; \t\techo 'RemoteIPInternalProxy 10.0.0.0/8'; \t\techo 'RemoteIPInternalProxy 172.16.0.0/12'; \t\techo 'RemoteIPInternalProxy 192.168.0.0/16'; \t\techo 'RemoteIPInternalProxy 169.254.0.0/16'; \t\techo 'RemoteIPInternalProxy 127.0.0.0/8'; \t} \u003e /etc/apache2/conf-available/remoteip.conf; \ta2enconf remoteip; \tfind /etc/apache2 -type f -name '*.conf' -exec sed -ri 's/([[:space:]]*LogFormat[[:space:]]+\"[^\"]*)%h([^\"]*\")/\\1%a\\2/g' '{}' + # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-06T20:31:30Z","created_by":"RUN /bin/sh -c set -eux; \tversion='6.4.2'; \tsha1='d1aedbfea77b243b09e0ab05b100b782497406dd'; \t\tcurl -o wordpress.tar.gz -fL \"https://wordpress.org/wordpress-$version.tar.gz\"; \techo \"$sha1 *wordpress.tar.gz\" | sha1sum -c -; \t\ttar -xzf wordpress.tar.gz -C /usr/src/; \trm wordpress.tar.gz; \t\t[ ! -e /usr/src/wordpress/.htaccess ]; \t{ \t\techo '# BEGIN WordPress'; \t\techo ''; \t\techo 'RewriteEngine On'; \t\techo 'RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]'; \t\techo 'RewriteBase /'; \t\techo 'RewriteRule ^index\\.php$ - [L]'; \t\techo 'RewriteCond %{REQUEST_FILENAME} !-f'; \t\techo 'RewriteCond %{REQUEST_FILENAME} !-d'; \t\techo 'RewriteRule . /index.php [L]'; \t\techo ''; \t\techo '# END WordPress'; \t} \u003e /usr/src/wordpress/.htaccess; \t\tchown -R www-data:www-data /usr/src/wordpress; \tmkdir wp-content; \tfor dir in /usr/src/wordpress/wp-content/*/ cache; do \t\tdir=\"$(basename \"${dir%/}\")\"; \t\tmkdir \"wp-content/$dir\"; \tdone; \tchown -R www-data:www-data wp-content; \tchmod -R 1777 wp-content # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-06T20:31:30Z","created_by":"VOLUME [/var/www/html]","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"COPY wp-config-docker.php /usr/src/wordpress/ # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-06T20:31:30Z","created_by":"COPY docker-entrypoint.sh /usr/local/bin/ # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-06T20:31:30Z","created_by":"ENTRYPOINT [\"docker-entrypoint.sh\"]","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"CMD [\"apache2-foreground\"]","comment":"buildkit.dockerfile.v0","empty_layer":true}]}accelerated-container-image-1.4.2/ci/uconv_reproduce/wordpress/config-turbo.json000066400000000000000000000403341514714641000301370ustar00rootroot00000000000000{"created":"2023-12-06T20:31:30Z","architecture":"amd64","os":"linux","config":{"ExposedPorts":{"80/tcp":{}},"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","PHPIZE_DEPS=autoconf \t\tdpkg-dev \t\tfile \t\tg++ \t\tgcc \t\tlibc-dev \t\tmake \t\tpkg-config \t\tre2c","PHP_INI_DIR=/usr/local/etc/php","APACHE_CONFDIR=/etc/apache2","APACHE_ENVVARS=/etc/apache2/envvars","PHP_CFLAGS=-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64","PHP_CPPFLAGS=-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64","PHP_LDFLAGS=-Wl,-O1 -pie","GPG_KEYS=39B641343D8C104B2B146DC3F9C39DC0B9698544 E60913E4DF209907D8E30D96659A97C9CF2A795A 1198C0117593497A5EC5C199286AF1F9897469DC","PHP_VERSION=8.2.13","PHP_URL=https://www.php.net/distributions/php-8.2.13.tar.xz","PHP_ASC_URL=https://www.php.net/distributions/php-8.2.13.tar.xz.asc","PHP_SHA256=2629bba10117bf78912068a230c68a8fd09b7740267bd8ebd3cfce91515d454b"],"Entrypoint":["docker-entrypoint.sh"],"Cmd":["apache2-foreground"],"Volumes":{"/var/www/html":{}},"WorkingDir":"/var/www/html","StopSignal":"SIGWINCH","ArgsEscaped":true},"rootfs":{"type":"layers","diff_ids":["sha256:e33f3ead13b687a680cf13d7f86a569702cf655a0589aafd55612a917d95a08a","sha256:a981bdd04833ba805d517139c73d4765900e9fcf376280d6d6214af1bd6b8778","sha256:027f5de7fe88041072702dc356b79fc50dd97d24e06f7b4d65b99ea75c4f7830","sha256:fbb748be473fe8f249e8347fce85c9d5a936f3fbb0747838c2f2bf253054787f","sha256:8dd2947c0b3a429ac9adf4f9faa3c15a248122d35897ef8d46dd1e2af3b36c01","sha256:db68b598ddf88bda65f765191ef86608c7aaa126ee0c4669418959ae11e781a4","sha256:e77b3f1483d51adeae13e4d1d69e5cc11b90143822d82b93413a3214b16acb15","sha256:49e1cf1081a4cf6b40f562e368881e71e568b7aa1f9d8644fb56da7eabfe41b7","sha256:66e2fb00208d3702982341cf56a508081c00f86b598b671a98fe61ea09e83a89","sha256:1e659d3861e6b03c5903e5db6219296a3af081dec18f92652435ec4f8c7c3632","sha256:fadf34c01d73497f746004932277becad9870d43cea3bac79efbf3be665837e0","sha256:8e4f893a5f6f6aab38a27190b7ce45ab7b097b700c2ad4b57fbc2b0de42e4558","sha256:a974b31c1d96831d3206e8a6554a00fe71b0b2f14897a4c5739ee587dd435ba9","sha256:93efa414a8f4fb34f41ddeebdf7a1f43c524303b167cca8da28a640f60768d03","sha256:e61d8299dc2ee7195d378ba80c7740a6e6c1caa8baa31b33257085295748f94b","sha256:e54206255b9641210e0b4f1faa50cba41db4a0246adc153f84dc39503a67db7c","sha256:274d932427d51570ffbeab54d17d6e0e761ef91e6182377478b714617e32c899","sha256:fcbfd73149b7721ee77ef9f521e64db7fcaa83afc3979b20e360cdd9925383f5","sha256:af9ee4517e4537ba1bff6a55d3af8ddbad3102b291e4cc6fe8f6a1d7b3cae370","sha256:c8cea1015d2f1531b4f5818e14f469f04f09c70b55c5bf0eec75f2addc19b613","sha256:d118b7f666a055940f7cff38109390afba70fd88f0d3422083871c011c5b8eaf"]},"history":[{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ADD file:ac3cd70031d35e46d86b876934946ffc8756de4de065fbc926dce642dac07ff3 in / "},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) CMD [\"bash\"]","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c set -eux; \t{ \t\techo 'Package: php*'; \t\techo 'Pin: release *'; \t\techo 'Pin-Priority: -1'; \t} \u003e /etc/apt/preferences.d/no-debian-php"},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENV PHPIZE_DEPS=autoconf \t\tdpkg-dev \t\tfile \t\tg++ \t\tgcc \t\tlibc-dev \t\tmake \t\tpkg-config \t\tre2c","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c set -eux; \tapt-get update; \tapt-get install -y --no-install-recommends \t\t$PHPIZE_DEPS \t\tca-certificates \t\tcurl \t\txz-utils \t; \trm -rf /var/lib/apt/lists/*"},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENV PHP_INI_DIR=/usr/local/etc/php","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c set -eux; \tmkdir -p \"$PHP_INI_DIR/conf.d\"; \t[ ! -d /var/www/html ]; \tmkdir -p /var/www/html; \tchown www-data:www-data /var/www/html; \tchmod 1777 /var/www/html"},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENV APACHE_CONFDIR=/etc/apache2","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENV APACHE_ENVVARS=/etc/apache2/envvars","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c set -eux; \tapt-get update; \tapt-get install -y --no-install-recommends apache2; \trm -rf /var/lib/apt/lists/*; \t\tsed -ri 's/^export ([^=]+)=(.*)$/: ${\\1:=\\2}\\nexport \\1/' \"$APACHE_ENVVARS\"; \t\t. \"$APACHE_ENVVARS\"; \tfor dir in \t\t\"$APACHE_LOCK_DIR\" \t\t\"$APACHE_RUN_DIR\" \t\t\"$APACHE_LOG_DIR\" \t\t\"$APACHE_RUN_DIR/socks\" \t; do \t\trm -rvf \"$dir\"; \t\tmkdir -p \"$dir\"; \t\tchown \"$APACHE_RUN_USER:$APACHE_RUN_GROUP\" \"$dir\"; \t\tchmod 1777 \"$dir\"; \tdone; \t\trm -rvf /var/www/html/*; \t\tln -sfT /dev/stderr \"$APACHE_LOG_DIR/error.log\"; \tln -sfT /dev/stdout \"$APACHE_LOG_DIR/access.log\"; \tln -sfT /dev/stdout \"$APACHE_LOG_DIR/other_vhosts_access.log\"; \tchown -R --no-dereference \"$APACHE_RUN_USER:$APACHE_RUN_GROUP\" \"$APACHE_LOG_DIR\""},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c a2dismod mpm_event \u0026\u0026 a2enmod mpm_prefork"},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c { \t\techo '\u003cFilesMatch \\.php$\u003e'; \t\techo '\\tSetHandler application/x-httpd-php'; \t\techo '\u003c/FilesMatch\u003e'; \t\techo; \t\techo 'DirectoryIndex disabled'; \t\techo 'DirectoryIndex index.php index.html'; \t\techo; \t\techo '\u003cDirectory /var/www/\u003e'; \t\techo '\\tOptions -Indexes'; \t\techo '\\tAllowOverride All'; \t\techo '\u003c/Directory\u003e'; \t} | tee \"$APACHE_CONFDIR/conf-available/docker-php.conf\" \t\u0026\u0026 a2enconf docker-php"},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENV PHP_CFLAGS=-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENV PHP_CPPFLAGS=-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENV PHP_LDFLAGS=-Wl,-O1 -pie","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENV GPG_KEYS=39B641343D8C104B2B146DC3F9C39DC0B9698544 E60913E4DF209907D8E30D96659A97C9CF2A795A 1198C0117593497A5EC5C199286AF1F9897469DC","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENV PHP_VERSION=8.2.13","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENV PHP_URL=https://www.php.net/distributions/php-8.2.13.tar.xz PHP_ASC_URL=https://www.php.net/distributions/php-8.2.13.tar.xz.asc","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENV PHP_SHA256=2629bba10117bf78912068a230c68a8fd09b7740267bd8ebd3cfce91515d454b","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c set -eux; \t\tsavedAptMark=\"$(apt-mark showmanual)\"; \tapt-get update; \tapt-get install -y --no-install-recommends gnupg; \trm -rf /var/lib/apt/lists/*; \t\tmkdir -p /usr/src; \tcd /usr/src; \t\tcurl -fsSL -o php.tar.xz \"$PHP_URL\"; \t\tif [ -n \"$PHP_SHA256\" ]; then \t\techo \"$PHP_SHA256 *php.tar.xz\" | sha256sum -c -; \tfi; \t\tif [ -n \"$PHP_ASC_URL\" ]; then \t\tcurl -fsSL -o php.tar.xz.asc \"$PHP_ASC_URL\"; \t\texport GNUPGHOME=\"$(mktemp -d)\"; \t\tfor key in $GPG_KEYS; do \t\t\tgpg --batch --keyserver keyserver.ubuntu.com --recv-keys \"$key\"; \t\tdone; \t\tgpg --batch --verify php.tar.xz.asc php.tar.xz; \t\tgpgconf --kill all; \t\trm -rf \"$GNUPGHOME\"; \tfi; \t\tapt-mark auto '.*' \u003e /dev/null; \tapt-mark manual $savedAptMark \u003e /dev/null; \tapt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false"},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) COPY file:ce57c04b70896f77cc11eb2766417d8a1240fcffe5bba92179ec78c458844110 in /usr/local/bin/ "},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c set -eux; \t\tsavedAptMark=\"$(apt-mark showmanual)\"; \tapt-get update; \tapt-get install -y --no-install-recommends \t\tapache2-dev \t\tlibargon2-dev \t\tlibcurl4-openssl-dev \t\tlibonig-dev \t\tlibreadline-dev \t\tlibsodium-dev \t\tlibsqlite3-dev \t\tlibssl-dev \t\tlibxml2-dev \t\tzlib1g-dev \t; \t\texport \t\tCFLAGS=\"$PHP_CFLAGS\" \t\tCPPFLAGS=\"$PHP_CPPFLAGS\" \t\tLDFLAGS=\"$PHP_LDFLAGS\" \t\tPHP_BUILD_PROVIDER='https://github.com/docker-library/php' \t\tPHP_UNAME='Linux - Docker' \t; \tdocker-php-source extract; \tcd /usr/src/php; \tgnuArch=\"$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)\"; \tdebMultiarch=\"$(dpkg-architecture --query DEB_BUILD_MULTIARCH)\"; \tif [ ! -d /usr/include/curl ]; then \t\tln -sT \"/usr/include/$debMultiarch/curl\" /usr/local/include/curl; \tfi; \t./configure \t\t--build=\"$gnuArch\" \t\t--with-config-file-path=\"$PHP_INI_DIR\" \t\t--with-config-file-scan-dir=\"$PHP_INI_DIR/conf.d\" \t\t\t\t--enable-option-checking=fatal \t\t\t\t--with-mhash \t\t\t\t--with-pic \t\t\t\t--enable-ftp \t\t--enable-mbstring \t\t--enable-mysqlnd \t\t--with-password-argon2 \t\t--with-sodium=shared \t\t--with-pdo-sqlite=/usr \t\t--with-sqlite3=/usr \t\t\t\t--with-curl \t\t--with-iconv \t\t--with-openssl \t\t--with-readline \t\t--with-zlib \t\t\t\t--disable-phpdbg \t\t\t\t--with-pear \t\t\t\t$(test \"$gnuArch\" = 's390x-linux-gnu' \u0026\u0026 echo '--without-pcre-jit') \t\t--with-libdir=\"lib/$debMultiarch\" \t\t\t\t--disable-cgi \t\t\t\t--with-apxs2 \t; \tmake -j \"$(nproc)\"; \tfind -type f -name '*.a' -delete; \tmake install; \tfind \t\t/usr/local \t\t-type f \t\t-perm '/0111' \t\t-exec sh -euxc ' \t\t\tstrip --strip-all \"$@\" || : \t\t' -- '{}' + \t; \tmake clean; \t\tcp -v php.ini-* \"$PHP_INI_DIR/\"; \t\tcd /; \tdocker-php-source delete; \t\tapt-mark auto '.*' \u003e /dev/null; \t[ -z \"$savedAptMark\" ] || apt-mark manual $savedAptMark; \tfind /usr/local -type f -executable -exec ldd '{}' ';' \t\t| awk '/=\u003e/ { so = $(NF-1); if (index(so, \"/usr/local/\") == 1) { next }; gsub(\"^/(usr/)?\", \"\", so); print so }' \t\t| sort -u \t\t| xargs -r dpkg-query --search \t\t| cut -d: -f1 \t\t| sort -u \t\t| xargs -r apt-mark manual \t; \tapt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \trm -rf /var/lib/apt/lists/*; \t\tpecl update-channels; \trm -rf /tmp/pear ~/.pearrc; \t\tphp --version"},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) COPY multi:e11221d43af7136e4dbad5a74e659bcfa753214a9e615c3daf357f1633d9d3d1 in /usr/local/bin/ "},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c docker-php-ext-enable sodium"},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENTRYPOINT [\"docker-php-entrypoint\"]","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) STOPSIGNAL SIGWINCH","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) COPY file:e3123fcb6566efa979f945bfac1c94c854a559d7b82723e42118882a8ac4de66 in /usr/local/bin/ "},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) WORKDIR /var/www/html","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) EXPOSE 80","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) CMD [\"apache2-foreground\"]","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"RUN /bin/sh -c set -eux; \tapt-get update; \tapt-get install -y --no-install-recommends \t\tghostscript \t; \trm -rf /var/lib/apt/lists/* # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-06T20:31:30Z","created_by":"RUN /bin/sh -c set -ex; \t\tsavedAptMark=\"$(apt-mark showmanual)\"; \t\tapt-get update; \tapt-get install -y --no-install-recommends \t\tlibfreetype6-dev \t\tlibicu-dev \t\tlibjpeg-dev \t\tlibmagickwand-dev \t\tlibpng-dev \t\tlibwebp-dev \t\tlibzip-dev \t; \t\tdocker-php-ext-configure gd \t\t--with-freetype \t\t--with-jpeg \t\t--with-webp \t; \tdocker-php-ext-install -j \"$(nproc)\" \t\tbcmath \t\texif \t\tgd \t\tintl \t\tmysqli \t\tzip \t; \tpecl install imagick-3.6.0; \tdocker-php-ext-enable imagick; \trm -r /tmp/pear; \t\tout=\"$(php -r 'exit(0);')\"; \t[ -z \"$out\" ]; \terr=\"$(php -r 'exit(0);' 3\u003e\u00261 1\u003e\u00262 2\u003e\u00263)\"; \t[ -z \"$err\" ]; \t\textDir=\"$(php -r 'echo ini_get(\"extension_dir\");')\"; \t[ -d \"$extDir\" ]; \tapt-mark auto '.*' \u003e /dev/null; \tapt-mark manual $savedAptMark; \tldd \"$extDir\"/*.so \t\t| awk '/=\u003e/ { so = $(NF-1); if (index(so, \"/usr/local/\") == 1) { next }; gsub(\"^/(usr/)?\", \"\", so); print so }' \t\t| sort -u \t\t| xargs -r dpkg-query --search \t\t| cut -d: -f1 \t\t| sort -u \t\t| xargs -rt apt-mark manual; \t\tapt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \trm -rf /var/lib/apt/lists/*; \t\t! { ldd \"$extDir\"/*.so | grep 'not found'; }; \terr=\"$(php --version 3\u003e\u00261 1\u003e\u00262 2\u003e\u00263)\"; \t[ -z \"$err\" ] # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-06T20:31:30Z","created_by":"RUN /bin/sh -c set -eux; \tdocker-php-ext-enable opcache; \t{ \t\techo 'opcache.memory_consumption=128'; \t\techo 'opcache.interned_strings_buffer=8'; \t\techo 'opcache.max_accelerated_files=4000'; \t\techo 'opcache.revalidate_freq=2'; \t} \u003e /usr/local/etc/php/conf.d/opcache-recommended.ini # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-06T20:31:30Z","created_by":"RUN /bin/sh -c { \t\techo 'error_reporting = E_ERROR | E_WARNING | E_PARSE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING | E_RECOVERABLE_ERROR'; \t\techo 'display_errors = Off'; \t\techo 'display_startup_errors = Off'; \t\techo 'log_errors = On'; \t\techo 'error_log = /dev/stderr'; \t\techo 'log_errors_max_len = 1024'; \t\techo 'ignore_repeated_errors = On'; \t\techo 'ignore_repeated_source = Off'; \t\techo 'html_errors = Off'; \t} \u003e /usr/local/etc/php/conf.d/error-logging.ini # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-06T20:31:30Z","created_by":"RUN /bin/sh -c set -eux; \ta2enmod rewrite expires; \t\ta2enmod remoteip; \t{ \t\techo 'RemoteIPHeader X-Forwarded-For'; \t\techo 'RemoteIPInternalProxy 10.0.0.0/8'; \t\techo 'RemoteIPInternalProxy 172.16.0.0/12'; \t\techo 'RemoteIPInternalProxy 192.168.0.0/16'; \t\techo 'RemoteIPInternalProxy 169.254.0.0/16'; \t\techo 'RemoteIPInternalProxy 127.0.0.0/8'; \t} \u003e /etc/apache2/conf-available/remoteip.conf; \ta2enconf remoteip; \tfind /etc/apache2 -type f -name '*.conf' -exec sed -ri 's/([[:space:]]*LogFormat[[:space:]]+\"[^\"]*)%h([^\"]*\")/\\1%a\\2/g' '{}' + # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-06T20:31:30Z","created_by":"RUN /bin/sh -c set -eux; \tversion='6.4.2'; \tsha1='d1aedbfea77b243b09e0ab05b100b782497406dd'; \t\tcurl -o wordpress.tar.gz -fL \"https://wordpress.org/wordpress-$version.tar.gz\"; \techo \"$sha1 *wordpress.tar.gz\" | sha1sum -c -; \t\ttar -xzf wordpress.tar.gz -C /usr/src/; \trm wordpress.tar.gz; \t\t[ ! -e /usr/src/wordpress/.htaccess ]; \t{ \t\techo '# BEGIN WordPress'; \t\techo ''; \t\techo 'RewriteEngine On'; \t\techo 'RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]'; \t\techo 'RewriteBase /'; \t\techo 'RewriteRule ^index\\.php$ - [L]'; \t\techo 'RewriteCond %{REQUEST_FILENAME} !-f'; \t\techo 'RewriteCond %{REQUEST_FILENAME} !-d'; \t\techo 'RewriteRule . /index.php [L]'; \t\techo ''; \t\techo '# END WordPress'; \t} \u003e /usr/src/wordpress/.htaccess; \t\tchown -R www-data:www-data /usr/src/wordpress; \tmkdir wp-content; \tfor dir in /usr/src/wordpress/wp-content/*/ cache; do \t\tdir=\"$(basename \"${dir%/}\")\"; \t\tmkdir \"wp-content/$dir\"; \tdone; \tchown -R www-data:www-data wp-content; \tchmod -R 1777 wp-content # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-06T20:31:30Z","created_by":"VOLUME [/var/www/html]","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"COPY wp-config-docker.php /usr/src/wordpress/ # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-06T20:31:30Z","created_by":"COPY docker-entrypoint.sh /usr/local/bin/ # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-06T20:31:30Z","created_by":"ENTRYPOINT [\"docker-entrypoint.sh\"]","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"CMD [\"apache2-foreground\"]","comment":"buildkit.dockerfile.v0","empty_layer":true}]}accelerated-container-image-1.4.2/ci/uconv_reproduce/wordpress/config.json000066400000000000000000000403111514714641000270010ustar00rootroot00000000000000{"created":"2023-12-06T20:31:30Z","architecture":"amd64","os":"linux","config":{"ExposedPorts":{"80/tcp":{}},"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","PHPIZE_DEPS=autoconf \t\tdpkg-dev \t\tfile \t\tg++ \t\tgcc \t\tlibc-dev \t\tmake \t\tpkg-config \t\tre2c","PHP_INI_DIR=/usr/local/etc/php","APACHE_CONFDIR=/etc/apache2","APACHE_ENVVARS=/etc/apache2/envvars","PHP_CFLAGS=-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64","PHP_CPPFLAGS=-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64","PHP_LDFLAGS=-Wl,-O1 -pie","GPG_KEYS=39B641343D8C104B2B146DC3F9C39DC0B9698544 E60913E4DF209907D8E30D96659A97C9CF2A795A 1198C0117593497A5EC5C199286AF1F9897469DC","PHP_VERSION=8.2.13","PHP_URL=https://www.php.net/distributions/php-8.2.13.tar.xz","PHP_ASC_URL=https://www.php.net/distributions/php-8.2.13.tar.xz.asc","PHP_SHA256=2629bba10117bf78912068a230c68a8fd09b7740267bd8ebd3cfce91515d454b"],"Entrypoint":["docker-entrypoint.sh"],"Cmd":["apache2-foreground"],"Volumes":{"/var/www/html":{}},"WorkingDir":"/var/www/html","StopSignal":"SIGWINCH"},"rootfs":{"type":"layers","diff_ids":["sha256:a640fc493a1522f8e40b74dc77386011d1d10783d81750b8135e127b703d5d93","sha256:e5f775c51a00c1d7a5dbe56ff34c458c26b8f3f73843d13f5121f8cd07808e5d","sha256:5d7a051db3487521da30a410961456b5f48b64c3ce2fc063c84784e74608f626","sha256:9ee6a265a4d9f538325dac46a437cd233cc6a183b880cdfc466010ae903444d9","sha256:2848c7fd52333199a1256b52e41df6006678d0b612db0833044dcb832bfa02c3","sha256:244b44c809eabe23e9686c39eda0f18f542a24a7c3ac529ecbc95f08e877cf0b","sha256:ad808cce9e6a890923c8a3f7b93ec26e68d839b41bfc93675cf2c9e0866db285","sha256:06bfa4d803cf6cdd68f1d17580724694987430fe0308c72ddc6d45ae4301265c","sha256:33604ee832c8c1df56b238783961f037280cbb20d43eadb927144812c9f531eb","sha256:2cff53a92357f9cb4f22330f8d35099d92739a40dbdd20350e41359bfdedcf3a","sha256:7233b64ea14fbd5eefb33bd3ff10da04051b8d0fb8ebf26b6c2405b379bfa5fd","sha256:67d1c60ff764c4b619dbf93558884b3986d84a81b4f7892e9834695ab2bcd2eb","sha256:d540210467d470bea7c1bafd888082eceee7bfe4c317e382c06d73f5da228c4a","sha256:67bfbfdea701389b54a5ec4067802b654aefbb3410c770070d1bb1ec964c967a","sha256:838413a00bd736e2415a0fe9ff47ddb1948970c04576462ec66b233201384143","sha256:0c8dd0b2b24e537de3d77de5f5c9995a0a58bef1fe08c4bc2d99d0108c612cd8","sha256:31780e73a33b0a3f5c4bc38222db3435a3ccb4b62eb93d7e0ae583bff482dae8","sha256:d8827bd5d200d3890f5aa57e4ca5fba27e2e277cf0928741a2a09fac18f1d5c9","sha256:c52860334c6afea417ccd68c47b102eed19538ec71c047b4b06301b9ae05f17f","sha256:5b2e00b6ed5844284ef076f04ba432b586ca3a47b86c890d584acf0da4177bc7","sha256:5edaadc382896dd4f6be882fd83dfb0f759e5b28dedca83a3785a4f0d4b93845"]},"history":[{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ADD file:ac3cd70031d35e46d86b876934946ffc8756de4de065fbc926dce642dac07ff3 in / "},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) CMD [\"bash\"]","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c set -eux; \t{ \t\techo 'Package: php*'; \t\techo 'Pin: release *'; \t\techo 'Pin-Priority: -1'; \t} \u003e /etc/apt/preferences.d/no-debian-php"},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENV PHPIZE_DEPS=autoconf \t\tdpkg-dev \t\tfile \t\tg++ \t\tgcc \t\tlibc-dev \t\tmake \t\tpkg-config \t\tre2c","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c set -eux; \tapt-get update; \tapt-get install -y --no-install-recommends \t\t$PHPIZE_DEPS \t\tca-certificates \t\tcurl \t\txz-utils \t; \trm -rf /var/lib/apt/lists/*"},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENV PHP_INI_DIR=/usr/local/etc/php","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c set -eux; \tmkdir -p \"$PHP_INI_DIR/conf.d\"; \t[ ! -d /var/www/html ]; \tmkdir -p /var/www/html; \tchown www-data:www-data /var/www/html; \tchmod 1777 /var/www/html"},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENV APACHE_CONFDIR=/etc/apache2","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENV APACHE_ENVVARS=/etc/apache2/envvars","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c set -eux; \tapt-get update; \tapt-get install -y --no-install-recommends apache2; \trm -rf /var/lib/apt/lists/*; \t\tsed -ri 's/^export ([^=]+)=(.*)$/: ${\\1:=\\2}\\nexport \\1/' \"$APACHE_ENVVARS\"; \t\t. \"$APACHE_ENVVARS\"; \tfor dir in \t\t\"$APACHE_LOCK_DIR\" \t\t\"$APACHE_RUN_DIR\" \t\t\"$APACHE_LOG_DIR\" \t\t\"$APACHE_RUN_DIR/socks\" \t; do \t\trm -rvf \"$dir\"; \t\tmkdir -p \"$dir\"; \t\tchown \"$APACHE_RUN_USER:$APACHE_RUN_GROUP\" \"$dir\"; \t\tchmod 1777 \"$dir\"; \tdone; \t\trm -rvf /var/www/html/*; \t\tln -sfT /dev/stderr \"$APACHE_LOG_DIR/error.log\"; \tln -sfT /dev/stdout \"$APACHE_LOG_DIR/access.log\"; \tln -sfT /dev/stdout \"$APACHE_LOG_DIR/other_vhosts_access.log\"; \tchown -R --no-dereference \"$APACHE_RUN_USER:$APACHE_RUN_GROUP\" \"$APACHE_LOG_DIR\""},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c a2dismod mpm_event \u0026\u0026 a2enmod mpm_prefork"},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c { \t\techo '\u003cFilesMatch \\.php$\u003e'; \t\techo '\\tSetHandler application/x-httpd-php'; \t\techo '\u003c/FilesMatch\u003e'; \t\techo; \t\techo 'DirectoryIndex disabled'; \t\techo 'DirectoryIndex index.php index.html'; \t\techo; \t\techo '\u003cDirectory /var/www/\u003e'; \t\techo '\\tOptions -Indexes'; \t\techo '\\tAllowOverride All'; \t\techo '\u003c/Directory\u003e'; \t} | tee \"$APACHE_CONFDIR/conf-available/docker-php.conf\" \t\u0026\u0026 a2enconf docker-php"},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENV PHP_CFLAGS=-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENV PHP_CPPFLAGS=-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENV PHP_LDFLAGS=-Wl,-O1 -pie","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENV GPG_KEYS=39B641343D8C104B2B146DC3F9C39DC0B9698544 E60913E4DF209907D8E30D96659A97C9CF2A795A 1198C0117593497A5EC5C199286AF1F9897469DC","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENV PHP_VERSION=8.2.13","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENV PHP_URL=https://www.php.net/distributions/php-8.2.13.tar.xz PHP_ASC_URL=https://www.php.net/distributions/php-8.2.13.tar.xz.asc","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENV PHP_SHA256=2629bba10117bf78912068a230c68a8fd09b7740267bd8ebd3cfce91515d454b","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c set -eux; \t\tsavedAptMark=\"$(apt-mark showmanual)\"; \tapt-get update; \tapt-get install -y --no-install-recommends gnupg; \trm -rf /var/lib/apt/lists/*; \t\tmkdir -p /usr/src; \tcd /usr/src; \t\tcurl -fsSL -o php.tar.xz \"$PHP_URL\"; \t\tif [ -n \"$PHP_SHA256\" ]; then \t\techo \"$PHP_SHA256 *php.tar.xz\" | sha256sum -c -; \tfi; \t\tif [ -n \"$PHP_ASC_URL\" ]; then \t\tcurl -fsSL -o php.tar.xz.asc \"$PHP_ASC_URL\"; \t\texport GNUPGHOME=\"$(mktemp -d)\"; \t\tfor key in $GPG_KEYS; do \t\t\tgpg --batch --keyserver keyserver.ubuntu.com --recv-keys \"$key\"; \t\tdone; \t\tgpg --batch --verify php.tar.xz.asc php.tar.xz; \t\tgpgconf --kill all; \t\trm -rf \"$GNUPGHOME\"; \tfi; \t\tapt-mark auto '.*' \u003e /dev/null; \tapt-mark manual $savedAptMark \u003e /dev/null; \tapt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false"},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) COPY file:ce57c04b70896f77cc11eb2766417d8a1240fcffe5bba92179ec78c458844110 in /usr/local/bin/ "},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c set -eux; \t\tsavedAptMark=\"$(apt-mark showmanual)\"; \tapt-get update; \tapt-get install -y --no-install-recommends \t\tapache2-dev \t\tlibargon2-dev \t\tlibcurl4-openssl-dev \t\tlibonig-dev \t\tlibreadline-dev \t\tlibsodium-dev \t\tlibsqlite3-dev \t\tlibssl-dev \t\tlibxml2-dev \t\tzlib1g-dev \t; \t\texport \t\tCFLAGS=\"$PHP_CFLAGS\" \t\tCPPFLAGS=\"$PHP_CPPFLAGS\" \t\tLDFLAGS=\"$PHP_LDFLAGS\" \t\tPHP_BUILD_PROVIDER='https://github.com/docker-library/php' \t\tPHP_UNAME='Linux - Docker' \t; \tdocker-php-source extract; \tcd /usr/src/php; \tgnuArch=\"$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)\"; \tdebMultiarch=\"$(dpkg-architecture --query DEB_BUILD_MULTIARCH)\"; \tif [ ! -d /usr/include/curl ]; then \t\tln -sT \"/usr/include/$debMultiarch/curl\" /usr/local/include/curl; \tfi; \t./configure \t\t--build=\"$gnuArch\" \t\t--with-config-file-path=\"$PHP_INI_DIR\" \t\t--with-config-file-scan-dir=\"$PHP_INI_DIR/conf.d\" \t\t\t\t--enable-option-checking=fatal \t\t\t\t--with-mhash \t\t\t\t--with-pic \t\t\t\t--enable-ftp \t\t--enable-mbstring \t\t--enable-mysqlnd \t\t--with-password-argon2 \t\t--with-sodium=shared \t\t--with-pdo-sqlite=/usr \t\t--with-sqlite3=/usr \t\t\t\t--with-curl \t\t--with-iconv \t\t--with-openssl \t\t--with-readline \t\t--with-zlib \t\t\t\t--disable-phpdbg \t\t\t\t--with-pear \t\t\t\t$(test \"$gnuArch\" = 's390x-linux-gnu' \u0026\u0026 echo '--without-pcre-jit') \t\t--with-libdir=\"lib/$debMultiarch\" \t\t\t\t--disable-cgi \t\t\t\t--with-apxs2 \t; \tmake -j \"$(nproc)\"; \tfind -type f -name '*.a' -delete; \tmake install; \tfind \t\t/usr/local \t\t-type f \t\t-perm '/0111' \t\t-exec sh -euxc ' \t\t\tstrip --strip-all \"$@\" || : \t\t' -- '{}' + \t; \tmake clean; \t\tcp -v php.ini-* \"$PHP_INI_DIR/\"; \t\tcd /; \tdocker-php-source delete; \t\tapt-mark auto '.*' \u003e /dev/null; \t[ -z \"$savedAptMark\" ] || apt-mark manual $savedAptMark; \tfind /usr/local -type f -executable -exec ldd '{}' ';' \t\t| awk '/=\u003e/ { so = $(NF-1); if (index(so, \"/usr/local/\") == 1) { next }; gsub(\"^/(usr/)?\", \"\", so); print so }' \t\t| sort -u \t\t| xargs -r dpkg-query --search \t\t| cut -d: -f1 \t\t| sort -u \t\t| xargs -r apt-mark manual \t; \tapt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \trm -rf /var/lib/apt/lists/*; \t\tpecl update-channels; \trm -rf /tmp/pear ~/.pearrc; \t\tphp --version"},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) COPY multi:e11221d43af7136e4dbad5a74e659bcfa753214a9e615c3daf357f1633d9d3d1 in /usr/local/bin/ "},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c docker-php-ext-enable sodium"},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) ENTRYPOINT [\"docker-php-entrypoint\"]","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) STOPSIGNAL SIGWINCH","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) COPY file:e3123fcb6566efa979f945bfac1c94c854a559d7b82723e42118882a8ac4de66 in /usr/local/bin/ "},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) WORKDIR /var/www/html","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) EXPOSE 80","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"/bin/sh -c #(nop) CMD [\"apache2-foreground\"]","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"RUN /bin/sh -c set -eux; \tapt-get update; \tapt-get install -y --no-install-recommends \t\tghostscript \t; \trm -rf /var/lib/apt/lists/* # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-06T20:31:30Z","created_by":"RUN /bin/sh -c set -ex; \t\tsavedAptMark=\"$(apt-mark showmanual)\"; \t\tapt-get update; \tapt-get install -y --no-install-recommends \t\tlibfreetype6-dev \t\tlibicu-dev \t\tlibjpeg-dev \t\tlibmagickwand-dev \t\tlibpng-dev \t\tlibwebp-dev \t\tlibzip-dev \t; \t\tdocker-php-ext-configure gd \t\t--with-freetype \t\t--with-jpeg \t\t--with-webp \t; \tdocker-php-ext-install -j \"$(nproc)\" \t\tbcmath \t\texif \t\tgd \t\tintl \t\tmysqli \t\tzip \t; \tpecl install imagick-3.6.0; \tdocker-php-ext-enable imagick; \trm -r /tmp/pear; \t\tout=\"$(php -r 'exit(0);')\"; \t[ -z \"$out\" ]; \terr=\"$(php -r 'exit(0);' 3\u003e\u00261 1\u003e\u00262 2\u003e\u00263)\"; \t[ -z \"$err\" ]; \t\textDir=\"$(php -r 'echo ini_get(\"extension_dir\");')\"; \t[ -d \"$extDir\" ]; \tapt-mark auto '.*' \u003e /dev/null; \tapt-mark manual $savedAptMark; \tldd \"$extDir\"/*.so \t\t| awk '/=\u003e/ { so = $(NF-1); if (index(so, \"/usr/local/\") == 1) { next }; gsub(\"^/(usr/)?\", \"\", so); print so }' \t\t| sort -u \t\t| xargs -r dpkg-query --search \t\t| cut -d: -f1 \t\t| sort -u \t\t| xargs -rt apt-mark manual; \t\tapt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \trm -rf /var/lib/apt/lists/*; \t\t! { ldd \"$extDir\"/*.so | grep 'not found'; }; \terr=\"$(php --version 3\u003e\u00261 1\u003e\u00262 2\u003e\u00263)\"; \t[ -z \"$err\" ] # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-06T20:31:30Z","created_by":"RUN /bin/sh -c set -eux; \tdocker-php-ext-enable opcache; \t{ \t\techo 'opcache.memory_consumption=128'; \t\techo 'opcache.interned_strings_buffer=8'; \t\techo 'opcache.max_accelerated_files=4000'; \t\techo 'opcache.revalidate_freq=2'; \t} \u003e /usr/local/etc/php/conf.d/opcache-recommended.ini # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-06T20:31:30Z","created_by":"RUN /bin/sh -c { \t\techo 'error_reporting = E_ERROR | E_WARNING | E_PARSE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING | E_RECOVERABLE_ERROR'; \t\techo 'display_errors = Off'; \t\techo 'display_startup_errors = Off'; \t\techo 'log_errors = On'; \t\techo 'error_log = /dev/stderr'; \t\techo 'log_errors_max_len = 1024'; \t\techo 'ignore_repeated_errors = On'; \t\techo 'ignore_repeated_source = Off'; \t\techo 'html_errors = Off'; \t} \u003e /usr/local/etc/php/conf.d/error-logging.ini # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-06T20:31:30Z","created_by":"RUN /bin/sh -c set -eux; \ta2enmod rewrite expires; \t\ta2enmod remoteip; \t{ \t\techo 'RemoteIPHeader X-Forwarded-For'; \t\techo 'RemoteIPInternalProxy 10.0.0.0/8'; \t\techo 'RemoteIPInternalProxy 172.16.0.0/12'; \t\techo 'RemoteIPInternalProxy 192.168.0.0/16'; \t\techo 'RemoteIPInternalProxy 169.254.0.0/16'; \t\techo 'RemoteIPInternalProxy 127.0.0.0/8'; \t} \u003e /etc/apache2/conf-available/remoteip.conf; \ta2enconf remoteip; \tfind /etc/apache2 -type f -name '*.conf' -exec sed -ri 's/([[:space:]]*LogFormat[[:space:]]+\"[^\"]*)%h([^\"]*\")/\\1%a\\2/g' '{}' + # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-06T20:31:30Z","created_by":"RUN /bin/sh -c set -eux; \tversion='6.4.2'; \tsha1='d1aedbfea77b243b09e0ab05b100b782497406dd'; \t\tcurl -o wordpress.tar.gz -fL \"https://wordpress.org/wordpress-$version.tar.gz\"; \techo \"$sha1 *wordpress.tar.gz\" | sha1sum -c -; \t\ttar -xzf wordpress.tar.gz -C /usr/src/; \trm wordpress.tar.gz; \t\t[ ! -e /usr/src/wordpress/.htaccess ]; \t{ \t\techo '# BEGIN WordPress'; \t\techo ''; \t\techo 'RewriteEngine On'; \t\techo 'RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]'; \t\techo 'RewriteBase /'; \t\techo 'RewriteRule ^index\\.php$ - [L]'; \t\techo 'RewriteCond %{REQUEST_FILENAME} !-f'; \t\techo 'RewriteCond %{REQUEST_FILENAME} !-d'; \t\techo 'RewriteRule . /index.php [L]'; \t\techo ''; \t\techo '# END WordPress'; \t} \u003e /usr/src/wordpress/.htaccess; \t\tchown -R www-data:www-data /usr/src/wordpress; \tmkdir wp-content; \tfor dir in /usr/src/wordpress/wp-content/*/ cache; do \t\tdir=\"$(basename \"${dir%/}\")\"; \t\tmkdir \"wp-content/$dir\"; \tdone; \tchown -R www-data:www-data wp-content; \tchmod -R 1777 wp-content # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-06T20:31:30Z","created_by":"VOLUME [/var/www/html]","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"COPY wp-config-docker.php /usr/src/wordpress/ # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-06T20:31:30Z","created_by":"COPY docker-entrypoint.sh /usr/local/bin/ # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2023-12-06T20:31:30Z","created_by":"ENTRYPOINT [\"docker-entrypoint.sh\"]","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2023-12-06T20:31:30Z","created_by":"CMD [\"apache2-foreground\"]","comment":"buildkit.dockerfile.v0","empty_layer":true}]}accelerated-container-image-1.4.2/ci/uconv_reproduce/wordpress/manifest-turbo-erofs.json000066400000000000000000000335701514714641000316200ustar00rootroot00000000000000{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:ff39437409213237e506a0fc435969ca6803527ca64271eb23fa8c5c4f39fa14","size":16604},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:8669c0b06bac9a3b6437647361cc0fed70a500744e513bdbb4088c71db0c4c01","size":1104210,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:8669c0b06bac9a3b6437647361cc0fed70a500744e513bdbb4088c71db0c4c01","containerd.io/snapshot/overlaybd/blob-size":"1104210","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:af107e978371b6cd6339127a05502c5eacd1e6b0e9eb7b2f4aa7b6fc87e2dd81","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:25360256fe049f4acb1462cf7ffc1ef18a6eb0a6d9f8ef18557660cae631928f","size":4690,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:25360256fe049f4acb1462cf7ffc1ef18a6eb0a6d9f8ef18557660cae631928f","containerd.io/snapshot/overlaybd/blob-size":"4690","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:6480d4ad61d22c10d4a7237828e51d8a2ca3b5d60fcdf3d9800350353b5ac3fa","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:bff13056218c5f4bab210ed60a05aa2514ed51d793a8a91e8f8ae879b587e3c6","size":3729901,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:bff13056218c5f4bab210ed60a05aa2514ed51d793a8a91e8f8ae879b587e3c6","containerd.io/snapshot/overlaybd/blob-size":"3729901","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:95f5176ece8bbdb815830df5e9eab427554c171d3b1459ced51bd7f991b2d9c5","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:a6362e9e9f454aed6b038b9cc8c9f4b16fa49409162c17e4231b49b772a6c0db","size":9237,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:a6362e9e9f454aed6b038b9cc8c9f4b16fa49409162c17e4231b49b772a6c0db","containerd.io/snapshot/overlaybd/blob-size":"9237","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:0ebe7ec824ca8231eb30bfc1bc1416a2e73ecc77cefb226cba43ab8c84676045","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:dfb2c075446189b98e73809005d7119e65ca1cb2903fd709afc63082de5c94b9","size":741121,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:dfb2c075446189b98e73809005d7119e65ca1cb2903fd709afc63082de5c94b9","containerd.io/snapshot/overlaybd/blob-size":"741121","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:673e01769ec9ab92c5580bf1b3de45ce83ad6fb946a2a1c53bf6f546d61ab109","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:1a6d634f7f67aad6b2ec963bedcc4265c000861250e0d9ce13171bda8a3add0b","size":10073,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:1a6d634f7f67aad6b2ec963bedcc4265c000861250e0d9ce13171bda8a3add0b","containerd.io/snapshot/overlaybd/blob-size":"10073","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:74f0c50b3097295649c2ae489f3540f903bff7f8d18a53e291445c931f6cc7a0","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:8d50083849f6564812e97a590e3fdb0a40956c578b194dc199cc495974f1ac95","size":10121,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:8d50083849f6564812e97a590e3fdb0a40956c578b194dc199cc495974f1ac95","containerd.io/snapshot/overlaybd/blob-size":"10121","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:1a19a72eb529a45c1f923059e6e81d8d3fef65976b2272628309d6b46464f661","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:586a1252ccaac88a07ba482a0a50bdf110e7e58feb8930aa60bfd62d3ce5d8f6","size":420054,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:586a1252ccaac88a07ba482a0a50bdf110e7e58feb8930aa60bfd62d3ce5d8f6","containerd.io/snapshot/overlaybd/blob-size":"420054","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:50436df89cfb4bc199a39a93f389b6722c7ea0baa0c56a076b135cf9c4337ffc","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:ab6567d30b5c784cf48d08ddeaa09bd5f79526ec8a4cac0587ad131bd2de082a","size":8447,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:ab6567d30b5c784cf48d08ddeaa09bd5f79526ec8a4cac0587ad131bd2de082a","containerd.io/snapshot/overlaybd/blob-size":"8447","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:8b616b90f7e6ac6918a6c1b63cd7209432bf96bf18837f173f4d52e8110189df","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:df329225f0612a1ac52f637a1084faa62b9c9e239ab0e087fe1eff3a5f484ace","size":484866,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:df329225f0612a1ac52f637a1084faa62b9c9e239ab0e087fe1eff3a5f484ace","containerd.io/snapshot/overlaybd/blob-size":"484866","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:df9d2e4043f86e2b7f0664e2ad4c18a27a97a6737ae86f196b5c5a1673471f58","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:ccb88fc016e5e965c727dc15bd9176dd6aa3b8976f68376b987438f668b6646d","size":9144,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:ccb88fc016e5e965c727dc15bd9176dd6aa3b8976f68376b987438f668b6646d","containerd.io/snapshot/overlaybd/blob-size":"9144","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:d6236f3e94a1d7933968c52dd07cf9f1cfe84298ad43aad025d0a5486e9500fb","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:701de2bf6bea6ff80ec86410a9e9bfe04c338dafb12faafd29e6b874e4d6ff2e","size":9241,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:701de2bf6bea6ff80ec86410a9e9bfe04c338dafb12faafd29e6b874e4d6ff2e","containerd.io/snapshot/overlaybd/blob-size":"9241","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:59fa8b76a6b31300b51ab61ab6c40f6bb47c70fd58027b1e2ee53d8de2e601ce","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:b019d49efed82da03c05909f75c2d9d9df9d0f41da7acefb0e1a8b01e870d3c5","size":9247,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:b019d49efed82da03c05909f75c2d9d9df9d0f41da7acefb0e1a8b01e870d3c5","containerd.io/snapshot/overlaybd/blob-size":"9247","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:99eb3419cf60b135da25ba3ce0d4d6f8ad28d45ff592e274badec2b4d5ac9324","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:aea837759955c5a9284ec8b0d14bb6a677600dfca89b5c142993fa76f288c534","size":1033339,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:aea837759955c5a9284ec8b0d14bb6a677600dfca89b5c142993fa76f288c534","containerd.io/snapshot/overlaybd/blob-size":"1033339","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:22f5c20b545ddaebb310e24093556e46342770cfcf30ce996850e6f751aeaa65","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:bdaf28f28929869c17b94c1dbbee23b21776cd87eb8c8e33b070a8a97366851f","size":1107054,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:bdaf28f28929869c17b94c1dbbee23b21776cd87eb8c8e33b070a8a97366851f","containerd.io/snapshot/overlaybd/blob-size":"1107054","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:1f0d2c1603d0d6095336756bff11794ea7f1741e054da47cb764341e11956a3e","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:03e2880ca1e5b6fc9c6a63e40c6acd9fdb87b3320ec1cd252d45050766caec0b","size":10607,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:03e2880ca1e5b6fc9c6a63e40c6acd9fdb87b3320ec1cd252d45050766caec0b","containerd.io/snapshot/overlaybd/blob-size":"10607","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:4624824acfeaa10302d0d16c62a8ebe748db54a5f45407a37b4afe000e530038","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:38100974d474562afbcd4136004efabf2e61ab6c0f99da39d30be44e1fcb99d3","size":10648,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:38100974d474562afbcd4136004efabf2e61ab6c0f99da39d30be44e1fcb99d3","containerd.io/snapshot/overlaybd/blob-size":"10648","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:79c3af11cab54ec46a4301653795e642dd2b1471680fcd2f4d2c91024e27de28","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:cc516f854251f6714b473aa4b83e98b19cdfe6096d9d2afaaba3318d3ce4993c","size":13333,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:cc516f854251f6714b473aa4b83e98b19cdfe6096d9d2afaaba3318d3ce4993c","containerd.io/snapshot/overlaybd/blob-size":"13333","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:e8d8239610fbe0feecb32b687b7bd93a3f45f9a0021441d0b46113c16c429459","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:a469e2c72131d57af1e1d822cc388c7fbabb7593c41082adda56415260f6fc68","size":978270,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:a469e2c72131d57af1e1d822cc388c7fbabb7593c41082adda56415260f6fc68","containerd.io/snapshot/overlaybd/blob-size":"978270","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:a1ff013e1d949ab7328badb2dfa36e2b7c6299b4aeae938297126bfde09f380d","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:6b8903af229ef3a90035952e6e7e5e167e5e9c624c9f1386b5788f8aa0e79979","size":12126,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:6b8903af229ef3a90035952e6e7e5e167e5e9c624c9f1386b5788f8aa0e79979","containerd.io/snapshot/overlaybd/blob-size":"12126","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:31076364071cff601c1fe5ad59188f489eb813a67eeabc6cff4b231c6d672317","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:9c2424dbf068b7ce578750ffc26447254bb437c197fccdb8cb30575d1fea0c3c","size":10326,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:9c2424dbf068b7ce578750ffc26447254bb437c197fccdb8cb30575d1fea0c3c","containerd.io/snapshot/overlaybd/blob-size":"10326","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:87728bbad961b16b1d1e1ea1ce05f297e6a98597738bc55730a4668caa2fb38d","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}}]}accelerated-container-image-1.4.2/ci/uconv_reproduce/wordpress/manifest-turbo.json000066400000000000000000000335541514714641000305060ustar00rootroot00000000000000{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:b4de25536735fd98d35f54df67a88fe9f98aa8e56d7f3cfa6b8efa79000b02bb","size":16604},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:93d5e7762541cd51f8e8dceea4978516596aa3dd837978334b60f0f8c03ca7b0","size":1124551,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:93d5e7762541cd51f8e8dceea4978516596aa3dd837978334b60f0f8c03ca7b0","containerd.io/snapshot/overlaybd/blob-size":"1124551","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:af107e978371b6cd6339127a05502c5eacd1e6b0e9eb7b2f4aa7b6fc87e2dd81","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:4c7ebac7e7f8a16bebbb038d2e499302495c43819434338e3f3a5869280c1392","size":5528,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:4c7ebac7e7f8a16bebbb038d2e499302495c43819434338e3f3a5869280c1392","containerd.io/snapshot/overlaybd/blob-size":"5528","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:6480d4ad61d22c10d4a7237828e51d8a2ca3b5d60fcdf3d9800350353b5ac3fa","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:a4098ac44bfd674118a738a14fbab19c961307cdf889ee880cd8ab6d1214b5da","size":3728652,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:a4098ac44bfd674118a738a14fbab19c961307cdf889ee880cd8ab6d1214b5da","containerd.io/snapshot/overlaybd/blob-size":"3728652","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:95f5176ece8bbdb815830df5e9eab427554c171d3b1459ced51bd7f991b2d9c5","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:6eb6eb5b6ffc7f87bc06f61db2f863eb052c670ac20f7717af8b6e8c502e5b7b","size":6315,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:6eb6eb5b6ffc7f87bc06f61db2f863eb052c670ac20f7717af8b6e8c502e5b7b","containerd.io/snapshot/overlaybd/blob-size":"6315","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:0ebe7ec824ca8231eb30bfc1bc1416a2e73ecc77cefb226cba43ab8c84676045","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:d729cfeb8f3ed13dcb690164589724ab602b68269f1395c753c86b25f0836087","size":732370,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:d729cfeb8f3ed13dcb690164589724ab602b68269f1395c753c86b25f0836087","containerd.io/snapshot/overlaybd/blob-size":"732370","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:673e01769ec9ab92c5580bf1b3de45ce83ad6fb946a2a1c53bf6f546d61ab109","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:25e3b68969285ea72213e66038c0b533cafc0301c181a6c92cad2f30a85e0f74","size":8355,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:25e3b68969285ea72213e66038c0b533cafc0301c181a6c92cad2f30a85e0f74","containerd.io/snapshot/overlaybd/blob-size":"8355","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:74f0c50b3097295649c2ae489f3540f903bff7f8d18a53e291445c931f6cc7a0","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:5d3fa372874fba47ff77f979eaa96e19175665e2299bb2985a28839071618984","size":7165,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:5d3fa372874fba47ff77f979eaa96e19175665e2299bb2985a28839071618984","containerd.io/snapshot/overlaybd/blob-size":"7165","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:1a19a72eb529a45c1f923059e6e81d8d3fef65976b2272628309d6b46464f661","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:f9688d8888c93028caad9bf2537947e16b016f7f05d96e2a524576ff65be0ad5","size":387537,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:f9688d8888c93028caad9bf2537947e16b016f7f05d96e2a524576ff65be0ad5","containerd.io/snapshot/overlaybd/blob-size":"387537","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:50436df89cfb4bc199a39a93f389b6722c7ea0baa0c56a076b135cf9c4337ffc","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:530492a678c0a2de1338e093baac46748374d26ea0a2de5d8383e53ce0384bde","size":5901,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:530492a678c0a2de1338e093baac46748374d26ea0a2de5d8383e53ce0384bde","containerd.io/snapshot/overlaybd/blob-size":"5901","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:8b616b90f7e6ac6918a6c1b63cd7209432bf96bf18837f173f4d52e8110189df","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:cf263280fb79d0b18cd7cf78c6b5f3572670b636983114774054e544a3fdc951","size":454851,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:cf263280fb79d0b18cd7cf78c6b5f3572670b636983114774054e544a3fdc951","containerd.io/snapshot/overlaybd/blob-size":"454851","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:df9d2e4043f86e2b7f0664e2ad4c18a27a97a6737ae86f196b5c5a1673471f58","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:51660c045e95b363f1c2eff1d2852dba693b5c5367a59f7c56ece3eba0116cec","size":6924,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:51660c045e95b363f1c2eff1d2852dba693b5c5367a59f7c56ece3eba0116cec","containerd.io/snapshot/overlaybd/blob-size":"6924","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:d6236f3e94a1d7933968c52dd07cf9f1cfe84298ad43aad025d0a5486e9500fb","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:ef0799b0e25d2106fa75eebe0b56483af9a58b71cc42380055e83d8c30c451ee","size":6538,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:ef0799b0e25d2106fa75eebe0b56483af9a58b71cc42380055e83d8c30c451ee","containerd.io/snapshot/overlaybd/blob-size":"6538","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:59fa8b76a6b31300b51ab61ab6c40f6bb47c70fd58027b1e2ee53d8de2e601ce","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:6b5ddff6b5eddc8d14e9ff1048465913fd672a93a0562b534256950e5ba2aea1","size":6189,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:6b5ddff6b5eddc8d14e9ff1048465913fd672a93a0562b534256950e5ba2aea1","containerd.io/snapshot/overlaybd/blob-size":"6189","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:99eb3419cf60b135da25ba3ce0d4d6f8ad28d45ff592e274badec2b4d5ac9324","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:834d2ff05f42fddc0cdbc9866fbcc6cc7ff23cadf08866e3a95d49400e763cd3","size":1019248,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:834d2ff05f42fddc0cdbc9866fbcc6cc7ff23cadf08866e3a95d49400e763cd3","containerd.io/snapshot/overlaybd/blob-size":"1019248","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:22f5c20b545ddaebb310e24093556e46342770cfcf30ce996850e6f751aeaa65","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:a06f4247b7fd404e707c06bcb163c5f1160e8b96255a9c66effecd1cf158e093","size":1078568,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:a06f4247b7fd404e707c06bcb163c5f1160e8b96255a9c66effecd1cf158e093","containerd.io/snapshot/overlaybd/blob-size":"1078568","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:1f0d2c1603d0d6095336756bff11794ea7f1741e054da47cb764341e11956a3e","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:647d2ac12d560caba1a6b996bce827dc7615375a2b0f51d7c3d0abb722094fe0","size":6909,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:647d2ac12d560caba1a6b996bce827dc7615375a2b0f51d7c3d0abb722094fe0","containerd.io/snapshot/overlaybd/blob-size":"6909","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:4624824acfeaa10302d0d16c62a8ebe748db54a5f45407a37b4afe000e530038","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:7066234c5322ac633e4abb779571ecd2dbb27b776bcea6fb8f6e8293f793bf9f","size":6609,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:7066234c5322ac633e4abb779571ecd2dbb27b776bcea6fb8f6e8293f793bf9f","containerd.io/snapshot/overlaybd/blob-size":"6609","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:79c3af11cab54ec46a4301653795e642dd2b1471680fcd2f4d2c91024e27de28","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:ddf14d025df79cb932b115f17ce5f7294b3686569c74012b5ad7c9b9d09049f0","size":13508,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:ddf14d025df79cb932b115f17ce5f7294b3686569c74012b5ad7c9b9d09049f0","containerd.io/snapshot/overlaybd/blob-size":"13508","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:e8d8239610fbe0feecb32b687b7bd93a3f45f9a0021441d0b46113c16c429459","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:93483bc0f0f21c9c44bf1add3a9e79edc1eb56deb642c3311ccb1f21fe9ce6ac","size":971908,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:93483bc0f0f21c9c44bf1add3a9e79edc1eb56deb642c3311ccb1f21fe9ce6ac","containerd.io/snapshot/overlaybd/blob-size":"971908","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:a1ff013e1d949ab7328badb2dfa36e2b7c6299b4aeae938297126bfde09f380d","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:7c1f46e8c266005b6cf5e518be8eb5e8b29c60c59c7ef7e22dabd0468715921f","size":6310,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:7c1f46e8c266005b6cf5e518be8eb5e8b29c60c59c7ef7e22dabd0468715921f","containerd.io/snapshot/overlaybd/blob-size":"6310","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:31076364071cff601c1fe5ad59188f489eb813a67eeabc6cff4b231c6d672317","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:525d4b705a2197479e9ecae90ac7c3a144cd5d1dd25c57b4b1e33dbebde26c9c","size":5861,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:525d4b705a2197479e9ecae90ac7c3a144cd5d1dd25c57b4b1e33dbebde26c9c","containerd.io/snapshot/overlaybd/blob-size":"5861","containerd.io/snapshot/overlaybd/turbo-oci/target-digest":"sha256:87728bbad961b16b1d1e1ea1ce05f297e6a98597738bc55730a4668caa2fb38d","containerd.io/snapshot/overlaybd/turbo-oci/target-media-type":"application/vnd.docker.image.rootfs.diff.tar.gzip","containerd.io/snapshot/overlaybd/version":"0.1.0-turbo.ociv1"}}]}accelerated-container-image-1.4.2/ci/uconv_reproduce/wordpress/manifest.json000066400000000000000000000207731514714641000273540ustar00rootroot00000000000000{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:7311cd4439b038bc095207d3f70a3e7e1e40f157f5d25602f88dd64a7afd5aff","size":16585},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:a640fc493a1522f8e40b74dc77386011d1d10783d81750b8135e127b703d5d93","size":46912000,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:a640fc493a1522f8e40b74dc77386011d1d10783d81750b8135e127b703d5d93","containerd.io/snapshot/overlaybd/blob-size":"46912000","containerd.io/snapshot/overlaybd/version":"0.1.0"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:e5f775c51a00c1d7a5dbe56ff34c458c26b8f3f73843d13f5121f8cd07808e5d","size":674304,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:e5f775c51a00c1d7a5dbe56ff34c458c26b8f3f73843d13f5121f8cd07808e5d","containerd.io/snapshot/overlaybd/blob-size":"674304","containerd.io/snapshot/overlaybd/version":"0.1.0"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:5d7a051db3487521da30a410961456b5f48b64c3ce2fc063c84784e74608f626","size":177016320,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:5d7a051db3487521da30a410961456b5f48b64c3ce2fc063c84784e74608f626","containerd.io/snapshot/overlaybd/blob-size":"177016320","containerd.io/snapshot/overlaybd/version":"0.1.0"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:9ee6a265a4d9f538325dac46a437cd233cc6a183b880cdfc466010ae903444d9","size":716288,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:9ee6a265a4d9f538325dac46a437cd233cc6a183b880cdfc466010ae903444d9","containerd.io/snapshot/overlaybd/blob-size":"716288","containerd.io/snapshot/overlaybd/version":"0.1.0"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:2848c7fd52333199a1256b52e41df6006678d0b612db0833044dcb832bfa02c3","size":33137664,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:2848c7fd52333199a1256b52e41df6006678d0b612db0833044dcb832bfa02c3","containerd.io/snapshot/overlaybd/blob-size":"33137664","containerd.io/snapshot/overlaybd/version":"0.1.0"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:244b44c809eabe23e9686c39eda0f18f542a24a7c3ac529ecbc95f08e877cf0b","size":771584,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:244b44c809eabe23e9686c39eda0f18f542a24a7c3ac529ecbc95f08e877cf0b","containerd.io/snapshot/overlaybd/blob-size":"771584","containerd.io/snapshot/overlaybd/version":"0.1.0"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:ad808cce9e6a890923c8a3f7b93ec26e68d839b41bfc93675cf2c9e0866db285","size":776704,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:ad808cce9e6a890923c8a3f7b93ec26e68d839b41bfc93675cf2c9e0866db285","containerd.io/snapshot/overlaybd/blob-size":"776704","containerd.io/snapshot/overlaybd/version":"0.1.0"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:06bfa4d803cf6cdd68f1d17580724694987430fe0308c72ddc6d45ae4301265c","size":13896192,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:06bfa4d803cf6cdd68f1d17580724694987430fe0308c72ddc6d45ae4301265c","containerd.io/snapshot/overlaybd/blob-size":"13896192","containerd.io/snapshot/overlaybd/version":"0.1.0"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:33604ee832c8c1df56b238783961f037280cbb20d43eadb927144812c9f531eb","size":761856,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:33604ee832c8c1df56b238783961f037280cbb20d43eadb927144812c9f531eb","containerd.io/snapshot/overlaybd/blob-size":"761856","containerd.io/snapshot/overlaybd/version":"0.1.0"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:2cff53a92357f9cb4f22330f8d35099d92739a40dbdd20350e41359bfdedcf3a","size":21018112,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:2cff53a92357f9cb4f22330f8d35099d92739a40dbdd20350e41359bfdedcf3a","containerd.io/snapshot/overlaybd/blob-size":"21018112","containerd.io/snapshot/overlaybd/version":"0.1.0"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:7233b64ea14fbd5eefb33bd3ff10da04051b8d0fb8ebf26b6c2405b379bfa5fd","size":720384,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:7233b64ea14fbd5eefb33bd3ff10da04051b8d0fb8ebf26b6c2405b379bfa5fd","containerd.io/snapshot/overlaybd/blob-size":"720384","containerd.io/snapshot/overlaybd/version":"0.1.0"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:67d1c60ff764c4b619dbf93558884b3986d84a81b4f7892e9834695ab2bcd2eb","size":703488,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:67d1c60ff764c4b619dbf93558884b3986d84a81b4f7892e9834695ab2bcd2eb","containerd.io/snapshot/overlaybd/blob-size":"703488","containerd.io/snapshot/overlaybd/version":"0.1.0"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:d540210467d470bea7c1bafd888082eceee7bfe4c317e382c06d73f5da228c4a","size":709120,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:d540210467d470bea7c1bafd888082eceee7bfe4c317e382c06d73f5da228c4a","containerd.io/snapshot/overlaybd/blob-size":"709120","containerd.io/snapshot/overlaybd/version":"0.1.0"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:67bfbfdea701389b54a5ec4067802b654aefbb3410c770070d1bb1ec964c967a","size":39392768,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:67bfbfdea701389b54a5ec4067802b654aefbb3410c770070d1bb1ec964c967a","containerd.io/snapshot/overlaybd/blob-size":"39392768","containerd.io/snapshot/overlaybd/version":"0.1.0"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:838413a00bd736e2415a0fe9ff47ddb1948970c04576462ec66b233201384143","size":53039616,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:838413a00bd736e2415a0fe9ff47ddb1948970c04576462ec66b233201384143","containerd.io/snapshot/overlaybd/blob-size":"53039616","containerd.io/snapshot/overlaybd/version":"0.1.0"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:0c8dd0b2b24e537de3d77de5f5c9995a0a58bef1fe08c4bc2d99d0108c612cd8","size":699904,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:0c8dd0b2b24e537de3d77de5f5c9995a0a58bef1fe08c4bc2d99d0108c612cd8","containerd.io/snapshot/overlaybd/blob-size":"699904","containerd.io/snapshot/overlaybd/version":"0.1.0"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:31780e73a33b0a3f5c4bc38222db3435a3ccb4b62eb93d7e0ae583bff482dae8","size":688640,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:31780e73a33b0a3f5c4bc38222db3435a3ccb4b62eb93d7e0ae583bff482dae8","containerd.io/snapshot/overlaybd/blob-size":"688640","containerd.io/snapshot/overlaybd/version":"0.1.0"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:d8827bd5d200d3890f5aa57e4ca5fba27e2e277cf0928741a2a09fac18f1d5c9","size":907264,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:d8827bd5d200d3890f5aa57e4ca5fba27e2e277cf0928741a2a09fac18f1d5c9","containerd.io/snapshot/overlaybd/blob-size":"907264","containerd.io/snapshot/overlaybd/version":"0.1.0"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:c52860334c6afea417ccd68c47b102eed19538ec71c047b4b06301b9ae05f17f","size":41038848,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:c52860334c6afea417ccd68c47b102eed19538ec71c047b4b06301b9ae05f17f","containerd.io/snapshot/overlaybd/blob-size":"41038848","containerd.io/snapshot/overlaybd/version":"0.1.0"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:5b2e00b6ed5844284ef076f04ba432b586ca3a47b86c890d584acf0da4177bc7","size":670208,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:5b2e00b6ed5844284ef076f04ba432b586ca3a47b86c890d584acf0da4177bc7","containerd.io/snapshot/overlaybd/blob-size":"670208","containerd.io/snapshot/overlaybd/version":"0.1.0"}},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar","digest":"sha256:5edaadc382896dd4f6be882fd83dfb0f759e5b28dedca83a3785a4f0d4b93845","size":651264,"annotations":{"containerd.io/snapshot/overlaybd/blob-digest":"sha256:5edaadc382896dd4f6be882fd83dfb0f759e5b28dedca83a3785a4f0d4b93845","containerd.io/snapshot/overlaybd/blob-size":"651264","containerd.io/snapshot/overlaybd/version":"0.1.0"}}]}accelerated-container-image-1.4.2/cmd/000077500000000000000000000000001514714641000175605ustar00rootroot00000000000000accelerated-container-image-1.4.2/cmd/convertor/000077500000000000000000000000001514714641000216015ustar00rootroot00000000000000accelerated-container-image-1.4.2/cmd/convertor/builder/000077500000000000000000000000001514714641000232275ustar00rootroot00000000000000accelerated-container-image-1.4.2/cmd/convertor/builder/builder.go000066400000000000000000000371001514714641000252050ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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 builder import ( "context" "crypto/tls" "crypto/x509" "encoding/json" "errors" "fmt" "io" "net" "net/http" "os" "path/filepath" "runtime" "strings" "sync" "sync/atomic" "time" "github.com/containerd/accelerated-container-image/cmd/convertor/database" "github.com/containerd/containerd/v2/core/images" "github.com/containerd/containerd/v2/core/remotes" "github.com/containerd/containerd/v2/core/remotes/docker" "github.com/containerd/containerd/v2/pkg/reference" "github.com/containerd/log" "github.com/containerd/platforms" "github.com/opencontainers/go-digest" v1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/sirupsen/logrus" "golang.org/x/sync/errgroup" ) type BuilderOptions struct { Ref string TargetRef string Auth string PlainHTTP bool WorkDir string OCI bool FsType string Mkfs bool Vsize int DB database.ConversionDatabase Engine BuilderEngineType CertOption Reserve bool NoUpload bool DumpManifest bool // ConcurrencyLimit limits the number of manifests that can be built at once // 0 means no limit ConcurrencyLimit int // disable sparse file when converting overlaybd DisableSparse bool // Push manifests with subject Referrer bool } type graphBuilder struct { // required Resolver remotes.Resolver // options BuilderOptions // private fetcher remotes.Fetcher pusher remotes.Pusher tagPusher remotes.Pusher group *errgroup.Group sem chan struct{} id atomic.Int32 } func (b *graphBuilder) Build(ctx context.Context) error { fetcher, err := b.Resolver.Fetcher(ctx, b.Ref) if err != nil { return fmt.Errorf("failed to obtain new fetcher: %w", err) } pusher, err := b.Resolver.Pusher(ctx, b.TargetRef+"@") // append '@' to avoid tag if err != nil { return fmt.Errorf("failed to obtain new pusher: %w", err) } tagPusher, err := b.Resolver.Pusher(ctx, b.TargetRef) // append '@' to avoid tag if err != nil { return fmt.Errorf("failed to obtain new tag pusher: %w", err) } b.fetcher = fetcher b.pusher = pusher b.tagPusher = tagPusher _, src, err := b.Resolver.Resolve(ctx, b.Ref) if err != nil { return fmt.Errorf("failed to resolve: %w", err) } g, gctx := errgroup.WithContext(ctx) b.group = g if b.ConcurrencyLimit > 0 { b.sem = make(chan struct{}, b.ConcurrencyLimit) } g.Go(func() error { target, err := b.process(gctx, src, true) if err != nil { return fmt.Errorf("failed to build %q: %w", src.Digest, err) } log.G(gctx).Infof("converted to %q, digest: %q", b.TargetRef, target.Digest) return nil }) return g.Wait() } func (b *graphBuilder) process(ctx context.Context, src v1.Descriptor, tag bool) (v1.Descriptor, error) { switch src.MediaType { case v1.MediaTypeImageManifest, images.MediaTypeDockerSchema2Manifest: return b.buildOne(ctx, src, tag) case v1.MediaTypeImageIndex, images.MediaTypeDockerSchema2ManifestList: var index v1.Index rc, err := b.fetcher.Fetch(ctx, src) if err != nil { return v1.Descriptor{}, fmt.Errorf("failed to fetch index: %w", err) } defer rc.Close() indexBytes, err := io.ReadAll(rc) if err != nil { return v1.Descriptor{}, fmt.Errorf("failed to read index: %w", err) } if err := json.Unmarshal(indexBytes, &index); err != nil { return v1.Descriptor{}, fmt.Errorf("failed to unmarshal index: %w", err) } var wg sync.WaitGroup for _i, _m := range index.Manifests { i := _i m := _m wg.Add(1) b.group.Go(func() error { defer wg.Done() target, err := b.process(ctx, m, false) if err != nil { return fmt.Errorf("failed to build %q: %w", m.Digest, err) } index.Manifests[i] = target return nil }) } wg.Wait() if ctx.Err() != nil { return v1.Descriptor{}, ctx.Err() } // upload index if b.Referrer { index.ArtifactType = b.Engine.ArtifactType() index.Subject = &v1.Descriptor{ MediaType: src.MediaType, Digest: src.Digest, Size: src.Size, } } if b.OCI { index.MediaType = v1.MediaTypeImageIndex } indexBytes, err = json.Marshal(index) if err != nil { return v1.Descriptor{}, fmt.Errorf("failed to marshal index: %w", err) } if b.DumpManifest { if err := os.WriteFile(filepath.Join(b.WorkDir, "index.json"), indexBytes, 0644); err != nil { return v1.Descriptor{}, fmt.Errorf("failed to dump index: %w", err) } } expected := v1.Descriptor{ MediaType: index.MediaType, Digest: digest.FromBytes(indexBytes), Size: int64(len(indexBytes)), } var pusher remotes.Pusher if tag { pusher = b.tagPusher } else { pusher = b.pusher } if err := uploadBytes(ctx, pusher, expected, indexBytes); err != nil { return v1.Descriptor{}, fmt.Errorf("failed to upload index: %w", err) } log.G(ctx).Infof("index uploaded, %s", expected.Digest) return expected, nil default: return v1.Descriptor{}, fmt.Errorf("unsupported media type %q", src.MediaType) } } func (b *graphBuilder) buildOne(ctx context.Context, src v1.Descriptor, tag bool) (v1.Descriptor, error) { if b.sem != nil { select { case <-ctx.Done(): return v1.Descriptor{}, ctx.Err() case b.sem <- struct{}{}: } } defer func() { if b.sem != nil { select { case <-ctx.Done(): case <-b.sem: } } }() id := b.id.Add(1) var platform string if src.Platform == nil { platform = "" } else { platform = platforms.Format(*src.Platform) ctx = log.WithLogger(ctx, log.G(ctx).WithField("platform", platform)) } workdir := filepath.Join(b.WorkDir, fmt.Sprintf("%d-%s-%s", id, strings.ReplaceAll(platform, "/", "_"), src.Digest.Encoded())) log.G(ctx).Infof("building %s ...", workdir) // init build engine manifest, config, err := fetchManifestAndConfig(ctx, b.fetcher, src) if err != nil { return v1.Descriptor{}, fmt.Errorf("failed to fetch manifest and config: %w", err) } var pusher remotes.Pusher if tag { pusher = b.tagPusher } else { pusher = b.pusher } engineBase := &builderEngineBase{ resolver: b.Resolver, fetcher: b.fetcher, pusher: pusher, manifest: *manifest, config: *config, inputDesc: src, referrer: b.Referrer, } engineBase.workDir = workdir engineBase.oci = b.OCI engineBase.fstype = b.FsType engineBase.mkfs = b.Mkfs engineBase.vsize = b.Vsize engineBase.db = b.DB refspec, err := reference.Parse(b.Ref) if err != nil { return v1.Descriptor{}, err } engineBase.host = refspec.Hostname() engineBase.repository = strings.TrimPrefix(refspec.Locator, engineBase.host+"/") engineBase.reserve = b.Reserve engineBase.noUpload = b.NoUpload engineBase.dumpManifest = b.DumpManifest var engine builderEngine switch b.Engine { case Overlaybd: engine = NewOverlayBDBuilderEngine(engineBase) engine.(*overlaybdBuilderEngine).disableSparse = b.DisableSparse case TurboOCI: engine = NewTurboOCIBuilderEngine(engineBase) } // build builder := &overlaybdBuilder{ layers: len(engineBase.manifest.Layers), engine: engine, } desc, err := builder.Build(ctx) if err != nil { return v1.Descriptor{}, fmt.Errorf("failed to build %s: %w", workdir, err) } // preserve the other fields from src descriptor src.Digest = desc.Digest src.Size = desc.Size src.MediaType = desc.MediaType return src, nil } func Build(ctx context.Context, opt BuilderOptions) error { tlsConfig, err := loadTLSConfig(opt.CertOption) if err != nil { return fmt.Errorf("failed to load certifications: %w", err) } transport := &http.Transport{ DialContext: (&net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, FallbackDelay: 300 * time.Millisecond, }).DialContext, MaxConnsPerHost: 32, // max http concurrency MaxIdleConns: 32, IdleConnTimeout: 30 * time.Second, TLSHandshakeTimeout: 10 * time.Second, TLSClientConfig: tlsConfig, ExpectContinueTimeout: 5 * time.Second, } client := &http.Client{Transport: transport} resolver := docker.NewResolver(docker.ResolverOptions{ Hosts: docker.ConfigureDefaultRegistries( docker.WithAuthorizer(docker.NewDockerAuthorizer( docker.WithAuthClient(client), docker.WithAuthHeader(make(http.Header)), docker.WithAuthCreds(func(s string) (string, string, error) { if i := strings.IndexByte(opt.Auth, ':'); i > 0 { return opt.Auth[0:i], opt.Auth[i+1:], nil } return "", "", nil }), )), docker.WithClient(client), docker.WithPlainHTTP(func(s string) (bool, error) { if opt.PlainHTTP { return docker.MatchAllHosts(s) } else { return false, nil } }), ), }) return (&graphBuilder{ Resolver: resolver, BuilderOptions: opt, }).Build(ctx) } type overlaybdBuilder struct { layers int engine builderEngine } // Build return a descriptor of the converted target, as the caller may need it // to tag or compose an index func (b *overlaybdBuilder) Build(ctx context.Context) (v1.Descriptor, error) { defer b.engine.Cleanup() alreadyConverted := make([]chan *v1.Descriptor, b.layers) downloaded := make([]chan error, b.layers) converted := make([]chan error, b.layers) // check if manifest conversion result is already present in registry, if so, we can avoid conversion. // when errors are encountered fallback to regular conversion if convertedDesc, err := b.engine.CheckForConvertedManifest(ctx); err == nil && convertedDesc.Digest != "" { logrus.Infof("Image found already converted in registry with digest %s", convertedDesc.Digest) // Even if the image has been found we still need to make sure the requested tag is set // fetch the manifest then push again with the requested tag if err := b.engine.TagPreviouslyConvertedManifest(ctx, convertedDesc); err != nil { logrus.Warnf("failed to tag previously converted manifest: %s. Falling back to regular conversion", err) } else { return convertedDesc, nil } } // Errgroups will close the context after wait returns so the operations need their own // derived context. g, rctx := errgroup.WithContext(ctx) for i := 0; i < b.layers; i++ { idx := i downloaded[idx] = make(chan error) converted[idx] = make(chan error) alreadyConverted[idx] = make(chan *v1.Descriptor) // deduplication Goroutine g.Go(func() error { defer close(alreadyConverted[idx]) // try to find chainID -> converted digest conversion if available desc, err := b.engine.CheckForConvertedLayer(rctx, idx) if err != nil { // in the event of failure fallback to regular process return nil } select { case <-rctx.Done(): case alreadyConverted[idx] <- &desc: } return nil }) // download goroutine g.Go(func() error { var cachedLayer *v1.Descriptor select { case <-rctx.Done(): case cachedLayer = <-alreadyConverted[idx]: } defer close(downloaded[idx]) if cachedLayer != nil { // download the converted layer err := b.engine.DownloadConvertedLayer(rctx, idx, *cachedLayer) if err == nil { logrus.Infof("downloaded cached layer %d", idx) sendToChannel(rctx, downloaded[idx], nil) return nil } logrus.Infof("failed to download cached layer %d falling back to conversion : %s", idx, err) } if err := b.engine.DownloadLayer(rctx, idx); err != nil { return err } logrus.Infof("downloaded layer %d", idx) sendToChannel(rctx, downloaded[idx], nil) return nil }) // convert goroutine g.Go(func() error { defer close(converted[idx]) if waitForChannel(rctx, downloaded[idx]); rctx.Err() != nil { return rctx.Err() } if idx > 0 { if waitForChannel(rctx, converted[idx-1]); rctx.Err() != nil { return rctx.Err() } } if err := b.engine.BuildLayer(rctx, idx); err != nil { return fmt.Errorf("failed to convert layer %d: %w", idx, err) } logrus.Infof("layer %d converted", idx) // send to upload(idx) and convert(idx+1) once each sendToChannel(rctx, converted[idx], nil) if idx+1 < b.layers { sendToChannel(rctx, converted[idx], nil) } return nil }) g.Go(func() error { if waitForChannel(rctx, converted[idx]); rctx.Err() != nil { return rctx.Err() } if err := b.engine.UploadLayer(rctx, idx); err != nil { return fmt.Errorf("failed to upload layer %d: %w", idx, err) } b.engine.StoreConvertedLayerDetails(rctx, idx) logrus.Infof("layer %d uploaded", idx) return nil }) } if err := g.Wait(); err != nil { return v1.Descriptor{}, err } targetDesc, err := b.engine.UploadImage(ctx) if err != nil { return v1.Descriptor{}, fmt.Errorf("failed to upload manifest or config: %w", err) } b.engine.StoreConvertedManifestDetails(ctx) logrus.Info("convert finished") return targetDesc, nil } // block until ctx.Done() or sent func sendToChannel(ctx context.Context, ch chan<- error, value error) { select { case <-ctx.Done(): case ch <- value: } } // block until ctx.Done() or received func waitForChannel(ctx context.Context, ch <-chan error) { select { case <-ctx.Done(): case <-ch: } } // -------------------- certification -------------------- type CertOption struct { CertDirs []string RootCAs []string ClientCerts []string Insecure bool } func loadTLSConfig(opt CertOption) (*tls.Config, error) { type clientCertPair struct { certFile string keyFile string } var clientCerts []clientCertPair // client certs from option `--client-cert` for _, cert := range opt.ClientCerts { s := strings.Split(cert, ":") if len(s) != 2 { return nil, fmt.Errorf("client cert %s: invalid format", cert) } clientCerts = append(clientCerts, clientCertPair{ certFile: s[0], keyFile: s[1], }) } // root CAs / client certs from option `--cert-dir` for _, d := range opt.CertDirs { fs, err := os.ReadDir(d) if err != nil && !errors.Is(err, os.ErrNotExist) && !errors.Is(err, os.ErrPermission) { return nil, fmt.Errorf("failed to read cert directory %q: %w", d, err) } for _, f := range fs { if strings.HasSuffix(f.Name(), ".crt") { opt.RootCAs = append(opt.RootCAs, filepath.Join(d, f.Name())) } if strings.HasSuffix(f.Name(), ".cert") { clientCerts = append(clientCerts, clientCertPair{ certFile: filepath.Join(d, f.Name()), keyFile: filepath.Join(d, strings.TrimSuffix(f.Name(), ".cert")+".key"), }) } } } tlsConfig := &tls.Config{} // root CAs from ENV ${SSL_CERT_FILE} and ${SSL_CERT_DIR} systemPool, err := x509.SystemCertPool() if err != nil { if runtime.GOOS == "windows" { systemPool = x509.NewCertPool() } else { return nil, fmt.Errorf("failed to get system cert pool: %w", err) } } tlsConfig.RootCAs = systemPool // root CAs from option `--root-ca` for _, file := range opt.RootCAs { b, err := os.ReadFile(file) if err != nil { return nil, fmt.Errorf("failed to read root CA file %q: %w", file, err) } tlsConfig.RootCAs.AppendCertsFromPEM(b) } // load client certs for _, c := range clientCerts { cert, err := tls.LoadX509KeyPair(c.certFile, c.keyFile) if err != nil { return nil, fmt.Errorf("failed to load client cert pair {%q, %q}: %w", c.certFile, c.keyFile, err) } tlsConfig.Certificates = append(tlsConfig.Certificates, cert) } tlsConfig.InsecureSkipVerify = opt.Insecure return tlsConfig, nil } accelerated-container-image-1.4.2/cmd/convertor/builder/builder_engine.go000066400000000000000000000164321514714641000265370ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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 builder import ( "context" "encoding/json" "fmt" "path" "github.com/containerd/accelerated-container-image/cmd/convertor/database" "github.com/containerd/containerd/v2/core/images" "github.com/containerd/containerd/v2/core/remotes" "github.com/containerd/containerd/v2/pkg/archive/compression" "github.com/containerd/continuity" "github.com/containerd/log" "github.com/opencontainers/go-digest" specs "github.com/opencontainers/image-spec/specs-go/v1" ) type BuilderEngineType int const ( Overlaybd BuilderEngineType = iota TurboOCI ) const ( ArtifactTypeOverlaybd = "application/vnd.containerd.overlaybd.native.v1+json" ArtifactTypeTurboOCI = "application/vnd.containerd.overlaybd.turbo.v1+json" ) func (engine BuilderEngineType) ArtifactType() string { switch engine { case Overlaybd: return ArtifactTypeOverlaybd case TurboOCI: return ArtifactTypeTurboOCI default: return "" } } type builderEngine interface { DownloadLayer(ctx context.Context, idx int) error // build layer archive, maybe tgz or zfile BuildLayer(ctx context.Context, idx int) error UploadLayer(ctx context.Context, idx int) error // UploadImage upload new manifest and config, return the descriptor of the manifest UploadImage(ctx context.Context) (specs.Descriptor, error) // Cleanup removes workdir Cleanup() Deduplicateable } // Deduplicateable provides a number of functions to avoid duplicating work when converting images // It is used by the builderEngine to avoid re-converting layers and manifests type Deduplicateable interface { // deduplication functions // finds already converted layer in db and validates presence in registry CheckForConvertedLayer(ctx context.Context, idx int) (specs.Descriptor, error) // downloads the already converted layer DownloadConvertedLayer(ctx context.Context, idx int, desc specs.Descriptor) error // store chainID -> converted layer mapping for layer deduplication StoreConvertedLayerDetails(ctx context.Context, idx int) error // store manifest digest -> converted manifest to avoid re-conversion CheckForConvertedManifest(ctx context.Context) (specs.Descriptor, error) // tag a converted manifest -> converted manifest to avoid re-conversion TagPreviouslyConvertedManifest(ctx context.Context, desc specs.Descriptor) error // store manifest digest -> converted manifest to avoid re-conversion StoreConvertedManifestDetails(ctx context.Context) error } type builderEngineBase struct { resolver remotes.Resolver fetcher remotes.Fetcher pusher remotes.Pusher manifest specs.Manifest config specs.Image workDir string oci bool fstype string mkfs bool vsize int db database.ConversionDatabase host string repository string inputDesc specs.Descriptor // original manifest descriptor outputDesc specs.Descriptor // converted manifest descriptor reserve bool noUpload bool dumpManifest bool referrer bool } func (e *builderEngineBase) isGzipLayer(ctx context.Context, idx int) (bool, error) { rc, err := e.fetcher.Fetch(ctx, e.manifest.Layers[idx]) if err != nil { return false, fmt.Errorf("isGzipLayer: failed to open layer %d: %w", idx, err) } drc, err := compression.DecompressStream(rc) if err != nil { return false, fmt.Errorf("isGzipLayer: failed to open decompress stream for layer %d: %w", idx, err) } compress := drc.GetCompression() switch compress { case compression.Uncompressed: return false, nil case compression.Gzip: return true, nil default: return false, fmt.Errorf("isGzipLayer: unsupported layer format with compression %s", compress.Extension()) } } func (e *builderEngineBase) mediaTypeManifest() string { if e.oci { return specs.MediaTypeImageManifest } else { return images.MediaTypeDockerSchema2Manifest } } func (e *builderEngineBase) mediaTypeConfig() string { if e.oci { return specs.MediaTypeImageConfig } else { return images.MediaTypeDockerSchema2Config } } func (e *builderEngineBase) mediaTypeImageLayerGzip() string { if e.oci { return specs.MediaTypeImageLayerGzip } else { return images.MediaTypeDockerSchema2LayerGzip } } func (e *builderEngineBase) mediaTypeImageLayer() string { if e.oci { return specs.MediaTypeImageLayer } else { return images.MediaTypeDockerSchema2Layer } } func (e *builderEngineBase) uploadManifestAndConfig(ctx context.Context) (specs.Descriptor, error) { cbuf, err := json.Marshal(e.config) if err != nil { return specs.Descriptor{}, err } e.manifest.Config = specs.Descriptor{ MediaType: e.mediaTypeConfig(), Digest: digest.FromBytes(cbuf), Size: (int64)(len(cbuf)), } if !e.noUpload { if err = uploadBytes(ctx, e.pusher, e.manifest.Config, cbuf); err != nil { return specs.Descriptor{}, fmt.Errorf("failed to upload config: %w", err) } log.G(ctx).Infof("config uploaded") } if e.dumpManifest { confPath := path.Join(e.workDir, "config.json") if err := continuity.AtomicWriteFile(confPath, cbuf, 0644); err != nil { return specs.Descriptor{}, err } log.G(ctx).Infof("config dumped") } e.manifest.MediaType = e.mediaTypeManifest() cbuf, err = json.Marshal(e.manifest) if err != nil { return specs.Descriptor{}, err } manifestDesc := specs.Descriptor{ MediaType: e.mediaTypeManifest(), Digest: digest.FromBytes(cbuf), Size: (int64)(len(cbuf)), } if !e.noUpload { if err = uploadBytes(ctx, e.pusher, manifestDesc, cbuf); err != nil { return specs.Descriptor{}, fmt.Errorf("failed to upload manifest: %w", err) } e.outputDesc = manifestDesc log.G(ctx).Infof("manifest uploaded, %s", manifestDesc.Digest) } if e.dumpManifest { descPath := path.Join(e.workDir, "manifest.json") if err := continuity.AtomicWriteFile(descPath, cbuf, 0644); err != nil { return specs.Descriptor{}, err } log.G(ctx).Infof("manifest dumped") } return manifestDesc, nil } func getBuilderEngineBase(ctx context.Context, resolver remotes.Resolver, ref, targetRef string) (*builderEngineBase, error) { _, desc, err := resolver.Resolve(ctx, ref) if err != nil { return nil, fmt.Errorf("failed to resolve reference %q: %w", ref, err) } fetcher, err := resolver.Fetcher(ctx, ref) if err != nil { return nil, fmt.Errorf("failed to get fetcher for %q: %w", ref, err) } pusher, err := resolver.Pusher(ctx, targetRef) if err != nil { return nil, fmt.Errorf("failed to get pusher for %q: %w", targetRef, err) } manifest, config, err := fetchManifestAndConfig(ctx, fetcher, desc) if err != nil { return nil, fmt.Errorf("failed to fetch manifest and config: %w", err) } return &builderEngineBase{ resolver: resolver, fetcher: fetcher, pusher: pusher, manifest: *manifest, config: *config, inputDesc: desc, }, nil } accelerated-container-image-1.4.2/cmd/convertor/builder/builder_engine_test.go000066400000000000000000000101721514714641000275710ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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 builder import ( "context" "encoding/json" "fmt" "testing" testingresources "github.com/containerd/accelerated-container-image/cmd/convertor/testingresources" "github.com/containerd/containerd/v2/core/remotes" _ "github.com/containerd/containerd/v2/pkg/testutil" // Handle custom root flag "github.com/opencontainers/go-digest" specs "github.com/opencontainers/image-spec/specs-go/v1" ) func Test_builderEngineBase_isGzipLayer(t *testing.T) { ctx := context.Background() resolver := testingresources.GetTestResolver(t, ctx) type fields struct { fetcher remotes.Fetcher manifest specs.Manifest } getFields := func(ctx context.Context, ref string) fields { _, desc, err := resolver.Resolve(ctx, ref) if err != nil { t.Error(err) } fetcher, err := resolver.Fetcher(ctx, ref) if err != nil { t.Error(err) } manifestStream, err := fetcher.Fetch(ctx, desc) if err != nil { t.Error(err) } if err != nil { t.Error(err) } parsedManifest := specs.Manifest{} decoder := json.NewDecoder(manifestStream) if err = decoder.Decode(&parsedManifest); err != nil { t.Error(err) } return fields{ fetcher: fetcher, manifest: parsedManifest, } } type args struct { ctx context.Context idx int } tests := []struct { name string fields fields args args want bool wantErr bool }{ // TODO Add more layers types for validation // Unknown Layer Type // Uncompressed Layer Type { name: "Valid Gzip Layer", fields: getFields(ctx, testingresources.DockerV2_Manifest_Simple_Ref), args: args{ ctx: ctx, idx: 0, }, want: true, wantErr: false, }, { name: "Layer Not Found", fields: func() fields { fields := getFields(ctx, testingresources.DockerV2_Manifest_Simple_Ref) fields.manifest.Layers[0].Digest = digest.FromString("sample") return fields }(), args: args{ ctx: ctx, idx: 0, }, want: false, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { e := &builderEngineBase{ fetcher: tt.fields.fetcher, manifest: tt.fields.manifest, } got, err := e.isGzipLayer(tt.args.ctx, tt.args.idx) if (err != nil) != tt.wantErr { t.Errorf("builderEngineBase.isGzipLayer() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("builderEngineBase.isGzipLayer() = %v, want %v", got, tt.want) } }) } } func Test_getBuilderEngineBase(t *testing.T) { resolver := testingresources.GetTestResolver(t, context.Background()) engine, err := getBuilderEngineBase(context.TODO(), resolver, testingresources.DockerV2_Manifest_Simple_Ref, fmt.Sprintf("%s-obd", testingresources.DockerV2_Manifest_Simple_Ref), ) if err != nil { t.Error(err) } testingresources.Assert(t, engine.fetcher != nil, "Fetcher is nil") testingresources.Assert(t, engine.pusher != nil, "Pusher is nil") testingresources.Assert(t, engine.manifest.Config.Digest == testingresources.DockerV2_Manifest_Simple_Config_Digest, fmt.Sprintf("Config Digest is not equal to %s", testingresources.DockerV2_Manifest_Simple_Config_Digest)) content, err := testingresources.ConsistentManifestMarshal(&engine.manifest) if err != nil { t.Errorf("Could not parse obtained manifest, got: %v", err) } testingresources.Assert(t, digest.FromBytes(content) == testingresources.DockerV2_Manifest_Simple_Digest, fmt.Sprintf("Manifest Digest is not equal to %s", testingresources.DockerV2_Manifest_Simple_Digest)) } func Test_uploadManifestAndConfig(t *testing.T) { } accelerated-container-image-1.4.2/cmd/convertor/builder/builder_test.go000066400000000000000000000111521514714641000262430ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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 builder import ( "context" "fmt" "math/rand" "testing" "time" _ "github.com/containerd/containerd/v2/pkg/testutil" // Handle custom root flag specs "github.com/opencontainers/image-spec/specs-go/v1" ) // Test_builder_Err_Fuzz_Build This test is for the arguably complex error handling and potential go routine // locking that can happen for the builder component. It works by testing multiple potential error patterns // across all stages of the process (Through consistent pseudo random generation, for reproducibility and // ease of adjustment). The test is designed to run in parallel to maximize the chance of a contention. func Test_builder_Build_Contention(t *testing.T) { // If timeout of 1 second is exceeded with the mock fuz builder engine // then there is a high likelihood of a present contention error contentionTimeout := time.Second * 1 patternCount := int64(500) // Try out 500 different seeds var i int64 for i = 0; i < patternCount; i++ { seed := i t.Run(fmt.Sprintf("Test_builder_Err_Lock Contention Seed %d", i), func(t *testing.T) { t.Parallel() fixedRand := rand.New(rand.NewSource(seed)) builderEngine := newMockBuilderEngine(fixedRand) b := &overlaybdBuilder{ engine: builderEngine, layers: 25, } ctx, cancel := context.WithTimeout(context.Background(), contentionTimeout) defer cancel() b.Build(ctx) // Build will typically return an error but completes successfully for some seeds as well if ctx.Err() != nil { if ctx.Err() == context.DeadlineExceeded { t.Errorf("Context deadline was exceeded, likely contention error") } } }) } } const ( failRate = 0.05 // 5% of the time, fail any given operation ) type mockFuzzBuilderEngine struct { fixedRand *rand.Rand } func newMockBuilderEngine(fixedRand *rand.Rand) builderEngine { return &mockFuzzBuilderEngine{ fixedRand: fixedRand, } } func (e *mockFuzzBuilderEngine) DownloadLayer(ctx context.Context, idx int) error { if e.fixedRand.Float64() < failRate { return fmt.Errorf("random error on download") } return nil } func (e *mockFuzzBuilderEngine) BuildLayer(ctx context.Context, idx int) error { if e.fixedRand.Float64() < failRate { return fmt.Errorf("random error on BuildLayer") } return nil } func (e *mockFuzzBuilderEngine) UploadLayer(ctx context.Context, idx int) error { if e.fixedRand.Float64() < failRate { return fmt.Errorf("random error on UploadLayer") } return nil } func (e *mockFuzzBuilderEngine) UploadImage(ctx context.Context) (specs.Descriptor, error) { if e.fixedRand.Float64() < failRate { return specs.Descriptor{}, fmt.Errorf("random error on UploadImage") } return specs.Descriptor{}, nil } func (e *mockFuzzBuilderEngine) CheckForConvertedLayer(ctx context.Context, idx int) (specs.Descriptor, error) { if e.fixedRand.Float64() < failRate { return specs.Descriptor{}, fmt.Errorf("random error on CheckForConvertedLayer") } return specs.Descriptor{}, nil } func (e *mockFuzzBuilderEngine) StoreConvertedLayerDetails(ctx context.Context, idx int) error { if e.fixedRand.Float64() < failRate { return fmt.Errorf("random error on StoreConvertedLayerDetails") } return nil } func (e *mockFuzzBuilderEngine) CheckForConvertedManifest(ctx context.Context) (specs.Descriptor, error) { if e.fixedRand.Float64() < failRate { return specs.Descriptor{}, fmt.Errorf("random error on CheckForConvertedManifest") } return specs.Descriptor{}, nil } func (e *mockFuzzBuilderEngine) StoreConvertedManifestDetails(ctx context.Context) error { if e.fixedRand.Float64() < failRate { return fmt.Errorf("random error on StoreConvertedManifestDetails") } return nil } func (e *mockFuzzBuilderEngine) DownloadConvertedLayer(ctx context.Context, idx int, desc specs.Descriptor) error { if e.fixedRand.Float64() < failRate { return fmt.Errorf("random error on DownloadConvertedLayer") } return nil } func (e *mockFuzzBuilderEngine) TagPreviouslyConvertedManifest(ctx context.Context, desc specs.Descriptor) error { return nil } func (e *mockFuzzBuilderEngine) Cleanup() { } accelerated-container-image-1.4.2/cmd/convertor/builder/builder_utils.go000066400000000000000000000202641514714641000264300ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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 builder import ( "archive/tar" "bytes" "context" "crypto/sha256" "encoding/json" "errors" "fmt" "io" "os" "path" "github.com/containerd/containerd/v2/core/content" "github.com/containerd/containerd/v2/core/images" "github.com/containerd/containerd/v2/core/remotes" "github.com/containerd/containerd/v2/pkg/archive/compression" "github.com/containerd/continuity" "github.com/containerd/errdefs" "github.com/containerd/log" "github.com/containerd/platforms" "github.com/opencontainers/go-digest" specs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/sirupsen/logrus" t "github.com/containerd/accelerated-container-image/pkg/types" ) func fetch(ctx context.Context, fetcher remotes.Fetcher, desc specs.Descriptor, target any) error { rc, err := fetcher.Fetch(ctx, desc) if err != nil { return fmt.Errorf("failed to fetch digest %v: %w", desc.Digest, err) } defer func() { rc.Close() }() buf, err := io.ReadAll(rc) if err != nil { return fmt.Errorf("failed to read digest %v: %w", desc.Digest, err) } if err = json.Unmarshal(buf, target); err != nil { return fmt.Errorf("failed to unmarshal digest %v: %w", desc.Digest, err) } return nil } func fetchManifest(ctx context.Context, fetcher remotes.Fetcher, desc specs.Descriptor) (*specs.Manifest, error) { platformMatcher := platforms.Default() log.G(ctx).Infof("fetching manifest %v with type %v", desc.Digest, desc.MediaType) switch desc.MediaType { case images.MediaTypeDockerSchema2Manifest, specs.MediaTypeImageManifest: manifest := specs.Manifest{} if err := fetch(ctx, fetcher, desc, &manifest); err != nil { return nil, fmt.Errorf("failed to fetch manifest: %w", err) } return &manifest, nil case images.MediaTypeDockerSchema2ManifestList, specs.MediaTypeImageIndex: var target *specs.Descriptor manifestList := specs.Index{} if err := fetch(ctx, fetcher, desc, &manifestList); err != nil { return nil, fmt.Errorf("failed to fetch manifest list: %w", err) } for _, manifest := range manifestList.Manifests { if platformMatcher.Match(*manifest.Platform) { target = &manifest break } } if target == nil { return nil, fmt.Errorf("no match platform found in manifest list") } else { return fetchManifest(ctx, fetcher, *target) } default: return nil, fmt.Errorf("non manifest type digest fetched") } } func fetchConfig(ctx context.Context, fetcher remotes.Fetcher, desc specs.Descriptor) (*specs.Image, error) { config := specs.Image{} if err := fetch(ctx, fetcher, desc, &config); err != nil { return nil, fmt.Errorf("failed to fetch config: %w", err) } return &config, nil } func fetchManifestAndConfig(ctx context.Context, fetcher remotes.Fetcher, desc specs.Descriptor) (*specs.Manifest, *specs.Image, error) { var manifest *specs.Manifest var config *specs.Image manifest, err := fetchManifest(ctx, fetcher, desc) if err != nil { return nil, nil, fmt.Errorf("builder: failed to fetch manifest: %w", err) } config, err = fetchConfig(ctx, fetcher, manifest.Config) if err != nil { return nil, nil, fmt.Errorf("builder: failed to fetch config: %w", err) } return manifest, config, nil } func downloadLayer(ctx context.Context, fetcher remotes.Fetcher, targetFile string, desc specs.Descriptor, decompress bool) error { rcoriginal, err := fetcher.Fetch(ctx, desc) if err != nil { return err } verifier := desc.Digest.Verifier() // tee the reader to verify the digest // this is because the decompression result // will be different from the original for which // the digest is calculated. rc := io.TeeReader(rcoriginal, verifier) dir := path.Dir(targetFile) if err := os.MkdirAll(dir, 0755); err != nil { return err } ftar, err := os.Create(targetFile) if err != nil { return err } if decompress { rc, err = compression.DecompressStream(rc) if err != nil { return err } } if _, err = io.Copy(ftar, rc); err != nil { return err } if !verifier.Verified() { return fmt.Errorf("failed to verify digest %v", desc.Digest) } return nil } // TODO maybe refactor this func writeConfig(dir string, configJSON *t.OverlayBDBSConfig) error { data, err := json.Marshal(configJSON) if err != nil { return err } confPath := path.Join(dir, "config.json") if err := continuity.AtomicWriteFile(confPath, data, 0600); err != nil { return err } return nil } func getFileDesc(filepath string, decompress bool) (specs.Descriptor, error) { file, err := os.Open(filepath) if err != nil { return specs.Descriptor{}, err } defer file.Close() var rc io.ReadCloser if decompress { rc, err = compression.DecompressStream(file) if err != nil { return specs.Descriptor{}, err } } else { rc = file } h := sha256.New() size, err := io.Copy(h, rc) if err != nil { return specs.Descriptor{}, err } dgst := digest.NewDigest(digest.SHA256, h) return specs.Descriptor{ Digest: dgst, Size: size, }, nil } func uploadBlob(ctx context.Context, pusher remotes.Pusher, path string, desc specs.Descriptor) error { cw, err := pusher.Push(ctx, desc) if err != nil { if errdefs.IsAlreadyExists(err) { logrus.Infof("layer %s exists", desc.Digest.String()) return nil } return err } defer cw.Close() fobd, err := os.Open(path) if err != nil { return err } defer fobd.Close() if err = content.Copy(ctx, cw, fobd, desc.Size, desc.Digest); err != nil { return err } return nil } func uploadBytes(ctx context.Context, pusher remotes.Pusher, desc specs.Descriptor, data []byte) error { cw, err := pusher.Push(ctx, desc) if err != nil { if errdefs.IsAlreadyExists(err) { logrus.Infof("content %s exists", desc.Digest.String()) return nil } return err } defer cw.Close() return content.Copy(ctx, cw, bytes.NewReader(data), desc.Size, desc.Digest) } func tagPreviouslyConvertedManifest(ctx context.Context, pusher remotes.Pusher, fetcher remotes.Fetcher, desc specs.Descriptor) error { manifest := specs.Manifest{} if err := fetch(ctx, fetcher, desc, &manifest); err != nil { return fmt.Errorf("failed to fetch converted manifest: %w", err) } cbuf, err := json.Marshal(manifest) if err != nil { return err } if err := uploadBytes(ctx, pusher, desc, cbuf); err != nil { return fmt.Errorf("failed to tag converted manifest: %w", err) } return nil } func buildArchiveFromFiles(ctx context.Context, target string, compress compression.Compression, files ...string) error { archive, err := os.Create(target) if err != nil { return fmt.Errorf("failed to create tgz file: %q: %w", target, err) } defer archive.Close() fzip, err := compression.CompressStream(archive, compress) if err != nil { return fmt.Errorf("failed to create compression %v: %w", compress, err) } defer fzip.Close() ftar := tar.NewWriter(fzip) defer ftar.Close() for _, file := range files { if err := addFileToArchive(ctx, ftar, file); err != nil { return fmt.Errorf("failed to add file %q to archive %q: %w", file, target, err) } } return nil } func addFileToArchive(ctx context.Context, ftar *tar.Writer, filepath string) error { file, err := os.Open(filepath) if err != nil { if errors.Is(err, os.ErrNotExist) { return nil } return fmt.Errorf("failed to open file: %q: %w", filepath, err) } defer file.Close() info, err := file.Stat() if err != nil { return err } header, err := tar.FileInfoHeader(info, info.Name()) if err != nil { return err } // remove timestamp for consistency if err = ftar.WriteHeader(&tar.Header{ Name: header.Name, Mode: header.Mode, Size: header.Size, Typeflag: header.Typeflag, }); err != nil { return err } _, err = io.Copy(ftar, file) if err != nil { return err } return nil } accelerated-container-image-1.4.2/cmd/convertor/builder/builder_utils_test.go000066400000000000000000000376341514714641000275000ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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 builder import ( "compress/gzip" "context" "encoding/json" "fmt" "io" "os" "path" "reflect" "testing" testingresources "github.com/containerd/accelerated-container-image/cmd/convertor/testingresources" sn "github.com/containerd/accelerated-container-image/pkg/types" "github.com/containerd/containerd/v2/core/images" "github.com/containerd/containerd/v2/core/remotes" _ "github.com/containerd/containerd/v2/pkg/testutil" // Handle custom root flag "github.com/opencontainers/go-digest" v1 "github.com/opencontainers/image-spec/specs-go/v1" ) func Test_fetchManifest(t *testing.T) { ctx := context.Background() resolver := testingresources.GetTestResolver(t, ctx) _, desc, _ := resolver.Resolve(ctx, testingresources.Docker_Manifest_List_Ref) fmt.Println(desc) type args struct { ctx context.Context fetcher remotes.Fetcher desc v1.Descriptor } tests := []struct { name string args args want *v1.Manifest wantErr bool wantSubDesc v1.Descriptor }{ { name: "Fetch existing manifest", args: args{ fetcher: testingresources.GetTestFetcherFromResolver(t, ctx, resolver, testingresources.DockerV2_Manifest_Simple_Ref), desc: v1.Descriptor{ MediaType: images.MediaTypeDockerSchema2Manifest, Digest: testingresources.DockerV2_Manifest_Simple_Digest, Size: testingresources.DockerV2_Manifest_Simple_Size, }, ctx: ctx, }, wantErr: false, }, { name: "Fetch manifest List", args: args{ fetcher: testingresources.GetTestFetcherFromResolver(t, ctx, resolver, testingresources.Docker_Manifest_List_Ref), desc: v1.Descriptor{ MediaType: images.MediaTypeDockerSchema2ManifestList, Digest: testingresources.Docker_Manifest_List_Digest, Size: 2069, }, ctx: ctx, }, // The manifest list is expected to select the first manifest that can be converted // in the list, for this image that is the very first one. wantSubDesc: v1.Descriptor{ MediaType: images.MediaTypeDockerSchema2Manifest, Digest: testingresources.DockerV2_Manifest_Simple_Digest, Size: 525, Platform: &v1.Platform{ Architecture: "amd64", OS: "linux", }, }, wantErr: false, }, { name: "Fetch unknown manifest errors", args: args{ fetcher: testingresources.GetTestFetcherFromResolver(t, ctx, resolver, "sample.localstore.io/hello-world:idontexist"), desc: v1.Descriptor{ MediaType: images.MediaTypeDockerSchema2Manifest, Digest: "sha256:82c7f9c92844bbbb5d0a101b12f7c2a7949e40f8ee90c8b3bc396879d95e899a", Size: 524, }, ctx: ctx, }, wantErr: true, }, { name: "Fetch invalid digest", args: args{ fetcher: testingresources.GetTestFetcherFromResolver(t, ctx, resolver, testingresources.DockerV2_Manifest_Simple_Ref), desc: v1.Descriptor{ MediaType: images.MediaTypeDockerSchema2Manifest, Digest: "sha256:829d95e899a", Size: 524, }, ctx: ctx, }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { manifest, err := fetchManifest(tt.args.ctx, tt.args.fetcher, tt.args.desc) if (err == nil) && tt.wantErr { t.Error("fetchManifest() error was expected but no error was returned") } if err != nil { if !tt.wantErr { t.Errorf("fetchManifest() unexpectedly returned error %v", err) } return } content, err := testingresources.ConsistentManifestMarshal(manifest) if err != nil { t.Errorf("Could not parse obtained manifest, got: %v", err) } contentDigest := digest.FromBytes(content) if tt.args.desc.MediaType != images.MediaTypeDockerSchema2ManifestList && tt.args.desc.MediaType != v1.MediaTypeImageIndex { if tt.args.desc.Digest != contentDigest { t.Errorf("fetchManifest() = %v, want %v", manifest, tt.want) } } else { if tt.wantSubDesc.Digest != contentDigest { t.Errorf("fetchManifest() = %v, want %v", manifest, tt.want) } } }) } } func Test_fetchConfig(t *testing.T) { ctx := context.Background() resolver := testingresources.GetTestResolver(t, ctx) type args struct { ctx context.Context fetcher remotes.Fetcher desc v1.Descriptor } tests := []struct { name string args args want *v1.Image wantErr bool }{ // TODO: "Fetch Config with supported mediaType (oci)", { name: "Fetch Config with supported mediaType (docker v2)", args: args{ fetcher: testingresources.GetTestFetcherFromResolver(t, ctx, resolver, testingresources.DockerV2_Manifest_Simple_Ref), desc: v1.Descriptor{ MediaType: images.MediaTypeDockerSchema2Config, Digest: testingresources.DockerV2_Manifest_Simple_Config_Digest, Size: testingresources.DockerV2_Manifest_Simple_Config_Size, Platform: &v1.Platform{ Architecture: "amd64", OS: "linux", }, }, ctx: ctx, }, wantErr: false, }, { name: "Fetch unknown config", args: args{ fetcher: testingresources.GetTestFetcherFromResolver(t, ctx, resolver, testingresources.DockerV2_Manifest_Simple_Ref), desc: v1.Descriptor{ MediaType: images.MediaTypeDockerSchema1Manifest, Digest: "sha256:82c7f9c92844bbab5d0a101b12f7c2a7949e40f8ee90c8b3bc396879d95e899a", Size: 524, }, ctx: ctx, }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := fetchConfig(tt.args.ctx, tt.args.fetcher, tt.args.desc) if (err == nil) && tt.wantErr { t.Error("fetchConfig() error was expected but no error was returned") } if err != nil { if !tt.wantErr { t.Errorf("fetchConfig() unexpectedly returned error %v", err) } return } if got.Architecture != tt.args.desc.Platform.Architecture || got.OS != tt.args.desc.Platform.OS { t.Errorf("fetchConfig() config is not as expected") } if len(got.RootFS.DiffIDs) == 0 { t.Errorf("fetchConfig() Expected some DiffIds") } if len(got.History) == 0 { t.Errorf("fetchConfig() Expected layer history") } }) } } func Test_uploadBytes(t *testing.T) { ctx := context.Background() sourceManifest := testingresources.DockerV2_Manifest_Simple_Ref targetManifest := "sample.localstore.io/hello-world:another" resolver := testingresources.GetTestResolver(t, ctx) _, desc, err := resolver.Resolve(ctx, sourceManifest) if err != nil { t.Error(err) } fetcher := testingresources.GetTestFetcherFromResolver(t, ctx, resolver, sourceManifest) pusher := testingresources.GetTestPusherFromResolver(t, ctx, resolver, targetManifest) // Load manifest content, err := fetcher.Fetch(ctx, desc) if err != nil { t.Error(err) } test_uploadBytes := func(manifest v1.Manifest, pusher remotes.Pusher) error { manifestBytes, err := testingresources.ConsistentManifestMarshal(&manifest) if err != nil { return err } newDesc := v1.Descriptor{ MediaType: images.MediaTypeDockerSchema2Manifest, Digest: digest.FromBytes(manifestBytes), Size: int64(len(manifestBytes)), } err = uploadBytes(ctx, pusher, newDesc, manifestBytes) if err != nil { return err } return nil } // Docker v2 manifest manifest := v1.Manifest{} json.NewDecoder(content).Decode(&manifest) // Re-Push Manifest error should be handled testingresources.Assert(t, test_uploadBytes(manifest, testingresources.GetTestPusherFromResolver(t, ctx, resolver, sourceManifest)) == nil, "Could not upload Re upload Docker v2 Manifest with layers present") // Docker v2 manifest // Modify manifest to change digest manifest.Annotations = map[string]string{ "test": "test", } testingresources.Assert(t, test_uploadBytes(manifest, pusher) == nil, "Could not upload Docker v2 Manifest with layers present") // Docker v2 manifest // OCI manifest manifest.MediaType = v1.MediaTypeImageManifest for i := range manifest.Layers { manifest.Layers[i].MediaType = v1.MediaTypeImageLayerGzip } testingresources.Assert(t, test_uploadBytes(manifest, pusher) == nil, "Could not upload OCI Manifest with layers present") // Docker v2 manifest // Missing layer manifest.Layers[0].Digest = digest.FromString("not there") testingresources.Assert(t, test_uploadBytes(manifest, pusher) != nil, "Expected layer not found error") // Docker v2 manifest } func Test_uploadBlob(t *testing.T) { ctx := context.Background() // Create a new inmemory registry to push to reg := testingresources.GetTestRegistry(t, ctx, testingresources.RegistryOptions{ InmemoryRegistryOnly: true, ManifestPushIgnoresLayers: false, }) resolver := testingresources.GetCustomTestResolver(t, ctx, reg) pusher := testingresources.GetTestPusherFromResolver(t, ctx, resolver, "sample.localstore.io/hello-world:latest") blobPath := path.Join(testingresources.GetLocalRegistryPath(), "hello-world", "blobs", "sha256", digest.Digest(testingresources.DockerV2_Manifest_Simple_Layer_0_Digest).Encoded()) desc := v1.Descriptor{ MediaType: images.MediaTypeDockerSchema2LayerGzip, Digest: testingresources.DockerV2_Manifest_Simple_Layer_0_Digest, Size: testingresources.DockerV2_Manifest_Simple_Layer_0_Size, } testingresources.Assert(t, uploadBlob(ctx, pusher, blobPath, desc) == nil, "uploadBlob() expected no error but got one") // Uploads already present shuld give no issues testingresources.Assert(t, uploadBlob(ctx, pusher, blobPath, desc) == nil, "uploadBlob() retry expected no error but got one") // Validate manifest of pushed blob fetcher := testingresources.GetTestFetcherFromResolver(t, ctx, resolver, "sample.localstore.io/hello-world:latest") blob, err := fetcher.Fetch(ctx, desc) if err != nil { t.Error(err) } blobDigest, err := digest.FromReader(blob) if err != nil { t.Error(err) } testingresources.Assert(t, blobDigest == desc.Digest, "uploadBlob() blob digest does not match stored value") } func Test_getFileDesc(t *testing.T) { test_getFileDesc := func(blobPath string, compressed bool, expectedDigest string, expectedSize int64) { desc, err := getFileDesc(blobPath, compressed) if err != nil { t.Error(err) } testingresources.Assert(t, desc.Digest.String() == expectedDigest, "getFileDesc() wrong digest returned") testingresources.Assert(t, desc.Size == expectedSize, "getFileDesc() wrong size returned") } blobPath := path.Join(testingresources.GetLocalRegistryPath(), "hello-world", "blobs", "sha256") // Compressed blob test_getFileDesc( path.Join(blobPath, digest.Digest(testingresources.DockerV2_Manifest_Simple_Layer_0_Digest).Encoded()), false, testingresources.DockerV2_Manifest_Simple_Layer_0_Digest, testingresources.DockerV2_Manifest_Simple_Layer_0_Size) // Uncompressed blob test_getFileDesc( path.Join(blobPath, digest.Digest(testingresources.DockerV2_Manifest_Simple_Config_Digest).Encoded()), false, testingresources.DockerV2_Manifest_Simple_Config_Digest, testingresources.DockerV2_Manifest_Simple_Config_Size) } func Test_downloadLayer(t *testing.T) { ctx := context.Background() testDownloadLayer := func(t *testing.T, ctx context.Context, testName string, sourceDesc v1.Descriptor, decompress bool) { testingresources.RunTestWithTempDir(t, ctx, testName, func(t *testing.T, ctx context.Context, workdir string) { resolver := testingresources.GetTestResolver(t, ctx) fetcher := testingresources.GetTestFetcherFromResolver(t, ctx, resolver, testingresources.DockerV2_Manifest_Simple_Ref) layerPath := path.Join(workdir, "layer.tar") err := downloadLayer(ctx, fetcher, layerPath, sourceDesc, decompress) if err != nil { t.Error(err) } _, err = os.Stat(layerPath) if err != nil { t.Errorf("Expected layer file to exist") } var outputDesc v1.Descriptor if !decompress { outputDesc, err = getFileDesc(layerPath, !decompress) if err != nil { t.Error(err) } } else { // Compress again to verify digest file, err := os.Open(layerPath) if err != nil { t.Error(err) } defer file.Close() r, w := io.Pipe() defer r.Close() gzWriter := gzip.NewWriter(w) defer w.Close() defer gzWriter.Close() go func() { _, err := io.Copy(gzWriter, file) defer gzWriter.Close() if err != nil { t.Error(err) } }() data := make([]byte, sourceDesc.Size) _, err = io.ReadFull(r, data) if err != nil { t.Error(err) } outputDesc = v1.Descriptor{ Digest: digest.FromBytes(data), Size: int64(len(data)), } } testingresources.Assert(t, outputDesc.Digest == sourceDesc.Digest, "downloadLayer() wrong digest returned") testingresources.Assert(t, outputDesc.Size == sourceDesc.Size, "downloadLayer() wrong size returned") }) } testDownloadLayer(t, ctx, "downloadGzippedLayer", v1.Descriptor{ MediaType: images.MediaTypeDockerSchema2LayerGzip, Digest: testingresources.DockerV2_Manifest_Simple_Layer_0_Digest, Size: testingresources.DockerV2_Manifest_Simple_Layer_0_Size, }, true) testDownloadLayer(t, ctx, "downloadLayer", v1.Descriptor{ MediaType: images.MediaTypeDockerSchema2Config, Digest: testingresources.DockerV2_Manifest_Simple_Config_Digest, Size: testingresources.DockerV2_Manifest_Simple_Config_Size, }, false) } func Test_writeConfig(t *testing.T) { ctx := context.Background() testingresources.RunTestWithTempDir(t, ctx, "writeConfigMinimal", func(t *testing.T, ctx context.Context, workdir string) { configSample := sn.OverlayBDBSConfig{ ResultFile: "", Lowers: []sn.OverlayBDBSConfigLower{ { File: overlaybdBaseLayer, }, { File: path.Join(workdir, commitFile), }, }, Upper: sn.OverlayBDBSConfigUpper{ Data: path.Join(workdir, "writable_data"), Index: path.Join(workdir, "writable_index"), }, } err := writeConfig(workdir, &configSample) if err != nil { t.Error(err) } file, err := os.Open(path.Join(workdir, "config.json")) if err != nil { t.Errorf("Expected layer file to exist") } defer file.Close() configRes := sn.OverlayBDBSConfig{} err = json.NewDecoder(file).Decode(&configRes) if err != nil { t.Error(err) } if !reflect.DeepEqual(configSample, configRes) { t.Errorf("Input config and output config are not equal, wanted: %+v \n got: %+v", configSample, configRes) } }) } func Test_tagPreviouslyConvertedManifest(t *testing.T) { ctx := context.Background() resolver := testingresources.GetTestResolver(t, ctx) pusher := testingresources.GetTestPusherFromResolver(t, ctx, resolver, "sample.localstore.io/hello-world:anothertag") fetcher := testingresources.GetTestFetcherFromResolver(t, ctx, resolver, testingresources.DockerV2_Manifest_Simple_Converted_Ref) _, convertedDesc, err := resolver.Resolve(ctx, testingresources.DockerV2_Manifest_Simple_Converted_Ref) // Simulate a previously converted manifest testingresources.Assert(t, err == nil, "Could not resolve manifest") convertedDesc.Annotations = map[string]string{} // Simulate a manifest that has been converted and is found by digest err = tagPreviouslyConvertedManifest(ctx, pusher, fetcher, convertedDesc) testingresources.Assert(t, err == nil, "Could not tag previously converted manifest") // Check if the manifest was tagged correctly _, desc, err := resolver.Resolve(ctx, "sample.localstore.io/hello-world:anothertag") testingresources.Assert(t, err == nil, "Could not resolve tagged manifest") testingresources.Assert(t, desc.Digest == convertedDesc.Digest, "Tagged manifest digest does not match original") } accelerated-container-image-1.4.2/cmd/convertor/builder/overlaybd_builder.go000066400000000000000000000431001514714641000272510ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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 builder import ( "archive/tar" "bufio" "context" "encoding/json" "errors" "fmt" "io" "os" "path" "github.com/containerd/accelerated-container-image/pkg/label" sn "github.com/containerd/accelerated-container-image/pkg/types" "github.com/containerd/accelerated-container-image/pkg/utils" "github.com/containerd/accelerated-container-image/pkg/version" "github.com/containerd/errdefs" "github.com/opencontainers/go-digest" "github.com/opencontainers/image-spec/identity" specs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/sirupsen/logrus" ) const ( overlaybdBaseLayer = "/opt/overlaybd/baselayers/ext4_64" commitFile = "overlaybd.commit" labelDistributionSource = "containerd.io/distribution.source" ) type overlaybdConvertResult struct { desc specs.Descriptor chainID string fromDedup bool } type overlaybdBuilderEngine struct { *builderEngineBase disableSparse bool overlaybdConfig *sn.OverlayBDBSConfig overlaybdLayers []overlaybdConvertResult } func NewOverlayBDBuilderEngine(base *builderEngineBase) builderEngine { config := &sn.OverlayBDBSConfig{ Lowers: []sn.OverlayBDBSConfigLower{}, ResultFile: "", } if !base.mkfs { config.Lowers = append(config.Lowers, sn.OverlayBDBSConfigLower{ File: overlaybdBaseLayer, }) logrus.Infof("using default baselayer") } overlaybdLayers := make([]overlaybdConvertResult, len(base.manifest.Layers)) var chain []digest.Digest srcDiffIDs := base.config.RootFS.DiffIDs for i := 0; i < len(base.manifest.Layers); i++ { chain = append(chain, srcDiffIDs[i]) chainID := identity.ChainID(chain).String() overlaybdLayers[i].chainID = chainID } return &overlaybdBuilderEngine{ builderEngineBase: base, overlaybdConfig: config, overlaybdLayers: overlaybdLayers, } } func (e *overlaybdBuilderEngine) DownloadLayer(ctx context.Context, idx int) error { desc := e.manifest.Layers[idx] targetFile := path.Join(e.getLayerDir(idx), "layer.tar") return downloadLayer(ctx, e.fetcher, targetFile, desc, true) } func (e *overlaybdBuilderEngine) BuildLayer(ctx context.Context, idx int) error { layerDir := e.getLayerDir(idx) // If the layer is from dedup we should have a downloaded commit file commitFilePresent := false if _, err := os.Stat(path.Join(layerDir, commitFile)); err != nil { if !os.IsNotExist(err) { return fmt.Errorf("failed to check if layer %d is already present abort conversion: %w", idx, err) } // commit file is not present } else { commitFilePresent = true logrus.Debugf("layer %d commit file detected", idx) } if e.overlaybdLayers[idx].fromDedup { // check if the previously converted layer is present if commitFilePresent { logrus.Debugf("layer %d is from dedup", idx) } else { return fmt.Errorf("layer %d is from dedup but commit file is missing", idx) } } else { // This should not happen, but if it does, we should fail the conversion or // risk corrupting the image. if commitFilePresent { return fmt.Errorf("layer %d is not from dedup but commit file is present", idx) } mkfs := e.mkfs && (idx == 0) vsizeGB := 0 if idx == 0 { if mkfs { vsizeGB = e.vsize } else { vsizeGB = 64 // in case that using default baselayer } } if err := e.create(ctx, layerDir, mkfs, vsizeGB); err != nil { return err } e.overlaybdConfig.Upper = sn.OverlayBDBSConfigUpper{ Data: path.Join(layerDir, "writable_data"), Index: path.Join(layerDir, "writable_index"), } if err := writeConfig(layerDir, e.overlaybdConfig); err != nil { return err } if err := e.apply(ctx, layerDir); err != nil { return err } if err := e.commit(ctx, layerDir, idx); err != nil { return err } if !e.reserve { os.Remove(path.Join(layerDir, "layer.tar")) os.Remove(path.Join(layerDir, "writable_data")) os.Remove(path.Join(layerDir, "writable_index")) } } e.overlaybdConfig.Lowers = append(e.overlaybdConfig.Lowers, sn.OverlayBDBSConfigLower{ File: path.Join(layerDir, commitFile), }) return nil } func (e *overlaybdBuilderEngine) UploadLayer(ctx context.Context, idx int) error { layerDir := e.getLayerDir(idx) desc, err := getFileDesc(path.Join(layerDir, commitFile), false) if err != nil { return fmt.Errorf("failed to get descriptor for layer %d: %w", idx, err) } if e.overlaybdLayers[idx].fromDedup { // validate that the layer digests match if the layer is from dedup if desc.Digest != e.overlaybdLayers[idx].desc.Digest { return fmt.Errorf("layer %d digest mismatch, expected %s, got %s", idx, e.overlaybdLayers[idx].desc.Digest, desc.Digest) } } desc.MediaType = e.mediaTypeImageLayer() desc.Annotations = map[string]string{ label.OverlayBDVersion: version.OverlayBDVersionNumber, label.OverlayBDBlobDigest: desc.Digest.String(), label.OverlayBDBlobSize: fmt.Sprintf("%d", desc.Size), } if !e.noUpload { if err := uploadBlob(ctx, e.pusher, path.Join(layerDir, commitFile), desc); err != nil { return fmt.Errorf("failed to upload layer %d: %w", idx, err) } } e.overlaybdLayers[idx].desc = desc return nil } func (e *overlaybdBuilderEngine) UploadImage(ctx context.Context) (specs.Descriptor, error) { for idx := range e.manifest.Layers { e.manifest.Layers[idx] = e.overlaybdLayers[idx].desc e.config.RootFS.DiffIDs[idx] = e.overlaybdLayers[idx].desc.Digest } if !e.mkfs { baseDesc, err := e.uploadBaseLayer(ctx) if err != nil { return specs.Descriptor{}, err } e.manifest.Layers = append([]specs.Descriptor{baseDesc}, e.manifest.Layers...) e.config.RootFS.DiffIDs = append([]digest.Digest{baseDesc.Digest}, e.config.RootFS.DiffIDs...) } if e.referrer { e.manifest.ArtifactType = ArtifactTypeOverlaybd e.manifest.Subject = &specs.Descriptor{ MediaType: e.inputDesc.MediaType, Digest: e.inputDesc.Digest, Size: e.inputDesc.Size, } } return e.uploadManifestAndConfig(ctx) } func (e *overlaybdBuilderEngine) CheckForConvertedLayer(ctx context.Context, idx int) (specs.Descriptor, error) { if e.db == nil { return specs.Descriptor{}, errdefs.ErrNotFound } chainID := e.overlaybdLayers[idx].chainID // try to find the layer in the target repo entry := e.db.GetLayerEntryForRepo(ctx, e.host, e.repository, chainID) if entry != nil && entry.ChainID != "" { desc := specs.Descriptor{ MediaType: e.mediaTypeImageLayer(), Digest: entry.ConvertedDigest, Size: entry.DataSize, } rc, err := e.fetcher.Fetch(ctx, desc) if err == nil { rc.Close() logrus.Infof("layer %d found in remote with chainID %s", idx, chainID) return desc, nil } if errdefs.IsNotFound(err) { // invalid record in db, which is not found in registry, remove it err := e.db.DeleteLayerEntry(ctx, e.host, e.repository, chainID) if err != nil { return specs.Descriptor{}, err } } } // fallback to a registry wide search // found record in other repos, try mounting it to the target repo entries := e.db.GetCrossRepoLayerEntries(ctx, e.host, chainID) for _, entry := range entries { desc := specs.Descriptor{ MediaType: e.mediaTypeImageLayer(), Digest: entry.ConvertedDigest, Size: entry.DataSize, Annotations: map[string]string{ fmt.Sprintf("%s.%s", labelDistributionSource, e.host): entry.Repository, }, } _, err := e.pusher.Push(ctx, desc) if errdefs.IsAlreadyExists(err) { desc.Annotations = nil if err := e.db.CreateLayerEntry(ctx, e.host, e.repository, entry.ConvertedDigest, chainID, entry.DataSize); err != nil { continue // try a different repo if available } logrus.Infof("layer %d mount from %s was successful", idx, entry.Repository) logrus.Infof("layer %d found in remote with chainID %s", idx, chainID) return desc, nil } } logrus.Infof("layer %d not found in remote", idx) return specs.Descriptor{}, errdefs.ErrNotFound } // If manifest is already converted, avoid conversion. (e.g During tag reuse or cross repo mounts) // Note: This is output mediatype sensitive, if the manifest is converted to a different mediatype, // we will still convert it normally. func (e *overlaybdBuilderEngine) CheckForConvertedManifest(ctx context.Context) (specs.Descriptor, error) { if e.db == nil { return specs.Descriptor{}, errdefs.ErrNotFound } // try to find the manifest in the target repo entry := e.db.GetManifestEntryForRepo(ctx, e.host, e.repository, e.mediaTypeManifest(), e.inputDesc.Digest) if entry != nil && entry.ConvertedDigest != "" { convertedDesc := specs.Descriptor{ MediaType: e.mediaTypeManifest(), Digest: entry.ConvertedDigest, Size: entry.DataSize, } rc, err := e.fetcher.Fetch(ctx, convertedDesc) if err == nil { rc.Close() logrus.Infof("manifest %s found in remote with resulting digest %s", e.inputDesc.Digest, convertedDesc.Digest) return convertedDesc, nil } if errdefs.IsNotFound(err) { // invalid record in db, which is not found in registry, remove it err := e.db.DeleteManifestEntry(ctx, e.host, e.repository, e.mediaTypeManifest(), e.inputDesc.Digest) if err != nil { return specs.Descriptor{}, err } } } // fallback to a registry wide search entries := e.db.GetCrossRepoManifestEntries(ctx, e.host, e.mediaTypeManifest(), e.inputDesc.Digest) for _, entry := range entries { convertedDesc := specs.Descriptor{ MediaType: e.mediaTypeManifest(), Digest: entry.ConvertedDigest, Size: entry.DataSize, } fetcher, err := e.resolver.Fetcher(ctx, fmt.Sprintf("%s/%s@%s", entry.Host, entry.Repository, convertedDesc.Digest.String())) if err != nil { return specs.Descriptor{}, err } manifest, err := fetchManifest(ctx, fetcher, convertedDesc) if err != nil { if errdefs.IsNotFound(err) { // invalid record in db, which is not found in registry, remove it err := e.db.DeleteManifestEntry(ctx, entry.Host, entry.Repository, e.mediaTypeManifest(), e.inputDesc.Digest) if err != nil { return specs.Descriptor{}, err } } continue } if err := e.mountImage(ctx, *manifest, convertedDesc, entry.Repository); err != nil { continue // try a different repo if available } if err := e.db.CreateManifestEntry(ctx, e.host, e.repository, e.mediaTypeManifest(), e.inputDesc.Digest, convertedDesc.Digest, entry.DataSize); err != nil { continue // try a different repo if available } logrus.Infof("manifest %s mount from %s was successful", convertedDesc.Digest, entry.Repository) return convertedDesc, nil } logrus.Infof("manifest %s not found already converted in remote", e.inputDesc.Digest) return specs.Descriptor{}, errdefs.ErrNotFound } // If a converted manifest has been found we still need to tag it to match the expected output tag. func (e *overlaybdBuilderEngine) TagPreviouslyConvertedManifest(ctx context.Context, desc specs.Descriptor) error { return tagPreviouslyConvertedManifest(ctx, e.pusher, e.fetcher, desc) } // mountImage is responsible for mounting a specific manifest from a source repository, this includes // mounting all layers + config and then pushing the manifest. func (e *overlaybdBuilderEngine) mountImage(ctx context.Context, manifest specs.Manifest, desc specs.Descriptor, mountRepository string) error { // Mount Config Blobs config := manifest.Config config.Annotations = map[string]string{ fmt.Sprintf("%s.%s", labelDistributionSource, e.host): mountRepository, } _, err := e.pusher.Push(ctx, config) if errdefs.IsAlreadyExists(err) { logrus.Infof("config blob mount from %s was successful", mountRepository) } else if err != nil { return fmt.Errorf("failed to mount config blob from %s repository : %w", mountRepository, err) } // Mount Layer Blobs for idx, layer := range manifest.Layers { desc := layer desc.Annotations = map[string]string{ fmt.Sprintf("%s.%s", labelDistributionSource, e.host): mountRepository, } _, err := e.pusher.Push(ctx, desc) if errdefs.IsAlreadyExists(err) { logrus.Infof("layer %d mount from %s was successful", idx, mountRepository) } else if err != nil { return fmt.Errorf("failed to mount all layers from %s repository : %w", mountRepository, err) } } // Push Manifest cbuf, err := json.Marshal(manifest) if err != nil { return err } return uploadBytes(ctx, e.pusher, desc, cbuf) } func (e *overlaybdBuilderEngine) StoreConvertedManifestDetails(ctx context.Context) error { if e.db == nil { return nil } if e.outputDesc.Digest == "" { return errors.New("manifest is not yet converted") } return e.db.CreateManifestEntry(ctx, e.host, e.repository, e.mediaTypeManifest(), e.inputDesc.Digest, e.outputDesc.Digest, e.outputDesc.Size) } func (e *overlaybdBuilderEngine) StoreConvertedLayerDetails(ctx context.Context, idx int) error { if e.db == nil { return nil } if e.overlaybdLayers[idx].fromDedup { logrus.Infof("layer %d skip storing conversion details", idx) return nil } return e.db.CreateLayerEntry(ctx, e.host, e.repository, e.overlaybdLayers[idx].desc.Digest, e.overlaybdLayers[idx].chainID, e.overlaybdLayers[idx].desc.Size) } func (e *overlaybdBuilderEngine) DownloadConvertedLayer(ctx context.Context, idx int, desc specs.Descriptor) error { targetFile := path.Join(e.getLayerDir(idx), commitFile) err := downloadLayer(ctx, e.fetcher, targetFile, desc, true) if err != nil { // We should remove the commit file if the download failed to allow for fallback conversion os.Remove(targetFile) // Remove any file that may have failed to download return fmt.Errorf("failed to download layer %d: %w", idx, err) } // Mark that this layer is from dedup e.overlaybdLayers[idx].fromDedup = true e.overlaybdLayers[idx].desc = desc // If we are deduping store the dedup descriptor for later validation return nil } func (e *overlaybdBuilderEngine) Cleanup() { if !e.reserve { os.RemoveAll(e.workDir) } } func (e *overlaybdBuilderEngine) uploadBaseLayer(ctx context.Context) (specs.Descriptor, error) { // add baselayer with tar header tarFile := path.Join(e.workDir, "ext4_64.tar") fdes, err := os.Create(tarFile) if err != nil { return specs.Descriptor{}, fmt.Errorf("failed to create file %s: %w", tarFile, err) } digester := digest.Canonical.Digester() countWriter := &writeCountWrapper{w: io.MultiWriter(fdes, digester.Hash())} tarWriter := tar.NewWriter(countWriter) fsrc, err := os.Open(overlaybdBaseLayer) if err != nil { return specs.Descriptor{}, fmt.Errorf("failed to open %s: %w", overlaybdBaseLayer, err) } fstat, err := os.Stat(overlaybdBaseLayer) if err != nil { return specs.Descriptor{}, fmt.Errorf("failed to get info of %s: %w", overlaybdBaseLayer, err) } if err := tarWriter.WriteHeader(&tar.Header{ Name: commitFile, Mode: 0444, Size: fstat.Size(), Typeflag: tar.TypeReg, }); err != nil { return specs.Descriptor{}, fmt.Errorf("failed to write tar header: %w", err) } if _, err := io.Copy(tarWriter, bufio.NewReader(fsrc)); err != nil { return specs.Descriptor{}, fmt.Errorf("failed to copy IO: %w", err) } if err = tarWriter.Close(); err != nil { return specs.Descriptor{}, fmt.Errorf("failed to close tar file: %w", err) } baseDesc := specs.Descriptor{ MediaType: e.mediaTypeImageLayer(), Digest: digester.Digest(), Size: countWriter.c, Annotations: map[string]string{ label.OverlayBDVersion: version.OverlayBDVersionNumber, label.OverlayBDBlobDigest: digester.Digest().String(), label.OverlayBDBlobSize: fmt.Sprintf("%d", countWriter.c), }, } if !e.noUpload { if err = uploadBlob(ctx, e.pusher, tarFile, baseDesc); err != nil { return specs.Descriptor{}, fmt.Errorf("failed to upload baselayer: %w", err) } logrus.Infof("baselayer uploaded") } return baseDesc, nil } func (e *overlaybdBuilderEngine) getLayerDir(idx int) string { return path.Join(e.workDir, fmt.Sprintf("%04d_", idx)+e.manifest.Layers[idx].Digest.String()) } func (e *overlaybdBuilderEngine) create(ctx context.Context, dir string, mkfs bool, vsizeGB int) error { opts := []string{fmt.Sprintf("%d", vsizeGB)} if !e.disableSparse { opts = append(opts, "-s") } if mkfs { opts = append(opts, "--mkfs") logrus.Infof("mkfs for baselayer, vsize: %d GB", vsizeGB) } return utils.Create(ctx, dir, opts...) } func (e *overlaybdBuilderEngine) apply(ctx context.Context, dir string) error { return utils.ApplyOverlaybd(ctx, dir) } func (e *overlaybdBuilderEngine) commit(ctx context.Context, dir string, idx int) error { var parentUUID string if idx > 0 { parentUUID = chainIDtoUUID(e.overlaybdLayers[idx-1].chainID) } else { parentUUID = "" } curUUID := chainIDtoUUID(e.overlaybdLayers[idx].chainID) opts := []string{"-z", "-t", "--uuid", curUUID} if parentUUID != "" { opts = append(opts, "--parent-uuid", parentUUID) } if err := utils.Commit(ctx, dir, dir, false, opts...); err != nil { return err } logrus.Infof("layer %d committed, uuid: %s, parent uuid: %s", idx, curUUID, parentUUID) return nil } type writeCountWrapper struct { w io.Writer c int64 } func (wc *writeCountWrapper) Write(p []byte) (n int, err error) { n, err = wc.w.Write(p) wc.c += int64(n) return } // UUID: 8-4-4-4-12 func chainIDtoUUID(chainID string) string { dStr := chainID[7:] return fmt.Sprintf("%s-%s-%s-%s-%s", dStr[0:8], dStr[8:12], dStr[12:16], dStr[16:20], dStr[20:32]) } accelerated-container-image-1.4.2/cmd/convertor/builder/overlaybd_builder_test.go000066400000000000000000000442141514714641000303170ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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 builder import ( "context" "fmt" "os" "path" "path/filepath" "testing" testingresources "github.com/containerd/accelerated-container-image/cmd/convertor/testingresources" sn "github.com/containerd/accelerated-container-image/pkg/types" "github.com/containerd/errdefs" "github.com/containerd/containerd/v2/core/images" _ "github.com/containerd/containerd/v2/pkg/testutil" // Handle custom root flag "github.com/opencontainers/go-digest" v1 "github.com/opencontainers/image-spec/specs-go/v1" ) func Test_overlaybd_builder_CheckForConvertedLayer(t *testing.T) { ctx := context.Background() db := testingresources.NewLocalDB() resolver := testingresources.GetTestResolver(t, ctx) fetcher := testingresources.GetTestFetcherFromResolver(t, ctx, resolver, testingresources.DockerV2_Manifest_Simple_Ref) base := &builderEngineBase{ fetcher: fetcher, host: "sample.localstore.io", repository: "hello-world", } // TODO: Maybe change this for an actually converted layer in the future targetDesc := v1.Descriptor{ Digest: testingresources.DockerV2_Manifest_Simple_Layer_0_Digest, Size: testingresources.DockerV2_Manifest_Simple_Layer_0_Size, MediaType: v1.MediaTypeImageLayerGzip, } fakeChainId := "fake-chain-id" // We don't validate the chainID itself so such values are fine for testing e := &overlaybdBuilderEngine{ builderEngineBase: base, overlaybdLayers: []overlaybdConvertResult{ { chainID: fakeChainId, }, }, } t.Run("No DB Present", func(t *testing.T) { _, err := e.CheckForConvertedLayer(ctx, 0) testingresources.Assert(t, errdefs.IsNotFound(err), fmt.Sprintf("CheckForConvertedLayer() returned an unexpected Error: %v", err)) }) base.db = db t.Run("No Entry in DB", func(t *testing.T) { _, err := e.CheckForConvertedLayer(ctx, 0) testingresources.Assert(t, errdefs.IsNotFound(err), fmt.Sprintf("CheckForConvertedLayer() returned an unexpected Error: %v", err)) }) err := base.db.CreateLayerEntry(ctx, e.host, e.repository, targetDesc.Digest, fakeChainId, targetDesc.Size) if err != nil { t.Fatal(err) } t.Run("Layer entry in DB and in Registry", func(t *testing.T) { desc, err := e.CheckForConvertedLayer(ctx, 0) if err != nil { t.Fatal(err) } testingresources.Assert(t, desc.Size == targetDesc.Size, "CheckForConvertedLayer() returned improper size layer") testingresources.Assert(t, desc.Digest == targetDesc.Digest, "CheckForConvertedLayer() returned incorrect digest") }) // cross repo mount (change target repo) base.repository = "hello-world2" newImageRef := "sample.localstore.io/hello-world2:amd64" e.resolver = testingresources.GetTestResolver(t, ctx) e.pusher = testingresources.GetTestPusherFromResolver(t, ctx, e.resolver, newImageRef) t.Run("Cross repo layer entry found in DB mount", func(t *testing.T) { desc, err := e.CheckForConvertedLayer(ctx, 0) if err != nil { t.Fatal(err) } testingresources.Assert(t, desc.Size == targetDesc.Size, "CheckForConvertedLayer() returned improper size layer") testingresources.Assert(t, desc.Digest == targetDesc.Digest, "CheckForConvertedLayer() returned incorrect digest") // check that the images can be pulled from the mounted repo fetcher := testingresources.GetTestFetcherFromResolver(t, ctx, e.resolver, newImageRef) rc, err := fetcher.Fetch(ctx, desc) if err != nil { t.Fatal(err) } rc.Close() }) base.db = testingresources.NewLocalDB() // Reset DB digestNotInRegistry := digest.FromString("Not in reg") err = base.db.CreateLayerEntry(ctx, e.host, e.repository, digestNotInRegistry, fakeChainId, 10) if err != nil { t.Fatal(err) } t.Run("Entry in DB but not in registry", func(t *testing.T) { _, err := e.CheckForConvertedLayer(ctx, 0) testingresources.Assert(t, errdefs.IsNotFound(err), fmt.Sprintf("CheckForConvertedLayer() returned an unexpected Error: %v", err)) entry := base.db.GetLayerEntryForRepo(ctx, e.host, e.repository, fakeChainId) testingresources.Assert(t, entry == nil, "CheckForConvertedLayer() Invalid entry was not cleaned up") }) } func Test_overlaybd_builder_CheckForConvertedManifest(t *testing.T) { ctx := context.Background() db := testingresources.NewLocalDB() resolver := testingresources.GetTestResolver(t, ctx) fetcher := testingresources.GetTestFetcherFromResolver(t, ctx, resolver, testingresources.DockerV2_Manifest_Simple_Ref) // Unconverted hello world-image inputDesc := v1.Descriptor{ MediaType: images.MediaTypeDockerSchema2Manifest, Digest: testingresources.DockerV2_Manifest_Simple_Digest, Size: testingresources.DockerV2_Manifest_Simple_Size, } // Converted hello world-image outputDesc := v1.Descriptor{ MediaType: v1.MediaTypeImageManifest, Digest: testingresources.DockerV2_Manifest_Simple_Converted_Digest, Size: testingresources.DockerV2_Manifest_Simple_Converted_Size, } base := &builderEngineBase{ fetcher: fetcher, host: "sample.localstore.io", repository: "hello-world", inputDesc: inputDesc, resolver: resolver, oci: true, } e := &overlaybdBuilderEngine{ builderEngineBase: base, } t.Run("No DB Present", func(t *testing.T) { _, err := e.CheckForConvertedManifest(ctx) testingresources.Assert(t, errdefs.IsNotFound(err), fmt.Sprintf("CheckForConvertedManifest() returned an unexpected Error: %v", err)) }) base.db = db // Store a fake converted manifest in the DB err := base.db.CreateManifestEntry(ctx, e.host, e.repository, outputDesc.MediaType, inputDesc.Digest, outputDesc.Digest, outputDesc.Size) if err != nil { t.Fatal(err) } t.Run("Entry in DB and in Registry", func(t *testing.T) { desc, err := e.CheckForConvertedManifest(ctx) if err != nil { t.Fatal(err) } testingresources.Assert(t, desc.Size == outputDesc.Size, "CheckForConvertedManifest() returned incorrect size") testingresources.Assert(t, desc.Digest == outputDesc.Digest, "CheckForConvertedManifest() returned incorrect digest") }) // cross repo mount (change target repo) base.repository = "hello-world2" newImageRef := "sample.localstore.io/hello-world2:amd64" e.resolver = testingresources.GetTestResolver(t, ctx) e.pusher = testingresources.GetTestPusherFromResolver(t, ctx, e.resolver, newImageRef) t.Run("Cross Repo Entry found in DB mount", func(t *testing.T) { _, err := e.CheckForConvertedManifest(ctx) testingresources.Assert(t, err == nil, fmt.Sprintf("CheckForConvertedManifest() returned an unexpected Error: %v", err)) // check that the images can be pulled from the mounted repo fetcher := testingresources.GetTestFetcherFromResolver(t, ctx, e.resolver, newImageRef) _, desc, err := e.resolver.Resolve(ctx, newImageRef) if err != nil { t.Fatal(err) } manifest, config, err := fetchManifestAndConfig(ctx, fetcher, desc) if err != nil { t.Fatal(err) } if manifest == nil || config == nil { t.Fatalf("Could not pull mounted manifest or config") } rc, err := fetcher.Fetch(ctx, manifest.Layers[0]) if err != nil { t.Fatal(err) } rc.Close() }) base.db = testingresources.NewLocalDB() // Reset DB digestNotInRegistry := digest.FromString("Not in reg") err = base.db.CreateManifestEntry(ctx, e.host, e.repository, outputDesc.MediaType, inputDesc.Digest, digestNotInRegistry, outputDesc.Size) if err != nil { t.Fatal(err) } t.Run("Entry in DB but not in registry", func(t *testing.T) { _, err := e.CheckForConvertedManifest(ctx) testingresources.Assert(t, errdefs.IsNotFound(err), fmt.Sprintf("CheckForConvertedManifest() returned an unexpected Error: %v", err)) entry := base.db.GetManifestEntryForRepo(ctx, e.host, e.repository, outputDesc.MediaType, inputDesc.Digest) testingresources.Assert(t, entry == nil, "CheckForConvertedManifest() Invalid entry was not cleaned up") }) } func Test_overlaybd_builder_StoreConvertedLayerDetails(t *testing.T) { ctx := context.Background() base := &builderEngineBase{ db: nil, repository: "hello-world", host: "sample.localstore.io", } e := &overlaybdBuilderEngine{ builderEngineBase: base, overlaybdLayers: []overlaybdConvertResult{ { fromDedup: true, // TODO: Maybe change this for an actually converted layer in the future desc: v1.Descriptor{ Digest: testingresources.DockerV2_Manifest_Simple_Layer_0_Digest, Size: testingresources.DockerV2_Manifest_Simple_Layer_0_Size, MediaType: v1.MediaTypeImageLayerGzip, }, chainID: "fake-chain-id", }, }, } e.manifest = v1.Manifest{ Layers: []v1.Descriptor{ { Digest: testingresources.DockerV2_Manifest_Simple_Layer_0_Digest, Size: testingresources.DockerV2_Manifest_Simple_Layer_0_Size, MediaType: v1.MediaTypeImageLayerGzip, }, }, } t.Run("No DB Present", func(t *testing.T) { err := e.StoreConvertedLayerDetails(ctx, 0) testingresources.Assert(t, err == nil, "StoreConvertedLayerDetails() returned an unexpected Error") }) t.Run("Layer is marked as deduplicated, avoid storing", func(t *testing.T) { base.db = testingresources.NewLocalDB() err := e.StoreConvertedLayerDetails(ctx, 0) testingresources.Assert(t, err == nil, "StoreConvertedLayerDetails() returned an unexpected Error") base.db.GetLayerEntryForRepo(ctx, e.host, e.repository, "fake-chain-id") }) } func Test_overlaybd_builder_BuildLayer_HandlesPreviouslyConvertedLayers(t *testing.T) { ctx := context.Background() resolver := testingresources.GetTestResolver(t, ctx) fetcher := testingresources.GetTestFetcherFromResolver(t, ctx, resolver, testingresources.DockerV2_Manifest_Simple_Ref) base := &builderEngineBase{ fetcher: fetcher, host: "sample.localstore.io", repository: "hello-world", } // TODO: Maybe change this for an actually converted layer in the future targetDesc := v1.Descriptor{ Digest: testingresources.DockerV2_Manifest_Simple_Layer_0_Digest, Size: testingresources.DockerV2_Manifest_Simple_Layer_0_Size, MediaType: v1.MediaTypeImageLayerGzip, } config := &sn.OverlayBDBSConfig{ Lowers: []sn.OverlayBDBSConfigLower{}, ResultFile: "", } config.Lowers = append(config.Lowers, sn.OverlayBDBSConfigLower{ File: overlaybdBaseLayer, }) t.Run("Dedup working as expected", func(t *testing.T) { e := &overlaybdBuilderEngine{ builderEngineBase: base, overlaybdConfig: config, } e.manifest.Layers = []v1.Descriptor{targetDesc} tmpDir := t.TempDir() e.workDir = tmpDir e.overlaybdLayers = []overlaybdConvertResult{ { fromDedup: true, }, } // Try before setting the commit file. This will fail because create tool // is not present but helps verify the fallback. Error type here depends // on if the tool is present or not. if err := e.BuildLayer(ctx, 0); err == nil { t.Fatal("Expected an error but got none") } // Simulate a commit file if err := os.MkdirAll(e.getLayerDir(0), 0777); err != nil { t.Fatal(err) } // Try again with parent directory present if err := e.BuildLayer(ctx, 0); err == nil { t.Fatal("Expected an error but got none") } file, err := os.Create(filepath.Join(e.getLayerDir(0), commitFile)) if err != nil { t.Fatal(err) } file.Close() if err = e.BuildLayer(ctx, 0); err != nil { t.Error(err) } }) // We attempt to clean any leftover files when we fail to download a dedup // layer so this scenario is unlikely to happen but it's a good sanity check. // In this case, we assume the commit file is present but invalid. t.Run("Dedup left partial commit file", func(t *testing.T) { e := &overlaybdBuilderEngine{ builderEngineBase: base, overlaybdConfig: config, } e.manifest.Layers = []v1.Descriptor{targetDesc} tmpDir := t.TempDir() e.workDir = tmpDir e.overlaybdLayers = []overlaybdConvertResult{ { fromDedup: false, }, } // Simulate a commit file if err := os.MkdirAll(e.getLayerDir(0), 0777); err != nil { t.Fatal(err) } file, err := os.Create(filepath.Join(e.getLayerDir(0), commitFile)) if err != nil { t.Fatal(err) } file.Close() if err = e.BuildLayer(ctx, 0); err == nil { t.Fatal("Expected an error but got none") } else { if err.Error() != "layer 0 is not from dedup but commit file is present" { t.Fatalf("Unexpected error: %v", err) } } }) // This is a scenario that should not happen but it's a good sanity check. t.Run("Dedup but somehow failed to download commit file", func(t *testing.T) { e := &overlaybdBuilderEngine{ builderEngineBase: base, overlaybdConfig: config, } e.manifest.Layers = []v1.Descriptor{targetDesc} tmpDir := t.TempDir() e.workDir = tmpDir e.overlaybdLayers = []overlaybdConvertResult{ { fromDedup: true, }, } // Simulate a commit file if err := os.MkdirAll(e.getLayerDir(0), 0777); err != nil { t.Fatal(err) } if err := e.BuildLayer(ctx, 0); err == nil { t.Fatal("Expected an error but got none") } else { if err.Error() != "layer 0 is from dedup but commit file is missing" { t.Fatalf("Unexpected error: %v", err) } } }) } func Test_overlaybd_builder_DownloadConvertedLayer(t *testing.T) { ctx := context.Background() resolver := testingresources.GetTestResolver(t, ctx) fetcher := testingresources.GetTestFetcherFromResolver(t, ctx, resolver, testingresources.DockerV2_Manifest_Simple_Ref) base := &builderEngineBase{ fetcher: fetcher, host: "sample.localstore.io", repository: "hello-world", } // TODO: Maybe change this for an actually converted layer in the future targetDesc := v1.Descriptor{ Digest: testingresources.DockerV2_Manifest_Simple_Layer_0_Digest, Size: testingresources.DockerV2_Manifest_Simple_Layer_0_Size, MediaType: v1.MediaTypeImageLayerGzip, } config := &sn.OverlayBDBSConfig{ Lowers: []sn.OverlayBDBSConfigLower{}, ResultFile: "", } config.Lowers = append(config.Lowers, sn.OverlayBDBSConfigLower{ File: overlaybdBaseLayer, }) t.Run("DownloadConvertedLayer Succeeds", func(t *testing.T) { e := &overlaybdBuilderEngine{ builderEngineBase: base, overlaybdConfig: config, } e.manifest.Layers = []v1.Descriptor{targetDesc} tmpDir := t.TempDir() e.workDir = tmpDir e.overlaybdLayers = []overlaybdConvertResult{ { fromDedup: false, }, } if err := e.DownloadConvertedLayer(ctx, 0, targetDesc); err != nil { t.Fatalf("DownloadConvertedLayer() failed with error: %v", err) } // Check if the commit file is present if _, err := os.Stat(filepath.Join(e.getLayerDir(0), commitFile)); err != nil { t.Fatalf("Expected commit file but got: %v", err) } testingresources.Assert(t, e.overlaybdLayers[0].fromDedup, "DownloadConvertedLayer() did not mark layer as dedup") testingresources.Assert(t, e.overlaybdLayers[0].desc.Digest != "", "DownloadConvertedLayer() did not set the digest") }) } func Test_overlaybd_builder_UploadLayer(t *testing.T) { ctx := context.Background() targetManifest := "sample.localstore.io/hello-world:another" resolver := testingresources.GetTestResolver(t, ctx) fetcher := testingresources.GetTestFetcherFromResolver(t, ctx, resolver, testingresources.DockerV2_Manifest_Simple_Ref) pusher := testingresources.GetTestPusherFromResolver(t, ctx, resolver, targetManifest) base := &builderEngineBase{ fetcher: fetcher, host: "sample.localstore.io", repository: "hello-world", pusher: pusher, } // TODO: Maybe change this for an actually converted layer in the future targetDesc := v1.Descriptor{ Digest: testingresources.DockerV2_Manifest_Simple_Layer_0_Digest, Size: testingresources.DockerV2_Manifest_Simple_Layer_0_Size, MediaType: v1.MediaTypeImageLayerGzip, } config := &sn.OverlayBDBSConfig{ Lowers: []sn.OverlayBDBSConfigLower{}, ResultFile: "", } config.Lowers = append(config.Lowers, sn.OverlayBDBSConfigLower{ File: overlaybdBaseLayer, }) t.Run("UploadLayer Succeeds for non dedup layer", func(t *testing.T) { e := &overlaybdBuilderEngine{ builderEngineBase: base, overlaybdConfig: config, } e.manifest.Layers = []v1.Descriptor{targetDesc} tmpDir := t.TempDir() e.workDir = tmpDir e.overlaybdLayers = []overlaybdConvertResult{ { fromDedup: false, }, } // Get a commit file (We are using the downloadConvertedLayer here just to get the file) if err := e.DownloadConvertedLayer(ctx, 0, targetDesc); err != nil { t.Fatalf("DownloadConvertedLayer() failed with error: %v", err) } e.overlaybdLayers[0].fromDedup = false // Reset the flag to simulate a non dedup layer if err := e.UploadLayer(ctx, 0); err != nil { t.Fatalf("UploadLayer() failed with error: %v", err) } }) t.Run("UploadLayer Fails for non matching dedup layer", func(t *testing.T) { e := &overlaybdBuilderEngine{ builderEngineBase: base, overlaybdConfig: config, } e.manifest.Layers = []v1.Descriptor{targetDesc} tmpDir := t.TempDir() e.workDir = tmpDir e.overlaybdLayers = []overlaybdConvertResult{ { desc: targetDesc, // Set the desc to the targetDesc to simulate a mismatch fromDedup: true, }, } // Simulate a corrupted commit file if err := os.MkdirAll(e.getLayerDir(0), 0777); err != nil { t.Fatal(err) } file, err := os.Create(filepath.Join(e.getLayerDir(0), commitFile)) if err != nil { t.Fatal(err) } file.WriteString("corrupted overlaybdfile simulation") file.Close() layerDir := e.getLayerDir(0) corruptedDesc, err := getFileDesc(path.Join(layerDir, commitFile), false) if err != nil { t.Fatalf("getFileDesc() failed with error: %v", err) } // Upload should fail because the converted layer is not as expected if err = e.UploadLayer(ctx, 0); err != nil { if err.Error() != fmt.Sprintf("layer %d digest mismatch, expected %s, got %s", 0, targetDesc.Digest, corruptedDesc.Digest) { t.Fatalf("UploadLayer() failed with unexpected error: %v", err) } } }) } accelerated-container-image-1.4.2/cmd/convertor/builder/turboOCI_builder.go000066400000000000000000000233361514714641000267610ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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 builder import ( "context" "fmt" "os" "path" "github.com/containerd/accelerated-container-image/pkg/label" sn "github.com/containerd/accelerated-container-image/pkg/types" "github.com/containerd/accelerated-container-image/pkg/utils" "github.com/containerd/accelerated-container-image/pkg/version" "github.com/containerd/containerd/v2/core/images" "github.com/containerd/containerd/v2/pkg/archive/compression" "github.com/containerd/errdefs" "github.com/opencontainers/go-digest" specs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/sirupsen/logrus" ) const ( // index of OCI layers (gzip) gzipMetaFile = "gzip.meta" // index of block device fsMetaFileSuffix = ".fs.meta" // foci index layer (gzip) tociLayerTar = "turboOCIv1.tar.gz" // tociIdentifier is an empty file just used as a identifier tociIdentifier = ".turbo.ociv1" ) type turboOCIBuilderEngine struct { *builderEngineBase overlaybdConfig *sn.OverlayBDBSConfig tociLayers []specs.Descriptor isGzip []bool } func NewTurboOCIBuilderEngine(base *builderEngineBase) builderEngine { config := &sn.OverlayBDBSConfig{ Lowers: []sn.OverlayBDBSConfigLower{}, ResultFile: "", } if !base.mkfs { config.Lowers = append(config.Lowers, sn.OverlayBDBSConfigLower{ File: overlaybdBaseLayer, }) logrus.Infof("using default baselayer") } return &turboOCIBuilderEngine{ builderEngineBase: base, overlaybdConfig: config, tociLayers: make([]specs.Descriptor, len(base.manifest.Layers)), isGzip: make([]bool, len(base.manifest.Layers)), } } func (e *turboOCIBuilderEngine) DownloadLayer(ctx context.Context, idx int) error { var err error if e.isGzip[idx], err = e.isGzipLayer(ctx, idx); err != nil { return err } desc := e.manifest.Layers[idx] targetFile := path.Join(e.getLayerDir(idx), "layer.tar") return downloadLayer(ctx, e.fetcher, targetFile, desc, false) } func (e *turboOCIBuilderEngine) BuildLayer(ctx context.Context, idx int) error { layerDir := e.getLayerDir(idx) if err := e.create(ctx, idx); err != nil { return err } e.overlaybdConfig.Upper = sn.OverlayBDBSConfigUpper{ Data: path.Join(layerDir, "writable_data"), Index: path.Join(layerDir, "writable_index"), Target: path.Join(layerDir, "layer.tar"), } if err := writeConfig(layerDir, e.overlaybdConfig); err != nil { return err } if err := e.apply(ctx, layerDir); err != nil { return err } var fsMetaFile string if e.fstype == "" { fsMetaFile = "ext4" + fsMetaFileSuffix } else { fsMetaFile = e.fstype + fsMetaFileSuffix } if err := e.commit(ctx, layerDir, fsMetaFile); err != nil { return err } if err := e.createIdentifier(idx); err != nil { return fmt.Errorf("failed to create identifier %q: %w", tociIdentifier, err) } files := []string{ path.Join(layerDir, fsMetaFile), path.Join(layerDir, tociIdentifier), } gzipIndexPath := "" if e.isGzip[idx] { gzipIndexPath = path.Join(layerDir, gzipMetaFile) files = append(files, gzipIndexPath) } if err := buildArchiveFromFiles(ctx, path.Join(layerDir, tociLayerTar), compression.Gzip, files...); err != nil { return fmt.Errorf("failed to create turboOCIv1 archive for layer %d: %w", idx, err) } e.overlaybdConfig.Lowers = append(e.overlaybdConfig.Lowers, sn.OverlayBDBSConfigLower{ TargetFile: path.Join(layerDir, "layer.tar"), TargetDigest: string(e.manifest.Layers[idx].Digest), // TargetDigest should be set to work with gzip cache File: path.Join(layerDir, fsMetaFile), GzipIndex: gzipIndexPath, }) os.Remove(path.Join(layerDir, "writable_data")) os.Remove(path.Join(layerDir, "writable_index")) return nil } func (e *turboOCIBuilderEngine) UploadLayer(ctx context.Context, idx int) error { layerDir := e.getLayerDir(idx) desc, err := getFileDesc(path.Join(layerDir, tociLayerTar), false) if err != nil { return fmt.Errorf("failed to get descriptor for layer %d: %w", idx, err) } desc.MediaType = e.mediaTypeImageLayerGzip() desc.Annotations = map[string]string{ label.OverlayBDVersion: version.TurboOCIVersionNumber, label.OverlayBDBlobDigest: desc.Digest.String(), label.OverlayBDBlobSize: fmt.Sprintf("%d", desc.Size), label.TurboOCIDigest: e.manifest.Layers[idx].Digest.String(), } targetMediaType := "" if images.IsDockerType(e.manifest.Layers[idx].MediaType) { if e.isGzip[idx] { targetMediaType = images.MediaTypeDockerSchema2LayerGzip } else { targetMediaType = images.MediaTypeDockerSchema2Layer } } else { if e.isGzip[idx] { targetMediaType = specs.MediaTypeImageLayerGzip } else { targetMediaType = specs.MediaTypeImageLayer } } desc.Annotations[label.TurboOCIMediaType] = targetMediaType if err := uploadBlob(ctx, e.pusher, path.Join(layerDir, tociLayerTar), desc); err != nil { return fmt.Errorf("failed to upload layer %d: %w", idx, err) } e.tociLayers[idx] = desc return nil } func (e *turboOCIBuilderEngine) UploadImage(ctx context.Context) (specs.Descriptor, error) { for idx := range e.manifest.Layers { layerDir := e.getLayerDir(idx) uncompress, err := getFileDesc(path.Join(layerDir, tociLayerTar), true) if err != nil { return specs.Descriptor{}, fmt.Errorf("failed to get uncompressed descriptor for layer %d: %w", idx, err) } e.manifest.Layers[idx] = e.tociLayers[idx] e.config.RootFS.DiffIDs[idx] = uncompress.Digest } baseDesc := specs.Descriptor{ MediaType: e.mediaTypeImageLayer(), Digest: "sha256:c3a417552a6cf9ffa959b541850bab7d7f08f4255425bf8b48c85f7b36b378d9", Size: 4737695, Annotations: map[string]string{ label.OverlayBDVersion: version.OverlayBDVersionNumber, label.OverlayBDBlobDigest: "sha256:c3a417552a6cf9ffa959b541850bab7d7f08f4255425bf8b48c85f7b36b378d9", label.OverlayBDBlobSize: "4737695", }, } if !e.mkfs { if err := uploadBlob(ctx, e.pusher, overlaybdBaseLayer, baseDesc); err != nil { return specs.Descriptor{}, fmt.Errorf("failed to upload baselayer %q: %w", overlaybdBaseLayer, err) } e.manifest.Layers = append([]specs.Descriptor{baseDesc}, e.manifest.Layers...) e.config.RootFS.DiffIDs = append([]digest.Digest{baseDesc.Digest}, e.config.RootFS.DiffIDs...) } if e.referrer { e.manifest.ArtifactType = ArtifactTypeTurboOCI e.manifest.Subject = &specs.Descriptor{ MediaType: e.inputDesc.MediaType, Digest: e.inputDesc.Digest, Size: e.inputDesc.Size, } } return e.uploadManifestAndConfig(ctx) } // If a converted manifest has been found we still need to tag it to match the expected output tag. func (e *turboOCIBuilderEngine) TagPreviouslyConvertedManifest(ctx context.Context, desc specs.Descriptor) error { return tagPreviouslyConvertedManifest(ctx, e.pusher, e.fetcher, desc) } // Layer deduplication in FastOCI is not currently supported due to conversion not // being reproducible at the moment which can lead to occasional bugs. // CheckForConvertedLayer TODO func (e *turboOCIBuilderEngine) CheckForConvertedLayer(ctx context.Context, idx int) (specs.Descriptor, error) { return specs.Descriptor{}, errdefs.ErrNotFound } // StoreConvertedLayerDetails TODO func (e *turboOCIBuilderEngine) StoreConvertedLayerDetails(ctx context.Context, idx int) error { return nil } // DownloadConvertedLayer TODO func (e *turboOCIBuilderEngine) DownloadConvertedLayer(ctx context.Context, idx int, desc specs.Descriptor) error { return errdefs.ErrNotImplemented } // DownloadConvertedLayer TODO func (e *turboOCIBuilderEngine) CheckForConvertedManifest(ctx context.Context) (specs.Descriptor, error) { return specs.Descriptor{}, errdefs.ErrNotImplemented } // DownloadConvertedLayer TODO func (e *turboOCIBuilderEngine) StoreConvertedManifestDetails(ctx context.Context) error { return errdefs.ErrNotImplemented } func (e *turboOCIBuilderEngine) Cleanup() { if !e.reserve { os.RemoveAll(e.workDir) } } func (e *turboOCIBuilderEngine) getLayerDir(idx int) string { return path.Join(e.workDir, fmt.Sprintf("%04d_", idx)+e.manifest.Layers[idx].Digest.String()) } func (e *turboOCIBuilderEngine) createIdentifier(idx int) error { targetFile := path.Join(e.getLayerDir(idx), tociIdentifier) file, err := os.Create(targetFile) if err != nil { return fmt.Errorf("failed to create identifier file %q: %w", tociIdentifier, err) } defer file.Close() return nil } func (e *turboOCIBuilderEngine) create(ctx context.Context, idx int) error { vsizeGB := 64 // use default baselayer if e.mkfs { vsizeGB = e.vsize } opts := []string{"-s", fmt.Sprintf("%d", vsizeGB), "--turboOCI"} if e.mkfs && idx == 0 { logrus.Infof("mkfs for baselayer, vsize: %d GB", vsizeGB) if e.fstype != "erofs" { opts = append(opts, "--mkfs") } } return utils.Create(ctx, e.getLayerDir(idx), opts...) } func (e *turboOCIBuilderEngine) apply(ctx context.Context, dir string) error { if e.fstype != "" && e.fstype != "ext4" { opts := []string{"--fstype", e.fstype} return utils.ApplyTurboOCI(ctx, dir, gzipMetaFile, opts...) } return utils.ApplyTurboOCI(ctx, dir, gzipMetaFile) } func (e *turboOCIBuilderEngine) commit(ctx context.Context, dir string, fsMetaFile string) error { if err := utils.Commit(ctx, dir, dir, false, "-z", "--fastoci"); err != nil { return err } return os.Rename(path.Join(dir, commitFile), path.Join(dir, fsMetaFile)) } accelerated-container-image-1.4.2/cmd/convertor/database/000077500000000000000000000000001514714641000233455ustar00rootroot00000000000000accelerated-container-image-1.4.2/cmd/convertor/database/database.go000066400000000000000000000036661514714641000254530ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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 database import ( "context" "github.com/opencontainers/go-digest" ) type ConversionDatabase interface { // Layer Entries CreateLayerEntry(ctx context.Context, host, repository string, convertedDigest digest.Digest, chainID string, size int64) error GetLayerEntryForRepo(ctx context.Context, host, repository, chainID string) *LayerEntry GetCrossRepoLayerEntries(ctx context.Context, host, chainID string) []*LayerEntry DeleteLayerEntry(ctx context.Context, host, repository, chainID string) error // Manifest Entries CreateManifestEntry(ctx context.Context, host, repository, mediatype string, original, convertedDigest digest.Digest, size int64) error GetManifestEntryForRepo(ctx context.Context, host, repository, mediatype string, original digest.Digest) *ManifestEntry GetCrossRepoManifestEntries(ctx context.Context, host, mediatype string, original digest.Digest) []*ManifestEntry DeleteManifestEntry(ctx context.Context, host, repository, mediatype string, original digest.Digest) error } type LayerEntry struct { ConvertedDigest digest.Digest DataSize int64 Repository string ChainID string Host string } type ManifestEntry struct { ConvertedDigest digest.Digest OriginalDigest digest.Digest DataSize int64 Repository string Host string MediaType string } accelerated-container-image-1.4.2/cmd/convertor/database/mysql.go000066400000000000000000000111641514714641000250440ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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 database import ( "context" "database/sql" "fmt" "github.com/containerd/log" "github.com/opencontainers/go-digest" ) type sqldb struct { db *sql.DB } func NewSqlDB(db *sql.DB) ConversionDatabase { return &sqldb{ db: db, } } func (m *sqldb) CreateLayerEntry(ctx context.Context, host, repository string, convertedDigest digest.Digest, chainID string, size int64) error { _, err := m.db.ExecContext(ctx, "insert into overlaybd_layers(host, repo, chain_id, data_digest, data_size) values(?, ?, ?, ?, ?)", host, repository, chainID, convertedDigest, size) return err } func (m *sqldb) GetLayerEntryForRepo(ctx context.Context, host, repository, chainID string) *LayerEntry { var entry LayerEntry row := m.db.QueryRowContext(ctx, "select host, repo, chain_id, data_digest, data_size from overlaybd_layers where host=? and repo=? and chain_id=?", host, repository, chainID) if err := row.Scan(&entry.Host, &entry.Repository, &entry.ChainID, &entry.ConvertedDigest, &entry.DataSize); err != nil { return nil } return &entry } func (m *sqldb) GetCrossRepoLayerEntries(ctx context.Context, host, chainID string) []*LayerEntry { rows, err := m.db.QueryContext(ctx, "select host, repo, chain_id, data_digest, data_size from overlaybd_layers where host=? and chain_id=?", host, chainID) if err != nil { if err == sql.ErrNoRows { return nil } log.G(ctx).Infof("query error %v", err) return nil } var entries []*LayerEntry for rows.Next() { var entry LayerEntry err = rows.Scan(&entry.Host, &entry.Repository, &entry.ChainID, &entry.ConvertedDigest, &entry.DataSize) if err != nil { continue } entries = append(entries, &entry) } return entries } func (m *sqldb) DeleteLayerEntry(ctx context.Context, host, repository string, chainID string) error { _, err := m.db.Exec("delete from overlaybd_layers where host=? and repo=? and chain_id=?", host, repository, chainID) if err != nil { return fmt.Errorf("failed to remove invalid record in db: %w", err) } return nil } func (m *sqldb) CreateManifestEntry(ctx context.Context, host, repository, mediaType string, original, convertedDigest digest.Digest, size int64) error { _, err := m.db.ExecContext(ctx, "insert into overlaybd_manifests(host, repo, src_digest, out_digest, data_size, mediatype) values(?, ?, ?, ?, ?, ?)", host, repository, original, convertedDigest, size, mediaType) return err } func (m *sqldb) GetManifestEntryForRepo(ctx context.Context, host, repository, mediaType string, original digest.Digest) *ManifestEntry { var entry ManifestEntry row := m.db.QueryRowContext(ctx, "select host, repo, src_digest, out_digest, data_size, mediatype from overlaybd_manifests where host=? and repo=? and src_digest=? and mediatype=?", host, repository, original, mediaType) if err := row.Scan(&entry.Host, &entry.Repository, &entry.OriginalDigest, &entry.ConvertedDigest, &entry.DataSize, &entry.MediaType); err != nil { return nil } return &entry } func (m *sqldb) GetCrossRepoManifestEntries(ctx context.Context, host, mediaType string, original digest.Digest) []*ManifestEntry { rows, err := m.db.QueryContext(ctx, "select host, repo, src_digest, out_digest, data_size, mediatype from overlaybd_manifests where host=? and src_digest=? and mediatype=?", host, original, mediaType) if err != nil { if err == sql.ErrNoRows { return nil } log.G(ctx).Infof("query error %v", err) return nil } var entries []*ManifestEntry for rows.Next() { var entry ManifestEntry err = rows.Scan(&entry.Host, &entry.Repository, &entry.OriginalDigest, &entry.ConvertedDigest, &entry.DataSize, &entry.MediaType) if err != nil { continue } entries = append(entries, &entry) } return entries } func (m *sqldb) DeleteManifestEntry(ctx context.Context, host, repository, mediaType string, original digest.Digest) error { _, err := m.db.Exec("delete from overlaybd_manifests where host=? and repo=? and src_digest=? and mediatype=?", host, repository, original, mediaType) if err != nil { return fmt.Errorf("failed to remove invalid record in db: %w", err) } return nil } accelerated-container-image-1.4.2/cmd/convertor/main.go000066400000000000000000000167521514714641000230670ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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" "database/sql" "os" "os/signal" "github.com/containerd/accelerated-container-image/cmd/convertor/builder" "github.com/containerd/accelerated-container-image/cmd/convertor/database" _ "github.com/go-sql-driver/mysql" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) var ( commitID string = "unknown" repo string user string plain bool tagInput string digestInput string tagOutput string dir string oci bool fsType string mkfs bool verbose bool vsize int fastoci string turboOCI string overlaybd string dbstr string dbType string concurrencyLimit int disableSparse bool referrer bool // certification certDirs []string rootCAs []string clientCerts []string insecure bool // debug reserve bool noUpload bool dumpManifest bool rootCmd = &cobra.Command{ Use: "convertor", Short: "An image conversion tool from oci image to overlaybd image.", Long: ` Description: overlaybd convertor is a standalone userspace image conversion tool that helps converting oci images to overlaybd images. Version: ` + commitID, Run: func(cmd *cobra.Command, args []string) { if verbose { logrus.SetLevel(logrus.DebugLevel) } tb := "" if digestInput == "" && tagInput == "" { logrus.Error("one of input-tag [-i] or input-digest [-g] is required") os.Exit(1) } if overlaybd == "" && fastoci == "" && turboOCI == "" { if tagOutput == "" { logrus.Error("output-tag is required, you can specify it by [-o|--overlaybd|--turboOCI]") os.Exit(1) } overlaybd = tagOutput } if fastoci != "" { tb = fastoci } if turboOCI != "" { tb = turboOCI } if referrer { oci = true } ctx := context.Background() ref := repo + ":" + tagInput if tagInput == "" { ref = repo + "@" + digestInput } opt := builder.BuilderOptions{ Ref: ref, Auth: user, PlainHTTP: plain, WorkDir: dir, OCI: oci, FsType: fsType, Mkfs: mkfs, Vsize: vsize, CertOption: builder.CertOption{ CertDirs: certDirs, RootCAs: rootCAs, ClientCerts: clientCerts, Insecure: insecure, }, Reserve: reserve, NoUpload: noUpload, DumpManifest: dumpManifest, ConcurrencyLimit: concurrencyLimit, DisableSparse: disableSparse, Referrer: referrer, } if overlaybd != "" { logrus.Info("building [Overlaybd - Native] image...") opt.Engine = builder.Overlaybd opt.TargetRef = repo + ":" + overlaybd switch dbType { case "mysql": if dbstr == "" { logrus.Warnf("no db-str was provided, falling back to no deduplication") } db, err := sql.Open("mysql", dbstr) if err != nil { logrus.Errorf("failed to open the provided mysql db: %v", err) os.Exit(1) } opt.DB = database.NewSqlDB(db) case "": default: logrus.Warnf("db-type %s was provided but is not one of known db types. Available: mysql", dbType) logrus.Warnf("falling back to no deduplication") } if err := builder.Build(ctx, opt); err != nil { logrus.Errorf("failed to build overlaybd: %v", err) os.Exit(1) } logrus.Info("overlaybd build finished") } if tb != "" { logrus.Info("building [Overlaybd - Turbo OCIv1] image...") opt.Engine = builder.TurboOCI opt.TargetRef = repo + ":" + tb if err := builder.Build(ctx, opt); err != nil { logrus.Errorf("failed to build TurboOCIv1 image: %v", err) os.Exit(1) } logrus.Info("TurboOCIv1 build finished") } }, } ) func init() { rootCmd.Flags().SortFlags = false rootCmd.Flags().StringVarP(&repo, "repository", "r", "", "repository for converting image (required)") rootCmd.Flags().StringVarP(&user, "username", "u", "", "user[:password] Registry user and password") rootCmd.Flags().BoolVarP(&plain, "plain", "", false, "connections using plain HTTP") rootCmd.Flags().BoolVarP(&verbose, "verbose", "", false, "show debug log") rootCmd.Flags().StringVarP(&tagInput, "input-tag", "i", "", "tag for image converting from (required when input-digest is not set)") rootCmd.Flags().StringVarP(&digestInput, "input-digest", "g", "", "digest for image converting from (required when input-tag is not set)") rootCmd.Flags().StringVarP(&tagOutput, "output-tag", "o", "", "tag for image converting to") rootCmd.Flags().StringVarP(&dir, "dir", "d", "tmp_conv", "directory used for temporary data") rootCmd.Flags().BoolVarP(&oci, "oci", "", false, "export image with oci spec") rootCmd.Flags().StringVar(&fsType, "fstype", "ext4", "filesystem type of converted image.") rootCmd.Flags().BoolVarP(&mkfs, "mkfs", "", true, "make ext4 fs in bottom layer") rootCmd.Flags().IntVarP(&vsize, "vsize", "", 64, "virtual block device size (GB)") rootCmd.Flags().StringVar(&fastoci, "fastoci", "", "build 'Overlaybd-Turbo OCIv1' format (old name of turboOCIv1. deprecated)") rootCmd.Flags().StringVar(&turboOCI, "turboOCI", "", "build 'Overlaybd-Turbo OCIv1' format") rootCmd.Flags().StringVar(&overlaybd, "overlaybd", "", "build overlaybd format") rootCmd.Flags().StringVar(&dbstr, "db-str", "", "db str for overlaybd conversion") rootCmd.Flags().StringVar(&dbType, "db-type", "", "type of db to use for conversion deduplication. Available: mysql. Default none") rootCmd.Flags().IntVar(&concurrencyLimit, "concurrency-limit", 4, "the number of manifests that can be built at the same time, used for multi-arch images, 0 means no limit") rootCmd.Flags().BoolVar(&disableSparse, "disable-sparse", false, "disable sparse file for overlaybd") rootCmd.Flags().BoolVar(&referrer, "referrer", false, "push converted manifests with subject, note '--oci' will be enabled automatically if '--referrer' is set, cause the referrer must be in OCI format.") // certification rootCmd.Flags().StringArrayVar(&certDirs, "cert-dir", nil, "In these directories, root CA should be named as *.crt and client cert should be named as *.cert, *.key") rootCmd.Flags().StringArrayVar(&rootCAs, "root-ca", nil, "root CA certificates") rootCmd.Flags().StringArrayVar(&clientCerts, "client-cert", nil, "client cert certificates, should form in ${cert-file}:${key-file}") rootCmd.Flags().BoolVarP(&insecure, "insecure", "", false, "don't verify the server's certificate chain and host name") // debug rootCmd.Flags().BoolVar(&reserve, "reserve", false, "reserve tmp data") rootCmd.Flags().BoolVar(&noUpload, "no-upload", false, "don't upload layer and manifest") rootCmd.Flags().BoolVar(&dumpManifest, "dump-manifest", false, "dump manifest") rootCmd.MarkFlagRequired("repository") } func main() { sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt) go func() { <-sigChan os.Exit(0) }() rootCmd.Execute() } accelerated-container-image-1.4.2/cmd/convertor/resources/000077500000000000000000000000001514714641000236135ustar00rootroot00000000000000accelerated-container-image-1.4.2/cmd/convertor/resources/samples/000077500000000000000000000000001514714641000252575ustar00rootroot00000000000000mysql-db-manifest-cache-sample-workload.sh000077500000000000000000000025211514714641000352330ustar00rootroot00000000000000accelerated-container-image-1.4.2/cmd/convertor/resources/samples# Validation Examples registry=$1 # registry to push to username=$2 # username for registry password=$3 # password for registry sourceImage=$4 # public image to convert repository=$5 # repository to push to tag=$6 # tag to push to mysqldbuser=$7 # mysql user mysqldbpassword=$8 # mysql password oras login $registry -u $username -p $password oras cp $sourceImage $registry/$repository:$tag # Try one conversion ./bin/convertor --repository $registry/$repository -u $username:$password --input-tag $tag --oci --overlaybd $tag-obd-cache --db-str "$mysqldbuser:$mysqldbpassword@tcp(127.0.0.1:3306)/conversioncache" --db-type mysql # Retry, result manifest should be cached ./bin/convertor --repository $registry/$repository -u $username:$password --input-tag $tag --oci --overlaybd $tag-obd-cache-2 --db-str "$mysqldbuser:$mysqldbpassword@tcp(127.0.0.1:3306)/conversioncache" --db-type mysql # Retry, cross repo mount oras cp $sourceImage $registry/$repository-2:$tag ./bin/convertor --repository $registry/$repository-2 -u $username:$password --input-tag $tag --oci --overlaybd $tag-obd-cache-3 --db-str "$mysqldbuser:$mysqldbpassword@tcp(127.0.0.1:3306)/conversioncache" --db-type mysql # Expected output in the registry: # # -- # -- -obd-cache # -- -obd-cache-2 # -2 # -- # -- -obd-cache-3 accelerated-container-image-1.4.2/cmd/convertor/resources/samples/mysql-db-setup.sh000077500000000000000000000006141514714641000305050ustar00rootroot00000000000000#!/bin/sh # Reference Script for getting started with mysql DB for userspace convertor mysqldbuser=$1 mysqldbpassword=$2 # Set up mysql apt update apt install mysql-server service mysql start cat ./mysql.conf | mysql echo "CREATE USER '$mysqldbuser'@'localhost' IDENTIFIED BY '$mysqldbpassword'; GRANT ALL PRIVILEGES ON conversioncache.* TO '$mysqldbuser'@'localhost'; FLUSH PRIVILEGES;" | mysqlaccelerated-container-image-1.4.2/cmd/convertor/resources/samples/mysql.conf000066400000000000000000000021031514714641000272670ustar00rootroot00000000000000CREATE database conversioncache; USE conversioncache; CREATE TABLE `overlaybd_layers` ( `host` varchar(255) NOT NULL, `repo` varchar(255) NOT NULL, `chain_id` varchar(255) NOT NULL COMMENT 'chain-id of the normal image layer', `data_digest` varchar(255) NOT NULL COMMENT 'digest of overlaybd layer', `data_size` bigint(20) NOT NULL COMMENT 'size of overlaybd layer', PRIMARY KEY (`host`,`repo`,`chain_id`), KEY `index_registry_chainId` (`host`,`chain_id`) USING BTREE ) DEFAULT CHARSET=utf8; CREATE TABLE `overlaybd_manifests` ( `host` varchar(255) NOT NULL, `repo` varchar(255) NOT NULL, `src_digest` varchar(255) NOT NULL COMMENT 'digest of the normal image manifest', `out_digest` varchar(255) NOT NULL COMMENT 'digest of overlaybd manifest', `data_size` bigint(20) NOT NULL COMMENT 'size of overlaybd manifest', `mediatype` varchar(255) NOT NULL COMMENT 'mediatype of the converted image manifest', PRIMARY KEY (`host`,`repo`,`src_digest`, `mediatype`), KEY `index_registry_src_digest` (`host`,`src_digest`, `mediatype`) USING BTREE ) DEFAULT CHARSET=utf8;run-userspace-convertor-ubuntu.Dockerfile000066400000000000000000000054151514714641000353310ustar00rootroot00000000000000accelerated-container-image-1.4.2/cmd/convertor/resources/samples#NOTE: This Dockerfile should be executed from the root of the repository # docker build . -f ./cmd/convertor/resources/samples/run-userspace-convertor-ubuntu.Dockerfile -t convertor FROM ubuntu:latest AS base # Required Build/Run Tools Dependencies for Overlaybd tools RUN apt-get update && \ apt-get install -y ca-certificates && \ update-ca-certificates RUN apt update && \ apt install -y libcurl4-openssl-dev libext2fs-dev libaio-dev mysql-server # --- OVERLAYBD TOOLS --- FROM base AS overlaybd-build RUN apt update && \ apt install -y libgflags-dev libssl-dev libnl-3-dev libnl-genl-3-dev libzstd-dev && \ apt install -y zlib1g-dev binutils make git wget sudo tar gcc cmake build-essential g++ && \ apt install -y uuid-dev libjson-c-dev libkmod-dev libsystemd-dev autoconf automake libtool libpci-dev nasm && \ apt install -y pkg-config # Download and install Golang version 1.21 RUN wget https://go.dev/dl/go1.26.0.linux-amd64.tar.gz && \ tar -C /usr/local -xzf go1.26.0.linux-amd64.tar.gz && \ rm go1.26.0.linux-amd64.tar.gz # Set environment variables ENV PATH="/usr/local/go/bin:${PATH}" ENV GOPATH="/go" RUN git clone https://github.com/containerd/overlaybd.git && \ cd overlaybd && \ git submodule update --init && \ mkdir build && \ cd build && \ cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=0 -DENABLE_DSA=0 -DENABLE_ISAL=0 && \ make -j8 && \ make install # --- BUILD LOCAL CONVERTER --- FROM overlaybd-build AS convert-build WORKDIR /home/limiteduser/accelerated-container-image COPY . . WORKDIR /home/limiteduser/accelerated-container-image RUN make # --- FINAL --- FROM base WORKDIR /home/limiteduser/ # Copy Conversion Tools COPY --from=overlaybd-build /opt/overlaybd/bin /opt/overlaybd/bin COPY --from=overlaybd-build /opt/overlaybd/lib /opt/overlaybd/lib COPY --from=overlaybd-build /opt/overlaybd/baselayers /opt/overlaybd/baselayers # This is necessary for overlaybd_apply to work COPY --from=overlaybd-build /etc/overlaybd/overlaybd.json /etc/overlaybd/overlaybd.json COPY --from=convert-build /home/limiteduser/accelerated-container-image/bin/convertor ./bin/convertor # EXTRAS # Useful resources COPY cmd/convertor/resources/samples/mysql.conf ./mysql.conf COPY cmd/convertor/resources/samples/mysql-db-setup.sh ./mysql-db-setup.sh COPY cmd/convertor/resources/samples/mysql-db-manifest-cache-sample-workload.sh ./mysql-db-manifest-cache-sample-workload.sh RUN apt update && apt install -y wget # Add Oras CLI RUN wget "https://github.com/oras-project/oras/releases/download/v1.2.0/oras_1.2.0_linux_amd64.tar.gz" && \ mkdir -p oras-install/ && \ tar -zxf oras_1.2.0_*.tar.gz -C oras-install/ && \ mv oras-install/oras /usr/local/bin/ && \ rm -rf oras_1.2.0_*.tar.gz oras-install/ CMD ["./bin/convertor"]accelerated-container-image-1.4.2/cmd/convertor/testingresources/000077500000000000000000000000001514714641000252115ustar00rootroot00000000000000accelerated-container-image-1.4.2/cmd/convertor/testingresources/consts.go000066400000000000000000000061341514714641000270550ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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 testingresources import ( "context" "strings" "github.com/containerd/containerd/v2/pkg/reference" ) /* This package provides a local implementation of a registry complete with sample images of different types. Its built in such a way that we can add more complex images as well as more complex tests are required in the future. Note that the local registry is not particularly optimized or a good model for how to implement a local registry but its convenient to utilize with existing skopeo tooling. This is something that can be easily revised as more complex scenarios arise. For now we are using abstractions from https://pkg.go.dev/github.com/containers/image/v5 for the purpose of maintaining compatibility with skopeo image downloads as a quick, easy and reproducible way of adding and downloading images. */ const ( // MINIMAL MANIFESTS (For unit testing) // DOCKER V2 (amd64) DockerV2_Manifest_Simple_Ref = "sample.localstore.io/hello-world:amd64" DockerV2_Manifest_Simple_Digest = "sha256:7e9b6e7ba2842c91cf49f3e214d04a7a496f8214356f41d81a6e6dcad11f11e3" DockerV2_Manifest_Simple_Size = 525 DockerV2_Manifest_Simple_Config_Digest = "sha256:9c7a54a9a43cca047013b82af109fe963fde787f63f9e016fdc3384500c2823d" DockerV2_Manifest_Simple_Config_Size = 1470 DockerV2_Manifest_Simple_Layer_0_Digest = "sha256:719385e32844401d57ecfd3eacab360bf551a1491c05b85806ed8f1b08d792f6" DockerV2_Manifest_Simple_Layer_0_Size = 2457 // DOCKER V2 (amd64-converted) - overlaybd DockerV2_Manifest_Simple_Converted_Ref = "sample.localstore.io/hello-world:amd64-converted" DockerV2_Manifest_Simple_Converted_Digest = "sha256:42caa56a19e082b872d43f645bb392e25c9e78bce429755bd709fac598265f88" DockerV2_Manifest_Simple_Converted_Size = 641 // DOCKER MANIFEST LIST Docker_Manifest_List_Ref = "sample.localstore.io/hello-world:docker-list" Docker_Manifest_List_Digest = "sha256:726023f73a8fc5103fa6776d48090539042cb822531c6b751b1f6dd18cb5705d" ) const ( // OTHER CONSTS (For unit testing) ExpectedOverlaybdBaseLayerDigest = "sha256:a8b5fca80efae55088290f3da8110d7742de55c2a378d5ab53226a483f390e21" ) // ParseRef Parses a ref into its components: host, repository, tag/digest func ParseRef(ctx context.Context, ref string) (string, string, string, error) { refspec, err := reference.Parse(ref) if err != nil { return "", "", "", err } host := refspec.Hostname() repository := strings.TrimPrefix(refspec.Locator, host+"/") object := refspec.Object return host, repository, object, nil } accelerated-container-image-1.4.2/cmd/convertor/testingresources/local_db.go000066400000000000000000000110051514714641000272740ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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 testingresources import ( "context" "sync" "github.com/containerd/accelerated-container-image/cmd/convertor/database" "github.com/opencontainers/go-digest" ) type localdb struct { layerRecords []*database.LayerEntry manifestRecords []*database.ManifestEntry layerLock sync.Mutex // Protects layerRecords manifestLock sync.Mutex // Protects manifestRecords } // NewLocalDB returns a new local database for testing. This is a simple unoptimized in-memory database. func NewLocalDB() database.ConversionDatabase { return &localdb{} } func (l *localdb) CreateLayerEntry(ctx context.Context, host string, repository string, convertedDigest digest.Digest, chainID string, size int64) error { l.layerLock.Lock() defer l.layerLock.Unlock() l.layerRecords = append(l.layerRecords, &database.LayerEntry{ Host: host, Repository: repository, ChainID: chainID, ConvertedDigest: convertedDigest, DataSize: size, }) return nil } func (l *localdb) GetLayerEntryForRepo(ctx context.Context, host string, repository string, chainID string) *database.LayerEntry { l.layerLock.Lock() defer l.layerLock.Unlock() for _, entry := range l.layerRecords { if entry.Host == host && entry.ChainID == chainID && entry.Repository == repository { return entry } } return nil } func (l *localdb) GetCrossRepoLayerEntries(ctx context.Context, host, chainID string) []*database.LayerEntry { l.layerLock.Lock() defer l.layerLock.Unlock() var entries []*database.LayerEntry for _, entry := range l.layerRecords { if entry.Host == host && entry.ChainID == chainID { entries = append(entries, entry) } } return entries } func (l *localdb) DeleteLayerEntry(ctx context.Context, host, repository, chainID string) error { l.layerLock.Lock() defer l.layerLock.Unlock() // host - repo - chainID should be unique for i, entry := range l.layerRecords { if entry.Host == host && entry.ChainID == chainID && entry.Repository == repository { l.layerRecords = append(l.layerRecords[:i], l.layerRecords[i+1:]...) return nil } } return nil // No error if entry not found } func (l *localdb) CreateManifestEntry(ctx context.Context, host, repository, mediaType string, original, convertedDigest digest.Digest, size int64) error { l.manifestLock.Lock() defer l.manifestLock.Unlock() l.manifestRecords = append(l.manifestRecords, &database.ManifestEntry{ Host: host, Repository: repository, OriginalDigest: original, ConvertedDigest: convertedDigest, DataSize: size, MediaType: mediaType, }) return nil } func (l *localdb) GetManifestEntryForRepo(ctx context.Context, host, repository, mediaType string, original digest.Digest) *database.ManifestEntry { l.manifestLock.Lock() defer l.manifestLock.Unlock() for _, entry := range l.manifestRecords { if entry.Host == host && entry.OriginalDigest == original && entry.Repository == repository && entry.MediaType == mediaType { return entry } } return nil } func (l *localdb) GetCrossRepoManifestEntries(ctx context.Context, host, mediaType string, original digest.Digest) []*database.ManifestEntry { l.manifestLock.Lock() defer l.manifestLock.Unlock() var entries []*database.ManifestEntry for _, entry := range l.manifestRecords { if entry.Host == host && entry.OriginalDigest == original && entry.MediaType == mediaType { entries = append(entries, entry) } } return entries } func (l *localdb) DeleteManifestEntry(ctx context.Context, host, repository, mediaType string, original digest.Digest) error { l.manifestLock.Lock() defer l.manifestLock.Unlock() // Identify indices of items to be deleted. for i, entry := range l.manifestRecords { if entry.Host == host && entry.OriginalDigest == original && entry.Repository == repository && entry.MediaType == mediaType { l.manifestRecords = append(l.manifestRecords[:i], l.manifestRecords[i+1:]...) } } return nil // No error if entry not found } accelerated-container-image-1.4.2/cmd/convertor/testingresources/local_registry.go000066400000000000000000000115211514714641000305620ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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 testingresources import ( "context" "errors" "io" "os" "path/filepath" "github.com/containerd/containerd/v2/core/images" "github.com/containerd/errdefs" v1 "github.com/opencontainers/image-spec/specs-go/v1" ) // TestRegistry is a mock registry that can be used for testing purposes. // The implementation is a combination of in memory and local storage, where // the in memory storage is used for pushes and overrides. The local storage // provides a prebuilt index of repositories and manifests for pulls. // Features: Pull, Push, Resolve. // Limitations: Cross Repository Mounts are not currently supported, Delete // is not supported. type TestRegistry struct { internalRegistry internalRegistry opts RegistryOptions } type RegistryOptions struct { InmemoryRegistryOnly bool // Specifies if the registry should not load any resources from storage LocalRegistryPath string // Specifies the path to the local registry ManifestPushIgnoresLayers bool // Specifies if the registry should require layers to be pushed before manifest } type internalRegistry map[string]*RepoStore func NewTestRegistry(ctx context.Context, opts RegistryOptions) (*TestRegistry, error) { TestRegistry := TestRegistry{ internalRegistry: make(internalRegistry), opts: opts, } if !opts.InmemoryRegistryOnly { files, err := os.ReadDir(opts.LocalRegistryPath) if err != nil { return nil, err } for _, file := range files { if file.IsDir() { // Actual load from storage is done in a deferred manner as an optimization repoStore := NewRepoStore( ctx, filepath.Join(opts.LocalRegistryPath, file.Name()), &opts, ) TestRegistry.internalRegistry[file.Name()] = repoStore } } if err != nil { return nil, err } } return &TestRegistry, nil } func (r *TestRegistry) Resolve(ctx context.Context, ref string) (v1.Descriptor, error) { _, repository, tag, err := ParseRef(ctx, ref) if err != nil { return v1.Descriptor{}, err } if repo, ok := r.internalRegistry[repository]; ok { return repo.Resolve(ctx, tag) } return v1.Descriptor{}, errors.New("repository not found") } func (r *TestRegistry) Fetch(ctx context.Context, repository string, descriptor v1.Descriptor) (io.ReadCloser, error) { // Add in memory store for overrides/pushes if repo, ok := r.internalRegistry[repository]; ok { return repo.Fetch(ctx, descriptor) } return nil, errdefs.ErrNotFound } // Push Adds content to the in-memory store func (r *TestRegistry) Push(ctx context.Context, repository string, tag string, descriptor v1.Descriptor, content []byte) error { repo, ok := r.internalRegistry[repository] if ok { return repo.Push(ctx, descriptor, tag, content) } // If the repository does not exist we create a new one inmemory repo = NewRepoStore(ctx, repository, &r.opts) repo.inmemoryRepoOnly = true r.internalRegistry[repository] = repo repo.Push(ctx, descriptor, tag, content) return nil } func (r *TestRegistry) Exists(ctx context.Context, repository string, tag string, desc v1.Descriptor) (bool, error) { repo, ok := r.internalRegistry[repository] if !ok { return false, nil } // If no tag needs to be verified we only care about the digest if tag == "" { return repo.Exists(ctx, desc) } // If a tag is specified for a digest we need to match the tag to the digest switch desc.MediaType { case v1.MediaTypeImageManifest, v1.MediaTypeImageIndex, images.MediaTypeDockerSchema2Manifest, images.MediaTypeDockerSchema2ManifestList: digest, ok := repo.inmemoryRepo.tags[tag] if !ok { return false, nil } if digest != desc.Digest { return false, nil } // Tag matches provided digest. Since no delete option exists // we don't need to check if the digest exists in the repo stores. return true, nil default: return repo.Exists(ctx, desc) } } // Mount simulates a cross repo mount by copying blobs from srcRepository to targetRepository func (r *TestRegistry) Mount(ctx context.Context, srcRepository string, targetRepository string, desc v1.Descriptor) error { rd, err := r.Fetch(ctx, srcRepository, desc) if err != nil { return err } defer rd.Close() var body []byte if body, err = io.ReadAll(rd); err != nil { return err } return r.Push(ctx, targetRepository, "", desc, body) } accelerated-container-image-1.4.2/cmd/convertor/testingresources/local_remotes.go000066400000000000000000000157721514714641000304040ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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 testingresources import ( "context" "errors" "fmt" "io" "time" "github.com/containerd/containerd/v2/core/content" "github.com/containerd/containerd/v2/core/remotes" "github.com/containerd/containerd/v2/core/remotes/docker" "github.com/containerd/errdefs" "github.com/opencontainers/go-digest" v1 "github.com/opencontainers/image-spec/specs-go/v1" ) const ( labelDistributionSource = "containerd.io/distribution.source" ) type MockLocalResolver struct { testReg *TestRegistry } func NewMockLocalResolver(ctx context.Context, localRegistryPath string) (*MockLocalResolver, error) { reg, err := NewTestRegistry(ctx, RegistryOptions{ LocalRegistryPath: localRegistryPath, InmemoryRegistryOnly: false, ManifestPushIgnoresLayers: false, }) if err != nil { return nil, err } return &MockLocalResolver{ testReg: reg, }, nil } func NewCustomMockLocalResolver(ctx context.Context, testReg *TestRegistry) (*MockLocalResolver, error) { return &MockLocalResolver{ testReg: testReg, }, nil } func (r *MockLocalResolver) Resolve(ctx context.Context, ref string) (string, v1.Descriptor, error) { desc, err := r.testReg.Resolve(ctx, ref) if err != nil { return "", v1.Descriptor{}, err } return "", desc, nil } func (r *MockLocalResolver) Fetcher(ctx context.Context, ref string) (remotes.Fetcher, error) { _, repository, _, err := ParseRef(ctx, ref) if err != nil { return nil, err } return &MockLocalFetcher{ testReg: r.testReg, repository: repository, }, nil } func (r *MockLocalResolver) Pusher(ctx context.Context, ref string) (remotes.Pusher, error) { host, repository, tag, err := ParseRef(ctx, ref) if err != nil { return nil, err } return &MockLocalPusher{ testReg: r.testReg, repository: repository, tag: tag, host: host, tracker: docker.NewInMemoryTracker(), }, nil } type MockLocalFetcher struct { testReg *TestRegistry repository string } func (f *MockLocalFetcher) Fetch(ctx context.Context, desc v1.Descriptor) (io.ReadCloser, error) { return f.testReg.Fetch(ctx, f.repository, desc) } type MockLocalPusher struct { testReg *TestRegistry repository string tag string host string tracker docker.StatusTracker } // Writer is not used by overlaybd conversion. func (p MockLocalPusher) Writer(ctx context.Context, opts ...content.WriterOpt) (content.Writer, error) { return nil, errors.New("not implemented") } func (p MockLocalPusher) Push(ctx context.Context, desc v1.Descriptor) (content.Writer, error) { return p.push(ctx, desc, remotes.MakeRefKey(ctx, desc), false) } func (p MockLocalPusher) push(ctx context.Context, desc v1.Descriptor, ref string, unavailableOnFail bool) (content.Writer, error) { if l, ok := p.tracker.(docker.StatusTrackLocker); ok { l.Lock(ref) defer l.Unlock(ref) } status, err := p.tracker.GetStatus(ref) if err == nil { if status.Committed && status.Offset == status.Total { return nil, fmt.Errorf("ref %v: %w", ref, errdefs.ErrAlreadyExists) } if unavailableOnFail && status.ErrClosed == nil { // Another push of this ref is happening elsewhere. The rest of function // will continue only when `errdefs.IsNotFound(err) == true` (i.e. there // is no actively-tracked ref already). return nil, fmt.Errorf("push is on-going: %w", errdefs.ErrUnavailable) } } else if !errdefs.IsNotFound(err) { return nil, fmt.Errorf("failed to get status: %w", err) } // Check Exists first ok, err := p.testReg.Exists(ctx, p.repository, p.tag, desc) if err != nil { return nil, err } if ok { return nil, errdefs.ErrAlreadyExists } // Layer mounts if mountRepo, ok := desc.Annotations[fmt.Sprintf("%s.%s", labelDistributionSource, p.host)]; ok { err = p.testReg.Mount(ctx, mountRepo, p.repository, desc) if err != nil { return nil, err } return nil, errdefs.ErrAlreadyExists } respC := make(chan error, 1) p.tracker.SetStatus(ref, docker.Status{ Status: content.Status{ Ref: ref, Total: desc.Size, Expected: desc.Digest, StartedAt: time.Now(), }, }) pr, pw := io.Pipe() body := io.NopCloser(pr) go func() { defer close(respC) // Reader must be read first before other error checks buf, err := io.ReadAll(body) if err != nil { respC <- err pr.CloseWithError(err) return } err = p.testReg.Push(ctx, p.repository, p.tag, desc, buf) if err != nil { respC <- err pr.CloseWithError(err) return } respC <- nil }() return &pushWriter{ ref: ref, pipe: pw, responseC: respC, expected: desc.Digest, tracker: p.tracker, }, nil } type pushWriter struct { ref string pipe *io.PipeWriter responseC <-chan error expected digest.Digest tracker docker.StatusTracker } func (pw *pushWriter) Write(p []byte) (n int, err error) { status, err := pw.tracker.GetStatus(pw.ref) if err != nil { return n, err } n, err = pw.pipe.Write(p) status.Offset += int64(n) status.UpdatedAt = time.Now() pw.tracker.SetStatus(pw.ref, status) return } func (pw *pushWriter) Close() error { status, err := pw.tracker.GetStatus(pw.ref) if err == nil && !status.Committed { // Closing an incomplete writer. Record this as an error so that following write can retry it. status.ErrClosed = errors.New("closed incomplete writer") pw.tracker.SetStatus(pw.ref, status) } return pw.pipe.Close() } func (pw *pushWriter) Status() (content.Status, error) { status, err := pw.tracker.GetStatus(pw.ref) if err != nil { return content.Status{}, err } return status.Status, nil } func (pw *pushWriter) Digest() digest.Digest { return pw.expected } func (pw *pushWriter) Commit(ctx context.Context, size int64, expected digest.Digest, opts ...content.Opt) error { // Check whether read has already thrown an error if _, err := pw.pipe.Write([]byte{}); err != nil && err != io.ErrClosedPipe { return fmt.Errorf("pipe error before commit: %w", err) } if err := pw.pipe.Close(); err != nil { return err } err := <-pw.responseC if err != nil { return err } status, err := pw.tracker.GetStatus(pw.ref) if err != nil { return fmt.Errorf("failed to get status: %w", err) } if size > 0 && size != status.Offset { return fmt.Errorf("unexpected size %d, expected %d", status.Offset, size) } status.Committed = true status.UpdatedAt = time.Now() pw.tracker.SetStatus(pw.ref, status) return nil } func (pw *pushWriter) Truncate(size int64) error { return errors.New("cannot truncate remote upload") } accelerated-container-image-1.4.2/cmd/convertor/testingresources/local_repo.go000066400000000000000000000137531514714641000276700ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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 testingresources import ( "bytes" "context" "encoding/json" "errors" "fmt" "io" "strings" "github.com/containerd/containerd/v2/core/images" "github.com/containerd/errdefs" "github.com/opencontainers/go-digest" v1 "github.com/opencontainers/image-spec/specs-go/v1" "oras.land/oras-go/v2/content/oci" ) type RepoStore struct { path string inmemoryRepoOnly bool fileStore *oci.Store inmemoryRepo *inmemoryRepo opts *RegistryOptions } type inmemoryRepo struct { blobs map[string][]byte tags map[string]digest.Digest } // NewRepoStore creates a new repo store. Path provides the filesystem path to the OCI layout store. The // inmemory component is initialized with an empty store. Both components work together to provide a // unified view of the repository. func NewRepoStore(ctx context.Context, path string, opts *RegistryOptions) *RepoStore { inmemoryRepo := &inmemoryRepo{ blobs: make(map[string][]byte), tags: make(map[string]digest.Digest), } return &RepoStore{ path: path, opts: opts, inmemoryRepo: inmemoryRepo, } } // LoadStore loads the OCI layout store from the provided path func (r *RepoStore) LoadStore(ctx context.Context) error { // File Store is already initialized if r.fileStore != nil { return nil } if r.opts.InmemoryRegistryOnly && !r.inmemoryRepoOnly { return errors.New("LoadStore should not be invoked if registry or repo is memory only") } if r.path == "" { return errors.New("LoadStore was not provided a path") } // Load an OCI layout store store, err := oci.New(r.path) if err != nil { return err } r.fileStore = store return nil } // Resolve resolves a tag to a descriptor func (r *RepoStore) Resolve(ctx context.Context, tag string) (v1.Descriptor, error) { if digest, ok := r.inmemoryRepo.tags[tag]; ok { if blob, ok := r.inmemoryRepo.blobs[digest.String()]; ok { parsedManifest := v1.Manifest{} json.Unmarshal(blob, &parsedManifest) return v1.Descriptor{ Digest: digest, Size: int64(len(blob)), MediaType: parsedManifest.MediaType, }, nil } } if !r.opts.InmemoryRegistryOnly && !r.inmemoryRepoOnly { if err := r.LoadStore(ctx); err != nil { return v1.Descriptor{}, err } return r.fileStore.Resolve(ctx, tag) } return v1.Descriptor{}, errdefs.ErrNotFound } // Fetch fetches a blob from the repository func (r *RepoStore) Fetch(ctx context.Context, descriptor v1.Descriptor) (io.ReadCloser, error) { if blob, ok := r.inmemoryRepo.blobs[descriptor.Digest.String()]; ok { return io.NopCloser(bytes.NewReader(blob)), nil } if !r.opts.InmemoryRegistryOnly && !r.inmemoryRepoOnly { if err := r.LoadStore(ctx); err != nil { return nil, err } content, err := r.fileStore.Fetch(ctx, descriptor) if err != nil { if strings.Contains(err.Error(), "not found") { return nil, errdefs.ErrNotFound // Convert to containerd error } return nil, err } return content, nil } return nil, errdefs.ErrNotFound } // Exists checks if a blob exists in the repository func (r *RepoStore) Exists(ctx context.Context, descriptor v1.Descriptor) (bool, error) { if _, ok := r.inmemoryRepo.blobs[descriptor.Digest.String()]; ok { return true, nil } if !r.opts.InmemoryRegistryOnly && !r.inmemoryRepoOnly { if err := r.LoadStore(ctx); err != nil { return false, err } exists, err := r.fileStore.Exists(ctx, descriptor) if err != nil { return false, err } if exists { return true, nil } } return false, nil } // Push pushes a blob to the in memory repository. If the blob already exists, it returns an error. // Tag is optional and can be empty. func (r *RepoStore) Push(ctx context.Context, desc v1.Descriptor, tag string, content []byte) error { manifestExists, err := r.Exists(ctx, desc) if err != nil { return err } // Verify content by computing the digest. Real registries are expected to do this. contentDigest := digest.FromBytes(content) if contentDigest != desc.Digest { return fmt.Errorf("content digest %s does not match descriptor digest %s", contentDigest.String(), desc.Digest.String()) } isManifest := false switch desc.MediaType { case v1.MediaTypeImageManifest, images.MediaTypeDockerSchema2Manifest: isManifest = true if r.opts.ManifestPushIgnoresLayers || manifestExists { break // No layer verification necessary } manifest := v1.Manifest{} json.Unmarshal(content, &manifest) for _, layer := range manifest.Layers { exists, err := r.Exists(ctx, layer) if err != nil { return err } if !exists { return fmt.Errorf("layer %s not found", layer.Digest.String()) } } case v1.MediaTypeImageIndex, images.MediaTypeDockerSchema2ManifestList: isManifest = true if r.opts.ManifestPushIgnoresLayers || manifestExists { break // No manifest verification necessary } manifestList := v1.Index{} json.Unmarshal(content, &manifestList) for _, subManifestDesc := range manifestList.Manifests { exists, err := r.Exists(ctx, subManifestDesc) if err != nil { return err } if !exists { return fmt.Errorf("sub manifest %s not found", subManifestDesc.Digest.String()) } } } r.inmemoryRepo.blobs[desc.Digest.String()] = content // We need to always store the manifest digest to tag mapping as the latest pushed manifest // may have a different digest than the previous one. if isManifest && tag != "" { r.inmemoryRepo.tags[tag] = desc.Digest } return nil } accelerated-container-image-1.4.2/cmd/convertor/testingresources/mocks/000077500000000000000000000000001514714641000263255ustar00rootroot00000000000000accelerated-container-image-1.4.2/cmd/convertor/testingresources/mocks/generate.sh000077500000000000000000000026601514714641000304620ustar00rootroot00000000000000# Usage: ./generate.sh $registry $username $password # Prerequisites: oras # Generates simple hello-world images in ./registry folder # This script serves as a way to regenerate the images in the registry folder if necessary # and to document the taken steps to generate the test registry. Add more if new images are needed. # do note that it might be necessary to adjust the consts.go file to make sure tests don't break if # the source hello-world images are updated. registry=$1 username=$2 password=$3 srcTag="linux" srcRepo="hello-world" srcImage="docker.io/library/$srcRepo:$srcTag" srcRegistry="docker.io/library" destFolder="./registry" echo "Begin image generation based on src image: $srcImage" oras cp --to-oci-layout $srcImage $destFolder/hello-world:docker-list # Tag some submanifests oras cp --to-oci-layout --platform linux/arm64 $srcRegistry/hello-world:linux $destFolder/hello-world/:arm64 oras cp --to-oci-layout --platform linux/amd64 $srcRegistry/hello-world:linux $destFolder/hello-world/:amd64 # Add sample converted manifest oras login $registry -u $username -p $password oras cp --from-oci-layout $destFolder/hello-world/:amd64 $registry/hello-world:amd64 cwd=$(pwd) cd ../../../../ make sudo bin/convertor --repository $registry/hello-world --input-tag amd64 --oci --overlaybd amd64-converted -u $username:$password cd $cwd oras cp --to-oci-layout $registry/hello-world:amd64-converted $destFolder/hello-world/:amd64-convertedaccelerated-container-image-1.4.2/cmd/convertor/testingresources/mocks/registry/000077500000000000000000000000001514714641000301755ustar00rootroot00000000000000accelerated-container-image-1.4.2/cmd/convertor/testingresources/mocks/registry/hello-world/000077500000000000000000000000001514714641000324255ustar00rootroot00000000000000accelerated-container-image-1.4.2/cmd/convertor/testingresources/mocks/registry/hello-world/blobs/000077500000000000000000000000001514714641000335265ustar00rootroot00000000000000sha256/000077500000000000000000000000001514714641000344575ustar00rootroot00000000000000accelerated-container-image-1.4.2/cmd/convertor/testingresources/mocks/registry/hello-world/blobs004d23c66201b22fce069b7505756f17088de7889c83891e9bc69d749fa3690e000066400000000000000000000010151514714641000446640ustar00rootroot00000000000000accelerated-container-image-1.4.2/cmd/convertor/testingresources/mocks/registry/hello-world/blobs/sha256{ "schemaVersion": 2, "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "config": { "mediaType": "application/vnd.docker.container.image.v1+json", "size": 1468, "digest": "sha256:a8117b42328be6ba54c594f9f14e216db10203deb22cc28aaa2c134f9ae2e0fd" }, "layers": [ { "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", "size": 2716, "digest": "sha256:630da353b594c9ca52c67727920737dba3e5aaa4fbe20cdd0fc70c0591b7a639" } ] }022231c3f1af70e0cc42f0c3781eabf174cff608210585bf93fc83e16d33672d000066400000000000000000023470001514714641000451660ustar00rootroot00000000000000accelerated-container-image-1.4.2/cmd/convertor/testingresources/mocks/registry/hello-world/blobs/sha256overlaybd.pax 17 5356 xustar0015 size=639491 overlaybd.commit100644 0 0 0 0 12025 0ustar00rootrootZFiletuji.yyf@Alibaba`E4'7DpCUmLSMTe~cҔDLOϮ01BB4FCE-3EB1-B56B-05AD-F99504DAFD310- _h[P>p@ ?## sS 8kSgF= $,Pk]   y y >o}@ @$z#@$$@$$@$$@$$@$$@$$@$   $  @$   $  @$   $  @$   $  @$   $  @$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@P/   }   @$!!!$!!@$"""$""@$###$##@$$$$$$$@$%%%$%%@$&&&$&&@$'''$''@$((($((@$)))$))@$***$**@$+++$++@$,,,$,,@$---$--@$...$..@$///$//@$000$00@$111$11@$222$22@$333$33@$444$44@$555$55@$666$66@$777$77@$888$88@$999$99@$:::$::@$;;;$;;@$<<<$<<@$===$==@$>>>$>>@$???$??@P~u@@@} @@@$AAA$AA@$BBB$BB@$CCC$CC@$DDD$DD@$EEE$EE@$FFF$FF@$GGG$GG@$HHH$HH@$III$II@$JJJ$JJ@$KKK$KK@$LLL$LL@$MMM$MM@$NNN$NN@$OOO$OO@$PPP$PP@$QQQ$QQ@$RRR$RR@$SSS$SS@$TTT$TT@$UUU$UU@$VVV$VV@$WWW$WW@$XXX$XX@$YYY$YY@$ZZZ$ZZ@$[[[$[[@$\\\$\\@$]]]$]]@$^^^$^^@$___$__@P```} ``@$aaa$aa@$bbb$bb@$ccc$cc@$ddd$dd@$eee$ee@$fff$ff@$ggg$gg@$hhh$hh@$iii$ii@$jjj$jj@$kkk$kk@$lll$ll@$mmm$mm@$nnn$nn@$ooo$oo@$ppp$pp@$qqq$qq@$rrr$rr@$sss$ss@$ttt$tt@$uuu$uu@$vvv$vv@$www$ww@$xxx$xx@$yyy$yy@$zzz$zz@${{{${{@$|||$||@$}}}$}}@$~~~$~~@$$@P4} @$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@P+} @$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@P_E} @$$@$€$@$À$@$Ā$@$ŀ$@$ƀ$@$ǀ$@$Ȁ$@$ɀ$@$ʀ$@$ˀ$@$̀$@$̀$@$΀$@$π$@$Ѐ$@$р$@$Ҁ$@$Ӏ$@$Ԁ$@$Հ$@$ր$@$׀$@$؀$@$ـ$@$ڀ$@$ۀ$@$܀$@$݀$@$ހ$@$߀$@P } @$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$" yP P@\ Pƕ PO&# P P^޹lPDɏPQ3VP }P&="PPPȏP(WP>PemP6yWP"ȏP9/mPP("PנP'::Pa P`ʏ!!PU""PoI&##Po$$P~ %%PQ &&Pq3''P((P\ ))P4**PSM ++P,,PBU--PU!ʏ..PMo//P00P11P22P7!33Pl/44Ptn55P/66PT77P:ˏ88P=99P*n::P3ˏ;;P%hT<<Pp(==P4+>>Pı??P;!@@Pb$AAPK9ZBBPcCCPDЁDDPQEEPUOΏFFP!vkGGPZzHHPfΏIIPw=qQJJPHKKPxkLLP׻MMPid$NNP%]OOPf~PPP=SQQPGjRRP2ϏSSP͆jPTTP#FUUPVVP,_̺WWP%XXPxYYPCˀZZP%[[PA\\Pmj]]P^^P[P__PTϏ``PWqaaPbbPX(ccPsH'ddPIkdeeP0hffPF͏ggP]RhhPkZhiiPjjPd,RkkPwc͏llPuoOmmP4nnPz'ooPvppPޗSqqP!x̏rrPVAissP. ttP&uuP?NmvvPTwwP0烏xxP๏yyPS&zzPRj{{P ||P̏}}PJFS~~PP iPP=bPǏP[XPCP (PP٢-PťP.P/-P!_PGbP0P:XP?݉ǏPd4,P䇳Pk~P% Pz=!YPfƏPucPPXƏPYPWzP!&cPF9 Pb,PIP3PJ[PďPaP/P.PP PR:P2N=P.P=PP,(ďPӗ[P# PVaP4PxoP&Pw/PPf `Pw3ŏPi,ZP0`PDk4P ZPKŏPPZ!Ps/PU(PڠSP%*€€PaُÀÀP*:jĀĀP"FŀŀP;y_ƀƀPǀǀP4eȀȀPx_ɀɀPʀʀPeeˀˀP>À̀P&m*̀̀P}޵΀΀PππPTЀЀPSdррPZҀҀP\c^ӀӀPDԀԀPM\ՀՀPOրրPBv+׀׀PŴ؀؀PoŽــPqڀڀP`HۀۀP@+܀܀PqX݀݀PddހހP~]߀߀P^P9+PpxP6A)PɱP'fPmP(hT\P3ÏP/PtSfP jÏP\PPFPlP7)PUqPO]PP@HgPdPQ(PP^M]PQZ(Ps 鷏PP|cPO]PmPgPbIvPJPQՏP0'pP^kPs?PO( P2P@遚P Pm5? P4 Pbo PwՏ Ps, JPP|pP'PԤP(NP.>P9 PVqP6̈ԏPɗ;KPP PMz5!!P!s""PB֏##P I$$PS %%P&&P\9''Pb<((Pq~))P%**P~<++P',,Po s--P..P`=I//Pf2֏00P=11P;_<22P33P444PچH55P%)׏66PGr77P*88P׏99P[H::P.;;Pr<<P悱==P=>>PC;??P@@PpAAPfÝBBPQ8CCPi IDDPewEEPxIFFPMGGPw\ҏHHP[IIPZwJJPUҏKKPUbMLLPNMMPDMNNPħOOPKw8PPPӏQQPyLRRP/@SSPtvTTPlߦUUP7l9VVPUWWPXXP,9YYPӱRZZP#+k[[Pp؜\\P2hL]]P3Gӏ^^P=~v__P``Pz褏aaP@[;bbPubccPddPdяeePNNffPkXwggPthhPFNiiPDpяjjPIItkkPllPX;mmPennPW\\ooPppPeRuqqP >rrPOssPkЏttPGuuPvvP&:wwP}~xxPayyyP0:zzP{{P?@:||Pl}}P.u~~P"ЏP!yUOP3ۏP?hDPᏃP0.~PޱP!1PpP.+; P7<1PlP P P)DPۏPt~P/ᏐPIM PPF'0PהPWϸP PX2EPUڏPuIP5Pz ڏPӿEPk˓P Pd PQ0P#}P<⏢P,yGP"؏P=:Pa)P22P͠PPP}.P&2P>⏭Pe}P;؏PGP5PU3PPZ\ PD ُPKFP㏷PD|PFPiُP|PfX'㏼P@ 3PwP Px2PRʓP y P@ÁÁP6āāPŁŁPlyƁƁPU܏ǁǁPJCȁȁPVyɁɁP4 RʁʁPėkCˁˁP;܏́́P ́́P*G΁΁P~6ρρP%NͩЁЁP~,pBссPwݏҁҁPqxӁӁPIԁԁP`e7ՁՁP֨ցցPoo ׁׁP4\؁؁PB([ففPs7ځځPMђہہPb ܁܁P\Nݏ݁݁PBށށPSk߁߁P0wxPR5P᪏PPCkP [G@PߏPzP~P(yߏP׆@P'PG@zP6_lP5P9搏PUPPb[{PfbޏPm=AP%P|~NPwPs4PP^pPbI4PQ9P!{P@zeP\APOޏP!P&P&P)}>PeTP8>ˏPȤnP7+P,ˏ PT P" Pyn Pa9 P:!PP PPPoP_X7ʏPUPNP@PA"P PlP%Pc\ PProPD0P} UPʏ P:l!!P7,""P5##P"$$P$%%P۵9m&&P+/ȏ''PtW((Phm))P3**P >W++Pȏ,,P--P..P++"//Pp00P%V11PLIɏ22Pӯl33PC44P0#55PR˃66PQ77P] 88P99PpM#::Pׄ;;P7<<Pɏ==PnϨV>>PU??Pa"l@@PiAAPiBBPGPSCCP̏DDPEEP_|FFPE&GGPHHP҂IIP-BJJPC{KKP"&LLPMMP3[WiNNPn̏OOPkkPPllP/|PmmPϏnnP JjooPEppPsqqP{(KrrPr$ssPtttPkuuPe^vvP0gQwwPjkΏxxPwyyPG,`kzzPYΏ{{PHQ||PƁ}}PYu~~P4LPVo$P%PH~/P7PGPŏPVZPf"PY=`P!ZPtz%ŏP`P{P/Pj0Pb Pe9P>[aPP1[P>ďP P߂P/.PC+P_,PP P.P9PㆊaPďPG[PT%PP[o,P4PJ,0cPwPEYP ƏPhPcPgkƏP07YPv(PsPy鑳P",PПǏP",XPP-JbPRP< 9-P̓P3ȳPԴ-PP>PNPVXP ǏP+bP̘PD`Pg‚‚PÂÂPpY]ĂĂPułłPaƂƂPǂǂPn\L(ȂȂP@KɂɂPCʂʂP(˂˂PLr̂̂P^g͂͂P]΂΂P]ςςPRXgЂЂP :)ттPai҂҂PPӂӂP㌏ԂԂP\ՂՂP|ÏււPyEfׂׂP"؂؂P5>ÏققPeB\ڂڂP:{ۂۂPŤf܂܂P+䶏݂݂PW)ނނP$}n߂߂P&Pc^PKPlrdPUP}M+P^PrgPԎP_ӴP`+PP YPQPAIPu^PNLPdPBPPpȵP+{*P3WP hePPn_PֵieP)Pt_P&/PP7|P7lϏP*P8EPP N-P=PPꗋrP8PVHP ׏P P7Jr P?׏ P8H P֓ P)PR*P& =P}k$֏P0IPrPsPc1P<Pl(PsPAo<P4PNP6P_IP֏PP,sPw# P!!Pŵ>""P_##P?$$P ԏ%%PGK&&Pݙ''P*q((P+-K))Pԏ**P$[q++P,,P58>--PC..P:ٲ//Pł00Pp11Pa22P!6J33PnzՏ44Pb55P966P#?77Pp88P䗚99P]$::P%;;PR~?<<Pf==PC=1p>>PՏ??PLJ@@PtCOAAPQӏccPeLddP>}ΜeeP&}ffP1DggP9hhPiiPCjjP:z9kkPaɦllPyvmmP"VnnP oLooPӏppPa8qqPVҧrrP@ssPYXttPtMuuPHXҏvvPwwwPGMxxPJҏyyPjMzzPD{{Pesw||P_}}Pt\8~~P՝P{fP$Pe PPjM3PU1㏅P{|PُPtFP|PY㏊PFPVI6ُPQ PG P3PH#PGP-؏Ph}P3⏔P +2Pp8PPP/P2P l?P7P1/؏PtGP>*⏟P}Py\0PPv6 PƅPgީEPڏPh#PDPEXڏP$EPJP®P[ڂP10PTP@ P"ᏱPy~PۏP?DPP PaP:*1P&- P3}P1P<PҤ8~P-᏾PeDP">ۏPޏPRJAƒƒPwsÃÃP],{ăăP4쫏ŃŃPLo_4ƃƃPfǃǃPCȃȃP4ɃɃPnaʃʃPsX˃˃Pa(둏̃̃P0A̓̓Ppktޏ΃΃PM{σσPЃЃP$CууPۓ҃҃P+ 5ӃӃPRzԃԃP:JVzՃՃPփփP5@׃׃Poߏ؃؃PhككPzڃڃP ߏۃۃPVQ@܃܃PN}݃݃PރރP ߃߃PD5PN=axPfPABPXݏPPt PǒP_~7P%MPr9JPb P}Ps7Pl_PxPczݏP!fBPC۩P8h6PȂQP7 P܏P&}CPDP)[yPGCPC܏PzyP P6PVPo P_ܓP7SPȐiP8 ̏PQjSP)IFPP&̹P&P x/3PB?Pm6oA1Q b  ) =)4@JO` 4 p 00 [ !m3(Sd P~ýP$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$ . ..  lost+found(helloPq .. PglP8P8  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~wލELF@>1T@@0@8@ ,2M!8 p 8p B h!l8S"/ "?8<3 @Qtd87PRtdpQ'gPdcHPX H|$ 621*APH'`ZH1HH5HbH7HWE1I@H2@fH=/Hq9tH@t @H=a)05Z)HH?HHHtAvfD@!=9/u/UH=.Ht H=.h/]x{SH1L?@P|$ H@@H1H HHuHHHtH%w HPHT HH$V@.D0$ ^tHpD$PH uH$%AOHDHP0X0H2HƄt.C0HZ,L!*"H@+[H[HC 1[AULoATUHSQH+L%,0Ѝ8II!6(LH+C(LIIEHSHsH+{(HB;ELIl$Z[]A\A]RLO11LG(ALMtd0uLttBHt7HH+H.Qtdu&Hp(D*L9vHTIG5*HIHG HtEHB HJH r sB(H*:BHeP0Hb 0;@* E1. ?HHrH!HH 0)Ѓw H+0 1="00sapH5` xvA"I11 J@&RAqXHc!!<\$ATIHvRAD ILA\HHrDbt H uHHHtuHppdH%uL4P^^ Hello from Docker! This message shows that your installation appears to be working correctly. To generate tX,n took the follow@steps: 1. The)client contacted5 daemon. 23S pull0 "h-world" imMHub. (amd64) 3V@creaRa newBinerK0_which runFeYexecutablat produce!a outpum are currently reading. 4astream$atF(to'C,sO!it"toterminaltry somethmore ambitious,1@ can an Ubuntu with: $ dX(R-it u)bash Sh*s, autom+K@flow"nd{Pe a freID: https://hub.k.com/ For:examplesLideas, visit>Hdocs? get-started/ /dev/nullzRx "$`A` v2@@ p~#3@@!.shstrtab.init.text.fini.rodata.eh_frame$b_array$ 1gotb.plt.63bssh+ G,@@@Y@""# @/2 @ ;2A%@"8#@"8##4 W/"?@0@W;@@@G@@ L@0"U@@ :8[$@$@X <@`8PVcP$P$P$P$P$P$P$P$P$P$P$P$@ ?## sS 8kSgF= $,Pǎ   y y >o}@ @$z#@$$@$$@$$@$$@$$@$$@$   $  @$   $  @$   $  @$   $  @$   $  @$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@PiU   }   @$!!!$!!@$"""$""@$###$##@$$$$$$$@$%%%$%%@$&&&$&&@$'''$''@$((($((@$)))$))@$***$**@$+++$++@$,,,$,,@$---$--@$...$..@$///$//@$000$00@$111$11@$222$22@$333$33@$444$44@$555$55@$666$66@$777$77@$888$88@$999$99@$:::$::@$;;;$;;@$<<<$<<@$===$==@$>>>$>>@$???$??@P~u@@@} @@@$AAA$AA@$BBB$BB@$CCC$CC@$DDD$DD@$EEE$EE@$FFF$FF@$GGG$GG@$HHH$HH@$III$II@$JJJ$JJ@$KKK$KK@$LLL$LL@$MMM$MM@$NNN$NN@$OOO$OO@$PPP$PP@$QQQ$QQ@$RRR$RR@$SSS$SS@$TTT$TT@$UUU$UU@$VVV$VV@$WWW$WW@$XXX$XX@$YYY$YY@$ZZZ$ZZ@$[[[$[[@$\\\$\\@$]]]$]]@$^^^$^^@$___$__@P```} ``@$aaa$aa@$bbb$bb@$ccc$cc@$ddd$dd@$eee$ee@$fff$ff@$ggg$gg@$hhh$hh@$iii$ii@$jjj$jj@$kkk$kk@$lll$ll@$mmm$mm@$nnn$nn@$ooo$oo@$ppp$pp@$qqq$qq@$rrr$rr@$sss$ss@$ttt$tt@$uuu$uu@$vvv$vv@$www$ww@$xxx$xx@$yyy$yy@$zzz$zz@${{{${{@$|||$||@$}}}$}}@$~~~$~~@$$@P4} @$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@P+} @$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@P_E} @$$@$€$@$À$@$Ā$@$ŀ$@$ƀ$@$ǀ$@$Ȁ$@$ɀ$@$ʀ$@$ˀ$@$̀$@$̀$@$΀$@$π$@$Ѐ$@$р$@$Ҁ$@$Ӏ$@$Ԁ$@$Հ$@$ր$@$׀$@$؀$@$ـ$@$ڀ$@$ۀ$@$܀$@$݀$@$ހ$@$߀$@P } @$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$" yPP$P$P$P$P$P$P$P$P$/4PdOoNP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$,/PP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$@ ?## sS 8kSgF= $,P>b   y y >o}@ @$z#@$$@$$@$$@$$@$$@$$@$   $  @$   $  @$   $  @$   $  @$   $  @$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@PiU   }   @$!!!$!!@$"""$""@$###$##@$$$$$$$@$%%%$%%@$&&&$&&@$'''$''@$((($((@$)))$))@$***$**@$+++$++@$,,,$,,@$---$--@$...$..@$///$//@$000$00@$111$11@$222$22@$333$33@$444$44@$555$55@$666$66@$777$77@$888$88@$999$99@$:::$::@$;;;$;;@$<<<$<<@$===$==@$>>>$>>@$???$??@P~u@@@} @@@$AAA$AA@$BBB$BB@$CCC$CC@$DDD$DD@$EEE$EE@$FFF$FF@$GGG$GG@$HHH$HH@$III$II@$JJJ$JJ@$KKK$KK@$LLL$LL@$MMM$MM@$NNN$NN@$OOO$OO@$PPP$PP@$QQQ$QQ@$RRR$RR@$SSS$SS@$TTT$TT@$UUU$UU@$VVV$VV@$WWW$WW@$XXX$XX@$YYY$YY@$ZZZ$ZZ@$[[[$[[@$\\\$\\@$]]]$]]@$^^^$^^@$___$__@P```} ``@$aaa$aa@$bbb$bb@$ccc$cc@$ddd$dd@$eee$ee@$fff$ff@$ggg$gg@$hhh$hh@$iii$ii@$jjj$jj@$kkk$kk@$lll$ll@$mmm$mm@$nnn$nn@$ooo$oo@$ppp$pp@$qqq$qq@$rrr$rr@$sss$ss@$ttt$tt@$uuu$uu@$vvv$vv@$www$ww@$xxx$xx@$yyy$yy@$zzz$zz@${{{${{@$|||$||@$}}}$}}@$~~~$~~@$$@P4} @$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@P+} @$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@P_E} @$$@$€$@$À$@$Ā$@$ŀ$@$ƀ$@$ǀ$@$Ȁ$@$ɀ$@$ʀ$@$ˀ$@$̀$@$̀$@$΀$@$π$@$Ѐ$@$р$@$Ҁ$@$Ӏ$@$Ԁ$@$Հ$@$ր$@$׀$@$؀$@$ـ$@$ڀ$@$ۀ$@$܀$@$݀$@$ހ$@$߀$@P } @$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$$@$" yPP$P$P$P$P$P$P$P$P$/4PdOoNP4:P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$P$"00A! q " @ $P:0P# 0P$0P%0P&0P'0P( 0P)Y0P* )0P+ ,!P0P- 40P.80P/<!P7@0P1D0P2H0P3L!PgP0P5T0P6X0P7\!P`0P9d0P:h0P;l!Pp0P=t0P>x0P?|!P0PA0PB0PC!P'0PE0PF0PG!PW0PI0PJ0PK!P0PM0PN0PO!P0PQ0PR0PS!P0PU0PV0PW!P0PY0PZ0P[!PG0P]0P^0P_!Pw!Pa b c B e f g  i $j (k ,0m 4n 8o <7@q Dr Hs LgPu Tv Xw \`y dz h{ lp} t~ x |   '   W               G   w!P        $ ( ,0 4 8 <7@ D H LgP T X \` d h lp t x |   '   W                 G   w!P        $ ( , 0 4 8 <7 @ D H Lg P T X \ ` d h l p t x |         G  pyP  $P!"# $%&'( )$*(+,,0-4.8/<0@1D2H3L4P5T6X7\8`9d:h;l<p=t>x?|@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`!abc defgh i$j(k,l0m4n8o<p@qDrHsLtPuTvXw\x`ydzh{l|p}t~x|!  $(,048<@DHLPTX\`dhlptx|!  $(,048<@DHLPTX\`dhlptx|""     pP P  3+P&"LSMTe~cҔDLOϮ0C!1BB4FCE-3EB1-B56B-05AD-F99504DAFD310- QWlP]SSSSSSY%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%$$TE%%]SSSSSSY$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"$"]SSSSSSY$" IZFiletuji.yyf@Alibaba`a'7DpCUm06bca41ba617acf0b3644df05d0d9c2d2f82ccaab629c0e39792b24682970040000066400000000000000000000010151514714641000452250ustar00rootroot00000000000000accelerated-container-image-1.4.2/cmd/convertor/testingresources/mocks/registry/hello-world/blobs/sha256{ "schemaVersion": 2, "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "config": { "mediaType": "application/vnd.docker.container.image.v1+json", "size": 1473, "digest": "sha256:e4728a982b0acd74e810aeb5e8a5c1bd87f0de7ed93a678d58b3a79e6f7da2e9" }, "layers": [ { "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", "size": 4065, "digest": "sha256:4bf3d0e19af8069cca924c84fccd58558900bd382ffbde49778906172aa135fb" } ] }084c3bdd1271adc754e2c5f6ba7046f1a2c099597dbd9643592fa8eb99981402000066400000000000000000000010151514714641000451400ustar00rootroot00000000000000accelerated-container-image-1.4.2/cmd/convertor/testingresources/mocks/registry/hello-world/blobs/sha256{ "schemaVersion": 2, "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "config": { "mediaType": "application/vnd.docker.container.image.v1+json", "size": 1483, "digest": "sha256:de23d9589845285bb62b33da594c4a19802fda5b09bb6aef4d00e5e8c747a8df" }, "layers": [ { "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", "size": 3658, "digest": "sha256:337a0e31c52265fdbab8ffb4f8922b73f2ea3689cf9970150afee8a5a026db30" } ] }337a0e31c52265fdbab8ffb4f8922b73f2ea3689cf9970150afee8a5a026db30000066400000000000000000000071121514714641000454210ustar00rootroot00000000000000accelerated-container-image-1.4.2/cmd/convertor/testingresources/mocks/registry/hello-world/blobs/sha256zmlyo|֍H*:1=)Erhю?fb63EJ:+niɩy-"$!&n[ En! Η)ڄF o1{GEݝ3<2fR|Ļ8w=Ǻ=эǺc ? ƿa|țz{?B?Bx$5`цƗhB=QSSwPCV?'dQ)in]T7wJ)h}1K0u5ZuXWz3+f^jj r!X,Vv-RZ^%"e w>{ ־Kd_56d3=lFYfQ5g 5 2)[3pBЬqGQGyIWmpJ0^ٯ$M8Sf ASib{v+/cl ZO%[Z 揧d:ĀeWQ>a'̞I`;L N>>WEEa·iQRZ((iXFiX6Ұ4,ry~.E,cO:d3 ( FddN򷞷TgAgƯq?S:zO : U,7ʧ%hV*{ziwJ|ZiOWra>-ش Ʀbai]uM'o?OpӪF^9u4;'I^~0{b]W(z`sRbڴT=Wbdm9U/4yo:lļ*}XG|^mD (sJd8vM\IOѾ&|*\U/hs#@(Y=bUOIgeOF>+|J6YI<٥Ԋݔì({*IssigSFv1G/OjvM::(-·EȊ./ɻ|?Na:iM<6򂤴 o+Uc \lW3s1mK+(_rzMir@CF-r/@ScD-Đok5CC(&u`\2*<IJ$ul+[kC [;%JԸ`cעWU[nV4~C3S[#/ҍUF RŮ:sGǀpB_,vn%ۭHJj0;fɎ̬qymuJ4sm*ϋb!~6~ n6VQglс+~#b|yGr@osm>/g[?ݎA#wo/#JJߨ?5Υ-씣;:{ҹP=m Pup֏=͠nʚZ"ZB(̱^LX27TLf,\6lK963jG(4a猴K3=ZݍK3L*9M# 68flGRI#mLLƨn4ag!WTYv"7j?ɥFdqf!kKƏ=Cv|GrFB `i|Edm2L Ώ%GXn"h ȄN`61|L ,3D.gԫ,g$Fs!ֽ:G;ԝgnh;Uܵ޳f߼ G,;&fWr%ovQ<}@9Lm ͸i}& x(-,OЄaLJ}&kһdj]ȕYBE9, cy %~9Nqm!8넱Q(ȿh$;Fޚ2 \2;l9˂';-k!8nYycƚsJqsRWD@G$EBo->wLxL$FzŸ3>χƺg@ÉLmU"i ?HM6vqYsNƚql~ 5lz v߉#=ҨUkOi8@JGuo{ Ŕ2&Xu _)M^|<E{7xQG"myi&#N3{@QǞl*9q!G sJNl3Vq^PkalgPk`$9G #Uoui9~ 8 5N3/֑ փg 8ios@|9Vg{ (_M†=7N[sν6$fٲ\:#aup_+QGݍ: JKLiu@F%!':pz w:XXp ' uF*N8u*q ׽`uVɩ5|#@DI3&"Z\FjKkFE{QvtX(ǂӁc!RY,8{1L2¢ßEH`3UYBX* Y"jP#r8 0banF2.o ?IUugB08iGo%ܵK,q=/DygSoX8 ^sF{O ۚx [K뽁aDzN$BL"noĠw>m]qI@8;g-{T7֜"S48౷#%/̓S 8N/&DPs=xO33,ӌy (k9Gcq5Jdk USp퀂*)N~ˑ?x戈p [#w_n\%bmKiUQe P\ CkXRh ad9N"SRY`S"8DS"Ы #B7(RUUN{QgR$,naoC<>%tF`W`kgHWks=s̫;ŀy`v5"Ы1E(2ԟe ѰkP'\&{W`TMC_^gޛz" z|Ĕia0Cp{g Mix߂(rY3o{ۃ kƑ+@dڠ._S_і@=}ͼ?0ot!=89"zCH1 JCĒk}d: 9ns)咀?Al`uvTX%xYf*u:[g3[mJ,q@^fǾf,AWDN,] ZÝU@_?5NS%)mmԘ;R) ($< lѝg4-aAC0=+a7B $yn o_7dqxGJ)^[eۻʖ}7 *!Z4, "A,\'gy~4uV.4\9ˬ\4 V.4\7Y&:+Lb[Lʥ_2~Ko3nr4V.4{KL+xpYBH'o“>O/.<<6FqZbƋ>?N0/6&SMܟwu;~{~mo~vԭy fgYn`G`DtY r" | /les&d<~a VEx.hvALzʖ ׾Dתּ=x{z:5o11t{[xXo$k͞h;ݑ ~YϠ>:Bnȥ 7dtL/Nܡ"53IMhvh&GGf67N,#:=+ܗ7h*W()sHO5q4ujwf.w53:MFGs8}}=tg?=ћѬ>nTnLL]swZRˍϿ[Q{o=+jivklpbRJl]g{Wc*I.I<ɲz1i;dSZ]a=5a&GFu1_i)E 3?ڛ& :MM 9z7{讫S4 zrU5i6V<+"E+lꅱxrsY8B1pό :MdlnJJshr~edbܜh1ġC#TvxѬI'<đd1 WҵT&'M!6y[s~4IBwC3/2#VX\'zd~=[̚-Z.Ul%7t3Z4Sb==icm+Ux >T .ux?z?_=; >}o}W!&˷>}_}ÇЕhy>>*)39?cɑlz4iџϚ7&, 9-i&ѯg,]HOBܷFDћ7'YȎLzGOYoF]w&wߙZ%N>}}蛃7㠙'ڜߖxC7O?(}06z7P w7߇[+"]mͼ߳AnլO 669_/l55^s{>7kKZǚOߞemx|<>AG &4f1af0faa5862569c7c76d72b1c8e09ba01f2adeea0a44f8f3758d185e95e918000066400000000000000000000075221514714641000454510ustar00rootroot00000000000000accelerated-container-image-1.4.2/cmd/convertor/testingresources/mocks/registry/hello-world/blobs/sha256mp[UzW2xz6@H8ر%Yf Mb[Bҕm2SgfL8@~6ζδ촓y)R ;K[ N{degI=Gy99cx\:n;a;wv;︳cg;:v@%e2V)t}{+}{I0w>&A4a9zZڶV¶XF+mC\wEu?1j0\TGOUjF`IKwC(-;)ym"Z$7IK~-jcV[=}y\|NܖO<#3UPߢPo*Z.<'Uع|_7;: 6sr`ƈ62w\>s6S݌P.w_3B}~4(N3z0sŗ_mxg ֽܖ9 {cqoض=ȇL;H/c{?7b)Wg;f{Glv%z'cg;+~ UK\es0S qϧ:uB36D$@X! {cmxu|*l`Z땯Smcw*ٮ3O: K7Uר(k$zĖ׬µF ~R6=k@Nbn /)-Wm+,ܲ9Ű[1RZےB>P3zˢmM26h 6 b}]ȇpӆC.UXf0{|㢽 ~Va~GCK_# Kȇp=(iPJN䤿t|AZZ9 f>O#6rfYg{z~MXt|N(IwFI[~.ɮzņ g;{uw᝟Z$ a\e?ϸ6&VNʉfꮮ|]-k)=;,fY'c݋Y Mȅ:"fg|o:kiVGXgFpSۤ[6ǴڭO.f,٬z1kgOdgmܾQI/tv=Rt{ ~\G|>wh-˗yTCS$Zd#ef6?FuW4kzFiKڭڎ0JZ 1$Zڀ/S~ZI[͚V_w>O :X`^qo=;:_aqh vݙ}.4DChF{h͌6и=̧mV_f\+Mscv\3fNW;:W`G>`GVѹGC͈Ȗ/'4{ 漡†6KlCwoXdLzZOmIB|OZSwa?UcDg]-]`nU9^FOYClTo=e'}.>ƺ:a~V2\s2os5rA-_YHa\e=jh]tke[FYbWc ]Dn*x?/[ռ]~v k砎溪g#kqfe<+0/|8_h[Kڎ'E'C>F3=-gR쳟io~M{k+k|A>WM{=6ȧ4<{vE2rǴdqQ~,+?3FA#_g̘w'Oy`F0 [|a#_ 5Ă)E.Z> )W_3A7F5~AOB>Ԁ߽lEp\ b~m F0.kvmoΝ+^b=?؀`8t{%~VnsZ_e Fv; ? CjM1CN8q4&[^'=ץjZ\g _g6fH S \ _~?_^BA}oBAr%{$o%ɍ{]au[<3]uO݅I~WH.Dd\r@!";qwa]r~$ཤu~یt`xPg%CNl{81*3 {3Xv403"=31x_r/FVWfm8h q5+{Zp3[|#NHܿ|aOtO bF3}EߍiKXbe^sWt/0#:{-<3+[\H#/9筽va@9u୙l@weǒ3;g#5g\Csǘ_97=S]ñc͹!.kcF\5rV9AW.{"fޮqxiy=߇՘[O_q_*8fdHsZq;7?!.9W^Vl9.SGW=cTY{k&o.~'ں_0߽}XS0mm]ȇx-wcAa], ڵף޻e4 aEfVv[f4 -aEf,WQ-ZsѽWZEư2>b_"ji7OJ:=ġEUpb*%'jt|<b4T&E69jĨx \P߮FqLU]۷M[c퍍{)KSѢq%mڊ*tx:h&bZz[ZTet{c#*>95>~o£N 3?d^!3Bk#M                                                         #m@{z,qh?=Q+cHE'D*Q9PnSm ݚ\'_:a[߰V^\+_׭~Ww3[skuMߴi֯Ynn\+u`>>v{_Y`WcU}d_s߹vAkݡ) 5735de2b810bba182986adaf3f0d2e6567697caecbebb5d3f2934d8d37f32d2d000066400000000000000000000026771514714641000456060ustar00rootroot00000000000000accelerated-container-image-1.4.2/cmd/convertor/testingresources/mocks/registry/hello-world/blobs/sha256{"architecture":"ppc64le","config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/hello"],"Image":"sha256:d10f62162101fe360d2292f3163e7d4d07c9bd3a4bbba4a48a4bdccfa622cd5d","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"container":"bf5c33b539ecc0a88ba3448ed5d95fdc587a6fb295347fc31a296286a419aea6","container_config":{"Hostname":"bf5c33b539ec","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh","-c","#(nop) ","CMD [\"/hello\"]"],"Image":"sha256:d10f62162101fe360d2292f3163e7d4d07c9bd3a4bbba4a48a4bdccfa622cd5d","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":{}},"created":"2023-05-04T23:35:14.791724762Z","docker_version":"20.10.23","history":[{"created":"2023-05-04T23:35:14.47386416Z","created_by":"/bin/sh -c #(nop) COPY file:d983edb4c32b67abbd6c309cebc3bf9ace8e1c065505fe8ae03686b5a75a5552 in / "},{"created":"2023-05-04T23:35:14.791724762Z","created_by":"/bin/sh -c #(nop) CMD [\"/hello\"]","empty_layer":true}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:ac1bc0996cc2106a1e725a10fac1236fb365304b85257f0b82ab950db3bd8880"]}}574efe68740d3bee2ef780036aee2e2da5cf7097ac06513f9f01f41e03365399000066400000000000000000000010151514714641000452140ustar00rootroot00000000000000accelerated-container-image-1.4.2/cmd/convertor/testingresources/mocks/registry/hello-world/blobs/sha256{ "schemaVersion": 2, "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "config": { "mediaType": "application/vnd.docker.container.image.v1+json", "size": 1470, "digest": "sha256:d549b3d1a2550a5e42a3f3250ce9fa4aa882b25f8528fd2eea1b789ce136631b" }, "layers": [ { "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", "size": 3265, "digest": "sha256:d898e3d98788d70a8f874f818177c11569c5bb3c818ef45b877e7bba29a3bf7f" } ] }630da353b594c9ca52c67727920737dba3e5aaa4fbe20cdd0fc70c0591b7a639000066400000000000000000000052341514714641000453300ustar00rootroot00000000000000accelerated-container-image-1.4.2/cmd/convertor/testingresources/mocks/registry/hello-world/blobs/sha256[}l[}\¥~E6;Աi(maC 4G!-pcnpM HMœ5& !@hD:MJ٤Ңvݭ,HI3k1M1ݟt=w<9>c'TJ/P0xwAkC!|gK8tˆQ&xև87,/4D jp= x"Y^ֶX n^`m)TNb]uī@}EӁ8p8^Z~->_3& 77v9gS뭟dZ1bd(hڌ81'F"s9>yb!jlC}f׭L;)t9J*;&)45tzfm<_u~rK!Ȝ^C@>Cnl>ml:=`aUK{\ܑ&6yxi1vlq2Ψ^FlP>S)E}̗yD),܊=S.-;AVq=Heֲ#+oent'3bNe8oދPni\'DKaA:G/?īܦ/u9Mfx]8 #glJh}N f6Fo/G[cXG5v9" JGre-rP|ϻؒ#56i狟%o&qz[@)ek+qXmqdYj &@6U2}84j!vv>H}ܹݳliBXͻ\C֣֣6A[p|1G;OY UƄE)-O8.Pk=ϯ~<nLp>h;sc VwHGgmACӔvX->3&4qC&# _O/5n6qPg2qFwuZ[-leSk"OAaP3jluf;7`13Jc^F>fzv$sȶ\o̍ZcjGc- N;][v-Gl[̴-]oF`t{_(LǦX,(z#:w;lî?8phHNGt@gk5Lje5:y}k%q6/ ]>9A4k|Z~P?}F1 oҧ\V%wo9CB_*BqT)曏7O Yﰛaw]{$ 6)x})na*ՕN6Ⱦ2;YVP4Qţ輿sG,{/<*oɖ6L6K/^C~{|XvZ@KPvooJ5Mv7՝lUO^57<g)赘nj:wT#.s{Mbߘ/M Lp'=]??rG'G׷%((# W'B'93?2C틼A(::lܛן'߬+~(>sW9>txpQC=dLE^DȲχ<a%/n8z³RJdԫ}(,6HIUuRHB$h)(VJ$)/z$4 +u(&+#]F뤴DBnc"zTJ`U"=(@ iK%4A19MX\tܔr7R yEG>{ZOB KAg4H"S!4PVLKʌ5T$R4Kdە6I1=)LWl5үٹF"(R6#E5_OJIɌ"WV`cEʲ$""HJ_2- GH$`'+z$)kj0&YhHL-MD $Iba/{gEI2GTO436"Q#rVI)yeQ"] rJү.ojJh=@LkxLiןTV2.j#ژTjv\IdJDH&ƦJ=U \/oϙ\(Qz#risڦ=^߿_w8p(Y^x$ B5ϛ/>WP*Qd:I @Mt)D"IYQ!/ľSen oϩ|g>?|\Ud*}^:nw@UV_oDqT1wTJyOxߪ > *xz-TX x^0B}`)ż\Yu^'8p?P:70f5ac315c5af948332962ff4678084ebcc215809506e4b8cd9e509660417205000066400000000000000000000061661514714641000445750ustar00rootroot00000000000000accelerated-container-image-1.4.2/cmd/convertor/testingresources/mocks/registry/hello-world/blobs/sha256ZlW~619Ӌ(1!1᭡q {mTakv9%)ۦY.81ۤic5 Nrw䨲7Ej &8P?Nڏ~|w4V8\4ojjmMyoii{[ZZm 66}~KjJMo[œ }A'̮3Zð.06wтقXPdb|kֿ|aI uM^W;iݮO7Xnhޒa:洌5ɕkZ%wg}wh?dg4D3נg=a0j?84@Icy8z_=z#@U-/~``ly;\n$[u~O>|:o]V {bc $0lgOtpmqDzNQ;4]+%~fYv\w3Uz#+[1-LN3@CgF2s z9I30 |9_.gb<׌!18YpanjW'Yr*)ߎ;0 .ƕΈNZVu؛AmJ.[ֽb>ҹ`u:5qLq^G {q݅!7˒kW`bO;=+CϺ,=᜼(Cd^W7Xdu{ zɣK<3Z`o_q SʬkA9y8ϖZ֩/;x?:a@`{|[.s˪OL|$Kn\1[ :5)%3!Weľ\^iY͏dfZSh؛uB&'/2C}C3—:g\!^g AU9g~u溌e);JL0r9du(IYsr-T+GqE<ӪPekpRƸiU`n<9P N_Q10~1Ӳז2~vėd΂qq_ =1_?Ųs PeU_Yxh.U]OZ F |v7(][Nեs {YlwAJud꜌@'Ô$!@`E {H)#m#/#6ҟ/}khjq [7xs-/wN]~ k 6pS.}s'@m4. +K S,+べ/0ȸqgDUZ`olG&^=\a;oJ뜺Nv$1@ױ`9xL;@{f>_A I=9 Ɠ1>Y*[=:BhїTRu%*Sd#Mpa T1>X.+37 |Vsv;?ѿ8bOc83 @:-1jg f;^Fa}O̒*yO}!v,cǀ+C]n6 :YW*'npEʊ|AuU?~ov ).Odԇ%}@1K+^p׃Uu@k9D+m1(l _N-lTdoܥgE킞ҳ..uZO:fZm`çW`'* p^n$ޜ(qǞY+QGY+pc''YVOoYCbAFy@2j{͍H׳K>%7*~+ӳ:sy @zZ|º!i:uL]w@`U 3 [LĊU`isf1S{mvfUf\f!/4.<$tW"CC{ԄGGM&*MT +=KH,)ѨE1J"I8RxbO$≄Ң=@VcjBTFx|*F>7U* %jL Uv7zhIj UghQดhGZ<55)h2ޣjaqcz *=]-K6Ub]}1o^ E}ZOF.%xv$H%ҧ{biW4OźxT܄> kZou}]ySx:x"T+=Q5iVd}kwY^'Eoh)s|E|wQ}r3|wQy?u{?R;v~n;߷x}:m}:z*}~y(胷PB %|3*719385e32844401d57ecfd3eacab360bf551a1491c05b85806ed8f1b08d792f6000066400000000000000000000046311514714641000451330ustar00rootroot00000000000000accelerated-container-image-1.4.2/cmd/convertor/testingresources/mocks/registry/hello-world/blobs/sha256[[l?ؓo!P8^\!dɷD8F1+vw;.H5qaRԷTjEC!oKm6uHlX"߃~\l5Y}ttl޼u8E6G:PkM];&;lTs7?t7|(b^_0jQwh jDQ -D.Ua.W#7_Zh; l֏Z U ;~eGt}ĥanyj CIjїkuyU9[s&?BN?Bbe! BT3B'~`|=b?*D> 6) f+vds3~wGUQi`]  3[H ~H[;¼k]3&O]O|.{b=Y({X%}vA@;_hkh4>֯\k|b*E,%eָpX_c{TxM0^eA9و`vo_yloB\ 6fn8dǂpw1YflX%0n ` Ro%r˲\"1#4޺?sy%{Bv,ˢE~lC˕k7-+ɩ&ysf'>,,(peK" fq@u U#E랙O#Ł ~:'RQ lO(l(`ƅnz6f:bFBp(|&\=q1]MOwb8'|gT ) nq-<) A\-W3ƾtGbh'MS\!_yhsVBn;k}xn1&hGlĂ&wm=B:㚫?dpieY'?NMHm"$a'hA05?Vfw0vn$s bOۢ-v#HgwOI0uN3 ɘo"ʹ@y9 3=IJᶸzrBֿy3Ѽ_t!4:΂9LM| ATLN~ ur3'{ءYn,s!۴`w ՂgC9HdƸ4\p=B8--K`|<tAv9>b' lgD#;;vTh-˲$؄رlfy-4kY#͕'?Z"={t뼺j!K_ Z [p p/Zo7J@O5: ;_)梵ΟbWo $0ֳY~eèfSkp&:{ `(1mR.~NQ,2$G2yY~4I,v`ԉ4Ⱚlaݞl,2N*Iv NhDݧDQRۙ3nI +"DVsuVcXSr2Ie)`d]k+LJyPf"F3$-r3ކ Ct ЛfM3cI'Jm(˧,*n(iBmpZ 9BI%WTG)rYYceR㑌!5ꔒjdA#JT8@<٬{nVw8wi?B轋Plyrw=xNsUԵ_-jw58.ӯo~ԵUǯ" #y{|}!\u_Q˭sswW!Qu߭%[]һp;zkXȯ_?H)ߡ >)3/gA1|& _C hMe*qq]+* XOb@5 u.n(a^$NV5lTΆKg 4U{Dl oGn}剈xCoHcUţ#*Ĵʩ-'HߔxLOGcGp|6haP_E|'s&3M'?k HHob{"Pz8"RA |Űfc%=R#FꙫKnq߻>Lv@ yYק^@U]_-m̮]͝n6oN>Ί&tJTsZtӒ@FF}9x&S@5/sha3cϣo(#m{f@>dgs%3/N *U_+j/ѣ_ɼ|n `b>d5fc= o2LHODT(jk-o$ K0QVvo4լ1_H2yO"O8#i(>cXv1vaz m ^}xٍb穿$-̯a*_Em?z!џ8jd\; FExvAbuW(ȑWHDUaIw/f|05JFtY$1ad_D?実E!K" zS$!Pr /&\sMZ6:>OV4k*VV$cYq} CIDw JZI+uKAoHDx?^@ oGٻsF y#!d^)ZԼvyowIξЛ/0y%6p_d:u67}7Zw}vúcwntO!Chs$4U!T-W[=qqLh9_OHzZ0^xtgK)~ԮU` wmI2=)z~z`_많[^Ǐ}WG45Ofs?dBrR{xllwNI\XuY'E{/v|R(Ugx&>{&M,C}7sg]C{9{W԰]Mt-qlNsM4{;mk'42MFzK Cfxjah߮TBt4ϵWG+/)h>&9FmkaA| }8>spag#ulj}r*91OM%#;Hu ,G!~s*ON֋Q~B{>Q]] ;F6W=6h>U@_YN;|𭠿"z{ڡrů.֎?"M0^:bvͦ$}2nXb}6:D_" { 5o^$I7|sXydI:$cU_$)d\/+ٯdI+wI 1ٯHj_~vxFDO*)>uFI܊oT6SFX 2/Q՜?!^Ŕy:Լp7_/0DM;Ri"lrCv#z؁ ޼ 'e|/yyz"z F} ņj-W/Խl:d9iq`a))06#XvimI ~LXf^vP͜%~R(C jrlw \sb^pi"krvbvaάR tT2MlH=v6sem+Tj_WAGQ:׶m#U0,1r" ejbV;ǧ2B*be-}F[yr rVn9u,sLR)#M,Kj:_[vƦN(,`Z?C՛;?d~P,Ypz1uϮIw l?N7){]03&i`[0<ϴjfFЬ|Pۀ,m\GWsS/tH rv~ >IX_0~x< [_MtR^~>mu+9)}NJ7~r_bi<c{l5ȯb67b1f1224de620e9a932ab7ff0e819d7dd9d236605dd8027e5ecf2df650e65e000066400000000000000000000011101514714641000454310ustar00rootroot00000000000000accelerated-container-image-1.4.2/cmd/convertor/testingresources/mocks/registry/hello-world/blobs/sha256{"created":"2023-05-04T17:37:03.872958712Z","architecture":"amd64","os":"linux","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/hello"]},"rootfs":{"type":"layers","diff_ids":["sha256:022231c3f1af70e0cc42f0c3781eabf174cff608210585bf93fc83e16d33672d"]},"history":[{"created":"2023-05-04T17:37:03.801840823Z","created_by":"/bin/sh -c #(nop) COPY file:201f8f1849e89d53be9f6aa76937f5e209d745abfd15a8552fcf2ba45ab267f9 in / "},{"created":"2023-05-04T17:37:03.872958712Z","created_by":"/bin/sh -c #(nop) CMD [\"/hello\"]","empty_layer":true}]}c4018b8bf4381a79e05c15591e1a1adf4452a9d383c4dcb63b1b7d4617d73af8000066400000000000000000000056431514714641000452640ustar00rootroot00000000000000accelerated-container-image-1.4.2/cmd/convertor/testingresources/mocks/registry/hello-world/blobs/sha256X}ly޻㇎4?٦e:~%ى,Kdɵ',̓u"Ϻ)ލ o[ܯ|(螀F0A|@0P 5ʚjxAX8^?s4jbԚLu)aG]Y-*(s( xƃE+a%7Uoc_>%+n6B/DeHk20Ft3j[qM|ʡ ՗>7<gk5ne'|'2G/Ts1⾦1^hZK;J+ӥw ݶw4\F;.i6Pu&Փ,q#sP;gJDr 7NNM@AȖͻL]P}<5sVi.4@&r/V=_톛fᩰ#7'g'FnږFs%tapiJFXH,MB [Z_"cvieg JW |T*5;sƜ ~gvouA 4r&瘲):VU*U wXПZ%MԴnx5 )( A~8H ^`9tNnzu@^8 n^VD@S$ޅkEh3E)NQ noΞS{H}9gC'}FQ0ݛ;;YW '׋/ 2C]oKX.Bg. XKQ ;RT9!#K;d]&յ5]pȐXjYav(u#99SП?-ŘA}AFh?BBRԏ@~O"J;11~@KñKI7Z`taՍ3oM0~qpzD7l0-;Syw6~olodeIG6?$B=oRMgp [Z/VR٘)E{m>H4[OH Pv ?ocԏ *,z0.hyX^#̿.MF hB $ OHtwYƢ/`$@?̺fZ秩Y+pGt|dvwH".77H{0P t}RCNz=35{FR2KsVɫNoq=^*N=i5 N۫{9m jӶÚ3iHDpڶ>hTsE_,NsF Vl NۼW 1Vsⴭ5#ZiiiMQt֍37pZMl8mЫwsږSxӶ5#ZU#m4z>\M?^nTӦ:7NjZ-ۅ+Z]yZ]Ó: bN| !%CG̗w|*-cFuqQa! VE^cr6TFI^ExP#rq9jr,De<$4 X]\UY>UQdR13d:iEa OJBJq9qUHXJ/ ˩Bl2Y[g9Lci،Њ}Y$;1ƸOoO SH1)%yb`D"NgSfZYL eٸ`UņϦBJM'PQ?lEʫk,>r+.ēJ* a)'˵Sc8# hfXN TIf-| )`6f%bDRN‰u"8["AZ4c>ft*)kDٴɇy|*-|PIcQUL}bv0T6heMaVBiHI]CbT!a_Bxf_*L~P~ ={S>rYaz ȚsU:'~~Pdqm[vq*GP ڌTKѣT=PF̨iT¨ SRJPZN*!Aʘ!YP<@ȓ_ 񪚖?U`ˀrÚٿw\c[U!\1ߎ μZj@1nRt7vHXi9g ά뇿%+>fu8Oƻd%&>>2FY[7q|[EPCy(,u,d549b3d1a2550a5e42a3f3250ce9fa4aa882b25f8528fd2eea1b789ce136631b000066400000000000000000000026761514714641000453440ustar00rootroot00000000000000accelerated-container-image-1.4.2/cmd/convertor/testingresources/mocks/registry/hello-world/blobs/sha256{"architecture":"s390x","config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/hello"],"Image":"sha256:0a4ed120e12f159b468ae1fbdf6d7e2c053fd5547f6ad1e12019283e425a4f0c","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"container":"16de42fe001ec70d74120e5db0a765dc7cf960bc3ecdfdd23fe034dac2b12962","container_config":{"Hostname":"16de42fe001e","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh","-c","#(nop) ","CMD [\"/hello\"]"],"Image":"sha256:0a4ed120e12f159b468ae1fbdf6d7e2c053fd5547f6ad1e12019283e425a4f0c","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":{}},"created":"2023-05-05T06:04:49.524781092Z","docker_version":"20.10.23","history":[{"created":"2023-05-05T06:04:49.407407092Z","created_by":"/bin/sh -c #(nop) COPY file:2412b45fdbb28b2191d4b0d59a4c66e460863549bae786d0aaf49c123b876e0b in / "},{"created":"2023-05-05T06:04:49.524781092Z","created_by":"/bin/sh -c #(nop) CMD [\"/hello\"]","empty_layer":true}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:3aced21b93452b84cf84067c6ef10ea4a4841f8ca36a18300504f9b408f0082f"]}}d898e3d98788d70a8f874f818177c11569c5bb3c818ef45b877e7bba29a3bf7f000066400000000000000000000063011514714641000453020ustar00rootroot00000000000000accelerated-container-image-1.4.2/cmd/convertor/testingresources/mocks/registry/hello-world/blobs/sha256zlug+Hks9"\-)itk]+ƕSF:+[n/!R&0f |m Y4mB@.R׮$,c.C 1=DK"@[}>oޯy;Z^d,vI״{ͧU];5ܹ}W=_~.PlBNZk:He2vCEMh dTV@GR>ˁQTqܗ>6a{Q)/x9*nc]x@lfH˗;z8?%fCqyubSw0;R8P¶@A϶UQU(Na k*eQliOS)ùU>t̋eXKg tʡh ?9 t~2(E; /CAyDvHb9o }/H8"s3sk/ʹ?)_qRXQ"r9Ğ &;ԡm)jR[Rj?_yWއ~F|˪9qpo>_ų;h"G~mr'? &kҚv &0e]*lwS/;tn:w?%J/d:i}\وb !pyo_CW+w/N#P]ݻ *o؝c_砸dϴ0ZO\8UF"Yd6~^=F%{t+tT=^+&JEѺ=:Of㸍m:QUϽo,B "PL3Z!_k|3yV.Gg/I`ðӍfQ_>y4*p)/.wOA[6Ge}̽,#"=6_5C6iWQq?pO3s~%)˖a³g ҳ"gGE{($~)ku=N)zKE X $ռ PݸρIvu)^qa 計olRo1W^A̍{#ޏs{ zWө7g"YUR࿿Īui}!]O+sJb!{_suIi14pK%KJO Jiϰ%%ӎKޒp^ȯ)fZen}l"2s%x FD? lu'JOyoM.)AΑ1"ƋDD(v12jnQEVo%VF٢=P cT7eoǧ]QM "N.z٣@]im׶(2Ɨ@"=uQͩC3vq/L+¢e^[Dl~v$xQ6`HěD# x% xyn|n{`ezEQgnެ9qv9*!hp ϰB+_gfsύ{hf+18i;0жUOY"wL7ݸ8*5GE? QmRTX8*?_Wnpfm(w% je<8uFG~p M C/']LGɍ^q]K^GX:N0B1>1!Q2g%o, UrDXJI7 "=͠h:/mN/:8#y9X`JuKwsU^wnvξ8՛U耙XD\wP*#f>(g+n+q 'Z?/*{еSkoX^9s ͧvN7M36zOiᤝD:v"1;d[RsG~2b-ֿ o`y ]ŷ:󷰿_~US>1sx&x1Q|5+۠JU size -> digest, oci images seem to be created with descriptors // whose order matches mediaType -> digest -> size. Json marshalling in golang will // result in the previous version of the descriptor given the order of the fields defined // in the struct. This is why we need to provide a custom descriptor marshalling function // for docker images. type DockerDescriptor struct { // MediaType is the media type of the object this schema refers to. MediaType string `json:"mediaType,omitempty"` // Size specifies the size in bytes of the blob. Size int64 `json:"size"` // Digest is the digest of the targeted content. Digest digest.Digest `json:"digest"` // URLs specifies a list of URLs from which this object MAY be downloaded URLs []string `json:"urls,omitempty"` // Annotations contains arbitrary metadata relating to the targeted content. Annotations map[string]string `json:"annotations,omitempty"` // Data is an embedding of the targeted content. This is encoded as a base64 // string when marshalled to JSON (automatically, by encoding/json). If // present, Data can be used directly to avoid fetching the targeted content. Data []byte `json:"data,omitempty"` // Platform describes the platform which the image in the manifest runs on. // // This should only be used when referring to a manifest. Platform *v1.Platform `json:"platform,omitempty"` // ArtifactType is the IANA media type of this artifact. ArtifactType string `json:"artifactType,omitempty"` } type DockerManifest struct { specs.Versioned // MediaType specifies the type of this document data structure e.g. `application/vnd.oci.image.manifest.v1+json` MediaType string `json:"mediaType,omitempty"` // Config references a configuration object for a container, by digest. // The referenced configuration object is a JSON blob that the runtime uses to set up the container. Config DockerDescriptor `json:"config"` // Layers is an indexed list of layers referenced by the manifest. Layers []DockerDescriptor `json:"layers"` // Subject is an optional link from the image manifest to another manifest forming an association between the image manifest and the other manifest. Subject *DockerDescriptor `json:"subject,omitempty"` // Annotations contains arbitrary metadata for the image manifest. Annotations map[string]string `json:"annotations,omitempty"` } func ConsistentManifestMarshal(manifest *v1.Manifest) ([]byte, error) { // If OCI Manifest if manifest.MediaType == v1.MediaTypeImageManifest { return json.MarshalIndent(manifest, "", " ") } if manifest.MediaType != images.MediaTypeDockerSchema2Manifest { return nil, errors.New("unsupported manifest media type") } // If Docker Manifest content, err := json.Marshal(manifest) if err != nil { return nil, err } // Remarshal from dockerManifest Struct var dockerManifest DockerManifest err = json.Unmarshal(content, &dockerManifest) if err != nil { return nil, err } return json.MarshalIndent(dockerManifest, "", " ") } accelerated-container-image-1.4.2/cmd/convertor/testingresources/test_utils.go000066400000000000000000000054741514714641000277510ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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 testingresources import ( "context" "os" "path" "path/filepath" "testing" "github.com/containerd/containerd/v2/core/remotes" "github.com/containerd/containerd/v2/pkg/testutil" ) func GetLocalRegistryPath() string { cwd, err := os.Getwd() if err != nil { panic(err) } return path.Join(cwd, "..", "testingresources", "mocks", "registry") } // GetTestRegistry returns a TestRegistry with the specified options. If opts.LocalRegistryPath is not specified, // the default local registry path will be used. func GetTestRegistry(t *testing.T, ctx context.Context, opts RegistryOptions) *TestRegistry { if opts.LocalRegistryPath == "" { opts.LocalRegistryPath = GetLocalRegistryPath() } reg, err := NewTestRegistry(ctx, opts) if err != nil { t.Error(err) } return reg } func GetTestResolver(t *testing.T, ctx context.Context) remotes.Resolver { localRegistryPath := GetLocalRegistryPath() resolver, err := NewMockLocalResolver(ctx, localRegistryPath) if err != nil { t.Error(err) } return resolver } func GetCustomTestResolver(t *testing.T, ctx context.Context, testRegistry *TestRegistry) remotes.Resolver { resolver, err := NewCustomMockLocalResolver(ctx, testRegistry) if err != nil { t.Error(err) } return resolver } func GetTestFetcherFromResolver(t *testing.T, ctx context.Context, resolver remotes.Resolver, ref string) remotes.Fetcher { fetcher, err := resolver.Fetcher(ctx, ref) if err != nil { t.Error(err) } return fetcher } func GetTestPusherFromResolver(t *testing.T, ctx context.Context, resolver remotes.Resolver, ref string) remotes.Pusher { pusher, err := resolver.Pusher(ctx, ref) if err != nil { t.Error(err) } return pusher } func Assert(t *testing.T, condition bool, msg string) { if !condition { t.Error(msg) } } // RunTestWithTempDir runs the specified test function with a temporary writable directory. func RunTestWithTempDir(t *testing.T, ctx context.Context, name string, testFn func(t *testing.T, ctx context.Context, tmpDir string)) { tmpDir := t.TempDir() work := filepath.Join(tmpDir, "work") if err := os.MkdirAll(work, 0777); err != nil { t.Fatal(err) } defer testutil.DumpDirOnFailure(t, tmpDir) t.Run(name, func(t *testing.T) { testFn(t, ctx, work) }) } accelerated-container-image-1.4.2/cmd/ctr/000077500000000000000000000000001514714641000203505ustar00rootroot00000000000000accelerated-container-image-1.4.2/cmd/ctr/main.go000066400000000000000000000017571514714641000216350ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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/v2/cmd/ctr/app" "github.com/urfave/cli/v2" ) var pluginCmds = []*cli.Command{ rpullCommand, convertCommand, recordTraceCommand, } func main() { app := app.New() app.Commands = append(app.Commands, pluginCmds...) if err := app.Run(os.Args); err != nil { fmt.Fprintf(os.Stderr, "ctr: %s\n", err) os.Exit(1) } } accelerated-container-image-1.4.2/cmd/ctr/overlaybd_conv.go000066400000000000000000000075731514714641000237270ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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 ( "errors" "fmt" "time" obdconv "github.com/containerd/accelerated-container-image/pkg/convertor" "github.com/containerd/containerd/v2/cmd/ctr/commands" "github.com/containerd/containerd/v2/core/images/converter" "github.com/containerd/containerd/v2/core/leases" _ "github.com/go-sql-driver/mysql" "github.com/urfave/cli/v2" ) var convertCommand = &cli.Command{ Name: "obdconv", Usage: "convert image layer into overlaybd format type", ArgsUsage: " ", Description: `Export images to an OCI tar[.gz] into zfile format`, Flags: append(commands.RegistryFlags, &cli.StringFlag{ Name: "fstype", Usage: "filesystem type(required), used to mount block device, support specifying mount options and mkfs options, separate fs type and options by ';', separate mount options by ',', separate mkfs options by ' '", Value: "ext4", }, &cli.StringFlag{ Name: "dbstr", Usage: "data base config string used for layer deduplication", Value: "", }, &cli.StringFlag{ Name: "algorithm", Usage: "compress algorithm uses in zfile, [lz4|zstd]", Value: "", }, &cli.IntFlag{ Name: "bs", Usage: "The size of a compressed data block in KB. Must be a power of two between 4K~64K [4/8/16/32/64]", Value: 0, }, &cli.IntFlag{ Name: "vsize", Usage: "virtual block device size (GB)", Value: 64, }, ), Action: func(context *cli.Context) error { var ( srcImage = context.Args().First() targetImage = context.Args().Get(1) ) if srcImage == "" || targetImage == "" { return errors.New("please provide src image name(must in local) and dest image name") } cli, ctx, cancel, err := commands.NewClient(context) if err != nil { return err } defer cancel() ctx, done, err := cli.WithLease(ctx, leases.WithID(fmt.Sprintf(obdconv.ConvContentNameFormat, obdconv.UniquePart())), leases.WithExpiration(1*time.Hour), ) if err != nil { return fmt.Errorf("failed to create lease: %w", err) } defer done(ctx) var ( convertOpts = []converter.Opt{} obdOpts = []obdconv.Option{} ) fsType := context.String("fstype") fmt.Printf("filesystem type: %s\n", fsType) obdOpts = append(obdOpts, obdconv.WithFsType(fsType)) dbstr := context.String("dbstr") if dbstr != "" { obdOpts = append(obdOpts, obdconv.WithDbstr(dbstr)) fmt.Printf("database config string: %s\n", dbstr) } algorithm := context.String("algorithm") obdOpts = append(obdOpts, obdconv.WithAlgorithm(algorithm)) blockSize := context.Int("bs") obdOpts = append(obdOpts, obdconv.WithBlockSize(blockSize)) vsize := context.Int("vsize") fmt.Printf("vsize: %d GB\n", vsize) obdOpts = append(obdOpts, obdconv.WithVsize(vsize)) resolver, err := commands.GetResolver(ctx, context) if err != nil { return err } obdOpts = append(obdOpts, obdconv.WithResolver(resolver)) obdOpts = append(obdOpts, obdconv.WithImageRef(srcImage)) obdOpts = append(obdOpts, obdconv.WithClient(cli)) convertOpts = append(convertOpts, converter.WithIndexConvertFunc(obdconv.IndexConvertFunc(obdOpts...))) newImg, err := converter.Convert(ctx, cli, targetImage, srcImage, convertOpts...) if err != nil { return err } fmt.Printf("new image digest: %s\n", newImg.Target.Digest.String()) return nil }, } accelerated-container-image-1.4.2/cmd/ctr/record_trace.go000066400000000000000000000456661514714641000233540ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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 ( "bytes" "context" "crypto/rand" "encoding/hex" "encoding/json" "errors" "fmt" "io" "os" "os/exec" "os/signal" "path/filepath" "syscall" "time" obdconv "github.com/containerd/accelerated-container-image/pkg/convertor" "github.com/containerd/accelerated-container-image/pkg/label" containerd "github.com/containerd/containerd/v2/client" "github.com/containerd/containerd/v2/cmd/ctr/commands" "github.com/containerd/containerd/v2/cmd/ctr/commands/tasks" "github.com/containerd/containerd/v2/core/containers" "github.com/containerd/containerd/v2/core/content" "github.com/containerd/containerd/v2/core/images" "github.com/containerd/containerd/v2/core/leases" "github.com/containerd/containerd/v2/core/remotes" "github.com/containerd/containerd/v2/core/snapshots" "github.com/containerd/containerd/v2/pkg/oci" "github.com/containerd/containerd/v2/plugins" "github.com/containerd/errdefs" "github.com/containerd/go-cni" "github.com/containerd/platforms" "github.com/opencontainers/go-digest" "github.com/opencontainers/image-spec/identity" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/opencontainers/runtime-spec/specs-go" "github.com/urfave/cli/v2" "golang.org/x/sys/unix" ) const ( uniqueObjectFormat = "record-trace-%s-%s" traceNameInLayer = ".trace" cniConf = `{ "cniVersion": "0.3.1", "name": "record-trace-cni", "plugins": [ { "type": "bridge", "bridge": "r-trace-br0", "isGateway": false, "ipMasq": true, "promiscMode": true, "ipam": { "type": "host-local", "ranges": [ [{ "subnet": "10.99.0.0/16" }], [{ "subnet": "2001:4860:4860::/64" }] ], "routes": [ ] } } ] } ` containerdDir = "/var/lib/containerd/" v1ContentsDir = "io.containerd.content.v1.content/blobs/" ) var ( networkNamespace string namespacePath string maxCollectTraceTime time.Duration // In case there are back-store bugs and the trace file is not generated maxTaskRunningTime time.Duration // In case the task is not able to respond to SIGTERM maxLeaseTime time.Duration // After which the temporary resources shall be cleaned emptyDesc ocispec.Descriptor ) var recordTraceCommand = &cli.Command{ Name: "record-trace", Usage: "record trace for prefetch", ArgsUsage: "OldImage NewImage [COMMAND] [ARG...]", Description: "Make an new image from the old one, with a trace layer on top of it. A temporary container will be created and do the recording.", Flags: []cli.Flag{ &cli.UintFlag{ Name: "time", Usage: "record time in seconds. When time expires, a TERM signal will be sent to the task. The task might fail to respond signal if time is too short.", Value: 60, }, &cli.StringFlag{ Name: "priority_list", Usage: "path of a file-list contains files to be prefetched", Value: "", }, &cli.StringFlag{ Name: "working-dir", Value: "/tmp/ctr-record-trace/", Usage: "temporary working dir for trace files", }, &cli.StringFlag{ Name: "snapshotter", Usage: "snapshotter name.", Value: "overlaybd", }, &cli.StringFlag{ Name: "runtime", Usage: "runtime name", Value: string(plugins.RuntimePluginV2), }, &cli.IntFlag{ Name: "max-concurrent-downloads", Usage: "Set the max concurrent downloads for each pull", Value: 8, }, &cli.BoolFlag{ Name: "disable-network-isolation", Usage: "Do not use cni to provide network isolation, default is false", }, &cli.StringFlag{ Name: "cni-plugin-dir", Usage: "cni plugin dir", Value: "/opt/cni/bin/", }, }, Before: func(cliCtx *cli.Context) error { if cliCtx.IsSet("priority_list") && cliCtx.Args().Len() > 2 { return errors.New("command args and priority_list can't be set at the same time") } return nil }, Action: func(cliCtx *cli.Context) (err error) { recordTime := time.Duration(cliCtx.Uint("time")) * time.Second if recordTime == 0 { return errors.New("time can't be 0") } maxTaskRunningTime = recordTime maxCollectTraceTime = recordTime maxLeaseTime = 3 * recordTime // Create client client, ctx, cancel, err := commands.NewClient(cliCtx) if err != nil { return err } defer cancel() cs := client.ContentStore() // Validate arguments ref := cliCtx.Args().Get(0) if ref == "" { return errors.New("image ref must be provided") } newRef := cliCtx.Args().Get(1) if newRef == "" { return errors.New("new image ref must be provided") } if _, err = client.ImageService().Get(ctx, newRef); err == nil { return fmt.Errorf("few image %s exists", newRef) } else if !errdefs.IsNotFound(err) { return fmt.Errorf("fail to lookup image %s", newRef) } // Get image instance imgModel, err := client.ImageService().Get(ctx, ref) if err != nil { return fmt.Errorf("fail to get image %s", ref) } image := containerd.NewImage(client, imgModel) imageManifest, err := images.Manifest(ctx, cs, image.Target(), platforms.Default()) if err != nil { return err } // Validate top layer. Get fs type topLayer := imageManifest.Layers[len(imageManifest.Layers)-1] if _, ok := topLayer.Annotations[label.OverlayBDBlobDigest]; !ok { return errors.New("must be an overlaybd image") } if topLayer.Annotations[label.AccelerationLayer] == "yes" { return errors.New("acceleration layer already exists") } fsType, ok := topLayer.Annotations[label.OverlayBDBlobFsType] if !ok { fsType = "" } // Create trace file if err := os.Mkdir(cliCtx.String("working-dir"), 0644); err != nil && !os.IsExist(err) { return fmt.Errorf("failed to create working dir: %w", err) } traceFile := filepath.Join(cliCtx.String("working-dir"), uniqueObjectString()) var traceFd *os.File if traceFd, err = os.Create(traceFile); err != nil { return errors.New("failed to create trace file") } defer os.Remove(traceFile) if !cliCtx.IsSet("priority_list") { _ = traceFd.Close() // Create lease ctx, deleteLease, err := client.WithLease(ctx, leases.WithID(uniqueObjectString()), leases.WithExpiration(maxLeaseTime), ) if err != nil { return fmt.Errorf("failed to create lease: %w", err) } defer deleteLease(ctx) // Create isolated network if !cliCtx.Bool("disable-network-isolation") { networkNamespace = uniqueObjectString() namespacePath = "/var/run/netns/" + networkNamespace if err = exec.Command("ip", "netns", "add", networkNamespace).Run(); err != nil { return fmt.Errorf("failed to add netns: %w", err) } defer func() { if nextErr := exec.Command("ip", "netns", "delete", networkNamespace).Run(); err == nil && nextErr != nil { err = fmt.Errorf("failed to delete netns: %w", nextErr) } }() cniObj, err := createIsolatedNetwork(cliCtx) if err != nil { return err } defer func() { if nextErr := cniObj.Remove(ctx, networkNamespace, namespacePath); err == nil && nextErr != nil { err = fmt.Errorf("failed to teardown network: %w", nextErr) } }() if _, err = cniObj.Setup(ctx, networkNamespace, namespacePath); err != nil { return fmt.Errorf("failed to setup network for namespace: %w", err) } } // Create container and run task fmt.Println("Create container") container, err := createContainer(ctx, client, cliCtx, image, traceFile) if err != nil { return err } defer container.Delete(ctx, containerd.WithSnapshotCleanup) task, err := tasks.NewTask(ctx, client, container, "", nil, false, "", nil) if err != nil { return err } defer task.Delete(ctx) var statusC <-chan containerd.ExitStatus if statusC, err = task.Wait(ctx); err != nil { return err } if err := task.Start(ctx); err != nil { return err } fmt.Println("Task is running ...") timer := time.NewTimer(recordTime) watchStop := make(chan bool) // Start a thread to watch timeout and signals go watchThread(ctx, timer, task, watchStop) // Wait task stopped status := <-statusC if _, _, err := status.Result(); err != nil { return fmt.Errorf("failed to get exit status: %w", err) } if timer.Stop() { watchStop <- true fmt.Println("Task finished before timeout ...") } // Collect trace if err = collectTrace(traceFile); err != nil { return err } } else { fmt.Println("Set priority list as acceleration layer") defer traceFd.Close() fn := cliCtx.String("priority_list") inf, err := os.OpenFile(fn, os.O_RDONLY, 0644) if err != nil { fmt.Printf("failed to open priority list: %s", err.Error()) return err } defer inf.Close() _, err = io.Copy(traceFd, inf) if err != nil { return err } } // Load trace file into content, and generate an acceleration layer loader := obdconv.NewContentLoaderWithFsType(true, fsType, obdconv.ContentFile{SrcFilePath: traceFile, DstFileName: "trace"}) accelLayer, err := loader.Load(ctx, cs) if err != nil { return fmt.Errorf("loadCommittedSnapshotInContent failed: %v", err) } // Create image with the acceleration layer on top newManifestDesc, err := createImageWithAccelLayer(ctx, cs, imageManifest, accelLayer) if err != nil { return fmt.Errorf("createImageWithAccelLayer failed: %v", err) } imgName := cliCtx.Args().Get(1) is := client.ImageService() img := images.Image{ Name: imgName, Target: newManifestDesc, } for { if _, err := is.Create(ctx, img); err != nil { if !errdefs.IsAlreadyExists(err) { return fmt.Errorf("createImage failed: %v", err) } if _, err := is.Update(ctx, img); err != nil { if errdefs.IsNotFound(err) { continue } return fmt.Errorf("createImage failed: %v", err) } } fmt.Printf("New image %s is created\n", newRef) return nil } }, } func watchThread(ctx context.Context, timer *time.Timer, task containerd.Task, watchStop chan bool) { // Allow termination by user signals sigStop := make(chan bool) sigChan := registerSignalsForTask(ctx, task, sigStop) select { case <-sigStop: timer.Stop() break case <-watchStop: break case <-timer.C: fmt.Println("Timeout, stop recording ...") break } signal.Stop(sigChan) close(sigChan) st, err := task.Status(ctx) if err != nil { fmt.Printf("Failed to get task status: %v\n", err) } if st.Status == containerd.Running { // Note the task will not be deleted until the end go preventTaskHang(ctx, task) if err = task.Kill(ctx, unix.SIGTERM); err != nil { fmt.Printf("Failed to kill task: %v\n", err) } } } func preventTaskHang(ctx context.Context, task containerd.Task) { time.Sleep(maxTaskRunningTime) st, err := task.Status(ctx) if err != nil { fmt.Printf("Failed to get task status: %v\n", err) } if st.Status == containerd.Running { fmt.Println("Reached maxTaskRunningTime, force kill ...") _ = task.Kill(ctx, unix.SIGKILL) } } func collectTrace(traceFile string) error { sigStop := make(chan bool) sigChan := registerSignalsForTraceCollection(sigStop) defer func() { signal.Stop(sigChan) close(sigChan) }() lockFile := traceFile + ".lock" if err := os.Remove(lockFile); err != nil && !os.IsNotExist(err) { fmt.Printf("Remove lock file %s failed: %v\n", lockFile, err) return err } okFile := traceFile + ".ok" defer os.Remove(okFile) fmt.Println("Collecting trace ...") var elapsed time.Duration for { select { case <-sigStop: return errors.New("trace collection was canceled") default: } if elapsed < maxCollectTraceTime { elapsed += time.Second time.Sleep(time.Second) } else { return errors.New("trace collection was timed-out") } if _, err := os.Stat(okFile); err == nil { fmt.Printf("Found OK file, trace is available now at %s\n", traceFile) return nil } } } func createImageWithAccelLayer(ctx context.Context, cs content.Store, oldManifest ocispec.Manifest, l obdconv.Layer) (ocispec.Descriptor, error) { oldConfigData, err := content.ReadBlob(ctx, cs, oldManifest.Config) if err != nil { return emptyDesc, err } var oldConfig ocispec.Image if err := json.Unmarshal(oldConfigData, &oldConfig); err != nil { return emptyDesc, err } buildTime := time.Now() var newHistory = []ocispec.History{{ Created: &buildTime, CreatedBy: "Acceleration Layer", }} oldConfig.History = append(newHistory, oldConfig.History...) oldConfig.RootFS.DiffIDs = append(oldConfig.RootFS.DiffIDs, l.DiffID) oldConfig.Created = &buildTime newConfigData, err := json.MarshalIndent(oldConfig, "", " ") if err != nil { return emptyDesc, fmt.Errorf("failed to marshal image: %w", err) } newConfigDesc := ocispec.Descriptor{ MediaType: oldManifest.Config.MediaType, Digest: digest.Canonical.FromBytes(newConfigData), Size: int64(len(newConfigData)), } ref := remotes.MakeRefKey(ctx, newConfigDesc) if err := content.WriteBlob(ctx, cs, ref, bytes.NewReader(newConfigData), newConfigDesc); err != nil { return emptyDesc, fmt.Errorf("failed to write image config: %w", err) } newManifest := ocispec.Manifest{} newManifest.SchemaVersion = oldManifest.SchemaVersion newManifest.Config = newConfigDesc newManifest.Layers = append(oldManifest.Layers, l.Desc) imageMediaType := oldManifest.MediaType // V2 manifest is not adopted in OCI spec yet, so follow the docker registry V2 spec here var newManifestV2 = struct { ocispec.Manifest MediaType string `json:"mediaType"` }{ Manifest: newManifest, MediaType: imageMediaType, // images.MediaTypeDockerSchema2Manifest, } newManifestData, err := json.MarshalIndent(newManifestV2, "", " ") if err != nil { return emptyDesc, err } newManifestDesc := ocispec.Descriptor{ MediaType: imageMediaType, // images.MediaTypeDockerSchema2Manifest, Digest: digest.Canonical.FromBytes(newManifestData), Size: int64(len(newManifestData)), } labels := map[string]string{} labels["containerd.io/gc.ref.content.config"] = newConfigDesc.Digest.String() for i, layer := range newManifest.Layers { labels[fmt.Sprintf("containerd.io/gc.ref.content.l.%d", i)] = layer.Digest.String() } ref = remotes.MakeRefKey(ctx, newManifestDesc) if err := content.WriteBlob(ctx, cs, ref, bytes.NewReader(newManifestData), newManifestDesc, content.WithLabels(labels)); err != nil { return emptyDesc, fmt.Errorf("failed to write image manifest: %w", err) } return newManifestDesc, nil } func createContainer(ctx context.Context, client *containerd.Client, cliCtx *cli.Context, image containerd.Image, traceFile string) (containerd.Container, error) { snapshotter := cliCtx.String("snapshotter") unpacked, err := image.IsUnpacked(ctx, snapshotter) if err != nil { return nil, err } if !unpacked { if err := image.Unpack(ctx, snapshotter); err != nil { return nil, err } } var ( opts []oci.SpecOpts cOpts []containerd.NewContainerOpts spec containerd.NewContainerOpts key = uniqueObjectString() // Specify the binary and arguments for the application to execute args = cliCtx.Args().Slice()[2:] ) cOpts = append(cOpts, containerd.WithImage(image), containerd.WithSnapshotter(cliCtx.String("snapshotter")), containerd.WithImageStopSignal(image, "SIGTERM"), containerd.WithRuntime(cliCtx.String("runtime"), nil), withNewSnapshot(key, image, cliCtx.String("snapshotter"), traceFile), ) opts = append(opts, oci.WithDefaultSpec(), oci.WithDefaultUnixDevices, oci.WithImageConfig(image), ) if !cliCtx.Bool("disable-network-isolation") { opts = append(opts, oci.WithLinuxNamespace(specs.LinuxNamespace{ Type: specs.NetworkNamespace, Path: namespacePath, })) } if len(args) > 0 { opts = append(opts, oci.WithProcessArgs(args...)) } var s specs.Spec spec = containerd.WithSpec(&s, opts...) cOpts = append(cOpts, spec) // oci.WithImageConfig (WithUsername, WithUserID) depends on access to rootfs for resolving via // the /etc/{passwd,group} files. So cOpts needs to have precedence over opts. container, err := client.NewContainer(ctx, key, cOpts...) if err != nil { return nil, err } return container, nil } func createIsolatedNetwork(cliCtx *cli.Context) (cni.CNI, error) { cniObj, err := cni.New( cni.WithMinNetworkCount(2), cni.WithPluginDir([]string{cliCtx.String("cni-plugin-dir")}), ) if err != nil { return nil, fmt.Errorf("failed to initialize cni library: %w", err) } if err = cniObj.Load(cni.WithConfListBytes([]byte(cniConf)), cni.WithLoNetwork); err != nil { return nil, fmt.Errorf("failed to load cni conf: %w", err) } return cniObj, nil } func withNewSnapshot(key string, img containerd.Image, snapshotter, traceFile string) containerd.NewContainerOpts { return func(ctx context.Context, client *containerd.Client, c *containers.Container) error { diffIDs, err := img.RootFS(ctx) if err != nil { return err } parent := identity.ChainID(diffIDs).String() s := client.SnapshotService(snapshotter) if s == nil { return fmt.Errorf("snapshotter %s was not found: %w", snapshotter, errdefs.ErrNotFound) } opt := snapshots.WithLabels(map[string]string{ label.RecordTrace: "yes", label.RecordTracePath: traceFile, }) if _, err := s.Prepare(ctx, key, parent, opt); err != nil { return err } c.SnapshotKey = key c.Snapshotter = snapshotter c.Image = img.Name() return nil } } func uniqueObjectString() string { now := time.Now() var b [2]byte rand.Read(b[:]) return fmt.Sprintf(uniqueObjectFormat, now.Format("20060102150405"), hex.EncodeToString(b[:])) } func registerSignalsForTraceCollection(sigStop chan bool) chan os.Signal { sigc := make(chan os.Signal, 128) signal.Notify(sigc) go func() { for s := range sigc { if canIgnoreSignal(s) { continue } if s == unix.SIGTERM || s == unix.SIGINT { sigStop <- true } } }() return sigc } func registerSignalsForTask(ctx context.Context, task containerd.Task, sigStop chan bool) chan os.Signal { sigc := make(chan os.Signal, 128) signal.Notify(sigc) go func() { for s := range sigc { if canIgnoreSignal(s) { continue } if s == unix.SIGTERM || s == unix.SIGINT { fmt.Printf("Received signal %s\n", s) sigStop <- true } // Forward all signals to task if err := task.Kill(ctx, s.(syscall.Signal)); err != nil { if errdefs.IsNotFound(err) { return } fmt.Printf("failed to forward signal %s\n", s) } } }() return sigc } // Go 1.14 started to use non-cooperative goroutine preemption, SIGURG should be ignored func canIgnoreSignal(s os.Signal) bool { return s == unix.SIGURG } accelerated-container-image-1.4.2/cmd/ctr/rpull.go000066400000000000000000000073461514714641000220470ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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" "fmt" "github.com/containerd/accelerated-container-image/pkg/label" containerd "github.com/containerd/containerd/v2/client" "github.com/containerd/containerd/v2/cmd/ctr/commands" ctrcontent "github.com/containerd/containerd/v2/cmd/ctr/commands/content" "github.com/containerd/containerd/v2/core/images" "github.com/containerd/containerd/v2/core/snapshots" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/urfave/cli/v2" ) var rpullCommand = &cli.Command{ Name: "rpull", Usage: "rpull an image from a remote for overlaybd snapshotter", ArgsUsage: "[flags] ", Description: `Fetch and prepare an image for use in containerd.`, Flags: append(append(commands.RegistryFlags, commands.LabelFlag), &cli.StringFlag{ Name: "snapshotter", Usage: "snapshotter name. Empty value stands for the default value.", Value: "overlaybd", EnvVars: []string{"CONTAINERD_SNAPSHOTTER"}, }, &cli.BoolFlag{ Name: "download-blobs", Usage: "download overlaybd blobs", }, ), Action: func(context *cli.Context) error { var ( ref = context.Args().First() ) 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) config, err := ctrcontent.NewFetchConfig(ctx, context) if err != nil { return err } if err := rpull(ctx, client, ref, context.String("snapshotter"), config, context.Bool("download-blobs")); err != nil { return err } fmt.Println("done") return nil }, } // rpull pulls image with special labels // // NOTE: Based on https://github.com/containerd/containerd/blob/v1.5.0-beta.2/cmd/ctr/commands/content/fetch.go#L148. func rpull(ctx context.Context, client *containerd.Client, ref string, sn string, config *ctrcontent.FetchConfig, download bool) error { ongoing := ctrcontent.NewJobs(ref) pctx, stopProgress := context.WithCancel(ctx) progress := make(chan struct{}) go func() { if config.ProgressOutput != nil { ctrcontent.ShowProgress(pctx, ongoing, client.ContentStore(), config.ProgressOutput) } close(progress) }() h := images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { ongoing.Add(desc) return nil, nil }) snLabels := map[string]string{label.TargetImageRef: ref} if download { snLabels[label.DownloadRemoteBlob] = "download" } labels := commands.LabelArgs(config.Labels) opts := []containerd.RemoteOpt{ containerd.WithPullLabels(labels), containerd.WithResolver(config.Resolver), containerd.WithImageHandler(h), containerd.WithPullUnpack, containerd.WithPullSnapshotter(sn, snapshots.WithLabels(snLabels)), } if config.PlatformMatcher != nil { opts = append(opts, containerd.WithPlatformMatcher(config.PlatformMatcher)) } else { for _, platform := range config.Platforms { opts = append(opts, containerd.WithPlatform(platform)) } } _, err := client.Pull(pctx, ref, opts...) stopProgress() return err } accelerated-container-image-1.4.2/cmd/overlaybd-attacher/000077500000000000000000000000001514714641000233405ustar00rootroot00000000000000accelerated-container-image-1.4.2/cmd/overlaybd-attacher/README.md000066400000000000000000000100371514714641000246200ustar00rootroot00000000000000# overlaybd-attacher A command-line tool to attach and detach overlaybd block devices through the kernel's configfs interface. ## Overview `overlaybd-attacher` provides a simple interface to manage overlaybd devices by calling `AttachDevice` and `DetachDevice` functions from `pkg/snapshot/storage.go`. It creates TCMU (TCM User) devices via the Linux kernel's configfs target subsystem. ## Installation Build from source: ```bash make bin/overlaybd-attacher ``` The binary will be generated in the `bin/` directory. ## Usage ### Attach Device Attach an overlaybd device with a given configuration: ```bash overlaybd-attacher attach --id --config [options] ``` **Options:** - `--id` (required): Device ID (snapshot ID) - `--config` (required): Path to overlaybd configuration file (config.v1.json) - `-v, --verbose`: Enable verbose logging **Note:** Specifying custom tenant ID is not supported. The tool uses the default tenant (-1). **Example:** ```bash # Basic attach overlaybd-attacher attach --id 123456 --config /path/to/config.v1.json # With verbose logging overlaybd-attacher attach --id 123456 --config /path/to/config.v1.json -v ``` The command outputs the device path (e.g., `/dev/sda`) to stdout on success. **Debug Log:** The attach process writes debug information to a result file. The location is determined by: 1. If `resultFile` is specified in config.v1.json: - Use the specified path (absolute or relative to config file directory) 2. If `resultFile` is not specified: - Default to `init-debug.log` in the same directory as the config file This file contains detailed information about the device attachment process and can be used for troubleshooting. **Example config.v1.json:** ```json { "repoBlobUrl": "...", "lowers": [...], "upper": {...}, "resultFile": "/var/log/overlaybd/init-debug.log" } ``` or with relative path: ```json { "repoBlobUrl": "...", "lowers": [...], "upper": {...}, "resultFile": "debug.log" } ``` ### Detach Device Detach an overlaybd device: ```bash overlaybd-attacher detach --id [options] ``` **Options:** - `--id` (required): Device ID (snapshot ID) - `-v, --verbose`: Enable verbose logging **Note:** Specifying custom tenant ID is not supported. The tool uses the default tenant (-1). **Example:** ```bash # Basic detach overlaybd-attacher detach --id 123456 # With verbose logging overlaybd-attacher detach --id 123456 -v ``` ## Retry Mechanism The `attach` command includes an automatic retry mechanism on failure: - Retry count: 5 attempts (hardcoded) - Retry interval: 1 second between attempts - Retries occur automatically without user intervention This helps handle transient failures during device attachment, such as temporary resource unavailability or timing issues with kernel module operations. ## Implementation Details ### Device Creation Process The `attach` command performs the following operations: 1. Creates TCMU device in configfs: `/sys/kernel/config/target/core/user_/` 2. Configures device parameters (max_data_area_mb, cmd_time_out) 3. Creates loopback target: `/sys/kernel/config/target/loopback/naa.` 4. Creates loopback LUN and links to TCMU device 5. Waits for SCSI block device to appear in `/sys/class/scsi_device/` 6. Returns device path (e.g., `/dev/sda`) ### Device Destruction Process The `detach` command performs cleanup in reverse order: 1. Removes loopback LUN link 2. Removes loopback target configuration 3. Removes TCMU device from configfs ## Multi-tenancy Support **Note:** Specifying custom tenant ID is not supported. The tool always uses the default tenant (-1), which corresponds to HBA 999999999 and NAA prefix 199. ## Requirements - Linux kernel with TCM_LOOPBACK and TCM_USER modules loaded - Overlaybd backstore process running - Root privileges (required for configfs operations) ## Exit Codes - `0`: Success - `1`: Error occurred (check stderr for details) ## Logging Logs are written to stderr using logrus. Enable verbose logging with `-v` flag for detailed operation information.accelerated-container-image-1.4.2/cmd/overlaybd-attacher/main.go000066400000000000000000000103451514714641000246160ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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" "encoding/json" "fmt" "os" "path/filepath" "time" "github.com/containerd/accelerated-container-image/pkg/snapshot" "github.com/containerd/accelerated-container-image/pkg/types" "github.com/sirupsen/logrus" "github.com/urfave/cli/v2" ) var ( commitID string = "unknown" ) func main() { app := &cli.App{ Name: "overlaybd-attacher", Usage: "A CLI tool to attach/detach overlaybd devices", Version: commitID, Commands: []*cli.Command{ { Name: "attach", Usage: "Attach an overlaybd device with given ID and configuration", Flags: []cli.Flag{ &cli.StringFlag{ Name: "id", Usage: "Device ID (snapshot ID)", Required: true, }, &cli.StringFlag{ Name: "config", Usage: "Path to overlaybd configuration file (config.v1.json)", Required: true, }, &cli.BoolFlag{ Name: "verbose", Usage: "Enable verbose logging", Aliases: []string{"v"}, }, }, Action: attachAction, }, { Name: "detach", Usage: "Detach an overlaybd device with given ID", Flags: []cli.Flag{ &cli.StringFlag{ Name: "id", Usage: "Device ID (snapshot ID)", Required: true, }, &cli.BoolFlag{ Name: "verbose", Usage: "Enable verbose logging", Aliases: []string{"v"}, }, }, Action: detachAction, }, }, } if err := app.Run(os.Args); err != nil { logrus.Fatal(err) } } func attachAction(c *cli.Context) error { if c.Bool("verbose") { logrus.SetLevel(logrus.DebugLevel) } id := c.String("id") tenant := -1 configPath := c.String("config") retryCount := 5 if _, err := os.Stat(configPath); err != nil { return fmt.Errorf("config file does not exist: %s: %w", configPath, err) } data, err := os.ReadFile(configPath) if err != nil { return fmt.Errorf("failed to read config file: %w", err) } var config types.OverlayBDBSConfig if err := json.Unmarshal(data, &config); err != nil { return fmt.Errorf("invalid JSON config file: %w", err) } absConfigPath, err := filepath.Abs(configPath) if err != nil { return fmt.Errorf("failed to get absolute path for config: %w", err) } var resultFile string if config.ResultFile != "" { if filepath.IsAbs(config.ResultFile) { resultFile = config.ResultFile } else { resultFile = filepath.Join(filepath.Dir(absConfigPath), config.ResultFile) } } else { resultFile = filepath.Join(filepath.Dir(absConfigPath), "init-debug.log") } ctx := context.Background() params := snapshot.NewAttachDeviceParams(id, tenant, absConfigPath, resultFile) logrus.Infof("attaching device: id=%s, tenant=%d, config=%s, resultFile=%s", id, tenant, absConfigPath, resultFile) var devName string var lastErr error for attempt := 1; attempt <= retryCount; attempt++ { if attempt > 1 { logrus.Warnf("retry attempt %d/%d after error: %v", attempt, retryCount, lastErr) time.Sleep(1 * time.Second) } devName, lastErr = snapshot.AttachDevice(ctx, params) if lastErr == nil { fmt.Println(devName) logrus.Infof("device attached successfully: %s", devName) return nil } } return fmt.Errorf("failed to attach device after %d attempts: %w", retryCount, lastErr) } func detachAction(c *cli.Context) error { if c.Bool("verbose") { logrus.SetLevel(logrus.DebugLevel) } id := c.String("id") tenant := -1 ctx := context.Background() logrus.Infof("detaching device: id=%s, tenant=%d", id, tenant) if err := snapshot.DetachDevice(ctx, id, tenant); err != nil { return fmt.Errorf("failed to detach device: %w", err) } logrus.Infof("device detached successfully") return nil } accelerated-container-image-1.4.2/cmd/overlaybd-snapshotter/000077500000000000000000000000001514714641000241175ustar00rootroot00000000000000accelerated-container-image-1.4.2/cmd/overlaybd-snapshotter/main.go000066400000000000000000000116611514714641000253770ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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" "encoding/json" "fmt" "net" "os" "os/signal" "path/filepath" "runtime" "strings" "github.com/containerd/accelerated-container-image/pkg/metrics" overlaybd "github.com/containerd/accelerated-container-image/pkg/snapshot" snapshotsapi "github.com/containerd/containerd/api/services/snapshots/v1" "github.com/containerd/containerd/v2/contrib/snapshotservice" "github.com/sirupsen/logrus" "golang.org/x/sys/unix" "google.golang.org/grpc" ) const defaultConfigPath = "/etc/overlaybd-snapshotter/config.json" var pconfig *overlaybd.BootConfig var commitID string = "unknown" func parseConfig(fpath string) error { logrus.Info("parse config file: ", fpath) data, err := os.ReadFile(fpath) if err != nil { return fmt.Errorf("failed to read plugin config from %s: %w", fpath, err) } if err := json.Unmarshal(data, pconfig); err != nil { return fmt.Errorf("failed to parse plugin config from %s: %w", string(data), err) } logrus.Infof("snapshotter commitID: %s, rwMode: %s, autoRemove: %v, writableLayerType: %s, asyncRemoveSnapshot: %v", commitID, pconfig.RwMode, pconfig.AutoRemoveDev, pconfig.WritableLayerType, pconfig.AsyncRemove) return nil } // TODO: use github.com/urfave/cli/v2 func main() { pconfig = overlaybd.DefaultBootConfig() fnConfig := defaultConfigPath if len(os.Args) == 2 { fnConfig = os.Args[1] } if err := parseConfig(fnConfig); err != nil { logrus.Error(err) os.Exit(1) } if pconfig.LogReportCaller { logrus.SetReportCaller(true) } metrics.Config = pconfig.ExporterConfig if pconfig.ExporterConfig.Enable { go metrics.Init() logrus.Infof("set Prometheus metrics exporter in http://localhost:%d%s", metrics.Config.Port, metrics.Config.UriPrefix) } contain := func(fsType string) bool { for _, fst := range pconfig.TurboFsType { if fst == fsType { return true } } return false } if !contain("ext4") { pconfig.TurboFsType = append(pconfig.TurboFsType, "ext4") } if !contain("erofs") { pconfig.TurboFsType = append(pconfig.TurboFsType, "erofs") } if err := setLogLevel(pconfig.LogLevel); err != nil { logrus.Errorf("failed to set log level: %v", err) } else { logrus.Infof("set log level: %s", pconfig.LogLevel) } sn, err := overlaybd.NewSnapshotter(pconfig) if err != nil { logrus.Errorf("failed to init overlaybd snapshotter: %v", err) os.Exit(1) } defer sn.Close() srv := grpc.NewServer() snapshotsapi.RegisterSnapshotsServer(srv, snapshotservice.FromSnapshotter(sn)) address := strings.TrimSpace(pconfig.Address) if address == "" { logrus.Errorf("invalid address path(%s)", address) os.Exit(1) } if err := os.MkdirAll(filepath.Dir(address), 0700); err != nil { logrus.Errorf("failed to create directory %v", filepath.Dir(address)) os.Exit(1) } // try to remove the socket file to avoid EADDRINUSE if err := os.RemoveAll(address); err != nil { logrus.Errorf("failed to remove %v", address) os.Exit(1) } l, err := net.Listen("unix", address) if err != nil { logrus.Errorf("failed to listen on %s: %v", address, err) os.Exit(1) } go func() { if err := srv.Serve(l); err != nil { logrus.Errorf("failed to server: %v", err) os.Exit(1) } }() logrus.Infof("start to serve overlaybd snapshotter on %s", address) signals := make(chan os.Signal, 32) signal.Notify(signals, unix.SIGTERM, unix.SIGINT, unix.SIGPIPE) <-handleSignals(context.TODO(), signals, srv) if pconfig.ExporterConfig.Enable { metrics.IsAlive.Set(0) } } func handleSignals(ctx context.Context, signals chan os.Signal, server *grpc.Server) chan struct{} { doneCh := make(chan struct{}, 1) go func() { for { s := <-signals switch s { case unix.SIGUSR1: dumpStacks() case unix.SIGPIPE: continue default: if server == nil { close(doneCh) return } server.GracefulStop() close(doneCh) return } } }() return doneCh } func dumpStacks() { var ( buf []byte stackSize int ) bufferLen := 16384 for stackSize == len(buf) { buf = make([]byte, bufferLen) stackSize = runtime.Stack(buf, true) bufferLen *= 2 } buf = buf[:stackSize] logrus.Infof("=== BEGIN goroutine stack dump ===\n%s\n=== END goroutine stack dump ===", buf) } func setLogLevel(level string) error { logLevel, err := logrus.ParseLevel(level) if err != nil { return err } logrus.SetLevel(logLevel) return nil } accelerated-container-image-1.4.2/docs/000077500000000000000000000000001514714641000177455ustar00rootroot00000000000000accelerated-container-image-1.4.2/docs/BUILDING.md000066400000000000000000000024201514714641000214620ustar00rootroot00000000000000# Building This doc includes: * [Requirements](#requirements) * [Build from source](#build-from-source) * [Configure](#configure) * [Proxy snapshotter plugin config](#proxy-snapshotter-plugin-config) * [containerd config](#containerd-config) * [Run](#run) ## Requirements * Install Go >= 1.26.x * Install runc >= 1.0 * Install containerd >= 2.0.x * See [Downloads at containerd.io](https://containerd.io/downloads/). ### Build from source You need git to checkout the source code and compile: ```bash git clone https://github.com/containerd/accelerated-container-image.git cd accelerated-container-image make ``` The snapshotter and ctr plugin are generated in `bin`. ## Configure ### proxy snapshotter plugin config ```bash sudo mkdir /etc/overlaybd-snapshotter sudo cat <<-EOF | sudo tee /etc/overlaybd-snapshotter/config.json { "root": "/var/lib/overlaybd/", "address": "/run/overlaybd-snapshotter/overlaybd.sock" } EOF ``` ### containerd config ```bash sudo cat <<-EOF | sudo tee --append /etc/containerd/config.toml [proxy_plugins.overlaybd] type = "snapshot" address = "/run/overlaybd-snapshotter/overlaybd.sock" EOF ``` ## Run ```bash # run snapshotter plugin sudo bin/overlaybd-snapshotter # restart containerd sudo systemctl restart containerd ``` accelerated-container-image-1.4.2/docs/CONTRIBUTING.md000066400000000000000000000051671514714641000222070ustar00rootroot00000000000000# Contributing Contributions should be made via pull requests. Pull requests will be reviewed by one or more maintainers and merged when acceptable. Every contribution is very appreciated!!! ## Commit Style The seven rules of a great Git commit message: * Separate the subject from body with a blank line * Limit the subject line to 50 characters * Capitalize the subject line * Do not end the subject line with a period * Use the imperative mood in the subject line * Wrap the body at 72 characters * Use the body to explain what and why vs. how Read more on [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/). ## Sign your work The sign-off is a simple line at the end of the explanation for the patch. Your signature certifies that you wrote the patch or otherwise have the right to pass it on as an open-source patch. The rules are pretty simple: if you can certify the below (from developercertificate.org): ``` Developer Certificate of Origin Version 1.1 Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 660 York Street, Suite 102, San Francisco, CA 94110 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Developer's Certificate of Origin 1.1 By making a contribution to this project, I certify that: (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. ``` Then you just add a line to every git commit message: ``` Signed-off-by: Joe Smith ``` Use your real name (sorry, no pseudonyms or anonymous contributions.) If you set your `user.name` and `user.email` git configs, you can sign your commit automatically with `git commit -s`. accelerated-container-image-1.4.2/docs/EXAMPLES.md000066400000000000000000000132131514714641000215050ustar00rootroot00000000000000# Getting Started Before checkout the examples, make sure that you have built or installed the components described in [overlaybd](https://github.com/containerd/overlaybd/blob/main/README.md) and [BUILDING](BUILDING.md). This doc includes: - [Getting Started](#getting-started) - [Check Components](#check-components) - [Overlaybd](#overlaybd) - [Proxy overlaybd snapshotter](#proxy-overlaybd-snapshotter) - [Ondemand Pulling Image](#ondemand-pulling-image) - [Convert OCI Image into overlaybd](#convert-oci-image-into-overlaybd) ## Check Components ### Overlaybd Check whether the `overlaybd-tcmu` is running. Overlaybd-snapshotter v1.0.1+ requires overlaybd-tcmu v1.0.4+. For any problems, please checkout [overlaybd](https://github.com/containerd/overlaybd). ### Proxy overlaybd snapshotter First, you should make sure that '/etc/containerd/config.toml' has been changed and contains the following contents like this: ```bash [proxy_plugins.overlaybd] type = "snapshot" address = "/run/overlaybd-snapshotter/overlaybd.sock" ``` Use `ctr` to check the plugin. ```bash # in other terminal sudo ctr plugin ls | grep overlaybd sudo ctr snapshot --snapshotter overlaybd ls ``` If there is no overlaybd plugin, please checkout [BUILDING](BUILDING.md). ## Ondemand Pulling Image The containerd feature [supports target snapshot references on prepare](https://github.com/containerd/containerd/pull/3793) is released by v1.4.x. __[Recommend]__ Now we support to start an overlaybd container by [`nerdctl`](https://github.com/containerd/nerdctl)[(#603)](https://github.com/containerd/nerdctl/pull/603). ```bash $ sudo nerdctl run --net host -it --rm --snapshotter=overlaybd registry.hub.docker.com/overlaybd/redis:6.2.1_obd 1:C 03 Mar 2021 04:39:31.804 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo 1:C 03 Mar 2021 04:39:31.804 # Redis version=6.2.1, bits=64, commit=00000000, modified=0, pid=1, just started 1:C 03 Mar 2021 04:39:31.804 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf 1:M 03 Mar 2021 04:39:31.805 # You requested maxclients of 10000 requiring at least 10032 max file descriptors. 1:M 03 Mar 2021 04:39:31.805 # Server can't set maximum open files to 10032 because of OS error: Operation not permitted. 1:M 03 Mar 2021 04:39:31.805 # Current maximum open files is 1024. maxclients has been reduced to 992 to compensate for low ulimit. If you need higher maxclients increase 'ulimit -n'. 1:M 03 Mar 2021 04:39:31.805 * monotonic clock: POSIX clock_gettime _._ _.-``__ ''-._ _.-`` `. `_. ''-._ Redis 6.2.1 (00000000/0) 64 bit .-`` .-```. ```\/ _.,_ ''-._ ( ' , .-` | `, ) Running in standalone mode |`-._`-...-` __...-.``-._|'` _.-'| Port: 6379 | `-._ `._ / _.-' | PID: 1 `-._ `-._ `-./ _.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | http://redis.io `-._ `-._`-.__.-'_.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | `-._ `-._`-.__.-'_.-' _.-' `-._ `-.__.-' _.-' `-._ _.-' `-.__.-' 1:M 03 Mar 2021 04:39:31.808 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128. 1:M 03 Mar 2021 04:39:31.808 # Server initialized 1:M 03 Mar 2021 04:39:31.808 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect. 1:M 03 Mar 2021 04:39:31.810 * Ready to accept connections ``` And also, you can use `ctr` to run this image. However, you should first use the subcommand `rpull` in the customized `bin/ctr` to pull image ```bash # you will see that the rpull doesn't pull layer data. sudo bin/ctr rpull registry.hub.docker.com/overlaybd/redis:6.2.1_obd # start the container sudo ctr run --net-host --snapshotter=overlaybd --rm -t registry.hub.docker.com/overlaybd/redis:6.2.1_obd demo ``` After container launched success, we could see a new block device (sdb) has been created, and its mount-point is the lowerdir of overlayfs. ```bash $ sudo lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sdb 8:16 0 256G 1 disk /var/lib/overlaybd/snapshots/52/block/mountpoint ``` ## Convert OCI Image into overlaybd Overlaybd image convertor helps to convert a normal image to overlaybd-format remote image. ```bash # pull the source image sudo nerdctl pull registry.hub.docker.com/library/redis:6.2.1 # convert sudo bin/ctr obdconv registry.hub.docker.com/library/redis:6.2.1 registry.hub.docker.com/overlaybd/redis:6.2.1_obd_new # push the overlaybd image to registry, then the new converted image can be used as a remote image sudo nerdctl push registry.hub.docker.com/overlaybd/redis:6.2.1_obd_new # remove the local overlaybd image sudo nerdctl rmi registry.hub.docker.com/overlaybd/redis:6.2.1_obd_new # run sudo nerdctl run --net host --rm --snapshotter=overlaybd registry.hub.docker.com/overlaybd/redis:6.2.1_obd_new 1:C 03 Mar 2021 04:50:12.853 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo 1:C 03 Mar 2021 04:50:12.853 # Redis version=6.2.1, bits=64, commit=00000000, modified=0, pid=1, just started 1:C 03 Mar 2021 04:50:12.853 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf 1:M 03 Mar 2021 04:50:12.854 # You requested maxclients of 10000 requiring at least 10032 max file descriptors. ... ...... ```accelerated-container-image-1.4.2/docs/EXAMPLES_CRI.md000066400000000000000000000023541514714641000222060ustar00rootroot00000000000000# Getting Started ## Check Components Before checkout the cri examples, make sure that you have built or installed the components described in [overlaybd](https://github.com/containerd/overlaybd/README.md) and [BUILDING](BUILDING.md). You can check your overlaybd components as described in [EXAMPLES](EXAMPLES.md#check-components). ## Containerd config Config the plugin cri for containerd. And make sure `cri` is not listed in `disabled_plugins` of containerd config file. ```bash sudo cat <<-EOF | sudo tee --append /etc/containerd/config.toml [plugins.cri] [plugins.cri.containerd] snapshotter = "overlaybd" disable_snapshot_annotations = false EOF ``` ## Pod and container config The example pod-config and container-config. ```bash sudo cat <<-EOF | sudo tee pod-config.yaml metadata: attempt: 1 name: redis-obd namespace: default log_directory: /tmp linux: security_context: namespace_options: network: 2 EOF ``` ```bash sudo cat <<-EOF | sudo tee container-config.yaml metadata: name: redis-obd-container image: image: registry.hub.docker.com/overlaybd/redis:6.2.1_obd log_path: redis.container.log EOF ``` ## Create container by crictl ```bash crictl run container-config.yaml pod-config.yaml ``` accelerated-container-image-1.4.2/docs/IMAGE_CONVERTOR.md000066400000000000000000000074201514714641000225550ustar00rootroot00000000000000# Embedded Image Convertor We provide a ctr command tool to convert OCIv1 images into overlaybd format, which is stored in `bin` after `make` or downloading the release package. # Basic Usage ```bash # usage $ bin/ctr obdconv --help NAME: ctr obdconv - convert image layer into overlaybd format type USAGE: ctr obdconv [command options] DESCRIPTION: Export images to an OCI tar[.gz] into zfile format OPTIONS: --skip-verify, -k skip SSL certificate validation --plain-http allow connections using plain HTTP --user value, -u value user[:password] Registry user and password --refresh value refresh token for authorization server --hosts-dir value Custom hosts configuration directory --tlscacert value path to TLS root CA --tlscert value path to TLS client certificate --tlskey value path to TLS client key --http-dump dump all HTTP request/responses when interacting with container registry --http-trace enable HTTP tracing for registry interactions --fstype value filesystem type(required), used to mount block device, support specifying mount options and mkfs options, separate fs type and options by ';', separate mount options by ',', separate mkfs options by ' ' (default: "ext4") --dbstr value data base config string used for layer deduplication --algorithm value compress algorithm uses in zfile, [lz4|zstd] --bs value The size of a compressed data block in KB. Must be a power of two between 4K~64K [4/8/16/32/64] (default: 0) --vsize value virtual block device size (GB) (default: 64) ``` ```bash # pull the source image sudo ctr i pull registry.hub.docker.com/library/redis:6.2.1 # call our ctr to do image conversion # bin/ctr obdconv sudo bin/ctr obdconv registry.hub.docker.com/library/redis:6.2.1 registry.hub.docker.com/overlaybd/redis:6.2.1_obd_new # push ctr i push registry.hub.docker.com/overlaybd/redis:6.2.1_obd_new ``` # Layer Deduplication To avoid converting the same layer for every image conversion, a database is required to store the correspondence between OCIv1 image layer and overlaybd layer. We provide an implementation based on mysql database. First, create a database and the `overlaybd_layers` table, the table schema is as follow: ```sql CREATE TABLE `overlaybd_layers` ( `host` varchar(255) NOT NULL, `repo` varchar(255) NOT NULL, `chain_id` varchar(255) NOT NULL COMMENT 'chain-id of the normal image layer', `data_digest` varchar(255) NOT NULL COMMENT 'digest of overlaybd layer', `data_size` bigint(20) NOT NULL COMMENT 'size of overlaybd layer', PRIMARY KEY (`host`,`repo`,`chain_id`), KEY `index_registry_chainId` (`host`,`chain_id`) USING BTREE ) DEFAULT CHARSET=utf8; ``` Then, execute the ctr obdconv tool: ```bash # pull the source image sudo ctr i pull registry.hub.docker.com/library/redis:6.2.1 # call our ctr to do image conversion # bin/ctr obdconv --dbstr dbstr -u registry_username:password sudo bin/ctr obdconv --dbstr "username:password@tcp(db_host:port)/db_name" registry.hub.docker.com/library/redis:6.2.1 registry.hub.docker.com/overlaybd/redis:6.2.1_obd_new ``` After that, the new overlaybd image automatically is uploaded to registry and the layers correspondences are saved to database. The `dbstr` is the config string of database, please refer to [go-sql-driver/mysql](https://github.com/go-sql-driver/mysql). The other options are the same as `ctr content push-object`. For the converted blobs have to be pushed to registry during conversion to synchronize registry with database, the registry related options must be provided. The most important is the `--user` option which is used for authentication. accelerated-container-image-1.4.2/docs/MULTI_FS_SUPPORT.md000066400000000000000000000030631514714641000230070ustar00rootroot00000000000000# Multi-FS Supporting in Overlaybd ## Convert OCI Image into overlaybd with Specified File System You can specify file system when converting OCI Image into overlaybd with `--fstype` options. For example: ```bash sudo bin/ctr obdconv --fstype "xfs" registry.hub.docker.com/library/redis:6.2.1 localhost:5000/redis:6.2.1_obd_xfs ``` Push the generated overlaybd image to registry, then pull the uploaded image with `rpull` as described in [EXAMPLES](EXAMPLES.md). ## Mount/Mkfs options It's supported to use different options to mount/mkfs for specified file system. Specify mount/mkfs options in `--fstype` option, for example, `--fystype "fstype;mount_opt_1,mount_opt_2;mkfs_opt_1 mkfs_opt_2"`. If only file system type is given, we use default mount/mkfs option to fulfill better performance. + Default Mount/Mkfs Options |FS Type|Mount Options|Mkfs Options| |---|---|---| |ext4|discard|-O ^has_journal,sparse_super,flex_bg -G 1 -E discard| |xfs|nouuid,discard|-f -l size=4m -m crc=0| |ntfs|-|-F -f| + Override Default Options It's valid to override default mount/mkfs options. |`--fstype`|Description| |---|---| |`"fstype"`|use default mount/mkfs options| |`"fstype;"`|invalid mount options, and use default mkfs options| |`"fstype;;"`|invalid mount/mkfs options| |`"fstype;;mkfs_opts"`|invalid mount options, and use specified mkfs options| |`"fstype;mount_opts"`|use specified mount options, and use default mkfs options| |`"fstype;mount_opts;"`|use specified mount options, and invalid mkfs options| |`"fstype;mount_opts;mkfs_options"`|use specified mount/mkfs options|accelerated-container-image-1.4.2/docs/PERFORMANCE.md000066400000000000000000000031221514714641000220260ustar00rootroot00000000000000# Performance This document aims to compare the startup speed between Accelerated Container Image and standard OCI tar image. ## Environment Two WordPress images: one is in overlaybd format, the other is in standard OCI tar format. ## Why WordPress? WordPress is one of the most popular solutions to build a website and setup a web service. Its main process is Apache server. Unlike other lightweight apps such as busybox, WordPress image contains a lot of static resource files that need to be extracted before the admin logs in for the first time. Some IO and CPU workloads will thus be created. So we think it an appropriate way to simulate user actions in real business scenarios. ## Steps We provide a setup script to measure the service available time of WordPress app. Before start, please make sure port 80 is not occupied in your host. Use the clean script to stop running containers, remove images and drop local cache. ```bash cd script/performance/ ./clean-env.sh time ./setup-wordpress.sh registry.hub.docker.com/overlaybd/wordpress:5.7.0_obd ./clean-env.sh time ./setup-wordpress.sh registry.hub.docker.com/overlaybd/wordpress:5.7.0 ``` ## Performance results | **Image Format** | **Service Available Time** | | :----: | :----: | | overlaybd | 6.433s | | OCI tar | 15.341s | Conclusion: Overlaybd format outperforms OCI tar format at startup speed. Note: The [prefetch](https://github.com/containerd/accelerated-container-image/blob/main/docs/trace-prefetch.md) feature is enabled. Note: [Here](./TURBO_OCI.md) is the TurboOCI images' performance compares with OverlayBD and OCI images. accelerated-container-image-1.4.2/docs/PROMETHEUS.md000066400000000000000000000046331514714641000217700ustar00rootroot00000000000000# Collect Overlaybd metrics with Prometheus Prometheus is an open-source systems monitoring and alerting toolkit. You can configure overlaybd as a Prometheus target. This topic shows you how to configure overlaybd to use Prometheus. ## Configure Overlaybd To configure overlaybd as a Prometheus target, you need to specify `uriPrefix` and `port` of the exporter, they can be set in `/etc/overlaybd-snapshotter/config.json` (overlaybd-snapshotter config path), this is an example. ```json { "root": "/var/lib/containerd/io.containerd.snapshotter.v1.overlaybd", "asyncRemove": false, "address": "/run/overlaybd-snapshotter/overlaybd.sock", "verbose": "info", "rwMode": "overlayfs", "logReportCaller": false, "autoRemoveDev": false, "exporterConfig": { "enable": true, "uriPrefix": "/metrics", "port": 9863 } } ``` After configuration, you can run overlaybd-snapshotter and curl `http://localhost:9863/metrics` to get Prometheus metrics. ## Configure Prometheus To use Prometheus to monitor overlaybd, you should modify the `scrape_configs`, and add a `job_name` for overlaybd in `.../prometheus/prometheus.yml`. ```toml # my global config global: scrape_interval: 15s # Set the scrape interval to every 15 seconds. The default is every 1 minute. evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute. # scrape_timeout is set to the global default (10s). # Alertmanager configuration alerting: alertmanagers: - static_configs: - targets: # - alertmanager:9093 # Load rules once and periodically evaluate them according to the global 'evaluation_interval'. rule_files: # - "first_rules.yml" # - "second_rules.yml" # A scrape configuration containing exactly one endpoint to scrape: # Here it's Prometheus itself. scrape_configs: # The job name is added as a label `job=` to any timeseries scraped from this config. - job_name: "prometheus" # metrics_path defaults to '/metrics' # scheme defaults to 'http'. static_configs: - targets: ["localhost:9090"] - job_name: "overlaybd" metrics_path: "/metrics" static_configs: - targets: ["localhost:9863"] ``` In this configuration, you set a job named `overlaybd`, and set `metrics_path` equal to `uriPrefix` in the overlaybd configuration. Next, you can start a Prometheus service using this configuration to monitor overlaybd.accelerated-container-image-1.4.2/docs/QUICKSTART.md000066400000000000000000000222431514714641000217640ustar00rootroot00000000000000# Quickstart Guide This guide helps to config and run the common case of overlaybd image service. - [Quickstart Guide](#quickstart-guide) - [Install](#install) - [overlaybd-snapshotter](#overlaybd-snapshotter) - [Compile from source](#compile-from-source) - [Download release](#download-release) - [Config](#config) - [Start service](#start-service) - [overlaybd-tcmu](#overlaybd-tcmu) - [Compile from source](#compile-from-source-1) - [Download release](#download-release-1) - [Config](#config-1) - [Start service](#start-service-1) - [Configuration](#configuration) - [Containerd](#containerd) - [Authentication](#authentication) - [Run overlaybd images](#run-overlaybd-images) - [Image conversion](#image-conversion) - [Image build](#image-build) - [Install](#install-1) - [Run buildkitd](#run-buildkitd) - [P2P](#p2p) ## Install There are two components to be installed, overlaybd-snapshotter and overlaybd-tcmu. They are located in two separate repositries. - overlaybd-snapshotter is in this repositry - overlaybd-tcmu is in https://github.com/containerd/overlaybd. ### overlaybd-snapshotter Users can compile the latest code to install or download the [release](https://github.com/containerd/accelerated-container-image/releases). #### Compile from source Install dependencies: - golang 1.26+ Run the following commands to build: ```bash git clone https://github.com/containerd/accelerated-container-image.git cd accelerated-container-image make sudo make install ``` #### Download release After download, install the rpm/deb package. #### Config The config file is `/etc/overlaybd-snapshotter/config.json`. Please create the file if not exists. **We suggest the root path of snapshotter is a subpath of containerd's root** ```json { "root": "/var/lib/containerd/io.containerd.snapshotter.v1.overlaybd", "address": "/run/overlaybd-snapshotter/overlaybd.sock", "verbose": "info", "rwMode": "overlayfs", "logReportCaller": false, "autoRemoveDev": false, "exporterConfig": { "enable": false, "uriPrefix": "/metrics", "port": 9863 }, "mirrorRegistry": [ { "host": "localhost:5000", "insecure": true }, { "host": "registry-1.docker.io", "insecure": false } ] } ``` | Field | Description | | ----- | ----------- | | `root` | the root directory to store snapshots. **Suggestion: This path should be a subpath of containerd's root** | | `address` | the socket address used to connect withcontainerd. | | `verbose` | log level, `info` or `debug` | | `rwMode` | rootfs mode about wether to use native writable layer. See [Native Support for Writable](./WRITABLE.md) for detail. | | `logReportCaller` | enable/disable the calling method | | `autoRemoveDev` | enable/disable auto clean-up overlaybd device after container removed | | `exporterConfig.enable` | whether or not create a server to show Prometheus metrics | | `exporterConfig.uriPrefix` | URI prefix for export metrics, default `/metrics` | | `exporterConfig.port` | port for http server to show metrics, default `9863` | | `mirrorRegistry` | an arrary of mirror registries | | `mirrorRegistry.host` | host address, eg. `registry-1.docker.io`` | | `mirrorRegistry.insecure` | `true` or `false` | #### Start service Run the `/opt/overlaybd/snapshotter/overlaybd-snapshotter` binary or start it as a service by enable and start [overlaybd-snapshotter.service](https://github.com/containerd/accelerated-container-image/blob/main/script/overlaybd-snapshotter.service). If installed from source, please run the following to start service. ```bash sudo systemctl enable /opt/overlaybd/snapshotter/overlaybd-snapshotter.service sudo systemctl start overlaybd-snapshotter ``` ### overlaybd-tcmu Users can compile the latest code to install or download the [release](https://github.com/containerd/overlaybd/releases). There is no strong dependency between the overlaybd-snapshotter and overlaybd-tcmu versions. However, overlaybd-snapshotter v1.0.1+ requires overlaybd-tcmu v1.0.4+ because there have been adjustments made to the parameters for image conversion. #### Compile from source Install dependencies: - cmake 3.15+ - gcc/g++ 7+ - development dependencies: - CentOS 7/Fedora: `sudo yum install libaio-devel libcurl-devel openssl-devel libnl3-devel libzstd-static e2fsprogs-devel` - CentOS 8: `sudo yum install libaio-devel libcurl-devel openssl-devel libnl3-devel libzstd-devel e2fsprogs-devel` - Debian/Ubuntu: `sudo apt install libcurl4-openssl-dev libssl-dev libaio-dev libnl-3-dev libnl-genl-3-dev libgflags-dev libzstd-dev libext2fs-dev` Run the following commands to build: ```bash git clone https://github.com/containerd/overlaybd.git cd overlaybd git submodule update --init mkdir build cd build cmake -DCMAKE_BUILD_TYPE=Release .. make -j sudo make install ``` #### Download release After download, install the rpm/deb package. #### Config The config file is `/etc/overlaybd/overlaybd.json`. The default if installed automatically and can be used directly without change. For more details please refer to [configuration](https://github.com/containerd/overlaybd/blob/main/README.md#configuration). #### Start service ```bash sudo systemctl enable /opt/overlaybd/overlaybd-tcmu.service sudo systemctl start overlaybd-tcmu ``` ## Configuration ### Containerd Containerd 1.4+ is required. Add snapshotter config to containerd config file (default `/etc/containerd/config.toml`). ```toml [proxy_plugins.overlaybd] type = "snapshot" address = "/run/overlaybd-snapshotter/overlaybd.sock" ``` If k8s/cri is used, add the following config. ```toml [plugins.cri] [plugins.cri.containerd] snapshotter = "overlaybd" disable_snapshot_annotations = false ``` Make sure `cri` is not listed in `disabled_plugins` in containerd config file. At last do not forget to restart containerd. ### Authentication Since Authentication cannot share between containerd and overlaybd-tcmu, authentication has to be configured to overlaybd-tcmu individually. The auth config file path for ovelaybd-tcmu can be specified in `/etc/overlaybd/overlaybd.json`. (default `/opt/overlaybd/cred.json`). This is an example, and the format is the same as docker auth file (`/root/.docker/config.json`) ```json { "auths": { "hub.docker.com": { "username": "username", "password": "password" }, "hub.docker.com/hello/world": { "auth": "dXNlcm5hbWU6cGFzc3dvcmQK" } } } ``` ## Run overlaybd images Now users can run overlaybd images. There are several methods. - use nerdctl ```bash sudo nerdctl run --net host -it --rm --snapshotter=overlaybd registry.hub.docker.com/overlaybd/redis:6.2.1_obd ``` - use rpull ```bash # use rpull to pull image without layer downloading sudo /opt/overlaybd/snapshotter/ctr rpull -u {user}:{pass} registry.hub.docker.com/overlaybd/redis:6.2.1_obd # run by ctr run sudo ctr run --net-host --snapshotter=overlaybd --rm -t registry.hub.docker.com/overlaybd/redis:6.2.1_obd demo ``` - use k8s/cri Run with k8s or crictl, refer to [EXAMPLES_CRI](https://github.com/containerd/accelerated-container-image/blob/main/docs/EXAMPLES_CRI.md). ## Image conversion There are 2 ways to convert images from oci format to overlaybd format, by using embedded image-convertor or using standalone userspace image-convertor respectively. - use embedded image-convertor ```bash # pull the source image (nerdctl or ctr) sudo nerdctl pull registry.hub.docker.com/library/redis:6.2.1 # convert sudo /opt/overlaybd/snapshotter/ctr obdconv registry.hub.docker.com/library/redis:6.2.1 registry.hub.docker.com/overlaybd/redis:6.2.1_obd_new # push the overlaybd image to registry, then the new converted image can be used as a remote image sudo nerdctl push registry.hub.docker.com/overlaybd/redis:6.2.1_obd_new # remove the local overlaybd image sudo nerdctl rmi registry.hub.docker.com/overlaybd/redis:6.2.1_obd_new ``` - use standalone userspace image-convertor ```bash # userspace-image-convertor will automatically pull and push images from and to the registry sudo /opt/overlaybd/snapshotter/convertor -r registry.hub.docker.com/library/redis -i 6.2.1 -o 6.2.1_obd_new ``` ## Image build Overlaybd images can be efficiently built from overlaybd images by using the [customized buildkit](https://github.com/data-accelerator/buildkit). ### Install ```bash https://github.com/data-accelerator/buildkit.git # 202210 is the latest branch git checkout 202210 make sudo make install ``` ### Run buildkitd First, make sure the overlaybd-snapshotter and overlaybd-tcmu running. ```bash # use containerd worker with overlaybd snapshotter buildkitd --containerd-worker-snapshotter=overlaybd --oci-worker=false --containerd-worker=true ``` Then, write your Dockerfile and run buildctl to build images. The `FROM` if Dockerfile must be an overlaybd image. ```bash buildctl build \ --frontend dockerfile.v0 \ --local context=. \ --local dockerfile=. \ --output type=image,name={new image},push=true,oci-mediatypes=true,compression=uncompressed ``` `oci-mediatypes=true` and `compression=uncompressed` are required. ## P2P ... accelerated-container-image-1.4.2/docs/TURBO_OCI.md000066400000000000000000000112001514714641000216460ustar00rootroot00000000000000# Overlaybd - TurboOCIv1 __Overlaybd - TurboOCIv1__ (_TurboOCI_ for short) image, which means you can use OCIv1 images fastly. If you convert OCI image to overlaybd image to use remotely, you need to provide additional time to convert and space (may more than double) to store and manage the overlaybd image. But in TurboOCI, only you need is build a meta image for OCI image which contains only a small amount of metadata. ## Feature ### No image conversion This is problematic for container developers who don't want to manage the cost and complexity of keeping copies of images in two formats. It also creates problems for image signing, since the conversion step invalidates any signatures that were created against the original OCI image. TurboOCI addresses these issues by loading from the original, unmodified OCI image. Instead of converting the image, it builds a separate image with a small amount of data of the ext4 filesystem (which is the "meta image"), which lives in the remote registry and usually has 3% size of the original image. ### Good stability, reliability and performance At present, the products similar to TurboOCI on the market are mainly AWS's FUSE-based solution SOCI. As a user mode file system, FUSE will frequently switch between user mode and kernel mode, and cannot be recovered after restart. TurboOCI uses block devices the same as overlaybd whose performance and stability performance is far superior to FUSE. Relying on Alibaba Cloud's DADI acceleration link, you can obtain performance close to __Overlaybd Native__ images with less space. ### Compatible with overlaybd TurboOCI is compatible with overlaybd format. Even for different layers of the same image, you can use overlaybd and TurboOCI at the same time without conflicts. You can use TurboOCI in much the same way as you used overlaybd. ## Usage ### Configure The format of OCI image's data may be tar or gzip (depending on `mediatype` of the OCI image layer). For the gzip format, we provide a cache of the decompressed data, you can modify the gzip cache options in `/etc/overlaybd/config.json`. ```json { // ... "gzipCacheConfig": { "enable": true, "cacheSizeGB": 4, "cacheDir": "/opt/overlaybd/gzip_cache" }, // ... } ``` After this setting, when reading the gzip data of the OCI image, the data will be decompressed into `/opt/overlaybd/gzip_cache`, and the size of the cache pool is 4GB. ### Build Before building the TurboOCI image, you should make sure you have permission to pull and push images to the repo. ```bash # build Overlaybd-turboOCIv1 image bin/convertor -r -i --turboOCI # build Overlaybd-native image bin/convertor -r -i --overlaybd # build both turboOCI and overlaybd in one task bin/convertor -r -i --turboOCI --overlaybd ``` ### Pull and run It is the same with the overlaybd image, see [QUICKSTART](./QUICKSTART). ## Performance We used a series of images with different characteristics to test the performance of turboOCI, overlaybd, and OCI images. "Image Size" of the turboOCI image refers to the size of the meta image in the registry. The value in brackets is the decompressed size of the gzip data of the OCI image. "Service Available Time" is the time from image pull to service available. For OCI images, this includes the time to download all data, for turboOCI and overlaybd, "pull" means executing `rpull`. **ai-cat-or-dog** An AI model consumes a lot of resources. | **Image Format** | **Image Size** | **Service Available Time** | | :----: | :----: | :----: | | Overlaybd - TurboOCIv1 | 29.1 MB | 14.65s | | Overlaybd - Native| 1.09 GB | 11.72s | | OCIv1 gzip | 730.2 MB (1.81 GB) | 30.77s | **php-laravel-nginx** An nginx server similar to the user's actual behavior. | **Image Format** | **Image Size** | **Service Available Time** | | :----: | :----: | :----: | | Overlaybd - TurboOCIv1 | 11.35 MB | 4.15s | | Overlaybd - Native | 326.3 MB | 3.74s | | OCI gzip | 193.3 MB (567 MB) | 9.47s | **python-small-import** A Flask app with a small number of import libraries. | **Image Format** | **Image Size** | **Service Available Time** | | :----: | :----: | :----: | | Overlaybd - TurboOCIv1 | 30.3 MB | 4.61s | | Overlaybd - Native | 1.31 GB | 3.6s | | OCI gzip | 728.1 MB (2.51 GB) | 30.38s | **python-large-import** A Flask app with a large number of import libraries. | **Image Format** | **Image Size** | **Service Available Time** | | :----: | :----: | :----: | | Overlaybd - TurboOCIv1 | 30.3 MB | 9.09s | | Overlaybd - Native | 1.31 GB | 7.77s | | OCI gzip | 728.1 MB (2.51 GB) | 31.2s | accelerated-container-image-1.4.2/docs/USERSPACE_CONVERTOR.md000066400000000000000000000242551514714641000232720ustar00rootroot00000000000000# Standalone Userspace Image Convertor Standalone userspace image-convertor is a tool to convert OCIv1 images into overlaybd format in userspace, without the dependences of containerd and tcmu. Only several ovelraybd tools binary are required. This convertor is stored in `bin` after `make`. This is an experimental feature and will be continuously improved. ## Requirement There's no need to install containerd, no need to launch and mount tcmu devices, no need to run as root. Only several tools are required: - overlaybd-create, overlaybd-commit and overlaybd-apply Three overlaybd tools provided in [overlaybd](https://github.com/containerd/overlaybd), stored at `/opt/overlaybd/bin`. - baselayer stored at `/opt/overlaybd/baselayers/ext4_64` after installing [overlaybd](https://github.com/containerd/overlaybd). This is required if flag `--mkfs` is false. Overall, the requirements are `/opt/overlaybd/bin/{overlaybd-create,overlaybd-commit,overlaybd-apply}` and `/opt/overlaybd/baselayers/ext4_64`(optional). ## Basic Usage ```bash # usage $ bin/convertor --help overlaybd convertor is a standalone userspace image conversion tool that helps converting oci images to overlaybd images Usage: convertor [flags] Flags: -r, --repository string repository for converting image (required) -u, --username string user[:password] Registry user and password --plain connections using plain HTTP --verbose show debug log -i, --input-tag string tag for image converting from (required when input-digest is not set) -g, --input-digest string digest for image converting from (required when input-tag is not set) -o, --output-tag string tag for image converting to -d, --dir string directory used for temporary data (default "tmp_conv") --oci export image with oci spec --fstype string filesystem type of converted image. (default "ext4") --mkfs make ext4 fs in bottom layer (default true) --vsize int virtual block device size (GB) (default 64) --fastoci string build 'Overlaybd-Turbo OCIv1' format (old name of turboOCIv1. deprecated) --turboOCI string build 'Overlaybd-Turbo OCIv1' format --overlaybd string build overlaybd format --db-str string db str for overlaybd conversion --db-type string type of db to use for conversion deduplication. Available: mysql. Default none --concurrency-limit int the number of manifests that can be built at the same time, used for multi-arch images, 0 means no limit (default 4) --disable-sparse disable sparse file for overlaybd --referrer push converted manifests with subject, note '--oci' will be enabled automatically if '--referrer' is set, cause the referrer must be in OCI format. --cert-dir stringArray In these directories, root CA should be named as *.crt and client cert should be named as *.cert, *.key --root-ca stringArray root CA certificates --client-cert stringArray client cert certificates, should form in ${cert-file}:${key-file} --insecure don't verify the server's certificate chain and host name --reserve reserve tmp data --no-upload don't upload layer and manifest --dump-manifest dump manifest -h, --help help for convertor # examples $ bin/convertor -r docker.io/overlaybd/redis -u user:pass -i 6.2.6 -o 6.2.6_obd $ bin/convertor -r docker.io/overlaybd/redis -u user:pass -g sha256:309f99718ff2424f4ae5ebf0e46f7f0ce03058bf47d9061d1d66e4af53b70ffc -o 309f99718ff2424f4ae5ebf0e46f7f0ce03058bf47d9061d1d66e4af53b70ffc_obd $ bin/convertor -r docker.io/overlaybd/redis -u user:pass -i 6.2.6 --overlaybd 6.2.6_obd --fastoci 6.2.6_foci $ bin/convertor -r docker.io/overlaybd/redis -u user:pass -i 6.2.6 -o 6.2.6_obd --vsize 256 ``` ### Referrers API support (Experimental) Referrers API provides the ability to reference artifacts to existing artifacts, it returns all artifacts that have a `subject` field of the given manifest digest. If your registry has supported this feature, you can enable `--referrer` so that the converted image will be referenced to the original image. See [Listing Referrers](https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#listing-referrers) and for more details. The artifact type for overlaybd and turboOCIv1 is `application/vnd.containerd.overlaybd.native.v1+json` and `application/vnd.containerd.overlaybd.turbo.v1+json` respectively. The format of the converted images is as follows, note that if the original image is an index (multi-arch image), all converted indexes and manifests will have a `subject` field. #### index.json ``` { "schemaVersion": 2, "mediaType": "application/vnd.oci.image.index.v1+json", "artifactType": "application/vnd.containerd.overlaybd.native.v1+json", "manifests": [ { "mediaType": "application/vnd.oci.image.manifest.v1+json", "digest": "sha256:b0a40a33547de0961b6e0064a298e55484a2636830ba8bf5d05e34fae88b1443", "size": 882, "platform": { "architecture": "386", "os": "linux" } }, ... ], "subject": { "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json", "digest": "sha256:5df8d0e068b9c8c95c330607dfd96db51ac0b670b3a974ab23449866c0aa70a1", "size": 1076 } } ``` #### manifest.json ``` { "schemaVersion": 2, "mediaType": "application/vnd.oci.image.manifest.v1+json", "artifactType": "application/vnd.containerd.overlaybd.native.v1+json", "config": { "mediaType": "application/vnd.oci.image.config.v1+json", "digest": "sha256:e006fc50e6cec5e81844abb28abd0a01f4ff599432818a3bb9dfb96ce3e5daae", "size": 571 }, "layers": [ { "mediaType": "application/vnd.oci.image.layer.v1.tar", "digest": "sha256:63e766ab33f12958a0d94676a0bf5f7b800e04e4fac2124d785a8a54a8108e45", "size": 3933696, "annotations": { "containerd.io/snapshot/overlaybd/blob-digest": "sha256:63e766ab33f12958a0d94676a0bf5f7b800e04e4fac2124d785a8a54a8108e45", "containerd.io/snapshot/overlaybd/blob-size": "3933696", "containerd.io/snapshot/overlaybd/version": "0.1.0" } } ], "subject": { "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "digest": "sha256:096958b089cdfa4b345dba0ae0a1e43bea59e4de6e084c26429b6c85096322cf", "size": 528 } } ``` ### Layer/Manifest Deduplication To avoid converting the same layer for every image conversion, a database is required to store the correspondence between OCIv1 image layer and overlaybd layer. We provide a default implementation based on mysql database, but others can be added through the ConversionDatabase abstraction. To use the default: First, create a database and the `overlaybd_layers` table, the table schema is as follows: ```sql CREATE TABLE `overlaybd_layers` ( `host` varchar(255) NOT NULL, `repo` varchar(255) NOT NULL, `chain_id` varchar(255) NOT NULL COMMENT 'chain-id of the normal image layer', `data_digest` varchar(255) NOT NULL COMMENT 'digest of overlaybd layer', `data_size` bigint(20) NOT NULL COMMENT 'size of overlaybd layer', PRIMARY KEY (`host`,`repo`,`chain_id`), KEY `index_registry_chainId` (`host`,`chain_id`) USING BTREE ) DEFAULT CHARSET=utf8; ``` If you also want caching for manifests to avoid reconverting the same manifest twice, you can create the `overlaybd_manifests` table, the table schema is as follows: ```sql CREATE TABLE `overlaybd_manifests` ( `host` varchar(255) NOT NULL, `repo` varchar(255) NOT NULL, `src_digest` varchar(255) NOT NULL COMMENT 'digest of the normal image manifest', `out_digest` varchar(255) NOT NULL COMMENT 'digest of overlaybd manifest', `data_size` bigint(20) NOT NULL COMMENT 'size of overlaybd manifest', `mediatype` varchar(255) NOT NULL COMMENT 'mediatype of the converted image manifest', PRIMARY KEY (`host`,`repo`,`src_digest`, `mediatype`), KEY `index_registry_src_digest` (`host`,`src_digest`) USING BTREE ) DEFAULT CHARSET=utf8; ``` with this database you can then provide the following flags: ```bash Flags: --db-str db str for overlaybd conversion --db-type type of db to use for conversion deduplication. Available: mysql. Default none # example $ bin/convertor -r docker.io/overlaybd/redis -u user:pass -i 6.2.6 -o 6.2.6_obd --db-str "dbuser:dbpass@tcp(127.0.0.1:3306)/dedup" --db-type mysql ``` * Note that we have also provided some tools to create such a database and examples of usage as well as a dockerfile that could be used to setup a simple converter with caching capabilities, see [samples](../cmd/convertor/resources/samples). ## libext2fs Standalone userspace image-convertor is developed based on [libext2fs](https://github.com/tytso/e2fsprogs), and we have provided a [customized libext2fs](https://github.com/data-accelerator/e2fsprogs) to make the conversion faster. We used `standalone userspace image-convertor (with custom libext2fs)`, `standalone userspace image-convertor (with origin libext2fs)` and `embedded image-convertor` to convert some images and did a comparison for reference. ### Performance | Image | Image Size | with custom libext2fs | with origin libext2fs | embedded image-convertor | |:-------------------:|:----------:|:---------------------:|:---------------------:|:------------------------:| | jupyter-notebook | 4.84 GB | 93 s | 238 s | 101 s | | php-laravel-nginx | 567 MB | 13 s | 20 s | 15 s | | ai-cat-or-dog | 1.81 GB | 27 s | 54 s | 60 s | | cypress-chrome | 2.73 GB | 70 s | 212 s | 87 s | ### Use with origin libext2fs Standalone userspace image-convertor uses customized libext2fs by default. If you want to use original libext2fs instead of it, see the cmake cache entry `-D ORIGIN_EXT2FS=1` of [overlaybd](https://github.com/containerd/overlaybd#build). accelerated-container-image-1.4.2/docs/WRITABLE.md000066400000000000000000000043361514714641000215060ustar00rootroot00000000000000# Writable layer Overlaybd writable/container layer can be used for image build/conversion, and container runtime. There are two kinds of writable layer, `append-only` based and `sparse-file` based: * **append-only** It is a writable layer in the form of log-structure that data is appended to the end of the data file with index update. It has high performance but the size will increase without gc (not implemented yet). It is suitable for image conversion and image building. * **sparse-file** Is is a writable layer based on sparse file. It is suitable for container runtime. *This feature is incomming.* There are two kinds of mount mode, `dir` mod and `dev` mod: * **dir** It returns a bind mount when calling snapshotter `Mounts`, the image with writable layer will be mount to a dir before `Mounts` return. This dir can be used as rootfs directly. * **dev** It returns a mount of a writable device with fs type parameter when calling snapshotter `Mounts`. It is up to the caller to decide when and where to mount the device. This device can be passed to a secure container or a vm runtime through virtio-blk. ## Usage * for containerd/ctr 1.6+ Just pass the parameter through `--snapshotter-label`, by setting `dev` or `dir` to `containerd.io/snapshot/overlaybd.writable`. [#5660](https://github.com/containerd/containerd/pull/5660) ```bash ctr run --net-host --snapshotter=overlaybd --rm -t --snapshotter-label containerd.io/snapshot/overlaybd.writable=dev registry.hub.docker.com/overlaybd/redis:6.2.1_obd test_rw ``` * for containerd api ```go snOpts := snapshots.WithLabels(map[string]string{"containerd.io/snapshot/overlaybd.writable": "dev"}) redis, err := client.NewContainer(context, "redis-master", containerd.WithSnapshotter("overlaybd"), containerd.WithNewSnapshot("redis-rootfs", image, snOpts), containerd.WithNewSpec(oci.WithImageConfig(image)), ) ``` * for proxy snapshotter plugin config ```json { "root": "/var/lib/containerd/io.containerd.snapshotter.v1.overlaybd", "address": "/run/overlaybd-snapshotter/overlaybd.sock", "rwMode": "dev", // overlayfs, dir or dev "writableLayerType": "sparse" // append or sparse } ```accelerated-container-image-1.4.2/docs/images/000077500000000000000000000000001514714641000212125ustar00rootroot00000000000000accelerated-container-image-1.4.2/docs/images/image-flow.jpg000066400000000000000000010432321514714641000237500ustar00rootroot00000000000000JFIFtExifMM*>F(iN 28Photoshop 3.08BIM8BIM%ُ B~ICC_PROFILEapplmntrRGB XYZ ,/acspAPPLAPPL-appldesc\bdscmcprt\#wtptrXYZgXYZbXYZrTRC aarg vcgt0ndin,>chadl,mmod(vcgp8bTRC gTRC aabg aagg descDisplaymluc& hrHRkoKR nbNOid huHUcsCZ0daDKFnlNLbfiFIxitITesESroROfrCAarukUAheILzhTW $viVN.skSK;L>@>289 LCD LCD _irLCDLCD MuFarebn LCD&25B=>9 -48A?;59Colour LCDLCD couleurWarna LCD 0   @ ( LCDLCD *5LCD en colorFarb-LCDColor LCDLCD ColoridoKolor LCD  LCDFrg-LCDRenkli LCDLCD a Cores000LCDtextCopyright Apple Inc., 2021XYZ XYZ =dXYZ L$ XYZ 'ȋcurv #(-26;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Kmparaff Y [vcgtndin6RC& @PT@333333sf32 rrqmmod>dvcgpffffff3343343342 " }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzC  C  ?( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h(?( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h(?( ( JZ(_P7Í7B4+]MRa<10 +l+9> jK/?6?J]?w%G>?|]@? &/)+t^uڻ~ N2\w%G>h?W_{jK/:?+s^mڻ~ M2O|]@? 'Q/(?W_{ɴjK/?6?J]?w%G>?|]@? &/)+t^uڻ~ N2\w%G>h?W_{jK/:?+s^mڻ~ M2O|]@? 'Q/(?W_{ɴjK/?6?J]?w%G>?|]@? &/)+t^uڻ~ N2\w%G>h?W_{jK/:?+s^mڻ~ M2O|]@? 'Q/(?W_{ɴjK/?6?J]?w%G>?|]@? &/)+t^uڻ~ N2\w%G>h?W_{jK/:?+s^mڻ~ M2O|]@? 'Q/(?W_{ɴjK/?6?J]?w%G>?|]@? &/)+t^uڻ~ N2\w%G>h?W_{jK/:?+s^mڻ~ M2O|]@? 'Q/(?W_{ɴjK/?6?J]?w%G>w5o~Dג?JnY!Ym"A3 (,H=kԨ ( JZ(( ( JZ(( ( JZ((ۗ Omu?u3F̷`Y_sGu|+/|]@? 'PE.ڻ~ M?W_yɴFRW/?w%@ejK/6|]@? &I_˧>^uQW/?w%@e%.ڻ~ N?W_{FQ_˟>h^mQjK/:|]@? 'PE.ڻ~ M?W_yɴFRW/?w%@ejK/6|]@? &I_˧>^uQW/?w%@e%.ڻ~ N?W_{FQ_˟>h^mQjK/:|]@? 'PE.ڻ~ M?W_yɴFRW/?w%@ejK/6|]@? &I_˧>^uQW/?w%@e%.ڻ~ N?W_{FQ_˟>h^mQjK/:|]@? 'PE.ڻ~ M?W_yɴFRW/?w%@ejK/6|]@? &I_˧>^uQW/?w%@e%.ڻ~ N?W_{FQ_˟>h^mQjK/:|]@? 'PE.ڻ~ M?W_yɴFRW/?w%@ejK/6|]@? &I_˧>^uQW/?w%@e%.ڻ~ N?W_{FQ_˟>h^mQjK/:|]@? 'PE.ڻ~ M?W_yɴFRW;Z_𖉮ުƣcms @B%v $ 2MttQERR@Q@%-QERR@Q@%-QERR@Q@%-Q_OK4x BoV[q7 sdFmdۍǦk/ۏ'!PU%~ 4_# m?~ ? m?z~ > m?~ > m?z-yg(߂O=O7G(߂O=O7^Eyg(߂O=O7G(߂O=O5^K@Y 7 7ש@Y 7 7WR'D)t)D)utP'D)t'D)UT~ > m?~ ? m?z~ > m?~ > m?z-yg(߂O=O7G(߂O=O7^Eyg(߂O=O7G(߂O=O5^K@Y 7 7ש@Y 7 7WR'D)t)D)utP'D)t'D)UT~ > m?~ ? m?z~ > m?~ > m?z-yg(߂O=O7G(߂O=O7^Eyg(߂O=O7G(߂O=O5^K@Y 7 7ש@Y 7 7WR'D)t)D)utP'D)t'D)UT~ > m?~ ? m?z~ > m?~ > m?z-yg(߂O=O7G(߂O=O7^Eyg(߂O=O7G(߂O=O5^K@Y 7 7ש@Y 7 7WR'D)t)D)utP'D)t'D)UT~ > m?~ ? m?z~ > m?~ > m?z-yg(߂O=O7_ΗC>wOXGE$V6[#.XAl2y-ذ[q@߲w<=+ NY7bg}@Q@QEQIK@Q@QEQIK@Q@QEQIK@WO[:|6ĘaVο_+7$ueǜy(߂O=O7G(߂O=O5^K@Y 7 7ש@Y 7 7WR'D)t)D)utP'D)t'D)UT~ > m?~ ? m?z~ > m?~ > m?z-yg(߂O=O7G(߂O=O7^Eyg(߂O=O7G(߂O=O5^K@Y 7 7ש@Y 7 7WR'D)t)D)utP'D)t'D)UT~ > m?~ ? m?z~ > m?~ > m?z-yg(߂O=O7G(߂O=O7^Eyg(߂O=O7G(߂O=O5^K@Y 7 7ש@Y 7 7WR'D)t)D)utP'D)t'D)UT~ > m?~ ? m?z~ > m?~ > m?z-yg(߂O=O7G(߂O=O7^Eyg(߂O=O7G(߂O=O5^K@Y 7 7ש@Y 7 7WR'D)t)D)utP'D)t'D)UT~ > m?~ ? m?z~ > m?~ > m?z-yg(߂O=O7G(߂O=O7^Eyg(߂O=O7G(߂O=O5^K@Y 7 7ש@Y 7 7WR'D)t)D)utP'D)t'D)UT~ > m?~ ? m?z~ > m?~ > m?z-yg(߂O=O7G(߂O=O7^Eyg(߂O=O7_w>\4 V5'evog-0_'N]9ri$¿ ]q? ?W6?!+JZ(((JZ(((JZ(((Gu(ϋ4]R/>Po b?L#ɔña&1UҖ> b_Lctña&1U֊+ 0&Q]W+JZ+ 1%]W+Z(v/3D?G;c u_L)hv/5D?G;c u_Lhؿke_g52g<3_j/QG QvcHu$2H?j43mŰ{i-Lq;]PH ]Wb_LcɕW;a u_L 1&WޔW;c u_K 1&W޴P__g52v/3D?_zR__k5.v/5D?_z=BIu;,yV qFݏTI<@ ña&U?ؿke{'6/E9bX<'?9@7]Wb_Lcɕlx|T>7чZO(k&<+`>\g9pؿ Pña&U?ؿke{Z~ƣki? b?L#ɔña&1UҖ> b_Lctña&1U֊+ 0&Q]W+JZ+ 1%]W+Z(v/3D?G;c u_L)hv/5D?G;c u_Lhؿke_g52ؿk]_k52> b?L#ɔña&1UҖ> b_Lctña&1U֊+ 0&Q]W+JZ+ 1%|*_ğCE[ڬNYIbyc`q_$?T>-MoQIK@Q@QEQIK@( ( JZ(t_(mF|>{IH_)XmF|>{IH))h(())h(())h(())h((:~jڥVv :Ɗ=و.RV>?xMMg΋qg:\BuFYIVhA/LD*l4a9 {1 2zi)h(())h(())h(())h(())h(())h(-ذ[q_4?[?/a?wd5&x{MW&o  JZ(( ( JZ(( ( JZ(( (?+7$ueǝs__ɯO[:j(())h(())h(())h(())h(())h(())h(())h(())h(())h(())h,GػsQ_ʿ'N]9qi$Ÿ ]q_ ?xW6?!+(())k ?Rj?'gf4:-qvL):P|fM(^ nm'L'FYBAW:owRz/YDلWIv?Sh|O@XYykw{ pC,i`TC30k#º?ڑ@SJa $+7fBvJt;xVQ,Eݛ8Y ,Dz FG5L:(ɤxwe'nmHUGݑBڣ xcǍ|B<[Y\ %-Ư\ȱC.cskl3)E~RS")Iq$rrdGRPEPIKEQEPCbK|AcF+ޯƍEW@%-QERR@Q@%-QERR@Q@q:w/oXxI2,emJV@\ 0k>&π<4Cd_ƀ?/V_!X9K/ ڌl1h+HI@ʂx g~Q7XS>.ݵ $6"FjxMC/8+kSӬck(rK+U⿶m\K+|i}7@ټ7 Hm!TvX#$>0?߳Ϗ j><[Kif62KR;F!wpOD/V;_=l\ܮ}_`?ڿ_z]奇ַ2UI$i5ZFUiH\wo g \'vfw}>$O+MPB[Eg^$kOoYD- l,|C}KLVȆ5Wrɞ3  O_xņjm{dW)'4#`wt/J?|wu?ٻC#J6Ez~o #3oQWdoW |`G+h$wvSzI\$IN? fmVp`\Y7i`Xd ¹2r>';ǿ~xWJV;?`ۮ/,^V.v{[yU t)W!q I__O4 U?| S}I^A~ѿ fo?)fik ʷ#scz(??Vk15'}5K#HӘ%EF*J\Nz%>9G}Pjڔ  p[ v$K#: *Z& Y?yZYAz It4D%Ovpf g|7࿊Зþ%m.ͽͣȰ)i&Iu  XjvWnoݥo ʂH@Uс 85jS7_aEuD[E#h ڣ!*eO|K⏊:P|-H1g)ePf6#Hbb CS#Y-kM8 HY}AO\sm6"ΥwxZ@B/tz4|¶6 Okq}^nIykҟt/VQ5KKx1Gw䟓W(pĭ/WŸI6O iWꅇ$8=]P=M W7:wrw}3H~gY[$ՙ@k?i|&OG-usVԤ7 FQ3\itkgφ|o($ikq],`?ߘ~5[ǀ> P3WEд<ɤ3pEHE&=!jOj>ii+S4ҴqĈo B`xk[6> 4;F2 Ы,QQ㖝I|J~ZH$G,lݓʍ" (_3O^qjRŧ'jC"\ m%-QERR@Q@%-QERR@Q@$?T>-MoWCLro@Q@%-QERR@Q@ ( ))h(th_(mF?|>{IHh_)XmF|>{IH(((JZ(((JZ(( O/?:_߇ˆ0rh@Lv8_ :;Ps]sxoilݕLc5{O;%݇YwxH 9 {JZݯ  91Em@W-??W kFhO#licGifE4rh t'_'7Ɵ|l>8%ψJխ[svd SZ A\; awetSj?אAyy H=cGmX= ]]5[X!лJ}ExƯڃGt_E徕 [nV_ /̿?AvROK-[vye(Q^8. xx^<C]APOn H0H?_Ûï [UP2nWcbv}(g+…/ouuK]JammbvnIbeya<@ȧ< ZxWǿ-H H7 s68{sQȾ4Fp-%aufQtє"= /#εj_RNn/glmܚvVۛì2c_"~zf_ge_Ou߲%τz-i:XI%6sq$Ѭ2IE_kr8h|q>ڗG !7Z\\ȗ9I#&+vL)-oz ~)[oyZجlCx| /oũk![ ,)yr\\@ҀUB~|N__>ߍK@b2M|QչWGVGSс^3C?C%apNyǁ{࿉j%bf&(2eXGu|K:|J.g?.CF&o pNR3*)w!UA$?7\3>x[㇌ 2I[nn#7P,\\K#o/`x]O/x{~h`y-5Ǹi&(4@G)52|}!^'GW+_|/gxJk|ƿq7 nX?{ďف |'_3Z8=cN ~?>%M΋\g/j?:6Ztq4X@?gox[ߋ.kMVk)}gy<.GsA\GOi >xcޓgLb cr±>'ԞkZE޿+426EbAy$rTP*g xN7IUI|?yЅOٸ*!^zia|-bJ$BS* )pbS$8ci`3$z_^3~:7tEC1[PfO%'hD(qyӊ+3O:Iӄ7V8^vEhrwmO(?dK|5׈q-8I!In.N7$jvPiYK]l+q{6#gyjJ%(R;N23"ї?kO qu4?UXaibsq &ıf?g~ H[sm$4o37$l=RREPE%-QERREPE%-QERR_KY_-z-ذ[q@߲w<=+ NY7bg}@Q@QEQIK@Q@QEQIK@Q@QEQIK@WO[:Wif?$/ TRREPE%-QERREPE%-QERR?OMC⇃w|UGËi74mAt|3.k1"zW 3HVgoQ_SωcUL8&T᤟:xBWU0@ 6S/~)x> .P&lcts(pT)gP?ƞc Cƞ3Eռb@v' |5*O\?商z~4QFNGTmi^\Gqi[ c#HѱEBXIkUf_z4Zޗw\i Vw2ET21 G*Lmٷ>vdZN1,dts88e_ejԴM6A2n4n̡ 4W'eu NĞ#b:v \l$Bا]{( Xd}{^7Ե OB{;l.ߗ9$I#Ky%G8.;C.?k߄g$Ou'&n23]gsn DŁZ+⿋"B[FJAa2Br 9"4oxT4U`mM;1@W?- AIi+X\!l|˓&7p;E}q/gaZxdXo,Yv8 eXGb(+'[pТ׾.n [`{˦^xH2+~ͱ؍Kž0ӴH>l Q;;"P3@W+xWGxRWеvK{H9 +WU@4Q|KuMǏ5X]^Rn^kqf`,<@yA?S?9n<%k./ih>87s0k{;Pge|h~-ơ^E_M$6/ʆuW0Iݜ`i3uxŸ7O7, $Bs^1 6ǶKk[x׬eJO+B[# ~QE|% 7/ZτG\VLVN[m;8]GmY]gX[-7~cY4($Hƾ(L4z$1Zn9?.Mŀx iv+c[~ 0ɔ.L`s濗'g~5Idx=|{\| S$i?k{ĂyS'"]~} _xBOWѼu]o^ك)QxŶ_JGkBXgkbP+J?:/& ,k]@[G^ʈ6ݕ@-@~ͺ7[MPV4{)B6i ">Pe7T=OxᵖIߴӪ k?j<_˜[^j,7v2Mȅ\d+8#JPE%-QERREPE%-QERREP_ʿ'N]9c 'z.9|4q_脮ڸN|+`PE%-QERR_wR!پmjƹDDI¿W|]Ÿ+f>Rj>^ɛѪ란,]z>)yk;_*6>dv_+m_|[sեǽs9N9QwaK2@>6g?~"ٰDҧٯບ!mv ?NhhzV`{*,΃&u2ۂޭC>}5gkoMl%M\&=|W?g67na,-bXB&yǏpO{|#O+«ТCtﶄ9Q{si=W9?e\|%l73\_X#QU\|PE;ޱ=_fRhXX"|W?MiEs5r!gk~.sx2kkWAIWnE-,=WE~] l9I_QIK@Q@QE؟ѢJ!?% DvoPEPE%-QERREPE%-QERR_Yκ[Y]xWC*giO';[-?X}4{g@r\>< }ր>P߆^#?$wW[*B68u~ݿK jkZ۝CNԤZdo$qF)e}@"1y#5_@K]}usW~6:־)е;Ik#} ?KcIq}8m`X5 _߷/텡_ 4#o7Vvi rt8"ip]7S\7蟆/ xD^z+heXgdhL o◇8|FU}?IfoeEd/Z'&^=ń!GGՓ=h?&Dq??#_3S^R$][/1vx~$4Okm\\#KWt.559~.8%fϵ.!;DE%#v ? gIe?]q}^A/!p=ʯ潇~/>϶sIN6LjRph#BHF[p~ jx|GZ6ms^s$r2(_J^cмC4SkpKY.%LUq*W*|)χ`+st NHid<a$ 濘85??W'mkڼcɎw̰ح s$A?WSRcKO]-2Lc}{":Ma>Z.-aNQ"!}4O~9i/ e6$plbx#H!dh`H`Cal|x|I/x/xqlM- ,_b%ݤr 1 PO#;>Lд8fpv!Q,ɪ )xCÍ⯃屺K^A+oI#k)HOOzW9xH9WDO~9$/rO"=f)ޒp_·kGOgο/cn."D¿oۻ㎅!7WBG[zM"BȊʌpK8'j6[9|&Ker`Dh_49BU WhyX|?Jk=&jf>|>/>U{#!ure>1 g>;o.}&ycI8uCn#(IDf(t%MK7jĶv>o2@q)k<%Ui+7.ߍ$_?kq᫘+=Dj`)*ѬgQ_<}[5KO}BiquinL̩H8D&~!:5mpr4m=L8{d音m_ubt!jI[k#i~>JZk}vYi-ğhs|]^G|=_yqk#ե*}A %YW 0pHwjƿ{&{+LEIlP?>!b=5F= W Os:_?l~e dgKK^8kGVߊ<'RG@8vԔ?ډ  S0񥤛[ֵ B9Eʬ;x7'*1<ZӜ+W! z+Bx{_ei<;MϷ1an6'Ӱ cj?*W?Z_DcqqLǨ;\,բ42]~Rf#xğaw i-k;_/ݍmÇ8˟&5~%C*~0ʼΫispOTFC|<'e1i}~0q  gqWm匽tq4 ƱXW[oE~1_[Kkm>s#;%EyABx[o-$:,J;XdeX8=w ῴk&,Mƅ]KjǏʵjwA׹Ww#~ZͲ*gij&c(C^m46&fcUG1?9 axmz"O ۫'#$1] |{8({_ >㟈?o"4{}јpe>r`I=EGjSߨhZ5x.k *c\KFiXP%.jZƚJEg}c޿o^ ?o|?.icwsNbh{ .KIa|Gnޭ&1#\(#sLa:eIN0~+kY2tHGO%鮖?$lLd)Vc|Wof}ֱNeܽ>XԳ(|GA-ʷ,OҥE±@d7hE#+ gÛM u B54N\݂e/Z;^#NiTp-3Y2L|*Gf!8H5X?e5k{g@pMT>*}"s_/~ k =g4>TZ\DdƮs_#f>'~Ѻ/kiwHLL8[QV]?/!o=-,m؎ަS"g+Vŏ'ߌ~߱ׄ{IH_)XmF|>{IH))h(())h(())hx-`ET*ISWǟTGoA/}}6iFFH>Bs¿^~̟QZ&?|BҴxkq6ړJE̶mx鿲/E]U,.!G ٣7B7#!>aFBq_ο?-Z->vmeD:`802?گ_-/o{9~mB]@pB[)ϨfcTR]o_~>k!`FCBR]ޱfa-=Þ{C,> 4S _?MeRq@w? !WVGSb7)X!N: z#?hkZ4w5F٘|@; )aÏ'G/CxX`A=U1B%tOs@=FkO$֭?mi3,|Ϳ+ak$_|YJ-V#jUXQ 3)  7ῆvV$3/5Gd۟ƿE?~~/ >#WċJ@`DQC8 ? eyVĨ8oZ|Q#{>:f; $⟊Wpf-b>Skh>(M_ -ek#Ι3/uF8#GkC -m'c\AG֧$XUFc "C@/g"g{ݤ3/MbfC3#B  7!S~_44 ܋}2D^jI4@ ▥Ff/ukm|+W0Z;|"'ue_Vzax[A:; >Hf :* ռ9~׿`+#mcfu/-yhOo_D-TxXD ȷX`S_̧W[v\ż׳ƥ#W[xcˆ,Ul-$vq?o)-g=ѬtM/B{avKu n$d*3F;2y g{ >*_mvmMۘ_ʇ~1$|^vK]WabO@+s h3ۆaEi-by d6H>g(>#x)YlZiZ@8XLs~Ķkoã]6]~5ɖr"V123_o;bχb6p\4hƿo?'m3\k?':}N˹VH"cAFqЪSu[.]?Eӡ$NsR32A߲ž;1G#3KKs"AŠ g{?9oOSmOM4{BC'ռA` }`_?li~~-Es6g4-uq p,G bK)0|3>1~cC=ƙ Z-˗`'`_7^|/uZLguĆGː31W ~κ.\ajlZZ΅&y%,1"9`W W T8eoF%ZiC-쿇Ag.%kSϑD9a=^ b]W}1!Z{V( f85h?l";HJ@[E' '_ 5[Z]ksd|Q>y@˿s||2_w▏}ƓT0Z)K˧0Ew屑|a C<y]~r#eP_Z˾zUPrkk"F) <i- 91Fu@FO)?~xPhb墅"l.w *rcF"ͅmoj? e.$0_[*9oHI]'È,_ < t[Kb P{[' (X~4F~_4|l/G^dss2[fn-\pg\'^WtlA儦:ǵcߴ(> &X&д- |D迍>2xWf-׈i&+pwǞ"]ր?cĞ&?ax 'qmtqϠk^y7Z+;]7Ŷ.&i񆃤|)Oŕ#-5v:tgz,oUi#<=kֿ_ণw{7{kUIq\y<3R&?`-.m~t{LKQjJ]ٯ_#Cf;Ez?b9~N:59ã2:_×w(6V_oB]> dNVAAL031_/GM^Z3[Y8+y}[ve[ "߳ σ~]-'ފfpz;WQhk4Ň|5IiXAFun1,aRo xEt]R"e(Ou䃀A IO,xs#M:/Ekȑ::EYE~} g~$ZOx"WFn`|Iuq1;_[UA|N-帿E[e70 l!t432 ؁@>ڇUd!ZL;LrE QvRc.?h3ፔ>騒 9R q1R-DZF ~3|A վ%j2rGӷA<8Apzs@?_ /[\yiZ32f$Gw; +#O4O ܋m&D]jK<-ՔAG99?koZ_C Xk-|+G0[ 1.qղz_/ ?|E:wt#k 1Cwx|eATv[4++Xt.Pd1C>ޭK8+c ?쑤Z.5w@H!"C_7u~UHKhP[Ƭ=OWJ7tO1Zncmڧ`b~ ?㿋?iO~1nd[tNV[x;3HxONtǩ1G#3KKs"IUؠE~ֿ?/s{mF{Ye@18р8y~??l7o_<[-IR8I1o c!?!fzg?? gA[%rlcV$ k *'Hdtٯ5+i0v@Ƭ@$`pj>3ggh Wbt)3(d&Q.o.M'qJjMD.+PNT Ã_߳g//> _]Se1kkyp;7+ ? $j9'/5V?p\*}v(UʿBjmVgcſ^ecImVz'4PSpb]k־|sE'[O,Y'Pp^Pt] y4lW+o JV|++@XYȱ=3PT;$M`|cG[xj'I񲿇~]eZ~ґ.{oS@ς? >x6(СpY y\e (|%1/fxȇ"vqtJ=x픇#~~֥#Mޡ!1HCçgVsހ>J~϶(x|]b|246у!/GVf5K~[IlA rϴ COVi#ŖR<+:N2 & q$lsUno5 m<1%S/YٝH hٹp+xgᎻK×]xj{&Ibtbe*Ov[O.&I$~[Vrdv5qg)1P%7^#iIoqC\HBHOB=Nyoz|~\Pka-:",'Ͷ/Ff)_{tKWI Bu^{UDӕUT\ $@?,+D4?HҴX%繼U xkzwQlޭqtF DO&an>7~7 g啠4u20 =Ij_~>~>N\]Eޱ0?vOᮏxgOYW3ZBQ]]BscUO?kSþѬXt-/NKRtk04hHG>aRxR}Uo `!l6C_6xǑE%5xTQ(:g|5t85D෼Vʚ/Q2]A$vHܔ]29w> k7ƉYOͥpht2M8$PS$$~~_>iʦ}*^eB]KHLz^@'? |yO ko^o`g)g=r x}yW~ۿLJ>%jɢVmAf[%ݴm+2܀U-ಾsc{`nk4nKga OGPv~=8\ӗDI, &Q-.'#fwl⽂ ( JZ(( ( JZ(( Uwb_E*X;wM &D%v|4q_脮ڀ ( JZ((kEonA&A>*~?ih`jt5yajcmqy-vdc<i0|Y?Pբo/]2IepT(\? 4PEPIKEQEPCbK|AcF+ޯƍEW@%-QERR@Q@%-QERR@Q@@5/>TI`h-p3IJuEë6 !CO;^+onƌeߕJ(;S;/| ҵ uȦIf8X~"R2FHwg|c6Z_B&i&j>Fyۚ(?ߵBxZ[} N7z4چH"i +8୿~8|jo~i^!ڦdw)8aevȤ(,<܁~@_iM&EoVjNLwY 6ğ&\8S!6,D_R/.R_^*g]`Y9D>sD 38m HtPA |g:~*~: |3wsv}hc TjIPZ -jt_0xFo˘-gev}ȭ`6 ?_ _G]+XXOcܹ :l3_Qj/^-[8KVE*e j%,vW_NI|-zv]^%{k6א0 vUo^7!aNGbicxKSZ.wƀLIWIPTu?\u<η LȀ6@Gli$c0Rm:[xR_i&;tTH!X"( WCE!R:?n|NM.u "g+afo(hnIcUkfߏ,-ĺnXiOD6/P'fOg4wo>]8~kr)VidfBB\/؊+;W$mŭf9uK.cYE!*'?_ sctυw麎w,pYO~? ~zr_~!\oP˸fkUQ(kŸi ~z7x!+{B}2A\%$fq"PA n(t;-sŠXI%TM!{E]~&a[쮟ebo2IPoirRGJ_ |CÓHm 2h{y瓘nwa?a'X.<1ڞFfԴ#^y=WOxXY5>ZF0/2GnΛu&+(CE3|E(xk~"kX5 ƦT@I!TI>~_|0׾ Z_OZ7kdW6 :oq1X&3qFbT H` 1EAso弶H%edtnC+ }>=hgm_LPא:]Tˆ;⿦:(??g|/~,\%kWb!qpӻ 8]|8P_wE/ ³9^񦰊%[[)$#>ޢ? jό{IHh_)XmF|>{IH(((JZ(((JZ(+~ݞ7tKԯn!ke^b7ě#!be|?}-_|<Gu߇ndZ,rm,P#brO'ǿ+τ5/k_5}kZm:էodd| K*RI` ۊ( ?h_Cq hWR޻ kfvWio2Gm_|0>H Yb^Y dFgl2p4g|?_gC.m/&[{ -o* |gBCoNZ>E5KxWx/ !r~nS9[hxO3+b`1@Ư6V_ <[o,7ZleRejOO%T8 ~ ~-~|(ƍxg:? h]\-Tҙ_Mn !$"nS1o]_^审hZՑͼ6$'6u%+C~U+㧋 -7X;g& VB2Fp@HYIMÚsxU2Ju>2HY@iU>Bo*Hwb+A(/R0\r퐨;q?[x ۭxPωS!;Np2+?usFꎝ׉4O XJkWnj|{𧌾x[OIpnI|6S8wScc![P>T/^̦+ (~QJ|r?KxOAԴ=N%(d蠱]߱¡yXj) eb ʱ8r{ kbxZ[X5¥[4TR\  0* s@⯉~ k% ~ym2- e)Xَ:+?fCk]h<[jĒ35HW   }iK¾φk-$ܶ~ocz!e{ tJH~M}yghq4FH-(}dBUY[Oţ+P<5{>,ܝͼ8fWݹdV9<|#T Qh0w3i#!+Oe+1ӭ5h/$@[fEo)m&0]%&~/H𥏆u[S[м54rI$+4eT8 c?tg#/~!|.̹u90D9*s^_D xE[Ze2\< K*#_Wz_k]=FKO;81˺%x~oǛhOi2/I^GERZIdvI _e-,_KoڗWѯx0\^&NY.DiѺE.Fo87p|mo\k^6,Dm]H#LE_~!k%xnQ$@w,hOcR}' AR|ӾWhV/ou9ὑ["OdP|‡(@+:Z o.ς^5Rk7H&+\#T+s5wW?O}Cүx'ş!ipiw(\ ;C#4D2A*?(e$&uJܻhʘqݫ5MZ^[+Nmkh! #)eB(g>Kgi/Bý2~cJD(1\`Fa袀 ))h( ( ))h( ( ))h g%,W_KX_-;Mct|'ɬ3n(JZ(((JZ(((JZ(( '?IYv_s_+4xOG_]y\P))h( ( ))h( ( gɚN7744kf-rO3b5d` |Wܴ\]M{y#M=ô;ܒrks~~#|(WOx>}B{Yw[yQ]Γbe{w1Pok BxSQ L*{4 :"gu䯣"EϤw5Rt?'mij~puE H5|Owk|Lg@=d5KE% ]גxQxU*ZѦch2o= E~~ÿ *|t׾|wǡ| E< -)uICmfYy*nQB [VoX9eK(<@?ş>9x-.f摞 rut#Ѳ wj)w[_t/uc,v0-ʠӢCA)'|r4? Ke7Y P#Gr)Wګ+Fr?٧G+_%Gh&S16zTn[Āeʁ;ýw>1<-X}B #wPH-k ?⧇u|$u#Ι5ٜ5 9hfe pVI, ]#şZJn|? Ϻr!P w cU2^NDѷ%eFG_WK@ ~߷o/ڿLޜn,<}k{[<p MK"Ry^ ï*W㯆?>/x+w {{ #H HYU[+=J[FRE*el>U|+-| ӾaM"&kn_8;%p$9 ӖO[jxğw7#O_奬ΕCITw`ϕ*gq<9σdtbY䱱طR)9]~;f;XS2HuV[7 |{meu.@=mowZwZUΕ@.,ڨe)*9DUc;W_ݣ{ Ŷ{Mg= E:t;aY% %pXCG<e/> gCmg[ eaB\[yrazᘩ=AɠO -OC/!ױi#OK~\ul }bᇉ|in4{Z̸{w62sۃO G?gt閷&74Vȭc W=m~}qt'u YK쬪 #.`ha޻`r?'x}n^Ү'5XP6 o}la,Bv{|7whּ-jEK ŌCQ@*>Lxv7:]F[Mwn"Rۋ fUu*A~xkF~ۥ%py{h+a'[oS:ubS2oQ, _C w 3# |hA5EgjWpvt\M!E *]ݏ` >¿.Yα{k| Yb ز`\xis_:Tڕ:{ s(?O}MpGnI5ڗDCn%|6$_#e//=fa&QEuC/+߇ y/)Il" GnAfi$*c@O<%_J_lct]+KKh(aP<$$h( ( ))h( ( ))h(_ 'z.S_tz8lBWm\OO'>MJ࡟P7>|4ȓF?mieoH.`b/ൿUTW? SI!|#J,ӟ`N]Y\ ΀e%Xrz))h( ( ))h( ( + GQ|Mxcſ Ʋ,f75E0K(*gT|8g~)}6p}ۧ$r|yJ(!?% DvoWCbK|AcF+ջxm'3$A x_Rf3W⪫s+Sѳ ]IwѭWMBJZ:Š(?/{PWKy&h2xI"#>A+}|pK=U1*Mz8 U++vXƭZR[%K͟'ٯkVZk71B'a\ZIu @KT:LWe/ NW{ c? ?׮W[ԓWS+ӏ@Wsr]_?|ik+O?> .y}?g}S/]_|ik(sr+> ][ٸ?ܿ_).>h?49CK_9_͟. g-w?v_?Yz_KW4Z?u|Afσ??uhl3@;Gn/C/   -Wg3g 6| ]7!wޗ?@Ror]_?|ik+O?> .y};K ~9CK_9G.>h?4Y g'͟sh7J)+|g~O'Bl5S~?ۦǿ>]/wsCCpIz?j_39_XTW'>)w fäuKkZ68R1, ~x+~]7ƞ['VMA)0 +咠ٯgUZ5¢>Z5掦(4aE%-Q\{IH_)XmF|>{IH))h(())h(())h(())h(())h((+>?~i,w2Z]DM圄`Jܤl p1P&myp~?xwRi0dKUs2SFU|#|W>;x?T.oPo%+ʎ& ؃_Pn@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@4?[?/a⿥h_ bm~~kL+;MgtPEPIKEQEPEPIKEQEPEPIKEQE%_Vo=o?IH˲;ʿ_+7$ueYǝs_(Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-W~_Xх6d9X`JnR 68j(AQ\|6?ck+ 2``yk-+__?7OŽF [p9`"Q  x_Þ <#[>5c^}O'ޢ))h(())h(; 9>%^Gގ$WR\[9&Mǖ$^g6>%X;'IGSB 6`@hL{Wg}VŚ?|WlHnQuqU!NGQ@%-QERR@Q@:C|7@.ZkK Ic ?2s_DO />%jpY,#;rt'n+? M,Z&hK-$ҰF#`e€EPEPIKEQEPEPIKEQEP_ʿ#N]9_ '|.84qO脮ڸN<+`PkN5GWw|=jwI&wXj(Qo,ͯZw%O )Ais[Gx[]lݕ6go'$(Lh*IW +sU}WiYh-n4{Yqth$jl-{KoS c}ZU延{x'"I濖o'fy~_ -lUemi ڇ4"~?C-3֡yt&'->^|wd )g  y,4iӧUk cǿ K_I]i0#1;]2Ck&rn 1nI5LJ?:;{_gm+[ ]$Ca\%%I4) F|+U1HcygClK+xnV`g |R%xo^4mZX?%RKdDO&x\ _,mĐ1_s)#[m.y5 ;^Iɭ&5}Ŏ2I?E Ŀxf,tV[dhD@B/g?hW~д= @I٭n弸hy T??H~mxMdFɤnV%,qO-oτ?PӅu?ി4 Okm'Ojq4FdKH<4%O¶qi\7o/Zv ;T zY࿂?/ B&w`쨃#$2kO > JCuo%쉜/jʢ t OY|qgX|Ja Y-øpX"+{c ׉n54kk]m-DW$"oگk?Eؽ{cqmib9Wy"]FYHm~?A?$?-gx_?4lA>5 GHxR>Ba`yÐN?A?$?ki)hƍEW_)-'z?ni po37BGਵ'ysGOM+~A&g,1g qs<*c{}'5G/F~!EYxP:nO@S 8nRT(:{N9#", 5o^k>inȈ2y| .p=jg gsٟˈC| kf!|1ͫ7Wd}2/|%K[-dV{oOJW+? _[VԥZJ(n(VrXu՟ߊ t;[ybKM 0A#.1W毙i[]S9oA BYP/Lج*}XkOD7iZie6kl5"G=OwJҴ[oc*!dL0Jn䞋^VxK\:r0Ci] p@l׎gYyJl+57<+au8XYS^|TTf<RüDj^U'&ϭ>.\ OpCly3p`*NkŚG"0xT+M;f(8 5Ax|zom-|ɤ]s)ycT(LpkioUo_n9n e#.b3At8beLL,*R)ϖ:uCxfe /CZ%gy[^gyo)yo;7<|> (W-|Lxcny*lPpzb")z'g|1WUჄK~w ( JZ(k  CU[ M5Æ0Zjǥfiuzl+okij00+7OÍ&NbФѝ?k]m}k\j0]D~>.8S^1O (ׂ(+4O_T'6[3؞w$ &sQNRvHݹT#EQԱ8}kռ{|B>N6iu1l"Xa|G,3iu1~n[c³(~8ڸ}郎׾9S+K$< ̤,e/?<VM}m+[MGFLE6\2 hW?cMr}-[&[:0w`:~i??AηMKq/42EŁ%;sGҴRz4z/S´}OE~C|>(࿄Zeݻ갽XZ8dRGAvaRpE_&UߋpWLܶ?z2hX)6J7^Fj~E}hQEQE^kؖ{{htawTyo|J<%s#cAbW+fm3Q𺷆x(m?_WL5 k_Ow[E Dt-($eֿۓ2Ja zs}1jRPg%oƯ5[oڔk5\Z HmUTV#|2 _?;S]K2K4nQ;X_u~Aor%vڽ쬞=L[P4 n1n[q? O'i w9ʒ~&gb=OQSGo)ڏ ×E;Qt?vk)J! ?;G9wSCkF};KռĄSft*N\XN])ڏ 'V¿;;lX-[Y%f'ěON9^W_ j֔_bGArj?.]?G}S:kz}ߴLn3VQ#=nGOƏrj?.^?G.Ie^6 =ԟY??P9{SCh.v~Q+;GQ/xv1 #>lGk "VyA()m4,M|] ;nԏ?Jaߌ߲ìx;}kr0yfd9) @ +?ߊeT\CWV Rc0ȹoP ׹CsSںi&tZiUfi.?hS7.4;)w'DIE,k,"/e!}[O}uL|"RR@Q@%-QERR@Q@$?T>-MoWCLro@Q@%-QERR@Q@ ( ))h(th_(mF?|>{IHh_)XmF|>{IH(((JZ(((JZ(((JZ(((JZ(((JZ(((JZ(((JZ(((JZ(((JZ(((JZ+~/+/WCoKn({NX7bg}_?k?L(( ( ))h( ( ))h( ( ))h*xOG_]y\ M?z'~ep5TjJZ(((JZ(((JZ(((JZ(((JZ(((JZ(((JZ(((JZ(((JZ(((JZ(((,OػsO_'N]9ri$¿ ]q? ?W6?!+ !'skb֝jTf߁-i^l{&/9[]mx8޿O@g *g TR?H_#wwLIddk?!If*pGFYJ3~>d_:߃_hf3jOʼfI<\_ZuPOƯO CJPiwEջ`1w YeY^Buk {e[K6[[\3A3Hcv8/3_/|^5=bXЀyVSʲk$YsB?j6'>@l4Ҁ>mG43)&ov;2KOLof[TNH'gL1Sӥ4'O?#ᗁxKmM%[K_:k,M+y$l#7h<_-nK˟]R/6Vv[*(hO YZ[|떱x'KuOEik?#2wF1?3|?>2S'#LFOR $8ƫ2rI$_(?{'_xR5O:n!IldHvc%?#w~j:oL]4KG&K+#9cT Z6HIҴ J|>%EENI5ď&_}7>.#&_Jfʻcc=|\~En~io5$&KDB1*$H'Ϲ`8)H+:b'wo_+sKy/eKsoFvUYd~g <׵zῃ^{N.Ej&# Y|2M{UQ@)-'zR[4Oi_|T|#/i#jpn*c9 'rN8<2fѴ´aWiؓpwW}Lʬ·Eև 2QK_$f6SjZ=m1 bIGTgRs_opT^~~wDU %\śX\ֵSN֕_oNYJXV}叅?eOZ`'RO,6 )i$/w#_~%} _#Q<;_$6rA9*8Nn]/pIm˷e_ltj"C%kh\3 ϜC??<#gS::4V: P!5Fq>YVu<Vר5g|+\M:ώ/`㰷2X[ f.WalϾQEx\u*n[ *ptQ@0 nx Y<2koo u_ 1?Bƾ~lc_/ L]&'#Cq _DԚe+ˋAu%hnbHI_Pz,-(,'g߳O Ii]J-䶛}mܬ@;P㩦 =Xmnn|- .d,Βq"meS\'EQ ^kq~_*+pr|swK#uf8C$8qum{ux9O/6tl[{t[?!| g/75%W)(T\GyF׍z^sz{ #FsN VX`oQEz顊( ( W ?n[ÒƻgqÒcOWVuC?(R?Z(0 h'O_d>cX-칯z/NK@T-O' Ee9_?*K< מRU_3_ʿ ))kC =)h cK=T#`z[ᶑG~?lze i[nRƿt-pMuw&ed mvx>ެ~|]C|\/nK86[{ddql)m࿈zO:γMy+I6x%\z~z+ωjvڳ ky|Qta|oG)8J.BKKkМz}ԔQIK@ vGĪW WOVXSPڬ) ^y?#_E:w-|(QtQEPJEk 4?mowKKK72Ve ^~Q_4?gVUԭI!Q/dhgJ2oU$Xs^y~~^3xRtC,^l+{IH_)XmF|>{IH))h((̪2(4c*j:z5@i_Cj_c(hQY_ChG#/QuBki_[K}zV??/ר:ѡIT?/?ƎG>Gލ +?[K_Ch}}Ф_ChG#^F?Əm/!4r>?ξhRUm/!4ki_QuBG?9`_z4)*??/ר:ѡEgki_[K}zC[K_Ch}}Т/?ƎG>Gލ J?Əm/!4r>?ξhQY_ChG#^F%PG?9`_z4(m/!4ki_QuBki_[K}zV??/ר:ѡIT?/?ƎG>Gލ +?[K_Ch}}Ф_ChG#^F?Əm/!4r>?ξhRUm/!4ki_QuBG?9`_z4)*??/ר:ѡEgki_[K}zC[K_Ch}}Т/?ƎG>Gލ J?Əm/!4r>?ξhQY_CiFp9_`_z/T1O2?ʧhԕQE%-QEWCoKn+^ g%,/P&o ~ ؙ7A_@PIKEQEPEPIKEQEPEPIKEQEPEPUf?$/k5xOG_Uy5B-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-W_tz+W/o=N? ?xS6?!++ &D%vQERR@Q@%-QERR@Q@%-QERR@Q@%-؟ѢJ!?% DvoPXڿ?kڥ'ȏ3;ۼ#8#'_Zu֤kM,j,+ܹ6$)]^b1+y9)β*BX*֕al#[$3ѕvaW>!ռ1FX~mQgxŐ/`r D@zuK:5[%wuϕŕyhTI(vVzm{X#E1ʱ/yrEklUo;2,3!: `_'70vZ6I%b7.*#9׶ !<)O.hlceDU2GSoRj,GSXa\)ͽZ5N- {YTTFl  kL`:\ZWhT HHPY|bmB;hi\ ',@H AI}~DJp񷃧!팺..bibb# A't3wA'4+x8UWEP.Ho [:ˡAnWcudեn;_T())h_ڪ6^xu{ O x?ŦBmҁ$$Aq$U2\dr=њo Kco1QEsQ@q|J^O}?Yh$+O)": h"8.WLt|k,GeTfԭIt !B}h>PeMAVܷr\m=\}6MĚma7߷'W ͓}b9´ȟ5> rAy_m_\pagY+X7$,@߯>Fw}FBzG$68#9zʟ?띏[L?s^65[t?is~$ӭm6h e ~Dx/:f%U(-ZE\bE''E}gW2RV;( ( ( gɪ\?n? Y|lbOAk?7OX#c]T-o~Qx[ C'e}E%-'AEP_G'?ƍkK^/NM@S.?'^zK_U*K< מRS_3_ʿEJŢJM?ࣞ<cIT%jqofIUꦿ߅~>>)7] a5t{#L ̏a؂;WQS|aχp,:O[)lkucǛu<#GV,~^4n6y>|gw̾C:1q$L-ŋ~"0Siנ\ofՒɮ3ͳ?|i=T3ំ-X0UBL&p_9j^1yv3@0D)vgrL>aBC['RRQ݆@ ri$j*E_<{>wN-#B3\˝,B,@+o?ψ *L֫#[gd]UACG.JvϘ=C Zυ>huWBus$B$^Lq {PK“~WeQw7ڃEq<{Ɏ%_!p6a<<ecCVi(׾ֶ柳Sm,>.|m5O[ 62iru;K_ xM jE>'+ĉlƜ),u6Y< &1*v .9sQc3hۛEiuI/sR)EhQI@ E.jFX?uW[c_o뼟"vb6G3A_?[?'>ˤ\ɡߺ3T%\~W_!pH-6IlB.-@7M#>"W?L.l2rvHwb~/  uU<}R H#S_~? U#ڵвѯ.[IFZ/!8E!IX+~0x^ixHN gw-MoQERR@Q@%-QE ( ))h(th_(mF?|>{IHh_)XmF|>{IH(((W"]Q^CyyHeos kjV9FAO*Ưs EF+G[9N.tӫaKIEt"QEPEPKIEQEPEPKIEQEPEPKIEQEPEPKIEQEPEPKIEQEPEPKIEQEPEPKIEQEPEPG#/7g<=A~UQR;ҾBVc%eG:z}q kD&d]x5!')ǬV!+s$( ))h g%,W_KX_-;Mct|'ɬ3n(JZ(((JZ(((JZ(( '?IYv_s_+4xOG_]y\P))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( U?wb_=+X;wM &D%v4s_脮ڀ ))h(_*/5 ~)<5\7^iJ<(kȕxʰy_V2oP[?~տ=\5;xK]6dQd؄ ꥺe+?UG?_|j]sk0s}NktiH ğheA]>_{q? ~t߄_KOUix{q]2a#E RHo/O?EHxwc'yX8bp`k=7[68Ѥ+kg*w X&!`2Tv'n|O)kWOƏ ~_xZxO"X].dt[K#mr9}u{ai _@-e33m>XoXlU'Ǐ;%n > ƻ6vNZΫ[ADWEgg RNs?b^&ymxƖjKmmi(4v჻ʽT ֽ7ז1rNrWۥkױ0Y?ৈqg94GV}ږrJd d^QWTg 4MJ]7JV9Q5ic|k-ceh\ d)'h+X hKh<'%yzQs$f*2xy;S5J8P%TH%c|\]9ξ>85uտ۷w/2C7B&ѦֶVoEnoq0woe߂^5uM\],j/@nI6zgGj;q.Gaokio+\c)o3q+|W :[hu]sko!5G@݉|ӚUVya9uYEtIuZ3K /p=ܗWշꏝ^Y &7m:#<2#E '/> FLڬ7:ĴC0vP: ~1OٷZqVAmH] ]ivGSp"3E}44iYxys Owf1򧙕NI @lEJ3m~gmz\ӧWbbKOk|wO#袊JZ( 7ZFZSEǙwG4dd`z 6 _Wƽ6AZ^l$>3 i\[Gu>m#\Q]sQ^_(u#ӳbZ,ҷti.]-3-^1%v:B̅[ς_4]j^f?x 帲_XVOZIcwQ7|isD%Yg?G&EW{? W1i}?(f?  '?G&EWw? W1}sE D?v/еq;G;^j7v`_r04?7ȟ?gT_Ux?_@ÿe?cwh-\K_3LA OvwB n{>i}"c@"| ;aQTiT_U}Z/еq;G0_/PO3l?*/*M<A ? W1ÿe?cwh /CIA!VF_ᦕy}<;7_P~I)4j7a]>>^oE)}w?s6'/oxdoK{;"5ѡo+ [> $o $Vqp .dǛ32vUw&kMFoX[GokA QQ ֯;0U*qx^']ⱓ杬E~ow(LŠJZ+h'ѭtEOZho%_U)kW q'?Z$Wo7J?T$&qW'yJRm_ }F3*<qZ;wrG繧m (ʥ%m4{W{}O׳A1jƷ. fW2ٮ~g\Ət4 z봟"nb!k'k'[أ5 3*RRx|!)UVyejx MH}U?ىxN|Wi'i6!a7GZ.{0O5둯_o6>$/.X-7іT"xUy+o+mXළã_0ZiBy[zx覯kKao_լֳY]' 4n?2s5_?>>G?u u&O\ocєj񟂾;m\}Pu3[3$LWIjjyOI5y oZ's*#_o >>< ކn"4g!d98aH9Ͷo /^h^6Y{`l rN3@WO_iZ~ ؏`> ך+SsZ_.(yۓ׾ c't(͒o*%/3<׫1>CLro_$?T>-MoQIK@Q@QEQIK@( ( JZ(t_(mF|>{IH_)XmF|>{IH))h(g/4zU,8?΢GO( ( ( ( ( ( ( ( ~,>3 tXl6 e@W:պbw~)]3H\]G'pWeKhmw}!~3&i?ydM~|9g{cᛋ.IdHgwxDI'vo T*U*g9d73_G~~Ϳ 9ZxZO[9.$Xǖ]q+fU Ou_C+P5 wѨ?Sꑦ[K-O󞧽icС, jõyW~XImcVZ5 %| E6?Ko:x? :~,`{G75GcSOq.2"!I!,BBnfiӡ,>VJWShƍ[ u6Ceywn;f#baKyo4kٯMMZkie|Cd4q RVX8,2W+!< j^ڵ&Go[7o}Q[<|R~_e.5dG+=l#Ι>T[YjWwY|}4? xrXXney`E+OPյ9kkh"St :T$ oZ| K4 hW3A/ϞΤN?T_41{YTWnKDuMlVk_YEᘴ{{D]ᄱf؁dm585 >t%XT85-$ƍ7]⾻jV:Zއ(%o[I pNOJgOZ~#wMҭ{O&街tr!#wXlG:s>E;-[_}G_'-~osx^%ndE81IrCAQ)`Ex+}\>!x2¶wvp8*y08AԔWoE5;6y4xG2K‹_Ɗf ǚEz2jMj"O Fd7i8T>x5#SLw"Kmq-+w U($\;dBSr~Q_^6y#39L㷛׌a#9JO_kE  @XUNq3X>_*n1hg=>g+ouhYJӞ@qkG{"!#C8鞕xwǾg;4}f.mgN# :2 A*֌qf3g}kW:J(JaIKEsXQElxŅۊ~7 'ɬ|3nd5&x{MWRR@Q@%-QERR@Q@%-QERR@Q@%-QEYO`'I#.<*Mz'~ev TQEPEPIKEQEPEPIKEQEPEPIKEQEPEPIKEQEPEPIKEQEPEPIKEQEPEPIKEQEPEPIKEQEPEPIKEQEPEPIKEb?W7CU?wb@ӏO'MJi$¿ ]QEP_V2oW ~oX؛ I'_]kXoMZGӴU&+K լJg sֿ^ɟz7eYO57CO>;/u-3gijJ@Ž_ΧOr?u_ԇ $W:K_Or?u@ |Cz7+- s]P[xcGU "@Kܒk||Cz7+$Z?@/ma?mYe 8c(}W?| 忲!~׷|],n_&ӻ59)4uwᾕ> yZȱo.B^2z Wm੖?<=oz>4eo7Ə?*P/GtWϟhտG{(ϗ:J_V[h}"}E|Ə}[g4gRz>o7Ə?*P/GtWY"Z,m@z_.ޏ8F:RKT{%sz-1nPdx#Ot883,>*BW ( ?h8?4k_]5r4~phֿj q'?Z|Ap vp$$¿o%_U)kz/kV_ Gi|5fxTmqeg޺JOx@)qWiӛ8oFq^]{IHh_)XmF|>{IH(((']'U,'UC0(()i(((+t#:bw~)]3H,~%L4-:3\еUtAnPmq^/Lo\k;Vcvv{W&~Ewt[3,ON_C $I &7qW^?Ò3{\٩/CN%<?GS.<{~>lv1E$6u$;N:k5'NjNۗv P'MVT!>G7,`e>ǯyMr=>v?O< 0| -uXDw||-|O+Ķ mZ 5mV74^@zm! EfzHv/3` !߅D-X$di-/-f #p%PoU"ɮxkEK ]{eP?iߴ_귺Bֶht-$>Z?/ l4~^y",M#FQ_l"5x>PV]:[E™bO8 j|nwF`)4){*l_>dN|Ww⶿m.U/~XW,6??}_f֭^*>{Ĭ?I{K_5suח\`1Xiz|&tce?[X׵(Zm#T%ANA=4Տ;]n}#r?߳O~x'-e> [ W/9r"~m??g>"ׂ֛zWi on1&ˠ.6?㗇gs|IF{i\m,v Jy*k݅<$q_7Y V ?|woNTKY"Uܳx^k}ۆK)ϿEZ^zL^XrSnq{7m7y>??t#C>o`-~A˴~`|mf._ ~(Yi?[{+WL"fٶ0RzW¾'Wd{68h1⯉_ENj5 &+%",Vز`pN\2GJ8LLI=^zV/^] xH̶w%ŻvSn@T`kF,^(Ixb{K;xbo:ATHu8ʹ?me<\85Xq\\`s_(߃|]'xBS-2́!wv=Y῅&gxjU_.|L4_Y5k 4-3KԕWpZ[P+,xUG }/f eZ4h&M"ڋܢKyc4F%2QܤO7!\\4w:|Q^5hTB*-k}%?-^=:Ģ,0j,72pCą :{+پ?O6\*Dբ_?MNvJ.u]9XY;_E/ŏV5EW?h=-Э!QX+eb,0W1;s#G5XghZhSY^E )dXMXgy1U` ._=MʵJzz_PΗ/,k)'ӭ!}]EbU.$bg|3 /:g>!i0k^[{+Ķs|I#oM 峌GL'pjkпmRGYuøY;gw;OC?| j/.n\##`W7߲k9iڦ^ j+נ-b֡JcZu8nѺZJ+()i((()i((y#^^w=?Ok+|">JaEW9E%-lxŕۊ~7 'ɬ|3nd5&x{MWQEQIK@Q@QEQIK@Q@QEQIK@Q@YO`'I#.<k&Vo=o?IH˲8?*E%-QERREPE%-QERREPE%-QERREPE%-QERREPE%-QERREPE%-QERREPE%-QERREPE%-QERREPE%-QERREPE%-QERREP_ʿ'N]9c 'z.9|4q_脮ڸN|+`PE%-QERREPE%-QERR9x?>'h&c4\!r9V>cac[%m}A/WU( ( ))h(!?% DvoWCbK|AcF+ޠ}-kWr) e?1 07CO_O}^#ϗj؏fip}ֿ@STU+7}cs**%^VmwӦO~?xO~0i"`ckyaQ'SrnB9?ZDִZpVI<GA?Qt5 ]q#ϖP;O2½KRe?fKx,tCN'{P2IrfYd*qignt:rt)K7}E|1{◌<O^ #MzѝCFLs[ SRR>2vI} LV;̷f6&xϔoҼwM^Ǩ*nt~tWxǾ.g8.3Ŀ|Ky49ցѤo;26 @&] zK}c̴;~,G;o cDuߓ,~ryJMG/|xkr^IA .% X?gfÚ+B'Yd=^03nW+ԕzQT,WMksS rrrsk}vWh|Px<^塃SKDQ+Pv}z{%뿇-&2uM~ZK;p a<$ A luISI}{;YN55I[V]>X>1x猴>K{]J&;vo]HՆц}OǏ UѩUμ6J=RRQEs*Of#IOkë~ 9viI^U^6X ,Y UB͝ĹoI8LTk)ݣxv\¬QEl|RQ@Q@&Q+xeE9W ̚=WGD+KJ))k?d ?h8?4k_]-r4~phֿZ q'?Zu_5.?'^zK_׈/f9*l?9TWG`QE?QԿ)_Ï _ďFK N$i?RԿ)Naɨimpԇ5kye#/ZW[q:|4'o~'}CPPp᭥XvbJ /C~0,moc>ZG\H)S5;? ?Ƴe)!^֎|6@:ȫ6jȱx~5ա8ҫU=w`X-"uKEUT#J$7(8(rסSTpѧ(fyٯ_Ů]ѴLbjp[yrgqA#7ि4φ 4IhSe"/7.UBDpcIdž~Ϗ?H|:6zXݙKBF&eݘcpEZCM{o;{ Ibl`o ǹU7ݗ,ES&\CZ7߇hqZF(>y8'+19SBDk=3]I^u7ud$azmzqJ5W⻿_(f]Bt&'+OY3E 2?}_B׿ON]JKlkWwB5_QXlEQEB ))h( ( ))h '޿H੟}^1Z/ޢ ( ))h(?( ( JZ(t_(mF|>{IH_)XmF|>{IH))h(PVWS1ujkVVV]}]|]NtQEp((((((( ]kwKԠ8. on6Ue$ak"Υ%5fz>u_U[]ts=LJ|#zIkY2Q@8$q ttQZ\ܟSۯf;oxgWmޏܭ4Qm G01-8 w\W{auHډYI,零|fKG"wZk韑߱uok}թO}vk y_[~_?j_ B_"ڔj& x"$d`CVQ^F+quqQƹZ4g|o$Ib6Okk"R_ZMh>o}R^4:uwS'Y4]6X:+8 OXJ<۴(OkO/&V|1H5^,cyVܠmY/8U&ܬkK2y#_S ;]; IQRI^ߓ?~(>,X|KeiJNX$I|t?߇Y3~LѴPĜݝ۫;,ybI5׀Vu/*.IGKS) u KFeA8 19U,EDtHw~g_? ,HdF2ؠI 1R6oJ?|iԠ>0ҴamNOub}5CȻmuȤ),DP#I ,yej~|=h_|-L-R-y|Y؏c\`Qjٍ<S e9=wI^|LsO~>>TR9uWCG~xMgaEw6=Ue+B3웉qX(k?o&ECj_|k {cV!?9@5aO xKq[fҴ |J䴒nwbItP=")P5A6+³J+dKo _P|BԼIkzY.仵i.̎\F8V 9PkL+㯄$y:ܳO-RAr[#7#( q }T*zx86zT1n[Io?_|v?$wW0rFaT{*/~_BӟLRL2[B@C=8z+WF-h*S濵g_ C?xVU#H%wL7QL4V2܏`/7#Z+i.i1sy|>rۻ89 Ϋ <m'w>&f~ /xN5e9f&`,)v@y]Ga#߳skIu 3OY`6ͣtBۆӟ{%%x5mr'}QEQ@Q@Q@Q@Q@Q@Q@Q@Ő~[wX0k Q<E+{o JZ+Š(-ذ[q_4?[?/a?wd5&x{MW&o  JZ(( ( JZ(( ( JZ(( (?+7$ueǝs__ɯO[:j(())h(())h(())h(())h(())h(())h(())h(())h(())h(())h,GػsQ_ʿ'N]9qi$Ÿ ]q_ ?xW6?!+o9yk6`a=#,O %u5Dk۪mxB["6>L u3ga#j-~Ԥ#\|qޤ}{i?8{7_)Z:/dsAi,L]>py_ſOWUM>mn2ieLj'B/߷WǺi/du}@?j+ AKx=W;mK^y=YڙXb YUI%ƫw~'~j~?Zj-e;eY|1I6|s@\W__wOx\kyWVu,B5u<2n޽ [O_8-;/ i*XeI!qr! ((˯+Qm{XW}sǤ'_ ??h[ -Z S-ʒ$ ?P/ٛ>|H-5htUA$:.J0<W֞N 5. +mBJ3[DJ*J0$GJT?YDό:|g=ӬXb-a`c8:W~㏄_f xf?~tyK6:E҆ F9o@~H$E׼EHm71G6<ڤK3C݁ rz-yUk 6mw>zsȈȠw"?E+7Fִ6k{iXfU VR9WqP[7^RKTn[x 7 7PtWƞ?`ퟍΓyv8Ngqg8&}duʲBaGq@חv]46 %I¾:~?f[)EP mmU$)dy6r9_Uů-m5\,O*dS@>Ge>Ѿx\u˧\B.zj;JO&))hƍEW_)-'^sm-2.7) 3{>!h MhVFi@?v{=D#Wl|֟ȖKm`/.Vn%.l.A'z5_Ԯ5sĞ$m=ݴ;t3ZxWSq%7TL7/|Ɲ k,Mi!݂ X0*)*QX<#۫~jɩ$Tߢ^xy4/aG|/H#"&F#(F-+f$ZV| Ei6bkfQێ-Ia'5_Kk+mZ饲ddq0+{8>|xv*q1nUE߻=_NIQvG?<:nf$j:rN85_v#dU9'|%_D|'E>N|..f!.vP 8U;dr4|O|AwsbtۏC5(qV# zdVyej3XgeKizfWοcĿmeo~~ͦDdlEtG!g;l_|8+ܼH-f!U.|m'ynmBt!7{6iYUZ.5ks8W=V@ ѭf&{gC!!yrKF z8WW+%ǃ~xvim$O{⵼`[qsv:qxzTAג?~#k>zԛM4~<>&凄*K^#|H+࿊e/v,,}YHA0y#tB&4Q/|1[GD.]ƎSվ*`+v }2^>xX-|9sp3[݈sJ tޑ<{rs''5|+м7h~.mcQLHط?iU}!WUE<잮6סե);+OU?h>$ߴW"akKl&ueC@OݴZ ewOm1U): (Ǐ_?C~|j[H4BXdgsWG=o|X[]!91*<Uܜ[Ge)bKNw=:?)kWXb_xsFmuv_<7u Q^_?QE|QEQEWxؖ8K41(z(0 pf ?RMZ(e2T9W>oejGOI>(s =u>}0l K-Tֿ?J8 yֿ&gx*N}'`@?hRxFUx?yW]F niyI'g-_GZ=ͳcW+)pA⿢2*'[&`:Xn-wZ|6(X{|ikcZ]@]Ur) 8ȣ4<hb3:ZYq*,y'CE4依d*A.okk[Rx%_GVQZPLdӺ>h7 Da߬} h7t}fj!!54T{ZtT%:+kUIUlifyixVI$%C,ĜI$3GD"5Qʻcþm i6f ie [0H N$Unp8e4f>5ZyNbҡ0Č0n?5hfQN]6\0HFwc* b0-˝EIfO6?) FSm+u5h߂9#~ұ ߁eB'5 }s|W؟__E>[ڣ~wOl¿{\vPԔUQERR@Q@%-QEWCLro_$?T>-MoQERR@Q@%-QE ( ))h(th_(mF?|>{IHh_)XmF|>{IH(((žQP]0'tx"e0G_HU+3O;ex!yr|K^1ϣML?ÿó>9_8}OϯD*C;>>_ /DZC;>?G;0_<>'^"?G!OU_f /DZC;>?G;0_<>'^"?G!OU_f /DZC;>?G;0_<>'^"?G!OU_f /DZC;>?G;0_<>'^"?G!OU_f /DZC;>?G;0_<>'^"?G!OU_f /DZC;>?G;0_<>'^"?G!OU_f /DZC;>?G;0_<>'^"?G!OU_f /DZC;>?G;0_<>'^"?G!OU_f /DZC;>?G;0_<>'^"?G!OU_f /DZC;>?G;0_<>'^"?G!OU_f /DZC;>?G;0_<>'^"?G!OU_f /DZC;>?G;0_<>'^"?G!OU_f /DZC;>?G;0_<>'^"?G!OU_f /DZC;>?G;0_<>'^"?G!OU_f /DZC;>?G;0_<>'^"?K*ó"xn :jR~y>pkB,?e,"F#P8 YT/]#}~nߑ6vcV))klzQU (JZ+~/+/WCoKn({NX7bg}_?k?L(( ( ))h( ( ))h( ( ))h*xOG_]y\ M?z'~ep5TjJZ(((JZ(((JZ(((JZ(((JZ(((JZ(((JZ(((JZ(((JZ(((JZ(((,OػsO_'N]9ri$¿ ]q? ?W6?!++V{|:Nv:Uᄑ@W4~ uR[ hOrJww}ƿk+]JOKou* b _|o[,zuӮYq-{+}pyh?|j x,V-jٍN35m/5hVU)ayjE dg'/3k~5+K,?Co,bXw΁.AS۔~?I"xL'unNN7Bndk@ l+gN|w?ǁt{k{}I XG+7R(Jϊo~\kW6V\r<6dB#Kq`.Pu?o bl.ɪy@Dv*+0;C3ml?HC/ xνtuZ!cdVI_*P+wDSlG<4F{{k-wOn}xM|VN>6o!!sf }ÐvI}+to?h#wzTȄ:g/?/ƈ|WMDŽ;)=ZQGio*1eU_ 2wo٧9GZӵ  QFǘm%P$Io+ qLGKF1 h̞Œ*^] > ~+JD5 i%Į|3j]%Se3fxZЮXKKtmB9|-JLuW^93^]Fx|BuO{?<|-wQuiw_tOHC2GBYXd2rFdA?I>Q{1'cerW^uyevHf YfY\_59|2[?k5˳(׼$g_FhQEtIDk"@?^#_I4׼+Jz87&#'=d~U VN82iY]GR%_~u\] OtgUez*o\'M/ٮ&=2 ZɿIkɧp3Ϝ*|c۟g>mjmi! v} S}y?i~s<51Ilo.jwBkR9bC2'u)ci4KKZ$Ȋ=\&M'ٮ?&6JQk寄OTOx#qlsyq3LXDqjA8o\'M/ٮ&TV[^|ϜS&^?EumzoWj 8Xc '?CTfM|O\&M %5W6쬯wevD4TfO|_\'M;c>̂5o}y7i=2*= s5kk7+Ih*\ݛQWi䙟km-v }k IP+ѳsp[=[^N2p ѕJӝ=BJZ>+xo[goC۶0$[Ri}&2)S_ح~ITcS㿀0|:k& #%.d--5ie q|N1kF a\6Rx,濲_?<kw.xHz:0 dpz< KW /S [NF{6E~u|㒬'R:^Mv~v쮚(^ۨX((9K!&mkڴ?o=Z}S?Q?*D/CZ?/CZi?ѣvO]W#jj5} ѣwl_ʍW#jjk4BѣvO]W#jjk4Cѣwl_ʍW#jjk4BѣvO]W#jjk4Cѣw_AN?bN?կb8(ٓ޴>%,Қ'_gZg&Q2\DnJu@ r`XW[\S{IH_)XmF|>{IH))h(J}sׁtP%p} >۳^M%W-Sy"2"v-t~`xsc4vߟ^"Z6dOdC?m6)ke|2i}' ]m_ QOC˿_eG>mG4OyGyw򿽇24Oy?<1@?.@.W_/_r>o?(?|/@?.@C˿?܏|/@ ~Quyw򿽇24OyG ~Qu-]a_̾}} ?.h 6(C˿?܏|/@?.h 6)hP{2e#h 6!tDxc]|E]a_̾} ~QtD_]|KG+/_D_# (P{2e#h 6' Z?<_̿}' ]m_ QG+/_D_]>m_ ReG>mCo?(?<_̿}' o?(.W_/_r>o?G4G?yGQeG>mG4OyGyw򿽇24Oy?<1@?.@.W_/_r>o?(?|/@?.@C˿?܏|/@ ~Quyw򿽇24OyG ~Qu-]a_̾}} ?.h 6(C˿?܏|/@?.h 6)hP{2e#h 6!tDxc]|E]a_̾} ~QtD_]|KG+/_D_# (P{2e#h 6' Z?<_̿}' ]m_ QG+/_D_]>m_ ReG>mCiף豟WǴQ?2e#K/m wg$ACg?z"%w(ySyS@5]KxSUV%1/uqӨ漬úxY5.UhrXGL!5Clx7m=Q X[ZR&֌XԊ%-QEWCoKn+^ g%,/P&o ~ ؙ7A_@PIKEQEPEPIKEQEPEPIKEQEPEPUf?$/k5xOG_Uy5B-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-W_tz+W/o=N? ?xS6?!++ &D%vWğ1u_01XkvщcaDİ]I*3nV[x4-FK[@w=SHP BkirYXζZ^+t7Q#kH\ n^Ah_'W{FNj|M#Wе+]{Nn.do0 9_,|WN^;(jj0_x;¦WcOo:$)h@ _ (ƙѼ lȭ{Q~_"b#(2&?J=uxRIik]%xߴ1濡(fxhw߁ 1E]^Ermld0玸VZΑSIU˹(]y Nqz -xAx֣iéCUvߺe ֊udxß#T0eK)I»fH *s6A` 6nCX/U߽WY[i>XvYU){է+Zח^Ḟ?X mSgkz꺟QHHPI8kO?OsipsY+K\5 %xӓ3y.ҩGqEp<AoG,rUebԿG~GuI\7,r?G,rQRXka?$wTW 3?-X/~GuI\7,r?G,rQRXka?$wTW 3?-X/~GuI\7,r?G,rQRXka?$wTW 3?-X/~GuI\7,r?G,?-X/~GuEq+]@?tZ~jv2 5\jjK3jX4ߓLԤN(?+j%w?Z]i1ymyS~ %5%?N𵧊c'#I-N;Zc__D gQf ,G\KLU$v)k[S^IijՌ_K?`*Y?o $_G?_ ?o n?q #?/?Oao'?Əao'?/?/?__ ߶O#߶O#W /?>'A?ᅿl$ F?ᅿl$ F?o n?o nw??3?xh?x\ .7G.y8?o?%g -`$7 -`$5__%t_%t`A/0DG0D}mAs?s6Kω/?/?Oao'?Ƥm^Z{mAmA__#O>E/:8a!ϩyTƾ[{-MoWCLro@Q@%-QERR@Q@ ( ))h(th_(mF?|>{IHh_)XmF|>{IH(((_6^|z,n/~GB#HUTs)l)i(LEJ)i(J)i+Oڣo>||m v)?5(VssQJUUe^hy421㯎^㷷{6-<>Jh.эz{>晎xZҡS`.~۞G7QgjO6"5 s +sh`Wv/kҿz)+ ZJ(h ZJ㏁ u]j7o)Xd`k0C FUlܿr{uxץJ =AK_ٟq;VKF7y320r5S|bж=,`M>ryZqFXWJV+hhujO25%K.FF{fB?i/KM+rZ<e3Cx<*rrr1^^#8KO +OnևɫUܰ߿KIEz-RPKIE-i~~os,i%tU00M}]?ǏxW^ (ږc%؛9ڼ{W(O GWۧ#&K O+rwGRQ^ERQ@ ERQ@ ERQ@c~w?,lGȊ5uW=y}ik k3#vww mmG u!8:OsZMyL `;CqƓ4i"'4Ή[ـsK_|R.6;ޡEȌ# :FA0Y'cbRMwvW_n |mxYc[nbfyձuA$+$'ujJIUkC fh]-y1mTKb`D<z,6s6bk+أZ"M>kP9TVk [?sѮh,ڗ*wrTx^QS$t/EV-]]_WA-K%ͬ *GpG TiT}kֵp4ekL =z_zumlvk"Jvd!eII9I0dWw׍Z*wӥǀsYPZbZ))kϾ hz#]&擃Fxvs^_}ΦSo͟[wB,69lH6^Nz^a/^[់:Xu3 IR0qX3|5)J&_7'e}uE~v л߫_I߷k6q˩Aۗb$2J9Gr+k+x[0\Ek gÏ:!FKS+*0T{_ л߫_IvsIBELIEM.?|EC)hs[`RNYXRT೼maKkY"&2#Zs<>%7BjVef"7X>0~_> ߮o5rOg5#+e999coG⦹mT; S!HxG y5}_5IoOCj(L–5 ߮\F F_Ú0n999>5_F~ΚGjN{txo_ Y=:IbR׺3x 9G3~v}(D((JZ((*g_떋z!xhozJZ(((JZ( ( JZ(t_(mF|>{IH_)XmF|>{IH))h(Ⱦۏj}ѿUh|(R–)i(>4i~ ;kqMdV*A?7{7?v?l.Zhnm&ʕ@Wk:0So ?W{xCNNrǾ1| |4kj OjvȪ=^7 }WѡVWQi~'0UFiw~[??ӴG[i,䪱3>Ұoj?X+o rO䬅 0r0s~w~Wߴ'u?i o-|8d{e_?a~7Z&OL1"EDQPy8V69ڝ4Uog, F8,=H^FkwyAxgY/HtI4ŷ Upчr?C/|3o~^C;6mFV"3J2Ü { E9+x>$Ѱ-M>N. ڸqy:8<JSZOXm: zi_n}iK> ɮ4<@Ԓ4V#?̀'_>*]MӧM6ѹ ȏ=;Z7t]QiuශGʣ__޹8-QFxG:rZrggw;8;3VJl7#Qz7uuV?GeX|K:ѓK[xiK&Ws\OO\ ?^R\:d!;9_5 < 3lV3,-'[Vn]]im{;a0X<0U۳v?~kw[å0𭶝jncwt4>R^:~?v~oUbJrUxYe \eJn_/wekk{n׈u-Heа*>RX|7w=̏wOF[Ͼ+JC"̊¾<##i+jVyZicDJ%c\.fEb2s\v!k?G,6\M !?`>9ej(W3O.]E*X9UFHnm -|=݂-^9 R($79`#vp-m/ ?~y#Ěj [];Pռaasqlc-3& >gI,!\^QUtuu;QgWErkkfz&+ MM_ip.$?7D=|̃w/~F4,k!(0yXH@|ॿM%agw mHAu㄁[UѼolB$` 3ܑ+GS<0n*ײ}4片:RvW>~ǖqxz}ņ ?ζsi%V  ۓֿT |N𥗌x/J> ::UA2'?l?7xmFkɩToKa8V=w +G_ m--U3!ASbj{H];Y[^3ai9A٫5?|e_?<{sx6FfQ'=HI__?o9xUmK#}D-(v-,E6p9uzW=GgY2fXz2vM׈9Wm>Ze_o&HTr@zg/(ΙjhhtHUlLlOl/Vlw\d>?~-tH%.T=̈Y\Ԝ x-VO" 43E$>[DB5\;ƼG3Vn.*k<\|XM3;ym)Uu #!tҿ32D3h?t5%dԯ I,`>$k)4I ; ǟwLtq^.W~$\Dkpd&k4} 'q3F2 .9Ǚ# |*bP*['8I~"XjZő`0'8u#־5e)Y\neuAH |/[+-C$ldX+ c89ם|'=<'7Gʭok $;9xc.8|BZқ[&xٖ/-aSuS]O=m?[jvh Ե@/˧^v] HkZύ$MCIu`щ ,vqqR0|+_N}CK7& &B[;ۍnQrҞ9^ѽ GV&yZl~(ij[i>ܷ:.ܬP 8,2$Tꥍg v֖p#AUU(8Z=ZTkT{%#JW*4#]%<_ķici?DE9$ @+R< +g>ئIeGxS^*->[@4١!A>8 -E=7}[WTo[{f BVr;3Wf^#0ϑ%vwjʲ&.S(џ7xo&jeͽՉs Iac`-3wqlh5yvԭ&3m'eaP |7ƟƼz|M, EN}z3٧VqU05WWjIxVzX3=ԈBUu$&qy%ƋMOC U=JĹ>NJ5IA\M6+Ҷʡ2~15fui>K(!HAU@^I,F&71^f8|5* 5wYGOlQɊ˾ $)0I*[ -r]_ip.^kYUdI3~ei|T]&_ ra.@*7D_bhʽO>_+a+{*&2_u[56 `(ZvT+ {?UMVSOkyMGE =9.O ^ xDO+\:3\̊5UFzvEk?,]5 z].pO!U&%r&|#sk =Ʒv|y+${7`p׵gS˖uu]w*4jbc# WV]>j ιg$ %XD`pp:tx"|3nmSNmb;_zDXr zW7Fו.;cW7!,^;0klJɩ^38"k]A0% xtAu3L_95| >⯉&#Y@OW{x:_la,;DY"gV A#5krvʭ:1!৆J˙ ⟄>1x:ƾ 3YE&kyE2vw $Oߴw-}.aYpګ j2G$ g/ ~˞ SQ2C|2WR(Ђwy9ot?|[K o,f=dY r_ZgAQJR{Rw2ȸw Z;Μ>F Mn uo hi$Ks Jȹq__?x׆αMj_iҰ]g`qW ;[n}_7v7<3C%,Fk+ +_Oď~H-5 AmaCU'Oћy_0Txoa8+z[?ZJ+3p ZJZrn]Z?g/:}/V~HQl))h>(+~7 /WCoKn({NX7bg}_?k?L( ( h//_e?Za|IXh_D6ZflGH]sW_|?lWFե4xvȂw"|,C`0UPnvO;hpqBm_J'AgKY6캼mSĚȖB2<B@-w ?b/?I譴,`IngEVY$pewcs^/~ km?]R/ o fWdSK9;WWy K׈4 s!˺A_.=,#;Gi?*?Hj?K'/g]7V?-h1 <6hq9v98|~|{I?ƿ qbKWXL",DTQ3W+B떿XW5ᯍ*IZMjbeD^ʀd@ (ef_ j^$|Es`;O kCS= E'CSq)Uwjj?dψڏuX BSwnK[<:*?k\wo[Zä2<\:ƸU-ܐ$Kr? f~?e|MSQ95 CKK>15QU 2lT/i_?`uy+At;;뇎IgNXJQ>'f_ľ:M ĖKk֍=K$MHQ"U/N2<~'7eq"ƷG18/2E)TO@kB4#ZAͭ$x&PȌ2YH +Oxᆁi_Y^ՕKoicH$ vʼnMIZ_Z1e5=R, n@QEPEPUf?$/k5xOG_Uy5B-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-W_tz+W/o=N? ?xS6?!++ &D%vQERR@Q@F߄j#_|'̞(t[-J<_ysmiy Ԑ$Ium_.m|%xhѾ3n߼wg?6ݹ9w)+AeN4Z{(")C" 檢(WDu*|I! q?'hlWמ_C[O_QY|9׿ uz;w +QIL`|<[-7KԭᲷhT \C\s_}O; B0ې}ݲ=xs'zޛmւ=9RVڴ%N RJZ=v2Qש7V+駥7Ix#ZҾx]'ÚV3:Kmma:v̘ܼ-H&~ o xVﮬјM h#K'H>mj`30Xgpz'ƿڶO7I7:s>7ۭO\ݧL=sj1T;fzwsTs+u,ӗ5K;x~iZ}#\Q׊Mos g ~ByX̾˦; ϧQ^q$_os. ^Ik{cȢ_cs_&6ȥYO/'w|Aķ?gflpżL/tsJ?r_}OՕb0_?o_?~:_߇ϝ.[cGCHMz:> |6lkEؔ ''י~m/ڳ_ZKT 5"!bWL?Co5#_m4}%9{ ~.Wiy9;$nI[K'wɑRžÿ_~ 1ΝOW9Rs }#uxT֭HyhO y1j?o᧊ne/ZTDvGV_ѕYXpf3by&1U0ቓrJ-4͎z0WV7a%p\z|<⻯xgLfbH D0ULytĻ߰N5Gc_>c )^w?D 'VYZYN-=$knW=7xmþ$m帴:z-7e@2 ⎥y|5*6 ec%_@Y@+_ %XIff$,~1ߥ~F4t~#\]E} cs 5<>m$̤Un9v&#(GPqosgSOOµp62܄:*ȱ `W*p0+ x?V&o]=񔁣fڨlrr)_Wwm׼b DQYyrJ {7㿎HJ<6ɮ9 \-H <ϓQ/+/iUVu߮+eZZvS7ڞ_jz?zm9I ]m Q_S,Rn%&ҰRRՐ%{_GefS++ھ%p{1# GW&QEPEPIKEQES?e\_7_3O_E}[QEPEPIKEQE ( ))h(th_(mF?|>{IHh_)XmF|>{IH((({ w;^p5wl4RȄ{TkN)JjM0h ZJ( 2 =_/5A=7WJǞG!u?O|]u[}>,1.O6scs+師& au}}_ gTugF|π7s▬d(d~K^n b_e?m~^_࿄>xFOx5gKfty%*4p~wGn|⛋]>Xi,`аu$\9pYJ9TwF _61jދl $D y?7O^jZ/ .V):@ z2oYm+i:.mu=4mN0*F=2MyKezn?lj6ݪɭOkY~Ծ%lҭR+m+|/{mxnb1ȱ),"Ȓ#Xv{|[U:6M=~k<.i`qNk֞}oߊҽuuk[a@$QVQ~_?kiE"Zw_hII+Wf'޸_e_xcTu Rm xLj8#b=o=TxW7:>Q,YAPmA4r++m60 nqczUZJ%{tGf &$6_V77^ -sPh͚Y]G1 q|`gp>'Ef$\fB><~O\[6yl㵝RVaQ2w=zOط_¿|9kjv-ps5[0* !s\4pDc 'do:ٮUb0yJNqjy4EGL~՞&o&i4֢[?hq_Gׅ#ռ)hZ}1f84퍁˜|qǩώ_Qk> YrKdʲ/xW.kLH#F|ȸGnk ù04|}Μgeaq4\5mr_6ԯ/Aß|<-swua̯zŦm 8:ܟ :8ZTgRL9B.j{JM?6ॿM%agw!i=o~|G]&ZO$T avrGo/˃sXIn\L1)A=9^ ?~j~Ӽ=i>e{d p%@R_hO1KF4}v[,}ީmi]?kO2>|:!o٦ď4u(II$`5Yi>*{]Iӭ 'aEu!j|;ej^xĚkn6ʱ, lJB+D 6gX" h[eN69cGZ1Z[f9 0 ˙Rzv#kc-5{{ᎩeͤEJ4>U% z #8_t9+ ş <)FZvɫ}6K=[y,V_hp7&t/h^-:z&A$nz|%VXj')s'׍ QQPMǕp~tE[Gkp/[ʎ@Vh?!ޠ\ `nOW&|kJN /?éU=ĩgJSqPQW;lZ?.z&kwis1DŒ̩F?'mP֥ n6KXd_`2cS_~h.*BЭ#"X` qUӖs _*J)usfx 0Kw&]zW?c]CZ{K<Ne1J}YſƿY,6Ei HG}9c_Wxg,YO0ZӵMfM^Y͘e*F=95F^&o=-杩C%/dE*~;W <j+۷8+Cm1[_#/KU)/ Bfw?5ՂTxrT5_ [Z_|-Ai3 sjcqѣ PLnT־կXxeKu|Tĺ?ZkYu>>^5u?6 ^&*6)d?쩯aڃ>ψB/uJC# !ub0r뼑4meaA | zƻۿ t䷵X z` d>(gF.89Z>z.s Y~>7]߶?e_Z ũ-q}y$&w4u53m~5~{TcKmљa!j%B[.@5?eχ5MSVnMBJ/EU(b IFe[1$՞|-U\Vw_v|8li {}J͝wDZi0$A#U#H?_4K~eӼ[[ٖϕ$pK @?LTg=,eO90e(Nk[Y?'xsVaP$i$b"9b9#r ߋm%|*]j- D.x.NJAW^U-Q𵤷Ԩc}Fķ*F&,*U>$-90έ?Evc4/GlJoep$zя>~#fO^E-p o[f(8OkۍcA uחC.p+Ji?֯dBRѸ~*yj%ľ+^kfc\\HQłgU~r5ԓ_&i}g5m ˘&%J/rM@6wJд+H4 BGh0@+4 >: }vl:XX+.vvcz}_㷖]4cjUtz[Eodm#rjit)/$ld"8fxŧV Meol(JZ+~/+/WCoKn({NX7bg}_?k?L(( h/_e?Z?Gٟ0?Ƨij&]38N#A 'KFI}>+Žזdr+^xvRO49PO >牢K\ݺrFj `ci?a̟>"~?LX~,8m5?ŗ0|%0gp B>nk[ (j~ |&4jvVN2Mk*l@H5RX${'ai;R]pт[щcG QdqgNȏb_[%{H+w )H!Ҩ gُ?e״Y.\}T$|D4~͞)է|k^ I~ ZF f}Z? Gm{wtu(,Yy@2Tɴ3~(M|V$'ı& ?^;Ci?*Ri_oIOY㫽CGѴ5[ʲ[,tP 2?+~? d-;Zg]OXԢ,\^ky]PG{{p .W> -B_g6_$jؗO/fkZcxP^-DIEqݻqJg~ !YBJ{.,R., C)'Ԛ))h(?+7$ueǜ '?IYv_s_@(JZ(((JZ(((JZ(((JZ(((JZ(((JZ(((JZ(((JZ(((JZ(((JZ(+Wޯo=TbW7C?/N<+`WIυcj((JZwฟ6|%_FWEO5{:)|nߍ>m &ɾ{i-C D({8QW/·^ ;?^>4is$qϾY\e"AxdzAsOًu͏MOYՔ{%UadW/gaq7u]o}J;xᴶp#fwPPePzfCTO|L^H_VSIgt: Z& .0Ur W% %|%/u;Kn+[{R$XYL*ɂ@lP/O/IFbX?_^ZO_g{]( )W 2ۥuunenGcP|Xeo>6G^"HcKQް 1\ڊ 3@߷{CM/\A뮁fh!`O굤6sEJgsO^vyi5+S$5K"*䳼E@g~o~>9i>!uMKR_6SG%"4y Q}6]:8-%}z;猠嵴m4Xܢ"0$aBoڛ cͫBJ/=BE D~Y'_tqj:Ѩ(^y)T*jq}a6tY.nm[yP0)%Ӳ)C'|UzS R[od-IV\dPNNEk^J;_e63 aN2ۿgŸ)V =[W6:iww~.wDr~~5z{xM.%” \&DS+#sb%&JNqt'j2qI&d?^3Ok1< ɹUr[v#qIE{VSGKQmv|oս[%>|F?mWŚu[=M[-3h0L#*Ӝ6F:>S% iԍIFܱQaKIEzǎ-RP_-XL0m⌏_|9ϋSiem/$9 _@k&#:T]gaVZ;%@0?S XYa.p?p*;x:MϊH@9v}s_64r'nes3X+=} EIRQ@ _jÚ后:kV"scc%yO uB߱dyLu^[ϊaO?Sj&Ӧ5^Iq<143GGdg\o+~2񯂼'k^P[ 8 %bi+IiX\_t>vhBK >_S/|k > i/|F 5_kK-WDnemp#$ G?ԿGW{UEyNA8'MO4J-7{߃5cW~xZ텬plh"V69,\F0yJ8F&|L䜛JKIEQ"׼~v//.Af?;5x{AXPseF)-qN4Ǝ_8_}og[0$Oq+# ((JZ((*g_떋z!xhozJZ(((JZ( ( JZ(t_(mF|>{IH_)XmF|>{IH))h(⮅>N9lW5G,ıѲ? PeFY _!uЏ-C@?_pFY(*{uGS˜ե(IMm~JZx2VM.HaJ\ߧ ύxjx˥?Mg|.?ؚ\ߦ ~̅*+Z?Rb?q~(d§˥?Mg|.?ؚ\ߦ =?2kKY Kυ{ .5Obk?q~(d§˥/Mg|.?/.>oA*+Z?Qυo{ .5Oغ\ߧ =?2kOY G&>oA*+Z?Rb?q~(d§˥?Mg|.?ؚ\ߦ =?2kKY Kυ{ .5Obk?q~(d§˥/Mg|.?/.>oA*+Z?Qυo{ .5Oغ\ߧ =?2kOY G&>oA*+Z?Rb?q~(d§˥?Mg|.?ؚ\ߦ =?2kKY Kυ{ .5Obk?q~(d§˥/Mg|.?/.>oA*+Z?Qυo{ .5Oغ\ߧ =?2kOY G&>oA*+Z?Rb?q~(d§˥?Mg|.?ؚ\ߦ =?2kKY Kυ{ .5Obk?q~(d§˥/Mg|.?/.>oA*+Z?SA' G/W2ikW i9=UXk|.;uoT[5!a?sYa0s5~ݿ=B7nY/)?1#]=B3UXk0K^u弛gN_(Gh;(-ذ[q_4?[?/a?wd5&x{MW&o  JZ(( ( JZ((kP%<+p<ݷ񞙠 QEPEPIKEQE%_Vo=o?IH˲;ʿ_+7$ueYǝs_(Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@*X;wMꢿ,OػsIDž?cj8lBWm@Q@%-QERR@Q@%-QMfTR0UQOܚ OýzZK$"e?2#BT2=Β())h(HlOIo?h?][؟ѢJ ( JZ(( ( JZ(( !|cT>1?"c?s0qi}H|DU~"ό?*梏?`?_z#R??G)"&;c?޿gOQ C'j(X!>_[k gO]><̭jddCY!T}r~n?$^oGܺIH|--ptXv+d$oVnA; ih^u&TwovϳBVKd(BYjr+qo8ʰU*&*I>m׿g úZF3/pA\Tό{`ؗ=x~7iǗ~gbx-.nKz7mdt8?}Etx7Cܻg{/n;Cܻg{/ng{nϢ0?ݥ?x{t8?}Ea?p=˻K>0~~(_!]_y3?罗7K c_QCܻg{/ng{nϢ0?ݥ?x{t8?}Ea?p=˻K>2[O7W-g;Q=P(ξ\}KT|>˓7{IHh_)XmF|>{IH((+ ^Y\9GFW؜g2CyorDo!E~sO?5? O5^ϟ?y[?G ?JT O5Gϟ?[?G ?JT_?5?^g>o4W7<3D*SR4O?5?^g>oTW7o4W7<3D*SR4O?5?^g>oTW7o4W7<3D*SR4O?5?^g>oTW7o4W7<3D*SR4O?5?^g>oTW7 ؙ7A_@Ww<=+ (((JZ((H~\*o/8kh:emka:c +۱dnQ|-C.>|k6ԛawjrq3fkĐ +/x~|nkx=w3S#"nw(QtWG~,N?m{CWt76ϹIz20 ]RREPE%-%_Vo=o?IH˲8?!_ɧO[:ʀ-QIK@Q@QEQIK@Q@QEQIK@Q@QEQIK@Q@QEQIK@Q@QEQIK@Q@QEQIK@Q@QEQIK@Q@QEQIK@Q@QEQIK@Q@QEW_tzXޯo=N_ ?xW6?!+' &D%vQIK@\z/-4K?,1m/1u0۷ ghs>$qDn#9\n023rX5m95. 2C* $1<@TW)x |CjWeKy6p&vߊZ7p"l4]P;&H2ϵtƑ% j2#p݄s89/^5VHT4isxlHQ-Kᯊiw[Wm՝fAk_S%YV69VGKk?|]\ťx#eo4UL5?)'*Rr7>|}Br^1]T-"xiP+>ox/ὧEkc6L2NCFAB7 GPs@ lO7|-7Az"q6b#ill*6?ៀ-,2NяZ\;]mwVU5Y~9£RG nRbDD>U3)`$ e~QLWW}QERREPCbK|AcF+ޯƍEW@Q@QEWWK_!U _yq- &*xueɸv+ CMst~&'{?~#)?~Rח8~zuo gg-~GR?h~׿?W[ CjZUeKGGտ.?kIƨ^߈ O5_tQQ8~uo gg-~GR?h~׿?W-AV?~&'{?~#)?~GGտ.?kIƨ^߈ O5_T[ CjZUgEAV?~&'{?~#)?~R8~uo gg-~GR?h~׿?W[ CjZUeKGGտ.?kIƨ^߈ O5_tQQ8~uo gg-~GR?h~׿?W-AV?~&'{?~#)?~GGտ.?kIƨ^߈ O5_T[ CjZUgEAV?~&'{?~#)?~R8~uo gg-~GR?h~׿?W[ CjZUeKGGտ.?kIƨ^߈ O5_tQQ8~uo gg-~GR?h~׿?W-AV?~&'{?~#)?~GGտ.?kIƨ^߈ O5_T[ CjZUgEAV?~&'{?~#)?~R8~uo gg-~GR?h~׿?W[ CjZUeKGGտ.?kIƨ^߈ O5_tQQ8~uo gg-~GR?h~׿?W-AV?~&'{?~#)?~GGտ.?kIƨ^߈ O5_T[ Cj?95wYVbB !c8˯ί(W :r>$C \˳}/:\, 7サJ( h"E욎tׂYE*Wq&jz? %)~lnX:2q䂊(,ŠJZ+!hoz*gW떋z(JZ((( ( JZ(t_(mF|>{IH_)XmF|>{IH))h( ?þ= xUΠ&xypO"è<+b?e:] ?%x..CG|aN2%t |=9x:-E7%VSb9J_'>1_W׈륏^!k?k޾ž.n:r|V^O J{J4^9?A ( h_ bmKlxŅۊ> ؙ7A_@Ww<=+ )FM:_>C@?W?>ƺt.co%Y&@6 dk7\u6'XnfZq?k?+)G6ogE?6Jc7\tfWSk.h?k?+)G6ogE?6Jc7\tfWSk.h$_:^K㯊'][^D.K5eMBK\L 8)(h>j_G~-gevqK _,D*J~gڿIFh4}w 3Qqh8+OE ֚5+ k{iUІV4]j~,m|N OE PjRPH*ːTAj(Jz'~ev&Vo=o?IHʲ;P( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( Uwb_E*X;wM &D%v|4q_脮ڀ3l?xWV<[eIh# e끙S_dLc-_o"-T񖥨8 +/ by+z~?k >'uI쎙&w0϶Dt!AGoE{7kM^QĪ \C }V9LUx3M{ 3D\Yqyʙ/U\b߳'{߇VhZƣN#ΪALW e/;iӄv橹e.iO\xtUmldnK>w>>!lFf|]w|dӭu |" S2y8(O?gND1Hgo߅Ek~c!~1t-l ȞYxTF~}iEgk.~C~vAK(y-ݏc8oGkWe [r:.>>|om&Mv%0i25W½#4'Ƿ83ydfS' @ڽ+m?g/g+d,.qx-QF &F >E`jk >!w_ht[ĂGxq*lߤnKa"BG'<]o3,K[7P$т\ =xLԼW]i$ҸéC)#J6TW#U~ߴoh^ys=Ou%[vYnpDq"6Xzb0_ |H[h:ޯ9R$&4 "T`())hƍEW_)-'z JZ(( K͗JRk?eҽ(g &|ȗC>?%1oU#]Gl(肒())h(wZUk رZWE~F;+.bAAr ? WC|?᡾Ll{ xg6_vh_?R1/;@I^? _sehE3?94WC|?~Kl{ %x47)~͗ ^= gsei@'L}dO^9P s$ jv}Yk)Yq$n2 PGZ(())hkƦI*'Myυ\mD>&me>PWKB|Uo~Fd$.p23xg6_v=ho?S<3/;G4/)~͗aE/?9C|+ǿ᡾Llпc6_v= gsehhE3?9BJho?R3/;G47)͗a?'$u{3B[5tPEPIKEQE {yG_ yG_Q3|_"/ɼxc,c7 }Wg/͝AIKEyQES?e\_7_3O_E}[QEPEPIKEQE ( ))h(th_(mF?|>{IHh_)XmF|>{IH((?e:]֧g_._Z\Wp;XtRRtQERREPE%-QERREPE%r>6焾hr:Zm|/#c!#A;E]*R B*T"7dO G [Agm5Οq:5'ԎYJR#8k2ʍ,Ζ{y&-g޴Wov\YSno,Xįzc]e+taB=lZUп Udо3D<=bEpc,^ hZ/}gx\D:SMSW|J<1E Kj.Ф;H0UN5kzMΕ=זr<3E&u`m*AU=Ǜnf9 .\D;}X,Wc[Ƨ߄v,ZC4@*#xB?)쬐ت{f'¸TktGM)9=ڱx{Z--] 4-8: x#5_?88+4{RaETRREPE%-QERREPE%-QE|eɽxX\UOo|c_-l7K+C "όE} I_i$~E'WC_WKn+^ g%,/P&o ~ ؙ7A_@PY{7օg_C@̟Ju %K?_PQK@ KIE-%PQK@ KIE-~~PݿWEć}c{[>Sgls±?>{+Ծ'jv^"h,^^/P9x}2R~?0| jQݼum' 򎠎H)e h% x[z-"ǖv%I<׻))h*xOG_]y\ M?z'~ep5TjJZ(((JZ(((JZ(((JZ(((JZ(((JZ(((JZ(((JZ(((JZ(((JZ(((,OػsO_'N]9ri$¿ ]q? ?W6?!+nblO@~mgzLEXy>pݜsq? N_Ժhs o  wSh=(濁־(Qo  3Q@1Bx㆙w[yH; ;Xċx|TJ9% b|'0j:o-YfU*̀@>uOxz|7j:0VHT$_iᶒ&te&~Г6^>CC,}Ltl${9ʆUfsEiEPCbK|AcF+ޯƍEW@Q@QEW_ glzՕ$Y/G<Ʒ_y7DB_/EW5vz|%">aE%-|aEPE%-QEcx:C&Mt$E 0gv'Ux(~ڿ&qc{xV"V[ɭ7G"oX]Q?C\8/gyEbo^i^?b_߳_?xR_-5˭͞x:QU73O"w:oPaK o;u&h\yu'wM_4P>Q' U$X@=aiSo??? oÐ~Oտ.qÏr)tÐ~OտjZ8 7 KAoE?V?Ƣ??? oAoE?V.qh~6]/9[`8 7 M"âl ?A_?{:ĐӾ.^C.>S.>nWx+?aGK]O 5:A09fҧɎN5uJP7{qF"YH)K$dBT!R~hQfi$gl,z(EqJV-IַVZ8 dFGR=4b(JZ+?ۏu-oZ7VB*)WENFThxZx!Io[->uӭ 69+,3Gٗ_+ x;Qk$ZHm&9Fٌ, w^|<5_SMF "\NZDHG 0S?OZ.@ӠSoj(~6]So??? oÐ~Oտ.qÏr)tÐ~OտjZ8 7 KAoE?V?Ƣ??? oAoE?V.qh~6]/9[`/}#⦦ik7]ßqɨCE8A{IH_)XmF|>{IH))h(?e:] ?$:Lt/$|/+O JZ(( ( JZ((9AuMC%ɱӣ.@=] M~|Ic?[ CǍ$ pFzz|]x? |߶}[ɰtgqߥs_`jUN򔖿3񰥇N1Vb_ q ?\Ėvِ̇ k:j澞(GRҰPM~ ίk#ŷxr{l$x8weE;x pTprQrm>)aQrQ}>,7@qֆeQ v޿?e/ԯ&mC63`}~SW@K:i+ IK!_TKV'>٤aĿ|!\mvL#=ȏ5rSd_=O}:/-Õr:F*W H!Ab9SEYZVg[y$ɟT7E7tmepH`{E~P~ߞ/փ1mq mHz'$žkJt6 w6xO_м,Jƺ=0w5ncW/7w3Z0Y %֣+V_bbp0_pvX پ>i.rʰ4S{Y%}I;F .2gE:)еI[~1!^V>ra#QIT]:n9f{El;'~_tH1xv R#͗3U?J'׃˴ZmN?W緎O?S.'V?‘f?x]O]M]vѧGk"@ˍaSM~q "JjqI]޼G],*'>1_W׈륏^!k?k޾$o\_%-'Q@4?[?/a⿥h_ bm~~kL+;Mgtdz} hV~4`?'_9kyি>(]uiZivw3E WyFRxE~V6*YotW__ t%|s$_oʹW__ t%|s$_oʹW__ t%|s$_oʹW__ t%|s$_oʹW__ ח@YOxlWxBʒdu=JZ|/OxYQ?,qwf'Twf {_wqiӴ%lK*¼yGr4xoZlzW4/IO;ϲ(|:~_f#Mk_t][?}q D>=+ 4Ib%T@1@ЗLD0v#`REPUf?$/k5xOG_Uy5B-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-QERR@Q@%-W_tz+W/o=N? ?xS6?!++ &D%vM_w_b~#AKdk/ D>Z1S_3l7{T>t1wO, | ·cPe2&,XKIk.9jZiwq( R+꛿VG_վ߼Myzݳը6u}SJ:l >nyQs\G<<.uoG oDAoq+~.|RW+}jch8"ǝ_ݟr_=|n]H#C̚8v=ȟޥ4`Z|\Y&mh V/7O9ۿZெl|D4m<o&$1߭{.|_ZGĶgiXmN/>'Y8\}{~עE_!$|W;cq}>;? |S\Zᡖ$i*n9e/Z+Vҵ>[CP=,N*J(F+9i Z=rmF Xb2pB sYv sωXo| ] -Zm8/!ڬNkHћ\;ʬSze%-W_ glzWյ$Y/G<Ʒ_y7DB_uxĚ C^)K4^v02z9&|ȗC?m4o-j_%e[~H/Q:~տğx+_tmw]|ϳYZ,KҾvƌ %W/{O uukcm-oon$HFY'*,_S__>&|iw/g2@WTbM+/߆7bL;|K.oEbj2i: ΂S>YC4G jO, -/w<߳KGƟ@~k V15ݴ7),DWs#j/7e.OΗ:}M+\D儆I Μ6Fl?Jf~z7vH^mF6/+yr$ɿZ%߇=m(y1%1調)?'?ݟ_=PEE2:9 F୎ ks[߷׌xv!7ëk=";&ɞdh|7I֊JZ?hk_~5"(_gimBխ2<42%gh~r PG Gs|Ri-*KtֺEE0LE<'n!k ,4{B_&;|IxxˉQGX_$n +׏7εkF8/m CHn\ya m??Mi? xGjGHԠ4xm젳73-Ӎ6v۴h꒾5ⷀ'8 Cwm.",6V1\y(h@Tkdk+[_~>iq+gk3ZhT[kaWC+S{%OVEH~l$m?ѥֿwtd!WeH59Ր_?|~ߺ7/P=3~ F>'5-kJ7 `IKE~otXalXnB?gne_K[Erz&l&1?඲'Pg8A$! 9σO))h(gƺW*} 13_?C9.0xQ4KTcĆI9*b!?xhڂv VNp:#Ix?~QERR@k6uNJuҠҎ I\"ʏ0u|@V-5 >&-㽺کD@€pW~Ҟ(~ ǞZjv2_$I+BŹI$`d&@9[0ԕO>~̗~!c~$/Ɖ>gTãOV`x 3>@:OKOoYRj)q!_U;Wt+P_;1J袊))h(:_/x3?V:_/3?83Ft~>o]Wc7 }Wq&jz"rH))h B(*gW떋z!xhoz(())h( ( ))h(th_(mF?|>{IHh_)XmF|>{IH((?e:]֧g_._Z\Wp;XtRRtQERREPE%-QERREP!y-_6K.$C?݄Q__w#쟿ֵ[wV걠CᾣW~+H[#n"nB?7cajaM΋{s(b=y} ~ĸS"*;>TKj;pƮ_7L-?oIh ,eܚZ +H+ |%׊|I:--cu$d 9< V>!_'_ q{t7=]~T^@rĪ_HA=^f/GY췷\Ouǎ>)A⛸O ݻ>}WS𾱧In"?G:~|4oYx7˺;|ӰKq;( 8jIvHOk<㱞? >g*a='?gk֩'O7^#mLW y(D1P6X|g;~dxź{oE, Lod!̇r<IO_> ]⺐_V"mN匒|ybKz8jޢVN>WBxXA*}{#j|iip#&frʡ;~c+HoOݮV xĒKp|PTbB@?ࠚMσ|+BW)<`=9k?bx#~9ݦ-}4Wt>K)$gn:YpO$YERmjiEirQmk촾uC-u&O x>r=ⲤP&JO|/AҴ^GCb2#wf9' yo+mwhP.,S2[\;wW)<'^W4h$!{2b~qT'n7ϩ,6'9{{=/ʿ ?<[ƚu<ܟ m~|0<+$HoOj,>e2 cTWa㏍"\GY蛵9~׵&c4JKG7ݻ/Ϳ%o"T662qq74xC_M"|f<feUkiji 4o A{PvCeL/ް[-*A2Kr.©7`p2H)eSW9NkFGhBOI#suׂ[GIǒu8e61ʞZK~''|/ %M+*ǡ k+?OOuK$ ~Z;7$oDd 2}o7oiڄwi%V5:A85,$\ӛi{|1';1Ivχ[+)>Hr O9- 1~F|JId׫Z[Ia*@=|ags6v|o&ggse #s <,ck'}1%cWWV6>'xi|eĞ)->\Jџ,p#_*;Ya_<B|=GWe$o>R=Zڃţ_fֲ~Cc䞑^j~~3Z'W#D蠒}A|/o=wMI/'uʪ6T}y5f^rW +Ym4ڿM~7ck1^Xt=xsܞv4RCq&0^Ky^\ c2ON_h!񿈠Ѵ[iu=[T<9bOAԳ2I~|# >h^,iic,3&Flg+z9;Կv8|>GR*з}>s(/xWP?Ʉea_n|],ÖRH?A_!|g'l]NUQ=35}xrᎱEek[Q1>Ӓơdvǡod ҒK-_tcgoc|{ |7Ɏt~e c71_R|\|!nWYT& 2 q# J{1?'?ڇecfn)CO6l*+9D(cp^K/kGNwK{>3쾣OK-lF4yL;EԮľF;ȯ#g| ҈"Kһ^`8"gdI'+c\#?z-Š(>((JZ(((JZ(wztҸ?,-[׷_o^#?WxEݯzտƿq?H+O ))h g%,W_KX_-;Mct|'ɬ3n]'_Pj!~"Mig5i y]}v[(&?:Uf~T :R/ҿE?*??*?(?+cQҔ|y~/gQgP?(G(_~pFtڿञo x̸2ZP1 ___>.B&>]鸸`R a.yh4cmmsyqO<0HK;P2I'1?~$ Hx<2j=Vc8hx+^ǹ $G,~ ~ EZ%*qTE^}֚\HL`]{M6U=DTQ@"@[(JZJz'~ep5BO+7$ueǜZ ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h( ( ))h(_ 'z.S_tz8lBWm\OO'>MJ)Դd w5Hd9}(Fjޟu?)>GxBռ~Lne=-D$yig:oIEs0Y/< K9π|>]^7Clh#I H>ߍu|ABpBDjF#W1ҿfb(sI'4p|Pg7z>:x:c`FU ?ٟ-<fx$Y72ҿ^z{&(~Tȼ?~2\~ǟ|[ }o~\`6pmǵu9?LJko(4/$x)\6Rxb98ֿ@43w?*_i@> 4^>`$i| s#3sZ4׆|K4?]ZDMp4kHWt~G-=WP/ڧXg& 実1?nnsC/4Euu"<ؼ ۸ѱȮ q ZĞ#O{rF͟^+\QxSd^G beᕣ\(?6aՈEB@R~_=7G~-WVBڲUhcGKFIe 5 m'IP4,8$oHyϳh? & `{YGҌ6HG־x^ Эt iQ0`E囻I>< ku7-UD* 8P*bR@QE؟ѢJ!?% DvoP\Y:5PMm%(A>QklI/HYGaMDWvWU4iI/Y!ԫi?75O(^ɤqnY@ˀʇoF '$+nτ5I^z(- ppŅBJ8[9Fe^TswU+PAF DNב?%*+9Mލ_?gkO |@Ṯy$F?p9L`ceer #>>~>ZuZo {G7x 0~(ԼAfKַ ]ϤL%!G"x\m$8 Z1X:RK-^2~u$Y/e|iGx;CQc?qM/EWhZԿKW>K_!U쟶7/?2$q?͟OY=_}0m?nOr1!7;U/(V &o|7&A:yˡaMmW7po,3$R7*n_vcmnᯈ#@vBjwmhy%ncK}k+ 5 g(Q88P~ ?ADP~(Hז:~~uWO@Q@~&/ +֤t{ۗ6љ$oTy6ߊkO־%|@֍Mnt2 fk%T.@{]? :vW.h4 ~qM9i~7ci:όJMlfEmcG,U@\,Y6?8~9?gr|j ]' O<0<BainHW'# εh7W>h|XEט 痙dޖi<2ۣG My4vvT-|O< WK]FR8q 3})_^i+͢]*C&Doܛ~TZOT#IƑo(DݕɽաxRkYnb('R#?g?@O!S[nYkX*EPp>\k)O~ [ou9焆<ӟ&%iW(z+ݿ?%ؓP>6v,ԡų8x,sw WcҀ==N~ҿo>.\Yxnڬ+}/%dy,fU~ fڏWS~&T_\OX;Ŝ ȑ2@9~o-Ծd^x6ɵkFԮ\}wwpv'̿G鿳3W̚/ k':%ݭ%7Y ѷe!*O~v!omC{թkxgOƻ<PX$2+a2 wO~erl<H$QpA]5M7D.b;+ dg PĥݛUPI'*=ċz`WN"!;eD<2A_Y/ WAZD$O9$O<9|=~:~1^eO%^&g.uH{a#sFmȿ!=_C?i?[ Gv0hYwrmm8,HI9?m|Jǂ?M41_hZBi,$5-MoWCLro@QEQIK@Q@( ( JZ(t_(mF|>{IH_)XmF|>{IH))h(?e:] ?$:Lt/$|/+O JZ(( ( JZ((9x3>n<3>=KNɜ0ó)WDZR_WVUTJ(y^9 ʎہ#^k+84\(kwG0؉)Յz>)5f=k⟎5M$>VFssʬWO<vr#wgcv=ى>QK1Q6`6QUKEf(.#xz[E|3s [$:yP0"DQ-z%$(A]e\EGV'a)PQcByY~\о@a AkK M4Yس^M~Q]vw&S iׂm]I7¶-䇹s >Uryb'ᏍfxNE(.ܿgS^EaY|gP?W\ow/ N³^\jrI}$R2$Eg9(x:y>\-:0TF][|9)&_:<Ȝ0VG +~ >kxI'{r o6q@ py(Y*Fr4,D5??\YOs<=."I,Ik/_W22Rj;Œ*[ M_~xZ=dC呱;=zO u$+~}M*{7^8藗wcԴu EI$[7,*StLTナSI}xW73,v}YԒ~ܯ~? ۅӼA+YӤ]#2WA-umG[e',n#bd3piQE/484%]O̗pHY8=~?}[RmV_5Ι: 97#R:^Ey^}s</eTRec^_E֟( _8eǙ[>?fԢWYgg?|ЮKz ۗdԬ^Z{W&oNGm kkUsֵ71ǨC a (NФ ( JZ(( ( JZ((>]޼G],*'>1_W׈륏^!k?k޾$o\_%-'Q@4?[?/a⿥h_ bm~~kL+;MgtPEPIKEQEPEP[ /|?rILd0j4^ck`U!^aAgTP1W#QR{QKEQEPEPUf?$/k5xOG_Uy5B-QERR@Q@%-QERR@Q@%-QERR@62T|gEmtrZF ,C&Xٛ _6^ fM/7X돥zlV/}3iO΢U%|/@?sM}i~n# Q3?7?_~Q_~7R~3R/,OeGs 2# (OCu/Q(na_~Q_~7R~3RXeG%| 5?Q ᎟_? Q0w ?1Bn# ?1Bf# ?=7w J&j?? ԿG'aE~}cKG2~~SE5Q03eGTFw!`l5]?XXʑ#o I]oڬp^Ck#jhEJ6~~F5n4h𤥢 ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(_ 'z.TW_tz~8lBWm\WO'MJ( ( JZ((.16:(/(c&lj||$?oƈHPfEot;{bܱh<Ȇ@tl*(vtv)h JZ((?R[4Oi_$?'4h.ҿ+)O]0Y{noAѼI`tz+Fx1LмL{qJ"fǘ]|=J)ۙ5_YG˦wTLCx}ՁO?l֤z؛q_sjMzu]!idoW yU{ɤB+m?Tk%~Iw_|#[/0H|{VSS鲆^xŶ_I^蓜pp r8/~h>2W6Z+T&`7#u湳ZT5gO߿tHiCW5o/"Rk?eҾm/ |}>[/< "_!j7~~hwvVbE,EQ7ȋ`r|/EW5vz%">ggG_uD+:?&E|G0?gߍ1xCwZ|ShX]UMD%B~~>!x" E`iV ` #DU_BPG ZNs\%rjBOGp+> *jo"!i;P%aEiWs4o< ?ᎉV/Eofo[I\ctP=Zuਇ+-qoOO}ja_M~m%e'(lrEql3߾#_ |AYډc xt3; "آ9[[\ڬZ={mcM j#a~~Qm_t5-5.pFO0E{%OP߳pqjB]GTfi X0C4 rzגxK S/zM-A5p<\@Hؕ > i'l<=|=>rlwEMc%wO5o&x!ӵoCvZ/L3 ,O$7ls,,t\G~Ɵ>8(ÝZZNIxŤ0Dcd>j:2 EW$9#u 0x Z|_sOWŸv(G Mܯۤy5̱ٽT ._S| oS| Nվ xz;ZΦKiFF_i39NX_X4i'`.m4.qKuЊ%=–tkk()m?E()ZK|2u#2\{wQ;oyi_9)qܫ5ޙchT:8*zx 1a|Q~u{ !Dl V0URh6(奾i=y\G"䌣20FA| M?gx[x#%7W]iJ~{=0|r$fA(-wft{\u?rQ@-|J>Qv>i/†].ѡ$BS`VOC=|/kڶo>ͧj4jY\V6hߕM}'-I#)Iwb4Y,Oz{]oW/|D76erWNsk8RuueebYH# GP睇Y_|>wICZWJYo֗iΰ˷̉ (R\ ⽣'-'gլƟ}{٬YcFVxj$c%4?:h7w֥cW&aqn%D(TDyػ~gi|O'!=&\%g&~Xh?i^&-\\ޜ׺{ ksYc#`7FeKk[G7ýw:$m4dN) +)C3 h),^xo6Hkymd 7y1?q_ ~ ;+^hƳ_H=:--i230O_}[v;zh))h(:_/x3?V:_/3?83Ft~>o]Wc7 }Wq&jz"rH))h B(*gW떋z!xhoz(())h( ( ))h(th_(mF?|>{IHh_)XmF|>{IH((?e:]֧g_._Z\Wp;XtRRtQERREPE%-QERREPE%-QERREPE%-QERREPE%-QERREPE%-QERREPE%-QERRE:xOPxS&2@] F;7n8ta+MSFWdE`x[_^ҼMmo-:7Q8TI: Hd[^RJKfQEIAE%-QERREP˿W׈륏^!g?k޽ztҸ?,[`䞭5 GQ_}QIK@4?[?/e⿥h_ bm~~kL+;MgtQERREPE%-QERREPE%-QERREPE%-%_Vo=o?IH˲8?!_ɧO[:ʀ-QIK@Q@QEQIK@Q@QEQIK@|Oq6ة\y<Px,Fr~D~ך_+ۣtxB 3SzWU Sh҄n-nɜU+NeVq]Rkuvgu;2M/$K&A˨,>Wݿ6QExgQEQEQEQEQEQEQE1U)"S،x' 񷁧h$|sG"S Hax"UZnfg/<'phk4[>ÿhxB'캜AE;dHG_>$GrZA.b'M}_Yq7g[^;?Q^qQEQIK@Q@QEQIK@Q@QEQIK@Q@QEQIK@Q@QEW_tzXޯo=N_ ?xW6?!+' &D%vQIK@Q@QEQIK@Q@QEQIK@Q@QE؟ѢJ!?% DvoP^Mxqyemݰ`x;]`z|f1_=fϿTf,jbӞI?zҧR;_dž5{ostf $M1\Z72}ƖZ,AIw.[`ݐBl+Ci iig_)E {מzŏڒ3sb6ϩE&\ZVUc"Yb*VYчӧ嶨.Wb)=z{*@8Ab_tַCu=BI +!2ÿA,-58l+$m#+i0|IMvO%6#JԼ+?x TM7Eukp_f*X}kF_ºZ|WVJMϮp%P焓vk]+/KH_6_=kO͗J(g &|ȗC>?%1oU#]Gl(>(((j_oGx{ ͻ'p-2g#i~+9Ը\6Xѿ _>uş YwИaNelJ~mѾ*vU jw#KIIzl?q◅duΟfb1/|9ϐ.-Alo!o"£zٿ^ E+?9A |9]?T5_֟h?R3/;G4O)^͗orß*AÐ>P.?ZJl?xg6_v%jK@sECU?i E+?9A |9]'9 W ' WsekWt?T5_֟h?R3/;G4O)^͗orß*AÐ>P.?ZJl2OkOQu ?m~LÐ>P.O"Py;Wacn?c@wGɦA%ů đX~ &,O_'xg|$I"ӡ{7L'#ICуⴌLo՟_N^,խHh|A2ބaanpCS ?Owwǯ~_M(CG =c#䵶P>_>o0)QEQIK@~JdOm?m/.#״ ֠R0hQ;U p<2E~C~_W/_4M#㼧~*5I RMݸ/xKrJJA#WZg]0wZ2kvHّ ؀k78e?Lju ˢ5(VnK׷w'bz_> 37`Ֆ;IX}YbPLG( 8ՑRJl~LAhO'j?ڱ?ɅG@sECU?j E+?9A |9]/9 W ' Wsek[t?T5_֯h?R3/;G4O)^͗_rß*AÐ>P.?ZJl?xg6_v%jI@sECU?j E+?9A |9]/9 W ' Wsek[t?T5_i|-?؏5ynsXM>5)8yϟlf:!,/Ym-?ELKĚڡϒZϲ|hë[~_5.e y*0 :+mf@(e??>ʗۛfUgʉTG8 ^gy3>荡|"𭦁 LWzy2L@f v ))h( ȿ#Zȿ#L/u_w?1M_OW?dž߾ɫʇc +=P H੟}~1Z/ޯ 'ހ?))h( ( ))h( ( JZ(t_(mF|>{IH_)XmF|>{IH))h(?e:] ?$:Lt/$|/+O JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(|g?ռ]ܺ Z"("7)(jɜb'1<}K -v-mѣMsp#0ܐ3FO57/#M"^Osi<HV(KG*rH?|S.F|Cޡq;ZiHk1)Сhzr[5qcW,q6vDWӨ󴌧A040UIΌ[n5\[BU*$-嵶gu]z#HӣT%m :@$NCijV6w+i˨P@&ݚ0yWαP蚁3x#\ yato6L#R"~4wOMIfba}:q1<U7r敝U■f{[ԯ_,nn˷__ WL][ DWEq?,Xޡ3'f2|Xe$WGo"Ggq>=9{K l !N߼!GCws~hES\kh4W;&=;{.A8lL!Nd]Kk]7V'1O9NE6}4۩~'!>k_]Kc_@t3OY#CsOq"d8Jտ xT2BQՊ&dtH:809O?f=+JSӴiK>dyE|-,T2IM_]OI=6q)%{{~8XT@r#MHxG}?A_Bx{4˝ȗ1_| r?3(|)!Dc3ܞ™J]nI4 =_G?[_KKF K.>VTӒ~nǽx}~|^ ௃,y}66n؅P<rH&A_ C")u&a]ѻ=}opRP3ծf򽏚sׅNRoh*vZ|t~ut 68#[]2!(șvQ?u) ]X66r3,:2Jz; rϏWZkKv<~JXn*׼q|~b\ ͒[7 SFQ|魚Mg bꦭt=uդ|? x#.!ӝcKvfP)eS򓃐9>Q.F"ԮgA{jܳWY1q㗶^OasMm#Fd]v228*H=A|?:r$-t{}NtZ>t+VˮjiQ\<倖dvoG!Wzɪ67:~WW 8RzQ'Ҿw{i6vq ^Eh\w9` *ܮ&iR䮓'RMEG 7~VLq[H2,[r\(p s_O{zaџʿ͚\h]0$+3x}zGe]C>&+ywl+,s;򝔒W3ڼ޾/4QF9B۩o{[}^\sTk?QEyyW #.~ƿIos֝]W%lf;tO伷r JZ+=( ( JZ(( ( JZ(( ( JZ(( ( JZ(( Uwb_E*X;wM &D%v|4q_脮ڀ ( JZ(( ( JZ(( ( JZ((?R[4Oi_$?'4h.ҿ+¿hx?}G׷x]蘍.lʿ*q`d׺]8?%1oU#]Gl(肒())h+i=uxq x>SLERG[+MBnk!J<-%|/~ٗo=cxj-Nkqr2ɣ77ů|;|A𝽭֣gqkGz$%gQm7Z驕օucJt]x_> ? a[|5uT+wH!bYnW bqԚ2+bV3z8J%~C|>1.⏇t)C \̦5l%)9cE}.WƿZO/jW]"YNՑlFrǚX1;r[<6mFYQh} E~i~7?l;HҠgy%4ѐ SĻqۜ6.,J$eq,sQS% :;r>O6*# J{|w:YxJZuk^M(Qn!)ʚ,g9{t7%О?|-]?Iƽ!) hgmÞ8dҷ/j+g~o韣W??| I/<#miu%KHvL[qrF㏛𯋿f۳^sm+D5Iior .ƒE2rhdЖ"WPfiV |N߉I^k:^+ƋS_zu :DD8VF+ ~K3 =^0м?{xy-;[YZ;[&Qv@ 2=d0JV nmG5 Ί+M>@<7:]eN[{ioykK z%s> ֮IWʉsiR`|W_M#AC'-f&.V:@kbg*tEcs:Xx)ٟtW~͟?<7[z3Gh/u, dyec98k/KRxnu{.$|۷ F㏗ ֫^Xx|Jb3:TƼo?c2|xƶ6wZvl[392Ye;@8+> ~Η^:e|hE4M&*h09Q ?U_ :K~hE񵆗ggGOxɰ2(#W~Y mkew}>%rI,S9 E$M1xSZBgJT!|'U/?5|fKmB+c yWkIq"Ok>=> G?:- \<,~uFF# 1yMjB\ΕjR O]D7x?Q}Iroƞ3էTH1GDJ۳)݌c+X[ 97Whϯ;;ZOl4uY++D!x<hCv7,B:|3DX4Kl;9}| 6}>l^1>h+U桪%F#h%Kݘ$jUv>.tk]>[;P\G6Tq?-rZ0rW6 /?Q(~$k /ԴthcGxs-ctcs~niS~t wRයYV9 ]2#ڌK^)Vk/>ͨЩSxV<%JqƮ BΡ O<<7F[ũ:63 ڎWEGe( Ѽ{IHh_)XmF|>{IH((?e:]֧g_._Z\Wp;XtRRtQERREPE%-QE =wTa}CqGe5?R?xok;JJ1I)]\f'C+InvIzw__ˠx"]dxdЎB?RW߶K=DW[QjOCn=_f3RUp_j4/;)EG_SLši8)?O%v맡?vGncL?玑k+~7f? G&z?[_6' <4o;G6' <4o;_?K#vc@!+k>熑jdh,X$kh! kk>ѿnZ{)_o6RInqʾGkgk}N>r\:3JN23q_UR _zͧ|)p: $OPád0<2-3GP3|{)FNVMt|{K*eZkߺ+>h"҇2|%%O\+z}z29SN|ѩR髠+#@okrxk!'iZuڟC- UtEnȩ5= Mq rъ\#uCףT)q d߶_1,:4Hq}N߇bogc$NHrۏ$Nz|,007)')Eٽ$W[} r ,v78IF EF/TMmޞ؟/?؟'?|E|F>!kk>?熑h熑k(ݘ5AA5ȟ@b~пHvb~ПHvm EO??hO;??h_;?7f? G"Eo '؟/?9lo NMm0EO)tL>%j}5+ϦX-I") Eve_-%T}Ar<<'tIpX`Ҿu˙ M!xmEd1ŹB A2qI_p0`2[iŊ6KؔS=3#c pY."}V=# 2nUd}Oxoc>)(q"Ƥ `2Oa_"_F_ ~ex+8ш%Ny!^-MG!AW:.oNHm kkU}Oz eP#F/_|0,^ O-4ic9dRE!N܎3rj_|D_?i,WXK)Ht =Lb{xwVk}mKo)WF<_e>ӡe]yyⷝE6#;W_ٛꚦ[ iW^_ RKȬ[@f̒ S޿GvՔjSotGKiEғ'ɢm_{{ OoYKv!ӼG /H`EGvW92 CGď zwCO]2-:ijq`ϗ 2wg#PWlm`kC?u>8jZƪl,SZmbp19_sgZ|S}Z5DoGc#{M~-r^64I_ y+xG\Dzk_?ɞoEW ><3 o_iKq0#*yוW?GW:nj{hkx&7wRK 54OEVQWok#gR8JnVv;ڻ⹾hZf.}GU8 x߸c=ƃ;#ž#rj$D@ -/a{ ž%B[M:q4$QT)9*3ϨOmo^,xq42ChѠ=I$O ,MJNou$Qv-}OiRP7k9J+Azo~'.]\4 >YMPM&_\В 1 b2&pEy 7ᗈ/&|=t$.O|L<7l9s>*i# uCqiDRŲE*.NH:&_)FPSKmka)PQq&]e|oֵO j_t[O?h =2[0 >Zt;VX5i#0; ́Y _qE(K*6{GEm嶧pI<:o~F3|oIF6KYw$cdW9v=e;čv_\I/=$DrޜV>|E"o VpkpȬ!;>ON_O[_ϠX#dh^l(*$s@83%l,V*8s%̮kk#qnbԜݞ՞! (~֚ڒj睋}7ZDkxNmd|ճ=2z?Ku>&3ĭ]? +m` R6\ &Icж@3x:/ x㟄o [4u\}:m"2(f]$(UC SV۽TVW-l{'^Tڍ:zv]^_D: x3b*:v"l}jGw|YyϮ$zk?h]\ZJ{c2n#^T] ėOIg[b>#V6.c{8ڊ,Z½/{_GQE~h}QEg !i~W"55lf;3O.AEWzE%-QERREPE%-QERREPE%-QERREPE%-QERREPE%-QEbW7CzV??wb@ӗO'MJi$¿ ]RREPE%-QERREPE%-QERREPE%-QE$?'4h.ҿHlOIo?h?][QEx_žQ=ŤZOvō,\$+XDσt3KX+kRixC\ZbzXܵ*ar.xkClKZG7xUc3ن8b2]?kx? 4Li@E ȱyjڃt:wGE*zt\M~K0 ץXiƒ%M//Gdzve:D૶HLִ[loI 0tW glzא?|Exk2IshG%΍Iv`p '$Y/~uvh?[' ~o'~+?*A "_!jÌe[~H߄]Gl(>((( ښaΊ.:. pǝ ):C}(t+cͶw8\T)j#VjW}΁sg5 8mІ\ɽ=X/a> xNOt-h9>B6yFry?Q[*K W*Xʕ0<'D|m'Ƌž!{fN]20p9R##<k,~98u2iP^xH`HV subNiZe#iQt"QǷűQaWW1<Qh}0 -+r\+qi^ށhPmҍ~xX|xc]s/4vFB*`1pO#Oq2jXIq n|)T0[kWMQ+l2:S'-?ho[xT5֏-VmU+Hs09>aj?ٳnҿg YѴj}i5b(cBKg9)hir;hhFQ.Ix~Ug.V_3"0s1}k?c sn/ė:̚h"US*If$1z.#WT~v%u}?>=>ˤ%7>|1I _?`_ |Jmޥ>ӕ8O: 9erFڿ@)hb)ta+EN[-R8KG]okk(u9tP̥K$FzW߈{KckHsI8~F4P)Y?@e+IJnWq[<<Bx{{X4H3'ÐgJda0|' >yw+)o3+db=q_tRf5IΔbpkEBne4SFn|NrH2cj|=RE֑3kCn!s0<+JZ(5u+IqVQMQoošbmf=J޴D2#}e]ƈ|>JR_3F"s6|}k(cYV—{w#hkW5-d GߐU9F>iߴ/- }V-rFk0|ڽޖ5Yb%/ဣ^G|3:wč;wzr\ 8Ѽ"K+l+|?]{{xky |J~:'!hX%RPW Y14Z/wi:ƶuq<)s!mfw-ژc~osC{~_{|Gg ;Z6|XbYYb-19{7(~ )զ{q0J)昈Qt#/uӨe%UV} >14־_I,* ə&R@9)_N |;_^^M^zj4.)`q_aJiTh#-VjHݣZ<~!K!5;Yd ~cíCY|:jA5uc{ <:~U&Qm_x 5{ˋm"! č!PK䁻gKSkatek]%V7|3E_h>ktHwP cqPHk⿎f,mY6 o ND`5E<6e^GV'1}*Wi߳;M%zOڮno~BJ7>ШB_P]ӵ?ao#~Yl2pnW3i𯌾)k/čxwS\GYZ7uS0Y]"72\f֩Wv< TO_ڳ 4/> wLnn˞ 0+|%?[x3]Ϧ$גe)['@>9{t\߯QIKY~uB_g}^~uB_g}^pg&|ȺGn~K&M?dyg~EC1QEQIK@$?T>-MoWCLro@QEQIK@Q@( ( JZ(t_(mF|>{IH_)XmF|>{IH))h(?e:] ?$:Lt/$|/+O JZ(g 17ǟ?e?xᶱ.YwFtY@/̤}I_JگB[tUncd8$:~PEs-Vo 3Mh׷sݎ t.}p9=2_WďKc*SO ! JgTUH@w? u%(/¿m"% ,XIuXZb{} c#(`~ӟuD_$gܹt8?u_>k;(i Cpp1+|XcqU0 N<=oScƭJP-l?dO$^;xF-Τ/ۿ?_UAo mmJUUSQ^715y/E/-))h#*8mS;[i ;wk?[\[(41w/epEW&|)'BiWeƴeԥCb3ĩ^l~-ymo+/B_\׿}5MܡVԬn4Eov*ve8< _߲i`m'Ny,4q30vuĒbV8$~nYljЭ]܉t^񙃥^k+9EWzaIKE% C(e9Gz//hZ,#_5ѵط3y- U夔|rpA&O~`6i[D=#/+履滝{<.`b*^ݍZJZ+=0(>]޼G],*'>1_W׈륏^!k?k޾$o\_%-'Q@4?[?/a⿥h_ bm~~kL+;MgtPEPIKEQEPEPIKEQEPEPIKEQE%_Vo=o?IH˲;ʿ_+7$ueYǝs_(Q@%-QERR@Q@|iU~ÿ k?]oL1\m=!gŵH1v9.*?4WRÕe|gw+~|?O1hT]9Y:ڮvk&F!  kʿ ^3;E~@Õe|gw+;wٟᅟ^_3\$Kr^̎' 54P_œߴK %6mS+W~h~ӼKY`6ϖ;=2w21xzʥ9(V_7G c'ө'.oϏF*,e`#3%8YRJQwL(JZ((( V3[]#HE t8}eys-K_ tm{R[Kx$!'d6 oM|K^_&%wmjs :ti_# xTΡhi?Nn.h d2LvmˑʀG_ߌ_+|:FG_)aeUXb 3E)nSɔYWzEPIKEQEPEPIKEQEPEPIKEQEPEPIKEQEPEPIKEb?W7CU?wb@ӏO'MJi$¿ ]QEPEPIKEQEPEPIKEQEPEPIKE$?'4h.ҿHlOIo?h?][RR@/GO~Rբ{+}2"* Ԋ{߅uM*{mF6;gA2v3 wI5HZ[JH_Y|q|W:jY/}1ky$O:vYsm*RV@UUN,jRI_OS8 Mԧ[*i4wM_C;R:arO*nlya=j6:Fb\!ՌXpN1-s^;{qw_&ndt}F#ԼX<O oljK_!U ]c#*ޫG/ Ⱥoۻt|l~):Ԗ^ౚ<ԌZ֓O̗~ּK4M٧L0đ0kص[Ɋ4?x>$(,RG3'ï>9+l'˨C!Ɵeo IF9W??v j=MBCL6S>vn/Rvzժ[^"|\S +3_Mj_3Xq2WkF_Wh佒.]2$q@ЅgϥWv_2L-ZyjXGfU.‚FH\[v&]L-NaG2 s?/z=JOKsiYrU]T2p9{b #:_+Qo1]ɴЯEy>4|ntOxAе@5kk>[\+s9@ ?VRҮb]M ȧVRAJZ[5FuAWS[3(ɿw3@wu<;C]MY"wqg˅Yۢ'?'ď?G7^ӵo[x,e[Xin*E~][X}y"on$ , ~4j| 7&!V5xf[@Xg*odu=fɝ $殆 (c^5olVam}F+Hc83*4[cY~ʚoƽ> ȅNfɦӗ yNrWq^-|v> i?xV[mml[L KFvԃ_X~3eWUM4tsٯ^"I'|O [wo y,CmyYM!cɹ4 l.xীi|I<%[YD3A|ɹa 힛g>Om,J#4ݛ*I'+㯀࿋3J{&Dɪ-vnϑVwsǸmL|q ry}F1|E63yN 2Dx*xM€:/N|?a|NuVwO æynfu@PMk8U*X(?BxÿVwvtoDIӒu;˃nHv/}wR #F!/74;WҟHU(}>.f;VŸ?ਞ]AgeTo TbI 2u1%s}/(,O{0[ϥm.n l+d$o 3^_|K⏈GWv&VxVKb|i㴌PEPIKEQEP_ӿwtK6jfJ/ELHǓ.~s+D^4ttf2_-Xcz{IHh_)XmF|>{IH((?e:]֧g_._Z\Wp;XtRRtQEa&oi?[~WLſlTCV?q'Oo@ F<?_|%Ѯ5k\{kfDEpro#<x?92][B7)V7GkmXwk0ƿqS /5/_Z=h/5ac 9[c##p}>m1{%E=41q>9!c#j>(NեNpOza|X{STCQ-I!h O<''ÏY.x~-magowb]KMvN7~8H?DUɞȓ1| `?3hB?((((`Oz~QG_S (s ??G|w`SIdK4$w?J\w'#7kj|= {Fc{?DpW_(>((((+j}Y|G_n~Pn+Z?gOHgEPE%-QE~{~߾)?x_Yy[%ۯrw4k;.>"xzhVkYN`lmФil0*pr9&~$~ܶ~"?$(ym4 ]?Wγk PΎ}wQ6FUQINph$^rkd+y1jNJVt]F-MAcykY海vWqH'Z4?&^ Z[5֭s#V{@Kqɯ3,_.{(tM,zjMo7$ f=: #RP|ʏdÞ𱭇^rµ_KE;%vұ2]G:4o&ܖ},0ԼiUxT4?\KPMW`T  NwS{7So]\Kt-e!6+"?gZ>WњPdþY&Ҙp9Ͽ?>,xoƲz>WsȀgt0o8U\YbIs%y;[Eє:XQ%&zW׽>" Pk.K]Պh.oefP9g$Mzپt(`4/-9]*Vr8gew~4owi-IKdIZX$LpʦHj'/u[O×1Xo"kW6:( 7=%* sVԼEωb(^9P9y9=<$\_|^zYǫrToI{H&y_K5څQ@._o^#?WxEz+MJ{ȳo_}z|./G4QE| AE%-lxŕۊ~7 'ɬ|3nd5&x{MWQEQIK@Q@QEQIK@Q@QEQIK@Q@YO`'I#.<k&Vo=o?IH˲8?*E%-QERREPE%-QERREPE%-+/<9TӵSC YOӐA AET&Ԣ2qkF~RF#x2kүtgbi+wp*M=f'Ş̂2M~Y[َ3I~oߛW+/f8({,*PH̛K;/?Փi7QgMW%+ʊԾ'g~gMW%( KW-_ 19/7?_*j /GT_"|J2a\Щ$E2SUIK2NA K/5_*(Rs_ 9/7?_*j /GT_"|ihRs_ ӟbp?eB4gMW%+ʊ?Ԯ&NA KTɢ_^g1I_t. ʓ1>)3)3~jz-{?aO/J-MJ ( ))h( ( ))h( ( ))h(!?% DvoWCbK|AcF+ޠB[ᦙZE݆eh,R.) xl/#4OGM 3C>b|998PӫQ^)꼺uMMh ~_|:oHE w!'dzõFt?ۃD\i]ɒXPe9Q~!xcᶩkt?NyYZ(R.0W;֯y/u/# !'HS"Dy8 VT| 5JF[^|[|MYc@:+_txoyp\jI,vȲ4*Yr|<3 a-i&ߪoGeؼ8cyPM;zK͗ZSk?eҾK?Yoo'~+?*A "_!jV_8KE} !'skb֝k{ o&+w6-i:J?8uA5oY^Aq]ʛv 7#+ A|g|A?ikI-חך-{mcwdIִd+^r~oF/Aoſ8s6Ӊ"Pv_f'g]#W7ke?6S2J>! ۥ/L ?^|^~n|au0{m3ORi"%S$  K[᷈lefKc]1䄊)r8smVg3)¯xZѧ Bmj[AMqU*.N4<g6L+ d!k'Wu2ZЯ`tAsm" $BU Z4W`ož$ռUéf{ۂS͸Wgs77vo L`&̖'&(~#F*7<akmT[1㵝V}=o?? |3X[ $m]św&4aF帜4#iuOX+p((JZ((,|M*?o/ &0y655~OÚvy[su2iv}ؽs"dCN7\ޭmj K8T5}/e?SVkjOԑe E~2M?dr9bQf2$r+6 % C?u ?ψf|Cuk+' WS"873/MC˭Km+iÝ0GȧPGKJȰ[ZK?6<c@Uzm8Qh%?ݠ /"?Fe[.|ey16>Z뼳±?n'G~.鿱Gz~)n}^),\ S&!3/̃*J}*? |8i.x‘k7D`]^IT[q|63__IM >+x/k:^}_ݪI/[N@ `ٷwGO]ޥnɫsO(>dg Ęl_scďKKVjzKG$1D"Ԧz?ge:)lwko VҴTJ+ c iAT1΍ egg4sʵ̬ w /ſ P7]M"Iωu=_[YEeSŻ9B#w܄(3{x7?1S37&iui2]]@yq uG~ gl ?!Γ3|^י4ƶQ8nvUϺ G<jG::ub7W (" r9'J}_ w&l|<<>n6'cXpg\ "_4/Ox<+߉ool<$O!P Cdݦ/'Pį= M˟|2{jRroR;LOa_%|OG/?j_4]J\K\͂ҔUEEhDl%FILW.]'N_ |`hLh#l#E@ZΖ堻I&7ߤ0F?9#ߵAOC=v5.Fk^޲HXe3(? ſ^Eۯbƾ6S_: t;M$r%,Iɉ*yB_7ïڝ[vɤien'JԞ1_0?>j8-/öikX!n'Tkgh} R@x T4=rS5o.D*$1 e_U |*o\ύXui@wt==hA~4ٿl--'zڮ_rB 0*Whw}\|*j2[Wج@p9?4OVYחΤn qBrAę~j~߳W[okK藇GY+wO,lo||_ڟ+I(! M[w#i p,OJ/_?V$H{yVtk\ .N;|W|2lt[;?ukH>(5A>~ѧ/2xeuHNElK#3ךxi")6\q m(?@k̿_MbhĀiZEcos@{|8q࿄ >;G%I$!,+˻1,ǭvQEQIK@Q@QEW?j?A7Z2a|ĺt+1h߆5swkoS38\O,dAS'Ы]C+G^<ֽjt\+i_ `}?kC^oT+k ['Ame&7ڼM|x?F*3ji% V.啪ԡVBJZί(W :ί(W : ϛU~hMd|ɼxg,;?~9~lȿܨ? ( ))h '޿H੟}^1Z/ޢ ( ))h(?( ( JZ(t_(mF|>{IH_)XmF|>{IH))h(?e:] ?$:Lt/$|/+O JZ(+7Hث􆹿;><'ώtKh%[-F+i l E21VG9j!; MCNKu^xDM B$D68x#|i@ N3 ;Q ,gwo uwXx/Ln/4M}* m^=BK'aج1y n?&NWm^aej̱:3 l1]lm`TdfEln%y-ƃXU9 51?QcJ`1kģR+C}p yqe$L\HK JZ+=3$w?J/D;u?J\w'# 7kj? {Fkc{?DpW_JZ3邊(JZ((՗WuF]Y~CKG^,) o& oᾄ5B[L2,0.cUp3Q_a0V4+4V{.ڞI\烵Mc[𞍬xz\ڜ gLFHt<ׂ0o_|WO+úWK"h ]rKtⵥԚ^ӹ\| mkzo& sᾄ5B[L2,0.cUp3j_z=X5;8& Fr6#Q<F5ݬ[맑pBUA^'%%-u~9_xviFh q-~𮞺Wtm*ND6$)]$=k^I#7gƀ3I=|yCZ<yo+!y8`ѥJVm E~sϫWFnݕv{o|/ZCa"XKwQ,n彍zupD$,k  ctW'Gj'$gKkYx;IYU.2)կ:MJ!"~ Gz߉<+j77wH8]zF yf,j O/vpWh׊|PĿ ]O $'M_fĐǿn84#(уm_k+_ ufۧ=KB/c.I_ȳ-[daW'V-Ʃwxz}i]/^( 'W rԎyڊXgt:ZmǑ//u x#I@\ZGJk:Mzq,Q(D@UF)SRMۻӿWQE`lP˿W׈륏^!g?k޽ztҸ?-x[`䞭5 ?( g%,/W_KX_-;Mct|'ɬ3n())h(())h(())h( '?IYv_o\Wkf?$/kZ( ( JZ(( ( JZ((c9$ӫG EPM???Hm?nO\~7'ӿ/KϬ_?C?[g? wb$t_7NX ?-dW" sK$~\7'??g?~Hf_ӿ/YȇC\g? wbk;1__ٺw|/QϬ_?C?[g? wb$tRfGEPC!s?3k;1G5Cz/;}b(ӿ/YȇC\g? wbk;1_?ٺw~)t_"G@(!9Go5Crx?Wn> fGEPC!s?3k;1G5Cr;}bkٺw|/Qk#z?k_ #x?Q q<ޟy+K7NX ?t_"G@(!9Go5Crx?Wn>ߵ _;}b(=_ 5/ s<ܟy(?oO%Ϭ_ٺw|/Qk#z?k_ #x?Q q<ܟy+G7NX/n> ZD???Hm?nO\~7ӿ/;}b(=_ 5/ s<ܟy(?nO#Ϭ_—7NX ?-dW" sK$~\7'??g?~IfGn> ZD???Hm?nOeS|`6F?_?ٺw~) 1EQg"o\=eoA:KJԟ5KմrWEnx$Ycqꬤ__wč_G'Aߊncf%#f 2\z?e9UK5*mYi4hUǖ7i+mtw]{QY%-QERR@Q@%-QERR@Q@%-W_tz+W/o=N? ?xS6?!++ &D%vQERR@Q@%-QERR@Q@%-QERR@Q@%-؟ѢJ!?% DvoPIKEq_MO'Y}J4Ƹ,,]3.@=~C~}McyizgR8h݅=ئiR!`zbE*~iO\j$:W9%nm;YY'OJࡆG6+.}[>'08(7]?@3_Vzݶw4q&H' Ew5ǁ?x;}2lˎ$\5}jYMQ{?P"'H;s(FIxw J1 q_쎬?UO>m/ |k/KH_6_=+zoo'~+?*Ak "_!jV_98KE} 燴.=OhKn<$uD9S]_'9W򺾜ߴoƈ>x~}3-oWDnX-QS_4PUigj?S]__wms|.4Zt,eg{w!ci7+YWR@7k~# -F^jgM07.i|ۻ8+﯄aSByozڔzB:A%A`>:?goKGuau1 /p̤w"Cϳ}m6m6F !;挂̀c_Gaªu偡t7}IKE|Q@%-QERR@Q@ lAė^-\$bՅߗBrbL2Cp/ן gW?G|cv|3F?.[ez?a"H( gF].[̞V>ecHEQUnSS//Len-Z)bhrt*cA bρ^{kW~1fKZk'vFFDV.VT@O$k۟ >jo^N5=U7K C [~%Ջ$mE6,Z9SItз~Q']h^=>"i:}ݼVZv}i8aJIf9& o?i?xǧ0F,NTrQ쿵]v&5%LwqnM̎A9(cثUN|QFQ Ѵ%v n|.UwߴPjh'o4#v]G&cRr0}k~!sFS'E4}&-ݮmZF\X8}A$2 [s%ռ$p=A1'W;G|9ɠjޗ{eiő%/s1H4נǏ|Qkjw6uWt.0/|+7]K▫5ׄ{k;BfKh!@6DZFv l5d}o]Wc7 }Wq&jz"rH))h B(*gW떋z!xhoz(())h( ( ))h(th_(mF?|>{IHh_)XmF|>{IH((?e:]֧g_._Z\Wp;XtRRtQERREPE%-QE7S,]h}B5f]dv))DhP W^N4ZjI՚~aXZ2iM; gyݲQCV/wIo˯|5Oo%ԡɉPCEnQ(?c CΑ~'eQ^o,t]'3Cnqy?G=Of%Q-L-y&IB ?W۟?g_|hP֦Mj7X3=V0>XԞÓL+f-GWYA0mm&I߽?k%ʜe;&ߖ)Zn;m9| d3Ƚ$vWRRׯ*IgPp8+$AEVF\Y|IOvMSMQ?S]e-]*R[*AJ./f>>r4m +-2:$_cxᖥ10FV@,6'/EMt/xWS1+f~"Y98͸2[4ioMʚQRRRIY=Nۦg^ G7O??h?/jOCnQTh8:~?4 2Wxk2c@'\ET+Q1[bG̸Ϩ>~>%뷱(dɌ6?k~м jZitb(Ԗ=K1%I$/IdxJZ5*O1[{۶۳8^qR)B.ksYJRKdݒWc ( ))hMg5~([ fqxve])b)<s~qozc 7Plo#8M>܏N]8\ zxy)Y4-#hBM|z񜟳++J:sD x/? 6𭗄<-m{5p^YߒF'c?oO||ДK;K˶\Iu>0]tEzњfV 8Ee O֤_Š(BJZ+_ڏJ(BM'H"aoYLj[b l`@zQ+/~I kjf'XȞm, b&FH}g}8%$qi}^>,?+6oqEx'KTagȐQ<%F g:潲oBUZKt}NJN}B(sp (>]޼G],*'>1_W׈륏^!g?k޾$o\_?DhJZ+~/+/WCoKn({NX7bg}_?k?L(( ( ))h( ( ))h( ( ))h*xOG_]y\ M?z'~ep5TjJZ(((JZ(_l^l&sh-% ۂ׭}AExw/#1|'IM-asQL@ &Cڽƀ (S7o ~:ΥhR($(,I$$𯃟}V," 3dѦwn{V$-{)O&a+E%Fa"AgEtRQ@Q@Q@Q@WU|ksͬA=Fi.>'X6s:3M:rQZ^ryZ.s>q L v9ME]*iWiڝVRXR:)j)Ef4WAERQO)&b QO-m+NW;\D0pAWRm-os (,(K/xSQKۛHi^hh5 9kJt7h+:nUGmA;FNp=M%fXQ[$ك}O#fݿ7Ilj5M IԒL &?1@qR<+#tQАmbjQEQEQEQES.$Q*}Â?~W s@+x!7# ( ))h( ( ))h( ( U?wb_=+X;wM &D%v4s_脮ڀ ))h( ( ))h( ( ))h( (?R[4Oi_$?'4h.ҿ()뺗z?fwq#!d&ebPdj1Ox3úǟ q-A{-.pI>CPr1}ci\i xWVSA~@do\x'T_ Zl 7H"pȳ݀X peU(×]U>}N2yr$/^=O/xt_]VpmONcYn!?x2T }KO|]m<=4e=2=ԃq__ k/|)i:-/ytI†DO11_W:𷋼C[-R/ <}t d֙ [ V⢣%M꟧G~N2m/[Dz[l}_)~_@5_VW glzWpG>?qM/EW5~|>K_!U ]c#*ޫG ȺQIK_2}QEQIK@Q@+~vDx,,QK~ H˗ʰ O?¤wyZ9Wm{o|!|=kOjy=غ\F%2__˷SޓX^_ȚrRTE,Gvc#O|[ֺ|֗3Aqۡ\ %ċ?hi5tXl!1&s^1o|BӼUuM m%"eNrxSWQ~[hZGq7/E|G>/_ oqhv18я0JC ~F뺝ʱ"-t*F{C]okI5h$xa$޾,wV4@a{Hdh8RS[4!{GO3:8W/~Uм+j>eqq% :pGֿhRHaMJkfp2TL z9Ou}(l #?+-X8Uy G+p]CtWߏ?>kt+XVwP @ϦkߍO~5|N~&_LX>xی38'"0zw**r/s[heKT=J⟏ _kgiqk$*q$ǵxG[HkoZvymF!IsDž1O A9_QϿl>x-7TWw٘(+Izה?FK6KM.k0$ǘn%p~lb5\JUሥx5?is߱_56??,t/G̀Ίh}6is)vNs7lƟDŽ|?x*OoU#TDRAOe#kOODm Ь>WKsh5Tnd%d)e4N~Xrߗ>]lϊ*O=zm{.#̊H q*Ü}mN|S_ [Zc,q#Os-cx\J(g7:IJMeMn(9ml'~j 45Ffg&V ^c`2w} ,ZJ5_5#kgg x ^44nm{k{PrNM~E xzjG%ދ²Ѵ<)RTWÿ ?~_Bi;pIk,A,Ҿ"u O1[iTh& QUʼ{t}2Xњ?{L><]ֲh~ 4Epe~W孟Vwp]8*s=+*?'շv)bگ;~$tEi6'+Be!PI`g~|(V㯆Q_kMeedp(,p 𞳬_x&Y 8S+ _^>7mpqxOIBmu2Zѣ<65$/N~0|(ot+;F8exRC裊X_,n.hH $6Hߕx'.|H>#^N[%kxc#)ǀ?w=+_l~|9wRjML<7H!wID\\D@1O 9~8O^6Ӭn-iK̎Ϳ͖RNTcShBoc]S:4ZtzxtYciwfSe#_N7i晥kkk B* ͜ZTb{G]v)QuOz]|s:>E{&=s B) ,evySEw=cgU4"Iʓ eQl_[fi-?B]r}hr*0C:C2+cM tR`a"UW)w =[._ףmoTwUB/w JQcI$ϵX䍲O!?o/3xkMe&'QJ63ܺrοA=Is<Jۀ~'|I>#REIKXY 5*/ؿ[x w “qbI[SXtѰ(eR/ ?O e MecvQ[s_gqij_#3cqqzTdPѾ^wK.OyXx~Fuiw,@KRF d*7@>26Z>6^m4̉dE'W|?)Vz5)qo8~(<<37^Xͮ=_CU95\kM◻Gܬږ.Tcٛ⇈>3|[[jzfKYFT98k;oo=݃ie_2VXf+o1޾#-#᥆&n J7O$* Lu_;П.@x|yxHl"IWmrwj05k9U_>2-!O_nڛ?|kgags߭K,hPį)''?i~G:m$?hъiv<&h9g=s߳o~>|93kI݋h"FY>\ikH:>6y0o}FХrI*Hq6͎W56'=[Z_5խG4 @$fLbG>OZ2y.F~RqZ߂4V-'҅[ym<,H|_e@eOk%x-59,y)]˸g##4xKkAQŬ4]ODUz^|R׍$ci{iC PN@3D|Xe0mo)Kkt~ E|I'u++w;=baFajNO5P Y/ VqxXd&J2X^wcl,,t(42;KKTXh0 tQ^gQIK@~uB_g}^~uB_g}^pg&|ȺGn~K&M?dyg~EC1QEQIK@$?T>-MoWCLro@QEQIK@Q@( ( JZ(t_(mF|>{IH_)XmF|>{IH))h(?e:] ?$:Lt/$|/+O JZ(( ( JZ(ߊ~]z{pᱶQ-ԫnJ\7;($ Uo8uz2#6 Q aB/_8C١I_Y~Gǃ;X;RG+ yc~U?ō~ Eq3tY#<xJQ{h^fsQEyǠPEPIKEQE-x/5]4k3wpdi8K#$dg'wujz]nA'E`@o>ץ5NOo?N#j2;>^gTVvk)yc{ ѝ$n2& JZ) (|L|1Cy\M8ت,aA*&A n!H.y:1m]*0$%~|M~h15K\*8FQ~XЅ{Kƫ?#gT <,?BT}Ѕ{KƩcWh{"~RW7W?ߖ!^R?H?h{"~Q_ߖ!^R??BTcWl?D Oo(?,?BTcWl?D =W3_ri~ |Yâ^r$T6ŏPWUQ,p8MmJR>.|] sBöp`te{T^ RSG>XpkD`d򧑾_,y 0W;YX.<{wF]xM3+ .Fy]vV% *GDQT`N>9}9hoM{^>g8VijK];>N,EaIK^kgzu5ovgUnM̨1Etpkыoч$W߷拖6~_v!^Ry-u/HY_7c7,K'XH8 +o(uG!4}SXmusu\7kh2G᳏P뺋k/ 0*rM4l}՗¯z?6̟4 ^_ߖ!^R??BUb2MYʤ[n߇${43,58FJܟ"~RWߚn~ri %s|.t_m;DӪn72E/IE6y_RKi#Z*յWS$G$leYXdG X%L))h˿W׈륏^!g?k޽ztҸ?-x[`䞭5 ?( g%,/W_KX_-;Mct|'ɬ3n())h(())h(())h( '?IYv_o\Wkf?$/kZ( ( JZ(( #g_mDq?lK^:?k~*M}~WSMo_9^N ?{㥷G_w')~vw WA`J>(~?mtx<%I,`/&e2#uDF-a?W/|^е/Wq!u7)뢰 v~_¿_Rïi^֠4*PU aP??tOjz^'y_еUjiaE%Fa"AgIK_G QEQIK@Q@WWzγ &կg<<F q8C $8:T-%qw*CGM~|}Əm ֿ[iHqF#>#~ɗ>,>بt"WP, F 0FMrdgY?u< +FZ28t107Wk;Y5wu8,X$J6֋Ϗ5b:]>q-3?ΡM#Ve;IF<v1_T|_ᨼ-iѴm\=Y[9 E/~9 qʚu̍FYl;Xcq#^ O6Y+7ngѽlIj۲WZ=mckj>Ws| ꃌ+/ǫ36-+÷l42#& <6F'$^Xu)gev]hSf*sMV-^ʹIeQ[,RE.R9E}`wx Z]>721K# ;|OՇ-KHL#4XB{*6iH~\yS_,h<ӉoKvdd,Y`NY̷/ĨܦmcV0ch9Cߌ\oQ-߂:]ߏ~$Vo~a/D(U%QGANI~|>Ծ&ºsyK.dz@kWTpוݖowot]Z޷Ke]jRtin%bFr5k~u{KDFc[H"Tp3\?XfG]"OGlm +G:[xW3JxkFk%,,eXr#i;h-O3䱸ʘjr)s-uu[K wEEZźU` 0d;TךO_:^vFחHG"@D>5oğk7쭡kfWGUȑp !Տ>>exZ>Ǧ!45" e1Ot2k*9B*Vカ99"5N\nSZ<~:x\ xu LC0s7~b_n~ ^ ~;ukf,faјJ)S"=/_Lx6rx=>2|8æEG8$gxPhf9s*+qI{/y95wֵ]͎XwJq\s$@R8PI8kjԔ'v>5dQEAaE%-QERR'a  W~4[bk?,/ҟŠ(p())h(())h(+Wޯo=Ub7C?N<)`WIDžcj())h(())h(())h(HlOIo?h?][؟ѢJ9x^Swi=˺v$MρKR)C?Q~[Ӽ7[:O5k5"  8 pֿ>l^eb,qZ,e-qA}w`UjU?ٝGufo'q5ҩMktо/|C~*D*))h((lU'knxiQ b`Nmԫ% jʽxR=Gd}'I_]c~m ;Ok襒O,ʒǘ:3}kiϋz={hQ#$*t+B6<3 R*TATIM H 8~55ߍ~xWW%νXYؒ]@NXVَK_ -p9 Kq;ZJ׉~ү,K r +e9nWܿ?ŭgw +&goa{-o )D4@囐<ɫѣm{<6mFWFW+|q>LG=;R2]`ye2u0zg^~Ѿ'<;ge.ukgmu2䷘r:5x%ufe]+Wg[ iVZk^$ a][/_0>_ž%t6NZ$󷳉i03J5ya`mEW~ r~3kKXjiyol"혜ck~~ҬlY&mm#/,&V^^_5N7;t~W|^ÿ)֛w7zqySnB[i⏈[xz4P֮ʳʨJR249=|L%:KE֍ Tz^+O,G"z'ieE|W7W+Q[ڽC撹sXl-n;Ud!sNk3Wß>(&"B)fGmO|Sʫb'*txʕ)z3ʊ?v߇~$vX" )<`wnD`@meosyfd|qF*V^*T~~W߲?'ϊ,ʒrQ_~hD6aEZEqYM71J㖿?|3dž]J}N ,$J凖r cEL1 ׼PRO>٧ߌGÝ{eӼUy+ҾGoo@--|wW =ѡQ{lʕZNMOW>F'x} mwTHѽts)l3_]jPBQK*3 J5V09,D\=I_)3kDTtNUvLʽ3ۚ'QuN55(߉QxgH5k>)%GN+6@Bp1'R_{hl5vH!2aw845|=8ը{k5^M꼏[Oký@/졵f3IJvH8W_W_~П omM>MfX7e"1&R:JM^] 9y_> kzt zʹמnh( 庌yjuTvC8~2>,Ч.tU֎U~QQH?~8x IC3׼5i}X4Hѽ Z0[NN_V6ؼveK ?d+;G T;[vbw kW_ kk[-ZG&^y!k!]jeFv'2FjvO9g>??_uxq|=Xѹ2F; <;x+)u^5`A’(+ "?@Ek "?@?gG3E~_=8xY5}=_0o~K&߫_;/*䂒T(+!xhoz*gW떋z))h(( ( ))h(th_(mF?|>{IHh_)XmF|>{IH((?e:]֧g_._Z\Wp;XtRRtQERREPE%-QE#0U,rI{ןҾ{"hf]oN6ֿxu~CF.3W<˜&[}5~"jr~M:c [ye+z6 N}U]B\WIzQE-AEPEPUm`pտOqjRHգ'E4#N%JTi?AaOjZuS^c]!!r=3k_$?0p?2=N(\ާIogvSJ.2WdQE垙&?cqW=Y~Z路o޼G],*'>1_/'G#EWtRR_KY_-z-ذ[q@߲w<=+ NY7bg}@Q@QEQIK@Q@QEQIK@Q@QEQIK@WO[:Wif?$/ TRREPE%-QERR_Fτj+ 7 Wuj u}MVܮ=%c= ygv9%?h ?gOx{go w>+QWlQdT*YA#~gW/[o.?ʿ7 > GÕjo|ڏ+Vo|#Ö/&kg{e*o/ SՔAs3?/W1#n_k:3ͧZگM,q9J20N>x^]JIbo&gC\uʿ!/׮W-_?N?؃샪xVjIt톑= N_EWnqSǯگNCW O _?_^6)+/"T%1,G׿gƉozn|-|# x$A=oi ;3*I5SKqq:ƓJ,CjbB@AqTxs:%EE+vM=u9gñ^Rom]Zimմna*&Lwc#/C4xov I*b @8!wpIƅ/EY6 Ue v1ɮ?g Oغv7m|%bDZ ~R{Wu4yҴ/7#8<]8.*T;Q}kKo/}ISۋܪT۝_9Kx{O%qԨ.oToq$+ضI~eu"%դg @.ʫ=|_OI"e:S"7XLtqpߖ6SrljK )~?,5?itXY$C.Frc&я^{ψ^kG7Vqqmy݋"zd#^^=ȼykCypGC9E$M#K+w9fcIIJxIa)вr滕EwKzY%e&[[%eM]h~Z6lEKMsy+9Ca8ԳPɮ+dmD˯] zӆmHחCg|y1gAvOm{~э,;iIO[m5ۿ^dW]<%__›Q {Ȉ1 ij&Lj|5jj:?۵dY`F+I bng+*,>/qRwkɩ9Tm'ՅQ_4{EPEPEPEPa  W~4[b?,/ҟŠJZ0(((JZ(((JZ(+Wޯo=TbW7C?/N<+`WIυcj((JZ(((JZ(((JZ(HlOIo?h?][؟ѢJύ OWyڠ#%;" cs[aR4۲WPI$GqKm>@tVd[yF eB$_ c?v>E55hDќwd^)޽ɼW#ɋqu*1@e$mQ9R5]ҿUxR:R%'f{t׷0U*ƭ|#qZ'.ϭO'Hm4? GM$jF3p}Vk_Һw9P#Wc Dž[P eRRnAhf8Y:ɧg䕰XE5C/KH_6_=kO͗Jug5#ɿ?%ϟ'~+?*Ae[~H/Q ))kO ( ))h(ulnZJHc7b@bpH 6f4i~4wK (\)s"?*?QW+K my,l`=c_oOM>ٞi@y0 ,= }@O >tA{5i} ^cq^Ey8ֵZ'-zx|*.W ]oc8Dǁ -GM?da߳-v'OZhfJQ+~0y] ;nm7gι8L>қsW~wtN4.$Q+K/|ۻxRf؊V˦C,JN6~ŸQ_I ŀK,,g6/Q͚p\}Цys؉QXyKeWe#[ۨᏓjOz?^ÖxwpEߴ1Eor_iSő@u+Mٙc~>\cs_nDslBe/sK,}aw xWQ>?|8ėP\[$g\ǥ|? ZjEiN yIϴɷf~1_Pͱ:uepPUVo%NRGy/5xf7?7v3no 2-7]'C3. ȑdۻg3C_tQͱxQEe+Nnכ0$YoA&Wu-,i_ a+_}x̶"7t1 }i6vnݞgǻnN38ǩίN[?Ci_ۍ,Kyn&zJ4ס':R{ѯ ^ Zh R:EQ2yO|v~3_0~?#?hE 06G=6_|RC3N ZNv]w }]5|1~<+|@5;{_I;lG\b_ڗQxwPߵn+9~U{=Y}G>m}}Oe/ٳ_V!:ŴvY_gϗvzc^J|O#_[:Y=<Ⱥ9i3*){}t}ß`>|P>%'Z:tw S phϙ1v~1_a|^zc]>"_2vnM:nQ֪Tv]5 >_F7JmO˯5mx|C{à6GP,.jm;N38Ԭt _bxc;w\3W)huR+ߠa24(?_W[KL"+I7pGm\'L֍ٮ#e^W/X<6~^@l[Ǟ$|B{#JQ6Jy?kٿKnn3t:_ )~uAv|{o7Zx^ Y]-Be(ԧ7ϏfMV}i/ǟ3;sx?f:x_z vE͞dǕӎ~"BrUR(Bϭ_C"EJkZ'ضY}\6m:`կsNi~(>"ߎ2Ẇ9ݻ;[>vP̡co{f 5j } oolŏOLcpw:ck(b!E/qL򏼺ݞSe]4}HZXWB<}N+_|;ځ/9^ooSm1mKF6QRz',ZjHݭf'tq/j2jvwl)nFq#>W'_QͱdZKA(bu{ydiƩh?3-Sg+[^Ffﵮvp3+:6_X̾!%Z7p? | >x{ y48>c,cvϷ>־`=G? svX5n~58Yl{Ռg'0z]jr.'.ZQ]-<{QsOFĺi_$!sj{.k{W1]ӥX/ G*p~Y7Fg9ø<"̕ <kƟM[|R&4O_~jw?I foiͺ\kUNUfv-m>vQYRKG4ͽ|E  {IH_)XmF|>{IH))h(?e:] ?$:Lt/$|/+O JZ(l'Xk\pҴ(tFs_NLſlT~Qןoς<9\jKfW@NNs s_uObmto|({/>*Cxn{`(XF6RɬO|/S%<&t NKHC<ay $oVvxOeL};NY$!݀ h@hK.<5-q}[R ({ONkֿ#Gb6Jn {/kJu_ _o_7u?g?PQu?8%Q_gPQIK@[jJ#oR a(MsJ"?Bz7^鸟ywSu?%-瞁&?cqW=Y}路o޼G],*'^1_/'Gn?Di)h>(+~7 /WCoKn({NX7bg}_?k?L( ( JZ(( ( JZ(( ( JZ((*xOG_]y5UYO`'I#*<!@))h(())h((+K;R^Οjal_^"k<j[P 0 dF>+O8$ҕZroEw6W+am62vGeSZx%.[JkeXpARA[+JnU 5i4LǙ%HNϼ6TV/$?A[+ = .m/6_H4~h/$? = .73fGuH4{@\]ro !f oG|Qg[&_2lXhQ ?oB "MAd?ڢ -h? ?,+!KC͚+ ~? ? -YVsCuɿ6 TV/$?_— ~Go?D?\iEbA:G$? = .73jG|RA[(Pȇm/6h_H4[(G|Qg[&_2mQXh~? _H4{@\]ro !f ?h? ?,+!KCͪ+ K ?oB "MAd?٢ o ~Go?D?\iEbA@[) -YVsCuɿ6 4V/$?A[(Pȇm/6_H4~h5G(Ș E\AIf_d\hViNe|1C@pA?Mx' \!xn[98 q82}L2eGs_QLm_7 +g%+(q'8/Զ =j^_VF.K[+&j}9d+(k7}4ZhW_ glzWյ$Y/;CQw?qM/EW-~}>K_!U ]c#*ޫG' ȺSH~~c_ j|l@ղ gPH))h0( ("C/*JQIH>)o8'n!fj,QJnj$|d ~ᦿ௞'J>{$]٣ƺdOWN'_^H? կKү58&kHd[*԰ ̪]$ֹ7gM :5 [vŽd]~_{߳Ė>%5K=>=ud5&. yew(s_G u7ܿ:WΣ=yHb4h^+?|h;P0ރj kR|0gMQ~lf:Rxww=0E`ϗ ;EOJ4O~7xoᆽkW>X`<;U#$)u<,EIY51Pi5~z@nMB2kWLʶ°Q@U%s z͓;1I] Q\NJk.mz,VpfU϶hڷƲ4{|A RmMY.C#$ZuJ|~1𭾷5=٠E8)_t?5gmxPVit)c_۪EĒN?P+ශ4<Y#=C41(Bsh>5|\/OyKEopgkiIĂ7`pr=6}CP;[[XYGh736UOW_g'g7+LT[>'"(-p )TڙZ ocTxlfe.T(t^0~^"ᶭM+][lt#RpTQ~o/hZ.N$ w!v_rG#C_oiv>i.erQ|]w-ˁ1_'><Q<+D+ #ĒAd4cK_PXφg 5?`K3^_]:(WaHfT?44Kź4>MŖ #K6]i9 ))h(())h<k rW÷:̺Y!"UFiܤï+ICA&KkYWPxݜϕA(+܃d%vk:=dt[_͡}w:6}[+{8e } w]dF3Άg]Cq3hP"K io_+,iPk(8 Ƚ#Zȿ#L/u_w?<1M_OW?dž?߾ɫʇc ( H੟}^2Z/ޯ '/ހ?( JZ(( (? ( ))h(th_(mF?|>{IHh_)XmF|>{IH((?e:]֧g_._Z\Wp;XtRRtQEa&oi?[~WLſlTCV?q%|d>#JuJHtLMuk8r&59#HGoO3?dOG=UяőXwe 81qzɝ|Yh'gحMR?)Z3u _mW"K- J䛯_)S= {([?(kJO5cJ)^ՏG_9%s?=WD8sKE%-~~:QERREPE%-~@~_q+ëk/8Zύ3^i>Ⱦ}~~KCa r<MAq'O8wʟaEW{GCm`ˏ4O[ko5*_ϦG?ލZ(O߂(((((`O[aQ`O[aQ {!?S ))k=95'zK?m?O_F`p Ic/WO_?7/.ڧr (>]޼G],*'>1_W׈륏^!g?k޾$o\_?DhJZ+~/+/WCoKn({NX7bg}_?k?L(( ( ))h( ( ))h( ( ))h*xOG_]y\ M?z'~ep5TjJZ(((JZ(((JZ(xSxwN=^u&Ǔ]2ץRL1g<('G—;B&_7G)"h?+utU}~{'4}ORDWk/wMvn*a?Ⱦy)"h?+t—=B&_7^E_?4}ORDWk/wMvn*a?Ⱦy)"h?+t—=B&_7^E_?4}ORDWk/wMvn*a?Ⱦy)"h?+t—=B&_7^E_?4}ORDWk/wMvn*a?Ⱦy)"h?+t—=B&_7^E_?4}ORDWk/wMvn*a?Ⱦy)"h?+t—=B&_7^E_?4}ORDWk/wMvn*a?Ⱦy)"h?+t—=B&_7^E_?4}ORDWk/wMvn*a?Ⱦy)"h?+t—=B&_7^E_?4}ORDWk/wMvn*a?Ⱦy)"h?+t—=B&_7^E_?4}ORDWkφ:lqxCG:K_!U ]c#*ޫG Ⱥd?mZӿu~-~}u<^CNŭ;G\ٟZ+x'i"ѿiד}QE#f9x ̟F~vi:ݰl ˸eMákuCWޞ nh:Fe+#Fde8 P|:4=>g֮Q8PH0ZRE+{44_Fm,wp+Ȭl6ܔ*u+o>! ׁL.5]N$` ̼ zW7.. kw[j`O"B-+"nCj,GXx tWV/IFDHv4 QlzZyhA? l%YYOxMsh%Lhw̿2 *? 5n[ EXEu{&uRobB ~5%4)$~> ..[+q!-Gp 3Rݟ7xűݭ5XJS?{(&l&:woRKx×:4 6[I*2p,m܀h/oBvß 4'>%tq}mfv]OA,rڛ Oդuu-0)o5%@*H:N{^fGFozW>+W8wNjMw"e^\6ؠ1碌(-e&N߈j1Ftڔeb³} pHu|gxc?_~'|nU?dM@8) vCd-7/.|9^EK΢H${0:e=|?~Lu+sֱ.s6 JP mV HT%3@7_¿t;~?. 8qi2;c򰍰;Qk~:[떂i$4^h~j#Wg@/~??ewToiv{zJ#%c@ `|̠3zn_N|?%t4Ȗ'&$ $;r S>-~jw-o&EY|(cR{~sT!xXXl٥`!Se םqP3IW|!o++PiMNk;h;HlaQP1~W<5es>7a|I{ [{ۈB WT-fn!TS$#]j*I} ,|q^qsy˫n^G?bz=>N|?cۭ[g^_O:ѻX 61ǹ AdS~_mo_ .^ fngg{{<:(@R:g޿|=mg_j 'OԠ16K|Belm(+ ?e(|X~&X"o1X[ѭq$(;`SY^l/?zլm#HIF.ִm"K+M:PFyCA,nv xo^k7 9W߱ċsYǣ3r2M|=#:5|c_>mAi}vٽ}ǂx+LwL]ú:Ȗ$4.ijREPE%-QERREP_?_ t?wzI1BI@@+s׏5|;եM$RD$33m"GSЃz>jhnkJyz\ +:37gl^_OO} #@C/?}ǖ|뎼W=FmK*t]g{EUVu ))k;¿:_/3?V:_/3?83Ft~>o]Wc7 }Wq&jz"rH(T(*g_떋z!xhozJZ(((JZ( ( JZ(t_(mF|>{IH_)XmF|>{IH))h(?e:] ?$:Lt/$|/+O JZ(?[|$m,iEʵ2mi0Y@9Cq{eUii*?5xWW|*Tu3ϱ y,|/=3۳s}@_ |S5Ͷ[G͑.7Lm"H8܌=(fo'W?S~!uK>m9.m&$r3 K'{@Ə|Oh%M?_ A7юgp?>[[{ 5p_; [ETߧ3%пXT_C(,(lO'V?>:ZV-.XfdI +^g~+I,}\TEW'IKEQEPEPe'}`_:u^_#:~? $I௺koIw?7/_tT~HWܩ6W{G~6'Uâ&_tuo^5nUI"sI##$Wv tJߓ>ù%M>_8EW9RREPE%-QERPp9< Ͼ?`O1int{@;q@U|1o]+8L/d~F_\ ))k ( ))h(F?+'OGfbSf/_uTPIJΣWz'˿W׈륏^!g?k޽ztҸ?-x[`䞭5 ?( g%,/W_KX_-;Mct|'ɬ3n())h(())h(())h( '?IYv_o\Wkf?$/kZ( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(_ 'z.TW_tz~8lBWm\WO'MJ( ( JZ(( ( JZ(( ( JZ(!?% DvoWCbK|AcF+ޠ())hRk?eҾm/ |g?g5#ɿ?%ϯ'~+?*Ae[~H/Q?,?l&~.CP|FPŦ[#;g|n1gNp>PloUE|G8g[τ >;i hOڌ~O>ny^ٿfOZ*(Ow9G<9_K>$dzg-9W wƪڟ~/tC~}Yݸu8.))h_6uOxľ3oa^ ӚfnyBBT6 ~|x xjڧB0}<ߝ>^ǥy\7"мAm}V ;h]j,1,~? >l`m!LeEѝwd )<U*ӏ^^X n~oj ( JZ(( (>b~:.3[_`Hͫ /Y,Ęeu#_>!x'į~-gGkmOQ~\3L)0!RE~Q@331x N]<|˫5nP&`_/[<_'ZRr> UЂ8%q?iVP֮c_̗Z>רOB\0*I$ɷ??||h/Cǖ jz.o8@)"x2¸CMb[ߋg:N Ӽ?d;PKJlTg$ T`SpEOE~>4>&|K>. iIt($mXsFP ?osNм{iύ>,|Dxt˻x)$`qHÆNsM?j~~"OaӼ7Y;ܨ7_iP'k?+M3{jKwܛљw rPQ@V<4>%Y MƍAii[JA\*( iho ݧ~.~'xOMiPGi$ O I |,L`0<=CnOhMah[=\Z$Rp8 (IdA/P>*xoZZt   IS[`wa5 Q*1wsW@u.Q"K;^8]6cf 6x@%;WDkàj:jyڮ\JpdfPzmP0|fS"Ɲv;2ۃJ{eV1+osu$NӼyͩieRc25%8\a~_Egd-4qa@U278cAkWѵᵋ[Bzo]Wc7 }Wq&jz"rH))h B(*gW떋z!xhoz(())h( ( ))h(th_(mF?|>{IHh_)XmF|>{IH((?e:]֧g_._Z\Wp;XtRRtQERREPE%-QEB-O_CKn{00Yf9w1X;A'5c:u J2WMy=SZ_Rq]$z5G?>,ZH][׃?#|JZ7_W?Pʕ?#\K/tkW?B67ny)hSo?OH6}AN~Zx(нQ_t~̟u~&|N[H,lQH 2w%@R@K+˰Yv3Yn4)JݓvOsb1EQE+F1v}JZeQ@QEQIK@ ~_^:˸#'Jnj9Q=,]i !^*GiGu}ַM><%NVkq67jkW墼'9@R$z6}AN_|JZ7G#_Gֿ%-w TgϿ7)F%Џ_#k_q_wTϟoS_|JZ7G#_Gֿ%-?;*TgϿ7)F%Џ_#k_q_wTϟoSq5>7iukӤ Ow#A;G #t s_ف2<%X0T)'ߕͮLf&E^|7o> FHt>;}+YT՜FPZ 4 )2~;wou6~EnJ*@xRlpÍf`΅zjt礣-n5Ѧ9Q<+RH2뿓Oj ? %n<G67CįuK^*G>OF%Џ_#k_q_wTgϿ7)F%Ѝ _#_q*AG_k'u>7i[῁|uwNESԑc{Xj+ yGe4%YG>';Ϋҭ+ߖ0ҺQcFo־Lیw$m7.g8;(>IK^#:%RzRMY-QE&?cqW=Y~Z路o޼G],*'>1_/'G#EWtRR_KY_-z-ذ[q@߲w<=+ NY7bg}@Q@QEQIK@Q@QEQIK@Q@QEQIK@WO[:Wif?$/ TRREPE%-QERREPE%-QERREPE%-QERREPE%-QERREPE%-QERREPE%-QERREPE%-QERREPE%-QERREPE%-QERREPE%-QEbW7CzV??wb@ӗO'MJi$¿ ]RREPE%-QERREPE%-QERREPE%-QE$?'4h.ҿHlOIo?h?][QEQIK@Q@|iG}Y_)_@5^3kG>K_!U _?OWaU~Woʷ_.6RR̟FQERREPȿoBx&[A&+21^%{J7v4k!^/ޛk_شw{*<3Kyl9L_=~߶;|Wni=r0{_P~ӟ~ |ڷXlb|'1k?gZ/`5:{8[g]ϴKo40 %SG*߭}5A|I|}88n7yNe87=k?GO6&^ԭl#q"O B˺B2 #~2|.~t)yS$s_ |5o|v> /t8}sgiǥ,a?߶iftw#wZ5}vIt;Fhcg9_zo_j ҵۻg7m3dgֿ*?հLoL F\Ko;5Smo~!#1ǸoH'x[ƾ ŧy5 8Ky5fĀd~C EP3_3a_uk}syd+Iwv}eOt4Ρq7~ğo޻cԱCFq]Ĝ}M|G~> |e]ú]-< k})v H@~*(em#I|7y{n}7|Wo]okxc}i=V>^1sѕOSx[o[mv }k@Ϗ O?s#'?(XIw>1 <פ [o D1SJ|9WH>eѳǝ13zQ K_0UO wo U֕ii]gH'+B3q_zk1fqAiV>v3LM]wg2:ќO~ Fkt۷a9wmhUgnr3kk^ mLcgU>}̜ħ+m}K( |)I+1V'* Ę~|.eߏ>Wڄ)ouvw|{VFUI `d0!ֵCXYdm`3W_ïxÞ[iֺ6y~o٢Xwm7tW:pԌR_Zۦߡ>үlY93 [ cbrIc _..ahm`A!@F$y3=;^fU0 C_~ဆ5b|ny|\~6~# އv׺L:Ive\OQ庍DL?S6gqo kgaKy܍biز?7ǖ76-;[;v82եkZxrx&m>y*ˌyLRt_-1]n_׷osi_~:Wo3YꏮMsz9ycxx;Ě[Xr_mxb |aJOL OڏbO[N#>–]~oǝl{W 2~>8|f/> 7tl>˷|K.qc{<`j`>Կ~ylaq}Oa־&Xv6nef}NGnk/9YegK2I5AJ6J_s~W[o \A?Diǐ۶~dyϮ+OPо$V:$S<2gڤۍ=˪`j5M kA4⯋<^8ӠA.\n4G8 ॿ5i:-Džth.vSqVY _<>!|?M4y~o{n]sn&COs ǗYq(*`)o85ԏն~Wu⥽/R#vPI_+s[Ylkcn9~Td[;/;1}Sy[szNJƐϬ֗$yدq ʹ$8P >*x:_MKh:0[lg+A?چcmem}%~ܶstɯ?N~'Gi^{m&&zJx%Zo߿Y1nVo3➭ރ'\V7bHmyK{`]7"0NOS_&՟~|POxwAӵ+FnfZE+: Wؿ/z7%?FnOڌ?g>q$ɍf߼s""/gA+e Ɵ,"뿦1с c&[oO0CT~_7?hk".iM-%įr{W5c>+G4-S/lwOs_䟳w5 oߚm~5n6\#X??쬟9ޖkhv}__Ϸ߸NY s%l4tGw3U0cxi~6[jWOAfb$b~FScك*dN[-+J4-2Fm"!GQ¢*(= ))i + "?@Ek "?@?gG3E~_=8wY5}=_0o~K&߫_;/*䂊( BJZ+!hoz*gW떋z(JZ((( ( JZ(t_(mF|>{IH_)XmF|>{IH))h(?e:] ?$:,_>o^-esy0fT-r0A Uۇ#j8XtWϟ_6Q S?o~n?|.3|}_G_}I_>T8ߛ5G5WCTb?̿AA_>U~8ߛ5G5OCTb?̿AAWϿ?6Q U?o~n?ظ/la?%gtWϟ_6Q S?o~n?ظ/la?%gt5OCTU~8ߛ5G.3|O 5WCTT8ߛ5G.3|O %| S?o~n?_6Q2_G_}E| U?o~n??6Q2_G_}I_>T8ߛ5G5WCTb?̿AA_>U~8ߛ5G5OCTb?̿AAWϿ?6Q U?o~n?ظ/la?%gtWϟ_6Q S?o~n?ظ/la?%gt5OCTU~8ߛ5G.3|O 5WCTT8ߛ5G.3|O %| S?o~n?_6Q2_G_}E| U?o~n??6Q2_G_}I_>T8ߛ5G5WCTb?̿AA_>U~8ߛ5G5OCTb?̿AAWϿ?6Q U?o~n?ظ/la?%gtWϟ_6Q S?o~n?ظ/la?%gt5OCTU~8ߛ5G.3|O 5WCTT8ߛ5G.3|O %| S?o~n?_6Q2_G_}E| U?o~n??6Q2_G_}I_>T8ߛ5G5WCTb?̿AA_>U~8ߛ5G5OCTb?̿AAWϿ?6Q U?o~n?ظ/la?%gtWϟ_6Q S?o~n?ظ/la?%gt5OCTU~8ߛ5G.3|O 5WCTT8ߛ5G.3|O %| S?o~n?_6Q2_G_}E| U?o~n??6Q2_G_}I_>T8ߛ5G5WCTb?̿AW׈륏^!g?kޯ>ྷ xCS{C XGq.8POZk?k޾ʖ.q|t>B&\IIr=:?@ ( h_ bmKlxŅۊ> ؙ7A_@Ww<=+ ))h(())h(())h((Jz'~ev&Vo=o?IHʲ;P( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( Uwb_E*X;wM &D%v|4q_脮ڀ ( JZ(( ( JZ(( ( JZ((?R[4Oi_$?'4h.ҿ))h(+/KH_6_=+K͗JxwF[/< "_!Z|Wx?kŞ$2-p(23_wqC^uv*E맒<SY}$}|\G,5?G,5?_7J}֩2ޒY?k:.Y?k:.UG0/_y\G,5?G,5?G*#}j/+''_sReqC^tqC^t}R?֩2ޒY?k:.Y?k:.UG0/_y\G,5?G,5?G*#}j/+''_sReqC^tqC^t}R?֩2ޒY?k:.Y?k:.UG0/_y\G,5?G,5?G*#}j/+''_sReqC^tqC^t}R?֩2ޒY?k:.Y?k:.UG0/_y\G,5?G,5?G*#}j/+''_sReqC^tqC^t}R?֩2ޒY?k:.Y?k:.UG0/_y\G,5?G,5?G*#}j/+''_sReqC^tqC^t}R?֩2ޒY?k:.Y?k:.UG0/_y\G,5?G,5?G*#}j/+''_sReqC^tqC^t}R?֩2ޒY?k:.Y?k:.UG0/_y\G,5?G,5?G*#}j/+''_sReqC^tqC^t}R?֩2ޒY?k:.Y?k:.UG0/_y\G,5?G,5?G*#}j/ "?@'O> ^Sxnn̂9F8lpvs*.Qikɟ9Ÿo.NhS8xY5}=_0o~K&߫_=\ʇc ( H੟}^2Z/ޯ '/ހ?( JZ(( (? ( ))h(th_(mF?|>{IHh_)XmF|>{IH((6{}qy OHm7?j%\;`|ީfea+mZѣ??U4,Լ}~23??UŠ?׌~!yoeg;+?C_uW}-i?XԼ}~23??UŠ?׌~!yoeg;+?C_uW}-i?XԼ}~23??UŠ?׌~!yoeg;+?C_uW}-i?XԼ}~23??UŠ?׌~!yoeg;+?C_uW}-i?XԼ}~23??UŠ?׌~!yoeg;+?C_uW}-i?XԼ}~23??UŠ?׌~!yoeg;+?C_uW}-i?XԼ}~23??UŠ?׌~!yoeg;+?C_uW}-i?XԼ}~23??UŠ?׌~!yoeg;+?C_uW}-i?XԼ}~23??UŠ?׌~!yoeg;+?C_uW}-i?XԼ}~23??UŠ?׌~!yoeg;+?C_uW}-i?XԼ}~23??UŠ?׌~!yoeg;+?C_uW}-i?XԼ}~23??UŠ?׌~!yoeg;+?C_uW}-i?XԼ}~23??UŠ?׌~!yoeg;+?C_uW}-i?XԼ}~23??UŠ?׌~!yoeg;+?C_uW}-i?XԼ}~23??UŠ?׌~!yoeg;+?C_uW}-i?XԼ}~23??UŠ?׌~!yoeg;+?C_uW}-i?XԼ}~23??UŠ?׌~!yoeg;+y~ΜuiwپJWds+ZF;IѯR}, j4%VQE|WC_WKn+^ g%,/P&o ~ ؙ7A_@PEPE%-QERREPE%-QERREPE%-QERRUf?$/YO`'I#.<kQEQIK@Q@QEQIK@Q@QEQIK@Q@QEQIK@Q@QEQIK@Q@QEQIK@Q@QEQIK@Q@QEQIK@Q@QEQIK@Q@QEQIK@Q@*X;wMꞿ,OػsIDžcj~9lBWm@QEQIK@Q@QEQIK@Q@QEQIK@Q@)-'zR[4Oi_QERREP^%B8~+/<ɭྺE{tWlGlql&&Tjƴ7bѭNTAm .`_п"9u'~Ad?G0/O h_sKK"c}Q2? ~2[BF'/j|DǺ~Ad?G0/O h_sKK"c}Q2? ~2[BF'/j|DǺ~Ad?G0/O h_sKK"c}Q2? ~2[BF'/j|DǺ~Ad?G0/O h_sKK"c}Q2? ~2[BF'/j|DǺ~Ad?G0/O h_sKK"c}Q2? ~2[BF'/j|DǺ~Ad?G0/O h_sKK"c}Q2? ~2[BF'/j|DǺ~Ad?G0/O h_sKK"c}Q2? ~2[BF'/j|DǺ~Ad?G0/O h_sKK"c}Q2? ~2[BF'/j|DǺ~Ad?G0/O h_sKK"c}Q2? ~2[BF'/j|DǺ~Ad?G0/O h_sKK"c}Q2? ~2[BF'/j|DǺ~Ad?G0/O h_sKK"c}Q2? ~2[BF'/j|DǺ~Ad?G0/O h_sKK"c}Q2? ~2[BF'/j|DǺ~Ad?G0/O h_sKK"c}Q2? ~2[BF'/j|DǺ~Ad?G0/O h_sKK"c}Q2? ~2[BF'/j|DǺ~Ad?G0/O h_sKK"c}Q2? ~2[BF'/j|DǺ~Ad?G0/O h_sKK"c}Q2? ~2[BF'/j|DǺx/υZ?C5{\ՙ>uǏTs^IK_JYUml-MoQIK@Q@QEQIK@( ( JZ(t_(mF|>{IH_)XmF|>{IH))h(())h(())h(())h(())h(())h(())h(())h(())h(())h((+~7 /WCoKn({NX7bg}_?k?L( ( JZ(( ( JZ(( ( JZ((*xOG_]y5UYO`'I#*<!@))h(())h(())h(())h(())h(())h(())h(())h(())h(())h(+Wޯo=Ub7C?N<)`WIDžcj())h(())h(())h(HlOIo?h?][؟ѢJ ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( ( JZ(( '/޿H੟}^2Z/ޢ( ( JZ(( ( ))h(th_(mF?|>{IHh_)Xm~|"?d=?=_~khuOnhmGC ~_c/*OFtc/*OFtE<2o7G<2o7@dQ_c/*OFtc/*OFtE<2o7G<2o7@dQ_c/*OFtc/*OFtE<2o7G<2o7@dQ_c/*OFtc/*OFtE<2o7G<2o7@dQ_c/*OFtc/*OFtE<2o7G<2o7@dQ_c/*OFtc/*OFtE<2o7G<2o7@dQ_c/*OFtc/*OFtE<2o7G<2o7@dQ_c/*OFtc/*OFtE<2o7G<2o7@dQ_c/*OFtc/*OFtE<2o7G<2o7@dQ_c/*OFtc/*OFtE<2o7G<2o7@dQ_c/*OFtc/*OFtE<2o7G<2o7@dQ_c/*OFtc/*OFtE<2o7G<2o7@dQ_c/*OFtc/*OFtE<2o7G<2o7@dQ_c/*OFtc/*OFtE<2o7G<2o7@dQ_c/*OFtc/*OFtE<2o7G<2o7@dQ_c/*OFtc/*OFtE<2o7G<2o7@dQ_c/*OFtc/*OFtE<2o7G<2o7@dQ_c/*OFtc/*OFtE<2o7G<2o7@dQ_c/*OFtc/*OFtE<2o7G<2o7@dQ_c/*OFtc/*OFtE<2o7G<2o7@dWC_WKn+[ V7*Ư3xG Ȣ7_U_U+x/eE_Inx/eE[In>Ȣ7_U_U+x/eE_Inx/eE[In>Ȣ7_U_U+x/eE_Inx/eE[In>Ȣ7_U_U+x/eE_Inx/eE[In>Ȣ7_U_U+x/eE_Inx/eE[In>Ȣ7_U_U+x/eE_Inx/eE[In>Ȣ7_U_U+x/eE_Inx/eE[In>Ȣ7_U_U+x/eE_Inx/eE[In>Ȣ7_U_U+x/eE_Inx/eE[In>Ȣ7_U_U+x/eE_Inx/eE[In>Ȣ7_U_U+x/eE_Inx/eE[In>Ȣ7_U_U+x/eE_Inx/eE[In>Ȣ7_U_U+x/eE_Inx/eE[In>Ȣ7_U_U+x/eE_Inx/eE[In>Ȣ7_U_U+x/eE_Inx/eE[In>Ȣ7_U_U+x/eE_Inx/eE[In>Ȣ7_U_U+x/eE_Inx/eE[In>Ȣ7_U_U+x/eE_Inx/eE[In>Ȣ7_U_U+x/eE_Inx/eE[In>Ȣ7_U_UU?wb__;Sϊ2 5-MTqCVuNM3Y_hpZX$YcmᐐpF=h ( ))h(?( ( JZ(n꿵Ż^<hYNk '1_Î|CE~u @G8?WJG8WJ_нÎ@% 0|/:%5I^bՋD0գd S JSl): ȞbC@$XMH3}VHHIBOGbCnₜJc%VhʢG`(*x%(30s 8, Ě30n~@\;b 'v$%)-rH @=iF`yF'{Vc``ww1Py!e58eXIfMM*i81N-@IDATxeE7"I$c%*QPPUp]u?eQqu +UPD2Ȑ@e@C_==C03L~y ;<_5V4 @ @ @s @ @ @T/ @ @ @D@'0 @ @ @  @ @ @ @@A @ @ @ @ @ @pɃ4  @ @ @} @ @ @ '>yA @ @ @ @ @ @D@'0 @ @ @  @ @ @ @@A @ @ @ @ @ @pɃ4  @ @ @} @ @ @ '>yA @ @ @ @ @ @D@'0 @ @ @  @ @ @ @@A @ @ @ @ @ @pɃ4  @ @ @} @ @ @ '>yA @ @ @ @ @ @D@'0 @ @ @  @ @ @ @@A @ @ @ @ @ @pɃ4  @ @ @} @ @ @ '>yA @ @ @ @ @ @D@'0 @ @ @  @ @ @ @@A @ @ @ @ @ @pɃ4  @ @ @} @ @ @ '>yA @ @ @ @ @ @D@'0 @ @ @  @ @ @ @@A @ @ @ @ @ @pɃ4  @ @ @} @ @ @ '>yA @ @ @ @ @ @D@'0 @ @ @  @ @ @ @@A @ @ @ @ @ @pɃ4  @ @ @} @ @ @ '>yA @ @ @ @ @ @D@'0 @ @ @  @ @ @ @@A @ @ @ @ @ @pc_|~e%th\PV}u[q]vee 7Γ&M*?_v۲뮻K/+\>=f„ 7M2{ 6(o~˒K.vusO>dr-뽬eko!駟^o+7׽k){l Y/䒒5=P=OS9ꨣJNKkΩַoewn7\}3)z8G?Ѳzհ4(ndMJN;J}٧7\M}u/'|rc=z&'>:L/_.²ꪫc]w:槞z~N;+_㫯o:z/vX_p=Q!kt @0{f @Y(vĉ%!knV?g[ %Lp3dxܸqB7ǯ 5G>eu)/gQ^m/~~r!JM[jwEtUԧjpT>ϯ!w*9~K6΄ <:.wݖ{o:J.c'?;o,/1ͽws9zo*{cb @ @S zJ[ @ Ы}+_9HAg_{Twޠv[8UկJT/w8Ni?,wygfyׯ9iT%l~ ,PRyZ^Wp6S;߯ggvءܹK^?A=fM7~ @ @=|"@ @@Ȕݖi&ʹs=xbZy)x֪HZi9G*bS9ۭ _/~uu]W-k7-@gᴬ!s9??kqNsKY&|߮ׯJvL*3s:~VN}1NwݎJ @2SA @zտ KW\qE VC~%\r Tmƽ}Y7r_=޾i%[K|3uZzN~^VN;uMLۜvUW5}S!ւ{Ns=w喫SE洜?վCp& sݴ׾4ʚCe꬙u<̒5 @ @`J& @ 02-sFmTѭM5Жj[۝:^헿:cs;Nj^//StOuO>Y|e饗.?4wqG]߷{P;_%]c5/kwy뮻n`m:m8ͳkJT8SP6iҤr'֏zY[xUW"nx%@ @Wk @@ ,N|eK,DZC7i.{Wow]lƽ#|U%!v%Mkf׿^6diVɶ{J>̶i,.knL2u^yڛL `:ԙιVeVy}GrTZWU @,  @c\ U-l7{TYnݔwWXa[)ǭWxL]wΏ>hiAThւT?wߪ?Ce*$kp}S*/}K{flJzUV)m{ {b.|dzH[S*Gzs/?Pi-F[ @/  @YK7HZ7笅3Smɺ{.2q[U뮷zuZI3a„`Uܶ~_~y]8!n?^Xwu-Nw^ݟin5mg~N{?z|~vm%R#,?p??(>(4N8ߦ>ꨣ~Hok\OϹo,kVͺݱ=ig5s暫.j1< @DkωO= @ 音&n7( ouW԰7}O/ˏڧjkJe6k뵝o|Tvm뾄~W_]..;X6ut;_^{[k%n6y9- r' o&ǏU_3%>Sry'kk v񩧞Z>}/{՗>j@ ?O3y!0v/zI:Z jgֽfVw%RK-5N< @Lx:t%@ @Z;O1C9p%:g> */}KS?mNqgp 76s|-nao}luM޻ i=ۯ袋NV/u'\?ϻ[M馛L)PC-{l~0huY%me]|+_瞒)W[mя~dS/s@ev>[#@ @kpO~o @ @`4 ]S鉇kY6'Lj*ަnc@(y-)U_LL05\fej *-uW,,HowXUW]=orE]T/䒃C1yNsujՅiVn~C)a{uYQ!YY714%]pCٜR7陳j-XoO;_rh/^j @Q" %m @ 0R-dZl2; w7t4/?Hz)XjU<' @s-0:y\ @HyWzZŗٴ;LۗOܠTϧ[ܦ5zr*T gz9WfnCn-w7?v{%@ 0fc8 @ 0#mOuCلY w<@ ݊/g M<4M#$w+gzMUrVX8 VO7G*k/ە鞻QyC 0c ` @ @P&RڭNm3sPZCۄkV>]whX=I[sm$&NriAl,o*#>ߩUǰ]K @kč @.o}94YvF[L+x}[~4Y8Uݪ.uMN7S s| {[KoZUEc @+򤍓 @ [޺>b-;w*v&.ihѡWy.>}^3ᄏgᄯ{\[ovݾz?tlCfJe]9@ @cX@< @ @^ !e75?ݖiǏ[:f5[}n[o7U 3nEsBXvVL5[Er\sym-;P @@?  @ 0ڷ`*q}hx4!SO=5}%ʭ%Xsin5S!;#mV*?T'NkAv]mp*s ~ @ @%53LܣqmV2nܸrWT~{Cqf@ml{jBLi^o\ SKe_rw*Swz#d{Tٶ#!wlVdjmF^S Cuc&@ @`D]vYy'jT#:;o/{G/(nyF`jԝN9!eL_j=*m? z-(44M {*{_Wuniojy $속mg>8E۝9k' M%pMH> ^3r*} ==SZasd=ChN5qk˴Ɩj[o~ы^Tϝݬ952gmvv9mJSѦz{瑴']8٦ @ 0X@<' @x5yv}d$Lp OvCᮛoUvιJX:[<{wI<#SA'9W7Hg @} @ 0D ^nrWmݶNn jr嗗pY~kމ'X;CʚkYϚN+KRzzrg &wܱ??e6*__{?(7|sݴ׽ueem]}MhtL9w}{@gcinSR >A{/y{J5\9.r%5Xc饗]Smx5ה+\e/[lE/YN9nN@f馛} s׾~z |o5AlkvClOPr֩VBӿ\ ?O:.rGQC?^?09k>S;sjT򕯜{B:|/[ne]8bo.o[j$>#{˽#6?z&>&^mʻ'@O<ìw|/G'zˇ>rW\qE 7|o}[u{ﶄjTwy׻U+Vޤ%춄 vYgUC|xkH ]sĉ%aV> %dGϯzիj=&x Ͽ}s7lX2m*3T?잪|ckv%ri;뢞{5d?pj-PzW5F RYowIЛ2,S+ճfo7MTt Z %O/ W @S 4 @5 CBn0%iZ˄M1mu){W{^v֪}+SI'H;򲗽/N +0(5KlPW;&q6h;>4ɩN;kkHZ:s߾WU̇zht9k0;'L[K6omS]3a|ۿ[oZL-Fh*r夓N? 3tk]tQ}*fiB -4Ⱦs$M3T+wZ,maNzZi @R@\6 @s@BTe-nKKi~򓟔]wݵNs ?Ӳ>p̈́iFMK T%Mowi6dT%$m-t[wEmk>չ̴ͩYW8ղ F33iw\M3u,ryڞ?ϻՠ7讻*j: 2]t*zSmYC8-[mU 'DN*T0'(NKa۽Ս~x#,])o$P1^2LsY=cK @ 0um!@ @` $0lӦ~_>5S/2U&Tέ5kk__8cZ;\M<4κ:Tn4wח9jѧv!w<̯vO9Gm[3tZ93Ϭ%f/g ޶r~eW_YW8뮻nnM͝> .䒼N)vꩧ)~5]#\ 8ahf#ȴtYüs} 3T21yi5O @_d @hY5զ SMJѴ}:iYM+ ¦e ٖ3U|i2K_ҺU'i?{é]z뺹Tv艹`q =B/JLڂۡcijl}5Lnk5yc)z5J`{uו5\3z-տR:t*r)ܦ?X @97 @9gm׽ue 7_ W\qAS36ST7ۭ͆כ@5mUV)+R}u'3S8of>+_Jk 6ձ QS%z-UwmJ?gZLb :e+Κi km{%e&gL/N 7Ni=:}*%/sg:~ @ 0 '~ @xNR{뭷{ d۔ 9h}ͯ-=۶': 9vK28gZ Fsn˚>#ʗysoy[C3tZszFZdi pԵGCǒgmC뎁_ W^y1J5sBV_ʦm5~5[8Uõ~fo??TXuY @ @$ MO˽ @ 0Z`;|__z!f W[m^8kzS뮽{Lp4-S-gMڗ 5۳Vmk~z >3=tNqkRS%9! Cs+Atmn>k[8k8.rJ&gw,}٧7vƒS5u^W׏Y9ɩm!ͽL1ݦƬ* }f==أv{衇v @ xLn @%Ђe]V&MmZ kAӦ^nw׊lO.mQO*]3;8:'s?ni|~^s==yO=E]Tc-,Ha',MK<̺NK,Q el'pBJ9LJvX o[r-G>袋fӠ/|aоlIݖ*|35M`{F2-i{$([ξ6tS#@ @Q@<{&@ @` 7SA_x'?I9hZsA?sTv/^RI7 guY|JT&:mN:kVO5~S{i -Syw:޴ƹ~*~Smism٦uK_Һ?criƖ@8S2g}T'G 6-aZַC1f }r쩪Nq9,2uJTeLxjm%ν&Oxs @s IQg @$m{gԩwm^U]6Ab NBv; h[馯2;o=,_LZΑ5r& =%$>K[SO4}rMpNN{LkN;T0g矿V`|M7;wk׿cYr%[)^3~%AsΙ9Hм*S=e}LkRK՟שνg ű|mc@IDATtuwG}o  0"Z$: @4Lt" @:LE[7Pޭ޺1i3hjv[|/~1hz4}-|Jun @ zA @.hSY)VZi:Z~=ew/g塹O @sqs @[|.|e}g:7߼e/S>ove]v)twn @F,05= @ @` >u-ݯ}k%%_xsΩO~袋*Y+.!rQG?|g7G @F5Gr @ynwy宻* ,@]o~F @ @ @m @ @ @ @ @ @ 0[5Z @ @@<ē\U-  @ 0Cbs @ @}tAy'덼piR/zno  @xNL8 @ @`&=t~ sIOO A @@Q @ 0Vοr&7v @;󬍔 @&>Vμ䆩Ko,=TA @@  @ g]ۓ:'TN9 @5: @ @yow=㈮r O @O@Ԉ @ @>夳N:IO @?Q @ @}.p{8QNx`b9q## @! h @ @@ <4r7Lϼ#Oq @ @` Gs @ @8ʓOMSνfs @W@g}' @ @` @ @cT+o)?Y~W{q @|! @ 0X9" 7l\;C<> @ @? @3SϿ<ԤaG6 =va\{Ͱl$@ @, @[\+.Y6Y{aeFkP\ia_}g# @! h @ @@Lz_]9h{k6v_8\蓾õξ} @>C4 @ @οr^]bau7.eMn꽟rz!@ @< @M|y Î`+;mް۸+-pv \#F @Fxt??wO @O/IÎhm6(/xnc*O>5r5 @-  @ @}"p]+okѬeWvߴ6nvxqwWtfky1)ܳFu @s\zon @ 0~z7;iX?װۧua @Q)=Gcs @ @Xn<׌ރ @Yo  @ @ @-" @ @ @Yo  @ @ @-" @ @ @Yo  @ @ @-" @ @ @Yo  @ @ @-" @ @ @Yo  @ @ @-" @ @ @K @ @gn?M @@?5y@ @ @ @c]o @ @ @ 7yB @ @ @Xo @ @ @ 7yB @ @ @Xo @ @ @ 7yB @ @ @Xo @ @ @ 7H  @mƍ+'N,+RYr%}WβB 5\yM74llL5X5\S+[/b6?({^{5.#ڞ6xi j-rf?%wo~S?w}ifnjk/KYeUb-6#m @3]@L'uB @ {ʦnZN?c}>$qSYkjPyQGɛ현#m7[a呞#m]me]VoO @fx:% @g{キ^5X_^}*Nvb Tq;-%?#i_|qy{[Ϧ㌷U'{կ~u9zn㛖KLx㏗}c婧*o~?zO|5|ի^U;KτMys=|r$Lkd.zךN;I&F @`v zvj @euYg%ꪫnڎ}{S?)_ZAor56qsMqpgC3s?g',sK?\=ܶȎ/~Qx?O/ ,@ΪlǛ VX,첳66A}<׾=tyF}{_=SO~6Mk!t/>y` 1 AK{q:Nq_W)]j>ߙg  @Y- _ @ @`ZޚUmc[egw]׶m ܬUo{M??;SZ3Rٙ}L3[^ׁV_z饵:3;nkw}wW?Z̔o{k-ܲ CĩMOZv}z6oƑio.Ї@[N>W<uMs}.?яʷ^'?zoyNTf][\?Ï92myy|+{~*9Ә?.<-@nJŴF @`v zvI @fk w{>mo{$7n\R7fkmzvԂމ$~Pg2Pզ@l [p*bkxa5\XZ@7뺦e᷼-@s}9&@;(-lۻ?pyӛTկ~dO@t8Δi 1g-Nq?SYtE˭ZN;:}sB\s&0tqɱ{gI__kʔ /-+\g{3F+Ann?P]]C脵z9HK;P^ N8iߋq_@6aĉ|W?C2vzv(jӾy韖`?izZ *ʙ{g۴j @fxvI @-rh+պY5y߂Ԭ@tj}o 5v\ ^h&H}k_[u[pW_}u 7դ rS՚ϴ6ҽvx7:/%aZB˶Vl0_]{_ |&N5|: pgRpqms}"6Iڮ5t3|H8'馛jo7T=6ߟo1 O྅*5&rszѩ]hzۮ?hoOٶ 䬷 @]]ҮC @@ Sl!%(X˷h-K~=Agn?kր. 5!ck-x5[6Њv y=Zя~g*`~K_S w`zڡO5gwAc׭< &XNpujM!snKet~Zخun50ͱ,N%ջε^͵)Zs[Ci h߮Px-tC-2~jb?Shs*3JL7ު '$N(;:E3lww:mvyO @`f _3E @@hl yYy{^rWP8!hpnV5X̺ إo[p :n>3po?K"M0Vn5L+_k*Z6ަeLKsssnkAi :/yC'%Hl5ۖ*7N袋jqMmƓڬ [U7t~r8- >smY߶=WZõ Ʀ8Tw[3hӆv֊ k۲pV:ӆ^[Kr:ۚmW @R@<+u @@NKH;\K5'Xwe=Z} 8֦]uU8&tC[ΛK3տi ӎ;9^o넽vn_7xm g nkcodw_{)Raۮ_b{T,_ZY3 3tB$lΚ~ǥ^Z3-S`jvT̶ƶgu٤2yۯLgH=mS`'<ھMg}C=e-괬{ @f)g @ @>Xz̦JR+,Rt}ZH0MjqӪm}[ꪫԶ9AoFweG}tlտn@陶@kqMq?Oms}Menc!Re⧞zmMB s?OΙ|ꅼ slk -׊iimL]1>aoaNw{^?&9HX֮?}25tr)%?i<":;vw=A|Zw_*BtAu_ @fxvIEs H0s{z3NŬY bJ%/qavv6tWWWw{g*@@b p;WO;4'5zh{衇 2K5U@-6 )w?o馘/ c`Jx^2_8p$v ک^R Z y/+K/y9C5WbGe?3ܹsL`:7oal{9~5lt>}#<[kaկtU'|/#oF@~j=z0 =y;8Xׯ n?4;}g|̘195$;k!Ǖ5{sysDKCs$/mPZ;  --L   #}{wM_EY}5iī^r3*4QdaΪlӊSi/7 +:7vVEnCkh@_}U<ק ƍm1e嫠Vv~gLagyŪٳ5jȖ,Y+0 u~_~QǯY=S>3]oշXj>pyTVAEÏkmZw _\xjAXW~ch7xÙYYM657ý}6UϦ!  JE    @PV9rSի[nQ U`tҁ\Xaرp<،7߰ۮ];'۶vN/==+֭[{Cjγg϶GmsxK._|9SO= \`T-ʢU@WFn&-ֻqذa6k,'بTO} 'XzM j8b#un܏?}wNjƍ6 (kѻ/_ ۧ2mb 'sXԅ :*ج9^t=!:kkr5$nQ\@Ǐw+`\ҥ3ő 6(\׮ϟ2Muv֜ + 6v@@ \P   {\@+x`dv{\BZAT( \ kl9|9]Y˙YAn]MA@@`O 0x    P`n氲@){VwNMW_O<5Zao_]/'$*@(d%#   P\o NJ3 N4?9z|vQG9skr Ǽyfg /X w-ʀܹa)  ’    P 5--4-E٘1cy|5ɜ=ekkʕ+]i  @ WN:C@@@@@@ O9 Ϟ##      *@8_9 @@@@@@(GxWSacF1:ԶZhtxJMtԀNqSm”_6} ۞ۦ_ܿoEիWw_#˕+g˗wURŴmĈ֠A  PX_}aE@@ ++ kΝ;h#}D{mӾ?_b>?'?&Xl\@)x[>f[u^07试ل "k$ sd~9.^+^{@@ N! Ζ@@r)iӦ{*ӤVZ %c6" PK;E³RVڪ1(;)௻Ǵjtn%k׮XN2kԨw6\~vc#  @ݐ@@I`ҥv饗Z۶muۤIDᛰc6" Z4k5yUXEe)gfcnݖ27s*K@?,W<Moس>kmڴʙ#^{@@&yco@@|2dtMa{y8?N:NƉ+WGyUoW'8!f_թ?CTnΟ_♖)!Mٽ/}l[ZiXԮ|RvǥY*y{Z[?۶ms{۝0m;蠃o駟믿[ׂƃs9'P   @ݐ@@ }w4k,X0<Ј@)qS*ɎoE'DOѣG墋.ޘC8&  @͒ @@V^m=z UZZZZ@b(wyFuk P#GtpQWΊ@@ soǞ  yXj~@/YN>ݚ6m  %MtRֿw'ˊ'Eylr!' / YJ*yiڎ;[g@@ nH  5`u]yHW\  @1hѸuk,WvHf2P o)SXVah>g E.wsF@u줓NocÇw~.&e  o6o{_ضlQ˗^~UR1GX~> P 8%s  @j lٲ}Q8pZg  :bM @;~9ڇ   P#   @ ص+y}r]RWܨn aߤh  @`bs/@@@(K;YV?9b;  Pp1\   OZvͲC65klŊE9o@@P*?G@@@r&pRqwXzdـ@* ,_ `쳏{ゥ|  Spb  @Xn9vQ|.+A@V`'>ѵZqTg7[nΝ[|.+A@z_3ȍְ#:UOEA5jxa{u@@ @@ 0l0gUVO/edd֭[[|.+A@@@@ N  UB ְazy\    )!@pJN@@@@@@Ȼ       S6p       @ݐ@@@@@@@(gI     5kX׮]-33vi5kִYfa  $h    /e-uVo@@:{#Z  AbŊVT)+S-[ֶmۖ@@ .|dk׮~\  d+'! >S0  @ϽJ@@(UTJ'  بQ<9 @(M@@,up    @ WN:C@      @ .8[zF@@@Q֢Q[Ֆ,_k7mIZRrWVVUP\ɖ^o7oK5Zj)S~_6Qj\\Ykodh u9V:ro9ڗ   h   $-УSKֶECS_oW>h<6o-pX[;.NYPvFdo ,׫Y]ruiiXrlۑii}e X/ka[5yMt-?ͫ /ԭY]n̚6mV 8zW  MT4y;&{#   "傇r'Rti^Npu O xVlED?"PowG-?w26*veiNj^ۮ֬a+WW^xvWЫ[f3~jQ߯UV, "  S,8 @@@@}vm#ٶ.A=Z&tW;]Qֽ}so-Ժ~Eնt$`N1e,+50wi ЅߺlIzF`V@@@ ws^   PV6@IDATWU^w_)]^~kܸq;Krχ~8pڵk۪Uܛf G=p mǎcHz!r!0ަ`[{3zc?jT,k8o߱3u7߷~PcFG~gd5ylm<s/~Mש^d jC etܡm!{u=֭nVo7c@@r @8X4E@ȹ2:  PjWru` q^g,Wߴʫ.W״ɬm^z߽ف}ohw:Cpis }ĕO ߧ=WY#/ ZU[u",#  395    %Тq0`ݶ#3]2uocg%2nۼq@EiYͱJfXՑKY pjtcUfDF  d/@8{#Z   ͜9͛g׷[V,ʺϝ[lYPBԵj[regpce͟?ߦLb֥K_w_ sNw9_wp5yd[r P,hN~-[f˗/27k׮mM4C=4&<`RLhrͽDm6~rSÆ k׮ּyswswۉ'Zffuڶm N@ 5jR۔52/ g}Hg Ϋ.P.U[UL  '/N \   )*>kwu^ťifO>v/cժUnQxƌ>~xݻ7|=\N믿n6 v9<åC`zРAvmW\a7D\r.т= 6,|Wn~jvTXٸE~+VZjUga}K\tE{yn;#MUQ<=cf͚5jثcRS`uSj`ݿRLik^glnE}6sal,n^}u5ԬbҽuBjZ 坪۶+om3mMVn^mڜT7_M%v4B@@cP  @ ovW_2|RP}w?gy:,2?^xqTF|VY2R/[paLI&ّG:t7lܹ_>} q d+]^='[tn/حr䖩(ݨQ#n:;lQTױcG=zoK/~mbckΏnܬ4 hiw<=x)yOrN`׫,Xzٶn=Wg]^@NڏswY;jpW u *a54sڲeׇl *|F5]#'ujiv[<8 s[<+]O>zj+NT{Q7?\쯙 D@Sp~j  @tSr./ڷ~QA٧~ڙ3WAKePVYC+_iӦْ%07XtM,TLWWR۬9)~y9p &8s땭@ ,+6\>4 I/cǎ5hEC9>sUQ=z#F8ss6={k(jpQ~ԨQЉW:&N>BxQ֬Q@غ=PLn)[ogi<ЭrޫU譿Op߾@?.>&2w6 1+}d~`K{o-}9ʹwy/Y/W JW Qlgw@@ ( 'r6*#P0 ~SO5yAF !{믿v晭]ӿ_U-[WYKˣ>jzWm8_3 Ze'*o}k柟׫-e&͏f,k㏷:uN-[USV~ J]O8|I'k[П~UTɩ֭nڳtjHjU Eޫ:CMAk]R_}Z˽y'{9Getwoj_G0yҧa6qm v݆ zYs}c:[:vZú#A9#Ͻ<_';/s"hoRޫvyp79XA@@ }@@l݁`ezzz@Bs_w{Orx;wuuC5kzK-[ꫯ1߯j{ߘ Csc#ݢ9}@ ~:nqƍ-jxwF:@?78T~6O]_(޽6@ ʵCW8~zy ˗3ee_ Tw@}=N2kHUDDv@tÆ LcǚmwN5kv+n N!ng ;ĺo+VtW^=0TuxlogRS@AYF}Dg`g:x~W6WS:D#s]QxҬ_klW[^(   P ֗@@ȱ@۶m_cǎQ  V`&L?ce:k%QRC& 83<ӮҥUVSN6mڴѰ@_@͛W1cD (RqAY6mbmrʖ-kw^ w}tUd3O<6\{Q~}E# #+--{-[] @ق?G@@(WYnQߥ$xgW^^#eqNUK5ѣ UFPUᏯ {f15$4^v5UW]_ 45kںu.yk۷/n.v,` +wqNsm: }衇:.X DˠA*.?|3gδ>عw=z     @vb;  @4|^ ."\nQ7t^nVZ_-WT4pxHb5PN|A'yvw~'8̇e#+{mܸN=To=cn:MAY .IQv?w+-K{ uzW&>b@@@HZ!!  {Vs8raw}gWbe͟Wټ'N9tcmʗ)QQfrxhhQu9昨zÍv횭Og j[A}h[AdT;    PTG@@ (3jOn-2͋Rn]gNY %kxkHC9!kDe˖5,#lƌ`jխ_~vM7ͳҶm[w1rֹsgՠI&^f͚٤I{ﵡCњG{iaVN@V Yl ?C|Yq3neʔ:*=\kѢ=ε+ݩS'\r3z@@@KTd8>  P` ޶msA{˖-/8{WzۮXBfHV |ҥvZSPV}5~UAfsS`=Rvqsv}oLDS@ g].xVNF "p-]E [4[jԨa  $'@prNB@@ %4o \ԫ0g's>y̘1N/07ݷuֹenk.5>   ] @@ Dg N xb裏0hР؀ /_Fp%xG@ @@r)ݼ(={:sHt I ##b$EE#@@ Jd@@ 4ju_˗ϯ|Pэ7hF2e[t   i28C@J%˽ /M6(oР5nqBy"   d+@8["   @qP^@@@@8 0tq\      H%s       Pǻ5!      @`y۹h@@@@RS`Æ ֺukWnsY@@H,@8[@@@@@` ڵҼ#fddx,  d/@@ +VҥK{۷7vE@@@@Dd'a  @L/     o@@@@@@@="@x0s@@*0fZj5nܸz9w8}ݷ5   "Щr'8@@̴YA [n |֬Y@@@J%ns       P.ַC@ _ ++O"gPfMkРA*UYA ?6lխ[7?/@R^FtR}3oI'dw9̶lbv2[).شil9gNeZ/ֺuk%yM/!   @! DP@@@ ~s>k} 7u9kÆ Y@`V$ gwqY+Vp{"@+zgc"ˬ . +X=H09+?Y_TfoOE޾H 6ٗ :v7r$HufEہqUanHPcƌq^e/=WVaf'N7u> \e&L0#ӧ[[aݳgOn^[2z)g̘,Y vuYG QyuQ@@@@ 2A@( ̅/,]Zdv!>c}Mw /=j(o] E2u=pu`]?:!Ɗ ]{Gcl_ې!C 4Tg}ZPڑ@sΉr 4kEA#8̙kW}uw 4󩧞]HDN𯍑t'0aݢ>o}[o1a6"   @jNY  3eV6 xjNXB vwXde0`iWQuOt᩹|6ޕuN]#C#; >Iͧ{}… >fmu>J|!Mdyzg͚e'Ov,2I V+YOh^]Uz{3'%23Uf™DoW|;/ Y?*5=cpˋ/99fΜiC͛͝Ȑ~( ZgjO~֯ur   @B9*E@@ hѣG{ =82wyNU`_ڭ[7JpQkd[Oʕ+ Eukx{ F浵m:~|PJ̭kgyLYn LQ_I +` K/۔ݫW/JV- +/xv Fv%oqLaU:2>т4hPIj՜L^yFu6O!E?$gBvkw?{jG2_}Ue="D!&F@@=,L1p @N|MYR̴ir @(Pn @ 0;U;eٲe^o 5kXΝ i]ׂ?ݦu[:Q@Uvpׯ5i$P dKfM+ /@/ɔֵ/U/u7V59R/ZYa_Ve+`LQƵu[lit$֜zsεuֹMwe?r3'aN?@ gبofEu3L(eM_иqcպu|9;  'cvW:Y!]p]`[aKv,# 8vځaTƯ3yrJL/E ׻xb[`)E /DoSTC9*&8\op DdN:U_w^ШQ#ݻ3@f<VVr2%y6lՏ>#mf ým6'kի{m-Q8㌨:U(;9V M=bwVrnP+lG殨d̲ٕG5ЈG)|  S6pm+WڻeP@[xܢg˗/5jD]W8#4V2ڄ ;ܸQSbŊpfcze.>UQJ w~i9̻+0kKCW{XApI<'^ѐމ>޴iަMK™XDT+ֽSP=k_)NAhXffYo5.@@( ]o.@qpʎX }WiY8^OWV٢*Q#p$ $wWv(as[4ڵki^n47m!eq m~-:QQ:|vSN *Н{$ryr'cN>.ipjjKA@@'@pɻ\1EZ@ bh"#[oՔA/US1tpv>.:;v{챁z?@EiTu4o4^bV0|ܸqH8~9 Cih%B8\rejԲհcƌ)@}[G!Y p Nvt2Ih8s lպMٞ@6.mmK@@(^j*Seΰ{-Z}3ٲ3n=L|n;=ؠA]聯E994SvN˖-MC:ׯ_m+E'a= 4}@w?)5 ?UR4 ?79YҡCSR@o߾Q7x3OACztM1gi#8m|lwCźv@`gاwO'αm:5qN~G@@%)Z"Pd4UW]e'O9a<0Ͻh mxÇ6w'ܸq)?daÜ VNkqsz駟jST?iz\s5Cd2Qx Ob^5k2W_K/_e&MrԪryFAܡC !>hz,oQP[);>vC!C7l/Ȗ΃ @"eto;qP@~ իW/M_~pC*x?LATu{i![jo[]AZTއ_|_r^^edAٹeH)GxSOu7? oӉ'N{sҏo۹>{ `$ǂ ܟ9m:e.EVh+;{^/َ}ܙe#O+O#"  @ 0ps,]vu2l`U V4]mڴ7|3fW聫zHq8mΜyڮL>~k9'G7s\pq=U6W_}Ug?3d!{eU+P+sϹysy>^k hf2L7gѢE/:ApWaՇ~.B?N삿W٤wuWe*Xv2p5?9/ފ LSO=[^ft-s ?Cw%e)>v$VW33U߫JmG}O?+|%8r#(rYg9U G}{Q;;c @Yf,Hgf/~9>;   @2))o>c֤I~n;w)K7`tߕ gʄQֲ6`2YY3*𬠵C^ 띇.8 @Nbةe{Yȏp(5jFЏ(   )y+Z"g͛7EsE9ٷϏڮ,ޕ*CE)sXC++_4?﯊\bE'wޱGyŝkP|5ndi^wyAz8uTgBek߿@TC/pkhpWJ?#gCeH~ ڟa]?y㵥(~|T0]˕UhEٝa^ ~Tf)ƴG\ jXd~(9){'))*;M٠@olk#̶m2 eVV-M/HNdhpw3eOmueH(٪ur}m\ EE@4]jРAQ9u@H 2S6p%E@YePn 68йu~XC**sE4k.[|nzP*z3̲24siȾ+2B8{uywgzs{;E4z?5nUVu IR*Q8M6un>VV cܩS'Ӄ[9e/+(}L-=CՇ/\V?bH͜930YA\+:IQǎM?dI_~3߹M>ָqmڴ;..ZC%˚W߿hl7~H~le];gDq+;V喙wiC4Tt=h4[b)R@ ϰO'>8ǺԨ@@R[T$0+OC y}W\4ܷ֭s 0 Q7IoSO4nQ <(kW9s1߻;Ms_聮˂ L?Ll߾͘1ܙ OA^I`/Q5O/>zvm׼sL[o 2 {,(YuU CZe]+_LGɴ|r'oz@3Ys jE}ts9}y_tmzW[I˩iY/}^4fmS(9 eYC* 4M-6Kݝ[u*]IOOwMW b wќ+V=y0U_aXqSq> ƍ[ݺusu2]x#?ߥ\uN gF~g,s? ~/@@H 2S6pPQU#M-0]] n$* % Ɍ RO睛fD 6I6'ѻT<M.i8X%5)H.@ox xE5,7˔)Saտqʊװ䚻&>Q?lC/H4*lv˶m۬k׮ΏC1}_# gvDFz>nZ̃)]l{)3zu_vM}^V.XF@@%Ph.gn!(r: |Iv ^lZ'̨^ h[TGx眹.f͚9A𶼬r{z \`tvAXQ>ʶ0si.l  h6mڸλiZ+4-@`?A>Uhk?;7XY{`%Ѳe˜9UÇ_hM>ݫְ1wͮ\tE5I~P!g̘!>zhg~tty) _@?{뭷;>4L#ah_MSWsS@_L38VlAVmN ߹z;lveý:H% 6XֻM,ΙsA@RIp* % 0sL'CzJ.]>Wr=LAر67Wٿ>y6է>eֹs@ y̘1Om^ }|Ƞ p@YKvQ2lKY+V>:W_iN?=vgpL("+("gi"_`΢e6}Ҙں^q?]}[ lQeXmZ0C.QҼY@@^pF(' '`UVuܩšh_~,X]СCmҥvI'97x^@IDAT~Godt;>Üj|M2TI& *3n퓟 ժUNy8իAn:Uyu8}~{]C\phnLnZ̫,S{yܕqSԷre(ڙ @@HY){kJi8;4܏=S/ d^U=W6^Ϟ=ߚ^Jzy۞x≨ࣆm|˚/YDQ^.(W7z{a+ׯϮIoWƯ LEs*+2^ѐo_(L ڭ[7+.?)LK[:Vj@@]nx}H+Wj.5X[ŜSx 9fVM6   PDJ4K R*ކ we~I ܠA|ڽ{wu2_rzNqT|*T d:tӦMJ*Ms eiO>9] IuB# A_N;͞ζ/  @J tb9pS_jWd}տ->[:5*էF׏2  D br#ehsl7AzU4Qbް+QIF1_E(FذXhҋ*^ygsf޻}ϗ wN?}R[|yrz!+b{B"iEF .4mܸ$FPv# #=uT[v_\D 6mؽk:tp͂O ƍg͚53BSo EuMko#F׭4_O>6j(Wl;w4\LSxUT jpXfy3ήX'/lڗzժUAwO9[xuz~=BN (j]w召m33aH@ G@{*,fy^m=vImLiVa/ J*aݏ:غv:: @H1%|Y7IEA@jհs5ޞ:p.S%Jlネ_LBONf۲e͟?[h <_7+Wڒ%K|qXk ő~kueaÆ駟&f܋Nl۶m]W @Xn8}/ƛOďƫn#ͰYͪq+ }H@/W^=AժUރLN @@NI`…&VδB%#9"|B(\ړcǎ1{]s}ƌ&@a@.,@%W85)$/6  A=uJYk׮:{W"@(|+WC.#<_cZ LZ_~@L@r掙3g@ t&W?޼ys/Ν;c=Dd*RbSO!zc2I@Qx4 @iH@ u! @OgGK@@JL׭[>3<3Hsz#&m!@ɂ3@ @ ,8 %ի_o «!W-7|@t @ @ | @7ZrL2Ѐ @(&.&iB @`/(c8@@JpVL  @ @Jy%F}@ @ @ @IJ8I/ ӂ @ @ @ W+1E٠AEּyskٲU^#@ @ G`ֺu`UT3fiN @'=Jɓ F8SlĈA@ @ #{n[lYM6@ L93FX|yL 4I @ 5/_J,۷oO @ @ $!$(2իW,v1i 0."ڵi>? FL9{r dA @ @H"NnSYvm̒kԨ&@{_moV0p4ZCP  @ @ $%<Ǥ6nJ*ŤI@'PJA?c I@ @ @@@NK>ڼysb+V&@{~3hguL! @ @ @H:IwIgB۶mYlrb$ @`,3bD@ @ @  -*Ν;ݻmǎsNkذaOg we˖i}4 ={ӌ6 @iJjժt`%K p. Qh|ͦ !Pt쭁^ PP%J(h @H#W^B %p7@RJx׮]1i[4CA @ @JPq @xٳobMG&M}R@M$ @ @҄p\h @ f͚  @ @'s @ >Um,Ef_6): @ p#C@ @ P0np[nS:ZG^ax{!'@ @ 'ar @ ;wG_M>ܵ|R$۱s͘phT/ @ |8Ihرr`]t:uiN @ @D`޲56o[b=e26oҬ^L~n-, ڽF|;~ٚׯqd ~zkݺu0*U،34' @@ΞEH_~6r`Ç[݃4' @ t"PenָK~볉vXR'ܱk=*Ҙݻmٲ̗6m*xd[# @EIEI!@|Vdؾ};T @CVvh6Yq˲kjƸUycծ^)n @@%\|3 :!C؉',10ٳG` 8skQB# Tݽ0@ @  5eE |Xh=:8͛h@ weJ]{'-l켓ښ @ #zהA @ P ޢܴnLܦ5n%/sӜZ7cmZ6HԔ|@ @@.C @ z.J,wao}6vLwGM[>/.n @@j@N* @ @H!UlwEkmQϊ[Ͽmn[vJqȄ @R@X(tb*eYnVqm L{Lp@@ ҹ0}IڈoGqjT`ݽ>1$;UҥKi,K@ D!зoߢ^!@H*%J_Td2 $$Pt);虜on/X%*S_@cz} $-^KK @@jغu޽;8ʖ- c @պikӲ^1C@ @@ /$_WF J`?c6jȚ5k9 @Vd'kw*Yu*{KLj @' v @ @'0lT7~TkP @@* t*_]@ @@J8skQBע6ݽ @ >ZR@ @)2K];-@ ֬ @ bLusMX r] @@j`Ըrcǎ+WsҥթS'Hs@ @@,k7_ڽ' *UT]R$uUT3vB 'PG@~lȑAÇ[݃4' @ UuF^ vd+Xtݻw۲e˂oڴ)8 @ gΙ5 @ @|Vdؾ}{z) @ݏjm5THBe;gzO&H @ S(@@ 2N<8p`:Bc#T) @ Pt); [wR[S  @HONΪ!@q ,ZFu)8 7?۸8Ls\W۰y[L?*_"&DnL-@ $1$8L  @ ;g[6M lV _~5luޠQ'c.H@ @ % ed @  ? @қpz_V@ @@1'[/ Ӈ @@!@.dt{]tJ* ֭s@@SΒX  @@Z-]4dɒ9' @9@Ι5@߾}g @ %LY1@ d%z% @HzI  @(n$ @ @ P 'A7K.:u,QF9'/ @ @N8h@HY 64 @ @ O%紙5 @ @ @ D G @ @ @ PL  Ǵ!@ @ @ D pF`رr`.]X:u4' @ @G`ֺu`UT3fiN @'=J@~lȑÇݻiN @ @ ڶmk֭t15!$ݻw۲e˂mڴ)8 @ g93 @@/_޶ouV+[l N`޼y6y䠣SO=58 @ҋpz]oV @Ȗ!ClA={Z޽4'={ G&@ @C8}5+ H`ѢE6z蠞Gb $?f @@Q@.* @ @Œc=䓭~_G}d7[o5Xŋ?)SUV&OX֓O>i?/~ޣ>js=öy栾`YFo]WC `}5 Q{'Cؼ~֭o D}x6a„ _ A%N @ @ e eaXrmڴ)*AH5kִ:u$| @ i{Ƕm,[w{><ȏhkSO=վ;sb>}DƤG߿PoƍGYbs=NOA @@@NK< ҥUT)PݺusN @H{IŰ@ -D[T)C{ﵰ_|kD[ԩSps^y䑀\z~w}, 7`aFviْ%Kl̘19FN bJjժt`D5Pp@rE8WT  @ (Q" !"=P#}u%ꆅ6mژvYfvwLfΜiFڅ}._5ʚ7o e˖%2>loװaC駟n-C'18H@ŐիW gΔ!@@r@N, @@غukcpy|O]7- ƍ+,o:,O~-8׉BA˴w X̙cG+$*9W.(F|ׯ"  @Ҕp^x @GK.jԨQjA O|>&+c_[~7bfٯ_t4PΞy7o?0(eo =exp@ @ = ug Q*[L@@  W\qEjͫwb|/^{/giڢE ;wnuֵO>{_}AiW믿_I8 @ @ g @ "w5k۰[.C2+tv=oN뮻Ύ?x+Uɓw~7N]`AL 4I+BJ< >!@ /tV@ @EI38>޹sM81cǎ; uԱvSL W^zuawСvZ5?O mc? O;tD,>h裏qz EB`΢_>It @ (j^Uy6W\9o?_~ꩧ:Gu5CGonݺى:_EOwh1aOڕ)i*[n|k0tPG}y#lܵ%L@@?'-[˗/OPl@ xG<@ B# ۷aٲe4'@& h5w-{w>oҤ]|V Js\:ٵ^k:2M7Dm%7o^SJg{ya2ȩpܔ\Ryn9UOXP. a"0O}xSAYreFiN2==OeVjh6i@ *̿8 [: @8ѣ'{(`@^%p7t}ǹWaym*ZŊsJ*oQ7탆'|M6>GC?' -gq>ϙ@ɒ_(Y" |ذa'yNn*~e89^ҥKSޏ39K仌A}gf.rl?,Z?W}Mܝ!p:n$ @pw@ @ŚឋKɟqֵk״ E,>,nua;vR2dg-q\}~=9}sIT"s\d@Rp ]ⶔ.]XJi׭[78 !gϞY +@PGyq۶m[+Wg}N;R }W7l~+BYfqKO`WwuUk7F;Y6mfmvS% n9pӵ́ _7?n⥋)ѓmŹ=_ԭPjS)>Uҥ%K]|RB s!w/۷^! @P{7nl_͙3?j/^l.oFL%~z81#pʕ]v10ul0rȘxT ^ߑ6zgw=M,K~adtڎ=EЕ?;HtRjE{ K1U֩n:md{Qc:G{4֫ao37k٦-;d=OP+3f,YTMuouR} oFGC@ غukL5$ tn0`@pB<5r4hP˃>h=j֬Yuu w2aX{˟YIsx~:SrsU^v3cd[7 {ct/tdg]; Xk @ p  &p%XN PP%H{=cƌɳfHO`ԩ֯_?{gW(+jx޴a=~=JѮ=O_orɸ6o[{wjkn?\mvj ~ʔn}tRby[^nW۵sw3^>ţ}t(]rcַaVM(TljKwo|#puvVҷ{to=]^}֪뉝lc> @ȉ@ɜ*P@ @ASO38#=*S@ݺu{)93ݻwMOyOā3"yZZ5q:=I'uͪ틡 @BBH' @?%Kچ rƍYge=/^l͚5UTJM>P͕+Wv2=݊>I.n֦e}{sL%?7M8۪(Rϊ j zJwgƾXo_U_yʓ1SlOϰ\Bkr@ ux޼eUvUӞ w;wdܛTP.}e7o6\Wz4rٺu͞=;wƌCe]OHXd?޿߼"\ҦOnSL&Mʑ*Jn߾Ν_iӦٟgRJ1 ZSzՌ;Y.]N:.' @ *Ut%C q .>4džTa<`cƌC=};sU?*{.eL1??Kl߱ӶPjVheR\S*δ_ŧ47?` :Ԯfg{IDO`ֺufht&|:+]jURCxvi6ydիIK [~o޼/ƛ܀5k2>c-믿>#[|?{.2'QꫯΝ;}o߾@j tj^b*\\p1iҤb1o& @ BZ VZtQz^j7!O2KkN<ύW*tM˕בj]ճ7xC^۶m;cCؘ iM`vAbfg~ꩧlĉV-W/։ܙ2[~Ҕn+44\&wx0umdvn +a4pj\S8o,nٲeDF+okCZժUM޶Ç;('xl-ݺuE?WUOk׮I5_|s*l·v|1c腸ҥbV0QN "O, $˛S9Ce'aYO9G[C!M0mڼӜ;_M[";/:Xv< /N3^a;c5>_ho6vY_Sx[/=)Fi~74ޅڙ މmA똄Sg[n ?oNt=zt9U^?> =ys%wy}<ϏUzͧծ!ȯ۸Ŧ]fg.Yyr˻ٱm[5MeKY3|~GI '?(߿zǎ~8^ zzD+a*F@Qz_jj^z:rU?Y+=qӹ?Ouv+@  %T4?uo~充}X-u|Ӊߗp٩Ȉ5A (ō7hGqS/]|q}f%38t=Ulڌ;&kQ٩A֪U|3QC(#O`YqG> PpgH !0 ֣{Ҝ@ ?7tֵg|/vF_~uwR[3Bm lRF̃8O|9x&3A{\ yueuNխ{@ۼjy'z$nc@Oͫ5S͆wlPxa6us702 Am;}4ξ0K7ײ^ΰ=u{r@ͪ6?&]ӎ>z vaMn=~y}ϣs'y2zE< B~gpvזّJ2gW{UӅβϊ?2Ėy'tMKO`Y%(Z;_g2*ޱ~X蓽Ї.,kG AR??9۽{wӑl6j(^`Gpq5CVR%;#n#FT޿n?kE5O@xxMxΝYC P-ZdGy< Ú׳qh ?CW'S[LboԎi;gl"N-#& sMò_7T9X=7{/6 5yGhozO!V咸oBl;op~q?WXhy_}7{֞ ]=Iw"k1ZJ孫s>ǣAKafzL7Iu0W8sbyY2 MdǵkE07g Xy$?{'6: c&̵_`e0ͲjU?^p.Ra#(F=Cc`=|>$*V(kw\if:hS=Ymk=!_|> VLbn$BG 1ה5WӪT,gӮ>m _,wy-=phjo_cW6إث\1G{΋L%;Sxl O1l{=Z~3bpn',Akx"Xa=~S)s,KPʅcz}Hfx^)tn燅8@[<#gK8 !Pz6k,ۼys038SA׿W :?I|QLQ$^tEeXSsc>Q%JJؕmy뭷9uQ6l0oL[F)d{GwK/d^{ߧG[Kf̘An_'V 7t۩S'~t~,}WG/sϵW_}5hK{oJKoٲ%Yft$yuj۶EkI8S'9[nφjvE]\<0o'1wIdnԨ/f+,u H{N @ chz׍7.;dό {˄@wh(tŚ> :^???x{_W0[~xNh'o|\j{)+cJyR韗32pg{-+xWe=;U5=kYuc]v-O#WF4-,aK*e aXOohHpֹ.yzQ- |uwvs+N܆ܤ}1@۷ Ǫ}< 2{}EG /w}HOVY&M_7#,kذ/ *yB͍NRMbqH@?yMua4g }_~O^ė]v/nKI +V}1T/\~O^vmzYgٸq۫c.P#}ݺGicƌE]b榄Bxk%xB'|{@k<_~e_Wxx@yHTݻ/m$KX-,a[%}ds@ } Nߥ}M@!QA\x}=/Ƈ @p t k,ߞGm"瑫u5o#SgXy#V4&ZvљJOxk۰Cس5\pakT7~(:yռ wt{^mL>q֙!/O ^[pٲ3 e;ل=,2GUb7m?hW57ZoQ?2lĸ+K}Wy9hm$~ Q= $(2Zo3qSv mezv*OM6W^y.r,֬Y㋒|%J\t&Yb.W˶ Z|[n?A1WZ减0۱cG;駟ɍ7苙ʓGG^ѷ~=~fH~apkݴn9WJ~5S [#/`?L|;%|CW,'j|@MjXWƔ4`9QXhׯ_?UҘ^K7)(ܳDa=NދĠL/SbI $#WBL{$JP]ܹsdx/[@z5Di asT^xN c_T̙cz NB:KoٲeO׍ =zNxezDiZ~Zᰵ}L[;<_v~M%V W^NU]Xkͫ~W+|_eO=m>€3=;~]_ǘ $ xx} @@cC8#԰!{ [2C]o<ZB#yX)ܤ Wڌ_5VtEyq<'W~L~uRxlobEݳ <T?ʞp8p;yEBko;ysaC%幒./W>= BWX.#TX5R ,Ν3#H'L^['ɻיey*8d{/Z=+ ,Q{9/+2n- yʋT&љR /&2&oTm!$?O?}sX 3/OdgṺ򤕰,R$xK޶n`֋`ۙ[Έp$/_+. ;Z$ ˛WvM7^~^Hv{ u(*رc*nfW_A,Y`.6lZKYeUT{$KDh[uZ0VL5Bk=3J@&LAҞ@Æ MKV'?3{?~~vuvz~5Ů<#jG0H'o$f{~ *e7۶'\[ٙzxͱ˄8O{=$e/]#䗵 2#'5 ֣6ZgO nyKDw¾g{VlyG[/Pi~Z}<3~ƒ=L "_!B8̀8(B7n>˗/o:TDQ_LooH4LaMԏn\~_$?_s͓0kzPiZ%Z›˺qV xb~4\m:5]{Gvjk>5o_sڵKnV{w ]}h- }I`U&~wQ'voĨcczq|R1U ۼE^g"g^iac{Džam՟O=y`}{ANdKb2$nԭYQ;()Dtɀ @(6~Ǐ˃3jzfDUn׮_EIIXd7|FCNV¢,*Qxs̙3g?׿zjժσ4o O}4|E>  A03n,vFoExAD)d$jқ pn¡J5AO\hyvœ-q7+W (Q b"7:OKl(;R¥n^QfZWxJ*silu7rdj1uժU#$تkU /x3 j븻6SGm1izU߯CA(,hi9CWٵ^X~~sa^yBq&^≏iZ"?^$2NL!uě.7/86F:/soX4]Z4 kb Uη\rb6FbynژBm?ڨn7onN|7m5K_GZu8 @H=ڇ= +];ׯ Jkx_Y5\s֬YA]'*CbIQ{J,UBѹ{"}s:C z_}/i߾|wb[O죯2F}y֐*۾cg Pܶ}=:hd83 }zs+3Co6q>V|Y[K~w$t{O8nx7'>qWn7ݽ{==nۆ= hzNtӘ @@%:B!}æpeZ/ˣ^L%=AG̸w׳y5=/q&OQEZ{70—^z_gq/b;%B;Zaæ }7Z߾}EWϊ رcƝP z;p=6|[jEٓO>w/Y$ìŋ[^LbE2[eW^ }֧O(gzncq…~Xi ]l˾mԩuI.[tZ'YuԼڽ^yvq@pF@ol;GkH$UX1J6Y0 JyroR]زMS"iʮ c#|Ser2+HkW0Q Å kn]ɨ]+GEd}gILFM_Qjl}]_}Ghu^<4w?jC@Q~fyƩZuN?pY? gŜ7R9n}G&>C9Ԫ4πb43_yϳW ?z|Y#[YcRg\w>Ns)vZǃ%PI'4@ OZ$L@NܛmZa?ػ ob*TD,F0^G7>ё&NeXxMBdgj~ԟLԍn6ݜ/šMͨM`9Z8K Y,Ѫ Sd68ÙDT*[P]XGNݵR:GnX7 -sk-4\#뚩UQ\>_ B]+5@,k@Xָ k$<= S}9~~%  k-Wt9:gXtwfY+q"Ӿa9\~ÃM!\ʗx`.׹~>?9D6}ok~{ᕵ Ύ{./卛Y2ضc}8fJ/'YyhhQ7e]ﱋ'NJx]N,[ct  @9#̙{|B ȻU&.j@M SܿSS?yе^{+?1SZp4`SBsk]ϱCs&`֧hs5gR5}K/N ۬xz-SWn0笷7ڻ%Gn( ̋'*JxC tcB̸uJpU8z躨pH ` tR7?ׇ֩kn uo| aZ­Ɩ{guq}SM!@^ p&Xa[*}WI/Uӏ^ >Ћ6џA+ k[֍uu%=_lq;yRIN4ZԳÚ׋)֞wɋhX߰?q]qe|?Edi&ӏEksSFqSd |+&԰٢ϋWB71~3۷4ǻ귍ӲV {3TqɘϲKٹ'9Oºv:Ⱥu<Țկe+_mg,3ٷ^靻vMh/7ý_m޶&{aG~7_C6K8wo>p{ PN/XҶ|^DL^߃nY"9ڻ^ ɒ_>s E9.}CrKܒD/mq&ybaWm#~W9Js`~ b5g˗/DӰ+XgZg=k%'p=5~];M"s{~ƭM"qOǴ9ԉХ%}nyV{wZd{G{a1@ {ww?+? @  'UaN%h 7 H˦qI ΢a!..,Eˢ֡y GF9Tݜ^>֥zr7t}ukM)X$nxz$uH*h'Vk kPxCKCRh QZr;ߨ^P!AXB&v!@ +SƾtU\xᜣЯC @%hcz>[>3E oU߾i@( Y( : bL$R3wl~pm.?2xl<1?suֿ7qи_~KsstڱBK 'qTvmէ<% vg6?}jW!ZsL4濷yG@ FlRVg|}" @ P(>bk{zQ(}  @( U^ q+*Jץ<7gDDvӔ鄶hQ1ԉJtryj=H%K0D|J$x6QyUx^뺺rI5#;Ni][TG;Ubat^oNtmy\B sax*!1l輰 aR¦ !1<-s mV}:#,kx^#BS'̙R~$J,^kՑ(|ªZjB׉b<_{ GMsl), S9iMu㐆;{Suvkc] D^z\ @%P2$/ daI=#]ʛ6m`6y:p~\j>ǛҤIQyZ+9G!VaӛZ$pYmy$3*V %ǫW5Tt8+V<6w޾NQk>]ڰ(,AV󏾜/k|'*k{XUE'2~\'nZ^Tt׎O@`Yޟ #B @ @ ט((QF@" #X֪U+2*zz0߸qcVD°7j3sѵD&]Xs7*1,lJhԑu6!V;:TO_r%֩S~`(( @ @@bP&O%JF=|DhB%eWDns{B)絟1ŋު.qvsȪui^ashc]˗7<ás .gWO<$@>Qc9EM^/l ,߰z<|.8^u N{LׁA @ @@$50k-%K!sH'-;/\ UG C*:*vJ'@9*f7oWަ.uS%̯\2T.!X"w]7yIHumq0Z&ZEO]ywm4P6zϪO o`ט_/ DVZ 1TQ!ڵ:HbB=6j  @ @ Sʲ 1M^ 5 WD/ N<&AOޫT&2צkTœ꫎61%J(t{k;g y5 :E%JT([>/2cV?h-Q4%됸!;|wu=5\cEM!d@d^&Я_?9rd0í{A@ P\ |^WJED(*ׂ]\QEv{mXA(*(V tARypg ! I|>N}fK3Q@Ȥaf vs5k^n[pQ &@ #);jUVis'q926nho{ӗGT՟zȮsrf  ^uQpg}.I&Vjuq\  C ;L]$kVZt/[@@ա9n&Fi7p-]#1 S.Q,CPZ2$)*Uԡq0ٿy%D@  G/&  K{ӧ}}#6b,x˳'A@ czڴi pz"vڴiM2Zli%JptƹֲBvEaNtÝ7fϞ*6mJ*qfTOצ֭h:߻|+Wڌ3UVVhQ7->mR7ܹs04VFv-?c",kZCu_UX1zq6^|2e"oR,+Wl5=!E`Wr6  \hBVr9+R(YeJs}.Q4ZŽ}kT*k<2{+`ʕ2eZ+/eeK鵫U_d,zBۤb=ߚ7oƍgj ˗2[-]m֦N.MA܋/Zhae˖:g}ժU5\z (j[)2ntynڵk۵kgGmҤIn[k]t h|6j(8qb>Zo׮]RJvځh~zm-/5,Xgmosଂźe۷ooʕsܥK';udowuaРAn[ ުv '^p:uj7]v{TOÛ1sݷS@@ȿ.;5_];l6یy&xOºuhfժdŊ{qb{㓟l̷Ν[>vpjVFEwDczn{ͯlA(Tp?]Pg?[޳u)pzZv>jkŚ73/Y=l+ggcMVsO=&_˗)aWq5=]U+WzWşڷ?/>dwO3/:{!#ɁU枝e7nb};]Z[5U%ך'uEyk}~1b/gm:@@ (0/ڵ^k .t٧~k5N{A߽zV@cǎ9wmY=l^uS&ZX2X?|{]pTbŊ_n>,Բݺu>#t(`jժG}K/dW^yˆU0pwy-ZM5Gx)T`VÈ(@k +,f۹tU;ܔnV+}C%^%+ֆ='2}/&b9 /`Q"BC }-[7Wt*p»n<gϞO>邆2CNe.[0UX-]2:a{-VSfem֭.ू 7nl+V^zSO=4U TE5n!CXÆ W^qX-=X-rT ov7_2 sT:7yu z~geU+]3H{?cZネsؗ/ScBeF@@ 챘o\p!SSX7$a7bco䶇uvX,^z3Vm wVLO~􂫣/G\ t^8N9XAt<=?}WZ}/:YWׄ?sZL7u{埾LV9l/?sWO_r@HO@\ۗ䤀*u o)kUAV3gںu'p[5kָFU dɒPW٩~Sg>h \?P+SjW]u{Q3K^xTʠU#z8h|cIO\Wf_US ]#u㏃UZw;t,M oU]w3 еLk]J{+SXejsYÚ8 NsJ4MdWsA@>@[*rqU9ZEdC^{Neo_ ^it&sm”6}R7�`_Yʌ};6^ph=5+ꕡn |dLA~m̏y5#72=?$]wk\ pMxkWrz[Ǝ8;Â/}Ƚ1A?pЪ߅M:>a].{_V^27V/Jz߶ն{ԭ]yϘ }i]Z+_{Eo[/=1NJ8ͨ, 7TG~I-@@(_j 0.* w}w5?n8=EC=k8ŋioM͛7v衇|+Y*uիWaez֦M;O݌4? 2UZZM]Ϳ8 +3Y-Yyijsu}qÇRnc}wV0>QܹUPe0N:$r@`_&f_c" @8:[Ωv4dV a3E~vA;rcWaU{Y6뻾{tOզ> `oZe 2^p{VwSmĽ){tz޸VoBCןao7'ؐc#lX+b^a#veIh_˽ ~MO}x.Q4"<%:e>+colfW~þ~{ӟ\*'ZsOo?JS+x+Sϫ51j#=WyX-Sf/w3ϙ-Loy9Q@ȄرUIpXc*STMQarT! ܹs6kexn`Q`YO5nXb>7ό1}attOr 3UVɓUEVƬm񷣟5a8(xxj*1oբ3?#:LhK.ݺu3e6k$xVif>h|O'4IUfYYϾWkJ&O+?p3f;q]՟> 2z͔Mgk+[Z*s36|@@%^ӬL tRM?[i^umVƮMԶlnzdժR޺8n.啘*XZ5G_{A`e8kqt7Iowl̳ j%Oѭ7^O87(ᴶmѢE.2#슧~=۫ڵkݢ2eۅa@N@%tTo9zJ jxe~ᇶxbꪫ\vD*VhjoFgy={+K/ϟo\pA2jzN|˖-n\bmLcCNyE2l7|sPj9X5g*`ڪU+~WLuZtܰEvg{뭷n4<#MV/+OĻJo4O?/bە}1UfX@IDATva @@iu۶ƲUjZ U1۽p3A1;{+nwmvWBh rAd;92%+}sb]I>A' J%/#$ڗ  1h9#?r!.ٷo_7Ygq*+xm&d…G2u5l52 @dq~LP *Vٶ~`SpV8XeU0v+Cc k]e*45WqvEOUgΜʈ^ *yn `iwOӦMMc.]:"G> 5z뭈2ֵkv)3Wbի猔-qO5**Q 뮻x]7tFG5i @n'7甯4ބJMwwURş@7U/m.{=Eߍ nۗ]R˪]j[eeO~妈-+2 6Y]'h Z:` aS{^'ˇyYmh-r*Q,xru77&S%íJ2٘KV/׬ҕ2Z[g>*C+}]oA@rM>ݺvjƍs[-5k,.:V6<N:$M۶m&M*?,X{嗛d>l{{n^h_dYhQۼy +X˦q{;5+1tHռ8HjWשcpnO=ܴjsk:?>|[OQѾ^ݪBkP9ieA+Hs~~G9QS^t례Jl'ڏ  /SغcʿNcƌ#ʼn|@'cu}%ٴnD/|[WVv ;앃߰OeP]#KOޤNxu}ϴS '޷W>!b(׽϶EX |Te5*ҡ~Yd~Y4ײ>ݏc1}k\L%XlBJL.QZuxV󖬌Ye FҞ2|lK7 .x! /? * Z5e*,;t(誀]íQF.mmD5Szmlzej^zEK`'|'t+WtN27nn\1;7F2^cm=x.YeWc +Yp\1VV??"s:|wiLW+ g;xсMeR͘gx3l>/G7^[պcǵnhȃzuuccx }9Ev'N&jJ^kfk\yàpqo pv/ 7=z€tߝe;_;DsD.]\@@ h 2y*WVFsuqecVx LMԞ}ٸT1駟NlW& {ꩧOɓ'gDᡇqܓy?㸻s#wyGJ8ۿDhyϞ=&@< 8] < Y+oO*GY{Tz^_Btb6|P\TJAۻo~69&f{!@hP>Zib^PX+eSݢs" ^y}s:;zEpX} ;CwwqN/@]=Ԩ\*߽E\V+;6yf3~ql ;A]l';INj{p1u},d>Ty嚍h@lwwn#s>ݸƚ? bSP\e˄Qka hw bEvU{^Fʵy(hnz}|rzZci~K/@LrG>ׯ5kv\lYA@ U~SթSw/\y!8j?j*Mo"Yg.7-PW_z4v8*MCH_pFl  @(q=ofm۾í_lWy>`{q{7nzMW/'Ng}`Z AQY<},޻IqOX~+R<䥱e7[-od=4?3g*]gïgyɊ6tgv%ݗRYp bG5kq⬀v9 kը5_Úԭf T-[ij/]wWRocog,N^йWqݪUl6a<<+e ^b]|?n-ZȓI# yUB vmG^~1bD^=}-{ y* E4oPno)o3    %wG{{Q @_uhU?@ #MVf{M,tΝy˖-jժrYɒ% 6Xnjw֡C_{-Zb [l=_tR+TUR%bݎ;l6ydGi 4H1#A@@RT@Rڹ},0qD[|ypڵR#X   ]pA^{L.B{WY/cǎWB .{G؏?薝xv뭷9cK,q]vqG+69mƌ_~tk֬ iBCE]@ ƍ]LӃ)>tQt JE^9f/Zs}u"oو )˽뮻O? Ni̘13d@@R+vƎqm&L?~|V0jSN9Ţ -XTyӦMܹ3b^3hٳKq7Wp=lܹ6hР۰ YL  /@@@`/+# G݋.ke˶o>_~q喵`…x`&FElgKHwhӦvg~n[omذtMvZ%}u,d@@@ eKͅ" oᲛTR~:tQGtva5 #с[?_ᄈ͛7JF *7^Sv7kѢxvI'E>: Ugi~^{lʔ)2ߔg@@@t\. ~kݺ?3 N[nv5׸.N`X.ucqҤIֶmۈƦx$yv-,̂GC=c]7WݺuM۩;t~L#  /j@@@2(PfM;w{|g.gl_nO=[v"W^ٽ#MDM \ҪVh#  SJ@B@@H_ ]b}G6o<,T%/e ]#˔)c+VX73Wu=4@@@ 8^s@@@ |́ǭLwѕ|VgLp cۇ'֨Qn&;3#EԫW/z   ) @8^zڵRJGi @@! Zƍm֬YSN ܡC+Z鳋256 g;eĺu>`Zj*bPV:z} Xlٲt U  qb,8p`Hw  I@cN>ݔH *72K,Xڷo6=oNgY}s=2e|Znwuˮ9cuֵѣG[޽v/pWL#yJ@?WΙE@rWsA<+0m4;lȐ!@زeܹ3x)R$;CL@%u5kf Bvav3t-/C5q{M}]f͘{A5\?#VD 7f>n*Ud={ Ŋ(k)3   ^ |s #F?vu=ظqL_k{"?ڵk<#i茶LwĎyF@8g}֍ڣG ̭UVč|o-[ *_/ʖϺi#.8NxBnjc_|͛7/*V޳&M˘@@@H-zs +V+"כܞ2e*ď,&lЗz@ ?g+Fѯ_?WVmw}}i4A"D#<͖+W_} io v_u4i]tM8{GO?MC 3uRaڴiڗ@@ DHZse  =Ǖ^@  >}XZZ_o߾{; /=)@t m۶坓Llnonݻw~e  ?^$?^W@rNpYs( բdlPԩS3}W_}&LY:'΁c?bŊE\ƋѣG￿W{uPvF@\-ƺ! d\pƭ2 A,>!W+Vnmr.eםY(8guRHxWlшNbk͒߿[.K@/F=+@@Wp{wܙdkV!ogk׮듘1c 6lK@X-guxdTtnذ!b)0tPKT&jժq/*˗B@H=7F\tR"A@H.@8kQM6v[γիg |76|4j(eZj⮻;lٲeqױ}-u_ϛeʔ8񬸡&Cfr\`6xǭ[]yqo͛]Mw @@ >h۲e&ٳg?Z\-  3/0`5j146j˖-3{"M lstY\`=ƻ[~7:"KׯH8~Ŋ݅h:uU?zuYtiFy_) +/w?  :Eʕ+[ ~s\)  .@ <6uԸxUWY-Be9s1q릇 &]B֭[#_pyfȌJ߯^4f… SN}rѣߏ{6'|r)q U梋.g#U7"6d@@@@ 4@TXbEu]wݕ.c=f2(Ymnl@6 \}6i$7n}vYge>Tb*THKרR^{mT>w]B\\nnݺX    RT_~hJ}qq~kr)͘1 !jԨaGyuNd;S"etƯK/Ea4no؀@ȷa%wnŋϷɅ!@ww;w NJ*֤I`ޟn+WgPBvG?}5k+Zխ}qu>M<=VXazuQhѢ~s͚5ZjAd۷oQFw/Ν;ۥ7ĉ53bR\DYuJ(Ӎ!\RJ/^p|~L .O>S[t;}PiN:Y˖-\ϞaYfz,\нFw>n~-x~wk֬_$^,C 76 }"?y~z( uE Z%/Oo< Ӽ1#6ևվMz Fl}M4hP?@T>py7ˤ%k믿Ҽߑ^o7&p̵]r%ߑvZ̶i^7 /LGَ  ,Y|n.W\Z>}6mڔ/M?ׯgw;wnZ6mӞ|Ɉ}FE_tyxy}iL2iv[L_^06b38# D|͚5] -7n\܍o?廉)S$&mѢEi:tH '& ly7G'C q g!@ h'mh9ݩt駛dž oN"%O?m/uY~:t)#)J, -mW_}u%yt-ѝzL DaÆ|rUs˗7?;2t6l[`ewu3Ç! @ <.3MW݀2NW@~Kz2/#(ck׮.5z×_~U"T%DͻU"2mXvLLw'YSֳ=hk׼1@użn̺wAԷ2Ǐ*b~ر믿X?e2=:f@Y^T%2@V *Ic pPI/daK@_ Vˋ.(PJw$Z_*G8bdJ@_t%J*9u5טnӸOu\zħ/!n,\C,ø! @j xXF;<4 'y矏8W$*%rˍ7X ᦒ "&pSyc/8M+@7DDVTڵgϞy"8[@7 =s.0 ̙3^-JW\lU^x+̜4iRL5~ؼ&8^YcJ-'_ٶ}77jzm./L?M on5:AUF1K/9g`]QYv7ۆ ‹Oӧm+pS^Ki8>i\d]Ɗw!Qc-@8?Bo4 @ g7FyU۳>k83Y9=! :7P[o>F,d@@ 4ĄJh7&%%AF<#`ntS0\{q7Fb rN%#Viʕ~Ƣܴ *c66{l]m*_lX wast68^{);vhrٳ ;+_իP07_tp8KWU'^|.^MsQ\!oVO~:_={ejA>8"xe$@vʎN xGӗSIh 9'8c[HC txUW%  Z*Ž2TE)ݔ%ĝ;wc9Ǝ:ꨄ3~? $*s8ܚ4i 3vv뭷&]h?}fWv/mڴiV~};c]M6nR d+6խ[׍ l^YƖ.]ꆕXsq .wyV2M 4aÆE$IK_'U1^'@C ;g.}+_իW7x\P CC9} ?ުV^4Y" uXFNSƔzʒ   @([)J**#MId\{ќkN:ƣU2ܖ-[JMYX*7UfnJHKVZX7' ׷*z}+DlfUp8(^jgqFҟ ֩S'?Fl.+Gr>Ac/+RF?+{7%JPx-ۓN22#@ ̨O NĄƞ&FG q/^SFw=.}UpZj1  "\Y6lUQiO W4g}fZJz VA۪rf_F:Jҹe *#i 1cYga5vp]9]pFJ/+ˆYo 83|N&ѼޱcG,G KO͒TB_뮣sdld71T)pJ /t @@ 5U9ѢE&WR QnڵIϷQF?ڸq>p3fĔV'owc*/0NzPoMԔq7M}B-Y;ݼiӦ~7vkw&jT=Bkctl#Fp;F$4t6lo^'u!*To"B>J@x}^@ 1c 0 i=8p>5  @j MןG 7Dߘjժri;vtj[c_{*|r)P=ztdɒqdp"WmC'_vOGסwUWY]~8&TQ?>aYffJ:kc,V:E7 LP7Z}%7mJFd/NAHW@"&OlGq#8LCB$kK,Iu  X --?x{srj ŔHVntKB.em֭ ~In7t{=~B_q 6m֬Yn:|כJ]k 92ު?Dӹsg;" :ϙ3'bLÛi2}1w\.uO?ԭGHzn2[@^.S;JsrzyL@u>Tvݎ=XWʉ;j " @J hlC>rѢEW\ir 9R"T3_|=Cַo_w!*{WDW>X_BUK7\n޼NpۆK$ʦ V&n޾}{p,ӛM k1&L`۷L*UPN4,~D^e*nw3ٺu)(^{Wcok\V2ULM{1ڵ[@x5lҥn^(7n,SO7vw<#m@[GuT:a6%TF@6x`CrVNK+Hh1TI76ĻAxwN4)惧N/liRY~/?E3ز~~fO}?СCҿP7^>} _ۆe hٲ}>FdeAe~v_owϢK@T ߅ **ZLSypQ)h:8+ozp .UP:܎>踟mtP۶muVfM\k _5U3gtO>qVJlЍF'N4mnq+fO<)Nv$M~gs^7)(rguV*),/}os?~^ꫯX "GC W xwy?D֮]ϓKMo:vKMbSn-?#Ik?95H7dȐn(+xxxꩧ,;3p2w^ $TڻsFK3tdׅrsZyQ y7seﱽ@r}kѢE]eyA UwK㏃5J#t7Ǜ̥yYa:uG}JyLUK? 7A92܇DO{c`!OZX`|VZl93@ w 7.w g@2LW1Yeu].59]pv'Zm^0UBPVkF!ƎUv4>J,{AW+4F'/ʖgU(8 wyǔ)cY}ƫro?rV^=m۶ޗ_~}Q6zZټWDQFa]saG=m:LYFIGY-zjҥgA}ϛ7ϕHwǦ?@ ? 䪗[}P!!Wh#M s_ Je $ I `^&+UWP"W]u?]|ZwM TJY7t'k *ɺ>ޞ;w)2]rXtrK/dÇwqUVa4h^;VnjEѿt*Ǭq}՟C^+T[%qչPpUTZY^58YfK؟*{!#U#G(K7DA p(0 `?h?#w ٥ 4򊀾,ԇG=ԔR@cɚ>o Gtӝɚ>tn޼9&/Yfwj܏蟣3kݺuk\fM6I7b% mӦ;[*͛7υW)!   *w@ g-=g'pB]66r@zC9&NhGq+}/SC@@@H?4̙ ,O @8P2w˺vj7n4Klb۶mschM:uʥ1"6uMN+?4~5$!QnTCG?y5ul@IDATkm&݆    @…sP<i^C@ s˗/U\J[lYr  C\"2dHr  @ѢEm֭1;* B76{ʘӽ5S%f9 @HeRv@@@@@@@ ? O&ׂ      )-@8_~.@@@@@@jr-       S@@@@@@@ ? O&ׂ      )-@8_~.@@@@@@jr-       S@@@@@@@ ? O&ׂ      )-@8_~.@@@@@@jr-       S@@@@@@@ ? O&ׂ      )-@8_~.@@@@@@jr-       S@@@@@@@ ? O&ׂ      )-@8_~.@@@@@@jr-       R'xΈeY9ӻwo۲ˈ8cH"dsΈ~+VX*UY?;YY"Cq&yA -ˆ3'JhFI?qPBx0LZ'gO"?!8⠄eg֭sf2+?{-M{,C ;gn6mذ!vryOOdfGD='Z$rOGhy?sE"yDzD='Z$rOGN\}x$'_kzJ@g)="      D>a       @ ʔe}6md7n̶S/Q*U*'MoڴiL*U3f(Nd?ngq%FI?qPBx0LgСֿ~ِ!Cg!8(EBq&yA -ˆ3'JhFɬ|ԬYӶms-[X"Ebgł~@Wo_TOt~g+Wα%Us 8P6?ty5 Nͫft?l^6=ejw(P`LMC ZA'8L^OpY,F (mt      h       @ 6Z:F@@@@@@rVpzs4@@@@@@@ g-#      9+@8g9       m@@@@@@@  @@@@@@6FK       @ Yo      dlc@@@@@@@ g7GC@@@@@@Mp1       s֛!      &P(zc@@@@@@`z6:t{,g"}~چ qRJ˘A'@8&     mѢEAժU Ș+l̙o޼9bȝΝ g      =&c@@@@@@@ w Ν g      c1;      @* h iӦ=Cf͚fذa϶l2;ìvIJKK_Ս[tit۷۬Ylܹ֨Q#(P@xl^d|rqVY{v,fϞm7 >3_e/?6mdʕ+l eh{WbsEXT^vaW_}/_ku- +޵kW܎(_N:~uN'tyv[-s9dCLN4:t/( AP@$(H IIʓ$A AD2"sXr7ΜNթ{MVϯ mɓG B/BR@IFVj}'|s$`odXN$@$@$@$@$@$@$@$@$@$@$t kNK9uI,@ L$ץK ҴÇˌ3l2ڇwy˓O>)C xͬ'OJ?ӧO۫ի'=]tIڷoDuիW;wN,X O<;\Lhk׮{mrժUҲeK޽[o]cƌpgϞ DehB/^Du;iӦ?,+W/w}W+Vp|xÇy n _~_~Y:uDo?PEa 3lnM~۶mSyw:sL5N] ׯ #m{'mZ„ S^Ռkښ5kŋVr'#ieK/ֽ{VyX0h baìc 6;o<%ۿXo]@*< wDh^Ӑ{kľ>,R N!ETnW]洅gq>}T)Rȵkל2 ^d$O\#5rp=B$#-#OiȽ6T1.]Z2ᕝ<_ul'4xΝ[LR>nӦ !2Btkƺ\osVzQtR ܶ6xSd:ܴiS]]FxiCw߾}5*"Dk :t`ogW6yτwg6lpnE 9%@`I @$aM~Y]lrhN"<3>{K"V*QY C5!     !zЌ     KΝ=vCZĉ^zD֬Y=ю7syF#Tɓ'=RlwͲ`ː_^;ҢE O0x0gΜ54iRwAxEnt[P ^ **Ra !$@$@$@$@$zF)ʕS3S x#гgOGqU%Gvaj„ D️G'sΕ:uBW{3믿Vy}[xh#ǰi/_Vh矗]z!?nv__Pݣai$/zKHHHHH[L#    meʔ kH /r-ZT̜j*}YS^ ⪿_cO> _U-Z$WVyq/^v V-n߾ŋBU{3.[ix0=&LpW@)X ]tiscƌ#G Zj7$`#@$@$@$@$@$@Bvawz8u*C9…WJ5Θ1 e Êo-[)S Br!1q u;L 4j?GҩS'I` ݻw1}tix.|,X`sHHHHH;:HR*3X}衇UVnu2xʔ)2o<5@,O.]8&O\vo7|'*/}G(f0 4<^Mbw^2dYET[yp9VOM&ÇG}T-5aLwӡyx9!t.eʫBi-ZPǓ'OVX\lƂ>P@?Pj֬l;w\= zOL$@$@$@$@AK"|(Dxۄ0hń XmbBZn-MzL(@<0_UޣGV3V㛆Ƀݻw 2̙#!gHHHHH b%O?Ty"}6@k"ጵ> ye:4֮];LHDٜ_re:]o/BY[JJ ѣk h7CaAmbuo(a/gB;@(}nN󼹟+W.Yb _ns""@8"BG)Ǐ8ŦfʔjiYߌbdq/Ӑim&M?;LX Lc6kxN:^ES:t,]TEHaCgWZ'N,A֋󈦄\۶mSl1D4(vquN8"<w>}},E#`c}     &i&Kdj _ƫVZjE6A@Ee \!c/P ̙3ޚp+Ga Fьl=l5j|tx7B)_ck$A,  $/L$WP!F dG*U  a0M-\P]G&9.& 0t끷v͚5=_\mPy뭷E~amne=| >ܒ D<>zoZ.R|´gmcٲe|"cXA 0D|OT-&ڈy]`}|2  `Av˗/WHd˖-n x%"L6v ǥw1] e=Ex8mM6u\e1lĒt_%       C=)I"IFIݺuBtգSNy͛*ί^Z?SNVJHHHO00sεnK/Y \ٮ]TUGh'3_^=*^|߯"+Y1dB!5HHHHHHH@0$@$`#0m4)Rh1c,Y,۷o>Hmۦȭ[T 䙀wHHH k߾}!fXe˖|]8 9"c{ƣ{\|Q:4?-[֣ L l 6s !r]nIHHHHHHH@_ @FN!P=zTx r.\ IHH P \xQD|X!)y>- G5k֔:bE#=#f$@!FPBខaΝ2rHHL}s^EuM.\`Y|N: :DݻHW{=AD?jæ]={l̙}gyF6l(᫽~Mq޶n#u1M4rVΉ'| V% @<#yږ-[Z-ZD1'=2K1FpdC$@Dwޒ#GG8p:tH׎%Lx^qԩNդZjҬY3s,$  @#cQ>1͛Tg1b@tEN *ƍ`s&tfi׮|*_0B0f`_vta͞=mʕ+W2Ӄ* @P,>8 xH25ҥK^=ܼyS:vh/V#ٶm[>ҥ sVhܒ <xBr8*eۀ0Yzuɜ9s71dիgE˚5k@۷o޺7nٲe-`kg&ŋ7O}<)RI$q˃/X@@ޫg}V.4z$@$@$@~gdrz^k]vCf-]L^$;?orׅ/]PW8xa[ bc$@$@$(kC D1c敀gdo/VIf 9u{)gΜчRT)k1/_,8 ȑ#NEXfEd.EʳX7uVyG9sy2&H}]M&"qҥ>sNp*N,6mCP[[n-X3߃=̵IHH'ӧO ~/_^&M$~أJoֻvʔ)2a\iF?n˝ ;ȠI dن=e!np7yLem%"߾ˣx쌕2iZ9 x@$@$@A@pD> @Ono \oaq>I:$@$@$ :2ı|wsiӦSfGcQ+]/bSx>|[nũ\rs< -\:=N.L6ߺ>P]?QsK$@$@ND1NgEn[xBd})kaÆɞ={WS$ kIDh$@$@$@c<ӎ}}?w;g.V3O`1zT"B*'/F.?rekW("+M1޼vkQT(G_z]9KDTWR%7c  :Hb̙3I&n1&- Gهrʩp#F)שS1/BNl$@$@$@$ Lt !YFXۃH:,sZ^dIٴi`{\ ʩs<(aBɜ=frIz"rYxϿlˣ ,%UJw)Y mw}\{'ņc !Wn'v[n+_'aݣ2 HHH HP } ]FF:u(_ IHH P@dOV$N&O,p:%{9s:c! AI̙W^r)sfaxxt,ӦM+Çaq$q"O}r-\~:LXH$@$@JpY> @ӧȑ#mWVM7ox *F8h'xSx+/Xt @H> :Ϟ=+o9o߾r10`dz'gU;Ӧ:LYs(tYdI*%ϱHH@Wx|6 i3f̐g}f{g˖-71V$ /|nwfߨ5$@$@$@JS+V_+xڵRJ~yFPܼy\ɒ%:G\7nݒʩs@B 0ٱcsF :ȸqby[j%'ObsV{DtmsF"/& N$g0ݵk?bRb  L cƌ2x(=B޽)F/"   ̙3GqӦM+Çv;ĉDVJ-HH  O$g#9rzjժIͽ   pБ Jn"s @H> :4=`ɚ5k۹ dLRj/[HXR2B  -H@ʔ)eȑN83 IHH  7uޞ?Iz;r    mVʗ/e%KOVB)](u~*,YE$@$@$,(˛s &MH:u< bŊyHH< 5nXj׮OU!   ' AH/3a^D̢Oo"YIF:^@$@$@FpQ> ='7HFOɓ'; rHHH  0r05j*ܾ}_~)[m%%ʕEl!  !@8F0         V\)'OW{9QƽF$@$@$(kaHHHHHHHHHHHHHH F^A$@$@$@$@$@$@$@$@$@$@$@$@$@$b'         'purJ7IH $H@ҤI -I nHHHHHHHHH"pe9ru d'/}#XհcIٳ4 $u# @/rҥ9J$@Q @8 x  !$@$@$@$Lo2B$J&UɃHHHHHHH (G KWs1H Ά_vlHHHHHHB@P{`>/ + f\$@$@$@$@$@$@$@$@$@$@$@$@$@!GpȽr>0 @oE$@$@$@$@$@$@$@$@$@$@$@$@$r(+ + f\$@$@$@$@$@$@$@$@$@$@$@$@$@!GpȽr>0 @oE$@$@$@$@$@$@$@$@$@$@$@$@$r(+ +`|.         --[Ǐ#Ǐ[v+_~޽[%,,L2f('N>LOniԨQRX1uvZׯu7ސuZ!  7(μ -ׯK„ %q;wrm|(O,usK$$M:       _|۷Ou%eʔңGn;vL9oܸq>?ҴiSz#$JH?^>lv횵sGq'b;vo֪ؤI)Yu  `I E1cȲe*PH" 8PJ(!k׶֯__ fcSqdΜ99 @ 9s߿_u%mڴp 36xqIYfY'y  Db] N: V$y>O/\xQ]THywo5IH-.ͫK2xU|P]oǟM\mUȷ 6Xԯ\\j+o.Ftհʗ%WiN Ev";2f w΢@#A$@$@$?ܹӭ;w~Xpו*UdϞ @` xC`ҥY_F>B zRU[eqdv)[Ț^]rֿ2{\~&KDWȺ_"%{9-w']0v 0e:rgHHHH@ȫZ _|+iܸC#  \ݰg$tݹ'|Rv*SN-.]^xAV+D ))Am]~\z=`8ś Ds*ԅ $C r})11Ajwޭ>YfܹsK,YniݫB B:Ǧ}g̭l۶MN>-ŋ02FbHb=ʕ+!bp/FL2bɓ'["1`8d̘1᭡3g #Go5Q-{۵k [^`AAڵ LǏ={ٳgUh˩?$,,L}?!!ͺ$@$(>cށݞUV9sf,]tҭ[7;$@$@$@C kH+HHH X_^+Yrܼ陮gyF-O}ZAi:u2l0Y`@H3jժҽ{w%EdG @IDATرJSJkԨkN~L6M^zպRLu*U*Y| @#u&M|P {ڵk%<<ܺ)p/8DoU" ժU_~)[LR,Y"ޢC/^lGUǫW/B֭['*-[V*UXף _~v&[lQzw=ߥKӺ;. 0TR7݃ꫪ N4xߤ<}  {Oֽ?H$`aiXj 樮DNaݺuZ4iҘ]>Vn޼YjX+[+T M3?±j3Ydfkuj'OA!Vo_].g0{~O'*0s)~>J.~{`1b0TءCg_ ~-*I$Q<;VnذAbVX1wHH"Oۇ$,YR¿b   護r;{A1m< U@[hVXy)b = qx:S{?**nݺVV;ыܹs=~0cf%Ԋܨ@B]Eh'0OSV\b ~v_~E^uTՏe lՀlU0v0ZګW/ɖ-:3zh>}Q.~\kWJ՗8 X^=۷?g`,EhSLQe o zgsGu1= @? %I,27nSw=+66$vΘ6_']LTƽ|$q"ɚu9w׵CiR6P/[43"s =%~y7=@,xS3<+'oFi۵5Odڵ<|=N. %m2V߶#}:6w>~dp ~th/]DҤfiSdq e;䛟~-{NkczPҹy5Z ЉsuaYfYGnTՒ,*'9Wz%}.~T)ɫTx$];|?G?o)?KW/uoUK_U%Zᵬ@$@$@Eb5sBW6DŲ簅0͛7Wh^1~ӻj19^ycN"-Jaac[ 1Gf?Q38L7gΜj!}ɒ%խy!Rp'(/WU?nF\rJ܎+gϞ!Jam쵸 \hoWϴiF iFHlm_s@  >bDoa.- #݊w"T`7.1't0B3kJIx(A gs ^b ֳ۹Dl2od;y*06%O&*g8v "ﴭ-k') ?oT"7HwVh.i$@$@$]k"S9k$-[Z/ADÂq3-<w,4n۶jE9k%8ւy}dgxfjC%$/1'0f]䞅77(C@ǜr[1Q"6`B(G_ -LLKkgv)qOciІfZ"3~o/c>}5?%c|!rc6G7|Q'C RreRӧHlF{Woҭ Sy3&G Ke *j&(_rys-XF$@$@"NCwiȈXm71ܼyu (yǎV9,SNv-*Hׄq7 ܹSl1W02l=YՉ^;4׷i9}#N`w*sDֆ0/Xwxd5R9D(W޹:Gݱ5` tC_X1AƵXh=m!b>{e˖UcP͋ C$@$+( V6J$Y("̎6,H!0@eU9:v۰o68'~c56(G'32XA@D0@x>s*b SysdV #i c Rt J0"Y /UZC@TDhsԨQ8Tf|eܒ @m2}}PQmW`=6+Di+7%ϻyϸGۮ_M%M,@>["NƱ<^Ϳ+U~8~VyvnV]ҸBNPZ z΃2mF9A|ab<c.o]Sǹ-[T_-]^}w6]t X~M]Q)dK'5.,]^za4.FЗ8n V2Gq/f! I~aq;xc1'j.um1̖-iЬEfj|gY`g,6S)!|Nfz| !zc ?3b0y-c/,MxC5xL\{Blk,(a4S8>;v' z@@:7A8:RmS8獿9eo$@$@qG Cw& @luK`E8tȐ&YdA(K#3B`%(Vb#kᖰX t5cXܻI&RR% i. oC`6%x6:LqlF4 5ջhyʝ6dO_[r6 Nüﯮ95' αHH P;3 A t̙b1(61>ҧb|kbhuʩ~lC4f^ݎ]偰-= /ZHM&n#El̘1RJY8%ḧqN-uA0Fej޸q5&G={*Oe8:ka;YHH ~;[ޒ J[~=V"3 D~oݺU}[+@ /a jayU $>K,G_㴵s4s 97˼ dZNUNTXF$~ټו+1.QonP8?C⍊9xBf%Vz,DU5m^E%.oヮf93ˆ$i ,?t<IZ8o"56qjv(<& 6gc#3,60c<]`oΝ;)xJ2Kq gx+~xܯFckkoa=s:N:Kꃰ۷oM6)^l4Y0 Ts4#G3< }m֬hcڵ*Sµ#FP}oȆ>cnxN 'Y@$@$tg1@$@Nj?u~y ,z%[V+8W^_U+WTa{ "W +!fu 4!/,7saNŠモL2{;G~dSuHH“-$-f leQ]?B%͸/'4{% <^[X]vܹsU.\}_ 8~7|0h0hJP3-?{3xjJ}R AlzB\֦.S(%{4$@$@qHaMӥNryj;y/}G n,4YJ(NE_U DwD̦Et6 Û7^xt6>E{ :6HHH+Ӽ zD[A0]B_!_9vN"Zm.-rBbرJ5ecСwy̬x?}'a&Z\0c9p5] ՜"?K$F^Ӝ?#sNſEnIV;N ਿ35<{lElvƌZŘ_¢ @h;,4OI$޲ׯ[ A^wk}t\ _T?իqMQI*H?XXYN;5ﭟ4矗oF!l9}TwŊÉ'Ӡ:#\CO3XdʆpϬ-kB _HʭeQw❼>5ߚ)>zCB^ayҠ~TXϖ1o;/lV_5wu=ܴt,+Or'I9}Z2_vebnݺnB%KT6~!yG-N_+SUͰK.u˗#UVj܁\jUHFA=d˖-2DC4:mn6q:wP{,kbB2;3̸_ӦMm% G$@9L0h9z,XBC`խ[7]?YԩSnemա*Ur[Y%KL #<3ܹsn?ޫW&Us?SnDm{rl,C6~ԪUK;2_oX/F f1\ 0h|=V%sLD`9% ^oe=[\uq_WbҤL4vU}Bn;#Mz|.N{> }⹽ X<{Č]}矂/0*O?"]/`)2mNPw 1 @_-gw=UfsVl٫. ;!=.qng/\K'c[lp!Q /!|LZK2|N9 K~w+'g뚅Zehyua{Z=u۶mPq[r&{ن*/Hߗbfݭa $ɓK="֭[-"[{,֎IӧG*%1 uU{b5B;yDGkzuL{ȑRZ5 :m): <*Zxb! u5޶xz8Ewѹwu&Mj/V-V}3bnTAye [_\rݭ`aڃu-A[Ӗl.6ӵoGu}ڍy3]^yfY>Ҥfi^[HHH u:Fy@ PfJ%Q`Yd̙3,}V=\M1~g1R TyAim!B￯IIc7w,>,\ޣ]}9$ ]T>.ѳ\\RSeK=~u}L ds:qNbXi/rVXoƯ#=ӅXP~{Bp'_60Fm{יBZL%>ۘC?xJ4Z1e/_9NB4.*_|ףw!Z"A0|ޓQ"?1߃$<̚5bj.4՛<}|N x.O'Qli N].Ͽt-B-3;fXa1BD1hРy  8 @8$ *THﳯ0@A&s"i+VT9]PFUpP~cp5W>[YlL2E 0WWWյNnu]<]F߼V@5J6l ? o_3IX%1vhAj_;cp]}<>sNu/pDX|1Wfb0bC;Xe 6J^-?ڽ!A"=~z&rF1E@OEԞ j5Q[VoS"]@;gܼTt {{ p2[oB]M Φ>VkK$mV26_;`/ͻƊ$v-,S$㉳Wˎٜ鲅mfE${X7yryȗ UlSxGfvɖH GN1VHz;C y2  yբ}yP{/O͈Z^롍{B37 ^Nsv>|X֯_o]9_)` љ w PH R|DCƏ!Cx=g 9"b]T" {150^'f7oV?3"0_KT#2>0Pobn Pپ݃F!wuiE|r0 /9 t}c`5?+{ŧ}x.^C{&/[HwoI;Gb]NDOv;x6;vSI:~Z_qSṼX9 '^%=$u24i|>wieo#l+_kyֽ߻rhpiZc<xv|Z]WܴVW~߀IHHH Б԰͈kpJHx' ݎ}gI QG\o &5k&۶mh$ a"2=viC@l.]Ȝ9yF&,+\t#h \jf_jY#k:x+sq Z8m+V])JEWɓ&] 7-8m(-&%gYI~Kv*swrݼ cl^}gu}>%ʟ{%D(e ?s /HY{!#_E͗#v &uy"2xfqfƳ9xB6l? wnm%M,WZ?܅+J*;\!#EfS᥋߶#>5[vk\[ < Zx }g-]]+v;u?:j&sdv ]+x / ĮO$@$o BH< o^eIloǑ#[Dk P $ PFx\*.BHyPdɒ(v@HlgJ槟~:=wIa qWȽ(G@$PHbg7jڴi<p$<{z>E]taQyΝN ywBGQ(;擐 @`Py|N X#P^=~5l{F<{{--[+,aÆH$@$@$@$@$X&M:tUIaf9B3:&Ix՛˗~@戼ۨ8 fO?ݞ}Yc'=̘=gꈀl}?䓭RJۣ+ 9|{sʕ˴aF߰|h"{뭷ޕ.]:O`n|){˗ /`^zi'툀@^(7ӽD@ ?Iط O$tX)d" " " " "_ |wa_xGdUB>y֮Y&ۣު"Ѣ#9 9rf(jo͝;E 1b͘1È`]kР8OD@D@D`{PA]B@૯ŋ#p ^jʑeӧ!.Xvesϵg{2C|k׮]*lhҤ}'pϏ=<~ F? DdkI9aq ߃;^\uU&ߢȽuY;7XtK^@;" " " "V^=T? W^yaIKKs}Kv+?Fh{3*;  :8g 6[l}yTƌcFVXs}W_殉n/ޕ3罆Wu<~DM*q2}{;Sh"4sBLD@D@༤{@!!ѣGAaÆ-3)u't_YKX$o+V4tWVwmofXO>i'pBXa/g髷.MhӦMaaGUjraQ${qB|.W4< 7`El.[#c5}QYXBd /0|x@I)^լV6ɖ\k˖)e;%?v֍w?H4}=t /:uاߩ5c7kP[r0s=3:3_-%~_o}1a}8T@%o8=Kg# r~=CyQ"=g.]XM{=;8mywPDa9O߼،G_/˓-ᚣd`Q`FO?ݞ{9G2> Y 5B=Mb3SHX0Yw}w[#" " yI@p^ֽD`bD /-x{~5‡L~0B\]f1_&;a5f.yֱ*)Vzkܸ[1_i„ գxyA:Z/G./`*cb(+O? x6Տ ͩ{!+Wf d/nAVF']G&;YAOoLx|+_LXa[ަi]U}km|>PFǝ4v?5.HZx=;˖.;Ύhۧ;?'r@dԇ-ebс@%E[?VsalK7M_ %.$I4GsIUw绦wm7mqkR&}_ȵ.k0yeN%gH~İ.wo-BzT0իWM%yzGێLJ;(nݺoB·QжE,ŇY+h"G r^o|*ƟG.6Tkbukl}⪾dۻKxYY%0[p} F6؈3R<$>Nkojw^t]iɸS 9E@D@D@ \–H K6⯯נAܹsfy3y)Fj*s#VV2xsαɖD ƻ=餓;,Xgd }EK%Z쎧5׾_Y꽽Q}gmE@D@D 7 HMj[ )=J.zkK|9wKy%p$§z6 m#&I径!;t`=zHuI#2yf1͋MNo>lpSm:qL$rxckV`PR2nn Tf ^dT'*g63/rm-xoV׎o?X`;?jȘʑ8,F;oM7XnG;[k ܳKk{myިvv,m[749䫟҉1;دIt?/Av{s^5#ħ"Ks8v1/7o|l0L>֥֜o>Жʉ2E ^UGѣE>)b%'pBٌ̻sٝ?!7n9[dH65r/hhfMwv9  C_%o Q]+" "UJLE@boЂg!%^8<5 ņ#3Aܥϝ`Xk\OIbu^[Xњ 'ʊ! I썼>~?ْ[t/KguRmƎs#U$7t8 ?ׯ3?o"0 0y)zGu[0;+b3gtVlzꩮ+YdB7s*?<@(3`ܹl-\xhίd#t;Q"+O!V"cQ ψa=Dx]$7&VN5BBғɪ5팛_ېSsYGE4[}{B_uyoû%=lqS{P/*8䰶G_hۯO^l]m{O_fO_o +ONs=o Tcug'nG7OMu(|$3E~~7m|\wnm<ٳk}#=@4噿E5jc|̘Wat..X8@Z4o6ꎺ@&w^ v//L r&3/r>ڮdo9%iv ܶȥC~ʼjW<}8D"^71r{OgNjp6- b2}'{ipx7]XQj&tUW]_Æ YHû7a7jf=yPV G {~#ӖoxDsW~xQ'8>><~>Դ?5?#ؕW^O-(ߨQ#qID/S?;ᜰNɷ5 9~yuﰷyNd;4 w91/e <{w,S"Ypj6kR<|ڿ oW<~_}=e?L\`}Y g1W7rG__k#ڻVvEfOk+" " "P3kw$ C- 9X㯉GSݿk!!}o xgo/ϩm}Ԏ$# 8$$~! ,ByM!K$s L\O&o|'PԢ yneeΜי=X ;>Q:uU͑rzɑH&@jDE\3 {vךAQP㈟~fDbT:Yhԏ~ȝ!va,ZA8.‹Nn/^ǿ93Dz:-[tڿ2d 醇sX\Ӿ}0"XOwnA߽{8M}<1# <3r1k3l\;aTܚʁ\op xʾ_ 7L8nkZ+;Q 4ʕ- qW/8qڝLOUb bmb? ,fj{lR%wH:M7׳bpLD@D@D@ $;Q?('W_}V->MRx2nֿ[dI|\s5\wA5xEtWM!DA|"6YJ1+(tײ>.-JUWD@D@J kj/"P 6ۋ')QK4ȬH|80=~X 9ڧ#dڕ/wr*\4L+C3+3։ߏ~6,Ɋ2I_t@h\DI<̽pK;:qx㹊M0=K?r饗O|ʸkM;Nh/}̅_3v&^ݒ"DӨNw2?;ܗꈀ@Q"P(~B_Idٳg7NBwzp!:UTq[~Zot/Mu s{"d8TQ]sӷ, s!_ӀwHv|r " " yE@p^}D`"—V`_hV{] ;D?SOޘD:87,~Q'QiVn:Z%~e9:Y z `a|f_&xz LF'{$AD=3`=CO?uW8x 70eP`oE18e|GOs@`/rǿ| /8s½zrm3~xoh3g)|~pQVN mԢo~ͺ~7ݶJ̥$HwaPP?s!F-&Ll}:sӜ5Z7jΪ\atV/ת@"S^X5j|ߵ;w.|Yg9]3lDx-fCD?^l//饗B,(Fh^gu̻EL|ߞ" " "[$Y+YϚ5y%#v(B""|aoBȏDo!Q?QɃm۶n JNw0ID'o+c=oO.0މYW@'zmm w)/"7^iܲQחڰaCoHԢ%`5{E'z#|NZQaM;bE_fMsw0ryynI"viC'̴7}9[^x8,ZG%4]OZU i?z[܎Kxֆ+Tl(QSk2ډ|+O;{#5hJۻA _lϷ?bTuND@D@DOG* S!2"E?xyQ˵爝'xbJx{#B&{CP_<9aUDAO(>}&s;I{v4 {GQ7~,::"=2G]_ϼ E\FƵe/EѾ-mE@D@D 7 HMj[ )ByQfDDzN8э x< _ Q^^LJNov˝E_=pP^aܬ7&)^B6Jf ^x0zcL&|8X_>b馛 Jÿ́C`g%rt5/xuG'2Q/L3&Q[}BH&~B7A \Z1eʔpյO9ti\MX4 xÂr _8 o5o9QQ܇_;,ѥ,*{bpH袋\y5o .YyeVFK։(Afe1_n'WLPd%Ux|e[ÊIvf/ğnC{s_ۭA^`adO- K*a;.7oNKR:E91cşxГE> Hj}DŽ:fwϽYL~;OqpٵkWw;n~{flٺu0zr,V;1vG:ojXc7޵<aQڬ‰LdeA}m!P|[.ֵ" E@*h~51"Oؠ΄Ǚ%J_'"VD=p2 )s6`opSNu?i+9+F^Nڕ$wv'"8 /?^v?^/ F.ބTET]~19~4h<1MGC *zӏ>*qW?ݲ"׋ 'tRzyȢ}NT7eMy⽋DKr@9\D`L*ڿwBOpD)TlX{djڴia=8dEcT a>'#bohk',Z'Y;LY˨g=R+ߣk_b̉FLHԖϳ̹!Su +^付z1$UE;#^~n=`z֯ba%K}w%+m5av~e2iLY/2u֬~_G~MwSbC?6g[z]XVJ'*wVWk\wW_dko(w/%m 4(].b?^τ-Nbyo, .ڊ@nP&E@㋷p 'ګW՗L6eLr |P'jL !LX/6\+8}&nœ0E*43Ilmx/@ʸ&xd'3&:j[_ڇ"RTxz򹈾(eTX^7_Q|sڊ.EmEݨ@vyƒ/ VDFd~GSY4|4vBHv4CTֹE`޺U*nL]szg;6Ŷk AݚASY-ׇM=?&>]S2kU*_mRʔں R/4.!0߁б&UJPp ìc8ʗ-cm ܴnW-M4~{7޽}3'"dۦiݨ={[ak9틀LD}ڵkHD\"Ox# Ƨ~hԤ^d`;~3Lj|Mv-iԨQ!G(=!AXgE7xZ*}[7pBxꩧ\3a.`g{2o,MymE@D@D ~[wU" _Z}.YӉ_P{0d/Lo?Z5*!H;۷1Q-'ɩ=)/^va~2gK7nM5ae=! #‹9K:nx:DŽ}F$fr;c&xpY}9r$'D#r?{c˳f㍉i D,X|P ^X o'z~W>/j@LI{ ܋T,͇ǏKAm~iXoUTÊr7c5>eX?cW&" " "P A˧h>|-Qbw2wsyqLRo[!>pvMi/+㥿9gM |LwUGD@D@r@o%9~+5("PzꩡzjxC]1\FI1go&1_JڄB&X̮uYO*ӧC8VDR]='>^3Qu~W]u?LE"2l2^zB=7V^ZVtig04.M'4¾c~E{TX͌JxK^xx&pA "H2x=)OO7r{V~̈́ \o_ˉ ꫯO+i@Wןb6NdYXvvcw#7}~Թ (G@ή=W6E}lNM("O63]pBס sS`g>2,D@D@D@ "u uQoŮxo~e4 cK. ڊ@ntնbxgncHv'!OfLVмL&'ڍoT#E;uЋ8*/"m .N۷oK`"GdިgF$NV'[rzO_\5kqx:n h|~hgB!{_M cO<'ʇb;_Cd+_m&@\nPÚ7aag.QA8 YNGAo_QFe]۴n֨NusY۟o٭bfgDco|CZyk(eq9ǜr=lAx.rYOg&Ίi7z8zg}4I6z`пY_=˴/" " "P=\DXڭ[ c_"eWźŮV:$ 8/i^"Pֻwo[ݨXsDD,hZ}QmB=>aeE!Ž!Fk׮. ޳gO\T!q=W _M׫Wυ>'w"hH^͝dmb} >"vӦM;1F<̙蛏@Qʖ-8+,!=(E;o9z?s<`;蠃ygqLjﯿ˹ O'fF1~I6XOs~7~Ŧ^ld־83SU BH *[]\r}._-?"o]tu9 O}XD*NQq yZwBG?!, ^ E|.Qr>CN)j x&^T-ٌXxe{N9Oc29vM}%_dmYFY$sg& ^}&DGm rmb*<\]Oi3}xwqrmE@D@D@D@D@"vyg#W_}JIUԌGxucN?#E@D@3 "P Lhg}/r[h"KV7~\p/VD@D@D@D@D /Ȋ67qEF/" "HWCGCN$\}˂7QFٿ]I~kƍpزGwߵ~y"#6i$:믿Ή 40re˖a}Chdgk"kժs=Tujժm*#ax~zw/h~l0獍X59 dOA" " " " " " " " "  ť.w"͛|jQD@r@*Ubasjq{ 89;lw~ 'm+:; u@%pƳg" "P \2 S.1̙"6t WD@< .(fϞBhڵ|[_gT 3TE@D@D@D@D@D@D@D@D@b H#"D/66ok9Y>OD@D@D@D@D@D@D@D@D c3f" " " 7oMe]6u>$o@> @~e~ E?m۶W"M@p~@Q PdIڵ_1$!PX1+^x*E@PoD@  Y" " " "-v#+?2K@p}Y K͛7S" ")JS뮅nLHg@D@D@D@D@8E۱t=u^D 1JE@D@D@D@D@D@D@(Xҳ:!" " " " " " " " " " " " " "P`/0=UGE@D@D@D@D@D@D@D@D@D@D@D@D@D@RN@! <+TD@D@D@D@D@D@D@D@D@D@D@D@D@RN@! <+TD@D@D@D@D@D@D@D@D@D@D@D@D@RN@! <+TD@D@D@D@D@D@D@D@D@D@D@D@D@RN@! <+TD@D@D@D@D@D@D@D@D@D@D@D@D@RN@! <+TD@D@D@D@D@D@D@D@D@D@D@D@D@RN@! <+TD@D@D@D@D@D@D@D@D@D@D@D@D@RN@! <+TD@D@D@D@D@D@D@D@D@D@D@D@D@RN@! <+TD@D@D@D@D@D@D@D@D@D@D@D@D@RN@! <+TD@D@D@D@D@D@D@D@D@D@D@D@D@RNt.W?o7o.C)_j5 DI(lJi<" " " " " " " " " " "56ڐѓmԹV,n*v`=' 5b͘ĦZ`7Zrj^D@D(?VD@D@D@D@D@D@D@D@D@D@ :͛l_1?)[?+[&,N~{obQU-\" " C@ppU" " " " " " " " " " ہ-X2߻_s*6'2IԱc;6 $oi]-"  HNGgE@D@D@D@D@D@D@D@D@D@ pɺfivUC^Z>G)B$" "xDpc~Ն\|y\6 l޼٬Xu2-3&Ŗ MкLD@2  @$Q^A-XͬlR1ts1f/\FcX+W… mݺuVZ5U(Q֮]kK.ufjl{6md3gδӧԩSg ղ0 SwE@D@D@D@D@D@D@D@D@Dlgϩn;[Z[|L`y6dd[6cozƍSOkf&Lptvi''"6m֯_o]vGy$,Y `oZʎ=X{R^v '_hbŊَ;huq+th@A#PuX( kZfuϿֹ߷KWBfwYbFy69]vcʕˆرtI;ٸNJu}XqSZ-mZvrb'-- }dɒIرuQVLut"o 603}5wn߾/re'tR̝;׎9Li yEZucsT}_iƏXt=(fO;c?ocǎur)VNmKXÇ۟i-Z-̙3Ǿ[4iNswu"3F{Ί/BRׯ_VZgΜr[ݓ?c'L|iY]~:ƍzNu]>rⷻi)/\9}Mo饗\Nd׭[ם~G}5ó^>jԨr/LJS^pOV8!>xƋ6'g/s ٜ[r{F>JJx{o޼NT? YO=l^|Ecn~6ny>&ӠA3af7g{qDžu#Y!eNY" " " " " " " " " " N/̲ +gH2BkիI <@. .ߴi1N3}'2. %D@D@D@D@D@D@D@D@D@D@r/ Jj`[ڠ{g+ҥKKX*Wl{RX}g38îj_jUG,[Ϟ=f͚Oʽ,^xzO^zRL*Udg}v8FxϞp N?1 P&^x2sٛ3bo_gС٠Amݜ4;yE@nڴq [yVC"zCˎ-o8 ër^߿+_d͛7ӌS~im۶uŏg}!3u26mZx{7|ka{*0D(A_lrL썼'x~]lOi ɾ\[*ygꋀg2@IDAT*-P@Jl&sӄSm 7ժU+h tkVeJhJgfW^z%+iUV6XnkK( ewvLe+UMW!`&uN؎?۸Y>zfb/MrF{1r{R]113\7cƈ+- o[ u.BSO=65pP ! O[\9W0R9ƫA{83 */a|M$+dr (j<;,^x"c&3ߖ+?"KUE@D@D@D@D@D@D@D@D@D@@vkNlc~Նo7/s۱iZ'F0BU7<˩6u~{xרVztni{Ԫ#7gE8q {WmÆ {z!:wqj#Fp=sr9`B*G o1cƸ"rz'"9Ca#r֘ѻp0᝗-[flԩ1^\D{^(F,?s>$5F`&LV_miE׊@ tV sYfu,-/YGfs8U:3OgYZjLCk{wq%C"!֥2<< պngE~!TZ{!]vz(O _:iiig*x]p>1yfj1:EM&2BTcx=';]9!\reև˦܋ΈYf_y1ԇ0>(`Hhi}qcm>{V͛7/#5wy3޻|}g^{)Yώ_8@LK@pv:Zi;C{ ׇZp7/?n?Ljߟä/{Us `=/"{%G`;3]f^߈䄆~,ry;L?"$`T," " " " " " " " " "ԯY5 }xh lx~jNėtuTk݁?{|v)%Wz^'k&M%JXonѢ/ \x Żcǎc 4"MDu"X ({u%ޯ>ܹs_%K QŨÏll쨣r[,,9x^wG26sLחoم|[ u~_-!K>bvϛ wk֬ڸkcb C E[}s>P~!뙼x"sLcd@ß:fFv[u" " " " " " " " " " "K`ya6m`P ^NCGpx'2DVg5/ݤIC(j;A"8^}N}1r)6h M9xׯwtBE##s2. %D@D@D@D@D@D@D@D@D@D@; \bivPֽC3+[v{a ngpmԵc;6T]f\OĸxQ5 ŋ;Oh|#"" m\ҪTPo׬YE/yiy 㭌.޸N}e$--[fͳRJ9ڨWr3f0G}]7n{d+I~c q0QN|v`u%Kt<+Vgi+% 8t@ ysW:G[qKT;}\Tut.4f_ᅄ{ѹQ+f  U\UdDxFt^D@D@D@D@D@D@D@D@D@D@3ŋ!Mt3Zԕ< r^Ɍpψ"7#Gt@fL%N\vm]IBx ϫ, ԨV:lh'x~ ӱp@ܷzaÆ@~ )" " " " " " " " " " 9Jܜc~^ۙGQcon{S[n;pϱ|r4qDٳˁuܹ 80+ͨ@$ >t YD@D@D@D@D@D@D@D@D@ 59dd;\D@D@D@D@D@D@D@D@D@D@D@D@D@bH($g@  18t " " " " " " " " " "P ?yxjlb:#"PH.B[CJ`Μ9v19STa sD@D@@v 6ؽkw}_ފ+fcƌ]'Ǝkt!Av[߾}tE," yK@pD@D@D@D@D@D@D@D@D@D@СC._5쳏M8var M6YVlʔ)1 #Xcu " ":gy5L{>#ӉtmOn^?_F3QX52A@9" " " " " " " " " " H{뮻TVbE1c.\ {ڪUR^IX뮻΅.SLʺ:)" "5/ȇܴiS0`@/#e9KH%3ϪYfƳ@~&uѢEp ${>T ALg*eٳK/ʹ{G?l q7| *X5 J(  &n? _ޫW/{嗓駟NxXbV|y{ュZj Pr"vD@D@D@D@D@D@D@D@D@D@@V=)Eߣ:*3ğ]qn:&M` ,pof 6[nziŋ+jFϳ͇4_vKvg$m~G 'mM>~'C _~Vzl  BO׊9B]iFӷo_3 KQ[:=e{3 c'w={7Ur5 txzó{6x`Wϟ׶pXtԬY3Á֣GWon-f̙ɓ rkvD hJNT;" " " " " " " " " " J/c9zӦMs^Վa,"3.<;uf!Cvp 63/4 5jƌc;S_vqDž^zYݧguzN sըD@D@D@D@D@D@D@D@D@D[o5f&/ឩG8V?G}Mfr ɻ˳3ڪU-?"zMx ,aÆw9c ~#%\g̘a_|E:Y)@%:sϬ\⇫@A';$3uv2rp<^&MĮ!j!kcyxF[5_ `֭[>]ݘmRlw} <|}cݾkE8yU.]q^g4ޥ7omۺP/[5kfuquǎkp;.1׮]xC1X0NBSۼyVٲe ?O>ZnP\|}W.2i|>e,wYP\9[r5ouTW>zhw;wg/Daƍk{wxq+mbڦM>N0gvK;餓u;j 9q6nܘ˸Tm*SOuުqzkX/\ve/f=?իWOCp|iA\צW9̚?!'~M=!"#:xOX igС/`KϞ=ݹ]?,˴S(p[!_+Sv'g9i Jd~|.ryzر=s.2!he뮻ML(^{馛{q^z% Pw>KsB/cz>[nc֭[ʹ}F(f,.u[.a/g eڇ-Dgw`wL~ 7Lp箽 =h c|m x?c֡ c<:t3g¨5jȅu5jޯ1y{m.>7<3ce*}E?G᯾ <@vmδ,Y҅99:C-`6ʱ@7xoͷթS'_ma$P4mHD@D@D@D@D@D@D@D@D@D3B [~^b>k6>X &m"z r:_|8z˕^Z믿a]g* rW/7]U+sqKY7/vxPƄ#3u  駟ry7: +Olvi޽:}^D\8IRR9$嘈(9"p#BN9lHW\w|\kfk=Ϙs<1׾K{|’%HKޜ<> K:zjTOF @ @c -mg!V5O7ި/*/T_|SnKZT+^駟>6J-{O%H}}ZڲT'R,Ne %זs_C=.!锪״ Tz\8Ue_zqYVG; 'Levo_Ne ^SUoqu袋BO%xAsV9w}ί;餓]}?=ku=S mXr%U%~*TΖ<]DsNE֋WhJP 2y\csatŖ*W'--RO|Av|ݖ"@ @ @m] !ZMHoo$|T,^ok dfRѹEpN.yrV%l_}| MvRu֌%-!qq5.P믿'߯G tҾϷ|V㗶$q{Tü^[]9mIYRt1NY9[SZVnoQ*T>O~\pA'L/v^{tmMdY9!box:#R]x旼[A.SZBR5\M}W&]xE=?.m>?i G}T[ ^X.S^le).@>޹ug @ @R0{eUtc eTQn,h[5r~|衇~x=cJ}sDk]^c]-R/tMSY^JӴ쫚scaaŽje?r]ۓxqvYNKPڂ&X]V! ~Mepbp=ۗ$_#%͘l7k/\㘤j9_X}3߄k׮ny&gC[oʲյ5NkZwlKжTR0vc_ hw:jqg[ZYV縍9GT@xa}2tAMg~>{5?#b<f~Uݖ!@ @ @$'Z6ꦂ򪫮Ryëj{\$d62ǹ.s߼ N%PKrȴ7pC=ΏTe [*-[ۮ,û%tlgn-/gyVg iyVۖ_ujs;6jKhѲp]JK5xSn-sOԐ]<_9u$g.n6 c'ncaj*`= e֖`4*_چ{6IZ3⹯,WE!m8Kh繄ŻC?rAw1 >_xW뵭裏ly|{Ű8simZe/}X}7 @ @R H*l L5g=ϫd[g2_~&2v1 dǿxw}76.[8U7K2??@vLKoɛh9/$%kaa;5LlZAN%lZ[v9/{fiMjV %S}8-i RI]J{ }}yU"ϟ,ڄ [K%qW|۹TƾKZ9At{\+̃5ˮZssNe'pBԴr<{rm_wۋµٛUl}u/Äbn-Y%o;BY<Y:۞|V}$8^sV_Џ%Pe @ @ @mN੧`Sv%xVm>:ߜ9\fNVgw>;oVYY>We]6+մV g%\N g%Wge%}Jp7+\v=jY f9..+וzge_YY6{V:TkKErSNYr]_tEK_r%| gvجۯ?g%uVE Fм^_ˉ>ʒY}VY tg%aK;+!!}^xu+³Pw~]vN2_%PtVBz>cJ~Y 篓~ʗfeYYvz/|ҿڳw޹z/9R<˜1|ܮvzv_8r_v>kWrke5 @ @ d),ϛez[en"Օكvqc{ dVmVo-!k-Yx'_O͏S*J;o>ú$q*KKZ^'Ul^nT)՚U.^, o%׬Y9?s*43~)U%LKo\Z 9T sk{n6J~-6W^y劕o89s*o@n-TZR6{*T62Twrϩ2:#l/UR\SO<̺u*g__ǵƒ?xzy9g(Ҫs|vTF?o&sTyӓO>YB2έ[o5s9K*|vm7e>V t63޹ug @ @#j$xܜ+JB8zurӲLrt(u:hj{nxgz}  @ @ @HMd79wP.lfMQ*Mim2MF/  @ @ @:묩!:]uU- t=@J-KfnuIەsof}ògމ-& b^ @ @@>~;S23' yz@K~{7]v˿w @` xzq @ @ُk~i>ZnݺR]. accelerated-container-image-1.4.2/docs/trace-prefetch.md000066400000000000000000000141301514714641000231620ustar00rootroot00000000000000## Prefetch - Overview Cache has been playing an important role in the whole architecture of ACI's [I/O flow](images/image-flow.jpg "image data flow"). When there is no cache (container cold start), however, the backend storage engine will still need to visit Registry frequently, and temporarily. Prefetch is a common mechanism to avoid this situation. As it literally suggests, the key is to retrieve data in advance, and save them into cache. There are many ways to do prefetch, for instance, we can simply read extra data beyond the designated range of a Registry blob. That might be called as Expand Prefetch, and the expand ratio could be 2x, 4x, or even higher, if our network bandwidth is sufficient. Another way is to [prioritize files and use landmarks](https://github.com/containerd/stargz-snapshotter/blob/master/docs/stargz-estargz.md#prioritized-files-and-landmark-files), which is already adopted in Google's stargz. The storage engine runtime will prefetch the range where prioritized files are contained. And finally this information will be leveraged for increasing cache hit ratio and mitigating read overhead. In this article we are about to introduce two prefetch modes in overlayBD. One is to set prioritized files, another is a new prefetch mechanism based on time sequenced I/O patterns (trace). These two mechanisms have been integrated as a feature into `ctr record-trace` command. ## Prefetch Mode ### Prioritize Files Setting prioritized files is a simple way to improve container's cold start time. It is suitable for the condition where the target files needed be fully loaded. When overlaybd device has been created, it will get prioritized files from the priority_list and analyze the filesystem via libext4 before mounting, then download the target files to overalybd's cache. **Only support images based on EXT4 filesystem** The priority list is a simple text file, each line contains a file path like follow: ```bash ## cat /tmp/priority_list.txt /usr/bin/containerd /usr/bin/nerdctl /opt/cni/dhcp /opt/cni/vlan ``` ### Trace Prefetch Since every single I/O request happens on user's own filesystem will eventually be mapped into one overlaybd's layer blob, we can then record all I/Os from the layer blob's perspective, and replay them later. That's why we call it Trace Prefetch. Trace prefetch is time based, and it has greater granularity and predication accuracy than stargz. We don't mark a file, because user app might only need to read a small part of it in the beginning, simply prefetching the whole file would be less efficient. Instead, we replay the trace, by the exact I/O records that happened before. Each record contains only necessary information, such as the offset and length of the blob being read. **!! Note !!** Both priority list and I/O trace are stored as an independent image layer, and MUST always be the uppermost one. Neither image manifest nor container snapshotter needs to know if it is a trace layer, snapshotter just downloads and extracts it as usual. The overlaybd backstore MUST recognize trace layer, and replay it accordingly. ## Terminology ### Record Recording is to run a temporary container based on the target image, persist I/O records during startup, and then dump them into a trace blob. The trace blob will be chained, and become the top layer. Recording functionality SHOULD be integrated into container's build (compose) system, and MUST have a parameter to indicate how long the user wishes to record. After timeout, the build system MUST stop the running container, so the recording terminates as well. The container could be either stateless or stateful. CNI is enabled by default to provide an isolated network, so that the recording container is unlikely to cause unexpected consequences in production environment. When building a new image from a base image, the old trace layer (if exists in the base image) MUST be removed. New trace layer might be added later, if recording is desired. ### Push Push command will save both data layer and trace layer to Registry. The trace layer is transparent to the push command. ### Replay After Recording and Pushing, users could pull and run the specific image somewhere else. Snapshotter's storage backend SHOULD load the trace blob, and replay I/O records for each layer blob. ## Example Usage The example usage of building a new image with trace layer would be as follows: ``` bin/ctr rpull --download-blobs ## trace prefetch bin/ctr record-trace --time 20 ## prioritized files bin/ctr record-trace --priority_list ctr i push ``` Note the `` must be in overlaybd format. A temporary container will be created and do the recording. The recording progress will be terminated by either timeout, or user signals. Due to current limitations, this command might ask you remove the old image locally, in order to prepare a clean environment for the recording. We also support triggering `record` and `replay` process by passing labels through `--snapshotter-label` when `run` container, which needing containerd/ctr 1.6+. ``` ctr run --snapshotter=overlaybd --snapshotter-label containerd.io/snapshot/overlaybd/record-trace=yes --snapshotter-label containerd.io/snapshot/overlaybd/record-trace-path= --rm -t demo ``` Setting label `containerd.io/snapshot/overlaybd/record-trace` to `yes` to enable this feature, setting label `containerd.io/snapshot/overlaybd/record-trace-path` to `` for tracing. Noting that, `` should exist before running. If `` is `0` sized, `record` process will be triggered, otherwise will be `replay`. `record` process will stop when overlaybd device stops, and `` will be generated. ## Performance Measure the service available time (in seconds) of a Wordpress container. The testing host is deployed in a different region with registry service. The network bandwidth is 25Mbps. | | **With cache** | **Without cache (cold start)** | | :----: | :----: | :----: | | **With trace prefetch** | 4.981s | 15.253s | | **Without trace prefetch** | 5.259s | 60.511s | accelerated-container-image-1.4.2/go.mod000066400000000000000000000121371514714641000201270ustar00rootroot00000000000000module github.com/containerd/accelerated-container-image go 1.26.0 require ( github.com/containerd/containerd/api v1.8.0 github.com/containerd/containerd/v2 v2.0.7 github.com/containerd/continuity v0.4.5 github.com/containerd/errdefs v1.0.0 github.com/containerd/go-cni v1.1.12 github.com/containerd/log v0.1.0 github.com/containerd/platforms v1.0.0-rc.2 github.com/data-accelerator/zdfs v0.1.5 github.com/docker/go-units v0.5.0 github.com/go-sql-driver/mysql v1.8.1 github.com/moby/locker v1.0.1 github.com/moby/sys/mountinfo v0.7.2 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0 github.com/opencontainers/runtime-spec v1.2.0 github.com/prometheus/client_golang v1.20.5 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.8.1 github.com/urfave/cli/v2 v2.27.5 golang.org/x/sync v0.16.0 golang.org/x/sys v0.34.0 google.golang.org/grpc v1.68.1 oras.land/oras-go/v2 v2.5.0 ) require ( filippo.io/edwards25519 v1.1.1 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20231105174938-2b5cbb29f3e2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/hcsshim v0.12.9 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cilium/ebpf v0.11.0 // indirect github.com/containerd/cgroups/v3 v3.0.3 // indirect github.com/containerd/console v1.0.4 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/fifo v1.1.0 // indirect github.com/containerd/go-runc v1.1.0 // indirect github.com/containerd/plugin v1.0.0 // indirect github.com/containerd/ttrpc v1.2.7 // indirect github.com/containerd/typeurl/v2 v2.2.3 // indirect github.com/containernetworking/cni v1.2.3 // indirect github.com/containernetworking/plugins v1.5.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/cyphar/filepath-securejoin v0.5.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/distribution/reference v0.6.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/intel/goresctrl v0.8.0 // indirect github.com/klauspost/compress v1.17.11 // indirect github.com/mdlayher/socket v0.4.1 // indirect github.com/mdlayher/vsock v1.2.1 // indirect github.com/moby/sys/sequential v0.6.0 // indirect github.com/moby/sys/signal v0.7.1 // indirect github.com/moby/sys/symlink v0.3.0 // indirect github.com/moby/sys/user v0.3.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/opencontainers/runtime-tools v0.9.1-0.20221107090550-2e043c6bd626 // indirect github.com/opencontainers/selinux v1.13.1 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sasha-s/go-deadlock v0.3.5 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/testify v1.11.1 // indirect github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect go.etcd.io/bbolt v1.3.11 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect go.opentelemetry.io/otel v1.31.0 // indirect go.opentelemetry.io/otel/metric v1.31.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc // indirect golang.org/x/mod v0.26.0 // indirect golang.org/x/net v0.42.0 // indirect golang.org/x/text v0.27.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect google.golang.org/protobuf v1.35.2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apimachinery v0.31.2 // indirect sigs.k8s.io/yaml v1.4.0 // indirect tags.cncf.io/container-device-interface v1.0.1 // indirect tags.cncf.io/container-device-interface/specs-go v1.0.0 // indirect ) // v0.5.2 was retagged: 71d70d4e738679154f62e2869e94784558cb9b36 -> fd022b910830015d8665a7b497f47bba1f90dd18 retract v0.5.2 accelerated-container-image-1.4.2/go.sum000066400000000000000000001042031514714641000201500ustar00rootroot00000000000000cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= filippo.io/edwards25519 v1.1.1 h1:YpjwWWlNmGIDyXOn8zLzqiD+9TyIlPhGFG96P39uBpw= filippo.io/edwards25519 v1.1.1/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20231105174938-2b5cbb29f3e2 h1:dIScnXFlF784X79oi7MzVT6GWqr/W1uUt0pB5CsDs9M= github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20231105174938-2b5cbb29f3e2/go.mod h1:gCLVsLfv1egrcZu+GoJATN5ts75F2s62ih/457eWzOw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/hcsshim v0.12.9 h1:2zJy5KA+l0loz1HzEGqyNnjd3fyZA31ZBCGKacp6lLg= github.com/Microsoft/hcsshim v0.12.9/go.mod h1:fJ0gkFAna6ukt0bLdKB8djt4XIJhF/vEPuoIWYVvZ8Y= 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/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y= github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs= 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/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0= github.com/containerd/cgroups/v3 v3.0.3/go.mod h1:8HBe7V3aWGLFPd/k03swSIsGjZhHI2WzJmticMgVuz0= github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro= github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= github.com/containerd/containerd/api v1.8.0 h1:hVTNJKR8fMc/2Tiw60ZRijntNMd1U+JVMyTRdsD2bS0= github.com/containerd/containerd/api v1.8.0/go.mod h1:dFv4lt6S20wTu/hMcP4350RL87qPWLVa/OHOwmmdnYc= github.com/containerd/containerd/v2 v2.0.7 h1:55JsNhqP/L7VZOijyfq6Qn0O8Oeff0UizfRuP+2pc90= github.com/containerd/containerd/v2 v2.0.7/go.mod h1:su8B0Z1NFQMEIztOIbHwy7xtznbCms/kFlfsxIcQrZ8= github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4= github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY= github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o= github.com/containerd/go-cni v1.1.12 h1:wm/5VD/i255hjM4uIZjBRiEQ7y98W9ACy/mHeLi4+94= github.com/containerd/go-cni v1.1.12/go.mod h1:+jaqRBdtW5faJxj2Qwg1Of7GsV66xcvnCx4mSJtUlxU= github.com/containerd/go-runc v1.1.0 h1:OX4f+/i2y5sUT7LhmcJH7GYrjjhHa1QI4e8yO0gGleA= github.com/containerd/go-runc v1.1.0/go.mod h1:xJv2hFF7GvHtTJd9JqTS2UVxMkULUYw4JN5XAUZqH5U= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/platforms v1.0.0-rc.2 h1:0SPgaNZPVWGEi4grZdV8VRYQn78y+nm6acgLGv/QzE4= github.com/containerd/platforms v1.0.0-rc.2/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4= github.com/containerd/plugin v1.0.0 h1:c8Kf1TNl6+e2TtMHZt+39yAPDbouRH9WAToRjex483Y= github.com/containerd/plugin v1.0.0/go.mod h1:hQfJe5nmWfImiqT1q8Si3jLv3ynMUIBB47bQ+KexvO8= github.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRqQ= github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40= github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= github.com/containernetworking/cni v1.2.3 h1:hhOcjNVUQTnzdRJ6alC5XF+wd9mfGIUaj8FuJbEslXM= github.com/containernetworking/cni v1.2.3/go.mod h1:DuLgF+aPd3DzcTQTtp/Nvl1Kim23oFKdm2okJzBQA5M= github.com/containernetworking/plugins v1.5.1 h1:T5ji+LPYjjgW0QM+KyrigZbLsZ8jaX+E5J/EcKOE4gQ= github.com/containernetworking/plugins v1.5.1/go.mod h1:MIQfgMayGuHYs0XdNudf31cLLAC+i242hNm6KuDGqCM= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cyphar/filepath-securejoin v0.5.1 h1:eYgfMq5yryL4fbWfkLpFFy2ukSELzaJOTaUTuh+oF48= github.com/cyphar/filepath-securejoin v0.5.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/data-accelerator/zdfs v0.1.5 h1:F7td8AwicTZ3t618SsEYr8CMyMcxBE1KjkoyYO4T2GI= github.com/data-accelerator/zdfs v0.1.5/go.mod h1:/MyNTsQHHKVLznaRBz+PivhIDwglu+wuXKoZxUNmLKI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.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/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 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-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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.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.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 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.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM= github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 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/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/intel/goresctrl v0.8.0 h1:N3shVbS3kA1Hk2AmcbHv8805Hjbv+zqsCIZCGktxx50= github.com/intel/goresctrl v0.8.0/go.mod h1:T3ZZnuHSNouwELB5wvOoUJaB7l/4Rm23rJy/wuWJlr0= 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.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ= github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE= github.com/mndrix/tap-go v0.0.0-20171203230836-629fa407e90b/go.mod h1:pzzDgJWZ34fGzaAZGFW22KVZDfyrYW+QABMrWnJBnSs= 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/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= github.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0= github.com/moby/sys/signal v0.7.1/go.mod h1:Se1VGehYokAkrSQwL4tDzHvETwUZlnY7S5XtQ50mQp8= github.com/moby/sys/symlink v0.3.0 h1:GZX89mEZ9u53f97npBy4Rc3vJKj7JBDj/PN2I22GrNU= github.com/moby/sys/symlink v0.3.0/go.mod h1:3eNdhduHmYPcgsJtZXW1W4XUJdZGBIkttZ8xKqPUJq0= github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= 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.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/opencontainers/runtime-spec v1.0.3-0.20220825212826-86290f6a00fb/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk= github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-tools v0.9.1-0.20221107090550-2e043c6bd626 h1:DmNGcqH3WDbV5k8OJ+esPWbqUOX5rMLR2PMvziDMJi0= github.com/opencontainers/runtime-tools v0.9.1-0.20221107090550-2e043c6bd626/go.mod h1:BRHJJd0E+cx42OybVYSgUvZmU0B8P9gZuRXlZUP7TKI= github.com/opencontainers/selinux v1.9.1/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/opencontainers/selinux v1.13.1 h1:A8nNeceYngH9Ow++M+VVEwJVpdFmrlxsN22F+ISDCJE= github.com/opencontainers/selinux v1.13.1/go.mod h1:S10WXZ/osk2kWOYKy1x2f/eXF5ZHJoUs8UU/2caNRbg= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 h1:Dx7Ovyv/SFnMFw3fD4oEoeorXc6saIiQ23LrGLth0Gw= github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= 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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sasha-s/go-deadlock v0.3.5 h1:tNCOEEDG6tBqrNDOX35j/7hL5FcFViG6awUGROb2NsU= github.com/sasha-s/go-deadlock v0.3.5/go.mod h1:bugP6EGbdGYObIlx7pUZtWqlvo8k9H6vCBBsiChJQ5U= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= 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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/urfave/cli v1.19.1/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 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/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc h1:TS73t7x3KarrNd5qAipmspBDS1rkMcgVG/fS1aRb4Rc= golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc= 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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= 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-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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-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-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= 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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI= google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0= google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw= 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.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/apimachinery v0.31.2 h1:i4vUt2hPK56W6mlT7Ry+AO8eEsyxMD1U44NR22CLTYw= k8s.io/apimachinery v0.31.2/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= oras.land/oras-go/v2 v2.5.0 h1:o8Me9kLY74Vp5uw07QXPiitjsw7qNXi8Twd+19Zf02c= oras.land/oras-go/v2 v2.5.0/go.mod h1:z4eisnLP530vwIOUOJeBIj0aGI0L1C3d53atvCBqZHg= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= tags.cncf.io/container-device-interface v1.0.1 h1:KqQDr4vIlxwfYh0Ed/uJGVgX+CHAkahrgabg6Q8GYxc= tags.cncf.io/container-device-interface v1.0.1/go.mod h1:JojJIOeW3hNbcnOH2q0NrWNha/JuHoDZcmYxAZwb2i0= tags.cncf.io/container-device-interface/specs-go v1.0.0 h1:8gLw29hH1ZQP9K1YtAzpvkHCjjyIxHZYzBAvlQ+0vD8= tags.cncf.io/container-device-interface/specs-go v1.0.0/go.mod h1:u86hoFWqnh3hWz3esofRFKbI261bUlvUfLKGrDhJkgQ= accelerated-container-image-1.4.2/internal/000077500000000000000000000000001514714641000206315ustar00rootroot00000000000000accelerated-container-image-1.4.2/internal/log/000077500000000000000000000000001514714641000214125ustar00rootroot00000000000000accelerated-container-image-1.4.2/internal/log/helper.go000066400000000000000000000015071514714641000232230ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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 log import ( "context" "fmt" clog "github.com/containerd/log" ) func TracedErrorf(ctx context.Context, format string, args ...any) error { err := fmt.Errorf(format, args...) clog.G(ctx).Error(err) return err } accelerated-container-image-1.4.2/nfpm.yaml000066400000000000000000000017451514714641000206500ustar00rootroot00000000000000# nfpm example config file # # check https://nfpm.goreleaser.com/configuration for detailed usage # name: "overlaybd-snapshotter" arch: ${GOARCH} platform: ${GOOS} version: ${SEMVER} release: ${RELEASE} section: "default" priority: "extra" maintainer: "overlaybd authors" description: | Overlaybd snapshotter vendor: "overlaybd" homepage: "https://github.com/containerd/accelerated-container-image" license: "Apache-2.0" contents: - src: ./bin/overlaybd-attacher dst: /opt/overlaybd/snapshotter/overlaybd-attacher - src: ./bin/ctr dst: /opt/overlaybd/snapshotter/ctr - src: ./bin/overlaybd-snapshotter dst: /opt/overlaybd/snapshotter/overlaybd-snapshotter - src: ./bin/convertor dst: /opt/overlaybd/snapshotter/convertor - src: ./script/overlaybd-snapshotter.service dst: /opt/overlaybd/snapshotter/overlaybd-snapshotter.service type: config - src: ./script/config.json dst: /etc/overlaybd-snapshotter/config.json type: config overrides: rpm: scripts: deb: scripts: accelerated-container-image-1.4.2/pkg/000077500000000000000000000000001514714641000175765ustar00rootroot00000000000000accelerated-container-image-1.4.2/pkg/convertor/000077500000000000000000000000001514714641000216175ustar00rootroot00000000000000accelerated-container-image-1.4.2/pkg/convertor/convertor.go000066400000000000000000000540011514714641000241670ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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 convertor import ( "archive/tar" "bufio" "bytes" "context" "crypto/rand" "database/sql" "encoding/base64" "encoding/json" "fmt" "io" "os" "path/filepath" "strings" "time" "github.com/containerd/accelerated-container-image/pkg/label" "github.com/containerd/accelerated-container-image/pkg/utils" "github.com/containerd/accelerated-container-image/pkg/version" containerd "github.com/containerd/containerd/v2/client" "github.com/containerd/containerd/v2/core/content" "github.com/containerd/containerd/v2/core/images" "github.com/containerd/containerd/v2/core/images/converter" "github.com/containerd/containerd/v2/core/mount" "github.com/containerd/containerd/v2/core/remotes" "github.com/containerd/containerd/v2/core/snapshots" "github.com/containerd/containerd/v2/pkg/archive" "github.com/containerd/containerd/v2/pkg/archive/compression" "github.com/containerd/containerd/v2/pkg/reference" "github.com/containerd/errdefs" "github.com/containerd/log" "github.com/containerd/platforms" "github.com/opencontainers/go-digest" "github.com/opencontainers/image-spec/identity" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "golang.org/x/sync/errgroup" ) const ( labelBuildLayerFrom = "containerd.io/snapshot/overlaybd/build.layer-from" labelDistributionSource = "containerd.io/distribution.source" ) var ( emptyString string emptyDesc ocispec.Descriptor emptyLayer Layer convSnapshotNameFormat = "overlaybd-conv-%s" ConvContentNameFormat = convSnapshotNameFormat ) type ZFileConfig struct { Algorithm string `json:"algorithm"` BlockSize int `json:"blockSize"` } type ImageConvertor interface { Convert(ctx context.Context, srcManifest ocispec.Manifest, fsType string) (ocispec.Descriptor, error) } type Layer struct { Desc ocispec.Descriptor DiffID digest.Digest } func (l *Layer) GetInfo() (ocispec.Descriptor, digest.Digest) { return l.Desc, l.DiffID } // NewContentLoaderWithFsType constructs a new contentLoader. // // contentLoader can load multiple files into content.Store service, and return an oci.v1.tar layer. func NewContentLoaderWithFsType(isAccelLayer bool, fsType string, files ...ContentFile) *contentLoader { return &contentLoader{ files: files, isAccelLayer: isAccelLayer, fsType: fsType, } } type ContentFile struct { SrcFilePath string // .../{ID}/fs/overlaybd.sealed DstFileName string // overlaybd.commit } type contentLoader struct { files []ContentFile isAccelLayer bool fsType string } func (loader *contentLoader) Load(ctx context.Context, cs content.Store) (l Layer, err error) { refName := fmt.Sprintf(ConvContentNameFormat, UniquePart()) contentWriter, err := content.OpenWriter(ctx, cs, content.WithRef(refName)) if err != nil { return emptyLayer, fmt.Errorf("failed to open content writer: %w", err) } defer contentWriter.Close() srcPathList := make([]string, 0) digester := digest.Canonical.Digester() countWriter := &writeCountWrapper{w: io.MultiWriter(contentWriter, digester.Hash())} tarWriter := tar.NewWriter(countWriter) openedSrcFile := make([]*os.File, 0) defer func() { for _, each := range openedSrcFile { _ = each.Close() } }() for _, loader := range loader.files { if loader.DstFileName == "overlaybd.commit" { commitPath := filepath.Dir(loader.SrcFilePath) commitFile := filepath.Join(commitPath, "overlaybd.commit") srcPathList = append(srcPathList, commitFile) err := utils.Commit(ctx, commitPath, commitPath, true, "-z", "-t") if err != nil { return emptyLayer, fmt.Errorf("failed to overlaybd-commit for sealed file: %w", err) } srcFile, err := os.Open(commitFile) if err != nil { return emptyLayer, fmt.Errorf("failed to open src file of %s: %w", loader.SrcFilePath, err) } openedSrcFile = append(openedSrcFile, srcFile) _, err = io.Copy(countWriter, bufio.NewReader(srcFile)) if err != nil { log.G(ctx).Errorf("failed to do io.Copy(), error: %v", err) return emptyLayer, err } } else { // normal file srcPathList = append(srcPathList, loader.SrcFilePath) srcFile, err := os.Open(loader.SrcFilePath) if err != nil { return emptyLayer, fmt.Errorf("failed to open src file of %s: %w", loader.SrcFilePath, err) } openedSrcFile = append(openedSrcFile, srcFile) fi, err := srcFile.Stat() if err != nil { return emptyLayer, fmt.Errorf("failed to get info of %s: %w", loader.SrcFilePath, err) } if err := tarWriter.WriteHeader(&tar.Header{ Name: loader.DstFileName, Mode: 0444, Size: fi.Size(), Typeflag: tar.TypeReg, }); err != nil { return emptyLayer, fmt.Errorf("failed to write tar header: %w", err) } if _, err := io.Copy(tarWriter, bufio.NewReader(srcFile)); err != nil { return emptyLayer, fmt.Errorf("failed to copy IO: %w", err) } } } if err = tarWriter.Close(); err != nil { return emptyLayer, fmt.Errorf("failed to close tar file: %w", err) } labels := map[string]string{ labelBuildLayerFrom: strings.Join(srcPathList, ","), } if err := contentWriter.Commit(ctx, countWriter.c, digester.Digest(), content.WithLabels(labels)); err != nil { if !errdefs.IsAlreadyExists(err) { return emptyLayer, fmt.Errorf("failed to commit content: %w", err) } } l = Layer{ Desc: ocispec.Descriptor{ MediaType: ocispec.MediaTypeImageLayer, Digest: digester.Digest(), Size: countWriter.c, Annotations: map[string]string{ label.OverlayBDVersion: version.OverlayBDVersionNumber, label.OverlayBDBlobDigest: digester.Digest().String(), label.OverlayBDBlobSize: fmt.Sprintf("%d", countWriter.c), }, }, DiffID: digester.Digest(), } if loader.isAccelLayer { l.Desc.Annotations[label.AccelerationLayer] = "yes" } if loader.fsType != "" { l.Desc.Annotations[label.OverlayBDBlobFsType] = loader.fsType } return l, nil } type overlaybdConvertor struct { ImageConvertor cs content.Store sn snapshots.Snapshotter remote bool fetcher remotes.Fetcher pusher remotes.Pusher db *sql.DB host string repo string zfileCfg ZFileConfig vsize int } func NewOverlaybdConvertor(ctx context.Context, cs content.Store, sn snapshots.Snapshotter, resolver remotes.Resolver, ref string, dbstr string, zfileCfg ZFileConfig, vsize int) (ImageConvertor, error) { c := &overlaybdConvertor{ cs: cs, sn: sn, remote: false, zfileCfg: zfileCfg, vsize: vsize, } var err error if dbstr != "" { c.remote = true c.db, err = sql.Open("mysql", dbstr) if err != nil { return nil, err } c.pusher, err = resolver.Pusher(ctx, ref) if err != nil { return nil, err } c.fetcher, err = resolver.Fetcher(ctx, ref) if err != nil { return nil, err } refspec, err := reference.Parse(ref) if err != nil { return nil, err } c.host = refspec.Hostname() c.repo = strings.TrimPrefix(refspec.Locator, c.host+"/") } return c, nil } func (c *overlaybdConvertor) Convert(ctx context.Context, srcManifest ocispec.Manifest, fsType string) (ocispec.Descriptor, error) { configData, err := content.ReadBlob(ctx, c.cs, srcManifest.Config) if err != nil { return emptyDesc, err } var srcCfg ocispec.Image if err := json.Unmarshal(configData, &srcCfg); err != nil { return emptyDesc, err } committedLayers, err := c.convertLayers(ctx, srcManifest.Layers, srcCfg.RootFS.DiffIDs, fsType) if err != nil { return emptyDesc, err } return c.commitImage(ctx, srcManifest, srcCfg, committedLayers) } func (c *overlaybdConvertor) commitImage(ctx context.Context, srcManifest ocispec.Manifest, imgCfg ocispec.Image, committedLayers []Layer) (ocispec.Descriptor, error) { var copyManifest = struct { ocispec.Manifest `json:",omitempty"` // MediaType is the media type of the object this schema refers to. MediaType string `json:"mediaType,omitempty"` }{ Manifest: srcManifest, MediaType: images.MediaTypeDockerSchema2Manifest, } imgCfg.RootFS.DiffIDs = nil copyManifest.Layers = nil for _, l := range committedLayers { copyManifest.Layers = append(copyManifest.Layers, l.Desc) imgCfg.RootFS.DiffIDs = append(imgCfg.RootFS.DiffIDs, l.DiffID) } configData, err := json.MarshalIndent(imgCfg, "", " ") if err != nil { return emptyDesc, fmt.Errorf("failed to marshal image: %w", err) } config := ocispec.Descriptor{ MediaType: srcManifest.Config.MediaType, Digest: digest.Canonical.FromBytes(configData), Size: int64(len(configData)), } ref := remotes.MakeRefKey(ctx, config) if err := content.WriteBlob(ctx, c.cs, ref, bytes.NewReader(configData), config); err != nil { return ocispec.Descriptor{}, fmt.Errorf("failed to write image config: %w", err) } if c.remote { if err := c.pushObject(ctx, config); err != nil { return ocispec.Descriptor{}, fmt.Errorf("failed to push image config: %w", err) } log.G(ctx).Infof("config pushed") } copyManifest.Manifest.Config = config mb, err := json.MarshalIndent(copyManifest, "", " ") if err != nil { return emptyDesc, err } desc := ocispec.Descriptor{ MediaType: copyManifest.MediaType, Digest: digest.Canonical.FromBytes(mb), Size: int64(len(mb)), } labels := map[string]string{} labels["containerd.io/gc.ref.content.config"] = copyManifest.Config.Digest.String() for i, ch := range copyManifest.Layers { labels[fmt.Sprintf("containerd.io/gc.ref.content.l.%d", i)] = ch.Digest.String() } ref = remotes.MakeRefKey(ctx, desc) if err := content.WriteBlob(ctx, c.cs, ref, bytes.NewReader(mb), desc, content.WithLabels(labels)); err != nil { return emptyDesc, fmt.Errorf("failed to write image manifest: %w", err) } if c.remote { if err := c.pushObject(ctx, desc); err != nil { return ocispec.Descriptor{}, fmt.Errorf("failed to push image manifest: %w", err) } log.G(ctx).Infof("image pushed") } return desc, nil } type OverlaybdLayer struct { Host string Repo string ChainID string DataDigest string DataSize int64 } func (c *overlaybdConvertor) findRemote(ctx context.Context, chainID string) (ocispec.Descriptor, error) { row := c.db.QueryRow("select host, repo, chain_id, data_digest, data_size from overlaybd_layers where host=? and repo=? and chain_id=?", c.host, c.repo, chainID) // try to find in the same repo, check existence on registry var layer OverlaybdLayer if err := row.Scan(&layer.Host, &layer.Repo, &layer.ChainID, &layer.DataDigest, &layer.DataSize); err == nil { desc := ocispec.Descriptor{ MediaType: ocispec.MediaTypeImageLayer, Digest: digest.Digest(layer.DataDigest), Size: layer.DataSize, } rc, err := c.fetcher.Fetch(ctx, desc) if err == nil { rc.Close() log.G(ctx).Infof("found remote layer for chainID %s", chainID) return desc, nil } if errdefs.IsNotFound(err) { // invalid record in db, which is not found in registry, remove it _, err := c.db.Exec("delete from overlaybd_layers where host=? and repo=? and chain_id=?", c.host, c.repo, chainID) if err != nil { return emptyDesc, fmt.Errorf("failed to remove invalid record in db: %w", err) } } } // found record in other repo, mount it to target repo rows, err := c.db.Query("select host, repo, chain_id, data_digest, data_size from overlaybd_layers where host=? and chain_id=?", c.host, chainID) if err != nil { if err == sql.ErrNoRows { return emptyDesc, errdefs.ErrNotFound } log.G(ctx).Infof("query error %v", err) return emptyDesc, err } for rows.Next() { var layer OverlaybdLayer err = rows.Scan(&layer.Host, &layer.Repo, &layer.ChainID, &layer.DataDigest, &layer.DataSize) if err != nil { continue } // try mount desc := ocispec.Descriptor{ MediaType: ocispec.MediaTypeImageLayer, Digest: digest.Digest(layer.DataDigest), Size: layer.DataSize, Annotations: map[string]string{ fmt.Sprintf("%s.%s", labelDistributionSource, c.host): layer.Repo, }, } _, err := c.pusher.Push(ctx, desc) if errdefs.IsAlreadyExists(err) { desc.Annotations = nil _, err := c.db.Exec("insert into overlaybd_layers(host, repo, chain_id, data_digest, data_size) values(?, ?, ?, ?, ?)", c.host, c.repo, chainID, desc.Digest.String(), desc.Size) if err != nil { continue } log.G(ctx).Infof("mount from %s success", layer.Repo) log.G(ctx).Infof("found remote layer for chainID %s", chainID) return desc, nil } } log.G(ctx).Infof("layer not found in remote") return emptyDesc, errdefs.ErrNotFound } func (c *overlaybdConvertor) pushObject(ctx context.Context, desc ocispec.Descriptor) error { ra, err := c.cs.ReaderAt(ctx, desc) if err != nil { return err } defer ra.Close() cw, err := c.pusher.Push(ctx, desc) if err != nil { if errdefs.IsAlreadyExists(err) { return nil } return err } return content.Copy(ctx, cw, content.NewReader(ra), desc.Size, desc.Digest) } func (c *overlaybdConvertor) sentToRemote(ctx context.Context, desc ocispec.Descriptor, chainID string) error { // upload to registry err := c.pushObject(ctx, desc) if err != nil { return err } // update db _, err = c.db.Exec("insert into overlaybd_layers(host, repo, chain_id, data_digest, data_size) values(?, ?, ?, ?, ?)", c.host, c.repo, chainID, desc.Digest.String(), desc.Size) if err != nil { log.G(ctx).Warnf("failed to insert to db, err: %v", err) if strings.Contains(err.Error(), "Duplicate entry") { fmt.Printf("Conflict when inserting into db, maybe other process is converting the same blob, please try again later\n") } return err } return nil } // convertLayers applys image layers on overlaybd with specified filesystem and // exports the layers based on zfile. func (c *overlaybdConvertor) convertLayers(ctx context.Context, srcDescs []ocispec.Descriptor, srcDiffIDs []digest.Digest, fsType string) ([]Layer, error) { var ( lastParentID string err error commitLayers = make([]Layer, len(srcDescs)) chain []digest.Digest ) var sendToContentStore = func(ctx context.Context, snID string) (Layer, error) { info, err := c.sn.Stat(ctx, snID) if err != nil { return emptyLayer, err } loader := NewContentLoaderWithFsType(false, fsType, ContentFile{ info.Labels[label.LocalOverlayBDPath], "overlaybd.commit"}) return loader.Load(ctx, c.cs) } eg, ctx := errgroup.WithContext(ctx) for idx, desc := range srcDescs { chain = append(chain, srcDiffIDs[idx]) chainID := identity.ChainID(chain).String() var remoteDesc ocispec.Descriptor if c.remote { remoteDesc, err = c.findRemote(ctx, chainID) if err != nil { if !errdefs.IsNotFound(err) { return nil, err } } } if c.remote && err == nil { key := fmt.Sprintf(convSnapshotNameFormat, chainID) opts := []snapshots.Opt{ snapshots.WithLabels(map[string]string{ "containerd.io/snapshot.ref": key, "containerd.io/snapshot/image-ref": c.host + "/" + c.repo, label.OverlayBDBlobDigest: remoteDesc.Digest.String(), label.OverlayBDBlobSize: fmt.Sprintf("%d", remoteDesc.Size), }), } _, err = c.sn.Prepare(ctx, "prepare-"+key, lastParentID, opts...) if !errdefs.IsAlreadyExists(err) { // failed to prepare remote snapshot if err == nil { // rollback c.sn.Remove(ctx, "prepare-"+key) } return nil, fmt.Errorf("failed to prepare remote snapshot: %w", err) } lastParentID = key commitLayers[idx] = Layer{ Desc: ocispec.Descriptor{ MediaType: ocispec.MediaTypeImageLayer, Digest: remoteDesc.Digest, Size: remoteDesc.Size, Annotations: map[string]string{ label.OverlayBDBlobDigest: remoteDesc.Digest.String(), label.OverlayBDBlobSize: fmt.Sprintf("%d", remoteDesc.Size), }, }, DiffID: remoteDesc.Digest, } continue } opts := []snapshots.Opt{ snapshots.WithLabels(map[string]string{ label.SupportReadWriteMode: "dir", label.OverlayBDBlobFsType: fsType, label.OverlayBDVsize: fmt.Sprintf("%d", c.vsize), }), } cfgStr, err := json.Marshal(c.zfileCfg) if err != nil { return nil, err } opts = append(opts, snapshots.WithLabels(map[string]string{ label.ZFileConfig: string(cfgStr), })) lastParentID, err = c.applyOCIV1LayerInObd(ctx, lastParentID, desc, opts, nil) if err != nil { return nil, err } if c.remote { // must synchronize registry and db, can not do concurrently commitLayers[idx], err = sendToContentStore(ctx, lastParentID) if err != nil { return nil, err } err = c.sentToRemote(ctx, commitLayers[idx].Desc, chainID) if err != nil { return nil, err } } else { idxI := idx snID := lastParentID eg.Go(func() error { var err error commitLayers[idxI], err = sendToContentStore(ctx, snID) return err }) } } if err := eg.Wait(); err != nil { return nil, err } return commitLayers, nil } // applyOCIV1LayerInObd applys the OCIv1 tarfile in overlaybd format and commit it. func (c *overlaybdConvertor) applyOCIV1LayerInObd( ctx context.Context, parentID string, // the ID of parent snapshot desc ocispec.Descriptor, // the descriptor of layer snOpts []snapshots.Opt, // apply for the commit snapshotter afterApply func(root string) error, // do something after apply tar stream ) (string, error) { ra, err := c.cs.ReaderAt(ctx, desc) if err != nil { return emptyString, fmt.Errorf("failed to get reader %s from content store: %w", desc.Digest, err) } defer ra.Close() var ( key string mounts []mount.Mount ) for { key = fmt.Sprintf(convSnapshotNameFormat, UniquePart()) mounts, err = c.sn.Prepare(ctx, key, parentID, snOpts...) if err != nil { // retry other key if errdefs.IsAlreadyExists(err) { continue } return emptyString, fmt.Errorf("failed to preprare snapshot %q: %w", key, err) } break } var ( rollback = true digester = digest.Canonical.Digester() rc = io.TeeReader(content.NewReader(ra), digester.Hash()) ) defer func() { if rollback { if rerr := c.sn.Remove(ctx, key); rerr != nil { log.G(ctx).WithError(rerr).WithField("key", key).Warnf("apply failure and failed to cleanup snapshot") } } }() rc, err = compression.DecompressStream(rc) if err != nil { return emptyString, fmt.Errorf("failed to detect layer mediatype: %w", err) } if err = mount.WithTempMount(ctx, mounts, func(root string) error { _, err := archive.Apply(ctx, root, rc) if err == nil && afterApply != nil { err = afterApply(root) } return err }); err != nil { return emptyString, fmt.Errorf("failed to apply layer in snapshot %s: %w", key, err) } // Read any trailing data if _, err := io.Copy(io.Discard, rc); err != nil { return emptyString, err } commitID := fmt.Sprintf(convSnapshotNameFormat, digester.Digest()) if err = c.sn.Commit(ctx, commitID, key, snOpts...); err != nil { if !errdefs.IsAlreadyExists(err) { return emptyString, err } } rollback = err != nil return commitID, nil } // UniquePart is based on https://github.com/containerd/containerd/blob/v1.4.3/rootfs/apply.go#L181-L187 func UniquePart() string { t := time.Now() var b [3]byte // Ignore read failures, just decreases uniqueness _, _ = rand.Read(b[:]) return fmt.Sprintf("%d-%s", t.Nanosecond(), strings.ReplaceAll(base64.URLEncoding.EncodeToString(b[:]), "_", "-")) } type writeCountWrapper struct { w io.Writer c int64 } func (wc *writeCountWrapper) Write(p []byte) (n int, err error) { n, err = wc.w.Write(p) wc.c += int64(n) return } // NOTE: based on https://github.com/containerd/containerd/blob/v1.6.8/images/converter/converter.go#L29-L71 type options struct { fsType string dbstr string imgRef string algorithm string blockSize int vsize int resolver remotes.Resolver client *containerd.Client } type Option func(o *options) error func WithFsType(fsType string) Option { return func(o *options) error { o.fsType = fsType return nil } } func WithDbstr(dbstr string) Option { return func(o *options) error { o.dbstr = dbstr return nil } } func WithImageRef(imgRef string) Option { return func(o *options) error { o.imgRef = imgRef return nil } } func WithAlgorithm(algorithm string) Option { return func(o *options) error { o.algorithm = algorithm return nil } } func WithBlockSize(blockSize int) Option { return func(o *options) error { o.blockSize = blockSize return nil } } func WithVsize(vsize int) Option { return func(o *options) error { o.vsize = vsize return nil } } func WithResolver(resolver remotes.Resolver) Option { return func(o *options) error { o.resolver = resolver return nil } } func WithClient(client *containerd.Client) Option { return func(o *options) error { o.client = client return nil } } func IndexConvertFunc(opts ...Option) converter.ConvertFunc { return func(ctx context.Context, cs content.Store, desc ocispec.Descriptor) (*ocispec.Descriptor, error) { var copts options for _, o := range opts { if err := o(&copts); err != nil { return nil, err } } client := copts.client imgRef := copts.imgRef sn := client.SnapshotService("overlaybd") srcImg, err := client.GetImage(ctx, imgRef) if err != nil { return nil, err } srcManifest, err := images.Manifest(ctx, cs, srcImg.Target(), platforms.Default()) if err != nil { return nil, fmt.Errorf("failed to read manifest: %w", err) } zfileCfg := ZFileConfig{ Algorithm: copts.algorithm, BlockSize: copts.blockSize, } c, err := NewOverlaybdConvertor(ctx, cs, sn, copts.resolver, imgRef, copts.dbstr, zfileCfg, copts.vsize) if err != nil { return nil, err } newMfstDesc, err := c.Convert(ctx, srcManifest, copts.fsType) if err != nil { return nil, err } return &newMfstDesc, nil } } accelerated-container-image-1.4.2/pkg/label/000077500000000000000000000000001514714641000206555ustar00rootroot00000000000000accelerated-container-image-1.4.2/pkg/label/label.go000066400000000000000000000124271514714641000222710ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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 label // support on-demand loading by the labels const ( // TargetSnapshotRef is the interface to know that Prepare // action is to pull image, not for container Writable snapshot. // // NOTE: Only available in >= containerd 1.4.0 and containerd.Pull // with Unpack option. // // FIXME(fuweid): With containerd design, we don't know that what purpose // snapshotter.Prepare does for. For unpacked image, prepare is for // container's rootfs. For pulling image, the prepare is for committed. // With label "containerd.io/snapshot.ref" in preparing, snapshotter // author will know it is for pulling image. It will be useful. // // The label is only propagated during pulling image. So, is it possible // to propagate by image.Unpack()? TargetSnapshotRef = "containerd.io/snapshot.ref" // TargetImageRef is the label to mark where the snapshot comes from. // // TODO(fuweid): Is it possible to use it in upstream? TargetImageRef = "containerd.io/snapshot/image-ref" // OverlayBDBlobDigest is the annotation key in the manifest to // describe the digest of blob in OverlayBD format. // // NOTE: The annotation is part of image layer blob's descriptor. OverlayBDBlobDigest = "containerd.io/snapshot/overlaybd/blob-digest" // OverlayBDBlobSize is the annotation key in the manifest to // describe the size of blob in OverlayBD format. // // NOTE: The annotation is part of image layer blob's descriptor. OverlayBDBlobSize = "containerd.io/snapshot/overlaybd/blob-size" // OverlayBDBlobFsType is the annotation key in the manifest to // describe the filesystem type to be mounted as of blob in OverlayBD format. // // NOTE: The annotation is part of image layer blob's descriptor. OverlayBDBlobFsType = "containerd.io/snapshot/overlaybd/blob-fs-type" // AccelerationLayer is the annotation key in the manifest to indicate // whether a top layer is acceleration layer or not. AccelerationLayer = "containerd.io/snapshot/overlaybd/acceleration-layer" // RecordTrace tells snapshotter to record trace RecordTrace = "containerd.io/snapshot/overlaybd/record-trace" // RecordTracePath is the file path to record trace RecordTracePath = "containerd.io/snapshot/overlaybd/record-trace-path" // ZFileConfig is the config of ZFile ZFileConfig = "containerd.io/snapshot/overlaybd/zfile-config" // OverlayBD virtual block device size OverlayBDVsize = "containerd.io/snapshot/overlaybd/vsize" // CRIImageRef is the image-ref from cri CRIImageRef = "containerd.io/snapshot/cri.image-ref" // TurboOCIDigest is the index annotation key for image layer digest FastOCIDigest = "containerd.io/snapshot/overlaybd/fastoci/target-digest" // legacy TurboOCIDigest = "containerd.io/snapshot/overlaybd/turbo-oci/target-digest" // TurboOCIMediaType is the index annotation key for image layer media type FastOCIMediaType = "containerd.io/snapshot/overlaybd/fastoci/target-media-type" // legacy TurboOCIMediaType = "containerd.io/snapshot/overlaybd/turbo-oci/target-media-type" // DownloadRemoteBlob is a label for download remote blob DownloadRemoteBlob = "containerd.io/snapshot/overlaybd/download-remote-blob" RemoteLabel = "containerd.io/snapshot/remote" RemoteLabelVal = "remote snapshot" // OverlayBDVersion is the version number of overlaybd blob OverlayBDVersion = "containerd.io/snapshot/overlaybd/version" // LayerToTurboOCI is used to convert local layer to turboOCI with tar index LayerToTurboOCI = "containerd.io/snapshot/overlaybd/convert2turbo-oci" SnapshotType = "containerd.io/snapshot/type" // RootfsQuotaLabel sets container rootfs diskquota RootfsQuotaLabel = "containerd.io/snapshot/disk_quota" ) // OverlayBDAnnotations is used in filterAnnotationsForSave (https://github.com/moby/buildkit/blob/v0.11/cache/refs.go#L882) var OverlayBDAnnotations = []string{ LocalOverlayBDPath, OverlayBDBlobDigest, OverlayBDBlobSize, OverlayBDBlobFsType, } // interface const ( // SupportReadWriteMode is used to support writable block device // for active snapshotter. // // By default, multiple active snapshotters can share one block device // from parent snapshotter(committed). Like image builder and // sandboxed-like container runtime(KataContainer, Firecracker), those // cases want to use the block device alone or as writable. // There are two ways to provide writable devices: // - 'dir' mark the snapshotter // as wriable block device and mount it on rootfs. // - 'dev' mark the snapshotter // as wriable block device without mount. SupportReadWriteMode = "containerd.io/snapshot/overlaybd.writable" // LocalOverlayBDPath is used to export the commit file path. // // NOTE: Only used in image build. LocalOverlayBDPath = "containerd.io/snapshot/overlaybd.localcommitpath" ) accelerated-container-image-1.4.2/pkg/metrics/000077500000000000000000000000001514714641000212445ustar00rootroot00000000000000accelerated-container-image-1.4.2/pkg/metrics/metrics.go000066400000000000000000000031551514714641000232450ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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 metrics import ( "fmt" "net/http" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promhttp" ) type ExporterConfig struct { Enable bool `json:"enable"` UriPrefix string `json:"uriPrefix"` Port int `json:"port"` } var ( Config ExporterConfig IsAlive = promauto.NewGauge(prometheus.GaugeOpts{ Name: "is_alive", Help: "Indicates whether overlaybd-snapshotter is running or not.", }) GRPCErrCount = promauto.NewCounterVec(prometheus.CounterOpts{ Name: "grpc_error_count", Help: "Error count of GRPC APIs.", }, []string{"function"}) GRPCLatency = promauto.NewHistogramVec(prometheus.HistogramOpts{ Name: "grpc_latency_seconds", Help: "Latency of GRPC APIs.", Buckets: prometheus.ExponentialBuckets(0.0001, 2, 20), }, []string{"function"}) ) func Init() { http.Handle(Config.UriPrefix, promhttp.Handler()) http.ListenAndServe(":"+fmt.Sprintf("%d", Config.Port), nil) } accelerated-container-image-1.4.2/pkg/snapshot/000077500000000000000000000000001514714641000214355ustar00rootroot00000000000000accelerated-container-image-1.4.2/pkg/snapshot/diskquota/000077500000000000000000000000001514714641000234415ustar00rootroot00000000000000accelerated-container-image-1.4.2/pkg/snapshot/diskquota/prjquota.go000066400000000000000000000160521514714641000256410ustar00rootroot00000000000000//go:build linux // +build linux /* Copyright The Accelerated Container Image 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 diskquota import ( "fmt" "math" "path" "strconv" "strings" "sync" "github.com/containerd/containerd/v2/core/mount" "github.com/containerd/log" "github.com/docker/go-units" ) const ( // QuotaMinID represents the minimum quota id. // The value is unit32(2^24). QuotaMinID = uint32(16777216) // QuotaMaxID represents the maximum quota id. QuotaMaxID = uint32(200000000) ) func safeConvertToUInt32(strVal string) (uint32, error) { intVal, err := strconv.Atoi(strVal) if err != nil { return 0, fmt.Errorf("failed to parse integer: %w", err) } // Check if the value is within the uint32 range and is non-negative. if intVal < 0 || intVal > math.MaxUint32 { return 0, fmt.Errorf("value %d is out of range for uint32", intVal) } return uint32(intVal), nil } // SetDiskQuotaBytes set dir project quota to the quotaId func SetDiskQuotaBytes(dir string, limit int64, quotaID uint32) error { driver := &PrjQuotaDriver{} mountPoint, hasQuota, err := driver.CheckMountpoint(dir) if err != nil { return err } if !hasQuota { // no need to remount option prjquota for mountpoint return fmt.Errorf("mountpoint: (%s) not enable prjquota", mountPoint) } if err := checkDevLimit(mountPoint, uint64(limit)); err != nil { return fmt.Errorf("failed to check device limit, dir: (%s), limit: (%d)kb: %w", dir, limit, err) } err = driver.SetFileAttr(dir, quotaID) if err != nil { return fmt.Errorf("failed to set subtree, dir: (%s), quota id: (%d): %w", dir, quotaID, err) } return driver.setQuota(quotaID, uint64(limit/1024), mountPoint) } // PrjQuotaDriver represents project quota driver. type PrjQuotaDriver struct { lock sync.Mutex // quotaIDs saves all of quota ids. // key: quota ID which means this ID is used in the global scope. // value: stuct{} QuotaIDs map[uint32]struct{} // lastID is used to mark last used quota ID. // quota ID is allocated increasingly by sequence one by one. LastID uint32 } // SetDiskQuota uses the following two parameters to set disk quota for a directory. // * quota size: a byte size of requested quota. // * quota ID: an ID represent quota attr which is used in the global scope. func (quota *PrjQuotaDriver) SetDiskQuota(dir string, size string, quotaID uint32) error { mountPoint, hasQuota, err := quota.CheckMountpoint(dir) if err != nil { return err } if !hasQuota { // no need to remount option prjquota for mountpoint return fmt.Errorf("mountpoint: (%s) not enable prjquota", mountPoint) } limit, err := units.RAMInBytes(size) if err != nil { return fmt.Errorf("failed to change size: (%s) to kilobytes: %w", size, err) } if err := checkDevLimit(mountPoint, uint64(limit)); err != nil { return fmt.Errorf("failed to check device limit, dir: (%s), limit: (%d)kb: %w", dir, limit, err) } err = quota.SetFileAttr(dir, quotaID) if err != nil { return fmt.Errorf("failed to set subtree, dir: (%s), quota id: (%d): %w", dir, quotaID, err) } return quota.setQuota(quotaID, uint64(limit/1024), mountPoint) } func (quota *PrjQuotaDriver) CheckMountpoint(dir string) (string, bool, error) { mountInfo, err := mount.Lookup(dir) if err != nil { return "", false, fmt.Errorf("failed to get mount info, dir(%s): %w", dir, err) } if strings.Contains(mountInfo.VFSOptions, "prjquota") { return mountInfo.Mountpoint, true, nil } return mountInfo.Mountpoint, false, nil } // setQuota uses system tool "setquota" to set project quota for binding of limit and mountpoint and quotaID. // * quotaID: quota ID which means this ID is used in the global scope. // * blockLimit: block limit number for mountpoint. // * mountPoint: the mountpoint of the device in the filesystem // ext4: setquota -P qid $softlimit $hardlimit $softinode $hardinode mountpoint func (quota *PrjQuotaDriver) setQuota(quotaID uint32, blockLimit uint64, mountPoint string) error { quotaIDStr := strconv.FormatUint(uint64(quotaID), 10) blockLimitStr := strconv.FormatUint(blockLimit, 10) // ext4 set project quota limit // log.L.Infof("setquota -P %s 0 %s 0 0 %s", quotaIDStr, blockLimitStr, mountPoint) stdout, stderr, err := ExecSync("setquota", "-P", quotaIDStr, "0", blockLimitStr, "0", "0", mountPoint) if err != nil { return fmt.Errorf("failed to set quota, mountpoint: (%s), quota id: (%d), quota: (%d kbytes), stdout: (%s), stderr: (%s): %w", mountPoint, quotaID, blockLimit, stdout, stderr, err) } return nil } // GetQuotaIDInFileAttr gets attributes of the file which is in the inode. // The returned result is quota ID. // return 0 if failure happens, since quota ID must be positive. // execution command: `lsattr -p $dir` func (quota *PrjQuotaDriver) GetQuotaIDInFileAttr(dir string) uint32 { parent := path.Dir(dir) stdout, _, err := ExecSync("lsattr", "-p", parent) if err != nil { // failure, then return invalid value 0 for quota ID. return 0 } // example output: // 16777256 --------------e---P ./exampleDir lines := strings.Split(stdout, "\n") for _, line := range lines { parts := strings.Split(line, " ") if len(parts) > 2 && parts[2] == dir { // find the corresponding quota ID, return directly. qid, _ := safeConvertToUInt32(parts[0]) return qid } } return 0 } // GetNextQuotaID returns the next available quota id. func (quota *PrjQuotaDriver) GetNextQuotaID() (quotaID uint32, err error) { quota.lock.Lock() defer quota.lock.Unlock() if quota.LastID == 0 { quota.QuotaIDs, quota.LastID, err = loadQuotaIDs("-Pan") if err != nil { return 0, fmt.Errorf("failed to load quota list: %w", err) } } id := quota.LastID for { if id < QuotaMinID { id = QuotaMinID } id++ if _, ok := quota.QuotaIDs[id]; !ok { if id <= QuotaMaxID { break } log.L.Infof("reach the maximum, try to reuse quotaID") quota.QuotaIDs, quota.LastID, err = loadQuotaIDs("-Pan") if err != nil { return 0, fmt.Errorf("failed to load quota list: %w", err) } id = quota.LastID } } quota.QuotaIDs[id] = struct{}{} quota.LastID = id return id, nil } // SetFileAttr set the file attr. // ext4: chattr -p quotaid +P $DIR func (quota *PrjQuotaDriver) SetFileAttr(dir string, quotaID uint32) error { strID := strconv.FormatUint(uint64(quotaID), 10) // ext4 use chattr to change project id stdout, stderr, err := ExecSync("chattr", "-p", strID, "+P", dir) if err != nil { return fmt.Errorf("failed to set file(%s) quota id(%s), stdout: (%s), stderr: (%s): %w", dir, strID, stdout, stderr, err) } log.L.Debugf("set quota id (%s) to file (%s) attr", strID, dir) return nil } accelerated-container-image-1.4.2/pkg/snapshot/diskquota/quota_utils.go000066400000000000000000000070431514714641000263450ustar00rootroot00000000000000//go:build linux // +build linux /* Copyright The Accelerated Container Image 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 diskquota import ( "bytes" "fmt" "os" "os/exec" "strings" "syscall" ) // CheckRegularFile is used to check the file is regular file or directory. func CheckRegularFile(file string) (bool, error) { fd, err := os.Lstat(file) if err != nil { return false, err } if fd.Mode()&(os.ModeSymlink|os.ModeNamedPipe|os.ModeSocket|os.ModeDevice) == 0 { return true, nil } return false, nil } // loadQuotaIDs loads quota IDs for quota driver from reqquota execution result. // This function utils `repquota` which summarizes quotas for a filesystem. // see http://man7.org/linux/man-pages/man8/repquota.8.html // // $ repquota -Pan // Project used soft hard grace used soft hard grace // ---------------------------------------------------------------------- // #0 -- 220 0 0 25 0 0 // #123 -- 4 0 88589934592 1 0 0 // #8888 -- 8 0 0 2 0 0 func loadQuotaIDs(repquotaOpt string) (map[uint32]struct{}, uint32, error) { quotaIDs := make(map[uint32]struct{}) minID := QuotaMinID output, stderr, err := ExecSync("repquota", repquotaOpt) if err != nil { return nil, 0, fmt.Errorf("failed to execute [repquota %s], stdout: (%s), stderr: (%s): %w", repquotaOpt, output, stderr, err) } lines := strings.Split(output, "\n") for _, line := range lines { if len(line) == 0 || line[0] != '#' { continue } // find all lines with prefix '#' parts := strings.Split(line, " ") // part[0] is "#123456" if len(parts[0]) <= 1 { continue } quotaID, err := safeConvertToUInt32(parts[0][1:]) if err == nil && quotaID > QuotaMinID { quotaIDs[quotaID] = struct{}{} if quotaID > minID { minID = quotaID } } } return quotaIDs, minID, nil } // getDevLimit returns the device storage upper limit. func getDevLimit(mountPoint string) (uint64, error) { // get storage upper limit of the device which the dir is on. var stfs syscall.Statfs_t if err := syscall.Statfs(mountPoint, &stfs); err != nil { return 0, fmt.Errorf("failed to get path(%s) limit: %w", mountPoint, err) } return stfs.Blocks * uint64(stfs.Bsize), nil } // checkDevLimit checks if the device on which the input dir lies has already been recorded in driver. func checkDevLimit(mountPoint string, size uint64) error { limit, err := getDevLimit(mountPoint) if err != nil { return fmt.Errorf("failed to get device(%s) limit: %w", mountPoint, err) } if limit < size { return fmt.Errorf("dir %s quota limit %v must be less than %v", mountPoint, size, limit) } return nil } func ExecSync(bin string, args ...string) (std, serr string, err error) { var stdout bytes.Buffer var stderr bytes.Buffer cmd := exec.Command(bin, args...) cmd.Stdout = &stdout cmd.Stderr = &stderr err = cmd.Run() if err != nil { return stdout.String(), stderr.String(), err } return stdout.String(), stderr.String(), nil } accelerated-container-image-1.4.2/pkg/snapshot/overlay.go000066400000000000000000001357431514714641000234620ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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 ( "bytes" "context" "encoding/binary" "errors" "fmt" "os" "os/exec" "path/filepath" "strings" "sync" "syscall" "time" "github.com/containerd/accelerated-container-image/pkg/label" "github.com/containerd/accelerated-container-image/pkg/snapshot/diskquota" mylog "github.com/containerd/accelerated-container-image/internal/log" "github.com/containerd/accelerated-container-image/pkg/metrics" "github.com/data-accelerator/zdfs" "github.com/containerd/containerd/v2/core/mount" "github.com/containerd/containerd/v2/core/snapshots" "github.com/containerd/containerd/v2/core/snapshots/storage" "github.com/containerd/continuity/fs" "github.com/containerd/errdefs" "github.com/containerd/log" "github.com/moby/locker" ) // storageType is used to indicate that what kind of the snapshot it is. type storageType int const ( // storageTypeUnknown is placeholder for unknown type. storageTypeUnknown storageType = iota // storageTypeNormal means that the unpacked layer data is from tar.gz // or other valid media type from OCI image spec. This kind of the // snapshotter can be used as normal lowerdir layer of overlayFS. storageTypeNormal // storageTypeLocalBlock means that the unpacked layer data is in // Overlaybd format. storageTypeLocalBlock // storageTypeRemoteBlock means that there is no unpacked layer data. // But there are labels to mark data that will be pulling on demand. storageTypeRemoteBlock // storageTypeLocalLayer means that the unpacked layer data is in // a tar file, which needs to generate overlaybd-turboOCI meta before // create an overlaybd device storageTypeLocalLayer ) const ( RoDir = "overlayfs" // overlayfs as rootfs. upper + lower (overlaybd) RwDir = "dir" // mount overlaybd as rootfs RwDev = "dev" // use overlaybd directly LayerBlob = "layer" // decompressed tgz layer (maybe compressed by ZFile) ) type Registry struct { Host string `json:"host"` Insecure bool `json:"insecure"` } type BootConfig struct { AsyncRemove bool `json:"asyncRemove"` Address string `json:"address"` Root string `json:"root"` LogLevel string `json:"verbose"` LogReportCaller bool `json:"logReportCaller"` RwMode string `json:"rwMode"` // overlayfs, dir or dev AutoRemoveDev bool `json:"autoRemoveDev"` ExporterConfig metrics.ExporterConfig `json:"exporterConfig"` WritableLayerType string `json:"writableLayerType"` // append or sparse MirrorRegistry []Registry `json:"mirrorRegistry"` DefaultFsType string `json:"defaultFsType"` RootfsQuota string `json:"rootfsQuota"` // "20g" rootfs quota, only effective when rwMode is 'overlayfs' Tenant int `json:"tenant"` // do not set this if only a single snapshotter service in the host TurboFsType []string `json:"turboFsType"` } func DefaultBootConfig() *BootConfig { return &BootConfig{ AsyncRemove: false, LogLevel: "info", RwMode: "overlayfs", LogReportCaller: false, AutoRemoveDev: false, ExporterConfig: metrics.ExporterConfig{ Enable: false, UriPrefix: "/metrics", Port: 9863, }, MirrorRegistry: nil, WritableLayerType: "append", DefaultFsType: "ext4", RootfsQuota: "", Tenant: -1, TurboFsType: []string{ "erofs", "ext4", }, } } type ZFileConfig struct { Algorithm string `json:"algorithm"` BlockSize int `json:"blockSize"` } // SnapshotterConfig is used to configure the snapshotter instance type SnapshotterConfig struct { // OverlayBDUtilBinDir contains overlaybd-create/overlaybd-commit tools // to handle writable device. OverlayBDUtilBinDir string `toml:"overlaybd_util_bin_dir" json:"overlaybd_util_bin_dir"` } var defaultConfig = SnapshotterConfig{ OverlayBDUtilBinDir: "/opt/overlaybd/bin", } // Opt is an option to configure the 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 *BootConfig) error { config.AsyncRemove = true return nil } // snapshotter is implementation of github.com/containerd/containerd/snapshots.Snapshotter. // // It is a snapshotter plugin. The layout of root dir is organized: // // # snapshots stores each snapshot's data in unique folder named by auto- // # -incrementing integer (a.k.a Version ID). // # // # The snapshotter is based on overlayFS. It is the same with the containerd // # overlayFS plugin, which means that it can support normal OCI image. // # // # If the pull job doesn't support `containerd.io/snapshot.ref` and `containerd.io/snapshot/image-ref`, // # the snapshotter will use localBD mode to support the blob data in overlaybd // # format. The ${ID}/fs will be empty and the real file data will be placed // # in the ${ID}/block/mountpoint. It is the same to the remoteBD mode. // # // - snapshots/ // |_ ${ID}/ // | |_ fs/ # lowerdir or upperdir // | |_ work/ # workdir // | |_ block/ # tcmu block device // | |_ config.v1.json # config for overlaybd // | |_ init-debug.log # shows the debug log when creating overlaybd device // | |_ mountpoint # the block device will mount on this if the snapshot is based on overlaybd // | |_ writable_data # exists if the block is writable in active snapshotter // | |_ writable_index # exists if the block is writable in active snapshotter // | // |_ ... // // // # metadata.db is managed by github.com/containerd/containerd/snapshots/storage // # based on boltdb. // # // - metadata.db type snapshotter struct { root string rwMode string config SnapshotterConfig metacopyOption string ms *storage.MetaStore indexOff bool autoRemoveDev bool writableLayerType string mirrorRegistry []Registry defaultFsType string tenant int locker *locker.Locker turboFsType []string asyncRemove bool quotaDriver *diskquota.PrjQuotaDriver quotaSize string } // NewSnapshotter returns a Snapshotter which uses block device based on overlayFS. func NewSnapshotter(bootConfig *BootConfig, opts ...Opt) (snapshots.Snapshotter, error) { config := defaultConfig for _, opt := range opts { if err := opt(&config); err != nil { return nil, err } } if err := os.MkdirAll(bootConfig.Root, 0700); err != nil { return nil, err } ms, err := storage.NewMetaStore(filepath.Join(bootConfig.Root, "metadata.db")) if err != nil { return nil, err } if err := os.Mkdir(filepath.Join(bootConfig.Root, "snapshots"), 0700); err != nil && !os.IsExist(err) { return nil, err } root, err := filepath.EvalSymlinks(bootConfig.Root) if err != nil { log.L.Errorf("invalid root: %s. (%s)", bootConfig.Root, err.Error()) return nil, err } log.L.Infof("new snapshotter: root = %s", root) metacopyOption := "" if _, err := os.Stat("/sys/module/overlay/parameters/metacopy"); err == nil { metacopyOption = "metacopy=on" } // figure out whether "index=off" option is recognized by the kernel var indexOff bool if _, err = os.Stat("/sys/module/overlay/parameters/index"); err == nil { indexOff = true } if bootConfig.MirrorRegistry != nil { log.L.Infof("mirror Registry: %+v", bootConfig.MirrorRegistry) } return &snapshotter{ root: root, rwMode: bootConfig.RwMode, ms: ms, indexOff: indexOff, config: config, metacopyOption: metacopyOption, autoRemoveDev: bootConfig.AutoRemoveDev, writableLayerType: bootConfig.WritableLayerType, mirrorRegistry: bootConfig.MirrorRegistry, defaultFsType: bootConfig.DefaultFsType, locker: locker.New(), turboFsType: bootConfig.TurboFsType, tenant: bootConfig.Tenant, quotaSize: bootConfig.RootfsQuota, quotaDriver: &diskquota.PrjQuotaDriver{ QuotaIDs: make(map[uint32]struct{}), }, asyncRemove: bootConfig.AsyncRemove, }, nil } // Stat returns the info for an active or committed snapshot by the key. func (o *snapshotter) Stat(ctx context.Context, key string) (_ snapshots.Info, retErr error) { log.G(ctx).Infof("Stat (key: %s)", key) start := time.Now() defer func() { if retErr != nil { metrics.GRPCErrCount.WithLabelValues("Stat").Inc() } metrics.GRPCLatency.WithLabelValues("Stat").Observe(time.Since(start).Seconds()) }() 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 } // Updates updates the label of the given snapshot. // // NOTE: It supports patch-update. // // TODO(fuweid): should not touch the interface-like or internal label! func (o *snapshotter) Update(ctx context.Context, info snapshots.Info, fieldpaths ...string) (_ snapshots.Info, retErr error) { log.G(ctx).Infof("Update (fieldpaths: %s)", fieldpaths) start := time.Now() defer func() { if retErr != nil { metrics.GRPCErrCount.WithLabelValues("Update").Inc() } metrics.GRPCLatency.WithLabelValues("Update").Observe(time.Since(start).Seconds()) }() 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. func (o *snapshotter) Usage(ctx context.Context, key string) (_ snapshots.Usage, retErr error) { log.G(ctx).Infof("Usage (key: %s)", key) start := time.Now() defer func() { if retErr != nil { metrics.GRPCErrCount.WithLabelValues("Usage").Inc() } metrics.GRPCLatency.WithLabelValues("Usage").Observe(time.Since(start).Seconds()) }() 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() if err != nil { return snapshots.Usage{}, err } stype, err := o.identifySnapshotStorageType(ctx, id, info) if err != nil { return snapshots.Usage{}, err } switch info.Kind { case snapshots.KindActive: return o.diskUsageWithBlock(ctx, id, stype) case snapshots.KindCommitted: if stype == storageTypeRemoteBlock || stype == storageTypeLocalBlock { return o.diskUsageWithBlock(ctx, id, stype) } } return usage, nil } func (o *snapshotter) getWritableType(ctx context.Context, id string, info snapshots.Info) (mode string) { defer func() { log.G(ctx).Infof("snapshot R/W label: %s", mode) }() // check image type (OCIv1 or overlaybd) if id != "" { if _, err := o.loadBackingStoreConfig(id); err != nil { log.G(ctx).Debugf("[%s] is not an overlaybd image.", id) return RoDir } } else { log.G(ctx).Debugf("empty snID get. It should be an initial layer.") } // overlaybd rwMode := func(m string) string { if m == "dir" { return RwDir } if m == "dev" { return RwDev } return RoDir } m, ok := info.Labels[label.SupportReadWriteMode] if !ok { return rwMode(o.rwMode) } return rwMode(m) } func (o *snapshotter) checkTurboOCI(labels map[string]string) (bool, string, string) { if st1, ok1 := labels[label.TurboOCIDigest]; ok1 { return true, st1, labels[label.TurboOCIMediaType] } if st2, ok2 := labels[label.FastOCIDigest]; ok2 { return true, st2, labels[label.FastOCIMediaType] } return false, "", "" } func (o *snapshotter) isPrepareRootfs(info snapshots.Info) bool { if _, ok := info.Labels[label.TargetSnapshotRef]; ok { return false } if info.Labels[label.SnapshotType] == "image" { return false } return true } func (o *snapshotter) createMountPoint(ctx context.Context, kind snapshots.Kind, key string, parent string, opts ...snapshots.Opt) (_ []mount.Mount, retErr error) { ctx, t, err := o.ms.TransactionContext(ctx, true) if err != nil { return nil, err } rollback := true defer func() { if retErr != nil && rollback { if rerr := t.Rollback(); rerr != nil { log.G(ctx).WithError(rerr).Warn("failed to rollback transaction") } } }() id, info, err := o.createSnapshot(ctx, kind, key, parent, opts) if err != nil { return nil, err } log.G(ctx).Infof("sn: %s, labels:[%+v]", id, info.Labels) defer func() { // the transaction rollback makes created snapshot invalid, just clean it. if retErr != nil && rollback { if rerr := os.RemoveAll(o.snPath(id)); rerr != nil { log.G(ctx).WithError(rerr).Warn("failed to cleanup") } } }() s, err := storage.GetSnapshot(ctx, key) if err != nil { return nil, err } var ( parentID string parentInfo snapshots.Info ) if parent != "" { parentID, parentInfo, _, err = storage.GetInfo(ctx, parent) if err != nil { return nil, fmt.Errorf("failed to get info of parent snapshot %s: %w", parent, err) } } // If the layer is in overlaybd format, construct backstore spec and saved it into snapshot dir. // Return ErrAlreadyExists to skip pulling and unpacking layer. See https://github.com/containerd/containerd/blob/master/docs/remote-snapshotter.md#snapshotter-apis-for-querying-remote-snapshots // This part code is only for Pull. if targetRef, ok := info.Labels[label.TargetSnapshotRef]; ok { // Acceleration layer will not use remote snapshot. It still needs containerd to pull, // unpack blob and commit snapshot. So return a normal mount point here. isAccelLayer := info.Labels[label.AccelerationLayer] log.G(ctx).Debugf("Prepare (targetRefLabel: %s, accelLayerLabel: %s)", targetRef, isAccelLayer) if isAccelLayer == "yes" { if err := o.constructSpecForAccelLayer(id, parentID); err != nil { return nil, fmt.Errorf("constructSpecForAccelLayer failed: id %s: %w", id, err) } rollback = false if err := t.Commit(); err != nil { return nil, err } return []mount.Mount{{ Source: o.upperPath(id), Type: "bind", Options: []string{ "rw", "bind", }}, }, nil } stype, err := o.identifySnapshotStorageType(ctx, id, info) log.G(ctx).Debugf("Prepare (storageType: %d)", stype) if err != nil { return nil, err } // Download blob downloadBlob := info.Labels[label.DownloadRemoteBlob] if downloadBlob == "download" && stype == storageTypeRemoteBlock { log.G(ctx).Infof("download blob %s", targetRef) if err := o.ConstructOverlayBDSpec(ctx, key, false); err != nil { return nil, err } rollback = false if err := t.Commit(); err != nil { return nil, err } return []mount.Mount{{ Source: o.upperPath(id), Type: "bind", Options: []string{ "rw", "bind", }}, }, nil } // Normal prepare for TurboOCI if isTurboOCI, digest, _ := o.checkTurboOCI(info.Labels); isTurboOCI { log.G(ctx).Infof("%s is turboOCI.v1 layer: %s", s.ID, digest) stype = storageTypeNormal } if stype == storageTypeRemoteBlock { info.Labels[label.RemoteLabel] = label.RemoteLabelVal // Mark this snapshot as remote if _, _, err = o.commit(ctx, targetRef, key, append(opts, snapshots.WithLabels(info.Labels))...); err != nil { return nil, err } if err := o.ConstructOverlayBDSpec(ctx, targetRef, false); err != nil { return nil, err } rollback = false if err := t.Commit(); err != nil { return nil, err } return nil, fmt.Errorf("target snapshot %q: %w", targetRef, errdefs.ErrAlreadyExists) } } stype := storageTypeNormal writeType := o.getWritableType(ctx, parentID, info) // If Preparing for rootfs, find metadata from its parent (top layer), launch and mount backstore device. if o.isPrepareRootfs(info) { log.G(ctx).Infof("Preparing rootfs(%s). writeType: %s", s.ID, writeType) if writeType != RoDir { stype = storageTypeLocalBlock if err := o.ConstructOverlayBDSpec(ctx, key, true); err != nil { log.G(ctx).Errorln(err.Error()) return nil, err } } else if parent != "" { stype, err = o.identifySnapshotStorageType(ctx, parentID, parentInfo) if err != nil { return nil, err } } switch stype { case storageTypeLocalBlock, storageTypeRemoteBlock: if parent != "" { parentIsAccelLayer := parentInfo.Labels[label.AccelerationLayer] == "yes" needRecordTrace := info.Labels[label.RecordTrace] == "yes" recordTracePath := info.Labels[label.RecordTracePath] log.G(ctx).Infof("Prepare rootfs (sn: %s, parentIsAccelLayer: %t, needRecordTrace: %t, recordTracePath: %s)", id, parentIsAccelLayer, needRecordTrace, recordTracePath) if parentIsAccelLayer { log.G(ctx).Infof("get accel-layer in parent (id: %s)", id) // If parent is already an acceleration layer, there is certainly no need to record trace. // Just mark this layer to get accelerated (trace replay) err = o.updateSpec(parentID, true, "") if writeType != RoDir { o.updateSpec(id, true, "") } } else if needRecordTrace && recordTracePath != "" { err = o.updateSpec(parentID, false, recordTracePath) if writeType != RoDir { o.updateSpec(id, false, recordTracePath) } } else { // For the compatibility of images which have no accel layer err = o.updateSpec(parentID, false, "") } if err != nil { return nil, fmt.Errorf("updateSpec failed for snapshot %s: %w", parentID, err) } } var obdID string var obdInfo *snapshots.Info if writeType != RoDir { obdID = id obdInfo = &info } else { obdID = parentID obdInfo = &parentInfo } fsType, ok := obdInfo.Labels[label.OverlayBDBlobFsType] if !ok { if isTurboOCI, _, _ := o.checkTurboOCI(obdInfo.Labels); isTurboOCI { _, fsType = o.turboOCIFsMeta(obdID) } else { log.G(ctx).Warnf("cannot get fs type from label, %v, using %s", obdInfo.Labels, fsType) fsType = o.defaultFsType } } log.G(ctx).Debugf("attachAndMountBlockDevice (obdID: %s, writeType: %s, fsType %s, targetPath: %s)", obdID, writeType, fsType, overlaybdTargetPath(obdHbaNum(o.tenant), obdID)) if err = o.attachAndMountBlockDevice(ctx, obdID, writeType, fsType, parent == ""); err != nil { log.G(ctx).Errorf("%v", err) return nil, fmt.Errorf("failed to attach and mount for snapshot %v: %w", obdID, err) } defer func() { if retErr != nil && writeType == RwDir { // It's unnecessary to umount overlay block device if writeType == writeTypeRawDev if rerr := mount.Unmount(o.overlaybdMountpoint(obdID), 0); rerr != nil { log.G(ctx).WithError(rerr).Warnf("failed to umount writable block %s", o.overlaybdMountpoint(obdID)) } } }() default: // do nothing } } if _, writableBD := info.Labels[label.SupportReadWriteMode]; stype == storageTypeNormal && writableBD { // if is not overlaybd writable layer, delete label before commit delete(info.Labels, label.SupportReadWriteMode) storage.UpdateInfo(ctx, info) } rollback = false if err := t.Commit(); err != nil { return nil, err } var m []mount.Mount switch stype { case storageTypeNormal: if _, ok := info.Labels[label.LayerToTurboOCI]; ok { m = []mount.Mount{ { Source: o.upperPath(s.ID), Type: "bind", Options: []string{ "rw", "rbind", }, }, } } else { m = o.normalOverlayMount(s) } case storageTypeLocalBlock, storageTypeRemoteBlock: m, err = o.basedOnBlockDeviceMount(ctx, s, writeType) if err != nil { return nil, err } default: panic("unreachable") } log.G(ctx).Debugf("return mount point(R/W mode: %s): %v", writeType, m) return m, nil } // Prepare creates an active snapshot identified by key descending from the provided parent. func (o *snapshotter) Prepare(ctx context.Context, key, parent string, opts ...snapshots.Opt) (_ []mount.Mount, retErr error) { log.G(ctx).Infof("Prepare (key: %s, parent: %s)", key, parent) start := time.Now() defer func() { if retErr != nil { metrics.GRPCErrCount.WithLabelValues("Prepare").Inc() } else { log.G(ctx).WithFields(log.Fields{ "d": time.Since(start), "key": key, "parent": parent, }).Infof("Prepare") } metrics.GRPCLatency.WithLabelValues("Prepare").Observe(time.Since(start).Seconds()) }() return o.createMountPoint(ctx, snapshots.KindActive, key, parent, opts...) } // View returns a readonly view on parent snapshotter. func (o *snapshotter) View(ctx context.Context, key, parent string, opts ...snapshots.Opt) (_ []mount.Mount, retErr error) { log.G(ctx).Infof("View (key: %s, parent: %s)", key, parent) defer log.G(ctx).Debugf("return View (key: %s, parent: %s)", key, parent) start := time.Now() defer func() { if retErr != nil { metrics.GRPCErrCount.WithLabelValues("View").Inc() } metrics.GRPCLatency.WithLabelValues("View").Observe(time.Since(start).Seconds()) }() return o.createMountPoint(ctx, snapshots.KindView, key, parent, opts...) } // 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, retErr error) { log.G(ctx).Infof("Mounts (key: %s)", key) start := time.Now() defer func() { if retErr != nil { metrics.GRPCErrCount.WithLabelValues("Mounts").Inc() } else { log.G(ctx).WithFields(log.Fields{ "d": time.Since(start), "key": key, }).Infof("Mounts") } metrics.GRPCLatency.WithLabelValues("Mounts").Observe(time.Since(start).Seconds()) }() ctx, t, err := o.ms.TransactionContext(ctx, false) if err != nil { return nil, err } defer t.Rollback() s, err := storage.GetSnapshot(ctx, key) if err != nil { return nil, fmt.Errorf("failed to get active mount: %w", err) } log.G(ctx).Debugf("Mounts (key: %s, id: %s, parentID: %s, kind: %d)", key, s.ID, s.ParentIDs, s.Kind) if len(s.ParentIDs) > 0 { if o.autoRemoveDev { o.locker.Lock(s.ID) defer o.locker.Unlock(s.ID) o.locker.Lock(s.ParentIDs[0]) defer o.locker.Unlock(s.ParentIDs[0]) } _, info, _, err := storage.GetInfo(ctx, key) if err != nil { return nil, fmt.Errorf("failed to get info: %w", err) } writeType := o.getWritableType(ctx, s.ID, info) if writeType != RoDir { return o.basedOnBlockDeviceMount(ctx, s, writeType) } parentID, parentInfo, _, err := storage.GetInfo(ctx, info.Parent) if err != nil { return nil, fmt.Errorf("failed to get info of parent snapshot %s: %w", info.Parent, err) } parentStype, err := o.identifySnapshotStorageType(ctx, parentID, parentInfo) if err != nil { return nil, fmt.Errorf("failed to identify storage of parent snapshot %s: %w", parentInfo.Name, err) } if parentStype == storageTypeRemoteBlock || parentStype == storageTypeLocalBlock { fsType, ok := parentInfo.Labels[label.OverlayBDBlobFsType] if !ok { if isTurboOCI, _, _ := o.checkTurboOCI(parentInfo.Labels); isTurboOCI { _, fsType = o.turboOCIFsMeta(parentID) } else { fsType = o.defaultFsType } } if err := o.attachAndMountBlockDevice(ctx, parentID, RoDir, fsType, false); err != nil { return nil, fmt.Errorf("failed to attach and mount for snapshot %v: %w", key, err) } return o.basedOnBlockDeviceMount(ctx, s, RoDir) } } return o.normalOverlayMount(s), nil } // Commit func (o *snapshotter) Commit(ctx context.Context, name, key string, opts ...snapshots.Opt) (retErr error) { log.G(ctx).Infof("Commit (key: %s, name: %s)", key, name) start := time.Now() defer func() { if retErr != nil { metrics.GRPCErrCount.WithLabelValues("Commit").Inc() } else { log.G(ctx).WithFields(log.Fields{ "d": time.Since(start), "name": name, "key": key, }).Infof("Commit") } metrics.GRPCLatency.WithLabelValues("Commit").Observe(time.Since(start).Seconds()) }() ctx, t, err := o.ms.TransactionContext(ctx, true) if err != nil { return err } rollback := true defer func() { if retErr != nil && rollback { if rerr := t.Rollback(); rerr != nil { log.G(ctx).WithError(rerr).Warn("failed to rollback transaction") } } }() id, oinfo, _, err := storage.GetInfo(ctx, key) if err != nil { return fmt.Errorf("failed to get info of snapshot %s: %w", key, err) } // if writable, should commit the data and make it immutable. if _, writableBD := oinfo.Labels[label.SupportReadWriteMode]; writableBD { // TODO(fuweid): how to rollback? if oinfo.Labels[label.AccelerationLayer] == "yes" { log.G(ctx).Info("Commit accel-layer requires no writable_data") } else if _, err := o.loadBackingStoreConfig(id); err != nil { log.G(ctx).Info("not an overlaybd writable layer") } else { if err := o.UnmountAndDetachBlockDevice(ctx, id); err != nil { return fmt.Errorf("failed to destroy target device for snapshot %s: %w", key, err) } if err := o.sealWritableOverlaybd(ctx, id); err != nil { return err } opts = append(opts, snapshots.WithLabels(map[string]string{label.LocalOverlayBDPath: o.overlaybdSealedFilePath(id)})) } } if isOverlaybd, err := zdfs.PrepareOverlayBDSpec(ctx, key, id, o.snPath(id), oinfo, o.snPath); isOverlaybd { log.G(ctx).Infof("sn: %s is an overlaybd layer", id) } else if err != nil { log.G(ctx).Errorf("prepare overlaybd spec failed(sn: %s): %s", id, err.Error()) return err } id, info, err := o.commit(ctx, name, key, opts...) if err != nil { return err } stype, err := o.identifySnapshotStorageType(ctx, id, info) if err != nil { return err } log.G(ctx).Debugf("Commit info (id: %s, info: %v, stype: %d)", id, info.Labels, stype) // For turboOCI, we need to construct OverlayBD spec after unpacking // since there could be multiple fs metadata in a turboOCI layer if isTurboOCI, digest, _ := o.checkTurboOCI(info.Labels); isTurboOCI { log.G(ctx).Infof("commit turboOCI.v1 layer: (%s, %s)", id, digest) if err := o.ConstructOverlayBDSpec(ctx, name, false); err != nil { return fmt.Errorf("failed to construct overlaybd config: %w", err) } stype = storageTypeNormal } // Firstly, try to convert an OCIv1 tarball to a turboOCI layer. // then change stype to 'storageTypeLocalBlock' which can make it attach a overlaybd block if stype == storageTypeLocalLayer { log.G(ctx).Infof("convet a local blob to turboOCI layer (sn: %s)", id) if err := o.ConstructOverlayBDSpec(ctx, name, false); err != nil { return fmt.Errorf("failed to construct overlaybd config: %w", err) } stype = storageTypeLocalBlock } if stype == storageTypeLocalBlock { if err := o.ConstructOverlayBDSpec(ctx, name, false); err != nil { return fmt.Errorf("failed to construct overlaybd config: %w", err) } if info.Labels == nil { info.Labels = make(map[string]string) } info.Labels[label.LocalOverlayBDPath] = o.overlaybdSealedFilePath(id) delete(info.Labels, label.SupportReadWriteMode) info, err = storage.UpdateInfo(ctx, info) if err != nil { return err } log.G(ctx).Debugf("Commit info (info: %v)", info.Labels) } rollback = false return t.Commit() } func (o *snapshotter) commit(ctx context.Context, name, key string, opts ...snapshots.Opt) (string, snapshots.Info, error) { id, _, _, err := storage.GetInfo(ctx, key) if err != nil { return "", snapshots.Info{}, err } usage, err := fs.DiskUsage(ctx, o.upperPath(id)) if err != nil { return "", snapshots.Info{}, err } if _, err := storage.CommitActive(ctx, key, name, snapshots.Usage(usage), opts...); err != nil { return "", snapshots.Info{}, fmt.Errorf("failed to commit snapshot: %w", err) } id, info, _, err := storage.GetInfo(ctx, name) if err != nil { return "", snapshots.Info{}, err } return id, info, nil } func (o *snapshotter) cleanupDirectories(ctx context.Context) ([]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) } func (o *snapshotter) getCleanupDirectories(ctx context.Context, t storage.Transactor) ([]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 _, ok := ids[d]; ok { continue } cleanup = append(cleanup, filepath.Join(snapshotDir, d)) } return cleanup, nil } // Cleanup cleans up disk resources from removed or abandoned snapshots// Cleanup cleans up disk resources from removed or abandoned snapshots func (o *snapshotter) Cleanup(ctx context.Context) error { cleanup, err := o.cleanupDirectories(ctx) if err != nil { return err } for _, dir := range cleanup { if err := os.RemoveAll(dir); err != nil { log.G(ctx).WithError(err).WithField("path", dir).Warn("failed to remove directory") } } return nil } // Remove abandons the snapshot identified by key. The snapshot will // immediately become unavailable and unrecoverable. func (o *snapshotter) Remove(ctx context.Context, key string) (err error) { log.G(ctx).Infof("Remove (key: %s)", key) start := time.Now() defer func() { if err != nil { metrics.GRPCErrCount.WithLabelValues("Remove").Inc() } metrics.GRPCLatency.WithLabelValues("Remove").Observe(time.Since(start).Seconds()) }() ctx, t, err := o.ms.TransactionContext(ctx, true) if err != nil { return err } rollback := true defer func() { if rollback { if rerr := t.Rollback(); rerr != nil { log.G(ctx).WithError(rerr).Warn("failed to rollback transaction") } } else { t.Commit() } }() id, info, _, err := storage.GetInfo(ctx, key) if err != nil { return err } stype, err := o.identifySnapshotStorageType(ctx, id, info) if err != nil { return err } if stype != storageTypeNormal { _, err = os.Stat(o.overlaybdBackstoreMarkFile(id)) if err == nil { err = o.UnmountAndDetachBlockDevice(ctx, id) if err != nil { return mylog.TracedErrorf(ctx, "failed to destroy target device for snapshot %s: %w", key, err) } } } // for TypeNormal, verify its(parent) meets the condition of overlaybd format if o.autoRemoveDev { if s, err := storage.GetSnapshot(ctx, key); err == nil && s.Kind == snapshots.KindActive && len(s.ParentIDs) > 0 { o.locker.Lock(s.ID) defer o.locker.Unlock(s.ID) o.locker.Lock(s.ParentIDs[0]) defer o.locker.Unlock(s.ParentIDs[0]) log.G(ctx).Debugf("try to verify parent snapshots format.") if st, err := o.identifySnapshotStorageType(ctx, s.ParentIDs[0], info); err == nil && st != storageTypeNormal { err = o.UnmountAndDetachBlockDevice(ctx, s.ParentIDs[0]) if err != nil { return mylog.TracedErrorf(ctx, "failed to destroy target device for snapshot %s: %w", key, err) } } } } // Just in case, check if snapshot contains mountpoint mounted, err := o.isMounted(ctx, o.overlaybdMountpoint(id)) if err != nil { return mylog.TracedErrorf(ctx, "failed to check mountpoint: %w", err) } else if mounted { return mylog.TracedErrorf(ctx, "try to remove snapshot %s which still have mountpoint", id) } _, _, err = storage.Remove(ctx, key) if err != nil { return fmt.Errorf("failed to remove: %w", err) } if !o.asyncRemove { var removals []string removals, err = o.getCleanupDirectories(ctx, t) if err != nil { return fmt.Errorf("unable to get directories for removal: %w", err) } defer func() { if err == nil { for _, dir := range removals { if err := os.RemoveAll(dir); err != nil { log.G(ctx).WithError(err).WithField("path", dir).Warn("failed to remove directory") } } } }() } else { log.G(ctx).Info("asyncRemove enabled, remove snapshots in Cleanup() method.") } rollback = false return nil } // Walk the snapshots. func (o *snapshotter) Walk(ctx context.Context, fn snapshots.WalkFunc, fs ...string) (err error) { log.G(ctx).Infof("Walk (fs: %s)", fs) start := time.Now() defer func() { if err != nil { metrics.GRPCErrCount.WithLabelValues("Walk").Inc() } metrics.GRPCLatency.WithLabelValues("Walk").Observe(time.Since(start).Seconds()) }() ctx, t, err := o.ms.TransactionContext(ctx, false) if err != nil { return err } defer t.Rollback() return storage.WalkInfo(ctx, fn, fs...) } 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 err := os.Mkdir(filepath.Join(td, "block"), 0711); err != nil { return td, err } if err := os.Mkdir(filepath.Join(td, "block", "mountpoint"), 0711); err != nil { return td, err } f, err := os.Create(filepath.Join(td, "block", "init-debug.log")) f.Close() if 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) basedOnBlockDeviceMount(ctx context.Context, s storage.Snapshot, writeType string) (m []mount.Mount, err error) { defer func() { if err == nil { log.G(ctx).Infof("return mount point(R/W mode: %s): %v", writeType, m) } else { log.G(ctx).Errorf("basedOnBlockDeviceMount return error: %v", err) } }() rwflag := "rw" if s.Kind == snapshots.KindView && (writeType == rwflag || writeType == RwDev) { log.G(ctx).Infof("snapshot.Kind == View, reset overlaybd as READ-ONLY.") rwflag = "ro" } if writeType == RwDir { return []mount.Mount{ { Source: o.overlaybdMountpoint(s.ID), Type: "bind", Options: []string{ rwflag, "rbind", }, }, }, nil } if writeType == RwDev { devName, err := os.ReadFile(o.overlaybdBackstoreMarkFile(s.ID)) if err != nil { return nil, err } // TODO: support multi fs return []mount.Mount{ { Source: string(devName), Type: "ext4", Options: []string{ rwflag, "discard", }, }, }, nil } if len(s.ParentIDs) >= 1 { // for ro mode, always has parent if s.Kind == snapshots.KindActive { var options []string if o.indexOff { options = append(options, "index=off") } options = append(options, fmt.Sprintf("workdir=%s", o.workPath(s.ID)), fmt.Sprintf("upperdir=%s", o.upperPath(s.ID)), fmt.Sprintf("lowerdir=%s", o.overlaybdMountpoint(s.ParentIDs[0])), ) return []mount.Mount{ { Type: "overlay", Source: "overlay", Options: options, }, }, nil } return []mount.Mount{ { Source: o.overlaybdMountpoint(s.ParentIDs[0]), Type: "bind", Options: []string{ "ro", "rbind", }, }, }, nil } // ro and no parent, will not work return []mount.Mount{ { Source: o.overlaybdMountpoint(s.ID), Type: "bind", Options: []string{ "ro", "rbind", }, }, }, nil } func (o *snapshotter) normalOverlayMount(s storage.Snapshot) []mount.Mount { if len(s.ParentIDs) == 0 { roFlag := "rw" if s.Kind == snapshots.KindView { roFlag = "ro" } return []mount.Mount{ { Source: o.upperPath(s.ID), Type: "bind", Options: []string{ roFlag, "rbind", }, }, } } var options []string if o.indexOff { options = append(options, "index=off") } if s.Kind == snapshots.KindActive { options = append(options, fmt.Sprintf("workdir=%s", o.workPath(s.ID)), fmt.Sprintf("upperdir=%s", o.upperPath(s.ID)), ) // if o.metacopyOption != "" { // options = append(options, o.metacopyOption) // } } else if len(s.ParentIDs) == 1 { return []mount.Mount{ { Source: o.upperPath(s.ParentIDs[0]), Type: "bind", Options: []string{ "ro", "rbind", }, }, } } 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, ":"))) return []mount.Mount{ { Type: "overlay", Source: "overlay", Options: options, }, } } func (o *snapshotter) getDiskQuotaSize(info *snapshots.Info) string { if info.Labels != nil { if size, ok := info.Labels[label.RootfsQuotaLabel]; ok { return size } } return o.quotaSize } func (o *snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, key, parent string, opts []snapshots.Opt) (_ string, _ snapshots.Info, err error) { var td, path string defer func() { if err != nil { if td != "" { if err1 := os.RemoveAll(td); err1 != nil { log.G(ctx).WithError(err1).Warn("failed to cleanup temp snapshot directory") } } if path != "" { if err1 := os.RemoveAll(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 { return "", snapshots.Info{}, fmt.Errorf("failed to create prepare snapshot dir: %w", err) } s, err := storage.CreateSnapshot(ctx, kind, key, parent, opts...) if err != nil { return "", snapshots.Info{}, 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 "", snapshots.Info{}, 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 { return "", snapshots.Info{}, fmt.Errorf("failed to chown: %w", err) } } // _, tmpinfo, _, err := storage.GetInfo(ctx, key) id, info, _, err := storage.GetInfo(ctx, key) if o.isPrepareRootfs(info) { if diskQuotaSize := o.getDiskQuotaSize(&info); diskQuotaSize != "" { log.G(ctx).Infof("set usage quota %s for rootfs(sn: %s)", diskQuotaSize, s.ID) upperPath := filepath.Join(td, "fs") if err := o.setDiskQuota(ctx, upperPath, diskQuotaSize, diskquota.QuotaMinID); err != nil { return "", snapshots.Info{}, fmt.Errorf("failed to set diskquota on upperpath, snapshot id: %s: %w", s.ID, err) } // if there's no parent, we just return a bind mount, so no need to set quota on workerpath if len(s.ParentIDs) > 0 { workpath := filepath.Join(td, "work") if err := o.setDiskQuota(ctx, workpath, diskQuotaSize, diskquota.QuotaMinID); err != nil { return "", snapshots.Info{}, fmt.Errorf("failed to set diskquota on workpath, snapshot id: %s: %w", s.ID, err) } } } } path = filepath.Join(snapshotDir, s.ID) if err = os.Rename(td, path); err != nil { return "", snapshots.Info{}, fmt.Errorf("failed to rename: %w", err) } td = "" // id, info, _, err := storage.GetInfo(ctx, key) if err != nil { return "", snapshots.Info{}, fmt.Errorf("failed to get snapshot info: %w", err) } img, ok := info.Labels[label.CRIImageRef] if !ok { img, ok = info.Labels[label.TargetImageRef] } if ok { log.G(ctx).Infof("found imageRef: %s", img) if err := os.WriteFile(filepath.Join(path, "image_ref"), []byte(img), 0644); err != nil { log.G(ctx).Errorf("write imageRef '%s'. path: %s, err: %v", img, filepath.Join(path, "image_ref"), err) } } else { log.G(ctx).Warnf("imageRef meta not found.") } return id, info, nil } func (o *snapshotter) identifySnapshotStorageType(ctx context.Context, id string, info snapshots.Info) (storageType, error) { if _, ok := info.Labels[label.TargetSnapshotRef]; ok { _, hasBDBlobSize := info.Labels[label.OverlayBDBlobSize] _, hasBDBlobDigest := info.Labels[label.OverlayBDBlobDigest] _, hasRef := info.Labels[label.TargetImageRef] _, hasCriRef := info.Labels[label.CRIImageRef] if hasBDBlobSize && hasBDBlobDigest { if hasRef || hasCriRef { return storageTypeRemoteBlock, nil } } } // check overlaybd.commit filePath := o.magicFilePath(id) st, err := o.identifyLocalStorageType(filePath) if err == nil { return st, nil } log.G(ctx).Debugf("failed to identify by %s, error %v, try to identify by writable_data", filePath, err) // check sealed data file filePath = o.overlaybdSealedFilePath(id) st, err = o.identifyLocalStorageType(filePath) if err == nil { return st, nil } // check writable data file filePath = o.overlaybdWritableDataPath(id) st, err = o.identifyLocalStorageType(filePath) if err == nil { return st, nil } // check layer.tar if it should be converted to turboOCI filePath = o.overlaybdOCILayerPath(id) if _, err := os.Stat(filePath); err == nil { log.G(ctx).Infof("uncompressed layer found in sn: %s", id) return storageTypeLocalLayer, nil } if os.IsNotExist(err) { // check config.v1.json log.G(ctx).Debugf("failed to identify by writable_data(sn: %s), try to identify by config.v1.json", id) filePath = o.overlaybdConfPath(id) if _, err := os.Stat(filePath); err == nil { log.G(ctx).Debugf("%s/config.v1.json found, return storageTypeRemoteBlock", id) return storageTypeRemoteBlock, nil } return storageTypeNormal, nil } log.G(ctx).Debugf("storageType(sn: %s): %d", id, st) return st, err } // SetDiskQuota is used to set quota for directory. func (o *snapshotter) setDiskQuota(ctx context.Context, dir string, size string, quotaID uint32) error { log.G(ctx).Infof("setDiskQuota: dir %s, size %s", dir, size) if isRegular, err := diskquota.CheckRegularFile(dir); err != nil || !isRegular { log.G(ctx).Errorf("set quota skip not regular file: %s", dir) return err } id := o.quotaDriver.GetQuotaIDInFileAttr(dir) if id > 0 && id != quotaID { return fmt.Errorf("quota id is already set, quota id: %d", id) } log.G(ctx).Infof("try to set disk quota, dir(%s), size(%s), quotaID(%d)", dir, size, quotaID) if err := o.quotaDriver.SetDiskQuota(dir, size, quotaID); err != nil { return fmt.Errorf("failed to set dir(%s) disk quota: %w", dir, err) } return nil } func (o *snapshotter) snPath(id string) string { return filepath.Join(o.root, "snapshots", id) } 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") } func (o *snapshotter) convertTempdir(id string) string { return filepath.Join(o.root, "snapshots", id, "temp") } func (o *snapshotter) blockPath(id string) string { return filepath.Join(o.root, "snapshots", id, "block") } var erofsSupported = false var erofsSupportedOnce sync.Once // IsErofsSupported checks if EROFS fsmeta exists and is prioritized, check and modprobe erofs. func IsErofsSupported() bool { erofsSupportedOnce.Do(func() { fs, err := os.ReadFile("/proc/filesystems") if err != nil || !bytes.Contains(fs, []byte("\terofs\n")) { // Try to `modprobe erofs` again cmd := exec.Command("modprobe", "erofs") _, err = cmd.CombinedOutput() if err != nil { return } fs, err = os.ReadFile("/proc/filesystems") if err != nil || !bytes.Contains(fs, []byte("\terofs\n")) { return } } erofsSupported = true }) return erofsSupported } func (o *snapshotter) turboOCIFsMeta(id string) (string, string) { for _, fsType := range o.turboFsType { fsmeta := filepath.Join(o.root, "snapshots", id, "fs", fsType+".fs.meta") if _, err := os.Stat(fsmeta); err == nil { if fsType == "erofs" && !IsErofsSupported() { log.L.Warn("erofs is not supported on this system, fallback to other fs type") continue } return fsmeta, fsType } else if !errors.Is(err, os.ErrNotExist) { log.L.Errorf("error while checking fs meta file: %s", err) } } log.L.Warn("no fs meta file found, fallback to ext4") return filepath.Join(o.root, "snapshots", id, "fs", "ext4.fs.meta"), "ext4" } func (o *snapshotter) magicFilePath(id string) string { return filepath.Join(o.root, "snapshots", id, "fs", "overlaybd.commit") } func (o *snapshotter) overlaybdSealedFilePath(id string) string { return filepath.Join(o.root, "snapshots", id, "fs", "overlaybd.sealed") } func (o *snapshotter) overlaybdMountpoint(id string) string { return filepath.Join(o.root, "snapshots", id, "block", "mountpoint") } func (o *snapshotter) overlaybdConfPath(id string) string { return filepath.Join(o.root, "snapshots", id, "block", "config.v1.json") } func (o *snapshotter) overlaybdInitDebuglogPath(id string) string { return filepath.Join(o.root, "snapshots", id, "block", "init-debug.log") } func (o *snapshotter) overlaybdWritableIndexPath(id string) string { return filepath.Join(o.root, "snapshots", id, "block", "writable_index") } func (o *snapshotter) overlaybdWritableDataPath(id string) string { return filepath.Join(o.root, "snapshots", id, "block", "writable_data") } func (o *snapshotter) overlaybdOCILayerPath(id string) string { return filepath.Join(o.root, "snapshots", id, "layer.tar") } func (o *snapshotter) overlaybdOCILayerMeta(id string) string { return filepath.Join(o.root, "snapshots", id, "layer.metadata") } func (o *snapshotter) overlaybdBackstoreMarkFile(id string) string { return filepath.Join(o.root, "snapshots", id, "block", "backstore_mark") } func (o *snapshotter) turboOCIGzipIdx(id string) string { return filepath.Join(o.root, "snapshots", id, "fs", "gzip.meta") } // Close closes the snapshotter func (o *snapshotter) Close() error { return o.ms.Close() } func (o *snapshotter) identifyLocalStorageType(filePath string) (storageType, error) { f, err := os.Open(filePath) if err != nil { return storageTypeUnknown, err } const overlaybdHeaderSize = 32 data := make([]byte, overlaybdHeaderSize) _, err = f.Read(data) f.Close() if err != nil { return storageTypeUnknown, fmt.Errorf("failed to read %s: %w", filePath, err) } if isOverlaybdFileHeader(data) { return storageTypeLocalBlock, nil } return storageTypeNormal, nil } func isOverlaybdFileHeader(header []byte) bool { if len(header) < 24 { return false } magic0 := binary.LittleEndian.Uint64(header[0:8]) magic1 := binary.LittleEndian.Uint64(header[8:16]) magic2 := binary.LittleEndian.Uint64(header[16:24]) return (magic0 == 281910587246170 && magic1 == 7384066304294679924 && magic2 == 7017278244700045632) || // "ZFile\x00\x01\x00", "tuji.yyf", "@Alibaba" (magic0 == 564050879402828 && magic1 == 5478704352671792741 && magic2 == 9993152565363659426) // "LSMT\x00\x01\x02\x00", …, … } accelerated-container-image-1.4.2/pkg/snapshot/overlay_test.go000066400000000000000000000025501514714641000245060ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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" "testing" "github.com/containerd/containerd/v2/core/snapshots" "github.com/containerd/containerd/v2/core/snapshots/testsuite" "github.com/containerd/containerd/v2/pkg/testutil" ) func newSnapshotterWithOpts(opts ...Opt) testsuite.SnapshotterFunc { return func(ctx context.Context, root string) (snapshots.Snapshotter, func() error, error) { cfg := DefaultBootConfig() cfg.Root = root snapshotter, err := NewSnapshotter(cfg, opts...) if err != nil { return nil, nil, err } return snapshotter, func() error { return snapshotter.Close() }, nil } } func TestBasicSnapshotterOnOverlayFS(t *testing.T) { testutil.RequiresRoot(t) testsuite.SnapshotterSuite(t, "overlaybd-on-overlayFS", newSnapshotterWithOpts()) } accelerated-container-image-1.4.2/pkg/snapshot/storage.go000066400000000000000000000710231514714641000234330ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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 ( "bufio" "context" "encoding/binary" "encoding/json" "fmt" "io" "math" "os" "os/exec" "path" "path/filepath" "strconv" "strings" "syscall" "time" sn "github.com/containerd/accelerated-container-image/pkg/types" "github.com/containerd/accelerated-container-image/pkg/label" "github.com/containerd/accelerated-container-image/pkg/utils" "github.com/containerd/containerd/v2/core/images" "github.com/containerd/containerd/v2/core/mount" "github.com/containerd/containerd/v2/core/snapshots" "github.com/containerd/containerd/v2/core/snapshots/storage" "github.com/containerd/containerd/v2/pkg/reference" "github.com/containerd/continuity" "github.com/containerd/continuity/fs" "github.com/containerd/log" "github.com/moby/sys/mountinfo" specs "github.com/opencontainers/image-spec/specs-go/v1" "golang.org/x/sys/unix" ) const ( maxAttachAttempts = 50 // hba number used to create tcmu devices in configfs // all overlaybd devices are configured in /sys/kernel/config/target/core/user_999999999/ // devices ares identified by their snID /sys/kernel/config/target/core/user_999999999/dev_$snID obdHbaNumDefault = 999999999 // Naa prefix for loopback devices in configfs // for example snID 128, the loopback device config in /sys/kernel/config/target/loopback/naa.1990000000000128 obdLoopNaaPrefixDefault = 199 // param used to restrict tcmu data area size // it is worked by setting max_data_area_mb for devices in configfs. obdMaxDataAreaMB = 4 // Just in case someone really needs to force to ext4 ext4FSFallbackFile = ".TurboOCI_ext4" ) type mountMatcherFunc func(fields []string, separatorIndex int) bool type AttachDeviceParams struct { id string tenant int // default -1 configPath string // config.v1.json resultFile string // init-debug.log } func NewAttachDeviceParams(id string, tenant int, configPath string, resultFile string) *AttachDeviceParams { return &AttachDeviceParams{ id: id, tenant: tenant, configPath: configPath, resultFile: resultFile, } } func (o *snapshotter) checkOverlaybdInUse(ctx context.Context, dir string) (bool, error) { matcher := func(fields []string, separatorIndex int) bool { // ... but in Linux <= 3.9 mounting a cifs with spaces in a share name // (like "//serv/My Documents") _may_ end up having a space in the last field // of mountinfo (like "unc=//serv/My Documents"). Since kernel 3.10-rc1, cifs // option unc= is ignored, so a space should not appear. In here we ignore // those "extra" fields caused by extra spaces. fstype := fields[separatorIndex+1] vfsOpts := fields[separatorIndex+3] return fstype == "overlay" && strings.Contains(vfsOpts, dir) } return o.matchMounts(ctx, matcher) } func (o *snapshotter) isMounted(ctx context.Context, mountpoint string) (bool, error) { matcher := func(fields []string, separatorIndex int) bool { mp := fields[4] return path.Clean(mountpoint) == path.Clean(mp) } return o.matchMounts(ctx, matcher) } func (o *snapshotter) matchMounts(ctx context.Context, matcher mountMatcherFunc) (bool, error) { f, err := os.Open("/proc/self/mountinfo") if err != nil { return false, err } defer f.Close() b, err := o.parseAndCheckMounted(ctx, f, matcher) if err != nil { log.G(ctx).Errorf("Parsing mounts fields, error: %v", err) return false, err } return b, nil } func (o *snapshotter) parseAndCheckMounted(ctx context.Context, r io.Reader, matcher mountMatcherFunc) (bool, error) { // FIXME(thaJeztah): replace this with github.com/moby/sys/mountinfo.GetMountsFromReader or GetMounts s := bufio.NewScanner(r) for s.Scan() { if err := s.Err(); err != nil { return false, err } /* 489 29 0:51 / /run/containerd/io.containerd.runtime.v2.task/default/testwp1/rootfs rw,relatime shared:272 - overlay overlay rw,lowerdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlaybd/snapshots/2335/block/mountpoint,upperdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlaybd/snapshots/2344/fs,workdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlaybd/snapshots/2344/work (1)(2) (3) (4) (5) (6) (7) (8) (9) (10) (11) (1) mount ID: unique identifier of the mount (may be reused after umount) (2) parent ID: ID of parent (or of self for the top of the mount tree) (3) major:minor: value of st_dev for files on filesystem (4) root: root of the mount within the filesystem (5) mount point: mount point relative to the process's root (6) mount options: per mount options (7) optional fields: zero or more fields of the form "tag[:value]" (8) separator: marks the end of the optional fields (9) filesystem type: name of filesystem of the form "type[.subtype]" (10) mount source: filesystem specific information or "none" (11) super options: per super block options */ text := s.Text() fields := strings.Split(text, " ") numFields := len(fields) if numFields < 10 { // should be at least 10 fields log.G(ctx).Warnf("Parsing '%s' failed: not enough fields (%d)", text, numFields) continue } // one or more optional fields, when a separator (-) i := 6 for ; i < numFields && fields[i] != "-"; i++ { switch i { case 6: // p.Optional = fields[6] default: /* NOTE there might be more optional fields before the such as fields[7]...fields[N] (where N < sepIndex), although as of Linux kernel 4.15 the only known ones are mount propagation flags in fields[6]. The correct behavior is to ignore any unknown optional fields. */ } } if i == numFields { log.G(ctx).Warnf("Parsing '%s' failed: missing separator ('-')", text) continue } // There should be 3 fields after the separator... if i+4 > numFields { log.G(ctx).Warnf("Parsing '%s' failed: not enough fields after a separator", text) continue } if matcher(fields, i) { return true, nil } } return false, nil } func DetachDevice(ctx context.Context, snID string, tenant int) (err error) { loopDevID := overlaybdLoopbackDeviceID(tenant, snID) lunPath := overlaybdLoopbackDeviceLunPath(loopDevID) linkPath := path.Join(lunPath, "dev_"+snID) err = os.RemoveAll(linkPath) if err != nil { return fmt.Errorf("failed to remove loopback link %s: %w", linkPath, err) } err = os.RemoveAll(lunPath) if err != nil { return fmt.Errorf("failed to remove loopback lun %s: %w", lunPath, err) } loopDevPath := overlaybdLoopbackDevicePath(loopDevID) tpgtPath := path.Join(loopDevPath, "tpgt_1") err = os.RemoveAll(tpgtPath) if err != nil { return fmt.Errorf("failed to remove loopback tgpt %s: %w", tpgtPath, err) } err = os.RemoveAll(loopDevPath) if err != nil { return fmt.Errorf("failed to remove loopback dir %s: %w", loopDevPath, err) } targetPath := overlaybdTargetPath(obdHbaNum(tenant), snID) err = os.RemoveAll(targetPath) if err != nil { return fmt.Errorf("failed to remove target dir %s: %w", targetPath, err) } return nil } // UnmountAndDetachBlockDevice will do nothing if the device is already destroyed func (o *snapshotter) UnmountAndDetachBlockDevice(ctx context.Context, snID string) (err error) { devName, err := os.ReadFile(o.overlaybdBackstoreMarkFile(snID)) if err != nil { log.G(ctx).Errorf("read device name failed: %s, err: %v", o.overlaybdBackstoreMarkFile(snID), err) } mountPoint := o.overlaybdMountpoint(snID) log.G(ctx).Debugf("check overlaybd mountpoint is in use: %s", mountPoint) busy, err := o.checkOverlaybdInUse(ctx, mountPoint) if err != nil { return err } if busy { log.G(ctx).Infof("device still in use.") return nil } log.G(ctx).Infof("umount device, mountpoint: %s", mountPoint) if err := mount.UnmountAll(mountPoint, 0); err != nil { return fmt.Errorf("failed to umount %s: %w", mountPoint, err) } err = DetachDevice(ctx, snID, o.tenant) if err != nil { return err } log.G(ctx).Infof("destroy overlaybd device success(sn: %s): %s", snID, devName) return nil } // IsErofsFilesystem determines whether the block device represented // by @path is eorfs filesystem. func IsErofsFilesystem(path string) bool { f, err := os.Open(path) if err != nil { return false } defer f.Close() byte4 := make([]byte, 4) _, err = f.ReadAt(byte4, 1024) if err != nil { return false } return binary.LittleEndian.Uint32(byte4) == 0xe0f5e1e2 } func AttachDevice(ctx context.Context, params *AttachDeviceParams) (devName string, e error) { devName = "" snID := params.id tenant := params.tenant configPath := params.configPath targetPath := overlaybdTargetPath(obdHbaNum(tenant), snID) err := os.MkdirAll(targetPath, 0700) if err != nil { return devName, fmt.Errorf("failed to create target dir for %s: %w", targetPath, err) } defer func() { if e != nil { log.G(ctx).Error(e.Error()) rerr := os.RemoveAll(targetPath) if rerr != nil { log.G(ctx).WithError(rerr).Warnf("failed to clean target dir %s", targetPath) } } }() if err = os.WriteFile(path.Join(targetPath, "control"), ([]byte)(fmt.Sprintf("dev_config=overlaybd/%s", configPath)), 0666); err != nil { return devName, fmt.Errorf("failed to write target dev_config for %s: dev_config=overlaybd/%s: %w", targetPath, configPath, err) } err = os.WriteFile(path.Join(targetPath, "control"), ([]byte)(fmt.Sprintf("max_data_area_mb=%d", obdMaxDataAreaMB)), 0666) if err != nil { return devName, fmt.Errorf("failed to write target max_data_area_mb for %s: max_data_area_mb=%d: %w", targetPath, obdMaxDataAreaMB, err) } // enable target may fails with EAGAIN, so we need to retry for retry := 0; retry < 100; retry++ { err = os.WriteFile(path.Join(targetPath, "enable"), ([]byte)("1"), 0666) if err != nil { perror, ok := err.(*os.PathError) if ok { if perror.Err == syscall.EAGAIN { log.G(ctx).Infof("write %s returned EAGAIN, retry", targetPath) time.Sleep(50 * time.Millisecond) continue } } return devName, fmt.Errorf("failed to write enable for %s: %w", targetPath, err) } else { break } } if err != nil { // read the init-debug.log for readable debugLogPath := params.resultFile if data, derr := os.ReadFile(debugLogPath); derr == nil { return devName, fmt.Errorf("failed to enable target for %s, %s", targetPath, data) } return devName, fmt.Errorf("failed to enable target for %s: %w", targetPath, err) } // fixed by fuweid err = os.WriteFile( path.Join(targetPath, "attrib", "cmd_time_out"), ([]byte)(fmt.Sprintf("%v", math.MaxInt32/1000)), 0666) if err != nil { return devName, fmt.Errorf("failed to update cmd_time_out: %w", err) } loopDevID := overlaybdLoopbackDeviceID(tenant, snID) loopDevPath := overlaybdLoopbackDevicePath(loopDevID) err = os.MkdirAll(loopDevPath, 0700) if err != nil { return devName, fmt.Errorf("failed to create loopback dir %s: %w", loopDevPath, err) } tpgtPath := path.Join(loopDevPath, "tpgt_1") lunPath := overlaybdLoopbackDeviceLunPath(loopDevID) err = os.MkdirAll(lunPath, 0700) if err != nil { return devName, fmt.Errorf("failed to create loopback lun dir %s: %w", lunPath, err) } defer func() { if e != nil { rerr := os.RemoveAll(lunPath) if rerr != nil { log.G(ctx).WithError(rerr).Warnf("failed to clean loopback lun %s", lunPath) } rerr = os.RemoveAll(tpgtPath) if rerr != nil { log.G(ctx).WithError(rerr).Warnf("failed to clean loopback tpgt %s", tpgtPath) } rerr = os.RemoveAll(loopDevPath) if rerr != nil { log.G(ctx).WithError(rerr).Warnf("failed to clean loopback dir %s", loopDevPath) } } }() nexusPath := path.Join(tpgtPath, "nexus") err = os.WriteFile(nexusPath, ([]byte)(loopDevID), 0666) if err != nil { return devName, fmt.Errorf("failed to write loopback nexus %s: %w", nexusPath, err) } linkPath := path.Join(lunPath, "dev_"+snID) err = os.Symlink(targetPath, linkPath) if err != nil { return devName, fmt.Errorf("failed to create loopback link %s: %w", linkPath, err) } defer func() { if e != nil { rerr := os.RemoveAll(linkPath) if err != nil { log.G(ctx).WithError(rerr).Warnf("failed to clean loopback link %s", linkPath) } } }() devAddressPath := path.Join(tpgtPath, "address") bytes, err := os.ReadFile(devAddressPath) if err != nil { return devName, fmt.Errorf("failed to read loopback address for %s: %w", devAddressPath, err) } deviceNumber := strings.TrimSuffix(string(bytes), "\n") // The device doesn't show up instantly. Need retry here. for retry := 0; retry < maxAttachAttempts; retry++ { devDirs, err := os.ReadDir(scsiBlockDevicePath(deviceNumber)) if err != nil { e = err time.Sleep(10 * time.Millisecond) continue } if len(devDirs) == 0 { e = fmt.Errorf("empty device found") time.Sleep(10 * time.Millisecond) continue } for _, dev := range devDirs { device := fmt.Sprintf("/dev/%s", dev.Name()) // /dev/sdX if err := os.WriteFile(path.Join("/sys/block", dev.Name(), "device", "timeout"), ([]byte)(fmt.Sprintf("%v", math.MaxInt32/1000)), 0666); err != nil { e = fmt.Errorf("failed to set timeout for %s: %w", device, err) time.Sleep(10 * time.Millisecond) break // retry } devName = device break } } log.G(ctx).Infof("Device has been created. {id: %s: dev: %s}", snID, devName) return devName, nil } // attachAndMountBlockDevice // // TODO(fuweid): need to track the middle state if the process has been killed. func (o *snapshotter) attachAndMountBlockDevice(ctx context.Context, snID string, writable string, fsType string, mkfs bool) (retErr error) { configPath := o.overlaybdConfPath(snID) log.G(ctx).Infof("attach block device. {id: %s, config: %s}", snID, configPath) log.G(ctx).Debugf("lookup device mountpoint(%s) if exists before attach.", snID) if err := lookup(o.overlaybdMountpoint(snID)); err == nil { return nil } else { log.G(ctx).Info(err.Error()) } device, err := AttachDevice(ctx, NewAttachDeviceParams( snID, o.tenant, configPath, o.overlaybdInitDebuglogPath(snID), ), ) if err != nil { return err } devSavedPath := o.overlaybdBackstoreMarkFile(snID) if err := os.WriteFile(devSavedPath, []byte(device), 0644); err != nil { // o.DetachDevice(ctx, snID) return fmt.Errorf("failed to create backstore mark file of snapshot %s: %w", snID, err) } log.G(ctx).Debugf("write device name: %s into file: %s", device, devSavedPath) options := strings.Split(fsType, ";") fstype := options[0] if IsErofsFilesystem(device) { fstype = "erofs" } if mkfs { args := []string{"-t", fstype} if len(options) > 2 { if options[2] != "" { mkfsOpts := strings.Split(options[2], " ") args = append(args, mkfsOpts...) } } else { switch fstype { case "ext4": args = append(args, "-O", "^has_journal,sparse_super,flex_bg", "-G", "1", "-E", "discard", "-F") case "xfs": args = append(args, "-f", "-l", "size=4m", "-m", "crc=0") case "f2fs": args = append(args, "-S", "-w", "4096") case "ntfs": args = append(args, "-F", "-f") default: } } args = append(args, device) log.G(ctx).Infof("fs type: %s, mkfs options: %v", fstype, args) out, err := exec.CommandContext(ctx, "mkfs", args...).CombinedOutput() if err != nil { return fmt.Errorf("failed to mkfs for dev %s: %s: %w", device, out, err) } } retErr = nil // mount device if writable != RwDev { var mountPoint = o.overlaybdMountpoint(snID) mountOpts := "" if len(options) > 1 { mountOpts = options[1] } else { switch fstype { case "ext4": mountOpts = "discard" case "xfs": mountOpts = "nouuid,discard" default: } } if fstype != "ntfs" { var mountFlag uintptr = unix.MS_RDONLY if writable != RoDir { mountFlag = 0 } log.G(ctx).Infof("fs type: %s, mount options: %s, rw: %s, mountpoint: %s", fstype, mountOpts, writable, mountPoint) for retry := 0; retry < maxAttachAttempts; retry++ { if err := unix.Mount(device, mountPoint, fstype, mountFlag, mountOpts); err != nil { retErr = fmt.Errorf("failed to mount %s to %s: %w", device, mountPoint, err) time.Sleep(10 * time.Millisecond) continue // retry } retErr = nil break } } else { args := []string{"-t", fstype} if writable == RoDir { args = append(args, "-r") } if mountOpts != "" { args = append(args, "-o", mountOpts) } args = append(args, device, mountPoint) log.G(ctx).Infof("fs type: %s, mount options: %v", fstype, args) for retry := 0; retry < maxAttachAttempts; retry++ { out, err := exec.CommandContext(ctx, "mount", args...).CombinedOutput() if err != nil { retErr = fmt.Errorf("failed to mount for dev %s: %s: %w", device, out, err) time.Sleep(10 * time.Millisecond) continue // retry } retErr = nil break } } } return retErr } // ConstructOverlayBDSpec generates the config spec for overlaybd target. func (o *snapshotter) ConstructOverlayBDSpec(ctx context.Context, key string, writable bool) error { id, info, _, err := storage.GetInfo(ctx, key) if err != nil { return fmt.Errorf("failed to get info for snapshot %s: %w", key, err) } stype, err := o.identifySnapshotStorageType(ctx, id, info) if err != nil { return fmt.Errorf("failed to identify storage of snapshot %s: %w", key, err) } configJSON := sn.OverlayBDBSConfig{ Lowers: []sn.OverlayBDBSConfigLower{}, ResultFile: o.overlaybdInitDebuglogPath(id), } // load the parent's config and reuse the lowerdir if info.Parent != "" { parentID, _, _, err := storage.GetInfo(ctx, info.Parent) if err != nil { return fmt.Errorf("failed to get info for parent snapshot %s: %w", info.Parent, err) } parentConfJSON, err := o.loadBackingStoreConfig(parentID) if err != nil { return err } configJSON.RepoBlobURL = parentConfJSON.RepoBlobURL configJSON.Lowers = parentConfJSON.Lowers } switch stype { case storageTypeRemoteBlock: if writable { return fmt.Errorf("remote block device is readonly, not support writable") } blobSize, err := strconv.Atoi(info.Labels[label.OverlayBDBlobSize]) if err != nil { return fmt.Errorf("failed to parse value of label %s of snapshot %s: %w", label.OverlayBDBlobSize, key, err) } blobDigest := info.Labels[label.OverlayBDBlobDigest] ref, hasRef := info.Labels[label.TargetImageRef] if !hasRef { criRef, hasCriRef := info.Labels[label.CRIImageRef] if !hasCriRef { return fmt.Errorf("no image-ref label") } ref = criRef } blobPrefixURL, err := o.constructImageBlobURL(ref) if err != nil { return fmt.Errorf("failed to construct image blob prefix url for snapshot %s: %w", key, err) } configJSON.RepoBlobURL = blobPrefixURL if isTurboOCI, dataDgst, compType := o.checkTurboOCI(info.Labels); isTurboOCI { var fsmeta string // If parent layers exist, follow the meta choice from the bottom layer if info.Parent != "" { _, fsmeta = filepath.Split(configJSON.Lowers[0].File) fsmeta = filepath.Join(o.root, "snapshots", id, "fs", fsmeta) } else { fsmeta, _ = o.turboOCIFsMeta(id) } lower := sn.OverlayBDBSConfigLower{ Dir: o.upperPath(id), // keep this to support ondemand turboOCI loading. File: fsmeta, TargetDigest: dataDgst, } if isGzipLayerType(compType) { lower.GzipIndex = o.turboOCIGzipIdx(id) } configJSON.Lowers = append(configJSON.Lowers, lower) } else { configJSON.Lowers = append(configJSON.Lowers, sn.OverlayBDBSConfigLower{ Digest: blobDigest, Size: int64(blobSize), Dir: o.upperPath(id), }) } case storageTypeLocalBlock: if writable { return fmt.Errorf("local block device is readonly, not support writable") } ociBlob := o.overlaybdOCILayerPath(id) if _, err := os.Stat(ociBlob); err == nil { log.G(ctx).Debugf("OCI layer blob found, construct overlaybd config with turboOCI (sn: %s)", id) configJSON.Lowers = append(configJSON.Lowers, sn.OverlayBDBSConfigLower{ TargetFile: o.overlaybdOCILayerPath(id), File: o.magicFilePath(id), }) } else { configJSON.Lowers = append(configJSON.Lowers, sn.OverlayBDBSConfigLower{ Dir: o.upperPath(id), // automatically find overlaybd.commit }) } case storageTypeLocalLayer: // 1. generate tar meta for oci layer blob // 2. convert local layer.tarmeta to overlaybd // 3. create layer's config var opt *utils.ConvertOption var rootfs_type string if info.Labels[label.OverlayBDBlobFsType] != "" { rootfs_type = info.Labels[label.OverlayBDBlobFsType] } else { rootfs_type = o.defaultFsType } if rootfs_type == "erofs" { opt = &utils.ConvertOption{ TarMetaPath: o.overlaybdOCILayerPath(id), Workdir: o.convertTempdir(id), Ext4FSMetaPath: o.magicFilePath(id), // overlaybd.commit Config: configJSON, } } else { log.G(ctx).Infof("generate metadata of layer blob (sn: %s)", id) if err := utils.GenerateTarMeta(ctx, o.overlaybdOCILayerPath(id), o.overlaybdOCILayerMeta(id)); err != nil { log.G(ctx).Errorf("generate tar metadata failed. (sn: %s)", id) return err } opt = &utils.ConvertOption{ TarMetaPath: o.overlaybdOCILayerMeta(id), Workdir: o.convertTempdir(id), Ext4FSMetaPath: o.magicFilePath(id), // overlaybd.commit Config: configJSON, } } log.G(ctx).Infof("convert layer to turboOCI (sn: %s)", id) if err := utils.ConvertLayer(ctx, opt, rootfs_type); err != nil { log.G(ctx).Error(err.Error()) os.RemoveAll(opt.Workdir) os.Remove(opt.Ext4FSMetaPath) return err } configJSON.Lowers = append(configJSON.Lowers, sn.OverlayBDBSConfigLower{ TargetFile: o.overlaybdOCILayerPath(id), File: opt.Ext4FSMetaPath, }) log.G(ctx).Debugf("generate config.json for %s:\n %+v", id, configJSON) default: if !writable { return fmt.Errorf("unexpect storage %v of snapshot %v during construct overlaybd spec(writable=%v, parent=%s)", stype, key, writable, info.Parent) } vsizeGB := 0 if info.Parent == "" { if vsize, ok := info.Labels[label.OverlayBDVsize]; ok { vsizeGB, err = strconv.Atoi(vsize) if err != nil { vsizeGB = 64 } } else { vsizeGB = 64 } } log.G(ctx).Infof("prepare writable layer. (sn: %s, vsize: %d GB)", id, vsizeGB) if err := o.prepareWritableOverlaybd(ctx, id, vsizeGB); err != nil { return err } configJSON.Upper = sn.OverlayBDBSConfigUpper{ Index: o.overlaybdWritableIndexPath(id), Data: o.overlaybdWritableDataPath(id), Vsize: vsizeGB, } } if isTurboOCI, _, _ := o.checkTurboOCI(info.Labels); isTurboOCI { // If the fallback file exists, enforce TurboOCI fstype to EXT4 ext4FSFallbackPath := filepath.Join(o.root, ext4FSFallbackFile) _, err = os.Stat(ext4FSFallbackPath) if err == nil && configJSON.Lowers[0].File != "" { var newLowers []sn.OverlayBDBSConfigLower log.G(ctx).Infof("fallback to EXT4 since %s exists", ext4FSFallbackPath) for _, l := range configJSON.Lowers { s, _ := filepath.Split(l.File) l.File = filepath.Join(s, "ext4.fs.meta") newLowers = append(newLowers, l) } configJSON.Lowers = newLowers } } configBuffer, _ := json.MarshalIndent(configJSON, "", " ") log.G(ctx).Infoln(string(configBuffer)) return o.atomicWriteOverlaybdTargetConfig(id, &configJSON) } func (o *snapshotter) constructSpecForAccelLayer(id, parentID string) error { config, err := o.loadBackingStoreConfig(parentID) if err != nil { return err } accelLayerLower := sn.OverlayBDBSConfigLower{Dir: o.upperPath(id)} config.Lowers = append(config.Lowers, accelLayerLower) return o.atomicWriteOverlaybdTargetConfig(id, config) } func (o *snapshotter) updateSpec(snID string, isAccelLayer bool, recordTracePath string) error { bsConfig, err := o.loadBackingStoreConfig(snID) if err != nil { return err } if isAccelLayer == bsConfig.AccelerationLayer && (recordTracePath == bsConfig.RecordTracePath && recordTracePath == "") { // No need to update return nil } bsConfig.RecordTracePath = recordTracePath bsConfig.AccelerationLayer = isAccelLayer if err = o.atomicWriteOverlaybdTargetConfig(snID, bsConfig); err != nil { return err } return nil } // loadBackingStoreConfig loads overlaybd target config. func (o *snapshotter) loadBackingStoreConfig(snID string) (*sn.OverlayBDBSConfig, error) { confPath := o.overlaybdConfPath(snID) data, err := os.ReadFile(confPath) if err != nil { return nil, fmt.Errorf("failed to read config(path=%s) of snapshot %s: %w", confPath, snID, err) } var configJSON sn.OverlayBDBSConfig if err := json.Unmarshal(data, &configJSON); err != nil { return nil, fmt.Errorf("failed to unmarshal data(%s): %w", string(data), err) } return &configJSON, nil } // constructImageBlobURL returns the https://host/v2//blobs/. // // TODO(fuweid): How to know the existing url schema? func (o *snapshotter) constructImageBlobURL(ref string) (string, error) { refspec, err := reference.Parse(ref) if err != nil { return "", fmt.Errorf("invalid repo url %s: %w", ref, err) } host := refspec.Hostname() repo := strings.TrimPrefix(refspec.Locator, host+"/") if host == "docker.io" { host = "registry-1.docker.io" } scheme := "https://" for _, reg := range o.mirrorRegistry { if host == reg.Host && reg.Insecure { scheme = "http://" } } return scheme + path.Join(host, "v2", repo) + "/blobs", nil } // atomicWriteOverlaybdTargetConfig func (o *snapshotter) atomicWriteOverlaybdTargetConfig(snID string, configJSON *sn.OverlayBDBSConfig) error { data, err := json.Marshal(configJSON) if err != nil { return fmt.Errorf("failed to marshal %+v configJSON into JSON: %w", configJSON, err) } confPath := o.overlaybdConfPath(snID) if err := continuity.AtomicWriteFile(confPath, data, 0600); err != nil { return fmt.Errorf("failed to commit the overlaybd config on %s: %w", confPath, err) } return nil } // prepareWritableOverlaybd func (o *snapshotter) prepareWritableOverlaybd(ctx context.Context, snID string, vsizeGB int) error { args := []string{fmt.Sprintf("%d", vsizeGB)} if o.writableLayerType == "sparse" { args = append(args, "-s") } return utils.Create(ctx, o.blockPath(snID), args...) } func (o *snapshotter) sealWritableOverlaybd(ctx context.Context, snID string) (retErr error) { return utils.Seal(ctx, o.blockPath(snID), o.upperPath(snID)) } func obdHbaNum(tenant int) int { if tenant == -1 { return obdHbaNumDefault } return tenant } func obdLoopNaaPrefix(tenant int) int { if tenant == -1 { return obdLoopNaaPrefixDefault } return tenant%100 + 100 // keep first num is '1' } func overlaybdTargetPath(tenant int, id string) string { return fmt.Sprintf("/sys/kernel/config/target/core/user_%d/dev_%s", tenant, id) } func overlaybdLoopbackDeviceID(tenant int, id string) string { paddings := strings.Repeat("0", 13-len(id)) return fmt.Sprintf("naa.%03d%s%s", obdLoopNaaPrefix(tenant), paddings, id) } func overlaybdLoopbackDevicePath(id string) string { return fmt.Sprintf("/sys/kernel/config/target/loopback/%s", id) } func overlaybdLoopbackDeviceLunPath(id string) string { return fmt.Sprintf("%s/tpgt_1/lun/lun_0", overlaybdLoopbackDevicePath(id)) } func scsiBlockDevicePath(deviceNumber string) string { return fmt.Sprintf("/sys/class/scsi_device/%s:0/device/block", deviceNumber) } // TODO: use device number to check? func lookup(dir string) error { dir = filepath.Clean(dir) m, err := mountinfo.GetMounts(mountinfo.SingleEntryFilter(dir)) if err != nil { return fmt.Errorf("failed to get mount info for %q: %w", dir, err) } if len(m) == 0 { return fmt.Errorf("failed to find the mount point for %q", dir) } return nil } func isGzipLayerType(mediaType string) bool { return mediaType == specs.MediaTypeImageLayerGzip || mediaType == images.MediaTypeDockerSchema2LayerGzip } func (o *snapshotter) diskUsageWithBlock(ctx context.Context, id string, stype storageType) (snapshots.Usage, error) { usage := snapshots.Usage{} du, err := fs.DiskUsage(ctx, o.upperPath(id)) if err != nil { return snapshots.Usage{}, err } usage = snapshots.Usage(du) if stype == storageTypeRemoteBlock || stype == storageTypeLocalBlock { du, err := utils.DiskUsageWithoutMountpoint(ctx, o.blockPath(id)) if err != nil { return snapshots.Usage{}, err } usage.Add(snapshots.Usage(du)) } return usage, nil } accelerated-container-image-1.4.2/pkg/types/000077500000000000000000000000001514714641000207425ustar00rootroot00000000000000accelerated-container-image-1.4.2/pkg/types/types.go000066400000000000000000000033371514714641000224430ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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 types // OverlayBDBSConfig is the config of overlaybd target. type OverlayBDBSConfig struct { RepoBlobURL string `json:"repoBlobUrl"` Lowers []OverlayBDBSConfigLower `json:"lowers"` Upper OverlayBDBSConfigUpper `json:"upper"` ResultFile string `json:"resultFile"` AccelerationLayer bool `json:"accelerationLayer,omitempty"` RecordTracePath string `json:"recordTracePath,omitempty"` } type OverlayBDBSConfigLower struct { GzipIndex string `json:"gzipIndex,omitempty"` File string `json:"file,omitempty"` Digest string `json:"digest,omitempty"` TargetFile string `json:"targetFile,omitempty"` TargetDigest string `json:"targetDigest,omitempty"` Size int64 `json:"size,omitempty"` Dir string `json:"dir,omitempty"` } type OverlayBDBSConfigUpper struct { Index string `json:"index,omitempty"` Data string `json:"data,omitempty"` Target string `json:"target,omitempty"` GzipIndex string `json:"gzipIndex,omitempty"` Vsize int `json:"vsize,omitempty"` } accelerated-container-image-1.4.2/pkg/utils/000077500000000000000000000000001514714641000207365ustar00rootroot00000000000000accelerated-container-image-1.4.2/pkg/utils/cmd.go000066400000000000000000000170401514714641000220320ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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 utils import ( "context" "encoding/json" "fmt" "os" "os/exec" "path" "path/filepath" "strings" sn "github.com/containerd/accelerated-container-image/pkg/types" "github.com/containerd/log" ) const ( obdBinCreate = "/opt/overlaybd/bin/overlaybd-create" obdBinCommit = "/opt/overlaybd/bin/overlaybd-commit" obdBinApply = "/opt/overlaybd/bin/overlaybd-apply" obdBinTurboOCIApply = "/opt/overlaybd/bin/turboOCI-apply" dataFile = "writable_data" idxFile = "writable_index" sealedFile = "overlaybd.sealed" commitTempFile = "overlaybd.commit.temp" commitFile = "overlaybd.commit" ) type ConvertOption struct { // src options // (TODO) LayerPath string // path of layer.tgz or layer.tar TarMetaPath string // path of layer.tar.meta Config sn.OverlayBDBSConfig Workdir string // output options Ext4FSMetaPath string // (TODO) GzipIndexPath string } var defaultServiceTemplate = ` { "registryFsVersion": "v2", "logPath": "", "logLevel": 1, "cacheConfig": { "cacheType": "file", "cacheDir": "%s", "cacheSizeGB": 4 }, "gzipCacheConfig": { "enable": false }, "credentialConfig": { "mode": "file", "path": "" }, "ioEngine": 0, "download": { "enable": false }, "enableAudit": false } ` func Create(ctx context.Context, dir string, opts ...string) error { dataPath := path.Join(dir, dataFile) indexPath := path.Join(dir, idxFile) os.RemoveAll(dataPath) os.RemoveAll(indexPath) args := append([]string{dataPath, indexPath}, opts...) log.G(ctx).Debugf("%s %s", obdBinCreate, strings.Join(args, " ")) out, err := exec.CommandContext(ctx, obdBinCreate, args...).CombinedOutput() if err != nil { return fmt.Errorf("failed to overlaybd-create: %s: %w", out, err) } return nil } func Seal(ctx context.Context, dir, toDir string, opts ...string) error { args := append([]string{ "--seal", path.Join(dir, dataFile), path.Join(dir, idxFile), }, opts...) log.G(ctx).Debugf("%s %s", obdBinCommit, strings.Join(args, " ")) out, err := exec.CommandContext(ctx, obdBinCommit, args...).CombinedOutput() if err != nil { return fmt.Errorf("failed to seal writable overlaybd: %s: %w", out, err) } if err := os.Rename(path.Join(dir, dataFile), path.Join(toDir, sealedFile)); err != nil { return fmt.Errorf("failed to rename sealed overlaybd file: %w", err) } os.RemoveAll(path.Join(dir, idxFile)) return nil } func Commit(ctx context.Context, dir, toDir string, sealed bool, opts ...string) error { var args []string if sealed { args = append([]string{ "--commit_sealed", path.Join(dir, sealedFile), path.Join(toDir, commitTempFile), }, opts...) } else { args = append([]string{ path.Join(dir, dataFile), path.Join(dir, idxFile), path.Join(toDir, commitFile), }, opts...) } log.G(ctx).Debugf("%s %s", obdBinCommit, strings.Join(args, " ")) out, err := exec.CommandContext(ctx, obdBinCommit, args...).CombinedOutput() if err != nil { return fmt.Errorf("failed to overlaybd-commit: %s: %w", out, err) } if sealed { return os.Rename(path.Join(toDir, commitTempFile), path.Join(toDir, commitFile)) } return nil } func ApplyOverlaybd(ctx context.Context, dir string, opts ...string) error { args := append([]string{ path.Join(dir, "layer.tar"), path.Join(dir, "config.json")}, opts...) log.G(ctx).Debugf("%s %s", obdBinApply, strings.Join(args, " ")) out, err := exec.CommandContext(ctx, obdBinApply, args...).CombinedOutput() if err != nil { return fmt.Errorf("failed to overlaybd-apply[native]: %s: %w", out, err) } return nil } func ApplyTurboOCI(ctx context.Context, dir, gzipMetaFile string, opts ...string) error { args := append([]string{ path.Join(dir, "layer.tar"), path.Join(dir, "config.json"), "--gz_index_path", path.Join(dir, gzipMetaFile)}, opts...) log.G(ctx).Debugf("%s %s", obdBinApply, strings.Join(args, " ")) out, err := exec.CommandContext(ctx, obdBinTurboOCIApply, args...).CombinedOutput() if err != nil { return fmt.Errorf("failed to overlaybd-apply[turboOCI]: %s: %w", out, err) } return nil } func GenerateTarMeta(ctx context.Context, srcTarFile string, dstTarMeta string) error { if _, err := os.Stat(srcTarFile); os.IsNotExist(err) { return nil } else if err != nil { return fmt.Errorf("error stating tar file: %w", err) } log.G(ctx).Infof("generate layer meta for %s", srcTarFile) if err := exec.Command(obdBinTurboOCIApply, srcTarFile, dstTarMeta, "--export").Run(); err != nil { return fmt.Errorf("failed to convert tar file to overlaybd device: %w", err) } return nil } // ConvertLayer produce a turbooci layer, target is path of ext4.fs.meta func ConvertLayer(ctx context.Context, opt *ConvertOption, fs_type string) error { if opt.Workdir == "" { opt.Workdir = "tmp_conv" } if err := os.MkdirAll(opt.Workdir, 0755); err != nil { return fmt.Errorf("failed to create work dir: %w", err) } pathWritableData := filepath.Join(opt.Workdir, "writable_data") pathWritableIndex := filepath.Join(opt.Workdir, "writable_index") pathFakeTarget := filepath.Join(opt.Workdir, "fake_target") pathService := filepath.Join(opt.Workdir, "service.json") pathConfig := filepath.Join(opt.Workdir, "config.v1.json") // overlaybd-create args := []string{pathWritableData, pathWritableIndex, "256", "-s", "--turboOCI"} if fs_type != "erofs" && len(opt.Config.Lowers) == 0 { args = append(args, "--mkfs") } if out, err := exec.CommandContext(ctx, obdBinCreate, args...).CombinedOutput(); err != nil { return fmt.Errorf("failed to overlaybd-create: %w, output: %s", err, out) } file, err := os.Create(pathFakeTarget) if err != nil { return fmt.Errorf("failed to create fake target: %w", err) } file.Close() opt.Config.Upper = sn.OverlayBDBSConfigUpper{ Data: pathWritableData, Index: pathWritableIndex, Target: pathFakeTarget, } // turboOCI-apply if err := os.WriteFile(pathService, []byte(fmt.Sprintf(defaultServiceTemplate, filepath.Join(opt.Workdir, "cache"))), 0644, ); err != nil { return fmt.Errorf("failed to write service.json: %w", err) } configBytes, err := json.Marshal(opt.Config) if err != nil { return fmt.Errorf("failed to marshal overlaybd config: %w", err) } if err := os.WriteFile(pathConfig, configBytes, 0644); err != nil { return fmt.Errorf("failed to write overlaybd config: %w", err) } args = []string{ opt.TarMetaPath, pathConfig, "--service_config_path", pathService, "--fstype", fs_type, } if fs_type != "erofs" { args = append(args, "--import") } log.G(ctx).Debugf("%s %s", obdBinTurboOCIApply, strings.Join(args, " ")) if out, err := exec.CommandContext(ctx, obdBinTurboOCIApply, args..., ).CombinedOutput(); err != nil { return fmt.Errorf("failed to turboOCI-apply: %w, output: %s", err, out) } // overlaybd-commit if out, err := exec.CommandContext(ctx, obdBinCommit, pathWritableData, pathWritableIndex, opt.Ext4FSMetaPath, "-z", "--turboOCI", ).CombinedOutput(); err != nil { return fmt.Errorf("failed to overlaybd-commit: %w, output: %s", err, out) } return nil } accelerated-container-image-1.4.2/pkg/utils/du_unix.go000066400000000000000000000037661514714641000227540ustar00rootroot00000000000000//go:build !windows /* Copyright The Accelerated Container Image 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. */ // Based on https://github.com/containerd/continuity/blob/main/fs/du_unix.go // Used to calculate the usage of the block dir, excluding the block/mountpoint dir. package utils import ( "context" "os" "path/filepath" "syscall" "github.com/containerd/continuity/fs" ) const blocksUnitSize = 512 type inode struct { dev, ino uint64 } func newInode(stat *syscall.Stat_t) inode { return inode{ dev: uint64(stat.Dev), //nolint: unconvert // dev is uint32 on darwin/bsd, uint64 on linux/solaris/freebsd ino: uint64(stat.Ino), //nolint: unconvert // ino is uint32 on bsd, uint64 on darwin/linux/solaris/freebsd } } func DiskUsageWithoutMountpoint(ctx context.Context, roots ...string) (fs.Usage, error) { var ( size int64 inodes = map[inode]struct{}{} // expensive! ) for _, root := range roots { if err := filepath.Walk(root, func(path string, fi os.FileInfo, err error) error { if fi.Name() == "mountpoint" { return filepath.SkipDir } if err != nil { return err } select { case <-ctx.Done(): return ctx.Err() default: } stat := fi.Sys().(*syscall.Stat_t) inoKey := newInode(stat) if _, ok := inodes[inoKey]; !ok { inodes[inoKey] = struct{}{} size += stat.Blocks * blocksUnitSize } return nil }); err != nil { return fs.Usage{}, err } } return fs.Usage{ Inodes: int64(len(inodes)), Size: size, }, nil } accelerated-container-image-1.4.2/pkg/version/000077500000000000000000000000001514714641000212635ustar00rootroot00000000000000accelerated-container-image-1.4.2/pkg/version/version.go000066400000000000000000000014401514714641000232760ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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 const ( OverlayBDVersionNumber = "0.1.0" TurboOCIVersionNumber = "0.1.0-turbo.ociv1" DeprecatedOCIVersionNumber = "0.1.0-fastoci" // old version of turboOCI ) accelerated-container-image-1.4.2/script/000077500000000000000000000000001514714641000203215ustar00rootroot00000000000000accelerated-container-image-1.4.2/script/config.json000066400000000000000000000004171514714641000224630ustar00rootroot00000000000000{ "root": "/var/lib/containerd/io.containerd.snapshotter.v1.overlaybd", "address": "/run/overlaybd-snapshotter/overlaybd.sock", "verbose": "info", "rwMode": "overlayfs", "logReportCaller": false, "autoRemoveDev": false, "mirrorRegistry": [] } accelerated-container-image-1.4.2/script/overlaybd-snapshotter.service000066400000000000000000000007051514714641000262440ustar00rootroot00000000000000[Unit] Description=overlaybd-snapshotter service After=network.target local-fs.target Before=shutdown.target DefaultDependencies=no Conflicts=shutdown.target [Service] LimitNOFILE=1048576 LimitCORE=infinity Type=simple ExecStartPre=/sbin/modprobe target_core_user overlay ExecStart=/opt/overlaybd/snapshotter/overlaybd-snapshotter GuessMainPID=no Restart=always RestartSec=1s KillMode=process OOMScoreAdjust=-999 [Install] WantedBy=multi-user.target accelerated-container-image-1.4.2/script/performance/000077500000000000000000000000001514714641000226225ustar00rootroot00000000000000accelerated-container-image-1.4.2/script/performance/clean-env.sh000077500000000000000000000014031514714641000250270ustar00rootroot00000000000000#!/bin/bash set -e echo "Stop running containers ..." for i in `sudo ctr task ls | grep -E '\sRUNNING' | awk '{print $1}'`; do sudo ctr task kill $i while true; do sleep 1 if sudo ctr task ls | grep -E '\sSTOPPED' | grep $i > /dev/null; then break fi done done echo "Remove containers ..." for i in `sudo ctr task ls -q`; do sudo ctr task delete $i done for i in `sudo ctr container ls -q`; do sudo ctr container delete $i done echo "Remove images ..." for i in `sudo ctr image ls -q`; do sudo ctr image rm --sync $i done sleep 3 echo "Clean registry cache and page cache ..." sudo rm -rf /opt/overlaybd/registry_cache/* sudo bash -c 'echo 1 > /proc/sys/vm/drop_caches' echo "Restarting overlaybd backstore ..." sudo systemctl restart overlaybd-tcmu accelerated-container-image-1.4.2/script/performance/setup-wordpress.sh000077500000000000000000000007431514714641000263530ustar00rootroot00000000000000#!/bin/bash if [ "$#" != "1" ]; then echo "Need to input image" exit 1 fi IMAGE="$1" sudo ../../bin/ctr rpull $IMAGE sudo ctr run --net-host --snapshotter=overlaybd -d $IMAGE wordpress_demo while true; do sleep 0.1 if nc -w 1 localhost 80 < /dev/null &> /dev/null; then break fi done wget -q -t 1000 --waitretry=0.1 -w 0 --retry-connrefused -O /dev/null http://127.0.0.1/wp-admin/setup-config.php echo "Service Available" accelerated-container-image-1.4.2/script/validate/000077500000000000000000000000001514714641000221125ustar00rootroot00000000000000accelerated-container-image-1.4.2/script/validate/template/000077500000000000000000000000001514714641000237255ustar00rootroot00000000000000accelerated-container-image-1.4.2/script/validate/template/go.txt000066400000000000000000000011431514714641000250720ustar00rootroot00000000000000/* Copyright The Accelerated Container Image 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. */